2
0

🐛 (openai) Fix assistant sequence was not correctly saved

Also add logs to debug open ai errors. Related to #393.
This commit is contained in:
Baptiste Arnaud
2023-03-15 17:47:05 +01:00
parent 76a8064e7c
commit 5aec8b6c66
4 changed files with 62 additions and 35 deletions

View File

@ -12,7 +12,7 @@ import {
OpenAICredentials, OpenAICredentials,
} 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 } from '@typebot.io/lib' import { isDefined, byId, isNotEmpty, isEmpty } from '@typebot.io/lib'
import { decrypt } from '@typebot.io/lib/api/encryption' import { decrypt } from '@typebot.io/lib/api/encryption'
import { saveErrorLog } from '@/features/logs/saveErrorLog' import { saveErrorLog } from '@/features/logs/saveErrorLog'
import { updateVariables } from '@/features/variables/updateVariables' import { updateVariables } from '@/features/variables/updateVariables'
@ -25,17 +25,20 @@ export const createChatCompletionOpenAI = async (
options, options,
}: { outgoingEdgeId?: string; options: ChatCompletionOpenAIOptions } }: { outgoingEdgeId?: string; options: ChatCompletionOpenAIOptions }
): Promise<ExecuteIntegrationResponse> => { ): Promise<ExecuteIntegrationResponse> => {
const {
typebot: { variables },
} = state
let newSessionState = 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({ const credentials = await prisma.credentials.findUnique({
where: { where: {
id: options.credentialsId, id: options.credentialsId,
}, },
}) })
if (!credentials) return { outgoingEdgeId } if (!credentials) {
console.error('Could not find credentials in database')
return { outgoingEdgeId }
}
const { apiKey } = decrypt( const { apiKey } = decrypt(
credentials.data, credentials.data,
credentials.iv credentials.iv
@ -43,28 +46,30 @@ export const createChatCompletionOpenAI = async (
const configuration = new Configuration({ const configuration = new Configuration({
apiKey, apiKey,
}) })
const { variablesTransformedToList, messages } = parseMessages(variables)( const { variablesTransformedToList, messages } = parseMessages(
options.messages newSessionState.typebot.variables
) )(options.messages)
if (variablesTransformedToList.length > 0) if (variablesTransformedToList.length > 0)
newSessionState = await updateVariables(state)(variablesTransformedToList) newSessionState = await updateVariables(state)(variablesTransformedToList)
const openai = new OpenAIApi(configuration)
try { try {
const { const openai = new OpenAIApi(configuration)
data: { choices, usage }, const response = await openai.createChatCompletion({
} = await openai.createChatCompletion({
model: options.model, model: options.model,
messages, messages,
}) })
const messageContent = choices[0].message?.content const messageContent = response.data.choices.at(0)?.message?.content
const totalTokens = usage?.total_tokens const totalTokens = response.data.usage?.total_tokens
if (!messageContent) { if (isEmpty(messageContent)) {
console.error('OpenAI block returned empty message', response)
return { outgoingEdgeId, newSessionState } return { outgoingEdgeId, newSessionState }
} }
const newVariables = options.responseMapping.reduce< const newVariables = options.responseMapping.reduce<
VariableWithUnknowValue[] VariableWithUnknowValue[]
>((newVariables, mapping) => { >((newVariables, mapping) => {
const existingVariable = variables.find(byId(mapping.variableId)) const existingVariable = newSessionState.typebot.variables.find(
byId(mapping.variableId)
)
if (!existingVariable) return newVariables if (!existingVariable) return newVariables
if (mapping.valueToExtract === 'Message content') { if (mapping.valueToExtract === 'Message content') {
newVariables.push({ newVariables.push({
@ -82,18 +87,14 @@ export const createChatCompletionOpenAI = async (
} }
return newVariables return newVariables
}, []) }, [])
if (newVariables.length > 0) { if (newVariables.length > 0)
newSessionState = await updateVariables(newSessionState)(newVariables) newSessionState = await updateVariables(newSessionState)(newVariables)
return {
outgoingEdgeId,
newSessionState,
}
}
return { return {
outgoingEdgeId, outgoingEdgeId,
newSessionState, newSessionState,
} }
} catch (err) { } catch (err) {
console.error(err)
const log = { const log = {
status: 'error', status: 'error',
description: 'OpenAI block returned error', description: 'OpenAI block returned error',

View File

@ -136,6 +136,8 @@ const parseBubbleBlock =
(variables: SessionState['typebot']['variables']) => (variables: SessionState['typebot']['variables']) =>
(block: BubbleBlock): ChatReply['messages'][0] => { (block: BubbleBlock): ChatReply['messages'][0] => {
switch (block.type) { switch (block.type) {
case BubbleBlockType.TEXT:
return deepParseVariables(variables, { takeLatestIfList: true })(block)
case BubbleBlockType.EMBED: { case BubbleBlockType.EMBED: {
const message = deepParseVariables(variables)(block) const message = deepParseVariables(variables)(block)
return { return {

View File

@ -1,27 +1,38 @@
import { Variable } from '@typebot.io/schemas' import { Variable } from '@typebot.io/schemas'
import { parseVariables } from './parseVariables' import {
defaultParseVariablesOptions,
parseVariables,
ParseVariablesOptions,
} from './parseVariables'
export const deepParseVariables = export const deepParseVariables =
(variables: Variable[]) => (
variables: Variable[],
options: ParseVariablesOptions = defaultParseVariablesOptions
) =>
<T extends Record<string, unknown>>(object: T): T => <T extends Record<string, unknown>>(object: T): T =>
Object.keys(object).reduce<T>((newObj, key) => { Object.keys(object).reduce<T>((newObj, key) => {
const currentValue = object[key] const currentValue = object[key]
if (typeof currentValue === 'string') if (typeof currentValue === 'string')
return { ...newObj, [key]: parseVariables(variables)(currentValue) } return {
...newObj,
[key]: parseVariables(variables, options)(currentValue),
}
if (currentValue instanceof Object && currentValue.constructor === Object) if (currentValue instanceof Object && currentValue.constructor === Object)
return { return {
...newObj, ...newObj,
[key]: deepParseVariables(variables)( [key]: deepParseVariables(
currentValue as Record<string, unknown> variables,
), options
)(currentValue as Record<string, unknown>),
} }
if (currentValue instanceof Array) if (currentValue instanceof Array)
return { return {
...newObj, ...newObj,
[key]: currentValue.map(deepParseVariables(variables)), [key]: currentValue.map(deepParseVariables(variables, options)),
} }
return { ...newObj, [key]: currentValue } return { ...newObj, [key]: currentValue }

View File

@ -2,13 +2,22 @@ import { isDefined } from '@typebot.io/lib'
import { Variable, VariableWithValue } from '@typebot.io/schemas' import { Variable, VariableWithValue } from '@typebot.io/schemas'
import { safeStringify } from './safeStringify' 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 = export const parseVariables =
( (
variables: Variable[], variables: Variable[],
options: { fieldToParse?: 'value' | 'id'; escapeForJson?: boolean } = { options: ParseVariablesOptions = defaultParseVariablesOptions
fieldToParse: 'value',
escapeForJson: false,
}
) => ) =>
(text: string | undefined): string => { (text: string | undefined): string => {
if (!text || text === '') return '' if (!text || text === '') return ''
@ -27,7 +36,11 @@ export const parseVariables =
return jsonParse( return jsonParse(
typeof value !== 'string' ? JSON.stringify(value) : value 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 '' if (!parsedValue) return ''
return parsedValue return parsedValue
}) })