2
0

(openai) Truncate messages sequence automatically if reaching token limit

This commit is contained in:
Baptiste Arnaud
2023-05-02 13:37:02 -04:00
parent 94735638a6
commit e58016e43a
6 changed files with 73 additions and 18 deletions

View File

@ -77,6 +77,8 @@ export const TemplatesModal = ({ isOpen, onClose, onTypebotChoose }: Props) => {
borderRightWidth={1} borderRightWidth={1}
justify="space-between" justify="space-between"
flexShrink={0} flexShrink={0}
overflowY="scroll"
className="hide-scrollbar"
> >
<Stack spacing={5}> <Stack spacing={5}>
<Stack spacing={2}> <Stack spacing={2}>

View File

@ -17,7 +17,7 @@ export const useToast = () => {
}: Omit<ToastProps, 'onClose'>) => { }: Omit<ToastProps, 'onClose'>) => {
toast({ toast({
position: 'top-right', position: 'top-right',
duration: details ? null : undefined, duration: details && status === 'error' ? null : undefined,
render: ({ onClose }) => ( render: ({ onClose }) => (
<Toast <Toast
title={title} title={title}

View File

@ -13,14 +13,15 @@
"test:report": "pnpm playwright show-report" "test:report": "pnpm playwright show-report"
}, },
"dependencies": { "dependencies": {
"@dqbd/tiktoken": "^1.0.7",
"@sentry/nextjs": "7.46.0", "@sentry/nextjs": "7.46.0",
"@trpc/server": "10.18.0", "@trpc/server": "10.18.0",
"@typebot.io/js": "workspace:*", "@typebot.io/js": "workspace:*",
"@typebot.io/prisma": "workspace:*",
"@typebot.io/react": "workspace:*", "@typebot.io/react": "workspace:*",
"aws-sdk": "2.1348.0", "aws-sdk": "2.1348.0",
"bot-engine": "workspace:*", "bot-engine": "workspace:*",
"cors": "2.8.5", "cors": "2.8.5",
"@typebot.io/prisma": "workspace:*",
"google-spreadsheet": "3.3.0", "google-spreadsheet": "3.3.0",
"got": "12.6.0", "got": "12.6.0",
"libphonenumber-js": "1.10.24", "libphonenumber-js": "1.10.24",
@ -39,6 +40,10 @@
"@faire/mjml-react": "3.2.0", "@faire/mjml-react": "3.2.0",
"@paralleldrive/cuid2": "2.2.0", "@paralleldrive/cuid2": "2.2.0",
"@playwright/test": "1.32.1", "@playwright/test": "1.32.1",
"@typebot.io/emails": "workspace:*",
"@typebot.io/lib": "workspace:*",
"@typebot.io/schemas": "workspace:*",
"@typebot.io/tsconfig": "workspace:*",
"@types/cors": "2.8.13", "@types/cors": "2.8.13",
"@types/google-spreadsheet": "3.3.1", "@types/google-spreadsheet": "3.3.1",
"@types/node": "18.15.11", "@types/node": "18.15.11",
@ -48,17 +53,13 @@
"@types/react": "18.0.32", "@types/react": "18.0.32",
"@types/sanitize-html": "2.9.0", "@types/sanitize-html": "2.9.0",
"dotenv": "16.0.3", "dotenv": "16.0.3",
"@typebot.io/emails": "workspace:*",
"eslint": "8.37.0", "eslint": "8.37.0",
"eslint-config-custom": "workspace:*", "eslint-config-custom": "workspace:*",
"google-auth-library": "8.7.0", "google-auth-library": "8.7.0",
"@typebot.io/schemas": "workspace:*",
"node-fetch": "3.3.1", "node-fetch": "3.3.1",
"papaparse": "5.4.1", "papaparse": "5.4.1",
"superjson": "1.12.2", "superjson": "1.12.2",
"@typebot.io/tsconfig": "workspace:*",
"typescript": "5.0.3", "typescript": "5.0.3",
"@typebot.io/lib": "workspace:*",
"zod": "3.21.4" "zod": "3.21.4"
} }
} }

View File

@ -11,6 +11,7 @@ import {
import { import {
ChatCompletionOpenAIOptions, ChatCompletionOpenAIOptions,
OpenAICredentials, OpenAICredentials,
modelLimit,
} from '@typebot.io/schemas/features/blocks/integrations/openai' } from '@typebot.io/schemas/features/blocks/integrations/openai'
import { OpenAIApi, Configuration, ChatCompletionRequestMessage } from 'openai' import { OpenAIApi, Configuration, ChatCompletionRequestMessage } from 'openai'
import { isDefined, byId, isNotEmpty, isEmpty } from '@typebot.io/lib' import { isDefined, byId, isNotEmpty, isEmpty } from '@typebot.io/lib'
@ -20,6 +21,9 @@ import { updateVariables } from '@/features/variables/updateVariables'
import { parseVariables } from '@/features/variables/parseVariables' import { parseVariables } from '@/features/variables/parseVariables'
import { saveSuccessLog } from '@/features/logs/saveSuccessLog' import { saveSuccessLog } from '@/features/logs/saveSuccessLog'
import { parseVariableNumber } from '@/features/variables/parseVariableNumber' import { parseVariableNumber } from '@/features/variables/parseVariableNumber'
import { encoding_for_model } from '@dqbd/tiktoken'
const minTokenCompletion = 200
export const createChatCompletionOpenAI = async ( export const createChatCompletionOpenAI = async (
state: SessionState, state: SessionState,
@ -56,7 +60,8 @@ export const createChatCompletionOpenAI = async (
apiKey, apiKey,
}) })
const { variablesTransformedToList, messages } = parseMessages( const { variablesTransformedToList, messages } = parseMessages(
newSessionState.typebot.variables newSessionState.typebot.variables,
options.model
)(options.messages) )(options.messages)
if (variablesTransformedToList.length > 0) if (variablesTransformedToList.length > 0)
newSessionState = await updateVariables(state)(variablesTransformedToList) newSessionState = await updateVariables(state)(variablesTransformedToList)
@ -148,7 +153,7 @@ export const createChatCompletionOpenAI = async (
} }
const parseMessages = const parseMessages =
(variables: Variable[]) => (variables: Variable[], model: ChatCompletionOpenAIOptions['model']) =>
( (
messages: ChatCompletionOpenAIOptions['messages'] messages: ChatCompletionOpenAIOptions['messages']
): { ): {
@ -156,8 +161,11 @@ const parseMessages =
messages: ChatCompletionRequestMessage[] messages: ChatCompletionRequestMessage[]
} => { } => {
const variablesTransformedToList: VariableWithValue[] = [] const variablesTransformedToList: VariableWithValue[] = []
const firstMessagesSequenceIndex = messages.findIndex(
(message) => message.role === 'Messages sequence ✨'
)
const parsedMessages = messages const parsedMessages = messages
.flatMap((message) => { .flatMap((message, index) => {
if (!message.role) return if (!message.role) return
if (message.role === 'Messages sequence ✨') { if (message.role === 'Messages sequence ✨') {
if ( if (
@ -189,23 +197,51 @@ const parseMessages =
variable.id === message.content?.assistantMessagesVariableId variable.id === message.content?.assistantMessagesVariableId
)?.value ?? []) as string[] )?.value ?? []) as string[]
let allMessages: ChatCompletionRequestMessage[] = []
if (userMessages.length > assistantMessages.length) if (userMessages.length > assistantMessages.length)
return userMessages.flatMap((userMessage, index) => [ allMessages = userMessages.flatMap((userMessage, index) => [
{ {
role: 'user', role: 'user',
content: userMessage, content: userMessage,
}, },
{ role: 'assistant', content: assistantMessages[index] }, { role: 'assistant', content: assistantMessages.at(index) ?? '' },
]) satisfies ChatCompletionRequestMessage[] ]) satisfies ChatCompletionRequestMessage[]
else { else {
return assistantMessages.flatMap((assistantMessage, index) => [ allMessages = assistantMessages.flatMap(
{ role: 'assistant', content: assistantMessage }, (assistantMessage, index) => [
{ { role: 'assistant', content: assistantMessage },
role: 'user', {
content: userMessages[index], role: 'user',
}, content: userMessages.at(index) ?? '',
]) satisfies ChatCompletionRequestMessage[] },
]
) satisfies ChatCompletionRequestMessage[]
} }
if (index !== firstMessagesSequenceIndex) return allMessages
const encoder = encoding_for_model(model)
let messagesToSend: ChatCompletionRequestMessage[] = []
let tokenCount = 0
for (let i = allMessages.length - 1; i >= 0; i--) {
const message = allMessages[i]
const tokens = encoder.encode(message.content)
if (
tokenCount + tokens.length - minTokenCompletion >
modelLimit[model]
) {
break
}
tokenCount += tokens.length
messagesToSend = [message, ...messagesToSend]
}
encoder.free()
return messagesToSend
} }
return { return {
role: message.role, role: message.role,

View File

@ -14,6 +14,15 @@ export const chatCompletionModels = [
'gpt-3.5-turbo-0301', 'gpt-3.5-turbo-0301',
] as const ] as const
export const modelLimit = {
'gpt-3.5-turbo': 4096,
'gpt-3.5-turbo-0301': 4096,
'gpt-4': 8192,
'gpt-4-0314': 8192,
'gpt-4-32k': 32768,
'gpt-4-32k-0314': 32768,
} as const
export const chatCompletionMessageRoles = [ export const chatCompletionMessageRoles = [
'system', 'system',
'user', 'user',

7
pnpm-lock.yaml generated
View File

@ -502,6 +502,9 @@ importers:
apps/viewer: apps/viewer:
dependencies: dependencies:
'@dqbd/tiktoken':
specifier: ^1.0.7
version: 1.0.7
'@sentry/nextjs': '@sentry/nextjs':
specifier: 7.46.0 specifier: 7.46.0
version: 7.46.0(next@13.2.4)(react@18.2.0) version: 7.46.0(next@13.2.4)(react@18.2.0)
@ -5228,6 +5231,10 @@ packages:
- webpack-cli - webpack-cli
dev: false dev: false
/@dqbd/tiktoken@1.0.7:
resolution: {integrity: sha512-bhR5k5W+8GLzysjk8zTMVygQZsgvf7W1F0IlL4ZQ5ugjo5rCyiwGM5d8DYriXspytfu98tv59niang3/T+FoDw==}
dev: false
/@emotion/babel-plugin@11.10.6: /@emotion/babel-plugin@11.10.6:
resolution: {integrity: sha512-p2dAqtVrkhSa7xz1u/m9eHYdLi+en8NowrmXeF/dKtJpU8lCWli8RUAati7NcSl0afsBott48pdnANuD0wh9QQ==} resolution: {integrity: sha512-p2dAqtVrkhSa7xz1u/m9eHYdLi+en8NowrmXeF/dKtJpU8lCWli8RUAati7NcSl0afsBott48pdnANuD0wh9QQ==}
dependencies: dependencies: