diff --git a/packages/global/common/string/tools.ts b/packages/global/common/string/tools.ts index 26147f5d347c..53d74a513a76 100644 --- a/packages/global/common/string/tools.ts +++ b/packages/global/common/string/tools.ts @@ -64,4 +64,22 @@ export const getNanoid = (size = 12) => { return `${firstChar}${randomsStr}`; }; +/* Custom text to reg, need to replace special chats */ export const replaceRegChars = (text: string) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + +/* slice json str */ +export const sliceJsonStr = (str: string) => { + str = str.replace(/(\\n|\\)/g, '').replace(/ /g, ''); + + const jsonRegex = /{[^{}]*}/g; + const matches = str.match(jsonRegex); + + if (!matches) { + return ''; + } + + // 找到第一个完整的 JSON 字符串 + const jsonStr = matches[0]; + + return jsonStr; +}; diff --git a/packages/global/core/workflow/template/system/runApp.ts b/packages/global/core/workflow/template/system/runApp.ts index 21ad2ab5bf42..425b609b2cd0 100644 --- a/packages/global/core/workflow/template/system/runApp.ts +++ b/packages/global/core/workflow/template/system/runApp.ts @@ -24,6 +24,7 @@ export const RunAppModule: FlowNodeTemplateType = { intro: '可以选择一个其他应用进行调用', showStatus: true, version: '481', + isTool: true, inputs: [ { key: NodeInputKeyEnum.runAppSelectApp, diff --git a/packages/service/core/ai/functions/queryExtension.ts b/packages/service/core/ai/functions/queryExtension.ts index ff8713bfc8a1..534a7058dbbc 100644 --- a/packages/service/core/ai/functions/queryExtension.ts +++ b/packages/service/core/ai/functions/queryExtension.ts @@ -65,7 +65,7 @@ Q: FastGPT 如何收费? A: FastGPT 收费可以参考…… """ 原问题: 你知道 laf 么? -检索词: ["laf是什么?","如何使用laf?","laf的介绍。"] +检索词: ["laf 的官网地址是多少?","laf 的使用教程。","laf 有什么特点和优势。"] ---------------- 历史记录: """ @@ -75,7 +75,7 @@ A: 1. 开源 3. 扩展性强 """ 原问题: 介绍下第2点。 -检索词: ["介绍下 FastGPT 简便的优势", "FastGPT 为什么使用起来简便?","FastGPT的有哪些简便的功能?"]。 +检索词: ["介绍下 FastGPT 简便的优势"]。 ---------------- 历史记录: """ @@ -85,7 +85,7 @@ Q: 什么是 Laf? A: Laf 是一个云函数开发平台。 """ 原问题: 它们有什么关系? -检索词: ["FastGPT和Laf有什么关系?","FastGPT的RAG是用Laf实现的么?"] +检索词: ["FastGPT和Laf有什么关系?","介绍下FastGPT","介绍下Laf"] ---------------- 历史记录: """ diff --git a/packages/service/core/workflow/dispatch/agent/extract.ts b/packages/service/core/workflow/dispatch/agent/extract.ts index 45066eb55a18..9d73b52c5fb3 100644 --- a/packages/service/core/workflow/dispatch/agent/extract.ts +++ b/packages/service/core/workflow/dispatch/agent/extract.ts @@ -12,7 +12,7 @@ import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workfl import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/type/index.d'; import { Prompt_ExtractJson } from '@fastgpt/global/core/ai/prompt/agent'; -import { replaceVariable } from '@fastgpt/global/common/string/tools'; +import { replaceVariable, sliceJsonStr } from '@fastgpt/global/common/string/tools'; import { LLMModelItemType } from '@fastgpt/global/core/ai/model.d'; import { getHistories } from '../utils'; import { ModelTypeEnum, getLLMModel } from '../../../ai/model'; @@ -348,10 +348,9 @@ Human: ${content}` const answer = data.choices?.[0].message?.content || ''; // parse response - const start = answer.indexOf('{'); - const end = answer.lastIndexOf('}'); + const jsonStr = sliceJsonStr(answer); - if (start === -1 || end === -1) { + if (!jsonStr) { return { rawResponse: answer, tokens: await countMessagesTokens(messages), @@ -359,11 +358,6 @@ Human: ${content}` }; } - const jsonStr = answer - .substring(start, end + 1) - .replace(/(\\n|\\)/g, '') - .replace(/ /g, ''); - try { return { rawResponse: answer, diff --git a/packages/service/core/workflow/dispatch/agent/runTool/constants.ts b/packages/service/core/workflow/dispatch/agent/runTool/constants.ts index 8c2fd40959d2..b6c354ee02c0 100644 --- a/packages/service/core/workflow/dispatch/agent/runTool/constants.ts +++ b/packages/service/core/workflow/dispatch/agent/runTool/constants.ts @@ -3,7 +3,7 @@ export const Prompt_Tool_Call = ` 工具使用了 JSON Schema 的格式声明,其中 toolId 是工具的 description 是工具的描述,parameters 是工具的参数,包括参数的类型和描述,required 是必填参数的列表。 -请你根据工具描述,决定回答问题或是使用工具。在完成任务过程中,USER代表用户的输入,TOOL_RESPONSE代表工具运行结果。ASSISTANT 代表你的输出。 +请你根据工具描述,决定回答问题或是使用工具。在完成任务过程中,USER代表用户的输入,TOOL_RESPONSE代表工具运行结果,ANSWER 代表你的输出。 你的每次输出都必须以0,1开头,代表是否需要调用工具: 0: 不使用工具,直接回答内容。 1: 使用工具,返回工具调用的参数。 @@ -12,14 +12,20 @@ export const Prompt_Tool_Call = ` USER: 你好呀 ANSWER: 0: 你好,有什么可以帮助你的么? -USER: 今天杭州的天气如何 -ANSWER: 1: {"toolId":"testToolId",arguments:{"city": "杭州"}} +USER: 现在几点了? +ANSWER: 1: {"toolId":"timeToolId"} +TOOL_RESPONSE: """ +2022/5/5 12:00 Thursday +""" +ANSWER: 0: 现在是2022年5月5日,星期四,中午12点。 +USER: 今天杭州的天气如何? +ANSWER: 1: {"toolId":"testToolId","arguments":{"city": "杭州"}} TOOL_RESPONSE: """ 晴天...... """ ANSWER: 0: 今天杭州是晴天。 USER: 今天杭州的天气适合去哪里玩? -ANSWER: 1: {"toolId":"testToolId2",arguments:{"query": "杭州 天气 去哪里玩"}} +ANSWER: 1: {"toolId":"testToolId2","arguments":{"query": "杭州 天气 去哪里玩"}} TOOL_RESPONSE: """ 晴天. 西湖、灵隐寺、千岛湖…… """ @@ -35,5 +41,4 @@ ANSWER: 0: 今天杭州是晴天,适合去西湖、灵隐寺、千岛湖等地 下面是正式的对话内容: USER: {{question}} -ANSWER: -`; +ANSWER: `; diff --git a/packages/service/core/workflow/dispatch/agent/runTool/promptCall.ts b/packages/service/core/workflow/dispatch/agent/runTool/promptCall.ts index 783f272cff48..d5dc71d6b4b7 100644 --- a/packages/service/core/workflow/dispatch/agent/runTool/promptCall.ts +++ b/packages/service/core/workflow/dispatch/agent/runTool/promptCall.ts @@ -20,7 +20,7 @@ import { dispatchWorkFlow } from '../../index'; import { DispatchToolModuleProps, RunToolResponse, ToolNodeItemType } from './type.d'; import json5 from 'json5'; import { countGptMessagesTokens } from '../../../../../common/string/tiktoken/index'; -import { getNanoid, replaceVariable } from '@fastgpt/global/common/string/tools'; +import { getNanoid, replaceVariable, sliceJsonStr } from '@fastgpt/global/common/string/tools'; import { AIChatItemType } from '@fastgpt/global/core/chat/type'; import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt'; import { updateToolInputValue } from './utils'; @@ -33,6 +33,8 @@ type FunctionCallCompletion = { toolAvatar?: string; }; +const ERROR_TEXT = 'Tool run error'; + export const runToolWithPromptCall = async ( props: DispatchToolModuleProps & { messages: ChatCompletionMessageParam[]; @@ -122,14 +124,23 @@ export const runToolWithPromptCall = async ( } })(); - const parseAnswerResult = parseAnswer(answer); + const { answer: replaceAnswer, toolJson } = parseAnswer(answer); // console.log(parseAnswer, '==11=='); // No tools - if (typeof parseAnswerResult === 'string') { + if (!toolJson) { + if (replaceAnswer === ERROR_TEXT && stream && detail) { + responseWrite({ + res, + event: SseResponseEventEnum.answer, + data: textAdaptGptResponse({ + text: replaceAnswer + }) + }); + } // No tool is invoked, indicating that the process is over const gptAssistantResponse: ChatCompletionAssistantMessageParam = { role: ChatCompletionRequestMessageRoleEnum.Assistant, - content: parseAnswerResult + content: replaceAnswer }; const completeMessages = filterMessages.concat(gptAssistantResponse); const tokens = await countGptMessagesTokens(completeMessages, undefined); @@ -148,18 +159,16 @@ export const runToolWithPromptCall = async ( // Run the selected tool. const toolsRunResponse = await (async () => { - if (!parseAnswerResult) return Promise.reject('tool run error'); - - const toolNode = toolNodes.find((item) => item.nodeId === parseAnswerResult.name); + const toolNode = toolNodes.find((item) => item.nodeId === toolJson.name); if (!toolNode) return Promise.reject('tool not found'); - parseAnswerResult.toolName = toolNode.name; - parseAnswerResult.toolAvatar = toolNode.avatar; + toolJson.toolName = toolNode.name; + toolJson.toolAvatar = toolNode.avatar; // run tool flow const startParams = (() => { try { - return json5.parse(parseAnswerResult.arguments); + return json5.parse(toolJson.arguments); } catch (error) { return {}; } @@ -172,11 +181,11 @@ export const runToolWithPromptCall = async ( event: SseResponseEventEnum.toolCall, data: JSON.stringify({ tool: { - id: parseAnswerResult.id, + id: toolJson.id, toolName: toolNode.name, toolAvatar: toolNode.avatar, - functionName: parseAnswerResult.name, - params: parseAnswerResult.arguments, + functionName: toolJson.name, + params: toolJson.arguments, response: '' } }) @@ -211,7 +220,7 @@ export const runToolWithPromptCall = async ( event: SseResponseEventEnum.toolResponse, data: JSON.stringify({ tool: { - id: parseAnswerResult.id, + id: toolJson.id, toolName: '', toolAvatar: '', params: '', @@ -237,7 +246,7 @@ export const runToolWithPromptCall = async ( // 合并工具调用的结果,使用 functionCall 格式存储。 const assistantToolMsgParams: ChatCompletionAssistantMessageParam = { role: ChatCompletionRequestMessageRoleEnum.Assistant, - function_call: parseAnswerResult + function_call: toolJson }; const concatToolMessages = [ ...filterMessages, @@ -248,7 +257,7 @@ export const runToolWithPromptCall = async ( ...concatToolMessages, { role: ChatCompletionRequestMessageRoleEnum.Function, - name: parseAnswerResult.name, + name: toolJson.name, content: toolsRunResponse.toolResponsePrompt } ]; @@ -266,7 +275,7 @@ export const runToolWithPromptCall = async ( : [toolsRunResponse.moduleRunResponse]; // get the next user prompt - lastMessage.content += `${answer} + lastMessage.content += `${replaceAnswer} TOOL_RESPONSE: """ ${toolsRunResponse.toolResponsePrompt} """ @@ -362,24 +371,37 @@ async function streamResponse({ return { answer: textAnswer.trim() }; } -const parseAnswer = (str: string): FunctionCallCompletion | string => { - // 首先,使用正则表达式提取TOOL_ID和TOOL_ARGUMENTS - const prefix = '1:'; +const parseAnswer = ( + str: string +): { + answer: string; + toolJson?: FunctionCallCompletion; +} => { str = str.trim(); - if (str.startsWith(prefix)) { - const toolString = str.substring(prefix.length).trim(); + // 首先,使用正则表达式提取TOOL_ID和TOOL_ARGUMENTS + const prefixReg = /^1(:|:)/; + + if (prefixReg.test(str)) { + const toolString = sliceJsonStr(str); try { const toolCall = json5.parse(toolString); return { - id: getNanoid(), - name: toolCall.toolId, - arguments: JSON.stringify(toolCall.arguments || toolCall.parameters) + answer: `1: ${toolString}`, + toolJson: { + id: getNanoid(), + name: toolCall.toolId, + arguments: JSON.stringify(toolCall.arguments || toolCall.parameters) + } }; } catch (error) { - return str; + return { + answer: ERROR_TEXT + }; } } else { - return str; + return { + answer: str + }; } };