Commit c4799df3 by Archer Committed by GitHub

perf: workflow code (#5548)

* perf: workflow code

* add tool call limit
parent 610634e1
...@@ -72,9 +72,10 @@ export type ChatDispatchProps = { ...@@ -72,9 +72,10 @@ export type ChatDispatchProps = {
maxRunTimes: number; maxRunTimes: number;
isToolCall?: boolean; isToolCall?: boolean;
workflowStreamResponse?: WorkflowResponseType; workflowStreamResponse?: WorkflowResponseType;
workflowDispatchDeep?: number;
version?: 'v1' | 'v2'; version?: 'v1' | 'v2';
workflowDispatchDeep: number;
responseAllData?: boolean; responseAllData?: boolean;
responseDetail?: boolean; responseDetail?: boolean;
}; };
......
...@@ -66,11 +66,12 @@ export const promptToolCallMessageRewrite = ( ...@@ -66,11 +66,12 @@ export const promptToolCallMessageRewrite = (
return cloneMessages; return cloneMessages;
}; };
const ERROR_TEXT = 'Tool run error'; const ERROR_TEXT = 'Tool call error';
export const parsePromptToolCall = ( export const parsePromptToolCall = (
str: string str: string
): { ): {
answer: string; answer: string;
streamAnswer?: string;
toolCalls?: ChatCompletionMessageToolCall[]; toolCalls?: ChatCompletionMessageToolCall[];
} => { } => {
str = str.trim(); str = str.trim();
...@@ -99,11 +100,13 @@ export const parsePromptToolCall = ( ...@@ -99,11 +100,13 @@ export const parsePromptToolCall = (
} catch (error) { } catch (error) {
if (prefixReg.test(str)) { if (prefixReg.test(str)) {
return { return {
answer: ERROR_TEXT answer: `${ERROR_TEXT}: ${str}`,
streamAnswer: `${ERROR_TEXT}: ${str}`
}; };
} else { } else {
return { return {
answer: str answer: str,
streamAnswer: str
}; };
} }
} }
......
...@@ -324,7 +324,11 @@ export const createStreamResponse = async ({ ...@@ -324,7 +324,11 @@ export const createStreamResponse = async ({
} }
const { reasoningContent, content, finish_reason, usage } = getResponseData(); const { reasoningContent, content, finish_reason, usage } = getResponseData();
const { answer: llmAnswer, toolCalls } = parsePromptToolCall(content); const { answer: llmAnswer, streamAnswer, toolCalls } = parsePromptToolCall(content);
if (streamAnswer) {
onStreaming?.({ text: streamAnswer });
}
toolCalls?.forEach((call) => { toolCalls?.forEach((call) => {
onToolCall?.({ call }); onToolCall?.({ call });
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d'; import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type'; import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { type SelectAppItemType } from '@fastgpt/global/core/workflow/template/system/abandoned/runApp/type'; import { type SelectAppItemType } from '@fastgpt/global/core/workflow/template/system/abandoned/runApp/type';
import { dispatchWorkFlow } from '../index'; import { runWorkflow } from '../index';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { import {
...@@ -59,27 +59,25 @@ export const dispatchAppRequest = async (props: Props): Promise<Response> => { ...@@ -59,27 +59,25 @@ export const dispatchAppRequest = async (props: Props): Promise<Response> => {
const chatHistories = getHistories(history, histories); const chatHistories = getHistories(history, histories);
const { files } = chatValue2RuntimePrompt(query); const { files } = chatValue2RuntimePrompt(query);
const { flowResponses, flowUsages, assistantResponses, system_memories } = await dispatchWorkFlow( const { flowResponses, flowUsages, assistantResponses, system_memories } = await runWorkflow({
{ ...props,
...props, runningAppInfo: {
runningAppInfo: { id: String(appData._id),
id: String(appData._id), teamId: String(appData.teamId),
teamId: String(appData.teamId), tmbId: String(appData.tmbId)
tmbId: String(appData.tmbId) },
}, runtimeNodes: storeNodes2RuntimeNodes(
runtimeNodes: storeNodes2RuntimeNodes( appData.modules,
appData.modules, getWorkflowEntryNodeIds(appData.modules)
getWorkflowEntryNodeIds(appData.modules) ),
), runtimeEdges: storeEdges2RuntimeEdges(appData.edges),
runtimeEdges: storeEdges2RuntimeEdges(appData.edges), histories: chatHistories,
histories: chatHistories, query: runtimePrompt2ChatsValue({
query: runtimePrompt2ChatsValue({ files,
files, text: userChatInput
text: userChatInput }),
}), variables: props.variables
variables: props.variables });
}
);
const completeMessages = chatHistories.concat([ const completeMessages = chatHistories.concat([
{ {
......
...@@ -201,7 +201,7 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise< ...@@ -201,7 +201,7 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
return runToolCall({ return runToolCall({
...props, ...props,
...requestParams, ...requestParams,
maxRunToolTimes: 30 maxRunToolTimes: 100
}); });
})(); })();
......
...@@ -8,7 +8,7 @@ import { responseWriteController } from '../../../../../common/response'; ...@@ -8,7 +8,7 @@ import { responseWriteController } from '../../../../../common/response';
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils'; import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants'; import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants';
import { dispatchWorkFlow } from '../../index'; import { runWorkflow } from '../../index';
import type { DispatchToolModuleProps, RunToolResponse, ToolNodeItemType } from './type'; import type { DispatchToolModuleProps, RunToolResponse, ToolNodeItemType } from './type';
import json5 from 'json5'; import json5 from 'json5';
import type { DispatchFlowResponse } from '../../type'; import type { DispatchFlowResponse } from '../../type';
...@@ -110,7 +110,7 @@ export const runToolCall = async ( ...@@ -110,7 +110,7 @@ export const runToolCall = async (
initToolCallEdges(runtimeEdges, interactiveEntryToolParams.entryNodeIds); initToolCallEdges(runtimeEdges, interactiveEntryToolParams.entryNodeIds);
// Run entry tool // Run entry tool
const toolRunResponse = await dispatchWorkFlow({ const toolRunResponse = await runWorkflow({
...workflowProps, ...workflowProps,
isToolCall: true isToolCall: true
}); });
...@@ -383,7 +383,7 @@ export const runToolCall = async ( ...@@ -383,7 +383,7 @@ export const runToolCall = async (
})(); })();
initToolNodes(runtimeNodes, [toolNode.nodeId], startParams); initToolNodes(runtimeNodes, [toolNode.nodeId], startParams);
const toolRunResponse = await dispatchWorkFlow({ const toolRunResponse = await runWorkflow({
...workflowProps, ...workflowProps,
isToolCall: true isToolCall: true
}); });
......
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d'; import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type'; import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { dispatchWorkFlow } from '../index'; import { runWorkflow } from '../index';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { import {
...@@ -132,7 +132,7 @@ export const dispatchRunAppNode = async (props: Props): Promise<Response> => { ...@@ -132,7 +132,7 @@ export const dispatchRunAppNode = async (props: Props): Promise<Response> => {
runTimes, runTimes,
workflowInteractiveResponse, workflowInteractiveResponse,
system_memories system_memories
} = await dispatchWorkFlow({ } = await runWorkflow({
...props, ...props,
lastInteractive: childrenInteractive, lastInteractive: childrenInteractive,
// Rewrite stream mode // Rewrite stream mode
......
...@@ -47,7 +47,7 @@ import { removeSystemVariable, rewriteRuntimeWorkFlow } from './utils'; ...@@ -47,7 +47,7 @@ import { removeSystemVariable, rewriteRuntimeWorkFlow } from './utils';
import { getHandleId } from '@fastgpt/global/core/workflow/utils'; import { getHandleId } from '@fastgpt/global/core/workflow/utils';
import { callbackMap } from './constants'; import { callbackMap } from './constants';
type Props = ChatDispatchProps & { type Props = Omit<ChatDispatchProps, 'workflowDispatchDeep'> & {
runtimeNodes: RuntimeNodeItemType[]; runtimeNodes: RuntimeNodeItemType[];
runtimeEdges: RuntimeEdgeItemType[]; runtimeEdges: RuntimeEdgeItemType[];
}; };
...@@ -58,8 +58,62 @@ type NodeResponseCompleteType = Omit<NodeResponseType, 'responseData'> & { ...@@ -58,8 +58,62 @@ type NodeResponseCompleteType = Omit<NodeResponseType, 'responseData'> & {
[DispatchNodeResponseKeyEnum.nodeResponse]?: ChatHistoryItemResType; [DispatchNodeResponseKeyEnum.nodeResponse]?: ChatHistoryItemResType;
}; };
/* running */ // Run workflow
export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowResponse> { export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowResponse> {
const { res, stream, externalProvider } = data;
let streamCheckTimer: NodeJS.Timeout | null = null;
// set sse response headers
if (res) {
res.setHeader('Connection', 'keep-alive'); // Set keepalive for long connection
if (stream) {
res.on('close', () => res.end());
res.on('error', () => {
addLog.error('Request error');
res.end();
});
res.setHeader('Content-Type', 'text/event-stream;charset=utf-8');
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('X-Accel-Buffering', 'no');
res.setHeader('Cache-Control', 'no-cache, no-transform');
// 10s sends a message to prevent the browser from thinking that the connection is disconnected
streamCheckTimer = setInterval(() => {
data?.workflowStreamResponse?.({
event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({
text: ''
})
});
}, 10000);
}
}
// Get default variables
const defaultVariables = {
...externalProvider.externalWorkflowVariables,
...getSystemVariables(data)
};
// Init some props
return runWorkflow({
...data,
variables: defaultVariables,
workflowDispatchDeep: 0
}).finally(() => {
if (streamCheckTimer) {
clearInterval(streamCheckTimer);
}
});
}
type RunWorkflowProps = ChatDispatchProps & {
runtimeNodes: RuntimeNodeItemType[];
runtimeEdges: RuntimeEdgeItemType[];
};
export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowResponse> => {
let { let {
res, res,
runtimeNodes = [], runtimeNodes = [],
...@@ -67,28 +121,15 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons ...@@ -67,28 +121,15 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
histories = [], histories = [],
variables = {}, variables = {},
externalProvider, externalProvider,
stream = false,
retainDatasetCite = true, retainDatasetCite = true,
version = 'v1', version = 'v1',
responseDetail = true, responseDetail = true,
responseAllData = true responseAllData = true
} = data; } = data;
const startTime = Date.now();
await rewriteRuntimeWorkFlow({ nodes: runtimeNodes, edges: runtimeEdges, lang: data.lang });
// 初始化深度和自动增加深度,避免无限嵌套
if (!data.workflowDispatchDeep) {
data.workflowDispatchDeep = 1;
} else {
data.workflowDispatchDeep += 1;
}
const isRootRuntime = data.workflowDispatchDeep === 1;
// 初始化 runtimeNodesMap
const runtimeNodesMap = new Map(runtimeNodes.map((item) => [item.nodeId, item]));
// Over max depth // Over max depth
data.workflowDispatchDeep++;
const isRootRuntime = data.workflowDispatchDeep === 1;
if (data.workflowDispatchDeep > 20) { if (data.workflowDispatchDeep > 20) {
return { return {
flowResponses: [], flowResponses: [],
...@@ -106,42 +147,9 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons ...@@ -106,42 +147,9 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
}; };
} }
let workflowRunTimes = 0; const startTime = Date.now();
let streamCheckTimer: NodeJS.Timeout | null = null;
// Init
if (isRootRuntime) {
// set sse response headers
res?.setHeader('Connection', 'keep-alive'); // Set keepalive for long connection
if (stream && res) {
res.on('close', () => res.end());
res.on('error', () => {
addLog.error('Request error');
res.end();
});
res.setHeader('Content-Type', 'text/event-stream;charset=utf-8');
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('X-Accel-Buffering', 'no');
res.setHeader('Cache-Control', 'no-cache, no-transform');
// 10s sends a message to prevent the browser from thinking that the connection is disconnected
streamCheckTimer = setInterval(() => {
data?.workflowStreamResponse?.({
event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({
text: ''
})
});
}, 10000);
}
// Get default variables await rewriteRuntimeWorkFlow({ nodes: runtimeNodes, edges: runtimeEdges, lang: data.lang });
variables = {
...externalProvider.externalWorkflowVariables,
...getSystemVariables(data)
};
}
/* /*
工作流队列控制 工作流队列控制
...@@ -161,7 +169,9 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons ...@@ -161,7 +169,9 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
- 触发交互节点后,需要跳过所有 skip 节点,避免后续执行了 skipNode。 - 触发交互节点后,需要跳过所有 skip 节点,避免后续执行了 skipNode。
*/ */
class WorkflowQueue { class WorkflowQueue {
runtimeNodesMap = new Map(runtimeNodes.map((item) => [item.nodeId, item]));
// Workflow variables // Workflow variables
workflowRunTimes = 0;
chatResponses: ChatHistoryItemResType[] = []; // response request and save to database chatResponses: ChatHistoryItemResType[] = []; // response request and save to database
chatAssistantResponse: AIChatItemValueItemType[] = []; // The value will be returned to the user chatAssistantResponse: AIChatItemValueItemType[] = []; // The value will be returned to the user
chatNodeUsages: ChatNodeUsageType[] = []; chatNodeUsages: ChatNodeUsageType[] = [];
...@@ -221,7 +231,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons ...@@ -221,7 +231,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
} }
const nodeId = this.activeRunQueue.keys().next().value; const nodeId = this.activeRunQueue.keys().next().value;
const node = nodeId ? runtimeNodesMap.get(nodeId) : undefined; const node = nodeId ? this.runtimeNodesMap.get(nodeId) : undefined;
if (nodeId) { if (nodeId) {
this.activeRunQueue.delete(nodeId); this.activeRunQueue.delete(nodeId);
...@@ -501,7 +511,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons ...@@ -501,7 +511,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
system_memories: newMemories system_memories: newMemories
}: NodeResponseCompleteType) => { }: NodeResponseCompleteType) => {
// Add run times // Add run times
workflowRunTimes += runTimes; this.workflowRunTimes += runTimes;
data.maxRunTimes -= runTimes; data.maxRunTimes -= runTimes;
if (newMemories) { if (newMemories) {
...@@ -650,7 +660,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons ...@@ -650,7 +660,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
// Get node run status by edges // Get node run status by edges
const status = checkNodeRunStatus({ const status = checkNodeRunStatus({
nodesMap: runtimeNodesMap, nodesMap: this.runtimeNodesMap,
node, node,
runtimeEdges runtimeEdges
}); });
...@@ -820,10 +830,6 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons ...@@ -820,10 +830,6 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
}); });
} }
if (streamCheckTimer) {
clearInterval(streamCheckTimer);
}
return { return {
flowResponses: workflowQueue.chatResponses, flowResponses: workflowQueue.chatResponses,
flowUsages: workflowQueue.chatNodeUsages, flowUsages: workflowQueue.chatNodeUsages,
...@@ -833,7 +839,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons ...@@ -833,7 +839,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
nextStepRunNodes: workflowQueue.debugNextStepRunNodes nextStepRunNodes: workflowQueue.debugNextStepRunNodes
}, },
workflowInteractiveResponse: interactiveResult, workflowInteractiveResponse: interactiveResult,
[DispatchNodeResponseKeyEnum.runTimes]: workflowRunTimes, [DispatchNodeResponseKeyEnum.runTimes]: workflowQueue.workflowRunTimes,
[DispatchNodeResponseKeyEnum.assistantResponses]: mergeAssistantResponseAnswerText( [DispatchNodeResponseKeyEnum.assistantResponses]: mergeAssistantResponseAnswerText(
workflowQueue.chatAssistantResponse workflowQueue.chatAssistantResponse
), ),
...@@ -848,7 +854,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons ...@@ -848,7 +854,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
: undefined, : undefined,
durationSeconds durationSeconds
}; };
} };
/* get system variable */ /* get system variable */
const getSystemVariables = ({ const getSystemVariables = ({
......
...@@ -4,7 +4,7 @@ import { ...@@ -4,7 +4,7 @@ import {
type DispatchNodeResultType, type DispatchNodeResultType,
type ModuleDispatchProps type ModuleDispatchProps
} from '@fastgpt/global/core/workflow/runtime/type'; } from '@fastgpt/global/core/workflow/runtime/type';
import { dispatchWorkFlow } from '..'; import { runWorkflow } from '..';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { import {
type AIChatItemValueItemType, type AIChatItemValueItemType,
...@@ -93,7 +93,7 @@ export const dispatchLoop = async (props: Props): Promise<Response> => { ...@@ -93,7 +93,7 @@ export const dispatchLoop = async (props: Props): Promise<Response> => {
index++; index++;
const response = await dispatchWorkFlow({ const response = await runWorkflow({
...props, ...props,
lastInteractive: interactiveData?.childrenResponse, lastInteractive: interactiveData?.childrenResponse,
variables: newVariables, variables: newVariables,
......
...@@ -20,7 +20,7 @@ import { filterSystemVariables, getNodeErrResponse } from '../utils'; ...@@ -20,7 +20,7 @@ import { filterSystemVariables, getNodeErrResponse } from '../utils';
import { getPluginRunUserQuery } from '@fastgpt/global/core/workflow/utils'; import { getPluginRunUserQuery } from '@fastgpt/global/core/workflow/utils';
import type { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import type { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { getChildAppRuntimeById } from '../../../app/plugin/controller'; import { getChildAppRuntimeById } from '../../../app/plugin/controller';
import { dispatchWorkFlow } from '../index'; import { runWorkflow } from '../index';
import { getUserChatInfoAndAuthTeamPoints } from '../../../../support/permission/auth/team'; import { getUserChatInfoAndAuthTeamPoints } from '../../../../support/permission/auth/team';
import { dispatchRunTool } from '../child/runTool'; import { dispatchRunTool } from '../child/runTool';
import type { PluginRuntimeType } from '@fastgpt/global/core/app/plugin/type'; import type { PluginRuntimeType } from '@fastgpt/global/core/app/plugin/type';
...@@ -118,7 +118,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi ...@@ -118,7 +118,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
...(externalProvider ? externalProvider.externalWorkflowVariables : {}) ...(externalProvider ? externalProvider.externalWorkflowVariables : {})
}; };
const { flowResponses, flowUsages, assistantResponses, runTimes, system_memories } = const { flowResponses, flowUsages, assistantResponses, runTimes, system_memories } =
await dispatchWorkFlow({ await runWorkflow({
...props, ...props,
// Rewrite stream mode // Rewrite stream mode
...(system_forbid_stream ...(system_forbid_stream
......
...@@ -141,18 +141,20 @@ describe('parsePromptToolCall function tests', () => { ...@@ -141,18 +141,20 @@ describe('parsePromptToolCall function tests', () => {
const input = '1: {"name": "tool", "arguments": invalid json}'; const input = '1: {"name": "tool", "arguments": invalid json}';
const result = parsePromptToolCall(input); const result = parsePromptToolCall(input);
expect(result).toEqual({ expect(result.answer).toEqual(
answer: 'Tool run error' 'Tool call error: 1: {"name": "tool", "arguments": invalid json}'
}); );
expect(result.streamAnswer).toEqual(
'Tool call error: 1: {"name": "tool", "arguments": invalid json}'
);
}); });
it('should return error message for incomplete JSON with 1:', () => { it('should return error message for incomplete JSON with 1:', () => {
const input = '1: {"name": "tool"'; const input = '1: {"name": "tool"';
const result = parsePromptToolCall(input); const result = parsePromptToolCall(input);
expect(result).toEqual({ expect(result.answer).toEqual('Tool call error: 1: {"name": "tool"');
answer: 'Tool run error' expect(result.streamAnswer).toEqual('Tool call error: 1: {"name": "tool"');
});
}); });
it('should handle empty JSON object with 1: (creates tool call with undefined properties)', () => { it('should handle empty JSON object with 1: (creates tool call with undefined properties)', () => {
...@@ -187,18 +189,16 @@ describe('parsePromptToolCall function tests', () => { ...@@ -187,18 +189,16 @@ describe('parsePromptToolCall function tests', () => {
const input = '1:'; const input = '1:';
const result = parsePromptToolCall(input); const result = parsePromptToolCall(input);
expect(result).toEqual({ expect(result.answer).toEqual('Tool call error: 1:');
answer: 'Tool run error' expect(result.streamAnswer).toEqual('Tool call error: 1:');
});
}); });
it('should handle input with only prefix and whitespace', () => { it('should handle input with only prefix and whitespace', () => {
const input = '1: '; const input = '1: ';
const result = parsePromptToolCall(input); const result = parsePromptToolCall(input);
expect(result).toEqual({ expect(result.answer).toEqual('Tool call error: 1:');
answer: 'Tool run error' expect(result.streamAnswer).toEqual('Tool call error: 1:');
});
}); });
it('should handle JSON5 syntax in tool call', () => { it('should handle JSON5 syntax in tool call', () => {
...@@ -244,9 +244,12 @@ describe('parsePromptToolCall function tests', () => { ...@@ -244,9 +244,12 @@ describe('parsePromptToolCall function tests', () => {
const result = parsePromptToolCall(input); const result = parsePromptToolCall(input);
// The sliceJsonStr function can't properly extract JSON when there's extra text after // The sliceJsonStr function can't properly extract JSON when there's extra text after
expect(result).toEqual({ expect(result.answer).toEqual(
answer: 'Tool run error' 'Tool call error: Text 1: {"name": "tool1", "arguments": {"param": "value"}} more text 1: {"name": "tool2", "arguments": {}}'
}); );
expect(result.streamAnswer).toEqual(
'Tool call error: Text 1: {"name": "tool1", "arguments": {"param": "value"}} more text 1: {"name": "tool2", "arguments": {}}'
);
}); });
it('should handle tool name with underscores and numbers', () => { it('should handle tool name with underscores and numbers', () => {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment