From 13b7e0a19269aadcbcd20636319277dde6b1c924 Mon Sep 17 00:00:00 2001 From: Archer <545436317@qq.com> Date: Tue, 22 Jul 2025 09:42:50 +0800 Subject: [PATCH] V4.11.0 features (#5270) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: workflow catch error (#5220) * feat: error catch * feat: workflow catch error * perf: add catch error to node * feat: system tool error catch * catch error * fix: ts * update doc * perf: training queue code (#5232) * doc * perf: training queue code * Feat: 优化错误提示与重试逻辑 (#5192) * feat: 批量重试异常数据 & 报错信息国际化 - 新增“全部重试”按钮,支持批量重试所有训练异常数据 - 报错信息支持国际化,常见错误自动映射为 i18n key - 相关文档和 i18n 资源已同步更新 * feat: enhance error message and retry mechanism * feat: enhance error message and retry mechanism * feat: add retry_failed i18n key * feat: enhance error message and retry mechanism * feat: enhance error message and retry mechanism * feat: enhance error message and retry mechanism : 5 * feat: enhance error message and retry mechanism : 6 * feat: enhance error message and retry mechanism : 7 * feat: enhance error message and retry mechanism : 8 * perf: catch chat error * perf: copy hook (#5246) * perf: copy hook * doc * doc * add app evaluation (#5083) * add app evaluation * fix * usage * variables * editing condition * var ui * isplus filter * migrate code * remove utils * name * update type * build * fix * fix * fix * delete comment * fix * perf: eval code * eval code * eval code * feat: ttfb time in model log * Refactor chat page (#5253) * feat: update side bar layout; add login and logout logic at chat page * refactor: encapsulate login logic and reuse it in `LoginModal` and `Login` page * chore: improve some logics and comments * chore: improve some logics * chore: remove redundant side effect; add translations --------- Co-authored-by: Archer <545436317@qq.com> * perf: chat page code * doc * perf: provider redirect * chore: ui improvement (#5266) * Fix: SSE * Fix: SSE * eval pagination (#5264) * eval scroll pagination * change eval list to manual pagination * number * fix build * fix * version doc (#5267) * version doc * version doc * doc * feat: eval model select * config eval model * perf: eval detail modal ui * doc * doc * fix: chat store reload * doc --------- Co-authored-by: colnii <1286949794@qq.com> Co-authored-by: heheer Co-authored-by: 酒川户 <76519998+chuanhu9@users.noreply.github.com> --- .../design/core/app/workflow/error_catch.mdc | 28 + README.md | 35 +- .../zh-cn/docs/development/upgrading/4110.md | 56 ++ .../content/zh-cn/docs/shopping_cart/intro.md | 7 +- env.d.ts | 35 -- packages/global/common/error/code/user.ts | 5 - packages/global/common/error/utils.ts | 6 + .../global/common/system/types/index.d.ts | 3 +- packages/global/common/system/utils.ts | 16 +- packages/global/core/ai/model.d.ts | 1 + packages/global/core/app/evaluation/api.d.ts | 20 + .../global/core/app/evaluation/constants.ts | 22 + packages/global/core/app/evaluation/type.d.ts | 51 ++ packages/global/core/app/evaluation/utils.ts | 10 + packages/global/core/app/mcpTools/utils.ts | 4 +- packages/global/core/app/plugin/type.d.ts | 3 + packages/global/core/app/type.d.ts | 1 + packages/global/core/workflow/constants.ts | 10 +- .../global/core/workflow/node/constant.ts | 1 + .../global/core/workflow/runtime/type.d.ts | 23 +- .../global/core/workflow/runtime/utils.ts | 3 +- .../global/core/workflow/template/output.ts | 9 + .../core/workflow/template/system/agent.ts | 5 +- .../workflow/template/system/aiChat/index.ts | 5 +- .../template/system/contextExtract/index.ts | 5 +- .../workflow/template/system/datasetSearch.ts | 5 +- .../core/workflow/template/system/http468.ts | 16 +- .../core/workflow/template/system/laf.ts | 8 +- .../template/system/readFiles/index.tsx | 4 +- .../workflow/template/system/sandbox/index.ts | 16 +- packages/global/core/workflow/type/node.d.ts | 1 + packages/global/core/workflow/utils.ts | 24 +- .../global/support/user/audit/constants.ts | 3 + .../global/support/wallet/usage/constants.ts | 6 +- .../global/support/wallet/usage/type.d.ts | 2 + packages/service/common/bullmq/index.ts | 1 + .../service/common/otel/trace/register.ts | 4 - .../core/ai/config/provider/ChatGLM.json | 30 - .../core/ai/config/provider/Claude.json | 35 -- .../core/ai/config/provider/DeepSeek.json | 10 - .../core/ai/config/provider/Doubao.json | 75 --- .../core/ai/config/provider/Ernie.json | 20 - .../core/ai/config/provider/Gemini.json | 82 +-- .../service/core/ai/config/provider/Grok.json | 52 +- .../service/core/ai/config/provider/Groq.json | 10 - .../core/ai/config/provider/Hunyuan.json | 35 -- .../core/ai/config/provider/Intern.json | 10 - .../core/ai/config/provider/MiniMax.json | 10 - .../core/ai/config/provider/MistralAI.json | 20 - .../core/ai/config/provider/Moonshot.json | 98 ++- .../core/ai/config/provider/OpenAI.json | 61 -- .../service/core/ai/config/provider/Qwen.json | 92 --- .../core/ai/config/provider/Siliconflow.json | 10 - .../core/ai/config/provider/SparkDesk.json | 30 - .../core/ai/config/provider/StepFun.json | 55 -- .../service/core/ai/config/provider/Yi.json | 10 - packages/service/core/ai/config/utils.ts | 9 + packages/service/core/ai/model.ts | 10 +- .../core/app/evaluation/evalItemSchema.ts | 56 ++ .../service/core/app/evaluation/evalSchema.ts | 57 ++ packages/service/core/app/evaluation/mq.ts | 80 +++ .../service/core/app/plugin/controller.ts | 45 +- .../core/app/plugin/systemPluginSchema.ts | 3 +- packages/service/core/app/plugin/type.d.ts | 2 +- packages/service/core/app/utils.ts | 22 + packages/service/core/chat/saveChat.ts | 2 +- .../workflow/dispatch/abandoned/runApp.ts | 8 +- .../core/workflow/dispatch/ai/agent/index.ts | 431 ++++++------- .../service/core/workflow/dispatch/ai/chat.ts | 463 +++++++------- .../workflow/dispatch/ai/classifyQuestion.ts | 4 +- .../core/workflow/dispatch/ai/extract.ts | 145 ++--- .../core/workflow/dispatch/child/runApp.ts | 208 +++++++ .../dispatch/{plugin => child}/runTool.ts | 136 +++-- .../core/workflow/dispatch/dataset/concat.ts | 10 +- .../core/workflow/dispatch/dataset/search.ts | 344 ++++++----- .../service/core/workflow/dispatch/index.ts | 132 ++-- .../workflow/dispatch/init/workflowStart.tsx | 7 +- .../dispatch/interactive/formInput.ts | 13 +- .../dispatch/interactive/userSelect.ts | 13 +- .../core/workflow/dispatch/loop/runLoop.ts | 10 +- .../workflow/dispatch/loop/runLoopStart.ts | 8 +- .../core/workflow/dispatch/plugin/run.ts | 271 ++++---- .../core/workflow/dispatch/plugin/runApp.ts | 203 ------ .../core/workflow/dispatch/plugin/runInput.ts | 29 +- .../core/workflow/dispatch/tools/answer.ts | 4 +- .../{code/run.ts => tools/codeSandbox.ts} | 60 +- .../core/workflow/dispatch/tools/http468.ts | 42 +- .../dispatch/tools/queryExternsion.ts | 6 +- .../core/workflow/dispatch/tools/readFiles.ts | 55 +- .../core/workflow/dispatch/tools/runIfElse.ts | 4 +- .../core/workflow/dispatch/tools/runLaf.ts | 25 +- .../workflow/dispatch/tools/textEditor.ts | 4 +- .../service/core/workflow/dispatch/utils.ts | 32 +- packages/service/package.json | 2 +- .../support/permission/evaluation/auth.ts | 64 ++ .../service/support/permission/teamLimit.ts | 4 - .../support/wallet/usage/controller.ts | 43 ++ .../service/support/wallet/usage/type.d.ts | 10 +- packages/service/type/env.d.ts | 53 ++ .../web/components/common/Icon/constants.ts | 25 +- .../common/Icon/icons/common/error.svg | 3 + .../Icon/icons/core/chat/sidebar/logout.svg | 3 + .../web/components/common/Icon/icons/mcp.svg | 3 + .../common/Input/SearchInput/index.tsx | 17 +- packages/web/hooks/useCopyData.tsx | 24 +- packages/web/hooks/usePagination.tsx | 21 +- packages/web/hooks/useScrollPagination.tsx | 38 +- packages/web/i18n/constants.ts | 3 +- packages/web/i18n/en/account_model.json | 2 + packages/web/i18n/en/account_team.json | 2 + packages/web/i18n/en/account_usage.json | 2 + packages/web/i18n/en/app.json | 2 +- packages/web/i18n/en/chat.json | 1 + packages/web/i18n/en/common.json | 6 + .../web/i18n/en/dashboard_evaluation.json | 50 ++ packages/web/i18n/en/dataset.json | 2 + packages/web/i18n/en/workflow.json | 3 +- packages/web/i18n/i18next.d.ts | 1 + packages/web/i18n/zh-CN/account_model.json | 2 + packages/web/i18n/zh-CN/account_team.json | 6 + packages/web/i18n/zh-CN/account_usage.json | 4 + packages/web/i18n/zh-CN/app.json | 2 +- packages/web/i18n/zh-CN/chat.json | 1 + packages/web/i18n/zh-CN/common.json | 7 + .../web/i18n/zh-CN/dashboard_evaluation.json | 53 ++ packages/web/i18n/zh-CN/dataset.json | 2 + packages/web/i18n/zh-CN/workflow.json | 3 +- packages/web/i18n/zh-Hant/account_model.json | 2 + packages/web/i18n/zh-Hant/account_team.json | 2 + packages/web/i18n/zh-Hant/account_usage.json | 2 + packages/web/i18n/zh-Hant/app.json | 2 +- packages/web/i18n/zh-Hant/chat.json | 1 + packages/web/i18n/zh-Hant/common.json | 6 + .../i18n/zh-Hant/dashboard_evaluation.json | 46 ++ packages/web/i18n/zh-Hant/dataset.json | 2 + packages/web/i18n/zh-Hant/workflow.json | 3 +- packages/web/styles/theme.ts | 5 + packages/web/support/user/audit/constants.ts | 18 + pnpm-lock.yaml | 10 +- projects/app/package.json | 2 +- projects/app/public/docs/versionIntro.md | 30 +- projects/app/public/imgs/fastgpt_slogan.png | Bin 0 -> 4622 bytes projects/app/src/components/Layout/auth.tsx | 1 + projects/app/src/components/Layout/navbar.tsx | 12 +- .../app/src/components/Layout/navbarPhone.tsx | 8 +- .../app/src/components/Select/AppSelect.tsx | 89 +++ .../common/folder/SelectOneResource.tsx | 4 +- .../components/core/app/InputGuideConfig.tsx | 2 +- .../ChatBox/components/Empty.tsx | 2 +- .../core/chat/ChatContainer/ChatBox/index.tsx | 11 +- .../chat/components/WholeResponseModal.tsx | 6 +- projects/app/src/global/aiproxy/type.d.ts | 2 + .../account/model/AddModelBox.tsx | 10 + .../account/model/Log/index.tsx | 36 +- .../account/usage/UsageDetail.tsx | 99 +-- .../app/detail/Logs/DetailLogsModal.tsx | 1 - .../app/detail/MCPTools/ChatTest.tsx | 1 - .../app/detail/SimpleApp/ChatTest.tsx | 1 - .../WorkflowComponents/Flow/ChatTest.tsx | 1 - .../Flow/SelectAppModal.tsx | 2 +- .../Flow/components/IOTitle.tsx | 51 +- .../detail/WorkflowComponents/Flow/index.tsx | 2 +- .../nodes/{NodeTools.tsx => NodeAgent.tsx} | 17 +- .../Flow/nodes/NodeCode.tsx | 10 +- .../Flow/nodes/NodeExtract/index.tsx | 36 +- .../Flow/nodes/NodeHttp/index.tsx | 34 +- .../Flow/nodes/NodeSimple.tsx | 27 +- .../nodes/render/Handle/ConnectionHandle.tsx | 10 +- .../Flow/nodes/render/NodeCard.tsx | 6 +- .../RenderInput/templates/Reference.tsx | 23 +- .../nodes/render/RenderOutput/CatchError.tsx | 25 + .../WorkflowComponents/context/index.tsx | 92 +-- .../{utils.tsx => utils.ts} | 26 +- .../app/evaluation/DetailModal.tsx | 578 ++++++++++++++++++ .../src/pageComponents/chat/ChatHeader.tsx | 6 +- .../pageComponents/chat/ChatHistorySlider.tsx | 60 +- .../src/pageComponents/chat/SliderApps.tsx | 139 +++-- .../app/src/pageComponents/chat/ToolMenu.tsx | 19 +- .../pageComponents/chat/UserAvatarPopover.tsx | 66 ++ .../pageComponents/dashboard/Container.tsx | 28 +- .../pageComponents/dashboard/apps/List.tsx | 6 +- .../detail/CollectionCard/TagManageModal.tsx | 15 +- .../detail/CollectionCard/TrainingStates.tsx | 53 +- .../login/ForgetPasswordForm.tsx | 1 - .../login/LoginForm/FormLayout.tsx | 148 ++--- .../src/pageComponents/login/LoginModal.tsx | 49 ++ .../src/pageComponents/login/RegisterForm.tsx | 1 - .../app/src/pageComponents/login/index.tsx | 297 +++++++++ projects/app/src/pages/_app.tsx | 6 +- projects/app/src/pages/api/core/app/del.ts | 11 + projects/app/src/pages/api/core/app/list.ts | 13 +- .../pages/api/core/dataset/collection/list.ts | 5 - .../api/core/dataset/collection/listV2.ts | 5 - .../dataset/training/updateTrainingData.ts | 6 +- projects/app/src/pages/chat/index.tsx | 172 +++--- projects/app/src/pages/chat/share.tsx | 1 - projects/app/src/pages/chat/team.tsx | 1 - .../app/src/pages/dashboard/apps/index.tsx | 48 +- .../src/pages/dashboard/evaluation/create.tsx | 378 ++++++++++++ .../src/pages/dashboard/evaluation/index.tsx | 294 +++++++++ projects/app/src/pages/login/index.tsx | 291 ++------- projects/app/src/pages/login/provider.tsx | 23 +- .../app/src/service/common/system/index.ts | 3 +- .../core/dataset/queues/datasetParse.ts | 470 +++++++------- .../service/core/dataset/queues/generateQA.ts | 275 ++++----- .../core/dataset/queues/generateVector.ts | 213 ++++--- .../app/src/web/core/app/api/evaluation.ts | 58 ++ .../src/web/core/chat/context/chatContext.tsx | 2 +- .../web/core/chat/context/chatItemContext.tsx | 4 - .../src/web/core/chat/context/useChatStore.ts | 9 +- projects/app/src/web/core/workflow/utils.ts | 91 ++- projects/mcp_server/Dockerfile | 1 + 212 files changed, 5840 insertions(+), 3400 deletions(-) create mode 100644 .cursor/design/core/app/workflow/error_catch.mdc create mode 100644 docSite/content/zh-cn/docs/development/upgrading/4110.md create mode 100644 packages/global/core/app/evaluation/api.d.ts create mode 100644 packages/global/core/app/evaluation/constants.ts create mode 100644 packages/global/core/app/evaluation/type.d.ts create mode 100644 packages/global/core/app/evaluation/utils.ts create mode 100644 packages/service/core/app/evaluation/evalItemSchema.ts create mode 100644 packages/service/core/app/evaluation/evalSchema.ts create mode 100644 packages/service/core/app/evaluation/mq.ts create mode 100644 packages/service/core/workflow/dispatch/child/runApp.ts rename packages/service/core/workflow/dispatch/{plugin => child}/runTool.ts (64%) delete mode 100644 packages/service/core/workflow/dispatch/plugin/runApp.ts rename packages/service/core/workflow/dispatch/{code/run.ts => tools/codeSandbox.ts} (57%) create mode 100644 packages/service/support/permission/evaluation/auth.ts create mode 100644 packages/service/type/env.d.ts create mode 100644 packages/web/components/common/Icon/icons/common/error.svg create mode 100644 packages/web/components/common/Icon/icons/core/chat/sidebar/logout.svg create mode 100644 packages/web/components/common/Icon/icons/mcp.svg create mode 100644 packages/web/i18n/en/dashboard_evaluation.json create mode 100644 packages/web/i18n/zh-CN/dashboard_evaluation.json create mode 100644 packages/web/i18n/zh-Hant/dashboard_evaluation.json create mode 100644 projects/app/public/imgs/fastgpt_slogan.png create mode 100644 projects/app/src/components/Select/AppSelect.tsx rename projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/{NodeTools.tsx => NodeAgent.tsx} (63%) create mode 100644 projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderOutput/CatchError.tsx rename projects/app/src/pageComponents/app/detail/WorkflowComponents/{utils.tsx => utils.ts} (89%) create mode 100644 projects/app/src/pageComponents/app/evaluation/DetailModal.tsx create mode 100644 projects/app/src/pageComponents/chat/UserAvatarPopover.tsx create mode 100644 projects/app/src/pageComponents/login/LoginModal.tsx create mode 100644 projects/app/src/pageComponents/login/index.tsx create mode 100644 projects/app/src/pages/dashboard/evaluation/create.tsx create mode 100644 projects/app/src/pages/dashboard/evaluation/index.tsx create mode 100644 projects/app/src/web/core/app/api/evaluation.ts diff --git a/.cursor/design/core/app/workflow/error_catch.mdc b/.cursor/design/core/app/workflow/error_catch.mdc new file mode 100644 index 000000000..9462f0de0 --- /dev/null +++ b/.cursor/design/core/app/workflow/error_catch.mdc @@ -0,0 +1,28 @@ +--- +description: +globs: +alwaysApply: false +--- +这是一个工作流节点报错捕获的设计文档。 + +# 背景 + +现在工作流运行节点若其中有一个报错,则整个工作流中断,无法继续运行,只有红色英文toast,不太友好。对于对工作流可控性要求高的用户,愿意通过编排为报错兜底。现有编排较麻烦。 + +这类似于代码里的 try catch 机制,用户可以获得 catch 的错误,而不是直接抛错并结束工作流。 + +# 实现效果 + +1. 部分节点可以拥有报错捕获选项,也就是 node 里 catchError 不为 undefined 的节点,catchError=true 代表启用报错捕获,catchError=false 代表不启用报错捕获。 +2. 支持报错捕获节点,在输出栏右侧会有一个“错误捕获”的开关。 +3. node 的 output 属性种,有一个`errorField`的字段,标识该输出是开启报错捕获时候,才会拥有的输出。 +4. 开启报错捕获的节点,在运行错误时,不会阻塞后续节点运行,而是输出报错信息,并继续向下执行。 +5. 开启报错捕获的节点,会多出一个“错误输出”的分支连线,错误时候会走错误的分支提示。 + +# 实现方案 + +1. FlowNodeCommonType 属性上增加一个`catchError`的可选 boolean 值。如果需要报错捕获的节点,则设置 true/false,标识启用报错捕获,并且设置默认是否启用报错捕获。 +2. FlowNodeOutputTypeEnume增加一个 error 枚举值,表示该字段是错误时候才展示的输出。 +3. IOTitle 组件里接收 catchError 字段,如果为 true,则在右侧展示“错误捕获”的开关。 +4. 所有现有的 RenderOutput 的组件,都需要改造。传入的 flowOutputList 都不包含 hidden 和 error类型的。 +5. 单独在`FastGPT/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderOutput`下新建一个`CatchError`的组件,用于专门渲染错误类型输出,同时有一个 SourceHandler。 \ No newline at end of file diff --git a/README.md b/README.md index 41624aa76..41f295c89 100644 --- a/README.md +++ b/README.md @@ -47,40 +47,41 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b ## 💡 RoadMap `1` 应用编排能力 - - [x] 对话工作流、插件工作流 - - [x] 工具调用 - - [x] Code sandbox - - [x] 循环调用 - - [x] 用户选择 - - [x] 表单输入 + - [x] 对话工作流、插件工作流,包含基础的 RPA 节点。 + - [x] Agent 调用 + - [x] 用户交互节点 + - [x] 双向 MCP + - [ ] 上下文管理 + - [ ] AI 生成工作流 -`2` 知识库能力 +`2` 应用调试能力 + - [x] 知识库单点搜索测试 + - [x] 对话时反馈引用并可修改与删除 + - [x] 完整调用链路日志 + - [ ] 应用评测 + - [ ] 高级编排 DeBug 调试模式 + - [ ] 应用节点日志 + +`3` 知识库能力 - [x] 多库复用,混用 - [x] chunk 记录修改和删除 - [x] 支持手动输入,直接分段,QA 拆分导入 - [x] 支持 txt,md,html,pdf,docx,pptx,csv,xlsx (有需要更多可 PR file loader),支持 url 读取、CSV 批量导入 - [x] 混合检索 & 重排 - [x] API 知识库 - - [ ] 自定义文件读取服务 - - [ ] 自定义分块服务 - -`3` 应用调试能力 - - [x] 知识库单点搜索测试 - - [x] 对话时反馈引用并可修改与删除 - - [x] 完整上下文呈现 - - [x] 完整模块中间值呈现 - - [ ] 高级编排 DeBug 模式 + - [ ] RAG 模块热插拔 `4` OpenAPI 接口 - [x] completions 接口 (chat 模式对齐 GPT 接口) - [x] 知识库 CRUD - [x] 对话 CRUD + - [ ] 完整 API Documents `5` 运营能力 - [x] 免登录分享窗口 - [x] Iframe 一键嵌入 - - [x] 聊天窗口嵌入支持自定义 Icon,默认打开,拖拽等功能 - [x] 统一查阅对话记录,并对数据进行标注 + - [ ] 应用运营日志 `6` 其他 - [x] 可视化模型配置。 diff --git a/docSite/content/zh-cn/docs/development/upgrading/4110.md b/docSite/content/zh-cn/docs/development/upgrading/4110.md new file mode 100644 index 000000000..05c4d1f1b --- /dev/null +++ b/docSite/content/zh-cn/docs/development/upgrading/4110.md @@ -0,0 +1,56 @@ +--- +title: 'V4.11.0(进行中)' +description: 'FastGPT V4.11.0 更新说明' +icon: 'upgrade' +draft: false +toc: true +weight: 783 +--- + + + +## 项目调整 + +1. 移除所有**开源功能**的限制,包括:应用数量和知识库数量上限。 +2. 调整 RoadMap,增加`上下文管理`,`AI 生成工作流`,`高级编排 DeBug 调试模式`等计划。 + +## 🚀 新增内容 + +1. 商业版增加**应用评测(Beta 版)**,可对应用进行有监督评分。 +2. 工作流部分节点支持报错捕获分支。 +3. 对话页独立 tab 页面UX。 +4. 支持 Signoz traces 和 logs 系统追踪。 +5. 新增 Gemini2.5, grok4, kimi 模型配置。 +6. 模型调用日志增加首字响应时长和请求 IP。 + +## ⚙️ 优化 + +1. 优化代码,避免递归造成的内存堆积。 +2. 知识库训练:支持全部重试当前集合异常数据。 +3. 工作流 valueTypeFormat,避免数据类型不一致。 + +## 🐛 修复 + +1. 问题分类和内容提取节点,默认模型无法通过前端校验,导致工作流无法运行和保存发布。 + +## 🔨 工具更新 + +1. Markdown 文本转 Docx 和 Xlsx 文件。 \ No newline at end of file diff --git a/docSite/content/zh-cn/docs/shopping_cart/intro.md b/docSite/content/zh-cn/docs/shopping_cart/intro.md index 4a7e93496..09f48361b 100644 --- a/docSite/content/zh-cn/docs/shopping_cart/intro.md +++ b/docSite/content/zh-cn/docs/shopping_cart/intro.md @@ -20,19 +20,18 @@ FastGPT 商业版是基于 FastGPT 开源版的增强版本,增加了一些独 | 文档知识库 | ✅ | ✅ | ✅ | | 外部使用 | ✅ | ✅ | ✅ | | API 知识库 | ✅ | ✅ | ✅ | -| 最大应用数量 | 500 | 无限制 | 由付费套餐决定 | -| 最大知识库数量(单个知识库内容无限制) | 30 | 无限制 | 由付费套餐决定 | | 自定义版权信息 | ❌ | ✅ | 设计中 | | 多租户与支付 | ❌ | ✅ | ✅ | | 团队空间 & 权限 | ❌ | ✅ | ✅ | | 应用发布安全配置 | ❌ | ✅ | ✅ | | 内容审核 | ❌ | ✅ | ✅ | +| 应用评测 | ❌ | ✅ | ✅ | | web站点同步 | ❌ | ✅ | ✅ | -| 增强训练模式 | ❌ | ✅ | ✅ | +| 图片知识库 | ❌ | ✅ | ✅ | +| 知识库索引增强 | ❌ | ✅ | ✅ | | 第三方应用快速接入(飞书、公众号) | ❌ | ✅ | ✅ | | 管理后台 | ❌ | ✅ | 不需要 | | SSO 登录(可自定义,也可使用内置:Github、公众号、钉钉、谷歌等) | ❌ | ✅ | 不需要 | -| 图片知识库 | ❌ | 设计中 | 设计中 | | 对话日志运营分析 | ❌ | 设计中 | 设计中 | | 完整商业授权 | ❌ | ✅ | ✅ | {{< /table >}} diff --git a/env.d.ts b/env.d.ts index 22af25344..11c4c6a1f 100644 --- a/env.d.ts +++ b/env.d.ts @@ -1,43 +1,8 @@ declare global { namespace NodeJS { interface ProcessEnv { - LOG_DEPTH: string; DEFAULT_ROOT_PSW: string; - DB_MAX_LINK: string; - FILE_TOKEN_KEY: string; - AES256_SECRET_KEY: string; - ROOT_KEY: string; - OPENAI_BASE_URL: string; - CHAT_API_KEY: string; - AIPROXY_API_ENDPOINT: string; - AIPROXY_API_TOKEN: string; - MULTIPLE_DATA_TO_BASE64: string; - MONGODB_URI: string; - MONGODB_LOG_URI?: string; - PG_URL: string; - OCEANBASE_URL: string; - MILVUS_ADDRESS: string; - MILVUS_TOKEN: string; - SANDBOX_URL: string; PRO_URL: string; - FE_DOMAIN: string; - FILE_DOMAIN: string; - NEXT_PUBLIC_BASE_URL: string; - LOG_LEVEL?: string; - STORE_LOG_LEVEL?: string; - USE_IP_LIMIT?: string; - WORKFLOW_MAX_RUN_TIMES?: string; - WORKFLOW_MAX_LOOP_TIMES?: string; - CHECK_INTERNAL_IP?: string; - CHAT_LOG_URL?: string; - CHAT_LOG_INTERVAL?: string; - CHAT_LOG_SOURCE_ID_PREFIX?: string; - ALLOWED_ORIGINS?: string; - SHOW_COUPON?: string; - CONFIG_JSON_PATH?: string; - PASSWORD_LOGIN_LOCK_SECONDS?: string; - PASSWORD_EXPIRED_MONTH?: string; - MAX_LOGIN_SESSION?: string; } } } diff --git a/packages/global/common/error/code/user.ts b/packages/global/common/error/code/user.ts index e7e6e4795..e859ec8bb 100644 --- a/packages/global/common/error/code/user.ts +++ b/packages/global/common/error/code/user.ts @@ -6,7 +6,6 @@ export enum UserErrEnum { userExist = 'userExist', unAuthRole = 'unAuthRole', account_psw_error = 'account_psw_error', - balanceNotEnough = 'balanceNotEnough', unAuthSso = 'unAuthSso' } const errList = [ @@ -22,10 +21,6 @@ const errList = [ statusText: UserErrEnum.account_psw_error, message: i18nT('common:code_error.account_error') }, - { - statusText: UserErrEnum.balanceNotEnough, - message: i18nT('common:code_error.user_error.balance_not_enough') - }, { statusText: UserErrEnum.unAuthSso, message: i18nT('user:sso_auth_failed') diff --git a/packages/global/common/error/utils.ts b/packages/global/common/error/utils.ts index d3a9a8020..c13c8e266 100644 --- a/packages/global/common/error/utils.ts +++ b/packages/global/common/error/utils.ts @@ -1,4 +1,5 @@ import { replaceSensitiveText } from '../string/tools'; +import { ERROR_RESPONSE } from './errorCode'; export const getErrText = (err: any, def = ''): any => { const msg: string = @@ -12,6 +13,11 @@ export const getErrText = (err: any, def = ''): any => { err?.msg || err?.error || def; + + if (ERROR_RESPONSE[msg]) { + return ERROR_RESPONSE[msg].message; + } + // msg && console.log('error =>', msg); return replaceSensitiveText(msg); }; diff --git a/packages/global/common/system/types/index.d.ts b/packages/global/common/system/types/index.d.ts index f6f6c183d..7710ee12f 100644 --- a/packages/global/common/system/types/index.d.ts +++ b/packages/global/common/system/types/index.d.ts @@ -51,6 +51,7 @@ export type FastGPTFeConfigsType = { bind_notification_method?: ['email' | 'phone']; googleClientVerKey?: string; mcpServerProxyEndpoint?: string; + chineseRedirectUrl?: string; show_emptyChat?: boolean; show_appStore?: boolean; @@ -82,7 +83,6 @@ export type FastGPTFeConfigsType = { customSharePageDomain?: string; systemTitle?: string; - systemDescription?: string; scripts?: { [key: string]: string }[]; favicon?: string; @@ -109,6 +109,7 @@ export type FastGPTFeConfigsType = { uploadFileMaxAmount?: number; uploadFileMaxSize?: number; + evalFileMaxLines?: number; // Compute by systemEnv.customPdfParse showCustomPdfParse?: boolean; diff --git a/packages/global/common/system/utils.ts b/packages/global/common/system/utils.ts index e58761f48..0a76d7b35 100644 --- a/packages/global/common/system/utils.ts +++ b/packages/global/common/system/utils.ts @@ -5,15 +5,17 @@ export const delay = (ms: number) => }, ms); }); -export const retryFn = async (fn: () => Promise, retryTimes = 3): Promise => { - try { - return fn(); - } catch (error) { - if (retryTimes > 0) { +export const retryFn = async (fn: () => Promise, attempts = 3): Promise => { + while (true) { + try { + return fn(); + } catch (error) { + if (attempts <= 0) { + return Promise.reject(error); + } await delay(500); - return retryFn(fn, retryTimes - 1); + attempts--; } - return Promise.reject(error); } }; diff --git a/packages/global/core/ai/model.d.ts b/packages/global/core/ai/model.d.ts index c32c3d251..4b5f4f802 100644 --- a/packages/global/core/ai/model.d.ts +++ b/packages/global/core/ai/model.d.ts @@ -47,6 +47,7 @@ export type LLMModelItemType = PriceType & usedInClassify?: boolean; // classify usedInExtractFields?: boolean; // extract fields usedInToolCall?: boolean; // tool call + useInEvaluation?: boolean; // evaluation functionCall: boolean; toolChoice: boolean; diff --git a/packages/global/core/app/evaluation/api.d.ts b/packages/global/core/app/evaluation/api.d.ts new file mode 100644 index 000000000..8c1d8e873 --- /dev/null +++ b/packages/global/core/app/evaluation/api.d.ts @@ -0,0 +1,20 @@ +import type { PaginationProps } from '@fastgpt/web/common/fetch/type'; + +export type listEvaluationsBody = PaginationProps<{ + searchKey?: string; +}>; + +export type listEvalItemsBody = PaginationProps<{ + evalId: string; +}>; + +export type retryEvalItemBody = { + evalItemId: string; +}; + +export type updateEvalItemBody = { + evalItemId: string; + question: string; + expectedResponse: string; + variables: Record; +}; diff --git a/packages/global/core/app/evaluation/constants.ts b/packages/global/core/app/evaluation/constants.ts new file mode 100644 index 000000000..d6b029858 --- /dev/null +++ b/packages/global/core/app/evaluation/constants.ts @@ -0,0 +1,22 @@ +import { i18nT } from '../../../../web/i18n/utils'; + +export const evaluationFileErrors = i18nT('dashboard_evaluation:eval_file_check_error'); + +export enum EvaluationStatusEnum { + queuing = 0, + evaluating = 1, + completed = 2 +} + +export const EvaluationStatusMap = { + [EvaluationStatusEnum.queuing]: { + name: i18nT('dashboard_evaluation:queuing') + }, + [EvaluationStatusEnum.evaluating]: { + name: i18nT('dashboard_evaluation:evaluating') + }, + [EvaluationStatusEnum.completed]: { + name: i18nT('dashboard_evaluation:completed') + } +}; +export const EvaluationStatusValues = Object.keys(EvaluationStatusMap).map(Number); diff --git a/packages/global/core/app/evaluation/type.d.ts b/packages/global/core/app/evaluation/type.d.ts new file mode 100644 index 000000000..2a497a509 --- /dev/null +++ b/packages/global/core/app/evaluation/type.d.ts @@ -0,0 +1,51 @@ +import type { EvaluationStatusEnum } from './constants'; + +export type EvaluationSchemaType = { + _id: string; + teamId: string; + tmbId: string; + evalModel: string; + appId: string; + usageId: string; + name: string; + createTime: Date; + finishTime?: Date; + score?: number; + errorMessage?: string; +}; + +export type EvalItemSchemaType = { + evalId: string; + question: string; + expectedResponse: string; + globalVariables?: Record; + history?: string; + response?: string; + responseTime?: Date; + finishTime?: Date; + status: EvaluationStatusEnum; + retry: number; + errorMessage?: string; + accuracy?: number; + relevance?: number; + semanticAccuracy?: number; + score?: number; +}; + +export type evaluationType = Pick< + EvaluationSchemaType, + 'name' | 'appId' | 'createTime' | 'finishTime' | 'evalModel' | 'errorMessage' | 'score' +> & { + _id: string; + executorAvatar: string; + executorName: string; + appAvatar: string; + appName: string; + completedCount: number; + errorCount: number; + totalCount: number; +}; + +export type listEvalItemsItem = EvalItemSchemaType & { + evalItemId: string; +}; diff --git a/packages/global/core/app/evaluation/utils.ts b/packages/global/core/app/evaluation/utils.ts new file mode 100644 index 000000000..adad61c67 --- /dev/null +++ b/packages/global/core/app/evaluation/utils.ts @@ -0,0 +1,10 @@ +import type { VariableItemType } from '../type'; + +export const getEvaluationFileHeader = (appVariables?: VariableItemType[]) => { + if (!appVariables || appVariables.length === 0) return '*q,*a,history'; + + const variablesStr = appVariables + .map((item) => (item.required ? `*${item.key}` : item.key)) + .join(','); + return `${variablesStr},*q,*a,history`; +}; diff --git a/packages/global/core/app/mcpTools/utils.ts b/packages/global/core/app/mcpTools/utils.ts index b7d2d0d91..9f60e43c9 100644 --- a/packages/global/core/app/mcpTools/utils.ts +++ b/packages/global/core/app/mcpTools/utils.ts @@ -32,11 +32,11 @@ export const getMCPToolSetRuntimeNode = ({ nodeId: getNanoid(16), flowNodeType: FlowNodeTypeEnum.toolSet, avatar, - intro: 'MCP Tools', + intro: '', inputs: [ { key: NodeInputKeyEnum.toolSetData, - label: 'Tool Set Data', + label: '', valueType: WorkflowIOValueTypeEnum.object, renderTypeList: [FlowNodeInputTypeEnum.hidden], value: { diff --git a/packages/global/core/app/plugin/type.d.ts b/packages/global/core/app/plugin/type.d.ts index 1377074cd..e3babb98f 100644 --- a/packages/global/core/app/plugin/type.d.ts +++ b/packages/global/core/app/plugin/type.d.ts @@ -34,6 +34,9 @@ export type SystemPluginTemplateItemType = WorkflowTemplateType & { versionList?: { value: string; description?: string; + + inputs: FlowNodeInputItemType[]; + outputs: FlowNodeOutputItemType[]; }[]; // Admin workflow tool diff --git a/packages/global/core/app/type.d.ts b/packages/global/core/app/type.d.ts index 3d83a9f44..8e4c86a94 100644 --- a/packages/global/core/app/type.d.ts +++ b/packages/global/core/app/type.d.ts @@ -66,6 +66,7 @@ export type AppListItemType = { inheritPermission?: boolean; private?: boolean; sourceMember: SourceMemberType; + hasInteractiveNode?: boolean; }; export type AppDetailType = AppSchema & { diff --git a/packages/global/core/workflow/constants.ts b/packages/global/core/workflow/constants.ts index 7498d30f4..533e2e776 100644 --- a/packages/global/core/workflow/constants.ts +++ b/packages/global/core/workflow/constants.ts @@ -269,11 +269,11 @@ export enum NodeOutputKeyEnum { reasoningText = 'reasoningText', // node reasoning. the value will be show but not save to history success = 'success', failed = 'failed', - error = 'error', text = 'system_text', addOutputParam = 'system_addOutputParam', rawResponse = 'system_rawResponse', systemError = 'system_error', + errorText = 'system_error_text', // start userFiles = 'userFiles', @@ -312,7 +312,13 @@ export enum NodeOutputKeyEnum { loopStartIndex = 'loopStartIndex', // form input - formInputResult = 'formInputResult' + formInputResult = 'formInputResult', + + // File + fileTitle = 'fileTitle', + + // @deprecated + error = 'error' } export enum VariableInputEnum { diff --git a/packages/global/core/workflow/node/constant.ts b/packages/global/core/workflow/node/constant.ts index 2b86b2f7c..667f244c5 100644 --- a/packages/global/core/workflow/node/constant.ts +++ b/packages/global/core/workflow/node/constant.ts @@ -99,6 +99,7 @@ export const FlowNodeInputMap: Record< export enum FlowNodeOutputTypeEnum { hidden = 'hidden', + error = 'error', source = 'source', static = 'static', dynamic = 'dynamic' diff --git a/packages/global/core/workflow/runtime/type.d.ts b/packages/global/core/workflow/runtime/type.d.ts index 8b69cb11a..a9db069be 100644 --- a/packages/global/core/workflow/runtime/type.d.ts +++ b/packages/global/core/workflow/runtime/type.d.ts @@ -9,7 +9,7 @@ import type { FlowNodeInputItemType, FlowNodeOutputItemType } from '../type/io.d import type { NodeToolConfigType, StoreNodeItemType } from '../type/node'; import type { DispatchNodeResponseKeyEnum } from './constants'; import type { StoreEdgeItemType } from '../type/edge'; -import type { NodeInputKeyEnum } from '../constants'; +import type { NodeInputKeyEnum, NodeOutputKeyEnum } from '../constants'; import type { ClassifyQuestionAgentItemType } from '../template/system/classifyQuestion/type'; import type { NextApiResponse } from 'next'; import { UserModelSchema } from '../../../support/user/type'; @@ -24,7 +24,10 @@ import type { AiChatQuoteRoleType } from '../template/system/aiChat/type'; import type { OpenaiAccountType } from '../../../support/user/team/type'; import { LafAccountType } from '../../../support/user/team/type'; import type { CompletionFinishReason } from '../../ai/type'; -import type { WorkflowInteractiveResponseType } from '../template/system/interactive/type'; +import type { + InteractiveNodeResponseType, + WorkflowInteractiveResponseType +} from '../template/system/interactive/type'; import type { SearchDataResponseItemType } from '../../dataset/type'; export type ExternalProviderType = { openaiAccount?: OpenaiAccountType; @@ -104,6 +107,9 @@ export type RuntimeNodeItemType = { // Tool toolConfig?: StoreNodeItemType['toolConfig']; + + // catch error + catchError?: boolean; }; export type RuntimeEdgeItemType = StoreEdgeItemType & { @@ -116,7 +122,12 @@ export type DispatchNodeResponseType = { runningTime?: number; query?: string; textOutput?: string; + + // Client will toast error?: Record | string; + // Just show + errorText?: string; + customInputs?: Record; customOutputs?: Record; nodeInputs?: Record; @@ -235,7 +246,7 @@ export type DispatchNodeResponseType = { extensionTokens?: number; }; -export type DispatchNodeResultType = { +export type DispatchNodeResultType = { [DispatchNodeResponseKeyEnum.skipHandleId]?: string[]; // skip some edge handle id [DispatchNodeResponseKeyEnum.nodeResponse]?: DispatchNodeResponseType; // The node response detail [DispatchNodeResponseKeyEnum.nodeDispatchUsages]?: ChatNodeUsageType[]; // Node total usage @@ -246,7 +257,11 @@ export type DispatchNodeResultType = { [DispatchNodeResponseKeyEnum.runTimes]?: number; [DispatchNodeResponseKeyEnum.newVariables]?: Record; [DispatchNodeResponseKeyEnum.memories]?: Record; -} & T; + [DispatchNodeResponseKeyEnum.interactive]?: InteractiveNodeResponseType; + + data?: T; + error?: ERR; +}; /* Single node props */ export type AIChatNodeProps = { diff --git a/packages/global/core/workflow/runtime/utils.ts b/packages/global/core/workflow/runtime/utils.ts index bde558c52..1fe272ce5 100644 --- a/packages/global/core/workflow/runtime/utils.ts +++ b/packages/global/core/workflow/runtime/utils.ts @@ -251,7 +251,8 @@ export const storeNodes2RuntimeNodes = ( outputs: node.outputs, pluginId: node.pluginId, version: node.version, - toolConfig: node.toolConfig + toolConfig: node.toolConfig, + catchError: node.catchError }; }) || [] ); diff --git a/packages/global/core/workflow/template/output.ts b/packages/global/core/workflow/template/output.ts index 826938fb8..c86963ad1 100644 --- a/packages/global/core/workflow/template/output.ts +++ b/packages/global/core/workflow/template/output.ts @@ -2,6 +2,7 @@ import type { FlowNodeOutputItemType } from '../type/io.d'; import { NodeOutputKeyEnum } from '../constants'; import { FlowNodeOutputTypeEnum } from '../node/constant'; import { WorkflowIOValueTypeEnum } from '../constants'; +import { i18nT } from '../../../../web/i18n/utils'; export const Output_Template_AddOutput: FlowNodeOutputItemType = { id: NodeOutputKeyEnum.addOutputParam, @@ -15,3 +16,11 @@ export const Output_Template_AddOutput: FlowNodeOutputItemType = { showDefaultValue: false } }; + +export const Output_Template_Error_Message: FlowNodeOutputItemType = { + id: NodeOutputKeyEnum.errorText, + key: NodeOutputKeyEnum.errorText, + type: FlowNodeOutputTypeEnum.error, + valueType: WorkflowIOValueTypeEnum.string, + label: i18nT('workflow:error_text') +}; diff --git a/packages/global/core/workflow/template/system/agent.ts b/packages/global/core/workflow/template/system/agent.ts index 3fe9baccd..ad9fe59ae 100644 --- a/packages/global/core/workflow/template/system/agent.ts +++ b/packages/global/core/workflow/template/system/agent.ts @@ -20,6 +20,7 @@ import { chatNodeSystemPromptTip, systemPromptTip } from '../tip'; import { LLMModelTypeEnum } from '../../../ai/constants'; import { i18nT } from '../../../../../web/i18n/utils'; import { Input_Template_File_Link } from '../input'; +import { Output_Template_Error_Message } from '../output'; export const AgentNode: FlowNodeTemplateType = { id: FlowNodeTypeEnum.agent, @@ -31,6 +32,7 @@ export const AgentNode: FlowNodeTemplateType = { name: i18nT('workflow:template.agent'), intro: i18nT('workflow:template.agent_intro'), showStatus: true, + catchError: false, courseUrl: '/docs/guide/dashboard/workflow/tool/', version: '4.9.2', inputs: [ @@ -107,6 +109,7 @@ export const AgentNode: FlowNodeTemplateType = { description: i18nT('common:core.module.output.description.Ai response content'), valueType: WorkflowIOValueTypeEnum.string, type: FlowNodeOutputTypeEnum.static - } + }, + Output_Template_Error_Message ] }; diff --git a/packages/global/core/workflow/template/system/aiChat/index.ts b/packages/global/core/workflow/template/system/aiChat/index.ts index 0881b0b85..e5c2d1b25 100644 --- a/packages/global/core/workflow/template/system/aiChat/index.ts +++ b/packages/global/core/workflow/template/system/aiChat/index.ts @@ -20,6 +20,7 @@ import { Input_Template_File_Link } from '../../input'; import { i18nT } from '../../../../../../web/i18n/utils'; +import { Output_Template_Error_Message } from '../../output'; export const AiChatQuoteRole = { key: NodeInputKeyEnum.aiChatQuoteRole, @@ -54,6 +55,7 @@ export const AiChatModule: FlowNodeTemplateType = { isTool: true, courseUrl: '/docs/guide/dashboard/workflow/ai_chat/', version: '4.9.7', + catchError: false, inputs: [ Input_Template_SettingAiModel, // --- settings modal @@ -158,6 +160,7 @@ export const AiChatModule: FlowNodeTemplateType = { const modelItem = llmModelList.find((item) => item.model === model); return modelItem?.reasoning !== true; } - } + }, + Output_Template_Error_Message ] }; diff --git a/packages/global/core/workflow/template/system/contextExtract/index.ts b/packages/global/core/workflow/template/system/contextExtract/index.ts index 04cab9130..2b3d1b808 100644 --- a/packages/global/core/workflow/template/system/contextExtract/index.ts +++ b/packages/global/core/workflow/template/system/contextExtract/index.ts @@ -13,6 +13,7 @@ import { import { Input_Template_SelectAIModel, Input_Template_History } from '../../input'; import { LLMModelTypeEnum } from '../../../../ai/constants'; import { i18nT } from '../../../../../../web/i18n/utils'; +import { Output_Template_Error_Message } from '../../output'; export const ContextExtractModule: FlowNodeTemplateType = { id: FlowNodeTypeEnum.contentExtract, @@ -25,6 +26,7 @@ export const ContextExtractModule: FlowNodeTemplateType = { intro: i18nT('workflow:intro_text_content_extraction'), showStatus: true, isTool: true, + catchError: false, courseUrl: '/docs/guide/dashboard/workflow/content_extract/', version: '4.9.2', inputs: [ @@ -76,6 +78,7 @@ export const ContextExtractModule: FlowNodeTemplateType = { description: i18nT('workflow:complete_extraction_result_description'), valueType: WorkflowIOValueTypeEnum.string, type: FlowNodeOutputTypeEnum.static - } + }, + Output_Template_Error_Message ] }; diff --git a/packages/global/core/workflow/template/system/datasetSearch.ts b/packages/global/core/workflow/template/system/datasetSearch.ts index 5a9b9b993..79e80f87b 100644 --- a/packages/global/core/workflow/template/system/datasetSearch.ts +++ b/packages/global/core/workflow/template/system/datasetSearch.ts @@ -15,6 +15,7 @@ import { import { Input_Template_UserChatInput } from '../input'; import { DatasetSearchModeEnum } from '../../../dataset/constants'; import { i18nT } from '../../../../../web/i18n/utils'; +import { Output_Template_Error_Message } from '../output'; export const Dataset_SEARCH_DESC = i18nT('workflow:template.dataset_search_intro'); @@ -29,6 +30,7 @@ export const DatasetSearchModule: FlowNodeTemplateType = { intro: Dataset_SEARCH_DESC, showStatus: true, isTool: true, + catchError: false, courseUrl: '/docs/guide/dashboard/workflow/dataset_search/', version: '4.9.2', inputs: [ @@ -143,6 +145,7 @@ export const DatasetSearchModule: FlowNodeTemplateType = { type: FlowNodeOutputTypeEnum.static, valueType: WorkflowIOValueTypeEnum.datasetQuote, valueDesc: datasetQuoteValueDesc - } + }, + Output_Template_Error_Message ] }; diff --git a/packages/global/core/workflow/template/system/http468.ts b/packages/global/core/workflow/template/system/http468.ts index ea0328043..07fb5ea1a 100644 --- a/packages/global/core/workflow/template/system/http468.ts +++ b/packages/global/core/workflow/template/system/http468.ts @@ -26,6 +26,7 @@ export const HttpNode468: FlowNodeTemplateType = { intro: i18nT('workflow:intro_http_request'), showStatus: true, isTool: true, + catchError: false, courseUrl: '/docs/guide/dashboard/workflow/http/', inputs: [ { @@ -123,14 +124,6 @@ export const HttpNode468: FlowNodeTemplateType = { label: i18nT('workflow:http_extract_output'), description: i18nT('workflow:http_extract_output_description') }, - { - id: NodeOutputKeyEnum.error, - key: NodeOutputKeyEnum.error, - label: i18nT('workflow:request_error'), - description: i18nT('workflow:http_request_error_info'), - valueType: WorkflowIOValueTypeEnum.object, - type: FlowNodeOutputTypeEnum.static - }, { id: NodeOutputKeyEnum.httpRawResponse, key: NodeOutputKeyEnum.httpRawResponse, @@ -139,6 +132,13 @@ export const HttpNode468: FlowNodeTemplateType = { description: i18nT('workflow:http_raw_response_description'), valueType: WorkflowIOValueTypeEnum.any, type: FlowNodeOutputTypeEnum.static + }, + { + id: NodeOutputKeyEnum.error, + key: NodeOutputKeyEnum.error, + label: i18nT('workflow:error_text'), + valueType: WorkflowIOValueTypeEnum.string, + type: FlowNodeOutputTypeEnum.error } ] }; diff --git a/packages/global/core/workflow/template/system/laf.ts b/packages/global/core/workflow/template/system/laf.ts index 4a4dfc912..34b06bf49 100644 --- a/packages/global/core/workflow/template/system/laf.ts +++ b/packages/global/core/workflow/template/system/laf.ts @@ -11,7 +11,7 @@ import { FlowNodeTemplateTypeEnum } from '../../constants'; import { Input_Template_DynamicInput } from '../input'; -import { Output_Template_AddOutput } from '../output'; +import { Output_Template_AddOutput, Output_Template_Error_Message } from '../output'; import { i18nT } from '../../../../../web/i18n/utils'; export const nodeLafCustomInputConfig = { @@ -31,6 +31,7 @@ export const LafModule: FlowNodeTemplateType = { intro: i18nT('workflow:intro_laf_function_call'), showStatus: true, isTool: true, + catchError: false, courseUrl: '/docs/guide/dashboard/workflow/laf/', inputs: [ { @@ -57,8 +58,7 @@ export const LafModule: FlowNodeTemplateType = { valueType: WorkflowIOValueTypeEnum.any, type: FlowNodeOutputTypeEnum.static }, - { - ...Output_Template_AddOutput - } + Output_Template_AddOutput, + Output_Template_Error_Message ] }; diff --git a/packages/global/core/workflow/template/system/readFiles/index.tsx b/packages/global/core/workflow/template/system/readFiles/index.tsx index 1e6711bcf..a0e869be0 100644 --- a/packages/global/core/workflow/template/system/readFiles/index.tsx +++ b/packages/global/core/workflow/template/system/readFiles/index.tsx @@ -11,6 +11,7 @@ import { FlowNodeTypeEnum } from '../../../node/constant'; import { type FlowNodeTemplateType } from '../../../type/node'; +import { Output_Template_Error_Message } from '../../output'; export const ReadFilesNode: FlowNodeTemplateType = { id: FlowNodeTypeEnum.readFiles, @@ -43,6 +44,7 @@ export const ReadFilesNode: FlowNodeTemplateType = { description: i18nT('app:workflow.read_files_result_desc'), valueType: WorkflowIOValueTypeEnum.string, type: FlowNodeOutputTypeEnum.static - } + }, + Output_Template_Error_Message ] }; diff --git a/packages/global/core/workflow/template/system/sandbox/index.ts b/packages/global/core/workflow/template/system/sandbox/index.ts index 53422c950..c34a8b6a1 100644 --- a/packages/global/core/workflow/template/system/sandbox/index.ts +++ b/packages/global/core/workflow/template/system/sandbox/index.ts @@ -25,6 +25,7 @@ export const CodeNode: FlowNodeTemplateType = { name: i18nT('workflow:code_execution'), intro: i18nT('workflow:execute_a_simple_script_code_usually_for_complex_data_processing'), showStatus: true, + catchError: false, courseUrl: '/docs/guide/dashboard/workflow/sandbox/', inputs: [ { @@ -89,14 +90,6 @@ export const CodeNode: FlowNodeTemplateType = { valueType: WorkflowIOValueTypeEnum.object, type: FlowNodeOutputTypeEnum.static }, - { - id: NodeOutputKeyEnum.error, - key: NodeOutputKeyEnum.error, - label: i18nT('workflow:execution_error'), - description: i18nT('workflow:error_info_returns_empty_on_success'), - valueType: WorkflowIOValueTypeEnum.object, - type: FlowNodeOutputTypeEnum.static - }, { id: 'qLUQfhG0ILRX', type: FlowNodeOutputTypeEnum.dynamic, @@ -110,6 +103,13 @@ export const CodeNode: FlowNodeTemplateType = { key: 'data2', valueType: WorkflowIOValueTypeEnum.string, label: 'data2' + }, + { + id: NodeOutputKeyEnum.error, + key: NodeOutputKeyEnum.error, + label: i18nT('workflow:error_text'), + valueType: WorkflowIOValueTypeEnum.string, + type: FlowNodeOutputTypeEnum.error } ] }; diff --git a/packages/global/core/workflow/type/node.d.ts b/packages/global/core/workflow/type/node.d.ts index ba45fd1d3..d04cbca67 100644 --- a/packages/global/core/workflow/type/node.d.ts +++ b/packages/global/core/workflow/type/node.d.ts @@ -48,6 +48,7 @@ export type FlowNodeCommonType = { isLatestVersion?: boolean; // Just ui show // data + catchError?: boolean; inputs: FlowNodeInputItemType[]; outputs: FlowNodeOutputItemType[]; diff --git a/packages/global/core/workflow/utils.ts b/packages/global/core/workflow/utils.ts index 2756f4537..15f63d52a 100644 --- a/packages/global/core/workflow/utils.ts +++ b/packages/global/core/workflow/utils.ts @@ -52,7 +52,11 @@ import { ChatRoleEnum } from '../../core/chat/constants'; import { runtimePrompt2ChatsValue } from '../../core/chat/adapt'; import { getPluginRunContent } from '../../core/app/plugin/utils'; -export const getHandleId = (nodeId: string, type: 'source' | 'target', key: string) => { +export const getHandleId = ( + nodeId: string, + type: 'source' | 'source_catch' | 'target', + key: string +) => { return `${nodeId}-${type}-${key}`; }; @@ -219,16 +223,14 @@ export const pluginData2FlowNodeIO = ({ ] : [], outputs: pluginOutput - ? [ - ...pluginOutput.inputs.map((item) => ({ - id: item.key, - type: FlowNodeOutputTypeEnum.static, - key: item.key, - valueType: item.valueType, - label: item.label || item.key, - description: item.description - })) - ] + ? pluginOutput.inputs.map((item) => ({ + id: item.key, + type: FlowNodeOutputTypeEnum.static, + key: item.key, + valueType: item.valueType, + label: item.label || item.key, + description: item.description + })) : [] }; }; diff --git a/packages/global/support/user/audit/constants.ts b/packages/global/support/user/audit/constants.ts index 635542c0f..8c60385ac 100644 --- a/packages/global/support/user/audit/constants.ts +++ b/packages/global/support/user/audit/constants.ts @@ -54,6 +54,9 @@ export enum AuditEventEnum { UPDATE_APP_PUBLISH_CHANNEL = 'UPDATE_APP_PUBLISH_CHANNEL', DELETE_APP_PUBLISH_CHANNEL = 'DELETE_APP_PUBLISH_CHANNEL', EXPORT_APP_CHAT_LOG = 'EXPORT_APP_CHAT_LOG', + CREATE_EVALUATION = 'CREATE_EVALUATION', + EXPORT_EVALUATION = 'EXPORT_EVALUATION', + DELETE_EVALUATION = 'DELETE_EVALUATION', //Dataset CREATE_DATASET = 'CREATE_DATASET', UPDATE_DATASET = 'UPDATE_DATASET', diff --git a/packages/global/support/wallet/usage/constants.ts b/packages/global/support/wallet/usage/constants.ts index 26486c550..34bc5bce3 100644 --- a/packages/global/support/wallet/usage/constants.ts +++ b/packages/global/support/wallet/usage/constants.ts @@ -12,7 +12,8 @@ export enum UsageSourceEnum { dingtalk = 'dingtalk', official_account = 'official_account', pdfParse = 'pdfParse', - mcp = 'mcp' + mcp = 'mcp', + evaluation = 'evaluation' } export const UsageSourceMap = { @@ -51,5 +52,8 @@ export const UsageSourceMap = { }, [UsageSourceEnum.mcp]: { label: i18nT('account_usage:mcp') + }, + [UsageSourceEnum.evaluation]: { + label: i18nT('account_usage:evaluation') } }; diff --git a/packages/global/support/wallet/usage/type.d.ts b/packages/global/support/wallet/usage/type.d.ts index 8a2da598f..56f4c59a8 100644 --- a/packages/global/support/wallet/usage/type.d.ts +++ b/packages/global/support/wallet/usage/type.d.ts @@ -8,6 +8,7 @@ export type UsageListItemCountType = { charsLength?: number; duration?: number; pages?: number; + count?: number; // Times // deprecated tokens?: number; @@ -17,6 +18,7 @@ export type UsageListItemType = UsageListItemCountType & { moduleName: string; amount: number; model?: string; + count?: number; }; export type UsageSchemaType = CreateUsageProps & { diff --git a/packages/service/common/bullmq/index.ts b/packages/service/common/bullmq/index.ts index 4bdc02a1c..ddd6f2b8f 100644 --- a/packages/service/common/bullmq/index.ts +++ b/packages/service/common/bullmq/index.ts @@ -20,6 +20,7 @@ const defaultWorkerOpts: Omit = { export enum QueueNames { datasetSync = 'datasetSync', + evaluation = 'evaluation', // abondoned websiteSync = 'websiteSync' } diff --git a/packages/service/common/otel/trace/register.ts b/packages/service/common/otel/trace/register.ts index 30e7995eb..208bbf52f 100644 --- a/packages/service/common/otel/trace/register.ts +++ b/packages/service/common/otel/trace/register.ts @@ -1,13 +1,9 @@ import { registerOTel, OTLPHttpJsonTraceExporter } from '@vercel/otel'; -// Add otel logging -// import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api'; import { SignozBaseURL, SignozServiceName } from '../const'; import { addLog } from '../../system/log'; -// diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO); export function connectSignoz() { if (!SignozBaseURL) { - addLog.warn('Signoz is not configured'); return; } addLog.info(`Connecting signoz, ${SignozBaseURL}, ${SignozServiceName}`); diff --git a/packages/service/core/ai/config/provider/ChatGLM.json b/packages/service/core/ai/config/provider/ChatGLM.json index a5f8a07f7..49bbaa293 100644 --- a/packages/service/core/ai/config/provider/ChatGLM.json +++ b/packages/service/core/ai/config/provider/ChatGLM.json @@ -15,11 +15,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -38,11 +33,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -61,11 +51,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -84,11 +69,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -106,11 +86,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -128,11 +103,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" diff --git a/packages/service/core/ai/config/provider/Claude.json b/packages/service/core/ai/config/provider/Claude.json index bde1872f3..6a9d8e18a 100644 --- a/packages/service/core/ai/config/provider/Claude.json +++ b/packages/service/core/ai/config/provider/Claude.json @@ -14,11 +14,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -36,11 +31,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -58,11 +48,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -80,11 +65,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -102,11 +82,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -124,11 +99,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -146,11 +116,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" diff --git a/packages/service/core/ai/config/provider/DeepSeek.json b/packages/service/core/ai/config/provider/DeepSeek.json index 8c0fd1b92..b53789b08 100644 --- a/packages/service/core/ai/config/provider/DeepSeek.json +++ b/packages/service/core/ai/config/provider/DeepSeek.json @@ -15,11 +15,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "type": "llm" }, { @@ -34,11 +29,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", diff --git a/packages/service/core/ai/config/provider/Doubao.json b/packages/service/core/ai/config/provider/Doubao.json index 06cc1ddd7..759a392b6 100644 --- a/packages/service/core/ai/config/provider/Doubao.json +++ b/packages/service/core/ai/config/provider/Doubao.json @@ -14,11 +14,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -36,11 +31,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -58,11 +48,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -80,11 +65,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -102,11 +82,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -124,11 +99,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -146,11 +116,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -168,11 +133,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -190,11 +150,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -210,11 +165,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -232,11 +182,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -254,11 +199,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -276,11 +216,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -298,11 +233,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -320,11 +250,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", diff --git a/packages/service/core/ai/config/provider/Ernie.json b/packages/service/core/ai/config/provider/Ernie.json index 955b5c9eb..8af2c0d53 100644 --- a/packages/service/core/ai/config/provider/Ernie.json +++ b/packages/service/core/ai/config/provider/Ernie.json @@ -12,11 +12,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -34,11 +29,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -56,11 +46,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -78,11 +63,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", diff --git a/packages/service/core/ai/config/provider/Gemini.json b/packages/service/core/ai/config/provider/Gemini.json index b36c604ef..6ab859908 100644 --- a/packages/service/core/ai/config/provider/Gemini.json +++ b/packages/service/core/ai/config/provider/Gemini.json @@ -1,6 +1,38 @@ { "provider": "Gemini", "list": [ + { + "model": "gemini-2.5-pro", + "name": "gemini-2.5-pro", + "maxContext": 1000000, + "maxResponse": 63000, + "quoteMaxToken": 1000000, + "maxTemperature": 1, + "vision": true, + "toolChoice": true, + "defaultSystemChatPrompt": "", + "defaultConfig": {}, + "fieldMap": {}, + "type": "llm", + "showTopP": true, + "showStopSign": true + }, + { + "model": "gemini-2.5-flash", + "name": "gemini-2.5-flash", + "maxContext": 1000000, + "maxResponse": 63000, + "quoteMaxToken": 1000000, + "maxTemperature": 1, + "vision": true, + "toolChoice": true, + "defaultSystemChatPrompt": "", + "defaultConfig": {}, + "fieldMap": {}, + "type": "llm", + "showTopP": true, + "showStopSign": true + }, { "model": "gemini-2.5-pro-exp-03-25", "name": "gemini-2.5-pro-exp-03-25", @@ -12,11 +44,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -34,11 +61,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -56,11 +78,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -78,11 +95,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -100,11 +112,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -122,11 +129,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -144,11 +146,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -166,11 +163,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -188,11 +180,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -210,11 +197,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", diff --git a/packages/service/core/ai/config/provider/Grok.json b/packages/service/core/ai/config/provider/Grok.json index ae8e407d7..40a979d7e 100644 --- a/packages/service/core/ai/config/provider/Grok.json +++ b/packages/service/core/ai/config/provider/Grok.json @@ -1,6 +1,38 @@ { "provider": "Grok", "list": [ + { + "model": "grok-4", + "name": "grok-4", + "maxContext": 256000, + "maxResponse": 8000, + "quoteMaxToken": 128000, + "maxTemperature": 1, + "showTopP": true, + "showStopSign": true, + "vision": true, + "toolChoice": true, + "defaultSystemChatPrompt": "", + "defaultConfig": {}, + "fieldMap": {}, + "type": "llm" + }, + { + "model": "grok-4-0709", + "name": "grok-4-0709", + "maxContext": 256000, + "maxResponse": 8000, + "quoteMaxToken": 128000, + "maxTemperature": 1, + "showTopP": true, + "showStopSign": true, + "vision": true, + "toolChoice": true, + "defaultSystemChatPrompt": "", + "defaultConfig": {}, + "fieldMap": {}, + "type": "llm" + }, { "model": "grok-3-mini", "name": "grok-3-mini", @@ -14,11 +46,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -36,11 +63,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -58,11 +80,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -80,11 +97,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" diff --git a/packages/service/core/ai/config/provider/Groq.json b/packages/service/core/ai/config/provider/Groq.json index 1d5366c3e..0206bb0c4 100644 --- a/packages/service/core/ai/config/provider/Groq.json +++ b/packages/service/core/ai/config/provider/Groq.json @@ -12,11 +12,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "type": "llm", "showTopP": true, @@ -33,11 +28,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "type": "llm", "showTopP": true, diff --git a/packages/service/core/ai/config/provider/Hunyuan.json b/packages/service/core/ai/config/provider/Hunyuan.json index f682da711..2cfe1d305 100644 --- a/packages/service/core/ai/config/provider/Hunyuan.json +++ b/packages/service/core/ai/config/provider/Hunyuan.json @@ -12,11 +12,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -34,11 +29,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -56,11 +46,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -78,11 +63,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -100,11 +80,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -122,11 +97,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -144,11 +114,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", diff --git a/packages/service/core/ai/config/provider/Intern.json b/packages/service/core/ai/config/provider/Intern.json index 8434563db..05426ad20 100644 --- a/packages/service/core/ai/config/provider/Intern.json +++ b/packages/service/core/ai/config/provider/Intern.json @@ -12,11 +12,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -34,11 +29,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", diff --git a/packages/service/core/ai/config/provider/MiniMax.json b/packages/service/core/ai/config/provider/MiniMax.json index 0d9702f33..acefb86a2 100644 --- a/packages/service/core/ai/config/provider/MiniMax.json +++ b/packages/service/core/ai/config/provider/MiniMax.json @@ -12,11 +12,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -34,11 +29,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", diff --git a/packages/service/core/ai/config/provider/MistralAI.json b/packages/service/core/ai/config/provider/MistralAI.json index 27c2fe6fe..ebf0085d2 100644 --- a/packages/service/core/ai/config/provider/MistralAI.json +++ b/packages/service/core/ai/config/provider/MistralAI.json @@ -12,11 +12,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -34,11 +29,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -56,11 +46,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -78,11 +63,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", diff --git a/packages/service/core/ai/config/provider/Moonshot.json b/packages/service/core/ai/config/provider/Moonshot.json index 0cf2cbbcf..2fdf74939 100644 --- a/packages/service/core/ai/config/provider/Moonshot.json +++ b/packages/service/core/ai/config/provider/Moonshot.json @@ -1,6 +1,74 @@ { "provider": "Moonshot", "list": [ + { + "model": "kimi-k2-0711-preview", + "name": "kimi-k2-0711-preview", + "maxContext": 128000, + "maxResponse": 32000, + "quoteMaxToken": 128000, + "maxTemperature": 1, + "vision": false, + "toolChoice": true, + "defaultSystemChatPrompt": "", + "defaultConfig": {}, + "fieldMap": {}, + "type": "llm", + "showTopP": true, + "showStopSign": true, + "responseFormatList": ["text", "json_object"] + }, + { + "model": "kimi-latest-8k", + "name": "kimi-latest-8k", + "maxContext": 8000, + "maxResponse": 4000, + "quoteMaxToken": 6000, + "maxTemperature": 1, + "vision": false, + "toolChoice": true, + "defaultSystemChatPrompt": "", + "defaultConfig": {}, + "fieldMap": {}, + "type": "llm", + "showTopP": true, + "showStopSign": true, + "responseFormatList": ["text", "json_object"] + }, + { + "model": "kimi-latest-32k", + "name": "kimi-latest-32k", + "maxContext": 32000, + "maxResponse": 16000, + "quoteMaxToken": 32000, + "maxTemperature": 1, + "vision": false, + "toolChoice": true, + "defaultSystemChatPrompt": "", + "defaultConfig": {}, + "fieldMap": {}, + "type": "llm", + "showTopP": true, + "showStopSign": true, + "responseFormatList": ["text", "json_object"] + }, + { + "model": "kimi-latest-128k", + "name": "kimi-latest-128k", + "maxContext": 128000, + "maxResponse": 32000, + "quoteMaxToken": 128000, + "maxTemperature": 1, + "vision": false, + "toolChoice": true, + "defaultSystemChatPrompt": "", + "defaultConfig": {}, + "fieldMap": {}, + "type": "llm", + "showTopP": true, + "showStopSign": true, + "responseFormatList": ["text", "json_object"] + }, { "model": "moonshot-v1-8k", "name": "moonshot-v1-8k", @@ -12,11 +80,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -35,11 +98,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -58,11 +116,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -81,11 +134,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -104,11 +152,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -127,11 +170,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", diff --git a/packages/service/core/ai/config/provider/OpenAI.json b/packages/service/core/ai/config/provider/OpenAI.json index c94dadf21..27c2ac3fd 100644 --- a/packages/service/core/ai/config/provider/OpenAI.json +++ b/packages/service/core/ai/config/provider/OpenAI.json @@ -15,10 +15,6 @@ "toolChoice": true, "functionCall": true, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -37,10 +33,6 @@ "toolChoice": true, "functionCall": true, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -59,10 +51,6 @@ "toolChoice": true, "functionCall": true, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -81,10 +69,6 @@ "toolChoice": true, "functionCall": true, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -103,11 +87,6 @@ "toolChoice": true, "functionCall": true, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm" @@ -123,11 +102,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": { "max_tokens": "max_completion_tokens" @@ -147,11 +121,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": { "max_tokens": "max_completion_tokens" @@ -171,11 +140,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": { "max_tokens": "max_completion_tokens" @@ -195,11 +159,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": { "max_tokens": "max_completion_tokens" @@ -219,11 +178,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": { "max_tokens": "max_completion_tokens" @@ -243,11 +197,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": { "stream": false }, @@ -271,11 +220,6 @@ "toolChoice": true, "functionCall": true, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "type": "llm" }, { @@ -291,11 +235,6 @@ "toolChoice": true, "functionCall": true, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "type": "llm" }, { diff --git a/packages/service/core/ai/config/provider/Qwen.json b/packages/service/core/ai/config/provider/Qwen.json index e16b76a22..2a44fc067 100644 --- a/packages/service/core/ai/config/provider/Qwen.json +++ b/packages/service/core/ai/config/provider/Qwen.json @@ -12,11 +12,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -35,11 +30,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -57,11 +47,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -80,11 +65,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "type": "llm", "showTopP": true, "showStopSign": true @@ -100,11 +80,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -124,11 +99,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": { "stream": true }, @@ -150,11 +120,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": { "stream": true }, @@ -176,11 +141,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": { "stream": true }, @@ -202,11 +162,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": { "stream": true }, @@ -228,11 +183,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": { "stream": true }, @@ -254,11 +204,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": { "stream": true }, @@ -280,11 +225,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": { "stream": true }, @@ -306,11 +246,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": { "stream": true }, @@ -336,7 +271,6 @@ "usedInClassify": false, "usedInExtractFields": false, "usedInQueryExtension": false, - "usedInToolCall": true, "defaultConfig": { "stream": true }, @@ -361,7 +295,6 @@ "usedInClassify": false, "usedInExtractFields": false, "usedInQueryExtension": false, - "usedInToolCall": true, "defaultConfig": { "stream": true }, @@ -381,11 +314,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -403,11 +331,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -426,11 +349,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -449,11 +367,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -472,11 +385,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", diff --git a/packages/service/core/ai/config/provider/Siliconflow.json b/packages/service/core/ai/config/provider/Siliconflow.json index ea0aaf21a..a0b807682 100644 --- a/packages/service/core/ai/config/provider/Siliconflow.json +++ b/packages/service/core/ai/config/provider/Siliconflow.json @@ -12,11 +12,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -55,11 +50,6 @@ "toolChoice": true, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", diff --git a/packages/service/core/ai/config/provider/SparkDesk.json b/packages/service/core/ai/config/provider/SparkDesk.json index b5a4dc33c..766e18a8a 100644 --- a/packages/service/core/ai/config/provider/SparkDesk.json +++ b/packages/service/core/ai/config/provider/SparkDesk.json @@ -9,11 +9,6 @@ "quoteMaxToken": 32000, "maxTemperature": 1, "vision": false, - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInToolCall": true, - "usedInQueryExtension": true, "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", @@ -29,11 +24,6 @@ "quoteMaxToken": 8000, "maxTemperature": 1, "vision": false, - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInToolCall": true, - "usedInQueryExtension": true, "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", @@ -49,11 +39,6 @@ "quoteMaxToken": 128000, "maxTemperature": 1, "vision": false, - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInToolCall": true, - "usedInQueryExtension": true, "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", @@ -69,11 +54,6 @@ "quoteMaxToken": 8000, "maxTemperature": 1, "vision": false, - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInToolCall": true, - "usedInQueryExtension": true, "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", @@ -92,11 +72,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -114,11 +89,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", diff --git a/packages/service/core/ai/config/provider/StepFun.json b/packages/service/core/ai/config/provider/StepFun.json index 69d19a2f2..3e9bddb90 100644 --- a/packages/service/core/ai/config/provider/StepFun.json +++ b/packages/service/core/ai/config/provider/StepFun.json @@ -9,11 +9,6 @@ "quoteMaxToken": 6000, "maxTemperature": 2, "vision": false, - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInToolCall": true, - "usedInQueryExtension": true, "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", @@ -29,11 +24,6 @@ "quoteMaxToken": 8000, "maxTemperature": 2, "vision": false, - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInToolCall": true, - "usedInQueryExtension": true, "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", @@ -49,11 +39,6 @@ "quoteMaxToken": 32000, "maxTemperature": 2, "vision": false, - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInToolCall": true, - "usedInQueryExtension": true, "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", @@ -69,11 +54,6 @@ "quoteMaxToken": 128000, "maxTemperature": 2, "vision": false, - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInToolCall": true, - "usedInQueryExtension": true, "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", @@ -89,11 +69,6 @@ "quoteMaxToken": 256000, "maxTemperature": 2, "vision": false, - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInToolCall": true, - "usedInQueryExtension": true, "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", @@ -109,11 +84,6 @@ "maxResponse": 8000, "maxTemperature": 2, "vision": true, - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInToolCall": true, - "usedInQueryExtension": true, "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", @@ -129,11 +99,6 @@ "quoteMaxToken": 8000, "maxTemperature": 2, "vision": true, - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInToolCall": true, - "usedInQueryExtension": true, "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", @@ -149,11 +114,6 @@ "maxResponse": 8000, "maxTemperature": 2, "vision": true, - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInToolCall": true, - "usedInQueryExtension": true, "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", @@ -169,11 +129,6 @@ "quoteMaxToken": 6000, "maxTemperature": 2, "vision": false, - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInToolCall": true, - "usedInQueryExtension": true, "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", @@ -189,11 +144,6 @@ "quoteMaxToken": 4000, "maxTemperature": 2, "vision": false, - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInToolCall": true, - "usedInQueryExtension": true, "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", @@ -209,11 +159,6 @@ "quoteMaxToken": 4000, "maxTemperature": 2, "vision": false, - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInToolCall": true, - "usedInQueryExtension": true, "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", diff --git a/packages/service/core/ai/config/provider/Yi.json b/packages/service/core/ai/config/provider/Yi.json index 903ef1e9b..5e1007a55 100644 --- a/packages/service/core/ai/config/provider/Yi.json +++ b/packages/service/core/ai/config/provider/Yi.json @@ -12,11 +12,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", @@ -34,11 +29,6 @@ "toolChoice": false, "functionCall": false, "defaultSystemChatPrompt": "", - "datasetProcess": true, - "usedInClassify": true, - "usedInExtractFields": true, - "usedInQueryExtension": true, - "usedInToolCall": true, "defaultConfig": {}, "fieldMap": {}, "type": "llm", diff --git a/packages/service/core/ai/config/utils.ts b/packages/service/core/ai/config/utils.ts index b7a67d46c..d02d0a850 100644 --- a/packages/service/core/ai/config/utils.ts +++ b/packages/service/core/ai/config/utils.ts @@ -43,6 +43,15 @@ export const loadSystemModels = async (init = false) => { const pushModel = (model: SystemModelItemType) => { global.systemModelList.push(model); + // Add default value + if (model.type === ModelTypeEnum.llm) { + model.datasetProcess = model.datasetProcess ?? true; + model.usedInClassify = model.usedInClassify ?? true; + model.usedInExtractFields = model.usedInExtractFields ?? true; + model.usedInToolCall = model.usedInToolCall ?? true; + model.useInEvaluation = model.useInEvaluation ?? true; + } + if (model.isActive) { global.systemActiveModelList.push(model); diff --git a/packages/service/core/ai/model.ts b/packages/service/core/ai/model.ts index 55a4f9747..7821b9e73 100644 --- a/packages/service/core/ai/model.ts +++ b/packages/service/core/ai/model.ts @@ -14,15 +14,15 @@ export const getDatasetModel = (model?: string) => { ?.find((item) => item.model === model || item.name === model) ?? getDefaultLLMModel() ); }; -export const getVlmModel = (model?: string) => { - return Array.from(global.llmModelMap.values()) - ?.filter((item) => item.vision) - ?.find((item) => item.model === model || item.name === model); -}; export const getVlmModelList = () => { return Array.from(global.llmModelMap.values())?.filter((item) => item.vision) || []; }; +export const getDefaultVLMModel = () => global?.systemDefaultModel.datasetImageLLM; +export const getVlmModel = (model?: string) => { + const list = getVlmModelList(); + return list.find((item) => item.model === model || item.name === model) || list[0]; +}; export const getDefaultEmbeddingModel = () => global?.systemDefaultModel.embedding!; export const getEmbeddingModel = (model?: string) => { diff --git a/packages/service/core/app/evaluation/evalItemSchema.ts b/packages/service/core/app/evaluation/evalItemSchema.ts new file mode 100644 index 000000000..45e8633da --- /dev/null +++ b/packages/service/core/app/evaluation/evalItemSchema.ts @@ -0,0 +1,56 @@ +import { connectionMongo, getMongoModel } from '../../../common/mongo'; +import { EvaluationCollectionName } from './evalSchema'; +import { + EvaluationStatusEnum, + EvaluationStatusValues +} from '@fastgpt/global/core/app/evaluation/constants'; +import type { EvalItemSchemaType } from '@fastgpt/global/core/app/evaluation/type'; + +const { Schema } = connectionMongo; + +export const EvalItemCollectionName = 'eval_items'; + +const EvalItemSchema = new Schema({ + evalId: { + type: Schema.Types.ObjectId, + ref: EvaluationCollectionName, + required: true + }, + question: { + type: String, + required: true + }, + expectedResponse: { + type: String, + required: true + }, + history: String, + globalVariables: Object, + response: String, + responseTime: Date, + + status: { + type: Number, + default: EvaluationStatusEnum.queuing, + enum: EvaluationStatusValues + }, + retry: { + type: Number, + default: 3 + }, + finishTime: Date, + + accuracy: Number, + relevance: Number, + semanticAccuracy: Number, + score: Number, // average score + + errorMessage: String +}); + +EvalItemSchema.index({ evalId: 1, status: 1 }); + +export const MongoEvalItem = getMongoModel( + EvalItemCollectionName, + EvalItemSchema +); diff --git a/packages/service/core/app/evaluation/evalSchema.ts b/packages/service/core/app/evaluation/evalSchema.ts new file mode 100644 index 000000000..a8678ebda --- /dev/null +++ b/packages/service/core/app/evaluation/evalSchema.ts @@ -0,0 +1,57 @@ +import { + TeamCollectionName, + TeamMemberCollectionName +} from '@fastgpt/global/support/user/team/constant'; +import { connectionMongo, getMongoModel } from '../../../common/mongo'; +import { AppCollectionName } from '../schema'; +import type { EvaluationSchemaType } from '@fastgpt/global/core/app/evaluation/type'; +import { UsageCollectionName } from '../../../support/wallet/usage/schema'; +const { Schema } = connectionMongo; + +export const EvaluationCollectionName = 'eval'; + +const EvaluationSchema = new Schema({ + teamId: { + type: Schema.Types.ObjectId, + ref: TeamCollectionName, + required: true + }, + tmbId: { + type: Schema.Types.ObjectId, + ref: TeamMemberCollectionName, + required: true + }, + appId: { + type: Schema.Types.ObjectId, + ref: AppCollectionName, + required: true + }, + usageId: { + type: Schema.Types.ObjectId, + ref: UsageCollectionName, + required: true + }, + evalModel: { + type: String, + required: true + }, + name: { + type: String, + required: true + }, + createTime: { + type: Date, + required: true, + default: () => new Date() + }, + finishTime: Date, + score: Number, + errorMessage: String +}); + +EvaluationSchema.index({ teamId: 1 }); + +export const MongoEvaluation = getMongoModel( + EvaluationCollectionName, + EvaluationSchema +); diff --git a/packages/service/core/app/evaluation/mq.ts b/packages/service/core/app/evaluation/mq.ts new file mode 100644 index 000000000..c0192d476 --- /dev/null +++ b/packages/service/core/app/evaluation/mq.ts @@ -0,0 +1,80 @@ +import { getQueue, getWorker, QueueNames } from '../../../common/bullmq'; +import { type Processor } from 'bullmq'; +import { addLog } from '../../../common/system/log'; + +export type EvaluationJobData = { + evalId: string; +}; + +export const evaluationQueue = getQueue(QueueNames.evaluation, { + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + delay: 1000 + } + } +}); + +const concurrency = process.env.EVAL_CONCURRENCY ? Number(process.env.EVAL_CONCURRENCY) : 3; +export const getEvaluationWorker = (processor: Processor) => { + return getWorker(QueueNames.evaluation, processor, { + removeOnFail: { + count: 1000 // Keep last 1000 failed jobs + }, + concurrency: concurrency + }); +}; + +export const addEvaluationJob = (data: EvaluationJobData) => { + const evalId = String(data.evalId); + + return evaluationQueue.add(evalId, data, { deduplication: { id: evalId } }); +}; + +export const checkEvaluationJobActive = async (evalId: string): Promise => { + try { + const jobId = await evaluationQueue.getDeduplicationJobId(String(evalId)); + if (!jobId) return false; + + const job = await evaluationQueue.getJob(jobId); + if (!job) return false; + + const jobState = await job.getState(); + return ['waiting', 'delayed', 'prioritized', 'active'].includes(jobState); + } catch (error) { + addLog.error('Failed to check evaluation job status', { evalId, error }); + return false; + } +}; + +export const removeEvaluationJob = async (evalId: string): Promise => { + const formatEvalId = String(evalId); + try { + const jobId = await evaluationQueue.getDeduplicationJobId(formatEvalId); + if (!jobId) { + addLog.warn('No job found to remove', { evalId }); + return false; + } + + const job = await evaluationQueue.getJob(jobId); + if (!job) { + addLog.warn('Job not found in queue', { evalId, jobId }); + return false; + } + + const jobState = await job.getState(); + + if (['waiting', 'delayed', 'prioritized'].includes(jobState)) { + await job.remove(); + addLog.info('Evaluation job removed successfully', { evalId, jobId, jobState }); + return true; + } else { + addLog.warn('Cannot remove active or completed job', { evalId, jobId, jobState }); + return false; + } + } catch (error) { + addLog.error('Failed to remove evaluation job', { evalId, error }); + return false; + } +}; diff --git a/packages/service/core/app/plugin/controller.ts b/packages/service/core/app/plugin/controller.ts index e08f9b638..661f1af2a 100644 --- a/packages/service/core/app/plugin/controller.ts +++ b/packages/service/core/app/plugin/controller.ts @@ -1,5 +1,8 @@ import { type FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node.d'; -import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; +import { + FlowNodeOutputTypeEnum, + FlowNodeTypeEnum +} from '@fastgpt/global/core/workflow/node/constant'; import { appData2FlowNodeIO, pluginData2FlowNodeIO, @@ -33,6 +36,7 @@ import type { FlowNodeOutputItemType } from '@fastgpt/global/core/workflow/type/io'; import { isProduction } from '@fastgpt/global/common/system/constants'; +import { Output_Template_Error_Message } from '@fastgpt/global/core/workflow/template/output'; /** plugin id rule: @@ -122,15 +126,24 @@ export const getSystemPluginByIdAndVersionId = async ( }; } + // System tool + const versionList = (plugin.versionList as SystemPluginTemplateItemType['versionList']) || []; + + if (versionList.length === 0) { + return Promise.reject('Can not find plugin version list'); + } + const version = versionId - ? plugin.versionList?.find((item) => item.value === versionId) - : plugin.versionList?.[0]; - const lastVersion = plugin.versionList?.[0]; + ? versionList.find((item) => item.value === versionId) ?? versionList[0] + : versionList[0]; + const lastVersion = versionList[0]; return { ...plugin, + inputs: version.inputs, + outputs: version.outputs, version: versionId ? version?.value : '', - versionLabel: version ? version?.value : '', + versionLabel: versionId ? version?.value : '', isLatestVersion: !version || !lastVersion || version.value === lastVersion?.value }; })(); @@ -198,8 +211,8 @@ export async function getChildAppPreviewNode({ return { flowNodeType: FlowNodeTypeEnum.tool, nodeIOConfig: { - inputs: app.inputs!, - outputs: app.outputs!, + inputs: app.inputs || [], + outputs: app.outputs || [], toolConfig: { systemTool: { toolId: app.id @@ -209,6 +222,7 @@ export async function getChildAppPreviewNode({ }; } + // Plugin workflow if (!!app.workflow.nodes.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)) { return { flowNodeType: FlowNodeTypeEnum.pluginModule, @@ -216,6 +230,7 @@ export async function getChildAppPreviewNode({ }; } + // Mcp if ( !!app.workflow.nodes.find((node) => node.flowNodeType === FlowNodeTypeEnum.toolSet) && app.workflow.nodes.length === 1 @@ -236,6 +251,7 @@ export async function getChildAppPreviewNode({ }; } + // Chat workflow return { flowNodeType: FlowNodeTypeEnum.appModule, nodeIOConfig: appData2FlowNodeIO({ chatConfig: app.workflow.chatConfig }) @@ -254,6 +270,7 @@ export async function getChildAppPreviewNode({ userGuide: app.userGuide, showStatus: true, isTool: true, + catchError: false, version: app.version, versionLabel: app.versionLabel, @@ -265,7 +282,10 @@ export async function getChildAppPreviewNode({ hasTokenFee: app.hasTokenFee, hasSystemSecret: app.hasSystemSecret, - ...nodeIOConfig + ...nodeIOConfig, + outputs: nodeIOConfig.outputs.some((item) => item.type === FlowNodeOutputTypeEnum.error) + ? nodeIOConfig.outputs + : [...nodeIOConfig.outputs, Output_Template_Error_Message] }; } @@ -414,8 +434,9 @@ export const getSystemPlugins = async (): Promise((item) => { const dbPluginConfig = systemPlugins.get(item.id); - const inputs = item.versionList[0]?.inputs as FlowNodeInputItemType[]; - const outputs = item.versionList[0]?.outputs as FlowNodeOutputItemType[]; + + const versionList = (item.versionList as SystemPluginTemplateItemType['versionList']) || []; + const inputs = versionList[0]?.inputs; return { isActive: item.isActive, @@ -439,9 +460,7 @@ export const getSystemPlugins = async (): Promise input.key === NodeInputKeyEnum.systemInputConfig) ?.inputList as any, diff --git a/packages/service/core/app/plugin/systemPluginSchema.ts b/packages/service/core/app/plugin/systemPluginSchema.ts index e45ab4f7b..76f4d91e3 100644 --- a/packages/service/core/app/plugin/systemPluginSchema.ts +++ b/packages/service/core/app/plugin/systemPluginSchema.ts @@ -25,8 +25,7 @@ const SystemPluginSchema = new Schema({ default: false }, pluginOrder: { - type: Number, - default: 0 + type: Number }, customConfig: Object, inputListVal: Object, diff --git a/packages/service/core/app/plugin/type.d.ts b/packages/service/core/app/plugin/type.d.ts index 9f4600741..8628b3d2e 100644 --- a/packages/service/core/app/plugin/type.d.ts +++ b/packages/service/core/app/plugin/type.d.ts @@ -9,7 +9,7 @@ export type SystemPluginConfigSchemaType = { currentCost: number; hasTokenFee: boolean; isActive: boolean; - pluginOrder: number; + pluginOrder?: number; customConfig?: { name: string; diff --git a/packages/service/core/app/utils.ts b/packages/service/core/app/utils.ts index 1cf2d6db5..123820106 100644 --- a/packages/service/core/app/utils.ts +++ b/packages/service/core/app/utils.ts @@ -79,6 +79,28 @@ export async function rewriteAppWorkflowToDetail({ node.currentCost = preview.currentCost; node.hasTokenFee = preview.hasTokenFee; node.hasSystemSecret = preview.hasSystemSecret; + + // Latest version + if (!node.version) { + const inputsMap = new Map(node.inputs.map((item) => [item.key, item])); + const outputsMap = new Map(node.outputs.map((item) => [item.key, item])); + + node.inputs = preview.inputs.map((item) => { + const input = inputsMap.get(item.key); + return { + ...item, + value: input?.value, + selectedTypeIndex: input?.selectedTypeIndex + }; + }); + node.outputs = preview.outputs.map((item) => { + const output = outputsMap.get(item.key); + return { + ...item, + value: output?.value + }; + }); + } } catch (error) { node.pluginData = { error: getErrText(error) diff --git a/packages/service/core/chat/saveChat.ts b/packages/service/core/chat/saveChat.ts index 088be3225..59fe5e4fc 100644 --- a/packages/service/core/chat/saveChat.ts +++ b/packages/service/core/chat/saveChat.ts @@ -166,7 +166,7 @@ export async function saveChat({ if (isUpdateUseTime) { await MongoApp.findByIdAndUpdate(appId, { updateTime: new Date() - }); + }).catch(); } } catch (error) { addLog.error(`update chat history error`, error); diff --git a/packages/service/core/workflow/dispatch/abandoned/runApp.ts b/packages/service/core/workflow/dispatch/abandoned/runApp.ts index 0f0cca9f5..47304c5d3 100644 --- a/packages/service/core/workflow/dispatch/abandoned/runApp.ts +++ b/packages/service/core/workflow/dispatch/abandoned/runApp.ts @@ -95,6 +95,10 @@ export const dispatchAppRequest = async (props: Props): Promise => { const { text } = chatValue2RuntimePrompt(assistantResponses); return { + data: { + answerText: text, + history: completeMessages + }, assistantResponses, system_memories, [DispatchNodeResponseKeyEnum.nodeResponse]: { @@ -108,8 +112,6 @@ export const dispatchAppRequest = async (props: Props): Promise => { moduleName: appData.name, totalPoints: flowUsages.reduce((sum, item) => sum + (item.totalPoints || 0), 0) } - ], - answerText: text, - history: completeMessages + ] }; }; diff --git a/packages/service/core/workflow/dispatch/ai/agent/index.ts b/packages/service/core/workflow/dispatch/ai/agent/index.ts index 44b1e27b1..f345d6d6f 100644 --- a/packages/service/core/workflow/dispatch/ai/agent/index.ts +++ b/packages/service/core/workflow/dispatch/ai/agent/index.ts @@ -6,7 +6,7 @@ import type { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type'; import { getLLMModel } from '../../../../ai/model'; -import { filterToolNodeIdByEdges, getHistories } from '../../utils'; +import { filterToolNodeIdByEdges, getNodeErrResponse, getHistories } from '../../utils'; import { runToolWithToolChoice } from './toolChoice'; import { type DispatchToolModuleProps, type ToolNodeItemType } from './type'; import { type ChatItemType, type UserChatItemValueItemType } from '@fastgpt/global/core/chat/type'; @@ -25,7 +25,6 @@ import { runToolWithPromptCall } from './promptCall'; import { replaceVariable } from '@fastgpt/global/common/string/tools'; import { getMultiplePrompt, Prompt_Tool_Call } from './constants'; import { filterToolResponseToPreview } from './utils'; -import { type InteractiveNodeResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type'; import { getFileContentFromLinks, getHistoryFileLinks } from '../../tools/readFiles'; import { parseUrlToFileType } from '@fastgpt/global/common/file/tools'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; @@ -38,7 +37,6 @@ import type { JSONSchemaInputType } from '@fastgpt/global/core/app/jsonschema'; type Response = DispatchNodeResultType<{ [NodeOutputKeyEnum.answerText]: string; - [DispatchNodeResponseKeyEnum.interactive]?: InteractiveNodeResponseType; }>; export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise => { @@ -64,244 +62,249 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise< } } = props; - const toolModel = getLLMModel(model); - const useVision = aiChatVision && toolModel.vision; - const chatHistories = getHistories(history, histories); + try { + const toolModel = getLLMModel(model); + const useVision = aiChatVision && toolModel.vision; + const chatHistories = getHistories(history, histories); - props.params.aiChatVision = aiChatVision && toolModel.vision; - props.params.aiChatReasoning = aiChatReasoning && toolModel.reasoning; - const fileUrlInput = inputs.find((item) => item.key === NodeInputKeyEnum.fileUrlList); - if (!fileUrlInput || !fileUrlInput.value || fileUrlInput.value.length === 0) { - fileLinks = undefined; - } - console.log(fileLinks, 22); + props.params.aiChatVision = aiChatVision && toolModel.vision; + props.params.aiChatReasoning = aiChatReasoning && toolModel.reasoning; + const fileUrlInput = inputs.find((item) => item.key === NodeInputKeyEnum.fileUrlList); + if (!fileUrlInput || !fileUrlInput.value || fileUrlInput.value.length === 0) { + fileLinks = undefined; + } - const toolNodeIds = filterToolNodeIdByEdges({ nodeId, edges: runtimeEdges }); + const toolNodeIds = filterToolNodeIdByEdges({ nodeId, edges: runtimeEdges }); - // Gets the module to which the tool is connected - const toolNodes = toolNodeIds - .map((nodeId) => { - const tool = runtimeNodes.find((item) => item.nodeId === nodeId); - return tool; - }) - .filter(Boolean) - .map((tool) => { - const toolParams: FlowNodeInputItemType[] = []; - // Raw json schema(MCP tool) - let jsonSchema: JSONSchemaInputType | undefined = undefined; - tool?.inputs.forEach((input) => { - if (input.toolDescription) { - toolParams.push(input); - } + // Gets the module to which the tool is connected + const toolNodes = toolNodeIds + .map((nodeId) => { + const tool = runtimeNodes.find((item) => item.nodeId === nodeId); + return tool; + }) + .filter(Boolean) + .map((tool) => { + const toolParams: FlowNodeInputItemType[] = []; + // Raw json schema(MCP tool) + let jsonSchema: JSONSchemaInputType | undefined = undefined; + tool?.inputs.forEach((input) => { + if (input.toolDescription) { + toolParams.push(input); + } - if (input.key === NodeInputKeyEnum.toolData || input.key === 'toolData') { - const value = input.value as McpToolDataType; - jsonSchema = value.inputSchema; - } + if (input.key === NodeInputKeyEnum.toolData || input.key === 'toolData') { + const value = input.value as McpToolDataType; + jsonSchema = value.inputSchema; + } + }); + + return { + ...(tool as RuntimeNodeItemType), + toolParams, + jsonSchema + }; }); - return { - ...(tool as RuntimeNodeItemType), - toolParams, - jsonSchema - }; + // Check interactive entry + props.node.isEntry = false; + const hasReadFilesTool = toolNodes.some( + (item) => item.flowNodeType === FlowNodeTypeEnum.readFiles + ); + + const globalFiles = chatValue2RuntimePrompt(query).files; + const { documentQuoteText, userFiles } = await getMultiInput({ + runningUserInfo, + histories: chatHistories, + requestOrigin, + maxFiles: chatConfig?.fileSelectConfig?.maxFiles || 20, + customPdfParse: chatConfig?.fileSelectConfig?.customPdfParse, + fileLinks, + inputFiles: globalFiles, + hasReadFilesTool }); - // Check interactive entry - props.node.isEntry = false; - const hasReadFilesTool = toolNodes.some( - (item) => item.flowNodeType === FlowNodeTypeEnum.readFiles - ); - - const globalFiles = chatValue2RuntimePrompt(query).files; - const { documentQuoteText, userFiles } = await getMultiInput({ - runningUserInfo, - histories: chatHistories, - requestOrigin, - maxFiles: chatConfig?.fileSelectConfig?.maxFiles || 20, - customPdfParse: chatConfig?.fileSelectConfig?.customPdfParse, - fileLinks, - inputFiles: globalFiles, - hasReadFilesTool - }); - - const concatenateSystemPrompt = [ - toolModel.defaultSystemChatPrompt, - systemPrompt, - documentQuoteText - ? replaceVariable(getDocumentQuotePrompt(version), { - quote: documentQuoteText - }) - : '' - ] - .filter(Boolean) - .join('\n\n===---===---===\n\n'); - - const messages: ChatItemType[] = (() => { - const value: ChatItemType[] = [ - ...getSystemPrompt_ChatItemType(concatenateSystemPrompt), - // Add file input prompt to histories - ...chatHistories.map((item) => { - if (item.obj === ChatRoleEnum.Human) { - return { - ...item, - value: toolCallMessagesAdapt({ - userInput: item.value, - skip: !hasReadFilesTool - }) - }; - } - return item; - }), - { - obj: ChatRoleEnum.Human, - value: toolCallMessagesAdapt({ - skip: !hasReadFilesTool, - userInput: runtimePrompt2ChatsValue({ - text: userChatInput, - files: userFiles + const concatenateSystemPrompt = [ + toolModel.defaultSystemChatPrompt, + systemPrompt, + documentQuoteText + ? replaceVariable(getDocumentQuotePrompt(version), { + quote: documentQuoteText }) - }) - } - ]; - if (lastInteractive && isEntry) { - return value.slice(0, -2); - } - return value; - })(); + : '' + ] + .filter(Boolean) + .join('\n\n===---===---===\n\n'); - // censor model and system key - if (toolModel.censor && !externalProvider.openaiAccount?.key) { - await postTextCensor({ - text: `${systemPrompt} + const messages: ChatItemType[] = (() => { + const value: ChatItemType[] = [ + ...getSystemPrompt_ChatItemType(concatenateSystemPrompt), + // Add file input prompt to histories + ...chatHistories.map((item) => { + if (item.obj === ChatRoleEnum.Human) { + return { + ...item, + value: toolCallMessagesAdapt({ + userInput: item.value, + skip: !hasReadFilesTool + }) + }; + } + return item; + }), + { + obj: ChatRoleEnum.Human, + value: toolCallMessagesAdapt({ + skip: !hasReadFilesTool, + userInput: runtimePrompt2ChatsValue({ + text: userChatInput, + files: userFiles + }) + }) + } + ]; + if (lastInteractive && isEntry) { + return value.slice(0, -2); + } + return value; + })(); + + // censor model and system key + if (toolModel.censor && !externalProvider.openaiAccount?.key) { + await postTextCensor({ + text: `${systemPrompt} ${userChatInput} ` - }); - } - - const { - toolWorkflowInteractiveResponse, - dispatchFlowResponse, // tool flow response - toolNodeInputTokens, - toolNodeOutputTokens, - completeMessages = [], // The actual message sent to AI(just save text) - assistantResponses = [], // FastGPT system store assistant.value response - runTimes, - finish_reason - } = await (async () => { - const adaptMessages = chats2GPTMessages({ - messages, - reserveId: false - // reserveTool: !!toolModel.toolChoice - }); - const requestParams = { - runtimeNodes, - runtimeEdges, - toolNodes, - toolModel, - messages: adaptMessages, - interactiveEntryToolParams: lastInteractive?.toolParams - }; - - if (toolModel.toolChoice) { - return runToolWithToolChoice({ - ...props, - ...requestParams, - maxRunToolTimes: 30 - }); - } - if (toolModel.functionCall) { - return runToolWithFunctionCall({ - ...props, - ...requestParams }); } - const lastMessage = adaptMessages[adaptMessages.length - 1]; - if (typeof lastMessage?.content === 'string') { - lastMessage.content = replaceVariable(Prompt_Tool_Call, { - question: lastMessage.content + const { + toolWorkflowInteractiveResponse, + dispatchFlowResponse, // tool flow response + toolNodeInputTokens, + toolNodeOutputTokens, + completeMessages = [], // The actual message sent to AI(just save text) + assistantResponses = [], // FastGPT system store assistant.value response + runTimes, + finish_reason + } = await (async () => { + const adaptMessages = chats2GPTMessages({ + messages, + reserveId: false + // reserveTool: !!toolModel.toolChoice }); - } else if (Array.isArray(lastMessage.content)) { - // array, replace last element - const lastText = lastMessage.content[lastMessage.content.length - 1]; - if (lastText.type === 'text') { - lastText.text = replaceVariable(Prompt_Tool_Call, { - question: lastText.text + const requestParams = { + runtimeNodes, + runtimeEdges, + toolNodes, + toolModel, + messages: adaptMessages, + interactiveEntryToolParams: lastInteractive?.toolParams + }; + + if (toolModel.toolChoice) { + return runToolWithToolChoice({ + ...props, + ...requestParams, + maxRunToolTimes: 30 }); + } + if (toolModel.functionCall) { + return runToolWithFunctionCall({ + ...props, + ...requestParams + }); + } + + const lastMessage = adaptMessages[adaptMessages.length - 1]; + if (typeof lastMessage?.content === 'string') { + lastMessage.content = replaceVariable(Prompt_Tool_Call, { + question: lastMessage.content + }); + } else if (Array.isArray(lastMessage.content)) { + // array, replace last element + const lastText = lastMessage.content[lastMessage.content.length - 1]; + if (lastText.type === 'text') { + lastText.text = replaceVariable(Prompt_Tool_Call, { + question: lastText.text + }); + } else { + return Promise.reject('Prompt call invalid input'); + } } else { return Promise.reject('Prompt call invalid input'); } - } else { - return Promise.reject('Prompt call invalid input'); - } - return runToolWithPromptCall({ - ...props, - ...requestParams + return runToolWithPromptCall({ + ...props, + ...requestParams + }); + })(); + + const { totalPoints, modelName } = formatModelChars2Points({ + model, + inputTokens: toolNodeInputTokens, + outputTokens: toolNodeOutputTokens, + modelType: ModelTypeEnum.llm }); - })(); + const toolAIUsage = externalProvider.openaiAccount?.key ? 0 : totalPoints; - const { totalPoints, modelName } = formatModelChars2Points({ - model, - inputTokens: toolNodeInputTokens, - outputTokens: toolNodeOutputTokens, - modelType: ModelTypeEnum.llm - }); - const toolAIUsage = externalProvider.openaiAccount?.key ? 0 : totalPoints; + // flat child tool response + const childToolResponse = dispatchFlowResponse.map((item) => item.flowResponses).flat(); - // flat child tool response - const childToolResponse = dispatchFlowResponse.map((item) => item.flowResponses).flat(); + // concat tool usage + const totalPointsUsage = + toolAIUsage + + dispatchFlowResponse.reduce((sum, item) => { + const childrenTotal = item.flowUsages.reduce((sum, item) => sum + item.totalPoints, 0); + return sum + childrenTotal; + }, 0); + const flatUsages = dispatchFlowResponse.map((item) => item.flowUsages).flat(); - // concat tool usage - const totalPointsUsage = - toolAIUsage + - dispatchFlowResponse.reduce((sum, item) => { - const childrenTotal = item.flowUsages.reduce((sum, item) => sum + item.totalPoints, 0); - return sum + childrenTotal; - }, 0); - const flatUsages = dispatchFlowResponse.map((item) => item.flowUsages).flat(); + const previewAssistantResponses = filterToolResponseToPreview(assistantResponses); - const previewAssistantResponses = filterToolResponseToPreview(assistantResponses); - - return { - [DispatchNodeResponseKeyEnum.runTimes]: runTimes, - [NodeOutputKeyEnum.answerText]: previewAssistantResponses - .filter((item) => item.text?.content) - .map((item) => item.text?.content || '') - .join(''), - [DispatchNodeResponseKeyEnum.assistantResponses]: previewAssistantResponses, - [DispatchNodeResponseKeyEnum.nodeResponse]: { - // 展示的积分消耗 - totalPoints: totalPointsUsage, - toolCallInputTokens: toolNodeInputTokens, - toolCallOutputTokens: toolNodeOutputTokens, - childTotalPoints: flatUsages.reduce((sum, item) => sum + item.totalPoints, 0), - model: modelName, - query: userChatInput, - historyPreview: getHistoryPreview( - GPTMessages2Chats(completeMessages, false), - 10000, - useVision - ), - toolDetail: childToolResponse, - mergeSignId: nodeId, - finishReason: finish_reason - }, - [DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [ - // 工具调用本身的积分消耗 - { - moduleName: name, - model: modelName, - totalPoints: toolAIUsage, - inputTokens: toolNodeInputTokens, - outputTokens: toolNodeOutputTokens + return { + data: { + [NodeOutputKeyEnum.answerText]: previewAssistantResponses + .filter((item) => item.text?.content) + .map((item) => item.text?.content || '') + .join('') }, - // 工具的消耗 - ...flatUsages - ], - [DispatchNodeResponseKeyEnum.interactive]: toolWorkflowInteractiveResponse - }; + [DispatchNodeResponseKeyEnum.runTimes]: runTimes, + [DispatchNodeResponseKeyEnum.assistantResponses]: previewAssistantResponses, + [DispatchNodeResponseKeyEnum.nodeResponse]: { + // 展示的积分消耗 + totalPoints: totalPointsUsage, + toolCallInputTokens: toolNodeInputTokens, + toolCallOutputTokens: toolNodeOutputTokens, + childTotalPoints: flatUsages.reduce((sum, item) => sum + item.totalPoints, 0), + model: modelName, + query: userChatInput, + historyPreview: getHistoryPreview( + GPTMessages2Chats(completeMessages, false), + 10000, + useVision + ), + toolDetail: childToolResponse, + mergeSignId: nodeId, + finishReason: finish_reason + }, + [DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [ + // 工具调用本身的积分消耗 + { + moduleName: name, + model: modelName, + totalPoints: toolAIUsage, + inputTokens: toolNodeInputTokens, + outputTokens: toolNodeOutputTokens + }, + // 工具的消耗 + ...flatUsages + ], + [DispatchNodeResponseKeyEnum.interactive]: toolWorkflowInteractiveResponse + }; + } catch (error) { + return getNodeErrResponse({ error }); + } }; const getMultiInput = async ({ diff --git a/packages/service/core/workflow/dispatch/ai/chat.ts b/packages/service/core/workflow/dispatch/ai/chat.ts index 88e8e3709..a90252344 100644 --- a/packages/service/core/workflow/dispatch/ai/chat.ts +++ b/packages/service/core/workflow/dispatch/ai/chat.ts @@ -17,10 +17,7 @@ import type { } from '@fastgpt/global/core/ai/type.d'; import { formatModelChars2Points } from '../../../../support/wallet/usage/utils'; import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d'; -import { - ChatCompletionRequestMessageRoleEnum, - getLLMDefaultUsage -} from '@fastgpt/global/core/ai/constants'; +import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants'; import type { ChatDispatchProps, DispatchNodeResultType @@ -47,7 +44,7 @@ import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/ty import type { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants'; -import { checkQuoteQAValue, getHistories } from '../utils'; +import { checkQuoteQAValue, getNodeErrResponse, getHistories } from '../utils'; import { filterSearchResultsByMaxChars } from '../../utils'; import { getHistoryPreview } from '@fastgpt/global/core/chat/utils'; import { computedMaxToken, llmCompletionsBodyFormat } from '../../../ai/utils'; @@ -59,6 +56,7 @@ import { parseUrlToFileType } from '@fastgpt/global/common/file/tools'; import { i18nT } from '../../../../../web/i18n/utils'; import { ModelTypeEnum } from '@fastgpt/global/core/ai/model'; import { postTextCensor } from '../../../chat/postTextCensor'; +import { getErrText } from '@fastgpt/global/common/error/utils'; export type ChatProps = ModuleDispatchProps< AIChatNodeProps & { @@ -67,11 +65,16 @@ export type ChatProps = ModuleDispatchProps< [NodeInputKeyEnum.aiChatDatasetQuote]?: SearchDataResponseItemType[]; } >; -export type ChatResponse = DispatchNodeResultType<{ - [NodeOutputKeyEnum.answerText]: string; - [NodeOutputKeyEnum.reasoningText]?: string; - [NodeOutputKeyEnum.history]: ChatItemType[]; -}>; +export type ChatResponse = DispatchNodeResultType< + { + [NodeOutputKeyEnum.answerText]: string; + [NodeOutputKeyEnum.reasoningText]?: string; + [NodeOutputKeyEnum.history]: ChatItemType[]; + }, + { + [NodeOutputKeyEnum.errorText]: string; + } +>; /* request openai chat */ export const dispatchChatCompletion = async (props: ChatProps): Promise => { @@ -114,243 +117,253 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise item.key === NodeInputKeyEnum.fileUrlList); - if (!fileUrlInput || !fileUrlInput.value || fileUrlInput.value.length === 0) { - fileLinks = undefined; - } + try { + aiChatVision = modelConstantsData.vision && aiChatVision; + aiChatReasoning = !!aiChatReasoning && !!modelConstantsData.reasoning; + // Check fileLinks is reference variable + const fileUrlInput = inputs.find((item) => item.key === NodeInputKeyEnum.fileUrlList); + if (!fileUrlInput || !fileUrlInput.value || fileUrlInput.value.length === 0) { + fileLinks = undefined; + } - const chatHistories = getHistories(history, histories); - quoteQA = checkQuoteQAValue(quoteQA); + const chatHistories = getHistories(history, histories); + quoteQA = checkQuoteQAValue(quoteQA); - const [{ datasetQuoteText }, { documentQuoteText, userFiles }] = await Promise.all([ - filterDatasetQuote({ - quoteQA, + const [{ datasetQuoteText }, { documentQuoteText, userFiles }] = await Promise.all([ + filterDatasetQuote({ + quoteQA, + model: modelConstantsData, + quoteTemplate: quoteTemplate || getQuoteTemplate(version) + }), + getMultiInput({ + histories: chatHistories, + inputFiles, + fileLinks, + stringQuoteText, + requestOrigin, + maxFiles: chatConfig?.fileSelectConfig?.maxFiles || 20, + customPdfParse: chatConfig?.fileSelectConfig?.customPdfParse, + runningUserInfo + }) + ]); + + if (!userChatInput && !documentQuoteText && userFiles.length === 0) { + return getNodeErrResponse({ error: i18nT('chat:AI_input_is_empty') }); + } + + const max_tokens = computedMaxToken({ model: modelConstantsData, - quoteTemplate: quoteTemplate || getQuoteTemplate(version) - }), - getMultiInput({ - histories: chatHistories, - inputFiles, - fileLinks, - stringQuoteText, - requestOrigin, - maxFiles: chatConfig?.fileSelectConfig?.maxFiles || 20, - customPdfParse: chatConfig?.fileSelectConfig?.customPdfParse, - runningUserInfo - }) - ]); + maxToken + }); - if (!userChatInput && !documentQuoteText && userFiles.length === 0) { - return Promise.reject(i18nT('chat:AI_input_is_empty')); - } - - const max_tokens = computedMaxToken({ - model: modelConstantsData, - maxToken - }); - - const [{ filterMessages }] = await Promise.all([ - getChatMessages({ - model: modelConstantsData, - maxTokens: max_tokens, - histories: chatHistories, - useDatasetQuote: quoteQA !== undefined, - datasetQuoteText, - aiChatQuoteRole, - datasetQuotePrompt: quotePrompt, - version, - userChatInput, - systemPrompt, - userFiles, - documentQuoteText - }), - // Censor = true and system key, will check content - (() => { - if (modelConstantsData.censor && !externalProvider.openaiAccount?.key) { - return postTextCensor({ - text: `${systemPrompt} + const [{ filterMessages }] = await Promise.all([ + getChatMessages({ + model: modelConstantsData, + maxTokens: max_tokens, + histories: chatHistories, + useDatasetQuote: quoteQA !== undefined, + datasetQuoteText, + aiChatQuoteRole, + datasetQuotePrompt: quotePrompt, + version, + userChatInput, + systemPrompt, + userFiles, + documentQuoteText + }), + // Censor = true and system key, will check content + (() => { + if (modelConstantsData.censor && !externalProvider.openaiAccount?.key) { + return postTextCensor({ + text: `${systemPrompt} ${userChatInput} ` - }); + }); + } + })() + ]); + + const requestMessages = await loadRequestMessages({ + messages: filterMessages, + useVision: aiChatVision, + origin: requestOrigin + }); + + const requestBody = llmCompletionsBodyFormat( + { + model: modelConstantsData.model, + stream, + messages: requestMessages, + temperature, + max_tokens, + top_p: aiChatTopP, + stop: aiChatStopSign, + response_format: { + type: aiChatResponseFormat as any, + json_schema: aiChatJsonSchema + } + }, + modelConstantsData + ); + // console.log(JSON.stringify(requestBody, null, 2), '==='); + const { response, isStreamResponse, getEmptyResponseTip } = await createChatCompletion({ + body: requestBody, + userKey: externalProvider.openaiAccount, + options: { + headers: { + Accept: 'application/json, text/plain, */*' + } } - })() - ]); + }); - const requestMessages = await loadRequestMessages({ - messages: filterMessages, - useVision: aiChatVision, - origin: requestOrigin - }); + let { answerText, reasoningText, finish_reason, inputTokens, outputTokens } = + await (async () => { + if (isStreamResponse) { + if (!res || res.closed) { + return { + answerText: '', + reasoningText: '', + finish_reason: 'close' as const, + inputTokens: 0, + outputTokens: 0 + }; + } + // sse response + const { answer, reasoning, finish_reason, usage } = await streamResponse({ + res, + stream: response, + aiChatReasoning, + parseThinkTag: modelConstantsData.reasoning, + isResponseAnswerText, + workflowStreamResponse, + retainDatasetCite + }); - const requestBody = llmCompletionsBodyFormat( - { - model: modelConstantsData.model, - stream, - messages: requestMessages, - temperature, - max_tokens, - top_p: aiChatTopP, - stop: aiChatStopSign, - response_format: { - type: aiChatResponseFormat as any, - json_schema: aiChatJsonSchema - } - }, - modelConstantsData - ); - // console.log(JSON.stringify(requestBody, null, 2), '==='); - const { response, isStreamResponse, getEmptyResponseTip } = await createChatCompletion({ - body: requestBody, - userKey: externalProvider.openaiAccount, - options: { - headers: { - Accept: 'application/json, text/plain, */*' - } - } - }); - - let { answerText, reasoningText, finish_reason, inputTokens, outputTokens } = await (async () => { - if (isStreamResponse) { - if (!res || res.closed) { - return { - answerText: '', - reasoningText: '', - finish_reason: 'close' as const, - inputTokens: 0, - outputTokens: 0 - }; - } - // sse response - const { answer, reasoning, finish_reason, usage } = await streamResponse({ - res, - stream: response, - aiChatReasoning, - parseThinkTag: modelConstantsData.reasoning, - isResponseAnswerText, - workflowStreamResponse, - retainDatasetCite - }); - - return { - answerText: answer, - reasoningText: reasoning, - finish_reason, - inputTokens: usage?.prompt_tokens, - outputTokens: usage?.completion_tokens - }; - } else { - const finish_reason = response.choices?.[0]?.finish_reason as CompletionFinishReason; - const usage = response.usage; - - const { content, reasoningContent } = (() => { - const content = response.choices?.[0]?.message?.content || ''; - // @ts-ignore - const reasoningContent: string = response.choices?.[0]?.message?.reasoning_content || ''; - - // API already parse reasoning content - if (reasoningContent || !aiChatReasoning) { return { - content, - reasoningContent + answerText: answer, + reasoningText: reasoning, + finish_reason, + inputTokens: usage?.prompt_tokens, + outputTokens: usage?.completion_tokens + }; + } else { + const finish_reason = response.choices?.[0]?.finish_reason as CompletionFinishReason; + const usage = response.usage; + + const { content, reasoningContent } = (() => { + const content = response.choices?.[0]?.message?.content || ''; + const reasoningContent: string = + // @ts-ignore + response.choices?.[0]?.message?.reasoning_content || ''; + + // API already parse reasoning content + if (reasoningContent || !aiChatReasoning) { + return { + content, + reasoningContent + }; + } + + const [think, answer] = parseReasoningContent(content); + return { + content: answer, + reasoningContent: think + }; + })(); + + const formatReasonContent = removeDatasetCiteText(reasoningContent, retainDatasetCite); + const formatContent = removeDatasetCiteText(content, retainDatasetCite); + + // Some models do not support streaming + if (aiChatReasoning && reasoningContent) { + workflowStreamResponse?.({ + event: SseResponseEventEnum.fastAnswer, + data: textAdaptGptResponse({ + reasoning_content: formatReasonContent + }) + }); + } + if (isResponseAnswerText && content) { + workflowStreamResponse?.({ + event: SseResponseEventEnum.fastAnswer, + data: textAdaptGptResponse({ + text: formatContent + }) + }); + } + + return { + reasoningText: formatReasonContent, + answerText: formatContent, + finish_reason, + inputTokens: usage?.prompt_tokens, + outputTokens: usage?.completion_tokens }; } - - const [think, answer] = parseReasoningContent(content); - return { - content: answer, - reasoningContent: think - }; })(); - const formatReasonContent = removeDatasetCiteText(reasoningContent, retainDatasetCite); - const formatContent = removeDatasetCiteText(content, retainDatasetCite); - - // Some models do not support streaming - if (aiChatReasoning && reasoningContent) { - workflowStreamResponse?.({ - event: SseResponseEventEnum.fastAnswer, - data: textAdaptGptResponse({ - reasoning_content: formatReasonContent - }) - }); - } - if (isResponseAnswerText && content) { - workflowStreamResponse?.({ - event: SseResponseEventEnum.fastAnswer, - data: textAdaptGptResponse({ - text: formatContent - }) - }); - } - - return { - reasoningText: formatReasonContent, - answerText: formatContent, - finish_reason, - inputTokens: usage?.prompt_tokens, - outputTokens: usage?.completion_tokens - }; + if (!answerText && !reasoningText) { + return getNodeErrResponse({ error: getEmptyResponseTip() }); } - })(); - if (!answerText && !reasoningText) { - return Promise.reject(getEmptyResponseTip()); - } - - const AIMessages: ChatCompletionMessageParam[] = [ - { - role: ChatCompletionRequestMessageRoleEnum.Assistant, - content: answerText, - reasoning_text: reasoningText // reasoning_text is only recorded for response, but not for request - } - ]; - - const completeMessages = [...requestMessages, ...AIMessages]; - const chatCompleteMessages = GPTMessages2Chats(completeMessages); - - inputTokens = inputTokens || (await countGptMessagesTokens(requestMessages)); - outputTokens = outputTokens || (await countGptMessagesTokens(AIMessages)); - - const { totalPoints, modelName } = formatModelChars2Points({ - model, - inputTokens, - outputTokens, - modelType: ModelTypeEnum.llm - }); - - return { - answerText: answerText.trim(), - reasoningText, - [DispatchNodeResponseKeyEnum.nodeResponse]: { - totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints, - model: modelName, - inputTokens: inputTokens, - outputTokens: outputTokens, - query: `${userChatInput}`, - maxToken: max_tokens, - reasoningText, - historyPreview: getHistoryPreview(chatCompleteMessages, 10000, aiChatVision), - contextTotalLen: completeMessages.length, - finishReason: finish_reason - }, - [DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [ + const AIMessages: ChatCompletionMessageParam[] = [ { - moduleName: name, + role: ChatCompletionRequestMessageRoleEnum.Assistant, + content: answerText, + reasoning_text: reasoningText // reasoning_text is only recorded for response, but not for request + } + ]; + + const completeMessages = [...requestMessages, ...AIMessages]; + const chatCompleteMessages = GPTMessages2Chats(completeMessages); + + inputTokens = inputTokens || (await countGptMessagesTokens(requestMessages)); + outputTokens = outputTokens || (await countGptMessagesTokens(AIMessages)); + + const { totalPoints, modelName } = formatModelChars2Points({ + model, + inputTokens, + outputTokens, + modelType: ModelTypeEnum.llm + }); + + return { + data: { + answerText: answerText.trim(), + reasoningText, + history: chatCompleteMessages + }, + [DispatchNodeResponseKeyEnum.nodeResponse]: { totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints, model: modelName, inputTokens: inputTokens, - outputTokens: outputTokens - } - ], - [DispatchNodeResponseKeyEnum.toolResponses]: answerText, - history: chatCompleteMessages - }; + outputTokens: outputTokens, + query: `${userChatInput}`, + maxToken: max_tokens, + reasoningText, + historyPreview: getHistoryPreview(chatCompleteMessages, 10000, aiChatVision), + contextTotalLen: completeMessages.length, + finishReason: finish_reason + }, + [DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [ + { + moduleName: name, + totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints, + model: modelName, + inputTokens: inputTokens, + outputTokens: outputTokens + } + ], + [DispatchNodeResponseKeyEnum.toolResponses]: answerText + }; + } catch (error) { + return getNodeErrResponse({ error }); + } }; async function filterDatasetQuote({ diff --git a/packages/service/core/workflow/dispatch/ai/classifyQuestion.ts b/packages/service/core/workflow/dispatch/ai/classifyQuestion.ts index 94f7788bd..0fffd7f2f 100644 --- a/packages/service/core/workflow/dispatch/ai/classifyQuestion.ts +++ b/packages/service/core/workflow/dispatch/ai/classifyQuestion.ts @@ -78,7 +78,9 @@ export const dispatchClassifyQuestion = async (props: Props): Promise item.key !== result.key) .map((item) => getHandleId(nodeId, 'source', item.key)), diff --git a/packages/service/core/workflow/dispatch/ai/extract.ts b/packages/service/core/workflow/dispatch/ai/extract.ts index 247b82e7b..7ca474efc 100644 --- a/packages/service/core/workflow/dispatch/ai/extract.ts +++ b/packages/service/core/workflow/dispatch/ai/extract.ts @@ -19,7 +19,7 @@ import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runti import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type'; import { sliceJsonStr } from '@fastgpt/global/common/string/tools'; import { type LLMModelItemType } from '@fastgpt/global/core/ai/model.d'; -import { getHistories } from '../utils'; +import { getNodeErrResponse, getHistories } from '../utils'; import { getLLMModel } from '../../../ai/model'; import { formatModelChars2Points } from '../../../../support/wallet/usage/utils'; import json5 from 'json5'; @@ -46,6 +46,7 @@ type Props = ModuleDispatchProps<{ type Response = DispatchNodeResultType<{ [NodeOutputKeyEnum.success]: boolean; [NodeOutputKeyEnum.contextExtractFields]: string; + [key: string]: any; }>; type ActionProps = Props & { extractModel: LLMModelItemType; lastMemory?: Record }; @@ -62,7 +63,7 @@ export async function dispatchContentExtract(props: Props): Promise { } = props; if (!content) { - return Promise.reject('Input is empty'); + return getNodeErrResponse({ error: 'Input is empty' }); } const extractModel = getLLMModel(model); @@ -75,88 +76,94 @@ export async function dispatchContentExtract(props: Props): Promise { any >; - const { arg, inputTokens, outputTokens } = await (async () => { - if (extractModel.toolChoice) { - return toolChoice({ + try { + const { arg, inputTokens, outputTokens } = await (async () => { + if (extractModel.toolChoice) { + return toolChoice({ + ...props, + histories: chatHistories, + extractModel, + lastMemory + }); + } + return completions({ ...props, histories: chatHistories, extractModel, lastMemory }); - } - return completions({ - ...props, - histories: chatHistories, - extractModel, - lastMemory - }); - })(); + })(); - // remove invalid key - for (let key in arg) { - const item = extractKeys.find((item) => item.key === key); - if (!item) { - delete arg[key]; - } - if (arg[key] === '') { - delete arg[key]; - } - } - - // auto fill required fields - extractKeys.forEach((item) => { - if (item.required && arg[item.key] === undefined) { - arg[item.key] = item.defaultValue || ''; - } - }); - - // auth fields - let success = !extractKeys.find((item) => !(item.key in arg)); - // auth empty value - if (success) { - for (const key in arg) { + // remove invalid key + for (let key in arg) { const item = extractKeys.find((item) => item.key === key); if (!item) { - success = false; - break; + delete arg[key]; + } + if (arg[key] === '') { + delete arg[key]; } } - } - const { totalPoints, modelName } = formatModelChars2Points({ - model: extractModel.model, - inputTokens: inputTokens, - outputTokens: outputTokens, - modelType: ModelTypeEnum.llm - }); + // auto fill required fields + extractKeys.forEach((item) => { + if (item.required && arg[item.key] === undefined) { + arg[item.key] = item.defaultValue || ''; + } + }); - return { - [NodeOutputKeyEnum.success]: success, - [NodeOutputKeyEnum.contextExtractFields]: JSON.stringify(arg), - [DispatchNodeResponseKeyEnum.memories]: { - [memoryKey]: arg - }, - ...arg, - [DispatchNodeResponseKeyEnum.nodeResponse]: { - totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints, - model: modelName, - query: content, - inputTokens, - outputTokens, - extractDescription: description, - extractResult: arg, - contextTotalLen: chatHistories.length + 2 - }, - [DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [ - { - moduleName: name, + // auth fields + let success = !extractKeys.find((item) => !(item.key in arg)); + // auth empty value + if (success) { + for (const key in arg) { + const item = extractKeys.find((item) => item.key === key); + if (!item) { + success = false; + break; + } + } + } + + const { totalPoints, modelName } = formatModelChars2Points({ + model: extractModel.model, + inputTokens: inputTokens, + outputTokens: outputTokens, + modelType: ModelTypeEnum.llm + }); + + return { + data: { + [NodeOutputKeyEnum.success]: success, + [NodeOutputKeyEnum.contextExtractFields]: JSON.stringify(arg), + ...arg + }, + [DispatchNodeResponseKeyEnum.memories]: { + [memoryKey]: arg + }, + [DispatchNodeResponseKeyEnum.nodeResponse]: { totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints, model: modelName, + query: content, inputTokens, - outputTokens - } - ] - }; + outputTokens, + extractDescription: description, + extractResult: arg, + contextTotalLen: chatHistories.length + 2 + }, + [DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [ + { + moduleName: name, + totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints, + model: modelName, + inputTokens, + outputTokens + } + ] + }; + } catch (error) { + return getNodeErrResponse({ error }); + } } const getJsonSchema = ({ params: { extractKeys } }: ActionProps) => { diff --git a/packages/service/core/workflow/dispatch/child/runApp.ts b/packages/service/core/workflow/dispatch/child/runApp.ts new file mode 100644 index 000000000..d35f30c58 --- /dev/null +++ b/packages/service/core/workflow/dispatch/child/runApp.ts @@ -0,0 +1,208 @@ +import type { ChatItemType } from '@fastgpt/global/core/chat/type.d'; +import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type'; +import { dispatchWorkFlow } from '../index'; +import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; +import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; +import { + getWorkflowEntryNodeIds, + storeEdges2RuntimeEdges, + rewriteNodeOutputByHistories, + storeNodes2RuntimeNodes, + textAdaptGptResponse +} from '@fastgpt/global/core/workflow/runtime/utils'; +import type { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; +import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; +import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants'; +import { filterSystemVariables, getNodeErrResponse, getHistories } from '../utils'; +import { chatValue2RuntimePrompt, runtimePrompt2ChatsValue } from '@fastgpt/global/core/chat/adapt'; +import { type DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type'; +import { authAppByTmbId } from '../../../../support/permission/app/auth'; +import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; +import { getAppVersionById } from '../../../app/version/controller'; +import { parseUrlToFileType } from '@fastgpt/global/common/file/tools'; +import { getUserChatInfoAndAuthTeamPoints } from '../../../../support/permission/auth/team'; + +type Props = ModuleDispatchProps<{ + [NodeInputKeyEnum.userChatInput]: string; + [NodeInputKeyEnum.history]?: ChatItemType[] | number; + [NodeInputKeyEnum.fileUrlList]?: string[]; + [NodeInputKeyEnum.forbidStream]?: boolean; + [NodeInputKeyEnum.fileUrlList]?: string[]; +}>; +type Response = DispatchNodeResultType<{ + [NodeOutputKeyEnum.answerText]: string; + [NodeOutputKeyEnum.history]: ChatItemType[]; +}>; + +export const dispatchRunAppNode = async (props: Props): Promise => { + const { + runningAppInfo, + histories, + query, + lastInteractive, + node: { pluginId: appId, version }, + workflowStreamResponse, + params, + variables + } = props; + + const { + system_forbid_stream = false, + userChatInput, + history, + fileUrlList, + ...childrenAppVariables + } = params; + const { files } = chatValue2RuntimePrompt(query); + + const userInputFiles = (() => { + if (fileUrlList) { + return fileUrlList.map((url) => parseUrlToFileType(url)).filter(Boolean); + } + // Adapt version 4.8.13 upgrade + return files; + })(); + + if (!userChatInput && !userInputFiles) { + return getNodeErrResponse({ error: 'Input is empty' }); + } + if (!appId) { + return getNodeErrResponse({ error: 'pluginId is empty' }); + } + + try { + // Auth the app by tmbId(Not the user, but the workflow user) + const { app: appData } = await authAppByTmbId({ + appId: appId, + tmbId: runningAppInfo.tmbId, + per: ReadPermissionVal + }); + const { nodes, edges, chatConfig } = await getAppVersionById({ + appId, + versionId: version, + app: appData + }); + + const childStreamResponse = system_forbid_stream ? false : props.stream; + // Auto line + if (childStreamResponse) { + workflowStreamResponse?.({ + event: SseResponseEventEnum.answer, + data: textAdaptGptResponse({ + text: '\n' + }) + }); + } + + const chatHistories = getHistories(history, histories); + + // Rewrite children app variables + const systemVariables = filterSystemVariables(variables); + const { externalProvider } = await getUserChatInfoAndAuthTeamPoints(appData.tmbId); + const childrenRunVariables = { + ...systemVariables, + ...childrenAppVariables, + histories: chatHistories, + appId: String(appData._id), + ...(externalProvider ? externalProvider.externalWorkflowVariables : {}) + }; + + const childrenInteractive = + lastInteractive?.type === 'childrenInteractive' + ? lastInteractive.params.childrenResponse + : undefined; + const runtimeNodes = rewriteNodeOutputByHistories( + storeNodes2RuntimeNodes( + nodes, + getWorkflowEntryNodeIds(nodes, childrenInteractive || undefined) + ), + childrenInteractive + ); + + const runtimeEdges = storeEdges2RuntimeEdges(edges, childrenInteractive); + const theQuery = childrenInteractive + ? query + : runtimePrompt2ChatsValue({ files: userInputFiles, text: userChatInput }); + + const { + flowResponses, + flowUsages, + assistantResponses, + runTimes, + workflowInteractiveResponse, + system_memories + } = await dispatchWorkFlow({ + ...props, + lastInteractive: childrenInteractive, + // Rewrite stream mode + ...(system_forbid_stream + ? { + stream: false, + workflowStreamResponse: undefined + } + : {}), + runningAppInfo: { + id: String(appData._id), + teamId: String(appData.teamId), + tmbId: String(appData.tmbId), + isChildApp: true + }, + runtimeNodes, + runtimeEdges, + histories: chatHistories, + variables: childrenRunVariables, + query: theQuery, + chatConfig + }); + + const completeMessages = chatHistories.concat([ + { + obj: ChatRoleEnum.Human, + value: query + }, + { + obj: ChatRoleEnum.AI, + value: assistantResponses + } + ]); + + const { text } = chatValue2RuntimePrompt(assistantResponses); + + const usagePoints = flowUsages.reduce((sum, item) => sum + (item.totalPoints || 0), 0); + + return { + data: { + [NodeOutputKeyEnum.answerText]: text, + [NodeOutputKeyEnum.history]: completeMessages + }, + system_memories, + [DispatchNodeResponseKeyEnum.interactive]: workflowInteractiveResponse + ? { + type: 'childrenInteractive', + params: { + childrenResponse: workflowInteractiveResponse + } + } + : undefined, + assistantResponses: system_forbid_stream ? [] : assistantResponses, + [DispatchNodeResponseKeyEnum.runTimes]: runTimes, + [DispatchNodeResponseKeyEnum.nodeResponse]: { + moduleLogo: appData.avatar, + totalPoints: usagePoints, + query: userChatInput, + textOutput: text, + pluginDetail: appData.permission.hasWritePer ? flowResponses : undefined, + mergeSignId: props.node.nodeId + }, + [DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [ + { + moduleName: appData.name, + totalPoints: usagePoints + } + ], + [DispatchNodeResponseKeyEnum.toolResponses]: text + }; + } catch (error) { + return getNodeErrResponse({ error }); + } +}; diff --git a/packages/service/core/workflow/dispatch/plugin/runTool.ts b/packages/service/core/workflow/dispatch/child/runTool.ts similarity index 64% rename from packages/service/core/workflow/dispatch/plugin/runTool.ts rename to packages/service/core/workflow/dispatch/child/runTool.ts index 81d48c636..ac8bfcb25 100644 --- a/packages/service/core/workflow/dispatch/plugin/runTool.ts +++ b/packages/service/core/workflow/dispatch/child/runTool.ts @@ -17,23 +17,25 @@ import type { StoreSecretValueType } from '@fastgpt/global/common/secret/type'; import { getSystemPluginById } from '../../../app/plugin/controller'; import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils'; import { pushTrack } from '../../../../common/middle/tracks/utils'; +import { getNodeErrResponse } from '../utils'; type SystemInputConfigType = { type: SystemToolInputTypeEnum; value: StoreSecretValueType; }; -type RunToolProps = ModuleDispatchProps< - { - [NodeInputKeyEnum.toolData]?: McpToolDataType; - [NodeInputKeyEnum.systemInputConfig]?: SystemInputConfigType; - } & Record ->; +type RunToolProps = ModuleDispatchProps<{ + [NodeInputKeyEnum.toolData]?: McpToolDataType; + [NodeInputKeyEnum.systemInputConfig]?: SystemInputConfigType; + [key: string]: any; +}>; type RunToolResponse = DispatchNodeResultType< { [NodeOutputKeyEnum.rawResponse]?: any; - } & Record + [key: string]: any; + }, + Record >; export const dispatchRunTool = async (props: RunToolProps): Promise => { @@ -43,7 +45,7 @@ export const dispatchRunTool = async (props: RunToolProps): Promise { - const res = await runSystemTool({ - toolId: formatToolId, - inputs, - systemVar: { - user: { - id: variables.userId, - teamId: runningUserInfo.teamId, - name: runningUserInfo.tmbId - }, - app: { - id: runningAppInfo.id, - name: runningAppInfo.id - }, - tool: { - id: formatToolId, - version: version || tool.versionList?.[0]?.value || '' - }, - time: variables.cTime + const res = await runSystemTool({ + toolId: formatToolId, + inputs, + systemVar: { + user: { + id: variables.userId, + teamId: runningUserInfo.teamId, + name: runningUserInfo.tmbId }, - onMessage: ({ type, content }) => { - if (workflowStreamResponse && content) { - workflowStreamResponse({ - event: type as unknown as SseResponseEventEnum, - data: textAdaptGptResponse({ - text: content - }) - }); - } + app: { + id: runningAppInfo.id, + name: runningAppInfo.id + }, + tool: { + id: formatToolId, + version: version || tool.versionList?.[0]?.value || '' + }, + time: variables.cTime + }, + onMessage: ({ type, content }) => { + if (workflowStreamResponse && content) { + workflowStreamResponse({ + event: type as unknown as SseResponseEventEnum, + data: textAdaptGptResponse({ + text: content + }) + }); } - }); - if (res.error) { - return Promise.reject(res.error); } - if (!res.output) return {}; + }); + let result = res.output || {}; - return res.output; - })(); + if (res.error) { + // 适配旧版:旧版本没有catchError,部分工具会正常返回 error 字段作为响应。 + if (catchError === undefined && typeof res.error === 'object') { + return { + data: res.error, + [DispatchNodeResponseKeyEnum.nodeResponse]: { + toolRes: res.error, + moduleLogo: avatar + }, + [DispatchNodeResponseKeyEnum.toolResponses]: res.error + }; + } + + // String error(Common error, not custom) + if (typeof res.error === 'string') { + throw new Error(res.error); + } + + // Custom error field + return { + error: res.error, + [DispatchNodeResponseKeyEnum.nodeResponse]: { + error: res.error, + moduleLogo: avatar + }, + [DispatchNodeResponseKeyEnum.toolResponses]: res.error + }; + } const usagePoints = (() => { - if ( - params.system_input_config?.type !== SystemToolInputTypeEnum.system || - result[NodeOutputKeyEnum.systemError] - ) { + if (params.system_input_config?.type !== SystemToolInputTypeEnum.system) { return 0; } return tool.currentCost ?? 0; @@ -140,6 +161,7 @@ export const dispatchRunTool = async (props: RunToolProps): Promise item.datasetId), - tmbId - }) - : await Promise.resolve(datasets.map((item) => item.datasetId)); + try { + const datasetIds = authTmbId + ? await filterDatasetsByTmbId({ + datasetIds: datasets.map((item) => item.datasetId), + tmbId + }) + : await Promise.resolve(datasets.map((item) => item.datasetId)); - if (datasetIds.length === 0) { - return emptyResult; - } + if (datasetIds.length === 0) { + return emptyResult; + } - // get vector - const vectorModel = getEmbeddingModel( - (await MongoDataset.findById(datasets[0].datasetId, 'vectorModel').lean())?.vectorModel - ); - // Get Rerank Model - const rerankModelData = getRerankModel(rerankModel); + // get vector + const vectorModel = getEmbeddingModel( + (await MongoDataset.findById(datasets[0].datasetId, 'vectorModel').lean())?.vectorModel + ); + // Get Rerank Model + const rerankModelData = getRerankModel(rerankModel); - // start search - const searchData = { - histories, - teamId, - reRankQuery: userChatInput, - queries: [userChatInput], - model: vectorModel.model, - similarity, - limit, - datasetIds, - searchMode, - embeddingWeight, - usingReRank, - rerankModel: rerankModelData, - rerankWeight, - collectionFilterMatch - }; - const { - searchRes, - embeddingTokens, - reRankInputTokens, - usingSimilarityFilter, - usingReRank: searchUsingReRank, - queryExtensionResult, - deepSearchResult - } = datasetDeepSearch - ? await deepRagSearch({ - ...searchData, - datasetDeepSearchModel, - datasetDeepSearchMaxTimes, - datasetDeepSearchBg - }) - : await defaultSearchDatasetData({ - ...searchData, - datasetSearchUsingExtensionQuery, - datasetSearchExtensionModel, - datasetSearchExtensionBg - }); - - // count bill results - const nodeDispatchUsages: ChatNodeUsageType[] = []; - // vector - const { totalPoints: embeddingTotalPoints, modelName: embeddingModelName } = - formatModelChars2Points({ + // start search + const searchData = { + histories, + teamId, + reRankQuery: userChatInput, + queries: [userChatInput], model: vectorModel.model, - inputTokens: embeddingTokens, - modelType: ModelTypeEnum.embedding - }); - nodeDispatchUsages.push({ - totalPoints: embeddingTotalPoints, - moduleName: node.name, - model: embeddingModelName, - inputTokens: embeddingTokens - }); - // Rerank - const { totalPoints: reRankTotalPoints, modelName: reRankModelName } = formatModelChars2Points({ - model: rerankModelData?.model, - inputTokens: reRankInputTokens, - modelType: ModelTypeEnum.rerank - }); - if (usingReRank) { + similarity, + limit, + datasetIds, + searchMode, + embeddingWeight, + usingReRank, + rerankModel: rerankModelData, + rerankWeight, + collectionFilterMatch + }; + const { + searchRes, + embeddingTokens, + reRankInputTokens, + usingSimilarityFilter, + usingReRank: searchUsingReRank, + queryExtensionResult, + deepSearchResult + } = datasetDeepSearch + ? await deepRagSearch({ + ...searchData, + datasetDeepSearchModel, + datasetDeepSearchMaxTimes, + datasetDeepSearchBg + }) + : await defaultSearchDatasetData({ + ...searchData, + datasetSearchUsingExtensionQuery, + datasetSearchExtensionModel, + datasetSearchExtensionBg + }); + + // count bill results + const nodeDispatchUsages: ChatNodeUsageType[] = []; + // vector + const { totalPoints: embeddingTotalPoints, modelName: embeddingModelName } = + formatModelChars2Points({ + model: vectorModel.model, + inputTokens: embeddingTokens, + modelType: ModelTypeEnum.embedding + }); nodeDispatchUsages.push({ - totalPoints: reRankTotalPoints, + totalPoints: embeddingTotalPoints, moduleName: node.name, - model: reRankModelName, - inputTokens: reRankInputTokens + model: embeddingModelName, + inputTokens: embeddingTokens }); - } - // Query extension - (() => { - if (queryExtensionResult) { - const { totalPoints, modelName } = formatModelChars2Points({ - model: queryExtensionResult.model, - inputTokens: queryExtensionResult.inputTokens, - outputTokens: queryExtensionResult.outputTokens, - modelType: ModelTypeEnum.llm - }); - nodeDispatchUsages.push({ - totalPoints, - moduleName: i18nT('common:core.module.template.Query extension'), - model: modelName, - inputTokens: queryExtensionResult.inputTokens, - outputTokens: queryExtensionResult.outputTokens - }); - return { - totalPoints - }; - } - return { - totalPoints: 0 - }; - })(); - // Deep search - (() => { - if (deepSearchResult) { - const { totalPoints, modelName } = formatModelChars2Points({ - model: deepSearchResult.model, - inputTokens: deepSearchResult.inputTokens, - outputTokens: deepSearchResult.outputTokens, - modelType: ModelTypeEnum.llm - }); - nodeDispatchUsages.push({ - totalPoints, - moduleName: i18nT('common:deep_rag_search'), - model: modelName, - inputTokens: deepSearchResult.inputTokens, - outputTokens: deepSearchResult.outputTokens - }); - return { - totalPoints - }; - } - return { - totalPoints: 0 - }; - })(); - - const totalPoints = nodeDispatchUsages.reduce((acc, item) => acc + item.totalPoints, 0); - - const responseData: DispatchNodeResponseType & { totalPoints: number } = { - totalPoints, - query: userChatInput, - embeddingModel: vectorModel.name, - embeddingTokens, - similarity: usingSimilarityFilter ? similarity : undefined, - limit, - searchMode, - embeddingWeight: searchMode === DatasetSearchModeEnum.mixedRecall ? embeddingWeight : undefined, // Rerank - ...(searchUsingReRank && { - rerankModel: rerankModelData?.name, - rerankWeight: rerankWeight, - reRankInputTokens - }), - searchUsingReRank, - // Results - quoteList: searchRes, - queryExtensionResult, - deepSearchResult - }; - - return { - quoteQA: searchRes, - [DispatchNodeResponseKeyEnum.nodeResponse]: responseData, - nodeDispatchUsages, - [DispatchNodeResponseKeyEnum.toolResponses]: { - prompt: getDatasetSearchToolResponsePrompt(), - cites: searchRes.map((item) => ({ - id: item.id, - sourceName: item.sourceName, - updateTime: item.updateTime, - content: `${item.q}\n${item.a}`.trim() - })) + const { totalPoints: reRankTotalPoints, modelName: reRankModelName } = formatModelChars2Points({ + model: rerankModelData?.model, + inputTokens: reRankInputTokens, + modelType: ModelTypeEnum.rerank + }); + if (usingReRank) { + nodeDispatchUsages.push({ + totalPoints: reRankTotalPoints, + moduleName: node.name, + model: reRankModelName, + inputTokens: reRankInputTokens + }); } - }; + // Query extension + (() => { + if (queryExtensionResult) { + const { totalPoints, modelName } = formatModelChars2Points({ + model: queryExtensionResult.model, + inputTokens: queryExtensionResult.inputTokens, + outputTokens: queryExtensionResult.outputTokens, + modelType: ModelTypeEnum.llm + }); + nodeDispatchUsages.push({ + totalPoints, + moduleName: i18nT('common:core.module.template.Query extension'), + model: modelName, + inputTokens: queryExtensionResult.inputTokens, + outputTokens: queryExtensionResult.outputTokens + }); + return { + totalPoints + }; + } + return { + totalPoints: 0 + }; + })(); + // Deep search + (() => { + if (deepSearchResult) { + const { totalPoints, modelName } = formatModelChars2Points({ + model: deepSearchResult.model, + inputTokens: deepSearchResult.inputTokens, + outputTokens: deepSearchResult.outputTokens, + modelType: ModelTypeEnum.llm + }); + nodeDispatchUsages.push({ + totalPoints, + moduleName: i18nT('common:deep_rag_search'), + model: modelName, + inputTokens: deepSearchResult.inputTokens, + outputTokens: deepSearchResult.outputTokens + }); + return { + totalPoints + }; + } + return { + totalPoints: 0 + }; + })(); + + const totalPoints = nodeDispatchUsages.reduce((acc, item) => acc + item.totalPoints, 0); + + const responseData: DispatchNodeResponseType & { totalPoints: number } = { + totalPoints, + query: userChatInput, + embeddingModel: vectorModel.name, + embeddingTokens, + similarity: usingSimilarityFilter ? similarity : undefined, + limit, + searchMode, + embeddingWeight: + searchMode === DatasetSearchModeEnum.mixedRecall ? embeddingWeight : undefined, + // Rerank + ...(searchUsingReRank && { + rerankModel: rerankModelData?.name, + rerankWeight: rerankWeight, + reRankInputTokens + }), + searchUsingReRank, + // Results + quoteList: searchRes, + queryExtensionResult, + deepSearchResult + }; + + return { + data: { + quoteQA: searchRes + }, + [DispatchNodeResponseKeyEnum.nodeResponse]: responseData, + nodeDispatchUsages, + [DispatchNodeResponseKeyEnum.toolResponses]: { + prompt: getDatasetSearchToolResponsePrompt(), + cites: searchRes.map((item) => ({ + id: item.id, + sourceName: item.sourceName, + updateTime: item.updateTime, + content: `${item.q}\n${item.a}`.trim() + })) + } + }; + } catch (error) { + return getNodeErrResponse({ error }); + } } diff --git a/packages/service/core/workflow/dispatch/index.ts b/packages/service/core/workflow/dispatch/index.ts index 9e5217dba..444c1ba43 100644 --- a/packages/service/core/workflow/dispatch/index.ts +++ b/packages/service/core/workflow/dispatch/index.ts @@ -49,7 +49,7 @@ import { dispatchRunTools } from './ai/agent/index'; import { dispatchStopToolCall } from './ai/agent/stopTool'; import { dispatchToolParams } from './ai/agent/toolParams'; import { dispatchChatCompletion } from './ai/chat'; -import { dispatchRunCode } from './code/run'; +import { dispatchCodeSandbox } from './tools/codeSandbox'; import { dispatchDatasetConcat } from './dataset/concat'; import { dispatchDatasetSearch } from './dataset/search'; import { dispatchSystemConfig } from './init/systemConfig'; @@ -60,10 +60,10 @@ import { dispatchLoop } from './loop/runLoop'; import { dispatchLoopEnd } from './loop/runLoopEnd'; import { dispatchLoopStart } from './loop/runLoopStart'; import { dispatchRunPlugin } from './plugin/run'; -import { dispatchRunAppNode } from './plugin/runApp'; +import { dispatchRunAppNode } from './child/runApp'; import { dispatchPluginInput } from './plugin/runInput'; import { dispatchPluginOutput } from './plugin/runOutput'; -import { dispatchRunTool } from './plugin/runTool'; +import { dispatchRunTool } from './child/runTool'; import { dispatchAnswer } from './tools/answer'; import { dispatchCustomFeedback } from './tools/customFeedback'; import { dispatchHttp468Request } from './tools/http468'; @@ -74,7 +74,8 @@ import { dispatchLafRequest } from './tools/runLaf'; import { dispatchUpdateVariable } from './tools/runUpdateVar'; import { dispatchTextEditor } from './tools/textEditor'; import type { DispatchFlowResponse } from './type'; -import { formatHttpError, removeSystemVariable, rewriteRuntimeWorkFlow } from './utils'; +import { removeSystemVariable, rewriteRuntimeWorkFlow } from './utils'; +import { getHandleId } from '@fastgpt/global/core/workflow/utils'; const callbackMap: Record = { [FlowNodeTypeEnum.workflowStart]: dispatchWorkflowStart, @@ -96,7 +97,7 @@ const callbackMap: Record = { [FlowNodeTypeEnum.lafModule]: dispatchLafRequest, [FlowNodeTypeEnum.ifElseNode]: dispatchIfElse, [FlowNodeTypeEnum.variableUpdate]: dispatchUpdateVariable, - [FlowNodeTypeEnum.code]: dispatchRunCode, + [FlowNodeTypeEnum.code]: dispatchCodeSandbox, [FlowNodeTypeEnum.textEditor]: dispatchTextEditor, [FlowNodeTypeEnum.customFeedback]: dispatchCustomFeedback, [FlowNodeTypeEnum.readFiles]: dispatchReadFiles, @@ -123,6 +124,14 @@ type Props = ChatDispatchProps & { runtimeNodes: RuntimeNodeItemType[]; runtimeEdges: RuntimeEdgeItemType[]; }; +type NodeResponseType = DispatchNodeResultType<{ + [NodeOutputKeyEnum.answerText]?: string; + [NodeOutputKeyEnum.reasoningText]?: string; + [key: string]: any; +}>; +type NodeResponseCompleteType = Omit & { + [DispatchNodeResponseKeyEnum.nodeResponse]?: ChatHistoryItemResType; +}; /* running */ export async function dispatchWorkFlow(data: Props): Promise { @@ -229,8 +238,7 @@ export async function dispatchWorkFlow(data: Props): Promise, - 'nodeResponse' - > + }: NodeResponseCompleteType ) { // Add run times workflowRunTimes += runTimes; @@ -316,22 +317,27 @@ export async function dispatchWorkFlow(data: Props): Promise = {} + result: NodeResponseCompleteType ): { nextStepActiveNodes: RuntimeNodeItemType[]; nextStepSkipNodes: RuntimeNodeItemType[]; } { pushStore(node, result); + const concatData: Record = { + ...(result.data ?? {}), + ...(result.error ?? {}) + }; + // Assign the output value to the next node node.outputs.forEach((outputItem) => { - if (result[outputItem.key] === undefined) return; + if (concatData[outputItem.key] === undefined) return; /* update output value */ - outputItem.value = result[outputItem.key]; + outputItem.value = concatData[outputItem.key]; }); // Get next source edges and update status - const skipHandleId = (result[DispatchNodeResponseKeyEnum.skipHandleId] || []) as string[]; + const skipHandleId = result[DispatchNodeResponseKeyEnum.skipHandleId] || []; const targetEdges = filterWorkflowEdges(runtimeEdges).filter( (item) => item.source === node.nodeId ); @@ -591,7 +597,7 @@ export async function dispatchWorkFlow(data: Props): Promise; + result: NodeResponseCompleteType; }> { // push run status messages if (node.showStatus && !props.isToolCall) { @@ -625,23 +631,66 @@ export async function dispatchWorkFlow(data: Props): Promise = await (async () => { + const dispatchRes: NodeResponseType = await (async () => { if (callbackMap[node.flowNodeType]) { + const targetEdges = runtimeEdges.filter((item) => item.source === node.nodeId); + try { - return await callbackMap[node.flowNodeType](dispatchData); + const result = (await callbackMap[node.flowNodeType](dispatchData)) as NodeResponseType; + const errorHandleId = getHandleId(node.nodeId, 'source_catch', 'right'); + + if (!result.error) { + const skipHandleId = + targetEdges.find((item) => item.sourceHandle === errorHandleId)?.sourceHandle || ''; + + return { + ...result, + [DispatchNodeResponseKeyEnum.skipHandleId]: (result[ + DispatchNodeResponseKeyEnum.skipHandleId + ] + ? [...result[DispatchNodeResponseKeyEnum.skipHandleId], skipHandleId] + : [skipHandleId] + ).filter(Boolean) + }; + } + + // Run error and not catch error, skip all edges + if (!node.catchError) { + return { + ...result, + [DispatchNodeResponseKeyEnum.skipHandleId]: targetEdges.map( + (item) => item.sourceHandle + ) + }; + } + + // Catch error + const skipHandleIds = targetEdges + .filter((item) => { + if (node.catchError) { + return item.sourceHandle !== errorHandleId; + } + return true; + }) + .map((item) => item.sourceHandle); + + return { + ...result, + [DispatchNodeResponseKeyEnum.skipHandleId]: result[ + DispatchNodeResponseKeyEnum.skipHandleId + ] + ? [...result[DispatchNodeResponseKeyEnum.skipHandleId], ...skipHandleIds].filter( + Boolean + ) + : skipHandleIds + }; } catch (error) { - // Get source handles of outgoing edges - const targetEdges = runtimeEdges.filter((item) => item.source === node.nodeId); - const skipHandleIds = targetEdges.map((item) => item.sourceHandle); - - toolRunResponse = getErrText(error); - // Skip all edges and return error return { [DispatchNodeResponseKeyEnum.nodeResponse]: { - error: formatHttpError(error) + error: getErrText(error) }, - [DispatchNodeResponseKeyEnum.skipHandleId]: skipHandleIds + [DispatchNodeResponseKeyEnum.skipHandleId]: targetEdges.map((item) => item.sourceHandle) }; } } @@ -649,15 +698,16 @@ export async function dispatchWorkFlow(data: Props): Promise { + const formatResponseData: NodeResponseCompleteType['responseData'] = (() => { if (!dispatchRes[DispatchNodeResponseKeyEnum.nodeResponse]) return undefined; + return { + ...dispatchRes[DispatchNodeResponseKeyEnum.nodeResponse], id: getNanoid(), nodeId: node.nodeId, moduleName: node.name, moduleType: node.flowNodeType, - runningTime: +((Date.now() - startTime) / 1000).toFixed(2), - ...dispatchRes[DispatchNodeResponseKeyEnum.nodeResponse] + runningTime: +((Date.now() - startTime) / 1000).toFixed(2) }; })(); @@ -675,11 +725,13 @@ export async function dispatchWorkFlow(data: Props): Promise { - if (!item.required) return; - if (dispatchRes[item.key] !== undefined) return; - dispatchRes[item.key] = valueTypeFormat(item.defaultValue, item.valueType); - }); + if (dispatchRes.data) { + node.outputs.forEach((item) => { + if (!item.required) return; + if (dispatchRes.data?.[item.key] !== undefined) return; + dispatchRes.data![item.key] = valueTypeFormat(item.defaultValue, item.valueType); + }); + } // Update new variables if (dispatchRes[DispatchNodeResponseKeyEnum.newVariables]) { @@ -691,7 +743,7 @@ export async function dispatchWorkFlow(data: Props): Promise; + result: NodeResponseCompleteType; }> { // Set target edges status to skipped const targetEdges = runtimeEdges.filter((item) => item.source === node.nodeId); diff --git a/packages/service/core/workflow/dispatch/init/workflowStart.tsx b/packages/service/core/workflow/dispatch/init/workflowStart.tsx index 4e7300029..40fbc7279 100644 --- a/packages/service/core/workflow/dispatch/init/workflowStart.tsx +++ b/packages/service/core/workflow/dispatch/init/workflowStart.tsx @@ -34,8 +34,9 @@ export const dispatchWorkflowStart = (props: Record): Response => { return { [DispatchNodeResponseKeyEnum.nodeResponse]: {}, - [NodeInputKeyEnum.userChatInput]: text || userChatInput, - [NodeOutputKeyEnum.userFiles]: [...queryFiles, ...variablesFiles] - // [NodeInputKeyEnum.inputFiles]: files + data: { + [NodeInputKeyEnum.userChatInput]: text || userChatInput, + [NodeOutputKeyEnum.userFiles]: [...queryFiles, ...variablesFiles] + } }; }; diff --git a/packages/service/core/workflow/dispatch/interactive/formInput.ts b/packages/service/core/workflow/dispatch/interactive/formInput.ts index 53866975a..09d1b2969 100644 --- a/packages/service/core/workflow/dispatch/interactive/formInput.ts +++ b/packages/service/core/workflow/dispatch/interactive/formInput.ts @@ -6,10 +6,7 @@ import type { DispatchNodeResultType, ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type'; -import type { - UserInputFormItemType, - UserInputInteractive -} from '@fastgpt/global/core/workflow/template/system/interactive/type'; +import type { UserInputFormItemType } from '@fastgpt/global/core/workflow/template/system/interactive/type'; import { addLog } from '../../../../common/system/log'; type Props = ModuleDispatchProps<{ @@ -17,8 +14,8 @@ type Props = ModuleDispatchProps<{ [NodeInputKeyEnum.userInputForms]: UserInputFormItemType[]; }>; type FormInputResponse = DispatchNodeResultType<{ - [DispatchNodeResponseKeyEnum.interactive]?: UserInputInteractive; [NodeOutputKeyEnum.formInputResult]?: Record; + [key: string]: any; }>; /* @@ -60,9 +57,11 @@ export const dispatchFormInput = async (props: Props): Promise; type UserSelectResponse = DispatchNodeResultType<{ - [NodeOutputKeyEnum.answerText]?: string; - [DispatchNodeResponseKeyEnum.interactive]?: UserSelectInteractive; [NodeOutputKeyEnum.selectResult]?: string; }>; @@ -59,6 +54,9 @@ export const dispatchUserSelect = async (props: Props): Promise item.value !== userSelectedVal) @@ -66,7 +64,6 @@ export const dispatchUserSelect = async (props: Props): Promise; type Response = DispatchNodeResultType<{ - [DispatchNodeResponseKeyEnum.interactive]?: LoopInteractive; [NodeOutputKeyEnum.loopArray]: Array; }>; @@ -133,6 +129,9 @@ export const dispatchLoop = async (props: Props): Promise => { } return { + data: { + [NodeOutputKeyEnum.loopArray]: outputValueArr + }, [DispatchNodeResponseKeyEnum.interactive]: interactiveResponse ? { type: 'loopInteractive', @@ -157,7 +156,6 @@ export const dispatchLoop = async (props: Props): Promise => { moduleName: name } ], - [NodeOutputKeyEnum.loopArray]: outputValueArr, [DispatchNodeResponseKeyEnum.newVariables]: newVariables }; }; diff --git a/packages/service/core/workflow/dispatch/loop/runLoopStart.ts b/packages/service/core/workflow/dispatch/loop/runLoopStart.ts index 3ae91e9db..331feffc6 100644 --- a/packages/service/core/workflow/dispatch/loop/runLoopStart.ts +++ b/packages/service/core/workflow/dispatch/loop/runLoopStart.ts @@ -18,10 +18,12 @@ type Response = DispatchNodeResultType<{ export const dispatchLoopStart = async (props: Props): Promise => { const { params } = props; return { + data: { + [NodeOutputKeyEnum.loopStartInput]: params.loopStartInput, + [NodeOutputKeyEnum.loopStartIndex]: params.loopStartIndex + }, [DispatchNodeResponseKeyEnum.nodeResponse]: { loopInputValue: params.loopStartInput - }, - [NodeOutputKeyEnum.loopStartInput]: params.loopStartInput, - [NodeOutputKeyEnum.loopStartIndex]: params.loopStartIndex + } }; }; diff --git a/packages/service/core/workflow/dispatch/plugin/run.ts b/packages/service/core/workflow/dispatch/plugin/run.ts index 2f47e1c0f..e9d490412 100644 --- a/packages/service/core/workflow/dispatch/plugin/run.ts +++ b/packages/service/core/workflow/dispatch/plugin/run.ts @@ -13,19 +13,29 @@ import { type DispatchNodeResultType } from '@fastgpt/global/core/workflow/runti import { authPluginByTmbId } from '../../../../support/permission/app/auth'; import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; import { computedPluginUsage } from '../../../app/plugin/utils'; -import { filterSystemVariables } from '../utils'; +import { filterSystemVariables, getNodeErrResponse } from '../utils'; import { getPluginRunUserQuery } from '@fastgpt/global/core/workflow/utils'; import type { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; +import type { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { getChildAppRuntimeById, splitCombinePluginId } from '../../../app/plugin/controller'; import { dispatchWorkFlow } from '../index'; import { getUserChatInfoAndAuthTeamPoints } from '../../../../support/permission/auth/team'; -import { dispatchRunTool } from './runTool'; +import { dispatchRunTool } from '../child/runTool'; +import type { PluginRuntimeType } from '@fastgpt/global/core/app/plugin/type'; type RunPluginProps = ModuleDispatchProps<{ [NodeInputKeyEnum.forbidStream]?: boolean; [key: string]: any; }>; -type RunPluginResponse = DispatchNodeResultType<{}>; +type RunPluginResponse = DispatchNodeResultType< + { + [key: string]: any; + }, + { + [NodeOutputKeyEnum.errorText]?: string; + } +>; + export const dispatchRunPlugin = async (props: RunPluginProps): Promise => { const { node: { pluginId, version }, @@ -34,142 +44,145 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise node.flowNodeType === FlowNodeTypeEnum.pluginOutput) - ?.inputs.reduce>((acc, cur) => { - acc[cur.key] = cur.isToolOutput === false ? false : true; - return acc; - }, {}) ?? {}; - const runtimeNodes = storeNodes2RuntimeNodes( - plugin.nodes, - getWorkflowEntryNodeIds(plugin.nodes) - ).map((node) => { - // Update plugin input value - if (node.flowNodeType === FlowNodeTypeEnum.pluginInput) { + const outputFilterMap = + plugin.nodes + .find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginOutput) + ?.inputs.reduce>((acc, cur) => { + acc[cur.key] = cur.isToolOutput === false ? false : true; + return acc; + }, {}) ?? {}; + const runtimeNodes = storeNodes2RuntimeNodes( + plugin.nodes, + getWorkflowEntryNodeIds(plugin.nodes) + ).map((node) => { + // Update plugin input value + if (node.flowNodeType === FlowNodeTypeEnum.pluginInput) { + return { + ...node, + showStatus: false, + inputs: node.inputs.map((input) => ({ + ...input, + value: data[input.key] ?? input.value + })) + }; + } return { ...node, - showStatus: false, - inputs: node.inputs.map((input) => ({ - ...input, - value: data[input.key] ?? input.value - })) + showStatus: false }; - } - return { - ...node, - showStatus: false - }; - }); - - const { externalProvider } = await getUserChatInfoAndAuthTeamPoints(runningAppInfo.tmbId); - const runtimeVariables = { - ...filterSystemVariables(props.variables), - appId: String(plugin.id), - ...(externalProvider ? externalProvider.externalWorkflowVariables : {}) - }; - const { flowResponses, flowUsages, assistantResponses, runTimes, system_memories } = - await dispatchWorkFlow({ - ...props, - // Rewrite stream mode - ...(system_forbid_stream - ? { - stream: false, - workflowStreamResponse: undefined - } - : {}), - runningAppInfo: { - id: String(plugin.id), - // 如果系统插件有 teamId 和 tmbId,则使用系统插件的 teamId 和 tmbId(管理员指定了插件作为系统插件) - teamId: plugin.teamId || runningAppInfo.teamId, - tmbId: plugin.tmbId || runningAppInfo.tmbId, - isChildApp: true - }, - variables: runtimeVariables, - query: getPluginRunUserQuery({ - pluginInputs: getPluginInputsFromStoreNodes(plugin.nodes), - variables: runtimeVariables, - files - }).value, - chatConfig: {}, - runtimeNodes, - runtimeEdges: storeEdges2RuntimeEdges(plugin.edges) }); - const output = flowResponses.find((item) => item.moduleType === FlowNodeTypeEnum.pluginOutput); - if (output) { - output.moduleLogo = plugin.avatar; - } - const usagePoints = await computedPluginUsage({ - plugin, - childrenUsage: flowUsages, - error: !!output?.pluginOutput?.error - }); - return { - // 嵌套运行时,如果 childApp stream=false,实际上不会有任何内容输出给用户,所以不需要存储 - assistantResponses: system_forbid_stream ? [] : assistantResponses, - system_memories, - // responseData, // debug - [DispatchNodeResponseKeyEnum.runTimes]: runTimes, - [DispatchNodeResponseKeyEnum.nodeResponse]: { - moduleLogo: plugin.avatar, - totalPoints: usagePoints, - pluginOutput: output?.pluginOutput, - pluginDetail: pluginData?.permission?.hasWritePer // Not system plugin - ? flowResponses.filter((item) => { - const filterArr = [FlowNodeTypeEnum.pluginOutput]; - return !filterArr.includes(item.moduleType as any); - }) - : undefined - }, - [DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [ - { - moduleName: plugin.name, - totalPoints: usagePoints - } - ], - [DispatchNodeResponseKeyEnum.toolResponses]: output?.pluginOutput - ? Object.keys(output.pluginOutput) - .filter((key) => outputFilterMap[key]) - .reduce>((acc, key) => { - acc[key] = output.pluginOutput![key]; - return acc; - }, {}) - : null, - ...(output ? output.pluginOutput : {}) - }; + const { externalProvider } = await getUserChatInfoAndAuthTeamPoints(runningAppInfo.tmbId); + const runtimeVariables = { + ...filterSystemVariables(props.variables), + appId: String(plugin.id), + ...(externalProvider ? externalProvider.externalWorkflowVariables : {}) + }; + const { flowResponses, flowUsages, assistantResponses, runTimes, system_memories } = + await dispatchWorkFlow({ + ...props, + // Rewrite stream mode + ...(system_forbid_stream + ? { + stream: false, + workflowStreamResponse: undefined + } + : {}), + runningAppInfo: { + id: String(plugin.id), + // 如果系统插件有 teamId 和 tmbId,则使用系统插件的 teamId 和 tmbId(管理员指定了插件作为系统插件) + teamId: plugin.teamId || runningAppInfo.teamId, + tmbId: plugin.tmbId || runningAppInfo.tmbId, + isChildApp: true + }, + variables: runtimeVariables, + query: getPluginRunUserQuery({ + pluginInputs: getPluginInputsFromStoreNodes(plugin.nodes), + variables: runtimeVariables, + files + }).value, + chatConfig: {}, + runtimeNodes, + runtimeEdges: storeEdges2RuntimeEdges(plugin.edges) + }); + const output = flowResponses.find((item) => item.moduleType === FlowNodeTypeEnum.pluginOutput); + + const usagePoints = await computedPluginUsage({ + plugin, + childrenUsage: flowUsages, + error: !!output?.pluginOutput?.error + }); + return { + data: output ? output.pluginOutput : {}, + // 嵌套运行时,如果 childApp stream=false,实际上不会有任何内容输出给用户,所以不需要存储 + assistantResponses: system_forbid_stream ? [] : assistantResponses, + system_memories, + // responseData, // debug + [DispatchNodeResponseKeyEnum.runTimes]: runTimes, + [DispatchNodeResponseKeyEnum.nodeResponse]: { + moduleLogo: plugin.avatar, + totalPoints: usagePoints, + pluginOutput: output?.pluginOutput, + pluginDetail: pluginData?.permission?.hasWritePer // Not system plugin + ? flowResponses.filter((item) => { + const filterArr = [FlowNodeTypeEnum.pluginOutput]; + return !filterArr.includes(item.moduleType as any); + }) + : undefined + }, + [DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [ + { + moduleName: plugin.name, + totalPoints: usagePoints + } + ], + [DispatchNodeResponseKeyEnum.toolResponses]: output?.pluginOutput + ? Object.keys(output.pluginOutput) + .filter((key) => outputFilterMap[key]) + .reduce>((acc, key) => { + acc[key] = output.pluginOutput![key]; + return acc; + }, {}) + : null + }; + } catch (error) { + return getNodeErrResponse({ error, customNodeResponse: { moduleLogo: plugin?.avatar } }); + } }; diff --git a/packages/service/core/workflow/dispatch/plugin/runApp.ts b/packages/service/core/workflow/dispatch/plugin/runApp.ts deleted file mode 100644 index 7caf8fcf9..000000000 --- a/packages/service/core/workflow/dispatch/plugin/runApp.ts +++ /dev/null @@ -1,203 +0,0 @@ -import type { ChatItemType } from '@fastgpt/global/core/chat/type.d'; -import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type'; -import { dispatchWorkFlow } from '../index'; -import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; -import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; -import { - getWorkflowEntryNodeIds, - storeEdges2RuntimeEdges, - rewriteNodeOutputByHistories, - storeNodes2RuntimeNodes, - textAdaptGptResponse -} from '@fastgpt/global/core/workflow/runtime/utils'; -import type { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; -import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants'; -import { filterSystemVariables, getHistories } from '../utils'; -import { chatValue2RuntimePrompt, runtimePrompt2ChatsValue } from '@fastgpt/global/core/chat/adapt'; -import { type DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type'; -import { authAppByTmbId } from '../../../../support/permission/app/auth'; -import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; -import { getAppVersionById } from '../../../app/version/controller'; -import { parseUrlToFileType } from '@fastgpt/global/common/file/tools'; -import { type ChildrenInteractive } from '@fastgpt/global/core/workflow/template/system/interactive/type'; -import { getUserChatInfoAndAuthTeamPoints } from '../../../../support/permission/auth/team'; - -type Props = ModuleDispatchProps<{ - [NodeInputKeyEnum.userChatInput]: string; - [NodeInputKeyEnum.history]?: ChatItemType[] | number; - [NodeInputKeyEnum.fileUrlList]?: string[]; - [NodeInputKeyEnum.forbidStream]?: boolean; - [NodeInputKeyEnum.fileUrlList]?: string[]; -}>; -type Response = DispatchNodeResultType<{ - [DispatchNodeResponseKeyEnum.interactive]?: ChildrenInteractive; - [NodeOutputKeyEnum.answerText]: string; - [NodeOutputKeyEnum.history]: ChatItemType[]; -}>; - -export const dispatchRunAppNode = async (props: Props): Promise => { - const { - runningAppInfo, - histories, - query, - lastInteractive, - node: { pluginId: appId, version }, - workflowStreamResponse, - params, - variables - } = props; - - const { - system_forbid_stream = false, - userChatInput, - history, - fileUrlList, - ...childrenAppVariables - } = params; - const { files } = chatValue2RuntimePrompt(query); - - const userInputFiles = (() => { - if (fileUrlList) { - return fileUrlList.map((url) => parseUrlToFileType(url)).filter(Boolean); - } - // Adapt version 4.8.13 upgrade - return files; - })(); - - if (!userChatInput && !userInputFiles) { - return Promise.reject('Input is empty'); - } - if (!appId) { - return Promise.reject('pluginId is empty'); - } - - // Auth the app by tmbId(Not the user, but the workflow user) - const { app: appData } = await authAppByTmbId({ - appId: appId, - tmbId: runningAppInfo.tmbId, - per: ReadPermissionVal - }); - const { nodes, edges, chatConfig } = await getAppVersionById({ - appId, - versionId: version, - app: appData - }); - - const childStreamResponse = system_forbid_stream ? false : props.stream; - // Auto line - if (childStreamResponse) { - workflowStreamResponse?.({ - event: SseResponseEventEnum.answer, - data: textAdaptGptResponse({ - text: '\n' - }) - }); - } - - const chatHistories = getHistories(history, histories); - - // Rewrite children app variables - const systemVariables = filterSystemVariables(variables); - const { externalProvider } = await getUserChatInfoAndAuthTeamPoints(appData.tmbId); - const childrenRunVariables = { - ...systemVariables, - ...childrenAppVariables, - histories: chatHistories, - appId: String(appData._id), - ...(externalProvider ? externalProvider.externalWorkflowVariables : {}) - }; - - const childrenInteractive = - lastInteractive?.type === 'childrenInteractive' - ? lastInteractive.params.childrenResponse - : undefined; - const runtimeNodes = rewriteNodeOutputByHistories( - storeNodes2RuntimeNodes( - nodes, - getWorkflowEntryNodeIds(nodes, childrenInteractive || undefined) - ), - childrenInteractive - ); - - const runtimeEdges = storeEdges2RuntimeEdges(edges, childrenInteractive); - const theQuery = childrenInteractive - ? query - : runtimePrompt2ChatsValue({ files: userInputFiles, text: userChatInput }); - - const { - flowResponses, - flowUsages, - assistantResponses, - runTimes, - workflowInteractiveResponse, - system_memories - } = await dispatchWorkFlow({ - ...props, - lastInteractive: childrenInteractive, - // Rewrite stream mode - ...(system_forbid_stream - ? { - stream: false, - workflowStreamResponse: undefined - } - : {}), - runningAppInfo: { - id: String(appData._id), - teamId: String(appData.teamId), - tmbId: String(appData.tmbId), - isChildApp: true - }, - runtimeNodes, - runtimeEdges, - histories: chatHistories, - variables: childrenRunVariables, - query: theQuery, - chatConfig - }); - - const completeMessages = chatHistories.concat([ - { - obj: ChatRoleEnum.Human, - value: query - }, - { - obj: ChatRoleEnum.AI, - value: assistantResponses - } - ]); - - const { text } = chatValue2RuntimePrompt(assistantResponses); - - const usagePoints = flowUsages.reduce((sum, item) => sum + (item.totalPoints || 0), 0); - - return { - system_memories, - [DispatchNodeResponseKeyEnum.interactive]: workflowInteractiveResponse - ? { - type: 'childrenInteractive', - params: { - childrenResponse: workflowInteractiveResponse - } - } - : undefined, - assistantResponses: system_forbid_stream ? [] : assistantResponses, - [DispatchNodeResponseKeyEnum.runTimes]: runTimes, - [DispatchNodeResponseKeyEnum.nodeResponse]: { - moduleLogo: appData.avatar, - totalPoints: usagePoints, - query: userChatInput, - textOutput: text, - pluginDetail: appData.permission.hasWritePer ? flowResponses : undefined, - mergeSignId: props.node.nodeId - }, - [DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [ - { - moduleName: appData.name, - totalPoints: usagePoints - } - ], - [DispatchNodeResponseKeyEnum.toolResponses]: text, - answerText: text, - history: completeMessages - }; -}; diff --git a/packages/service/core/workflow/dispatch/plugin/runInput.ts b/packages/service/core/workflow/dispatch/plugin/runInput.ts index dde187eba..530ebbfbe 100644 --- a/packages/service/core/workflow/dispatch/plugin/runInput.ts +++ b/packages/service/core/workflow/dispatch/plugin/runInput.ts @@ -2,13 +2,22 @@ import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt'; import { ChatFileTypeEnum } from '@fastgpt/global/core/chat/constants'; import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants'; -import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type'; +import type { + DispatchNodeResultType, + ModuleDispatchProps +} from '@fastgpt/global/core/workflow/runtime/type'; export type PluginInputProps = ModuleDispatchProps<{ [key: string]: any; }>; +export type PluginInputResponse = DispatchNodeResultType<{ + [NodeOutputKeyEnum.userFiles]?: string[]; + [key: string]: any; +}>; -export const dispatchPluginInput = (props: PluginInputProps) => { +export const dispatchPluginInput = async ( + props: PluginInputProps +): Promise => { const { params, query } = props; const { files } = chatValue2RuntimePrompt(query); @@ -33,12 +42,14 @@ export const dispatchPluginInput = (props: PluginInputProps) => { } return { - ...params, - [DispatchNodeResponseKeyEnum.nodeResponse]: {}, - [NodeOutputKeyEnum.userFiles]: files - .map((item) => { - return item?.url ?? ''; - }) - .filter(Boolean) + data: { + ...params, + [NodeOutputKeyEnum.userFiles]: files + .map((item) => { + return item?.url ?? ''; + }) + .filter(Boolean) + }, + [DispatchNodeResponseKeyEnum.nodeResponse]: {} }; }; diff --git a/packages/service/core/workflow/dispatch/tools/answer.ts b/packages/service/core/workflow/dispatch/tools/answer.ts index f8a20f43e..800665f13 100644 --- a/packages/service/core/workflow/dispatch/tools/answer.ts +++ b/packages/service/core/workflow/dispatch/tools/answer.ts @@ -30,7 +30,9 @@ export const dispatchAnswer = (props: Record): AnswerResponse => { }); return { - [NodeOutputKeyEnum.answerText]: responseText, + data: { + [NodeOutputKeyEnum.answerText]: responseText + }, [DispatchNodeResponseKeyEnum.nodeResponse]: { textOutput: formatText } diff --git a/packages/service/core/workflow/dispatch/code/run.ts b/packages/service/core/workflow/dispatch/tools/codeSandbox.ts similarity index 57% rename from packages/service/core/workflow/dispatch/code/run.ts rename to packages/service/core/workflow/dispatch/tools/codeSandbox.ts index 9fe554684..4fb9b9294 100644 --- a/packages/service/core/workflow/dispatch/code/run.ts +++ b/packages/service/core/workflow/dispatch/tools/codeSandbox.ts @@ -2,20 +2,26 @@ import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/ import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { type DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type'; import axios from 'axios'; -import { formatHttpError } from '../utils'; import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { SandboxCodeTypeEnum } from '@fastgpt/global/core/workflow/template/system/sandbox/constants'; +import { getErrText } from '@fastgpt/global/common/error/utils'; +import { getNodeErrResponse } from '../utils'; type RunCodeType = ModuleDispatchProps<{ [NodeInputKeyEnum.codeType]: string; [NodeInputKeyEnum.code]: string; [NodeInputKeyEnum.addInputParam]: Record; }>; -type RunCodeResponse = DispatchNodeResultType<{ - [NodeOutputKeyEnum.error]?: any; - [NodeOutputKeyEnum.rawResponse]?: Record; - [key: string]: any; -}>; +type RunCodeResponse = DispatchNodeResultType< + { + [NodeOutputKeyEnum.error]?: any; // @deprecated + [NodeOutputKeyEnum.rawResponse]?: Record; + [key: string]: any; + }, + { + [NodeOutputKeyEnum.error]: string; + } +>; function getURL(codeType: string): string { if (codeType == SandboxCodeTypeEnum.py) { @@ -25,14 +31,21 @@ function getURL(codeType: string): string { } } -export const dispatchRunCode = async (props: RunCodeType): Promise => { +export const dispatchCodeSandbox = async (props: RunCodeType): Promise => { const { + node: { catchError }, params: { codeType, code, [NodeInputKeyEnum.addInputParam]: customVariables } } = props; if (!process.env.SANDBOX_URL) { return { - [NodeOutputKeyEnum.error]: 'Can not find SANDBOX_URL in env' + error: { + [NodeOutputKeyEnum.error]: 'Can not find SANDBOX_URL in env' + }, + [DispatchNodeResponseKeyEnum.nodeResponse]: { + errorText: 'Can not find SANDBOX_URL in env', + customInputs: customVariables + } }; } @@ -51,24 +64,43 @@ export const dispatchRunCode = async (props: RunCodeType): Promise; -type HttpResponse = DispatchNodeResultType<{ - [NodeOutputKeyEnum.error]?: object; - [key: string]: any; -}>; +type HttpResponse = DispatchNodeResultType< + { + [key: string]: any; + }, + { + [NodeOutputKeyEnum.error]?: string; + } +>; const UNDEFINED_SIGN = 'UNDEFINED_SIGN'; @@ -349,7 +353,10 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise 0 ? params : undefined, @@ -358,21 +365,36 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise 0 ? results : rawResponse, - [NodeOutputKeyEnum.httpRawResponse]: rawResponse + Object.keys(results).length > 0 ? results : rawResponse }; } catch (error) { addLog.error('Http request error', error); + // @adapt + if (node.catchError === undefined) { + return { + data: { + [NodeOutputKeyEnum.error]: getErrText(error) + }, + [DispatchNodeResponseKeyEnum.nodeResponse]: { + params: Object.keys(params).length > 0 ? params : undefined, + body: Object.keys(formattedRequestBody).length > 0 ? formattedRequestBody : undefined, + headers: Object.keys(publicHeaders).length > 0 ? publicHeaders : undefined, + httpResult: { error: formatHttpError(error) } + } + }; + } + return { - [NodeOutputKeyEnum.error]: formatHttpError(error), + error: { + [NodeOutputKeyEnum.error]: getErrText(error) + }, [DispatchNodeResponseKeyEnum.nodeResponse]: { params: Object.keys(params).length > 0 ? params : undefined, body: Object.keys(formattedRequestBody).length > 0 ? formattedRequestBody : undefined, headers: Object.keys(publicHeaders).length > 0 ? publicHeaders : undefined, httpResult: { error: formatHttpError(error) } - }, - [NodeOutputKeyEnum.httpRawResponse]: getErrText(error) + } }; } }; diff --git a/packages/service/core/workflow/dispatch/tools/queryExternsion.ts b/packages/service/core/workflow/dispatch/tools/queryExternsion.ts index 24c64fdaa..afe87d6e5 100644 --- a/packages/service/core/workflow/dispatch/tools/queryExternsion.ts +++ b/packages/service/core/workflow/dispatch/tools/queryExternsion.ts @@ -59,6 +59,9 @@ export const dispatchQueryExtension = async ({ }); return { + data: { + [NodeOutputKeyEnum.text]: JSON.stringify(filterSameQueries) + }, [DispatchNodeResponseKeyEnum.nodeResponse]: { totalPoints, model: modelName, @@ -75,7 +78,6 @@ export const dispatchQueryExtension = async ({ inputTokens, outputTokens } - ], - [NodeOutputKeyEnum.text]: JSON.stringify(filterSameQueries) + ] }; }; diff --git a/packages/service/core/workflow/dispatch/tools/readFiles.ts b/packages/service/core/workflow/dispatch/tools/readFiles.ts index a79405040..eee033233 100644 --- a/packages/service/core/workflow/dispatch/tools/readFiles.ts +++ b/packages/service/core/workflow/dispatch/tools/readFiles.ts @@ -14,6 +14,7 @@ import { parseFileExtensionFromUrl } from '@fastgpt/global/common/string/tools'; import { addLog } from '../../../../common/system/log'; import { addRawTextBuffer, getRawTextBuffer } from '../../../../common/buffer/rawText/controller'; import { addMinutes } from 'date-fns'; +import { getNodeErrResponse } from '../utils'; type Props = ModuleDispatchProps<{ [NodeInputKeyEnum.fileUrlList]: string[]; @@ -58,31 +59,37 @@ export const dispatchReadFiles = async (props: Props): Promise => { // Get files from histories const filesFromHistories = version !== '489' ? [] : getHistoryFileLinks(histories); - const { text, readFilesResult } = await getFileContentFromLinks({ - // Concat fileUrlList and filesFromHistories; remove not supported files - urls: [...fileUrlList, ...filesFromHistories], - requestOrigin, - maxFiles, - teamId, - tmbId, - customPdfParse - }); + try { + const { text, readFilesResult } = await getFileContentFromLinks({ + // Concat fileUrlList and filesFromHistories; remove not supported files + urls: [...fileUrlList, ...filesFromHistories], + requestOrigin, + maxFiles, + teamId, + tmbId, + customPdfParse + }); - return { - [NodeOutputKeyEnum.text]: text, - [DispatchNodeResponseKeyEnum.nodeResponse]: { - readFiles: readFilesResult.map((item) => ({ - name: item?.filename || '', - url: item?.url || '' - })), - readFilesResult: readFilesResult - .map((item) => item?.nodeResponsePreviewText ?? '') - .join('\n******\n') - }, - [DispatchNodeResponseKeyEnum.toolResponses]: { - fileContent: text - } - }; + return { + data: { + [NodeOutputKeyEnum.text]: text + }, + [DispatchNodeResponseKeyEnum.nodeResponse]: { + readFiles: readFilesResult.map((item) => ({ + name: item?.filename || '', + url: item?.url || '' + })), + readFilesResult: readFilesResult + .map((item) => item?.nodeResponsePreviewText ?? '') + .join('\n******\n') + }, + [DispatchNodeResponseKeyEnum.toolResponses]: { + fileContent: text + } + }; + } catch (error) { + return getNodeErrResponse({ error }); + } }; export const getHistoryFileLinks = (histories: ChatItemType[]) => { diff --git a/packages/service/core/workflow/dispatch/tools/runIfElse.ts b/packages/service/core/workflow/dispatch/tools/runIfElse.ts index 28b381a4f..8cc66628a 100644 --- a/packages/service/core/workflow/dispatch/tools/runIfElse.ts +++ b/packages/service/core/workflow/dispatch/tools/runIfElse.ts @@ -157,7 +157,9 @@ export const dispatchIfElse = async (props: Props): Promise => { }); return { - [NodeOutputKeyEnum.ifElseResult]: res, + data: { + [NodeOutputKeyEnum.ifElseResult]: res + }, [DispatchNodeResponseKeyEnum.nodeResponse]: { totalPoints: 0, ifElseResult: res diff --git a/packages/service/core/workflow/dispatch/tools/runLaf.ts b/packages/service/core/workflow/dispatch/tools/runLaf.ts index 2a73ea9f2..7cf061a9b 100644 --- a/packages/service/core/workflow/dispatch/tools/runLaf.ts +++ b/packages/service/core/workflow/dispatch/tools/runLaf.ts @@ -6,16 +6,21 @@ import { valueTypeFormat } from '@fastgpt/global/core/workflow/runtime/utils'; import { SERVICE_LOCAL_HOST } from '../../../../common/system/tools'; import { addLog } from '../../../../common/system/log'; import { type DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type'; +import { getErrText } from '@fastgpt/global/common/error/utils'; type LafRequestProps = ModuleDispatchProps<{ [NodeInputKeyEnum.httpReqUrl]: string; [NodeInputKeyEnum.addInputParam]: Record; [key: string]: any; }>; -type LafResponse = DispatchNodeResultType<{ - [NodeOutputKeyEnum.failed]?: boolean; - [key: string]: any; -}>; +type LafResponse = DispatchNodeResultType< + { + [key: string]: any; + }, + { + [NodeOutputKeyEnum.errorText]?: string; + } +>; const UNDEFINED_SIGN = 'UNDEFINED_SIGN'; @@ -78,20 +83,24 @@ export const dispatchLafRequest = async (props: LafRequestProps): Promise 0 ? requestBody : undefined, httpResult: rawResponse }, - [DispatchNodeResponseKeyEnum.toolResponses]: rawResponse, - [NodeOutputKeyEnum.httpRawResponse]: rawResponse, - ...results + [DispatchNodeResponseKeyEnum.toolResponses]: rawResponse }; } catch (error) { addLog.error('Http request error', error); return { - [NodeOutputKeyEnum.failed]: true, + error: { + [NodeOutputKeyEnum.errorText]: getErrText(error) + }, [DispatchNodeResponseKeyEnum.nodeResponse]: { totalPoints: 0, body: Object.keys(requestBody).length > 0 ? requestBody : undefined, diff --git a/packages/service/core/workflow/dispatch/tools/textEditor.ts b/packages/service/core/workflow/dispatch/tools/textEditor.ts index ec54bdbaf..a80d06190 100644 --- a/packages/service/core/workflow/dispatch/tools/textEditor.ts +++ b/packages/service/core/workflow/dispatch/tools/textEditor.ts @@ -40,7 +40,9 @@ export const dispatchTextEditor = (props: Record): Response => { }); return { - [NodeOutputKeyEnum.text]: textResult, + data: { + [NodeOutputKeyEnum.text]: textResult + }, [DispatchNodeResponseKeyEnum.nodeResponse]: { textOutput: textResult } diff --git a/packages/service/core/workflow/dispatch/utils.ts b/packages/service/core/workflow/dispatch/utils.ts index ad0558392..aefed0b5b 100644 --- a/packages/service/core/workflow/dispatch/utils.ts +++ b/packages/service/core/workflow/dispatch/utils.ts @@ -9,7 +9,10 @@ import { } from '@fastgpt/global/core/workflow/runtime/type'; import { responseWrite } from '../../../common/response'; import { type NextApiResponse } from 'next'; -import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; +import { + DispatchNodeResponseKeyEnum, + SseResponseEventEnum +} from '@fastgpt/global/core/workflow/runtime/constants'; import { getNanoid } from '@fastgpt/global/common/string/tools'; import { type SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type'; import { getMCPToolRuntimeNode } from '@fastgpt/global/core/app/mcpTools/utils'; @@ -206,3 +209,30 @@ export const rewriteRuntimeWorkFlow = ( } } }; + +export const getNodeErrResponse = ({ + error, + customErr, + customNodeResponse +}: { + error: any; + customErr?: Record; + customNodeResponse?: Record; +}) => { + const errorText = getErrText(error); + + return { + error: { + [NodeOutputKeyEnum.errorText]: errorText, + ...(typeof customErr === 'object' ? customErr : {}) + }, + [DispatchNodeResponseKeyEnum.nodeResponse]: { + errorText, + ...(typeof customNodeResponse === 'object' ? customNodeResponse : {}) + }, + [DispatchNodeResponseKeyEnum.toolResponses]: { + error: errorText, + ...(typeof customErr === 'object' ? customErr : {}) + } + }; +}; diff --git a/packages/service/package.json b/packages/service/package.json index 824279873..1f1d35cfa 100644 --- a/packages/service/package.json +++ b/packages/service/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "type": "module", "dependencies": { - "@fastgpt-sdk/plugin": "^0.1.1", + "@fastgpt-sdk/plugin": "^0.1.2", "@fastgpt/global": "workspace:*", "@modelcontextprotocol/sdk": "^1.12.1", "@node-rs/jieba": "2.0.1", diff --git a/packages/service/support/permission/evaluation/auth.ts b/packages/service/support/permission/evaluation/auth.ts new file mode 100644 index 000000000..1622ef31e --- /dev/null +++ b/packages/service/support/permission/evaluation/auth.ts @@ -0,0 +1,64 @@ +import { parseHeaderCert } from '../controller'; +import { authAppByTmbId } from '../app/auth'; +import { + ManagePermissionVal, + ReadPermissionVal +} from '@fastgpt/global/support/permission/constant'; +import type { EvaluationSchemaType } from '@fastgpt/global/core/app/evaluation/type'; +import type { AuthModeType } from '../type'; +import { MongoEvaluation } from '../../../core/app/evaluation/evalSchema'; + +export const authEval = async ({ + evalId, + per = ReadPermissionVal, + ...props +}: AuthModeType & { + evalId: string; +}): Promise<{ + evaluation: EvaluationSchemaType; + tmbId: string; + teamId: string; +}> => { + const { teamId, tmbId, isRoot } = await parseHeaderCert(props); + + const evaluation = await MongoEvaluation.findById(evalId, 'tmbId').lean(); + if (!evaluation) { + return Promise.reject('Evaluation not found'); + } + + if (String(evaluation.tmbId) === tmbId) { + return { + teamId, + tmbId, + evaluation + }; + } + + // App read per + if (per === ReadPermissionVal) { + await authAppByTmbId({ + tmbId, + appId: evaluation.appId, + per: ReadPermissionVal, + isRoot + }); + return { + teamId, + tmbId, + evaluation + }; + } + + // Write per + await authAppByTmbId({ + tmbId, + appId: evaluation.appId, + per: ManagePermissionVal, + isRoot + }); + return { + teamId, + tmbId, + evaluation + }; +}; diff --git a/packages/service/support/permission/teamLimit.ts b/packages/service/support/permission/teamLimit.ts index 3c1a78ab2..ba172d0f3 100644 --- a/packages/service/support/permission/teamLimit.ts +++ b/packages/service/support/permission/teamLimit.ts @@ -113,10 +113,6 @@ export const checkTeamDatasetLimit = async (teamId: string) => { return Promise.reject(SystemErrEnum.licenseDatasetAmountLimit); } } - // Open source check - if (!global.feConfigs.isPlus && datasetCount >= 30) { - return Promise.reject(SystemErrEnum.communityVersionNumLimit); - } }; export const checkTeamDatasetSyncPermission = async (teamId: string) => { diff --git a/packages/service/support/wallet/usage/controller.ts b/packages/service/support/wallet/usage/controller.ts index 8ff311650..c8eed35cd 100644 --- a/packages/service/support/wallet/usage/controller.ts +++ b/packages/service/support/wallet/usage/controller.ts @@ -235,3 +235,46 @@ export const pushLLMTrainingUsage = async ({ return { totalPoints }; }; + +export const createEvaluationUsage = async ({ + teamId, + tmbId, + appName, + model, + session +}: { + teamId: string; + tmbId: string; + appName: string; + model: string; + session?: ClientSession; +}) => { + const [{ _id: usageId }] = await MongoUsage.create( + [ + { + teamId, + tmbId, + appName, + source: UsageSourceEnum.evaluation, + totalPoints: 0, + list: [ + { + moduleName: i18nT('account_usage:generate_answer'), + amount: 0, + count: 0 + }, + { + moduleName: i18nT('account_usage:answer_accuracy'), + amount: 0, + inputTokens: 0, + outputTokens: 0, + model + } + ] + } + ], + { session, ordered: true } + ); + + return { usageId }; +}; diff --git a/packages/service/support/wallet/usage/type.d.ts b/packages/service/support/wallet/usage/type.d.ts index 930d6723e..947b1041a 100644 --- a/packages/service/support/wallet/usage/type.d.ts +++ b/packages/service/support/wallet/usage/type.d.ts @@ -1,9 +1,13 @@ export type ConcatBillQueueItemType = { - billId: string; + billId: string; // usageId listIndex?: number; totalPoints: number; - inputTokens: number; - outputTokens: number; + + // Model usage + inputTokens?: number; + outputTokens?: number; + // Times + count?: number; }; declare global { diff --git a/packages/service/type/env.d.ts b/packages/service/type/env.d.ts new file mode 100644 index 000000000..aff70d4ee --- /dev/null +++ b/packages/service/type/env.d.ts @@ -0,0 +1,53 @@ +declare global { + namespace NodeJS { + interface ProcessEnv { + LOG_DEPTH: string; + DB_MAX_LINK: string; + FILE_TOKEN_KEY: string; + AES256_SECRET_KEY: string; + ROOT_KEY: string; + OPENAI_BASE_URL: string; + CHAT_API_KEY: string; + AIPROXY_API_ENDPOINT: string; + AIPROXY_API_TOKEN: string; + MULTIPLE_DATA_TO_BASE64: string; + MONGODB_URI: string; + MONGODB_LOG_URI?: string; + PG_URL: string; + OCEANBASE_URL: string; + MILVUS_ADDRESS: string; + MILVUS_TOKEN: string; + SANDBOX_URL: string; + FE_DOMAIN: string; + FILE_DOMAIN: string; + LOG_LEVEL?: string; + STORE_LOG_LEVEL?: string; + USE_IP_LIMIT?: string; + WORKFLOW_MAX_RUN_TIMES?: string; + WORKFLOW_MAX_LOOP_TIMES?: string; + CHECK_INTERNAL_IP?: string; + ALLOWED_ORIGINS?: string; + SHOW_COUPON?: string; + CONFIG_JSON_PATH?: string; + PASSWORD_LOGIN_LOCK_SECONDS?: string; + PASSWORD_EXPIRED_MONTH?: string; + MAX_LOGIN_SESSION?: string; + + // 安全配置 + // 密码登录锁定时间 + PASSWORD_LOGIN_LOCK_SECONDS?: string; + + // Signoz + SIGNOZ_BASE_URL?: string; + SIGNOZ_SERVICE_NAME?: string; + + CHAT_LOG_URL?: string; + CHAT_LOG_INTERVAL?: string; + CHAT_LOG_SOURCE_ID_PREFIX?: string; + + NEXT_PUBLIC_BASE_URL: string; + } + } +} + +export {}; diff --git a/packages/web/components/common/Icon/constants.ts b/packages/web/components/common/Icon/constants.ts index 1169958b9..2d17339a6 100644 --- a/packages/web/components/common/Icon/constants.ts +++ b/packages/web/components/common/Icon/constants.ts @@ -20,11 +20,11 @@ export const iconPaths = { 'common/addLight': () => import('./icons/common/addLight.svg'), 'common/addUser': () => import('./icons/common/addUser.svg'), 'common/administrator': () => import('./icons/common/administrator.svg'), - 'common/audit': () => import('./icons/common/audit.svg'), 'common/alipay': () => import('./icons/common/alipay.svg'), 'common/app': () => import('./icons/common/app.svg'), 'common/arrowLeft': () => import('./icons/common/arrowLeft.svg'), 'common/arrowRight': () => import('./icons/common/arrowRight.svg'), + 'common/audit': () => import('./icons/common/audit.svg'), 'common/backFill': () => import('./icons/common/backFill.svg'), 'common/backLight': () => import('./icons/common/backLight.svg'), 'common/billing': () => import('./icons/common/billing.svg'), @@ -47,6 +47,7 @@ export const iconPaths = { 'common/editor/resizer': () => import('./icons/common/editor/resizer.svg'), 'common/ellipsis': () => import('./icons/common/ellipsis.svg'), 'common/enable': () => import('./icons/common/enable.svg'), + 'common/error': () => import('./icons/common/error.svg'), 'common/errorFill': () => import('./icons/common/errorFill.svg'), 'common/file/move': () => import('./icons/common/file/move.svg'), 'common/fileNotFound': () => import('./icons/common/fileNotFound.svg'), @@ -111,9 +112,9 @@ export const iconPaths = { 'common/tickFill': () => import('./icons/common/tickFill.svg'), 'common/toolkit': () => import('./icons/common/toolkit.svg'), 'common/trash': () => import('./icons/common/trash.svg'), - 'common/upRightArrowLight': () => import('./icons/common/upRightArrowLight.svg'), 'common/uploadFileFill': () => import('./icons/common/uploadFileFill.svg'), 'common/upperRight': () => import('./icons/common/upperRight.svg'), + 'common/upRightArrowLight': () => import('./icons/common/upRightArrowLight.svg'), 'common/userInfo': () => import('./icons/common/userInfo.svg'), 'common/variable': () => import('./icons/common/variable.svg'), 'common/viewLight': () => import('./icons/common/viewLight.svg'), @@ -150,8 +151,6 @@ export const iconPaths = { 'core/app/simpleMode/tts': () => import('./icons/core/app/simpleMode/tts.svg'), 'core/app/simpleMode/variable': () => import('./icons/core/app/simpleMode/variable.svg'), 'core/app/simpleMode/whisper': () => import('./icons/core/app/simpleMode/whisper.svg'), - 'core/app/templates/TranslateRobot': () => - import('./icons/core/app/templates/TranslateRobot.svg'), 'core/app/templates/animalLife': () => import('./icons/core/app/templates/animalLife.svg'), 'core/app/templates/chinese': () => import('./icons/core/app/templates/chinese.svg'), 'core/app/templates/divination': () => import('./icons/core/app/templates/divination.svg'), @@ -161,6 +160,8 @@ export const iconPaths = { 'core/app/templates/plugin-dalle': () => import('./icons/core/app/templates/plugin-dalle.svg'), 'core/app/templates/plugin-feishu': () => import('./icons/core/app/templates/plugin-feishu.svg'), 'core/app/templates/stock': () => import('./icons/core/app/templates/stock.svg'), + 'core/app/templates/TranslateRobot': () => + import('./icons/core/app/templates/TranslateRobot.svg'), 'core/app/toolCall': () => import('./icons/core/app/toolCall.svg'), 'core/app/ttsFill': () => import('./icons/core/app/ttsFill.svg'), 'core/app/type/httpPlugin': () => import('./icons/core/app/type/httpPlugin.svg'), @@ -179,7 +180,6 @@ export const iconPaths = { 'core/app/variable/input': () => import('./icons/core/app/variable/input.svg'), 'core/app/variable/select': () => import('./icons/core/app/variable/select.svg'), 'core/app/variable/textarea': () => import('./icons/core/app/variable/textarea.svg'), - 'core/chat/QGFill': () => import('./icons/core/chat/QGFill.svg'), 'core/chat/backText': () => import('./icons/core/chat/backText.svg'), 'core/chat/cancelSpeak': () => import('./icons/core/chat/cancelSpeak.svg'), 'core/chat/chatFill': () => import('./icons/core/chat/chatFill.svg'), @@ -196,6 +196,7 @@ export const iconPaths = { 'core/chat/fileSelect': () => import('./icons/core/chat/fileSelect.svg'), 'core/chat/finishSpeak': () => import('./icons/core/chat/finishSpeak.svg'), 'core/chat/imgSelect': () => import('./icons/core/chat/imgSelect.svg'), + 'core/chat/QGFill': () => import('./icons/core/chat/QGFill.svg'), 'core/chat/quoteFill': () => import('./icons/core/chat/quoteFill.svg'), 'core/chat/quoteSign': () => import('./icons/core/chat/quoteSign.svg'), 'core/chat/recordFill': () => import('./icons/core/chat/recordFill.svg'), @@ -203,6 +204,7 @@ export const iconPaths = { 'core/chat/sendLight': () => import('./icons/core/chat/sendLight.svg'), 'core/chat/setTopLight': () => import('./icons/core/chat/setTopLight.svg'), 'core/chat/sideLine': () => import('./icons/core/chat/sideLine.svg'), + 'core/chat/sidebar/logout': () => import('./icons/core/chat/sidebar/logout.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'), @@ -283,13 +285,12 @@ export const iconPaths = { 'core/workflow/redo': () => import('./icons/core/workflow/redo.svg'), 'core/workflow/revertVersion': () => import('./icons/core/workflow/revertVersion.svg'), 'core/workflow/runError': () => import('./icons/core/workflow/runError.svg'), + 'core/workflow/running': () => import('./icons/core/workflow/running.svg'), 'core/workflow/runSkip': () => import('./icons/core/workflow/runSkip.svg'), 'core/workflow/runSuccess': () => import('./icons/core/workflow/runSuccess.svg'), - 'core/workflow/running': () => import('./icons/core/workflow/running.svg'), - 'core/workflow/template/BI': () => import('./icons/core/workflow/template/BI.svg'), - 'core/workflow/template/FileRead': () => import('./icons/core/workflow/template/FileRead.svg'), 'core/workflow/template/aiChat': () => import('./icons/core/workflow/template/aiChat.svg'), 'core/workflow/template/baseChart': () => import('./icons/core/workflow/template/baseChart.svg'), + 'core/workflow/template/BI': () => import('./icons/core/workflow/template/BI.svg'), 'core/workflow/template/bing': () => import('./icons/core/workflow/template/bing.svg'), 'core/workflow/template/bocha': () => import('./icons/core/workflow/template/bocha.svg'), 'core/workflow/template/codeRun': () => import('./icons/core/workflow/template/codeRun.svg'), @@ -306,6 +307,7 @@ export const iconPaths = { 'core/workflow/template/extractJson': () => import('./icons/core/workflow/template/extractJson.svg'), 'core/workflow/template/fetchUrl': () => import('./icons/core/workflow/template/fetchUrl.svg'), + 'core/workflow/template/FileRead': () => import('./icons/core/workflow/template/FileRead.svg'), 'core/workflow/template/formInput': () => import('./icons/core/workflow/template/formInput.svg'), 'core/workflow/template/getTime': () => import('./icons/core/workflow/template/getTime.svg'), 'core/workflow/template/google': () => import('./icons/core/workflow/template/google.svg'), @@ -335,12 +337,12 @@ export const iconPaths = { 'core/workflow/template/textConcat': () => import('./icons/core/workflow/template/textConcat.svg'), 'core/workflow/template/toolCall': () => import('./icons/core/workflow/template/toolCall.svg'), - 'core/workflow/template/toolParams': () => - import('./icons/core/workflow/template/toolParams.svg'), 'core/workflow/template/toolkitActive': () => import('./icons/core/workflow/template/toolkitActive.svg'), 'core/workflow/template/toolkitInactive': () => import('./icons/core/workflow/template/toolkitInactive.svg'), + 'core/workflow/template/toolParams': () => + import('./icons/core/workflow/template/toolParams.svg'), 'core/workflow/template/userSelect': () => import('./icons/core/workflow/template/userSelect.svg'), 'core/workflow/template/variable': () => import('./icons/core/workflow/template/variable.svg'), @@ -389,6 +391,7 @@ export const iconPaths = { key: () => import('./icons/key.svg'), keyPrimary: () => import('./icons/keyPrimary.svg'), loading: () => import('./icons/loading.svg'), + mcp: () => import('./icons/mcp.svg'), menu: () => import('./icons/menu.svg'), minus: () => import('./icons/minus.svg'), 'modal/AddClb': () => import('./icons/modal/AddClb.svg'), @@ -400,10 +403,10 @@ export const iconPaths = { 'modal/selectSource': () => import('./icons/modal/selectSource.svg'), 'modal/setting': () => import('./icons/modal/setting.svg'), 'modal/teamPlans': () => import('./icons/modal/teamPlans.svg'), - 'model/BAAI': () => import('./icons/model/BAAI.svg'), 'model/alicloud': () => import('./icons/model/alicloud.svg'), 'model/aws': () => import('./icons/model/aws.svg'), 'model/azure': () => import('./icons/model/azure.svg'), + 'model/BAAI': () => import('./icons/model/BAAI.svg'), 'model/baichuan': () => import('./icons/model/baichuan.svg'), 'model/chatglm': () => import('./icons/model/chatglm.svg'), 'model/claude': () => import('./icons/model/claude.svg'), diff --git a/packages/web/components/common/Icon/icons/common/error.svg b/packages/web/components/common/Icon/icons/common/error.svg new file mode 100644 index 000000000..7c6820aee --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/error.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/core/chat/sidebar/logout.svg b/packages/web/components/common/Icon/icons/core/chat/sidebar/logout.svg new file mode 100644 index 000000000..95c9d6a87 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/chat/sidebar/logout.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/web/components/common/Icon/icons/mcp.svg b/packages/web/components/common/Icon/icons/mcp.svg new file mode 100644 index 000000000..94a9a9064 --- /dev/null +++ b/packages/web/components/common/Icon/icons/mcp.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/web/components/common/Input/SearchInput/index.tsx b/packages/web/components/common/Input/SearchInput/index.tsx index 676d564f7..16b1b8396 100644 --- a/packages/web/components/common/Input/SearchInput/index.tsx +++ b/packages/web/components/common/Input/SearchInput/index.tsx @@ -4,11 +4,18 @@ import MyIcon from '../../Icon'; const SearchInput = (props: InputProps) => { return ( - - - - - + + + ); }; diff --git a/packages/web/hooks/useCopyData.tsx b/packages/web/hooks/useCopyData.tsx index c671b151a..7e58ff3dd 100644 --- a/packages/web/hooks/useCopyData.tsx +++ b/packages/web/hooks/useCopyData.tsx @@ -34,29 +34,7 @@ export const useCopyData = () => { }); } } else { - let textArea = document.createElement('textarea'); - textArea.value = data; - // 使text area不在viewport,同时设置不可见 - textArea.style.position = 'absolute'; - // @ts-ignore - textArea.style.opacity = 0; - textArea.style.left = '-999999px'; - textArea.style.top = '-999999px'; - document.body.appendChild(textArea); - textArea.focus(); - textArea.select(); - await new Promise((res, rej) => { - document.execCommand('copy') ? res('') : rej(); - textArea.remove(); - }).then(() => { - if (title) { - toast({ - title, - status: 'success', - duration - }); - } - }); + throw new Error('Clipboard is not supported'); } } catch (error) { setCopyContent(data); diff --git a/packages/web/hooks/usePagination.tsx b/packages/web/hooks/usePagination.tsx index 7258e6386..7571aec93 100644 --- a/packages/web/hooks/usePagination.tsx +++ b/packages/web/hooks/usePagination.tsx @@ -27,7 +27,9 @@ export function usePagination( onChange, refreshDeps, scrollLoadType = 'bottom', - EmptyTip + EmptyTip, + pollingInterval, + pollingWhenHidden = false }: { pageSize?: number; params?: DataT; @@ -38,6 +40,8 @@ export function usePagination( throttleWait?: number; scrollLoadType?: 'top' | 'bottom'; EmptyTip?: React.JSX.Element; + pollingInterval?: number; + pollingWhenHidden?: boolean; } ) { const { toast } = useToast(); @@ -56,6 +60,7 @@ export function usePagination( const fetchData = useMemoizedFn( async (num: number = pageNum, ScrollContainerRef?: RefObject) => { if (noMore && num !== 1) return; + setTrue(); try { @@ -65,7 +70,6 @@ export function usePagination( ...params }); - // Check total and set setPageNum(num); res.total !== undefined && setTotal(res.total); @@ -256,6 +260,19 @@ export function usePagination( } ); + useRequest( + async () => { + if (!pollingInterval) return; + await fetchData(pageNum); + }, + { + pollingInterval, + pollingWhenHidden, + manual: false, + refreshDeps: [pollingInterval] + } + ); + return { pageNum, setPageNum, diff --git a/packages/web/hooks/useScrollPagination.tsx b/packages/web/hooks/useScrollPagination.tsx index 7399771aa..708423b2c 100644 --- a/packages/web/hooks/useScrollPagination.tsx +++ b/packages/web/hooks/useScrollPagination.tsx @@ -70,7 +70,7 @@ export function useVirtualScrollPagination< overscan }); - const loadData = useLockFn(async (init = false) => { + const loadData = useLockFn(async ({ init = false }: { init?: boolean } = {}) => { if (noMore && !init) return; const offset = init ? 0 : data.length; @@ -140,7 +140,7 @@ export function useVirtualScrollPagination< // Reload data useRequest( async () => { - loadData(true); + loadData({ init: true }); }, { manual: false, @@ -156,7 +156,7 @@ export function useVirtualScrollPagination< const { scrollTop, scrollHeight, clientHeight } = containerRef.current; if (scrollTop + clientHeight >= scrollHeight - thresholdVal) { - loadData(false); + loadData({ init: false }); } }, [scroll], @@ -190,7 +190,8 @@ export function useScrollPagination< params, EmptyTip, showErrorToast = true, - disalbed = false, + disabled = false, + ...props }: { scrollLoadType?: 'top' | 'bottom'; @@ -199,7 +200,7 @@ export function useScrollPagination< params?: Omit; EmptyTip?: React.JSX.Element; showErrorToast?: boolean; - disalbed?: boolean; + disabled?: boolean; } & Parameters[1] ) { const { t } = useTranslation(); @@ -213,8 +214,15 @@ export function useScrollPagination< const noMore = data.length >= total; const loadData = useLockFn( - async (init = false, ScrollContainerRef?: RefObject) => { + async ({ + init = false, + ScrollContainerRef + }: { + init?: boolean; + ScrollContainerRef?: RefObject; + } = {}) => { if (noMore && !init) return; + setTrue(); if (init) { @@ -236,7 +244,7 @@ export function useScrollPagination< if (scrollLoadType === 'top') { const prevHeight = ScrollContainerRef?.current?.scrollHeight || 0; const prevScrollTop = ScrollContainerRef?.current?.scrollTop || 0; - // 使用 requestAnimationFrame 来调整滚动位置 + function adjustScrollPosition() { requestAnimationFrame( ScrollContainerRef?.current @@ -251,10 +259,12 @@ export function useScrollPagination< ); } - setData((prevData) => (offset === 0 ? res.list : [...res.list, ...prevData])); + const newData = offset === 0 ? res.list : [...res.list, ...data]; + setData(newData); adjustScrollPosition(); } else { - setData((prevData) => (offset === 0 ? res.list : [...prevData, ...res.list])); + const newData = offset === 0 ? res.list : [...data, ...res.list]; + setData(newData); } } catch (error: any) { if (showErrorToast) { @@ -302,7 +312,7 @@ export function useScrollPagination< scrollTop + clientHeight >= scrollHeight - thresholdVal) || (scrollLoadType === 'top' && scrollTop < thresholdVal) ) { - loadData(false, ref); + loadData({ init: false, ScrollContainerRef: ref }); } }, [scroll], @@ -332,7 +342,7 @@ export function useScrollPagination< cursor={loadText === t('common:request_more') ? 'pointer' : 'default'} onClick={() => { if (loadText !== t('common:request_more')) return; - loadData(false); + loadData({ init: false }); }} > {loadText} @@ -347,8 +357,8 @@ export function useScrollPagination< // Reload data useRequest2( async () => { - if (disalbed) return; - loadData(true); + if (disabled) return; + loadData({ init: true }); }, { manual: false, @@ -357,7 +367,7 @@ export function useScrollPagination< ); const refreshList = useMemoizedFn(() => { - loadData(true); + loadData({ init: true }); }); return { diff --git a/packages/web/i18n/constants.ts b/packages/web/i18n/constants.ts index dd0a2a5e8..d568e4783 100644 --- a/packages/web/i18n/constants.ts +++ b/packages/web/i18n/constants.ts @@ -19,7 +19,8 @@ export const I18N_NAMESPACES = [ 'account', 'account_team', 'account_model', - 'dashboard_mcp' + 'dashboard_mcp', + 'dashboard_evaluation' ]; export const I18N_NAMESPACES_MAP = I18N_NAMESPACES.reduce( diff --git a/packages/web/i18n/en/account_model.json b/packages/web/i18n/en/account_model.json index 7e27f56fb..6b3d824df 100644 --- a/packages/web/i18n/en/account_model.json +++ b/packages/web/i18n/en/account_model.json @@ -62,6 +62,7 @@ "model_request_times": "Request times", "model_test": "Model testing", "model_tokens": "Input/Output tokens", + "model_ttfb_time": "Response time of first word", "monitoring": "Monitoring", "output": "Output", "request_at": "Request time", @@ -81,6 +82,7 @@ "timespan_label": "Time Granularity", "timespan_minute": "Minute", "total_call_volume": "Request amount", + "use_in_eval": "Use in eval", "view_chart": "Chart", "view_table": "Table", "vlm_model": "Vlm", diff --git a/packages/web/i18n/en/account_team.json b/packages/web/i18n/en/account_team.json index d2cc7e00a..5c8a62e9e 100644 --- a/packages/web/i18n/en/account_team.json +++ b/packages/web/i18n/en/account_team.json @@ -73,6 +73,7 @@ "delete_dataset": "Delete the knowledge base", "delete_dataset_collaborator": "Knowledge Base Permission Deletion", "delete_department": "Delete sub-department", + "delete_evaluation": "Delete application review data", "delete_from_org": "Move out of department", "delete_from_team": "Move out of the team", "delete_group": "Delete a group", @@ -161,6 +162,7 @@ "log_delete_dataset": "【{{name}}】Deleted 【{{datasetType}}】 named [{{datasetName}}]", "log_delete_dataset_collaborator": "【{{name}}】Updated the collaborators of 【{{appType}}】 named 【{{appName}}】 to: Organization: 【{{orgList}}】, Group: 【{{groupList}}】, Member 【{{tmbList}}】; updated the permissions to: Read permission: 【{{readPermission}}】, Write permission: 【{{writePermission}}】, Administrator permission: 【{{managePermission}}】", "log_delete_department": "{{name}} deleted department {{departmentName}}", + "log_delete_evaluation": "【{{name}}】Deleted the evaluation data of [{{appType}}] named [{{appName}}]", "log_delete_group": "{{name}} deleted group {{groupName}}", "log_details": "Details", "log_export_app_chat_log": "【{{name}}】Export a chat history called [{{appName}}] called [{{appType}}]", diff --git a/packages/web/i18n/en/account_usage.json b/packages/web/i18n/en/account_usage.json index a754ef15d..6415a45bd 100644 --- a/packages/web/i18n/en/account_usage.json +++ b/packages/web/i18n/en/account_usage.json @@ -5,12 +5,14 @@ "auto_index": "Auto index", "billing_module": "Deduction module", "confirm_export": "A total of {{total}} pieces of data were filtered out. Are you sure to export?", + "count": "Number of runs", "current_filter_conditions": "Current filter conditions", "dashboard": "Dashboard", "details": "Details", "dingtalk": "DingTalk", "duration_seconds": "Duration (seconds)", "embedding_index": "Embedding", + "evaluation": "Application Review", "every_day": "Day", "every_month": "Moon", "every_week": "weekly", diff --git a/packages/web/i18n/en/app.json b/packages/web/i18n/en/app.json index 55fc48112..07d008c3f 100644 --- a/packages/web/i18n/en/app.json +++ b/packages/web/i18n/en/app.json @@ -56,6 +56,7 @@ "cron.interval": "Run at Intervals", "dataset_search_tool_description": "Call the \"Semantic Search\" and \"Full-text Search\" capabilities to find reference content that may be related to the problem from the \"Knowledge Base\". \nPrioritize calling this tool to assist in answering user questions.", "day": "Day", + "deleted": "App deleted", "document_quote": "Document Reference", "document_quote_tip": "Usually used to accept user-uploaded document content (requires document parsing), and can also be used to reference other string data.", "document_upload": "Document Upload", @@ -83,7 +84,6 @@ "interval.4_hours": "Every 4 Hours", "interval.6_hours": "Every 6 Hours", "interval.per_hour": "Every Hour", - "intro": "A comprehensive model application orchestration system that offers out-of-the-box data processing and model invocation capabilities. It allows for rapid Dataset construction and workflow orchestration through Flow visualization, enabling complex Dataset scenarios!", "invalid_json_format": "JSON format error", "keep_the_latest": "Keep the latest", "llm_not_support_vision": "This model does not support image recognition", diff --git a/packages/web/i18n/en/chat.json b/packages/web/i18n/en/chat.json index 1f6a67e92..9e9ae2b26 100644 --- a/packages/web/i18n/en/chat.json +++ b/packages/web/i18n/en/chat.json @@ -52,6 +52,7 @@ "module_runtime_and": "Total Module Runtime", "multiple_AI_conversations": "Multiple AI Conversations", "new_input_guide_lexicon": "New Lexicon", + "no_invalid_app": "There are no available applications under your account", "no_workflow_response": "No workflow data", "not_query": "Missing query content", "not_select_file": "No file selected", diff --git a/packages/web/i18n/en/common.json b/packages/web/i18n/en/common.json index e5711c20d..ce21dbf56 100644 --- a/packages/web/i18n/en/common.json +++ b/packages/web/i18n/en/common.json @@ -77,6 +77,8 @@ "Save": "Save", "Save_and_exit": "Save and Exit", "Search": "Search", + "Select_App": "Select an application", + "Select_all": "Select all", "Setting": "Setting", "Status": "Status", "Submit": "Submit", @@ -96,6 +98,7 @@ "add_success": "Added Successfully", "all_quotes": "All quotes", "all_result": "Full Results", + "app_evaluation": "App Evaluation(Beta)", "app_not_version": "This application has not been published, please publish it first", "auth_config": "Authentication", "auth_type": "Authentication type", @@ -195,6 +198,7 @@ "compliance.chat": "The content is generated by third-party AI and cannot be guaranteed to be true and accurate. It is for reference only.", "compliance.dataset": "Please ensure that your content strictly complies with relevant laws and regulations and avoid containing any illegal or infringing content. \nPlease be careful when uploading materials that may contain sensitive information.", "confirm_choice": "Confirm Choice", + "confirm_logout": "Confirm to log out?", "confirm_move": "Move Here", "confirm_update": "confirm_update", "contact_way": "Notification Received", @@ -865,6 +869,7 @@ "link.UnValid": "Invalid Link", "llm_model_not_config": "No language model was detected", "load_failed": "load_failed", + "logout": "Sign out", "max_quote_tokens": "Quote cap", "max_quote_tokens_tips": "The maximum number of tokens in a single search, about 1 character in Chinese = 1.7 tokens, and about 1 character in English = 1 token", "mcp_server": "MCP Services", @@ -1202,6 +1207,7 @@ "system.Concat us": "Contact Us", "system.Help Document": "Help Document", "system_help_chatbot": "Help Chatbot", + "system_intro": "{{title}} is a comprehensive model application orchestration system that offers out-of-the-box data processing and model invocation capabilities. It allows for rapid Dataset construction and workflow orchestration through Flow visualization, enabling complex Dataset scenarios!", "tag_list": "Tag List", "team_tag": "Team Tag", "templateTags.Image_generation": "Image generation", diff --git a/packages/web/i18n/en/dashboard_evaluation.json b/packages/web/i18n/en/dashboard_evaluation.json new file mode 100644 index 000000000..1809bcb4d --- /dev/null +++ b/packages/web/i18n/en/dashboard_evaluation.json @@ -0,0 +1,50 @@ +{ + "Action": "operate", + "Evaluation_app": "Evaluation app", + "Evaluation_app_tip": "Supports simple application and does not contain workflows with user interaction nodes. \nPlugins are not supported yet.", + "Evaluation_file": "Evaluation documents", + "Evaluation_model": "Evaluation model", + "Executor": "Executor", + "Overall_score": "Overall Score", + "Progress": "Progress", + "Start_end_time": "Start time / End time", + "Task_name_placeholder": "Please enter a task name", + "answer": "Answers", + "app_required": "Please select the evaluation application", + "app_response": "Application output", + "back": "back", + "check_error": "Error", + "check_error_tip": "Check, please:\n\n1. Is it consistent with the header data of the .csv template\n\n2. Whether required items are missing\n\n3. Global variables - whether the word limit or number range limit is exceeded\n\n4. Global variable-number box, whether the character is number\n\n5. Global variable - radio box, whether the characters are exactly the same as the options", + "check_format": "Format Check", + "comfirm_delete_item": "Confirm to delete this piece of data?", + "comfirm_delete_task": "Confirm deleting the task and all its data?", + "completed": "Completed", + "create_task": "Create a task", + "data": "data", + "data_list": "Data list", + "detail": "Detail", + "error": "abnormal", + "error_tooltip": "There are exception tasks. \n\nAfter clicking, open the task details pop-up window", + "eval_file_check_error": "Evaluation file validation failed", + "evaluating": "In evaluation", + "evaluation": "Application Review", + "evaluation_export_title": "Question,Standard Answer,Actual Response,Status,Average Score", + "evaluation_file_max_size": "{{count}} data", + "export": "Export Data", + "file_required": "Please select the evaluation file", + "file_uploading": "File uploading: {{num}}%", + "history": "History", + "paused": "Paused", + "question": "Questions", + "queuing": "Queue", + "search_task": "Find tasks", + "standard_response": "Standard output", + "start_evaluation": "Start the evaluation", + "stauts": "Status", + "task_creating": "Creating task", + "task_detail": "Task Details", + "task_name": "Task Name", + "team_has_running_evaluation": "The current team already has running application reviews. Please wait until it is completed before creating a new application review.", + "template_csv_file_select_tip": "Only support {{fileType}} files that are strictly in accordance with template format", + "variables": "Global variables" +} diff --git a/packages/web/i18n/en/dataset.json b/packages/web/i18n/en/dataset.json index d8884ad42..d8c57c4fa 100644 --- a/packages/web/i18n/en/dataset.json +++ b/packages/web/i18n/en/dataset.json @@ -166,6 +166,8 @@ "request_headers": "Request headers, will automatically append 'Bearer '", "retain_collection": "Adjust Training Parameters", "retrain_task_submitted": "The retraining task has been submitted", + "retry_all": "Retry all", + "retry_failed": "Retry Failed", "rootDirectoryFormatError": "Root directory data format is incorrect", "rootdirectory": "/rootdirectory", "same_api_collection": "The same API set exists", diff --git a/packages/web/i18n/en/workflow.json b/packages/web/i18n/en/workflow.json index 8e1c5bf95..b9b0f651f 100644 --- a/packages/web/i18n/en/workflow.json +++ b/packages/web/i18n/en/workflow.json @@ -51,7 +51,9 @@ "edit_output": "Edit output", "end_with": "Ends With", "enter_comment": "Enter comment", + "error_catch": "Error catch", "error_info_returns_empty_on_success": "Error information of code execution, returns empty on success", + "error_text": "Error text", "execute_a_simple_script_code_usually_for_complex_data_processing": "Execute a simple script code, usually for complex data processing.", "execute_different_branches_based_on_conditions": "Execute different branches based on conditions.", "execution_error": "Execution Error", @@ -80,7 +82,6 @@ "http_extract_output_description": "Specified fields in the response value can be extracted through JSONPath syntax", "http_raw_response_description": "Raw HTTP response. Only accepts string or JSON type response data.", "http_request": "HTTP", - "http_request_error_info": "HTTP request error information, returns empty on success", "ifelse.Input value": "Input Value", "ifelse.Select value": "Select Value", "input_description": "Field Description", diff --git a/packages/web/i18n/i18next.d.ts b/packages/web/i18n/i18next.d.ts index a22cff1e0..b831611da 100644 --- a/packages/web/i18n/i18next.d.ts +++ b/packages/web/i18n/i18next.d.ts @@ -44,6 +44,7 @@ export interface I18nNamespaces { account_thirdParty: typeof account_thirdParty; account_model: typeof account_model; dashboard_mcp: typeof dashboard_mcp; + dashboard_evaluation: typeof dashboard_evaluation; } export type I18nNsType = (keyof I18nNamespaces)[]; diff --git a/packages/web/i18n/zh-CN/account_model.json b/packages/web/i18n/zh-CN/account_model.json index 9aaf255b6..1b1ad8779 100644 --- a/packages/web/i18n/zh-CN/account_model.json +++ b/packages/web/i18n/zh-CN/account_model.json @@ -62,6 +62,7 @@ "model_request_times": "请求次数", "model_test": "模型测试", "model_tokens": "输入/输出 Tokens", + "model_ttfb_time": "首字响应时长", "monitoring": "监控", "output": "输出", "request_at": "请求时间", @@ -81,6 +82,7 @@ "timespan_label": "时间颗粒度", "timespan_minute": "分钟", "total_call_volume": "调用总量", + "use_in_eval": "用于应用评测", "view_chart": "图表", "view_table": "表格", "vlm_model": "图片理解模型", diff --git a/packages/web/i18n/zh-CN/account_team.json b/packages/web/i18n/zh-CN/account_team.json index 8eb175c41..3710d27ee 100644 --- a/packages/web/i18n/zh-CN/account_team.json +++ b/packages/web/i18n/zh-CN/account_team.json @@ -51,6 +51,7 @@ "create_dataset": "创建知识库", "create_dataset_folder": "创建知识库文件夹", "create_department": "创建子部门", + "create_evaluation": "创建应用评测", "create_group": "创建群组", "create_invitation_link": "创建邀请链接", "create_invoice": "开发票", @@ -73,6 +74,7 @@ "delete_dataset": "删除知识库", "delete_dataset_collaborator": "知识库权限删除", "delete_department": "删除子部门", + "delete_evaluation": "删除应用评测数据", "delete_from_org": "移出部门", "delete_from_team": "移出团队", "delete_group": "删除群组", @@ -86,6 +88,7 @@ "export_app_chat_log": "导出应用聊天记录", "export_bill_records": "导出账单记录", "export_dataset": "导出知识库", + "export_evaluation": "导出应用评测数据", "export_members": "导出成员", "forbid_hint": "停用后,该邀请链接将失效。 该操作不可撤销,是否确认停用?", "forbid_success": "停用成功", @@ -149,6 +152,7 @@ "log_create_dataset": "【{{name}}】创建了名为【{{datasetName}}】的【{{datasetType}}】", "log_create_dataset_folder": "【{{name}}】创建了名为{{folderName}}】的文件夹", "log_create_department": "【{{name}}】创建了部门【{{departmentName}}】", + "log_create_evaluation": "【{{name}}】创建了名为【{{appName}}】的【{{appType}}】的批量评测", "log_create_group": "【{{name}}】创建了群组【{{groupName}}】", "log_create_invitation_link": "【{{name}}】创建了邀请链接【{{link}}】", "log_create_invoice": "【{{name}}】进行了开发票操作", @@ -161,11 +165,13 @@ "log_delete_dataset": "【{{name}}】删除了名为【{{datasetName}}】的【{{datasetType}}】", "log_delete_dataset_collaborator": "【{{name}}】将名为【{{datasetName}}】的【{{datasetType}}】中名为【itemValueName】的【itemName】权限删除", "log_delete_department": "【{{name}}】删除了部门【{{departmentName}}】", + "log_delete_evaluation": "【{{name}}】删除了名为【{{appName}}】的【{{appType}}】的评测数据", "log_delete_group": "【{{name}}】删除了群组【{{groupName}}】", "log_details": "详情", "log_export_app_chat_log": "【{{name}}】导出了名为【{{appName}}】的【{{appType}}】的聊天记录", "log_export_bill_records": "【{{name}}】导出了账单记录", "log_export_dataset": "【{{name}}】导出了名为【{{datasetName}}】的【{{datasetType}}】", + "log_export_evaluation": "【{{name}}】导出了名为【{{appName}}】的【{{appType}}】的评测数据", "log_join_team": "【{{name}}】通过邀请链接【{{link}}】加入团队", "log_kick_out_team": "【{{name}}】移除了成员【{{memberName}}】", "log_login": "【{{name}}】登录了系统", diff --git a/packages/web/i18n/zh-CN/account_usage.json b/packages/web/i18n/zh-CN/account_usage.json index 8befa05ab..7ebb628f1 100644 --- a/packages/web/i18n/zh-CN/account_usage.json +++ b/packages/web/i18n/zh-CN/account_usage.json @@ -1,16 +1,19 @@ { "ai_model": "AI 模型", "all": "所有", + "answer_accuracy": "评测-回答准确性", "app_name": "应用名", "auto_index": "索引增强", "billing_module": "扣费模块", "confirm_export": "共筛选出 {{total}} 条数据,是否确认导出?", + "count": "运行次数", "current_filter_conditions": "当前筛选条件:", "dashboard": "仪表盘", "details": "详情", "dingtalk": "钉钉", "duration_seconds": "时长(秒)", "embedding_index": "索引生成", + "evaluation": "应用评测", "every_day": "天", "every_month": "月", "every_week": "每周", @@ -19,6 +22,7 @@ "export_success": "导出成功", "export_title": "时间,成员,类型,项目名,AI 积分消耗", "feishu": "飞书", + "generate_answer": "生成应用回答", "generation_time": "生成时间", "image_index": "图片索引", "image_parse": "图片标注", diff --git a/packages/web/i18n/zh-CN/app.json b/packages/web/i18n/zh-CN/app.json index a148dbbb0..7e5514546 100644 --- a/packages/web/i18n/zh-CN/app.json +++ b/packages/web/i18n/zh-CN/app.json @@ -56,6 +56,7 @@ "cron.interval": "间隔执行", "dataset_search_tool_description": "调用“语义检索”和“全文检索”能力,从“知识库”中查找可能与问题相关的参考内容。优先调用该工具来辅助回答用户的问题。", "day": "日", + "deleted": "应用已删除", "document_quote": "文档引用", "document_quote_tip": "通常用于接受用户上传的文档内容(这需要文档解析),也可以用于引用其他字符串数据。", "document_upload": "文档上传", @@ -83,7 +84,6 @@ "interval.4_hours": "每4小时", "interval.6_hours": "每6小时", "interval.per_hour": "每小时", - "intro": "是一个大模型应用编排系统,提供开箱即用的数据处理、模型调用等能力,可以快速的构建知识库并通过 Flow 可视化进行工作流编排,实现复杂的知识库场景!", "invalid_json_format": "JSON 格式错误", "keep_the_latest": "保持最新版本", "llm_not_support_vision": "该模型不支持图片识别", diff --git a/packages/web/i18n/zh-CN/chat.json b/packages/web/i18n/zh-CN/chat.json index 89fad715a..254b4551f 100644 --- a/packages/web/i18n/zh-CN/chat.json +++ b/packages/web/i18n/zh-CN/chat.json @@ -52,6 +52,7 @@ "module_runtime_and": "工作流总运行时间", "multiple_AI_conversations": "多组 AI 对话", "new_input_guide_lexicon": "新词库", + "no_invalid_app": "您账号下没有可用的应用", "no_workflow_response": "没有运行数据", "not_query": "缺少查询内容", "not_select_file": "未选择文件", diff --git a/packages/web/i18n/zh-CN/common.json b/packages/web/i18n/zh-CN/common.json index 83e3e4694..ec9ba9aae 100644 --- a/packages/web/i18n/zh-CN/common.json +++ b/packages/web/i18n/zh-CN/common.json @@ -77,6 +77,8 @@ "Save": "保存", "Save_and_exit": "保存并退出", "Search": "搜索", + "Select_App": "选择应用", + "Select_all": "全选", "Setting": "设置", "Status": "状态", "Submit": "提交", @@ -96,6 +98,7 @@ "add_success": "添加成功", "all_quotes": "全部引用", "all_result": "完整结果", + "app_evaluation": "应用评测(Beta)", "app_not_version": " 该应用未发布过,请先发布应用", "auth_config": "鉴权配置", "auth_type": "鉴权类型", @@ -195,6 +198,7 @@ "compliance.chat": "内容由第三方 AI 生成,无法确保真实准确,仅供参考", "compliance.dataset": "请确保您的内容严格遵守相关法律法规,避免包含任何违法或侵权的内容。请谨慎上传可能涉及敏感信息的资料。", "confirm_choice": "确认选择", + "confirm_logout": "确认退出登录?", "confirm_move": "移动到这", "confirm_update": "确认更新", "contact_way": "通知接收", @@ -865,6 +869,7 @@ "link.UnValid": "无效的链接", "llm_model_not_config": "检测到没有可用的语言模型", "load_failed": "加载失败", + "logout": "登出", "max_quote_tokens": "引用上限", "max_quote_tokens_tips": "单次搜索最大的 token 数量,中文约 1 字=1.7 tokens,英文约 1 字=1 token", "mcp_server": "MCP 服务", @@ -1153,6 +1158,7 @@ "support.wallet.subscription.Update extra dataset size": "额外存储量", "support.wallet.subscription.Upgrade plan": "升级套餐", "support.wallet.subscription.ai_model": "AI语言模型", + "support.wallet.subscription.eval_items_count": "单次评测数据条数: {{count}} 条", "support.wallet.subscription.function.History store": "{{amount}} 天对话记录保留", "support.wallet.subscription.function.Max app": "{{amount}} 个应用上限", "support.wallet.subscription.function.Max dataset": "{{amount}} 个知识库上限", @@ -1202,6 +1208,7 @@ "system.Concat us": "联系我们", "system.Help Document": "帮助文档", "system_help_chatbot": "机器人助手", + "system_intro": "{{title}} 是一个大模型应用编排系统,提供开箱即用的数据处理、模型调用等能力,可以快速的构建知识库并通过 Flow 可视化进行工作流编排,实现复杂的知识库场景!\n", "tag_list": "标签列表", "team_tag": "团队标签", "templateTags.Image_generation": "图片生成", diff --git a/packages/web/i18n/zh-CN/dashboard_evaluation.json b/packages/web/i18n/zh-CN/dashboard_evaluation.json new file mode 100644 index 000000000..abdc71303 --- /dev/null +++ b/packages/web/i18n/zh-CN/dashboard_evaluation.json @@ -0,0 +1,53 @@ +{ + "Action": "操作", + "Evaluation_app": "评测应用", + "Evaluation_app_tip": "支持简易应用,不含有用户交互节点的工作流。暂不支持插件。", + "Evaluation_file": "评测文件", + "Evaluation_model": "评测模型", + "Executor": "执行人", + "Overall_score": "综合评分", + "Progress": "进度", + "Start_end_time": "开始时间 / 结束时间", + "Task_name": "任务名", + "Task_name_placeholder": "请输入任务名", + "answer": "标准答案", + "app_required": "请选择评测应用", + "app_response": "应用输出", + "back": "退出", + "check_error": "校验失败", + "check_error_tip": "请检查:\n1. 是否与.csv模板表头数据一致 \n2. 是否缺失必填项 \n3. 全局变量-是否超出字数限制、数字范围限制 \n4. 全局变量-数字框, 字符是否为number \n5. 全局变量-单选框, 字符是否与选项完全一致", + "check_format": "格式校验", + "click_to_download_template": "点击下载该应用的 CSV 模板", + "comfirm_delete_item": "确认删除该条数据?", + "comfirm_delete_task": "确认删除该任务及其所有数据?", + "completed": "已完成", + "create_task": "创建任务", + "data": "数据", + "data_list": "数据列表", + "detail": "详情", + "error": "异常", + "error_tooltip": "有异常任务。 \n点击后,打开任务详情弹窗", + "eval_file_check_error": "评测文件校验失败", + "evaluating": "评估中", + "evaluation": "应用评测", + "evaluation_created": "评测任务创建成功", + "evaluation_export_title": "问题,标准答案,实际回答,状态,综合得分", + "evaluation_file_max_size": "{{count}} 条数据", + "export": "导出数据", + "file_required": "请选择评测文件", + "file_uploading": "文件上传中: {{num}}%", + "history": "历史记录", + "paused": "已暂停", + "question": "用户问题", + "queuing": "排队中", + "search_task": "查找任务", + "standard_response": "标准输出", + "start_evaluation": "开始评测", + "stauts": "状态", + "task_creating": "正在创建任务", + "task_detail": "任务详情", + "task_name": "任务名称", + "team_has_running_evaluation": "当前团队已有正在运行的应用评测,请等待完成后再创建新的应用评测", + "template_csv_file_select_tip": "仅支持严格按照模板填写的 {{fileType}} 文件", + "variables": "全局变量" +} diff --git a/packages/web/i18n/zh-CN/dataset.json b/packages/web/i18n/zh-CN/dataset.json index 539961490..af813c47f 100644 --- a/packages/web/i18n/zh-CN/dataset.json +++ b/packages/web/i18n/zh-CN/dataset.json @@ -166,6 +166,8 @@ "request_headers": "请求头参数,会自动补充 Bearer", "retain_collection": "调整训练参数", "retrain_task_submitted": "重新训练任务已提交", + "retry_all": "全部重试", + "retry_failed": "重试失败", "rootDirectoryFormatError": "根目录数据格式不正确", "rootdirectory": "/根目录", "same_api_collection": "存在相同的 API 集合", diff --git a/packages/web/i18n/zh-CN/workflow.json b/packages/web/i18n/zh-CN/workflow.json index b08a0f69b..0493c0c6b 100644 --- a/packages/web/i18n/zh-CN/workflow.json +++ b/packages/web/i18n/zh-CN/workflow.json @@ -51,7 +51,9 @@ "edit_output": "编辑输出", "end_with": "结束为", "enter_comment": "输入注释", + "error_catch": "报错捕获", "error_info_returns_empty_on_success": "代码运行错误信息,成功时返回空", + "error_text": "错误信息", "execute_a_simple_script_code_usually_for_complex_data_processing": "执行一段简单的脚本代码,通常用于进行复杂的数据处理。", "execute_different_branches_based_on_conditions": "根据一定的条件,执行不同的分支。", "execution_error": "运行错误", @@ -80,7 +82,6 @@ "http_extract_output_description": "可以通过 JSONPath 语法来提取响应值中的指定字段", "http_raw_response_description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。", "http_request": "HTTP 请求", - "http_request_error_info": "HTTP请求错误信息,成功时返回空", "ifelse.Input value": "输入值", "ifelse.Select value": "选择值", "input_description": "字段描述", diff --git a/packages/web/i18n/zh-Hant/account_model.json b/packages/web/i18n/zh-Hant/account_model.json index 04b83c6d8..38dc0a711 100644 --- a/packages/web/i18n/zh-Hant/account_model.json +++ b/packages/web/i18n/zh-Hant/account_model.json @@ -62,6 +62,7 @@ "model_request_times": "請求次數", "model_test": "模型測試", "model_tokens": "輸入/輸出 Tokens", + "model_ttfb_time": "首字響應時長", "monitoring": "監控", "output": "輸出", "request_at": "請求時間", @@ -81,6 +82,7 @@ "timespan_label": "時間顆粒度", "timespan_minute": "分鐘", "total_call_volume": "調用總量", + "use_in_eval": "用於應用評測", "view_chart": "圖表", "view_table": "表格", "vlm_model": "圖片理解模型", diff --git a/packages/web/i18n/zh-Hant/account_team.json b/packages/web/i18n/zh-Hant/account_team.json index 1d4545866..02b183095 100644 --- a/packages/web/i18n/zh-Hant/account_team.json +++ b/packages/web/i18n/zh-Hant/account_team.json @@ -73,6 +73,7 @@ "delete_dataset": "刪除知識庫", "delete_dataset_collaborator": "知識庫權限刪除", "delete_department": "刪除子部門", + "delete_evaluation": "刪除應用評測數據", "delete_from_org": "移出部門", "delete_from_team": "移出團隊", "delete_group": "刪除群組", @@ -161,6 +162,7 @@ "log_delete_dataset": "【{{name}}】刪除了名為【{{datasetName}}】的【{{datasetType}}】", "log_delete_dataset_collaborator": "【{{name}}】將名為【{{datasetName}}】的【{{datasetType}}】中名為【itemValueName】的【itemName】權限刪除", "log_delete_department": "{{name}} 刪除了部門 {{departmentName}}", + "log_delete_evaluation": "【{{name}}】刪除了名為【{{appName}}】的【{{appType}}】的評測數據", "log_delete_group": "{{name}} 刪除了群組 {{groupName}}", "log_details": "詳情", "log_export_app_chat_log": "【{{name}}】導出了名為【{{appName}}】的【{{appType}}】的聊天記錄", diff --git a/packages/web/i18n/zh-Hant/account_usage.json b/packages/web/i18n/zh-Hant/account_usage.json index db2f54fbd..799080d8f 100644 --- a/packages/web/i18n/zh-Hant/account_usage.json +++ b/packages/web/i18n/zh-Hant/account_usage.json @@ -5,12 +5,14 @@ "auto_index": "索引增強", "billing_module": "扣費模組", "confirm_export": "共篩選出 {{total}} 條資料,是否確認匯出?", + "count": "運行次數", "current_filter_conditions": "目前篩選條件:", "dashboard": "儀表板", "details": "詳細資訊", "dingtalk": "釘釘", "duration_seconds": "時長(秒)", "embedding_index": "索引生成", + "evaluation": "應用評測", "every_day": "天", "every_month": "月", "every_week": "每週", diff --git a/packages/web/i18n/zh-Hant/app.json b/packages/web/i18n/zh-Hant/app.json index edf2b3e46..d3ae7bb86 100644 --- a/packages/web/i18n/zh-Hant/app.json +++ b/packages/web/i18n/zh-Hant/app.json @@ -56,6 +56,7 @@ "cron.interval": "間隔執行", "dataset_search_tool_description": "呼叫「語意搜尋」和「全文搜尋」功能,從「知識庫」中尋找可能與問題相關的參考內容。優先呼叫這個工具來協助回答使用者的問題。", "day": "日", + "deleted": "應用已刪除", "document_quote": "文件引用", "document_quote_tip": "通常用於接受使用者上傳的文件內容(這需要文件解析),也可以用於引用其他字串資料。", "document_upload": "文件上傳", @@ -83,7 +84,6 @@ "interval.4_hours": "每 4 小時", "interval.6_hours": "每 6 小時", "interval.per_hour": "每小時", - "intro": "FastGPT 是一個基於大型語言模型的知識庫平臺,提供開箱即用的資料處理、向量檢索和視覺化 AI 工作流程編排等功能,讓您可以輕鬆開發和部署複雜的問答系統,而無需繁瑣的設定或設定。", "invalid_json_format": "JSON 格式錯誤", "keep_the_latest": "保持最新版本", "llm_not_support_vision": "這個模型不支援圖片辨識", diff --git a/packages/web/i18n/zh-Hant/chat.json b/packages/web/i18n/zh-Hant/chat.json index 1a1d24ea6..5dd49f08e 100644 --- a/packages/web/i18n/zh-Hant/chat.json +++ b/packages/web/i18n/zh-Hant/chat.json @@ -52,6 +52,7 @@ "module_runtime_and": "模組執行總時間", "multiple_AI_conversations": "多組 AI 對話", "new_input_guide_lexicon": "新增詞彙庫", + "no_invalid_app": "您賬號下沒有可用的應用", "no_workflow_response": "無工作流程資料", "not_query": "缺少查詢內容", "not_select_file": "尚未選取檔案", diff --git a/packages/web/i18n/zh-Hant/common.json b/packages/web/i18n/zh-Hant/common.json index bc4a8aea0..8173dea40 100644 --- a/packages/web/i18n/zh-Hant/common.json +++ b/packages/web/i18n/zh-Hant/common.json @@ -77,6 +77,8 @@ "Save": "儲存", "Save_and_exit": "儲存並離開", "Search": "搜尋", + "Select_App": "選擇應用", + "Select_all": "全選", "Setting": "設定", "Status": "狀態", "Submit": "送出", @@ -96,6 +98,7 @@ "add_success": "新增成功", "all_quotes": "全部引用", "all_result": "完整結果", + "app_evaluation": "應用評測(Beta)", "app_not_version": "該應用未發布過,請先發布應用", "auth_config": "鑑權配置", "auth_type": "鑑權類型", @@ -195,6 +198,7 @@ "compliance.chat": "內容由第三方 AI 產生,無法保證其真實性與準確性,僅供參考。", "compliance.dataset": "請確保您的內容嚴格遵守相關法律法規,避免包含任何違法或侵權的內容。\n在上傳可能涉及敏感資訊的資料時請務必謹慎。", "confirm_choice": "確認選擇", + "confirm_logout": "確認退出登錄?", "confirm_move": "移動至此", "confirm_update": "確認更新", "contact_way": "通知接收", @@ -865,6 +869,7 @@ "link.UnValid": "無效的連結", "llm_model_not_config": "偵測到沒有可用的語言模型", "load_failed": "載入失敗", + "logout": "登出", "max_quote_tokens": "引用上限", "max_quote_tokens_tips": "單次搜尋最大的 token 數量,中文約 1 字=1.7 tokens,英文約 1 字=1 token", "mcp_server": "MCP 服務", @@ -1202,6 +1207,7 @@ "system.Concat us": "聯絡我們", "system.Help Document": "說明文件", "system_help_chatbot": "機器人助手", + "system_intro": "{{title}} 是一個大模型應用編排系統,提供開箱即用的數據處理、模型調用等能力,可以快速的構建知識庫並通過 Flow 可視化進行工作流編排,實現複雜的知識庫場景!", "tag_list": "標籤列表", "team_tag": "團隊標籤", "templateTags.Image_generation": "圖片生成", diff --git a/packages/web/i18n/zh-Hant/dashboard_evaluation.json b/packages/web/i18n/zh-Hant/dashboard_evaluation.json new file mode 100644 index 000000000..508142b00 --- /dev/null +++ b/packages/web/i18n/zh-Hant/dashboard_evaluation.json @@ -0,0 +1,46 @@ +{ + "Action": "操作", + "Evaluation_app": "評測應用", + "Evaluation_app_tip": "支持簡易應用,不含有用戶交互節點的工作流。\n暫不支持插件。", + "Evaluation_file": "評測文件", + "Evaluation_model": "評測模型", + "Executor": "執行人", + "Overall_score": "綜合評分", + "Progress": "進度", + "Start_end_time": "開始時間 / 結束時間", + "Task_name_placeholder": "請輸入任務名", + "answer": "標準答案", + "app_required": "請選擇評測應用", + "app_response": "應用輸出", + "back": "退出", + "check_error": "校驗失敗", + "check_error_tip": "請檢查:\n1. 是否與.csv模板表頭數據一致 \n2. 是否缺失必填項 \n3. 全局變量-是否超出字數限制、數字範圍限制 \n4. 全局變量-數字框, 字符是否為number \n5. 全局變量-單選框, 字符是否與選項完全一致", + "check_format": "格式校驗", + "comfirm_delete_item": "確認刪除該條數據?", + "comfirm_delete_task": "確認刪除該任務及其所有數據?", + "completed": "已完成", + "create_task": "創建任務", + "data": "數據", + "data_list": "數據列表", + "error": "異常", + "error_tooltip": "有異常任務。 \n\n點擊後,打開任務詳情彈窗", + "evaluating": "評估中", + "evaluation": "應用評測", + "evaluation_export_title": "問題,標準答案,實際回答,狀態,綜合得分", + "evaluation_file_max_size": "{{count}} 條數據", + "export": "導出數據", + "file_required": "請選擇評測文件", + "file_uploading": "文件上傳中: {{num}}%", + "history": "歷史記錄", + "paused": "已暫停", + "question": "用戶問題", + "queuing": "排隊中", + "search_task": "查找任務", + "standard_response": "標準輸出", + "start_evaluation": "開始評測", + "task_creating": "正在創建任務", + "task_detail": "任務詳情", + "team_has_running_evaluation": "當前團隊已有正在運行的應用評測,請等待完成後再創建新的應用評測", + "template_csv_file_select_tip": "僅支持嚴格按照模板格式的 {{fileType}} 文件", + "variables": "全局變量" +} diff --git a/packages/web/i18n/zh-Hant/dataset.json b/packages/web/i18n/zh-Hant/dataset.json index e86d9f8e7..07bde6b26 100644 --- a/packages/web/i18n/zh-Hant/dataset.json +++ b/packages/web/i18n/zh-Hant/dataset.json @@ -166,6 +166,8 @@ "request_headers": "請求頭", "retain_collection": "調整訓練參數", "retrain_task_submitted": "重新訓練任務已提交", + "retry_all": "全部重試", + "retry_failed": "重試失敗", "rootDirectoryFormatError": "根目錄資料格式不正確", "rootdirectory": "/根目錄", "same_api_collection": "存在相同的 API 集合", diff --git a/packages/web/i18n/zh-Hant/workflow.json b/packages/web/i18n/zh-Hant/workflow.json index 7392a69aa..3bbfa9d65 100644 --- a/packages/web/i18n/zh-Hant/workflow.json +++ b/packages/web/i18n/zh-Hant/workflow.json @@ -51,7 +51,9 @@ "edit_output": "編輯輸出", "end_with": "結尾為", "enter_comment": "輸入註解", + "error_catch": "報錯捕獲", "error_info_returns_empty_on_success": "程式碼執行錯誤資訊,成功時回傳空值", + "error_text": "錯誤訊息", "execute_a_simple_script_code_usually_for_complex_data_processing": "執行一段簡單的腳本程式碼,通常用於複雜的資料處理。", "execute_different_branches_based_on_conditions": "根據條件執行不同的分支。", "execution_error": "執行錯誤", @@ -80,7 +82,6 @@ "http_extract_output_description": "可以透過 JSONPath 語法來擷取回應值中的指定欄位", "http_raw_response_description": "HTTP 請求的原始回應。僅接受字串或 JSON 類型回應資料。", "http_request": "HTTP 請求", - "http_request_error_info": "HTTP 請求錯誤資訊,成功時回傳空值", "ifelse.Input value": "輸入值", "ifelse.Select value": "選擇值", "input_description": "欄位描述", diff --git a/packages/web/styles/theme.ts b/packages/web/styles/theme.ts index 737ebc08a..5044e122d 100644 --- a/packages/web/styles/theme.ts +++ b/packages/web/styles/theme.ts @@ -312,6 +312,11 @@ const Button = defineStyleConfig({ }); const Input: ComponentStyleConfig = { + baseStyle: { + field: { + color: 'myGray.700' + } + }, sizes: { sm: defineStyle({ field: { diff --git a/packages/web/support/user/audit/constants.ts b/packages/web/support/user/audit/constants.ts index f0b17055b..210f9819a 100644 --- a/packages/web/support/user/audit/constants.ts +++ b/packages/web/support/user/audit/constants.ts @@ -281,6 +281,24 @@ export const auditLogMap = { typeLabel: i18nT('account_team:export_app_chat_log'), params: {} as { name?: string; appName: string; appType: string } }, + + // Eval + [AuditEventEnum.CREATE_EVALUATION]: { + content: i18nT('account_team:log_create_evaluation'), + typeLabel: i18nT('account_team:create_evaluation'), + params: {} as { name: string; appName: string } + }, + [AuditEventEnum.EXPORT_EVALUATION]: { + content: i18nT('account_team:log_export_evaluation'), + typeLabel: i18nT('account_team:export_evaluation'), + params: {} as { name: string } + }, + [AuditEventEnum.DELETE_EVALUATION]: { + content: i18nT('account_team:log_delete_evaluation'), + typeLabel: i18nT('account_team:delete_evaluation'), + params: {} as { name?: string } + }, + //Dataset [AuditEventEnum.CREATE_DATASET]: { content: i18nT('account_team:log_create_dataset'), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 62ab0438b..93451ce3e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -121,8 +121,8 @@ importers: packages/service: dependencies: '@fastgpt-sdk/plugin': - specifier: ^0.1.1 - version: 0.1.1(@types/node@20.17.24) + specifier: ^0.1.2 + version: 0.1.2(@types/node@20.17.24) '@fastgpt/global': specifier: workspace:* version: link:../global @@ -1973,8 +1973,8 @@ packages: resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@fastgpt-sdk/plugin@0.1.1': - resolution: {integrity: sha512-RiPTbVgUt0yVr8R7MDxXKhSjR/QCAWib7K1L5jdaRRTJQqJMmSdISNlRHzgF2HmbBLBoVomvVz9VYhHSiAqt/w==} + '@fastgpt-sdk/plugin@0.1.2': + resolution: {integrity: sha512-z8C0TCCSFxJpF81+V654Xv6SVnLn+/yBNQW78EfZmHe3udZ/WZGmXUTACMac0YrdcT0Wi3TV2/+mmXfePA2CSw==} '@fastify/accept-negotiator@1.1.0': resolution: {integrity: sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==} @@ -11204,7 +11204,7 @@ snapshots: '@eslint/js@8.57.1': {} - '@fastgpt-sdk/plugin@0.1.1(@types/node@20.17.24)': + '@fastgpt-sdk/plugin@0.1.2(@types/node@20.17.24)': dependencies: '@fortaine/fetch-event-source': 3.0.6 '@ts-rest/core': 3.52.1(@types/node@20.17.24)(zod@3.25.51) diff --git a/projects/app/package.json b/projects/app/package.json index 4113e0bbb..f347528ff 100644 --- a/projects/app/package.json +++ b/projects/app/package.json @@ -1,6 +1,6 @@ { "name": "app", - "version": "4.10.0", + "version": "4.11.0", "private": false, "scripts": { "dev": "next dev", diff --git a/projects/app/public/docs/versionIntro.md b/projects/app/public/docs/versionIntro.md index d480f406c..6163b293f 100644 --- a/projects/app/public/docs/versionIntro.md +++ b/projects/app/public/docs/versionIntro.md @@ -1,22 +1,24 @@ -### FastGPT V4.9.11 更新说明 +### FastGPT V4.11.0 更新说明 ## 🚀 新增内容 -1. 支持图片知识库。 -2. 工作流中增加节点搜索功能。 -3. 工作流中,子流程版本控制,可选择“保持最新版本”,无需手动更新。 -4. 增加更多审计操作日志。 -5. 知识库增加文档解析异步队列,导入文档时,无需等待文档解析完毕才进行导入。 - +1. 商业版增加应用评测 Beta 版功能,可对应用进行有监督评分。 +2. 工作流部分节点支持报错捕获分支。 +3. 对话页独立 tab 页面UX。 +4. 支持 Signoz traces 和 logs 系统追踪。 +5. 新增 Gemini2.5, grok4, kimi 模型配置。 +6. 模型调用日志增加首字响应时长和请求 IP。 + ## ⚙️ 优化 -1. 原文缓存改用 gridfs 存储,提高上限。 +1. 优化代码,避免递归造成的内存堆积。 +2. 知识库训练:支持全部重试当前集合异常数据。 +3. 工作流 valueTypeFormat,避免数据类型不一致。 ## 🐛 修复 -1. 工作流中,管理员声明的全局系统工具,无法进行版本管理。 -2. 工具调用节点前,有交互节点时,上下文异常。 -3. 修复备份导入,小于 1000 字时,无法分块问题。 -4. 自定义 PDF 解析,无法保存 base64 图片。 -5. 非流请求,未进行 CITE 标记替换。 -6. Python 沙盒存在隐藏风险。 \ No newline at end of file +1. 问题分类和内容提取节点,默认模型无法通过前端校验,导致工作流无法运行和保存发布。 + +## 🔨 工具更新 + +1. Markdown 文本转 Docx 和 Xlsx 文件。 \ No newline at end of file diff --git a/projects/app/public/imgs/fastgpt_slogan.png b/projects/app/public/imgs/fastgpt_slogan.png new file mode 100644 index 0000000000000000000000000000000000000000..988e8e7f5916a82cd976da0c185b61578a9f46e6 GIT binary patch literal 4622 zcmV+p67lVcP)fZ)%#hI503! z0ES`O8WDEI_4V~h9G$}m(6vMP1N@3dE z+|1y^Jc|$S`BW+ek3ar6kT7)uJ3Bj7fx_=MkCxvBY~mGxVVJhSSC~+gbQGnl_|Rti z`ua>!>O{7;w@rZ}>j^EuarQqkOnXo#OtvU3;iP`#$dQh*l2kl=_z>>hyGN5OP7Ko~ z)CiN^e0v8c^<&450X5;e0cz51ZEexyvMo;BtYerKQscq4eMdyZrD`$R=>V7e`}^V0p+nG(+`oSxN~IDB)cDDhC-cBCOiQR*Oy~ffCCi~( zMTsCMKV1V#j{t^YT81f1WGzt>uA4>4fy9Zfg>6AH4ATNkVM1$ZnkK6zM!z7M|QK$);==6N0DA>Y8eE}zcVVK0xuo*XI zJA8q$ple}wPCf$+!z2y~lip1^OJSkP31C-JU>GJ@(iDM8(pviXfiIgB1akD&EV}7fFJw}U~L0TagnwN3D9>a7nX=oxp{}aIcHEUYftwqiKnA;*_iVb)SCVL)j zChdj7wTyV}4HUdhfbYKy48wE~O%tZyyan*1Ujd{{W$`TZI+MH(CeQj(?1_eBqnW5+OG?aA-LI=$#3PTjjedXn1p}-X<5~L}@L{`j|-?^+Kl{k5)U1L*w z{5;|IXUC$V&%gY$<#)9>mWmq?xD5YUwXtaQz@*2o1>uW3du={WP?<(6MzWQIql#g3dqQL?U;3|d>F6$O(hT9YUOc?s#M;@W!!*pJ5lu6R89 z2a}@IGkbh!WMN}%<$`*3p~rgvc);_}AwilVOn><|ijvm~@+yk-X`*SCm87}13(3E3 z0^BPB9PR_A1IY}H!D>Wk;OyJIdjlF^W4j9fueDB9q%9%^IgmT`2jE;4Os+! zLwLxCgcb-@Y0beSLSlj9Gz*C#bpe$n5?1JNVi%?lZdpPhv|2$*+4E8^L863nXF8p7 z36rQ5v>IU(>fsietZ1jVp5#NL$KF4Z^V>Vj_g-{~QM zuYB6G+|%4s#Ov}BLm3!CAJu~iG|yZ*Ui*qMl-YtNyk(ODBcg}2Zu(kxt;Lnjhcdez*fmc zTrpcWAH)>{{7c=$KVN>m z!5fzLoi3%b7;!&~;+m1J2O94MvgU3YG974TWS$z$Y87c)D&lFW2%C`;+Wpz+E6;nw z>l##MRY5l){;{i><9DV1w@}11%>H4_XN#Ah(E^egMD045tCS&6P#O2Xslm<{Dm(;&Tu*{x^M&lL*uhn{*GJ-8+|hKf=cOWxdZ1b z)Yq_jBe?c2EypvS!$mXno)R;~53koXJyR9P8h8%QS*eiKb!lJ~-0|VlS0sq(`lV8jQwZCQ*!6*QM{`Bks)Hx$ zCRmwTf|qY*DV zy0wMYOM~a92K_N-{S7XGzLo0>5RuKb750ucnfNUxPiS9=`n)B_iei$(YQD)i9zY;o_!SXNp@13Gs&B@Wpf$wSUIgi_wijZBI!QV6= zlZpK+B|Uy7XFWe|Z-J7>dja~pEwdYM%;Q6&m!oWA^zF|0I)8neSE4|n=Hlv}lRGFP zgn!Fv6ptF`GKq;S$w}}#eRM1vy_LC|^;HorJU?6b=ICPuQh9)~X`SE?S{BdWx6he3 z>|W7grL4Ejq7eKMN8ZABHAko~YTtItGQ!JbLF$D7T696xM9^k8=f$jhw zDin^%D4U?~f&3=L+THkD4Sro5OegPCqf_Mj@!22)yFEHi?O8q;ee>HlJv@HVt{^9} z6nkQ5pBD|(B}I8V1%i;j~~3I2X2mBm!u z*1?rtK(qgQ8{j9e1HADz#!4{;GI$i25KwIRYi_SOi*lWODA2h=CzjvfR^e4{wSOazlA77?YHNLUGV@dT&au}Vx;a0 zbTCazuyoP_q-(xA`&(r()i}ktQ8nPVH zzd^#{Z*Xv}GUkZ7)tNd&Lwhu1$9w2zIC z0j~WG;M-%sG>BtGrL^5FmFSRGdcYWnau#H`eUPQ2vaZNag<$z()xtoS=me759_RDBB zb?V#A?fzR)Hj#a$z@$60U~&eCbEe;^L=NaO7>4HphZLpi>Qc;L)KBfCw?r#P$CP+1 zfyZpL_*x|BT9o{6^V9*XS%powDZgaYPNOM4CbJW-ElxH4a$TaM1z1e(ZOq-c#R}@5 znZLdV@U>2THKi2AxGD#dAXPwIu2|(lAWo=hfj%RQwn2nMNmuc;;gLlt(=%~fQPg}@ zS#wet*`RF=vx=loN`Wvl+5@+Z9RbTy>kd&On%arinqiHk$AVI|(CQ+3QZWsC^XQdO z9V<*RK_y-*23!p-NWzZlh0ow7pV3^#vVvwKCt6CF5Kzfdf3(<}cvb`}S2uf~{$})j z{N`Il>-@T<*cA!lg8l}0?wTeqbz7O_=A{dGO&gE3BSJX8o$Z{F^xFpKZQcVW0CHp- zM&+VXl&Z}|d6PQuQvML-$WS*FoENFNV>@NZddg{5({0SEr5#4#q~1s`{eAaJel350 zj!sr&W$IZtUnnrkt6gv4$5s9yei;XA;@jVMpxMc3=+TeY6t z$(3>8>QiQ7M)}aeu1ORt(^64dR5@BVW~(U=iY8{onxBUVpomZViM)uopKgyz2GR8D z0!|K7)mjo}HmlNNI&lP;m{Da9lfyc&))RGyh{+Y@N|vN1$$7M>=9K;@iV%iKX$XJp zl{{530RJS@9CaWLT|L3Z$-C1>^{rOqCXlE;)89yzZ` zQT6gUwU+{YBYow0jF$NdQ`(5XS|U{u5X$U#vjcI)=q+CeDkDOoB<$`oKIFUMB>_nh zf4TsueM~j$u`snBY3Km{;$-)g3k8Z&)-OZakb*M5^S%$e*{#L@0Og2fT*LT@Osfea zf2tC>RIXN(dM1t!jpl`BTobl9N=;^@AjA}{&6B|wUG5MA~0T!ktL5KED zzv_>|&@P@B8qM5sWf7Yx1&Z3xDyUvG{yFP}iS|MDneZ4FOozHv1ya|bd`=pw?iQlp z9}-t*mzxQkR#VNazNt#DpwhW`K3TPY%SvBBA)2htGMun7D4~wZn;))pMHoLRno{<} zT_Da8D)->bXOcl(XkO~L>@r#sn-qbgF+Jo}Q4$c!a`8I4{fb-L2LPBq*ebfgYZ;1;*eZXXxYLnD)IH1xd30z#_L~j>CD=gO32F4dJg5ak8R%>iyukQWXFh zv8-;D>iJ%u?9`guttNJ1dhU~ZO|_cl9MoG-Omfsdj0%CG2M(9ZzTFoZi7;8mSQ)4O zTo6j2NC5G8z2#HIkR^2~8{6E!suHG|EiH5ifwiynslff!k|#lma=uJsi1qb#dFap~ z=)9nWi+TJ~06B_0NI%il&tPswQXJ?Q3EfhVdizJSK-P zK5O}w{^+#aT?v;?_BX>Yj2~n%6{U=+LzIUvi~_v+O@L25;d+IW>(x%}jD1bmr@$~R zAT5L_8io<6v_%w-q3i19F@U?<0RLFAUe83+?QSvQHNkU?F%gepn9!svmCBp=0P4_W zj-o(~KMx0na+%7_8aIJqn8eXgl$Oalpn>Gz%8XO#40ES^&MopR{He)hqF)ZO^_UO^06qSN*pi-&8 z)|UB7u!5#(FJN<)Grk$7h17K7r3Vrp%$IPoaQE(A(~9VH$U1b1(m0C}!?YgXjv;6{ z>0~*fuSKVFC{z}u6LCtLP-*}PGN%VHOncy4_Jkf#YSQ53C3<)%Qq@Tbm}Di { '/app/detail', '/dashboard/templateMarket', '/dashboard/[pluginGroupId]', - '/dashboard/mcpServer' + '/dashboard/mcpServer', + '/dashboard/evaluation', + '/dashboard/evaluation/create' ] }, { @@ -143,7 +145,13 @@ const Navbar = ({ unread }: { unread: number }) => { })} {...(item.link !== router.asPath ? { - onClick: () => router.push(item.link) + onClick: () => { + if (item.link.startsWith('/chat')) { + window.open(item.link, '_blank', 'noopener,noreferrer'); + return; + } + router.push(item.link); + } } : {})} > diff --git a/projects/app/src/components/Layout/navbarPhone.tsx b/projects/app/src/components/Layout/navbarPhone.tsx index 5187f8c41..e93dec95c 100644 --- a/projects/app/src/components/Layout/navbarPhone.tsx +++ b/projects/app/src/components/Layout/navbarPhone.tsx @@ -30,7 +30,9 @@ const NavbarPhone = ({ unread }: { unread: number }) => { '/app/detail', '/dashboard/templateMarket', '/dashboard/[pluginGroupId]', - '/dashboard/mcpServer' + '/dashboard/mcpServer', + '/dashboard/evaluation', + '/dashboard/evaluation/create' ], unread: 0 }, @@ -95,6 +97,10 @@ const NavbarPhone = ({ unread }: { unread: number }) => { })} onClick={() => { if (item.link === router.asPath) return; + if (item.link.startsWith('/chat')) { + window.open(item.link, '_blank'); + return; + } router.push(item.link); }} > diff --git a/projects/app/src/components/Select/AppSelect.tsx b/projects/app/src/components/Select/AppSelect.tsx new file mode 100644 index 000000000..1f075e7d3 --- /dev/null +++ b/projects/app/src/components/Select/AppSelect.tsx @@ -0,0 +1,89 @@ +import { Box, Button, Flex } from '@chakra-ui/react'; +import MyPopover from '@fastgpt/web/components/common/MyPopover'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import { useCallback, useState } from 'react'; +import { useTranslation } from 'next-i18next'; +import SelectOneResource from '../common/folder/SelectOneResource'; +import type { + GetResourceFolderListProps, + GetResourceListItemResponse +} from '@fastgpt/global/common/parentFolder/type'; +import { getMyApps } from '@/web/core/app/api'; +import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; +import Avatar from '@fastgpt/web/components/common/Avatar'; + +const AppSelect = ({ value, onSelect }: { value: string; onSelect: (id: string) => void }) => { + const [currentApp, setCurrentApp] = useState(null); + const { t } = useTranslation(); + + const getAppList = useCallback(async ({ parentId }: GetResourceFolderListProps) => { + return getMyApps({ + parentId, + type: [AppTypeEnum.folder, AppTypeEnum.simple, AppTypeEnum.workflow] + }).then((res) => + res + .filter((item) => !item.hasInteractiveNode) + .map((item) => ({ + id: item._id, + name: item.name, + avatar: item.avatar, + isFolder: item.type === AppTypeEnum.folder + })) + ); + }, []); + + return ( + } + iconSpacing={2} + h={'auto'} + _active={{ + transform: 'none' + }} + _hover={{ + borderColor: 'primary.500' + }} + borderColor={'myGray.200'} + > + + {currentApp && } + {currentApp?.name || t('common:Select_App')} + + + } + > + {({ onClose }) => ( + + { + if (!item) return; + onSelect(item.id); + setCurrentApp(item); + onClose(); + }} + server={getAppList} + /> + + )} + + ); +}; + +export default AppSelect; diff --git a/projects/app/src/components/common/folder/SelectOneResource.tsx b/projects/app/src/components/common/folder/SelectOneResource.tsx index df21a1c4f..5d8043c32 100644 --- a/projects/app/src/components/common/folder/SelectOneResource.tsx +++ b/projects/app/src/components/common/folder/SelectOneResource.tsx @@ -28,7 +28,7 @@ const SelectOneResource = ({ }: { server: (e: GetResourceFolderListProps) => Promise; value?: ParentIdType; - onSelect: (e?: string) => any; + onSelect: (e?: ResourceItemType) => any; maxH?: BoxProps['maxH']; }) => { const { t } = useTranslation(); @@ -105,7 +105,7 @@ const SelectOneResource = ({ item.open = !item.open; setDataList([...dataList]); } else { - onSelect(item.id); + onSelect(item); } } })} diff --git a/projects/app/src/components/core/app/InputGuideConfig.tsx b/projects/app/src/components/core/app/InputGuideConfig.tsx index f773d7225..23b1d63a5 100644 --- a/projects/app/src/components/core/app/InputGuideConfig.tsx +++ b/projects/app/src/components/core/app/InputGuideConfig.tsx @@ -233,7 +233,7 @@ const LexiconConfigModal = ({ appId, onClose }: { appId: string; onClose: () => title: t('common:add_success') }); } - fetchData(1); + fetchData({ init: true }); }); }, { diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/Empty.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/Empty.tsx index af3f91066..0fcb0dbbd 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/Empty.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/Empty.tsx @@ -10,7 +10,7 @@ const Empty = () => { const { data: versionIntro } = useMarkdown({ url: '/versionIntro.md' }); return ( - + {/* version intro */} 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 2a881d3ce..ef8127797 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx @@ -551,10 +551,13 @@ const ChatBox = ({ // Check node response error const responseData = mergeChatResponseData(item.responseData || []); - if (responseData[responseData.length - 1]?.error) { + const err = + responseData[responseData.length - 1]?.error || + responseData[responseData.length - 1]?.errorText; + if (err) { toast({ - title: t(getErrText(responseData[responseData.length - 1].error)), - status: 'error' + title: t(getErrText(err)), + status: 'warning' }); } @@ -960,7 +963,7 @@ const ChatBox = ({ w={'100%'} overflow={'overlay'} px={[4, 0]} - pb={3} + pb={10} > {/* chat header */} diff --git a/projects/app/src/components/core/chat/components/WholeResponseModal.tsx b/projects/app/src/components/core/chat/components/WholeResponseModal.tsx index bcb91de9a..985a8762e 100644 --- a/projects/app/src/components/core/chat/components/WholeResponseModal.tsx +++ b/projects/app/src/components/core/chat/components/WholeResponseModal.tsx @@ -88,6 +88,9 @@ export const WholeResponseContent = ({ if (isObject) { return `~~~json\n${JSON.stringify(value, null, 2)}`; } + if (typeof value === 'string') { + return t(value); + } return `${value}`; }, [isObject, value]); @@ -115,7 +118,7 @@ export const WholeResponseContent = ({ ); }, - [RowRender] + [RowRender, t] ); return activeModule ? ( @@ -176,6 +179,7 @@ export const WholeResponseContent = ({ value={activeModule?.contextTotalLen} /> + {/* ai chat */} diff --git a/projects/app/src/global/aiproxy/type.d.ts b/projects/app/src/global/aiproxy/type.d.ts index 3fa1538f5..26e3b03fa 100644 --- a/projects/app/src/global/aiproxy/type.d.ts +++ b/projects/app/src/global/aiproxy/type.d.ts @@ -51,6 +51,8 @@ export type ChannelLogListItemType = { endpoint: string; content?: string; retry_times?: number; + ttfb_milliseconds?: number; + ip: string; }; export type DashboardDataItemType = { diff --git a/projects/app/src/pageComponents/account/model/AddModelBox.tsx b/projects/app/src/pageComponents/account/model/AddModelBox.tsx index 40cd9e4c5..cd3d9738e 100644 --- a/projects/app/src/pageComponents/account/model/AddModelBox.tsx +++ b/projects/app/src/pageComponents/account/model/AddModelBox.tsx @@ -663,6 +663,16 @@ export const ModelEditModal = ({ + {feConfigs?.isPlus && ( + + {t('account_model:use_in_eval')} + + + + + + + )} diff --git a/projects/app/src/pageComponents/account/model/Log/index.tsx b/projects/app/src/pageComponents/account/model/Log/index.tsx index f74b9d297..717d335cc 100644 --- a/projects/app/src/pageComponents/account/model/Log/index.tsx +++ b/projects/app/src/pageComponents/account/model/Log/index.tsx @@ -35,18 +35,13 @@ import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time'; import MyModal from '@fastgpt/web/components/common/MyModal'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import SearchInput from '@fastgpt/web/components/common/Input/SearchInput'; -import { type ChannelLogUsageType } from '@/global/aiproxy/type'; +import type { ChannelLogListItemType } from '@/global/aiproxy/type'; -type LogDetailType = { - id: number; - request_id: string; +type LogDetailType = Omit & { channelName: string | number; model: React.JSX.Element; duration: number; request_at: string; - code: number; - usage?: ChannelLogUsageType; - endpoint: string; retry_times?: number; content?: string; @@ -151,7 +146,7 @@ const ChannelLog = ({ Tab }: { Tab: React.ReactNode }) => { const provider = getModelProvider(model?.provider); return { - id: item.id, + ...item, channelName: channelName || item.channel, model: ( @@ -161,11 +156,7 @@ const ChannelLog = ({ Tab }: { Tab: React.ReactNode }) => { ), duration: durationSecond, request_at: formatTime2YMDHMS(item.request_at), - code: item.code, - usage: item.usage, - request_id: item.request_id, - endpoint: item.endpoint, - content: item.content + ttfb_milliseconds: item.ttfb_milliseconds ? item.ttfb_milliseconds / 1000 : 0 }; }); }, [channelList, data, systemModelList]); @@ -299,7 +290,6 @@ const LogDetail = ({ data, onClose }: { data: LogDetailType; onClose: () => void const { t } = useTranslation(); const { data: detailData } = useRequest2( async () => { - console.log(data); if (data.code === 200) return data; try { const res = await getLogDetail(data.id); @@ -367,19 +357,27 @@ const LogDetail = ({ data, onClose }: { data: LogDetailType; onClose: () => void {detailData?.request_id} + Request IP + {detailData?.ip} + + {t('account_model:channel_status')} {detailData?.code} - + Endpoint {detailData?.endpoint} - + {t('account_model:channel_name')} {detailData?.channelName} + + {t('account_model:model')} + {detailData?.model} + {t('account_model:request_at')} {detailData?.request_at} @@ -389,8 +387,10 @@ const LogDetail = ({ data, onClose }: { data: LogDetailType; onClose: () => void {detailData?.duration.toFixed(2)}s - {t('account_model:model')} - {detailData?.model} + {t('account_model:model_ttfb_time')} + + {detailData.ttfb_milliseconds ? `${detailData.ttfb_milliseconds}ms` : '-'} + {t('account_model:model_tokens')} diff --git a/projects/app/src/pageComponents/account/usage/UsageDetail.tsx b/projects/app/src/pageComponents/account/usage/UsageDetail.tsx index b12677547..b3fe4c8d9 100644 --- a/projects/app/src/pageComponents/account/usage/UsageDetail.tsx +++ b/projects/app/src/pageComponents/account/usage/UsageDetail.tsx @@ -26,51 +26,64 @@ const UsageDetail = ({ usage, onClose }: { usage: UsageItemType; onClose: () => [usage.list] ); - const { hasModel, hasToken, hasInputToken, hasOutputToken, hasCharsLen, hasDuration, hasPages } = - useMemo(() => { - let hasModel = false; - let hasToken = false; - let hasInputToken = false; - let hasOutputToken = false; - let hasCharsLen = false; - let hasDuration = false; - let hasPages = false; + const { + hasModel, + hasToken, + hasInputToken, + hasOutputToken, + hasCharsLen, + hasDuration, + hasPages, + hasCount + } = useMemo(() => { + let hasModel = false; + let hasToken = false; + let hasInputToken = false; + let hasOutputToken = false; + let hasCharsLen = false; + let hasDuration = false; + let hasPages = false; + let hasCount = false; - usage.list.forEach((item) => { - if (item.model !== undefined) { - hasModel = true; - } + usage.list.forEach((item) => { + if (item.model !== undefined) { + hasModel = true; + } - if (typeof item.tokens === 'number') { - hasToken = true; - } - if (typeof item.inputTokens === 'number') { - hasInputToken = true; - } - if (typeof item.outputTokens === 'number') { - hasOutputToken = true; - } - if (typeof item.charsLength === 'number') { - hasCharsLen = true; - } - if (typeof item.duration === 'number') { - hasDuration = true; - } - if (typeof item.pages === 'number') { - hasPages = true; - } - }); + if (typeof item.tokens === 'number') { + hasToken = true; + } + if (typeof item.inputTokens === 'number') { + hasInputToken = true; + } + if (typeof item.outputTokens === 'number') { + hasOutputToken = true; + } + if (typeof item.charsLength === 'number') { + hasCharsLen = true; + } + if (typeof item.duration === 'number') { + hasDuration = true; + } + if (typeof item.pages === 'number') { + hasPages = true; + } + if (typeof item.count === 'number') { + hasCount = true; + } + }); - return { - hasModel, - hasToken, - hasInputToken, - hasOutputToken, - hasCharsLen, - hasDuration, - hasPages - }; - }, [usage.list]); + return { + hasModel, + hasToken, + hasInputToken, + hasOutputToken, + hasCharsLen, + hasDuration, + hasPages, + hasCount + }; + }, [usage.list]); return ( {hasToken && {t('account_usage:token_length')}} {hasInputToken && {t('account_usage:input_token_length')}} {hasOutputToken && {t('account_usage:output_token_length')}} + {hasCount && {t('account_usage:count')}} {hasCharsLen && {t('account_usage:text_length')}} {hasDuration && {t('account_usage:duration_seconds')}} {hasPages && {t('account_usage:pages')}} @@ -128,6 +142,7 @@ const UsageDetail = ({ usage, onClose }: { usage: UsageItemType; onClose: () => {hasToken && {item.tokens ?? '-'}} {hasInputToken && {item.inputTokens ?? '-'}} {hasOutputToken && {item.outputTokens ?? '-'}} + {hasCount && {item.count ?? '-'}} {hasCharsLen && {item.charsLength ?? '-'}} {hasDuration && {item.duration ?? '-'}} {hasPages && {item.pages ?? '-'}} diff --git a/projects/app/src/pageComponents/app/detail/Logs/DetailLogsModal.tsx b/projects/app/src/pageComponents/app/detail/Logs/DetailLogsModal.tsx index 4e778c4e8..2bde67e5c 100644 --- a/projects/app/src/pageComponents/app/detail/Logs/DetailLogsModal.tsx +++ b/projects/app/src/pageComponents/app/detail/Logs/DetailLogsModal.tsx @@ -211,7 +211,6 @@ const Render = (props: Props) => { return ( { return ( { return ( setSelectedApp(id ? { id } : undefined)} + onSelect={(item) => setSelectedApp(item ? { id: item.id } : undefined)} server={getAppList} /> diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/components/IOTitle.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/components/IOTitle.tsx index 0b0833c62..3ebccb62c 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/components/IOTitle.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/components/IOTitle.tsx @@ -1,23 +1,72 @@ import React from 'react'; -import { Box, type StackProps, HStack } from '@chakra-ui/react'; +import { Box, type StackProps, HStack, Switch, Text } from '@chakra-ui/react'; import type { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io'; import ToolParamConfig from './ToolParamConfig'; +import { useTranslation } from 'next-i18next'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '../../context'; +import { WorkflowNodeEdgeContext } from '../../context/workflowInitContext'; +import { getHandleId } from '@fastgpt/global/core/workflow/utils'; +import { Position } from 'reactflow'; const IOTitle = ({ text, inputs, nodeId, + catchError, ...props }: { text?: 'Input' | 'Output' | string; inputs?: FlowNodeInputItemType[]; nodeId?: string; + catchError?: boolean; } & StackProps) => { + const { t } = useTranslation(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); + const onEdgesChange = useContextSelector(WorkflowNodeEdgeContext, (v) => v.onEdgesChange); + const edges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.edges); + + const handleCatchErrorChange = (checked: boolean) => { + if (!nodeId) return; + + onChangeNode({ + nodeId, + type: 'attr', + key: 'catchError', + value: checked + }); + + // Delete edges + onEdgesChange([ + { + type: 'remove', + id: edges.find( + (edge) => edge.sourceHandle === getHandleId(nodeId, 'source_catch', Position.Right) + )?.id! + } + ]); + }; + return ( {text} + + {/* Error catch switch for output */} + {catchError !== undefined && ( + + + {t('workflow:error_catch')} + + handleCatchErrorChange(e.target.checked)} + /> + + )} + ); diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/index.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/index.tsx index b692f7d6a..5b7c167d1 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/index.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/index.tsx @@ -44,7 +44,7 @@ const nodeTypes: Record = { [FlowNodeTypeEnum.pluginOutput]: dynamic(() => import('./nodes/NodePluginIO/PluginOutput')), [FlowNodeTypeEnum.pluginModule]: NodeSimple, [FlowNodeTypeEnum.queryExtension]: NodeSimple, - [FlowNodeTypeEnum.agent]: dynamic(() => import('./nodes/NodeTools')), + [FlowNodeTypeEnum.agent]: dynamic(() => import('./nodes/NodeAgent')), [FlowNodeTypeEnum.stopTool]: (data: NodeProps) => ( ), diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeTools.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeAgent.tsx similarity index 63% rename from projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeTools.tsx rename to projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeAgent.tsx index dd43902f4..0a200c519 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeTools.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeAgent.tsx @@ -10,10 +10,15 @@ import { Box } from '@chakra-ui/react'; import IOTitle from '../components/IOTitle'; import MyIcon from '@fastgpt/web/components/common/Icon'; import RenderOutput from './render/RenderOutput'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '../../context'; +import CatchError from './render/RenderOutput/CatchError'; -const NodeTools = ({ data, selected }: NodeProps) => { +const NodeAgent = ({ data, selected }: NodeProps) => { const { t } = useTranslation(); - const { nodeId, inputs, outputs } = data; + const { nodeId, inputs, outputs, catchError } = data; + const splitOutput = useContextSelector(WorkflowContext, (ctx) => ctx.splitOutput); + const { successOutputs, errorOutputs } = splitOutput(outputs); return ( @@ -22,9 +27,11 @@ const NodeTools = ({ data, selected }: NodeProps) => { - - + + + {catchError && } + ) => { ); }; -export default React.memo(NodeTools); +export default React.memo(NodeAgent); diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeCode.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeCode.tsx index b3321161a..4e37ba9f5 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeCode.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeCode.tsx @@ -24,10 +24,13 @@ import { } from '@fastgpt/global/core/workflow/template/system/sandbox/constants'; import MySelect from '@fastgpt/web/components/common/MySelect'; import PopoverConfirm from '@fastgpt/web/components/common/MyPopover/PopoverConfirm'; +import CatchError from './render/RenderOutput/CatchError'; const NodeCode = ({ data, selected }: NodeProps) => { const { t } = useTranslation(); - const { nodeId, inputs, outputs } = data; + const { nodeId, inputs, outputs, catchError } = data; + const splitOutput = useContextSelector(WorkflowContext, (ctx) => ctx.splitOutput); + const { successOutputs, errorOutputs } = splitOutput(outputs); const codeType = inputs.find( (item) => item.key === NodeInputKeyEnum.codeType @@ -140,9 +143,10 @@ const NodeCode = ({ data, selected }: NodeProps) => { /> - - + + + {catchError && } ); diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeExtract/index.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeExtract/index.tsx index 2da10e3d5..ca0fd467c 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeExtract/index.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeExtract/index.tsx @@ -36,15 +36,20 @@ import { useContextSelector } from 'use-context-selector'; import { WorkflowContext } from '../../../context'; import MyIconButton from '@fastgpt/web/components/common/Icon/button'; import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; +import CatchError from '../render/RenderOutput/CatchError'; const NodeExtract = ({ data, selected }: NodeProps) => { - const { inputs, outputs, nodeId } = data; + const { inputs, outputs, nodeId, catchError } = data; const { t } = useTranslation(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs); const { isTool, commonInputs } = splitToolInputs(inputs, nodeId); + + const splitOutput = useContextSelector(WorkflowContext, (ctx) => ctx.splitOutput); + const { successOutputs, errorOutputs } = splitOutput(outputs); + const [editExtractFiled, setEditExtractField] = useState(); const CustomComponent = useMemo( @@ -156,22 +161,19 @@ const NodeExtract = ({ data, selected }: NodeProps) => { )} - <> - - - - - - <> - - - - - + + + + + + + + + {catchError && } {!!editExtractFiled && ( import('./CurlImportModal')); const HeaderAuthConfig = dynamic(() => import('@/components/common/secret/HeaderAuthConfig')); @@ -836,9 +837,11 @@ const RenderPropsItem = ({ text, num }: { text: string; num: number }) => { const NodeHttp = ({ data, selected }: NodeProps) => { const { t } = useTranslation(); - const { nodeId, inputs, outputs } = data; + const { nodeId, inputs, outputs, catchError } = data; const splitToolInputs = useContextSelector(WorkflowContext, (v) => v.splitToolInputs); const { commonInputs, isTool } = splitToolInputs(inputs, nodeId); + const splitOutput = useContextSelector(WorkflowContext, (ctx) => ctx.splitOutput); + const { successOutputs, errorOutputs } = splitOutput(outputs); const HttpMethodAndUrl = useMemoizedFn(() => ( @@ -863,22 +866,19 @@ const NodeHttp = ({ data, selected }: NodeProps) => { )} - <> - - - - - - <> - - - - - + + + + + + + + + {catchError && } ); }; diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeSimple.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeSimple.tsx index 697bce661..a65c3e4d1 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeSimple.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeSimple.tsx @@ -7,10 +7,10 @@ import RenderInput from './render/RenderInput'; import RenderOutput from './render/RenderOutput'; import RenderToolInput from './render/RenderToolInput'; import { useTranslation } from 'next-i18next'; -import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import IOTitle from '../components/IOTitle'; import { useContextSelector } from 'use-context-selector'; import { WorkflowContext } from '../../context'; +import CatchError from './render/RenderOutput/CatchError'; const NodeSimple = ({ data, @@ -20,10 +20,12 @@ const NodeSimple = ({ }: NodeProps & { minW?: string | number; maxW?: string | number }) => { const { t } = useTranslation(); const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs); - const { nodeId, inputs, outputs } = data; + const splitOutput = useContextSelector(WorkflowContext, (ctx) => ctx.splitOutput); + const { nodeId, catchError, inputs, outputs } = data; const Render = useMemo(() => { const { isTool, commonInputs } = splitToolInputs(inputs, nodeId); + const { successOutputs, errorOutputs } = splitOutput(outputs); return ( @@ -42,17 +44,30 @@ const NodeSimple = ({ )} - {outputs.filter((output) => output.type !== FlowNodeOutputTypeEnum.hidden).length > 0 && ( + {successOutputs.length > 0 && ( <> - - + + )} + {catchError && } ); - }, [splitToolInputs, inputs, nodeId, minW, maxW, selected, data, t, outputs]); + }, [ + splitToolInputs, + inputs, + nodeId, + splitOutput, + outputs, + minW, + maxW, + selected, + data, + t, + catchError + ]); return Render; }; diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/Handle/ConnectionHandle.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/Handle/ConnectionHandle.tsx index 32234b812..59c30f31b 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/Handle/ConnectionHandle.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/Handle/ConnectionHandle.tsx @@ -10,10 +10,10 @@ import { type FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node'; export const ConnectionSourceHandle = ({ nodeId, - isFoldNode + sourceType = 'source' }: { nodeId: string; - isFoldNode?: boolean; + sourceType?: 'source' | 'source_catch'; }) => { const edges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.edges); const { connectingEdge, nodeList } = useContextSelector(WorkflowContext, (ctx) => ctx); @@ -29,7 +29,7 @@ export const ConnectionSourceHandle = ({ })(); const RightHandle = (() => { - const handleId = getHandleId(nodeId, 'source', Position.Right); + const handleId = getHandleId(nodeId, sourceType, Position.Right); const rightTargetConnected = edges.some( (edge) => edge.targetHandle === getHandleId(nodeId, 'target', Position.Right) ); @@ -43,7 +43,7 @@ export const ConnectionSourceHandle = ({ - already connected */ if ( - !(isFoldNode && node?.outputs.length) && + !(node?.isFolded && node?.outputs.length) && (!node || !node?.showSourceHandle || rightTargetConnected) ) return null; @@ -62,7 +62,7 @@ export const ConnectionSourceHandle = ({ showSourceHandle, RightHandle }; - }, [connectingEdge, edges, nodeId, nodeList, isFoldNode]); + }, [nodeList, nodeId, connectingEdge, sourceType, edges]); return showSourceHandle ? <>{RightHandle} : null; }; diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/NodeCard.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/NodeCard.tsx index eecf112ed..9d4d20d1d 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/NodeCard.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/NodeCard.tsx @@ -319,11 +319,11 @@ const NodeCard = (props: Props) => { const RenderHandle = useMemo(() => { return ( <> - + ); - }, [nodeId, isFolded]); + }, [nodeId]); const RenderToolHandle = useMemo( () => node?.flowNodeType === FlowNodeTypeEnum.agent ? : null, @@ -687,7 +687,7 @@ const NodeVersion = React.memo(function NodeVersion({ node }: { node: FlowNodeIt pluginId: node.pluginId }, refreshDeps: [node.pluginId, isOpen], - disalbed: !isOpen, + disabled: !isOpen, manual: false }); diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Reference.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Reference.tsx index b3a2835bd..57da4a556 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Reference.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Reference.tsx @@ -2,10 +2,7 @@ import React, { useCallback, useEffect, useMemo } from 'react'; import type { RenderInputProps } from '../type'; import { Flex, Box, type ButtonProps, Grid } from '@chakra-ui/react'; import MyIcon from '@fastgpt/web/components/common/Icon'; -import { - computedNodeInputReference, - filterWorkflowNodeOutputsByType -} from '@/web/core/workflow/utils'; +import { getNodeAllSource, filterWorkflowNodeOutputsByType } from '@/web/core/workflow/utils'; import { useTranslation } from 'next-i18next'; import { NodeOutputKeyEnum, @@ -19,7 +16,10 @@ import type { import dynamic from 'next/dynamic'; import { useContextSelector } from 'use-context-selector'; import { WorkflowContext } from '@/pageComponents/app/detail/WorkflowComponents/context'; -import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; +import { + FlowNodeOutputTypeEnum, + FlowNodeTypeEnum +} from '@fastgpt/global/core/workflow/node/constant'; import { AppContext } from '@/pageComponents/app/detail/context'; import { WorkflowNodeEdgeContext } from '../../../../../context/workflowInitContext'; @@ -69,7 +69,7 @@ export const useReference = ({ // 获取可选的变量列表 const referenceList = useMemo(() => { - const sourceNodes = computedNodeInputReference({ + const sourceNodes = getNodeAllSource({ nodeId, nodes: nodeList, edges: edges, @@ -77,8 +77,6 @@ export const useReference = ({ t }); - if (!sourceNodes) return []; - const isArray = valueType?.includes('array'); // 转换为 select 的数据结构 @@ -93,9 +91,12 @@ export const useReference = ({ ), value: node.nodeId, children: filterWorkflowNodeOutputsByType(node.outputs, valueType) - .filter( - (output) => output.id !== NodeOutputKeyEnum.addOutputParam && output.invalid !== true - ) + .filter((output) => { + if (output.type === FlowNodeOutputTypeEnum.error) { + return node.catchError === true; + } + return output.id !== NodeOutputKeyEnum.addOutputParam && output.invalid !== true; + }) .map((output) => { return { label: t(output.label as any), diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderOutput/CatchError.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderOutput/CatchError.tsx new file mode 100644 index 000000000..7ccb7b695 --- /dev/null +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderOutput/CatchError.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import type { FlowNodeOutputItemType } from '@fastgpt/global/core/workflow/type/io.d'; +import Container from '../../../components/Container'; +import RenderOutput from '.'; +import { ConnectionSourceHandle } from '../Handle/ConnectionHandle'; +import { Box } from '@chakra-ui/react'; + +const CatchError = ({ + nodeId, + errorOutputs +}: { + nodeId: string; + errorOutputs: FlowNodeOutputItemType[]; +}) => { + return ( + + + + + + + ); +}; + +export default React.memo(CatchError); diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/context/index.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/context/index.tsx index 25fb70a4c..5787b9cb8 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/context/index.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/context/index.tsx @@ -1,5 +1,6 @@ import { postWorkflowDebug } from '@/web/core/workflow/api'; import { + adaptCatchError, checkWorkflowNodeAndConnection, compareSnapshot, storeEdge2RenderEdge, @@ -57,6 +58,7 @@ import { getAppConfigByDiff } from '@/web/core/app/diff'; import WorkflowStatusContextProvider from './workflowStatusContext'; import { type ChatItemType, type UserChatItemValueItemType } from '@fastgpt/global/core/chat/type'; import { type WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type'; +import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; /* Context @@ -201,6 +203,11 @@ type WorkflowContextType = { toolInputs: FlowNodeInputItemType[]; commonInputs: FlowNodeInputItemType[]; }; + splitOutput: (outputs: FlowNodeOutputItemType[]) => { + successOutputs: FlowNodeOutputItemType[]; + hiddenOutputs: FlowNodeOutputItemType[]; + errorOutputs: FlowNodeOutputItemType[]; + }; initData: ( e: { nodes: StoreNodeItemType[]; @@ -296,6 +303,13 @@ export const WorkflowContext = createContext({ } { throw new Error('Function not implemented.'); }, + splitOutput: function (outputs: FlowNodeOutputItemType[]): { + successOutputs: FlowNodeOutputItemType[]; + hiddenOutputs: FlowNodeOutputItemType[]; + errorOutputs: FlowNodeOutputItemType[]; + } { + throw new Error('Function not implemented.'); + }, initData: function (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[]; @@ -588,6 +602,18 @@ const WorkflowContextProvider = ({ }, [edges] ); + const splitOutput = useCallback((outputs: FlowNodeOutputItemType[]) => { + return { + successOutputs: outputs.filter( + (item) => + item.type === FlowNodeOutputTypeEnum.dynamic || + item.type === FlowNodeOutputTypeEnum.static || + item.type === FlowNodeOutputTypeEnum.source + ), + hiddenOutputs: outputs.filter((item) => item.type === FlowNodeOutputTypeEnum.hidden), + errorOutputs: outputs.filter((item) => item.type === FlowNodeOutputTypeEnum.error) + }; + }, []); /* ui flow to store data */ const { fitView } = useReactFlow(); @@ -952,68 +978,11 @@ const WorkflowContextProvider = ({ }, isInit?: boolean ) => { + adaptCatchError(e.nodes, e.edges); + const nodes = e.nodes?.map((item) => storeNode2FlowNode({ item, t })) || []; const edges = e.edges?.map((item) => storeEdge2RenderEdge({ edge: item })) || []; - // Get storage snapshot,兼容旧版正在编辑的用户,刷新后会把 local 数据存到内存并删除 - const pastSnapshot = (() => { - try { - const pastSnapshot = localStorage.getItem(`${appId}-past`); - return pastSnapshot ? (JSON.parse(pastSnapshot) as WorkflowSnapshotsType[]) : []; - } catch (error) { - return []; - } - })(); - if (isInit && pastSnapshot.length > 0) { - const defaultState = pastSnapshot[pastSnapshot.length - 1].state; - - if (pastSnapshot[0].diff && defaultState) { - // 设置旧的历史记录 - setPast( - pastSnapshot - .map((item) => { - if (item.state) { - return { - title: t(`app:app.version_initial`), - isSaved: item.isSaved, - nodes: item.state.nodes, - edges: item.state.edges, - chatConfig: item.state.chatConfig - }; - } - if (item.diff) { - const currentState = getAppConfigByDiff(defaultState, item.diff); - return { - title: item.title, - isSaved: item.isSaved, - nodes: currentState.nodes, - edges: currentState.edges, - chatConfig: currentState.chatConfig - }; - } - return undefined; - }) - .filter(Boolean) as WorkflowSnapshotsType[] - ); - - // 设置当前版本 - const targetState = getAppConfigByDiff( - pastSnapshot[pastSnapshot.length - 1].state, - pastSnapshot[0].diff - ) as WorkflowStateType; - - setNodes(targetState.nodes); - setEdges(targetState.edges); - setAppDetail((state) => ({ - ...state, - chatConfig: targetState.chatConfig - })); - - localStorage.removeItem(`${appId}-past`); - return; - } - } - // 有历史记录,直接用历史记录覆盖 if (isInit && past.length > 0) { const firstPast = past[0]; @@ -1042,7 +1011,7 @@ const WorkflowContextProvider = ({ setAppDetail((state) => ({ ...state, chatConfig: e.chatConfig as AppChatConfigType })); } }, - [appDetail.chatConfig, appId, past, setAppDetail, setEdges, setNodes, t] + [appDetail.chatConfig, past, setAppDetail, setEdges, setNodes, t] ); const value = useMemo( @@ -1076,6 +1045,7 @@ const WorkflowContextProvider = ({ // function splitToolInputs, + splitOutput, initData, flowData2StoreDataAndCheck, flowData2StoreData, @@ -1111,7 +1081,7 @@ const WorkflowContextProvider = ({ past, pushPastSnapshot, redo, - setPast, + splitOutput, splitToolInputs, undo, workflowDebugData diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/utils.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/utils.ts similarity index 89% rename from projects/app/src/pageComponents/app/detail/WorkflowComponents/utils.tsx rename to projects/app/src/pageComponents/app/detail/WorkflowComponents/utils.ts index 2ad07e67c..7632a5144 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/utils.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/utils.ts @@ -1,7 +1,10 @@ -import { computedNodeInputReference } from '@/web/core/workflow/utils'; +import { getNodeAllSource } from '@/web/core/workflow/utils'; import { type AppDetailType } from '@fastgpt/global/core/app/type'; import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; -import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; +import { + FlowNodeOutputTypeEnum, + FlowNodeTypeEnum +} from '@fastgpt/global/core/workflow/node/constant'; import type { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge'; import { type FlowNodeItemType, @@ -31,7 +34,8 @@ export const uiWorkflow2StoreWorkflow = ({ outputs: item.data.outputs, isFolded: item.data.isFolded, pluginId: item.data.pluginId, - toolConfig: item.data.toolConfig + toolConfig: item.data.toolConfig, + catchError: item.data.catchError })); // get all handle @@ -84,10 +88,6 @@ export const filterExportModules = (modules: StoreNodeItemType[]) => { return JSON.stringify(modules, null, 2); }; -export default function Dom() { - return <>; -} - export const getEditorVariables = ({ nodeId, nodeList, @@ -116,7 +116,7 @@ export const getEditorVariables = ({ } })); - const sourceNodes = computedNodeInputReference({ + const sourceNodes = getNodeAllSource({ nodeId, nodes: nodeList, edges: edges, @@ -129,12 +129,16 @@ export const getEditorVariables = ({ : sourceNodes .map((node) => { return node.outputs - .filter( - (output) => + .filter((output) => { + if (output.type === FlowNodeOutputTypeEnum.error) { + return node.catchError === true; + } + return ( !!output.label && output.invalid !== true && output.id !== NodeOutputKeyEnum.addOutputParam - ) + ); + }) .map((output) => { return { label: t((output.label as any) || ''), diff --git a/projects/app/src/pageComponents/app/evaluation/DetailModal.tsx b/projects/app/src/pageComponents/app/evaluation/DetailModal.tsx new file mode 100644 index 000000000..a2bbf8b52 --- /dev/null +++ b/projects/app/src/pageComponents/app/evaluation/DetailModal.tsx @@ -0,0 +1,578 @@ +import { useSystemStore } from '@/web/common/system/useSystemStore'; +import { + Box, + Button, + Flex, + IconButton, + ModalBody, + Textarea, + Accordion, + AccordionItem, + AccordionButton, + AccordionPanel, + AccordionIcon, + Input +} from '@chakra-ui/react'; +import { getModelFromList } from '@fastgpt/global/core/ai/model'; +import Avatar from '@fastgpt/web/components/common/Avatar'; +import MyModal from '@fastgpt/web/components/common/MyModal'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import { useTranslation } from 'next-i18next'; +import { useEffect, useMemo, useState } from 'react'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import { + deleteEvalItem, + getEvalItemsList, + retryEvalItem, + updateEvalItem +} from '@/web/core/app/api/evaluation'; +import { usePagination } from '@fastgpt/web/hooks/usePagination'; +import { downloadFetch } from '@/web/common/system/utils'; +import PopoverConfirm from '@fastgpt/web/components/common/MyPopover/PopoverConfirm'; +import { type TFunction } from 'i18next'; +import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; +import { useForm } from 'react-hook-form'; +import { + EvaluationStatusMap, + EvaluationStatusEnum +} from '@fastgpt/global/core/app/evaluation/constants'; +import type { evaluationType, listEvalItemsItem } from '@fastgpt/global/core/app/evaluation/type'; +import type { updateEvalItemBody } from '@fastgpt/global/core/app/evaluation/api'; +import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; + +const formatEvaluationStatus = (item: { status: number; errorMessage?: string }, t: TFunction) => { + if (item.errorMessage) { + return ( + + {t('dashboard_evaluation:error')} + + ); + } + + const statusConfig = { + [EvaluationStatusEnum.queuing]: { + color: 'myGray.500', + key: t('dashboard_evaluation:queuing') + }, + [EvaluationStatusEnum.evaluating]: { + color: 'primary.600', + key: t('dashboard_evaluation:evaluating') + }, + [EvaluationStatusEnum.completed]: { + color: 'green.600', + key: t('dashboard_evaluation:completed') + } + }; + + const config = statusConfig[item.status as keyof typeof statusConfig] || null; + if (!config) return null; + + return ( + + {config.key} + + ); +}; + +const EvaluationDetailModal = ({ + evalDetail, + onClose, + fetchEvalList +}: { + evalDetail: evaluationType; + onClose: () => void; + fetchEvalList: () => void; +}) => { + const { t } = useTranslation(); + const [selectedIndex, setSelectedIndex] = useState(0); + const [editing, setEditing] = useState(false); + const [pollingInterval, setPollingInterval] = useState(10000); + + const { llmModelList } = useSystemStore(); + const modelData = useMemo( + () => getModelFromList(llmModelList, evalDetail.evalModel), + [evalDetail.evalModel, llmModelList] + ); + + const { + data: evalItemsList, + Pagination, + getData: fetchData + } = usePagination(getEvalItemsList, { + pageSize: 10, + params: { + evalId: evalDetail._id + }, + pollingInterval + }); + + useEffect(() => { + const hasRunningOrErrorTasks = evalItemsList.some((item) => { + return ( + item.status === EvaluationStatusEnum.evaluating || + item.status === EvaluationStatusEnum.queuing || + !!item.errorMessage + ); + }); + setPollingInterval(hasRunningOrErrorTasks ? 10000 : 0); + }, [evalItemsList]); + + const evalItem = evalItemsList[selectedIndex]; + + const statusMap = useMemo( + () => + Object.fromEntries( + Object.entries(EvaluationStatusMap).map(([key, config]) => [ + key, + { label: t(config.name as any) } + ]) + ), + [t] + ); + + const { runAsync: exportEval, loading: isDownloading } = useRequest2(async () => { + await downloadFetch({ + url: `/api/proApi/core/app/evaluation/exportItems?evalId=${evalDetail._id}`, + filename: `${evalDetail.name}.csv`, + body: { + title: t('dashboard_evaluation:evaluation_export_title'), + statusMap + } + }); + }); + + const { runAsync: delEvalItem, loading: isLoadingDelete } = useRequest2(deleteEvalItem, { + onSuccess: () => { + fetchData(); + fetchEvalList(); + } + }); + + const { runAsync: rerunItem, loading: isLoadingRerun } = useRequest2(retryEvalItem, { + onSuccess: () => { + fetchData(); + fetchEvalList(); + } + }); + + const { runAsync: updateItem, loading: isLoadingUpdate } = useRequest2( + async (data: updateEvalItemBody) => { + await updateEvalItem({ ...data, evalItemId: evalItem.evalItemId }); + }, + { + onSuccess: () => { + fetchData(); + fetchEvalList(); + } + } + ); + + const { register, handleSubmit } = useForm(); + + return ( + <> + + + {/* Summary */} + + + + {t('dashboard_evaluation:task_name')} + + + {evalDetail?.name} + + + + + {t('dashboard_evaluation:Evaluation_model')} + + + + + {modelData?.name} + + + + + + {t('dashboard_evaluation:Evaluation_app')} + + + + + {evalDetail?.appName} + + + + + + {t('dashboard_evaluation:Progress')} + + + {evalDetail?.completedCount} + {`/${evalDetail?.totalCount}`} + {evalDetail?.errorMessage && ( + + + + {t('dashboard_evaluation:paused')} + + + + + )} + + + + + {t('dashboard_evaluation:Overall_score')} + + + {typeof evalDetail.score === 'number' ? (evalDetail.score * 100).toFixed(2) : '-'} + + + + + + + + + + {`${t('dashboard_evaluation:data_list')}: ${evalDetail?.totalCount}`} + + + + + + + + + {t('dashboard_evaluation:detail')} + + + {evalItem && ( + + {editing ? ( + + ) : ( + <> + {(evalItem.status === EvaluationStatusEnum.queuing || + !!evalItem.errorMessage) && ( + } + onClick={() => { + setEditing(true); + }} + /> + )} + {!!evalItem.errorMessage && ( + } + isLoading={isLoadingRerun} + onClick={() => { + rerunItem({ + evalItemId: evalItem.evalItemId + }); + }} + /> + )} + {(evalItem.status === EvaluationStatusEnum.queuing || + !!evalItem.errorMessage) && ( + } + isLoading={isLoadingDelete} + /> + } + type="delete" + content={t('dashboard_evaluation:comfirm_delete_item')} + onConfirm={() => delEvalItem({ evalItemId: evalItem.evalItemId })} + /> + )} + + )} + + )} + + + + + + + + {t('dashboard_evaluation:question')} + + + {t('dashboard_evaluation:stauts')} + + + {t('dashboard_evaluation:Overall_score')} + + + + + {evalItemsList.map((item: listEvalItemsItem, index: number) => { + const formattedStatus = formatEvaluationStatus(item, t); + + return ( + { + setSelectedIndex(index); + setEditing(false); + }} + > + + + + {index < 9 ? `0${index + 1}` : index + 1} + + + {item.question} + + + + + {formattedStatus} + + + {typeof item.score === 'number' ? (item.score * 100).toFixed(2) : '-'} + + + ); + })} + + + + + + + + {evalItem ? ( + + {!editing && evalItem?.errorMessage && ( + + {evalItem?.errorMessage} + + )} + {Object.keys(evalItem?.globalVariables || {}).length > 0 && ( + + + + + + {t('dashboard_evaluation:variables')} + + + + + {Object.entries(evalItem?.globalVariables || {}).map( + ([key, value], index, arr) => ( + + + {key} + + + {editing ? ( + + ) : ( + + {value} + + )} + + + ) + )} + + + + + )} + + {t('dashboard_evaluation:question')} + {editing ? ( +