Commit fdd4e9ed by Archer Committed by GitHub

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>
parent 683ab6c1
...@@ -10,7 +10,9 @@ weight: 792 ...@@ -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 个工具参数: ...@@ -563,7 +563,7 @@ HTTP模块中,需要设置 3 个工具参数:
"hidden" "hidden"
], ],
"label": "", "label": "",
"value": 1500, "value": 5000,
"valueType": "number" "valueType": "number"
}, },
{ {
......
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[] = [ ...@@ -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[] = [ ...@@ -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[] = [ ...@@ -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[] = [ ...@@ -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[] = [ ...@@ -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) => { ...@@ -60,7 +60,7 @@ export const getExtractJsonToolPrompt = (version?: string) => {
""" """
- {{description}} - {{description}}
- 不是每个参数都是必须生成的,如果没有合适的参数值,不要生成该参数,或返回空字符串。 - 不是每个参数都是必须生成的,如果没有合适的参数值,不要生成该参数,或返回空字符串。
- 需要结合前面的对话内容,一起生成合适的参数。 - 需要结合历史记录,一起生成合适的参数。
""" """
本次输入内容: """{{content}}""" 本次输入内容: """{{content}}"""
......
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) => { ...@@ -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 语法优化回答格式。尤其是图片、表格、序列号等内容,需严格完整输出。
- 使用与问题相同的语言回答。`;
}; };
...@@ -60,6 +60,7 @@ export type ChatCompletionAssistantToolParam = { ...@@ -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;
}; };
......
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 = ( ...@@ -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 = ({ ...@@ -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 { ...@@ -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 = { ...@@ -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 = { ...@@ -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
}, },
{ {
......
...@@ -2,14 +2,14 @@ ...@@ -2,14 +2,14 @@
"provider": "Qwen", "provider": "Qwen",
"list": [ "list": [
{ {
"model": "qwen-vl-plus", "model": "qwen-max",
"name": "qwen-vl-plus", "name": "Qwen-max",
"maxContext": 32000, "maxContext": 32000,
"maxResponse": 2000, "maxResponse": 4000,
"quoteMaxToken": 20000, "quoteMaxToken": 6000,
"maxTemperature": 1.2, "maxTemperature": 1,
"vision": true, "vision": false,
"toolChoice": false, "toolChoice": true,
"functionCall": false, "functionCall": false,
"defaultSystemChatPrompt": "", "defaultSystemChatPrompt": "",
"datasetProcess": true, "datasetProcess": true,
...@@ -19,19 +19,22 @@ ...@@ -19,19 +19,22 @@
"usedInQueryExtension": true, "usedInQueryExtension": true,
"customExtractPrompt": "", "customExtractPrompt": "",
"usedInToolCall": true, "usedInToolCall": true,
"defaultConfig": {},
"fieldMap": {},
"type": "llm", "type": "llm",
"showTopP": true, "showTopP": true,
"showStopSign": true "showStopSign": true,
"responseFormatList": ["text", "json_object"]
}, },
{ {
"model": "qwen-plus", "model": "qwen-vl-max",
"name": "Qwen-plus", "name": "qwen-vl-max",
"maxContext": 64000, "maxContext": 32000,
"maxResponse": 8000, "maxResponse": 2000,
"quoteMaxToken": 60000, "quoteMaxToken": 20000,
"maxTemperature": 1, "maxTemperature": 1.2,
"vision": false, "vision": true,
"toolChoice": true, "toolChoice": false,
"functionCall": false, "functionCall": false,
"defaultSystemChatPrompt": "", "defaultSystemChatPrompt": "",
"datasetProcess": true, "datasetProcess": true,
...@@ -45,15 +48,14 @@ ...@@ -45,15 +48,14 @@
"fieldMap": {}, "fieldMap": {},
"type": "llm", "type": "llm",
"showTopP": true, "showTopP": true,
"showStopSign": true, "showStopSign": true
"responseFormatList": ["text", "json_object"]
}, },
{ {
"model": "qwen-turbo", "model": "qwen-plus",
"name": "Qwen-turbo", "name": "Qwen-plus",
"maxContext": 128000, "maxContext": 64000,
"maxResponse": 8000, "maxResponse": 8000,
"quoteMaxToken": 100000, "quoteMaxToken": 60000,
"maxTemperature": 1, "maxTemperature": 1,
"vision": false, "vision": false,
"toolChoice": true, "toolChoice": true,
...@@ -73,16 +75,15 @@ ...@@ -73,16 +75,15 @@
"showStopSign": true, "showStopSign": true,
"responseFormatList": ["text", "json_object"] "responseFormatList": ["text", "json_object"]
}, },
{ {
"model": "qwen-max", "model": "qwen-vl-plus",
"name": "Qwen-max", "name": "qwen-vl-plus",
"maxContext": 32000, "maxContext": 32000,
"maxResponse": 4000, "maxResponse": 2000,
"quoteMaxToken": 6000, "quoteMaxToken": 20000,
"maxTemperature": 1, "maxTemperature": 1.2,
"vision": false, "vision": true,
"toolChoice": true, "toolChoice": false,
"functionCall": false, "functionCall": false,
"defaultSystemChatPrompt": "", "defaultSystemChatPrompt": "",
"datasetProcess": true, "datasetProcess": true,
...@@ -92,22 +93,19 @@ ...@@ -92,22 +93,19 @@
"usedInQueryExtension": true, "usedInQueryExtension": true,
"customExtractPrompt": "", "customExtractPrompt": "",
"usedInToolCall": true, "usedInToolCall": true,
"defaultConfig": {},
"fieldMap": {},
"type": "llm", "type": "llm",
"showTopP": true, "showTopP": true,
"showStopSign": true, "showStopSign": true
"responseFormatList": ["text", "json_object"]
}, },
{ {
"model": "qwen-vl-max", "model": "qwen-turbo",
"name": "qwen-vl-max", "name": "Qwen-turbo",
"maxContext": 32000, "maxContext": 128000,
"maxResponse": 2000, "maxResponse": 8000,
"quoteMaxToken": 20000, "quoteMaxToken": 100000,
"maxTemperature": 1.2, "maxTemperature": 1,
"vision": true, "vision": false,
"toolChoice": false, "toolChoice": true,
"functionCall": false, "functionCall": false,
"defaultSystemChatPrompt": "", "defaultSystemChatPrompt": "",
"datasetProcess": true, "datasetProcess": true,
...@@ -121,7 +119,8 @@ ...@@ -121,7 +119,8 @@
"fieldMap": {}, "fieldMap": {},
"type": "llm", "type": "llm",
"showTopP": true, "showTopP": true,
"showStopSign": true "showStopSign": true,
"responseFormatList": ["text", "json_object"]
}, },
{ {
"model": "qwen3-235b-a22b", "model": "qwen3-235b-a22b",
...@@ -142,7 +141,9 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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' ...@@ -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({ ...@@ -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'; ...@@ -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} ...@@ -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} ...@@ -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));
......
...@@ -31,5 +31,6 @@ export const computedPluginUsage = async ({ ...@@ -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 ...@@ -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 ({ ...@@ -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 { ...@@ -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) => { ...@@ -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) => { ...@@ -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) => { ...@@ -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}` ...@@ -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 ...@@ -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 ( ...@@ -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 ( ...@@ -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 ( ...@@ -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({ ...@@ -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({ ...@@ -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({ ...@@ -533,17 +554,21 @@ async function streamResponse({
break; break;
} }
const responseChoice = part.choices?.[0]?.delta; const { content: toolChoiceContent, responseContent } = parsePart({
part,
parseThinkTag: false,
retainDatasetCite
});
if (responseChoice.content) { const responseChoice = part.choices?.[0]?.delta;
const content = responseChoice?.content || ''; textAnswer += toolChoiceContent;
textAnswer += content;
if (responseContent) {
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'; ...@@ -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 ( ...@@ -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 ( ...@@ -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 ( ...@@ -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 ( ...@@ -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({ ...@@ -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({ ...@@ -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({ ...@@ -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({ ...@@ -612,13 +622,15 @@ async function streamResponse({
if (content) { if (content) {
if (startResponseWrite) { if (startResponseWrite) {
if (responseContent) {
workflowStreamResponse?.({ workflowStreamResponse?.({
write, write,
event: SseResponseEventEnum.answer, event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({ data: textAdaptGptResponse({
text: content 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({ ...@@ -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 };
} }
......
...@@ -4,7 +4,11 @@ import type { ChatItemType, UserChatItemValueItemType } from '@fastgpt/global/co ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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,12 +262,11 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp ...@@ -258,12 +262,11 @@ 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: reasoningContent reasoning_content: removeDatasetCiteText(reasoningContent, retainDatasetCite)
}) })
}); });
} }
...@@ -271,11 +274,10 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp ...@@ -271,11 +274,10 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
workflowStreamResponse?.({ workflowStreamResponse?.({
event: SseResponseEventEnum.fastAnswer, event: SseResponseEventEnum.fastAnswer,
data: textAdaptGptResponse({ data: textAdaptGptResponse({
text: content text: removeDatasetCiteText(content, retainDatasetCite)
}) })
}); });
} }
}
return { return {
answerText: content, answerText: content,
...@@ -452,8 +454,7 @@ async function getChatMessages({ ...@@ -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({ ...@@ -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({ ...@@ -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({ ...@@ -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({ ...@@ -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({ ...@@ -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({ ...@@ -583,26 +589,12 @@ async function streamResponse({
}); });
} }
if (isResponseAnswerText && content) { if (isResponseAnswerText && responseContent) {
workflowStreamResponse?.({
write,
event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({
text: content
})
});
}
}
// 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?.({ workflowStreamResponse?.({
write, write,
event: SseResponseEventEnum.answer, event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({ data: textAdaptGptResponse({
text: answer text: responseContent
}) })
}); });
} }
......
...@@ -21,7 +21,7 @@ export async function dispatchDatasetConcat( ...@@ -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( ...@@ -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( ...@@ -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( ...@@ -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 ...@@ -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 ...@@ -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,
......
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 = ({ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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'; ...@@ -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) => { ...@@ -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) => { ...@@ -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) => { ...@@ -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 = ({ ...@@ -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) => { ...@@ -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) => { ...@@ -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) => { ...@@ -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({ ...@@ -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({ ...@@ -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'; ...@@ -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'; ...@@ -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;
......
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'; ...@@ -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}`>;
......
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 { ...@@ -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'; ...@@ -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 ...@@ -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 ...@@ -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'; ...@@ -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( ...@@ -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 ...@@ -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 { ...@@ -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 & ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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 { ...@@ -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 & ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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) => { ...@@ -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) => { ...@@ -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) => { ...@@ -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 { ...@@ -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 })}`; ...@@ -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'; ...@@ -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 ...@@ -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 = ({ ...@@ -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 { ...@@ -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', () => { ...@@ -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', () => { ...@@ -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);
}); });
}); });
......
[
"测试的呀,第一个表格\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 | 程序员 | 厦门 |"
]
\ No newline at end of file
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', () => {
......
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, ''));
......
import { parseReasoningStreamContent } from './utils'; import { CompletionFinishReason } from '@fastgpt/global/core/ai/type';
import { expect, test } from 'vitest'; import { parseLLMStreamResponse } from '@fastgpt/service/core/ai/utils';
import { describe, expect, it } from 'vitest';
test('Parse reasoning stream content test', async () => { describe('Parse reasoning stream content test', async () => {
const partList = [ const partList = [
{ {
data: [{ content: '你好1' }, { content: '你好2' }, { content: '你好3' }], data: [{ content: '你好1' }, { content: '你好2' }, { content: '你好3' }],
...@@ -117,8 +118,10 @@ test('Parse reasoning stream content test', async () => { ...@@ -117,8 +118,10 @@ test('Parse reasoning stream content test', async () => {
} }
]; ];
partList.forEach((part) => { // Remove think
const { parsePart } = parseReasoningStreamContent(); partList.forEach((part, index) => {
it(`Reasoning test:${index}`, () => {
const { parsePart } = parseLLMStreamResponse();
let answer = ''; let answer = '';
let reasoning = ''; let reasoning = '';
...@@ -134,12 +137,204 @@ test('Parse reasoning stream content test', async () => { ...@@ -134,12 +137,204 @@ test('Parse reasoning stream content test', async () => {
} }
] ]
}; };
const [reasoningContent, content] = parsePart(formatPart, true); const { reasoningContent, content } = parsePart({
part: formatPart,
parseThinkTag: true,
retainDatasetCite: false
});
answer += content; answer += content;
reasoning += reasoningContent; reasoning += reasoningContent;
}); });
expect(answer).toBe(part.correct.answer); expect(answer).toBe(part.correct.answer);
expect(reasoning).toBe(part.correct.reasoning); 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);
});
});
}); });
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment