From 5aec8b6c6682aa2da4034230a894f02add2faef2 Mon Sep 17 00:00:00 2001 From: Baptiste Arnaud Date: Wed, 15 Mar 2023 17:47:05 +0100 Subject: [PATCH] :bug: (openai) Fix assistant sequence was not correctly saved Also add logs to debug open ai errors. Related to #393. --- .../openai/createChatCompletionOpenAI.ts | 47 ++++++++++--------- .../src/features/chat/helpers/executeGroup.ts | 2 + .../features/variables/deepParseVariable.ts | 25 +++++++--- .../src/features/variables/parseVariables.ts | 23 +++++++-- 4 files changed, 62 insertions(+), 35 deletions(-) diff --git a/apps/viewer/src/features/blocks/integrations/openai/createChatCompletionOpenAI.ts b/apps/viewer/src/features/blocks/integrations/openai/createChatCompletionOpenAI.ts index e4407f2cd..28313a47b 100644 --- a/apps/viewer/src/features/blocks/integrations/openai/createChatCompletionOpenAI.ts +++ b/apps/viewer/src/features/blocks/integrations/openai/createChatCompletionOpenAI.ts @@ -12,7 +12,7 @@ import { OpenAICredentials, } from '@typebot.io/schemas/features/blocks/integrations/openai' import { OpenAIApi, Configuration, ChatCompletionRequestMessage } from 'openai' -import { isDefined, byId, isNotEmpty } from '@typebot.io/lib' +import { isDefined, byId, isNotEmpty, isEmpty } from '@typebot.io/lib' import { decrypt } from '@typebot.io/lib/api/encryption' import { saveErrorLog } from '@/features/logs/saveErrorLog' import { updateVariables } from '@/features/variables/updateVariables' @@ -25,17 +25,20 @@ export const createChatCompletionOpenAI = async ( options, }: { outgoingEdgeId?: string; options: ChatCompletionOpenAIOptions } ): Promise => { - const { - typebot: { variables }, - } = state let newSessionState = state - if (!options.credentialsId) return { outgoingEdgeId } + if (!options.credentialsId) { + console.error('OpenAI block has no credentials') + return { outgoingEdgeId } + } const credentials = await prisma.credentials.findUnique({ where: { id: options.credentialsId, }, }) - if (!credentials) return { outgoingEdgeId } + if (!credentials) { + console.error('Could not find credentials in database') + return { outgoingEdgeId } + } const { apiKey } = decrypt( credentials.data, credentials.iv @@ -43,28 +46,30 @@ export const createChatCompletionOpenAI = async ( const configuration = new Configuration({ apiKey, }) - const { variablesTransformedToList, messages } = parseMessages(variables)( - options.messages - ) + const { variablesTransformedToList, messages } = parseMessages( + newSessionState.typebot.variables + )(options.messages) if (variablesTransformedToList.length > 0) newSessionState = await updateVariables(state)(variablesTransformedToList) - const openai = new OpenAIApi(configuration) + try { - const { - data: { choices, usage }, - } = await openai.createChatCompletion({ + const openai = new OpenAIApi(configuration) + const response = await openai.createChatCompletion({ model: options.model, messages, }) - const messageContent = choices[0].message?.content - const totalTokens = usage?.total_tokens - if (!messageContent) { + const messageContent = response.data.choices.at(0)?.message?.content + const totalTokens = response.data.usage?.total_tokens + if (isEmpty(messageContent)) { + console.error('OpenAI block returned empty message', response) return { outgoingEdgeId, newSessionState } } const newVariables = options.responseMapping.reduce< VariableWithUnknowValue[] >((newVariables, mapping) => { - const existingVariable = variables.find(byId(mapping.variableId)) + const existingVariable = newSessionState.typebot.variables.find( + byId(mapping.variableId) + ) if (!existingVariable) return newVariables if (mapping.valueToExtract === 'Message content') { newVariables.push({ @@ -82,18 +87,14 @@ export const createChatCompletionOpenAI = async ( } return newVariables }, []) - if (newVariables.length > 0) { + if (newVariables.length > 0) newSessionState = await updateVariables(newSessionState)(newVariables) - return { - outgoingEdgeId, - newSessionState, - } - } return { outgoingEdgeId, newSessionState, } } catch (err) { + console.error(err) const log = { status: 'error', description: 'OpenAI block returned error', diff --git a/apps/viewer/src/features/chat/helpers/executeGroup.ts b/apps/viewer/src/features/chat/helpers/executeGroup.ts index 09043be4f..6beb1400e 100644 --- a/apps/viewer/src/features/chat/helpers/executeGroup.ts +++ b/apps/viewer/src/features/chat/helpers/executeGroup.ts @@ -136,6 +136,8 @@ const parseBubbleBlock = (variables: SessionState['typebot']['variables']) => (block: BubbleBlock): ChatReply['messages'][0] => { switch (block.type) { + case BubbleBlockType.TEXT: + return deepParseVariables(variables, { takeLatestIfList: true })(block) case BubbleBlockType.EMBED: { const message = deepParseVariables(variables)(block) return { diff --git a/apps/viewer/src/features/variables/deepParseVariable.ts b/apps/viewer/src/features/variables/deepParseVariable.ts index ed0955b80..753766ec4 100644 --- a/apps/viewer/src/features/variables/deepParseVariable.ts +++ b/apps/viewer/src/features/variables/deepParseVariable.ts @@ -1,27 +1,38 @@ import { Variable } from '@typebot.io/schemas' -import { parseVariables } from './parseVariables' +import { + defaultParseVariablesOptions, + parseVariables, + ParseVariablesOptions, +} from './parseVariables' export const deepParseVariables = - (variables: Variable[]) => + ( + variables: Variable[], + options: ParseVariablesOptions = defaultParseVariablesOptions + ) => >(object: T): T => Object.keys(object).reduce((newObj, key) => { const currentValue = object[key] if (typeof currentValue === 'string') - return { ...newObj, [key]: parseVariables(variables)(currentValue) } + return { + ...newObj, + [key]: parseVariables(variables, options)(currentValue), + } if (currentValue instanceof Object && currentValue.constructor === Object) return { ...newObj, - [key]: deepParseVariables(variables)( - currentValue as Record - ), + [key]: deepParseVariables( + variables, + options + )(currentValue as Record), } if (currentValue instanceof Array) return { ...newObj, - [key]: currentValue.map(deepParseVariables(variables)), + [key]: currentValue.map(deepParseVariables(variables, options)), } return { ...newObj, [key]: currentValue } diff --git a/apps/viewer/src/features/variables/parseVariables.ts b/apps/viewer/src/features/variables/parseVariables.ts index ac6493a98..3fb7cc01b 100644 --- a/apps/viewer/src/features/variables/parseVariables.ts +++ b/apps/viewer/src/features/variables/parseVariables.ts @@ -2,13 +2,22 @@ import { isDefined } from '@typebot.io/lib' import { Variable, VariableWithValue } from '@typebot.io/schemas' import { safeStringify } from './safeStringify' +export type ParseVariablesOptions = { + fieldToParse?: 'value' | 'id' + escapeForJson?: boolean + takeLatestIfList?: boolean +} + +export const defaultParseVariablesOptions: ParseVariablesOptions = { + fieldToParse: 'value', + escapeForJson: false, + takeLatestIfList: false, +} + export const parseVariables = ( variables: Variable[], - options: { fieldToParse?: 'value' | 'id'; escapeForJson?: boolean } = { - fieldToParse: 'value', - escapeForJson: false, - } + options: ParseVariablesOptions = defaultParseVariablesOptions ) => (text: string | undefined): string => { if (!text || text === '') return '' @@ -27,7 +36,11 @@ export const parseVariables = return jsonParse( typeof value !== 'string' ? JSON.stringify(value) : value ) - const parsedValue = safeStringify(value) + const parsedValue = safeStringify( + options.takeLatestIfList && Array.isArray(value) + ? value[value.length - 1] + : value + ) if (!parsedValue) return '' return parsedValue })