perf: tool promot and reg slice;query extension prompt (#1576)

This commit is contained in:
Archer
2024-05-23 15:14:22 +08:00
committed by GitHub
parent 4eb2c9bd07
commit c4ce1236ea
6 changed files with 85 additions and 45 deletions

View File

@@ -64,4 +64,22 @@ export const getNanoid = (size = 12) => {
return `${firstChar}${randomsStr}`; return `${firstChar}${randomsStr}`;
}; };
/* Custom text to reg, need to replace special chats */
export const replaceRegChars = (text: string) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); 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;
};

View File

@@ -24,6 +24,7 @@ export const RunAppModule: FlowNodeTemplateType = {
intro: '可以选择一个其他应用进行调用', intro: '可以选择一个其他应用进行调用',
showStatus: true, showStatus: true,
version: '481', version: '481',
isTool: true,
inputs: [ inputs: [
{ {
key: NodeInputKeyEnum.runAppSelectApp, key: NodeInputKeyEnum.runAppSelectApp,

View File

@@ -65,7 +65,7 @@ Q: FastGPT 如何收费?
A: FastGPT 收费可以参考…… A: FastGPT 收费可以参考……
""" """
原问题: 你知道 laf 么? 原问题: 你知道 laf 么?
检索词: ["laf是什么?","如何使用laf","laf的介绍。"] 检索词: ["laf 的官网地址是多少?","laf 的使用教程。","laf 有什么特点和优势。"]
---------------- ----------------
历史记录: 历史记录:
""" """
@@ -75,7 +75,7 @@ A: 1. 开源
3. 扩展性强 3. 扩展性强
""" """
原问题: 介绍下第2点。 原问题: 介绍下第2点。
检索词: ["介绍下 FastGPT 简便的优势", "FastGPT 为什么使用起来简便?","FastGPT的有哪些简便的功能"]。 检索词: ["介绍下 FastGPT 简便的优势"]。
---------------- ----------------
历史记录: 历史记录:
""" """
@@ -85,7 +85,7 @@ Q: 什么是 Laf
A: Laf 是一个云函数开发平台。 A: Laf 是一个云函数开发平台。
""" """
原问题: 它们有什么关系? 原问题: 它们有什么关系?
检索词: ["FastGPT和Laf有什么关系","FastGPT的RAG是用Laf实现的么"] 检索词: ["FastGPT和Laf有什么关系","介绍下FastGPT","介绍下Laf"]
---------------- ----------------
历史记录: 历史记录:
""" """

View File

@@ -12,7 +12,7 @@ import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workfl
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/type/index.d'; import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/type/index.d';
import { Prompt_ExtractJson } from '@fastgpt/global/core/ai/prompt/agent'; 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 { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
import { getHistories } from '../utils'; import { getHistories } from '../utils';
import { ModelTypeEnum, getLLMModel } from '../../../ai/model'; import { ModelTypeEnum, getLLMModel } from '../../../ai/model';
@@ -348,10 +348,9 @@ Human: ${content}`
const answer = data.choices?.[0].message?.content || ''; const answer = data.choices?.[0].message?.content || '';
// parse response // parse response
const start = answer.indexOf('{'); const jsonStr = sliceJsonStr(answer);
const end = answer.lastIndexOf('}');
if (start === -1 || end === -1) { if (!jsonStr) {
return { return {
rawResponse: answer, rawResponse: answer,
tokens: await countMessagesTokens(messages), tokens: await countMessagesTokens(messages),
@@ -359,11 +358,6 @@ Human: ${content}`
}; };
} }
const jsonStr = answer
.substring(start, end + 1)
.replace(/(\\n|\\)/g, '')
.replace(/ /g, '');
try { try {
return { return {
rawResponse: answer, rawResponse: answer,

View File

@@ -3,7 +3,7 @@ export const Prompt_Tool_Call = `<Instruction>
工具使用了 JSON Schema 的格式声明,其中 toolId 是工具的 description 是工具的描述parameters 是工具的参数包括参数的类型和描述required 是必填参数的列表。 工具使用了 JSON Schema 的格式声明,其中 toolId 是工具的 description 是工具的描述parameters 是工具的参数包括参数的类型和描述required 是必填参数的列表。
请你根据工具描述决定回答问题或是使用工具。在完成任务过程中USER代表用户的输入TOOL_RESPONSE代表工具运行结果。ASSISTANT 代表你的输出。 请你根据工具描述决定回答问题或是使用工具。在完成任务过程中USER代表用户的输入TOOL_RESPONSE代表工具运行结果ANSWER 代表你的输出。
你的每次输出都必须以0,1开头代表是否需要调用工具 你的每次输出都必须以0,1开头代表是否需要调用工具
0: 不使用工具,直接回答内容。 0: 不使用工具,直接回答内容。
1: 使用工具,返回工具调用的参数。 1: 使用工具,返回工具调用的参数。
@@ -12,14 +12,20 @@ export const Prompt_Tool_Call = `<Instruction>
USER: 你好呀 USER: 你好呀
ANSWER: 0: 你好,有什么可以帮助你的么? ANSWER: 0: 你好,有什么可以帮助你的么?
USER: 今天杭州的天气如何 USER: 现在几点了?
ANSWER: 1: {"toolId":"testToolId",arguments:{"city": "杭州"}} 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: """ TOOL_RESPONSE: """
晴天...... 晴天......
""" """
ANSWER: 0: 今天杭州是晴天。 ANSWER: 0: 今天杭州是晴天。
USER: 今天杭州的天气适合去哪里玩? USER: 今天杭州的天气适合去哪里玩?
ANSWER: 1: {"toolId":"testToolId2",arguments:{"query": "杭州 天气 去哪里玩"}} ANSWER: 1: {"toolId":"testToolId2","arguments":{"query": "杭州 天气 去哪里玩"}}
TOOL_RESPONSE: """ TOOL_RESPONSE: """
晴天. 西湖、灵隐寺、千岛湖…… 晴天. 西湖、灵隐寺、千岛湖……
""" """
@@ -35,5 +41,4 @@ ANSWER: 0: 今天杭州是晴天,适合去西湖、灵隐寺、千岛湖等地
下面是正式的对话内容: 下面是正式的对话内容:
USER: {{question}} USER: {{question}}
ANSWER: ANSWER: `;
`;

View File

@@ -20,7 +20,7 @@ import { dispatchWorkFlow } from '../../index';
import { DispatchToolModuleProps, RunToolResponse, ToolNodeItemType } from './type.d'; import { DispatchToolModuleProps, RunToolResponse, ToolNodeItemType } from './type.d';
import json5 from 'json5'; import json5 from 'json5';
import { countGptMessagesTokens } from '../../../../../common/string/tiktoken/index'; 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 { AIChatItemType } from '@fastgpt/global/core/chat/type';
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt'; import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { updateToolInputValue } from './utils'; import { updateToolInputValue } from './utils';
@@ -33,6 +33,8 @@ type FunctionCallCompletion = {
toolAvatar?: string; toolAvatar?: string;
}; };
const ERROR_TEXT = 'Tool run error';
export const runToolWithPromptCall = async ( export const runToolWithPromptCall = async (
props: DispatchToolModuleProps & { props: DispatchToolModuleProps & {
messages: ChatCompletionMessageParam[]; messages: ChatCompletionMessageParam[];
@@ -122,14 +124,23 @@ export const runToolWithPromptCall = async (
} }
})(); })();
const parseAnswerResult = parseAnswer(answer); const { answer: replaceAnswer, toolJson } = parseAnswer(answer);
// console.log(parseAnswer, '==11=='); // console.log(parseAnswer, '==11==');
// No tools // 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 // No tool is invoked, indicating that the process is over
const gptAssistantResponse: ChatCompletionAssistantMessageParam = { const gptAssistantResponse: ChatCompletionAssistantMessageParam = {
role: ChatCompletionRequestMessageRoleEnum.Assistant, role: ChatCompletionRequestMessageRoleEnum.Assistant,
content: parseAnswerResult content: replaceAnswer
}; };
const completeMessages = filterMessages.concat(gptAssistantResponse); const completeMessages = filterMessages.concat(gptAssistantResponse);
const tokens = await countGptMessagesTokens(completeMessages, undefined); const tokens = await countGptMessagesTokens(completeMessages, undefined);
@@ -148,18 +159,16 @@ export const runToolWithPromptCall = async (
// Run the selected tool. // Run the selected tool.
const toolsRunResponse = await (async () => { const toolsRunResponse = await (async () => {
if (!parseAnswerResult) return Promise.reject('tool run error'); const toolNode = toolNodes.find((item) => item.nodeId === toolJson.name);
const toolNode = toolNodes.find((item) => item.nodeId === parseAnswerResult.name);
if (!toolNode) return Promise.reject('tool not found'); if (!toolNode) return Promise.reject('tool not found');
parseAnswerResult.toolName = toolNode.name; toolJson.toolName = toolNode.name;
parseAnswerResult.toolAvatar = toolNode.avatar; toolJson.toolAvatar = toolNode.avatar;
// run tool flow // run tool flow
const startParams = (() => { const startParams = (() => {
try { try {
return json5.parse(parseAnswerResult.arguments); return json5.parse(toolJson.arguments);
} catch (error) { } catch (error) {
return {}; return {};
} }
@@ -172,11 +181,11 @@ export const runToolWithPromptCall = async (
event: SseResponseEventEnum.toolCall, event: SseResponseEventEnum.toolCall,
data: JSON.stringify({ data: JSON.stringify({
tool: { tool: {
id: parseAnswerResult.id, id: toolJson.id,
toolName: toolNode.name, toolName: toolNode.name,
toolAvatar: toolNode.avatar, toolAvatar: toolNode.avatar,
functionName: parseAnswerResult.name, functionName: toolJson.name,
params: parseAnswerResult.arguments, params: toolJson.arguments,
response: '' response: ''
} }
}) })
@@ -211,7 +220,7 @@ export const runToolWithPromptCall = async (
event: SseResponseEventEnum.toolResponse, event: SseResponseEventEnum.toolResponse,
data: JSON.stringify({ data: JSON.stringify({
tool: { tool: {
id: parseAnswerResult.id, id: toolJson.id,
toolName: '', toolName: '',
toolAvatar: '', toolAvatar: '',
params: '', params: '',
@@ -237,7 +246,7 @@ export const runToolWithPromptCall = async (
// 合并工具调用的结果,使用 functionCall 格式存储。 // 合并工具调用的结果,使用 functionCall 格式存储。
const assistantToolMsgParams: ChatCompletionAssistantMessageParam = { const assistantToolMsgParams: ChatCompletionAssistantMessageParam = {
role: ChatCompletionRequestMessageRoleEnum.Assistant, role: ChatCompletionRequestMessageRoleEnum.Assistant,
function_call: parseAnswerResult function_call: toolJson
}; };
const concatToolMessages = [ const concatToolMessages = [
...filterMessages, ...filterMessages,
@@ -248,7 +257,7 @@ export const runToolWithPromptCall = async (
...concatToolMessages, ...concatToolMessages,
{ {
role: ChatCompletionRequestMessageRoleEnum.Function, role: ChatCompletionRequestMessageRoleEnum.Function,
name: parseAnswerResult.name, name: toolJson.name,
content: toolsRunResponse.toolResponsePrompt content: toolsRunResponse.toolResponsePrompt
} }
]; ];
@@ -266,7 +275,7 @@ export const runToolWithPromptCall = async (
: [toolsRunResponse.moduleRunResponse]; : [toolsRunResponse.moduleRunResponse];
// get the next user prompt // get the next user prompt
lastMessage.content += `${answer} lastMessage.content += `${replaceAnswer}
TOOL_RESPONSE: """ TOOL_RESPONSE: """
${toolsRunResponse.toolResponsePrompt} ${toolsRunResponse.toolResponsePrompt}
""" """
@@ -362,24 +371,37 @@ async function streamResponse({
return { answer: textAnswer.trim() }; return { answer: textAnswer.trim() };
} }
const parseAnswer = (str: string): FunctionCallCompletion | string => { const parseAnswer = (
// 首先使用正则表达式提取TOOL_ID和TOOL_ARGUMENTS str: string
const prefix = '1:'; ): {
answer: string;
toolJson?: FunctionCallCompletion;
} => {
str = str.trim(); str = str.trim();
if (str.startsWith(prefix)) { // 首先使用正则表达式提取TOOL_ID和TOOL_ARGUMENTS
const toolString = str.substring(prefix.length).trim(); const prefixReg = /^1(:|)/;
if (prefixReg.test(str)) {
const toolString = sliceJsonStr(str);
try { try {
const toolCall = json5.parse(toolString); const toolCall = json5.parse(toolString);
return { return {
answer: `1: ${toolString}`,
toolJson: {
id: getNanoid(), id: getNanoid(),
name: toolCall.toolId, name: toolCall.toolId,
arguments: JSON.stringify(toolCall.arguments || toolCall.parameters) arguments: JSON.stringify(toolCall.arguments || toolCall.parameters)
}
}; };
} catch (error) { } catch (error) {
return str; return {
answer: ERROR_TEXT
};
} }
} else { } else {
return str; return {
answer: str
};
} }
}; };