Skip to content

Commit

Permalink
Rate-limit generative ai endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
IanPhilips committed Nov 26, 2024
1 parent 77283c4 commit aca53d3
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 219 deletions.
99 changes: 51 additions & 48 deletions backend/api/src/generate-ai-answers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,41 +7,42 @@ import {
multiChoiceOutcomeTypeDescriptions,
} from 'common/ai-creation-prompts'
import { log } from 'shared/utils'
import { rateLimitByUser } from './helpers/rate-limit'
import { HOUR_MS } from 'common/util/time'

export const generateAIAnswers: APIHandler<'generate-ai-answers'> = async (
props,
auth
) => {
const { question, shouldAnswersSumToOne, description, answers } = props
const answersString = answers.filter(Boolean).join(', ')
const prompt = `Question: ${question} ${
description && description !== '<p></p>'
? `\nDescription: ${description}`
: ''
} ${
answersString.length
? `\nHere are my suggested answers: ${answersString}`
: ''
}`
log('generateAIAnswers prompt', prompt)
const outcomeKey = shouldAnswersSumToOne
? 'DEPENDENT_MULTIPLE_CHOICE'
: 'INDEPENDENT_MULTIPLE_CHOICE'
log('generateAIAnswers', { props })
try {
// First use perplexity to research the topic
const { messages, citations } = await perplexity(prompt, {
model: largePerplexityModel,
systemPrompts: [
`You are a helpful AI assistant that researches information to help generate possible answers for a multiple choice question.`,
],
})
export const generateAIAnswers: APIHandler<'generate-ai-answers'> =
rateLimitByUser(
async (props, auth) => {
const { question, shouldAnswersSumToOne, description, answers } = props
const answersString = answers.filter(Boolean).join(', ')
const prompt = `Question: ${question} ${
description && description !== '<p></p>'
? `\nDescription: ${description}`
: ''
} ${
answersString.length
? `\nHere are my suggested answers: ${answersString}`
: ''
}`
log('generateAIAnswers prompt', prompt)
const outcomeKey = shouldAnswersSumToOne
? 'DEPENDENT_MULTIPLE_CHOICE'
: 'INDEPENDENT_MULTIPLE_CHOICE'
log('generateAIAnswers', { props })
try {
// First use perplexity to research the topic
const { messages, citations } = await perplexity(prompt, {
model: largePerplexityModel,
systemPrompts: [
`You are a helpful AI assistant that researches information to help generate possible answers for a multiple choice question.`,
],
})

const perplexityResponse =
[messages].join('\n') + '\n\nSources:\n' + citations.join('\n\n')
const perplexityResponse =
[messages].join('\n') + '\n\nSources:\n' + citations.join('\n\n')

// Then use Claude to generate the answers
const systemPrompt = `
// Then use Claude to generate the answers
const systemPrompt = `
You are a helpful AI assistant that generates possible answers for multiple choice prediction market questions.
The question type is ${outcomeKey}.
${multiChoiceOutcomeTypeDescriptions}
Expand All @@ -66,23 +67,25 @@ export const generateAIAnswers: APIHandler<'generate-ai-answers'> = async (
${perplexityResponse}
`

const claudePrompt = `${prompt}\n\nReturn ONLY a JSON object containing "answers" string array and "addAnswersMode" string.`
const claudePrompt = `${prompt}\n\nReturn ONLY a JSON object containing "answers" string array and "addAnswersMode" string.`

const claudeResponse = await promptClaude(claudePrompt, {
model: models.sonnet,
system: systemPrompt,
})
log('claudeResponse', claudeResponse)
const claudeResponse = await promptClaude(claudePrompt, {
model: models.sonnet,
system: systemPrompt,
})
log('claudeResponse', claudeResponse)

const result = JSON.parse(claudeResponse)
const result = JSON.parse(claudeResponse)

track(auth.uid, 'generate-ai-answers', {
question,
})
track(auth.uid, 'generate-ai-answers', {
question,
})

return result
} catch (e) {
console.error('Failed to generate answers:', e)
throw new APIError(500, 'Failed to generate answers. Please try again.')
}
}
return result
} catch (e) {
console.error('Failed to generate answers:', e)
throw new APIError(500, 'Failed to generate answers. Please try again.')
}
},
{ maxCalls: 60, windowMs: HOUR_MS }
)
119 changes: 63 additions & 56 deletions backend/api/src/generate-ai-description.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,52 +8,54 @@ import {
addAnswersModeDescription,
outcomeTypeDescriptions,
} from 'common/ai-creation-prompts'
import { HOUR_MS } from 'common/util/time'
import { rateLimitByUser } from './helpers/rate-limit'

export const generateAIDescription: APIHandler<
'generate-ai-description'
> = async (props, auth) => {
const {
question,
description,
answers,
outcomeType,
shouldAnswersSumToOne,
addAnswersMode,
} = props
const includeAnswers =
answers &&
answers.length > 0 &&
outcomeType &&
['MULTIPLE_CHOICE', 'POLL'].includes(outcomeType)
const outcomeKey =
outcomeType == 'MULTIPLE_CHOICE'
? shouldAnswersSumToOne
? 'DEPENDENT_MULTIPLE_CHOICE'
: 'INDEPENDENT_MULTIPLE_CHOICE'
: outcomeType
export const generateAIDescription: APIHandler<'generate-ai-description'> =
rateLimitByUser(
async (props, auth) => {
const {
question,
description,
answers,
outcomeType,
shouldAnswersSumToOne,
addAnswersMode,
} = props
const includeAnswers =
answers &&
answers.length > 0 &&
outcomeType &&
['MULTIPLE_CHOICE', 'POLL'].includes(outcomeType)
const outcomeKey =
outcomeType == 'MULTIPLE_CHOICE'
? shouldAnswersSumToOne
? 'DEPENDENT_MULTIPLE_CHOICE'
: 'INDEPENDENT_MULTIPLE_CHOICE'
: outcomeType

const userQuestionAndDescription = `Question: ${question} ${
description && description !== '<p></p>'
? `\nDescription: ${description}`
: ''
} ${includeAnswers ? `\nMaybe: ${answers.join(', ')}` : ''}
const userQuestionAndDescription = `Question: ${question} ${
description && description !== '<p></p>'
? `\nDescription: ${description}`
: ''
} ${includeAnswers ? `\nMaybe: ${answers.join(', ')}` : ''}
`

log('Generating AI description for:', userQuestionAndDescription)
try {
const { messages, citations } = await perplexity(
userQuestionAndDescription,
{
model: largePerplexityModel,
systemPrompts: [
`You are a helpful AI assistant that researches information about the user's prompt`,
],
}
)
const perplexityResponse =
[messages].join('\n') + '\n\nSources:\n' + citations.join('\n\n')
log('Generating AI description for:', userQuestionAndDescription)
try {
const { messages, citations } = await perplexity(
userQuestionAndDescription,
{
model: largePerplexityModel,
systemPrompts: [
`You are a helpful AI assistant that researches information about the user's prompt`,
],
}
)
const perplexityResponse =
[messages].join('\n') + '\n\nSources:\n' + citations.join('\n\n')

const systemPrompt = `
const systemPrompt = `
You are a helpful AI assistant that generates detailed descriptions for prediction markets. Your goal is to provide relevant context, background information, and clear resolution criteria that will help traders make informed predictions.
${
outcomeKey
Expand Down Expand Up @@ -89,20 +91,25 @@ export const generateAIDescription: APIHandler<
${perplexityResponse}
`

const prompt = `${userQuestionAndDescription}\n\n Only return the markdown description, nothing else`
const claudeResponse = await promptClaude(prompt, {
model: models.sonnet,
system: systemPrompt,
})
const prompt = `${userQuestionAndDescription}\n\n Only return the markdown description, nothing else`
const claudeResponse = await promptClaude(prompt, {
model: models.sonnet,
system: systemPrompt,
})

track(auth.uid, 'generate-ai-description', {
question,
hasExistingDescription: !!description,
})
track(auth.uid, 'generate-ai-description', {
question,
hasExistingDescription: !!description,
})

return { description: anythingToRichText({ markdown: claudeResponse }) }
} catch (e) {
console.error('Failed to generate description:', e)
throw new APIError(500, 'Failed to generate description. Please try again.')
}
}
return { description: anythingToRichText({ markdown: claudeResponse }) }
} catch (e) {
console.error('Failed to generate description:', e)
throw new APIError(
500,
'Failed to generate description. Please try again.'
)
}
},
{ maxCalls: 60, windowMs: HOUR_MS }
)
111 changes: 58 additions & 53 deletions backend/api/src/generate-ai-market-suggestions-2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,38 @@ import { anythingToRichText } from 'shared/tiptap'
import { track } from 'shared/analytics'
import { largePerplexityModel, perplexity } from 'shared/helpers/perplexity'
import { getContentFromPrompt } from './generate-ai-market-suggestions'
import { rateLimitByUser } from './helpers/rate-limit'
import { HOUR_MS } from 'common/util/time'

const perplexitySystemPrompt = `You are a helpful assistant that uses the internet to research all relevant information to a user's prompt and supplies the user with as much information as possible.`
// In this version, we use Perplexity to generate context for the prompt, and then Claude to generate market suggestions
export const generateAIMarketSuggestions2: APIHandler<
'generate-ai-market-suggestions-2'
> = async (props, auth) => {
const { prompt, existingTitles } = props
log('Prompt:', prompt)
export const generateAIMarketSuggestions2: APIHandler<'generate-ai-market-suggestions-2'> =
rateLimitByUser(
async (props, auth) => {
const { prompt, existingTitles } = props
log('Prompt:', prompt)

const promptIncludingTitlesToIgnore = existingTitles?.length
? `${prompt}\n\nPlease suggest new market ideas that are different from these ones:\n${existingTitles
.map((t) => `- ${t}`)
.join('\n')}`
: prompt
const promptIncludingTitlesToIgnore = existingTitles?.length
? `${prompt}\n\nPlease suggest new market ideas that are different from these ones:\n${existingTitles
.map((t) => `- ${t}`)
.join('\n')}`
: prompt

const promptIncludingUrlContent = await getContentFromPrompt(
promptIncludingTitlesToIgnore
)
const promptIncludingUrlContent = await getContentFromPrompt(
promptIncludingTitlesToIgnore
)

const perplexityResponse = await perplexity(promptIncludingUrlContent, {
model: largePerplexityModel,
systemPrompts: [perplexitySystemPrompt],
})
const perplexityResponse = await perplexity(promptIncludingUrlContent, {
model: largePerplexityModel,
systemPrompts: [perplexitySystemPrompt],
})

const { messages, citations } = perplexityResponse
log('Perplexity response:', messages.join('\n'))
log('Sources:', citations.join('\n'))
const { messages, citations } = perplexityResponse
log('Perplexity response:', messages.join('\n'))
log('Sources:', citations.join('\n'))

// Format the perplexity suggestions for Claude
const claudePrompt = `
// Format the perplexity suggestions for Claude
const claudePrompt = `
You are a helpful assistant that suggests ideas for engaging prediction markets on Manifold Markets.
Your role is to take in relevant current information from the internet and transform a user's prompt into approximately 6 well-structured prediction markets that encourage participation and meaningful forecasting.
${guidelinesPrompt}
Expand All @@ -63,37 +66,39 @@ export const generateAIMarketSuggestions2: APIHandler<
ONLY return a valid JSON array of market objects and do NOT include any other text.
`

const claudeResponse = await promptClaude(claudePrompt, {
model: models.sonnet,
system: claudeSystemPrompt,
})
const claudeResponse = await promptClaude(claudePrompt, {
model: models.sonnet,
system: claudeSystemPrompt,
})

// Parse the JSON response
let parsedMarkets: AIGeneratedMarket[] = []
try {
parsedMarkets = JSON.parse(claudeResponse).map(
(market: AIGeneratedMarket) => ({
...market,
description: anythingToRichText({
markdown: market.descriptionMarkdown,
}),
promptVersion: 2,
// Parse the JSON response
let parsedMarkets: AIGeneratedMarket[] = []
try {
parsedMarkets = JSON.parse(claudeResponse).map(
(market: AIGeneratedMarket) => ({
...market,
description: anythingToRichText({
markdown: market.descriptionMarkdown,
}),
promptVersion: 2,
})
)
} catch (e) {
console.error('Failed to parse Claude response:', e)
throw new APIError(
500,
'Failed to parse market suggestions from Claude. Please try again.'
)
}
track(auth.uid, 'generate-ai-market-suggestions', {
marketTitles: parsedMarkets.map((m) => m.question),
prompt,
regenerate: !!existingTitles,
hasScrapedContent:
promptIncludingUrlContent !== promptIncludingTitlesToIgnore,
})
)
} catch (e) {
console.error('Failed to parse Claude response:', e)
throw new APIError(
500,
'Failed to parse market suggestions from Claude. Please try again.'
)
}
track(auth.uid, 'generate-ai-market-suggestions', {
marketTitles: parsedMarkets.map((m) => m.question),
prompt,
regenerate: !!existingTitles,
hasScrapedContent:
promptIncludingUrlContent !== promptIncludingTitlesToIgnore,
})

return parsedMarkets
}
return parsedMarkets
},
{ maxCalls: 60, windowMs: HOUR_MS }
)
Loading

0 comments on commit aca53d3

Please sign in to comment.