From 54defd8a3c1199cff6a13798a37556380dfb3bf5 Mon Sep 17 00:00:00 2001
From: Archer <545436317@qq.com>
Date: Sat, 1 Feb 2025 18:04:44 +0800
Subject: [PATCH] perf: max_token count;feat: support resoner output;fix:
member scroll (#3681)
* perf: supplement assistant empty response
* check array
* perf: max_token count
* feat: support resoner output
* member scroll
* update provider order
* i18n
---
.../zh-cn/docs/development/upgrading/4820.md | 17 ++-
packages/global/core/ai/model.d.ts | 3 +-
packages/global/core/ai/provider.ts | 2 +-
packages/global/core/app/type.d.ts | 1 +
packages/global/core/chat/constants.ts | 3 +-
packages/global/core/chat/type.d.ts | 11 +-
packages/global/core/workflow/constants.ts | 4 +-
.../global/core/workflow/runtime/type.d.ts | 1 +
.../global/core/workflow/runtime/utils.ts | 13 +-
.../workflow/template/system/aiChat/index.ts | 11 +-
.../core/workflow/template/system/tools.ts | 4 +-
.../service/common/file/image/controller.ts | 2 +-
.../core/ai/config/provider/DeepSeek.json | 9 +-
.../core/ai/config/provider/OpenAI.json | 28 ++--
.../core/ai/functions/queryExtension.ts | 127 +++++++++++-------
packages/service/core/ai/utils.ts | 21 +--
packages/service/core/chat/utils.ts | 31 +----
.../core/workflow/dispatch/agent/extract.ts | 6 +-
.../dispatch/agent/runTool/functionCall.ts | 17 ++-
.../dispatch/agent/runTool/promptCall.ts | 17 ++-
.../dispatch/agent/runTool/toolChoice.ts | 18 +--
.../core/workflow/dispatch/chat/oneapi.ts | 93 ++++++++-----
.../service/core/workflow/dispatch/index.ts | 9 ++
.../web/components/common/Icon/constants.ts | 1 +
.../common/Icon/icons/core/chat/think.svg | 1 +
packages/web/hooks/useScrollPagination.tsx | 2 +-
packages/web/i18n/en/account.json | 4 +-
packages/web/i18n/en/app.json | 1 +
packages/web/i18n/en/chat.json | 1 +
packages/web/i18n/zh-CN/account.json | 4 +-
packages/web/i18n/zh-CN/app.json | 1 +
packages/web/i18n/zh-CN/chat.json | 1 +
packages/web/i18n/zh-Hant/account.json | 4 +-
packages/web/i18n/zh-Hant/app.json | 1 +
packages/web/i18n/zh-Hant/chat.json | 1 +
.../core/ai/AISettingModal/index.tsx | 78 ++++++-----
.../core/chat/ChatContainer/ChatBox/index.tsx | 20 +++
.../core/chat/ChatContainer/type.d.ts | 1 +
.../core/chat/components/AIResponseBox.tsx | 52 +++++++
.../account/model/ModelConfigTable.tsx | 8 ++
.../team/GroupManage/GroupManageMember.tsx | 8 +-
.../account/team/MemberTable.tsx | 10 +-
.../team/OrgManage/OrgMemberManageModal.tsx | 58 ++++----
.../RenderInput/templates/SettingLLMModel.tsx | 4 +-
projects/app/src/web/common/api/fetch.ts | 6 +
projects/app/src/web/core/app/templates.ts | 13 +-
46 files changed, 462 insertions(+), 266 deletions(-)
create mode 100644 packages/web/components/common/Icon/icons/core/chat/think.svg
diff --git a/docSite/content/zh-cn/docs/development/upgrading/4820.md b/docSite/content/zh-cn/docs/development/upgrading/4820.md
index ecf160a9b..07b7ac301 100644
--- a/docSite/content/zh-cn/docs/development/upgrading/4820.md
+++ b/docSite/content/zh-cn/docs/development/upgrading/4820.md
@@ -31,9 +31,14 @@ curl --location --request POST 'https://{{host}}/api/admin/initv4820' \
## 完整更新内容
1. 新增 - 可视化模型参数配置。预设超过 100 个模型配置。同时支持所有类型模型的一键测试。(预计下个版本会完全支持在页面上配置渠道)。
-2. 新增 - 使用记录导出和仪表盘。
-3. 新增 - markdown 语法扩展,支持音视频(代码块 audio 和 video)。
-4. 优化 - 页面组件抽离,减少页面组件路由。
-5. 优化 - 全文检索,忽略大小写。
-6. 优化 - 问答生成和增强索引改成流输出,避免部分模型超时。
-7. 优化 - 自动给 assistant 空 content,补充 null,同时合并连续的 text assistant,避免部分模型抛错。
\ No newline at end of file
+2. 新增 - DeepSeek resoner 模型支持输出思考过程。
+3. 新增 - 使用记录导出和仪表盘。
+4. 新增 - markdown 语法扩展,支持音视频(代码块 audio 和 video)。
+5. 新增 - 调整 max_tokens 计算逻辑。优先保证 max_tokens 为配置值,如超出最大上下文,则减少历史记录。例如:如果申请 8000 的 max_tokens,则上下文长度会减少 8000。
+6. 优化 - 问题优化增加上下文过滤,避免超出上下文。
+7. 优化 - 页面组件抽离,减少页面组件路由。
+8. 优化 - 全文检索,忽略大小写。
+9. 优化 - 问答生成和增强索引改成流输出,避免部分模型超时。
+10. 优化 - 自动给 assistant 空 content,补充 null,同时合并连续的 text assistant,避免部分模型抛错。
+11. 优化 - 调整图片 Host, 取消上传时补充 FE_DOMAIN,改成发送对话前
+12. 修复 - 部分场景成员列表无法触底加载。
\ No newline at end of file
diff --git a/packages/global/core/ai/model.d.ts b/packages/global/core/ai/model.d.ts
index 9fa75b277..235dd0e6f 100644
--- a/packages/global/core/ai/model.d.ts
+++ b/packages/global/core/ai/model.d.ts
@@ -29,10 +29,11 @@ export type LLMModelItemType = PriceType &
maxContext: number;
maxResponse: number;
quoteMaxToken: number;
- maxTemperature: number;
+ maxTemperature?: number;
censor?: boolean;
vision?: boolean;
+ reasoning?: boolean;
// diff function model
datasetProcess?: boolean; // dataset
diff --git a/packages/global/core/ai/provider.ts b/packages/global/core/ai/provider.ts
index 1f10b9ebc..f1bfc9370 100644
--- a/packages/global/core/ai/provider.ts
+++ b/packages/global/core/ai/provider.ts
@@ -11,8 +11,8 @@ export type ModelProviderIdType =
| 'AliCloud'
| 'Qwen'
| 'Doubao'
- | 'ChatGLM'
| 'DeepSeek'
+ | 'ChatGLM'
| 'Ernie'
| 'Moonshot'
| 'MiniMax'
diff --git a/packages/global/core/app/type.d.ts b/packages/global/core/app/type.d.ts
index 899628936..ec30e4b51 100644
--- a/packages/global/core/app/type.d.ts
+++ b/packages/global/core/app/type.d.ts
@@ -117,6 +117,7 @@ export type SettingAIDataType = {
isResponseAnswerText?: boolean;
maxHistories?: number;
[NodeInputKeyEnum.aiChatVision]?: boolean; // Is open vision mode
+ [NodeInputKeyEnum.aiChatReasoning]?: boolean; // Is open reasoning mode
};
// variable
diff --git a/packages/global/core/chat/constants.ts b/packages/global/core/chat/constants.ts
index 13aa64934..34c6ff1e0 100644
--- a/packages/global/core/chat/constants.ts
+++ b/packages/global/core/chat/constants.ts
@@ -25,7 +25,8 @@ export enum ChatItemValueTypeEnum {
text = 'text',
file = 'file',
tool = 'tool',
- interactive = 'interactive'
+ interactive = 'interactive',
+ reasoning = 'reasoning'
}
export enum ChatSourceEnum {
diff --git a/packages/global/core/chat/type.d.ts b/packages/global/core/chat/type.d.ts
index a9171b156..4e010c68f 100644
--- a/packages/global/core/chat/type.d.ts
+++ b/packages/global/core/chat/type.d.ts
@@ -70,14 +70,23 @@ export type SystemChatItemType = {
obj: ChatRoleEnum.System;
value: SystemChatItemValueItemType[];
};
+
export type AIChatItemValueItemType = {
- type: ChatItemValueTypeEnum.text | ChatItemValueTypeEnum.tool | ChatItemValueTypeEnum.interactive;
+ type:
+ | ChatItemValueTypeEnum.text
+ | ChatItemValueTypeEnum.reasoning
+ | ChatItemValueTypeEnum.tool
+ | ChatItemValueTypeEnum.interactive;
text?: {
content: string;
};
+ reasoning?: {
+ content: string;
+ };
tools?: ToolModuleResponseItemType[];
interactive?: WorkflowInteractiveResponseType;
};
+
export type AIChatItemType = {
obj: ChatRoleEnum.AI;
value: AIChatItemValueItemType[];
diff --git a/packages/global/core/workflow/constants.ts b/packages/global/core/workflow/constants.ts
index 175f0c19d..9aa4620bd 100644
--- a/packages/global/core/workflow/constants.ts
+++ b/packages/global/core/workflow/constants.ts
@@ -141,6 +141,7 @@ export enum NodeInputKeyEnum {
aiChatDatasetQuote = 'quoteQA',
aiChatVision = 'aiChatVision',
stringQuoteText = 'stringQuoteText',
+ aiChatReasoning = 'aiChatReasoning',
// dataset
datasetSelectList = 'datasets',
@@ -220,7 +221,8 @@ export enum NodeOutputKeyEnum {
// common
userChatInput = 'userChatInput',
history = 'history',
- answerText = 'answerText', // module answer. the value will be show and save to history
+ answerText = 'answerText', // node answer. the value will be show and save to history
+ reasoningText = 'reasoningText', // node reasoning. the value will be show but not save to history
success = 'success',
failed = 'failed',
error = 'error',
diff --git a/packages/global/core/workflow/runtime/type.d.ts b/packages/global/core/workflow/runtime/type.d.ts
index 995302b65..fdf1bfeba 100644
--- a/packages/global/core/workflow/runtime/type.d.ts
+++ b/packages/global/core/workflow/runtime/type.d.ts
@@ -220,6 +220,7 @@ export type AIChatNodeProps = {
[NodeInputKeyEnum.aiChatMaxToken]?: number;
[NodeInputKeyEnum.aiChatIsResponseText]: boolean;
[NodeInputKeyEnum.aiChatVision]?: boolean;
+ [NodeInputKeyEnum.aiChatReasoning]?: boolean;
[NodeInputKeyEnum.aiChatQuoteRole]?: AiChatQuoteRoleType;
[NodeInputKeyEnum.aiChatQuoteTemplate]?: string;
diff --git a/packages/global/core/workflow/runtime/utils.ts b/packages/global/core/workflow/runtime/utils.ts
index 01aeaa6e0..27c819ba6 100644
--- a/packages/global/core/workflow/runtime/utils.ts
+++ b/packages/global/core/workflow/runtime/utils.ts
@@ -364,12 +364,14 @@ export function replaceEditorVariable({
export const textAdaptGptResponse = ({
text,
+ reasoning_content,
model = '',
finish_reason = null,
extraData = {}
}: {
model?: string;
- text: string | null;
+ text?: string | null;
+ reasoning_content?: string | null;
finish_reason?: null | 'stop';
extraData?: Object;
}) => {
@@ -381,10 +383,11 @@ export const textAdaptGptResponse = ({
model,
choices: [
{
- delta:
- text === null
- ? {}
- : { role: ChatCompletionRequestMessageRoleEnum.Assistant, content: text },
+ delta: {
+ role: ChatCompletionRequestMessageRoleEnum.Assistant,
+ content: text,
+ ...(reasoning_content && { reasoning_content })
+ },
index: 0,
finish_reason
}
diff --git a/packages/global/core/workflow/template/system/aiChat/index.ts b/packages/global/core/workflow/template/system/aiChat/index.ts
index a346371b2..103d5f311 100644
--- a/packages/global/core/workflow/template/system/aiChat/index.ts
+++ b/packages/global/core/workflow/template/system/aiChat/index.ts
@@ -63,14 +63,14 @@ export const AiChatModule: FlowNodeTemplateType = {
key: NodeInputKeyEnum.aiChatTemperature,
renderTypeList: [FlowNodeInputTypeEnum.hidden], // Set in the pop-up window
label: '',
- value: 0,
+ value: undefined,
valueType: WorkflowIOValueTypeEnum.number
},
{
key: NodeInputKeyEnum.aiChatMaxToken,
renderTypeList: [FlowNodeInputTypeEnum.hidden], // Set in the pop-up window
label: '',
- value: 2000,
+ value: undefined,
valueType: WorkflowIOValueTypeEnum.number
},
@@ -91,6 +91,13 @@ export const AiChatModule: FlowNodeTemplateType = {
valueType: WorkflowIOValueTypeEnum.boolean,
value: true
},
+ {
+ key: NodeInputKeyEnum.aiChatReasoning,
+ renderTypeList: [FlowNodeInputTypeEnum.hidden],
+ label: '',
+ valueType: WorkflowIOValueTypeEnum.boolean,
+ value: true
+ },
// settings modal ---
{
...Input_Template_System_Prompt,
diff --git a/packages/global/core/workflow/template/system/tools.ts b/packages/global/core/workflow/template/system/tools.ts
index 29406a193..f1472e495 100644
--- a/packages/global/core/workflow/template/system/tools.ts
+++ b/packages/global/core/workflow/template/system/tools.ts
@@ -43,14 +43,14 @@ export const ToolModule: FlowNodeTemplateType = {
key: NodeInputKeyEnum.aiChatTemperature,
renderTypeList: [FlowNodeInputTypeEnum.hidden], // Set in the pop-up window
label: '',
- value: 0,
+ value: undefined,
valueType: WorkflowIOValueTypeEnum.number
},
{
key: NodeInputKeyEnum.aiChatMaxToken,
renderTypeList: [FlowNodeInputTypeEnum.hidden], // Set in the pop-up window
label: '',
- value: 2000,
+ value: undefined,
valueType: WorkflowIOValueTypeEnum.number
},
{
diff --git a/packages/service/common/file/image/controller.ts b/packages/service/common/file/image/controller.ts
index 27bc7ade5..5734f7ab9 100644
--- a/packages/service/common/file/image/controller.ts
+++ b/packages/service/common/file/image/controller.ts
@@ -40,7 +40,7 @@ export async function uploadMongoImg({
expiredTime: forever ? undefined : addHours(new Date(), 1)
});
- return `${process.env.FE_DOMAIN || ''}${process.env.NEXT_PUBLIC_BASE_URL || ''}${imageBaseUrl}${String(_id)}.${extension}`;
+ return `${process.env.NEXT_PUBLIC_BASE_URL || ''}${imageBaseUrl}${String(_id)}.${extension}`;
}
const getIdFromPath = (path?: string) => {
diff --git a/packages/service/core/ai/config/provider/DeepSeek.json b/packages/service/core/ai/config/provider/DeepSeek.json
index 1fb0b525f..1aee2c1ab 100644
--- a/packages/service/core/ai/config/provider/DeepSeek.json
+++ b/packages/service/core/ai/config/provider/DeepSeek.json
@@ -27,8 +27,9 @@
"maxContext": 64000,
"maxResponse": 4096,
"quoteMaxToken": 60000,
- "maxTemperature": 1.5,
+ "maxTemperature": null,
"vision": false,
+ "reasoning": true,
"toolChoice": false,
"functionCall": false,
"defaultSystemChatPrompt": "",
@@ -39,11 +40,9 @@
"usedInQueryExtension": true,
"customExtractPrompt": "",
"usedInToolCall": true,
- "defaultConfig": {
- "temperature": null
- },
+ "defaultConfig": {},
"fieldMap": {},
"type": "llm"
}
]
-}
\ No newline at end of file
+}
diff --git a/packages/service/core/ai/config/provider/OpenAI.json b/packages/service/core/ai/config/provider/OpenAI.json
index 4bfcc461e..b45942518 100644
--- a/packages/service/core/ai/config/provider/OpenAI.json
+++ b/packages/service/core/ai/config/provider/OpenAI.json
@@ -50,10 +50,10 @@
"maxContext": 128000,
"maxResponse": 4000,
"quoteMaxToken": 120000,
- "maxTemperature": 1.2,
+ "maxTemperature": null,
"vision": false,
"toolChoice": false,
- "functionCall": true,
+ "functionCall": false,
"defaultSystemChatPrompt": "",
"datasetProcess": true,
"usedInClassify": true,
@@ -63,8 +63,10 @@
"customExtractPrompt": "",
"usedInToolCall": true,
"defaultConfig": {
- "temperature": 1,
- "max_tokens": null
+ "stream": false
+ },
+ "fieldMap": {
+ "max_tokens": "max_completion_tokens"
},
"type": "llm"
},
@@ -74,10 +76,10 @@
"maxContext": 128000,
"maxResponse": 4000,
"quoteMaxToken": 120000,
- "maxTemperature": 1.2,
+ "maxTemperature": null,
"vision": false,
"toolChoice": false,
- "functionCall": true,
+ "functionCall": false,
"defaultSystemChatPrompt": "",
"datasetProcess": true,
"usedInClassify": true,
@@ -87,10 +89,11 @@
"customExtractPrompt": "",
"usedInToolCall": true,
"defaultConfig": {
- "temperature": 1,
- "max_tokens": null,
"stream": false
},
+ "fieldMap": {
+ "max_tokens": "max_completion_tokens"
+ },
"type": "llm"
},
{
@@ -99,10 +102,10 @@
"maxContext": 195000,
"maxResponse": 8000,
"quoteMaxToken": 120000,
- "maxTemperature": 1.2,
+ "maxTemperature": null,
"vision": false,
"toolChoice": false,
- "functionCall": true,
+ "functionCall": false,
"defaultSystemChatPrompt": "",
"datasetProcess": true,
"usedInClassify": true,
@@ -112,10 +115,11 @@
"customExtractPrompt": "",
"usedInToolCall": true,
"defaultConfig": {
- "temperature": 1,
- "max_tokens": null,
"stream": false
},
+ "fieldMap": {
+ "max_tokens": "max_completion_tokens"
+ },
"type": "llm"
},
{
diff --git a/packages/service/core/ai/functions/queryExtension.ts b/packages/service/core/ai/functions/queryExtension.ts
index 31bb9ee8d..c4b85ffcd 100644
--- a/packages/service/core/ai/functions/queryExtension.ts
+++ b/packages/service/core/ai/functions/queryExtension.ts
@@ -2,10 +2,12 @@ import { replaceVariable } from '@fastgpt/global/common/string/tools';
import { createChatCompletion } from '../config';
import { ChatItemType } from '@fastgpt/global/core/chat/type';
import { countGptMessagesTokens, countPromptTokens } from '../../../common/string/tiktoken/index';
-import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
+import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
import { getLLMModel } from '../model';
import { llmCompletionsBodyFormat } from '../utils';
import { addLog } from '../../../common/system/log';
+import { filterGPTMessageByMaxContext } from '../../chat/utils';
+import json5 from 'json5';
/*
query extension - 问题扩展
@@ -13,72 +15,73 @@ import { addLog } from '../../../common/system/log';
*/
const title = global.feConfigs?.systemTitle || 'FastAI';
-const defaultPrompt = `作为一个向量检索助手,你的任务是结合历史记录,从不同角度,为“原问题”生成个不同版本的“检索词”,从而提高向量检索的语义丰富度,提高向量检索的精度。
+const defaultPrompt = `## 你的任务
+你作为一个向量检索助手,你的任务是结合历史记录,从不同角度,为“原问题”生成个不同版本的“检索词”,从而提高向量检索的语义丰富度,提高向量检索的精度。
生成的问题要求指向对象清晰明确,并与“原问题语言相同”。
-参考 标中的示例来完成任务。
+## 参考示例
-
历史记录:
"""
+null
"""
原问题: 介绍下剧情。
检索词: ["介绍下故事的背景。","故事的主题是什么?","介绍下故事的主要人物。"]
----------------
历史记录:
"""
-Q: 对话背景。
-A: 当前对话是关于 Nginx 的介绍和使用等。
+user: 对话背景。
+assistant: 当前对话是关于 Nginx 的介绍和使用等。
"""
原问题: 怎么下载
检索词: ["Nginx 如何下载?","下载 Nginx 需要什么条件?","有哪些渠道可以下载 Nginx?"]
----------------
历史记录:
"""
-Q: 对话背景。
-A: 当前对话是关于 Nginx 的介绍和使用等。
-Q: 报错 "no connection"
-A: 报错"no connection"可能是因为……
+user: 对话背景。
+assistant: 当前对话是关于 Nginx 的介绍和使用等。
+user: 报错 "no connection"
+assistant: 报错"no connection"可能是因为……
"""
原问题: 怎么解决
检索词: ["Nginx报错"no connection"如何解决?","造成'no connection'报错的原因。","Nginx提示'no connection',要怎么办?"]
----------------
历史记录:
"""
-Q: 护产假多少天?
-A: 护产假的天数根据员工所在的城市而定。请提供您所在的城市,以便我回答您的问题。
+user: How long is the maternity leave?
+assistant: The number of days of maternity leave depends on the city in which the employee is located. Please provide your city so that I can answer your questions.
"""
-原问题: 沈阳
-检索词: ["沈阳的护产假多少天?","沈阳的护产假政策。","沈阳的护产假标准。"]
+原问题: ShenYang
+检索词: ["How many days is maternity leave in Shenyang?","Shenyang's maternity leave policy.","The standard of maternity leave in Shenyang."]
----------------
历史记录:
"""
-Q: 作者是谁?
-A: ${title} 的作者是 labring。
+user: 作者是谁?
+assistant: ${title} 的作者是 labring。
"""
原问题: Tell me about him
检索词: ["Introduce labring, the author of ${title}." ," Background information on author labring." "," Why does labring do ${title}?"]
----------------
历史记录:
"""
-Q: 对话背景。
-A: 关于 ${title} 的介绍和使用等问题。
+user: 对话背景。
+assistant: 关于 ${title} 的介绍和使用等问题。
"""
原问题: 你好。
检索词: ["你好"]
----------------
历史记录:
"""
-Q: ${title} 如何收费?
-A: ${title} 收费可以参考……
+user: ${title} 如何收费?
+assistant: ${title} 收费可以参考……
"""
原问题: 你知道 laf 么?
检索词: ["laf 的官网地址是多少?","laf 的使用教程。","laf 有什么特点和优势。"]
----------------
历史记录:
"""
-Q: ${title} 的优势
-A: 1. 开源
+user: ${title} 的优势
+assistant: 1. 开源
2. 简便
3. 扩展性强
"""
@@ -87,18 +90,20 @@ A: 1. 开源
----------------
历史记录:
"""
-Q: 什么是 ${title}?
-A: ${title} 是一个 RAG 平台。
-Q: 什么是 Laf?
-A: Laf 是一个云函数开发平台。
+user: 什么是 ${title}?
+assistant: ${title} 是一个 RAG 平台。
+user: 什么是 Laf?
+assistant: Laf 是一个云函数开发平台。
"""
原问题: 它们有什么关系?
检索词: ["${title}和Laf有什么关系?","介绍下${title}","介绍下Laf"]
-
------
+## 输出要求
-下面是正式的任务:
+1. 输出格式为 JSON 数组,数组中每个元素为字符串。无需对输出进行任何解释。
+2. 输出语言与原问题相同。原问题为中文则输出中文;原问题为英文则输出英文。
+
+## 开始任务
历史记录:
"""
@@ -125,26 +130,39 @@ export const queryExtension = async ({
outputTokens: number;
}> => {
const systemFewShot = chatBg
- ? `Q: 对话背景。
-A: ${chatBg}
+ ? `user: 对话背景。
+assistant: ${chatBg}
`
: '';
- const historyFewShot = histories
- .map((item) => {
- const role = item.obj === 'Human' ? 'Q' : 'A';
- return `${role}: ${chatValue2RuntimePrompt(item.value).text}`;
- })
- .join('\n');
- const concatFewShot = `${systemFewShot}${historyFewShot}`.trim();
const modelData = getLLMModel(model);
+ const filterHistories = await filterGPTMessageByMaxContext({
+ messages: chats2GPTMessages({ messages: histories, reserveId: false }),
+ maxContext: modelData.maxContext - 1000
+ });
+
+ const historyFewShot = filterHistories
+ .map((item) => {
+ const role = item.role;
+ const content = item.content;
+ if ((role === 'user' || role === 'assistant') && content) {
+ if (typeof content === 'string') {
+ return `${role}: ${content}`;
+ } else {
+ return `${role}: ${content.map((item) => (item.type === 'text' ? item.text : '')).join('\n')}`;
+ }
+ }
+ })
+ .filter(Boolean)
+ .join('\n');
+ const concatFewShot = `${systemFewShot}${historyFewShot}`.trim();
const messages = [
{
role: 'user',
content: replaceVariable(defaultPrompt, {
query: `${query}`,
- histories: concatFewShot
+ histories: concatFewShot || 'null'
})
}
] as any;
@@ -154,7 +172,7 @@ A: ${chatBg}
{
stream: false,
model: modelData.model,
- temperature: 0.01,
+ temperature: 0.1,
messages
},
modelData
@@ -172,22 +190,41 @@ A: ${chatBg}
};
}
+ const start = answer.indexOf('[');
+ const end = answer.lastIndexOf(']');
+ if (start === -1 || end === -1) {
+ addLog.warn('Query extension failed, not a valid JSON', {
+ answer
+ });
+ return {
+ rawQuery: query,
+ extensionQueries: [],
+ model,
+ inputTokens: 0,
+ outputTokens: 0
+ };
+ }
+
// Intercept the content of [] and retain []
- answer = answer.match(/\[.*?\]/)?.[0] || '';
- answer = answer.replace(/\\"/g, '"');
+ const jsonStr = answer
+ .substring(start, end + 1)
+ .replace(/(\\n|\\)/g, '')
+ .replace(/ /g, '');
try {
- const queries = JSON.parse(answer) as string[];
+ const queries = json5.parse(jsonStr) as string[];
return {
rawQuery: query,
- extensionQueries: Array.isArray(queries) ? queries : [],
+ extensionQueries: (Array.isArray(queries) ? queries : []).slice(0, 5),
model,
inputTokens: await countGptMessagesTokens(messages),
outputTokens: await countPromptTokens(answer)
};
} catch (error) {
- addLog.error(`Query extension error`, error);
+ addLog.warn('Query extension failed, not a valid JSON', {
+ answer
+ });
return {
rawQuery: query,
extensionQueries: [],
diff --git a/packages/service/core/ai/utils.ts b/packages/service/core/ai/utils.ts
index 74b4e718f..40951a372 100644
--- a/packages/service/core/ai/utils.ts
+++ b/packages/service/core/ai/utils.ts
@@ -2,33 +2,23 @@ import { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
import {
ChatCompletionCreateParamsNonStreaming,
ChatCompletionCreateParamsStreaming,
- ChatCompletionMessageParam,
StreamChatType
} from '@fastgpt/global/core/ai/type';
-import { countGptMessagesTokens } from '../../common/string/tiktoken';
import { getLLMModel } from './model';
-export const computedMaxToken = async ({
+/*
+ Count response max token
+*/
+export const computedMaxToken = ({
maxToken,
- model,
- filterMessages = []
+ model
}: {
maxToken?: number;
model: LLMModelItemType;
- filterMessages: ChatCompletionMessageParam[];
}) => {
if (maxToken === undefined) return;
maxToken = Math.min(maxToken, model.maxResponse);
- const tokensLimit = model.maxContext;
-
- /* count response max token */
- const promptsToken = await countGptMessagesTokens(filterMessages);
- maxToken = promptsToken + maxToken > tokensLimit ? tokensLimit - promptsToken : maxToken;
-
- if (maxToken <= 0) {
- maxToken = 200;
- }
return maxToken;
};
@@ -40,6 +30,7 @@ export const computedTemperature = ({
model: LLMModelItemType;
temperature: number;
}) => {
+ if (typeof model.maxTemperature !== 'number') return undefined;
temperature = +(model.maxTemperature * (temperature / 10)).toFixed(2);
temperature = Math.max(temperature, 0.01);
diff --git a/packages/service/core/chat/utils.ts b/packages/service/core/chat/utils.ts
index 130b894e8..b0b62170a 100644
--- a/packages/service/core/chat/utils.ts
+++ b/packages/service/core/chat/utils.ts
@@ -14,36 +14,19 @@ import { serverRequestBaseUrl } from '../../common/api/serverRequest';
import { i18nT } from '../../../web/i18n/utils';
import { addLog } from '../../common/system/log';
-export const filterGPTMessageByMaxTokens = async ({
+export const filterGPTMessageByMaxContext = async ({
messages = [],
- maxTokens
+ maxContext
}: {
messages: ChatCompletionMessageParam[];
- maxTokens: number;
+ maxContext: number;
}) => {
if (!Array.isArray(messages)) {
return [];
}
- const rawTextLen = messages.reduce((sum, item) => {
- if (typeof item.content === 'string') {
- return sum + item.content.length;
- }
- if (Array.isArray(item.content)) {
- return (
- sum +
- item.content.reduce((sum, item) => {
- if (item.type === 'text') {
- return sum + item.text.length;
- }
- return sum;
- }, 0)
- );
- }
- return sum;
- }, 0);
// If the text length is less than half of the maximum token, no calculation is required
- if (rawTextLen < maxTokens * 0.5) {
+ if (messages.length < 4) {
return messages;
}
@@ -55,7 +38,7 @@ export const filterGPTMessageByMaxTokens = async ({
const chatPrompts: ChatCompletionMessageParam[] = messages.slice(chatStartIndex);
// reduce token of systemPrompt
- maxTokens -= await countGptMessagesTokens(systemPrompts);
+ maxContext -= await countGptMessagesTokens(systemPrompts);
// Save the last chat prompt(question)
const question = chatPrompts.pop();
@@ -73,9 +56,9 @@ export const filterGPTMessageByMaxTokens = async ({
}
const tokens = await countGptMessagesTokens([assistant, user]);
- maxTokens -= tokens;
+ maxContext -= tokens;
/* 整体 tokens 超出范围,截断 */
- if (maxTokens < 0) {
+ if (maxContext < 0) {
break;
}
diff --git a/packages/service/core/workflow/dispatch/agent/extract.ts b/packages/service/core/workflow/dispatch/agent/extract.ts
index 61a0737bf..431bdb4f6 100644
--- a/packages/service/core/workflow/dispatch/agent/extract.ts
+++ b/packages/service/core/workflow/dispatch/agent/extract.ts
@@ -1,5 +1,5 @@
import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
-import { filterGPTMessageByMaxTokens, loadRequestMessages } from '../../../chat/utils';
+import { filterGPTMessageByMaxContext, loadRequestMessages } from '../../../chat/utils';
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import {
countMessagesTokens,
@@ -175,9 +175,9 @@ ${description ? `- ${description}` : ''}
}
];
const adaptMessages = chats2GPTMessages({ messages, reserveId: false });
- const filterMessages = await filterGPTMessageByMaxTokens({
+ const filterMessages = await filterGPTMessageByMaxContext({
messages: adaptMessages,
- maxTokens: extractModel.maxContext
+ maxContext: extractModel.maxContext
});
const requestMessages = await loadRequestMessages({
messages: filterMessages,
diff --git a/packages/service/core/workflow/dispatch/agent/runTool/functionCall.ts b/packages/service/core/workflow/dispatch/agent/runTool/functionCall.ts
index 92cdfa9cd..fa7e27187 100644
--- a/packages/service/core/workflow/dispatch/agent/runTool/functionCall.ts
+++ b/packages/service/core/workflow/dispatch/agent/runTool/functionCall.ts
@@ -1,5 +1,5 @@
import { createChatCompletion } from '../../../../ai/config';
-import { filterGPTMessageByMaxTokens, loadRequestMessages } from '../../../../chat/utils';
+import { filterGPTMessageByMaxContext, loadRequestMessages } from '../../../../chat/utils';
import {
ChatCompletion,
StreamChatType,
@@ -172,10 +172,14 @@ export const runToolWithFunctionCall = async (
};
});
+ const max_tokens = computedMaxToken({
+ model: toolModel,
+ maxToken
+ });
const filterMessages = (
- await filterGPTMessageByMaxTokens({
+ await filterGPTMessageByMaxContext({
messages,
- maxTokens: toolModel.maxContext - 300 // filter token. not response maxToken
+ maxContext: toolModel.maxContext - (max_tokens || 0) // filter token. not response maxToken
})
).map((item) => {
if (item.role === ChatCompletionRequestMessageRoleEnum.Assistant && item.function_call) {
@@ -190,16 +194,11 @@ export const runToolWithFunctionCall = async (
}
return item;
});
- const [requestMessages, max_tokens] = await Promise.all([
+ const [requestMessages] = await Promise.all([
loadRequestMessages({
messages: filterMessages,
useVision: toolModel.vision && aiChatVision,
origin: requestOrigin
- }),
- computedMaxToken({
- model: toolModel,
- maxToken,
- filterMessages
})
]);
const requestBody = llmCompletionsBodyFormat(
diff --git a/packages/service/core/workflow/dispatch/agent/runTool/promptCall.ts b/packages/service/core/workflow/dispatch/agent/runTool/promptCall.ts
index 7f380b55d..331496852 100644
--- a/packages/service/core/workflow/dispatch/agent/runTool/promptCall.ts
+++ b/packages/service/core/workflow/dispatch/agent/runTool/promptCall.ts
@@ -1,5 +1,5 @@
import { createChatCompletion } from '../../../../ai/config';
-import { filterGPTMessageByMaxTokens, loadRequestMessages } from '../../../../chat/utils';
+import { filterGPTMessageByMaxContext, loadRequestMessages } from '../../../../chat/utils';
import {
ChatCompletion,
StreamChatType,
@@ -196,21 +196,20 @@ export const runToolWithPromptCall = async (
return Promise.reject('Prompt call invalid input');
}
- const filterMessages = await filterGPTMessageByMaxTokens({
+ const max_tokens = computedMaxToken({
+ model: toolModel,
+ maxToken
+ });
+ const filterMessages = await filterGPTMessageByMaxContext({
messages,
- maxTokens: toolModel.maxContext - 500 // filter token. not response maxToken
+ maxContext: toolModel.maxContext - (max_tokens || 0) // filter token. not response maxToken
});
- const [requestMessages, max_tokens] = await Promise.all([
+ const [requestMessages] = await Promise.all([
loadRequestMessages({
messages: filterMessages,
useVision: toolModel.vision && aiChatVision,
origin: requestOrigin
- }),
- computedMaxToken({
- model: toolModel,
- maxToken,
- filterMessages
})
]);
const requestBody = llmCompletionsBodyFormat(
diff --git a/packages/service/core/workflow/dispatch/agent/runTool/toolChoice.ts b/packages/service/core/workflow/dispatch/agent/runTool/toolChoice.ts
index 2b0f7d457..b2b845f75 100644
--- a/packages/service/core/workflow/dispatch/agent/runTool/toolChoice.ts
+++ b/packages/service/core/workflow/dispatch/agent/runTool/toolChoice.ts
@@ -1,5 +1,5 @@
import { createChatCompletion } from '../../../../ai/config';
-import { filterGPTMessageByMaxTokens, loadRequestMessages } from '../../../../chat/utils';
+import { filterGPTMessageByMaxContext, loadRequestMessages } from '../../../../chat/utils';
import {
ChatCompletion,
ChatCompletionMessageToolCall,
@@ -228,11 +228,16 @@ export const runToolWithToolChoice = async (
};
});
+ const max_tokens = computedMaxToken({
+ model: toolModel,
+ maxToken
+ });
+
// Filter histories by maxToken
const filterMessages = (
- await filterGPTMessageByMaxTokens({
+ await filterGPTMessageByMaxContext({
messages,
- maxTokens: toolModel.maxContext - 300 // filter token. not response maxToken
+ maxContext: toolModel.maxContext - (max_tokens || 0) // filter token. not response maxToken
})
).map((item) => {
if (item.role === 'assistant' && item.tool_calls) {
@@ -248,16 +253,11 @@ export const runToolWithToolChoice = async (
return item;
});
- const [requestMessages, max_tokens] = await Promise.all([
+ const [requestMessages] = await Promise.all([
loadRequestMessages({
messages: filterMessages,
useVision: toolModel.vision && aiChatVision,
origin: requestOrigin
- }),
- computedMaxToken({
- model: toolModel,
- maxToken,
- filterMessages
})
]);
const requestBody = llmCompletionsBodyFormat(
diff --git a/packages/service/core/workflow/dispatch/chat/oneapi.ts b/packages/service/core/workflow/dispatch/chat/oneapi.ts
index 23dccfe93..93f23651f 100644
--- a/packages/service/core/workflow/dispatch/chat/oneapi.ts
+++ b/packages/service/core/workflow/dispatch/chat/oneapi.ts
@@ -1,5 +1,5 @@
import type { NextApiResponse } from 'next';
-import { filterGPTMessageByMaxTokens, loadRequestMessages } from '../../../chat/utils';
+import { filterGPTMessageByMaxContext, loadRequestMessages } from '../../../chat/utils';
import type { ChatItemType, UserChatItemValueItemType } from '@fastgpt/global/core/chat/type.d';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
@@ -58,6 +58,7 @@ export type ChatProps = ModuleDispatchProps<
>;
export type ChatResponse = DispatchNodeResultType<{
[NodeOutputKeyEnum.answerText]: string;
+ [NodeOutputKeyEnum.reasoningText]?: string;
[NodeOutputKeyEnum.history]: ChatItemType[];
}>;
@@ -87,22 +88,24 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise {
- // censor model and system key
if (modelConstantsData.censor && !externalProvider.openaiAccount?.key) {
return postTextCensor({
text: `${systemPrompt}
@@ -149,18 +158,11 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise {
+ const { answerText, reasoningText } = await (async () => {
if (res && isStreamResponse) {
// sse response
- const { answer } = await streamResponse({
+ const { answer, reasoning } = await streamResponse({
res,
stream: response,
+ aiChatReasoning,
workflowStreamResponse
});
return {
- answerText: answer
+ answerText: answer,
+ reasoningText: reasoning
};
} else {
const unStreamResponse = response as ChatCompletion;
const answer = unStreamResponse.choices?.[0]?.message?.content || '';
-
+ const reasoning = aiChatReasoning
+ ? // @ts-ignore
+ unStreamResponse.choices?.[0]?.message?.reasoning_content || ''
+ : '';
if (stream) {
// Some models do not support streaming
- workflowStreamResponse?.({
- event: SseResponseEventEnum.fastAnswer,
- data: textAdaptGptResponse({
- text: answer
- })
- });
+ reasoning &&
+ workflowStreamResponse?.({
+ event: SseResponseEventEnum.fastAnswer,
+ data: textAdaptGptResponse({
+ text: answer,
+ reasoning_content: reasoning
+ })
+ });
}
return {
- answerText: answer
+ answerText: answer,
+ reasoningText: reasoning
};
}
})();
@@ -241,6 +251,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise,
'nodeResponse'
@@ -251,6 +253,13 @@ export async function dispatchWorkFlow(data: Props): Promise import('./icons/core/chat/sideLine.svg'),
'core/chat/speaking': () => import('./icons/core/chat/speaking.svg'),
'core/chat/stopSpeech': () => import('./icons/core/chat/stopSpeech.svg'),
+ 'core/chat/think': () => import('./icons/core/chat/think.svg'),
'core/dataset/commonDataset': () => import('./icons/core/dataset/commonDataset.svg'),
'core/dataset/commonDatasetColor': () => import('./icons/core/dataset/commonDatasetColor.svg'),
'core/dataset/commonDatasetOutline': () =>
diff --git a/packages/web/components/common/Icon/icons/core/chat/think.svg b/packages/web/components/common/Icon/icons/core/chat/think.svg
new file mode 100644
index 000000000..260a944a9
--- /dev/null
+++ b/packages/web/components/common/Icon/icons/core/chat/think.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/web/hooks/useScrollPagination.tsx b/packages/web/hooks/useScrollPagination.tsx
index 4ecc77c44..a326a20b7 100644
--- a/packages/web/hooks/useScrollPagination.tsx
+++ b/packages/web/hooks/useScrollPagination.tsx
@@ -304,7 +304,7 @@ export function useScrollPagination<
);
return (
-
+
{scrollLoadType === 'top' && total > 0 && isLoading && (
{t('common:common.is_requesting')}
diff --git a/packages/web/i18n/en/account.json b/packages/web/i18n/en/account.json
index 6d5827bae..f4251adfb 100644
--- a/packages/web/i18n/en/account.json
+++ b/packages/web/i18n/en/account.json
@@ -49,10 +49,10 @@
"model.output_price": "Output price",
"model.output_price_tip": "The language model output price. If this item is configured, the model comprehensive price will be invalid.",
"model.param_name": "Parameter name",
- "model.request_auth": "Custom token",
+ "model.request_auth": "Custom key",
"model.request_auth_tip": "When making a request to a custom request address, carry the request header: Authorization: Bearer xxx to make the request.",
"model.request_url": "Custom url",
- "model.request_url_tip": "If this value is filled in, a request will be made directly to this address without going through OneAPI",
+ "model.request_url_tip": "If you fill in this value, you will initiate a request directly without passing. \nYou need to follow the API format of Openai and fill in the full request address, such as\n\nLLM: {Host}}/v1/Chat/Completions\n\nEmbedding: {host}}/v1/embeddings\n\nSTT: {Host}/v1/Audio/Transcriptions\n\nTTS: {Host}}/v1/Audio/Speech\n\nRERARARARARARARANK: {Host}}/v1/RERARARARARARARARARARANK",
"model.test_model": "Model testing",
"model.tool_choice": "Tool choice",
"model.tool_choice_tag": "ToolCall",
diff --git a/packages/web/i18n/en/app.json b/packages/web/i18n/en/app.json
index c280bcd09..c3ca4a379 100644
--- a/packages/web/i18n/en/app.json
+++ b/packages/web/i18n/en/app.json
@@ -109,6 +109,7 @@
"publish_channel": "Publish",
"publish_success": "Publish Successful",
"question_guide_tip": "After the conversation, 3 guiding questions will be generated for you.",
+ "reasoning_response": "Output thinking",
"saved_success": "Saved successfully! \nTo use this version externally, click Save and Publish",
"search_app": "Search apps",
"setting_app": "Workflow",
diff --git a/packages/web/i18n/en/chat.json b/packages/web/i18n/en/chat.json
index b930d44af..45604e158 100644
--- a/packages/web/i18n/en/chat.json
+++ b/packages/web/i18n/en/chat.json
@@ -2,6 +2,7 @@
"AI_input_is_empty": "The content passed to the AI node is empty",
"Delete_all": "Clear All Lexicon",
"LLM_model_response_empty": "The model flow response is empty, please check whether the model flow output is normal.",
+ "ai_reasoning": "Thinking process",
"chat_history": "Conversation History",
"chat_input_guide_lexicon_is_empty": "Lexicon not configured yet",
"chat_test_app": "Debug-{{name}}",
diff --git a/packages/web/i18n/zh-CN/account.json b/packages/web/i18n/zh-CN/account.json
index f4359db83..02901e774 100644
--- a/packages/web/i18n/zh-CN/account.json
+++ b/packages/web/i18n/zh-CN/account.json
@@ -49,10 +49,10 @@
"model.output_price": "模型输出价格",
"model.output_price_tip": "语言模型输出价格,如果配置了该项,则模型综合价格会失效",
"model.param_name": "参数名",
- "model.request_auth": "自定义请求 Tokens",
+ "model.request_auth": "自定义请求 Key",
"model.request_auth_tip": "向自定义请求地址发起请求时候,携带请求头:Authorization: Bearer xxx 进行请求",
"model.request_url": "自定义请求地址",
- "model.request_url_tip": "如果填写该值,则会直接向该地址发起请求,不经过 OneAPI",
+ "model.request_url_tip": "如果填写该值,则会直接向该地址发起请求,不经过 OneAPI。需要遵循 OpenAI 的 API格式,并填写完整请求地址,例如:\nLLM: {{host}}/v1/chat/completions\nEmbedding: {{host}}/v1/embeddings\nSTT: {{host}}/v1/audio/transcriptions\nTTS: {{host}}/v1/audio/speech\nRerank: {{host}}/v1/rerank",
"model.test_model": "模型测试",
"model.tool_choice": "支持工具调用",
"model.tool_choice_tag": "工具调用",
diff --git a/packages/web/i18n/zh-CN/app.json b/packages/web/i18n/zh-CN/app.json
index 59c7f1339..d75cd9ea5 100644
--- a/packages/web/i18n/zh-CN/app.json
+++ b/packages/web/i18n/zh-CN/app.json
@@ -109,6 +109,7 @@
"publish_channel": "发布渠道",
"publish_success": "发布成功",
"question_guide_tip": "对话结束后,会为你生成 3 个引导性问题。",
+ "reasoning_response": "输出思考",
"saved_success": "保存成功!如需在外部使用该版本,请点击“保存并发布”",
"search_app": "搜索应用",
"setting_app": "应用配置",
diff --git a/packages/web/i18n/zh-CN/chat.json b/packages/web/i18n/zh-CN/chat.json
index 856401274..e75ae0a21 100644
--- a/packages/web/i18n/zh-CN/chat.json
+++ b/packages/web/i18n/zh-CN/chat.json
@@ -2,6 +2,7 @@
"AI_input_is_empty": "传入AI 节点的内容为空",
"Delete_all": "清空词库",
"LLM_model_response_empty": "模型流响应为空,请检查模型流输出是否正常",
+ "ai_reasoning": "思考过程",
"chat_history": "聊天记录",
"chat_input_guide_lexicon_is_empty": "还没有配置词库",
"chat_test_app": "调试-{{name}}",
diff --git a/packages/web/i18n/zh-Hant/account.json b/packages/web/i18n/zh-Hant/account.json
index 41cee42f9..5edb5b08e 100644
--- a/packages/web/i18n/zh-Hant/account.json
+++ b/packages/web/i18n/zh-Hant/account.json
@@ -48,10 +48,10 @@
"model.output_price": "模型輸出價格",
"model.output_price_tip": "語言模型輸出價格,如果配置了該項,則模型綜合價格會失效",
"model.param_name": "參數名",
- "model.request_auth": "自訂請求 Tokens",
+ "model.request_auth": "自訂請求 Key",
"model.request_auth_tip": "向自訂請求地址發起請求時候,攜帶請求頭:Authorization: Bearer xxx 進行請求",
"model.request_url": "自訂請求地址",
- "model.request_url_tip": "如果填入該值,則會直接向該位址發起請求,不經過 OneAPI",
+ "model.request_url_tip": "如果填寫該值,則會直接向該地址發起請求,不經過 OneAPI。\n需要遵循 OpenAI 的 API格式,並填寫完整請求地址,例如:\n\nLLM: {{host}}/v1/chat/completions\n\nEmbedding: {{host}}/v1/embeddings\n\nSTT: {{host}}/v1/audio/transcriptions\n\nTTS: {{host}}/v1/audio/speech\n\nRerank: {{host}}/v1/rerank",
"model.test_model": "模型測試",
"model.tool_choice": "支援工具調用",
"model.tool_choice_tag": "工具調用",
diff --git a/packages/web/i18n/zh-Hant/app.json b/packages/web/i18n/zh-Hant/app.json
index 7d13b5465..c2b7651f3 100644
--- a/packages/web/i18n/zh-Hant/app.json
+++ b/packages/web/i18n/zh-Hant/app.json
@@ -109,6 +109,7 @@
"publish_channel": "發布通道",
"publish_success": "發布成功",
"question_guide_tip": "對話結束後,會為你產生 3 個引導性問題。",
+ "reasoning_response": "輸出思考",
"saved_success": "保存成功!\n如需在外部使用該版本,請點擊“儲存並發布”",
"search_app": "搜尋應用程式",
"setting_app": "應用程式設定",
diff --git a/packages/web/i18n/zh-Hant/chat.json b/packages/web/i18n/zh-Hant/chat.json
index cb44628d1..2980ba08b 100644
--- a/packages/web/i18n/zh-Hant/chat.json
+++ b/packages/web/i18n/zh-Hant/chat.json
@@ -2,6 +2,7 @@
"AI_input_is_empty": "傳送至 AI 節點的內容為空",
"Delete_all": "清除所有詞彙",
"LLM_model_response_empty": "模型流程回應為空,請檢查模型流程輸出是否正常",
+ "ai_reasoning": "思考過程",
"chat_history": "對話紀錄",
"chat_input_guide_lexicon_is_empty": "尚未設定詞彙庫",
"chat_test_app": "調試-{{name}}",
diff --git a/projects/app/src/components/core/ai/AISettingModal/index.tsx b/projects/app/src/components/core/ai/AISettingModal/index.tsx
index 65e385419..5059119c7 100644
--- a/projects/app/src/components/core/ai/AISettingModal/index.tsx
+++ b/projects/app/src/components/core/ai/AISettingModal/index.tsx
@@ -72,6 +72,7 @@ const AIChatSettingsModal = ({
defaultValues: defaultData
});
const model = watch('model');
+ const reasoning = watch(NodeInputKeyEnum.aiChatReasoning);
const showResponseAnswerText = watch(NodeInputKeyEnum.aiChatIsResponseText) !== undefined;
const showVisionSwitch = watch(NodeInputKeyEnum.aiChatVision) !== undefined;
const showMaxHistoriesSlider = watch('maxHistories') !== undefined;
@@ -84,6 +85,8 @@ const AIChatSettingsModal = ({
return getWebLLMModel(model);
}, [model]);
const llmSupportVision = !!selectedModel?.vision;
+ const llmSupportTemperature = typeof selectedModel?.maxTemperature === 'number';
+ const llmSupportReasoning = !!selectedModel?.reasoning;
const tokenLimit = useMemo(() => {
return selectedModel?.maxResponse || 4096;
@@ -258,36 +261,51 @@ const AIChatSettingsModal = ({
/>
-
-
-
- {t('app:temperature')}
-
-
- {
- setValue('temperature', e.target.checked ? 0 : undefined);
- }}
- />
-
-
-
- {
- setValue(NodeInputKeyEnum.aiChatTemperature, e);
- setRefresh(!refresh);
- }}
- />
-
-
-
+ {llmSupportTemperature && (
+
+
+
+ {t('app:temperature')}
+
+
+ {
+ setValue('temperature', e.target.checked ? 0 : undefined);
+ }}
+ />
+
+
+ {
+ setValue(NodeInputKeyEnum.aiChatTemperature, e);
+ setRefresh(!refresh);
+ }}
+ />
+
+
+ )}
+ {llmSupportReasoning && (
+
+
+ {t('app:reasoning_response')}
+ {
+ const value = e.target.checked;
+ setValue(NodeInputKeyEnum.aiChatReasoning, value);
+ }}
+ />
+
+
+ )}
{showResponseAnswerText && (
diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx
index 54534b622..56cb41e7f 100644
--- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx
+++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx
@@ -201,6 +201,7 @@ const ChatBox = ({
({
event,
text = '',
+ reasoningText,
status,
name,
tool,
@@ -247,6 +248,25 @@ const ChatBox = ({
value: item.value.slice(0, -1).concat(lastValue)
};
}
+ } else if (event === SseResponseEventEnum.answer && reasoningText) {
+ if (lastValue.type === ChatItemValueTypeEnum.reasoning && lastValue.reasoning) {
+ lastValue.reasoning.content += reasoningText;
+ return {
+ ...item,
+ value: item.value.slice(0, -1).concat(lastValue)
+ };
+ } else {
+ const val: AIChatItemValueItemType = {
+ type: ChatItemValueTypeEnum.reasoning,
+ reasoning: {
+ content: reasoningText
+ }
+ };
+ return {
+ ...item,
+ value: item.value.concat(val)
+ };
+ }
} else if (event === SseResponseEventEnum.toolCall && tool) {
const val: AIChatItemValueItemType = {
type: ChatItemValueTypeEnum.tool,
diff --git a/projects/app/src/components/core/chat/ChatContainer/type.d.ts b/projects/app/src/components/core/chat/ChatContainer/type.d.ts
index 74774e227..a8b8494a7 100644
--- a/projects/app/src/components/core/chat/ChatContainer/type.d.ts
+++ b/projects/app/src/components/core/chat/ChatContainer/type.d.ts
@@ -6,6 +6,7 @@ import { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/t
export type generatingMessageProps = {
event: SseResponseEventEnum;
text?: string;
+ reasoningText?: string;
name?: string;
status?: 'running' | 'finish';
tool?: ToolModuleResponseItemType;
diff --git a/projects/app/src/components/core/chat/components/AIResponseBox.tsx b/projects/app/src/components/core/chat/components/AIResponseBox.tsx
index 2f14b6f8c..552171a71 100644
--- a/projects/app/src/components/core/chat/components/AIResponseBox.tsx
+++ b/projects/app/src/components/core/chat/components/AIResponseBox.tsx
@@ -8,6 +8,7 @@ import {
Box,
Button,
Flex,
+ HStack,
Textarea
} from '@chakra-ui/react';
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
@@ -139,6 +140,55 @@ ${toolResponse}`}
},
(prevProps, nextProps) => isEqual(prevProps, nextProps)
);
+const RenderResoningContent = React.memo(function RenderResoningContent({
+ content,
+ showAnimation
+}: {
+ content: string;
+ showAnimation: boolean;
+}) {
+ const { t } = useTranslation();
+
+ return (
+
+
+
+
+
+ {t('chat:ai_reasoning')}
+
+
+ {showAnimation && }
+
+
+
+
+
+
+
+ );
+});
const RenderUserSelectInteractive = React.memo(function RenderInteractive({
interactive
}: {
@@ -290,6 +340,8 @@ const AIResponseBox = ({ value, isLastResponseValue, isChatting }: props) => {
return (
);
+ if (value.type === ChatItemValueTypeEnum.reasoning && value.reasoning)
+ return ;
if (value.type === ChatItemValueTypeEnum.tool && value.tools)
return ;
if (value.type === ChatItemValueTypeEnum.interactive && value.interactive) {
diff --git a/projects/app/src/pageComponents/account/model/ModelConfigTable.tsx b/projects/app/src/pageComponents/account/model/ModelConfigTable.tsx
index 980b2146d..bce13c11c 100644
--- a/projects/app/src/pageComponents/account/model/ModelConfigTable.tsx
+++ b/projects/app/src/pageComponents/account/model/ModelConfigTable.tsx
@@ -803,6 +803,10 @@ const ModelEditModal = ({
{
+ if (!e) {
+ setValue('defaultConfig', undefined);
+ return;
+ }
try {
setValue('defaultConfig', JSON.parse(e));
} catch (error) {
@@ -1009,6 +1013,10 @@ const ModelEditModal = ({
value={JSON.stringify(getValues('defaultConfig'), null, 2)}
resize
onChange={(e) => {
+ if (!e) {
+ setValue('defaultConfig', undefined);
+ return;
+ }
try {
setValue('defaultConfig', JSON.parse(e));
} catch (error) {
diff --git a/projects/app/src/pageComponents/account/team/GroupManage/GroupManageMember.tsx b/projects/app/src/pageComponents/account/team/GroupManage/GroupManageMember.tsx
index 95528e7ec..847e28d5e 100644
--- a/projects/app/src/pageComponents/account/team/GroupManage/GroupManageMember.tsx
+++ b/projects/app/src/pageComponents/account/team/GroupManage/GroupManageMember.tsx
@@ -14,7 +14,7 @@ import Avatar from '@fastgpt/web/components/common/Avatar';
import Tag from '@fastgpt/web/components/common/Tag';
import { useTranslation } from 'next-i18next';
-import React, { useMemo, useState } from 'react';
+import React, { useMemo, useRef, useState } from 'react';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useContextSelector } from 'use-context-selector';
import { TeamContext } from '../context';
@@ -50,6 +50,8 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro
const refetchMembers = useContextSelector(TeamContext, (v) => v.refetchMembers);
const MemberScrollData = useContextSelector(TeamContext, (v) => v.MemberScrollData);
const [hoveredMemberId, setHoveredMemberId] = useState();
+
+ const selectedMembersRef = useRef(null);
const [members, setMembers] = useState(group?.members || []);
const [searchKey, setSearchKey] = useState('');
@@ -155,7 +157,7 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro
setSearchKey(e.target.value);
}}
/>
-
+
{filtered.map((member) => {
return (
void; editGro
{t('common:chosen') + ': ' + members.length}
-
+
{members.map((member) => {
return (
-
-
+
+
@@ -246,9 +246,9 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
))}
-
-
-
+
+
+
diff --git a/projects/app/src/pageComponents/account/team/OrgManage/OrgMemberManageModal.tsx b/projects/app/src/pageComponents/account/team/OrgManage/OrgMemberManageModal.tsx
index fd5a990ae..8ecd21739 100644
--- a/projects/app/src/pageComponents/account/team/OrgManage/OrgMemberManageModal.tsx
+++ b/projects/app/src/pageComponents/account/team/OrgManage/OrgMemberManageModal.tsx
@@ -121,36 +121,34 @@ function OrgMemberManageModal({
setSearchKey(e.target.value);
}}
/>
-
-
- {filterMembers.map((member) => {
- return (
- handleToggleSelect(member.tmbId)}
- >
- }
- pointerEvents="none"
- />
-
- {member.memberName}
-
- );
- })}
-
-
+
+ {filterMembers.map((member) => {
+ return (
+ handleToggleSelect(member.tmbId)}
+ >
+ }
+ pointerEvents="none"
+ />
+
+ {member.memberName}
+
+ );
+ })}
+
{`${t('common:chosen')}:${selectedMembers.length}`}
diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SettingLLMModel.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SettingLLMModel.tsx
index 5fbb8601e..5e4e2c557 100644
--- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SettingLLMModel.tsx
+++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SettingLLMModel.tsx
@@ -38,7 +38,9 @@ const SelectAiModelRender = ({ item, inputs = [], nodeId }: RenderInputProps) =>
(input) => input.key === NodeInputKeyEnum.aiChatIsResponseText
)?.value,
aiChatVision:
- inputs.find((input) => input.key === NodeInputKeyEnum.aiChatVision)?.value ?? true
+ inputs.find((input) => input.key === NodeInputKeyEnum.aiChatVision)?.value ?? true,
+ aiChatReasoning:
+ inputs.find((input) => input.key === NodeInputKeyEnum.aiChatReasoning)?.value ?? true
}),
[inputs]
);
diff --git a/projects/app/src/web/common/api/fetch.ts b/projects/app/src/web/common/api/fetch.ts
index ba82ed797..960a049e9 100644
--- a/projects/app/src/web/common/api/fetch.ts
+++ b/projects/app/src/web/common/api/fetch.ts
@@ -186,6 +186,12 @@ export const streamFetch = ({
text: item
});
}
+
+ const reasoningText = parseJson.choices?.[0]?.delta?.reasoning_content || '';
+ onMessage({
+ event,
+ reasoningText
+ });
} else if (event === SseResponseEventEnum.fastAnswer) {
const text = parseJson.choices?.[0]?.delta?.content || '';
pushDataToQueue({
diff --git a/projects/app/src/web/core/app/templates.ts b/projects/app/src/web/core/app/templates.ts
index 1b128e11f..59a05ba8d 100644
--- a/projects/app/src/web/core/app/templates.ts
+++ b/projects/app/src/web/core/app/templates.ts
@@ -1,7 +1,7 @@
import { parseCurl } from '@fastgpt/global/common/string/http';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { AppSchema } from '@fastgpt/global/core/app/type';
-import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
+import { NodeInputKeyEnum, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import {
FlowNodeInputTypeEnum,
FlowNodeOutputTypeEnum,
@@ -150,7 +150,7 @@ export const emptyTemplates: Record<
key: 'temperature',
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
- value: 0,
+ value: undefined,
valueType: WorkflowIOValueTypeEnum.number,
min: 0,
max: 10,
@@ -160,7 +160,7 @@ export const emptyTemplates: Record<
key: 'maxToken',
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
- value: 2000,
+ value: undefined,
valueType: WorkflowIOValueTypeEnum.number,
min: 100,
max: 4000,
@@ -221,6 +221,13 @@ export const emptyTemplates: Record<
debugLabel: i18nT('common:core.module.Dataset quote.label'),
description: '',
valueType: WorkflowIOValueTypeEnum.datasetQuote
+ },
+ {
+ key: NodeInputKeyEnum.aiChatReasoning,
+ renderTypeList: [FlowNodeInputTypeEnum.hidden],
+ label: '',
+ valueType: WorkflowIOValueTypeEnum.boolean,
+ value: true
}
],
outputs: [