Test parse cite and add tool call parallel (#4737)
* add quote response filter (#4727) * chatting * add quote response filter * add test * remove comment * perf: cite hidden * perf: format llm response * feat: comment * update default chunk size * update default chunk size --------- Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
parent
683ab6c17d
commit
fdd4e9edbd
@ -10,7 +10,9 @@ weight: 792
|
|||||||
|
|
||||||
## 🚀 新增内容
|
## 🚀 新增内容
|
||||||
|
|
||||||
1. qwen3 模型预设
|
1. 支持 Toolcalls 并行执行。
|
||||||
|
2. 将所有内置任务,从非 stream 模式调整成 stream 模式,避免部分模型不支持非 stream 模式。如需覆盖,则可以在模型`额外 Body`参数中,强制指定`stream=false`。
|
||||||
|
3. qwen3 模型预设
|
||||||
|
|
||||||
## ⚙️ 优化
|
## ⚙️ 优化
|
||||||
|
|
||||||
|
|||||||
@ -563,7 +563,7 @@ HTTP模块中,需要设置 3 个工具参数:
|
|||||||
"hidden"
|
"hidden"
|
||||||
],
|
],
|
||||||
"label": "",
|
"label": "",
|
||||||
"value": 1500,
|
"value": 5000,
|
||||||
"valueType": "number"
|
"valueType": "number"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
18
packages/global/common/string/password.ts
Normal file
18
packages/global/common/string/password.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
export const checkPasswordRule = (password: string) => {
|
||||||
|
const patterns = [
|
||||||
|
/\d/, // Contains digits
|
||||||
|
/[a-z]/, // Contains lowercase letters
|
||||||
|
/[A-Z]/, // Contains uppercase letters
|
||||||
|
/[!@#$%^&*()_+=-]/ // Contains special characters
|
||||||
|
];
|
||||||
|
const validChars = /^[\dA-Za-z!@#$%^&*()_+=-]{6,100}$/;
|
||||||
|
|
||||||
|
// Check length and valid characters
|
||||||
|
if (!validChars.test(password)) return false;
|
||||||
|
|
||||||
|
// Count how many patterns are satisfied
|
||||||
|
const matchCount = patterns.filter((pattern) => pattern.test(password)).length;
|
||||||
|
|
||||||
|
// Must satisfy at least 2 patterns
|
||||||
|
return matchCount >= 2;
|
||||||
|
};
|
||||||
@ -88,8 +88,8 @@ export const Prompt_userQuotePromptList: PromptTemplateItem[] = [
|
|||||||
- 保持答案与 <Reference></Reference> 中描述的一致。
|
- 保持答案与 <Reference></Reference> 中描述的一致。
|
||||||
- 使用 Markdown 语法优化回答格式。
|
- 使用 Markdown 语法优化回答格式。
|
||||||
- 使用与问题相同的语言回答。
|
- 使用与问题相同的语言回答。
|
||||||
- 使用 [id](QUOTE) 格式来引用<Reference></Reference>中的知识,其中 QUOTE 是固定常量, id 为引文中的 id。
|
- 使用 [id](CITE) 格式来引用<Reference></Reference>中的知识,其中 CITE 是固定常量, id 为引文中的 id。
|
||||||
- 在每段结尾自然地整合引用。例如: "FastGPT 是一个基于大语言模型(LLM)的知识库问答系统[67e517e74767063e882d6861](QUOTE)。"
|
- 在每段结尾自然地整合引用。例如: "FastGPT 是一个基于大语言模型(LLM)的知识库问答系统[67e517e74767063e882d6861](CITE)。"
|
||||||
- 每段至少包含一个引用,也可根据内容需要加入多个引用,按顺序排列。`,
|
- 每段至少包含一个引用,也可根据内容需要加入多个引用,按顺序排列。`,
|
||||||
['4.9.2']: `使用 <Reference></Reference> 标记中的内容作为本次对话的参考:
|
['4.9.2']: `使用 <Reference></Reference> 标记中的内容作为本次对话的参考:
|
||||||
|
|
||||||
@ -146,8 +146,8 @@ export const Prompt_userQuotePromptList: PromptTemplateItem[] = [
|
|||||||
- 保持答案与 <Reference></Reference> 中描述的一致。
|
- 保持答案与 <Reference></Reference> 中描述的一致。
|
||||||
- 使用 Markdown 语法优化回答格式。
|
- 使用 Markdown 语法优化回答格式。
|
||||||
- 使用与问题相同的语言回答。
|
- 使用与问题相同的语言回答。
|
||||||
- 使用 [id](QUOTE) 格式来引用<Reference></Reference>中的知识,其中 QUOTE 是固定常量, id 为引文中的 id。
|
- 使用 [id](CITE) 格式来引用<Reference></Reference>中的知识,其中 CITE 是固定常量, id 为引文中的 id。
|
||||||
- 在每段结尾自然地整合引用。例如: "FastGPT 是一个基于大语言模型(LLM)的知识库问答系统[67e517e74767063e882d6861](QUOTE)。"
|
- 在每段结尾自然地整合引用。例如: "FastGPT 是一个基于大语言模型(LLM)的知识库问答系统[67e517e74767063e882d6861](CITE)。"
|
||||||
- 每段至少包含一个引用,也可根据内容需要加入多个引用,按顺序排列。
|
- 每段至少包含一个引用,也可根据内容需要加入多个引用,按顺序排列。
|
||||||
|
|
||||||
问题:"""{{question}}"""`,
|
问题:"""{{question}}"""`,
|
||||||
@ -217,8 +217,8 @@ export const Prompt_systemQuotePromptList: PromptTemplateItem[] = [
|
|||||||
- 保持答案与 <Reference></Reference> 中描述的一致。
|
- 保持答案与 <Reference></Reference> 中描述的一致。
|
||||||
- 使用 Markdown 语法优化回答格式。
|
- 使用 Markdown 语法优化回答格式。
|
||||||
- 使用与问题相同的语言回答。
|
- 使用与问题相同的语言回答。
|
||||||
- 使用 [id](QUOTE) 格式来引用<Reference></Reference>中的知识,其中 QUOTE 是固定常量, id 为引文中的 id。
|
- 使用 [id](CITE) 格式来引用<Reference></Reference>中的知识,其中 CITE 是固定常量, id 为引文中的 id。
|
||||||
- 在每段结尾自然地整合引用。例如: "FastGPT 是一个基于大语言模型(LLM)的知识库问答系统[67e517e74767063e882d6861](QUOTE)。"
|
- 在每段结尾自然地整合引用。例如: "FastGPT 是一个基于大语言模型(LLM)的知识库问答系统[67e517e74767063e882d6861](CITE)。"
|
||||||
- 每段至少包含一个引用,也可根据内容需要加入多个引用,按顺序排列。`,
|
- 每段至少包含一个引用,也可根据内容需要加入多个引用,按顺序排列。`,
|
||||||
['4.9.2']: `使用 <Reference></Reference> 标记中的内容作为本次对话的参考:
|
['4.9.2']: `使用 <Reference></Reference> 标记中的内容作为本次对话的参考:
|
||||||
|
|
||||||
@ -271,8 +271,8 @@ export const Prompt_systemQuotePromptList: PromptTemplateItem[] = [
|
|||||||
- 保持答案与 <Reference></Reference> 中描述的一致。
|
- 保持答案与 <Reference></Reference> 中描述的一致。
|
||||||
- 使用 Markdown 语法优化回答格式。
|
- 使用 Markdown 语法优化回答格式。
|
||||||
- 使用与问题相同的语言回答。
|
- 使用与问题相同的语言回答。
|
||||||
- 使用 [id](QUOTE) 格式来引用<Reference></Reference>中的知识,其中 QUOTE 是固定常量, id 为引文中的 id。
|
- 使用 [id](CITE) 格式来引用<Reference></Reference>中的知识,其中 CITE 是固定常量, id 为引文中的 id。
|
||||||
- 在每段结尾自然地整合引用。例如: "FastGPT 是一个基于大语言模型(LLM)的知识库问答系统[67e517e74767063e882d6861](QUOTE)。"
|
- 在每段结尾自然地整合引用。例如: "FastGPT 是一个基于大语言模型(LLM)的知识库问答系统[67e517e74767063e882d6861](CITE)。"
|
||||||
- 每段至少包含一个引用,也可根据内容需要加入多个引用,按顺序排列。
|
- 每段至少包含一个引用,也可根据内容需要加入多个引用,按顺序排列。
|
||||||
|
|
||||||
问题:"""{{question}}"""`,
|
问题:"""{{question}}"""`,
|
||||||
@ -321,24 +321,13 @@ export const Prompt_systemQuotePromptList: PromptTemplateItem[] = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export const getQuotePrompt = (
|
export const getQuotePrompt = (version?: string, role: 'user' | 'system' = 'user') => {
|
||||||
version?: string,
|
|
||||||
role: 'user' | 'system' = 'user',
|
|
||||||
parseQuote = true
|
|
||||||
) => {
|
|
||||||
const quotePromptTemplates =
|
const quotePromptTemplates =
|
||||||
role === 'user' ? Prompt_userQuotePromptList : Prompt_systemQuotePromptList;
|
role === 'user' ? Prompt_userQuotePromptList : Prompt_systemQuotePromptList;
|
||||||
|
|
||||||
const defaultTemplate = quotePromptTemplates[0].value;
|
const defaultTemplate = quotePromptTemplates[0].value;
|
||||||
|
|
||||||
return parseQuote
|
return getPromptByVersion(version, defaultTemplate);
|
||||||
? getPromptByVersion(version, defaultTemplate)
|
|
||||||
: getPromptByVersion(version, defaultTemplate).replace(
|
|
||||||
`- 使用 [id](QUOTE) 格式来引用<Reference></Reference>中的知识,其中 QUOTE 是固定常量, id 为引文中的 id。
|
|
||||||
- 在每段结尾自然地整合引用。例如: "FastGPT 是一个基于大语言模型(LLM)的知识库问答系统[67e517e74767063e882d6861](QUOTE)。"
|
|
||||||
- 每段至少包含一个引用,也可根据内容需要加入多个引用,按顺序排列。`,
|
|
||||||
''
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Document quote prompt
|
// Document quote prompt
|
||||||
|
|||||||
@ -60,7 +60,7 @@ export const getExtractJsonToolPrompt = (version?: string) => {
|
|||||||
"""
|
"""
|
||||||
- {{description}}
|
- {{description}}
|
||||||
- 不是每个参数都是必须生成的,如果没有合适的参数值,不要生成该参数,或返回空字符串。
|
- 不是每个参数都是必须生成的,如果没有合适的参数值,不要生成该参数,或返回空字符串。
|
||||||
- 需要结合前面的对话内容,一起生成合适的参数。
|
- 需要结合历史记录,一起生成合适的参数。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
本次输入内容: """{{content}}"""
|
本次输入内容: """{{content}}"""
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
export const getDatasetSearchToolResponsePrompt = (parseQuote: boolean) => {
|
export const getDatasetSearchToolResponsePrompt = () => {
|
||||||
return parseQuote
|
return `## Role
|
||||||
? `## Role
|
|
||||||
你是一个知识库回答助手,可以 "quotes" 中的内容作为本次对话的参考。为了使回答结果更加可信并且可追溯,你需要在每段话结尾添加引用标记。
|
你是一个知识库回答助手,可以 "quotes" 中的内容作为本次对话的参考。为了使回答结果更加可信并且可追溯,你需要在每段话结尾添加引用标记。
|
||||||
|
|
||||||
## Rules
|
## Rules
|
||||||
@ -9,16 +8,7 @@ export const getDatasetSearchToolResponsePrompt = (parseQuote: boolean) => {
|
|||||||
- 保持答案与 "quotes" 中描述的一致。
|
- 保持答案与 "quotes" 中描述的一致。
|
||||||
- 使用 Markdown 语法优化回答格式。尤其是图片、表格、序列号等内容,需严格完整输出。
|
- 使用 Markdown 语法优化回答格式。尤其是图片、表格、序列号等内容,需严格完整输出。
|
||||||
- 使用与问题相同的语言回答。
|
- 使用与问题相同的语言回答。
|
||||||
- 使用 [id](QUOTE) 格式来引用 "quotes" 中的知识,其中 QUOTE 是固定常量, id 为引文中的 id。
|
- 使用 [id](CITE) 格式来引用 "quotes" 中的知识,其中 CITE 是固定常量, id 为引文中的 id。
|
||||||
- 在每段话结尾自然地整合引用。例如: "FastGPT 是一个基于大语言模型(LLM)的知识库问答系统[67e517e74767063e882d6861](QUOTE)。"
|
- 在每段话结尾自然地整合引用。例如: "FastGPT 是一个基于大语言模型(LLM)的知识库问答系统[67e517e74767063e882d6861](CITE)。"
|
||||||
- 每段话至少包含一个引用,也可根据内容需要加入多个引用,按顺序排列。`
|
- 每段话至少包含一个引用,也可根据内容需要加入多个引用,按顺序排列。`;
|
||||||
: `## Role
|
|
||||||
你是一个知识库回答助手,可以 "quotes" 中的内容作为本次对话的参考。
|
|
||||||
|
|
||||||
## Rules
|
|
||||||
- 如果你不清楚答案,你需要澄清。
|
|
||||||
- 避免提及你是从 "quotes" 获取的知识。
|
|
||||||
- 保持答案与 "quotes" 中描述的一致。
|
|
||||||
- 使用 Markdown 语法优化回答格式。尤其是图片、表格、序列号等内容,需严格完整输出。
|
|
||||||
- 使用与问题相同的语言回答。`;
|
|
||||||
};
|
};
|
||||||
|
|||||||
1
packages/global/core/ai/type.d.ts
vendored
1
packages/global/core/ai/type.d.ts
vendored
@ -60,6 +60,7 @@ export type ChatCompletionAssistantToolParam = {
|
|||||||
tool_calls: ChatCompletionMessageToolCall[];
|
tool_calls: ChatCompletionMessageToolCall[];
|
||||||
};
|
};
|
||||||
export type ChatCompletionMessageToolCall = ChatCompletionMessageToolCall & {
|
export type ChatCompletionMessageToolCall = ChatCompletionMessageToolCall & {
|
||||||
|
index?: number;
|
||||||
toolName?: string;
|
toolName?: string;
|
||||||
toolAvatar?: string;
|
toolAvatar?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,9 +1,15 @@
|
|||||||
import { DispatchNodeResponseType } from '../workflow/runtime/type';
|
import { DispatchNodeResponseType } from '../workflow/runtime/type';
|
||||||
import { FlowNodeTypeEnum } from '../workflow/node/constant';
|
import { FlowNodeTypeEnum } from '../workflow/node/constant';
|
||||||
import { ChatItemValueTypeEnum, ChatRoleEnum, ChatSourceEnum } from './constants';
|
import { ChatItemValueTypeEnum, ChatRoleEnum, ChatSourceEnum } from './constants';
|
||||||
import { ChatHistoryItemResType, ChatItemType, UserChatItemValueItemType } from './type.d';
|
import {
|
||||||
|
AIChatItemValueItemType,
|
||||||
|
ChatHistoryItemResType,
|
||||||
|
ChatItemType,
|
||||||
|
UserChatItemValueItemType
|
||||||
|
} from './type.d';
|
||||||
import { sliceStrStartEnd } from '../../common/string/tools';
|
import { sliceStrStartEnd } from '../../common/string/tools';
|
||||||
import { PublishChannelEnum } from '../../support/outLink/constant';
|
import { PublishChannelEnum } from '../../support/outLink/constant';
|
||||||
|
import { removeDatasetCiteText } from '../../../service/core/ai/utils';
|
||||||
|
|
||||||
// Concat 2 -> 1, and sort by role
|
// Concat 2 -> 1, and sort by role
|
||||||
export const concatHistories = (histories1: ChatItemType[], histories2: ChatItemType[]) => {
|
export const concatHistories = (histories1: ChatItemType[], histories2: ChatItemType[]) => {
|
||||||
@ -77,6 +83,7 @@ export const getHistoryPreview = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Filter workflow public response
|
||||||
export const filterPublicNodeResponseData = ({
|
export const filterPublicNodeResponseData = ({
|
||||||
flowResponses = [],
|
flowResponses = [],
|
||||||
responseDetail = false
|
responseDetail = false
|
||||||
@ -112,6 +119,40 @@ export const filterPublicNodeResponseData = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Remove dataset cite in ai response
|
||||||
|
export const removeAIResponseCite = <T extends AIChatItemValueItemType[] | string>(
|
||||||
|
value: T,
|
||||||
|
retainCite: boolean
|
||||||
|
): T => {
|
||||||
|
if (retainCite) return value;
|
||||||
|
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return removeDatasetCiteText(value, false) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.map<AIChatItemValueItemType>((item) => {
|
||||||
|
if (item.text?.content) {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
text: {
|
||||||
|
...item.text,
|
||||||
|
content: removeDatasetCiteText(item.text.content, false)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (item.reasoning?.content) {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
reasoning: {
|
||||||
|
...item.reasoning,
|
||||||
|
content: removeDatasetCiteText(item.reasoning.content, false)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}) as T;
|
||||||
|
};
|
||||||
|
|
||||||
export const removeEmptyUserInput = (input?: UserChatItemValueItemType[]) => {
|
export const removeEmptyUserInput = (input?: UserChatItemValueItemType[]) => {
|
||||||
return (
|
return (
|
||||||
input?.filter((item) => {
|
input?.filter((item) => {
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import {
|
|||||||
export const minChunkSize = 64; // min index and chunk size
|
export const minChunkSize = 64; // min index and chunk size
|
||||||
|
|
||||||
// Chunk size
|
// Chunk size
|
||||||
export const chunkAutoChunkSize = 1500;
|
export const chunkAutoChunkSize = 1000;
|
||||||
export const getMaxChunkSize = (model: LLMModelItemType) => {
|
export const getMaxChunkSize = (model: LLMModelItemType) => {
|
||||||
return Math.max(model.maxContext - model.maxResponse, 2000);
|
return Math.max(model.maxContext - model.maxResponse, 2000);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -58,7 +58,7 @@ export type ChatDispatchProps = {
|
|||||||
chatConfig: AppSchema['chatConfig'];
|
chatConfig: AppSchema['chatConfig'];
|
||||||
lastInteractive?: WorkflowInteractiveResponseType; // last interactive response
|
lastInteractive?: WorkflowInteractiveResponseType; // last interactive response
|
||||||
stream: boolean;
|
stream: boolean;
|
||||||
parseQuote?: boolean;
|
retainDatasetCite?: boolean;
|
||||||
maxRunTimes: number;
|
maxRunTimes: number;
|
||||||
isToolCall?: boolean;
|
isToolCall?: boolean;
|
||||||
workflowStreamResponse?: WorkflowResponseType;
|
workflowStreamResponse?: WorkflowResponseType;
|
||||||
|
|||||||
@ -54,7 +54,7 @@ export const DatasetSearchModule: FlowNodeTemplateType = {
|
|||||||
key: NodeInputKeyEnum.datasetMaxTokens,
|
key: NodeInputKeyEnum.datasetMaxTokens,
|
||||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||||
label: '',
|
label: '',
|
||||||
value: 1500,
|
value: 5000,
|
||||||
valueType: WorkflowIOValueTypeEnum.number
|
valueType: WorkflowIOValueTypeEnum.number
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,79 +1,6 @@
|
|||||||
{
|
{
|
||||||
"provider": "Qwen",
|
"provider": "Qwen",
|
||||||
"list": [
|
"list": [
|
||||||
{
|
|
||||||
"model": "qwen-vl-plus",
|
|
||||||
"name": "qwen-vl-plus",
|
|
||||||
"maxContext": 32000,
|
|
||||||
"maxResponse": 2000,
|
|
||||||
"quoteMaxToken": 20000,
|
|
||||||
"maxTemperature": 1.2,
|
|
||||||
"vision": true,
|
|
||||||
"toolChoice": false,
|
|
||||||
"functionCall": false,
|
|
||||||
"defaultSystemChatPrompt": "",
|
|
||||||
"datasetProcess": true,
|
|
||||||
"usedInClassify": true,
|
|
||||||
"customCQPrompt": "",
|
|
||||||
"usedInExtractFields": true,
|
|
||||||
"usedInQueryExtension": true,
|
|
||||||
"customExtractPrompt": "",
|
|
||||||
"usedInToolCall": true,
|
|
||||||
"type": "llm",
|
|
||||||
"showTopP": true,
|
|
||||||
"showStopSign": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "qwen-plus",
|
|
||||||
"name": "Qwen-plus",
|
|
||||||
"maxContext": 64000,
|
|
||||||
"maxResponse": 8000,
|
|
||||||
"quoteMaxToken": 60000,
|
|
||||||
"maxTemperature": 1,
|
|
||||||
"vision": false,
|
|
||||||
"toolChoice": true,
|
|
||||||
"functionCall": false,
|
|
||||||
"defaultSystemChatPrompt": "",
|
|
||||||
"datasetProcess": true,
|
|
||||||
"usedInClassify": true,
|
|
||||||
"customCQPrompt": "",
|
|
||||||
"usedInExtractFields": true,
|
|
||||||
"usedInQueryExtension": true,
|
|
||||||
"customExtractPrompt": "",
|
|
||||||
"usedInToolCall": true,
|
|
||||||
"defaultConfig": {},
|
|
||||||
"fieldMap": {},
|
|
||||||
"type": "llm",
|
|
||||||
"showTopP": true,
|
|
||||||
"showStopSign": true,
|
|
||||||
"responseFormatList": ["text", "json_object"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "qwen-turbo",
|
|
||||||
"name": "Qwen-turbo",
|
|
||||||
"maxContext": 128000,
|
|
||||||
"maxResponse": 8000,
|
|
||||||
"quoteMaxToken": 100000,
|
|
||||||
"maxTemperature": 1,
|
|
||||||
"vision": false,
|
|
||||||
"toolChoice": true,
|
|
||||||
"functionCall": false,
|
|
||||||
"defaultSystemChatPrompt": "",
|
|
||||||
"datasetProcess": true,
|
|
||||||
"usedInClassify": true,
|
|
||||||
"customCQPrompt": "",
|
|
||||||
"usedInExtractFields": true,
|
|
||||||
"usedInQueryExtension": true,
|
|
||||||
"customExtractPrompt": "",
|
|
||||||
"usedInToolCall": true,
|
|
||||||
"defaultConfig": {},
|
|
||||||
"fieldMap": {},
|
|
||||||
"type": "llm",
|
|
||||||
"showTopP": true,
|
|
||||||
"showStopSign": true,
|
|
||||||
"responseFormatList": ["text", "json_object"]
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"model": "qwen-max",
|
"model": "qwen-max",
|
||||||
"name": "Qwen-max",
|
"name": "Qwen-max",
|
||||||
@ -123,6 +50,78 @@
|
|||||||
"showTopP": true,
|
"showTopP": true,
|
||||||
"showStopSign": true
|
"showStopSign": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"model": "qwen-plus",
|
||||||
|
"name": "Qwen-plus",
|
||||||
|
"maxContext": 64000,
|
||||||
|
"maxResponse": 8000,
|
||||||
|
"quoteMaxToken": 60000,
|
||||||
|
"maxTemperature": 1,
|
||||||
|
"vision": false,
|
||||||
|
"toolChoice": true,
|
||||||
|
"functionCall": false,
|
||||||
|
"defaultSystemChatPrompt": "",
|
||||||
|
"datasetProcess": true,
|
||||||
|
"usedInClassify": true,
|
||||||
|
"customCQPrompt": "",
|
||||||
|
"usedInExtractFields": true,
|
||||||
|
"usedInQueryExtension": true,
|
||||||
|
"customExtractPrompt": "",
|
||||||
|
"usedInToolCall": true,
|
||||||
|
"defaultConfig": {},
|
||||||
|
"fieldMap": {},
|
||||||
|
"type": "llm",
|
||||||
|
"showTopP": true,
|
||||||
|
"showStopSign": true,
|
||||||
|
"responseFormatList": ["text", "json_object"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "qwen-vl-plus",
|
||||||
|
"name": "qwen-vl-plus",
|
||||||
|
"maxContext": 32000,
|
||||||
|
"maxResponse": 2000,
|
||||||
|
"quoteMaxToken": 20000,
|
||||||
|
"maxTemperature": 1.2,
|
||||||
|
"vision": true,
|
||||||
|
"toolChoice": false,
|
||||||
|
"functionCall": false,
|
||||||
|
"defaultSystemChatPrompt": "",
|
||||||
|
"datasetProcess": true,
|
||||||
|
"usedInClassify": true,
|
||||||
|
"customCQPrompt": "",
|
||||||
|
"usedInExtractFields": true,
|
||||||
|
"usedInQueryExtension": true,
|
||||||
|
"customExtractPrompt": "",
|
||||||
|
"usedInToolCall": true,
|
||||||
|
"type": "llm",
|
||||||
|
"showTopP": true,
|
||||||
|
"showStopSign": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "qwen-turbo",
|
||||||
|
"name": "Qwen-turbo",
|
||||||
|
"maxContext": 128000,
|
||||||
|
"maxResponse": 8000,
|
||||||
|
"quoteMaxToken": 100000,
|
||||||
|
"maxTemperature": 1,
|
||||||
|
"vision": false,
|
||||||
|
"toolChoice": true,
|
||||||
|
"functionCall": false,
|
||||||
|
"defaultSystemChatPrompt": "",
|
||||||
|
"datasetProcess": true,
|
||||||
|
"usedInClassify": true,
|
||||||
|
"customCQPrompt": "",
|
||||||
|
"usedInExtractFields": true,
|
||||||
|
"usedInQueryExtension": true,
|
||||||
|
"customExtractPrompt": "",
|
||||||
|
"usedInToolCall": true,
|
||||||
|
"defaultConfig": {},
|
||||||
|
"fieldMap": {},
|
||||||
|
"type": "llm",
|
||||||
|
"showTopP": true,
|
||||||
|
"showStopSign": true,
|
||||||
|
"responseFormatList": ["text", "json_object"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"model": "qwen3-235b-a22b",
|
"model": "qwen3-235b-a22b",
|
||||||
"name": "qwen3-235b-a22b",
|
"name": "qwen3-235b-a22b",
|
||||||
@ -142,7 +141,9 @@
|
|||||||
"usedInQueryExtension": true,
|
"usedInQueryExtension": true,
|
||||||
"customExtractPrompt": "",
|
"customExtractPrompt": "",
|
||||||
"usedInToolCall": true,
|
"usedInToolCall": true,
|
||||||
"defaultConfig": {},
|
"defaultConfig": {
|
||||||
|
"stream": true
|
||||||
|
},
|
||||||
"fieldMap": {},
|
"fieldMap": {},
|
||||||
"type": "llm",
|
"type": "llm",
|
||||||
"showTopP": true,
|
"showTopP": true,
|
||||||
@ -168,7 +169,9 @@
|
|||||||
"usedInQueryExtension": true,
|
"usedInQueryExtension": true,
|
||||||
"customExtractPrompt": "",
|
"customExtractPrompt": "",
|
||||||
"usedInToolCall": true,
|
"usedInToolCall": true,
|
||||||
"defaultConfig": {},
|
"defaultConfig": {
|
||||||
|
"stream": true
|
||||||
|
},
|
||||||
"fieldMap": {},
|
"fieldMap": {},
|
||||||
"type": "llm",
|
"type": "llm",
|
||||||
"showTopP": true,
|
"showTopP": true,
|
||||||
@ -194,7 +197,9 @@
|
|||||||
"usedInQueryExtension": true,
|
"usedInQueryExtension": true,
|
||||||
"customExtractPrompt": "",
|
"customExtractPrompt": "",
|
||||||
"usedInToolCall": true,
|
"usedInToolCall": true,
|
||||||
"defaultConfig": {},
|
"defaultConfig": {
|
||||||
|
"stream": true
|
||||||
|
},
|
||||||
"fieldMap": {},
|
"fieldMap": {},
|
||||||
"type": "llm",
|
"type": "llm",
|
||||||
"showTopP": true,
|
"showTopP": true,
|
||||||
@ -220,7 +225,9 @@
|
|||||||
"usedInQueryExtension": true,
|
"usedInQueryExtension": true,
|
||||||
"customExtractPrompt": "",
|
"customExtractPrompt": "",
|
||||||
"usedInToolCall": true,
|
"usedInToolCall": true,
|
||||||
"defaultConfig": {},
|
"defaultConfig": {
|
||||||
|
"stream": true
|
||||||
|
},
|
||||||
"fieldMap": {},
|
"fieldMap": {},
|
||||||
"type": "llm",
|
"type": "llm",
|
||||||
"showTopP": true,
|
"showTopP": true,
|
||||||
@ -246,7 +253,9 @@
|
|||||||
"usedInQueryExtension": true,
|
"usedInQueryExtension": true,
|
||||||
"customExtractPrompt": "",
|
"customExtractPrompt": "",
|
||||||
"usedInToolCall": true,
|
"usedInToolCall": true,
|
||||||
"defaultConfig": {},
|
"defaultConfig": {
|
||||||
|
"stream": true
|
||||||
|
},
|
||||||
"fieldMap": {},
|
"fieldMap": {},
|
||||||
"type": "llm",
|
"type": "llm",
|
||||||
"showTopP": true,
|
"showTopP": true,
|
||||||
@ -272,7 +281,9 @@
|
|||||||
"usedInQueryExtension": true,
|
"usedInQueryExtension": true,
|
||||||
"customExtractPrompt": "",
|
"customExtractPrompt": "",
|
||||||
"usedInToolCall": true,
|
"usedInToolCall": true,
|
||||||
"defaultConfig": {},
|
"defaultConfig": {
|
||||||
|
"stream": true
|
||||||
|
},
|
||||||
"fieldMap": {},
|
"fieldMap": {},
|
||||||
"type": "llm",
|
"type": "llm",
|
||||||
"showTopP": true,
|
"showTopP": true,
|
||||||
@ -298,7 +309,9 @@
|
|||||||
"usedInQueryExtension": true,
|
"usedInQueryExtension": true,
|
||||||
"customExtractPrompt": "",
|
"customExtractPrompt": "",
|
||||||
"usedInToolCall": true,
|
"usedInToolCall": true,
|
||||||
"defaultConfig": {},
|
"defaultConfig": {
|
||||||
|
"stream": true
|
||||||
|
},
|
||||||
"fieldMap": {},
|
"fieldMap": {},
|
||||||
"type": "llm",
|
"type": "llm",
|
||||||
"showTopP": true,
|
"showTopP": true,
|
||||||
@ -324,7 +337,9 @@
|
|||||||
"usedInQueryExtension": true,
|
"usedInQueryExtension": true,
|
||||||
"customExtractPrompt": "",
|
"customExtractPrompt": "",
|
||||||
"usedInToolCall": true,
|
"usedInToolCall": true,
|
||||||
"defaultConfig": {},
|
"defaultConfig": {
|
||||||
|
"stream": true
|
||||||
|
},
|
||||||
"fieldMap": {},
|
"fieldMap": {},
|
||||||
"type": "llm",
|
"type": "llm",
|
||||||
"showTopP": true,
|
"showTopP": true,
|
||||||
@ -350,7 +365,9 @@
|
|||||||
"usedInQueryExtension": false,
|
"usedInQueryExtension": false,
|
||||||
"customExtractPrompt": "",
|
"customExtractPrompt": "",
|
||||||
"usedInToolCall": true,
|
"usedInToolCall": true,
|
||||||
"defaultConfig": {},
|
"defaultConfig": {
|
||||||
|
"stream": true
|
||||||
|
},
|
||||||
"fieldMap": {},
|
"fieldMap": {},
|
||||||
"type": "llm",
|
"type": "llm",
|
||||||
"showTopP": false,
|
"showTopP": false,
|
||||||
@ -375,7 +392,9 @@
|
|||||||
"usedInQueryExtension": false,
|
"usedInQueryExtension": false,
|
||||||
"customExtractPrompt": "",
|
"customExtractPrompt": "",
|
||||||
"usedInToolCall": true,
|
"usedInToolCall": true,
|
||||||
"defaultConfig": {},
|
"defaultConfig": {
|
||||||
|
"stream": true
|
||||||
|
},
|
||||||
"fieldMap": {},
|
"fieldMap": {},
|
||||||
"type": "llm",
|
"type": "llm",
|
||||||
"showTopP": false,
|
"showTopP": false,
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import type { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type.d'
|
|||||||
import { createChatCompletion } from '../config';
|
import { createChatCompletion } from '../config';
|
||||||
import { countGptMessagesTokens, countPromptTokens } from '../../../common/string/tiktoken/index';
|
import { countGptMessagesTokens, countPromptTokens } from '../../../common/string/tiktoken/index';
|
||||||
import { loadRequestMessages } from '../../chat/utils';
|
import { loadRequestMessages } from '../../chat/utils';
|
||||||
import { llmCompletionsBodyFormat, llmResponseToAnswerText } from '../utils';
|
import { llmCompletionsBodyFormat, formatLLMResponse } from '../utils';
|
||||||
import {
|
import {
|
||||||
QuestionGuidePrompt,
|
QuestionGuidePrompt,
|
||||||
QuestionGuideFooterPrompt
|
QuestionGuideFooterPrompt
|
||||||
@ -42,12 +42,12 @@ export async function createQuestionGuide({
|
|||||||
temperature: 0.1,
|
temperature: 0.1,
|
||||||
max_tokens: 200,
|
max_tokens: 200,
|
||||||
messages: requestMessages,
|
messages: requestMessages,
|
||||||
stream: false
|
stream: true
|
||||||
},
|
},
|
||||||
model
|
model
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
const { text: answer, usage } = await llmResponseToAnswerText(response);
|
const { text: answer, usage } = await formatLLMResponse(response);
|
||||||
|
|
||||||
const start = answer.indexOf('[');
|
const start = answer.indexOf('[');
|
||||||
const end = answer.lastIndexOf(']');
|
const end = answer.lastIndexOf(']');
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { ChatItemType } from '@fastgpt/global/core/chat/type';
|
|||||||
import { countGptMessagesTokens, countPromptTokens } from '../../../common/string/tiktoken/index';
|
import { countGptMessagesTokens, countPromptTokens } from '../../../common/string/tiktoken/index';
|
||||||
import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
|
import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
|
||||||
import { getLLMModel } from '../model';
|
import { getLLMModel } from '../model';
|
||||||
import { llmCompletionsBodyFormat, llmResponseToAnswerText } from '../utils';
|
import { llmCompletionsBodyFormat, formatLLMResponse } from '../utils';
|
||||||
import { addLog } from '../../../common/system/log';
|
import { addLog } from '../../../common/system/log';
|
||||||
import { filterGPTMessageByMaxContext } from '../../chat/utils';
|
import { filterGPTMessageByMaxContext } from '../../chat/utils';
|
||||||
import json5 from 'json5';
|
import json5 from 'json5';
|
||||||
@ -170,7 +170,7 @@ assistant: ${chatBg}
|
|||||||
const { response } = await createChatCompletion({
|
const { response } = await createChatCompletion({
|
||||||
body: llmCompletionsBodyFormat(
|
body: llmCompletionsBodyFormat(
|
||||||
{
|
{
|
||||||
stream: false,
|
stream: true,
|
||||||
model: modelData.model,
|
model: modelData.model,
|
||||||
temperature: 0.1,
|
temperature: 0.1,
|
||||||
messages
|
messages
|
||||||
@ -178,7 +178,7 @@ assistant: ${chatBg}
|
|||||||
modelData
|
modelData
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
const { text: answer, usage } = await llmResponseToAnswerText(response);
|
const { text: answer, usage } = await formatLLMResponse(response);
|
||||||
const inputTokens = usage?.prompt_tokens || (await countGptMessagesTokens(messages));
|
const inputTokens = usage?.prompt_tokens || (await countGptMessagesTokens(messages));
|
||||||
const outputTokens = usage?.completion_tokens || (await countPromptTokens(answer));
|
const outputTokens = usage?.completion_tokens || (await countPromptTokens(answer));
|
||||||
|
|
||||||
|
|||||||
@ -1,145 +0,0 @@
|
|||||||
import { parseReasoningStreamContent } from './utils';
|
|
||||||
import { expect, test } from 'vitest';
|
|
||||||
|
|
||||||
test('Parse reasoning stream content test', async () => {
|
|
||||||
const partList = [
|
|
||||||
{
|
|
||||||
data: [{ content: '你好1' }, { content: '你好2' }, { content: '你好3' }],
|
|
||||||
correct: { answer: '你好1你好2你好3', reasoning: '' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
data: [
|
|
||||||
{ reasoning_content: '这是' },
|
|
||||||
{ reasoning_content: '思考' },
|
|
||||||
{ reasoning_content: '过程' },
|
|
||||||
{ content: '你好1' },
|
|
||||||
{ content: '你好2' },
|
|
||||||
{ content: '你好3' }
|
|
||||||
],
|
|
||||||
correct: { answer: '你好1你好2你好3', reasoning: '这是思考过程' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
data: [
|
|
||||||
{ content: '<t' },
|
|
||||||
{ content: 'hink>' },
|
|
||||||
{ content: '这是' },
|
|
||||||
{ content: '思考' },
|
|
||||||
{ content: '过程' },
|
|
||||||
{ content: '</think>' },
|
|
||||||
{ content: '你好1' },
|
|
||||||
{ content: '你好2' },
|
|
||||||
{ content: '你好3' }
|
|
||||||
],
|
|
||||||
correct: { answer: '你好1你好2你好3', reasoning: '这是思考过程' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
data: [
|
|
||||||
{ content: '<think>' },
|
|
||||||
{ content: '这是' },
|
|
||||||
{ content: '思考' },
|
|
||||||
{ content: '过程' },
|
|
||||||
{ content: '</think>' },
|
|
||||||
{ content: '你好1' },
|
|
||||||
{ content: '你好2' },
|
|
||||||
{ content: '你好3' }
|
|
||||||
],
|
|
||||||
correct: { answer: '你好1你好2你好3', reasoning: '这是思考过程' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
data: [
|
|
||||||
{ content: '<think>这是' },
|
|
||||||
{ content: '思考' },
|
|
||||||
{ content: '过程' },
|
|
||||||
{ content: '</think>' },
|
|
||||||
{ content: '你好1' },
|
|
||||||
{ content: '你好2' },
|
|
||||||
{ content: '你好3' }
|
|
||||||
],
|
|
||||||
correct: { answer: '你好1你好2你好3', reasoning: '这是思考过程' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
data: [
|
|
||||||
{ content: '<think>这是' },
|
|
||||||
{ content: '思考' },
|
|
||||||
{ content: '过程</' },
|
|
||||||
{ content: 'think>' },
|
|
||||||
{ content: '你好1' },
|
|
||||||
{ content: '你好2' },
|
|
||||||
{ content: '你好3' }
|
|
||||||
],
|
|
||||||
correct: { answer: '你好1你好2你好3', reasoning: '这是思考过程' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
data: [
|
|
||||||
{ content: '<think>这是' },
|
|
||||||
{ content: '思考' },
|
|
||||||
{ content: '过程</think>' },
|
|
||||||
{ content: '你好1' },
|
|
||||||
{ content: '你好2' },
|
|
||||||
{ content: '你好3' }
|
|
||||||
],
|
|
||||||
correct: { answer: '你好1你好2你好3', reasoning: '这是思考过程' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
data: [
|
|
||||||
{ content: '<think>这是' },
|
|
||||||
{ content: '思考' },
|
|
||||||
{ content: '过程</think>你好1' },
|
|
||||||
{ content: '你好2' },
|
|
||||||
{ content: '你好3' }
|
|
||||||
],
|
|
||||||
correct: { answer: '你好1你好2你好3', reasoning: '这是思考过程' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
data: [
|
|
||||||
{ content: '<think>这是' },
|
|
||||||
{ content: '思考' },
|
|
||||||
{ content: '过程</th' },
|
|
||||||
{ content: '假的' },
|
|
||||||
{ content: '你好2' },
|
|
||||||
{ content: '你好3' },
|
|
||||||
{ content: '过程</think>你好1' },
|
|
||||||
{ content: '你好2' },
|
|
||||||
{ content: '你好3' }
|
|
||||||
],
|
|
||||||
correct: { answer: '你好1你好2你好3', reasoning: '这是思考过程</th假的你好2你好3过程' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
data: [
|
|
||||||
{ content: '<think>这是' },
|
|
||||||
{ content: '思考' },
|
|
||||||
{ content: '过程</th' },
|
|
||||||
{ content: '假的' },
|
|
||||||
{ content: '你好2' },
|
|
||||||
{ content: '你好3' }
|
|
||||||
],
|
|
||||||
correct: { answer: '', reasoning: '这是思考过程</th假的你好2你好3' }
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
partList.forEach((part) => {
|
|
||||||
const { parsePart } = parseReasoningStreamContent();
|
|
||||||
|
|
||||||
let answer = '';
|
|
||||||
let reasoning = '';
|
|
||||||
part.data.forEach((item) => {
|
|
||||||
const formatPart = {
|
|
||||||
choices: [
|
|
||||||
{
|
|
||||||
delta: {
|
|
||||||
role: 'assistant',
|
|
||||||
content: item.content,
|
|
||||||
reasoning_content: item.reasoning_content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
const [reasoningContent, content] = parsePart(formatPart, true);
|
|
||||||
answer += content;
|
|
||||||
reasoning += reasoningContent;
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(answer).toBe(part.correct.answer);
|
|
||||||
expect(reasoning).toBe(part.correct.reasoning);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -5,10 +5,12 @@ import {
|
|||||||
CompletionFinishReason,
|
CompletionFinishReason,
|
||||||
StreamChatType,
|
StreamChatType,
|
||||||
UnStreamChatType,
|
UnStreamChatType,
|
||||||
CompletionUsage
|
CompletionUsage,
|
||||||
|
ChatCompletionMessageToolCall
|
||||||
} from '@fastgpt/global/core/ai/type';
|
} from '@fastgpt/global/core/ai/type';
|
||||||
import { getLLMModel } from './model';
|
import { getLLMModel } from './model';
|
||||||
import { getLLMDefaultUsage } from '@fastgpt/global/core/ai/constants';
|
import { getLLMDefaultUsage } from '@fastgpt/global/core/ai/constants';
|
||||||
|
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Count response max token
|
Count response max token
|
||||||
@ -105,33 +107,84 @@ export const llmStreamResponseToAnswerText = async (
|
|||||||
): Promise<{
|
): Promise<{
|
||||||
text: string;
|
text: string;
|
||||||
usage?: CompletionUsage;
|
usage?: CompletionUsage;
|
||||||
|
toolCalls?: ChatCompletionMessageToolCall[];
|
||||||
}> => {
|
}> => {
|
||||||
let answer = '';
|
let answer = '';
|
||||||
let usage = getLLMDefaultUsage();
|
let usage = getLLMDefaultUsage();
|
||||||
|
let toolCalls: ChatCompletionMessageToolCall[] = [];
|
||||||
|
let callingTool: { name: string; arguments: string } | null = null;
|
||||||
|
|
||||||
for await (const part of response) {
|
for await (const part of response) {
|
||||||
usage = part.usage || usage;
|
usage = part.usage || usage;
|
||||||
|
const responseChoice = part.choices?.[0]?.delta;
|
||||||
|
|
||||||
const content = part.choices?.[0]?.delta?.content || '';
|
const content = responseChoice?.content || '';
|
||||||
answer += content;
|
answer += content;
|
||||||
|
|
||||||
|
// Tool calls
|
||||||
|
if (responseChoice?.tool_calls?.length) {
|
||||||
|
responseChoice.tool_calls.forEach((toolCall) => {
|
||||||
|
const index = toolCall.index;
|
||||||
|
|
||||||
|
if (toolCall.id || callingTool) {
|
||||||
|
// 有 id,代表新 call 工具
|
||||||
|
if (toolCall.id) {
|
||||||
|
callingTool = {
|
||||||
|
name: toolCall.function?.name || '',
|
||||||
|
arguments: toolCall.function?.arguments || ''
|
||||||
|
};
|
||||||
|
} else if (callingTool) {
|
||||||
|
// Continue call(Perhaps the name of the previous function was incomplete)
|
||||||
|
callingTool.name += toolCall.function?.name || '';
|
||||||
|
callingTool.arguments += toolCall.function?.arguments || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!callingTool) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// New tool, add to list.
|
||||||
|
const toolId = getNanoid();
|
||||||
|
toolCalls[index] = {
|
||||||
|
...toolCall,
|
||||||
|
id: toolId,
|
||||||
|
type: 'function',
|
||||||
|
function: callingTool
|
||||||
|
};
|
||||||
|
callingTool = null;
|
||||||
|
} else {
|
||||||
|
/* arg 追加到当前工具的参数里 */
|
||||||
|
const arg: string = toolCall?.function?.arguments ?? '';
|
||||||
|
const currentTool = toolCalls[index];
|
||||||
|
if (currentTool && arg) {
|
||||||
|
currentTool.function.arguments += arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
text: parseReasoningContent(answer)[1],
|
text: parseReasoningContent(answer)[1],
|
||||||
usage
|
usage,
|
||||||
|
toolCalls
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export const llmUnStreamResponseToAnswerText = async (
|
export const llmUnStreamResponseToAnswerText = async (
|
||||||
response: UnStreamChatType
|
response: UnStreamChatType
|
||||||
): Promise<{
|
): Promise<{
|
||||||
text: string;
|
text: string;
|
||||||
|
toolCalls?: ChatCompletionMessageToolCall[];
|
||||||
usage?: CompletionUsage;
|
usage?: CompletionUsage;
|
||||||
}> => {
|
}> => {
|
||||||
const answer = response.choices?.[0]?.message?.content || '';
|
const answer = response.choices?.[0]?.message?.content || '';
|
||||||
|
const toolCalls = response.choices?.[0]?.message?.tool_calls;
|
||||||
return {
|
return {
|
||||||
text: answer,
|
text: answer,
|
||||||
usage: response.usage
|
usage: response.usage,
|
||||||
|
toolCalls
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export const llmResponseToAnswerText = async (response: StreamChatType | UnStreamChatType) => {
|
export const formatLLMResponse = async (response: StreamChatType | UnStreamChatType) => {
|
||||||
if ('iterator' in response) {
|
if ('iterator' in response) {
|
||||||
return llmStreamResponseToAnswerText(response);
|
return llmStreamResponseToAnswerText(response);
|
||||||
}
|
}
|
||||||
@ -155,20 +208,31 @@ export const parseReasoningContent = (text: string): [string, string] => {
|
|||||||
return [thinkContent, answerContent];
|
return [thinkContent, answerContent];
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse <think></think> tags to think and answer - stream response
|
export const removeDatasetCiteText = (text: string, retainDatasetCite: boolean) => {
|
||||||
export const parseReasoningStreamContent = () => {
|
return retainDatasetCite ? text : text.replace(/\[([a-f0-9]{24})\]\(CITE\)/g, '');
|
||||||
let isInThinkTag: boolean | undefined;
|
};
|
||||||
|
|
||||||
const startTag = '<think>';
|
// Parse llm stream part
|
||||||
|
export const parseLLMStreamResponse = () => {
|
||||||
|
let isInThinkTag: boolean | undefined = undefined;
|
||||||
let startTagBuffer = '';
|
let startTagBuffer = '';
|
||||||
|
|
||||||
const endTag = '</think>';
|
|
||||||
let endTagBuffer = '';
|
let endTagBuffer = '';
|
||||||
|
|
||||||
|
const thinkStartChars = '<think>';
|
||||||
|
const thinkEndChars = '</think>';
|
||||||
|
|
||||||
|
let citeBuffer = '';
|
||||||
|
const maxCiteBufferLength = 32; // [Object](CITE)总长度为32
|
||||||
|
|
||||||
/*
|
/*
|
||||||
parseThinkTag - 只控制是否主动解析 <think></think>,如果接口已经解析了,则不再解析。
|
parseThinkTag - 只控制是否主动解析 <think></think>,如果接口已经解析了,则不再解析。
|
||||||
|
retainDatasetCite -
|
||||||
*/
|
*/
|
||||||
const parsePart = (
|
const parsePart = ({
|
||||||
|
part,
|
||||||
|
parseThinkTag = true,
|
||||||
|
retainDatasetCite = true
|
||||||
|
}: {
|
||||||
part: {
|
part: {
|
||||||
choices: {
|
choices: {
|
||||||
delta: {
|
delta: {
|
||||||
@ -177,147 +241,209 @@ export const parseReasoningStreamContent = () => {
|
|||||||
};
|
};
|
||||||
finish_reason?: CompletionFinishReason;
|
finish_reason?: CompletionFinishReason;
|
||||||
}[];
|
}[];
|
||||||
},
|
};
|
||||||
parseThinkTag = false
|
parseThinkTag?: boolean;
|
||||||
): {
|
retainDatasetCite?: boolean;
|
||||||
|
}): {
|
||||||
reasoningContent: string;
|
reasoningContent: string;
|
||||||
content: string;
|
content: string;
|
||||||
|
responseContent: string;
|
||||||
finishReason: CompletionFinishReason;
|
finishReason: CompletionFinishReason;
|
||||||
} => {
|
} => {
|
||||||
const content = part.choices?.[0]?.delta?.content || '';
|
|
||||||
const finishReason = part.choices?.[0]?.finish_reason || null;
|
const finishReason = part.choices?.[0]?.finish_reason || null;
|
||||||
|
const content = part.choices?.[0]?.delta?.content || '';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const reasoningContent = part.choices?.[0]?.delta?.reasoning_content || '';
|
const reasoningContent = part.choices?.[0]?.delta?.reasoning_content || '';
|
||||||
if (reasoningContent || !parseThinkTag) {
|
const isStreamEnd = !!finishReason;
|
||||||
isInThinkTag = false;
|
|
||||||
return { reasoningContent, content, finishReason };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!content) {
|
// Parse think
|
||||||
return {
|
const { reasoningContent: parsedThinkReasoningContent, content: parsedThinkContent } = (() => {
|
||||||
reasoningContent: '',
|
if (reasoningContent || !parseThinkTag) {
|
||||||
content: '',
|
|
||||||
finishReason
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果不在 think 标签中,或者有 reasoningContent(接口已解析),则返回 reasoningContent 和 content
|
|
||||||
if (isInThinkTag === false) {
|
|
||||||
return {
|
|
||||||
reasoningContent: '',
|
|
||||||
content,
|
|
||||||
finishReason
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检测是否为 think 标签开头的数据
|
|
||||||
if (isInThinkTag === undefined) {
|
|
||||||
// Parse content think and answer
|
|
||||||
startTagBuffer += content;
|
|
||||||
// 太少内容时候,暂时不解析
|
|
||||||
if (startTagBuffer.length < startTag.length) {
|
|
||||||
return {
|
|
||||||
reasoningContent: '',
|
|
||||||
content: '',
|
|
||||||
finishReason
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startTagBuffer.startsWith(startTag)) {
|
|
||||||
isInThinkTag = true;
|
|
||||||
return {
|
|
||||||
reasoningContent: startTagBuffer.slice(startTag.length),
|
|
||||||
content: '',
|
|
||||||
finishReason
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果未命中 think 标签,则认为不在 think 标签中,返回 buffer 内容作为 content
|
|
||||||
isInThinkTag = false;
|
|
||||||
return {
|
|
||||||
reasoningContent: '',
|
|
||||||
content: startTagBuffer,
|
|
||||||
finishReason
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确认是 think 标签内容,开始返回 think 内容,并实时检测 </think>
|
|
||||||
/*
|
|
||||||
检测 </think> 方案。
|
|
||||||
存储所有疑似 </think> 的内容,直到检测到完整的 </think> 标签或超出 </think> 长度。
|
|
||||||
content 返回值包含以下几种情况:
|
|
||||||
abc - 完全未命中尾标签
|
|
||||||
abc<th - 命中一部分尾标签
|
|
||||||
abc</think> - 完全命中尾标签
|
|
||||||
abc</think>abc - 完全命中尾标签
|
|
||||||
</think>abc - 完全命中尾标签
|
|
||||||
k>abc - 命中一部分尾标签
|
|
||||||
*/
|
|
||||||
// endTagBuffer 专门用来记录疑似尾标签的内容
|
|
||||||
if (endTagBuffer) {
|
|
||||||
endTagBuffer += content;
|
|
||||||
if (endTagBuffer.includes(endTag)) {
|
|
||||||
isInThinkTag = false;
|
isInThinkTag = false;
|
||||||
const answer = endTagBuffer.slice(endTag.length);
|
return { reasoningContent, content };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!content) {
|
||||||
return {
|
return {
|
||||||
reasoningContent: '',
|
reasoningContent: '',
|
||||||
content: answer,
|
content: ''
|
||||||
finishReason
|
|
||||||
};
|
|
||||||
} else if (endTagBuffer.length >= endTag.length) {
|
|
||||||
// 缓存内容超出尾标签长度,且仍未命中 </think>,则认为本次猜测 </think> 失败,仍处于 think 阶段。
|
|
||||||
const tmp = endTagBuffer;
|
|
||||||
endTagBuffer = '';
|
|
||||||
return {
|
|
||||||
reasoningContent: tmp,
|
|
||||||
content: '',
|
|
||||||
finishReason
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
reasoningContent: '',
|
// 如果不在 think 标签中,或者有 reasoningContent(接口已解析),则返回 reasoningContent 和 content
|
||||||
content: '',
|
if (isInThinkTag === false) {
|
||||||
finishReason
|
return {
|
||||||
};
|
reasoningContent: '',
|
||||||
} else if (content.includes(endTag)) {
|
content
|
||||||
// 返回内容,完整命中</think>,直接结束
|
};
|
||||||
isInThinkTag = false;
|
}
|
||||||
const [think, answer] = content.split(endTag);
|
|
||||||
return {
|
// 检测是否为 think 标签开头的数据
|
||||||
reasoningContent: think,
|
if (isInThinkTag === undefined) {
|
||||||
content: answer,
|
// Parse content think and answer
|
||||||
finishReason
|
startTagBuffer += content;
|
||||||
};
|
// 太少内容时候,暂时不解析
|
||||||
} else {
|
if (startTagBuffer.length < thinkStartChars.length) {
|
||||||
// 无 buffer,且未命中 </think>,开始疑似 </think> 检测。
|
if (isStreamEnd) {
|
||||||
for (let i = 1; i < endTag.length; i++) {
|
const tmpContent = startTagBuffer;
|
||||||
const partialEndTag = endTag.slice(0, i);
|
startTagBuffer = '';
|
||||||
// 命中一部分尾标签
|
return {
|
||||||
if (content.endsWith(partialEndTag)) {
|
reasoningContent: '',
|
||||||
const think = content.slice(0, -partialEndTag.length);
|
content: tmpContent
|
||||||
endTagBuffer += partialEndTag;
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
reasoningContent: think,
|
reasoningContent: '',
|
||||||
content: '',
|
content: ''
|
||||||
finishReason
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (startTagBuffer.startsWith(thinkStartChars)) {
|
||||||
|
isInThinkTag = true;
|
||||||
|
return {
|
||||||
|
reasoningContent: startTagBuffer.slice(thinkStartChars.length),
|
||||||
|
content: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果未命中 think 标签,则认为不在 think 标签中,返回 buffer 内容作为 content
|
||||||
|
isInThinkTag = false;
|
||||||
|
return {
|
||||||
|
reasoningContent: '',
|
||||||
|
content: startTagBuffer
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 确认是 think 标签内容,开始返回 think 内容,并实时检测 </think>
|
||||||
|
/*
|
||||||
|
检测 </think> 方案。
|
||||||
|
存储所有疑似 </think> 的内容,直到检测到完整的 </think> 标签或超出 </think> 长度。
|
||||||
|
content 返回值包含以下几种情况:
|
||||||
|
abc - 完全未命中尾标签
|
||||||
|
abc<th - 命中一部分尾标签
|
||||||
|
abc</think> - 完全命中尾标签
|
||||||
|
abc</think>abc - 完全命中尾标签
|
||||||
|
</think>abc - 完全命中尾标签
|
||||||
|
k>abc - 命中一部分尾标签
|
||||||
|
*/
|
||||||
|
// endTagBuffer 专门用来记录疑似尾标签的内容
|
||||||
|
if (endTagBuffer) {
|
||||||
|
endTagBuffer += content;
|
||||||
|
if (endTagBuffer.includes(thinkEndChars)) {
|
||||||
|
isInThinkTag = false;
|
||||||
|
const answer = endTagBuffer.slice(thinkEndChars.length);
|
||||||
|
return {
|
||||||
|
reasoningContent: '',
|
||||||
|
content: answer
|
||||||
|
};
|
||||||
|
} else if (endTagBuffer.length >= thinkEndChars.length) {
|
||||||
|
// 缓存内容超出尾标签长度,且仍未命中 </think>,则认为本次猜测 </think> 失败,仍处于 think 阶段。
|
||||||
|
const tmp = endTagBuffer;
|
||||||
|
endTagBuffer = '';
|
||||||
|
return {
|
||||||
|
reasoningContent: tmp,
|
||||||
|
content: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
reasoningContent: '',
|
||||||
|
content: ''
|
||||||
|
};
|
||||||
|
} else if (content.includes(thinkEndChars)) {
|
||||||
|
// 返回内容,完整命中</think>,直接结束
|
||||||
|
isInThinkTag = false;
|
||||||
|
const [think, answer] = content.split(thinkEndChars);
|
||||||
|
return {
|
||||||
|
reasoningContent: think,
|
||||||
|
content: answer
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// 无 buffer,且未命中 </think>,开始疑似 </think> 检测。
|
||||||
|
for (let i = 1; i < thinkEndChars.length; i++) {
|
||||||
|
const partialEndTag = thinkEndChars.slice(0, i);
|
||||||
|
// 命中一部分尾标签
|
||||||
|
if (content.endsWith(partialEndTag)) {
|
||||||
|
const think = content.slice(0, -partialEndTag.length);
|
||||||
|
endTagBuffer += partialEndTag;
|
||||||
|
return {
|
||||||
|
reasoningContent: think,
|
||||||
|
content: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完全未命中尾标签,还是 think 阶段。
|
||||||
|
return {
|
||||||
|
reasoningContent: content,
|
||||||
|
content: ''
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Parse datset cite
|
||||||
|
if (retainDatasetCite) {
|
||||||
|
return {
|
||||||
|
reasoningContent: parsedThinkReasoningContent,
|
||||||
|
content: parsedThinkContent,
|
||||||
|
responseContent: parsedThinkContent,
|
||||||
|
finishReason
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 完全未命中尾标签,还是 think 阶段。
|
// 缓存包含 [ 的字符串,直到超出 maxCiteBufferLength 再一次性返回
|
||||||
|
const parseCite = (text: string) => {
|
||||||
|
// 结束时,返回所有剩余内容
|
||||||
|
if (isStreamEnd) {
|
||||||
|
const content = citeBuffer + text;
|
||||||
|
return {
|
||||||
|
content: removeDatasetCiteText(content, false)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新内容包含 [,初始化缓冲数据
|
||||||
|
if (text.includes('[')) {
|
||||||
|
const index = text.indexOf('[');
|
||||||
|
const beforeContent = citeBuffer + text.slice(0, index);
|
||||||
|
citeBuffer = text.slice(index);
|
||||||
|
|
||||||
|
// beforeContent 可能是:普通字符串,带 [ 的字符串
|
||||||
|
return {
|
||||||
|
content: removeDatasetCiteText(beforeContent, false)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// 处于 Cite 缓冲区,判断是否满足条件
|
||||||
|
else if (citeBuffer) {
|
||||||
|
citeBuffer += text;
|
||||||
|
|
||||||
|
// 检查缓冲区长度是否达到完整Quote长度或已经流结束
|
||||||
|
if (citeBuffer.length >= maxCiteBufferLength) {
|
||||||
|
const content = removeDatasetCiteText(citeBuffer, false);
|
||||||
|
citeBuffer = '';
|
||||||
|
|
||||||
|
return {
|
||||||
|
content
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// 暂时不返回内容
|
||||||
|
return { content: '' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: text
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const { content: pasedCiteContent } = parseCite(parsedThinkContent);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
reasoningContent: content,
|
reasoningContent: parsedThinkReasoningContent,
|
||||||
content: '',
|
content: parsedThinkContent,
|
||||||
|
responseContent: pasedCiteContent,
|
||||||
finishReason
|
finishReason
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStartTagBuffer = () => startTagBuffer;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
parsePart,
|
parsePart
|
||||||
getStartTagBuffer
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -31,5 +31,6 @@ export const computedPluginUsage = async ({
|
|||||||
return plugin.hasTokenFee ? pluginCurrentCost + childrenUsages : pluginCurrentCost;
|
return plugin.hasTokenFee ? pluginCurrentCost + childrenUsages : pluginCurrentCost;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Personal plugins are charged regardless of whether they are successful or not
|
||||||
return childrenUsages;
|
return childrenUsages;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/ty
|
|||||||
import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
|
import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
|
||||||
import { getHandleId } from '@fastgpt/global/core/workflow/utils';
|
import { getHandleId } from '@fastgpt/global/core/workflow/utils';
|
||||||
import { loadRequestMessages } from '../../../chat/utils';
|
import { loadRequestMessages } from '../../../chat/utils';
|
||||||
import { llmCompletionsBodyFormat, llmResponseToAnswerText } from '../../../ai/utils';
|
import { llmCompletionsBodyFormat, formatLLMResponse } from '../../../ai/utils';
|
||||||
import { addLog } from '../../../../common/system/log';
|
import { addLog } from '../../../../common/system/log';
|
||||||
import { ModelTypeEnum } from '../../../../../global/core/ai/model';
|
import { ModelTypeEnum } from '../../../../../global/core/ai/model';
|
||||||
import { replaceVariable } from '@fastgpt/global/common/string/tools';
|
import { replaceVariable } from '@fastgpt/global/common/string/tools';
|
||||||
@ -135,13 +135,13 @@ const completions = async ({
|
|||||||
model: cqModel.model,
|
model: cqModel.model,
|
||||||
temperature: 0.01,
|
temperature: 0.01,
|
||||||
messages: requestMessages,
|
messages: requestMessages,
|
||||||
stream: false
|
stream: true
|
||||||
},
|
},
|
||||||
cqModel
|
cqModel
|
||||||
),
|
),
|
||||||
userKey: externalProvider.openaiAccount
|
userKey: externalProvider.openaiAccount
|
||||||
});
|
});
|
||||||
const { text: answer, usage } = await llmResponseToAnswerText(response);
|
const { text: answer, usage } = await formatLLMResponse(response);
|
||||||
|
|
||||||
// console.log(JSON.stringify(chats2GPTMessages({ messages, reserveId: false }), null, 2));
|
// console.log(JSON.stringify(chats2GPTMessages({ messages, reserveId: false }), null, 2));
|
||||||
// console.log(answer, '----');
|
// console.log(answer, '----');
|
||||||
|
|||||||
@ -30,7 +30,7 @@ import {
|
|||||||
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants';
|
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants';
|
||||||
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
|
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||||
import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
|
import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
|
||||||
import { llmCompletionsBodyFormat, llmResponseToAnswerText } from '../../../ai/utils';
|
import { llmCompletionsBodyFormat, formatLLMResponse } from '../../../ai/utils';
|
||||||
import { ModelTypeEnum } from '../../../../../global/core/ai/model';
|
import { ModelTypeEnum } from '../../../../../global/core/ai/model';
|
||||||
import {
|
import {
|
||||||
getExtractJsonPrompt,
|
getExtractJsonPrompt,
|
||||||
@ -226,10 +226,10 @@ const toolChoice = async (props: ActionProps) => {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const { response } = (await createChatCompletion({
|
const { response } = await createChatCompletion({
|
||||||
body: llmCompletionsBodyFormat(
|
body: llmCompletionsBodyFormat(
|
||||||
{
|
{
|
||||||
stream: false,
|
stream: true,
|
||||||
model: extractModel.model,
|
model: extractModel.model,
|
||||||
temperature: 0.01,
|
temperature: 0.01,
|
||||||
messages: filterMessages,
|
messages: filterMessages,
|
||||||
@ -239,16 +239,15 @@ const toolChoice = async (props: ActionProps) => {
|
|||||||
extractModel
|
extractModel
|
||||||
),
|
),
|
||||||
userKey: externalProvider.openaiAccount
|
userKey: externalProvider.openaiAccount
|
||||||
})) as { response: UnStreamChatType };
|
});
|
||||||
|
const { toolCalls, usage } = await formatLLMResponse(response);
|
||||||
|
|
||||||
const arg: Record<string, any> = (() => {
|
const arg: Record<string, any> = (() => {
|
||||||
try {
|
try {
|
||||||
return json5.parse(
|
return json5.parse(toolCalls?.[0]?.function?.arguments || '');
|
||||||
response?.choices?.[0]?.message?.tool_calls?.[0]?.function?.arguments || ''
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(agentFunction.parameters);
|
console.log(agentFunction.parameters);
|
||||||
console.log(response.choices?.[0]?.message?.tool_calls?.[0]?.function);
|
console.log(toolCalls?.[0]?.function);
|
||||||
console.log('Your model may not support tool_call', error);
|
console.log('Your model may not support tool_call', error);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -257,11 +256,10 @@ const toolChoice = async (props: ActionProps) => {
|
|||||||
const AIMessages: ChatCompletionMessageParam[] = [
|
const AIMessages: ChatCompletionMessageParam[] = [
|
||||||
{
|
{
|
||||||
role: ChatCompletionRequestMessageRoleEnum.Assistant,
|
role: ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||||
tool_calls: response.choices?.[0]?.message?.tool_calls
|
tool_calls: toolCalls
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const usage = response.usage;
|
|
||||||
const inputTokens = usage?.prompt_tokens || (await countGptMessagesTokens(filterMessages, tools));
|
const inputTokens = usage?.prompt_tokens || (await countGptMessagesTokens(filterMessages, tools));
|
||||||
const outputTokens = usage?.completion_tokens || (await countGptMessagesTokens(AIMessages));
|
const outputTokens = usage?.completion_tokens || (await countGptMessagesTokens(AIMessages));
|
||||||
return {
|
return {
|
||||||
@ -321,13 +319,13 @@ Human: ${content}`
|
|||||||
model: extractModel.model,
|
model: extractModel.model,
|
||||||
temperature: 0.01,
|
temperature: 0.01,
|
||||||
messages: requestMessages,
|
messages: requestMessages,
|
||||||
stream: false
|
stream: true
|
||||||
},
|
},
|
||||||
extractModel
|
extractModel
|
||||||
),
|
),
|
||||||
userKey: externalProvider.openaiAccount
|
userKey: externalProvider.openaiAccount
|
||||||
});
|
});
|
||||||
const { text: answer, usage } = await llmResponseToAnswerText(response);
|
const { text: answer, usage } = await formatLLMResponse(response);
|
||||||
const inputTokens = usage?.prompt_tokens || (await countMessagesTokens(messages));
|
const inputTokens = usage?.prompt_tokens || (await countMessagesTokens(messages));
|
||||||
const outputTokens = usage?.completion_tokens || (await countPromptTokens(answer));
|
const outputTokens = usage?.completion_tokens || (await countPromptTokens(answer));
|
||||||
|
|
||||||
|
|||||||
@ -26,7 +26,12 @@ import { getNanoid, sliceStrStartEnd } 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 { formatToolResponse, initToolCallEdges, initToolNodes } from './utils';
|
import { formatToolResponse, initToolCallEdges, initToolNodes } from './utils';
|
||||||
import { computedMaxToken, llmCompletionsBodyFormat } from '../../../../ai/utils';
|
import {
|
||||||
|
computedMaxToken,
|
||||||
|
llmCompletionsBodyFormat,
|
||||||
|
removeDatasetCiteText,
|
||||||
|
parseLLMStreamResponse
|
||||||
|
} from '../../../../ai/utils';
|
||||||
import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants';
|
import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants';
|
||||||
import { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
import { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||||
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
|
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
|
||||||
@ -48,6 +53,7 @@ export const runToolWithFunctionCall = async (
|
|||||||
runtimeEdges,
|
runtimeEdges,
|
||||||
externalProvider,
|
externalProvider,
|
||||||
stream,
|
stream,
|
||||||
|
retainDatasetCite = true,
|
||||||
workflowStreamResponse,
|
workflowStreamResponse,
|
||||||
params: {
|
params: {
|
||||||
temperature,
|
temperature,
|
||||||
@ -261,7 +267,8 @@ export const runToolWithFunctionCall = async (
|
|||||||
res,
|
res,
|
||||||
toolNodes,
|
toolNodes,
|
||||||
stream: aiResponse,
|
stream: aiResponse,
|
||||||
workflowStreamResponse
|
workflowStreamResponse,
|
||||||
|
retainDatasetCite
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -288,8 +295,18 @@ export const runToolWithFunctionCall = async (
|
|||||||
]
|
]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
|
const answer = result.choices?.[0]?.message?.content || '';
|
||||||
|
if (answer) {
|
||||||
|
workflowStreamResponse?.({
|
||||||
|
event: SseResponseEventEnum.fastAnswer,
|
||||||
|
data: textAdaptGptResponse({
|
||||||
|
text: removeDatasetCiteText(answer, retainDatasetCite)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
answer: result.choices?.[0]?.message?.content || '',
|
answer,
|
||||||
functionCalls: toolCalls,
|
functionCalls: toolCalls,
|
||||||
inputTokens: usage?.prompt_tokens,
|
inputTokens: usage?.prompt_tokens,
|
||||||
outputTokens: usage?.completion_tokens
|
outputTokens: usage?.completion_tokens
|
||||||
@ -509,12 +526,14 @@ async function streamResponse({
|
|||||||
res,
|
res,
|
||||||
toolNodes,
|
toolNodes,
|
||||||
stream,
|
stream,
|
||||||
workflowStreamResponse
|
workflowStreamResponse,
|
||||||
|
retainDatasetCite
|
||||||
}: {
|
}: {
|
||||||
res: NextApiResponse;
|
res: NextApiResponse;
|
||||||
toolNodes: ToolNodeItemType[];
|
toolNodes: ToolNodeItemType[];
|
||||||
stream: StreamChatType;
|
stream: StreamChatType;
|
||||||
workflowStreamResponse?: WorkflowResponseType;
|
workflowStreamResponse?: WorkflowResponseType;
|
||||||
|
retainDatasetCite?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const write = responseWriteController({
|
const write = responseWriteController({
|
||||||
res,
|
res,
|
||||||
@ -526,6 +545,8 @@ async function streamResponse({
|
|||||||
let functionId = getNanoid();
|
let functionId = getNanoid();
|
||||||
let usage = getLLMDefaultUsage();
|
let usage = getLLMDefaultUsage();
|
||||||
|
|
||||||
|
const { parsePart } = parseLLMStreamResponse();
|
||||||
|
|
||||||
for await (const part of stream) {
|
for await (const part of stream) {
|
||||||
usage = part.usage || usage;
|
usage = part.usage || usage;
|
||||||
if (res.closed) {
|
if (res.closed) {
|
||||||
@ -533,17 +554,21 @@ async function streamResponse({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { content: toolChoiceContent, responseContent } = parsePart({
|
||||||
|
part,
|
||||||
|
parseThinkTag: false,
|
||||||
|
retainDatasetCite
|
||||||
|
});
|
||||||
|
|
||||||
const responseChoice = part.choices?.[0]?.delta;
|
const responseChoice = part.choices?.[0]?.delta;
|
||||||
|
textAnswer += toolChoiceContent;
|
||||||
|
|
||||||
if (responseChoice.content) {
|
if (responseContent) {
|
||||||
const content = responseChoice?.content || '';
|
|
||||||
textAnswer += content;
|
|
||||||
|
|
||||||
workflowStreamResponse?.({
|
workflowStreamResponse?.({
|
||||||
write,
|
write,
|
||||||
event: SseResponseEventEnum.answer,
|
event: SseResponseEventEnum.answer,
|
||||||
data: textAdaptGptResponse({
|
data: textAdaptGptResponse({
|
||||||
text: content
|
text: responseContent
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
} else if (responseChoice.function_call) {
|
} else if (responseChoice.function_call) {
|
||||||
|
|||||||
@ -29,8 +29,9 @@ import { formatToolResponse, initToolCallEdges, initToolNodes } from './utils';
|
|||||||
import {
|
import {
|
||||||
computedMaxToken,
|
computedMaxToken,
|
||||||
llmCompletionsBodyFormat,
|
llmCompletionsBodyFormat,
|
||||||
|
removeDatasetCiteText,
|
||||||
parseReasoningContent,
|
parseReasoningContent,
|
||||||
parseReasoningStreamContent
|
parseLLMStreamResponse
|
||||||
} from '../../../../ai/utils';
|
} from '../../../../ai/utils';
|
||||||
import { WorkflowResponseType } from '../../type';
|
import { WorkflowResponseType } from '../../type';
|
||||||
import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants';
|
import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants';
|
||||||
@ -60,6 +61,7 @@ export const runToolWithPromptCall = async (
|
|||||||
runtimeEdges,
|
runtimeEdges,
|
||||||
externalProvider,
|
externalProvider,
|
||||||
stream,
|
stream,
|
||||||
|
retainDatasetCite = true,
|
||||||
workflowStreamResponse,
|
workflowStreamResponse,
|
||||||
params: {
|
params: {
|
||||||
temperature,
|
temperature,
|
||||||
@ -275,7 +277,8 @@ export const runToolWithPromptCall = async (
|
|||||||
toolNodes,
|
toolNodes,
|
||||||
stream: aiResponse,
|
stream: aiResponse,
|
||||||
workflowStreamResponse,
|
workflowStreamResponse,
|
||||||
aiChatReasoning
|
aiChatReasoning,
|
||||||
|
retainDatasetCite
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -318,7 +321,7 @@ export const runToolWithPromptCall = async (
|
|||||||
workflowStreamResponse?.({
|
workflowStreamResponse?.({
|
||||||
event: SseResponseEventEnum.fastAnswer,
|
event: SseResponseEventEnum.fastAnswer,
|
||||||
data: textAdaptGptResponse({
|
data: textAdaptGptResponse({
|
||||||
reasoning_content: reasoning
|
reasoning_content: removeDatasetCiteText(reasoning, retainDatasetCite)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -344,7 +347,7 @@ export const runToolWithPromptCall = async (
|
|||||||
workflowStreamResponse?.({
|
workflowStreamResponse?.({
|
||||||
event: SseResponseEventEnum.fastAnswer,
|
event: SseResponseEventEnum.fastAnswer,
|
||||||
data: textAdaptGptResponse({
|
data: textAdaptGptResponse({
|
||||||
text: replaceAnswer
|
text: removeDatasetCiteText(replaceAnswer, retainDatasetCite)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -566,13 +569,15 @@ async function streamResponse({
|
|||||||
res,
|
res,
|
||||||
stream,
|
stream,
|
||||||
workflowStreamResponse,
|
workflowStreamResponse,
|
||||||
aiChatReasoning
|
aiChatReasoning,
|
||||||
|
retainDatasetCite
|
||||||
}: {
|
}: {
|
||||||
res: NextApiResponse;
|
res: NextApiResponse;
|
||||||
toolNodes: ToolNodeItemType[];
|
toolNodes: ToolNodeItemType[];
|
||||||
stream: StreamChatType;
|
stream: StreamChatType;
|
||||||
workflowStreamResponse?: WorkflowResponseType;
|
workflowStreamResponse?: WorkflowResponseType;
|
||||||
aiChatReasoning?: boolean;
|
aiChatReasoning?: boolean;
|
||||||
|
retainDatasetCite?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const write = responseWriteController({
|
const write = responseWriteController({
|
||||||
res,
|
res,
|
||||||
@ -585,7 +590,7 @@ async function streamResponse({
|
|||||||
let finish_reason: CompletionFinishReason = null;
|
let finish_reason: CompletionFinishReason = null;
|
||||||
let usage = getLLMDefaultUsage();
|
let usage = getLLMDefaultUsage();
|
||||||
|
|
||||||
const { parsePart, getStartTagBuffer } = parseReasoningStreamContent();
|
const { parsePart } = parseLLMStreamResponse();
|
||||||
|
|
||||||
for await (const part of stream) {
|
for await (const part of stream) {
|
||||||
usage = part.usage || usage;
|
usage = part.usage || usage;
|
||||||
@ -595,11 +600,16 @@ async function streamResponse({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { reasoningContent, content, finishReason } = parsePart(part, aiChatReasoning);
|
const { reasoningContent, content, responseContent, finishReason } = parsePart({
|
||||||
|
part,
|
||||||
|
parseThinkTag: aiChatReasoning,
|
||||||
|
retainDatasetCite
|
||||||
|
});
|
||||||
finish_reason = finish_reason || finishReason;
|
finish_reason = finish_reason || finishReason;
|
||||||
answer += content;
|
answer += content;
|
||||||
reasoning += reasoningContent;
|
reasoning += reasoningContent;
|
||||||
|
|
||||||
|
// Reasoning response
|
||||||
if (aiChatReasoning && reasoningContent) {
|
if (aiChatReasoning && reasoningContent) {
|
||||||
workflowStreamResponse?.({
|
workflowStreamResponse?.({
|
||||||
write,
|
write,
|
||||||
@ -612,13 +622,15 @@ async function streamResponse({
|
|||||||
|
|
||||||
if (content) {
|
if (content) {
|
||||||
if (startResponseWrite) {
|
if (startResponseWrite) {
|
||||||
workflowStreamResponse?.({
|
if (responseContent) {
|
||||||
write,
|
workflowStreamResponse?.({
|
||||||
event: SseResponseEventEnum.answer,
|
write,
|
||||||
data: textAdaptGptResponse({
|
event: SseResponseEventEnum.answer,
|
||||||
text: content
|
data: textAdaptGptResponse({
|
||||||
})
|
text: responseContent
|
||||||
});
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
} else if (answer.length >= 3) {
|
} else if (answer.length >= 3) {
|
||||||
answer = answer.trimStart();
|
answer = answer.trimStart();
|
||||||
if (/0(:|:)/.test(answer)) {
|
if (/0(:|:)/.test(answer)) {
|
||||||
@ -640,22 +652,6 @@ async function streamResponse({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (answer === '') {
|
|
||||||
answer = getStartTagBuffer();
|
|
||||||
if (/0(:|:)/.test(answer)) {
|
|
||||||
// find first : index
|
|
||||||
const firstIndex = answer.indexOf('0:') !== -1 ? answer.indexOf('0:') : answer.indexOf('0:');
|
|
||||||
answer = answer.substring(firstIndex + 2).trim();
|
|
||||||
workflowStreamResponse?.({
|
|
||||||
write,
|
|
||||||
event: SseResponseEventEnum.answer,
|
|
||||||
data: textAdaptGptResponse({
|
|
||||||
text: answer
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { answer, reasoning, finish_reason, usage };
|
return { answer, reasoning, finish_reason, usage };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -26,7 +26,12 @@ import { countGptMessagesTokens } from '../../../../../common/string/tiktoken/in
|
|||||||
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
|
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
|
||||||
import { AIChatItemType } from '@fastgpt/global/core/chat/type';
|
import { AIChatItemType } from '@fastgpt/global/core/chat/type';
|
||||||
import { formatToolResponse, initToolCallEdges, initToolNodes } from './utils';
|
import { formatToolResponse, initToolCallEdges, initToolNodes } from './utils';
|
||||||
import { computedMaxToken, llmCompletionsBodyFormat } from '../../../../ai/utils';
|
import {
|
||||||
|
computedMaxToken,
|
||||||
|
llmCompletionsBodyFormat,
|
||||||
|
removeDatasetCiteText,
|
||||||
|
parseLLMStreamResponse
|
||||||
|
} from '../../../../ai/utils';
|
||||||
import { getNanoid, sliceStrStartEnd } from '@fastgpt/global/common/string/tools';
|
import { getNanoid, sliceStrStartEnd } from '@fastgpt/global/common/string/tools';
|
||||||
import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants';
|
import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants';
|
||||||
import { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
import { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||||
@ -89,12 +94,13 @@ export const runToolWithToolChoice = async (
|
|||||||
interactiveEntryToolParams,
|
interactiveEntryToolParams,
|
||||||
...workflowProps
|
...workflowProps
|
||||||
} = props;
|
} = props;
|
||||||
const {
|
let {
|
||||||
res,
|
res,
|
||||||
requestOrigin,
|
requestOrigin,
|
||||||
runtimeNodes,
|
runtimeNodes,
|
||||||
runtimeEdges,
|
runtimeEdges,
|
||||||
stream,
|
stream,
|
||||||
|
retainDatasetCite = true,
|
||||||
externalProvider,
|
externalProvider,
|
||||||
workflowStreamResponse,
|
workflowStreamResponse,
|
||||||
params: {
|
params: {
|
||||||
@ -104,9 +110,11 @@ export const runToolWithToolChoice = async (
|
|||||||
aiChatTopP,
|
aiChatTopP,
|
||||||
aiChatStopSign,
|
aiChatStopSign,
|
||||||
aiChatResponseFormat,
|
aiChatResponseFormat,
|
||||||
aiChatJsonSchema
|
aiChatJsonSchema,
|
||||||
|
aiChatReasoning
|
||||||
}
|
}
|
||||||
} = workflowProps;
|
} = workflowProps;
|
||||||
|
aiChatReasoning = !!aiChatReasoning && !!toolModel.reasoning;
|
||||||
|
|
||||||
if (maxRunToolTimes <= 0 && response) {
|
if (maxRunToolTimes <= 0 && response) {
|
||||||
return response;
|
return response;
|
||||||
@ -279,6 +287,7 @@ export const runToolWithToolChoice = async (
|
|||||||
messages: requestMessages,
|
messages: requestMessages,
|
||||||
tools,
|
tools,
|
||||||
tool_choice: 'auto',
|
tool_choice: 'auto',
|
||||||
|
parallel_tool_calls: true,
|
||||||
temperature,
|
temperature,
|
||||||
max_tokens,
|
max_tokens,
|
||||||
top_p: aiChatTopP,
|
top_p: aiChatTopP,
|
||||||
@ -288,7 +297,7 @@ export const runToolWithToolChoice = async (
|
|||||||
},
|
},
|
||||||
toolModel
|
toolModel
|
||||||
);
|
);
|
||||||
// console.log(JSON.stringify(filterMessages, null, 2), '==requestMessages');
|
// console.log(JSON.stringify(requestBody, null, 2), '==requestMessages');
|
||||||
/* Run llm */
|
/* Run llm */
|
||||||
const {
|
const {
|
||||||
response: aiResponse,
|
response: aiResponse,
|
||||||
@ -320,7 +329,9 @@ export const runToolWithToolChoice = async (
|
|||||||
res,
|
res,
|
||||||
workflowStreamResponse,
|
workflowStreamResponse,
|
||||||
toolNodes,
|
toolNodes,
|
||||||
stream: aiResponse
|
stream: aiResponse,
|
||||||
|
aiChatReasoning,
|
||||||
|
retainDatasetCite
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -335,11 +346,38 @@ export const runToolWithToolChoice = async (
|
|||||||
const finish_reason = result.choices?.[0]?.finish_reason as CompletionFinishReason;
|
const finish_reason = result.choices?.[0]?.finish_reason as CompletionFinishReason;
|
||||||
const calls = result.choices?.[0]?.message?.tool_calls || [];
|
const calls = result.choices?.[0]?.message?.tool_calls || [];
|
||||||
const answer = result.choices?.[0]?.message?.content || '';
|
const answer = result.choices?.[0]?.message?.content || '';
|
||||||
|
// @ts-ignore
|
||||||
|
const reasoningContent = result.choices?.[0]?.message?.reasoning_content || '';
|
||||||
const usage = result.usage;
|
const usage = result.usage;
|
||||||
|
|
||||||
// 加上name和avatar
|
if (aiChatReasoning && reasoningContent) {
|
||||||
|
workflowStreamResponse?.({
|
||||||
|
event: SseResponseEventEnum.fastAnswer,
|
||||||
|
data: textAdaptGptResponse({
|
||||||
|
reasoning_content: removeDatasetCiteText(reasoningContent, retainDatasetCite)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化 toolCalls
|
||||||
const toolCalls = calls.map((tool) => {
|
const toolCalls = calls.map((tool) => {
|
||||||
const toolNode = toolNodes.find((item) => item.nodeId === tool.function?.name);
|
const toolNode = toolNodes.find((item) => item.nodeId === tool.function?.name);
|
||||||
|
|
||||||
|
// 不支持 stream 模式的模型的这里需要补一个响应给客户端
|
||||||
|
workflowStreamResponse?.({
|
||||||
|
event: SseResponseEventEnum.toolCall,
|
||||||
|
data: {
|
||||||
|
tool: {
|
||||||
|
id: tool.id,
|
||||||
|
toolName: toolNode?.name || '',
|
||||||
|
toolAvatar: toolNode?.avatar || '',
|
||||||
|
functionName: tool.function.name,
|
||||||
|
params: tool.function?.arguments ?? '',
|
||||||
|
response: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...tool,
|
...tool,
|
||||||
toolName: toolNode?.name || '',
|
toolName: toolNode?.name || '',
|
||||||
@ -347,27 +385,11 @@ export const runToolWithToolChoice = async (
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// 不支持 stream 模式的模型的流失响应
|
|
||||||
toolCalls.forEach((tool) => {
|
|
||||||
workflowStreamResponse?.({
|
|
||||||
event: SseResponseEventEnum.toolCall,
|
|
||||||
data: {
|
|
||||||
tool: {
|
|
||||||
id: tool.id,
|
|
||||||
toolName: tool.toolName,
|
|
||||||
toolAvatar: tool.toolAvatar,
|
|
||||||
functionName: tool.function.name,
|
|
||||||
params: tool.function?.arguments ?? '',
|
|
||||||
response: ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
if (answer) {
|
if (answer) {
|
||||||
workflowStreamResponse?.({
|
workflowStreamResponse?.({
|
||||||
event: SseResponseEventEnum.fastAnswer,
|
event: SseResponseEventEnum.fastAnswer,
|
||||||
data: textAdaptGptResponse({
|
data: textAdaptGptResponse({
|
||||||
text: answer
|
text: removeDatasetCiteText(answer, retainDatasetCite)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -627,12 +649,16 @@ async function streamResponse({
|
|||||||
res,
|
res,
|
||||||
toolNodes,
|
toolNodes,
|
||||||
stream,
|
stream,
|
||||||
workflowStreamResponse
|
workflowStreamResponse,
|
||||||
|
aiChatReasoning,
|
||||||
|
retainDatasetCite
|
||||||
}: {
|
}: {
|
||||||
res: NextApiResponse;
|
res: NextApiResponse;
|
||||||
toolNodes: ToolNodeItemType[];
|
toolNodes: ToolNodeItemType[];
|
||||||
stream: StreamChatType;
|
stream: StreamChatType;
|
||||||
workflowStreamResponse?: WorkflowResponseType;
|
workflowStreamResponse?: WorkflowResponseType;
|
||||||
|
aiChatReasoning: boolean;
|
||||||
|
retainDatasetCite?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const write = responseWriteController({
|
const write = responseWriteController({
|
||||||
res,
|
res,
|
||||||
@ -642,105 +668,130 @@ async function streamResponse({
|
|||||||
let textAnswer = '';
|
let textAnswer = '';
|
||||||
let callingTool: { name: string; arguments: string } | null = null;
|
let callingTool: { name: string; arguments: string } | null = null;
|
||||||
let toolCalls: ChatCompletionMessageToolCall[] = [];
|
let toolCalls: ChatCompletionMessageToolCall[] = [];
|
||||||
let finishReason: CompletionFinishReason = null;
|
let finish_reason: CompletionFinishReason = null;
|
||||||
let usage = getLLMDefaultUsage();
|
let usage = getLLMDefaultUsage();
|
||||||
|
|
||||||
|
const { parsePart } = parseLLMStreamResponse();
|
||||||
|
|
||||||
for await (const part of stream) {
|
for await (const part of stream) {
|
||||||
usage = part.usage || usage;
|
usage = part.usage || usage;
|
||||||
if (res.closed) {
|
if (res.closed) {
|
||||||
stream.controller?.abort();
|
stream.controller?.abort();
|
||||||
finishReason = 'close';
|
finish_reason = 'close';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
reasoningContent,
|
||||||
|
content: toolChoiceContent,
|
||||||
|
responseContent,
|
||||||
|
finishReason
|
||||||
|
} = parsePart({
|
||||||
|
part,
|
||||||
|
parseThinkTag: true,
|
||||||
|
retainDatasetCite
|
||||||
|
});
|
||||||
|
textAnswer += toolChoiceContent;
|
||||||
|
finish_reason = finishReason || finish_reason;
|
||||||
|
|
||||||
const responseChoice = part.choices?.[0]?.delta;
|
const responseChoice = part.choices?.[0]?.delta;
|
||||||
const finish_reason = part.choices?.[0]?.finish_reason as CompletionFinishReason;
|
|
||||||
finishReason = finishReason || finish_reason;
|
|
||||||
|
|
||||||
if (responseChoice?.content) {
|
|
||||||
const content = responseChoice.content || '';
|
|
||||||
textAnswer += content;
|
|
||||||
|
|
||||||
|
// Reasoning response
|
||||||
|
if (aiChatReasoning && reasoningContent) {
|
||||||
workflowStreamResponse?.({
|
workflowStreamResponse?.({
|
||||||
write,
|
write,
|
||||||
event: SseResponseEventEnum.answer,
|
event: SseResponseEventEnum.answer,
|
||||||
data: textAdaptGptResponse({
|
data: textAdaptGptResponse({
|
||||||
text: content
|
reasoning_content: reasoningContent
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (responseChoice?.tool_calls?.[0]) {
|
if (responseContent) {
|
||||||
// @ts-ignore
|
workflowStreamResponse?.({
|
||||||
const toolCall: ChatCompletionMessageToolCall = responseChoice.tool_calls[0];
|
write,
|
||||||
// In a stream response, only one tool is returned at a time. If have id, description is executing a tool
|
event: SseResponseEventEnum.answer,
|
||||||
if (toolCall.id || callingTool) {
|
data: textAdaptGptResponse({
|
||||||
// Start call tool
|
text: responseContent
|
||||||
if (toolCall.id) {
|
})
|
||||||
callingTool = {
|
});
|
||||||
name: toolCall.function?.name || '',
|
}
|
||||||
arguments: toolCall.function?.arguments || ''
|
// Parse tool calls
|
||||||
};
|
if (responseChoice?.tool_calls?.length) {
|
||||||
} else if (callingTool) {
|
responseChoice.tool_calls.forEach((toolCall) => {
|
||||||
// Continue call
|
const index = toolCall.index;
|
||||||
callingTool.name += toolCall.function.name || '';
|
|
||||||
callingTool.arguments += toolCall.function.arguments || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const toolFunction = callingTool!;
|
// Call new tool
|
||||||
|
if (toolCall.id || callingTool) {
|
||||||
|
// 有 id,代表新 call 工具
|
||||||
|
if (toolCall.id) {
|
||||||
|
callingTool = {
|
||||||
|
name: toolCall.function?.name || '',
|
||||||
|
arguments: toolCall.function?.arguments || ''
|
||||||
|
};
|
||||||
|
} else if (callingTool) {
|
||||||
|
// Continue call(Perhaps the name of the previous function was incomplete)
|
||||||
|
callingTool.name += toolCall.function?.name || '';
|
||||||
|
callingTool.arguments += toolCall.function?.arguments || '';
|
||||||
|
}
|
||||||
|
|
||||||
const toolNode = toolNodes.find((item) => item.nodeId === toolFunction.name);
|
if (!callingTool) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (toolNode) {
|
const toolNode = toolNodes.find((item) => item.nodeId === callingTool!.name);
|
||||||
// New tool, add to list.
|
|
||||||
const toolId = getNanoid();
|
|
||||||
toolCalls.push({
|
|
||||||
...toolCall,
|
|
||||||
id: toolId,
|
|
||||||
type: 'function',
|
|
||||||
function: toolFunction,
|
|
||||||
toolName: toolNode.name,
|
|
||||||
toolAvatar: toolNode.avatar
|
|
||||||
});
|
|
||||||
|
|
||||||
workflowStreamResponse?.({
|
if (toolNode) {
|
||||||
event: SseResponseEventEnum.toolCall,
|
// New tool, add to list.
|
||||||
data: {
|
const toolId = getNanoid();
|
||||||
tool: {
|
toolCalls[index] = {
|
||||||
id: toolId,
|
...toolCall,
|
||||||
toolName: toolNode.name,
|
id: toolId,
|
||||||
toolAvatar: toolNode.avatar,
|
type: 'function',
|
||||||
functionName: toolFunction.name,
|
function: callingTool,
|
||||||
params: toolFunction?.arguments ?? '',
|
toolName: toolNode.name,
|
||||||
response: ''
|
toolAvatar: toolNode.avatar
|
||||||
|
};
|
||||||
|
|
||||||
|
workflowStreamResponse?.({
|
||||||
|
event: SseResponseEventEnum.toolCall,
|
||||||
|
data: {
|
||||||
|
tool: {
|
||||||
|
id: toolId,
|
||||||
|
toolName: toolNode.name,
|
||||||
|
toolAvatar: toolNode.avatar,
|
||||||
|
functionName: callingTool.name,
|
||||||
|
params: callingTool?.arguments ?? '',
|
||||||
|
response: ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
callingTool = null;
|
||||||
callingTool = null;
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
/* arg 追加到当前工具的参数里 */
|
||||||
/* arg 插入最后一个工具的参数里 */
|
const arg: string = toolCall?.function?.arguments ?? '';
|
||||||
const arg: string = toolCall?.function?.arguments ?? '';
|
const currentTool = toolCalls[index];
|
||||||
const currentTool = toolCalls[toolCalls.length - 1];
|
if (currentTool && arg) {
|
||||||
if (currentTool && arg) {
|
currentTool.function.arguments += arg;
|
||||||
currentTool.function.arguments += arg;
|
|
||||||
|
|
||||||
workflowStreamResponse?.({
|
workflowStreamResponse?.({
|
||||||
write,
|
write,
|
||||||
event: SseResponseEventEnum.toolParams,
|
event: SseResponseEventEnum.toolParams,
|
||||||
data: {
|
data: {
|
||||||
tool: {
|
tool: {
|
||||||
id: currentTool.id,
|
id: currentTool.id,
|
||||||
toolName: '',
|
toolName: '',
|
||||||
toolAvatar: '',
|
toolAvatar: '',
|
||||||
params: arg,
|
params: arg,
|
||||||
response: ''
|
response: ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { answer: textAnswer, toolCalls, finish_reason: finishReason, usage };
|
return { answer: textAnswer, toolCalls: toolCalls.filter(Boolean), finish_reason, usage };
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,11 @@ import type { ChatItemType, UserChatItemValueItemType } from '@fastgpt/global/co
|
|||||||
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 { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
|
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||||
import { parseReasoningContent, parseReasoningStreamContent } from '../../../ai/utils';
|
import {
|
||||||
|
removeDatasetCiteText,
|
||||||
|
parseReasoningContent,
|
||||||
|
parseLLMStreamResponse
|
||||||
|
} from '../../../ai/utils';
|
||||||
import { createChatCompletion } from '../../../ai/config';
|
import { createChatCompletion } from '../../../ai/config';
|
||||||
import type {
|
import type {
|
||||||
ChatCompletionMessageParam,
|
ChatCompletionMessageParam,
|
||||||
@ -75,7 +79,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
|||||||
res,
|
res,
|
||||||
requestOrigin,
|
requestOrigin,
|
||||||
stream = false,
|
stream = false,
|
||||||
parseQuote = true,
|
retainDatasetCite = true,
|
||||||
externalProvider,
|
externalProvider,
|
||||||
histories,
|
histories,
|
||||||
node: { name, version },
|
node: { name, version },
|
||||||
@ -159,8 +163,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
|||||||
userChatInput,
|
userChatInput,
|
||||||
systemPrompt,
|
systemPrompt,
|
||||||
userFiles,
|
userFiles,
|
||||||
documentQuoteText,
|
documentQuoteText
|
||||||
parseQuote
|
|
||||||
}),
|
}),
|
||||||
// Censor = true and system key, will check content
|
// Censor = true and system key, will check content
|
||||||
(() => {
|
(() => {
|
||||||
@ -223,7 +226,8 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
|||||||
aiChatReasoning,
|
aiChatReasoning,
|
||||||
parseThinkTag: modelConstantsData.reasoning,
|
parseThinkTag: modelConstantsData.reasoning,
|
||||||
isResponseAnswerText,
|
isResponseAnswerText,
|
||||||
workflowStreamResponse
|
workflowStreamResponse,
|
||||||
|
retainDatasetCite
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -258,23 +262,21 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
// Some models do not support streaming
|
// Some models do not support streaming
|
||||||
if (stream) {
|
if (aiChatReasoning && reasoningContent) {
|
||||||
if (aiChatReasoning && reasoningContent) {
|
workflowStreamResponse?.({
|
||||||
workflowStreamResponse?.({
|
event: SseResponseEventEnum.fastAnswer,
|
||||||
event: SseResponseEventEnum.fastAnswer,
|
data: textAdaptGptResponse({
|
||||||
data: textAdaptGptResponse({
|
reasoning_content: removeDatasetCiteText(reasoningContent, retainDatasetCite)
|
||||||
reasoning_content: reasoningContent
|
})
|
||||||
})
|
});
|
||||||
});
|
}
|
||||||
}
|
if (isResponseAnswerText && content) {
|
||||||
if (isResponseAnswerText && content) {
|
workflowStreamResponse?.({
|
||||||
workflowStreamResponse?.({
|
event: SseResponseEventEnum.fastAnswer,
|
||||||
event: SseResponseEventEnum.fastAnswer,
|
data: textAdaptGptResponse({
|
||||||
data: textAdaptGptResponse({
|
text: removeDatasetCiteText(content, retainDatasetCite)
|
||||||
text: content
|
})
|
||||||
})
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -452,8 +454,7 @@ async function getChatMessages({
|
|||||||
systemPrompt,
|
systemPrompt,
|
||||||
userChatInput,
|
userChatInput,
|
||||||
userFiles,
|
userFiles,
|
||||||
documentQuoteText,
|
documentQuoteText
|
||||||
parseQuote = true
|
|
||||||
}: {
|
}: {
|
||||||
model: LLMModelItemType;
|
model: LLMModelItemType;
|
||||||
maxTokens?: number;
|
maxTokens?: number;
|
||||||
@ -470,14 +471,13 @@ async function getChatMessages({
|
|||||||
|
|
||||||
userFiles: UserChatItemValueItemType['file'][];
|
userFiles: UserChatItemValueItemType['file'][];
|
||||||
documentQuoteText?: string; // document quote
|
documentQuoteText?: string; // document quote
|
||||||
parseQuote?: boolean;
|
|
||||||
}) {
|
}) {
|
||||||
// Dataset prompt ====>
|
// Dataset prompt ====>
|
||||||
// User role or prompt include question
|
// User role or prompt include question
|
||||||
const quoteRole =
|
const quoteRole =
|
||||||
aiChatQuoteRole === 'user' || datasetQuotePrompt.includes('{{question}}') ? 'user' : 'system';
|
aiChatQuoteRole === 'user' || datasetQuotePrompt.includes('{{question}}') ? 'user' : 'system';
|
||||||
|
|
||||||
const defaultQuotePrompt = getQuotePrompt(version, quoteRole, parseQuote);
|
const defaultQuotePrompt = getQuotePrompt(version, quoteRole);
|
||||||
|
|
||||||
const datasetQuotePromptTemplate = datasetQuotePrompt || defaultQuotePrompt;
|
const datasetQuotePromptTemplate = datasetQuotePrompt || defaultQuotePrompt;
|
||||||
|
|
||||||
@ -539,7 +539,8 @@ async function streamResponse({
|
|||||||
workflowStreamResponse,
|
workflowStreamResponse,
|
||||||
aiChatReasoning,
|
aiChatReasoning,
|
||||||
parseThinkTag,
|
parseThinkTag,
|
||||||
isResponseAnswerText
|
isResponseAnswerText,
|
||||||
|
retainDatasetCite = true
|
||||||
}: {
|
}: {
|
||||||
res: NextApiResponse;
|
res: NextApiResponse;
|
||||||
stream: StreamChatType;
|
stream: StreamChatType;
|
||||||
@ -547,6 +548,7 @@ async function streamResponse({
|
|||||||
aiChatReasoning?: boolean;
|
aiChatReasoning?: boolean;
|
||||||
parseThinkTag?: boolean;
|
parseThinkTag?: boolean;
|
||||||
isResponseAnswerText?: boolean;
|
isResponseAnswerText?: boolean;
|
||||||
|
retainDatasetCite: boolean;
|
||||||
}) {
|
}) {
|
||||||
const write = responseWriteController({
|
const write = responseWriteController({
|
||||||
res,
|
res,
|
||||||
@ -557,7 +559,7 @@ async function streamResponse({
|
|||||||
let finish_reason: CompletionFinishReason = null;
|
let finish_reason: CompletionFinishReason = null;
|
||||||
let usage: CompletionUsage = getLLMDefaultUsage();
|
let usage: CompletionUsage = getLLMDefaultUsage();
|
||||||
|
|
||||||
const { parsePart, getStartTagBuffer } = parseReasoningStreamContent();
|
const { parsePart } = parseLLMStreamResponse();
|
||||||
|
|
||||||
for await (const part of stream) {
|
for await (const part of stream) {
|
||||||
usage = part.usage || usage;
|
usage = part.usage || usage;
|
||||||
@ -568,7 +570,11 @@ async function streamResponse({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { reasoningContent, content, finishReason } = parsePart(part, parseThinkTag);
|
const { reasoningContent, content, responseContent, finishReason } = parsePart({
|
||||||
|
part,
|
||||||
|
parseThinkTag,
|
||||||
|
retainDatasetCite
|
||||||
|
});
|
||||||
finish_reason = finish_reason || finishReason;
|
finish_reason = finish_reason || finishReason;
|
||||||
answer += content;
|
answer += content;
|
||||||
reasoning += reasoningContent;
|
reasoning += reasoningContent;
|
||||||
@ -583,26 +589,12 @@ async function streamResponse({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isResponseAnswerText && content) {
|
if (isResponseAnswerText && responseContent) {
|
||||||
workflowStreamResponse?.({
|
workflowStreamResponse?.({
|
||||||
write,
|
write,
|
||||||
event: SseResponseEventEnum.answer,
|
event: SseResponseEventEnum.answer,
|
||||||
data: textAdaptGptResponse({
|
data: textAdaptGptResponse({
|
||||||
text: content
|
text: responseContent
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if answer is empty, try to get value from startTagBuffer. (Cause: The response content is too short to exceed the minimum parse length)
|
|
||||||
if (answer === '') {
|
|
||||||
answer = getStartTagBuffer();
|
|
||||||
if (isResponseAnswerText && answer) {
|
|
||||||
workflowStreamResponse?.({
|
|
||||||
write,
|
|
||||||
event: SseResponseEventEnum.answer,
|
|
||||||
data: textAdaptGptResponse({
|
|
||||||
text: answer
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export async function dispatchDatasetConcat(
|
|||||||
props: DatasetConcatProps
|
props: DatasetConcatProps
|
||||||
): Promise<DatasetConcatResponse> {
|
): Promise<DatasetConcatResponse> {
|
||||||
const {
|
const {
|
||||||
params: { limit = 1500, ...quoteMap }
|
params: { limit = 6000, ...quoteMap }
|
||||||
} = props as DatasetConcatProps;
|
} = props as DatasetConcatProps;
|
||||||
|
|
||||||
const quoteList = Object.values(quoteMap).filter((list) => Array.isArray(list));
|
const quoteList = Object.values(quoteMap).filter((list) => Array.isArray(list));
|
||||||
|
|||||||
@ -55,11 +55,10 @@ export async function dispatchDatasetSearch(
|
|||||||
runningUserInfo: { tmbId },
|
runningUserInfo: { tmbId },
|
||||||
histories,
|
histories,
|
||||||
node,
|
node,
|
||||||
parseQuote = true,
|
|
||||||
params: {
|
params: {
|
||||||
datasets = [],
|
datasets = [],
|
||||||
similarity,
|
similarity,
|
||||||
limit = 1500,
|
limit = 5000,
|
||||||
userChatInput = '',
|
userChatInput = '',
|
||||||
authTmbId = false,
|
authTmbId = false,
|
||||||
collectionFilterMatch,
|
collectionFilterMatch,
|
||||||
@ -114,7 +113,6 @@ export async function dispatchDatasetSearch(
|
|||||||
if (datasetIds.length === 0) {
|
if (datasetIds.length === 0) {
|
||||||
return emptyResult;
|
return emptyResult;
|
||||||
}
|
}
|
||||||
// console.log(concatQueries, rewriteQuery, aiExtensionResult);
|
|
||||||
|
|
||||||
// get vector
|
// get vector
|
||||||
const vectorModel = getEmbeddingModel(
|
const vectorModel = getEmbeddingModel(
|
||||||
@ -267,7 +265,7 @@ export async function dispatchDatasetSearch(
|
|||||||
[DispatchNodeResponseKeyEnum.nodeResponse]: responseData,
|
[DispatchNodeResponseKeyEnum.nodeResponse]: responseData,
|
||||||
nodeDispatchUsages,
|
nodeDispatchUsages,
|
||||||
[DispatchNodeResponseKeyEnum.toolResponses]: {
|
[DispatchNodeResponseKeyEnum.toolResponses]: {
|
||||||
prompt: getDatasetSearchToolResponsePrompt(parseQuote),
|
prompt: getDatasetSearchToolResponsePrompt(),
|
||||||
quotes: searchRes.map((item) => ({
|
quotes: searchRes.map((item) => ({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
sourceName: item.sourceName,
|
sourceName: item.sourceName,
|
||||||
|
|||||||
@ -135,7 +135,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
|||||||
timezone,
|
timezone,
|
||||||
externalProvider,
|
externalProvider,
|
||||||
stream = false,
|
stream = false,
|
||||||
parseQuote = true,
|
retainDatasetCite = true,
|
||||||
version = 'v1',
|
version = 'v1',
|
||||||
responseDetail = true,
|
responseDetail = true,
|
||||||
responseAllData = true,
|
responseAllData = true,
|
||||||
@ -607,7 +607,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
|||||||
timezone,
|
timezone,
|
||||||
externalProvider,
|
externalProvider,
|
||||||
stream,
|
stream,
|
||||||
parseQuote,
|
retainDatasetCite,
|
||||||
node,
|
node,
|
||||||
runtimeNodes,
|
runtimeNodes,
|
||||||
runtimeEdges,
|
runtimeEdges,
|
||||||
|
|||||||
@ -1,10 +1,7 @@
|
|||||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||||
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||||
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
|
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
|
||||||
import {
|
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||||
WorkflowIOValueTypeEnum,
|
|
||||||
NodeOutputKeyEnum
|
|
||||||
} from '@fastgpt/global/core/workflow/constants';
|
|
||||||
import {
|
import {
|
||||||
RuntimeEdgeItemType,
|
RuntimeEdgeItemType,
|
||||||
RuntimeNodeItemType,
|
RuntimeNodeItemType,
|
||||||
@ -34,31 +31,22 @@ export const getWorkflowResponseWrite = ({
|
|||||||
return ({
|
return ({
|
||||||
write,
|
write,
|
||||||
event,
|
event,
|
||||||
data,
|
data
|
||||||
stream
|
|
||||||
}: {
|
}: {
|
||||||
write?: (text: string) => void;
|
write?: (text: string) => void;
|
||||||
event: SseResponseEventEnum;
|
event: SseResponseEventEnum;
|
||||||
data: Record<string, any>;
|
data: Record<string, any>;
|
||||||
stream?: boolean; // Focus set stream response
|
|
||||||
}) => {
|
}) => {
|
||||||
const useStreamResponse = stream ?? streamResponse;
|
const useStreamResponse = streamResponse;
|
||||||
|
|
||||||
if (!res || res.closed || !useStreamResponse) return;
|
if (!res || res.closed || !useStreamResponse) return;
|
||||||
|
|
||||||
// Forbid show detail
|
// Forbid show detail
|
||||||
const detailEvent: Record<string, 1> = {
|
const notDetailEvent: Record<string, 1> = {
|
||||||
[SseResponseEventEnum.error]: 1,
|
[SseResponseEventEnum.answer]: 1,
|
||||||
[SseResponseEventEnum.flowNodeStatus]: 1,
|
[SseResponseEventEnum.fastAnswer]: 1
|
||||||
[SseResponseEventEnum.flowResponses]: 1,
|
|
||||||
[SseResponseEventEnum.interactive]: 1,
|
|
||||||
[SseResponseEventEnum.toolCall]: 1,
|
|
||||||
[SseResponseEventEnum.toolParams]: 1,
|
|
||||||
[SseResponseEventEnum.toolResponse]: 1,
|
|
||||||
[SseResponseEventEnum.updateVariables]: 1,
|
|
||||||
[SseResponseEventEnum.flowNodeResponse]: 1
|
|
||||||
};
|
};
|
||||||
if (!detail && detailEvent[event]) return;
|
if (!detail && !notDetailEvent[event]) return;
|
||||||
|
|
||||||
// Forbid show running status
|
// Forbid show running status
|
||||||
const statusEvent: Record<string, 1> = {
|
const statusEvent: Record<string, 1> = {
|
||||||
|
|||||||
@ -308,7 +308,7 @@
|
|||||||
"key": "limit",
|
"key": "limit",
|
||||||
"renderTypeList": ["hidden"],
|
"renderTypeList": ["hidden"],
|
||||||
"label": "",
|
"label": "",
|
||||||
"value": 1500,
|
"value": 5000,
|
||||||
"valueType": "number"
|
"valueType": "number"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -211,7 +211,7 @@
|
|||||||
"key": "limit",
|
"key": "limit",
|
||||||
"renderTypeList": ["hidden"],
|
"renderTypeList": ["hidden"],
|
||||||
"label": "",
|
"label": "",
|
||||||
"value": 1500,
|
"value": 5000,
|
||||||
"valueType": "number"
|
"valueType": "number"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -21,16 +21,16 @@ import MyBox from '@fastgpt/web/components/common/MyBox';
|
|||||||
import { getCollectionSourceData } from '@fastgpt/global/core/dataset/collection/utils';
|
import { getCollectionSourceData } from '@fastgpt/global/core/dataset/collection/utils';
|
||||||
import Markdown from '.';
|
import Markdown from '.';
|
||||||
import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils';
|
import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
|
|
||||||
const A = ({ children, chatAuthData, ...props }: any) => {
|
const A = ({ children, chatAuthData, showAnimation, ...props }: any) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const content = useMemo(() => String(children), [children]);
|
||||||
|
|
||||||
// empty href link
|
// empty href link
|
||||||
if (!props.href && typeof children?.[0] === 'string') {
|
if (!props.href && typeof children?.[0] === 'string') {
|
||||||
const text = useMemo(() => String(children), [children]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MyTooltip label={t('common:core.chat.markdown.Quick Question')}>
|
<MyTooltip label={t('common:core.chat.markdown.Quick Question')}>
|
||||||
<Button
|
<Button
|
||||||
@ -38,16 +38,23 @@ const A = ({ children, chatAuthData, ...props }: any) => {
|
|||||||
size={'xs'}
|
size={'xs'}
|
||||||
borderRadius={'md'}
|
borderRadius={'md'}
|
||||||
my={1}
|
my={1}
|
||||||
onClick={() => eventBus.emit(EventNameEnum.sendQuestion, { text })}
|
onClick={() => eventBus.emit(EventNameEnum.sendQuestion, { text: content })}
|
||||||
>
|
>
|
||||||
{text}
|
{content}
|
||||||
</Button>
|
</Button>
|
||||||
</MyTooltip>
|
</MyTooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quote
|
// Cite
|
||||||
if (props.href?.startsWith('QUOTE') && typeof children?.[0] === 'string') {
|
if (
|
||||||
|
(props.href?.startsWith('CITE') || props.href?.startsWith('QUOTE')) &&
|
||||||
|
typeof children?.[0] === 'string'
|
||||||
|
) {
|
||||||
|
if (!Types.ObjectId.isValid(content)) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: quoteData,
|
data: quoteData,
|
||||||
loading,
|
loading,
|
||||||
@ -74,6 +81,7 @@ const A = ({ children, chatAuthData, ...props }: any) => {
|
|||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onOpen={() => {
|
onOpen={() => {
|
||||||
onOpen();
|
onOpen();
|
||||||
|
if (showAnimation) return;
|
||||||
getQuoteDataById(String(children));
|
getQuoteDataById(String(children));
|
||||||
}}
|
}}
|
||||||
trigger={'hover'}
|
trigger={'hover'}
|
||||||
@ -90,7 +98,7 @@ const A = ({ children, chatAuthData, ...props }: any) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent boxShadow={'lg'} w={'500px'} maxW={'90vw'} py={4}>
|
<PopoverContent boxShadow={'lg'} w={'500px'} maxW={'90vw'} py={4}>
|
||||||
<MyBox isLoading={loading}>
|
<MyBox isLoading={loading || showAnimation}>
|
||||||
<PopoverArrow />
|
<PopoverArrow />
|
||||||
<PopoverBody py={0} px={0} fontSize={'sm'}>
|
<PopoverBody py={0} px={0} fontSize={'sm'}>
|
||||||
<Flex px={4} pb={1} justifyContent={'space-between'}>
|
<Flex px={4} pb={1} justifyContent={'space-between'}>
|
||||||
|
|||||||
@ -60,9 +60,9 @@ const MarkdownRender = ({
|
|||||||
img: Image,
|
img: Image,
|
||||||
pre: RewritePre,
|
pre: RewritePre,
|
||||||
code: Code,
|
code: Code,
|
||||||
a: (props: any) => <A {...props} chatAuthData={chatAuthData} />
|
a: (props: any) => <A {...props} showAnimation={showAnimation} chatAuthData={chatAuthData} />
|
||||||
};
|
};
|
||||||
}, [chatAuthData]);
|
}, [chatAuthData, showAnimation]);
|
||||||
|
|
||||||
const formatSource = useMemo(() => {
|
const formatSource = useMemo(() => {
|
||||||
if (showAnimation || forbidZhFormat) return source;
|
if (showAnimation || forbidZhFormat) return source;
|
||||||
|
|||||||
@ -27,14 +27,14 @@ export const mdTextFormat = (text: string) => {
|
|||||||
return match;
|
return match;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 处理 [quote:id] 格式引用,将 [quote:675934a198f46329dfc6d05a] 转换为 [675934a198f46329dfc6d05a](QUOTE)
|
// 处理 [quote:id] 格式引用,将 [quote:675934a198f46329dfc6d05a] 转换为 [675934a198f46329dfc6d05a](CITE)
|
||||||
text = text
|
text = text
|
||||||
// .replace(
|
// .replace(
|
||||||
// /([\u4e00-\u9fa5\u3000-\u303f])([a-zA-Z0-9])|([a-zA-Z0-9])([\u4e00-\u9fa5\u3000-\u303f])/g,
|
// /([\u4e00-\u9fa5\u3000-\u303f])([a-zA-Z0-9])|([a-zA-Z0-9])([\u4e00-\u9fa5\u3000-\u303f])/g,
|
||||||
// '$1$3 $2$4'
|
// '$1$3 $2$4'
|
||||||
// )
|
// )
|
||||||
// 处理 格式引用,将 [675934a198f46329dfc6d05a] 转换为 [675934a198f46329dfc6d05a](QUOTE)
|
// 处理 格式引用,将 [675934a198f46329dfc6d05a] 转换为 [675934a198f46329dfc6d05a](CITE)
|
||||||
.replace(/\[([a-f0-9]{24})\](?!\()/g, '[$1](QUOTE)');
|
.replace(/\[([a-f0-9]{24})\](?!\()/g, '[$1](CITE)');
|
||||||
|
|
||||||
// 处理链接后的中文标点符号,增加空格
|
// 处理链接后的中文标点符号,增加空格
|
||||||
text = text.replace(/(https?:\/\/[^\s,。!?;:、]+)([,。!?;:、])/g, '$1 $2');
|
text = text.replace(/(https?:\/\/[^\s,。!?;:、]+)([,。!?;:、])/g, '$1 $2');
|
||||||
|
|||||||
@ -240,11 +240,6 @@ const ChatItem = (props: Props) => {
|
|||||||
quoteId?: string;
|
quoteId?: string;
|
||||||
}) => {
|
}) => {
|
||||||
if (!setQuoteData) return;
|
if (!setQuoteData) return;
|
||||||
if (isChatting)
|
|
||||||
return toast({
|
|
||||||
title: t('chat:chat.waiting_for_response'),
|
|
||||||
status: 'info'
|
|
||||||
});
|
|
||||||
|
|
||||||
const collectionIdList = collectionId
|
const collectionIdList = collectionId
|
||||||
? [collectionId]
|
? [collectionId]
|
||||||
@ -277,18 +272,7 @@ const ChatItem = (props: Props) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[
|
[setQuoteData, quoteList, isShowReadRawSource, appId, chatId, chat.dataId, outLinkAuthData]
|
||||||
setQuoteData,
|
|
||||||
isChatting,
|
|
||||||
toast,
|
|
||||||
t,
|
|
||||||
quoteList,
|
|
||||||
isShowReadRawSource,
|
|
||||||
appId,
|
|
||||||
chatId,
|
|
||||||
chat.dataId,
|
|
||||||
outLinkAuthData
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -96,8 +96,6 @@ const RenderText = React.memo(function RenderText({
|
|||||||
text: string;
|
text: string;
|
||||||
chatItemDataId: string;
|
chatItemDataId: string;
|
||||||
}) {
|
}) {
|
||||||
const isResponseDetail = useContextSelector(ChatItemContext, (v) => v.isResponseDetail);
|
|
||||||
|
|
||||||
const appId = useContextSelector(ChatBoxContext, (v) => v.appId);
|
const appId = useContextSelector(ChatBoxContext, (v) => v.appId);
|
||||||
const chatId = useContextSelector(ChatBoxContext, (v) => v.chatId);
|
const chatId = useContextSelector(ChatBoxContext, (v) => v.chatId);
|
||||||
const outLinkAuthData = useContextSelector(ChatBoxContext, (v) => v.outLinkAuthData);
|
const outLinkAuthData = useContextSelector(ChatBoxContext, (v) => v.outLinkAuthData);
|
||||||
@ -106,10 +104,8 @@ const RenderText = React.memo(function RenderText({
|
|||||||
if (!text) return '';
|
if (!text) return '';
|
||||||
|
|
||||||
// Remove quote references if not showing response detail
|
// Remove quote references if not showing response detail
|
||||||
return isResponseDetail
|
return text;
|
||||||
? text
|
}, [text]);
|
||||||
: text.replace(/\[([a-f0-9]{24})\]\(QUOTE\)/g, '').replace(/\[([a-f0-9]{24})\](?!\()/g, '');
|
|
||||||
}, [text, isResponseDetail]);
|
|
||||||
|
|
||||||
const chatAuthData = useCreation(() => {
|
const chatAuthData = useCreation(() => {
|
||||||
return { appId, chatId, chatItemDataId, ...outLinkAuthData };
|
return { appId, chatId, chatItemDataId, ...outLinkAuthData };
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { getWebLLMModel } from '@/web/common/system/utils';
|
|||||||
const SearchParamsTip = ({
|
const SearchParamsTip = ({
|
||||||
searchMode,
|
searchMode,
|
||||||
similarity = 0,
|
similarity = 0,
|
||||||
limit = 1500,
|
limit = 5000,
|
||||||
responseEmptyText,
|
responseEmptyText,
|
||||||
usingReRank = false,
|
usingReRank = false,
|
||||||
datasetSearchUsingExtensionQuery,
|
datasetSearchUsingExtensionQuery,
|
||||||
|
|||||||
@ -5,8 +5,8 @@ import { useTranslation } from 'next-i18next';
|
|||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
import { updatePasswordByOld } from '@/web/support/user/api';
|
import { updatePasswordByOld } from '@/web/support/user/api';
|
||||||
import { checkPasswordRule } from '@/web/support/user/login/constants';
|
|
||||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||||
|
import { checkPasswordRule } from '@fastgpt/global/common/string/password';
|
||||||
|
|
||||||
type FormType = {
|
type FormType = {
|
||||||
oldPsw: string;
|
oldPsw: string;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React, { Dispatch } from 'react';
|
import React, { Dispatch } from 'react';
|
||||||
import { FormControl, Box, Input, Button } from '@chakra-ui/react';
|
import { FormControl, Box, Input, Button } from '@chakra-ui/react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { LoginPageTypeEnum, checkPasswordRule } from '@/web/support/user/login/constants';
|
import { LoginPageTypeEnum } from '@/web/support/user/login/constants';
|
||||||
import { postFindPassword } from '@/web/support/user/api';
|
import { postFindPassword } from '@/web/support/user/api';
|
||||||
import { useSendCode } from '@/web/support/user/hooks/useSendCode';
|
import { useSendCode } from '@/web/support/user/hooks/useSendCode';
|
||||||
import type { ResLogin } from '@/global/support/api/userRes.d';
|
import type { ResLogin } from '@/global/support/api/userRes.d';
|
||||||
@ -9,6 +9,7 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
|
|||||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
|
import { checkPasswordRule } from '@fastgpt/global/common/string/password';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
setPageType: Dispatch<`${LoginPageTypeEnum}`>;
|
setPageType: Dispatch<`${LoginPageTypeEnum}`>;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React, { Dispatch } from 'react';
|
import React, { Dispatch } from 'react';
|
||||||
import { FormControl, Box, Input, Button } from '@chakra-ui/react';
|
import { FormControl, Box, Input, Button } from '@chakra-ui/react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { LoginPageTypeEnum, checkPasswordRule } from '@/web/support/user/login/constants';
|
import { LoginPageTypeEnum } from '@/web/support/user/login/constants';
|
||||||
import { postRegister } from '@/web/support/user/api';
|
import { postRegister } from '@/web/support/user/api';
|
||||||
import { useSendCode } from '@/web/support/user/hooks/useSendCode';
|
import { useSendCode } from '@/web/support/user/hooks/useSendCode';
|
||||||
import type { ResLogin } from '@/global/support/api/userRes';
|
import type { ResLogin } from '@/global/support/api/userRes';
|
||||||
@ -19,6 +19,7 @@ import {
|
|||||||
getSourceDomain,
|
getSourceDomain,
|
||||||
removeFastGPTSem
|
removeFastGPTSem
|
||||||
} from '@/web/support/marketing/utils';
|
} from '@/web/support/marketing/utils';
|
||||||
|
import { checkPasswordRule } from '@fastgpt/global/common/string/password';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
loginSuccess: (e: ResLogin) => void;
|
loginSuccess: (e: ResLogin) => void;
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import { reRankRecall } from '@fastgpt/service/core/ai/rerank';
|
|||||||
import { aiTranscriptions } from '@fastgpt/service/core/ai/audio/transcriptions';
|
import { aiTranscriptions } from '@fastgpt/service/core/ai/audio/transcriptions';
|
||||||
import { isProduction } from '@fastgpt/global/common/system/constants';
|
import { isProduction } from '@fastgpt/global/common/system/constants';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import { llmCompletionsBodyFormat, llmResponseToAnswerText } from '@fastgpt/service/core/ai/utils';
|
import { llmCompletionsBodyFormat, formatLLMResponse } from '@fastgpt/service/core/ai/utils';
|
||||||
|
|
||||||
export type testQuery = { model: string; channelId?: number };
|
export type testQuery = { model: string; channelId?: number };
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ const testLLMModel = async (model: LLMModelItemType, headers: Record<string, str
|
|||||||
model
|
model
|
||||||
);
|
);
|
||||||
|
|
||||||
const { response, isStreamResponse } = await createChatCompletion({
|
const { response } = await createChatCompletion({
|
||||||
modelData: model,
|
modelData: model,
|
||||||
body: requestBody,
|
body: requestBody,
|
||||||
options: {
|
options: {
|
||||||
@ -88,7 +88,7 @@ const testLLMModel = async (model: LLMModelItemType, headers: Record<string, str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const { text: answer } = await llmResponseToAnswerText(response);
|
const { text: answer } = await formatLLMResponse(response);
|
||||||
|
|
||||||
if (answer) {
|
if (answer) {
|
||||||
return answer;
|
return answer;
|
||||||
|
|||||||
@ -9,7 +9,10 @@ import { authChatCrud } from '@/service/support/permission/auth/chat';
|
|||||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||||
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
|
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
|
||||||
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||||
import { filterPublicNodeResponseData } from '@fastgpt/global/core/chat/utils';
|
import {
|
||||||
|
filterPublicNodeResponseData,
|
||||||
|
removeAIResponseCite
|
||||||
|
} from '@fastgpt/global/core/chat/utils';
|
||||||
import { GetChatTypeEnum } from '@/global/core/chat/constants';
|
import { GetChatTypeEnum } from '@/global/core/chat/constants';
|
||||||
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
||||||
import { ChatItemType } from '@fastgpt/global/core/chat/type';
|
import { ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||||
@ -83,6 +86,13 @@ async function handler(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (!responseDetail) {
|
||||||
|
histories.forEach((item) => {
|
||||||
|
if (item.obj === ChatRoleEnum.AI) {
|
||||||
|
item.value = removeAIResponseCite(item.value, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
list: isPlugin ? histories : transformPreviewHistories(histories, responseDetail),
|
list: isPlugin ? histories : transformPreviewHistories(histories, responseDetail),
|
||||||
|
|||||||
@ -19,7 +19,7 @@ async function handler(req: ApiRequestProps<SearchTestProps>): Promise<SearchTes
|
|||||||
const {
|
const {
|
||||||
datasetId,
|
datasetId,
|
||||||
text,
|
text,
|
||||||
limit = 1500,
|
limit = 5000,
|
||||||
similarity,
|
similarity,
|
||||||
searchMode,
|
searchMode,
|
||||||
embeddingWeight,
|
embeddingWeight,
|
||||||
|
|||||||
@ -30,6 +30,7 @@ import {
|
|||||||
concatHistories,
|
concatHistories,
|
||||||
filterPublicNodeResponseData,
|
filterPublicNodeResponseData,
|
||||||
getChatTitleFromChatMessage,
|
getChatTitleFromChatMessage,
|
||||||
|
removeAIResponseCite,
|
||||||
removeEmptyUserInput
|
removeEmptyUserInput
|
||||||
} from '@fastgpt/global/core/chat/utils';
|
} from '@fastgpt/global/core/chat/utils';
|
||||||
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
|
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
|
||||||
@ -74,7 +75,7 @@ export type Props = ChatCompletionCreateParams &
|
|||||||
responseChatItemId?: string;
|
responseChatItemId?: string;
|
||||||
stream?: boolean;
|
stream?: boolean;
|
||||||
detail?: boolean;
|
detail?: boolean;
|
||||||
parseQuote?: boolean;
|
retainDatasetCite?: boolean;
|
||||||
variables: Record<string, any>; // Global variables or plugin inputs
|
variables: Record<string, any>; // Global variables or plugin inputs
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -107,7 +108,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
|
|
||||||
stream = false,
|
stream = false,
|
||||||
detail = false,
|
detail = false,
|
||||||
parseQuote = false,
|
retainDatasetCite = false,
|
||||||
messages = [],
|
messages = [],
|
||||||
variables = {},
|
variables = {},
|
||||||
responseChatItemId = getNanoid(),
|
responseChatItemId = getNanoid(),
|
||||||
@ -187,6 +188,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
chatId
|
chatId
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
retainDatasetCite = retainDatasetCite && !!responseDetail;
|
||||||
const isPlugin = app.type === AppTypeEnum.plugin;
|
const isPlugin = app.type === AppTypeEnum.plugin;
|
||||||
|
|
||||||
// Check message type
|
// Check message type
|
||||||
@ -291,7 +293,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
chatConfig,
|
chatConfig,
|
||||||
histories: newHistories,
|
histories: newHistories,
|
||||||
stream,
|
stream,
|
||||||
parseQuote,
|
retainDatasetCite,
|
||||||
maxRunTimes: WORKFLOW_MAX_RUN_TIMES,
|
maxRunTimes: WORKFLOW_MAX_RUN_TIMES,
|
||||||
workflowStreamResponse: workflowResponseWrite
|
workflowStreamResponse: workflowResponseWrite
|
||||||
});
|
});
|
||||||
@ -406,17 +408,18 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
|
|
||||||
return assistantResponses;
|
return assistantResponses;
|
||||||
})();
|
})();
|
||||||
|
const formatResponseContent = removeAIResponseCite(responseContent, retainDatasetCite);
|
||||||
const error = flowResponses[flowResponses.length - 1]?.error;
|
const error = flowResponses[flowResponses.length - 1]?.error;
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
...(detail ? { responseData: feResponseData, newVariables } : {}),
|
...(detail ? { responseData: feResponseData, newVariables } : {}),
|
||||||
error,
|
error,
|
||||||
id: chatId || '',
|
id: saveChatId,
|
||||||
model: '',
|
model: '',
|
||||||
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 1 },
|
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 1 },
|
||||||
choices: [
|
choices: [
|
||||||
{
|
{
|
||||||
message: { role: 'assistant', content: responseContent },
|
message: { role: 'assistant', content: formatResponseContent },
|
||||||
finish_reason: 'stop',
|
finish_reason: 'stop',
|
||||||
index: 0
|
index: 0
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,6 +30,7 @@ import {
|
|||||||
concatHistories,
|
concatHistories,
|
||||||
filterPublicNodeResponseData,
|
filterPublicNodeResponseData,
|
||||||
getChatTitleFromChatMessage,
|
getChatTitleFromChatMessage,
|
||||||
|
removeAIResponseCite,
|
||||||
removeEmptyUserInput
|
removeEmptyUserInput
|
||||||
} from '@fastgpt/global/core/chat/utils';
|
} from '@fastgpt/global/core/chat/utils';
|
||||||
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
|
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
|
||||||
@ -74,7 +75,7 @@ export type Props = ChatCompletionCreateParams &
|
|||||||
responseChatItemId?: string;
|
responseChatItemId?: string;
|
||||||
stream?: boolean;
|
stream?: boolean;
|
||||||
detail?: boolean;
|
detail?: boolean;
|
||||||
parseQuote?: boolean;
|
retainDatasetCite?: boolean;
|
||||||
variables: Record<string, any>; // Global variables or plugin inputs
|
variables: Record<string, any>; // Global variables or plugin inputs
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -107,7 +108,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
|
|
||||||
stream = false,
|
stream = false,
|
||||||
detail = false,
|
detail = false,
|
||||||
parseQuote = false,
|
retainDatasetCite = false,
|
||||||
messages = [],
|
messages = [],
|
||||||
variables = {},
|
variables = {},
|
||||||
responseChatItemId = getNanoid(),
|
responseChatItemId = getNanoid(),
|
||||||
@ -187,6 +188,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
chatId
|
chatId
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
retainDatasetCite = retainDatasetCite && !!responseDetail;
|
||||||
const isPlugin = app.type === AppTypeEnum.plugin;
|
const isPlugin = app.type === AppTypeEnum.plugin;
|
||||||
|
|
||||||
// Check message type
|
// Check message type
|
||||||
@ -290,7 +292,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
chatConfig,
|
chatConfig,
|
||||||
histories: newHistories,
|
histories: newHistories,
|
||||||
stream,
|
stream,
|
||||||
parseQuote,
|
retainDatasetCite,
|
||||||
maxRunTimes: WORKFLOW_MAX_RUN_TIMES,
|
maxRunTimes: WORKFLOW_MAX_RUN_TIMES,
|
||||||
workflowStreamResponse: workflowResponseWrite,
|
workflowStreamResponse: workflowResponseWrite,
|
||||||
version: 'v2',
|
version: 'v2',
|
||||||
@ -401,6 +403,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
|
|
||||||
return assistantResponses;
|
return assistantResponses;
|
||||||
})();
|
})();
|
||||||
|
const formatResponseContent = removeAIResponseCite(responseContent, retainDatasetCite);
|
||||||
const error = flowResponses[flowResponses.length - 1]?.error;
|
const error = flowResponses[flowResponses.length - 1]?.error;
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
@ -411,7 +414,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 1 },
|
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 1 },
|
||||||
choices: [
|
choices: [
|
||||||
{
|
{
|
||||||
message: { role: 'assistant', content: responseContent },
|
message: { role: 'assistant', content: formatResponseContent },
|
||||||
finish_reason: 'stop',
|
finish_reason: 'stop',
|
||||||
index: 0
|
index: 0
|
||||||
}
|
}
|
||||||
|
|||||||
@ -87,6 +87,7 @@ const OutLink = (props: Props) => {
|
|||||||
const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData);
|
const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData);
|
||||||
const quoteData = useContextSelector(ChatItemContext, (v) => v.quoteData);
|
const quoteData = useContextSelector(ChatItemContext, (v) => v.quoteData);
|
||||||
const setQuoteData = useContextSelector(ChatItemContext, (v) => v.setQuoteData);
|
const setQuoteData = useContextSelector(ChatItemContext, (v) => v.setQuoteData);
|
||||||
|
const isResponseDetail = useContextSelector(ChatItemContext, (v) => v.isResponseDetail);
|
||||||
|
|
||||||
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
||||||
const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount);
|
const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount);
|
||||||
@ -162,7 +163,8 @@ const OutLink = (props: Props) => {
|
|||||||
},
|
},
|
||||||
responseChatItemId,
|
responseChatItemId,
|
||||||
chatId: completionChatId,
|
chatId: completionChatId,
|
||||||
...outLinkAuthData
|
...outLinkAuthData,
|
||||||
|
retainDatasetCite: isResponseDetail
|
||||||
},
|
},
|
||||||
onMessage: generatingMessage,
|
onMessage: generatingMessage,
|
||||||
abortCtrl: controller
|
abortCtrl: controller
|
||||||
@ -200,6 +202,7 @@ const OutLink = (props: Props) => {
|
|||||||
chatId,
|
chatId,
|
||||||
customVariables,
|
customVariables,
|
||||||
outLinkAuthData,
|
outLinkAuthData,
|
||||||
|
isResponseDetail,
|
||||||
onUpdateHistoryTitle,
|
onUpdateHistoryTitle,
|
||||||
setChatBoxData,
|
setChatBoxData,
|
||||||
forbidLoadChat,
|
forbidLoadChat,
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import {
|
|||||||
} from '@fastgpt/service/common/string/tiktoken/index';
|
} from '@fastgpt/service/common/string/tiktoken/index';
|
||||||
import { pushDataListToTrainingQueueByCollectionId } from '@fastgpt/service/core/dataset/training/controller';
|
import { pushDataListToTrainingQueueByCollectionId } from '@fastgpt/service/core/dataset/training/controller';
|
||||||
import { loadRequestMessages } from '@fastgpt/service/core/chat/utils';
|
import { loadRequestMessages } from '@fastgpt/service/core/chat/utils';
|
||||||
import { llmCompletionsBodyFormat, llmResponseToAnswerText } from '@fastgpt/service/core/ai/utils';
|
import { llmCompletionsBodyFormat, formatLLMResponse } from '@fastgpt/service/core/ai/utils';
|
||||||
import { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
import { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
||||||
import {
|
import {
|
||||||
chunkAutoChunkSize,
|
chunkAutoChunkSize,
|
||||||
@ -140,7 +140,7 @@ ${replaceVariable(Prompt_AgentQA.fixedText, { text })}`;
|
|||||||
modelData
|
modelData
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
const { text: answer, usage } = await llmResponseToAnswerText(chatResponse);
|
const { text: answer, usage } = await formatLLMResponse(chatResponse);
|
||||||
const inputTokens = usage?.prompt_tokens || (await countGptMessagesTokens(messages));
|
const inputTokens = usage?.prompt_tokens || (await countGptMessagesTokens(messages));
|
||||||
const outputTokens = usage?.completion_tokens || (await countPromptTokens(answer));
|
const outputTokens = usage?.completion_tokens || (await countPromptTokens(answer));
|
||||||
|
|
||||||
|
|||||||
@ -37,6 +37,7 @@ import { saveChat } from '@fastgpt/service/core/chat/saveChat';
|
|||||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||||
import { createChatUsage } from '@fastgpt/service/support/wallet/usage/controller';
|
import { createChatUsage } from '@fastgpt/service/support/wallet/usage/controller';
|
||||||
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
|
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
|
||||||
|
import { removeDatasetCiteText } from '@fastgpt/service/core/ai/utils';
|
||||||
|
|
||||||
export const pluginNodes2InputSchema = (
|
export const pluginNodes2InputSchema = (
|
||||||
nodes: { flowNodeType: FlowNodeTypeEnum; inputs: FlowNodeInputItemType[] }[]
|
nodes: { flowNodeType: FlowNodeTypeEnum; inputs: FlowNodeInputItemType[] }[]
|
||||||
@ -288,7 +289,7 @@ export const callMcpServerTool = async ({ key, toolName, inputs }: toolCallProps
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
// Format response content
|
// Format response content
|
||||||
responseContent = responseContent.trim().replace(/\[\w+\]\(QUOTE\)/g, '');
|
responseContent = removeDatasetCiteText(responseContent.trim(), false);
|
||||||
|
|
||||||
return responseContent;
|
return responseContent;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -132,7 +132,7 @@ export const streamFetch = ({
|
|||||||
variables,
|
variables,
|
||||||
detail: true,
|
detail: true,
|
||||||
stream: true,
|
stream: true,
|
||||||
parseQuote: true
|
retainDatasetCite: data.retainDatasetCite ?? true
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -4,22 +4,3 @@ export enum LoginPageTypeEnum {
|
|||||||
forgetPassword = 'forgetPassword',
|
forgetPassword = 'forgetPassword',
|
||||||
wechat = 'wechat'
|
wechat = 'wechat'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const checkPasswordRule = (password: string) => {
|
|
||||||
const patterns = [
|
|
||||||
/\d/, // Contains digits
|
|
||||||
/[a-z]/, // Contains lowercase letters
|
|
||||||
/[A-Z]/, // Contains uppercase letters
|
|
||||||
/[!@#$%^&*()_+=-]/ // Contains special characters
|
|
||||||
];
|
|
||||||
const validChars = /^[\dA-Za-z!@#$%^&*()_+=-]{6,100}$/;
|
|
||||||
|
|
||||||
// Check length and valid characters
|
|
||||||
if (!validChars.test(password)) return false;
|
|
||||||
|
|
||||||
// Count how many patterns are satisfied
|
|
||||||
const matchCount = patterns.filter((pattern) => pattern.test(password)).length;
|
|
||||||
|
|
||||||
// Must satisfy at least 2 patterns
|
|
||||||
return matchCount >= 2;
|
|
||||||
};
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ describe('Markdown utils', () => {
|
|||||||
|
|
||||||
it('should convert quote references to proper markdown links', () => {
|
it('should convert quote references to proper markdown links', () => {
|
||||||
const input = '[123456789012345678901234]';
|
const input = '[123456789012345678901234]';
|
||||||
const expected = '[123456789012345678901234](QUOTE)';
|
const expected = '[123456789012345678901234](CITE)';
|
||||||
expect(mdTextFormat(input)).toBe(expected);
|
expect(mdTextFormat(input)).toBe(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ describe('Markdown utils', () => {
|
|||||||
const input =
|
const input =
|
||||||
'Math \\[x^2\\] with link https://test.com,and quote [123456789012345678901234]';
|
'Math \\[x^2\\] with link https://test.com,and quote [123456789012345678901234]';
|
||||||
const expected =
|
const expected =
|
||||||
'Math $$x^2$$ with link https://test.com ,and quote [123456789012345678901234](QUOTE)';
|
'Math $$x^2$$ with link https://test.com ,and quote [123456789012345678901234](CITE)';
|
||||||
expect(mdTextFormat(input)).toBe(expected);
|
expect(mdTextFormat(input)).toBe(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,5 +0,0 @@
|
|||||||
[
|
|
||||||
"测试的呀,第一个表格\n\n| 序号 | 姓名 | 年龄 | 职业 | 城市 |\n| --- | --- | --- | --- | --- |\n| 1 | 张三 | 25 | 工程师 | 北京 |\n| 2 | 李四 | 30 | 教师 | 上海 |\n| 3 | 王五 | 28 | 医生 | 广州 |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 4 | 赵六 | 35 | 律师 | 深圳 |\n| 5 | 孙七 | 27 | 设计师 | 杭州 |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 7 | 吴九 | 29 | 销售 | 武汉 |\n| 8 | 郑十 | 31 | 记者 | 南京 |\n| 9 | 刘一 | 33 | 建筑师 | 天津 |\n| 10 | 陈二 | 26 | 程序员 | 重庆 |\n| 1000 | 黄末 | 28 | 作家 | 厦门 |\n| 1001 | 杨一 | 34 | 程序员 | 厦门 |\n| 1002 | 杨二 | 34 | 程序员 | 厦门 |\n| 1003 | 杨三 | 34 | 程序员 | 厦门 |",
|
|
||||||
"| 序号 | 姓名 | 年龄 | 职业 | 城市 |\n| --- | --- | --- | --- | --- |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 1004 | 杨四 | 34 | 程序员 | 厦门 |\n| 1005 | 杨五 | 34 | 程序员 | 厦门 |\n| 1000 | 黄末 | 28 | 作家 | 厦门 |\n| 1000 | 黄末 | 28 | 作家 | 厦门 |\n| 1000 | 黄末 | 28 | 作家 | 厦门 |\n| 9 | 刘一 | 33 | 建筑师 | 天津 |\n| 10 | 陈二 | 26 | 程序员 | 重庆 |\n| 1000 | 黄末 | 28 | 作家 | 厦门 |\n| 1001 | 杨一 | 34 | 程序员 | 厦门 |\n| 1002 | 杨二 | 34 | 程序员 | 厦门 |\n| 1003 | 杨三 | 34 | 程序员 | 厦门 |\n| 1004 | 杨四 | 34 | 程序员 | 厦门 |\n| 1005 | 杨五 | 34 | 程序员 | 厦门 |\n\n| 序号 | 姓名 | 年龄 | 职业 | 城市 |\n| --- | --- | --- | --- | --- |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 1000 | 黄末 | 28 | 作家 | 厦门 |\n| 1000 | 黄末 | 28 | 作家 | 厦门 |\n| 1000 | 黄末 | 28 | 作家 | 厦门 |",
|
|
||||||
"这是第二段了,第二表格\n\n| 序号 | 姓名 | 年龄 | 职业 | 城市 |\n| --- | --- | --- | --- | --- |\n| 1 | 张三 | 25 | 工程师 | 北京 |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 2 | 李四 | 30 | 教师 | 上海 |\n| 3 | 王五 | 28 | 医生 | 广州 |\n| 4 | 赵六 | 35 | 律师 | 深圳 |\n| 5 | 孙七 | 27 | 设计师 | 杭州 |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 7 | 吴九 | 29 | 销售 | 武汉 |\n| 8 | 郑十 | 31 | 记者 | 南京 |\n| 9 | 刘一 | 33 | 建筑师 | 天津 |\n| 10 | 陈二 | 26 | 程序员 | 重庆 |\n| 10004 | 黄末 | 28 | 作家 | 厦门 |\n| 10013 | 杨一 | 34 | 程序员 | 厦门 |\n\n\n结束了\n\n| 序号22 | 姓名 | 年龄 | 职业 | 城市 |\n| --- | --- | --- | --- | --- |\n| 1 | 张三 | 25 | 工程师 | 北京 |\n| 2 | 李四 | 30 | 教师 | 上海 |\n| 3 | 王五 | 28 | 医生 | 广州 |\n| 4 | 赵六 | 35 | 律师 | 深圳 |\n| 5 | 孙七 | 27 | 设计师 | 杭州 |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 7 | 吴九 | 29 | 销售 | 武汉 |\n| 8 | 郑十 | 31 | 记者 | 南京 |\n| 9 | 刘一 | 33 | 建筑师 | 天津 |\n| 10 | 陈二 | 26 | 程序员 | 重庆 |\n| 10002 | 黄末 | 28 | 作家 | 厦门 |\n| 10012 | 杨一 | 34 | 程序员 | 厦门 |"
|
|
||||||
]
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { checkPasswordRule } from '@/web/support/user/login/constants';
|
import { checkPasswordRule } from '@fastgpt/global/common/string/password';
|
||||||
|
|
||||||
describe('PasswordRule', () => {
|
describe('PasswordRule', () => {
|
||||||
it('should be a valid password', () => {
|
it('should be a valid password', () => {
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import { it, expect } from 'vitest'; // 必须显式导入
|
import { it, expect } from 'vitest'; // 必须显式导入
|
||||||
import { splitText2Chunks } from '@fastgpt/global/common/string/textSplitter';
|
import { splitText2Chunks } from '@fastgpt/global/common/string/textSplitter';
|
||||||
import * as fs from 'fs';
|
|
||||||
|
|
||||||
const simpleChunks = (chunks: string[]) => {
|
const simpleChunks = (chunks: string[]) => {
|
||||||
return chunks.map((chunk) => chunk.replace(/\s+/g, ''));
|
return chunks.map((chunk) => chunk.replace(/\s+/g, ''));
|
||||||
|
|||||||
@ -0,0 +1,340 @@
|
|||||||
|
import { CompletionFinishReason } from '@fastgpt/global/core/ai/type';
|
||||||
|
import { parseLLMStreamResponse } from '@fastgpt/service/core/ai/utils';
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
describe('Parse reasoning stream content test', async () => {
|
||||||
|
const partList = [
|
||||||
|
{
|
||||||
|
data: [{ content: '你好1' }, { content: '你好2' }, { content: '你好3' }],
|
||||||
|
correct: { answer: '你好1你好2你好3', reasoning: '' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: [
|
||||||
|
{ reasoning_content: '这是' },
|
||||||
|
{ reasoning_content: '思考' },
|
||||||
|
{ reasoning_content: '过程' },
|
||||||
|
{ content: '你好1' },
|
||||||
|
{ content: '你好2' },
|
||||||
|
{ content: '你好3' }
|
||||||
|
],
|
||||||
|
correct: { answer: '你好1你好2你好3', reasoning: '这是思考过程' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: [
|
||||||
|
{ content: '<t' },
|
||||||
|
{ content: 'hink>' },
|
||||||
|
{ content: '这是' },
|
||||||
|
{ content: '思考' },
|
||||||
|
{ content: '过程' },
|
||||||
|
{ content: '</think>' },
|
||||||
|
{ content: '你好1' },
|
||||||
|
{ content: '你好2' },
|
||||||
|
{ content: '你好3' }
|
||||||
|
],
|
||||||
|
correct: { answer: '你好1你好2你好3', reasoning: '这是思考过程' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: [
|
||||||
|
{ content: '<think>' },
|
||||||
|
{ content: '这是' },
|
||||||
|
{ content: '思考' },
|
||||||
|
{ content: '过程' },
|
||||||
|
{ content: '</think>' },
|
||||||
|
{ content: '你好1' },
|
||||||
|
{ content: '你好2' },
|
||||||
|
{ content: '你好3' }
|
||||||
|
],
|
||||||
|
correct: { answer: '你好1你好2你好3', reasoning: '这是思考过程' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: [
|
||||||
|
{ content: '<think>这是' },
|
||||||
|
{ content: '思考' },
|
||||||
|
{ content: '过程' },
|
||||||
|
{ content: '</think>' },
|
||||||
|
{ content: '你好1' },
|
||||||
|
{ content: '你好2' },
|
||||||
|
{ content: '你好3' }
|
||||||
|
],
|
||||||
|
correct: { answer: '你好1你好2你好3', reasoning: '这是思考过程' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: [
|
||||||
|
{ content: '<think>这是' },
|
||||||
|
{ content: '思考' },
|
||||||
|
{ content: '过程</' },
|
||||||
|
{ content: 'think>' },
|
||||||
|
{ content: '你好1' },
|
||||||
|
{ content: '你好2' },
|
||||||
|
{ content: '你好3' }
|
||||||
|
],
|
||||||
|
correct: { answer: '你好1你好2你好3', reasoning: '这是思考过程' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: [
|
||||||
|
{ content: '<think>这是' },
|
||||||
|
{ content: '思考' },
|
||||||
|
{ content: '过程</think>' },
|
||||||
|
{ content: '你好1' },
|
||||||
|
{ content: '你好2' },
|
||||||
|
{ content: '你好3' }
|
||||||
|
],
|
||||||
|
correct: { answer: '你好1你好2你好3', reasoning: '这是思考过程' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: [
|
||||||
|
{ content: '<think>这是' },
|
||||||
|
{ content: '思考' },
|
||||||
|
{ content: '过程</think>你好1' },
|
||||||
|
{ content: '你好2' },
|
||||||
|
{ content: '你好3' }
|
||||||
|
],
|
||||||
|
correct: { answer: '你好1你好2你好3', reasoning: '这是思考过程' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: [
|
||||||
|
{ content: '<think>这是' },
|
||||||
|
{ content: '思考' },
|
||||||
|
{ content: '过程</th' },
|
||||||
|
{ content: '假的' },
|
||||||
|
{ content: '你好2' },
|
||||||
|
{ content: '你好3' },
|
||||||
|
{ content: '过程</think>你好1' },
|
||||||
|
{ content: '你好2' },
|
||||||
|
{ content: '你好3' }
|
||||||
|
],
|
||||||
|
correct: { answer: '你好1你好2你好3', reasoning: '这是思考过程</th假的你好2你好3过程' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: [
|
||||||
|
{ content: '<think>这是' },
|
||||||
|
{ content: '思考' },
|
||||||
|
{ content: '过程</th' },
|
||||||
|
{ content: '假的' },
|
||||||
|
{ content: '你好2' },
|
||||||
|
{ content: '你好3' }
|
||||||
|
],
|
||||||
|
correct: { answer: '', reasoning: '这是思考过程</th假的你好2你好3' }
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Remove think
|
||||||
|
partList.forEach((part, index) => {
|
||||||
|
it(`Reasoning test:${index}`, () => {
|
||||||
|
const { parsePart } = parseLLMStreamResponse();
|
||||||
|
|
||||||
|
let answer = '';
|
||||||
|
let reasoning = '';
|
||||||
|
part.data.forEach((item) => {
|
||||||
|
const formatPart = {
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
delta: {
|
||||||
|
role: 'assistant',
|
||||||
|
content: item.content,
|
||||||
|
reasoning_content: item.reasoning_content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
const { reasoningContent, content } = parsePart({
|
||||||
|
part: formatPart,
|
||||||
|
parseThinkTag: true,
|
||||||
|
retainDatasetCite: false
|
||||||
|
});
|
||||||
|
answer += content;
|
||||||
|
reasoning += reasoningContent;
|
||||||
|
});
|
||||||
|
expect(answer).toBe(part.correct.answer);
|
||||||
|
expect(reasoning).toBe(part.correct.reasoning);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Parse dataset cite content test', async () => {
|
||||||
|
const partList = [
|
||||||
|
{
|
||||||
|
// 完整的
|
||||||
|
data: [
|
||||||
|
{ content: '知识库' },
|
||||||
|
{ content: '问答系统' },
|
||||||
|
{ content: '[67e517e747' },
|
||||||
|
{ content: '67063e882d' },
|
||||||
|
{ content: '6861](CITE)' }
|
||||||
|
],
|
||||||
|
correct: {
|
||||||
|
content: '知识库问答系统[67e517e74767063e882d6861](CITE)',
|
||||||
|
responseContent: '知识库问答系统'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 缺失结尾
|
||||||
|
data: [
|
||||||
|
{ content: '知识库问答系统' },
|
||||||
|
{ content: '[67e517e747' },
|
||||||
|
{ content: '67063e882d' },
|
||||||
|
{ content: '6861](CITE' }
|
||||||
|
],
|
||||||
|
correct: {
|
||||||
|
content: '知识库问答系统[67e517e74767063e882d6861](CITE',
|
||||||
|
responseContent: '知识库问答系统[67e517e74767063e882d6861](CITE'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// ObjectId 不正确
|
||||||
|
data: [
|
||||||
|
{ content: '知识库问答系统' },
|
||||||
|
{ content: '[67e517e747' },
|
||||||
|
{ content: '67882d' },
|
||||||
|
{ content: '6861](CITE)' }
|
||||||
|
],
|
||||||
|
correct: {
|
||||||
|
content: '知识库问答系统[67e517e74767882d6861](CITE)',
|
||||||
|
responseContent: '知识库问答系统[67e517e74767882d6861](CITE)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 其他链接
|
||||||
|
data: [{ content: '知识库' }, { content: '问答系统' }, { content: '[](https://fastgpt.cn)' }],
|
||||||
|
correct: {
|
||||||
|
content: '知识库问答系统[](https://fastgpt.cn)',
|
||||||
|
responseContent: '知识库问答系统[](https://fastgpt.cn)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 不完整的其他链接
|
||||||
|
data: [{ content: '知识库' }, { content: '问答系统' }, { content: '[](https://fastgp' }],
|
||||||
|
correct: {
|
||||||
|
content: '知识库问答系统[](https://fastgp',
|
||||||
|
responseContent: '知识库问答系统[](https://fastgp'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 开头
|
||||||
|
data: [{ content: '[知识库' }, { content: '问答系统' }, { content: '[](https://fastgp' }],
|
||||||
|
correct: {
|
||||||
|
content: '[知识库问答系统[](https://fastgp',
|
||||||
|
responseContent: '[知识库问答系统[](https://fastgp'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 结尾
|
||||||
|
data: [{ content: '知识库' }, { content: '问答系统' }, { content: '[' }],
|
||||||
|
correct: {
|
||||||
|
content: '知识库问答系统[',
|
||||||
|
responseContent: '知识库问答系统['
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 中间
|
||||||
|
data: [
|
||||||
|
{ content: '知识库' },
|
||||||
|
{ content: '问答系统' },
|
||||||
|
{ content: '[' },
|
||||||
|
{ content: '问答系统]' }
|
||||||
|
],
|
||||||
|
correct: {
|
||||||
|
content: '知识库问答系统[问答系统]',
|
||||||
|
responseContent: '知识库问答系统[问答系统]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 双链接
|
||||||
|
data: [
|
||||||
|
{ content: '知识库' },
|
||||||
|
{ content: '问答系统' },
|
||||||
|
{ content: '[](https://fastgpt.cn)' },
|
||||||
|
{ content: '[67e517e747' },
|
||||||
|
{ content: '67063e882d' },
|
||||||
|
{ content: '6861](CITE)' }
|
||||||
|
],
|
||||||
|
correct: {
|
||||||
|
content: '知识库问答系统[](https://fastgpt.cn)[67e517e74767063e882d6861](CITE)',
|
||||||
|
responseContent: '知识库问答系统[](https://fastgpt.cn)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 双链接缺失部分
|
||||||
|
data: [
|
||||||
|
{ content: '知识库' },
|
||||||
|
{ content: '问答系统' },
|
||||||
|
{ content: '[](https://fastgpt.cn)' },
|
||||||
|
{ content: '[67e517e747' },
|
||||||
|
{ content: '67063e882d' },
|
||||||
|
{ content: '6861](CIT' }
|
||||||
|
],
|
||||||
|
correct: {
|
||||||
|
content: '知识库问答系统[](https://fastgpt.cn)[67e517e74767063e882d6861](CIT',
|
||||||
|
responseContent: '知识库问答系统[](https://fastgpt.cn)[67e517e74767063e882d6861](CIT'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 双Cite
|
||||||
|
data: [
|
||||||
|
{ content: '知识库' },
|
||||||
|
{ content: '问答系统' },
|
||||||
|
{ content: '[67e517e747' },
|
||||||
|
{ content: '67063e882d' },
|
||||||
|
{ content: '6861](CITE)' },
|
||||||
|
{ content: '[67e517e747' },
|
||||||
|
{ content: '67063e882d' },
|
||||||
|
{ content: '6861](CITE)' }
|
||||||
|
],
|
||||||
|
correct: {
|
||||||
|
content: '知识库问答系统[67e517e74767063e882d6861](CITE)[67e517e74767063e882d6861](CITE)',
|
||||||
|
responseContent: '知识库问答系统'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 双Cite-第一个假Cite
|
||||||
|
data: [
|
||||||
|
{ content: '知识库' },
|
||||||
|
{ content: '问答系统' },
|
||||||
|
{ content: '[67e517e747' },
|
||||||
|
{ content: '6861](CITE)' },
|
||||||
|
{ content: '[67e517e747' },
|
||||||
|
{ content: '67063e882d' },
|
||||||
|
{ content: '6861](CITE)' }
|
||||||
|
],
|
||||||
|
correct: {
|
||||||
|
content: '知识库问答系统[67e517e7476861](CITE)[67e517e74767063e882d6861](CITE)',
|
||||||
|
responseContent: '知识库问答系统[67e517e7476861](CITE)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
partList.forEach((part, index) => {
|
||||||
|
it(`Dataset cite test: ${index}`, () => {
|
||||||
|
const { parsePart } = parseLLMStreamResponse();
|
||||||
|
|
||||||
|
let answer = '';
|
||||||
|
let responseContent = '';
|
||||||
|
part.data.forEach((item, index) => {
|
||||||
|
const formatPart = {
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
delta: {
|
||||||
|
role: 'assistant',
|
||||||
|
content: item.content,
|
||||||
|
reasoning_content: ''
|
||||||
|
},
|
||||||
|
finish_reason: (index === part.data.length - 1
|
||||||
|
? 'stop'
|
||||||
|
: null) as CompletionFinishReason
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
const { content, responseContent: newResponseContent } = parsePart({
|
||||||
|
part: formatPart,
|
||||||
|
parseThinkTag: false,
|
||||||
|
retainDatasetCite: false
|
||||||
|
});
|
||||||
|
answer += content;
|
||||||
|
responseContent += newResponseContent;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(answer).toEqual(part.correct.content);
|
||||||
|
expect(responseContent).toEqual(part.correct.responseContent);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user