Compare commits

..

41 Commits

Author SHA1 Message Date
Archer
2423a096c5 update doc (#3136) 2024-11-12 21:23:03 +08:00
Archer
f68ae33cd8 perf: outlink default value (#3134) 2024-11-12 18:30:20 +08:00
Archer
211061d122 fix: null pointer (#3130)
* fix: null pointer

* perf: not input text

* update doc url
2024-11-12 18:30:20 +08:00
Jiangween
dc8aeefa2a 更新 docSite 文档内容 (#3131) 2024-11-12 18:30:20 +08:00
Archer
cdce94a202 fix: ts (#3129) 2024-11-12 18:30:20 +08:00
Archer
5e273341dd perf: outlink config (#3128)
* update action

* perf: outlink config
2024-11-12 18:30:20 +08:00
heheer
73d28d1fc3 fix: missing subroute (#3121) 2024-11-12 18:30:19 +08:00
papapatrick
5892ded567 feat: add more share config (#3120)
* feat: add more share config

* add i18n en
2024-11-12 18:30:19 +08:00
Archer
f4e0dfc9bd 4.8.13 test (#3117)
* perf: plugin

* updat eaction
2024-11-12 18:30:19 +08:00
Archer
d758ddb47c 4.8.13 perf (#3112)
* fix: share page load title error

* update file input doc

* perf: auto add file urls

* perf: auto ser loop node offset height
2024-11-12 18:30:19 +08:00
Archer
acdf6a0dd9 fix: share page load title error (#3111) 2024-11-12 18:30:18 +08:00
Archer
0201e63cfd perf: push chat log (#3109) 2024-11-12 18:30:18 +08:00
Archer
e9f0d5dad5 4.8.13 test (#3107)
* perf: select file

* perf: drop files

* fix: imple mode adapt files
2024-11-12 18:30:18 +08:00
Archer
11134f39e4 4.8.13 test (#3106)
* perf: select file

* perf: drop files

* perf: env template
2024-11-12 18:30:18 +08:00
a.e.
a5765ae32f feat: source id prefix env (#3103) 2024-11-12 18:30:18 +08:00
Archer
72836402be fix: plugin select files and ai response check (#3104)
* fix: plugin select files and ai response check

* perf: text editor selector;tool call tip;remove invalid image url;

* perf: select file

* perf: drop files
2024-11-12 18:30:17 +08:00
Archer
8bd0749afe 4.8.13 test (#3102)
* fix: loop index;edge parent check

* perf: reference invalid check

* fix: ts
2024-11-12 18:30:17 +08:00
Archer
49aaf9b77e feat: loop start add index (#3101)
* feat: loop start add index

* update doc
2024-11-12 18:30:17 +08:00
heheer
6c6c964b8a array reference check & node ui (#3100) 2024-11-12 18:30:17 +08:00
Archer
c5022654ca 4.8.13 test (#3098)
* perf: loop node refresh

* rename context

* comment

* fix: ts

* perf: push chat log
2024-11-12 18:30:16 +08:00
a.e.
0a238845ab feat: support push chat log (#3093)
* feat: custom uid/metadata

* to: custom info

* fix: chat push latest

* feat: add chat log envs

* refactor: move timer to pushChatLog

* fix: using precise log

---------

Co-authored-by: Finley Ge <m13203533462@163.com>
2024-11-12 18:30:15 +08:00
heheer
8ede7add01 loop node dynamic height (#3092)
* loop node dynamic height

* fix

* fix
2024-11-12 18:30:15 +08:00
Archer
91645cc420 fix: http tool response (#3097) 2024-11-12 18:30:15 +08:00
Archer
4d6f736be3 fix: workflow file upload refresh (#3088) 2024-11-12 18:30:15 +08:00
Archer
f1fb85ead0 4.8.13 test (#3087)
* fix: image expired

* fix: datacard navbar ui

* perf: build action
2024-11-12 18:30:14 +08:00
heheer
34fbd5a223 feat: support sub route config (#3071)
* feat: support sub route config

* dockerfile

* fix upload

* delete unused code
2024-11-12 18:30:14 +08:00
Archer
45aa2e7374 4.8.13 test (#3085)
* perf: workflow node ui

* chat iframe url
2024-11-12 18:30:14 +08:00
Archer
f58dba2eda perf: workflow context split (#3083)
* perf: workflow context split

* perf: context
2024-11-12 18:30:14 +08:00
heheer
78ef74f902 add dispatch try catch (#3075) 2024-11-12 18:30:13 +08:00
Archer
8303933ec2 feat: View will move when workflow check error;fix: ui refresh error when continuous file upload (#3077)
* fix: plugin output check

* fix: ui refresh error when continuous file upload

* feat: View will move when workflow check error
2024-11-12 18:30:13 +08:00
heheer
2388652858 node pluginoutput check (#3074) 2024-11-12 18:30:13 +08:00
Archer
ba61c9e2e6 feat: iframe code block;perf: workflow selector type (#3076)
* feat: iframe code block

* perf: workflow selector type
2024-11-12 18:30:13 +08:00
heheer
b0a14c585d feat: support array reference multi-select (#3041)
* feat: support array reference multi-select

* fix build

* fix

* fix loop multi-select

* adjust condition

* fix get value

* array and non-array conversion

* fix plugin input

* merge func
2024-11-12 18:30:12 +08:00
heheer
0d494fde45 fix ui (#3065)
* fix ui

* fix
2024-11-12 18:30:12 +08:00
Archer
5a76b6f76d perf: dockerfile proxy (#3067) 2024-11-12 18:30:11 +08:00
Archer
e78fa26ca7 Adapt findLast api;perf: markdown zh format. (#3066)
* perf: context code

* fix: adapt findLast api

* perf: commercial plugin run error

* perf: markdown zh format
2024-11-12 18:30:11 +08:00
Finley Ge
16280e5d94 pref: slow query of full text search (#3044) 2024-11-12 18:30:11 +08:00
papapatrick
e96f09ca9f add chatType (#3060) 2024-11-12 18:30:10 +08:00
papapatrick
788d87a26f feat: add chat history time label (#3024)
* feat:add chat and logs time

* feat: add chat history time label

* code perf

* code perf

---------

Co-authored-by: 勤劳上班的卑微小张 <jiazhan.zhang@ggimage.com>
2024-11-12 18:30:10 +08:00
Archer
dc1119ca90 New file upload (#3058)
* feat: toolNode aiNode readFileNode adapt new version

* update docker-compose

* update tip

* feat: adapt new file version

* perf: file input

* fix: ts
2024-11-12 18:30:10 +08:00
heheer
9e8138e55f chore(ui): login page & workflow page (#3046)
* login page & number input & multirow select & llm select

* workflow

* adjust nodes
2024-11-12 18:30:09 +08:00
59 changed files with 493 additions and 630 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

View File

@@ -32,7 +32,7 @@ curl --location --request POST 'https://{{host}}/api/admin/initv464' \
4. 优化 - 历史记录模块。弃用旧的历史记录模块,直接在对应地方填写数值即可。 4. 优化 - 历史记录模块。弃用旧的历史记录模块,直接在对应地方填写数值即可。
5. 调整 - 知识库搜索模块 topk 逻辑,采用 MaxToken 计算,兼容不同长度的文本块 5. 调整 - 知识库搜索模块 topk 逻辑,采用 MaxToken 计算,兼容不同长度的文本块
6. 调整鉴权顺序,提高 apikey 的优先级避免cookie抢占 apikey 的鉴权。 6. 调整鉴权顺序,提高 apikey 的优先级避免cookie抢占 apikey 的鉴权。
7. 链接读取支持多选择器。参考[Web 站点同步用法](/docs/guide/knowledge_base/websync/) 7. 链接读取支持多选择器。参考[Web 站点同步用法](/docs/course/websync)
8. 修复 - 分享链接图片上传鉴权问题 8. 修复 - 分享链接图片上传鉴权问题
9. 修复 - Mongo 连接池未释放问题。 9. 修复 - Mongo 连接池未释放问题。
10. 修复 - Dataset Intro 无法更新 10. 修复 - Dataset Intro 无法更新

View File

@@ -21,10 +21,10 @@ weight: 831
## V4.6.5 功能介绍 ## V4.6.5 功能介绍
1. 新增 - [问题优化模块](/docs/guide/workbench/workflow/coreferenceresolution/) 1. 新增 - [问题优化模块](/docs/workflow/modules/coreferenceresolution/)
2. 新增 - [文本编辑模块](/docs/guide/workbench/workflow/text_editor/) 2. 新增 - [文本编辑模块](/docs/workflow/modules/text_editor/)
3. 新增 - [判断器模块](/docs/guide/workbench/workflow/tfswitch//) 3. 新增 - [判断器模块](/docs/workflow/modules/tfswitch/)
4. 新增 - [自定义反馈模块](/docs/guide/workbench/workflow/custom_feedback/) 4. 新增 - [自定义反馈模块](/docs/workflow/modules/custom_feedback/)
5. 新增 - 【内容提取】模块支持选择模型,以及字段枚举 5. 新增 - 【内容提取】模块支持选择模型,以及字段枚举
6. 优化 - docx读取兼容表格表格转markdown 6. 优化 - docx读取兼容表格表格转markdown
7. 优化 - 高级编排连接线交互 7. 优化 - 高级编排连接线交互

View File

@@ -25,7 +25,7 @@ weight: 830
1. 查看 [FastGPT 2024 RoadMap](https://github.com/labring/FastGPT?tab=readme-ov-file#-%E5%9C%A8%E7%BA%BF%E4%BD%BF%E7%94%A8) 1. 查看 [FastGPT 2024 RoadMap](https://github.com/labring/FastGPT?tab=readme-ov-file#-%E5%9C%A8%E7%BA%BF%E4%BD%BF%E7%94%A8)
2. 新增 - Http 模块请求头支持 Json 编辑器。 2. 新增 - Http 模块请求头支持 Json 编辑器。
3. 新增 - [ReRank模型部署](/docs/development/custom-models/bge-rerank/) 3. 新增 - [ReRank模型部署](/docs/development/custom-models/reranker/)
4. 新增 - 搜索方式:分离向量语义检索,全文检索和重排,通过 RRF 进行排序合并。 4. 新增 - 搜索方式:分离向量语义检索,全文检索和重排,通过 RRF 进行排序合并。
5. 优化 - 问题分类提示词id引导。测试国产商用 api 模型(百度阿里智谱讯飞)使用 Prompt 模式均可分类。 5. 优化 - 问题分类提示词id引导。测试国产商用 api 模型(百度阿里智谱讯飞)使用 Prompt 模式均可分类。
6. UI 优化未来将逐步替换新的UI设计。 6. UI 优化未来将逐步替换新的UI设计。

View File

@@ -91,7 +91,7 @@ curl --location --request POST 'https://{{host}}/api/init/v468' \
1. 新增 - 知识库搜索合并模块。 1. 新增 - 知识库搜索合并模块。
2. 新增 - 新的 Http 模块,支持更加灵活的参数传入。同时支持了输入输出自动数据类型转化,例如:接口输出的 JSON 类型会自动转成字符串类型,直接给其他模块使用。此外,还补充了一些例子,可在文档中查看。 2. 新增 - 新的 Http 模块,支持更加灵活的参数传入。同时支持了输入输出自动数据类型转化,例如:接口输出的 JSON 类型会自动转成字符串类型,直接给其他模块使用。此外,还补充了一些例子,可在文档中查看。
3. 优化 - 内容补全。将内容补全内置到【知识库搜索】中并实现了一次内容补全即可完成“指代消除”和“问题扩展”。FastGPT知识库搜索详细流程可查看[知识库搜索介绍](/docs/guide/workbench/workflow/dataset_search/) 3. 优化 - 内容补全。将内容补全内置到【知识库搜索】中并实现了一次内容补全即可完成“指代消除”和“问题扩展”。FastGPT知识库搜索详细流程可查看[知识库搜索介绍](/docs/course/data_search/)
4. 优化 - LLM 模型配置,不再区分对话、分类、提取模型。同时支持模型的默认参数,避免不同模型参数冲突,可通过`defaultConfig`传入默认的配置。 4. 优化 - LLM 模型配置,不再区分对话、分类、提取模型。同时支持模型的默认参数,避免不同模型参数冲突,可通过`defaultConfig`传入默认的配置。
5. 优化 - 流响应,参考了`ChatNextWeb`的流,更加丝滑。此外,之前提到的乱码、中断,刷新后又正常了,可能会修复) 5. 优化 - 流响应,参考了`ChatNextWeb`的流,更加丝滑。此外,之前提到的乱码、中断,刷新后又正常了,可能会修复)
6. 修复 - 语音输入文件无法上传。 6. 修复 - 语音输入文件无法上传。

View File

@@ -13,8 +13,8 @@ weight: 811
### 2. 修改镜像 ### 2. 修改镜像
- 更新 FastGPT 镜像 tag: v4.8.13-beta - 更新 FastGPT 镜像 tag: v4.8.13-alpha
- 更新 FastGPT 商业版镜像 tag: v4.8.13-beta fastgpt-pro镜像 - 更新 FastGPT 管理端镜像 tag: v4.8.13-alpha fastgpt-pro镜像
- Sandbox 镜像,可以不更新 - Sandbox 镜像,可以不更新
### 3. 调整文件上传编排 ### 3. 调整文件上传编排
@@ -39,7 +39,6 @@ weight: 811
14. 优化 - Markdown 组件自动空格,避免分割 url 中的中文。 14. 优化 - Markdown 组件自动空格,避免分割 url 中的中文。
15. 优化 - 工作流上下文拆分,性能优化。 15. 优化 - 工作流上下文拆分,性能优化。
16. 优化 - 语音播报,不支持 mediaSource 的浏览器可等待完全生成语音后输出。 16. 优化 - 语音播报,不支持 mediaSource 的浏览器可等待完全生成语音后输出。
17. 优化 - 对话引导 csv 读取,自动识别编码。 17. 修复 - Dockerfile pnpm install 支持代理。
18. 修复 - Dockerfile pnpm install 支持代理。 18. 修复 - BI 图表生成无法写入文件。同时优化其解析,支持数字类型数组。
19. 修复 - BI 图表生成无法写入文件。同时优化其解析,支持数字类型数组。 19. 修复 - 分享链接首次加载时,标题显示不正确。
20. 修复 - 分享链接首次加载时,标题显示不正确。

View File

@@ -66,7 +66,7 @@ Tips: 可以通过点击上下文按键查看完整的上下文组成,便于
FastGPT 知识库采用 QA 对(不一定都是问答格式,仅代表两个变量)的格式存储,在转义成字符串时候会根据**引用模板**来进行格式化。知识库包含多个可用变量: q, a, sourceId数据的ID, index(第n个数据), source(数据的集合名、文件名)score(距离得分0-1) 可以通过 {{q}} {{a}} {{sourceId}} {{index}} {{source}} {{score}} 按需引入。下面一个模板例子: FastGPT 知识库采用 QA 对(不一定都是问答格式,仅代表两个变量)的格式存储,在转义成字符串时候会根据**引用模板**来进行格式化。知识库包含多个可用变量: q, a, sourceId数据的ID, index(第n个数据), source(数据的集合名、文件名)score(距离得分0-1) 可以通过 {{q}} {{a}} {{sourceId}} {{index}} {{source}} {{score}} 按需引入。下面一个模板例子:
可以通过 [知识库结构讲解](/docs/guide/knowledge_base/dataset_engine/) 了解详细的知识库的结构。 可以通过 [知识库结构讲解](/docs/course/dataset_engine/) 了解详细的知识库的结构。
#### 引用模板 #### 引用模板

View File

@@ -30,5 +30,5 @@ weight: 232
{{% alert icon="🍅" context="success" %}} {{% alert icon="🍅" context="success" %}}
具体配置参数介绍可以参考: [AI参数配置说明](/docs/guide/course/ai_settings/) 具体配置参数介绍可以参考: [AI参数配置说明](/docs/course/ai_settings)
{{% /alert %}} {{% /alert %}}

View File

@@ -36,4 +36,4 @@ weight: 264
## 示例 ## 示例
- [接入谷歌搜索](/docs/use-cases/app-cases/google_search/) - [接入谷歌搜索](/docs/workflow/examples/google_search/)

View File

@@ -5,28 +5,4 @@ icon: "form_input"
draft: false draft: false
toc: true toc: true
weight: 244 weight: 244
--- ---
## 特点
- 用户交互
- 可重复添加
- 触发执行
![](/imgs/form_input1.png)
## 功能
「表单输入」节点属于用户交互节点,当触发这个节点时,对话会进入“交互”状态,会记录工作流的状态,等用户完成交互后,继续向下执行工作流
![](/imgs/form_input2.png)
比如上图中的例子,当触发表单输入节点时,对话框隐藏,对话进入“交互状态”
![](/imgs/form_input3.png)
当用户填完必填的信息并点击提交后,节点能够收集用户填写的表单信息,传递到后续的节点中使用
## 作用
能够精准收集需要的用户信息,再根据用户信息进行后续操作

View File

@@ -250,6 +250,6 @@ export default async function (ctx: FunctionContext) {
## 相关示例 ## 相关示例
- [谷歌搜索](/docs/use-cases/app-cases/google_search/) - [谷歌搜索](/docs/workflow/examples/google_search/)
- [发送飞书webhook](/docs/use-cases/app-cases/feishu_webhook/) - [发送飞书webhook](/docs/workflow/examples/feishu_webhook/)
- [实验室预约(操作数据库)](/docs/use-cases/app-cases/lab_appointment/) - [实验室预约(操作数据库)](/docs/workflow/examples/lab_appointment/)

View File

@@ -29,4 +29,4 @@ weight: 246
## 示例 ## 示例
- [接入谷歌搜索](/docs/use-cases/app-cases/google_search/) - [接入谷歌搜索](/docs/workflow/examples/google_search/)

View File

@@ -7,21 +7,20 @@ toc: true
weight: 236 weight: 236
--- ---
![](/imgs/flow-tool1.png) ![](/imgs/flow-tool1.png)
### **什么是工具** ## 什么是工具
工具可以是一个系统模块例如AI对话、知识库搜索、HTTP模块等。也可以是一个插件。 工具可以是一个系统模块例如AI对话、知识库搜索、HTTP模块等。也可以是一个插件。
工具调用可以让 LLM 更动态的决策流程而不都是固定的流程。当然缺点就是费tokens 工具调用可以让 LLM 更动态的决策流程而不都是固定的流程。当然缺点就是费tokens
### **工具的组成** ## 工具的组成
1. 工具介绍。通常是模块的介绍或插件的介绍这个介绍会告诉LLM这个工具的作用是什么。 1. 工具介绍。通常是模块的介绍或插件的介绍这个介绍会告诉LLM这个工具的作用是什么。
2. 工具参数。对于系统模块来说,工具参数已经是固定的,无需额外配置。对于插件来说,工具参数是一个可配置项。 2. 工具参数。对于系统模块来说,工具参数已经是固定的,无需额外配置。对于插件来说,工具参数是一个可配置项。
### **工具是如何运行的** ## 工具是如何运行的
要了解工具如何运行的,首先需要知道它的运行条件。 要了解工具如何运行的,首先需要知道它的运行条件。
@@ -30,57 +29,43 @@ weight: 236
结合工具的介绍、参数介绍和参数是否必须LLM会决定是否调用这个工具。有以下几种情况 结合工具的介绍、参数介绍和参数是否必须LLM会决定是否调用这个工具。有以下几种情况
1. 无参数的工具:直接根据工具介绍,决定是否需要执行。例如:获取当前时间。 1. 无参数的工具:直接根据工具介绍,决定是否需要执行。例如:获取当前时间。
2. 有参数的工具: 2. 有参数的工具:
1. 无必须的参数尽管上下文中没有适合的参数也可以调用该工具。但有时候LLM会自己伪造一个参数。 1. 无必须的参数尽管上下文中没有适合的参数也可以调用该工具。但有时候LLM会自己伪造一个参数。
2. 有必须的参数如果没有适合的参数LLM可能不会调用该工具。可以通过提示词引导用户提供参数。 2. 有必须的参数如果没有适合的参数LLM可能不会调用该工具。可以通过提示词引导用户提供参数。
#### **工具调用逻辑**
在支持`函数调用`的模型中,可以一次性调用多个工具,调用逻辑如下: 在支持`函数调用`的模型中,可以一次性调用多个工具,调用逻辑如下:
![](/imgs/flow-tool2.png) ![](/imgs/flow-tool2.png)
### **怎么用** ## 怎么用
<div style="display: flex; gap: 10px;"> | 有工具调用模块 | 无工具调用模块 |
<img src="/imgs/flow-tool3.png" alt="工具调用模块示例 3" width="40%" /> | --- | --- |
<img src="/imgs/flow-tool4.png" alt="工具调用模块示例 4" width="60%" /> | ![](/imgs/flow-tool3.png) | ![](/imgs/flow-tool4.png) |
</div>
<!-- ![](/imgs/flow-tool3.png)!![](/imgs/flow-tool4.png) --> 高级编排中,托动工具调用的连接点,可用的工具头部会出现一个菱形,可以将它与工具调用模块底部的菱形相连接。
高级编排中,拖动工具调用的连接点,可用的工具头部会出现一个菱形,可以将它与工具调用模块底部的菱形相连接 被连接的工具,会自动分离工具输入与普通的输入,并且可以编辑`描述`,可以通过调整介绍,使得该工具调用时机更加精确。对于一些内置的节点,务必修改`描述`才能让模型正常调用
被连接的工具,会自动分离工具输入与普通的输入,并且可以编辑`介绍`,可以通过调整介绍,使得该工具调用时机更加精确。
关于工具调用,如何调试仍然是一个玄学,所以建议,不要一次性增加太多工具,选择少量工具调优后再进一步尝试。 关于工具调用,如何调试仍然是一个玄学,所以建议,不要一次性增加太多工具,选择少量工具调优后再进一步尝试。
#### 用途 ## 组合节点
默认清空下工具调用节点在决定调用工具后会将工具运行的结果返回给AI让 AI 对工具运行的结果进行总结输出。有时候,如果你不需要 AI 进行进一步的总结输出,可以使用该节点,将其接入对于工具流程的末尾。 ### 工具调用终止
如下图,在执行知识库搜索后,发送给了 HTTP 请求,搜索将不会返回搜索的结果给工具调用进行 AI 总结 工具调用默认会把子流程运行的结果作为`工具结果`,返回给模型进行回答。有时候,你可能不希望模型做回答,你可以给对应子流程的末尾增加上一个`工具调用终止`节点,这样,子流程的结果就不会被返回给模型
![](/imgs/flow-tool5.png) ![alt text](/imgs/image-3.png)
### 附加节点
当您使用了工具调用节点,同时就会出现工具调用终止节点和自定义变量节点,能够进一步提升工具调用的使用体验。
#### 工具调用终止
工具调用终止可用于结束本次调用,即可以接在某个工具后面,当工作流执行到这个节点时,便会强制结束本次工具调用,不再调用其他工具,也不会再调用 AI 针对工具调用结果回答问题。
![](/imgs/flow-tool6.png)
### 自定义工具变量 ### 自定义工具变量
自定义变量可以扩展工具的变量输入,即对于一些未被视作工具参数或无法工具调用的节点,可以自定义工具变量,填上对应的参数描述,那么工具调用便会相对应的调用这个节点,进而调用其之后的工作流 工具调用的子流程运行,有时候会依赖`AI`生成的一些变量,为了简化交互流程,我们给系统内置的节点都指定了`工具变量`。然而,有些时候,你需要的变量不仅是目标流程的`首个节点`的变量,而是需要更复杂的变量,此时你可以使用`自定义工具变量`。它允许你完全自定义该`工具流程`的变量
![](/imgs/flow-tool7.png) ![alt text](/imgs/image-4.png)
### **相关示例** ## 相关示例
- [谷歌搜索](https://doc.fastgpt.in/docs/use-cases/app-cases/google_search/) - [谷歌搜索](/docs/workflow/examples/google_search/)
- [发送飞书webhook](https://doc.fastgpt.in/docs/use-cases/app-cases/feishu_webhook/) - [发送飞书webhook](/docs/workflow/examples/feishu_webhook/)

View File

@@ -11,7 +11,7 @@ weight: 509
[chatgpt-on-wechat GitHub 地址](https://github.com/zhayujie/chatgpt-on-wechat) [chatgpt-on-wechat GitHub 地址](https://github.com/zhayujie/chatgpt-on-wechat)
由于 FastGPT 的 API 接口和 OpenAI 的规范一致,可以无需变更原来的应用即可使用 FastGPT 上编排好的应用。API 使用可参考 [这篇文章](/docs/use-cases/external-integration/openapi/)。编排示例,可参考 [高级编排介绍](/docs/workflow/intro) 由于 FastGPT 的 API 接口和 OpenAI 的规范一致,可以无需变更原来的应用即可使用 FastGPT 上编排好的应用。API 使用可参考 [这篇文章](/docs/course/openapi/)。编排示例,可参考 [高级编排介绍](/docs/workflow/intro)
## 1. 获取 OpenAPI 密钥 ## 1. 获取 OpenAPI 密钥

View File

@@ -16,7 +16,7 @@ export const bucketNameMap = {
} }
}; };
export const ReadFileBaseUrl = `${process.env.FE_DOMAIN || ''}${process.env.NEXT_PUBLIC_BASE_URL || ''}/api/common/file/read`; export const ReadFileBaseUrl = `${process.env.FE_DOMAIN || ''}${process.env.NEXT_PUBLIC_BASE_URL}/api/common/file/read`;
export const documentFileType = '.txt, .docx, .csv, .xlsx, .pdf, .md, .html, .pptx'; export const documentFileType = '.txt, .docx, .csv, .xlsx, .pdf, .md, .html, .pptx';
export const imageFileType = export const imageFileType =

View File

@@ -78,15 +78,11 @@ export const getHistoryPreview = (
}; };
export const filterPublicNodeResponseData = ({ export const filterPublicNodeResponseData = ({
flowResponses = [], flowResponses = []
responseDetail = false
}: { }: {
flowResponses?: ChatHistoryItemResType[]; flowResponses?: ChatHistoryItemResType[];
responseDetail?: boolean;
}) => { }) => {
const filedList = responseDetail const filedList = ['quoteList', 'moduleType', 'pluginOutput', 'runningTime'];
? ['quoteList', 'moduleType', 'pluginOutput', 'runningTime']
: ['moduleType', 'pluginOutput', 'runningTime'];
const filterModuleTypeList: any[] = [ const filterModuleTypeList: any[] = [
FlowNodeTypeEnum.pluginModule, FlowNodeTypeEnum.pluginModule,
FlowNodeTypeEnum.datasetSearchNode, FlowNodeTypeEnum.datasetSearchNode,

View File

@@ -95,10 +95,10 @@ export const DatasetSearchModule: FlowNodeTemplateType = {
}, },
{ {
key: NodeInputKeyEnum.collectionFilterMatch, key: NodeInputKeyEnum.collectionFilterMatch,
renderTypeList: [FlowNodeInputTypeEnum.textarea, FlowNodeInputTypeEnum.reference], renderTypeList: [FlowNodeInputTypeEnum.JSONEditor, FlowNodeInputTypeEnum.reference],
label: i18nT('workflow:collection_metadata_filter'), label: i18nT('workflow:collection_metadata_filter'),
valueType: WorkflowIOValueTypeEnum.string, valueType: WorkflowIOValueTypeEnum.object,
isPro: true, isPro: true,
description: i18nT('workflow:filter_description') description: i18nT('workflow:filter_description')
} }

View File

@@ -1,11 +1,5 @@
import type { UserModelSchema } from '@fastgpt/global/support/user/type'; import type { UserModelSchema } from '@fastgpt/global/support/user/type';
import OpenAI from '@fastgpt/global/core/ai'; import OpenAI from '@fastgpt/global/core/ai';
import {
ChatCompletionCreateParamsNonStreaming,
ChatCompletionCreateParamsStreaming
} from '@fastgpt/global/core/ai/type';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { addLog } from '../../common/system/log';
export const openaiBaseUrl = process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1'; export const openaiBaseUrl = process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1';
@@ -40,55 +34,3 @@ export const getAxiosConfig = (props?: { userKey?: UserModelSchema['openaiAccoun
authorization: `Bearer ${apiKey}` authorization: `Bearer ${apiKey}`
}; };
}; };
type CompletionsBodyType =
| ChatCompletionCreateParamsNonStreaming
| ChatCompletionCreateParamsStreaming;
type InferResponseType<T extends CompletionsBodyType> =
T extends ChatCompletionCreateParamsStreaming
? OpenAI.Chat.Completions.ChatCompletionChunk
: OpenAI.Chat.Completions.ChatCompletion;
export const createChatCompletion = async <T extends CompletionsBodyType>({
body,
userKey,
timeout,
options
}: {
body: T;
userKey?: UserModelSchema['openaiAccount'];
timeout?: number;
options?: OpenAI.RequestOptions;
}): Promise<{
response: InferResponseType<T>;
isStreamResponse: boolean;
}> => {
try {
const formatTimeout = timeout ? timeout : body.stream ? 60000 : 600000;
const ai = getAIApi({
userKey,
timeout: formatTimeout
});
const response = await ai.chat.completions.create(body, options);
const isStreamResponse =
typeof response === 'object' &&
response !== null &&
('iterator' in response || 'controller' in response);
return {
response: response as InferResponseType<T>,
isStreamResponse
};
} catch (error) {
addLog.error(`LLM response error`, error);
addLog.warn(`LLM response error`, {
baseUrl: userKey?.baseUrl,
requestBody: body
});
if (userKey?.baseUrl) {
return Promise.reject(`您的 OpenAI key 出错了: ${getErrText(error)}`);
}
return Promise.reject(error);
}
};

View File

@@ -55,7 +55,7 @@ export async function getVectorsByText({ model, input, type }: GetVectorProps) {
return result; return result;
} catch (error) { } catch (error) {
addLog.error(`Embedding Error`, error); console.log(`Embedding Error`, error);
return Promise.reject(error); return Promise.reject(error);
} }

View File

@@ -1,5 +1,5 @@
import type { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type.d'; import type { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type.d';
import { createChatCompletion } from '../config'; import { getAIApi } from '../config';
import { countGptMessagesTokens } from '../../../common/string/tiktoken/index'; import { countGptMessagesTokens } from '../../../common/string/tiktoken/index';
import { loadRequestMessages } from '../../chat/utils'; import { loadRequestMessages } from '../../chat/utils';
import { llmCompletionsBodyFormat } from '../utils'; import { llmCompletionsBodyFormat } from '../utils';
@@ -29,8 +29,11 @@ export async function createQuestionGuide({
} }
]; ];
const { response: data } = await createChatCompletion({ const ai = getAIApi({
body: llmCompletionsBodyFormat( timeout: 480000
});
const data = await ai.chat.completions.create(
llmCompletionsBodyFormat(
{ {
model, model,
temperature: 0.1, temperature: 0.1,
@@ -43,7 +46,7 @@ export async function createQuestionGuide({
}, },
model model
) )
}); );
const answer = data.choices?.[0]?.message?.content || ''; const answer = data.choices?.[0]?.message?.content || '';

View File

@@ -1,7 +1,8 @@
import { replaceVariable } from '@fastgpt/global/common/string/tools'; import { replaceVariable } from '@fastgpt/global/common/string/tools';
import { createChatCompletion } from '../config'; import { getAIApi } from '../config';
import { ChatItemType } from '@fastgpt/global/core/chat/type'; import { ChatItemType } from '@fastgpt/global/core/chat/type';
import { countGptMessagesTokens } from '../../../common/string/tiktoken/index'; import { countGptMessagesTokens } from '../../../common/string/tiktoken/index';
import { ChatCompletion, ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type';
import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt'; import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
import { getLLMModel } from '../model'; import { getLLMModel } from '../model';
import { llmCompletionsBodyFormat } from '../utils'; import { llmCompletionsBodyFormat } from '../utils';
@@ -137,6 +138,10 @@ A: ${chatBg}
const modelData = getLLMModel(model); const modelData = getLLMModel(model);
const ai = getAIApi({
timeout: 480000
});
const messages = [ const messages = [
{ {
role: 'user', role: 'user',
@@ -145,19 +150,20 @@ A: ${chatBg}
histories: concatFewShot histories: concatFewShot
}) })
} }
] as any; ] as ChatCompletionMessageParam[];
const { response: result } = await createChatCompletion({ const result = (await ai.chat.completions.create(
body: llmCompletionsBodyFormat( llmCompletionsBodyFormat(
{ {
stream: false, stream: false,
model: modelData.model, model: modelData.model,
temperature: 0.01, temperature: 0.01,
// @ts-ignore
messages messages
}, },
modelData modelData
) )
}); )) as ChatCompletion;
let answer = result.choices?.[0]?.message?.content || ''; let answer = result.choices?.[0]?.message?.content || '';
if (!answer) { if (!answer) {

View File

@@ -48,17 +48,14 @@ export const computedTemperature = ({
type CompletionsBodyType = type CompletionsBodyType =
| ChatCompletionCreateParamsNonStreaming | ChatCompletionCreateParamsNonStreaming
| ChatCompletionCreateParamsStreaming; | ChatCompletionCreateParamsStreaming;
type InferCompletionsBody<T> = T extends { stream: true }
? ChatCompletionCreateParamsStreaming
: ChatCompletionCreateParamsNonStreaming;
export const llmCompletionsBodyFormat = <T extends CompletionsBodyType>( export const llmCompletionsBodyFormat = <T extends CompletionsBodyType>(
body: T, body: T,
model: string | LLMModelItemType model: string | LLMModelItemType
): InferCompletionsBody<T> => { ) => {
const modelData = typeof model === 'string' ? getLLMModel(model) : model; const modelData = typeof model === 'string' ? getLLMModel(model) : model;
if (!modelData) { if (!modelData) {
return body as InferCompletionsBody<T>; return body;
} }
const requestBody: T = { const requestBody: T = {
@@ -84,5 +81,5 @@ export const llmCompletionsBodyFormat = <T extends CompletionsBodyType>(
// console.log(requestBody); // console.log(requestBody);
return requestBody as InferCompletionsBody<T>; return requestBody;
}; };

View File

@@ -118,10 +118,7 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
let createTimeCollectionIdList: string[] | undefined = undefined; let createTimeCollectionIdList: string[] | undefined = undefined;
try { try {
const jsonMatch = const jsonMatch = json5.parse(collectionFilterMatch);
typeof collectionFilterMatch === 'object'
? collectionFilterMatch
: json5.parse(collectionFilterMatch);
// Tag // Tag
let andTags = jsonMatch?.tags?.$and as (string | null)[] | undefined; let andTags = jsonMatch?.tags?.$and as (string | null)[] | undefined;
@@ -350,7 +347,7 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
teamId: new Types.ObjectId(teamId), teamId: new Types.ObjectId(teamId),
datasetId: new Types.ObjectId(id), datasetId: new Types.ObjectId(id),
$text: { $search: jiebaSplit({ text: query }) }, $text: { $search: jiebaSplit({ text: query }) },
...(filterCollectionIdList ...(filterCollectionIdList && filterCollectionIdList.length > 0
? { ? {
collectionId: { collectionId: {
$in: filterCollectionIdList.map((id) => new Types.ObjectId(id)) $in: filterCollectionIdList.map((id) => new Types.ObjectId(id))

View File

@@ -2,7 +2,7 @@ import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
import { countMessagesTokens } from '../../../../common/string/tiktoken/index'; import { countMessagesTokens } from '../../../../common/string/tiktoken/index';
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d'; import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { createChatCompletion } from '../../../ai/config'; import { getAIApi } from '../../../ai/config';
import type { ClassifyQuestionAgentItemType } from '@fastgpt/global/core/workflow/template/system/classifyQuestion/type'; import type { ClassifyQuestionAgentItemType } from '@fastgpt/global/core/workflow/template/system/classifyQuestion/type';
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
@@ -120,8 +120,13 @@ const completions = async ({
useVision: false useVision: false
}); });
const { response: data } = await createChatCompletion({ const ai = getAIApi({
body: llmCompletionsBodyFormat( userKey: user.openaiAccount,
timeout: 480000
});
const data = await ai.chat.completions.create(
llmCompletionsBodyFormat(
{ {
model: cqModel.model, model: cqModel.model,
temperature: 0.01, temperature: 0.01,
@@ -129,9 +134,8 @@ const completions = async ({
stream: false stream: false
}, },
cqModel cqModel
), )
userKey: user.openaiAccount );
});
const answer = data.choices?.[0].message?.content || ''; const answer = data.choices?.[0].message?.content || '';
// console.log(JSON.stringify(chats2GPTMessages({ messages, reserveId: false }), null, 2)); // console.log(JSON.stringify(chats2GPTMessages({ messages, reserveId: false }), null, 2));

View File

@@ -6,7 +6,7 @@ import {
countGptMessagesTokens countGptMessagesTokens
} from '../../../../common/string/tiktoken/index'; } from '../../../../common/string/tiktoken/index';
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { createChatCompletion } from '../../../ai/config'; import { getAIApi } from '../../../ai/config';
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/workflow/template/system/contextExtract/type'; import type { ContextExtractAgentItemType } from '@fastgpt/global/core/workflow/template/system/contextExtract/type';
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
@@ -222,8 +222,13 @@ const toolChoice = async (props: ActionProps) => {
} }
]; ];
const { response } = await createChatCompletion({ const ai = getAIApi({
body: llmCompletionsBodyFormat( userKey: user.openaiAccount,
timeout: 480000
});
const response = await ai.chat.completions.create(
llmCompletionsBodyFormat(
{ {
model: extractModel.model, model: extractModel.model,
temperature: 0.01, temperature: 0.01,
@@ -232,9 +237,8 @@ const toolChoice = async (props: ActionProps) => {
tool_choice: { type: 'function', function: { name: agentFunName } } tool_choice: { type: 'function', function: { name: agentFunName } }
}, },
extractModel extractModel
), )
userKey: user.openaiAccount );
});
const arg: Record<string, any> = (() => { const arg: Record<string, any> = (() => {
try { try {
@@ -268,8 +272,13 @@ const functionCall = async (props: ActionProps) => {
const { agentFunction, filterMessages } = await getFunctionCallSchema(props); const { agentFunction, filterMessages } = await getFunctionCallSchema(props);
const functions: ChatCompletionCreateParams.Function[] = [agentFunction]; const functions: ChatCompletionCreateParams.Function[] = [agentFunction];
const { response } = await createChatCompletion({ const ai = getAIApi({
body: llmCompletionsBodyFormat( userKey: user.openaiAccount,
timeout: 480000
});
const response = await ai.chat.completions.create(
llmCompletionsBodyFormat(
{ {
model: extractModel.model, model: extractModel.model,
temperature: 0.01, temperature: 0.01,
@@ -280,9 +289,8 @@ const functionCall = async (props: ActionProps) => {
functions functions
}, },
extractModel extractModel
), )
userKey: user.openaiAccount );
});
try { try {
const arg = JSON.parse(response?.choices?.[0]?.message?.function_call?.arguments || ''); const arg = JSON.parse(response?.choices?.[0]?.message?.function_call?.arguments || '');
@@ -350,8 +358,12 @@ Human: ${content}`
useVision: false useVision: false
}); });
const { response: data } = await createChatCompletion({ const ai = getAIApi({
body: llmCompletionsBodyFormat( userKey: user.openaiAccount,
timeout: 480000
});
const data = await ai.chat.completions.create(
llmCompletionsBodyFormat(
{ {
model: extractModel.model, model: extractModel.model,
temperature: 0.01, temperature: 0.01,
@@ -359,9 +371,8 @@ Human: ${content}`
stream: false stream: false
}, },
extractModel extractModel
), )
userKey: user.openaiAccount );
});
const answer = data.choices?.[0].message?.content || ''; const answer = data.choices?.[0].message?.content || '';
// parse response // parse response

View File

@@ -1,4 +1,5 @@
import { createChatCompletion } from '../../../../ai/config'; import { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
import { getAIApi } from '../../../../ai/config';
import { filterGPTMessageByMaxTokens, loadRequestMessages } from '../../../../chat/utils'; import { filterGPTMessageByMaxTokens, loadRequestMessages } from '../../../../chat/utils';
import { import {
ChatCompletion, ChatCompletion,
@@ -21,12 +22,12 @@ import { DispatchFlowResponse, WorkflowResponseType } from '../../type';
import { countGptMessagesTokens } from '../../../../../common/string/tiktoken/index'; import { countGptMessagesTokens } from '../../../../../common/string/tiktoken/index';
import { getNanoid, sliceStrStartEnd } from '@fastgpt/global/common/string/tools'; import { getNanoid, sliceStrStartEnd } from '@fastgpt/global/common/string/tools';
import { AIChatItemType } from '@fastgpt/global/core/chat/type'; import { AIChatItemType } from '@fastgpt/global/core/chat/type';
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt'; import { chats2GPTMessages, GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { formatToolResponse, initToolCallEdges, initToolNodes } from './utils'; import { formatToolResponse, initToolCallEdges, initToolNodes } from './utils';
import { computedMaxToken, llmCompletionsBodyFormat } from '../../../../ai/utils'; import { computedMaxToken, llmCompletionsBodyFormat } from '../../../../ai/utils';
import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants'; import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants';
import { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type'; import { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants'; import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { i18nT } from '../../../../../../web/i18n/utils'; import { i18nT } from '../../../../../../web/i18n/utils';
type FunctionRunResponseType = { type FunctionRunResponseType = {
@@ -44,7 +45,7 @@ export const runToolWithFunctionCall = async (
requestOrigin, requestOrigin,
runtimeNodes, runtimeNodes,
runtimeEdges, runtimeEdges,
user, node,
stream, stream,
workflowStreamResponse, workflowStreamResponse,
params: { temperature = 0, maxToken = 4000, aiChatVision } params: { temperature = 0, maxToken = 4000, aiChatVision }
@@ -216,18 +217,17 @@ export const runToolWithFunctionCall = async (
// console.log(JSON.stringify(requestMessages, null, 2)); // console.log(JSON.stringify(requestMessages, null, 2));
/* Run llm */ /* Run llm */
const { response: aiResponse, isStreamResponse } = await createChatCompletion({ const ai = getAIApi({
body: requestBody, timeout: 480000
userKey: user.openaiAccount, });
options: { const aiResponse = await ai.chat.completions.create(requestBody, {
headers: { headers: {
Accept: 'application/json, text/plain, */*' Accept: 'application/json, text/plain, */*'
}
} }
}); });
const { answer, functionCalls } = await (async () => { const { answer, functionCalls } = await (async () => {
if (res && isStreamResponse) { if (res && stream) {
return streamResponse({ return streamResponse({
res, res,
toolNodes, toolNodes,

View File

@@ -29,7 +29,6 @@ import { getFileContentFromLinks, getHistoryFileLinks } from '../../tools/readFi
import { parseUrlToFileType } from '@fastgpt/global/common/file/tools'; import { parseUrlToFileType } from '@fastgpt/global/common/file/tools';
import { Prompt_DocumentQuote } from '@fastgpt/global/core/ai/prompt/AIChat'; import { Prompt_DocumentQuote } from '@fastgpt/global/core/ai/prompt/AIChat';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { postTextCensor } from '../../../../../common/api/requestPlusApi';
type Response = DispatchNodeResultType<{ type Response = DispatchNodeResultType<{
[NodeOutputKeyEnum.answerText]: string; [NodeOutputKeyEnum.answerText]: string;
@@ -46,7 +45,6 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
requestOrigin, requestOrigin,
chatConfig, chatConfig,
runningAppInfo: { teamId }, runningAppInfo: { teamId },
user,
params: { params: {
model, model,
systemPrompt, systemPrompt,
@@ -152,15 +150,6 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
return value; return value;
})(); })();
// censor model and system key
if (toolModel.censor && !user.openaiAccount?.key) {
await postTextCensor({
text: `${systemPrompt}
${userChatInput}
`
});
}
const { const {
toolWorkflowInteractiveResponse, toolWorkflowInteractiveResponse,
dispatchFlowResponse, // tool flow response dispatchFlowResponse, // tool flow response
@@ -228,14 +217,13 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
tokens: toolNodeTokens, tokens: toolNodeTokens,
modelType: ModelTypeEnum.llm modelType: ModelTypeEnum.llm
}); });
const toolAIUsage = user.openaiAccount?.key ? 0 : totalPoints;
// flat child tool response // flat child tool response
const childToolResponse = dispatchFlowResponse.map((item) => item.flowResponses).flat(); const childToolResponse = dispatchFlowResponse.map((item) => item.flowResponses).flat();
// concat tool usage // concat tool usage
const totalPointsUsage = const totalPointsUsage =
toolAIUsage + totalPoints +
dispatchFlowResponse.reduce((sum, item) => { dispatchFlowResponse.reduce((sum, item) => {
const childrenTotal = item.flowUsages.reduce((sum, item) => sum + item.totalPoints, 0); const childrenTotal = item.flowUsages.reduce((sum, item) => sum + item.totalPoints, 0);
return sum + childrenTotal; return sum + childrenTotal;
@@ -252,7 +240,6 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
.join(''), .join(''),
[DispatchNodeResponseKeyEnum.assistantResponses]: previewAssistantResponses, [DispatchNodeResponseKeyEnum.assistantResponses]: previewAssistantResponses,
[DispatchNodeResponseKeyEnum.nodeResponse]: { [DispatchNodeResponseKeyEnum.nodeResponse]: {
// 展示的积分消耗
totalPoints: totalPointsUsage, totalPoints: totalPointsUsage,
toolCallTokens: toolNodeTokens, toolCallTokens: toolNodeTokens,
childTotalPoints: flatUsages.reduce((sum, item) => sum + item.totalPoints, 0), childTotalPoints: flatUsages.reduce((sum, item) => sum + item.totalPoints, 0),
@@ -267,14 +254,12 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
mergeSignId: nodeId mergeSignId: nodeId
}, },
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [ [DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
// 工具调用本身的积分消耗
{ {
moduleName: name, moduleName: name,
totalPoints: toolAIUsage, totalPoints,
model: modelName, model: modelName,
tokens: toolNodeTokens tokens: toolNodeTokens
}, },
// 工具的消耗
...flatUsages ...flatUsages
], ],
[DispatchNodeResponseKeyEnum.interactive]: toolWorkflowInteractiveResponse [DispatchNodeResponseKeyEnum.interactive]: toolWorkflowInteractiveResponse

View File

@@ -1,4 +1,4 @@
import { createChatCompletion } from '../../../../ai/config'; import { getAIApi } from '../../../../ai/config';
import { filterGPTMessageByMaxTokens, loadRequestMessages } from '../../../../chat/utils'; import { filterGPTMessageByMaxTokens, loadRequestMessages } from '../../../../chat/utils';
import { import {
ChatCompletion, ChatCompletion,
@@ -52,7 +52,7 @@ export const runToolWithPromptCall = async (
requestOrigin, requestOrigin,
runtimeNodes, runtimeNodes,
runtimeEdges, runtimeEdges,
user, node,
stream, stream,
workflowStreamResponse, workflowStreamResponse,
params: { temperature = 0, maxToken = 4000, aiChatVision } params: { temperature = 0, maxToken = 4000, aiChatVision }
@@ -225,15 +225,18 @@ export const runToolWithPromptCall = async (
// console.log(JSON.stringify(requestMessages, null, 2)); // console.log(JSON.stringify(requestMessages, null, 2));
/* Run llm */ /* Run llm */
const { response: aiResponse, isStreamResponse } = await createChatCompletion({ const ai = getAIApi({
body: requestBody, timeout: 480000
userKey: user.openaiAccount, });
options: { const aiResponse = await ai.chat.completions.create(requestBody, {
headers: { headers: {
Accept: 'application/json, text/plain, */*' Accept: 'application/json, text/plain, */*'
}
} }
}); });
const isStreamResponse =
typeof aiResponse === 'object' &&
aiResponse !== null &&
('iterator' in aiResponse || 'controller' in aiResponse);
const answer = await (async () => { const answer = await (async () => {
if (res && isStreamResponse) { if (res && isStreamResponse) {

View File

@@ -1,4 +1,4 @@
import { createChatCompletion } from '../../../../ai/config'; import { getAIApi } from '../../../../ai/config';
import { filterGPTMessageByMaxTokens, loadRequestMessages } from '../../../../chat/utils'; import { filterGPTMessageByMaxTokens, loadRequestMessages } from '../../../../chat/utils';
import { import {
ChatCompletion, ChatCompletion,
@@ -92,7 +92,6 @@ export const runToolWithToolChoice = async (
runtimeNodes, runtimeNodes,
runtimeEdges, runtimeEdges,
stream, stream,
user,
workflowStreamResponse, workflowStreamResponse,
params: { temperature = 0, maxToken = 4000, aiChatVision } params: { temperature = 0, maxToken = 4000, aiChatVision }
} = workflowProps; } = workflowProps;
@@ -272,265 +271,277 @@ export const runToolWithToolChoice = async (
); );
// console.log(JSON.stringify(requestBody, null, 2), '==requestBody'); // console.log(JSON.stringify(requestBody, null, 2), '==requestBody');
/* Run llm */ /* Run llm */
const { response: aiResponse, isStreamResponse } = await createChatCompletion({ const ai = getAIApi({
body: requestBody, timeout: 480000
userKey: user.openaiAccount, });
options: {
try {
const aiResponse = await ai.chat.completions.create(requestBody, {
headers: { headers: {
Accept: 'application/json, text/plain, */*' Accept: 'application/json, text/plain, */*'
} }
} });
}); const isStreamResponse =
typeof aiResponse === 'object' &&
aiResponse !== null &&
('iterator' in aiResponse || 'controller' in aiResponse);
const { answer, toolCalls } = await (async () => { const { answer, toolCalls } = await (async () => {
if (res && isStreamResponse) { if (res && isStreamResponse) {
return streamResponse({ return streamResponse({
res, res,
workflowStreamResponse, workflowStreamResponse,
toolNodes, toolNodes,
stream: aiResponse stream: aiResponse
}); });
} else { } else {
const result = aiResponse as ChatCompletion; const result = aiResponse as ChatCompletion;
const calls = result.choices?.[0]?.message?.tool_calls || []; const calls = result.choices?.[0]?.message?.tool_calls || [];
const answer = result.choices?.[0]?.message?.content || ''; const answer = result.choices?.[0]?.message?.content || '';
// 加上name和avatar // 加上name和avatar
const toolCalls = calls.map((tool) => { const toolCalls = calls.map((tool) => {
const toolNode = toolNodes.find((item) => item.nodeId === tool.function?.name); const toolNode = toolNodes.find((item) => item.nodeId === tool.function?.name);
return { return {
...tool, ...tool,
toolName: toolNode?.name || '', toolName: toolNode?.name || '',
toolAvatar: toolNode?.avatar || '' toolAvatar: toolNode?.avatar || ''
}; };
}); });
// 不支持 stream 模式的模型的流失响应 // 不支持 stream 模式的模型的流失响应
toolCalls.forEach((tool) => { toolCalls.forEach((tool) => {
workflowStreamResponse?.({ workflowStreamResponse?.({
event: SseResponseEventEnum.toolCall, event: SseResponseEventEnum.toolCall,
data: { data: {
tool: { tool: {
id: tool.id, id: tool.id,
toolName: tool.toolName, toolName: tool.toolName,
toolAvatar: tool.toolAvatar, toolAvatar: tool.toolAvatar,
functionName: tool.function.name, functionName: tool.function.name,
params: tool.function?.arguments ?? '', params: tool.function?.arguments ?? '',
response: '' response: ''
}
} }
} });
});
});
if (answer) {
workflowStreamResponse?.({
event: SseResponseEventEnum.fastAnswer,
data: textAdaptGptResponse({
text: answer
})
});
}
return {
answer,
toolCalls: toolCalls
};
}
})();
// Run the selected tool by LLM.
const toolsRunResponse = (
await Promise.all(
toolCalls.map(async (tool) => {
const toolNode = toolNodes.find((item) => item.nodeId === tool.function?.name);
if (!toolNode) return;
const startParams = (() => {
try {
return json5.parse(tool.function.arguments);
} catch (error) {
return {};
}
})();
initToolNodes(runtimeNodes, [toolNode.nodeId], startParams);
const toolRunResponse = await dispatchWorkFlow({
...workflowProps,
isToolCall: true
});
const stringToolResponse = formatToolResponse(toolRunResponse.toolResponses);
const toolMsgParams: ChatCompletionToolMessageParam = {
tool_call_id: tool.id,
role: ChatCompletionRequestMessageRoleEnum.Tool,
name: tool.function.name,
content: stringToolResponse
};
workflowStreamResponse?.({
event: SseResponseEventEnum.toolResponse,
data: {
tool: {
id: tool.id,
toolName: '',
toolAvatar: '',
params: '',
response: sliceStrStartEnd(stringToolResponse, 5000, 5000)
}
}
}); });
if (answer) {
workflowStreamResponse?.({
event: SseResponseEventEnum.fastAnswer,
data: textAdaptGptResponse({
text: answer
})
});
}
return { return {
toolRunResponse, answer,
toolMsgParams toolCalls: toolCalls
}; };
})
)
).filter(Boolean) as ToolRunResponseType;
const flatToolsResponseData = toolsRunResponse.map((item) => item.toolRunResponse).flat();
// concat tool responses
const dispatchFlowResponse = response
? response.dispatchFlowResponse.concat(flatToolsResponseData)
: flatToolsResponseData;
if (toolCalls.length > 0 && !res?.closed) {
// Run the tool, combine its results, and perform another round of AI calls
const assistantToolMsgParams: ChatCompletionAssistantMessageParam[] = [
...(answer
? [
{
role: ChatCompletionRequestMessageRoleEnum.Assistant as 'assistant',
content: answer
}
]
: []),
{
role: ChatCompletionRequestMessageRoleEnum.Assistant,
tool_calls: toolCalls
} }
]; })();
/* // Run the selected tool by LLM.
const toolsRunResponse = (
await Promise.all(
toolCalls.map(async (tool) => {
const toolNode = toolNodes.find((item) => item.nodeId === tool.function?.name);
if (!toolNode) return;
const startParams = (() => {
try {
return json5.parse(tool.function.arguments);
} catch (error) {
return {};
}
})();
initToolNodes(runtimeNodes, [toolNode.nodeId], startParams);
const toolRunResponse = await dispatchWorkFlow({
...workflowProps,
isToolCall: true
});
const stringToolResponse = formatToolResponse(toolRunResponse.toolResponses);
const toolMsgParams: ChatCompletionToolMessageParam = {
tool_call_id: tool.id,
role: ChatCompletionRequestMessageRoleEnum.Tool,
name: tool.function.name,
content: stringToolResponse
};
workflowStreamResponse?.({
event: SseResponseEventEnum.toolResponse,
data: {
tool: {
id: tool.id,
toolName: '',
toolAvatar: '',
params: '',
response: sliceStrStartEnd(stringToolResponse, 5000, 5000)
}
}
});
return {
toolRunResponse,
toolMsgParams
};
})
)
).filter(Boolean) as ToolRunResponseType;
const flatToolsResponseData = toolsRunResponse.map((item) => item.toolRunResponse).flat();
// concat tool responses
const dispatchFlowResponse = response
? response.dispatchFlowResponse.concat(flatToolsResponseData)
: flatToolsResponseData;
if (toolCalls.length > 0 && !res?.closed) {
// Run the tool, combine its results, and perform another round of AI calls
const assistantToolMsgParams: ChatCompletionAssistantMessageParam[] = [
...(answer
? [
{
role: ChatCompletionRequestMessageRoleEnum.Assistant as 'assistant',
content: answer
}
]
: []),
{
role: ChatCompletionRequestMessageRoleEnum.Assistant,
tool_calls: toolCalls
}
];
/*
... ...
user user
assistant: tool data assistant: tool data
*/ */
const concatToolMessages = [ const concatToolMessages = [
...requestMessages, ...requestMessages,
...assistantToolMsgParams ...assistantToolMsgParams
] as ChatCompletionMessageParam[]; ] as ChatCompletionMessageParam[];
// Only toolCall tokens are counted here, Tool response tokens count towards the next reply // Only toolCall tokens are counted here, Tool response tokens count towards the next reply
const tokens = await countGptMessagesTokens(concatToolMessages, tools); const tokens = await countGptMessagesTokens(concatToolMessages, tools);
/* /*
... ...
user user
assistant: tool data assistant: tool data
tool: tool response tool: tool response
*/ */
const completeMessages = [ const completeMessages = [
...concatToolMessages, ...concatToolMessages,
...toolsRunResponse.map((item) => item?.toolMsgParams) ...toolsRunResponse.map((item) => item?.toolMsgParams)
]; ];
/* /*
Get tool node assistant response Get tool node assistant response
history assistant history assistant
current tool assistant current tool assistant
tool child assistant tool child assistant
*/ */
const toolNodeAssistant = GPTMessages2Chats([ const toolNodeAssistant = GPTMessages2Chats([
...assistantToolMsgParams, ...assistantToolMsgParams,
...toolsRunResponse.map((item) => item?.toolMsgParams) ...toolsRunResponse.map((item) => item?.toolMsgParams)
])[0] as AIChatItemType; ])[0] as AIChatItemType;
const toolChildAssistants = flatToolsResponseData const toolChildAssistants = flatToolsResponseData
.map((item) => item.assistantResponses) .map((item) => item.assistantResponses)
.flat() .flat()
.filter((item) => item.type !== ChatItemValueTypeEnum.interactive); // 交互节点留着下次记录 .filter((item) => item.type !== ChatItemValueTypeEnum.interactive); // 交互节点留着下次记录
const toolNodeAssistants = [ const toolNodeAssistants = [
...assistantResponses, ...assistantResponses,
...toolNodeAssistant.value, ...toolNodeAssistant.value,
...toolChildAssistants ...toolChildAssistants
]; ];
const runTimes = const runTimes =
(response?.runTimes || 0) + (response?.runTimes || 0) +
flatToolsResponseData.reduce((sum, item) => sum + item.runTimes, 0); flatToolsResponseData.reduce((sum, item) => sum + item.runTimes, 0);
const toolNodeTokens = response ? response.toolNodeTokens + tokens : tokens; const toolNodeTokens = response ? response.toolNodeTokens + tokens : tokens;
// Check stop signal // Check stop signal
const hasStopSignal = flatToolsResponseData.some( const hasStopSignal = flatToolsResponseData.some(
(item) => !!item.flowResponses?.find((item) => item.toolStop) (item) => !!item.flowResponses?.find((item) => item.toolStop)
); );
// Check interactive response(Only 1 interaction is reserved) // Check interactive response(Only 1 interaction is reserved)
const workflowInteractiveResponseItem = toolsRunResponse.find( const workflowInteractiveResponseItem = toolsRunResponse.find(
(item) => item.toolRunResponse.workflowInteractiveResponse (item) => item.toolRunResponse.workflowInteractiveResponse
); );
if (hasStopSignal || workflowInteractiveResponseItem) { if (hasStopSignal || workflowInteractiveResponseItem) {
// Get interactive tool data // Get interactive tool data
const workflowInteractiveResponse = const workflowInteractiveResponse =
workflowInteractiveResponseItem?.toolRunResponse.workflowInteractiveResponse; workflowInteractiveResponseItem?.toolRunResponse.workflowInteractiveResponse;
// Flashback traverses completeMessages, intercepting messages that know the first user // Flashback traverses completeMessages, intercepting messages that know the first user
const firstUserIndex = completeMessages.findLastIndex((item) => item.role === 'user'); const firstUserIndex = completeMessages.findLastIndex((item) => item.role === 'user');
const newMessages = completeMessages.slice(firstUserIndex + 1); const newMessages = completeMessages.slice(firstUserIndex + 1);
const toolWorkflowInteractiveResponse: WorkflowInteractiveResponseType | undefined = const toolWorkflowInteractiveResponse: WorkflowInteractiveResponseType | undefined =
workflowInteractiveResponse workflowInteractiveResponse
? { ? {
...workflowInteractiveResponse, ...workflowInteractiveResponse,
toolParams: { toolParams: {
entryNodeIds: workflowInteractiveResponse.entryNodeIds, entryNodeIds: workflowInteractiveResponse.entryNodeIds,
toolCallId: workflowInteractiveResponseItem?.toolMsgParams.tool_call_id, toolCallId: workflowInteractiveResponseItem?.toolMsgParams.tool_call_id,
memoryMessages: newMessages memoryMessages: newMessages
}
} }
} : undefined;
: undefined;
return {
dispatchFlowResponse,
toolNodeTokens,
completeMessages,
assistantResponses: toolNodeAssistants,
runTimes,
toolWorkflowInteractiveResponse
};
}
return runToolWithToolChoice(
{
...props,
maxRunToolTimes: maxRunToolTimes - 1,
messages: completeMessages
},
{
dispatchFlowResponse,
toolNodeTokens,
assistantResponses: toolNodeAssistants,
runTimes
}
);
} else {
// No tool is invoked, indicating that the process is over
const gptAssistantResponse: ChatCompletionAssistantMessageParam = {
role: ChatCompletionRequestMessageRoleEnum.Assistant,
content: answer
};
const completeMessages = filterMessages.concat(gptAssistantResponse);
const tokens = await countGptMessagesTokens(completeMessages, tools);
// concat tool assistant
const toolNodeAssistant = GPTMessages2Chats([gptAssistantResponse])[0] as AIChatItemType;
return { return {
dispatchFlowResponse, dispatchFlowResponse: response?.dispatchFlowResponse || [],
toolNodeTokens, toolNodeTokens: response ? response.toolNodeTokens + tokens : tokens,
completeMessages, completeMessages,
assistantResponses: toolNodeAssistants, assistantResponses: [...assistantResponses, ...toolNodeAssistant.value],
runTimes, runTimes: (response?.runTimes || 0) + 1
toolWorkflowInteractiveResponse
}; };
} }
} catch (error) {
return runToolWithToolChoice( console.log(error);
{ addLog.warn(`LLM response error`, {
...props, requestBody
maxRunToolTimes: maxRunToolTimes - 1, });
messages: completeMessages return Promise.reject(error);
},
{
dispatchFlowResponse,
toolNodeTokens,
assistantResponses: toolNodeAssistants,
runTimes
}
);
} else {
// No tool is invoked, indicating that the process is over
const gptAssistantResponse: ChatCompletionAssistantMessageParam = {
role: ChatCompletionRequestMessageRoleEnum.Assistant,
content: answer
};
const completeMessages = filterMessages.concat(gptAssistantResponse);
const tokens = await countGptMessagesTokens(completeMessages, tools);
// concat tool assistant
const toolNodeAssistant = GPTMessages2Chats([gptAssistantResponse])[0] as AIChatItemType;
return {
dispatchFlowResponse: response?.dispatchFlowResponse || [],
toolNodeTokens: response ? response.toolNodeTokens + tokens : tokens,
completeMessages,
assistantResponses: [...assistantResponses, ...toolNodeAssistant.value],
runTimes: (response?.runTimes || 0) + 1
};
} }
}; };

View File

@@ -4,7 +4,7 @@ import type { ChatItemType, UserChatItemValueItemType } from '@fastgpt/global/co
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils'; import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
import { createChatCompletion } from '../../../ai/config'; import { getAIApi } from '../../../ai/config';
import type { ChatCompletion, StreamChatType } from '@fastgpt/global/core/ai/type.d'; import type { ChatCompletion, StreamChatType } from '@fastgpt/global/core/ai/type.d';
import { formatModelChars2Points } from '../../../../support/wallet/usage/utils'; import { formatModelChars2Points } from '../../../../support/wallet/usage/utils';
import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d'; import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
@@ -138,6 +138,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
if (modelConstantsData.censor && !user.openaiAccount?.key) { if (modelConstantsData.censor && !user.openaiAccount?.key) {
return postTextCensor({ return postTextCensor({
text: `${systemPrompt} text: `${systemPrompt}
${datasetQuoteText}
${userChatInput} ${userChatInput}
` `
}); });
@@ -170,16 +171,21 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
); );
// console.log(JSON.stringify(requestBody, null, 2), '==='); // console.log(JSON.stringify(requestBody, null, 2), '===');
try { try {
const { response, isStreamResponse } = await createChatCompletion({ const ai = getAIApi({
body: requestBody,
userKey: user.openaiAccount, userKey: user.openaiAccount,
options: { timeout: 480000
headers: { });
Accept: 'application/json, text/plain, */*' const response = await ai.chat.completions.create(requestBody, {
} headers: {
Accept: 'application/json, text/plain, */*'
} }
}); });
const isStreamResponse =
typeof response === 'object' &&
response !== null &&
('iterator' in response || 'controller' in response);
const { answerText } = await (async () => { const { answerText } = await (async () => {
if (res && isStreamResponse) { if (res && isStreamResponse) {
// sse response // sse response

View File

@@ -65,17 +65,7 @@ export async function dispatchDatasetSearch(
} }
if (!userChatInput) { if (!userChatInput) {
return { return Promise.reject(i18nT('common:core.chat.error.User input empty'));
quoteQA: [],
[DispatchNodeResponseKeyEnum.nodeResponse]: {
totalPoints: 0,
query: '',
limit,
searchMode
},
nodeDispatchUsages: [],
[DispatchNodeResponseKeyEnum.toolResponses]: []
};
} }
// query extension // query extension

View File

@@ -61,14 +61,7 @@ export const readFileRawText = ({
reject(getErrText(err, 'Load file error')); reject(getErrText(err, 'Load file error'));
}; };
detectFileEncoding(file).then((encoding) => { reader.readAsText(file);
console.log(encoding);
reader.readAsText(
file,
['iso-8859-1', 'windows-1252'].includes(encoding) ? 'gb2312' : 'utf-8'
);
});
} catch (error) { } catch (error) {
reject('The browser does not support file content reading'); reject('The browser does not support file content reading');
} }
@@ -78,24 +71,6 @@ export const readFileRawText = ({
export const readCsvRawText = async ({ file }: { file: File }) => { export const readCsvRawText = async ({ file }: { file: File }) => {
const rawText = await readFileRawText({ file }); const rawText = await readFileRawText({ file });
const csvArr = Papa.parse(rawText).data as string[][]; const csvArr = Papa.parse(rawText).data as string[][];
return csvArr; return csvArr;
}; };
async function detectFileEncoding(file: File): Promise<string> {
const buffer = await loadFile2Buffer({ file });
const encoding = (() => {
const encodings = ['utf-8', 'iso-8859-1', 'windows-1252'];
for (let encoding of encodings) {
try {
const decoder = new TextDecoder(encoding, { fatal: true });
decoder.decode(buffer);
return encoding; // 如果解码成功,返回当前编码
} catch (e) {
// continue to try next encoding
}
}
return null; // 如果没有编码匹配返回null
})();
return encoding || 'utf-8';
}

View File

@@ -245,7 +245,13 @@ export const MultipleRowArraySelect = ({
onClick={() => handleSelect(item)} onClick={() => handleSelect(item)}
{...(isSelected ? { color: 'primary.600' } : {})} {...(isSelected ? { color: 'primary.600' } : {})}
> >
{showCheckbox && <Checkbox isChecked={isChecked} mr={1} />} {showCheckbox && (
<Checkbox
isChecked={isChecked}
icon={<MyIcon name={'common/check'} w={'12px'} />}
mr={1}
/>
)}
<Box>{item.label}</Box> <Box>{item.label}</Box>
</Flex> </Flex>
); );

View File

@@ -14,6 +14,7 @@ type EditorVariablePickerType = {
}; };
export type Props = Omit<BoxProps, 'resize' | 'onChange'> & { export type Props = Omit<BoxProps, 'resize' | 'onChange'> & {
height?: number;
resize?: boolean; resize?: boolean;
defaultValue?: string; defaultValue?: string;
value?: string; value?: string;
@@ -110,7 +111,7 @@ const MyEditor = ({
borderWidth={'1px'} borderWidth={'1px'}
borderRadius={'md'} borderRadius={'md'}
borderColor={'myGray.200'} borderColor={'myGray.200'}
py={1} py={2}
height={height} height={height}
position={'relative'} position={'relative'}
pl={2} pl={2}
@@ -131,8 +132,8 @@ const MyEditor = ({
{resize && ( {resize && (
<Box <Box
position={'absolute'} position={'absolute'}
right={'-2.5'} right={'-1'}
bottom={'-3.5'} bottom={'-1'}
zIndex={10} zIndex={10}
cursor={'ns-resize'} cursor={'ns-resize'}
px={'4px'} px={'4px'}

View File

@@ -19,11 +19,9 @@ const CodeEditor = (props: Props) => {
iconSrc="modal/edit" iconSrc="modal/edit"
title={t('common:code_editor')} title={t('common:code_editor')}
w={'full'} w={'full'}
h={'85vh'}
isCentered
> >
<ModalBody flex={'1 0 0'} overflow={'auto'}> <ModalBody>
<MyEditor {...props} bg={'myGray.50'} height={'100%'} /> <MyEditor {...props} bg={'myGray.50'} defaultHeight={600} />
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button mr={2} onClick={onClose} px={6}> <Button mr={2} onClick={onClose} px={6}>

View File

@@ -12,27 +12,25 @@ const LANG_KEY = 'NEXT_LOCALE';
export const useI18nLng = () => { export const useI18nLng = () => {
const { i18n } = useTranslation(); const { i18n } = useTranslation();
const languageMap: Record<string, string> = {
zh: 'zh',
'zh-CN': 'zh',
'zh-Hans': 'zh',
en: 'en',
'en-US': 'en'
};
const onChangeLng = (lng: string) => { const onChangeLng = (lng: string) => {
const lang = languageMap[lng] || 'en'; setCookie(LANG_KEY, lng, {
expires: 30,
setCookie(LANG_KEY, lang, { sameSite: 'None',
expires: 30 secure: true
}); });
i18n?.changeLanguage(lang); i18n?.changeLanguage(lng);
}; };
const setUserDefaultLng = () => { const setUserDefaultLng = () => {
if (!navigator || !localStorage) return; if (!navigator || !localStorage) return;
if (getCookie(LANG_KEY)) return onChangeLng(getCookie(LANG_KEY) as string); if (getCookie(LANG_KEY)) return onChangeLng(getCookie(LANG_KEY) as string);
const languageMap: Record<string, string> = {
zh: 'zh',
'zh-CN': 'zh'
};
const lang = languageMap[navigator.language] || 'en'; const lang = languageMap[navigator.language] || 'en';
// currentLng not in userLang // currentLng not in userLang

View File

@@ -11,14 +11,11 @@ export const useWidthVariable = <T = any>({
}) => { }) => {
const value = useMemo(() => { const value = useMemo(() => {
// 根据 width 计算,找到第一个大于 width 的值 // 根据 width 计算,找到第一个大于 width 的值
const reversedWidthList = [...widthList].reverse(); const index = widthList.findLastIndex((item) => width > item);
const reversedList = [...list].reverse();
const index = reversedWidthList.findIndex((item) => width > item);
if (index === -1) { if (index === -1) {
return reversedList[0]; return list[0];
} }
return reversedList[index]; return list[index];
}, [list, width, widthList]); }, [list, width, widthList]);
return value; return value;

View File

@@ -206,7 +206,12 @@ const DatasetParamsModal = ({
</Box> </Box>
</Box> </Box>
<Box position={'relative'} w={'18px'} h={'18px'}> <Box position={'relative'} w={'18px'} h={'18px'}>
<Checkbox colorScheme="primary" isChecked={getValues('usingReRank')} size="lg" /> <Checkbox
colorScheme="primary"
isChecked={getValues('usingReRank')}
size="lg"
icon={<MyIcon name={'common/check'} w={'12px'} />}
/>
<Box position={'absolute'} top={0} right={0} bottom={0} left={0} zIndex={1}></Box> <Box position={'absolute'} top={0} right={0} bottom={0} left={0} zIndex={1}></Box>
</Box> </Box>
</Flex> </Flex>

View File

@@ -30,7 +30,6 @@ const ResponseTags = ({
const { t } = useTranslation(); const { t } = useTranslation();
const quoteListRef = React.useRef<HTMLDivElement>(null); const quoteListRef = React.useRef<HTMLDivElement>(null);
const dataId = historyItem.dataId; const dataId = historyItem.dataId;
const { const {
totalQuoteList: quoteList = [], totalQuoteList: quoteList = [],
llmModuleAccount = 0, llmModuleAccount = 0,

View File

@@ -117,12 +117,11 @@ function AddMemberModal({ onClose, mode = 'member' }: AddModalPropsType) {
<Flex flexDirection="column" mt="2" overflow={'auto'} maxH="400px"> <Flex flexDirection="column" mt="2" overflow={'auto'} maxH="400px">
{filterGroups.map((group) => { {filterGroups.map((group) => {
const onChange = () => { const onChange = () => {
setSelectedGroupIdList((state) => { if (selectedGroupIdList.includes(group._id)) {
if (state.includes(group._id)) { setSelectedGroupIdList(selectedGroupIdList.filter((v) => v !== group._id));
return state.filter((v) => v !== group._id); } else {
} setSelectedGroupIdList([...selectedGroupIdList, group._id]);
return [...state, group._id]; }
});
}; };
const collaborator = collaboratorList.find((v) => v.groupId === group._id); const collaborator = collaboratorList.find((v) => v.groupId === group._id);
return ( return (
@@ -142,7 +141,10 @@ function AddMemberModal({ onClose, mode = 'member' }: AddModalPropsType) {
}} }}
onClick={onChange} onClick={onChange}
> >
<Checkbox isChecked={selectedGroupIdList.includes(group._id)} /> <Checkbox
isChecked={selectedGroupIdList.includes(group._id)}
icon={<MyIcon name={'common/check'} w={'12px'} />}
/>
<MyAvatar src={group.avatar} w="1.5rem" borderRadius={'50%'} /> <MyAvatar src={group.avatar} w="1.5rem" borderRadius={'50%'} />
<Box ml="2" w="full"> <Box ml="2" w="full">
{group.name === DefaultGroupName ? userInfo?.team.teamName : group.name} {group.name === DefaultGroupName ? userInfo?.team.teamName : group.name}
@@ -155,12 +157,11 @@ function AddMemberModal({ onClose, mode = 'member' }: AddModalPropsType) {
})} })}
{filterMembers.map((member) => { {filterMembers.map((member) => {
const onChange = () => { const onChange = () => {
setSelectedMembers((state) => { if (selectedMemberIdList.includes(member.tmbId)) {
if (state.includes(member.tmbId)) { setSelectedMembers(selectedMemberIdList.filter((v) => v !== member.tmbId));
return state.filter((v) => v !== member.tmbId); } else {
} setSelectedMembers([...selectedMemberIdList, member.tmbId]);
return [...state, member.tmbId]; }
});
}; };
const collaborator = collaboratorList.find((v) => v.tmbId === member.tmbId); const collaborator = collaboratorList.find((v) => v.tmbId === member.tmbId);
return ( return (
@@ -204,12 +205,11 @@ function AddMemberModal({ onClose, mode = 'member' }: AddModalPropsType) {
<Flex flexDirection="column" mt="2" overflow={'auto'} maxH="400px"> <Flex flexDirection="column" mt="2" overflow={'auto'} maxH="400px">
{selectedGroupIdList.map((groupId) => { {selectedGroupIdList.map((groupId) => {
const onChange = () => { const onChange = () => {
setSelectedGroupIdList((state) => { if (selectedGroupIdList.includes(groupId)) {
if (state.includes(groupId)) { setSelectedGroupIdList(selectedGroupIdList.filter((v) => v !== groupId));
return state.filter((v) => v !== groupId); } else {
} setSelectedGroupIdList([...selectedGroupIdList, groupId]);
return [...state, groupId]; }
});
}; };
const group = groups.find((v) => String(v._id) === groupId); const group = groups.find((v) => String(v._id) === groupId);
return ( return (

View File

@@ -6,15 +6,11 @@ import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
const isLLMNode = (item: ChatHistoryItemResType) => const isLLMNode = (item: ChatHistoryItemResType) =>
item.moduleType === FlowNodeTypeEnum.chatNode || item.moduleType === FlowNodeTypeEnum.tools; item.moduleType === FlowNodeTypeEnum.chatNode || item.moduleType === FlowNodeTypeEnum.tools;
export function transformPreviewHistories( export function transformPreviewHistories(histories: ChatItemType[]): ChatItemType[] {
histories: ChatItemType[],
responseDetail: boolean
): ChatItemType[] {
return histories.map((item) => { return histories.map((item) => {
return { return {
...addStatisticalDataToHistoryItem(item), ...addStatisticalDataToHistoryItem(item),
responseData: undefined, responseData: undefined
...(responseDetail ? {} : { totalQuoteList: undefined })
}; };
}); });
} }
@@ -22,7 +18,6 @@ export function transformPreviewHistories(
export function addStatisticalDataToHistoryItem(historyItem: ChatItemType) { export function addStatisticalDataToHistoryItem(historyItem: ChatItemType) {
if (historyItem.obj !== ChatRoleEnum.AI) return historyItem; if (historyItem.obj !== ChatRoleEnum.AI) return historyItem;
if (historyItem.totalQuoteList !== undefined) return historyItem; if (historyItem.totalQuoteList !== undefined) return historyItem;
if (!historyItem.responseData) return historyItem;
// Flat children // Flat children
const flatResData: ChatHistoryItemResType[] = const flatResData: ChatHistoryItemResType[] =

View File

@@ -45,7 +45,6 @@ import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import { useSystem } from '@fastgpt/web/hooks/useSystem'; import { useSystem } from '@fastgpt/web/hooks/useSystem';
import MyImage from '@fastgpt/web/components/common/Image/MyImage'; import MyImage from '@fastgpt/web/components/common/Image/MyImage';
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
const StandDetailModal = dynamic(() => import('./standardDetailModal')); const StandDetailModal = dynamic(() => import('./standardDetailModal'));
const TeamMenu = dynamic(() => import('@/components/support/user/team/TeamMenu')); const TeamMenu = dynamic(() => import('@/components/support/user/team/TeamMenu'));
@@ -495,7 +494,7 @@ const PlanUsage = () => {
</Box> </Box>
</Flex> </Flex>
<Link <Link
href={getWebReqUrl(EXTRA_PLAN_CARD_ROUTE)} href={EXTRA_PLAN_CARD_ROUTE}
transform={'translateX(15px)'} transform={'translateX(15px)'}
display={'flex'} display={'flex'}
alignItems={'center'} alignItems={'center'}

View File

@@ -82,16 +82,11 @@ async function handler(
limit: pageSize limit: pageSize
}); });
const responseDetail = !shareChat || shareChat.responseDetail;
// Remove important information // Remove important information
if (shareChat && app.type !== AppTypeEnum.plugin) { if (shareChat && app.type !== AppTypeEnum.plugin) {
histories.forEach((item) => { histories.forEach((item) => {
if (item.obj === ChatRoleEnum.AI) { if (item.obj === ChatRoleEnum.AI) {
item.responseData = filterPublicNodeResponseData({ item.responseData = filterPublicNodeResponseData({ flowResponses: item.responseData });
flowResponses: item.responseData,
responseDetail
});
if (shareChat.showNodeStatus === false) { if (shareChat.showNodeStatus === false) {
item.value = item.value.filter((v) => v.type !== ChatItemValueTypeEnum.tool); item.value = item.value.filter((v) => v.type !== ChatItemValueTypeEnum.tool);
@@ -101,7 +96,7 @@ async function handler(
} }
return { return {
list: isPlugin ? histories : transformPreviewHistories(histories, responseDetail), list: isPlugin ? histories : transformPreviewHistories(histories),
total total
}; };
} }

View File

@@ -11,7 +11,6 @@ import { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat'; import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
import { authApp } from '@fastgpt/service/support/permission/app/auth'; import { authApp } from '@fastgpt/service/support/permission/app/auth';
import { filterPublicNodeResponseData } from '@fastgpt/global/core/chat/utils'; import { filterPublicNodeResponseData } from '@fastgpt/global/core/chat/utils';
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
export type getResDataQuery = OutLinkChatAuthProps & { export type getResDataQuery = OutLinkChatAuthProps & {
chatId?: string; chatId?: string;
@@ -27,57 +26,44 @@ async function handler(
req: ApiRequestProps<getResDataBody, getResDataQuery>, req: ApiRequestProps<getResDataBody, getResDataQuery>,
res: ApiResponseType<any> res: ApiResponseType<any>
): Promise<getResDataResponse> { ): Promise<getResDataResponse> {
const { appId, chatId, dataId, shareId } = req.query; const { appId, chatId, dataId } = req.query;
if (!appId || !chatId || !dataId) { if (!appId || !chatId || !dataId) {
return {}; return {};
} }
// 1. Un login api: share chat, team chat // 1. Un login api: share chat, team chat
// 2. Login api: account chat, chat log // 2. Login api: account chat, chat log
const authData = await (() => { try {
try { await authChatCrud({
return authChatCrud({ req,
req, authToken: true,
authToken: true, authApiKey: true,
authApiKey: true, ...req.query,
...req.query, per: ReadPermissionVal
per: ReadPermissionVal });
}); } catch (error) {
} catch (error) { await authApp({
return authApp({ req,
req, authToken: true,
authToken: true, authApiKey: true,
authApiKey: true, appId,
appId, per: ManagePermissionVal
per: ManagePermissionVal });
});
}
})();
const [chatData] = await Promise.all([
MongoChatItem.findOne(
{
appId,
chatId,
dataId
},
'obj responseData'
).lean(),
shareId ? MongoOutLink.findOne({ shareId }).lean() : Promise.resolve(null)
]);
if (chatData?.obj !== ChatRoleEnum.AI) {
return {};
} }
const flowResponses = chatData.responseData ?? {}; const chatData = await MongoChatItem.findOne(
return req.query.shareId {
? filterPublicNodeResponseData({ appId,
// @ts-ignore chatId,
responseDetail: authData.responseDetail, dataId
flowResponses: chatData.responseData },
}) 'obj responseData'
: flowResponses; ).lean();
if (chatData?.obj === ChatRoleEnum.AI) {
const data = chatData.responseData || {};
return req.query.shareId ? filterPublicNodeResponseData(data) : data;
} else return {};
} }
export default NextAPI(handler); export default NextAPI(handler);

View File

@@ -363,7 +363,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
/* select fe response field */ /* select fe response field */
const feResponseData = canWrite const feResponseData = canWrite
? flowResponses ? flowResponses
: filterPublicNodeResponseData({ flowResponses, responseDetail }); : filterPublicNodeResponseData({ flowResponses });
if (stream) { if (stream) {
workflowResponseWrite({ workflowResponseWrite({
@@ -380,10 +380,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
}); });
if (detail) { if (detail) {
workflowResponseWrite({ if (responseDetail || isPlugin) {
event: SseResponseEventEnum.flowResponses, workflowResponseWrite({
data: feResponseData event: SseResponseEventEnum.flowResponses,
}); data: feResponseData
});
}
} }
res.end(); res.end();

View File

@@ -104,10 +104,7 @@ const EditForm = ({
const formatVariables = useMemo( const formatVariables = useMemo(
() => () =>
formatEditorVariablePickerIcon([ formatEditorVariablePickerIcon([
...workflowSystemVariables.filter( ...workflowSystemVariables,
(variable) =>
!['appId', 'chatId', 'responseChatItemId', 'histories'].includes(variable.key)
),
...(appForm.chatConfig.variables || []) ...(appForm.chatConfig.variables || [])
]).map((item) => ({ ]).map((item) => ({
...item, ...item,

View File

@@ -260,7 +260,7 @@ const ExtraPlan = () => {
> >
<MyNumberInput <MyNumberInput
name="points" name="points"
register={registerExtraPoints} register={registerDatasetSize}
min={0} min={0}
max={10000} max={10000}
size={'sm'} size={'sm'}

View File

@@ -1,7 +1,7 @@
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema'; import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
import { pushQAUsage } from '@/service/support/wallet/usage/push'; import { pushQAUsage } from '@/service/support/wallet/usage/push';
import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constants'; import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constants';
import { createChatCompletion } from '@fastgpt/service/core/ai/config'; import { getAIApi } from '@fastgpt/service/core/ai/config';
import type { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type.d'; import type { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type.d';
import { addLog } from '@fastgpt/service/common/system/log'; import { addLog } from '@fastgpt/service/common/system/log';
import { splitText2Chunks } from '@fastgpt/global/common/string/textSplitter'; import { splitText2Chunks } from '@fastgpt/global/common/string/textSplitter';
@@ -109,8 +109,11 @@ ${replaceVariable(Prompt_AgentQA.fixedText, { text })}`;
} }
]; ];
const { response: chatResponse } = await createChatCompletion({ const ai = getAIApi({
body: llmCompletionsBodyFormat( timeout: 600000
});
const chatResponse = await ai.chat.completions.create(
llmCompletionsBodyFormat(
{ {
model: modelData.model, model: modelData.model,
temperature: 0.3, temperature: 0.3,
@@ -119,7 +122,7 @@ ${replaceVariable(Prompt_AgentQA.fixedText, { text })}`;
}, },
modelData modelData
) )
}); );
const answer = chatResponse.choices?.[0].message?.content || ''; const answer = chatResponse.choices?.[0].message?.content || '';
const qaArr = formatSplitText(answer, text); // 格式化后的QA对 const qaArr = formatSplitText(answer, text); // 格式化后的QA对
@@ -162,7 +165,6 @@ ${replaceVariable(Prompt_AgentQA.fixedText, { text })}`;
reduceQueue(); reduceQueue();
generateQA(); generateQA();
} catch (err: any) { } catch (err: any) {
addLog.error(`[QA Queue] Error`);
reduceQueue(); reduceQueue();
if (await checkInvalidChunkAndLock({ err, data, errText: 'QA模型调用失败' })) { if (await checkInvalidChunkAndLock({ err, data, errText: 'QA模型调用失败' })) {

View File

@@ -121,7 +121,6 @@ export async function generateVector(): Promise<any> {
reduceQueue(); reduceQueue();
generateVector(); generateVector();
} catch (err: any) { } catch (err: any) {
addLog.error(`[Vector Queue] Error`, err);
reduceQueue(); reduceQueue();
if (await checkInvalidChunkAndLock({ err, data, errText: '向量模型调用失败' })) { if (await checkInvalidChunkAndLock({ err, data, errText: '向量模型调用失败' })) {

View File

@@ -42,19 +42,18 @@ export async function authChatCrud({
chat?: ChatSchema; chat?: ChatSchema;
isOutLink: boolean; isOutLink: boolean;
uid?: string; uid?: string;
responseDetail: boolean;
}> { }> {
const isOutLink = Boolean((shareId || spaceTeamId) && outLinkUid); const isOutLink = Boolean((shareId || spaceTeamId) && outLinkUid);
if (!chatId) return { isOutLink, uid: outLinkUid, responseDetail: true }; if (!chatId) return { isOutLink, uid: outLinkUid };
const chat = await MongoChat.findOne({ appId, chatId }).lean(); const chat = await MongoChat.findOne({ appId, chatId }).lean();
const { uid, responseDetail } = await (async () => { const { uid } = await (async () => {
// outLink Auth // outLink Auth
if (shareId && outLinkUid) { if (shareId && outLinkUid) {
const { uid, shareChat } = await authOutLink({ shareId, outLinkUid }); const { uid } = await authOutLink({ shareId, outLinkUid });
if (!chat || (chat.shareId === shareId && chat.outLinkUid === uid)) { if (!chat || (chat.shareId === shareId && chat.outLinkUid === uid)) {
return { uid, responseDetail: shareChat.responseDetail }; return { uid };
} }
return Promise.reject(ChatErrEnum.unAuthChat); return Promise.reject(ChatErrEnum.unAuthChat);
} }
@@ -63,12 +62,12 @@ export async function authChatCrud({
const { uid } = await authTeamSpaceToken({ teamId: spaceTeamId, teamToken }); const { uid } = await authTeamSpaceToken({ teamId: spaceTeamId, teamToken });
addLog.debug('Auth team token', { uid, spaceTeamId, teamToken, chatUid: chat?.outLinkUid }); addLog.debug('Auth team token', { uid, spaceTeamId, teamToken, chatUid: chat?.outLinkUid });
if (!chat || (String(chat.teamId) === String(spaceTeamId) && chat.outLinkUid === uid)) { if (!chat || (String(chat.teamId) === String(spaceTeamId) && chat.outLinkUid === uid)) {
return { uid, responseDetail: true }; return { uid };
} }
return Promise.reject(ChatErrEnum.unAuthChat); return Promise.reject(ChatErrEnum.unAuthChat);
} }
if (!chat) return { id: outLinkUid, responseDetail: true }; if (!chat) return { id: outLinkUid };
// auth req // auth req
const { teamId, tmbId, permission } = await authApp({ const { teamId, tmbId, permission } = await authApp({
@@ -81,19 +80,18 @@ export async function authChatCrud({
if (String(teamId) !== String(chat.teamId)) return Promise.reject(ChatErrEnum.unAuthChat); if (String(teamId) !== String(chat.teamId)) return Promise.reject(ChatErrEnum.unAuthChat);
if (permission.hasManagePer) return { uid: outLinkUid, responseDetail: true }; if (permission.hasManagePer) return { uid: outLinkUid };
if (String(tmbId) === String(chat.tmbId)) return { uid: outLinkUid, responseDetail: true }; if (String(tmbId) === String(chat.tmbId)) return { uid: outLinkUid };
return Promise.reject(ChatErrEnum.unAuthChat); return Promise.reject(ChatErrEnum.unAuthChat);
})(); })();
if (!chat) return { isOutLink, uid, responseDetail }; if (!chat) return { isOutLink, uid };
return { return {
chat, chat,
isOutLink, isOutLink,
uid, uid
responseDetail
}; };
} }