2
0
Files
bot/packages/bot-engine/blocks/integrations/legacy/openai/createChatCompletionOpenAI.ts

182 lines
5.6 KiB
TypeScript
Raw Normal View History

import {
Block,
Credentials,
SessionState,
TypebotInSession,
} from '@typebot.io/schemas'
import {
ChatCompletionOpenAIOptions,
OpenAICredentials,
} from '@typebot.io/schemas/features/blocks/integrations/openai'
import { byId, isEmpty } from '@typebot.io/lib'
import { decrypt } from '@typebot.io/lib/api/encryption/decrypt'
import { resumeChatCompletion } from './resumeChatCompletion'
import { parseChatCompletionMessages } from './parseChatCompletionMessages'
import { executeChatCompletionOpenAIRequest } from './executeChatCompletionOpenAIRequest'
import { isPlaneteScale } from '@typebot.io/lib/isPlanetScale'
import prisma from '@typebot.io/lib/prisma'
import { ExecuteIntegrationResponse } from '../../../../types'
import { parseVariableNumber } from '@typebot.io/variables/parseVariableNumber'
import { updateVariablesInSession } from '@typebot.io/variables/updateVariablesInSession'
import {
chatCompletionMessageRoles,
defaultOpenAIOptions,
} from '@typebot.io/schemas/features/blocks/integrations/openai/constants'
import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/constants'
export const createChatCompletionOpenAI = async (
state: SessionState,
{
outgoingEdgeId,
options,
blockId,
}: {
outgoingEdgeId?: string
options: ChatCompletionOpenAIOptions
blockId: string
}
): Promise<ExecuteIntegrationResponse> => {
let newSessionState = state
const noCredentialsError = {
status: 'error',
description: 'Make sure to select an OpenAI account',
}
if (!options.credentialsId) {
return {
outgoingEdgeId,
logs: [noCredentialsError],
}
}
const credentials = await prisma.credentials.findUnique({
where: {
id: options.credentialsId,
},
})
if (!credentials) {
console.error('Could not find credentials in database')
return { outgoingEdgeId, logs: [noCredentialsError] }
}
const { apiKey } = (await decrypt(
credentials.data,
credentials.iv
)) as OpenAICredentials['data']
const { typebot } = newSessionState.typebotsQueue[0]
const { variablesTransformedToList, messages } = parseChatCompletionMessages(
typebot.variables
)(options.messages)
if (variablesTransformedToList.length > 0)
newSessionState = updateVariablesInSession(state)(
variablesTransformedToList
)
const temperature = parseVariableNumber(typebot.variables)(
options.advancedSettings?.temperature
)
const assistantMessageVariableName = typebot.variables.find(
(variable) =>
options.responseMapping?.find(
(m) => m.valueToExtract === 'Message content'
)?.variableId === variable.id
)?.name
if (
newSessionState.isStreamEnabled &&
!newSessionState.whatsApp &&
isNextBubbleMessageWithAssistantMessage(typebot)(
blockId,
assistantMessageVariableName
:zap: Introduce a new high-performing standalone chat API (#1200) Closes #1154 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added authentication functionality for user sessions in chat API. - Introduced chat-related API endpoints for starting, previewing, and continuing chat sessions, and streaming messages. - Implemented WhatsApp API webhook handling for receiving and processing messages. - Added environment variable `NEXT_PUBLIC_CHAT_API_URL` for chat API URL configuration. - **Bug Fixes** - Adjusted file upload logic to correctly determine the API host. - Fixed message streaming URL in chat integration with OpenAI. - **Documentation** - Updated guides for creating blocks, local installation, self-hosting, and deployment to use `bun` instead of `pnpm`. - **Refactor** - Refactored chat API functionalities to use modular architecture. - Simplified client log saving and session update functionalities by using external functions. - Transitioned package management and workflow commands to use `bun`. - **Chores** - Switched to `bun` for package management in Dockerfiles and GitHub workflows. - Added new Dockerfile for chat API service setup with Bun framework. - Updated `.prettierignore` and documentation with new commands. - **Style** - No visible changes to end-users. - **Tests** - No visible changes to end-users. - **Revert** - No reverts in this release. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-03-21 10:23:23 +01:00
) &&
!process.env.VERCEL_ENV
) {
return {
clientSideActions: [
{
type: 'streamOpenAiChatCompletion',
streamOpenAiChatCompletion: {
messages: messages as {
content?: string
role: (typeof chatCompletionMessageRoles)[number]
}[],
},
expectsDedicatedReply: true,
},
],
outgoingEdgeId,
newSessionState,
}
}
const { chatCompletion, logs } = await executeChatCompletionOpenAIRequest({
apiKey,
messages,
model: options.model ?? defaultOpenAIOptions.model,
temperature,
baseUrl: options.baseUrl,
apiVersion: options.apiVersion,
})
if (!chatCompletion)
return {
startTimeShouldBeUpdated: true,
outgoingEdgeId,
logs,
}
const messageContent = chatCompletion.choices.at(0)?.message?.content
const totalTokens = chatCompletion.usage?.total_tokens
if (isEmpty(messageContent)) {
console.error('OpenAI block returned empty message', chatCompletion.choices)
return { outgoingEdgeId, newSessionState, startTimeShouldBeUpdated: true }
}
return {
...(await resumeChatCompletion(newSessionState, {
options,
outgoingEdgeId,
logs,
})(messageContent, totalTokens)),
startTimeShouldBeUpdated: true,
}
}
const isNextBubbleMessageWithAssistantMessage =
(typebot: TypebotInSession) =>
(blockId: string, assistantVariableName?: string): boolean => {
if (!assistantVariableName) return false
const nextBlock = getNextBlock(typebot)(blockId)
if (!nextBlock) return false
return (
nextBlock.type === BubbleBlockType.TEXT &&
(nextBlock.content?.richText?.length ?? 0) > 0 &&
nextBlock.content?.richText?.at(0)?.children.at(0).text ===
`{{${assistantVariableName}}}`
)
}
const getNextBlock =
(typebot: TypebotInSession) =>
(blockId: string): Block | undefined => {
const group = typebot.groups.find((group) =>
group.blocks.find(byId(blockId))
)
if (!group) return
const blockIndex = group.blocks.findIndex(byId(blockId))
const nextBlockInGroup = group.blocks.at(blockIndex + 1)
if (nextBlockInGroup) return nextBlockInGroup
const outgoingEdgeId = group.blocks.at(blockIndex)?.outgoingEdgeId
if (!outgoingEdgeId) return
const outgoingEdge = typebot.edges.find(byId(outgoingEdgeId))
if (!outgoingEdge) return
const connectedGroup = typebot.groups.find(byId(outgoingEdge?.to.groupId))
if (!connectedGroup) return
return outgoingEdge.to.blockId
? connectedGroup.blocks.find(
(block) => block.id === outgoingEdge.to.blockId
)
: connectedGroup?.blocks.at(0)
}
const isCredentialsV2 = (credentials: Pick<Credentials, 'iv'>) =>
credentials.iv.length === 24