4.6.8-production (#822)
* Json completion (#16) * json-completion * fix duplicate * fix * fix: config json * feat: query extension * perf: i18n * 468 doc * json editor * perf: doc * perf: default extension model * docker file * doc * perf: token count * perf: search extension * format * perf: some constants data --------- Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
2
.vscode/settings.json
vendored
@@ -2,7 +2,7 @@
|
||||
"editor.formatOnSave": true,
|
||||
"editor.mouseWheelZoom": true,
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"prettier.prettierPath": "./node_modules/prettier",
|
||||
"prettier.prettierPath": "",
|
||||
"i18n-ally.localesPaths": [
|
||||
"projects/app/public/locales",
|
||||
],
|
||||
|
@@ -8,7 +8,7 @@ ARG proxy
|
||||
RUN [ -z "$proxy" ] || sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
|
||||
RUN apk add --no-cache libc6-compat && npm install -g pnpm@8.6.0
|
||||
# if proxy exists, set proxy
|
||||
RUN [ -z "$proxy" ] || pnpm config set registry https://registry.npm.taobao.org
|
||||
RUN [ -z "$proxy" ] || pnpm config set registry https://registry.npmmirror.com
|
||||
|
||||
# copy packages and one project
|
||||
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
@@ -28,7 +28,7 @@ ARG proxy
|
||||
RUN [ -z "$proxy" ] || sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
|
||||
RUN apk add --no-cache libc6-compat && npm install -g pnpm@8.6.0
|
||||
# if proxy exists, set proxy
|
||||
RUN [ -z "$proxy" ] || pnpm config set registry https://registry.npm.taobao.org
|
||||
RUN [ -z "$proxy" ] || pnpm config set registry https://registry.npmmirror.com
|
||||
|
||||
COPY ./worker /app/worker
|
||||
RUN cd /app/worker && pnpm i --production --ignore-workspace
|
||||
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 212 KiB |
BIN
docSite/assets/imgs/dataset_search_params2.png
Normal file
After Width: | Height: | Size: 92 KiB |
BIN
docSite/assets/imgs/dataset_search_params3.png
Normal file
After Width: | Height: | Size: 193 KiB |
BIN
docSite/assets/imgs/dataset_search_process.png
Normal file
After Width: | Height: | Size: 428 KiB |
BIN
docSite/assets/imgs/dataset_tree.png
Normal file
After Width: | Height: | Size: 100 KiB |
@@ -1,19 +1,77 @@
|
||||
---
|
||||
title: '知识库搜索参数'
|
||||
description: '知识库搜索原理'
|
||||
title: '知识库搜索介绍'
|
||||
description: '本节会详细介绍 FastGPT 知识库结构设计,理解其 QA 的存储格式和多向量映射,以便更好的构建知识库。同时会介绍每个搜索参数的功能。这篇介绍主要以使用为主,详细原理不多介绍。'
|
||||
icon: 'language'
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 106
|
||||
---
|
||||
|
||||
在知识库搜索的方式上,FastGPT提供了三种方式,分别为“语义检索”“增强语义检索”“混合检索”。
|
||||
## 理解向量
|
||||
|
||||

|
||||
FastGPT 采用了 RAG 中的 Embedding 方案构建知识库,要使用好 FastGPT 需要简单的理解`Embedding`向量是如何工作的及其特点。
|
||||
|
||||
## 搜索模式
|
||||
人类的文字、图片、视频等媒介是无法直接被计算机理解的,要想让计算机理解两段文字是否有相似性、相关性,通常需要将它们转成计算机可以理解的语言,向量是其中的一种方式。
|
||||
|
||||
### 语义检索
|
||||
向量可以简单理解为一个数字数组,两个向量之间可以通过数学公式得出一个`距离`,距离越小代表两个向量的相似度越大。从而映射到文字、图片、视频等媒介上,可以用来判断两个媒介之间的相似度。向量搜索便是利用了这个原理。
|
||||
|
||||
而由于文字是有多种类型,并且拥有成千上万种组合方式,因此在转成向量进行相似度匹配时,很难保障其精确性。在向量方案构建的知识库中,通常使用`topk`召回的方式,也就是查找前`k`个最相似的内容,丢给大模型去做更进一步的`语义判断`、`逻辑推理`和`归纳总结`,从而实现知识库问答。因此,在知识库问答中,向量搜索的环节是最为重要的。
|
||||
|
||||
影响向量搜索精度的因素非常多,主要包括:向量模型的质量、数据的质量(长度,完整性,多样性)、检索器的精度(速度与精度之间的取舍)。与数据质量对应的就是检索词的质量。
|
||||
|
||||
检索器的精度比较容易解决,向量模型的训练略复杂,因此数据和检索词质量优化成了一个重要的环节。
|
||||
|
||||
|
||||
### 提高向量搜索精度的方法
|
||||
|
||||
1. 更好分词分段:当一段话的结构和语义是完整的,并且是单一的,精度也会提高。因此,许多系统都会优化分词器,尽可能的保障每组数据的完整性。
|
||||
2. 精简`index`的内容,减少向量内容的长度:当`index`的内容更少,更准确时,检索精度自然会提高。但与此同时,会牺牲一定的检索范围,适合答案较为严格的场景。
|
||||
3. 丰富`index`的数量,可以为同一个`chunk`内容增加多组`index`。
|
||||
4. 优化检索词:在实际使用过程中,用户的问题通常是模糊的或是缺失的,并不一定是完整清晰的问题。因此优化用户的问题(检索词)很大程度上也可以提高精度。
|
||||
5. 微调向量模型:由于市面上直接使用的向量模型都是通用型模型,在特定领域的检索精度并不高,因此微调向量模型可以很大程度上提高专业领域的检索效果。
|
||||
|
||||
## FastGPT 构建知识库方案
|
||||
|
||||
### 数据存储结构
|
||||
|
||||
在 FastGPT 中,整个知识库由库、集合和数据 3 部分组成。集合可以简单理解为一个`文件`。一个`库`中可以包含多个`集合`,一个`集合`中可以包含多组`数据`。最小的搜索单位是`库`,也就是说,知识库搜索时,是对整个`库`进行搜索,而集合仅是为了对数据进行分类管理,与搜索效果无关。(起码目前还是)
|
||||
|
||||

|
||||
|
||||
### 向量存储结构
|
||||
|
||||
FastGPT 采用了`PostgresSQL`的`PG Vector`插件作为向量检索器,索引为`HNSW`。且`PostgresSQL`仅用于向量检索(该引擎可以替换成其它数据库),`MongoDB`用于其他数据的存取。
|
||||
|
||||
在`MongoDB`的`dataset.datas`表中,会存储向量原数据的信息,同时有一个`indexes`字段,会记录其对应的向量ID,这是一个数组,也就是说,一组向量可以对应多组数据。
|
||||
|
||||
在`PostgresSQL`的表中,设置一个`vector`字段用于存储向量。在检索时,会先召回向量,再根据向量的ID,去`MongoDB`中寻找原数据内容,如果对应了同一组原数据,则进行合并,向量得分取最高得分。
|
||||
|
||||

|
||||
|
||||
### 多向量的目的和使用方式
|
||||
|
||||
在一组向量中,内容的长度和语义的丰富度通常是矛盾的,无法兼得。因此,FastGPT 采用了多向量映射的方式,将一组数据映射到多组向量中,从而保障数据的完整性和语义的丰富度。
|
||||
|
||||
你可以为一组较长的文本,添加多组向量,从而在检索时,只要其中一组向量被检索到,该数据也将被召回。
|
||||
|
||||
### 检索方案
|
||||
|
||||
1. 通过`问题补全`实现指代消除和问题扩展,从而增加连续对话的检索能力以及语义丰富度。
|
||||
2. 通过`Concat query`来增加`Rerank`连续对话的时,排序的准确性。
|
||||
3. 通过`RRF`合并方式,综合多个渠道的检索效果。
|
||||
4. 通过`Rerank`来二次排序,提高精度。
|
||||
|
||||

|
||||
|
||||
|
||||
## 搜索参数
|
||||
| | | |
|
||||
| --- |---| --- |
|
||||
||  |  |
|
||||
|
||||
### 搜索模式
|
||||
|
||||
#### 语义检索
|
||||
|
||||
语义检索是通过向量距离,计算用户问题与知识库内容的距离,从而得出“相似度”,当然这并不是语文上的相似度,而是数学上的。
|
||||
|
||||
@@ -27,32 +85,49 @@ weight: 106
|
||||
- 精度不稳定
|
||||
- 受关键词和句子完整度影响
|
||||
|
||||
### 全文检索
|
||||
#### 全文检索
|
||||
|
||||
采用传统的全文检索方式。适合查找关键的主谓语等。
|
||||
|
||||
### 混合检索
|
||||
#### 混合检索
|
||||
|
||||
同时使用向量检索和全文检索,并通过 RRF 公式进行两个搜索结果合并,一般情况下搜索结果会更加丰富准确。
|
||||
|
||||
由于混合检索后的查找范围很大,并且无法直接进行相似度过滤,通常需要进行利用重排模型进行一次结果重新排序,并利用重排的得分进行过滤。
|
||||
|
||||
|
||||
|
||||
## 结果重排
|
||||
#### 结果重排
|
||||
|
||||
利用`ReRank`模型对搜索结果进行重排,绝大多数情况下,可以有效提高搜索结果的准确率。不过,重排模型与问题的完整度(主谓语齐全)有一些关系,通常会先走问题补全后再进行搜索-重排。重排后可以得到一个`0-1`的得分,代表着搜索内容与问题的相关度,该分数通常比向量的得分更加精确,可以根据得分进行过滤。
|
||||
|
||||
FastGPT 会使用 `RRF` 对重排结果、向量搜索结果、全文检索结果进行合并,得到最终的搜索结果。
|
||||
|
||||
## 引用上限
|
||||
### 搜索过滤
|
||||
#### 引用上限
|
||||
|
||||
每次搜索最多引用`n`个`tokens`的内容。
|
||||
|
||||
之所以不采用`top k`,是发现在混合知识库(问答库、文档库)时,不同`chunk`的长度差距很大,会导致`top k`的结果不稳定,因此采用了`tokens`的方式进行引用上限的控制。
|
||||
|
||||
## 最低相关度
|
||||
#### 最低相关度
|
||||
|
||||
一个`0-1`的数值,会过滤掉一些低相关度的搜索结果。
|
||||
|
||||
该值仅在`语义检索`或使用`结果重排`时生效。
|
||||
|
||||
### 问题补全
|
||||
|
||||
#### 背景
|
||||
|
||||
在 RAG 中,我们需要根据输入的问题去数据库里执行 embedding 搜索,查找相关的内容,从而查找到相似的内容(简称知识库搜索)。
|
||||
|
||||
在搜索的过程中,尤其是连续对话的搜索,我们通常会发现后续的问题难以搜索到合适的内容,其中一个原因是知识库搜索只会使用“当前”的问题去执行。看下面的例子:
|
||||
|
||||

|
||||
|
||||
用户在提问“第二点是什么”的时候,只会去知识库里查找“第二点是什么”,压根查不到内容。实际上需要查询的是“QA结构是什么”。因此我们需要引入一个【问题补全】模块,来对用户当前的问题进行补全,从而使得知识库搜索能够搜索到合适的内容。使用补全后效果如下:
|
||||
|
||||

|
||||
|
||||
#### 实现方式
|
||||
|
||||
在进行`数据检索`前,会先让模型进行`指代消除`与`问题扩展`,一方面可以可以解决指代对象不明确问题,同时可以扩展问题的语义丰富度。
|
||||
|
@@ -48,10 +48,10 @@ Authorization 为 sk-key。model 为刚刚在 One API 填写的自定义模型
|
||||
|
||||
## 接入 FastGPT
|
||||
|
||||
修改 config.json 配置文件,在 VectorModels 中加入 M3E 模型:
|
||||
修改 config.json 配置文件,在 vectorModels 中加入 M3E 模型:
|
||||
|
||||
```json
|
||||
"VectorModels": [
|
||||
"vectorModels": [
|
||||
{
|
||||
"model": "text-embedding-ada-002",
|
||||
"name": "Embedding-2",
|
||||
|
@@ -103,13 +103,21 @@ curl -O https://raw.githubusercontent.com/labring/FastGPT/main/projects/app/data
|
||||
|
||||
## 四、启动容器
|
||||
|
||||
在 docker-compose.yml 同级目录下执行
|
||||
|
||||
```bash
|
||||
# 在 docker-compose.yml 同级目录下执行
|
||||
# 进入项目目录
|
||||
cd 项目目录
|
||||
# 创建 mongo 密钥
|
||||
openssl rand -base64 756 > ./mongodb.key
|
||||
chmod 600 ./mongodb.key
|
||||
|
||||
# 启动容器
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## 四、初始化 Mongo 副本集(4.6.8以前可忽略)
|
||||
## 五、初始化 Mongo 副本集(4.6.8以前可忽略)
|
||||
|
||||
FastGPT 4.6.8 后使用了 MongoDB 的事务,需要运行在副本集上。副本集没法自动化初始化,需手动操作。
|
||||
|
||||
@@ -120,9 +128,9 @@ docker ps
|
||||
docker exec -it mongo bash
|
||||
|
||||
# 连接数据库
|
||||
mongo
|
||||
mongo -u myname -p mypassword --authenticationDatabase admin
|
||||
|
||||
# 初始化副本集。
|
||||
# 初始化副本集。如果需要外网访问,mongo:27017 可以改成 ip:27017。但是需要同时修改 FastGPT 连接的参数(MONGODB_URI=mongodb://myname:mypassword@mongo:27017/fastgpt?authSource=admin => MONGODB_URI=mongodb://myname:mypassword@ip:27017/fastgpt?authSource=admin)
|
||||
rs.initiate({
|
||||
_id: "rs0",
|
||||
members: [
|
||||
@@ -131,14 +139,6 @@ rs.initiate({
|
||||
})
|
||||
# 检查状态。如果提示 rs0 状态,则代表运行成功
|
||||
rs.status()
|
||||
|
||||
# 初始化用户
|
||||
use admin
|
||||
db.createUser({
|
||||
user: "admin",
|
||||
pwd: "password",
|
||||
roles: [{ role: "root", db: "admin" }]
|
||||
});
|
||||
```
|
||||
|
||||
## 五、访问 FastGPT
|
||||
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: 'V4.6.8(进行中)'
|
||||
description: 'FastGPT V4.6.7'
|
||||
title: 'V4.6.8(需要初始化)'
|
||||
description: 'FastGPT V4.6.8更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
toc: true
|
||||
@@ -9,7 +9,54 @@ weight: 828
|
||||
|
||||
## docker 部署 - 更新 Mongo
|
||||
|
||||
开启 Mongo 副本集模式。需要进入 mongo 执行一次 init,参考[初始化Mongo副本集](/docs/development/docker/#四初始化-mongo-副本集),这个比较麻烦,初始化后可以用 mongoshell 之类的连接试试,看能不能连接上。
|
||||
1. 修改 docker-compose.yml 的mongo部分,补上`command`和`mongodb.key`
|
||||
|
||||
```yml
|
||||
mongo:
|
||||
image: mongo:5.0.18
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/mongo:5.0.18 # 阿里云
|
||||
container_name: mongo
|
||||
ports:
|
||||
- 27017:27017
|
||||
networks:
|
||||
- fastgpt
|
||||
command: mongod --keyFile /data/mongodb.key --replSet rs0
|
||||
environment:
|
||||
# 这里密码不用变。
|
||||
- MONGO_INITDB_ROOT_USERNAME=myname
|
||||
- MONGO_INITDB_ROOT_PASSWORD=mypassword
|
||||
volumes:
|
||||
- ./mongo/data:/data/db
|
||||
- ./mongodb.key:/data/mongodb.key
|
||||
```
|
||||
|
||||
2. 创建 mongo 密钥
|
||||
|
||||
```bash
|
||||
cd 项目目录
|
||||
# 创建 mongo 密钥
|
||||
openssl rand -base64 756 > ./mongodb.key
|
||||
chmod 600 ./mongodb.key
|
||||
# 重启 Mongo
|
||||
docker-compose down
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
3. 进入容器初始化部分集合
|
||||
|
||||
```bash
|
||||
docker exec -it mongo bash
|
||||
mongo -u myname -p mypassword --authenticationDatabase admin
|
||||
# 初始化副本集。如果需要外网访问,mongo:27017 可以改成 ip:27017。但是需要同时修改 FastGPT 连接的参数(MONGODB_URI=mongodb://myname:mypassword@mongo:27017/fastgpt?authSource=admin => MONGODB_URI=mongodb://myname:mypassword@ip:27017/fastgpt?authSource=admin)
|
||||
rs.initiate({
|
||||
_id: "rs0",
|
||||
members: [
|
||||
{ _id: 0, host: "mongo:27017" }
|
||||
]
|
||||
})
|
||||
# 检查状态。如果提示 rs0 状态,则代表运行成功
|
||||
rs.status()
|
||||
```
|
||||
|
||||
## Sealos 部署 - 无需更新 Mongo
|
||||
|
||||
@@ -17,11 +64,26 @@ weight: 828
|
||||
|
||||
去除了重复的模型配置,LLM模型都合并到一个属性中:[点击查看最新的配置文件](/docs/development/configuration/)
|
||||
|
||||
## 商业版初始化
|
||||
|
||||
商业版用户需要执行一个初始化,格式化团队信息。
|
||||
|
||||
发起 1 个 HTTP 请求 ({{rootkey}} 替换成环境变量里的 `rootkey`,{{host}} 替换成自己域名)
|
||||
|
||||
```bash
|
||||
curl --location --request POST 'https://{{host}}/api/init/v468' \
|
||||
--header 'rootkey: {{rootkey}}' \
|
||||
--header 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
会初始化计费系统,内部使用可把免费的存储拉大。
|
||||
|
||||
## V4.6.8 更新说明
|
||||
|
||||
1. 新增 - 知识库搜索合并模块。
|
||||
1. 优化 - LLM 模型配置,不再区分对话、分类、提取模型。同时支持模型的默认参数,避免不同模型参数冲突,可通过`defaultConfig`传入默认的配置。
|
||||
2. 优化 - HTTP 模块,支持输出字符串自动序列化(JSON可自动转成字符串)
|
||||
3. 优化 - 流响应,参考了`ChatNextWeb`的流,更加丝滑。此外,之前提到的乱码、中断,刷新后又正常了,可能会修复)
|
||||
4. 修复 - 语音输入文件无法上传。
|
||||
5. 修复 - 对话框重新生成无法使用。
|
||||
2. **优化 - 内容补全。将内容补全内置到【知识库搜索】中,并实现了一次内容补全,即可完成“指代消除”和“问题扩展”。**FastGPT知识库搜索详细流程可查看:[知识库搜索介绍](/docs/course/data_search/)
|
||||
3. 优化 - LLM 模型配置,不再区分对话、分类、提取模型。同时支持模型的默认参数,避免不同模型参数冲突,可通过`defaultConfig`传入默认的配置。
|
||||
4. 优化 - HTTP 模块,支持输出字符串自动序列化(JSON可自动转成字符串)
|
||||
5. 优化 - 流响应,参考了`ChatNextWeb`的流,更加丝滑。此外,之前提到的乱码、中断,刷新后又正常了,可能会修复)
|
||||
6. 修复 - 语音输入文件无法上传。
|
||||
7. 修复 - 对话框重新生成无法使用。
|
@@ -994,7 +994,7 @@ export default async function (ctx: FunctionContext) {
|
||||
},
|
||||
{
|
||||
"moduleId": "p9h459",
|
||||
"name": "core.module.template.cfr",
|
||||
"name": "问题补全",
|
||||
"avatar": "/imgs/module/cfr.svg",
|
||||
"flowType": "cfr",
|
||||
"showStatus": true,
|
||||
|
@@ -638,15 +638,6 @@ HTTP 模块允许你调用任意 GET/POST 类型的 HTTP 接口,从而实现
|
||||
"value": "embedding",
|
||||
"connected": false
|
||||
},
|
||||
{
|
||||
"key": "datasetParamsModal",
|
||||
"type": "selectDatasetParamsModal",
|
||||
"label": "",
|
||||
"valueType": "any",
|
||||
"showTargetInApp": false,
|
||||
"showTargetInPlugin": false,
|
||||
"connected": false
|
||||
},
|
||||
{
|
||||
"key": "userChatInput",
|
||||
"type": "target",
|
||||
|
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: "问题补全"
|
||||
title: "问题补全(已合并到知识库搜索)"
|
||||
description: "问题补全模块介绍和使用"
|
||||
icon: "input"
|
||||
draft: false
|
||||
|
@@ -7,6 +7,8 @@ toc: true
|
||||
weight: 357
|
||||
---
|
||||
|
||||
知识库搜索具体参数说明,以及内部逻辑请移步:[FastGPT知识库搜索方案](/docs/course/data_search/)
|
||||
|
||||
## 特点
|
||||
|
||||
- 可重复添加(复杂编排时防止线太乱,可以更美观)
|
||||
|
@@ -22,14 +22,18 @@ services:
|
||||
image: mongo:5.0.18
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/mongo:5.0.18 # 阿里云
|
||||
container_name: mongo
|
||||
restart: always
|
||||
ports: # 生产环境建议不要暴露
|
||||
ports:
|
||||
- 27017:27017
|
||||
networks:
|
||||
- fastgpt
|
||||
command: --replSet rs0
|
||||
command: mongod --keyFile /data/mongodb.key --replSet rs0
|
||||
environment:
|
||||
# 默认的用户名和密码,只有首次允许有效
|
||||
- MONGO_INITDB_ROOT_USERNAME=myname
|
||||
- MONGO_INITDB_ROOT_PASSWORD=mypassword
|
||||
volumes:
|
||||
- ./mongo/data:/data/db
|
||||
- ./mongodb.key:/data/mongodb.key
|
||||
fastgpt:
|
||||
container_name: fastgpt
|
||||
image: ghcr.io/labring/fastgpt:latest # git
|
||||
@@ -52,8 +56,8 @@ services:
|
||||
- TOKEN_KEY=any
|
||||
- ROOT_KEY=root_key
|
||||
- FILE_TOKEN_KEY=filetoken
|
||||
# mongo 配置,不需要改. 用户名admin,密码password,是执行 db.createUser 时的账号和密码。
|
||||
- MONGODB_URI=mongodb://admin:password@mongo:27017/fastgpt?authSource=admin
|
||||
# mongo 配置,不需要改. 用户名myname,密码mypassword。
|
||||
- MONGODB_URI=mongodb://myname:mypassword@mongo:27017/fastgpt?authSource=admin
|
||||
# pg配置. 不需要改
|
||||
- PG_URL=postgresql://username:password@pg:5432/postgres
|
||||
volumes:
|
||||
|
@@ -17,7 +17,7 @@
|
||||
"i18next": "^22.5.1",
|
||||
"lint-staged": "^13.2.1",
|
||||
"next-i18next": "^13.3.0",
|
||||
"prettier": "^3.0.3",
|
||||
"prettier": "3.2.4",
|
||||
"react-i18next": "^12.3.1",
|
||||
"zhlint": "^0.7.1"
|
||||
},
|
||||
|
@@ -34,11 +34,6 @@ export function countPromptTokens(
|
||||
const enc = getTikTokenEnc();
|
||||
const text = `${role}\n${prompt}`;
|
||||
|
||||
// too large a text will block the thread
|
||||
if (text.length > 15000) {
|
||||
return text.length * 1.7;
|
||||
}
|
||||
|
||||
try {
|
||||
const encodeText = enc.encode(text);
|
||||
return encodeText.length + role.length; // 补充 role 估算值
|
||||
|
19
packages/global/core/app/type.d.ts
vendored
@@ -50,7 +50,7 @@ export type AppDetailType = AppSchema & {
|
||||
// };
|
||||
// Since useform cannot infer enumeration types, all enumeration keys can only be undone manually
|
||||
export type AppSimpleEditFormType = {
|
||||
templateId: string;
|
||||
// templateId: string;
|
||||
aiSettings: {
|
||||
model: string;
|
||||
systemPrompt?: string | undefined;
|
||||
@@ -62,14 +62,14 @@ export type AppSimpleEditFormType = {
|
||||
};
|
||||
dataset: {
|
||||
datasets: SelectedDatasetType;
|
||||
similarity: number;
|
||||
limit: number;
|
||||
searchMode: `${DatasetSearchModeEnum}`;
|
||||
usingReRank: boolean;
|
||||
searchEmptyText: string;
|
||||
};
|
||||
cfr: {
|
||||
background: string;
|
||||
similarity?: number;
|
||||
limit?: number;
|
||||
usingReRank?: boolean;
|
||||
searchEmptyText?: string;
|
||||
datasetSearchUsingExtensionQuery?: boolean;
|
||||
datasetSearchExtensionModel?: string;
|
||||
datasetSearchExtensionBg?: string;
|
||||
};
|
||||
userGuide: {
|
||||
welcomeText: string;
|
||||
@@ -116,9 +116,6 @@ export type AppSimpleEditConfigTemplateType = {
|
||||
usingReRank: boolean;
|
||||
searchEmptyText?: boolean;
|
||||
};
|
||||
cfr?: {
|
||||
background?: boolean;
|
||||
};
|
||||
userGuide?: {
|
||||
welcomeText?: boolean;
|
||||
variables?: boolean;
|
||||
|
@@ -6,9 +6,8 @@ import { getGuideModule, splitGuideModule } from '../module/utils';
|
||||
import { ModuleItemType } from '../module/type.d';
|
||||
import { DatasetSearchModeEnum } from '../dataset/constants';
|
||||
|
||||
export const getDefaultAppForm = (templateId = 'fastgpt-universal'): AppSimpleEditFormType => {
|
||||
export const getDefaultAppForm = (): AppSimpleEditFormType => {
|
||||
return {
|
||||
templateId,
|
||||
aiSettings: {
|
||||
model: 'gpt-3.5-turbo',
|
||||
systemPrompt: '',
|
||||
@@ -18,16 +17,15 @@ export const getDefaultAppForm = (templateId = 'fastgpt-universal'): AppSimpleEd
|
||||
quoteTemplate: '',
|
||||
maxToken: 4000
|
||||
},
|
||||
cfr: {
|
||||
background: ''
|
||||
},
|
||||
dataset: {
|
||||
datasets: [],
|
||||
similarity: 0.4,
|
||||
limit: 1500,
|
||||
searchEmptyText: '',
|
||||
searchMode: DatasetSearchModeEnum.embedding,
|
||||
usingReRank: false
|
||||
usingReRank: false,
|
||||
datasetSearchUsingExtensionQuery: true,
|
||||
datasetSearchExtensionBg: ''
|
||||
},
|
||||
userGuide: {
|
||||
welcomeText: '',
|
||||
@@ -41,14 +39,8 @@ export const getDefaultAppForm = (templateId = 'fastgpt-universal'): AppSimpleEd
|
||||
};
|
||||
|
||||
/* format app modules to edit form */
|
||||
export const appModules2Form = ({
|
||||
templateId,
|
||||
modules
|
||||
}: {
|
||||
modules: ModuleItemType[];
|
||||
templateId: string;
|
||||
}) => {
|
||||
const defaultAppForm = getDefaultAppForm(templateId);
|
||||
export const appModules2Form = ({ modules }: { modules: ModuleItemType[] }) => {
|
||||
const defaultAppForm = getDefaultAppForm();
|
||||
|
||||
const findInputValueByKey = (inputs: FlowNodeInputItemType[], key: string) => {
|
||||
return inputs.find((item) => item.key === key)?.value;
|
||||
@@ -100,6 +92,18 @@ export const appModules2Form = ({
|
||||
module.inputs,
|
||||
ModuleInputKeyEnum.datasetSearchUsingReRank
|
||||
);
|
||||
defaultAppForm.dataset.datasetSearchUsingExtensionQuery = findInputValueByKey(
|
||||
module.inputs,
|
||||
ModuleInputKeyEnum.datasetSearchUsingExtensionQuery
|
||||
);
|
||||
defaultAppForm.dataset.datasetSearchExtensionModel = findInputValueByKey(
|
||||
module.inputs,
|
||||
ModuleInputKeyEnum.datasetSearchExtensionModel
|
||||
);
|
||||
defaultAppForm.dataset.datasetSearchExtensionBg = findInputValueByKey(
|
||||
module.inputs,
|
||||
ModuleInputKeyEnum.datasetSearchExtensionBg
|
||||
);
|
||||
|
||||
// empty text
|
||||
const emptyOutputs =
|
||||
@@ -121,11 +125,6 @@ export const appModules2Form = ({
|
||||
questionGuide: questionGuide,
|
||||
tts: ttsConfig
|
||||
};
|
||||
} else if (module.flowType === FlowNodeTypeEnum.cfr) {
|
||||
const value = module.inputs.find((item) => item.key === ModuleInputKeyEnum.aiSystemPrompt);
|
||||
if (value) {
|
||||
defaultAppForm.cfr.background = value.value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
2
packages/global/core/chat/type.d.ts
vendored
@@ -109,6 +109,8 @@ export type moduleDispatchResType = {
|
||||
limit?: number;
|
||||
searchMode?: `${DatasetSearchModeEnum}`;
|
||||
searchUsingReRank?: boolean;
|
||||
extensionModel?: string;
|
||||
extensionResult?: string;
|
||||
|
||||
// cq
|
||||
cqList?: ClassifyQuestionAgentItemType[];
|
||||
|
@@ -64,7 +64,9 @@ export enum ModuleInputKeyEnum {
|
||||
datasetMaxTokens = 'limit',
|
||||
datasetSearchMode = 'searchMode',
|
||||
datasetSearchUsingReRank = 'usingReRank',
|
||||
datasetParamsModal = 'datasetParamsModal',
|
||||
datasetSearchUsingExtensionQuery = 'datasetSearchUsingExtensionQuery',
|
||||
datasetSearchExtensionModel = 'datasetSearchExtensionModel',
|
||||
datasetSearchExtensionBg = 'datasetSearchExtensionBg',
|
||||
|
||||
// context extract
|
||||
contextExtractInput = 'content',
|
||||
|
@@ -19,11 +19,11 @@ import { Output_Template_UserChatInput } from '../output';
|
||||
|
||||
export const AiCFR: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.chatNode,
|
||||
templateType: ModuleTemplateTypeEnum.tools,
|
||||
templateType: ModuleTemplateTypeEnum.other,
|
||||
flowType: FlowNodeTypeEnum.cfr,
|
||||
avatar: '/imgs/module/cfr.svg',
|
||||
name: 'core.module.template.cfr',
|
||||
intro: 'core.module.template.cfr intro',
|
||||
name: 'core.module.template.Query extension',
|
||||
intro: '该模块已合并到知识库搜索参数中,无需单独使用。',
|
||||
showStatus: true,
|
||||
inputs: [
|
||||
Input_Template_Switch,
|
||||
@@ -39,11 +39,11 @@ export const AiCFR: FlowModuleTemplateType = {
|
||||
{
|
||||
key: ModuleInputKeyEnum.aiSystemPrompt,
|
||||
type: FlowNodeInputTypeEnum.textarea,
|
||||
label: 'core.module.input.label.cfr background',
|
||||
label: 'core.module.input.label.Background',
|
||||
max: 300,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
description: 'core.app.edit.cfr background tip',
|
||||
placeholder: 'core.module.input.placeholder.cfr background',
|
||||
description: 'core.app.edit.Query extension background tip',
|
||||
placeholder: 'core.module.QueryExtension.placeholder',
|
||||
showTargetInApp: true,
|
||||
showTargetInPlugin: true
|
||||
},
|
||||
|
@@ -37,17 +37,10 @@ export const DatasetSearchModule: FlowModuleTemplateType = {
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.datasetSimilarity,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
type: FlowNodeInputTypeEnum.selectDatasetParamsModal,
|
||||
label: '',
|
||||
value: 0.4,
|
||||
valueType: ModuleIOValueTypeEnum.number,
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
markList: [
|
||||
{ label: '0', value: 0 },
|
||||
{ label: '1', value: 1 }
|
||||
],
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
},
|
||||
@@ -79,13 +72,31 @@ export const DatasetSearchModule: FlowModuleTemplateType = {
|
||||
value: false
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.datasetParamsModal,
|
||||
type: FlowNodeInputTypeEnum.selectDatasetParamsModal,
|
||||
key: ModuleInputKeyEnum.datasetSearchUsingExtensionQuery,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: '',
|
||||
valueType: ModuleIOValueTypeEnum.any,
|
||||
valueType: ModuleIOValueTypeEnum.boolean,
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false,
|
||||
value: true
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.datasetSearchExtensionModel,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: '',
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.datasetSearchExtensionBg,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: '',
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false,
|
||||
value: ''
|
||||
},
|
||||
Input_Template_UserChatInput
|
||||
],
|
||||
outputs: [
|
||||
|
159
packages/service/core/ai/functions/cfr.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import { replaceVariable } from '@fastgpt/global/common/string/tools';
|
||||
import { getAIApi } from '../config';
|
||||
import { ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
|
||||
/*
|
||||
cfr: coreference resolution - 指代消除
|
||||
可以根据上下文,完事当前问题指代内容,利于检索。
|
||||
*/
|
||||
|
||||
const defaultPrompt = `请不要回答任何问题。
|
||||
你的任务是结合历史记录,为当前问题,实现代词替换,确保问题描述的对象清晰明确。例如:
|
||||
历史记录:
|
||||
"""
|
||||
Q: 对话背景。
|
||||
A: 关于 FatGPT 的介绍和使用等问题。
|
||||
"""
|
||||
当前问题: 怎么下载
|
||||
输出: FastGPT 怎么下载?
|
||||
----------------
|
||||
历史记录:
|
||||
"""
|
||||
Q: 报错 "no connection"
|
||||
A: FastGPT 报错"no connection"可能是因为……
|
||||
"""
|
||||
当前问题: 怎么解决
|
||||
输出: FastGPT 报错"no connection"如何解决?
|
||||
----------------
|
||||
历史记录:
|
||||
"""
|
||||
Q: 作者是谁?
|
||||
A: FastGPT 的作者是 labring。
|
||||
"""
|
||||
当前问题: 介绍下他
|
||||
输出: 介绍下 FastGPT 的作者 labring。
|
||||
----------------
|
||||
历史记录:
|
||||
"""
|
||||
Q: 作者是谁?
|
||||
A: FastGPT 的作者是 labring。
|
||||
"""
|
||||
当前问题: 我想购买商业版。
|
||||
输出: FastGPT 商业版如何购买?
|
||||
----------------
|
||||
历史记录:
|
||||
"""
|
||||
Q: 对话背景。
|
||||
A: 关于 FatGPT 的介绍和使用等问题。
|
||||
"""
|
||||
当前问题: nh
|
||||
输出: nh
|
||||
----------------
|
||||
历史记录:
|
||||
"""
|
||||
Q: FastGPT 如何收费?
|
||||
A: FastGPT 收费可以参考……
|
||||
"""
|
||||
当前问题: 你知道 laf 么?
|
||||
输出: 你知道 laf 么?
|
||||
----------------
|
||||
历史记录:
|
||||
"""
|
||||
Q: FastGPT 的优势
|
||||
A: 1. 开源
|
||||
2. 简便
|
||||
3. 扩展性强
|
||||
"""
|
||||
当前问题: 介绍下第2点。
|
||||
输出: 介绍下 FastGPT 简便的优势。
|
||||
----------------
|
||||
历史记录:
|
||||
"""
|
||||
Q: 什么是 FastGPT?
|
||||
A: FastGPT 是一个 RAG 平台。
|
||||
Q: 什么是 Sealos?
|
||||
A: Sealos 是一个云操作系统。
|
||||
"""
|
||||
当前问题: 它们有什么关系?
|
||||
输出: FastGPT 和 Sealos 有什么关系?
|
||||
----------------
|
||||
历史记录:
|
||||
"""
|
||||
{{histories}}
|
||||
"""
|
||||
当前问题: {{query}}
|
||||
输出: `;
|
||||
|
||||
export const queryCfr = async ({
|
||||
chatBg,
|
||||
query,
|
||||
histories = [],
|
||||
model
|
||||
}: {
|
||||
chatBg?: string;
|
||||
query: string;
|
||||
histories: ChatItemType[];
|
||||
model: string;
|
||||
}) => {
|
||||
if (histories.length === 0 && !chatBg) {
|
||||
return {
|
||||
rawQuery: query,
|
||||
cfrQuery: query,
|
||||
model,
|
||||
inputTokens: 0,
|
||||
outputTokens: 0
|
||||
};
|
||||
}
|
||||
|
||||
const systemFewShot = chatBg
|
||||
? `Q: 对话背景。
|
||||
A: ${chatBg}
|
||||
`
|
||||
: '';
|
||||
const historyFewShot = histories
|
||||
.map((item) => {
|
||||
const role = item.obj === 'Human' ? 'Q' : 'A';
|
||||
return `${role}: ${item.value}`;
|
||||
})
|
||||
.join('\n');
|
||||
const concatFewShot = `${systemFewShot}${historyFewShot}`.trim();
|
||||
|
||||
const ai = getAIApi({
|
||||
timeout: 480000
|
||||
});
|
||||
|
||||
const result = await ai.chat.completions.create({
|
||||
model: model,
|
||||
temperature: 0.01,
|
||||
max_tokens: 150,
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: replaceVariable(defaultPrompt, {
|
||||
query: `${query}`,
|
||||
histories: concatFewShot
|
||||
})
|
||||
}
|
||||
],
|
||||
stream: false
|
||||
});
|
||||
|
||||
const answer = result.choices?.[0]?.message?.content || '';
|
||||
if (!answer) {
|
||||
return {
|
||||
rawQuery: query,
|
||||
cfrQuery: query,
|
||||
model,
|
||||
inputTokens: 0,
|
||||
outputTokens: 0
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
rawQuery: query,
|
||||
cfrQuery: answer,
|
||||
model,
|
||||
inputTokens: result.usage?.prompt_tokens || 0,
|
||||
outputTokens: result.usage?.completion_tokens || 0
|
||||
};
|
||||
};
|
@@ -1,61 +1,176 @@
|
||||
import { replaceVariable } from '@fastgpt/global/common/string/tools';
|
||||
import { getAIApi } from '../config';
|
||||
import { ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
|
||||
const prompt = `
|
||||
您的任务是生成根据用户问题,从不同角度,生成两个不同版本的问题,以便可以从矢量数据库检索相关文档。例如:
|
||||
问题: FastGPT如何使用?
|
||||
OUTPUT: ["FastGPT使用教程。","怎么使用FastGPT?"]
|
||||
-------------------
|
||||
问题: FastGPT如何收费?
|
||||
OUTPUT: ["FastGPT收费标准。","FastGPT是如何计费的?"]
|
||||
-------------------
|
||||
问题: 怎么FastGPT部署?
|
||||
OUTPUT: ["FastGPT的部署方式。","如何部署FastGPT?"]
|
||||
-------------------
|
||||
问题 question: {{q}}
|
||||
OUTPUT:
|
||||
`;
|
||||
/*
|
||||
query extension - 问题扩展
|
||||
可以根据上下文,消除指代性问题以及扩展问题,利于检索。
|
||||
*/
|
||||
|
||||
const defaultPrompt = `作为一个向量检索助手,你的任务是结合历史记录,从不同角度,为“原问题”生成个不同版本的“检索词”,从而提高向量检索的语义丰富度,提高向量检索的精度。生成的问题要求指向对象清晰明确。例如:
|
||||
历史记录:
|
||||
"""
|
||||
"""
|
||||
原问题: 介绍下剧情。
|
||||
检索词: ["发生了什么故事?","故事梗概是什么?","讲述了什么故事?"]
|
||||
----------------
|
||||
历史记录:
|
||||
"""
|
||||
Q: 对话背景。
|
||||
A: 当前对话是关于 FatGPT 的介绍和使用等。
|
||||
"""
|
||||
原问题: 怎么下载
|
||||
检索词: ["FastGPT 怎么下载?","下载 FastGPT 需要什么条件?","有哪些渠道可以下载 FastGPT?"]
|
||||
----------------
|
||||
历史记录:
|
||||
"""
|
||||
Q: 对话背景。
|
||||
A: 当前对话是关于 FatGPT 的介绍和使用等。
|
||||
Q: 报错 "no connection"
|
||||
A: 报错"no connection"可能是因为……
|
||||
"""
|
||||
原问题: 怎么解决
|
||||
检索词: ["FastGPT 报错"no connection"如何解决?", "报错 'no connection' 是什么原因?", "FastGPT提示'no connection',要怎么办?"]
|
||||
----------------
|
||||
历史记录:
|
||||
"""
|
||||
Q: 作者是谁?
|
||||
A: FastGPT 的作者是 labring。
|
||||
"""
|
||||
原问题: 介绍下他
|
||||
检索词: ["介绍下 FastGPT 的作者 labring。","作者 labring 的背景信息。","labring 为什么要做 FastGPT?"]
|
||||
----------------
|
||||
历史记录:
|
||||
"""
|
||||
Q: 对话背景。
|
||||
A: 当前对话是关于 FatGPT 的介绍和使用等。
|
||||
"""
|
||||
原问题: 高级编排怎么用
|
||||
检索词: ["FastGPT的高级编排是什么?","FastGPT高级编排的使用教程。","FastGPT高级编排有什么用?"]
|
||||
----------------
|
||||
历史记录:
|
||||
"""
|
||||
Q: 对话背景。
|
||||
A: 关于 FatGPT 的介绍和使用等问题。
|
||||
"""
|
||||
原问题: 你好。
|
||||
检索词: ["你好"]
|
||||
----------------
|
||||
历史记录:
|
||||
"""
|
||||
Q: FastGPT 如何收费?
|
||||
A: FastGPT 收费可以参考……
|
||||
"""
|
||||
原问题: 你知道 laf 么?
|
||||
检索词: ["laf是什么?","如何使用laf?","laf的介绍。"]
|
||||
----------------
|
||||
历史记录:
|
||||
"""
|
||||
Q: FastGPT 的优势
|
||||
A: 1. 开源
|
||||
2. 简便
|
||||
3. 扩展性强
|
||||
"""
|
||||
原问题: 介绍下第2点。
|
||||
检索词: ["介绍下 FastGPT 简便的优势", "FastGPT 为什么使用起来简便?","FastGPT的有哪些简便的功能?"]。
|
||||
----------------
|
||||
历史记录:
|
||||
"""
|
||||
Q: 什么是 FastGPT?
|
||||
A: FastGPT 是一个 RAG 平台。
|
||||
Q: 什么是 Laf?
|
||||
A: Laf 是一个云函数开发平台。
|
||||
"""
|
||||
原问题: 它们有什么关系?
|
||||
检索词: ["FastGPT和Laf有什么关系?","FastGPT的RAG是用Laf实现的么?"]
|
||||
----------------
|
||||
历史记录:
|
||||
"""
|
||||
{{histories}}
|
||||
"""
|
||||
原问题: {{query}}
|
||||
检索词: `;
|
||||
|
||||
export const queryExtension = async ({
|
||||
chatBg,
|
||||
query,
|
||||
histories = [],
|
||||
model
|
||||
}: {
|
||||
chatBg?: string;
|
||||
query: string;
|
||||
histories: ChatItemType[];
|
||||
model: string;
|
||||
}): Promise<{
|
||||
rawQuery: string;
|
||||
extensionQueries: string[];
|
||||
model: string;
|
||||
inputTokens: number;
|
||||
outputTokens: number;
|
||||
}> => {
|
||||
const systemFewShot = chatBg
|
||||
? `Q: 对话背景。
|
||||
A: ${chatBg}
|
||||
`
|
||||
: '';
|
||||
const historyFewShot = histories
|
||||
.map((item) => {
|
||||
const role = item.obj === 'Human' ? 'Q' : 'A';
|
||||
return `${role}: ${item.value}`;
|
||||
})
|
||||
.join('\n');
|
||||
const concatFewShot = `${systemFewShot}${historyFewShot}`.trim();
|
||||
|
||||
export const searchQueryExtension = async ({ query, model }: { query: string; model: string }) => {
|
||||
const ai = getAIApi({
|
||||
timeout: 480000
|
||||
});
|
||||
|
||||
const result = await ai.chat.completions.create({
|
||||
model,
|
||||
temperature: 0,
|
||||
model: model,
|
||||
temperature: 0.01,
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: replaceVariable(prompt, { q: query })
|
||||
content: replaceVariable(defaultPrompt, {
|
||||
query: `${query}`,
|
||||
histories: concatFewShot
|
||||
})
|
||||
}
|
||||
],
|
||||
stream: false
|
||||
});
|
||||
|
||||
const answer = result.choices?.[0]?.message?.content || '';
|
||||
let answer = result.choices?.[0]?.message?.content || '';
|
||||
if (!answer) {
|
||||
return {
|
||||
queries: [query],
|
||||
rawQuery: query,
|
||||
extensionQueries: [],
|
||||
model,
|
||||
inputTokens: 0,
|
||||
responseTokens: 0
|
||||
outputTokens: 0
|
||||
};
|
||||
}
|
||||
|
||||
answer = answer.replace(/\\"/g, '"');
|
||||
|
||||
try {
|
||||
const queries = JSON.parse(answer) as string[];
|
||||
|
||||
return {
|
||||
queries: JSON.parse(answer) as string[],
|
||||
rawQuery: query,
|
||||
extensionQueries: queries,
|
||||
model,
|
||||
inputTokens: result.usage?.prompt_tokens || 0,
|
||||
responseTokens: result.usage?.completion_tokens || 0
|
||||
outputTokens: result.usage?.completion_tokens || 0
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return {
|
||||
queries: [query],
|
||||
rawQuery: query,
|
||||
extensionQueries: [],
|
||||
model,
|
||||
inputTokens: 0,
|
||||
responseTokens: 0
|
||||
outputTokens: 0
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@@ -46,7 +46,9 @@ export function ChatContextFilter({
|
||||
|
||||
/* 整体 tokens 超出范围, system必须保留 */
|
||||
if (maxTokens <= 0) {
|
||||
if (chats.length > 1) {
|
||||
chats.shift();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
62
packages/service/core/dataset/search/utils.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
||||
import { queryExtension } from '../../ai/functions/queryExtension';
|
||||
import { ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { hashStr } from '@fastgpt/global/common/string/tools';
|
||||
|
||||
export const datasetSearchQueryExtension = async ({
|
||||
query,
|
||||
extensionModel,
|
||||
extensionBg = '',
|
||||
histories = []
|
||||
}: {
|
||||
query: string;
|
||||
extensionModel?: LLMModelItemType;
|
||||
extensionBg?: string;
|
||||
histories?: ChatItemType[];
|
||||
}) => {
|
||||
// concat query
|
||||
let queries = [query];
|
||||
let rewriteQuery =
|
||||
histories.length > 0
|
||||
? `${histories
|
||||
.map((item) => {
|
||||
return `${item.obj}: ${item.value}`;
|
||||
})
|
||||
.join('\n')}
|
||||
Human: ${query}
|
||||
`
|
||||
: query;
|
||||
|
||||
// ai extension
|
||||
const aiExtensionResult = await (async () => {
|
||||
if (!extensionModel) return;
|
||||
const result = await queryExtension({
|
||||
chatBg: extensionBg,
|
||||
query,
|
||||
histories,
|
||||
model: extensionModel.model
|
||||
});
|
||||
if (result.extensionQueries?.length === 0) return;
|
||||
return result;
|
||||
})();
|
||||
|
||||
if (aiExtensionResult) {
|
||||
queries = queries.concat(aiExtensionResult.extensionQueries);
|
||||
rewriteQuery = queries.join('\n');
|
||||
}
|
||||
|
||||
const set = new Set<string>();
|
||||
const filterSameQueries = queries.filter((item) => {
|
||||
// 删除所有的标点符号与空格等,只对文本进行比较
|
||||
const str = hashStr(item.replace(/[^\p{L}\p{N}]/gu, ''));
|
||||
if (set.has(str)) return false;
|
||||
set.add(str);
|
||||
return true;
|
||||
});
|
||||
|
||||
return {
|
||||
concatQueries: filterSameQueries,
|
||||
rewriteQuery,
|
||||
aiExtensionResult
|
||||
};
|
||||
};
|
@@ -131,6 +131,7 @@ export const iconPaths = {
|
||||
'modal/edit': () => import('./icons/modal/edit.svg'),
|
||||
'modal/manualDataset': () => import('./icons/modal/manualDataset.svg'),
|
||||
'modal/selectSource': () => import('./icons/modal/selectSource.svg'),
|
||||
'modal/setting': () => import('./icons/modal/setting.svg'),
|
||||
more: () => import('./icons/more.svg'),
|
||||
out: () => import('./icons/out.svg'),
|
||||
'phoneTabbar/me': () => import('./icons/phoneTabbar/me.svg'),
|
||||
|
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1706857056481" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4218" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M817.7 597.9c2.7-10.9 12.5-18.5 23.7-18.5h69.8V444.6h-74.4c-10.7 0-20.2-7-23.4-17.2-6.8-22-16.8-44.2-29.5-65.8-5.6-9.6-4.1-21.8 3.8-29.7l54.3-54.3-63.7-63.7-7.6 7.6c-14-7.5-28.7-14-43.8-19.5l-36.8 36.8c-7.6 7.6-19.4 9.4-28.9 4.3-19.5-10.5-40.9-19-63.7-25.4-10.6-3-17.9-12.6-17.9-23.6V113h-61.4v81c-24 6.7-47.1 15.8-68.7 27.4l-11.1-11.1c-3 3.4-7 5.9-11.6 7.2-22.8 6.4-44.2 14.9-63.7 25.4-9.5 5.1-21.2 3.4-28.9-4.3l-24.9-24.9-63.7 63.7 71.6 71.6c-13.1 22.2-24 45.9-31.8 71h-72.2c-0.8 2.4-1.6 4.8-2.4 7.3-3.2 10.2-12.7 17.2-23.4 17.2h-1v135.2c9.7 1.4 17.7 8.5 20.1 18.2 0.5 2 1 4 1.6 6h72.6c6.9 27.3 16.7 53.4 29.7 77.6l-65 65 63.7 63.7 13.7-13.7c7.9-7.9 20.1-9.5 29.7-3.8 23.3 13.7 48.2 24.3 74.1 31.6 0.2 0.1 0.5 0.2 0.7 0.2l10.7-10.7c24.8 14.6 51.6 26.1 79.9 34V911h61.4v-63.2c0-11 7.3-20.6 17.9-23.6 25.9-7.3 50.8-17.9 74.1-31.6 9.6-5.7 21.8-4.1 29.7 3.8l38.4 38.3c9-3.7 17.7-7.8 26.3-12.2l76.1-76.1-47.7-47.7c-7.6-7.6-9.4-19.4-4.3-28.9 12.1-21.8 21.4-46 27.9-71.9z" fill="#91B4FF" p-id="4219"></path><path d="M389 626.6c-7.5 0-15-3.5-19.8-10-3.5-4.7-6.7-9.6-9.7-14.7-6.9-11.6-3-26.7 8.6-33.5 11.6-6.9 26.7-3 33.5 8.6 2.2 3.7 4.5 7.2 7 10.7 8 10.9 5.6 26.2-5.3 34.2-4.2 3.2-9.3 4.7-14.3 4.7z" fill="#3778FF" p-id="4220"></path><path d="M512.2 689.1c-25.6 0-50.2-5.3-73.3-15.8-12.3-5.6-17.7-20.1-12.1-32.4 5.6-12.3 20.1-17.7 32.4-12.1 16.7 7.6 34.5 11.4 53 11.4 70.7 0 128.2-57.5 128.2-128.2s-57.5-128.2-128.2-128.2S384 441.3 384 512c0 13.5-11 24.5-24.5 24.5S335 525.5 335 512c0-97.7 79.5-177.1 177.1-177.1 97.7 0 177.1 79.5 177.1 177.1 0.1 97.7-79.3 177.1-177 177.1z" fill="#3778FF" p-id="4221"></path><path d="M603.8 960H420c-13.5 0-24.5-11-24.5-24.5v-69.7c-17.5-5.9-34.6-13.2-51.1-21.8l-49.7 49.7c-9.6 9.6-25.1 9.6-34.6 0l-130-130c-9.6-9.6-9.6-25.1 0-34.6l52.5-52.5c-7.2-15.4-13.4-31.5-18.5-48.3H88.3c-13.5 0-24.5-11-24.5-24.5V420.1c0-13.5 11-24.5 24.5-24.5h81.4c5.3-14.2 11.6-28.3 18.9-42.2L130 294.8c-9.6-9.6-9.6-25.1 0-34.6l130-130c9.6-9.6 25.1-9.6 34.6 0l61.4 61.4c12.6-5.9 25.8-11.2 39.4-15.7V88.4c0-13.5 11-24.5 24.5-24.5h183.8c13.5 0 24.5 11 24.5 24.5v87.5c13.6 4.6 26.8 9.8 39.4 15.7l61.4-61.4c9.6-9.6 25.1-9.6 34.6 0l130 130c9.6 9.6 9.6 25.1 0 34.6L835 353.4c7.3 13.9 13.6 28.1 18.9 42.2h81.4c13.5 0 24.5 11 24.5 24.5v183.8c0 13.5-11 24.5-24.5 24.5h-75.7c-5.1 16.8-11.3 33-18.5 48.3l52.5 52.5c9.6 9.6 9.6 25.1 0 34.6l-130 130c-9.6 9.6-25.1 9.6-34.6 0l-49.7-49.7c-16.5 8.6-33.6 15.8-51.1 21.8v69.7c0.1 13.5-10.9 24.4-24.4 24.4z m-159.4-48.9h134.9v-63.2c0-11 7.3-20.6 17.9-23.6 25.9-7.3 50.8-17.9 74.1-31.6 9.6-5.7 21.8-4.1 29.7 3.8l45.4 45.4 95.4-95.4-47.7-47.7c-7.6-7.6-9.4-19.4-4.3-28.9 11.8-21.9 21-46.1 27.5-72 2.7-10.9 12.5-18.5 23.7-18.5h69.8V444.6h-74.4c-10.7 0-20.2-7-23.4-17.2-6.8-22-16.8-44.2-29.5-65.8-5.6-9.6-4.1-21.8 3.8-29.7l54.3-54.3-95.4-95.4-56.6 56.6c-7.6 7.6-19.4 9.4-28.9 4.3-19.5-10.5-40.9-19-63.7-25.4-10.6-3-17.9-12.6-17.9-23.6V113H444.4v81c0 11-7.3 20.6-17.9 23.6-22.8 6.4-44.2 14.9-63.7 25.4-9.5 5.1-21.2 3.4-28.9-4.3l-56.6-56.6-95.3 95.4 54.3 54.3c7.9 7.9 9.4 20.1 3.8 29.7-12.7 21.6-22.6 43.8-29.5 65.8-3.2 10.2-12.7 17.2-23.4 17.2h-74.4v134.9h69.8c11.2 0 21 7.6 23.7 18.5 6.5 25.9 15.8 50.1 27.5 72 5.1 9.5 3.4 21.3-4.3 28.9L182 746.5l95.4 95.4 45.4-45.4c7.9-7.9 20.1-9.5 29.7-3.8 23.3 13.7 48.2 24.3 74.1 31.6 10.6 3 17.9 12.6 17.9 23.6v63.2z" fill="#3778FF" p-id="4222"></path></svg>
|
After Width: | Height: | Size: 3.6 KiB |
@@ -1,11 +1,14 @@
|
||||
import React from 'react';
|
||||
import Editor, { loader } from '@monaco-editor/react';
|
||||
import React, { useEffect } from 'react';
|
||||
import Editor, { loader, useMonaco } from '@monaco-editor/react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { Box, BoxProps, useToast } from '@chakra-ui/react';
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import MyIcon from '../../Icon';
|
||||
import { EditorVariablePickerType } from '../PromptEditor/type';
|
||||
import { useToast } from '../../../../hooks/useToast';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
loader.config({
|
||||
paths: { vs: '/js/monaco-editor.0.43.0' }
|
||||
paths: { vs: 'https://cdn.staticfile.net/monaco-editor/0.43.0/min/vs' }
|
||||
});
|
||||
|
||||
type Props = Omit<BoxProps, 'onChange' | 'resize' | 'height'> & {
|
||||
@@ -14,6 +17,7 @@ type Props = Omit<BoxProps, 'onChange' | 'resize' | 'height'> & {
|
||||
defaultValue?: string;
|
||||
value?: string;
|
||||
onChange?: (e: string) => void;
|
||||
variables?: EditorVariablePickerType[];
|
||||
};
|
||||
|
||||
const options = {
|
||||
@@ -38,10 +42,43 @@ const options = {
|
||||
tabSize: 2
|
||||
};
|
||||
|
||||
const JSONEditor = ({ defaultValue, value, onChange, resize, ...props }: Props) => {
|
||||
const toast = useToast();
|
||||
const JSONEditor = ({ defaultValue, value, onChange, resize, variables, ...props }: Props) => {
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation();
|
||||
const [height, setHeight] = useState(props.height || 100);
|
||||
const initialY = useRef(0);
|
||||
const completionRegisterRef = useRef<any>();
|
||||
const monaco = useMonaco();
|
||||
|
||||
useEffect(() => {
|
||||
completionRegisterRef.current = monaco?.languages.registerCompletionItemProvider('json', {
|
||||
triggerCharacters: ['"'],
|
||||
provideCompletionItems: function (model, position) {
|
||||
var word = model.getWordUntilPosition(position);
|
||||
var range = {
|
||||
startLineNumber: position.lineNumber,
|
||||
endLineNumber: position.lineNumber,
|
||||
startColumn: word.startColumn,
|
||||
endColumn: word.endColumn
|
||||
};
|
||||
return {
|
||||
suggestions:
|
||||
variables?.map((item) => ({
|
||||
label: `${item.label}`,
|
||||
kind: monaco.languages.CompletionItemKind.Function,
|
||||
documentation: item.label,
|
||||
insertText: `{{${item.label}}}`,
|
||||
range: range
|
||||
})) || [],
|
||||
dispose: () => {}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
completionRegisterRef.current?.dispose();
|
||||
};
|
||||
}, [monaco, completionRegisterRef.current]);
|
||||
|
||||
const handleMouseDown = useCallback((e: React.MouseEvent) => {
|
||||
initialY.current = e.clientY;
|
||||
@@ -111,15 +148,14 @@ const JSONEditor = ({ defaultValue, value, onChange, resize, ...props }: Props)
|
||||
onChange={(e) => onChange?.(e || '')}
|
||||
wrapperProps={{
|
||||
onBlur: () => {
|
||||
if (!value) return;
|
||||
try {
|
||||
JSON.parse(value as string);
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
title: 'Invalid JSON',
|
||||
title: t('common.Invalid Json'),
|
||||
description: error.message,
|
||||
position: 'top',
|
||||
status: 'error',
|
||||
duration: 3000,
|
||||
status: 'warning',
|
||||
isClosable: true
|
||||
});
|
||||
}
|
||||
|
4152
pnpm-lock.yaml
generated
@@ -48,7 +48,7 @@
|
||||
"model": "gpt-4-0125-preview",
|
||||
"name": "gpt-4-turbo",
|
||||
"maxContext": 125000,
|
||||
"maxResponse": 125000,
|
||||
"maxResponse": 4000,
|
||||
"quoteMaxToken": 100000,
|
||||
"maxTemperature": 1.2,
|
||||
"inputPrice": 0,
|
||||
|
@@ -10,9 +10,6 @@
|
||||
"quoteTemplate": false,
|
||||
"quotePrompt": false
|
||||
},
|
||||
"cfr": {
|
||||
"background": true
|
||||
},
|
||||
"dataset": {
|
||||
"datasets": true,
|
||||
"similarity": false,
|
||||
|
@@ -93,6 +93,7 @@
|
||||
"Finish": "Finish",
|
||||
"Input": "Input",
|
||||
"Intro": "Intro",
|
||||
"Invalid Json": "Invalid Json",
|
||||
"Last Step": "Last",
|
||||
"Last use time": "Last use time",
|
||||
"Load Failed": "Load Failed",
|
||||
@@ -274,13 +275,12 @@
|
||||
"deterministic": "Deterministic",
|
||||
"edit": {
|
||||
"Confirm Save App Tip": "The application may be in advanced orchestration mode, and the advanced orchestration configuration will be overwritten after saving, please confirm!",
|
||||
"Open cfr": "Open Cfr",
|
||||
"Out Ad Edit": "You are about to exit the Advanced orchestration page, please confirm",
|
||||
"Prompt Editor": "Prompt Editor",
|
||||
"Query extension background prompt": "Chat background description",
|
||||
"Query extension background tip": "Describing the scope of the current conversation makes it easier for AI to complete first or vague questions, thereby enhancing the knowledge base's ability to continue conversations.\nIf the value is empty, the question completion function is not used for [first question].",
|
||||
"Save and out": "Save out",
|
||||
"UnSave": "UnSave",
|
||||
"cfr background prompt": "Chat background description",
|
||||
"cfr background tip": "Describing the scope of the current conversation makes it easier for AI to complete first or vague questions, thereby enhancing the knowledge base's ability to continue conversations.\nIf the value is empty, the problem completion function is not used for the \"first problem\".\nIf the value is none, the problem completion function is not used."
|
||||
"UnSave": "UnSave"
|
||||
},
|
||||
"error": {
|
||||
"App name can not be empty": "App name can not be empty",
|
||||
@@ -432,6 +432,7 @@
|
||||
},
|
||||
"response": {
|
||||
"Complete Response": "Complete Response",
|
||||
"Extension model": "Extension model",
|
||||
"Plugin Resonse Detail": "Plugin Detail",
|
||||
"Read complete response": "Read Detail",
|
||||
"Read complete response tips": "Click to see the detailed process",
|
||||
@@ -649,10 +650,13 @@
|
||||
},
|
||||
"link": "Link",
|
||||
"search": {
|
||||
"Dataset Search Params": "Dataset Search Params",
|
||||
"Basic params": "Basic settings",
|
||||
"Dataset Search Params": "Dataset search configuration",
|
||||
"Embedding score": "Embedding score",
|
||||
"Empty result response": "Empty Response",
|
||||
"Empty result response Tips": "If you fill in the content, if no suitable content is found, you will directly reply to the content.",
|
||||
"Filter": "Search filter",
|
||||
"Limit": "Search limit",
|
||||
"Max Tokens": "Max Tokens",
|
||||
"Max Tokens Tips": "The maximum number of Tokens in a single search, about 1 word in Chinese =1.7Tokens, about 1 word in English =1 tokens",
|
||||
"Min Similarity": "Min Similarity",
|
||||
@@ -670,6 +674,8 @@
|
||||
"Source id": "Source ID",
|
||||
"Source name": "Source",
|
||||
"Top K": "Top K",
|
||||
"Using cfr": "Open query extension",
|
||||
"Using query extension": "",
|
||||
"mode": {
|
||||
"embedding": "Vector recall",
|
||||
"embedding desc": "Use vectors for text correlation queries",
|
||||
@@ -694,6 +700,9 @@
|
||||
},
|
||||
"search mode": "Search Mode"
|
||||
},
|
||||
"settings": {
|
||||
"Search basic params": ""
|
||||
},
|
||||
"status": {
|
||||
"active": "Ready",
|
||||
"syncing": "Syncing"
|
||||
@@ -752,6 +761,10 @@
|
||||
"Field key": "Key",
|
||||
"Input Type": "Input Type",
|
||||
"Plugin output must connect": "Custom outputs must all be connected",
|
||||
"QueryExtension": {
|
||||
"placeholder": "Questions about python introduction and usage, etc. The current conversation is related to the game GTA5.",
|
||||
"tip": "Describes the scope of the current conversation, making it easier for the AI to complete first or vague questions, thereby enhancing the knowledge base's ability to continue conversations.If \n is empty, the question completion function is not used in the first conversation. "
|
||||
},
|
||||
"Unlink tip": "[{{name}}] An unfilled or unconnected parameter exists",
|
||||
"Variable": "Variables",
|
||||
"Variable Setting": "Variable Setting",
|
||||
@@ -777,7 +790,6 @@
|
||||
"TFSwitch textarea": "Allows you to define a number of strings to achieve false matching, one per line, and supports regular expressions.",
|
||||
"Trigger": "Most of the time, you don't need to concatenate this property. You can connect this property when you need to delay execution, or precisely control the timing of execution.",
|
||||
"anyInput": "Can pass anything ",
|
||||
"cfr background": "Describes the scope of the current conversation, making it easier for the AI to complete first or vague questions, thereby enhancing the knowledge base's ability to continue conversations. If\nis empty, the question completion function is not used in the first conversation. ",
|
||||
"dynamic input": "Receives parameters dynamically added by the user and will be tiled in at run time ",
|
||||
"textEditor textarea": "The passed variable can be referenced by {{key}}. Variables support only strings or numbers."
|
||||
},
|
||||
@@ -794,15 +806,13 @@
|
||||
"TFSwitch textarea": "Custom False matching rule ",
|
||||
"aiModel": "AI model ",
|
||||
"anyInput": "Any input ",
|
||||
"cfr background": "Background",
|
||||
"chat history": "chat history",
|
||||
"switch": "Trigger",
|
||||
"textEditor textarea": "Text edit",
|
||||
"user question": "User question"
|
||||
},
|
||||
"placeholder": {
|
||||
"Classify background": "For example:\n1.AIGC (Artificial Intelligence Generates content) refers to the automatic or semi-automatic generation of digital content, such as text, images, music, videos, and so on, using artificial intelligence technologies. AIGC technologies include, but are not limited to, natural language processing, computer vision, machine learning, and deep learning. These technologies can create new content or modify existing content to meet specific creative, educational, entertainment or information needs.",
|
||||
"cfr background": "Questions about the introduction and use of python. \nThe current dialogue is related to the game GTA5."
|
||||
"Classify background": "For example:\n1.AIGC (Artificial Intelligence Generates content) refers to the automatic or semi-automatic generation of digital content, such as text, images, music, videos, and so on, using artificial intelligence technologies. AIGC technologies include, but are not limited to, natural language processing, computer vision, machine learning, and deep learning. These technologies can create new content or modify existing content to meet specific creative, educational, entertainment or information needs."
|
||||
}
|
||||
},
|
||||
"inputType": {
|
||||
@@ -858,6 +868,8 @@
|
||||
"Http request": "Http request",
|
||||
"Http request intro": " Can issue an HTTP request to implement more complex operations (Internet search, database query, etc.)",
|
||||
"My plugin module": "Personal plugins",
|
||||
"Query extension": "Query extension",
|
||||
"Query extension intro": "If the problem completion function is enabled, the accuracy of knowledge base search can be improved in continuous conversations. After this function is enabled, when searching the knowledge base, AI will be used to complete the missing information of the problem according to the conversation records.",
|
||||
"Response module": "Text output",
|
||||
"Running app": "Running app",
|
||||
"Running app intro": "You can select a different app to run",
|
||||
@@ -867,8 +879,6 @@
|
||||
"Tool module": "Tools",
|
||||
"UnKnow Module": "UnKnow Module",
|
||||
"User guide": "User guide",
|
||||
"cfr": "Coreference resolution",
|
||||
"cfr intro": "Refine the current issue based on history, making it more conducive to knowledge base search, while improving continuous conversation capabilities.",
|
||||
"textEditor": "Text Editor",
|
||||
"textEditor intro": "Output of fixed or incoming text after edit"
|
||||
},
|
||||
@@ -1342,6 +1352,9 @@
|
||||
"Data Length": "Data length",
|
||||
"Dataset store": "",
|
||||
"Duration": "Duration(s)",
|
||||
"Extension Input Token Length": "Extension input tokens",
|
||||
"Extension Output Token Length": "Extension output tokens",
|
||||
"Extension result": "Extension result",
|
||||
"Input Token Length": "Input tokens",
|
||||
"Module name": "Module name",
|
||||
"Next Step Guide": "",
|
||||
|
@@ -93,6 +93,7 @@
|
||||
"Finish": "完成",
|
||||
"Input": "输入",
|
||||
"Intro": "介绍",
|
||||
"Invalid Json": "无效的JSON格式,请注意检查。",
|
||||
"Last Step": "上一步",
|
||||
"Last use time": "最后使用时间",
|
||||
"Load Failed": "加载失败",
|
||||
@@ -274,13 +275,12 @@
|
||||
"deterministic": "严谨",
|
||||
"edit": {
|
||||
"Confirm Save App Tip": "该应用可能为高级编排模式,保存后将会覆盖高级编排配置,请确认!",
|
||||
"Open cfr": "开启自动补全",
|
||||
"Out Ad Edit": "您即将退出高级编排页面,请确认",
|
||||
"Prompt Editor": "提示词编辑",
|
||||
"Query extension background prompt": "对话背景描述",
|
||||
"Query extension background tip": "描述当前对话的范围,便于AI为当前问题进行补全和扩展。填写的内容,通常为该助手",
|
||||
"Save and out": "保存并退出",
|
||||
"UnSave": "不保存",
|
||||
"cfr background prompt": "对话背景描述",
|
||||
"cfr background tip": "描述当前对话的范围,便于AI补全首次问题或模糊的问题,从而增强知识库连续对话的能力。\n值为空时,表示【首个问题】不使用问题补全功能。\n值为 none 时,表示不使用问题补全功能。"
|
||||
"UnSave": "不保存"
|
||||
},
|
||||
"error": {
|
||||
"App name can not be empty": "应用名不能为空",
|
||||
@@ -432,6 +432,7 @@
|
||||
},
|
||||
"response": {
|
||||
"Complete Response": "完整响应",
|
||||
"Extension model": "问题补全模型",
|
||||
"Plugin Resonse Detail": "插件详情",
|
||||
"Read complete response": "查看详情",
|
||||
"Read complete response tips": "点击查看详细流程",
|
||||
@@ -445,7 +446,7 @@
|
||||
"module http result": "响应体",
|
||||
"module http url": "请求地址",
|
||||
"module limit": "单次搜索上限",
|
||||
"module maxToken": "最大 Tokens",
|
||||
"module maxToken": "最大响应 Tokens",
|
||||
"module model": "模型",
|
||||
"module name": "模型名",
|
||||
"module price": "计费",
|
||||
@@ -548,7 +549,8 @@
|
||||
"success": "开始同步"
|
||||
}
|
||||
},
|
||||
"training": {}
|
||||
"training": {
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"Auxiliary Data": "辅助数据",
|
||||
@@ -650,10 +652,13 @@
|
||||
},
|
||||
"link": "链接",
|
||||
"search": {
|
||||
"Dataset Search Params": "搜索参数",
|
||||
"Basic params": "基础参数",
|
||||
"Dataset Search Params": "知识库搜索配置",
|
||||
"Embedding score": "语意检索得分",
|
||||
"Empty result response": "空搜索回复",
|
||||
"Empty result response Tips": "若填写该内容,没有搜索到合适内容时,将直接回复填写的内容。",
|
||||
"Filter": "搜索过滤",
|
||||
"Limit": "",
|
||||
"Max Tokens": "引用上限",
|
||||
"Max Tokens Tips": "单次搜索最大的 Tokens 数量,中文约1字=1.7Tokens,英文约1字=1Tokens",
|
||||
"Min Similarity": "最低相关度",
|
||||
@@ -671,6 +676,8 @@
|
||||
"Source id": "来源ID",
|
||||
"Source name": "引用来源名",
|
||||
"Top K": "单次搜索上限",
|
||||
"Using cfr": "",
|
||||
"Using query extension": "使用问题补全",
|
||||
"mode": {
|
||||
"embedding": "语义检索",
|
||||
"embedding desc": "使用向量进行文本相关性查询",
|
||||
@@ -695,6 +702,9 @@
|
||||
},
|
||||
"search mode": "搜索模式"
|
||||
},
|
||||
"settings": {
|
||||
"Search basic params": "检索参数"
|
||||
},
|
||||
"status": {
|
||||
"active": "已就绪",
|
||||
"syncing": "同步中"
|
||||
@@ -753,6 +763,10 @@
|
||||
"Field key": "字段 Key",
|
||||
"Input Type": "输入类型",
|
||||
"Plugin output must connect": "自定义输出必须全部连接",
|
||||
"QueryExtension": {
|
||||
"placeholder": "例如:\n关于 python 的介绍和使用等问题。\n当前对话与游戏《GTA5》有关。",
|
||||
"tip": "描述当前对话的范围,便于AI补全首次问题或模糊的问题,从而增强知识库连续对话的能力。建议开启该功能后,都简单的描述在对话的背景,否则容易造成补全对象不准确。"
|
||||
},
|
||||
"Unlink tip": "【{{name}}】存在未填或未连接参数",
|
||||
"Variable": "全局变量",
|
||||
"Variable Setting": "变量设置",
|
||||
@@ -778,7 +792,6 @@
|
||||
"TFSwitch textarea": "允许定义一些字符串来实现 false 匹配,每行一个,支持正则表达式。",
|
||||
"Trigger": "大部分时候,你不需要连接该属性。\n当你需要延迟执行,或精确控制执行时机时,可以连接该属性。",
|
||||
"anyInput": "可传入任意内容",
|
||||
"cfr background": "描述当前对话的范围,便于AI补全首次问题或模糊的问题,从而增强知识库连续对话的能力。\n为空时,表示【首次对话】不使用问题补全功能。",
|
||||
"dynamic input": "接收用户动态添加的参数,会在运行时将这些参数平铺传入",
|
||||
"textEditor textarea": "可以通过 {{key}} 的方式引用传入的变量。变量仅支持字符串或数字。"
|
||||
},
|
||||
@@ -795,15 +808,13 @@
|
||||
"TFSwitch textarea": "自定义 False 匹配规则",
|
||||
"aiModel": "AI 模型",
|
||||
"anyInput": "任意内容输入",
|
||||
"cfr background": "背景知识",
|
||||
"chat history": "聊天记录",
|
||||
"switch": "触发器",
|
||||
"textEditor textarea": "文本编辑",
|
||||
"user question": "用户问题"
|
||||
},
|
||||
"placeholder": {
|
||||
"Classify background": "例如: \n1. AIGC(人工智能生成内容)是指使用人工智能技术自动或半自动地生成数字内容,如文本、图像、音乐、视频等。\n2. AIGC技术包括但不限于自然语言处理、计算机视觉、机器学习和深度学习。这些技术可以创建新内容或修改现有内容,以满足特定的创意、教育、娱乐或信息需求。",
|
||||
"cfr background": "关于 python 的介绍和使用等问题。\n当前对话与游戏《GTA5》有关。"
|
||||
"Classify background": "例如: \n1. AIGC(人工智能生成内容)是指使用人工智能技术自动或半自动地生成数字内容,如文本、图像、音乐、视频等。\n2. AIGC技术包括但不限于自然语言处理、计算机视觉、机器学习和深度学习。这些技术可以创建新内容或修改现有内容,以满足特定的创意、教育、娱乐或信息需求。"
|
||||
}
|
||||
},
|
||||
"inputType": {
|
||||
@@ -859,6 +870,8 @@
|
||||
"Http request": "Http 请求",
|
||||
"Http request intro": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)",
|
||||
"My plugin module": "个人插件",
|
||||
"Query extension": "问题补全",
|
||||
"Query extension intro": "开启问题补全功能,可以提高提高连续对话时,知识库搜索的精度。开启该功能后,在进行知识库搜索时,会根据对话记录,利用 AI 补全问题缺失的信息。",
|
||||
"Response module": "文本输出",
|
||||
"Running app": "应用调用",
|
||||
"Running app intro": "可以选择一个其他应用进行调用",
|
||||
@@ -868,8 +881,6 @@
|
||||
"Tool module": "工具",
|
||||
"UnKnow Module": "未知模块",
|
||||
"User guide": "用户引导",
|
||||
"cfr": "问题补全",
|
||||
"cfr intro": "根据历史记录,完善当前问题,使其更利于知识库搜索,同时提高连续对话能力。",
|
||||
"textEditor": "文本加工",
|
||||
"textEditor intro": "可对固定或传入的文本进行加工后输出"
|
||||
},
|
||||
@@ -1343,6 +1354,9 @@
|
||||
"Data Length": "数据长度",
|
||||
"Dataset store": "知识库存储",
|
||||
"Duration": "时长(秒)",
|
||||
"Extension Input Token Length": "问题补全输入Tokens",
|
||||
"Extension Output Token Length": "问题补全输出Tokens",
|
||||
"Extension result": "问题补全结果",
|
||||
"Input Token Length": "输入 Tokens",
|
||||
"Module name": "模块名",
|
||||
"Next Step Guide": "下一步指引",
|
||||
|
@@ -129,6 +129,7 @@ const ResponseBox = React.memo(function ResponseBox({
|
||||
<Tabs list={list} activeId={currentTab} onChange={setCurrentTab} />
|
||||
</Box>
|
||||
<Box py={2} px={4} flex={'1 0 0'} overflow={'auto'}>
|
||||
<>
|
||||
<Row label={t('core.chat.response.module name')} value={t(activeModule.moduleName)} />
|
||||
{activeModule?.price !== undefined && (
|
||||
<Row
|
||||
@@ -143,15 +144,23 @@ const ResponseBox = React.memo(function ResponseBox({
|
||||
<Row label={t('core.chat.response.module model')} value={activeModule?.model} />
|
||||
<Row label={t('wallet.bill.Chars length')} value={`${activeModule?.charsLength}`} />
|
||||
<Row label={t('wallet.bill.Input Token Length')} value={`${activeModule?.inputTokens}`} />
|
||||
<Row label={t('wallet.bill.Output Token Length')} value={`${activeModule?.outputTokens}`} />
|
||||
<Row
|
||||
label={t('wallet.bill.Output Token Length')}
|
||||
value={`${activeModule?.outputTokens}`}
|
||||
/>
|
||||
<Row label={t('core.chat.response.module query')} value={activeModule?.query} />
|
||||
<Row
|
||||
label={t('core.chat.response.context total length')}
|
||||
value={activeModule?.contextTotalLen}
|
||||
/>
|
||||
</>
|
||||
|
||||
{/* ai chat */}
|
||||
<Row label={t('core.chat.response.module temperature')} value={activeModule?.temperature} />
|
||||
<>
|
||||
<Row
|
||||
label={t('core.chat.response.module temperature')}
|
||||
value={activeModule?.temperature}
|
||||
/>
|
||||
<Row label={t('core.chat.response.module maxToken')} value={activeModule?.maxToken} />
|
||||
<Row
|
||||
label={t('core.chat.response.module historyPreview')}
|
||||
@@ -184,8 +193,10 @@ const ResponseBox = React.memo(function ResponseBox({
|
||||
rawDom={<QuoteList isShare={isShare} rawSearch={activeModule.quoteList} />}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
{/* dataset search */}
|
||||
<>
|
||||
{activeModule?.searchMode && (
|
||||
<Row
|
||||
label={t('core.dataset.search.search mode')}
|
||||
@@ -199,8 +210,18 @@ const ResponseBox = React.memo(function ResponseBox({
|
||||
label={t('core.chat.response.search using reRank')}
|
||||
value={activeModule?.searchUsingReRank}
|
||||
/>
|
||||
<Row
|
||||
label={t('core.chat.response.Extension model')}
|
||||
value={activeModule?.extensionModel}
|
||||
/>
|
||||
<Row
|
||||
label={t('wallet.bill.Extension result')}
|
||||
value={`${activeModule?.extensionResult}`}
|
||||
/>
|
||||
</>
|
||||
|
||||
{/* classify question */}
|
||||
<>
|
||||
<Row
|
||||
label={t('core.chat.response.module cq')}
|
||||
value={(() => {
|
||||
@@ -209,8 +230,10 @@ const ResponseBox = React.memo(function ResponseBox({
|
||||
})()}
|
||||
/>
|
||||
<Row label={t('core.chat.response.module cq result')} value={activeModule?.cqResult} />
|
||||
</>
|
||||
|
||||
{/* extract */}
|
||||
<>
|
||||
<Row
|
||||
label={t('core.chat.response.module extract description')}
|
||||
value={activeModule?.extractDescription}
|
||||
@@ -221,8 +244,10 @@ const ResponseBox = React.memo(function ResponseBox({
|
||||
value={`~~~json\n${JSON.stringify(activeModule?.extractResult, null, 2)}`}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
{/* http */}
|
||||
<>
|
||||
{activeModule?.body && (
|
||||
<Row
|
||||
label={t('core.chat.response.module http body')}
|
||||
@@ -235,8 +260,10 @@ const ResponseBox = React.memo(function ResponseBox({
|
||||
value={`~~~json\n${JSON.stringify(activeModule?.httpResult, null, 2)}`}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
{/* plugin */}
|
||||
<>
|
||||
{activeModule?.pluginDetail && activeModule?.pluginDetail.length > 0 && (
|
||||
<Row
|
||||
label={t('core.chat.response.Plugin Resonse Detail')}
|
||||
@@ -249,6 +276,7 @@ const ResponseBox = React.memo(function ResponseBox({
|
||||
value={`~~~json\n${JSON.stringify(activeModule?.pluginOutput, null, 2)}`}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
{/* text output */}
|
||||
<Row label={t('core.chat.response.text output')} value={activeModule?.textOutput} />
|
||||
|
@@ -1011,8 +1011,9 @@ export const useChatBox = () => {
|
||||
const historyDom = document.getElementById('history');
|
||||
if (!historyDom) return;
|
||||
const dom = Array.from(historyDom.children).map((child, i) => {
|
||||
const avatar = `<img src="${child.querySelector<HTMLImageElement>('.avatar')
|
||||
?.src}" alt="" />`;
|
||||
const avatar = `<img src="${
|
||||
child.querySelector<HTMLImageElement>('.avatar')?.src
|
||||
}" alt="" />`;
|
||||
|
||||
const chatContent = child.querySelector<HTMLDivElement>('.markdown');
|
||||
|
||||
|
@@ -90,7 +90,7 @@ const MySlider = ({
|
||||
borderRadius={'md'}
|
||||
transform={'translate(-50%, -155%)'}
|
||||
fontSize={'11px'}
|
||||
display={'none'}
|
||||
display={['block', 'none']}
|
||||
>
|
||||
<Box transform={'scale(0.9)'}>{value}</Box>
|
||||
</SliderMark>
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Box, Grid } from '@chakra-ui/react';
|
||||
import { Box, Flex, Grid, Image } from '@chakra-ui/react';
|
||||
import type { GridProps } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
||||
// @ts-ignore
|
||||
interface Props extends GridProps {
|
||||
list: { id: string; label: string | React.ReactNode }[];
|
||||
list: { id: string; icon?: string; label: string | React.ReactNode }[];
|
||||
activeId: string;
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
onChange: (id: string) => void;
|
||||
@@ -46,10 +47,11 @@ const Tabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) => {
|
||||
{...props}
|
||||
>
|
||||
{list.map((item) => (
|
||||
<Box
|
||||
<Flex
|
||||
key={item.id}
|
||||
py={sizeMap.inlineP}
|
||||
textAlign={'center'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
borderBottom={'2px solid transparent'}
|
||||
px={3}
|
||||
whiteSpace={'nowrap'}
|
||||
@@ -68,8 +70,17 @@ const Tabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) => {
|
||||
onChange(item.id);
|
||||
}}
|
||||
>
|
||||
{item.icon && (
|
||||
<>
|
||||
{item.icon.startsWith('/') ? (
|
||||
<Image mr={1} src={item.icon} alt={''} w={'16px'} />
|
||||
) : (
|
||||
<MyIcon mr={1} name={item.icon as any} w={'16px'} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{typeof item.label === 'string' ? t(item.label) : item.label}
|
||||
</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Grid>
|
||||
);
|
||||
|
@@ -34,14 +34,12 @@ const AIChatSettingsModal = ({
|
||||
onClose,
|
||||
onSuccess,
|
||||
defaultData,
|
||||
simpleModeTemplate = SimpleModeTemplate_FastGPT_Universal,
|
||||
pickerMenu = []
|
||||
}: {
|
||||
isAdEdit?: boolean;
|
||||
onClose: () => void;
|
||||
onSuccess: (e: AIChatModuleProps) => void;
|
||||
defaultData: AIChatModuleProps;
|
||||
simpleModeTemplate?: AppSimpleEditConfigTemplateType;
|
||||
pickerMenu?: EditorVariablePickerType[];
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -160,7 +158,6 @@ const AIChatSettingsModal = ({
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{simpleModeTemplate?.systemForm?.aiSettings?.temperature && (
|
||||
<Flex mb={10} mt={isAdEdit ? 8 : 6}>
|
||||
<Box {...LabelStyles} mr={2} w={'80px'}>
|
||||
{t('core.app.Temperature')}
|
||||
@@ -182,8 +179,6 @@ const AIChatSettingsModal = ({
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{simpleModeTemplate?.systemForm?.aiSettings?.maxToken && (
|
||||
<Flex mt={5} mb={5}>
|
||||
<Box {...LabelStyles} mr={2} w={'80px'}>
|
||||
{t('core.app.Max tokens')}
|
||||
@@ -206,9 +201,7 @@ const AIChatSettingsModal = ({
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
{simpleModeTemplate?.systemForm?.aiSettings?.quoteTemplate && (
|
||||
<Box>
|
||||
<Flex {...LabelStyles} mb={1}>
|
||||
{t('core.app.Quote templates')}
|
||||
@@ -236,6 +229,7 @@ const AIChatSettingsModal = ({
|
||||
|
||||
<PromptEditor
|
||||
variables={quoteTemplateVariables}
|
||||
h={160}
|
||||
title={t('core.app.Quote templates')}
|
||||
placeholder={t('template.Quote Content Tip', {
|
||||
default: Prompt_QuoteTemplateList[0].value
|
||||
@@ -247,8 +241,6 @@ const AIChatSettingsModal = ({
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{simpleModeTemplate?.systemForm?.aiSettings?.quotePrompt && (
|
||||
<Box mt={4}>
|
||||
<Flex {...LabelStyles} mb={1}>
|
||||
{t('core.app.Quote prompt')}
|
||||
@@ -262,7 +254,7 @@ const AIChatSettingsModal = ({
|
||||
<PromptEditor
|
||||
variables={quotePromptVariables}
|
||||
title={t('core.app.Quote prompt')}
|
||||
h={220}
|
||||
h={230}
|
||||
placeholder={t('template.Quote Prompt Tip', {
|
||||
default: Prompt_QuotePromptList[0].value
|
||||
})}
|
||||
@@ -272,7 +264,6 @@ const AIChatSettingsModal = ({
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant={'whiteBase'} onClick={onClose}>
|
||||
|
@@ -7,6 +7,7 @@ import {
|
||||
Flex,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
Switch,
|
||||
Textarea,
|
||||
useTheme
|
||||
} from '@chakra-ui/react';
|
||||
@@ -23,15 +24,27 @@ import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants';
|
||||
import MyRadio from '@/components/common/MyRadio';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import Tabs from '@/components/Tabs';
|
||||
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
|
||||
import SelectAiModel from '@/components/Select/SelectAiModel';
|
||||
|
||||
type DatasetParamsProps = {
|
||||
export type DatasetParamsProps = {
|
||||
searchMode: `${DatasetSearchModeEnum}`;
|
||||
searchEmptyText?: string;
|
||||
limit?: number;
|
||||
similarity?: number;
|
||||
usingReRank?: boolean;
|
||||
datasetSearchUsingExtensionQuery?: boolean;
|
||||
datasetSearchExtensionModel?: string;
|
||||
datasetSearchExtensionBg?: string;
|
||||
|
||||
maxTokens?: number;
|
||||
searchEmptyText?: string;
|
||||
};
|
||||
enum SearchSettingTabEnum {
|
||||
searchMode = 'searchMode',
|
||||
limit = 'limit',
|
||||
queryExtension = 'queryExtension'
|
||||
}
|
||||
|
||||
const DatasetParamsModal = ({
|
||||
searchMode = DatasetSearchModeEnum.embedding,
|
||||
@@ -40,22 +53,39 @@ const DatasetParamsModal = ({
|
||||
similarity,
|
||||
usingReRank,
|
||||
maxTokens = 3000,
|
||||
datasetSearchUsingExtensionQuery,
|
||||
datasetSearchExtensionModel,
|
||||
datasetSearchExtensionBg,
|
||||
onClose,
|
||||
onSuccess
|
||||
}: DatasetParamsProps & { onClose: () => void; onSuccess: (e: DatasetParamsProps) => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const { reRankModelList } = useSystemStore();
|
||||
const { reRankModelList, llmModelList } = useSystemStore();
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const { register, setValue, getValues, handleSubmit } = useForm<DatasetParamsProps>({
|
||||
const [currentTabType, setCurrentTabType] = useState(SearchSettingTabEnum.searchMode);
|
||||
|
||||
const { register, setValue, getValues, handleSubmit, watch } = useForm<DatasetParamsProps>({
|
||||
defaultValues: {
|
||||
searchEmptyText,
|
||||
limit,
|
||||
similarity,
|
||||
searchMode,
|
||||
usingReRank
|
||||
usingReRank,
|
||||
datasetSearchUsingExtensionQuery,
|
||||
datasetSearchExtensionModel: datasetSearchExtensionModel ?? llmModelList[0]?.model,
|
||||
datasetSearchExtensionBg
|
||||
}
|
||||
});
|
||||
const datasetSearchUsingCfrForm = watch('datasetSearchUsingExtensionQuery');
|
||||
const queryExtensionModel = watch('datasetSearchExtensionModel');
|
||||
const cfbBgDesc = watch('datasetSearchExtensionBg');
|
||||
|
||||
const chatModelSelectList = (() =>
|
||||
llmModelList.map((item) => ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
})))();
|
||||
|
||||
const searchModeList = useMemo(() => {
|
||||
const list = Object.values(DatasetSearchModeMap);
|
||||
@@ -82,10 +112,32 @@ const DatasetParamsModal = ({
|
||||
iconSrc="/imgs/modal/params.svg"
|
||||
title={t('core.dataset.search.Dataset Search Params')}
|
||||
w={['90vw', '550px']}
|
||||
h={['90vh', 'auto']}
|
||||
isCentered={searchEmptyText !== undefined}
|
||||
>
|
||||
<ModalBody flex={['1 0 0', 'auto']} overflow={'auto'}>
|
||||
<ModalBody flex={'auto'} overflow={'auto'}>
|
||||
<Tabs
|
||||
mb={3}
|
||||
list={[
|
||||
{
|
||||
icon: 'modal/setting',
|
||||
label: t('core.dataset.search.search mode'),
|
||||
id: SearchSettingTabEnum.searchMode
|
||||
},
|
||||
{
|
||||
icon: 'support/outlink/apikeyFill',
|
||||
label: t('core.dataset.search.Filter'),
|
||||
id: SearchSettingTabEnum.limit
|
||||
},
|
||||
{
|
||||
label: t('core.module.template.Query extension'),
|
||||
id: SearchSettingTabEnum.queryExtension,
|
||||
icon: '/imgs/module/cfr.svg'
|
||||
}
|
||||
]}
|
||||
activeId={currentTabType}
|
||||
onChange={(e) => setCurrentTabType(e as any)}
|
||||
/>
|
||||
{currentTabType === SearchSettingTabEnum.searchMode && (
|
||||
<>
|
||||
<MyRadio
|
||||
gridGap={2}
|
||||
gridTemplateColumns={'repeat(1,1fr)'}
|
||||
@@ -128,15 +180,29 @@ const DatasetParamsModal = ({
|
||||
</Box>
|
||||
</Box>
|
||||
<Box position={'relative'} w={'18px'} h={'18px'}>
|
||||
<Checkbox colorScheme="primary" isChecked={getValues('usingReRank')} size="lg" />
|
||||
<Box position={'absolute'} top={0} right={0} bottom={0} left={0} zIndex={1}></Box>
|
||||
<Checkbox
|
||||
colorScheme="primary"
|
||||
isChecked={getValues('usingReRank')}
|
||||
size="lg"
|
||||
/>
|
||||
<Box
|
||||
position={'absolute'}
|
||||
top={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
left={0}
|
||||
zIndex={1}
|
||||
></Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
|
||||
</>
|
||||
)}
|
||||
{currentTabType === SearchSettingTabEnum.limit && (
|
||||
<Box pt={5}>
|
||||
{limit !== undefined && (
|
||||
<Box display={['block', 'flex']} mt={5}>
|
||||
<Box display={['block', 'flex']}>
|
||||
<Box flex={'0 0 120px'} mb={[8, 0]}>
|
||||
{t('core.dataset.search.Max Tokens')}
|
||||
<MyTooltip label={t('core.dataset.search.Max Tokens Tips')} forceShow>
|
||||
@@ -162,7 +228,7 @@ const DatasetParamsModal = ({
|
||||
</Box>
|
||||
)}
|
||||
{showSimilarity && (
|
||||
<Box display={['block', 'flex']} mt={5}>
|
||||
<Box display={['block', 'flex']} mt={10}>
|
||||
<Box flex={'0 0 120px'} mb={[8, 0]}>
|
||||
{t('core.dataset.search.Min Similarity')}
|
||||
<MyTooltip label={t('core.dataset.search.Min Similarity Tips')} forceShow>
|
||||
@@ -187,7 +253,6 @@ const DatasetParamsModal = ({
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{searchEmptyText !== undefined && (
|
||||
<Box display={['block', 'flex']} pt={3}>
|
||||
<Box flex={'0 0 120px'} mb={[2, 0]}>
|
||||
@@ -203,6 +268,55 @@ const DatasetParamsModal = ({
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
{currentTabType === SearchSettingTabEnum.queryExtension && (
|
||||
<Box>
|
||||
<Box fontSize={'xs'} color={'myGray.500'}>
|
||||
{t('core.module.template.Query extension intro')}
|
||||
</Box>
|
||||
<Flex mt={3} alignItems={'center'}>
|
||||
<Box flex={'1 0 0'}>{t('core.dataset.search.Using query extension')}</Box>
|
||||
<Switch {...register('datasetSearchUsingExtensionQuery')} />
|
||||
</Flex>
|
||||
{datasetSearchUsingCfrForm === true && (
|
||||
<>
|
||||
<Flex mt={4} alignItems={'center'}>
|
||||
<Box flex={'0 0 100px'}>{t('core.ai.Model')}</Box>
|
||||
<Box flex={'1 0 0'}>
|
||||
<SelectAiModel
|
||||
width={'100%'}
|
||||
value={queryExtensionModel}
|
||||
list={chatModelSelectList}
|
||||
onchange={(val: any) => {
|
||||
setValue('datasetSearchExtensionModel', val);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Box mt={3}>
|
||||
<Flex alignItems={'center'}>
|
||||
{t('core.app.edit.Query extension background prompt')}
|
||||
<MyTooltip label={t('core.app.edit.Query extension background tip')} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<Box mt={1}>
|
||||
<PromptEditor
|
||||
h={200}
|
||||
showOpenModal={false}
|
||||
placeholder={t('core.module.QueryExtension.placeholder')}
|
||||
value={cfbBgDesc}
|
||||
onChange={(e) => {
|
||||
setValue('datasetSearchExtensionBg', e);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
|
||||
|
@@ -53,8 +53,8 @@ const TTSSelect = ({
|
||||
if (e === TTSTypeEnum.none || e === TTSTypeEnum.web) {
|
||||
onChange({ type: e as `${TTSTypeEnum}` });
|
||||
} else {
|
||||
const audioModel = audioSpeechModelList.find(
|
||||
(item) => item.voices?.find((voice) => voice.value === e)
|
||||
const audioModel = audioSpeechModelList.find((item) =>
|
||||
item.voices?.find((voice) => voice.value === e)
|
||||
);
|
||||
if (!audioModel) {
|
||||
return;
|
||||
|
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
|
||||
const NodeHttp = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
|
||||
return (
|
||||
<NodeCard minW={'350px'} selected={selected} {...data}>
|
||||
<Divider text="Input" />
|
||||
<Container>
|
||||
<RenderInput moduleId={moduleId} flowInputList={inputs} />
|
||||
</Container>
|
||||
<Divider text="Output" />
|
||||
<Container>
|
||||
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeHttp);
|
@@ -61,8 +61,9 @@ const NodeCard = (props: Props) => {
|
||||
icon: 'common/refreshLight',
|
||||
label: t('plugin.Synchronous version'),
|
||||
onClick: () => {
|
||||
const pluginId = inputs.find((item) => item.key === ModuleInputKeyEnum.pluginId)
|
||||
?.value;
|
||||
const pluginId = inputs.find(
|
||||
(item) => item.key === ModuleInputKeyEnum.pluginId
|
||||
)?.value;
|
||||
if (!pluginId) return;
|
||||
openConfirm(async () => {
|
||||
try {
|
||||
|
@@ -1,11 +1,34 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
import { onChangeNode, useFlowProviderStore } from '../../../../FlowProvider';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import JSONEditor from '@fastgpt/web/components/common/Textarea/JsonEditor';
|
||||
import {
|
||||
formatEditorVariablePickerIcon,
|
||||
getGuideModule,
|
||||
splitGuideModule
|
||||
} from '@fastgpt/global/core/module/utils';
|
||||
|
||||
const JsonEditor = ({ item, moduleId }: RenderInputProps) => {
|
||||
const JsonEditor = ({ inputs = [], item, moduleId }: RenderInputProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { nodes } = useFlowProviderStore();
|
||||
|
||||
// get variable
|
||||
const variables = useMemo(() => {
|
||||
const globalVariables = formatEditorVariablePickerIcon(
|
||||
splitGuideModule(getGuideModule(nodes.map((node) => node.data)))?.variableModules || []
|
||||
);
|
||||
const moduleVariables = formatEditorVariablePickerIcon(
|
||||
inputs
|
||||
.filter((input) => input.edit)
|
||||
.map((item) => ({
|
||||
key: item.key,
|
||||
label: item.label
|
||||
}))
|
||||
);
|
||||
|
||||
return [...globalVariables, ...moduleVariables];
|
||||
}, [inputs, nodes]);
|
||||
|
||||
const update = useCallback(
|
||||
(value: string) => {
|
||||
@@ -28,10 +51,11 @@ const JsonEditor = ({ item, moduleId }: RenderInputProps) => {
|
||||
bg={'myWhite.400'}
|
||||
placeholder={t(item.placeholder || '')}
|
||||
resize
|
||||
defaultValue={item.value}
|
||||
value={item.value}
|
||||
onChange={(e) => {
|
||||
update(e);
|
||||
}}
|
||||
variables={variables}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@@ -7,19 +7,24 @@ import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import DatasetParamsModal from '@/components/core/module/DatasetParamsModal';
|
||||
import DatasetParamsModal, {
|
||||
DatasetParamsProps
|
||||
} from '@/components/core/module/DatasetParamsModal';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
|
||||
const SelectDatasetParam = ({ inputs = [], moduleId }: RenderInputProps) => {
|
||||
const { nodes } = useFlowProviderStore();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { llmModelList } = useSystemStore();
|
||||
const [data, setData] = useState({
|
||||
|
||||
const [data, setData] = useState<DatasetParamsProps>({
|
||||
searchMode: DatasetSearchModeEnum.embedding,
|
||||
limit: 5,
|
||||
similarity: 0.5,
|
||||
usingReRank: false
|
||||
usingReRank: false,
|
||||
datasetSearchUsingExtensionQuery: true,
|
||||
datasetSearchExtensionModel: llmModelList[0]?.model,
|
||||
datasetSearchExtensionBg: ''
|
||||
});
|
||||
|
||||
const tokenLimit = useMemo(() => {
|
||||
@@ -69,6 +74,7 @@ const SelectDatasetParam = ({ inputs = [], moduleId }: RenderInputProps) => {
|
||||
maxTokens={tokenLimit}
|
||||
onClose={onClose}
|
||||
onSuccess={(e) => {
|
||||
setData(e);
|
||||
for (let key in e) {
|
||||
const item = inputs.find((input) => input.key === key);
|
||||
if (!item) continue;
|
||||
|
@@ -25,7 +25,7 @@ const nodeTypes: Record<`${FlowNodeTypeEnum}`, any> = {
|
||||
[FlowNodeTypeEnum.answerNode]: dynamic(() => import('./components/nodes/NodeAnswer')),
|
||||
[FlowNodeTypeEnum.classifyQuestion]: dynamic(() => import('./components/nodes/NodeCQNode')),
|
||||
[FlowNodeTypeEnum.contentExtract]: dynamic(() => import('./components/nodes/NodeExtract')),
|
||||
[FlowNodeTypeEnum.httpRequest]: NodeSimple,
|
||||
[FlowNodeTypeEnum.httpRequest]: dynamic(() => import('./components/nodes/NodeHttp')),
|
||||
[FlowNodeTypeEnum.runApp]: NodeSimple,
|
||||
[FlowNodeTypeEnum.pluginInput]: dynamic(() => import('./components/nodes/NodePluginInput')),
|
||||
[FlowNodeTypeEnum.pluginOutput]: dynamic(() => import('./components/nodes/NodePluginOutput')),
|
||||
|
@@ -14,9 +14,6 @@ export const SimpleModeTemplate_FastGPT_Universal: AppSimpleEditConfigTemplateTy
|
||||
quoteTemplate: true,
|
||||
quotePrompt: true
|
||||
},
|
||||
cfr: {
|
||||
background: true
|
||||
},
|
||||
dataset: {
|
||||
datasets: true,
|
||||
similarity: true,
|
||||
|
12
projects/app/src/global/core/dataset/api.d.ts
vendored
@@ -8,6 +8,7 @@ import {
|
||||
DatasetDataIndexItemType,
|
||||
SearchDataResponseItemType
|
||||
} from '@fastgpt/global/core/dataset/type';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
/* ================= dataset ===================== */
|
||||
export type CreateDatasetParams = {
|
||||
@@ -50,10 +51,13 @@ export type GetTrainingQueueResponse = {
|
||||
export type SearchTestProps = {
|
||||
datasetId: string;
|
||||
text: string;
|
||||
limit?: number;
|
||||
searchMode?: `${DatasetSearchModeEnum}`;
|
||||
usingReRank: boolean;
|
||||
similarity?: number;
|
||||
[ModuleInputKeyEnum.datasetSimilarity]?: number;
|
||||
[ModuleInputKeyEnum.datasetMaxTokens]?: number;
|
||||
[ModuleInputKeyEnum.datasetSearchMode]?: `${DatasetSearchModeEnum}`;
|
||||
[ModuleInputKeyEnum.datasetSearchUsingReRank]?: boolean;
|
||||
[ModuleInputKeyEnum.datasetSearchUsingExtensionQuery]?: boolean;
|
||||
[ModuleInputKeyEnum.datasetSearchExtensionModel]?: string;
|
||||
[ModuleInputKeyEnum.datasetSearchExtensionBg]?: string;
|
||||
};
|
||||
export type SearchTestResponse = {
|
||||
list: SearchDataResponseItemType[];
|
||||
|
@@ -58,7 +58,7 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [
|
||||
- 使用 Markdown 语法优化回答格式。
|
||||
- 使用与问题相同的语言回答。
|
||||
|
||||
问题:"{{question}}"`
|
||||
问题:"""{{question}}"""`
|
||||
},
|
||||
{
|
||||
title: '问答模板',
|
||||
@@ -73,7 +73,7 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [
|
||||
- 如果没有相关的问答对,你需要澄清。
|
||||
- 避免提及你是从 QA 获取的知识,只需要回复答案。
|
||||
|
||||
问题:"{{question}}"`
|
||||
问题:"""{{question}}"""`
|
||||
},
|
||||
{
|
||||
title: '标准严格模板',
|
||||
@@ -93,7 +93,7 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [
|
||||
- 使用 Markdown 语法优化回答格式。
|
||||
- 使用与问题相同的语言回答。
|
||||
|
||||
问题:"{{question}}"`
|
||||
问题:"""{{question}}"""`
|
||||
},
|
||||
{
|
||||
title: '严格问答模板',
|
||||
@@ -111,6 +111,6 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [
|
||||
|
||||
最后,避免提及你是从 QA 获取的知识,只需要回复答案。
|
||||
|
||||
问题:"{{question}}"`
|
||||
问题:"""{{question}}"""`
|
||||
}
|
||||
];
|
||||
|
@@ -66,13 +66,13 @@ export async function getInitConfig() {
|
||||
await Promise.all([
|
||||
initGlobal(),
|
||||
initSystemConfig(),
|
||||
getSimpleModeTemplates(),
|
||||
// getSimpleModeTemplates(),
|
||||
getSystemVersion(),
|
||||
getSystemPlugin()
|
||||
]);
|
||||
|
||||
console.log({
|
||||
simpleModeTemplates: global.simpleModeTemplates,
|
||||
// simpleModeTemplates: global.simpleModeTemplates,
|
||||
communityPlugins: global.communityPlugins
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -165,38 +165,38 @@ export function getSystemVersion() {
|
||||
}
|
||||
}
|
||||
|
||||
async function getSimpleModeTemplates() {
|
||||
if (global.simpleModeTemplates && global.simpleModeTemplates.length > 0) return;
|
||||
// async function getSimpleModeTemplates() {
|
||||
// if (global.simpleModeTemplates && global.simpleModeTemplates.length > 0) return;
|
||||
|
||||
try {
|
||||
const basePath =
|
||||
process.env.NODE_ENV === 'development' ? 'data/simpleTemplates' : '/app/data/simpleTemplates';
|
||||
// read data/simpleTemplates directory, get all json file
|
||||
const files = readdirSync(basePath);
|
||||
// filter json file
|
||||
const filterFiles = files.filter((item) => item.endsWith('.json'));
|
||||
// try {
|
||||
// const basePath =
|
||||
// process.env.NODE_ENV === 'development' ? 'data/simpleTemplates' : '/app/data/simpleTemplates';
|
||||
// // read data/simpleTemplates directory, get all json file
|
||||
// const files = readdirSync(basePath);
|
||||
// // filter json file
|
||||
// const filterFiles = files.filter((item) => item.endsWith('.json'));
|
||||
|
||||
// read json file
|
||||
const fileTemplates = filterFiles.map((item) => {
|
||||
const content = readFileSync(`${basePath}/${item}`, 'utf-8');
|
||||
return {
|
||||
id: item.replace('.json', ''),
|
||||
...JSON.parse(content)
|
||||
};
|
||||
});
|
||||
// // read json file
|
||||
// const fileTemplates = filterFiles.map((item) => {
|
||||
// const content = readFileSync(`${basePath}/${item}`, 'utf-8');
|
||||
// return {
|
||||
// id: item.replace('.json', ''),
|
||||
// ...JSON.parse(content)
|
||||
// };
|
||||
// });
|
||||
|
||||
// fetch templates from plus
|
||||
const plusTemplates = await getSimpleTemplatesFromPlus();
|
||||
// // fetch templates from plus
|
||||
// const plusTemplates = await getSimpleTemplatesFromPlus();
|
||||
|
||||
global.simpleModeTemplates = [
|
||||
SimpleModeTemplate_FastGPT_Universal,
|
||||
...plusTemplates,
|
||||
...fileTemplates
|
||||
];
|
||||
} catch (error) {
|
||||
global.simpleModeTemplates = [SimpleModeTemplate_FastGPT_Universal];
|
||||
}
|
||||
}
|
||||
// global.simpleModeTemplates = [
|
||||
// SimpleModeTemplate_FastGPT_Universal,
|
||||
// ...plusTemplates,
|
||||
// ...fileTemplates
|
||||
// ];
|
||||
// } catch (error) {
|
||||
// global.simpleModeTemplates = [SimpleModeTemplate_FastGPT_Universal];
|
||||
// }
|
||||
// }
|
||||
|
||||
function getSystemPlugin() {
|
||||
if (global.communityPlugins && global.communityPlugins.length > 0) return;
|
||||
|
@@ -294,7 +294,7 @@ function datasetTemplate({ formData, maxToken }: Props): ModuleItemType[] {
|
||||
valueType: 'string',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'vuc92c',
|
||||
moduleId: 'datasetSearch',
|
||||
key: 'userChatInput'
|
||||
}
|
||||
]
|
||||
@@ -387,15 +387,6 @@ function datasetTemplate({ formData, maxToken }: Props): ModuleItemType[] {
|
||||
value: true,
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'datasetParamsModal',
|
||||
type: 'selectDatasetParamsModal',
|
||||
label: '',
|
||||
valueType: 'any',
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false,
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'userChatInput',
|
||||
type: 'target',
|
||||
@@ -495,19 +486,6 @@ function datasetTemplate({ formData, maxToken }: Props): ModuleItemType[] {
|
||||
label: '温度',
|
||||
value: 0,
|
||||
valueType: 'number',
|
||||
min: 0,
|
||||
max: 10,
|
||||
step: 1,
|
||||
markList: [
|
||||
{
|
||||
label: '严谨',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: '发散',
|
||||
value: 10
|
||||
}
|
||||
],
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false,
|
||||
connected: false
|
||||
@@ -518,19 +496,6 @@ function datasetTemplate({ formData, maxToken }: Props): ModuleItemType[] {
|
||||
label: '回复上限',
|
||||
value: maxToken,
|
||||
valueType: 'number',
|
||||
min: 100,
|
||||
max: 4000,
|
||||
step: 50,
|
||||
markList: [
|
||||
{
|
||||
label: '100',
|
||||
value: 100
|
||||
},
|
||||
{
|
||||
label: '4000',
|
||||
value: 4000
|
||||
}
|
||||
],
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false,
|
||||
connected: false
|
||||
@@ -649,89 +614,6 @@ function datasetTemplate({ formData, maxToken }: Props): ModuleItemType[] {
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
moduleId: 'vuc92c',
|
||||
name: 'core.module.template.cfr',
|
||||
avatar: '/imgs/module/cfr.svg',
|
||||
flowType: 'cfr',
|
||||
showStatus: true,
|
||||
position: {
|
||||
x: 758.2985382279098,
|
||||
y: 1124.6527309337314
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
key: 'switch',
|
||||
type: 'target',
|
||||
label: 'core.module.input.label.switch',
|
||||
valueType: 'any',
|
||||
showTargetInApp: true,
|
||||
showTargetInPlugin: true,
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'model',
|
||||
type: 'selectExtractModel',
|
||||
label: 'core.module.input.label.aiModel',
|
||||
required: true,
|
||||
valueType: 'string',
|
||||
value: getLLMModel().model,
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false,
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'systemPrompt',
|
||||
type: 'textarea',
|
||||
label: 'core.module.input.label.cfr background',
|
||||
max: 300,
|
||||
value: formData.cfr.background,
|
||||
valueType: 'string',
|
||||
description: 'core.module.input.description.cfr background',
|
||||
placeholder: 'core.module.input.placeholder.cfr background',
|
||||
showTargetInApp: true,
|
||||
showTargetInPlugin: true,
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
type: 'numberInput',
|
||||
label: 'core.module.input.label.chat history',
|
||||
required: true,
|
||||
min: 0,
|
||||
max: 30,
|
||||
valueType: 'chatHistory',
|
||||
value: 6,
|
||||
showTargetInApp: true,
|
||||
showTargetInPlugin: true,
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'userChatInput',
|
||||
type: 'target',
|
||||
label: 'core.module.input.label.user question',
|
||||
required: true,
|
||||
valueType: 'string',
|
||||
showTargetInApp: true,
|
||||
showTargetInPlugin: true,
|
||||
connected: true
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'system_text',
|
||||
label: 'core.module.output.label.cfr result',
|
||||
valueType: 'string',
|
||||
type: 'source',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'datasetSearch',
|
||||
key: 'userChatInput'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
|
@@ -290,7 +290,7 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
|
||||
valueType: 'string',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'vuc92c',
|
||||
moduleId: 'datasetSearch',
|
||||
key: 'userChatInput'
|
||||
}
|
||||
]
|
||||
@@ -335,19 +335,6 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
|
||||
label: '最低相关性',
|
||||
value: formData.dataset.similarity,
|
||||
valueType: 'number',
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
markList: [
|
||||
{
|
||||
label: '0',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: '1',
|
||||
value: 1
|
||||
}
|
||||
],
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false,
|
||||
connected: false
|
||||
@@ -384,12 +371,33 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'datasetParamsModal',
|
||||
type: 'selectDatasetParamsModal',
|
||||
key: 'datasetSearchUsingExtensionQuery',
|
||||
type: 'hidden',
|
||||
label: '',
|
||||
valueType: 'any',
|
||||
valueType: 'boolean',
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false,
|
||||
value: formData.dataset.datasetSearchUsingExtensionQuery,
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'datasetSearchExtensionBg',
|
||||
type: 'hidden',
|
||||
label: '',
|
||||
valueType: 'string',
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false,
|
||||
value: formData.dataset.datasetSearchExtensionBg,
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'datasetSearchExtensionModel',
|
||||
type: 'hidden',
|
||||
label: '',
|
||||
valueType: 'string',
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false,
|
||||
value: formData.dataset.datasetSearchExtensionModel,
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
@@ -659,89 +667,6 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
moduleId: 'vuc92c',
|
||||
name: 'core.module.template.cfr',
|
||||
avatar: '/imgs/module/cfr.svg',
|
||||
flowType: 'cfr',
|
||||
showStatus: true,
|
||||
position: {
|
||||
x: 758.2985382279098,
|
||||
y: 1124.6527309337314
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
key: 'switch',
|
||||
type: 'target',
|
||||
label: 'core.module.input.label.switch',
|
||||
valueType: 'any',
|
||||
showTargetInApp: true,
|
||||
showTargetInPlugin: true,
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'model',
|
||||
type: 'selectExtractModel',
|
||||
label: 'core.module.input.label.aiModel',
|
||||
required: true,
|
||||
valueType: 'string',
|
||||
value: getLLMModel().model,
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false,
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'systemPrompt',
|
||||
type: 'textarea',
|
||||
label: 'core.module.input.label.cfr background',
|
||||
max: 300,
|
||||
value: formData.cfr.background,
|
||||
valueType: 'string',
|
||||
description: 'core.module.input.description.cfr background',
|
||||
placeholder: 'core.module.input.placeholder.cfr background',
|
||||
showTargetInApp: true,
|
||||
showTargetInPlugin: true,
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
type: 'numberInput',
|
||||
label: 'core.module.input.label.chat history',
|
||||
required: true,
|
||||
min: 0,
|
||||
max: 30,
|
||||
valueType: 'chatHistory',
|
||||
value: 6,
|
||||
showTargetInApp: true,
|
||||
showTargetInPlugin: true,
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'userChatInput',
|
||||
type: 'target',
|
||||
label: 'core.module.input.label.user question',
|
||||
required: true,
|
||||
valueType: 'string',
|
||||
showTargetInApp: true,
|
||||
showTargetInPlugin: true,
|
||||
connected: true
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'system_text',
|
||||
label: 'core.module.output.label.cfr result',
|
||||
valueType: 'string',
|
||||
type: 'source',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'datasetSearch',
|
||||
key: 'userChatInput'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
|
@@ -9,7 +9,9 @@ import { pushGenerateVectorBill } from '@/service/support/wallet/bill/push';
|
||||
import { searchDatasetData } from '@/service/core/dataset/data/controller';
|
||||
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
|
||||
import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import { searchQueryExtension } from '@fastgpt/service/core/ai/functions/queryExtension';
|
||||
import { getLLMModel } from '@/service/core/ai/model';
|
||||
import { queryExtension } from '@fastgpt/service/core/ai/functions/queryExtension';
|
||||
import { datasetSearchQueryExtension } from '@fastgpt/service/core/dataset/search/utils';
|
||||
|
||||
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -20,13 +22,16 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
limit = 1500,
|
||||
similarity,
|
||||
searchMode,
|
||||
usingReRank
|
||||
usingReRank,
|
||||
|
||||
datasetSearchUsingExtensionQuery = false,
|
||||
datasetSearchExtensionModel,
|
||||
datasetSearchExtensionBg = ''
|
||||
} = req.body as SearchTestProps;
|
||||
|
||||
if (!datasetId || !text) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
|
||||
const start = Date.now();
|
||||
|
||||
// auth dataset role
|
||||
@@ -37,20 +42,24 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
datasetId,
|
||||
per: 'r'
|
||||
});
|
||||
|
||||
// auth balance
|
||||
await authTeamBalance(teamId);
|
||||
|
||||
// query extension
|
||||
// const { queries } = await searchQueryExtension({
|
||||
// query: text,
|
||||
// model: global.llmModel[0].model
|
||||
// });
|
||||
const extensionModel =
|
||||
datasetSearchUsingExtensionQuery && datasetSearchExtensionModel
|
||||
? getLLMModel(datasetSearchExtensionModel)
|
||||
: undefined;
|
||||
const { concatQueries, rewriteQuery, aiExtensionResult } = await datasetSearchQueryExtension({
|
||||
query: text,
|
||||
extensionModel,
|
||||
extensionBg: datasetSearchExtensionBg
|
||||
});
|
||||
|
||||
const { searchRes, charsLength, ...result } = await searchDatasetData({
|
||||
teamId,
|
||||
rawQuery: text,
|
||||
queries: [text],
|
||||
reRankQuery: rewriteQuery,
|
||||
queries: concatQueries,
|
||||
model: dataset.vectorModel,
|
||||
limit: Math.min(limit, 20000),
|
||||
similarity,
|
||||
@@ -65,7 +74,14 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
tmbId,
|
||||
charsLength,
|
||||
model: dataset.vectorModel,
|
||||
source: apikey ? BillSourceEnum.api : BillSourceEnum.fastgpt
|
||||
source: apikey ? BillSourceEnum.api : BillSourceEnum.fastgpt,
|
||||
|
||||
...(aiExtensionResult &&
|
||||
extensionModel && {
|
||||
extensionModel: extensionModel.name,
|
||||
extensionInputTokens: aiExtensionResult.inputTokens,
|
||||
extensionOutputTokens: aiExtensionResult.outputTokens
|
||||
})
|
||||
});
|
||||
if (apikey) {
|
||||
updateApiKeyUsage({
|
||||
|
@@ -1,61 +0,0 @@
|
||||
import React, { useCallback, useState, useTransition } from 'react';
|
||||
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Box, Button, ModalBody, ModalFooter } from '@chakra-ui/react';
|
||||
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
|
||||
const CfrEditModal = ({
|
||||
defaultValue = '',
|
||||
onClose,
|
||||
onFinish
|
||||
}: {
|
||||
defaultValue?: string;
|
||||
onClose: () => void;
|
||||
onFinish: (value: string) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [value, setValue] = useState(defaultValue);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
iconSrc="/imgs/module/cfr.svg"
|
||||
w={'500px'}
|
||||
title={t('core.module.template.cfr')}
|
||||
>
|
||||
<ModalBody>
|
||||
{t('core.app.edit.cfr background prompt')}
|
||||
<MyTooltip label={t('core.app.edit.cfr background tip')} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
<Box mt={1} flex={1}>
|
||||
<PromptEditor
|
||||
h={200}
|
||||
showOpenModal={false}
|
||||
placeholder={t('core.module.input.placeholder.cfr background')}
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
setValue(e);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
onClick={() => {
|
||||
onFinish(value);
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
{t('common.Done')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(CfrEditModal);
|
@@ -30,7 +30,6 @@ import MySelect from '@/components/Select';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { SimpleModeTemplate_FastGPT_Universal } from '@/global/core/app/constants';
|
||||
import VariableEdit from '@/components/core/module/Flow/components/modules/VariableEdit';
|
||||
import MyTextarea from '@/components/common/Textarea/MyTextarea/index';
|
||||
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants';
|
||||
@@ -45,7 +44,6 @@ const TTSSelect = dynamic(
|
||||
() => import('@/components/core/module/Flow/components/modules/TTSSelect')
|
||||
);
|
||||
const QGSwitch = dynamic(() => import('@/components/core/module/Flow/components/modules/QGSwitch'));
|
||||
const CfrEditModal = dynamic(() => import('./CfrEditModal'));
|
||||
|
||||
const EditForm = ({
|
||||
divRef,
|
||||
@@ -59,7 +57,7 @@ const EditForm = ({
|
||||
const { t } = useTranslation();
|
||||
const { appDetail, updateAppDetail } = useAppStore();
|
||||
const { loadAllDatasets, allDatasets } = useDatasetStore();
|
||||
const { isPc, llmModelList, reRankModelList, simpleModeTemplates } = useSystemStore();
|
||||
const { isPc, llmModelList, reRankModelList } = useSystemStore();
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const [, startTst] = useTransition();
|
||||
|
||||
@@ -88,19 +86,16 @@ const EditForm = ({
|
||||
onOpen: onOpenDatasetParams,
|
||||
onClose: onCloseDatasetParams
|
||||
} = useDisclosure();
|
||||
const {
|
||||
isOpen: isOpenCfrModal,
|
||||
onOpen: onOpenCfrModal,
|
||||
onClose: onCloseCfrModal
|
||||
} = useDisclosure();
|
||||
|
||||
const { openConfirm: openConfirmSave, ConfirmModal: ConfirmSaveModal } = useConfirm({
|
||||
content: t('core.app.edit.Confirm Save App Tip')
|
||||
});
|
||||
|
||||
const aiSystemPrompt = watch('aiSettings.systemPrompt');
|
||||
const selectLLMModel = watch('aiSettings.model');
|
||||
const datasetSearchSetting = watch('dataset');
|
||||
const variables = watch('userGuide.variables');
|
||||
const formatVariables = useMemo(() => formatEditorVariablePickerIcon(variables), [variables]);
|
||||
const aiSystemPrompt = watch('aiSettings.systemPrompt');
|
||||
const searchMode = watch('dataset.searchMode');
|
||||
|
||||
const chatModelSelectList = (() =>
|
||||
@@ -114,16 +109,9 @@ const EditForm = ({
|
||||
[allDatasets, datasets]
|
||||
);
|
||||
|
||||
const selectSimpleTemplate = (() =>
|
||||
simpleModeTemplates?.find((item) => item.id === getValues('templateId')) ||
|
||||
SimpleModeTemplate_FastGPT_Universal)();
|
||||
|
||||
const tokenLimit = useMemo(() => {
|
||||
return (
|
||||
llmModelList.find((item) => item.model === getValues('aiSettings.model'))?.quoteMaxToken ||
|
||||
3000
|
||||
);
|
||||
}, [getValues, llmModelList]);
|
||||
return llmModelList.find((item) => item.model === selectLLMModel)?.quoteMaxToken || 3000;
|
||||
}, [selectLLMModel, llmModelList]);
|
||||
|
||||
const datasetSearchMode = useMemo(() => {
|
||||
if (!searchMode) return '';
|
||||
@@ -132,12 +120,11 @@ const EditForm = ({
|
||||
|
||||
const { mutate: onSubmitSave, isLoading: isSaving } = useRequest({
|
||||
mutationFn: async (data: AppSimpleEditFormType) => {
|
||||
const modules = await postForm2Modules(data, data.templateId);
|
||||
const modules = await postForm2Modules(data);
|
||||
|
||||
await updateAppDetail(appDetail._id, {
|
||||
modules,
|
||||
type: AppTypeEnum.simple,
|
||||
simpleTemplateId: data.templateId,
|
||||
permission: undefined
|
||||
});
|
||||
},
|
||||
@@ -149,7 +136,6 @@ const EditForm = ({
|
||||
['init', appDetail],
|
||||
() => {
|
||||
const formatVal = appModules2Form({
|
||||
templateId: appDetail.simpleTemplateId,
|
||||
modules: appDetail.modules
|
||||
});
|
||||
reset(formatVal);
|
||||
@@ -228,7 +214,7 @@ const EditForm = ({
|
||||
<Box px={4}>
|
||||
<Box bg={'white'} borderRadius={'md'} borderWidth={'1px'} borderColor={'borderColor.base'}>
|
||||
{/* simple mode select */}
|
||||
<Flex {...BoxStyles}>
|
||||
{/* <Flex {...BoxStyles}>
|
||||
<Flex alignItems={'center'} flex={'1 0 0'}>
|
||||
<MyIcon name={'core/app/simpleMode/template'} w={'20px'} />
|
||||
<Box mx={2}>{t('core.app.simple.mode template select')}</Box>
|
||||
@@ -248,27 +234,20 @@ const EditForm = ({
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex> */}
|
||||
|
||||
{/* ai */}
|
||||
{selectSimpleTemplate?.systemForm?.aiSettings && (
|
||||
<Box {...BoxStyles}>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'core/app/simpleMode/ai'} w={'20px'} />
|
||||
<Box ml={2} flex={1}>
|
||||
{t('app.AI Settings')}
|
||||
</Box>
|
||||
{(selectSimpleTemplate.systemForm.aiSettings.maxToken ||
|
||||
selectSimpleTemplate.systemForm.aiSettings.temperature ||
|
||||
selectSimpleTemplate.systemForm.aiSettings.quoteTemplate ||
|
||||
selectSimpleTemplate.systemForm.aiSettings.quotePrompt) && (
|
||||
<Flex {...BoxBtnStyles} onClick={onOpenAIChatSetting}>
|
||||
<MyIcon mr={1} name={'common/settingLight'} w={'14px'} />
|
||||
{t('common.More settings')}
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
{selectSimpleTemplate.systemForm.aiSettings?.model && (
|
||||
<Flex alignItems={'center'} mt={5}>
|
||||
<Box {...LabelStyles}>{t('core.ai.Model')}</Box>
|
||||
<Box flex={'1 0 0'}>
|
||||
@@ -288,9 +267,7 @@ const EditForm = ({
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
{selectSimpleTemplate.systemForm.aiSettings?.systemPrompt && (
|
||||
<Flex mt={10} alignItems={'flex-start'}>
|
||||
<Box {...LabelStyles}>
|
||||
{t('core.ai.Prompt')}
|
||||
@@ -312,38 +289,23 @@ const EditForm = ({
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* dataset */}
|
||||
{selectSimpleTemplate?.systemForm?.dataset && (
|
||||
<Box {...BoxStyles}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Flex alignItems={'center'} flex={1}>
|
||||
<MyIcon name={'core/app/simpleMode/dataset'} w={'20px'} />
|
||||
<Box ml={2}>{t('core.dataset.Choose Dataset')}</Box>
|
||||
</Flex>
|
||||
{selectSimpleTemplate.systemForm.dataset.datasets && (
|
||||
<Flex alignItems={'center'} {...BoxBtnStyles} onClick={onOpenKbSelect}>
|
||||
<SmallAddIcon />
|
||||
{t('common.Choose')}
|
||||
</Flex>
|
||||
)}
|
||||
{(selectSimpleTemplate.systemForm.dataset.limit ||
|
||||
selectSimpleTemplate.systemForm.dataset.searchMode ||
|
||||
selectSimpleTemplate.systemForm.dataset.searchEmptyText ||
|
||||
selectSimpleTemplate.systemForm.dataset.similarity) && (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
ml={3}
|
||||
{...BoxBtnStyles}
|
||||
onClick={onOpenDatasetParams}
|
||||
>
|
||||
<Flex alignItems={'center'} ml={3} {...BoxBtnStyles} onClick={onOpenDatasetParams}>
|
||||
<MyIcon name={'edit'} w={'14px'} mr={1} />
|
||||
{t('common.Params')}
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
{getValues('dataset.datasets').length > 0 && (
|
||||
<Flex mt={1} color={'myGray.600'} fontSize={'sm'} mb={2}>
|
||||
@@ -375,9 +337,7 @@ const EditForm = ({
|
||||
alignItems={'center'}
|
||||
p={2}
|
||||
bg={'white'}
|
||||
boxShadow={
|
||||
'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'
|
||||
}
|
||||
boxShadow={'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'}
|
||||
borderRadius={'md'}
|
||||
border={theme.borders.base}
|
||||
cursor={'pointer'}
|
||||
@@ -399,25 +359,8 @@ const EditForm = ({
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* cfr */}
|
||||
{selectSimpleTemplate?.systemForm?.cfr && getValues('dataset.datasets').length > 0 && (
|
||||
<Flex {...BoxStyles} alignItems={'center'}>
|
||||
<Image src={'/imgs/module/cfr.svg'} alt={''} w={'18px'} />
|
||||
<Box ml={2}>{t('core.module.template.cfr')}</Box>
|
||||
<MyTooltip label={t('core.module.template.cfr intro')} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
<Box flex={1} />
|
||||
<Flex {...BoxBtnStyles} onClick={onOpenCfrModal}>
|
||||
{getValues('cfr.background') === 'none' ? t('common.Not open') : t('common.Opened')}
|
||||
</Flex>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
{/* variable */}
|
||||
{selectSimpleTemplate?.systemForm?.userGuide?.variables && (
|
||||
<Box {...BoxStyles}>
|
||||
<VariableEdit
|
||||
variables={variables}
|
||||
@@ -427,10 +370,8 @@ const EditForm = ({
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* welcome */}
|
||||
{selectSimpleTemplate?.systemForm?.userGuide?.welcomeText && (
|
||||
<Box {...BoxStyles}>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'core/app/simpleMode/chat'} w={'20px'} />
|
||||
@@ -450,10 +391,8 @@ const EditForm = ({
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* tts */}
|
||||
{selectSimpleTemplate?.systemForm?.userGuide?.tts && (
|
||||
<Box {...BoxStyles}>
|
||||
<TTSSelect
|
||||
value={getValues('userGuide.tts')}
|
||||
@@ -463,10 +402,8 @@ const EditForm = ({
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* question guide */}
|
||||
{selectSimpleTemplate?.systemForm?.userGuide?.questionGuide && (
|
||||
<Box {...BoxStyles} borderBottom={'none'}>
|
||||
<QGSwitch
|
||||
isChecked={getValues('userGuide.questionGuide')}
|
||||
@@ -478,7 +415,6 @@ const EditForm = ({
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -491,7 +427,6 @@ const EditForm = ({
|
||||
onCloseAIChatSetting();
|
||||
}}
|
||||
defaultData={getValues('aiSettings')}
|
||||
simpleModeTemplate={selectSimpleTemplate}
|
||||
pickerMenu={formatVariables}
|
||||
/>
|
||||
)}
|
||||
@@ -508,28 +443,7 @@ const EditForm = ({
|
||||
)}
|
||||
{isOpenDatasetParams && (
|
||||
<DatasetParamsModal
|
||||
// {...getValues('dataset')}
|
||||
searchMode={getValues('dataset.searchMode')}
|
||||
searchEmptyText={
|
||||
selectSimpleTemplate?.systemForm?.dataset?.searchEmptyText
|
||||
? getValues('dataset.searchEmptyText')
|
||||
: undefined
|
||||
}
|
||||
limit={
|
||||
selectSimpleTemplate?.systemForm?.dataset?.limit
|
||||
? getValues('dataset.limit')
|
||||
: undefined
|
||||
}
|
||||
similarity={
|
||||
selectSimpleTemplate?.systemForm?.dataset?.similarity
|
||||
? getValues('dataset.similarity')
|
||||
: undefined
|
||||
}
|
||||
usingReRank={
|
||||
selectSimpleTemplate?.systemForm?.dataset?.usingReRank
|
||||
? getValues('dataset.usingReRank')
|
||||
: undefined
|
||||
}
|
||||
{...datasetSearchSetting}
|
||||
maxTokens={tokenLimit}
|
||||
onClose={onCloseDatasetParams}
|
||||
onSuccess={(e) => {
|
||||
@@ -542,15 +456,6 @@ const EditForm = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{isOpenCfrModal && (
|
||||
<CfrEditModal
|
||||
onClose={onCloseCfrModal}
|
||||
defaultValue={getValues('cfr.background')}
|
||||
onFinish={(e) => {
|
||||
setValue('cfr.background', e);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@@ -39,6 +39,8 @@ import { fileDownload } from '@/web/common/file/utils';
|
||||
import { readCsvContent } from '@fastgpt/web/common/file/read/csv';
|
||||
import { delay } from '@fastgpt/global/common/system/utils';
|
||||
import QuoteItem from '@/components/core/dataset/QuoteItem';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
|
||||
|
||||
@@ -48,9 +50,13 @@ type FormType = {
|
||||
inputText: string;
|
||||
searchParams: {
|
||||
searchMode: `${DatasetSearchModeEnum}`;
|
||||
usingReRank: boolean;
|
||||
limit: number;
|
||||
similarity: number;
|
||||
similarity?: number;
|
||||
limit?: number;
|
||||
usingReRank?: boolean;
|
||||
searchEmptyText?: string;
|
||||
datasetSearchUsingExtensionQuery?: boolean;
|
||||
datasetSearchExtensionModel?: string;
|
||||
datasetSearchExtensionBg?: string;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -58,6 +64,7 @@ const Test = ({ datasetId }: { datasetId: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const { toast } = useToast();
|
||||
const { llmModelList } = useSystemStore();
|
||||
const { datasetDetail } = useDatasetStore();
|
||||
const { pushDatasetTestItem } = useSearchTestStore();
|
||||
const [inputType, setInputType] = useState<'text' | 'file'>('text');
|
||||
@@ -77,12 +84,15 @@ const Test = ({ datasetId }: { datasetId: string }) => {
|
||||
searchMode: DatasetSearchModeEnum.embedding,
|
||||
usingReRank: false,
|
||||
limit: 5000,
|
||||
similarity: 0
|
||||
similarity: 0,
|
||||
datasetSearchUsingExtensionQuery: false,
|
||||
datasetSearchExtensionModel: llmModelList[0].model,
|
||||
datasetSearchExtensionBg: ''
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const searchModeData = DatasetSearchModeMap[getValues('searchParams.searchMode')];
|
||||
const searchModeData = DatasetSearchModeMap[getValues(`searchParams.searchMode`)];
|
||||
|
||||
const {
|
||||
isOpen: isOpenSelectMode,
|
||||
@@ -123,34 +133,34 @@ const Test = ({ datasetId }: { datasetId: string }) => {
|
||||
});
|
||||
}
|
||||
});
|
||||
const { mutate: onFileTest, isLoading: fileTestIsLoading } = useRequest({
|
||||
mutationFn: async ({ searchParams }: FormType) => {
|
||||
if (!selectFile) return Promise.reject('File is not selected');
|
||||
const { data } = await readCsvContent({ file: selectFile });
|
||||
const testList = data.slice(0, 100);
|
||||
const results: SearchTestResponse[] = [];
|
||||
// const { mutate: onFileTest, isLoading: fileTestIsLoading } = useRequest({
|
||||
// mutationFn: async ({ searchParams }: FormType) => {
|
||||
// if (!selectFile) return Promise.reject('File is not selected');
|
||||
// const { data } = await readCsvContent({ file: selectFile });
|
||||
// const testList = data.slice(0, 100);
|
||||
// const results: SearchTestResponse[] = [];
|
||||
|
||||
for await (const item of testList) {
|
||||
try {
|
||||
const result = await postSearchText({ datasetId, text: item[0].trim(), ...searchParams });
|
||||
results.push(result);
|
||||
} catch (error) {
|
||||
await delay(500);
|
||||
}
|
||||
}
|
||||
// for await (const item of testList) {
|
||||
// try {
|
||||
// const result = await postSearchText({ datasetId, text: item[0].trim(), ...searchParams });
|
||||
// results.push(result);
|
||||
// } catch (error) {
|
||||
// await delay(500);
|
||||
// }
|
||||
// }
|
||||
|
||||
return results;
|
||||
},
|
||||
onSuccess(res: SearchTestResponse[]) {
|
||||
console.log(res);
|
||||
},
|
||||
onError(err) {
|
||||
toast({
|
||||
title: getErrText(err),
|
||||
status: 'error'
|
||||
});
|
||||
}
|
||||
});
|
||||
// return results;
|
||||
// },
|
||||
// onSuccess(res: SearchTestResponse[]) {
|
||||
// console.log(res);
|
||||
// },
|
||||
// onError(err) {
|
||||
// toast({
|
||||
// title: getErrText(err),
|
||||
// status: 'error'
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
|
||||
const onSelectFile = async (files: File[]) => {
|
||||
const file = files[0];
|
||||
@@ -295,13 +305,13 @@ const Test = ({ datasetId }: { datasetId: string }) => {
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Button
|
||||
size={'sm'}
|
||||
isLoading={textTestIsLoading || fileTestIsLoading}
|
||||
isLoading={textTestIsLoading}
|
||||
isDisabled={inputType === 'file' && !selectFile}
|
||||
onClick={() => {
|
||||
if (inputType === 'text') {
|
||||
handleSubmit((data) => onTextTest(data))();
|
||||
} else {
|
||||
handleSubmit((data) => onFileTest(data))();
|
||||
// handleSubmit((data) => onFileTest(data))();
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@@ -35,6 +35,7 @@ import type {
|
||||
} from '@fastgpt/global/core/dataset/api.d';
|
||||
import { pushDataListToTrainingQueue } from '@fastgpt/service/core/dataset/training/controller';
|
||||
import { getVectorModel } from '../../ai/model';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
export async function pushDataToTrainingQueue(
|
||||
props: {
|
||||
@@ -272,7 +273,7 @@ export async function updateData2Dataset({
|
||||
};
|
||||
}
|
||||
|
||||
export async function searchDatasetData(props: {
|
||||
type SearchDatasetDataProps = {
|
||||
teamId: string;
|
||||
model: string;
|
||||
similarity?: number; // min distance
|
||||
@@ -280,12 +281,14 @@ export async function searchDatasetData(props: {
|
||||
datasetIds: string[];
|
||||
searchMode?: `${DatasetSearchModeEnum}`;
|
||||
usingReRank?: boolean;
|
||||
rawQuery: string;
|
||||
reRankQuery: string;
|
||||
queries: string[];
|
||||
}) {
|
||||
};
|
||||
|
||||
export async function searchDatasetData(props: SearchDatasetDataProps) {
|
||||
let {
|
||||
teamId,
|
||||
rawQuery,
|
||||
reRankQuery,
|
||||
queries,
|
||||
model,
|
||||
similarity = 0,
|
||||
@@ -307,27 +310,6 @@ export async function searchDatasetData(props: {
|
||||
let usingSimilarityFilter = false;
|
||||
|
||||
/* function */
|
||||
const countRecallLimit = () => {
|
||||
const oneChunkToken = 50;
|
||||
const estimatedLen = Math.max(20, Math.ceil(maxTokens / oneChunkToken));
|
||||
|
||||
if (searchMode === DatasetSearchModeEnum.embedding) {
|
||||
return {
|
||||
embeddingLimit: Math.min(estimatedLen, 80),
|
||||
fullTextLimit: 0
|
||||
};
|
||||
}
|
||||
if (searchMode === DatasetSearchModeEnum.fullTextRecall) {
|
||||
return {
|
||||
embeddingLimit: 0,
|
||||
fullTextLimit: Math.min(estimatedLen, 50)
|
||||
};
|
||||
}
|
||||
return {
|
||||
embeddingLimit: Math.min(estimatedLen, 60),
|
||||
fullTextLimit: Math.min(estimatedLen, 40)
|
||||
};
|
||||
};
|
||||
const embeddingRecall = async ({ query, limit }: { query: string; limit: number }) => {
|
||||
const { vectors, charsLength } = await getVectorsByText({
|
||||
model: getVectorModel(model),
|
||||
@@ -531,42 +513,13 @@ export async function searchDatasetData(props: {
|
||||
embeddingLimit: number;
|
||||
fullTextLimit: number;
|
||||
}) => {
|
||||
// In a group n recall, as long as one of the data appears minAmount of times, it is retained
|
||||
const getIntersection = (resultList: SearchDataResponseItemType[][], minAmount = 1) => {
|
||||
minAmount = Math.min(resultList.length, minAmount);
|
||||
|
||||
const map: Record<
|
||||
string,
|
||||
{
|
||||
amount: number;
|
||||
data: SearchDataResponseItemType;
|
||||
}
|
||||
> = {};
|
||||
|
||||
for (const list of resultList) {
|
||||
for (const item of list) {
|
||||
map[item.id] = map[item.id]
|
||||
? {
|
||||
amount: map[item.id].amount + 1,
|
||||
data: item
|
||||
}
|
||||
: {
|
||||
amount: 1,
|
||||
data: item
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return Object.values(map)
|
||||
.filter((item) => item.amount >= minAmount)
|
||||
.map((item) => item.data);
|
||||
};
|
||||
|
||||
// multi query recall
|
||||
const embeddingRecallResList: SearchDataResponseItemType[][] = [];
|
||||
const fullTextRecallResList: SearchDataResponseItemType[][] = [];
|
||||
let totalCharsLength = 0;
|
||||
for await (const query of queries) {
|
||||
|
||||
await Promise.all(
|
||||
queries.map(async (query) => {
|
||||
const [{ charsLength, embeddingRecallResults }, { fullTextRecallResults }] =
|
||||
await Promise.all([
|
||||
embeddingRecall({
|
||||
@@ -582,18 +535,28 @@ export async function searchDatasetData(props: {
|
||||
|
||||
embeddingRecallResList.push(embeddingRecallResults);
|
||||
fullTextRecallResList.push(fullTextRecallResults);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// rrf concat
|
||||
const rrfEmbRecall = datasetSearchResultConcat(
|
||||
embeddingRecallResList.map((list) => ({ k: 60, list }))
|
||||
).slice(0, embeddingLimit);
|
||||
const rrfFTRecall = datasetSearchResultConcat(
|
||||
fullTextRecallResList.map((list) => ({ k: 60, list }))
|
||||
).slice(0, fullTextLimit);
|
||||
|
||||
return {
|
||||
charsLength: totalCharsLength,
|
||||
embeddingRecallResults: embeddingRecallResList[0],
|
||||
fullTextRecallResults: fullTextRecallResList[0]
|
||||
embeddingRecallResults: rrfEmbRecall,
|
||||
fullTextRecallResults: rrfFTRecall
|
||||
};
|
||||
};
|
||||
|
||||
/* main step */
|
||||
// count limit
|
||||
const { embeddingLimit, fullTextLimit } = countRecallLimit();
|
||||
const embeddingLimit = 60;
|
||||
const fullTextLimit = 40;
|
||||
|
||||
// recall
|
||||
const { embeddingRecallResults, fullTextRecallResults, charsLength } = await multiQueryRecall({
|
||||
@@ -620,7 +583,7 @@ export async function searchDatasetData(props: {
|
||||
return true;
|
||||
});
|
||||
return reRankSearchResult({
|
||||
query: rawQuery,
|
||||
query: reRankQuery,
|
||||
data: filterSameDataResults
|
||||
});
|
||||
})();
|
||||
|
@@ -3,10 +3,13 @@ import { formatModelPrice2Store } from '@/service/support/wallet/bill/utils';
|
||||
import type { SelectedDatasetType } from '@fastgpt/global/core/module/api.d';
|
||||
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import { ModelTypeEnum, getVectorModel } from '@/service/core/ai/model';
|
||||
import { ModelTypeEnum, getLLMModel, getVectorModel } from '@/service/core/ai/model';
|
||||
import { searchDatasetData } from '@/service/core/dataset/data/controller';
|
||||
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { queryExtension } from '@fastgpt/service/core/ai/functions/queryExtension';
|
||||
import { getHistories } from '../utils';
|
||||
import { datasetSearchQueryExtension } from '@fastgpt/service/core/dataset/search/utils';
|
||||
|
||||
type DatasetSearchProps = ModuleDispatchProps<{
|
||||
[ModuleInputKeyEnum.datasetSelectList]: SelectedDatasetType;
|
||||
@@ -15,6 +18,9 @@ type DatasetSearchProps = ModuleDispatchProps<{
|
||||
[ModuleInputKeyEnum.datasetSearchMode]: `${DatasetSearchModeEnum}`;
|
||||
[ModuleInputKeyEnum.userChatInput]: string;
|
||||
[ModuleInputKeyEnum.datasetSearchUsingReRank]: boolean;
|
||||
[ModuleInputKeyEnum.datasetSearchUsingExtensionQuery]: boolean;
|
||||
[ModuleInputKeyEnum.datasetSearchExtensionModel]: string;
|
||||
[ModuleInputKeyEnum.datasetSearchExtensionBg]: string;
|
||||
}>;
|
||||
export type DatasetSearchResponse = {
|
||||
[ModuleOutputKeyEnum.responseData]: moduleDispatchResType;
|
||||
@@ -28,7 +34,19 @@ export async function dispatchDatasetSearch(
|
||||
): Promise<DatasetSearchResponse> {
|
||||
const {
|
||||
teamId,
|
||||
params: { datasets = [], similarity, limit = 1500, usingReRank, searchMode, userChatInput }
|
||||
histories,
|
||||
params: {
|
||||
datasets = [],
|
||||
similarity,
|
||||
limit = 1500,
|
||||
usingReRank,
|
||||
searchMode,
|
||||
userChatInput,
|
||||
|
||||
datasetSearchUsingExtensionQuery,
|
||||
datasetSearchExtensionModel,
|
||||
datasetSearchExtensionBg
|
||||
}
|
||||
} = props as DatasetSearchProps;
|
||||
|
||||
if (!Array.isArray(datasets)) {
|
||||
@@ -43,15 +61,21 @@ export async function dispatchDatasetSearch(
|
||||
return Promise.reject('core.chat.error.User input empty');
|
||||
}
|
||||
|
||||
// query extension
|
||||
const extensionModel =
|
||||
datasetSearchUsingExtensionQuery && datasetSearchExtensionModel
|
||||
? getLLMModel(datasetSearchExtensionModel)
|
||||
: undefined;
|
||||
const { concatQueries, rewriteQuery, aiExtensionResult } = await datasetSearchQueryExtension({
|
||||
query: userChatInput,
|
||||
extensionModel,
|
||||
extensionBg: datasetSearchExtensionBg,
|
||||
histories: getHistories(6, histories)
|
||||
});
|
||||
|
||||
// get vector
|
||||
const vectorModel = getVectorModel(datasets[0]?.vectorModel?.model);
|
||||
|
||||
// const { queries: extensionQueries } = await searchQueryExtension({
|
||||
// query: userChatInput,
|
||||
// model: global.llmModels[0].model
|
||||
// });
|
||||
const concatQueries = [userChatInput];
|
||||
|
||||
// start search
|
||||
const {
|
||||
searchRes,
|
||||
@@ -60,7 +84,7 @@ export async function dispatchDatasetSearch(
|
||||
usingReRank: searchUsingReRank
|
||||
} = await searchDatasetData({
|
||||
teamId,
|
||||
rawQuery: `${userChatInput}`,
|
||||
reRankQuery: `${rewriteQuery}`,
|
||||
queries: concatQueries,
|
||||
model: vectorModel.model,
|
||||
similarity,
|
||||
@@ -70,17 +94,14 @@ export async function dispatchDatasetSearch(
|
||||
usingReRank
|
||||
});
|
||||
|
||||
// count bill results
|
||||
// vector
|
||||
const { total, modelName } = formatModelPrice2Store({
|
||||
model: vectorModel.model,
|
||||
inputLen: charsLength,
|
||||
type: ModelTypeEnum.vector
|
||||
});
|
||||
|
||||
return {
|
||||
isEmpty: searchRes.length === 0 ? true : undefined,
|
||||
unEmpty: searchRes.length > 0 ? true : undefined,
|
||||
quoteQA: searchRes,
|
||||
responseData: {
|
||||
const responseData: moduleDispatchResType & { price: number } = {
|
||||
price: total,
|
||||
query: concatQueries.join('\n'),
|
||||
model: modelName,
|
||||
@@ -89,6 +110,29 @@ export async function dispatchDatasetSearch(
|
||||
limit,
|
||||
searchMode,
|
||||
searchUsingReRank: searchUsingReRank
|
||||
};
|
||||
|
||||
if (aiExtensionResult) {
|
||||
const { total, modelName } = formatModelPrice2Store({
|
||||
model: aiExtensionResult.model,
|
||||
inputLen: aiExtensionResult.inputTokens,
|
||||
outputLen: aiExtensionResult.outputTokens,
|
||||
type: ModelTypeEnum.llm
|
||||
});
|
||||
|
||||
responseData.price += total;
|
||||
responseData.inputTokens = aiExtensionResult.inputTokens;
|
||||
responseData.outputTokens = aiExtensionResult.outputTokens;
|
||||
responseData.extensionModel = modelName;
|
||||
responseData.extensionResult =
|
||||
aiExtensionResult.extensionQueries?.join('\n') ||
|
||||
JSON.stringify(aiExtensionResult.extensionQueries);
|
||||
}
|
||||
|
||||
return {
|
||||
isEmpty: searchRes.length === 0 ? true : undefined,
|
||||
unEmpty: searchRes.length > 0 ? true : undefined,
|
||||
quoteQA: searchRes,
|
||||
responseData
|
||||
};
|
||||
}
|
||||
|
@@ -1,11 +1,10 @@
|
||||
import type { ChatItemType, moduleDispatchResType } from '@fastgpt/global/core/chat/type.d';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { getHistories } from '../utils';
|
||||
import { getAIApi } from '@fastgpt/service/core/ai/config';
|
||||
import { replaceVariable } from '@fastgpt/global/common/string/tools';
|
||||
import { ModelTypeEnum, getLLMModel } from '@/service/core/ai/model';
|
||||
import { formatModelPrice2Store } from '@/service/support/wallet/bill/utils';
|
||||
import { queryCfr } from '@fastgpt/service/core/ai/functions/cfr';
|
||||
import { getHistories } from '../utils';
|
||||
|
||||
type Props = ModuleDispatchProps<{
|
||||
[ModuleInputKeyEnum.aiModel]: string;
|
||||
@@ -34,57 +33,18 @@ export const dispatchCFR = async ({
|
||||
};
|
||||
}
|
||||
|
||||
const extractModel = getLLMModel(model);
|
||||
const cfrModel = getLLMModel(model);
|
||||
const chatHistories = getHistories(history, histories);
|
||||
|
||||
const systemFewShot = systemPrompt
|
||||
? `Q: 对话背景。
|
||||
A: ${systemPrompt}
|
||||
`
|
||||
: '';
|
||||
const historyFewShot = chatHistories
|
||||
.map((item) => {
|
||||
const role = item.obj === 'Human' ? 'Q' : 'A';
|
||||
return `${role}: ${item.value}`;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
const concatFewShot = `${systemFewShot}${historyFewShot}`.trim();
|
||||
|
||||
const ai = getAIApi({
|
||||
timeout: 480000
|
||||
const { cfrQuery, inputTokens, outputTokens } = await queryCfr({
|
||||
chatBg: systemPrompt,
|
||||
query: userChatInput,
|
||||
histories: chatHistories,
|
||||
model: cfrModel.model
|
||||
});
|
||||
|
||||
const result = await ai.chat.completions.create({
|
||||
model: extractModel.model,
|
||||
temperature: 0,
|
||||
max_tokens: 150,
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: replaceVariable(defaultPrompt, {
|
||||
query: `${userChatInput}`,
|
||||
histories: concatFewShot
|
||||
})
|
||||
}
|
||||
],
|
||||
stream: false
|
||||
});
|
||||
|
||||
let answer = result.choices?.[0]?.message?.content || '';
|
||||
// console.log(
|
||||
// replaceVariable(defaultPrompt, {
|
||||
// query: userChatInput,
|
||||
// histories: concatFewShot
|
||||
// })
|
||||
// );
|
||||
// console.log(answer);
|
||||
|
||||
const inputTokens = result.usage?.prompt_tokens || 0;
|
||||
const outputTokens = result.usage?.completion_tokens || 0;
|
||||
|
||||
const { total, modelName } = formatModelPrice2Store({
|
||||
model: extractModel.model,
|
||||
model: cfrModel.model,
|
||||
inputLen: inputTokens,
|
||||
outputLen: outputTokens,
|
||||
type: ModelTypeEnum.llm
|
||||
@@ -97,85 +57,8 @@ A: ${systemPrompt}
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
query: userChatInput,
|
||||
textOutput: answer
|
||||
textOutput: cfrQuery
|
||||
},
|
||||
[ModuleOutputKeyEnum.text]: answer
|
||||
[ModuleOutputKeyEnum.text]: cfrQuery
|
||||
};
|
||||
};
|
||||
|
||||
const defaultPrompt = `请不要回答任何问题。
|
||||
你的任务是结合上下文,为当前问题,实现代词替换,确保问题描述的对象清晰明确。例如:
|
||||
历史记录:
|
||||
"""
|
||||
Q: 对话背景。
|
||||
A: 关于 FatGPT 的介绍和使用等问题。
|
||||
"""
|
||||
当前问题: 怎么下载
|
||||
输出: FastGPT 怎么下载?
|
||||
----------------
|
||||
历史记录:
|
||||
"""
|
||||
Q: 报错 "no connection"
|
||||
A: FastGPT 报错"no connection"可能是因为……
|
||||
"""
|
||||
当前问题: 怎么解决
|
||||
输出: FastGPT 报错"no connection"如何解决?
|
||||
----------------
|
||||
历史记录:
|
||||
"""
|
||||
Q: 作者是谁?
|
||||
A: FastGPT 的作者是 labring。
|
||||
"""
|
||||
当前问题: 介绍下他
|
||||
输出: 介绍下 FastGPT 的作者 labring。
|
||||
----------------
|
||||
历史记录:
|
||||
"""
|
||||
Q: 作者是谁?
|
||||
A: FastGPT 的作者是 labring。
|
||||
"""
|
||||
当前问题: 我想购买商业版。
|
||||
输出: FastGPT 商业版如何购买?
|
||||
----------------
|
||||
历史记录:
|
||||
"""
|
||||
Q: 对话背景。
|
||||
A: 关于 FatGPT 的介绍和使用等问题。
|
||||
"""
|
||||
当前问题: nh
|
||||
输出: nh
|
||||
----------------
|
||||
历史记录:
|
||||
"""
|
||||
Q: FastGPT 如何收费?
|
||||
A: FastGPT 收费可以参考……
|
||||
"""
|
||||
当前问题: 你知道 laf 么?
|
||||
输出: 你知道 laf 么?
|
||||
----------------
|
||||
历史记录:
|
||||
"""
|
||||
Q: FastGPT 的优势
|
||||
A: 1. 开源
|
||||
2. 简便
|
||||
3. 扩展性强
|
||||
"""
|
||||
当前问题: 介绍下第2点。
|
||||
输出: 介绍下 FastGPT 简便的优势。
|
||||
----------------
|
||||
历史记录:
|
||||
"""
|
||||
Q: 什么是 FastGPT?
|
||||
A: FastGPT 是一个 RAG 平台。
|
||||
Q: 什么是 Sealos?
|
||||
A: Sealos 是一个云操作系统。
|
||||
"""
|
||||
当前问题: 它们有什么关系?
|
||||
输出: FastGPT 和 Sealos 有什么关系?
|
||||
----------------
|
||||
历史记录:
|
||||
"""
|
||||
{{histories}}
|
||||
"""
|
||||
当前问题: {{query}}
|
||||
输出: `;
|
||||
|
@@ -26,63 +26,40 @@ export const dispatchHttpRequest = async (props: HttpRequestProps): Promise<Http
|
||||
variables,
|
||||
outputs,
|
||||
params: {
|
||||
system_httpMethod: httpMethod,
|
||||
url: abandonUrl,
|
||||
system_httpMethod: httpMethod = 'POST',
|
||||
system_httpReqUrl: httpReqUrl,
|
||||
system_httpHeader: httpHeader,
|
||||
...body
|
||||
}
|
||||
} = props;
|
||||
|
||||
if (!httpReqUrl) {
|
||||
return Promise.reject('Http url is empty');
|
||||
}
|
||||
|
||||
body = flatDynamicParams(body);
|
||||
|
||||
const { requestMethod, requestUrl, requestHeader, requestBody, requestQuery } = await (() => {
|
||||
// 2024-2-12 clear
|
||||
if (abandonUrl) {
|
||||
return {
|
||||
requestMethod: 'POST',
|
||||
requestUrl: abandonUrl,
|
||||
requestHeader: httpHeader,
|
||||
requestBody: {
|
||||
...body,
|
||||
appId,
|
||||
chatId,
|
||||
variables
|
||||
},
|
||||
requestQuery: {}
|
||||
};
|
||||
}
|
||||
if (httpReqUrl) {
|
||||
return {
|
||||
requestMethod: httpMethod,
|
||||
requestUrl: httpReqUrl,
|
||||
requestHeader: httpHeader,
|
||||
requestBody: {
|
||||
const requestBody = {
|
||||
appId,
|
||||
chatId,
|
||||
responseChatItemId,
|
||||
variables,
|
||||
data: body
|
||||
},
|
||||
requestQuery: {
|
||||
};
|
||||
const requestQuery = {
|
||||
appId,
|
||||
chatId,
|
||||
...variables,
|
||||
...body
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return Promise.reject('url is empty');
|
||||
})();
|
||||
|
||||
const formatBody = transformFlatJson({ ...requestBody });
|
||||
|
||||
// parse header
|
||||
const headers = await (() => {
|
||||
try {
|
||||
if (!requestHeader) return {};
|
||||
return JSON.parse(requestHeader);
|
||||
if (!httpHeader) return {};
|
||||
return JSON.parse(httpHeader);
|
||||
} catch (error) {
|
||||
return Promise.reject('Header 为非法 JSON 格式');
|
||||
}
|
||||
@@ -90,8 +67,8 @@ export const dispatchHttpRequest = async (props: HttpRequestProps): Promise<Http
|
||||
|
||||
try {
|
||||
const response = await fetchData({
|
||||
method: requestMethod,
|
||||
url: requestUrl,
|
||||
method: httpMethod,
|
||||
url: httpReqUrl,
|
||||
headers,
|
||||
body: formatBody,
|
||||
query: requestQuery
|
||||
|
@@ -87,7 +87,10 @@ export const pushGenerateVectorBill = ({
|
||||
tmbId,
|
||||
charsLength,
|
||||
model,
|
||||
source = BillSourceEnum.fastgpt
|
||||
source = BillSourceEnum.fastgpt,
|
||||
extensionModel,
|
||||
extensionInputTokens,
|
||||
extensionOutputTokens
|
||||
}: {
|
||||
billId?: string;
|
||||
teamId: string;
|
||||
@@ -95,19 +98,43 @@ export const pushGenerateVectorBill = ({
|
||||
charsLength: number;
|
||||
model: string;
|
||||
source?: `${BillSourceEnum}`;
|
||||
|
||||
extensionModel?: string;
|
||||
extensionInputTokens?: number;
|
||||
extensionOutputTokens?: number;
|
||||
}) => {
|
||||
let { total, modelName } = formatModelPrice2Store({
|
||||
const { total: totalVector, modelName: vectorModelName } = formatModelPrice2Store({
|
||||
model,
|
||||
inputLen: charsLength,
|
||||
type: ModelTypeEnum.vector
|
||||
});
|
||||
|
||||
const { extensionTotal, extensionModelName } = (() => {
|
||||
if (!extensionModel || !extensionInputTokens || !extensionOutputTokens)
|
||||
return {
|
||||
extensionTotal: 0,
|
||||
extensionModelName: ''
|
||||
};
|
||||
const { total, modelName } = formatModelPrice2Store({
|
||||
model: extensionModel,
|
||||
inputLen: extensionInputTokens,
|
||||
outputLen: extensionOutputTokens,
|
||||
type: ModelTypeEnum.llm
|
||||
});
|
||||
return {
|
||||
extensionTotal: total,
|
||||
extensionModelName: modelName
|
||||
};
|
||||
})();
|
||||
|
||||
const total = totalVector + extensionTotal;
|
||||
|
||||
// 插入 Bill 记录
|
||||
if (billId) {
|
||||
concatBill({
|
||||
teamId,
|
||||
tmbId,
|
||||
total,
|
||||
total: totalVector,
|
||||
billId,
|
||||
charsLength,
|
||||
listIndex: 0
|
||||
@@ -123,10 +150,21 @@ export const pushGenerateVectorBill = ({
|
||||
{
|
||||
moduleName: 'wallet.moduleName.index',
|
||||
amount: total,
|
||||
model: modelName,
|
||||
model: vectorModelName,
|
||||
charsLength
|
||||
},
|
||||
...(extensionModel !== undefined
|
||||
? [
|
||||
{
|
||||
moduleName: extensionModelName,
|
||||
amount: extensionTotal,
|
||||
model: extensionModelName,
|
||||
inputTokens: extensionInputTokens,
|
||||
outputTokens: extensionOutputTokens
|
||||
}
|
||||
]
|
||||
: [])
|
||||
]
|
||||
});
|
||||
}
|
||||
return { total };
|
||||
|
@@ -6,10 +6,7 @@ import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import type { FormatForm2ModulesProps } from '@fastgpt/global/core/app/api.d';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
|
||||
export async function postForm2Modules(
|
||||
data: AppSimpleEditFormType,
|
||||
templateId = 'fastgpt-universal'
|
||||
) {
|
||||
export async function postForm2Modules(data: AppSimpleEditFormType) {
|
||||
const llmModelList = useSystemStore.getState().llmModelList;
|
||||
function userGuideTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
|
||||
return [
|
||||
@@ -60,7 +57,7 @@ export async function postForm2Modules(
|
||||
llmModelList
|
||||
};
|
||||
|
||||
const modules = await POST<ModuleItemType[]>(`/core/app/form2Modules/${templateId}`, props);
|
||||
const modules = await POST<ModuleItemType[]>(`/core/app/form2Modules/fastgpt-universal`, props);
|
||||
|
||||
return [...userGuideTemplate(data), ...modules];
|
||||
}
|
||||
|
@@ -29,8 +29,7 @@ export const appSystemModuleTemplates: FlowModuleTemplateType[] = [
|
||||
RunAppModule,
|
||||
ClassifyQuestionModule,
|
||||
ContextExtractModule,
|
||||
HttpModule,
|
||||
AiCFR
|
||||
HttpModule
|
||||
];
|
||||
export const pluginSystemModuleTemplates: FlowModuleTemplateType[] = [
|
||||
PluginInputModule,
|
||||
@@ -42,8 +41,7 @@ export const pluginSystemModuleTemplates: FlowModuleTemplateType[] = [
|
||||
RunAppModule,
|
||||
ClassifyQuestionModule,
|
||||
ContextExtractModule,
|
||||
HttpModule,
|
||||
AiCFR
|
||||
HttpModule
|
||||
];
|
||||
|
||||
export const moduleTemplatesFlat: FlowModuleTemplateType[] = [
|
||||
|
@@ -329,11 +329,16 @@ const Switch = switchMultiStyle({
|
||||
baseStyle: switchPart({
|
||||
track: {
|
||||
bg: 'myGray.100',
|
||||
borderWidth: '1px',
|
||||
borderColor: 'borders.base',
|
||||
_checked: {
|
||||
bg: 'primary.600'
|
||||
}
|
||||
}
|
||||
})
|
||||
}),
|
||||
defaultProps: {
|
||||
size: 'md'
|
||||
}
|
||||
});
|
||||
|
||||
const Select = selectMultiStyle({
|
||||
|