Compare commits

...

13 Commits

Author SHA1 Message Date
Cheer
435b2fba25 feat: i18n modify; (#1336) 2024-05-10 11:20:31 +08:00
Carson Yang
d61de17df2 README: update wechat qr code (#1431)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2024-05-10 11:20:12 +08:00
Archer
08a310c41f Update doc (#1430) 2024-05-10 10:13:48 +08:00
samqin123
50716ff782 Create mongodump跨环境迁移数据库.md (#1426)
* Create mongodump跨环境迁移数据库.md

* Update mongodump跨环境迁移数据库.md
2024-05-10 10:00:59 +08:00
Archer
5e250b2f65 Change embedding (#1428)
* fix: text spliter

* perf: embedding model
2024-05-09 23:23:49 +08:00
Archer
434af56abd 4.8-alpha fix (#1424) 2024-05-09 22:48:44 +08:00
Archer
6463427d93 perf: detail=false, not response variable update (#1419) 2024-05-09 16:44:52 +08:00
heheer
af4c732d93 fix: change photo max size (#1416) 2024-05-09 16:31:56 +08:00
Archer
d4169bf066 fix: embedding recall drop-dead halt (#1415) 2024-05-09 16:13:06 +08:00
Archer
afe5039cd3 update docker-compose (#1414) 2024-05-09 15:52:15 +08:00
Archer
2155489be3 update doc and fix copy node (#1399)
* update doc

* fix: copy node

* perf: adapt tip

* update doc and package

* remove code
2024-05-09 14:09:24 +08:00
imgbot[bot]
eb36b71ac3 [ImgBot] Optimize images (#1398)
*Total -- 2,413.51kb -> 1,556.03kb (35.53%)

/docSite/assets/imgs/flow-tool2.png -- 171.30kb -> 34.32kb (79.96%)
/docSite/assets/imgs/judgement1.png -- 70.87kb -> 43.74kb (38.29%)
/docSite/assets/imgs/aichat2.png -- 64.94kb -> 40.39kb (37.8%)
/docSite/assets/imgs/demo-appointment5.png -- 70.95kb -> 45.23kb (36.25%)
/docSite/assets/imgs/cq1.png -- 104.80kb -> 67.09kb (35.98%)
/docSite/assets/imgs/string.png -- 67.67kb -> 43.75kb (35.35%)
/docSite/assets/imgs/chatinput.png -- 53.12kb -> 34.58kb (34.89%)
/docSite/assets/imgs/flow-intro2.png -- 263.99kb -> 173.15kb (34.41%)
/docSite/assets/imgs/flow-tool3.png -- 216.30kb -> 143.88kb (33.48%)
/docSite/assets/imgs/specialreply.png -- 152.58kb -> 101.64kb (33.39%)
/docSite/assets/imgs/aichat02.png -- 110.38kb -> 73.73kb (33.21%)
/docSite/assets/imgs/aichat.png -- 108.55kb -> 72.68kb (33.04%)
/docSite/assets/imgs/flow-intro1.png -- 306.26kb -> 205.67kb (32.85%)
/docSite/assets/imgs/flow-intro3.png -- 105.33kb -> 71.29kb (32.32%)
/docSite/assets/imgs/flow-dataset1.png -- 91.96kb -> 62.57kb (31.96%)
/docSite/assets/imgs/flow-tool1.png -- 94.70kb -> 64.60kb (31.78%)
/docSite/assets/imgs/extract1.png -- 74.00kb -> 50.68kb (31.51%)
/docSite/assets/imgs/flow-tool4.png -- 196.36kb -> 138.21kb (29.62%)
/docSite/assets/imgs/laf4.png -- 89.45kb -> 88.83kb (0.69%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
Co-authored-by: ImgBotApp <ImgBotHelp@gmail.com>
2024-05-08 23:02:27 +08:00
左风
2230bc40c5 Doc: update doc (#1391)
* Doc: update doc

* Doc: update video link
2024-05-08 22:38:11 +08:00
86 changed files with 716 additions and 356 deletions

View File

@@ -122,7 +122,7 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b
wx 扫一下加入:
![](https://oss.laf.run/htr4n1-images/fastgpt-qr-code.jpg)
![](https://oss.laf.run/htr4n1-images/wechat-qr-code.jpg)
<a href="#readme">
<img src="https://img.shields.io/badge/-返回顶部-7d09f1.svg" alt="#" align="right">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 KiB

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 KiB

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

After

Width:  |  Height:  |  Size: 293 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 KiB

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -7,7 +7,7 @@ toc: true
weight: 101
---
更多使用技巧,[查看视屏教程](https://www.bilibili.com/video/BV1n34y1A7Bo/?spm_id_from=333.337.search-card.all.click&vd_source=903c2b09b7412037c2eddc6a8fb9828b)
更多使用技巧,[查看视屏教程](https://www.bilibili.com/video/BV1sH4y1T7s9)
## 知识库

View File

@@ -23,10 +23,6 @@ images: []
可以。需要准备好向量模型和LLM模型。
### 页面中可以正常回复API 报错
页面中是用 stream=true 模式所以API也需要设置 stream=true 来进行测试。部分模型接口(国产居多)非 Stream 的兼容有点垃圾。
### 其他模型没法进行问题分类/内容提取
1. 看日志。如果提示 JSON invalidnot support tool 之类的,说明该模型不支持工具调用或函数调用,需要设置`toolChoice=false``functionCall=false`就会默认走提示词模式。目前内置提示词仅针对了商业模型API进行测试。问题分类基本可用内容提取不太行。
@@ -43,12 +39,36 @@ images: []
1. 问题补全需要经过一轮AI生成。
2. 会进行3~5轮的查询如果数据库性能不足会有明显影响。
### 模型响应为空(core.chat.Chat API is error or undefined)
### 对话接口报错或返回为空(core.chat.Chat API is error or undefined)
1. 检查 key 问题curl 请求看是否正常。务必用 stream=true 模式。并且 maxToken 等相关参数尽量一致。
1. 检查 AI 的 key 问题:通过 curl 请求看是否正常。务必用 stream=true 模式。并且 maxToken 等相关参数尽量一致。
2. 如果是国内模型,可能是命中风控了。
3. 查看模型请求日志,检查出入参数是否异常。
```sh
# curl 例子。
curl --location --request POST 'https://xxx.cn/v1/chat/completions' \
--header 'Authorization: Bearer sk-xxxx' \
--header 'Content-Type: application/json' \
--data-raw '{
"model": "gpt-3.5-turbo",
"stream": true,
"temperature": 1,
"max_tokens": 3000,
"messages": [
{
"role": "user",
"content": "你是谁"
}
]
}'
```
### 页面中可以正常回复API 报错
页面中是用 stream=true 模式所以API也需要设置 stream=true 来进行测试。部分模型接口(国产居多)非 Stream 的兼容有点垃圾。
和上一个问题一样curl 测试。
### 知识库索引没有进度/索引很慢
先看日志报错信息。有以下几种情况:
@@ -77,12 +97,14 @@ images: []
OneAPI 账号的余额不足,默认 root 用户只有 200 刀,可以手动修改。
路径打开OneAPI -> 用户 -> root用户右边的编辑 -> 剩余余额调大
### xxx渠道找不到
FastGPT 模型配置文件中的 model 必须与 OneAPI 渠道中的模型对应上,否则就会提示这个错误。可检查下面内容:
1. OneAPI 中没有配置该模型渠道,或者被禁用了。
2. 修改了 FastGPT 配置文件中一部分的模型,但没有全部修改,仍有模型是 OneAPI 没配置
2. FastGPT 配置文件有 OneAPI 没有配置的模型。如果 OneAPI 没配置对应模型的,配置文件中也不要写
3. 使用旧的向量模型创建了知识库,后又更新了向量模型。这时候需要删除以前的知识库,重建。
如果OneAPI中没有配置对应的模型`config.json`中也不要配置,否则容易报错。

View File

@@ -0,0 +1,187 @@
---
weight: 762
title: "Docker Mongo迁移(dump模式)"
description: "FastGPT Docker Mongo迁移"
icon: database
draft: false
images: []
---
## 作者
[https://github.com/samqin123](https://github.com/samqin123)
[相关PR。有问题可打开这里与作者交流](https://github.com/labring/FastGPT/pull/1426)
## 介绍
如何使用Mongodump来完成从A环境到B环境的Fastgpt的mongodb迁移
前提说明:
A环境我在阿里云上部署的fastgpt现在需要迁移到B环境。
B环境是新环境比如腾讯云新部署的fastgpt更特殊一点的是NAS群晖或者QNAP部署了fastgptmongo必须改成4.2或者4.4版本其实云端更方便支持fastgpt mongo默认版本
C环境妥善考虑用本地电脑作为C环境过渡保存相关文件并分离操作
## 1. 环境准备:进入 docker mongo 【A环境】
```
docker exec -it mongo sh
mongo -u 'username' -p 'password'
>> show dbs
```
看到fastgpt数据库以及其它几个确定下导出数据库名称
准备:
检查数据库,容器和宿主机都创建一下 backup 目录 【A环境 + C环境】
##### 准备:
检查数据库,容器和宿主机都创建一下“数据导出导入”临时目录 比如data/backup 【A环境建目录 + C环境建目录用于同步到容器中】
#### 先在【A环境】创建文件目录用于dump导出操作
容器先进入fastgpt docker容器
```
docker exec -it fastgpt sh
mkdir -p /data/backup
```
建好后未来导出mongo的数据会在A环境本地fastgpt的安装目录/Data/下看到自动同步好的目录数据会在data\backup中然后可以衔接后续的压缩和下载转移动作。如果没有同步到本地也可以手动建一下配合docker cp 把文件拷到本地用(基本不会发生)
#### 然后【C环境】宿主机目录类似操作用于把上传的文件自动同步到C环境部署的fastgpt容器里。
到fastgpt目录进入mongo目录有data目录下面建backup
```
mkdir -p /fastgpt/data/backup
```
准备好后,后续上传
```
### 新fastgpt环境【B】中也需要建一个比如/fastgpt/mongobackup目录注意不要在fastgpt/data目录下建立目录
```
mkdir -p /fastgpt/mongobackup
```
###2. 正题开始从fastgpt老环境【A】中导出数据
进入A环境使用mongodump 导出mongo数据库。
#### 2.1 导出
可以使用mongodump在源头容器中导出数据文件, 导出路径为上面指定临时目录,即"data\backup"
[导出的文件在代码中指定为/data/backup因为fastgpt配置文件已经建立了data的持久化所以会同步到容器所在环境本地fast/mongo/data应该就能看到这个导出的目录backup里面有文件]
一行指令导出代码,在服务器本地环境运行,不需要进入容器。
```
docker exec -it mongo bash -c "mongodump --db fastgpt -u 'username' -p 'password' --authenticationDatabase admin --out /data/backup"
```
也可以进入环境熟手可以结合建目录一次性完成建导出目录以及使用mongodump导出数据到该目录
```
1.docker exec -it fastgpt sh
2.mkdir -p /data/backup
3. mongodump --host 127.0.0.1:27017 --db fastgpt -u "username" -p "password" --authenticationDatabase admin --out /data/backup
##### 补充万一没自动同步也可以将mongodump导出的文件手工导出到宿主机【A环境】备用指令如下
```
docker cp mongo:/data/backup <A环境本地fastgpt目录>:/fastgpt/data/backup>
```
2.2 对新手建议稳妥起见压缩这个文件目录并将压缩文件下载到本地过渡环境【A环境 -> C环境】原因是因为留存一份并且检查文件数量是否一致。
熟手可以直接复制到新部署服务器腾讯云或者NAS【A环境-> B环境】
2.2.1 先进入 【A环境】源头系统的本地环境 fastgpt/mongo/data 目录
```
cd /usr/fastgpt/mongo/data
```
#执行,压缩文件命令
```
tar -czvf ../fastgpt-mongo-backup-$(date +%Y-%m-%d).tar.gz ./ 【A环境】
```
#接下来,把压缩包下载到本地 【A环境-> C环境】以便于检查和留存版本。熟手直接将该压缩包同步到B环境中新fastgpt目录data目录下备用。
```
scp -i /Users/path/<user.pem换成你自己的pem文件链接> root@<fastgpt所在云服务器地址>:/usr/fastgpt/mongo/fastgptbackup-2024-05-03.tar.gz /<本地电脑路径>/Downloads/fastgpt
```
熟手直接换成新环境地址
```
scp -i /Users/path/<user.pem换成你自己的pem文件链接> root@<老环境fastgpt服务器地址>:/usr/fastgpt/mongo/fastgptbackup-2024-05-03.tar.gz root@<新环境fastgpt服务器地址>:/Downloads/fastgpt2
```
2.2 【C环境】检查压缩文件是否完整如果不完整重新导出。事实上我也出现过问题因为跨环境scp会出现丢数据的情况。
压缩数据包导入到C环境本地后可以考虑在宿主机目录解压缩放在一个自定义目录比如. < user/fastgpt/mongobackup/data>
```
tar -xvzf fastgptbackup-2024-05-03.tar.gz -C user/fastgpt/mongobackup/data
```
解压缩后里面是bson文件这里可以检查下压缩文件数量是否一致。如果不一致后续启动新环境的fastgpt容器也不会有任何数据。
<img width="1561" alt="image" src="https://github.com/labring/FastGPT/assets/103937568/cbb8a93c-5834-4a0d-be6c-c45c701f593e">
如果没问题准备进入下一步将压缩包文件上传到B环境也就是新fastgpt环境里的指定目录比如/fastgpt/mongobackup, 注意不要放到fastgpt/data目录下因为下面会先清空一次这个目录否则导入会报错。
```
scp -rfv <本地电脑路径>/Downloads/fastgpt/fastgptbackup-2024-05-03.tar.gz root@<新环境fastgpt服务器地址>:/Downloads/fastgpt/backup
```
## 3 导入恢复: 实际恢复和导入步骤
### 3.1. 进入新fastgpt本地环境的安装目录后找到迁移的压缩文件包fastgptbackup-2024-05-03.tar.gz解压缩到指定目录
```
tar -xvzf fastgptbackup-2024-05-03.tar.gz -C user/fastgpt/mongobackup/data
```
再次核对文件数量,和上面对比一下。
熟手可以用tar指令检查文件完整性上面是给新手准备的便于比对核查。
### 3.2 手动上传新fastgpt docker容器里备用 【C环境】
说明因为没有放在data里所以不会自动同步到容器里。而且要确保容器的data目录被清理干净否则导入时会报错。
```
docker cp user/fastgpt/mongobackup/data mongo:/tmp/backup
```
### 3.3 建议初始化一次docker compose ,运行后建立新的 mongo/data 持久化目录
如果不是初始化的 mongo/db 目录, mongorestore 导入可能会报错。如果报错建议尝试初始化mongo。
操作指令
```
cd /fastgpt安装目录/mongo/data
rm -rf *
```
4.恢复: mongorestore 恢复 [C环境】
简单一点,退回到本地环境,用 docker 命令一键导入,当然你也可以在容器里操作
```
docker exec -it mongo mongorestore -u "username" -p "password" --authenticationDatabase admin /tmp/backup/ --db fastgpt
```
<img width="1668" alt="image" src="https://github.com/labring/FastGPT/assets/103937568/32c2cdb8-bf80-4d31-9269-4bf3909cf04e">
注意导入文件数量量级太少大概率是没导入成功的表现。如果导入不成功新环境fastgpt可以登入但是一片空白。
5.重启容器 【C环境】
```
docker compose restart
docker logs -f mongo **强烈建议先检查mongo运行情况在去做登陆动作如果mongo报错访问web也会报错”
```
如果mongo启动正常显示的是类似这样的而不是 “mongo is restarting”后者就是错误
<img width="1736" alt="iShot_2024-05-09_19 21 26" src="https://github.com/labring/FastGPT/assets/103937568/94ee00db-43de-48bd-a1fc-22dfe86aaa90">
报错情况
<img width="508" alt="iShot_2024-05-09_19 23 13" src="https://github.com/labring/FastGPT/assets/103937568/2e2afc9f-484c-4b63-93ee-1c14aef03de0">
6. 启动fastgpt容器服务后登陆新fastgpt web能看到原来的数据库内容完整显示说明已经导入系统了。
<img width="1728" alt="iShot_2024-05-09_19 23 51" src="https://github.com/labring/FastGPT/assets/103937568/846b6157-6b6a-4468-a1d9-c44d681ebf7c">

View File

@@ -0,0 +1,9 @@
---
weight: 960
title: "迁移&备份"
description: "FastGPT 迁移&备份"
icon: settings_backup_restore
draft: false
images: []
---
<!-- 960~970 -->

View File

@@ -0,0 +1,15 @@
---
weight: 762
title: "Docker 数据库迁移(无脑操作)"
description: "FastGPT Docker 数据库备份和迁移"
icon: database
draft: false
images: []
---
## Copy文件
Docker 部署数据库都会通过 volume 挂载本地的目录进入容器,如果要迁移,直接复制这些目录即可。
`PG 数据`: pg/data
`Mongo 数据`: mongo/data

View File

@@ -11,21 +11,45 @@ weight: 824
FastGPT workflow V2上线支持更加简洁的工作流模式。
**由于工作流差异较大,需要手动重新构建。**
{{% alert icon="🤖 " context="success" %}}
**由于工作流差异较大,不少地方需要手动重新构建。请依次重建插件和应用**
简易尽快更新工作流,避免未来持续迭代后导致无法兼容。
{{% /alert %}}
给应用和插件增加了 version 的字段,用于标识是旧工作流还是新工作流。当你更新 4.8 后,保存和新建的工作流均为新版,旧版工作流会有一个重置的弹窗提示。并且,如果是通过 API 和 分享链接 调用的工作流,仍可以正常使用,直到你下次保存它们。
## 商业版配置更新
商业版用户如果配置了邮件验证码,需要在管理端 -> 项目配置 -> 登录配置 -> 邮箱登录配置 -> 修改 **邮箱服务SMTP地址**,之前只能配置别名,现在可以配置自定义的地址。下面是一组别名和实际地址关系:
qq: smtp.qq.com
gmail: smtp.gmail.com
## V4.8 更新说明
1. 重构 - 工作流
2. 新增 - 判断器。支持 if elseIf else 判断。
3. 新增 - 变量更新节点。支持更新运行中工作流输出变量,或更新全局变量。
4. 新增 - 工作流 Debug 模式,可以调试单个节点或者逐步调试工作流
5. 新增 - 定时执行应用。可轻松实现定时任务
6. 新增 - 插件自定义输入优化,可以渲染输入组件
7. 优化 - 工作流连线,可以四向连接,方便构建循环工作流
8. 优化 - 工作流上下文传递,性能🚀。
9. 优化 - 简易模式,更新配置后自动更新调试框内容,无需保存
10. 优化 - worker进程管理并将计算 Token 任务分配给 worker 进程
11. 修复 - 工具调用时候name不能是数字开头随机数有概率数字开头
12. 修复 - 分享链接, query 全局变量会被缓存。
2. 新增 - 判断器。支持 if elseIf else 判断。 @newfish-cmyk preview版本的if else节点需要删除重建
3. 新增 - 变量更新节点。支持更新运行中工作流输出变量,或更新全局变量。@newfish-cmyk
4. 新增 - 工作流自动保存和版本管理
5. 新增 - 工作流 Debug 模式,可以调试单个节点或者逐步调试工作流
6. 新增 - 定时执行应用。可轻松实现定时任务
7. 新增 - 插件自定义输入优化,可以渲染输入组件
8. 新增 - 分享链接发送对话前 hook https://github.com/labring/FastGPT/pull/1252 @gaord
9. 优化 - 工作流连线,可以四向连接,方便构建循环工作流
10. 优化 - 工作流上下文传递,性能🚀
11. 优化 - ctrl和alt+enter换行换行符位置不正确。
12. 优化 - chat中存储变量配置。避免修改变量后影响旧的对话。
13. 优化 - 简易模式,更新配置后自动更新调试框内容,无需保存。
14. 优化 - worker进程管理并将计算 Token 任务分配给 worker 进程。
15. 优化 - 工具调用支持指定字段数据类型string, boolean, number https://github.com/labring/FastGPT/issues/1236
16. 优化 - completions接口size限制 https://github.com/labring/FastGPT/issues/1241
17. 优化 - Node api 中间件。优化 api 端代码。@c121914yu
18. 优化 - 对话记录保持为偶数进行截取避免部分模型不支持奇数的历史记录最大长度增加到50轮。 https://github.com/labring/FastGPT/issues/1384
19. 优化 - HTTP节点错误后终止进程 https://github.com/labring/FastGPT/issues/1290
20. 修复 - 工具调用时候name不能是数字开头随机数有概率数字开头@c121914yu
21. 修复 - 分享链接, query 全局变量会被缓存。 @c121914yu
22. 修复 - 工具调用字段兼容。 https://github.com/labring/FastGPT/issues/1253
23. 修复 - HTTP 模块url光标问题 https://github.com/labring/FastGPT/issues/1334 @maquannene

View File

@@ -17,16 +17,14 @@ FastGPT 从 V4 版本开始采用新的交互方式来构建 AI 应用。使用
在程序中,节点可以理解为一个个 Function 或者接口。可以理解为它就是一个**步骤**。将多个节点一个个拼接起来,即可一步步的去实现最终的 AI 输出。
如下图,这是一个最简单的 AI 对话。它由用户输入的问题、聊天记录以及 AI 对话节点组成。
如下图,这是一个最简单的 AI 对话。它由用流程开始和 AI 对话节点组成。
![](/imgs/flow-intro2.png)
执行流程如下:
1. 用户输入问题后,会向服务器发送一个请求,并携带问题。从而得到【用户问题】节点的输出
2. 根据设置的【最长记录数】来获取数据库中的记录数,从而得到【聊天记录】节点的输出
经过上面两个流程就得到了左侧两个蓝色点的结果。结果会被注入到右侧的【AI】对话节点。
3. 【AI 对话】节点根据传入的聊天记录和用户问题,调用对话接口,从而实现回答。(这里的对话结果输出隐藏了起来,默认只要触发了对话节点,就会往客户端输出内容)
1. 用户输入问题后,【流程开始】节点执行,用户问题被保存。
2. 【AI 对话】节点执行,此节点有两个必填参数“聊天记录” “用户问题”聊天记录的值是默认输入的6条表示此模块上下文长度。用户问题选择的是【流程开始】模块中保存的用户问题
3. 【AI 对话】节点根据传入的聊天记录和用户问题,调用对话接口,从而实现回答
### 节点分类
@@ -37,52 +35,46 @@ FastGPT 从 V4 版本开始采用新的交互方式来构建 AI 应用。使用
### 节点的组成
每个节点会包含 3 个核心部分:固定参数、外部输入(左边有个圆圈)和输出(右边有个圆圈)
每个节点会包含 3 个核心部分:输入、输出和触发器
![](/imgs/flow-intro3.png)
- 对话模型、温度、回复上限、系统提示词和限定词为固定参数,同时系统提示词和限定词也可以作为外部输入,意味着如果你有输入流向了系统提示词,那么原本填写的内容就会被**覆盖**
- 触发器、引用内容、聊天记录和用户问题则为外部输入,需要从其他节点的输出流入
- 回复结束则为该节点的输出
- AI模型、提示词、聊天记录、用户问题,知识库引用为输入,节点的输入可以是手动输入也可以是变量引用,变量引用的范围包括“全局变量”和之前任意一个节点的输出
- 新的上下文和AI回复内容为输出输出可以被之后任意节点变量引用
- 节点的上下左右有四个“触发器”可以被用来连接,被连接的节点按顺序决定是否执行
## 重点 - 工作流是如何运行的
与单出入口的工作流不同FastGPT的工作流可以指定**不同的入口**,并且没有**固定的出口**是以节点运行结束作为出口,如果在一个轮调用中,所有节点都不再允许,则工作流结束。
FastGPT的工作流从【流程开始】节点开始执行可以理解为从用户输入问题开始没有**固定的出口**,是以节点运行结束作为出口,如果在一个轮调用中,所有节点都不再允许,则工作流结束。
不过为了方便阅读,大部分时候,我们仍是设置一个模块作为入口,在工作流中,它被叫做`对话入口`下面我们来看下,工作流是如何运行的,以及每个节点何时被触发执行。
下面我们来看下,工作流是如何运行的,以及每个节点何时被触发执行。
记住3个**节点可执行**的原则:
![](/imgs/flow-intro1.png)
1. 仅关心**已连接的**外部输入,即左边的圆圈被连接了参数
2. 当**已连接的**内容都被赋值的时候触发。(这个地方经常会遇到,连接了很多根输入线,但是只要有一个输入没有值,这个节点也不会执行)
3. 可以多个输出连接到一个输入,后续的值会覆盖前面的值。
如上图所示节点会“被连接”也会“连接其他节点”我们称“被连接”的那根线为前置线“连接其他节点的线”为后置线。上图例子中【知识库搜索】模块左侧有一根前置线右侧有一根后置线。而【AI对话】节点只有左侧一根前置线
![](/imgs/workflow_process.png)
FastGPT工作流中的线有以下几种状态
- `waiting`:被连接的节点等待执行。
- `active`:被连接的节点可以执行。
- `skip`:被连接的节点不需要执行跳过。
### 示例 1
节点执行的原则:
聊天记录节点会自动执行因此聊天记录输入会自动赋值。当用户发送问题时【用户问题】节点会输出值此时【AI 对话】节点的用户问题输入也会被赋值。两个连接的输入都被赋值后,会执行 【AI 对话】节点。
1. 判断前置线中有没有状态为 `waiting` 的,如果有则等待。
2. 判断前置线中状态有没有状态为 `active` 如果有则执行。
3. 如果前置线中状态即没有 `waiting` 也没有 `active` 则认为此节点需要跳过。
4. 节点执行完毕后,需要根据实际情况更改后置线的状态为`active``skip`并且更改前置线状态为`waiting`等待下一轮执行。
![](/imgs/flow-intro1.png)
### 例子 2
下图是一个知识库搜索例子。
1. 历史记录会流入【AI 对话】节点。
2. 用户的问题会流入【知识库搜索】和【AI 对话】节点由于【AI 对话】节点的触发器和引用内容还是空,此时不会执行。
3. 【知识库搜索】节点仅一个外部输入,并且被赋值,开始执行。
4. 【知识库搜索】结果为空时“搜索结果不为空”的值为空不会输出因此【AI 对话】节点会因为触发器没有赋值而无法执行。而“搜索结果为空”会有输出,流向指定回复的触发器,因此【指定回复】节点进行输出。
5. 【知识库搜索】结果不为空时“搜索结果不为空”和“引用内容”都有输出会流向【AI 对话】此时【AI 对话】的 4 个外部输入都被赋值,开始执行。
![](/imgs/flow-intro4.png)
让我们看一下上面例子的执行过程:
1. 【流程开始】节点执行完毕,更改后置线为`active`
2. 【知识库搜索】节点判断前置线状态为`active`开始执行,执行完毕后更改后置线状态为`active` 前置线状态为`waiting`
3. 【AI对话】节点判断前置线状态为`active`开始执行,流程执行结束。
## 如何连接节点
1. 为了方便识别不同输入输出的类型FastGPT 每个节点的输入输出连接点赋予不同的颜色,你可以把相同颜色的连接点连接起来。其中,灰色代表任意类型,可以随意连接。
2. 位于左侧的连接点为输入,右侧的为输出,连接只能将一个输入和输出连接起来,不能连接“输入和输入”或者“输出和输出”
3. 可以点击连接线中间的 x 来删除连接线
4. 可以左键点击选中连接线
1. 为了方便连接FastGPT 每个节点的上下左右都有连接点,左和上是前置线连接点,右和下是后置线连接
2. 可以点击连接线中间的 x 来删除连接线
3. 可以左键点击选中连接线
## 如何阅读?
@@ -98,7 +90,4 @@ FastGPT 从 V4 版本开始采用新的交互方式来构建 AI 应用。使用
2. 知识库搜索合并,可以合并多个知识库搜索结果
3. 其他结果,无法直接合并,可以考虑传入到`HTTP`节点中进行合并,使用`[Laf](https://laf.run/)`可以快速实现一个无服务器HTTP接口。
### 节点为什么有2个用户问题
左侧的`用户问题`是指该节点所需的输入。右侧的`用户问题`是为了方便后续的连线,输出的值和传入的用户问题一样。

View File

@@ -10,8 +10,6 @@ weight: 351
## 特点
- 可重复添加
- 有外部输入
- 有静态配置
- 触发执行
- 核心模块

View File

@@ -10,7 +10,6 @@ weight: 352
## 特点
- 可重复添加
- 有外部输入
- 需要手动配置
- 触发执行
- function_call 模块
@@ -54,7 +53,5 @@ weight: 352
## 输出介绍
- **字段完全提取**:说明用户的问题中包含需要提取的所有内容。
- **提取字段缺失**:与 “字段完全提取” 对立,有缺失提取的字段时触发。
- **完整提取结果**: 一个 JSON 字符串,包含所有字段的提取结果。
- **目标字段提取结果**:类型均为字符串。

View File

@@ -29,16 +29,6 @@ weight: 357
[点击查看参数介绍](/docs/course/data_search/#搜索参数)
### 输出 - 搜索结果
输出部分给了两个 boolean 类型的搜索结果,以便根据搜索结果进行不同的处理,通常会有下方两个处理方式:
| 直接回复特定内容 | 对接普通的 gpt |
| ----------------------------- | ----------------------------- |
| ![](/imgs/flow-kbsearch2.png) | ![](/imgs/flow-kbsearch3.png) |
当然,你也可以连接到 HTTP 模块,从而实现无法从知识搜索到内容时,去进行联网搜索或者维基百科搜索。
### 输出 - 引用内容
以数组格式输出引用,长度可以为 0。意味着即使没有搜索到内容这个输出链路也会走通。

View File

@@ -10,7 +10,6 @@ weight: 355
## 特点
- 可重复添加
- 有外部输入
- 手动配置
- 触发执行
- 核中核模块
@@ -23,10 +22,11 @@ HTTP 模块会向对应的地址发送一个 `HTTP` 请求,实际操作与 Pos
- Params 为路径请求参数GET请求中用的居多。
- Body 为请求体POST/PUT请求中用的居多。
- Headers 为请求头,用于传递一些特殊的信息。
- Headers 为请求头,用于传递一些特殊的信息。
- 自定义变量中可以接收前方节点的输出作为变量
- 3 种数据中均可以通过 `{{}}` 来引用变量。
- url 也可以通过 `{{}}` 来引用变量。
- 变量来自于`全局变量``系统变量``局部传入`
- 变量来自于`全局变量``系统变量``前方节点输出`
## 参数结构

View File

@@ -9,9 +9,8 @@ weight: 356
## 特点
- 可重复添加(防止复杂编排时线太乱,重复添加可以更美观)
- 无外部输入
- 流程入口
- 无输入
- 自动执行
![](/imgs/chatinput.png)

View File

@@ -14,19 +14,13 @@ weight: 359
- 可外部输入
- 会输出结果给客户端
定回复模块通常用户特殊状态回复,当然你也可以像图 2 一样,实现一些比较骚的操作~ 触发逻辑非常简单
定回复模块通常用户特殊状态回复,回复内容有两种
1. 一种是写好回复内容,通过触发器触发
2. 一种是不写回复内容,直接由外部输入触发,并回复输入的内容
1. 一种是手动输入固定内容
2. 一种是通过变量引用
{{< figure
src="/imgs/specialreply.png"
alt=""
caption="图 1"
>}}
{{< figure
src="/imgs/specialreply2.png"
alt=""
caption="图 2"
>}}

View File

@@ -20,7 +20,7 @@ weight: 363
## 功能
对输入文本进行固定加工处理,入参仅支持字符串和数字格式,入参以变量形式使用在文本编辑区域。
根据上方示例图的处理方式,对任何输入都会在前面拼接“的问题是”。
根据上方示例图的处理方式,对任何输入都会在前面拼接“用户的问题是:”。
## 作用

View File

@@ -17,44 +17,13 @@ weight: 362
## 功能
对任意输入内容进行 True False 输出,默认情况下,当传入的内容为 false, undefined, null,0,none 时,会输出 false。
对任意变量进行`IF`判断,若满足条件则执行`IF`分支,不满足条件执行`ELSE`分支。
也可以增加自定义规则来补充输出 false 的内容,每行代表一个匹配规则,支持正则表达式。
**例子1**
上述例子中若「知识库引用」变量的长度等于0则执行`IF`分支,否则执行`ELSE`分支。
不填写任何自定义 False 规则。
| 输入 | 输出 |
| --- | --- |
| 123 | true |
| 这是一段文本 | true |
| false | false |
| 0 | false |
| null | false |
**例子2**
自定义 False 规则:
```
123
你好
aa
/dd/
```
| 输入 | 输出 | 说明 |
| --- | --- | --- |
| 123 | false | 命中自定义 false 规则 |
| 这是一段文本 | true | 未命中 |
| false | false | 命中自定义 内置 规则 |
| 0 | false | 命中自定义 内置 规则 |
| null | false | 命中自定义 内置 规则 |
| aa | false | 命中自定义 false 规则 |
| aaa | true | 未命中 |
| bb | false | 命中自定义 false 规则 |
| bbb | false | 命中自定义 false 规则(正则匹配通过) |
支持增加更多的判断条件和分支,同编程语言中的`IF`语句逻辑相同。
## 作用

View File

@@ -47,7 +47,7 @@ weight: 356
| --- | --- |
| ![](/imgs/flow-tool3.png) | ![](/imgs/flow-tool4.png) |
高级编排中,一旦有了工具调用模块,可用的工具头部会出现一个菱形,可以将它与工具调用模块底部的菱形相连接。
高级编排中,托动工具调用的连接点,可用的工具头部会出现一个菱形,可以将它与工具调用模块底部的菱形相连接。
被连接的工具,会自动分离工具输入与普通的输入,并且可以编辑`介绍`,可以通过调整介绍,使得该工具调用时机更加精确。

View File

@@ -1,24 +0,0 @@
---
title: "触发器"
description: "FastGPT 触发器模块介绍"
icon: "work_history"
draft: false
toc: true
weight: 360
---
细心的同学可以发现,在每个功能模块里都会有一个叫【触发器】的外部输入,并且是 any 类型。
它的**核心作用**就是控制模块的执行时机以下图两个知识库搜索中的【AI 对话】模块为例子:
| 图 1 | 图 2 |
| ---------------------------- | ---------------------------- |
| ![](/imgs/trigger1.png) | ![](/imgs/trigger2.png) |
【知识库搜索】模块中,由于**引用内容**始终会有输出会导致【AI 对话】模块的**引用内容**输入无论有没有搜到内容都会被赋值。如果此时不连接触发器(图 2在搜索结束后必定会执行【AI 对话】模块。
有时候,你可能希望空搜索时候进行额外处理,例如:回复固定内容、调用其他提示词的 GPT、发送一个 HTTP 请求…… 此时就需要用到触发器,需要将 **搜索结果不为空****触发器** 连接起来。
当搜索结果为空时,【知识库搜索】模块不会输出 **搜索结果不为空** 的结果,因此 【AI 对话】 模块的触发器始终为空,便不会执行。
总之,记住模块执行的逻辑就可以灵活的使用触发器:**外部输入字段(有连接的才有效)全部被赋值时才会被执行**。

View File

@@ -5,8 +5,8 @@
version: '3.3'
services:
pg:
image: ankane/pgvector:v0.5.0 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/pgvector:v0.5.0 # 阿里云
# image: pgvector/pgvector:0.7.0-pg15 # docker hub
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/pgvector:v0.7.0 # 阿里云
container_name: pg
restart: always
ports: # 生产环境建议不要暴露

View File

@@ -99,13 +99,41 @@ data:
}
],
"vectorModels": [
{
"model": "text-embedding-3-large",
"name": "Embedding-2",
"avatar": "/imgs/model/openai.svg",
"charsPointsPrice": 0,
"defaultToken": 512,
"maxToken": 3000,
"weight": 100,
"dbConfig": {},
"queryConfig": {},
"defaultConfig": {
"dimensions": 1024
}
},
{
"model": "text-embedding-3-small",
"name": "Embedding-2",
"avatar": "/imgs/model/openai.svg",
"charsPointsPrice": 0,
"defaultToken": 512,
"maxToken": 3000,
"weight": 100,
"dbConfig": {},
"queryConfig": {}
},
{
"model": "text-embedding-ada-002",
"name": "Embedding-2",
"avatar": "/imgs/model/openai.svg",
"charsPointsPrice": 0,
"defaultToken": 700,
"defaultToken": 512,
"maxToken": 3000,
"weight": 100
"weight": 100,
"dbConfig": {},
"queryConfig": {}
}
],
"reRankModels": [],

View File

@@ -1,24 +1,91 @@
import { getErrText } from '../error/utils';
import { replaceRegChars } from './tools';
/**
* text split into chunks
* chunkLen - one chunk len. max: 3500
* overlapLen - The size of the before and after Text
* chunkLen > overlapLen
* markdown
*/
export const splitText2Chunks = (props: {
export const CUSTOM_SPLIT_SIGN = '-----CUSTOM_SPLIT_SIGN-----';
type SplitProps = {
text: string;
chunkLen: number;
overlapRatio?: number;
customReg?: string[];
}): {
};
type SplitResponse = {
chunks: string[];
chars: number;
overlapRatio?: number;
} => {
};
// 判断字符串是否为markdown的表格形式
const strIsMdTable = (str: string) => {
// 检查是否包含表格分隔符 |
if (!str.includes('|')) {
return false;
}
const lines = str.split('\n');
// 检查表格是否至少有两行
if (lines.length < 2) {
return false;
}
// 检查表头行是否包含 |
const headerLine = lines[0].trim();
if (!headerLine.startsWith('|') || !headerLine.endsWith('|')) {
return false;
}
// 检查分隔行是否由 | 和 - 组成
const separatorLine = lines[1].trim();
const separatorRegex = /^(\|[\s:]*-+[\s:]*)+\|$/;
if (!separatorRegex.test(separatorLine)) {
return false;
}
// 检查数据行是否包含 |
for (let i = 2; i < lines.length; i++) {
const dataLine = lines[i].trim();
if (dataLine && (!dataLine.startsWith('|') || !dataLine.endsWith('|'))) {
return false;
}
}
return true;
};
const markdownTableSplit = (props: SplitProps): SplitResponse => {
let { text = '', chunkLen } = props;
const splitText2Lines = text.split('\n');
const header = splitText2Lines[0];
const headerSize = header.split('|').length - 2;
const mdSplitString = `| ${new Array(headerSize > 0 ? headerSize : 1)
.fill(0)
.map(() => '---')
.join(' | ')} |`;
const chunks: string[] = [];
let chunk = `${header}
${mdSplitString}
`;
for (let i = 2; i < splitText2Lines.length; i++) {
if (chunk.length + splitText2Lines[i].length > chunkLen * 1.2) {
chunks.push(chunk);
chunk = `${header}
${mdSplitString}
`;
}
chunk += `${splitText2Lines[i]}\n`;
}
return {
chunks,
chars: chunks.reduce((sum, chunk) => sum + chunk.length, 0)
};
};
const commonSplit = (props: SplitProps): SplitResponse => {
let { text = '', chunkLen, overlapRatio = 0.2, customReg = [] } = props;
const splitMarker = 'SPLIT_HERE_SPLIT_HERE';
const codeBlockMarker = 'CODE_BLOCK_LINE_MARKER';
const overlapLen = Math.round(chunkLen * overlapRatio);
@@ -253,3 +320,29 @@ export const splitText2Chunks = (props: {
throw new Error(getErrText(err));
}
};
/**
* text split into chunks
* chunkLen - one chunk len. max: 3500
* overlapLen - The size of the before and after Text
* chunkLen > overlapLen
* markdown
*/
export const splitText2Chunks = (props: SplitProps): SplitResponse => {
let { text = '' } = props;
const splitWithCustomSign = text.split(CUSTOM_SPLIT_SIGN);
const splitResult = splitWithCustomSign.map((item) => {
if (strIsMdTable(item)) {
return markdownTableSplit(props);
}
return commonSplit(props);
});
return {
chunks: splitResult.map((item) => item.chunks).flat(),
chars: splitResult.reduce((sum, item) => sum + item.chars, 0)
};
};

View File

@@ -23,7 +23,7 @@ export const defaultQAModels: LLMModelItemType[] = [
export const defaultVectorModels: VectorModelItemType[] = [
{
model: 'text-embedding-ada-002',
model: 'text-embedding-3-small',
name: 'Embedding-2',
charsPointsPrice: 0,
defaultToken: 500,

View File

@@ -56,8 +56,8 @@ export const countGptMessagesTokens = (
clearTimeout(timer);
// 检测是否有内存泄漏
addLog.info(`Count token time: ${Date.now() - start}, token: ${data}`);
console.log(Object.keys(global.tiktokenWorker.callbackMap));
// addLog.info(`Count token time: ${Date.now() - start}, token: ${data}`);
// console.log(process.memoryUsage());
};
worker.postMessage({

View File

@@ -9,7 +9,7 @@ export const checkTimerLock = async ({
timerId,
lockMinuted
}: {
timerId: `${TimerIdEnum}`;
timerId: TimerIdEnum;
lockMinuted: number;
}) => {
try {

View File

@@ -13,7 +13,8 @@ export type InsertVectorProps = {
};
export type EmbeddingRecallProps = {
teamId: string;
datasetIds: string[];
similarity?: number;
efSearch?: number;
// similarity?: number;
// efSearch?: number;
};

View File

@@ -23,7 +23,7 @@ export async function initPg() {
`);
await PgClient.query(
`CREATE INDEX CONCURRENTLY IF NOT EXISTS vector_index ON ${PgDatasetTableName} USING hnsw (vector vector_ip_ops) WITH (m = 32, ef_construction = 100);`
`CREATE INDEX CONCURRENTLY IF NOT EXISTS vector_index ON ${PgDatasetTableName} USING hnsw (vector vector_ip_ops) WITH (m = 32, ef_construction = 128);`
);
await PgClient.query(
`CREATE INDEX CONCURRENTLY IF NOT EXISTS team_dataset_collection_index ON ${PgDatasetTableName} USING btree(team_id, dataset_id, collection_id);`
@@ -129,16 +129,15 @@ export const embeddingRecall = async (
): Promise<{
results: EmbeddingRecallItemType[];
}> => {
const { datasetIds, vectors, limit, similarity = 0, retry = 2, efSearch = 100 } = props;
const { datasetIds, vectors, limit, retry = 2 } = props;
try {
const results: any = await PgClient.query(
`BEGIN;
SET LOCAL hnsw.ef_search = ${efSearch};
SET LOCAL hnsw.ef_search = ${global.systemEnv?.pgHNSWEfSearch || 100};
select id, collection_id, vector <#> '[${vectors[0]}]' AS score
from ${PgDatasetTableName}
where dataset_id IN (${datasetIds.map((id) => `'${String(id)}'`).join(',')})
AND vector <#> '[${vectors[0]}]' < -${similarity}
order by score limit ${limit};
COMMIT;`
);
@@ -153,10 +152,14 @@ export const embeddingRecall = async (
}))
};
} catch (error) {
console.log(error);
if (retry <= 0) {
return Promise.reject(error);
}
return embeddingRecall(props);
return embeddingRecall({
...props,
retry: retry - 1
});
}
};

View File

@@ -2,6 +2,7 @@ import { VectorModelItemType } from '@fastgpt/global/core/ai/model.d';
import { getAIApi } from '../config';
import { countPromptTokens } from '../../../common/string/tiktoken/index';
import { EmbeddingTypeEnm } from '@fastgpt/global/core/ai/constants';
import { addLog } from '../../../common/system/log';
type GetVectorProps = {
model: VectorModelItemType;
@@ -32,7 +33,8 @@ export async function getVectorsByText({ model, input, type }: GetVectorProps) {
})
.then(async (res) => {
if (!res.data) {
return Promise.reject('Embedding API 404');
addLog.error('Embedding API is not responding', res);
return Promise.reject('Embedding API is not responding');
}
if (!res?.data?.[0]?.embedding) {
console.log(res);

View File

@@ -62,7 +62,7 @@ const DatasetSchema = new Schema({
vectorModel: {
type: String,
required: true,
default: 'text-embedding-ada-002'
default: 'text-embedding-3-small'
},
agentModel: {
type: String,

View File

@@ -59,19 +59,19 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
const countRecallLimit = () => {
if (searchMode === DatasetSearchModeEnum.embedding) {
return {
embeddingLimit: 150,
embeddingLimit: 100,
fullTextLimit: 0
};
}
if (searchMode === DatasetSearchModeEnum.fullTextRecall) {
return {
embeddingLimit: 0,
fullTextLimit: 150
fullTextLimit: 100
};
}
return {
embeddingLimit: 100,
fullTextLimit: 80
embeddingLimit: 80,
fullTextLimit: 60
};
};
const embeddingRecall = async ({ query, limit }: { query: string; limit: number }) => {
@@ -82,10 +82,10 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
});
const { results } = await recallFromVectorStore({
vectors,
limit,
teamId,
datasetIds,
efSearch: global.systemEnv?.pgHNSWEfSearch
vectors,
limit
});
// get q and a

View File

@@ -127,8 +127,8 @@ const completions = async ({
});
const answer = data.choices?.[0].message?.content || '';
console.log(JSON.stringify(chats2GPTMessages({ messages, reserveId: false }), null, 2));
console.log(answer, '----');
// console.log(JSON.stringify(chats2GPTMessages({ messages, reserveId: false }), null, 2));
// console.log(answer, '----');
const id =
agents.find((item) => answer.includes(item.key))?.key ||

View File

@@ -10,9 +10,13 @@ export const readCsvRawText = async (params: ReadRawTextByBuffer): Promise<ReadF
const header = csvArr[0];
const formatText = header
? csvArr.map((item) => item.map((item, i) => `${header[i]}:${item}`).join('\n')).join('\n')
: '';
// format to md table
const formatText = `| ${header.join(' | ')} |
| ${header.map(() => '---').join(' | ')} |
${csvArr
.slice(1)
.map((row) => `| ${row.map((item) => item.replace(/\n/g, '\\n')).join(' | ')} |`)
.join('\n')}`;
return {
rawText,

View File

@@ -1,3 +1,4 @@
import { CUSTOM_SPLIT_SIGN } from '@fastgpt/global/common/string/textSplitter';
import { ReadRawTextByBuffer, ReadFileResponse } from '../type';
import xlsx from 'node-xlsx';
import Papa from 'papaparse';
@@ -18,25 +19,25 @@ export const readXlsxRawText = async ({
});
const rawText = format2Csv.map((item) => item.csvText).join('\n');
const formatText = format2Csv
.map((item) => {
const csvArr = Papa.parse(item.csvText).data as string[][];
const header = csvArr[0];
const formatText = header
? csvArr
.map((item) =>
item
.map((item, i) => (item ? `${header[i]}:${item}` : ''))
.filter(Boolean)
.join('\n')
)
.join('\n')
: '';
if (!header) return;
return `${item.title}\n${formatText}`;
const formatText = `| ${header.join(' | ')} |
| ${header.map(() => '---').join(' | ')} |
${csvArr
.slice(1)
.map((row) => `| ${row.map((item) => item.replace(/\n/g, '\\n')).join(' | ')} |`)
.join('\n')}`;
return formatText;
})
.join('\n');
.filter(Boolean)
.join(CUSTOM_SPLIT_SIGN);
return {
rawText: rawText,

View File

@@ -67,5 +67,5 @@ parentPort?.on('message', async (props: ReadRawTextProps<Uint8Array>) => {
});
}
global?.close?.();
process.exit();
});

View File

@@ -15,6 +15,5 @@ parentPort?.on('message', (params: { html: string }) => {
data: error
});
}
global?.close?.();
process.exit();
});

View File

@@ -26,91 +26,96 @@ parentPort?.on(
tools?: ChatCompletionTool[];
functionCall?: ChatCompletionCreateParams.Function[];
}) => {
const start = Date.now();
/* count one prompt tokens */
const countPromptTokens = (
prompt: string | ChatCompletionContentPart[] | null | undefined = '',
role: '' | `${ChatCompletionRequestMessageRoleEnum}` = ''
) => {
const promptText = (() => {
if (!prompt) return '';
if (typeof prompt === 'string') return prompt;
let promptText = '';
prompt.forEach((item) => {
if (item.type === 'text') {
promptText += item.text;
} else if (item.type === 'image_url') {
promptText += item.image_url.url;
}
});
return promptText;
})();
const text = `${role}\n${promptText}`.trim();
try {
const encodeText = enc.encode(text);
const supplementaryToken = role ? 4 : 0;
return encodeText.length + supplementaryToken;
} catch (error) {
return text.length;
}
};
const countToolsTokens = (
tools?: ChatCompletionTool[] | ChatCompletionCreateParams.Function[]
) => {
if (!tools || tools.length === 0) return 0;
const toolText = tools
? JSON.stringify(tools)
.replace('"', '')
.replace('\n', '')
.replace(/( ){2,}/g, ' ')
: '';
return enc.encode(toolText).length;
};
const total =
messages.reduce((sum, item) => {
// Evaluates the text of toolcall and functioncall
const functionCallPrompt = (() => {
let prompt = '';
if (item.role === ChatCompletionRequestMessageRoleEnum.Assistant) {
const toolCalls = item.tool_calls;
prompt +=
toolCalls
?.map((item) => `${item?.function?.name} ${item?.function?.arguments}`.trim())
?.join('') || '';
const functionCall = item.function_call;
prompt += `${functionCall?.name} ${functionCall?.arguments}`.trim();
}
return prompt;
try {
/* count one prompt tokens */
const countPromptTokens = (
prompt: string | ChatCompletionContentPart[] | null | undefined = '',
role: '' | `${ChatCompletionRequestMessageRoleEnum}` = ''
) => {
const promptText = (() => {
if (!prompt) return '';
if (typeof prompt === 'string') return prompt;
let promptText = '';
prompt.forEach((item) => {
if (item.type === 'text') {
promptText += item.text;
} else if (item.type === 'image_url') {
promptText += item.image_url.url;
}
});
return promptText;
})();
const contentPrompt = (() => {
if (!item.content) return '';
if (typeof item.content === 'string') return item.content;
return item.content
.map((item) => {
if (item.type === 'text') return item.text;
return '';
})
.join('');
})();
const text = `${role}\n${promptText}`.trim();
return sum + countPromptTokens(`${contentPrompt}${functionCallPrompt}`, item.role);
}, 0) +
countToolsTokens(tools) +
countToolsTokens(functionCall);
try {
const encodeText = enc.encode(text);
const supplementaryToken = role ? 4 : 0;
return encodeText.length + supplementaryToken;
} catch (error) {
return text.length;
}
};
const countToolsTokens = (
tools?: ChatCompletionTool[] | ChatCompletionCreateParams.Function[]
) => {
if (!tools || tools.length === 0) return 0;
parentPort?.postMessage({
id,
type: 'success',
data: total
});
const toolText = tools
? JSON.stringify(tools)
.replace('"', '')
.replace('\n', '')
.replace(/( ){2,}/g, ' ')
: '';
global?.close?.();
return enc.encode(toolText).length;
};
const total =
messages.reduce((sum, item) => {
// Evaluates the text of toolcall and functioncall
const functionCallPrompt = (() => {
let prompt = '';
if (item.role === ChatCompletionRequestMessageRoleEnum.Assistant) {
const toolCalls = item.tool_calls;
prompt +=
toolCalls
?.map((item) => `${item?.function?.name} ${item?.function?.arguments}`.trim())
?.join('') || '';
const functionCall = item.function_call;
prompt += `${functionCall?.name} ${functionCall?.arguments}`.trim();
}
return prompt;
})();
const contentPrompt = (() => {
if (!item.content) return '';
if (typeof item.content === 'string') return item.content;
return item.content
.map((item) => {
if (item.type === 'text') return item.text;
return '';
})
.join('');
})();
return sum + countPromptTokens(`${contentPrompt}${functionCallPrompt}`, item.role);
}, 0) +
countToolsTokens(tools) +
countToolsTokens(functionCall);
parentPort?.postMessage({
id,
type: 'success',
data: total
});
} catch (error) {
parentPort?.postMessage({
id,
type: 'success',
data: 0
});
}
}
);

View File

@@ -25,9 +25,12 @@ export const runWorker = <T = any>(name: WorkerNameEnum, params?: Record<string,
});
worker.on('error', (err) => {
worker.terminate();
reject(err);
worker.terminate();
});
worker.on('messageerror', (err) => {
reject(err);
worker.terminate();
});
});
};

View File

@@ -24,7 +24,6 @@
"i18next": "23.10.0",
"lexical": "0.12.6",
"lodash": "^4.17.21",
"mammoth": "^1.6.0",
"next-i18next": "15.2.0",
"papaparse": "^5.4.1",
"pdfjs-dist": "4.0.269",

3
pnpm-lock.yaml generated
View File

@@ -277,9 +277,6 @@ importers:
lodash:
specifier: ^4.17.21
version: 4.17.21
mammoth:
specifier: ^1.6.0
version: 1.6.0
next-i18next:
specifier: 15.2.0
version: 15.2.0(i18next@23.10.0)(next@13.5.2)(react-i18next@13.5.0)(react@18.2.0)

View File

@@ -80,6 +80,31 @@
}
],
"vectorModels": [
{
"model": "text-embedding-3-large",
"name": "Embedding-2",
"avatar": "/imgs/model/openai.svg",
"charsPointsPrice": 0,
"defaultToken": 512,
"maxToken": 3000,
"weight": 100,
"dbConfig": {},
"queryConfig": {},
"defaultConfig": {
"dimensions": 1024
}
},
{
"model": "text-embedding-3-small",
"name": "Embedding-2",
"avatar": "/imgs/model/openai.svg",
"charsPointsPrice": 0,
"defaultToken": 512,
"maxToken": 3000,
"weight": 100,
"dbConfig": {},
"queryConfig": {}
},
{
"model": "text-embedding-ada-002",
"name": "Embedding-2",

View File

@@ -488,7 +488,7 @@
"Speaking": "I'm listening, please speak...",
"Start Chat": "Start Chat",
"Stop Speak": "Stop Recording",
"Type a message": "Enter a question",
"Type a message": "Enter your question here, Send [Enter]/Wrap [Ctrl(Alt/Shift) + Enter]",
"Unpin": "Unpin",
"You need to a chat app": "You don't have an available app",
"error": {

View File

@@ -488,7 +488,7 @@
"Speaking": "我在听,请说...",
"Start Chat": "开始对话",
"Stop Speak": "停止录音",
"Type a message": "输入问题",
"Type a message": "输入问题,发送 [Enter]/换行 [Ctrl(Alt/Shift) + Enter]",
"Unpin": "取消置顶",
"You need to a chat app": "你没有可用的应用",
"error": {

View File

@@ -80,18 +80,13 @@ const nextConfig = {
return config;
},
transpilePackages: ['@fastgpt/*', 'ahooks'],
transpilePackages: ['@fastgpt/*', 'ahooks', '@chakra-ui/*', 'react'],
experimental: {
// 外部包独立打包
serverComponentsExternalPackages: ['mongoose', 'pg'],
// 指定导出包优化,按需引入包模块
optimizePackageImports: ['mongoose', 'pg'],
outputFileTracingRoot: path.join(__dirname, '../../'),
outputFileTracingIncludes: {
'/api/common/file/previewContent.ts': [
path.resolve(process.cwd(), '../../packages/service/worker/**/*')
]
}
outputFileTracingRoot: path.join(__dirname, '../../')
}
};

View File

@@ -1,10 +1,32 @@
### FastGPT V4.7.1
### FastGPT V4.8
1. 新增 - 语音输入完整配置。支持选择是否打开语音输入(包括分享页面),支持语音输入后自动发送,支持语音输入后自动语音播放(流式)
2. 新增 - Pptx 和 xlsx 文件读取。但所有文件读取都放服务端,会消耗更多的服务器资源,以及无法在上传时预览更多内容。
3. 新增 - 集成 Laf 云函数,可以读取 Laf 账号中的云函数作为 HTTP 模块。
4. 修改 - csv导入模板取消 header 校验,自动获取前两列。
5. 修复 - 问题补全历史记录BUG
6. [点击查看高级编排介绍文档](https://doc.fastgpt.in/docs/workflow/intro)
7. [使用文档](https://doc.fastgpt.in/docs/intro/)
8. [点击查看商业版](https://doc.fastgpt.in/docs/commercial/)
本次更新的重点是对工作流 (高级编排) 进行了重构,使其更加简洁和强大。但由于新旧工作流机制有较大变化,尽管我们进行了一定的自动转换,仍有部分工作流需要您手动重建。请尽快更新到新版本,并对工作流进行必要的调试和重新发布
❗ 重要提示:
1⃣ 旧工作流更新后暂不失效,打开旧工作流会弹出自动转换提示,重新编排后点 “发布” 按钮发布新工作流
2⃣ 发布新工作流前,工作流自动保存功能暂不生效
3⃣ 应用和插件新增 version 字段,标识适用新/旧版工作流,以实现兼容
✨ 新增功能亮点:
1⃣ 判断器:支持 if/elseIf/else 判断逻辑,工作流控制更灵活
2⃣ 变量更新节点:运行中可动态修改工作流输出变量或全局变量值
3⃣ 工作流自动保存和版本管理:自动保存修改,支持查看和回滚历史版本
4⃣ 工作流调试模式:更直观高效,可调试单节点或逐步执行,实时查看输入输出数据
5⃣ 定时执行应用:支持简单配置实现各种定时任务
🛠️ 其他优化与修复:
- 优化工作流节点连线方式,支持四向连接,易构建循环工作流
- 显著提升工作流上下文数据传递性能
- 简易模式下修改配置自动刷新调试框,免手动保存
- 改进 worker 进程管理,支持 Token 计算任务分配,提高效率
- 工具调用支持 string、boolean、number 数据类型
- 完善 completions 接口对 size 参数限制
- 重构 Node.js API 中间件和服务端代码
- 对话记录长度调整为偶数,最大长度增至 50 轮,避免奇数导致部分模型不兼容
- HTTP 节点出错将终止进程,避免异常影响
- 修复工具调用名称不能以数字开头问题
- 修复分享链接 query 参数缓存 bug
- 修复工具调用和 HTTP 模块兼容性问题
- [点击查看高级编排介绍文档](https://doc.fastgpt.in/docs/workflow/intro)
- [使用文档](https://doc.fastgpt.in/docs/intro/)
- [点击查看商业版](https://doc.fastgpt.in/docs/commercial/)

View File

@@ -55,6 +55,8 @@ const MessageInput = ({
const { t } = useTranslation();
const havInput = !!inputValue || fileList.length > 0;
const hasFileUploading = fileList.some((item) => !item.url);
const canSendMessage = havInput && !hasFileUploading;
/* file selector and upload */
const { File, onOpen: onOpenSelectFile } = useSelectFile({
@@ -69,9 +71,9 @@ const MessageInput = ({
const url = await compressImgFileAndUpload({
type: MongoImageTypeEnum.chatImage,
file: file.rawFile,
maxW: 4329,
maxH: 4329,
maxSize: 1024 * 1024 * 5,
maxW: 4320,
maxH: 4320,
maxSize: 1024 * 1024 * 16,
// 7 day expired.
expiredTime: addDays(new Date(), 7),
shareId,
@@ -142,7 +144,8 @@ const MessageInput = ({
);
/* on send */
const handleSend = useCallback(async () => {
const handleSend = async () => {
if (!canSendMessage) return;
const textareaValue = TextareaDom.current?.value || '';
onSendMessage({
@@ -150,7 +153,7 @@ const MessageInput = ({
files: fileList
});
replaceFile([]);
}, [TextareaDom, fileList, onSendMessage, replaceFile]);
};
/* whisper init */
const {
@@ -466,16 +469,20 @@ const MessageInput = ({
h={['28px', '32px']}
w={['28px', '32px']}
borderRadius={'md'}
bg={isSpeaking || isChatting ? '' : !havInput ? '#E5E5E5' : 'primary.500'}
bg={
isSpeaking || isChatting
? ''
: !havInput || hasFileUploading
? '#E5E5E5'
: 'primary.500'
}
cursor={havInput ? 'pointer' : 'not-allowed'}
lineHeight={1}
onClick={() => {
if (isChatting) {
return onStop();
}
if (havInput) {
return handleSend();
}
return handleSend();
}}
>
{isChatting ? (

View File

@@ -53,7 +53,7 @@ const Markdown = ({
);
const formatSource = source
.replace(/\\n/g, '\n&nbsp;')
// .replace(/\\n/g, '\n')
.replace(/(http[s]?:\/\/[^\s。]+)([。,])/g, '$1 $2')
.replace(/\n*(\[QUOTE SIGN\]\(.*\))/g, '$1');

View File

@@ -231,17 +231,20 @@ const MenuRender = React.memo(function MenuRender({
flowNodeType: node.data.flowNodeType,
inputs: node.data.inputs,
outputs: node.data.outputs,
showStatus: node.data.showStatus
showStatus: node.data.showStatus,
pluginId: node.data.pluginId
};
return state.concat(
storeNode2FlowNode({
item: {
flowNodeType: template.flowNodeType,
avatar: template.avatar,
name: template.name,
intro: template.intro,
nodeId: getNanoid(),
position: { x: node.position.x + 200, y: node.position.y + 50 },
flowNodeType: template.flowNodeType,
showStatus: template.showStatus,
pluginId: template.pluginId,
inputs: template.inputs,
outputs: template.outputs
}

View File

@@ -1,10 +1,7 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import type { AppUpdateParams } from '@/global/core/app/api';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { getScheduleTriggerApp } from '@/service/core/app/utils';
import { beforeUpdateAppFormat } from '@fastgpt/service/core/app/controller';
import { NextAPI } from '@/service/middle/entry';
@@ -36,7 +33,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
avatar,
intro,
permission,
version: 'v2',
...(teamTags && teamTags),
...(formatNodes && {
modules: formatNodes

View File

@@ -35,9 +35,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
csvFormat: true
});
// split chunks (5 chunk)
const sliceRawText = 10 * chunkSize;
const { chunks } = splitText2Chunks({
text: rawText.slice(0, sliceRawText),
text: rawText,
chunkLen: chunkSize,
overlapRatio,
customReg: customSplitChar ? [customSplitChar] : []

View File

@@ -40,3 +40,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
res.status(500).send(getErrText(err));
}
}
export const config = {
api: {
responseLimit: '16mb'
}
};

View File

@@ -288,13 +288,13 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
event: detail ? SseResponseEventEnum.answer : undefined,
data: '[DONE]'
});
responseWrite({
res,
event: SseResponseEventEnum.updateVariables,
data: JSON.stringify(newVariables)
});
if (responseDetail && detail) {
responseWrite({
res,
event: SseResponseEventEnum.updateVariables,
data: JSON.stringify(newVariables)
});
responseWrite({
res,
event: SseResponseEventEnum.flowResponses,
@@ -362,12 +362,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
}
export default NextAPI(handler);
export const config = {
api: {
responseLimit: '20mb'
}
};
const authShareChat = async ({
chatId,
...data
@@ -526,3 +520,9 @@ const authHeaderRequest = async ({
canWrite
};
};
export const config = {
api: {
responseLimit: '20mb'
}
};

View File

@@ -51,6 +51,8 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
>
>;
}) {
const isV2Workflow = app?.version === 'v2';
const theme = useTheme();
const { toast } = useToast();
const { t } = useTranslation();
@@ -97,7 +99,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
const onclickSave = useCallback(
async (forbid?: boolean) => {
// version preview / debug mode, not save
if (isShowVersionHistories || forbid) return;
if (!isV2Workflow || isShowVersionHistories || forbid) return;
const { nodes } = await getWorkflowStore();
@@ -219,7 +221,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
<Box fontSize={['md', 'lg']} fontWeight={'bold'}>
{app.name}
</Box>
{!isShowVersionHistories && (
{!isShowVersionHistories && isV2Workflow && (
<MyTooltip label={t('core.app.Onclick to save')}>
<Box
fontSize={'sm'}
@@ -306,22 +308,23 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
</>
);
}, [
ConfirmModal,
app.name,
flowData2StoreDataAndCheck,
theme.borders.base,
isSaving,
onExportWorkflow,
onOpenImport,
onclickPublish,
onclickSave,
openConfigPublish,
isShowVersionHistories,
saveAndBack,
saveLabel,
setIsShowVersionHistories,
setWorkflowTestData,
app.name,
isShowVersionHistories,
isV2Workflow,
t,
theme.borders.base
saveLabel,
onOpenImport,
onExportWorkflow,
openConfigPublish,
onclickPublish,
ConfirmModal,
onclickSave,
setIsShowVersionHistories,
flowData2StoreDataAndCheck,
setWorkflowTestData
]);
return (

View File

@@ -15,7 +15,7 @@ const Render = ({ app, onClose }: Props) => {
const { openConfirm, ConfirmModal } = useConfirm({
showCancel: false,
content:
'检测到您的高级编排为旧版,系统将为您自动格式化成新版工作流。\n\n由于版本差异较大会导致许多工作流无法正常排布,请重新手动连接工作流。如仍异常,可尝试删除对应节点后重新添加。\n\n你可以直接点击试进行调试,无需点击保存,点击保存为新版工作流。'
'检测到您的高级编排为旧版,系统将为您自动格式化成新版工作流。\n\n由于版本差异较大会导致一些工作流无法正常排布,请重新手动连接工作流。如仍异常,可尝试删除对应节点后重新添加。\n\n你可以直接点击试进行工作流测试,调试完毕后点击发布。直到你点击发布,新工作流才会真正保存生效。\n\n在你发布新工作流前自动保存不会生效。'
});
const initData = useContextSelector(WorkflowContext, (v) => v.initData);

View File

@@ -28,7 +28,7 @@ export default React.memo(FileLocal);
const csvTemplate = `"第一列内容","第二列内容"
"必填列","可选列。CSV 中请注意内容不能包含双引号,双引号是列分割符号"
"只会第一和第二列内容导入,其余列会被忽略",""
"只会第一和第二列内容导入,其余列会被忽略",""
"结合人工智能的演进历程,AIGC的发展大致可以分为三个阶段即:早期萌芽阶段(20世纪50年代至90年代中期)、沉淀积累阶段(20世纪90年代中期至21世纪10年代中期),以及快速发展展阶段(21世纪10年代中期至今)。",""
"AIGC发展分为几个阶段","早期萌芽阶段(20世纪50年代至90年代中期)、沉淀积累阶段(20世纪90年代中期至21世纪10年代中期)、快速发展展阶段(21世纪10年代中期至今)"`;

View File

@@ -32,7 +32,7 @@ export const useSearchTestStore = create<State>()(
datasetTestList: [],
pushDatasetTestItem(data) {
set((state) => {
state.datasetTestList = [data, ...state.datasetTestList].slice(0, 100);
state.datasetTestList = [data, ...state.datasetTestList].slice(0, 50);
});
},
delDatasetTestItemById(id) {