🐛 Fix openai total tokens variable set when streaming
This commit is contained in:
@ -13345,6 +13345,7 @@
|
|||||||
"google sheets",
|
"google sheets",
|
||||||
"stripe",
|
"stripe",
|
||||||
"whatsApp",
|
"whatsApp",
|
||||||
|
"zemanticAi",
|
||||||
"openai",
|
"openai",
|
||||||
"zemantic-ai",
|
"zemantic-ai",
|
||||||
"chat-node",
|
"chat-node",
|
||||||
@ -13382,6 +13383,7 @@
|
|||||||
"google sheets",
|
"google sheets",
|
||||||
"stripe",
|
"stripe",
|
||||||
"whatsApp",
|
"whatsApp",
|
||||||
|
"zemanticAi",
|
||||||
"openai",
|
"openai",
|
||||||
"zemantic-ai",
|
"zemantic-ai",
|
||||||
"chat-node",
|
"chat-node",
|
||||||
|
@ -7,7 +7,6 @@ import { NextResponse } from 'next/dist/server/web/spec-extension/response'
|
|||||||
import { getBlockById } from '@typebot.io/schemas/helpers'
|
import { getBlockById } from '@typebot.io/schemas/helpers'
|
||||||
import { forgedBlocks } from '@typebot.io/forge-repository/definitions'
|
import { forgedBlocks } from '@typebot.io/forge-repository/definitions'
|
||||||
import { decryptV2 } from '@typebot.io/lib/api/encryption/decryptV2'
|
import { decryptV2 } from '@typebot.io/lib/api/encryption/decryptV2'
|
||||||
import { VariableStore } from '@typebot.io/forge'
|
|
||||||
import {
|
import {
|
||||||
ParseVariablesOptions,
|
ParseVariablesOptions,
|
||||||
parseVariables,
|
parseVariables,
|
||||||
@ -16,6 +15,7 @@ import { IntegrationBlockType } from '@typebot.io/schemas/features/blocks/integr
|
|||||||
import { getChatCompletionStream } from '@typebot.io/bot-engine/blocks/integrations/legacy/openai/getChatCompletionStream'
|
import { getChatCompletionStream } from '@typebot.io/bot-engine/blocks/integrations/legacy/openai/getChatCompletionStream'
|
||||||
import { ChatCompletionOpenAIOptions } from '@typebot.io/schemas/features/blocks/integrations/openai/schema'
|
import { ChatCompletionOpenAIOptions } from '@typebot.io/schemas/features/blocks/integrations/openai/schema'
|
||||||
import { isForgedBlockType } from '@typebot.io/schemas/features/blocks/forged/helpers'
|
import { isForgedBlockType } from '@typebot.io/schemas/features/blocks/forged/helpers'
|
||||||
|
import { AsyncVariableStore } from '@typebot.io/forge/types'
|
||||||
|
|
||||||
export const preferredRegion = 'lhr1'
|
export const preferredRegion = 'lhr1'
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
@ -140,7 +140,7 @@ export async function POST(req: Request) {
|
|||||||
credentials.data,
|
credentials.data,
|
||||||
credentials.iv
|
credentials.iv
|
||||||
)
|
)
|
||||||
const variables: VariableStore = {
|
const variables: AsyncVariableStore = {
|
||||||
list: () => state.typebotsQueue[0].typebot.variables,
|
list: () => state.typebotsQueue[0].typebot.variables,
|
||||||
get: (id: string) => {
|
get: (id: string) => {
|
||||||
const variable = state.typebotsQueue[0].typebot.variables.find(
|
const variable = state.typebotsQueue[0].typebot.variables.find(
|
||||||
@ -151,7 +151,7 @@ export async function POST(req: Request) {
|
|||||||
parse: (text: string, params?: ParseVariablesOptions) =>
|
parse: (text: string, params?: ParseVariablesOptions) =>
|
||||||
parseVariables(state.typebotsQueue[0].typebot.variables, params)(text),
|
parseVariables(state.typebotsQueue[0].typebot.variables, params)(text),
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
set: (_1: string, _2: unknown) => {},
|
set: async (_1: string, _2: unknown) => {},
|
||||||
}
|
}
|
||||||
const { stream } = await action.run.stream.run({
|
const { stream } = await action.run.stream.run({
|
||||||
credentials: decryptedCredentials,
|
credentials: decryptedCredentials,
|
||||||
|
@ -3,7 +3,7 @@ import { ChatCompletionOpenAIOptions } from '@typebot.io/schemas/features/blocks
|
|||||||
import { OpenAI } from 'openai'
|
import { OpenAI } from 'openai'
|
||||||
import { decryptV2 } from '@typebot.io/lib/api/encryption/decryptV2'
|
import { decryptV2 } from '@typebot.io/lib/api/encryption/decryptV2'
|
||||||
import { forgedBlocks } from '@typebot.io/forge-repository/definitions'
|
import { forgedBlocks } from '@typebot.io/forge-repository/definitions'
|
||||||
import { VariableStore } from '@typebot.io/forge'
|
import { AsyncVariableStore } from '@typebot.io/forge'
|
||||||
import {
|
import {
|
||||||
ParseVariablesOptions,
|
ParseVariablesOptions,
|
||||||
parseVariables,
|
parseVariables,
|
||||||
@ -104,7 +104,7 @@ export const getMessageStream = async ({
|
|||||||
credentials.iv
|
credentials.iv
|
||||||
)
|
)
|
||||||
|
|
||||||
const variables: VariableStore = {
|
const variables: AsyncVariableStore = {
|
||||||
list: () => session.state.typebotsQueue[0].typebot.variables,
|
list: () => session.state.typebotsQueue[0].typebot.variables,
|
||||||
get: (id: string) => {
|
get: (id: string) => {
|
||||||
const variable = session.state.typebotsQueue[0].typebot.variables.find(
|
const variable = session.state.typebotsQueue[0].typebot.variables.find(
|
||||||
|
@ -275,7 +275,6 @@ export const ConversationContainer = (props: Props) => {
|
|||||||
const processClientSideActions = async (
|
const processClientSideActions = async (
|
||||||
actions: NonNullable<ContinueChatResponse['clientSideActions']>
|
actions: NonNullable<ContinueChatResponse['clientSideActions']>
|
||||||
) => {
|
) => {
|
||||||
console.log('YES')
|
|
||||||
if (isRecovered()) return
|
if (isRecovered()) return
|
||||||
for (const action of actions) {
|
for (const action of actions) {
|
||||||
if (
|
if (
|
||||||
|
@ -125,28 +125,34 @@ export const createChatMessage = createAction({
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onMessageEnd({ totalTokens, conversationId }) {
|
async onMessageEnd({ totalTokens, conversationId }) {
|
||||||
if (
|
if (
|
||||||
conversationVariableId &&
|
conversationVariableId &&
|
||||||
isNotEmpty(conversationId) &&
|
isNotEmpty(conversationId) &&
|
||||||
isEmpty(existingDifyConversationId?.toString())
|
isEmpty(existingDifyConversationId?.toString())
|
||||||
)
|
)
|
||||||
variables.set(conversationVariableId, conversationId)
|
await variables.set(
|
||||||
|
conversationVariableId,
|
||||||
|
conversationId
|
||||||
|
)
|
||||||
|
|
||||||
if ((responseMapping?.length ?? 0) === 0) return
|
if ((responseMapping?.length ?? 0) === 0) return
|
||||||
responseMapping?.forEach((mapping) => {
|
for (const mapping of responseMapping ?? []) {
|
||||||
if (!mapping.variableId) return
|
if (!mapping.variableId) continue
|
||||||
|
|
||||||
if (
|
if (
|
||||||
mapping.item === 'Conversation ID' &&
|
mapping.item === 'Conversation ID' &&
|
||||||
isNotEmpty(conversationId) &&
|
isNotEmpty(conversationId) &&
|
||||||
isEmpty(existingDifyConversationId?.toString())
|
isEmpty(existingDifyConversationId?.toString())
|
||||||
)
|
)
|
||||||
variables.set(mapping.variableId, conversationId)
|
await variables.set(
|
||||||
|
mapping.variableId,
|
||||||
|
conversationId
|
||||||
|
)
|
||||||
|
|
||||||
if (mapping.item === 'Total Tokens')
|
if (mapping.item === 'Total Tokens')
|
||||||
variables.set(mapping.variableId, totalTokens)
|
await variables.set(mapping.variableId, totalTokens)
|
||||||
})
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -235,7 +241,10 @@ export const createChatMessage = createAction({
|
|||||||
onMessage: (message) => {
|
onMessage: (message) => {
|
||||||
answer += message
|
answer += message
|
||||||
},
|
},
|
||||||
onMessageEnd: ({ totalTokens: tokens, conversationId: id }) => {
|
onMessageEnd: async ({
|
||||||
|
totalTokens: tokens,
|
||||||
|
conversationId: id,
|
||||||
|
}) => {
|
||||||
totalTokens = tokens
|
totalTokens = tokens
|
||||||
conversationId = id
|
conversationId = id
|
||||||
},
|
},
|
||||||
@ -302,7 +311,7 @@ const processDifyStream = async (
|
|||||||
}: {
|
}: {
|
||||||
totalTokens?: number
|
totalTokens?: number
|
||||||
conversationId: string
|
conversationId: string
|
||||||
}) => void
|
}) => Promise<void>
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
let jsonChunk = ''
|
let jsonChunk = ''
|
||||||
@ -317,27 +326,27 @@ const processDifyStream = async (
|
|||||||
const chunk = new TextDecoder().decode(value)
|
const chunk = new TextDecoder().decode(value)
|
||||||
|
|
||||||
const lines = chunk.toString().split('\n') as string[]
|
const lines = chunk.toString().split('\n') as string[]
|
||||||
lines
|
for (const line of lines.filter(
|
||||||
.filter((line) => line.length > 0 && line !== '\n')
|
(line) => line.length > 0 && line !== '\n'
|
||||||
.forEach((line) => {
|
)) {
|
||||||
jsonChunk += line
|
jsonChunk += line
|
||||||
if (jsonChunk.startsWith('event: ')) {
|
if (jsonChunk.startsWith('event: ')) {
|
||||||
jsonChunk = ''
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!jsonChunk.startsWith('data: ') || !jsonChunk.endsWith('}')) return
|
|
||||||
|
|
||||||
const data = JSON.parse(jsonChunk.slice(6)) as Chunk
|
|
||||||
jsonChunk = ''
|
jsonChunk = ''
|
||||||
if (data.event === 'message' || data.event === 'agent_message') {
|
continue
|
||||||
callbacks.onMessage(data.answer)
|
}
|
||||||
}
|
if (!jsonChunk.startsWith('data: ') || !jsonChunk.endsWith('}')) continue
|
||||||
if (data.event === 'message_end') {
|
|
||||||
callbacks.onMessageEnd?.({
|
const data = JSON.parse(jsonChunk.slice(6)) as Chunk
|
||||||
totalTokens: data.metadata.usage?.total_tokens,
|
jsonChunk = ''
|
||||||
conversationId: data.conversation_id,
|
if (data.event === 'message' || data.event === 'agent_message') {
|
||||||
})
|
callbacks.onMessage(data.answer)
|
||||||
}
|
}
|
||||||
})
|
if (data.event === 'message_end') {
|
||||||
|
await callbacks.onMessageEnd?.({
|
||||||
|
totalTokens: data.metadata.usage?.total_tokens,
|
||||||
|
conversationId: data.conversation_id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
AsyncVariableStore,
|
||||||
LogsStore,
|
LogsStore,
|
||||||
VariableStore,
|
VariableStore,
|
||||||
createAction,
|
createAction,
|
||||||
@ -237,7 +238,7 @@ const createAssistantStream = async ({
|
|||||||
variableId?: string | undefined
|
variableId?: string | undefined
|
||||||
}[]
|
}[]
|
||||||
logs?: LogsStore
|
logs?: LogsStore
|
||||||
variables: VariableStore
|
variables: AsyncVariableStore | VariableStore
|
||||||
}): Promise<ReadableStream | undefined> => {
|
}): Promise<ReadableStream | undefined> => {
|
||||||
if (isEmpty(assistantId)) {
|
if (isEmpty(assistantId)) {
|
||||||
logs?.add('Assistant ID is empty')
|
logs?.add('Assistant ID is empty')
|
||||||
@ -277,8 +278,9 @@ const createAssistantStream = async ({
|
|||||||
(mapping) => mapping.item === 'Thread ID'
|
(mapping) => mapping.item === 'Thread ID'
|
||||||
)
|
)
|
||||||
if (threadIdResponseMapping?.variableId)
|
if (threadIdResponseMapping?.variableId)
|
||||||
variables.set(threadIdResponseMapping.variableId, currentThreadId)
|
await variables.set(threadIdResponseMapping.variableId, currentThreadId)
|
||||||
else if (threadVariableId) variables.set(threadVariableId, currentThreadId)
|
else if (threadVariableId)
|
||||||
|
await variables.set(threadVariableId, currentThreadId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentThreadId) {
|
if (!currentThreadId) {
|
||||||
@ -331,9 +333,9 @@ const createAssistantStream = async ({
|
|||||||
args: parameters,
|
args: parameters,
|
||||||
})
|
})
|
||||||
|
|
||||||
newVariables?.forEach((variable) => {
|
for (const variable of newVariables ?? []) {
|
||||||
variables.set(variable.id, variable.value)
|
await variables.set(variable.id, variable.value)
|
||||||
})
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tool_call_id: toolCall.id,
|
tool_call_id: toolCall.id,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { VariableStore } from '@typebot.io/forge/types'
|
import { AsyncVariableStore, VariableStore } from '@typebot.io/forge/types'
|
||||||
import { ChatCompletionOptions } from './parseChatCompletionOptions'
|
import { ChatCompletionOptions } from './parseChatCompletionOptions'
|
||||||
import { APICallError, streamText, ToolCallPart, ToolResultPart } from 'ai'
|
import { APICallError, streamText, ToolCallPart, ToolResultPart } from 'ai'
|
||||||
import { createOpenAI } from '@ai-sdk/openai'
|
import { createOpenAI } from '@ai-sdk/openai'
|
||||||
@ -12,7 +12,7 @@ import { appendToolResultsToMessages } from '@typebot.io/ai/appendToolResultsToM
|
|||||||
type Props = {
|
type Props = {
|
||||||
credentials: { apiKey?: string }
|
credentials: { apiKey?: string }
|
||||||
options: ChatCompletionOptions
|
options: ChatCompletionOptions
|
||||||
variables: VariableStore
|
variables: AsyncVariableStore
|
||||||
config: { baseUrl: string; defaultModel?: string }
|
config: { baseUrl: string; defaultModel?: string }
|
||||||
compatibility?: 'strict' | 'compatible'
|
compatibility?: 'strict' | 'compatible'
|
||||||
}
|
}
|
||||||
@ -59,6 +59,7 @@ export const runOpenAIChatCompletionStream = async ({
|
|||||||
const response = await streamText(streamConfig)
|
const response = await streamText(streamConfig)
|
||||||
|
|
||||||
let totalToolCalls = 0
|
let totalToolCalls = 0
|
||||||
|
let totalTokens = 0
|
||||||
let toolCalls: ToolCallPart[] = []
|
let toolCalls: ToolCallPart[] = []
|
||||||
let toolResults: ToolResultPart[] = []
|
let toolResults: ToolResultPart[] = []
|
||||||
|
|
||||||
@ -69,6 +70,8 @@ export const runOpenAIChatCompletionStream = async ({
|
|||||||
|
|
||||||
await pumpStreamUntilDone(controller, reader)
|
await pumpStreamUntilDone(controller, reader)
|
||||||
|
|
||||||
|
totalTokens = (await response.usage).totalTokens
|
||||||
|
|
||||||
toolCalls = await response.toolCalls
|
toolCalls = await response.toolCalls
|
||||||
if (toolCalls.length > 0)
|
if (toolCalls.length > 0)
|
||||||
toolResults = (await response.toolResults) as ToolResultPart[]
|
toolResults = (await response.toolResults) as ToolResultPart[]
|
||||||
@ -89,11 +92,19 @@ export const runOpenAIChatCompletionStream = async ({
|
|||||||
})
|
})
|
||||||
const reader = newResponse.toAIStream().getReader()
|
const reader = newResponse.toAIStream().getReader()
|
||||||
await pumpStreamUntilDone(controller, reader)
|
await pumpStreamUntilDone(controller, reader)
|
||||||
|
totalTokens += (await newResponse.usage).totalTokens
|
||||||
toolCalls = await newResponse.toolCalls
|
toolCalls = await newResponse.toolCalls
|
||||||
if (toolCalls.length > 0)
|
if (toolCalls.length > 0)
|
||||||
toolResults = (await newResponse.toolResults) as ToolResultPart[]
|
toolResults = (await newResponse.toolResults) as ToolResultPart[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const totalTokenVariableId = options.responseMapping?.find(
|
||||||
|
(mapping) => mapping.item === 'Total tokens'
|
||||||
|
)?.variableId
|
||||||
|
|
||||||
|
if (totalTokenVariableId)
|
||||||
|
await variables.set(totalTokenVariableId, totalTokens)
|
||||||
|
|
||||||
controller.close()
|
controller.close()
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
@ -13,6 +13,10 @@ export type VariableStore = {
|
|||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AsyncVariableStore = Omit<VariableStore, 'set'> & {
|
||||||
|
set: (variableId: string, value: unknown) => Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
export type LogsStore = {
|
export type LogsStore = {
|
||||||
add: (
|
add: (
|
||||||
log:
|
log:
|
||||||
@ -63,7 +67,7 @@ export type ActionDefinition<
|
|||||||
run: (params: {
|
run: (params: {
|
||||||
credentials: CredentialsFromAuthDef<A>
|
credentials: CredentialsFromAuthDef<A>
|
||||||
options: z.infer<BaseOptions> & z.infer<Options>
|
options: z.infer<BaseOptions> & z.infer<Options>
|
||||||
variables: VariableStore
|
variables: AsyncVariableStore
|
||||||
}) => Promise<{
|
}) => Promise<{
|
||||||
stream?: ReadableStream<any>
|
stream?: ReadableStream<any>
|
||||||
httpError?: { status: number; message: string }
|
httpError?: { status: number; message: string }
|
||||||
|
Reference in New Issue
Block a user