Compare commits
41 Commits
v4.8.14-fi
...
v4.8.13-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2423a096c5 | ||
|
|
f68ae33cd8 | ||
|
|
211061d122 | ||
|
|
dc8aeefa2a | ||
|
|
cdce94a202 | ||
|
|
5e273341dd | ||
|
|
73d28d1fc3 | ||
|
|
5892ded567 | ||
|
|
f4e0dfc9bd | ||
|
|
d758ddb47c | ||
|
|
acdf6a0dd9 | ||
|
|
0201e63cfd | ||
|
|
e9f0d5dad5 | ||
|
|
11134f39e4 | ||
|
|
a5765ae32f | ||
|
|
72836402be | ||
|
|
8bd0749afe | ||
|
|
49aaf9b77e | ||
|
|
6c6c964b8a | ||
|
|
c5022654ca | ||
|
|
0a238845ab | ||
|
|
8ede7add01 | ||
|
|
91645cc420 | ||
|
|
4d6f736be3 | ||
|
|
f1fb85ead0 | ||
|
|
34fbd5a223 | ||
|
|
45aa2e7374 | ||
|
|
f58dba2eda | ||
|
|
78ef74f902 | ||
|
|
8303933ec2 | ||
|
|
2388652858 | ||
|
|
ba61c9e2e6 | ||
|
|
b0a14c585d | ||
|
|
0d494fde45 | ||
|
|
5a76b6f76d | ||
|
|
e78fa26ca7 | ||
|
|
16280e5d94 | ||
|
|
e96f09ca9f | ||
|
|
788d87a26f | ||
|
|
dc1119ca90 | ||
|
|
9e8138e55f |
2
.github/ISSUE_TEMPLATE/bugs.md
vendored
@@ -21,7 +21,7 @@ assignees: ''
|
|||||||
- [ ] 公有云版本
|
- [ ] 公有云版本
|
||||||
- [ ] 私有部署版本, 具体版本号:
|
- [ ] 私有部署版本, 具体版本号:
|
||||||
|
|
||||||
**问题描述, 日志截图,配置文件等**
|
**问题描述, 日志截图**
|
||||||
|
|
||||||
**复现步骤**
|
**复现步骤**
|
||||||
|
|
||||||
|
|||||||
4
.vscode/settings.json
vendored
@@ -16,8 +16,8 @@
|
|||||||
"i18n-ally.keystyle": "flat",
|
"i18n-ally.keystyle": "flat",
|
||||||
"i18n-ally.sortKeys": true,
|
"i18n-ally.sortKeys": true,
|
||||||
"i18n-ally.keepFulfilled": false,
|
"i18n-ally.keepFulfilled": false,
|
||||||
"i18n-ally.sourceLanguage": "zh-CN", // 根据此语言文件翻译其他语言文件的变量和内容
|
"i18n-ally.sourceLanguage": "zh", // 根据此语言文件翻译其他语言文件的变量和内容
|
||||||
"i18n-ally.displayLanguage": "zh-CN", // 显示语言
|
"i18n-ally.displayLanguage": "zh", // 显示语言
|
||||||
"i18n-ally.namespace": true,
|
"i18n-ally.namespace": true,
|
||||||
"i18n-ally.pathMatcher": "{locale}/{namespaces}.json",
|
"i18n-ally.pathMatcher": "{locale}/{namespaces}.json",
|
||||||
"i18n-ally.extract.targetPickingStrategy": "most-similar-by-key",
|
"i18n-ally.extract.targetPickingStrategy": "most-similar-by-key",
|
||||||
|
|||||||
@@ -105,12 +105,12 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b
|
|||||||
[点击查看 Sealos 一键部署 FastGPT 教程](https://doc.tryfastgpt.ai/docs/development/sealos/)
|
[点击查看 Sealos 一键部署 FastGPT 教程](https://doc.tryfastgpt.ai/docs/development/sealos/)
|
||||||
|
|
||||||
* [快速开始本地开发](https://doc.tryfastgpt.ai/docs/development/intro/)
|
* [快速开始本地开发](https://doc.tryfastgpt.ai/docs/development/intro/)
|
||||||
* [部署 FastGPT](https://doc.tryfastgpt.ai/docs/development/sealos/)
|
* [部署 FastGPT](https://doc.tryfastgpt.ai/docs/development/sealos)
|
||||||
* [系统配置文件说明](https://doc.tryfastgpt.ai/docs/development/configuration/)
|
* [系统配置文件说明](https://doc.tryfastgpt.ai/docs/development/configuration/)
|
||||||
* [多模型配置](https://doc.tryfastgpt.ai/docs/development/one-api/)
|
* [多模型配置](https://doc.tryfastgpt.ai/docs/development/one-api/)
|
||||||
* [版本更新/升级介绍](https://doc.tryfastgpt.ai/docs/development/upgrading/)
|
* [版本更新/升级介绍](https://doc.tryfastgpt.ai/docs/development/upgrading)
|
||||||
* [OpenAPI API 文档](https://doc.tryfastgpt.ai/docs/development/openapi/)
|
* [OpenAPI API 文档](https://doc.tryfastgpt.ai/docs/development/openapi/)
|
||||||
* [知识库结构详解](https://doc.tryfastgpt.ai/docs/guide/knowledge_base/rag/)
|
* [知识库结构详解](https://doc.tryfastgpt.ai/docs/course/dataset_engine/)
|
||||||
|
|
||||||
<a href="#readme">
|
<a href="#readme">
|
||||||
<img src="https://img.shields.io/badge/-返回顶部-7d09f1.svg" alt="#" align="right">
|
<img src="https://img.shields.io/badge/-返回顶部-7d09f1.svg" alt="#" align="right">
|
||||||
@@ -149,7 +149,7 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b
|
|||||||
|
|
||||||
## 🌿 第三方生态
|
## 🌿 第三方生态
|
||||||
|
|
||||||
- [COW 个人微信/企微机器人](https://doc.tryfastgpt.ai/docs/use-cases/external-integration/onwechat/)
|
- [OnWeChat 个人微信/企微机器人](https://doc.tryfastgpt.ai/docs/use-cases/onwechat/)
|
||||||
|
|
||||||
<a href="#readme">
|
<a href="#readme">
|
||||||
<img src="https://img.shields.io/badge/-返回顶部-7d09f1.svg" alt="#" align="right">
|
<img src="https://img.shields.io/badge/-返回顶部-7d09f1.svg" alt="#" align="right">
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 338 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 174 KiB |
|
Before Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 83 KiB |
@@ -23,7 +23,6 @@ weight: 708
|
|||||||
"systemEnv": {
|
"systemEnv": {
|
||||||
"vectorMaxProcess": 15,
|
"vectorMaxProcess": 15,
|
||||||
"qaMaxProcess": 15,
|
"qaMaxProcess": 15,
|
||||||
"tokenWorkers": 50, // Token 计算线程保持数,会持续占用内存,不能设置太大。
|
|
||||||
"pgHNSWEfSearch": 100 // 向量搜索参数。越大,搜索越精确,但是速度越慢。设置为100,有99%+精度。
|
"pgHNSWEfSearch": 100 // 向量搜索参数。越大,搜索越精确,但是速度越慢。设置为100,有99%+精度。
|
||||||
},
|
},
|
||||||
"llmModels": [
|
"llmModels": [
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ weight: 707
|
|||||||
|
|
||||||
### Milvus版本
|
### Milvus版本
|
||||||
|
|
||||||
生产部署首选,对于千万级以上向量性能更优秀。
|
暂不推荐,部分系统存在精度丢失,等待修复。
|
||||||
|
|
||||||
|
对于千万级以上向量性能更优秀。
|
||||||
|
|
||||||
[点击查看 Milvus 官方推荐配置](https://milvus.io/docs/prerequisite-docker.md)
|
[点击查看 Milvus 官方推荐配置](https://milvus.io/docs/prerequisite-docker.md)
|
||||||
|
|
||||||
@@ -49,7 +51,9 @@ weight: 707
|
|||||||
|
|
||||||
### zilliz cloud版本
|
### zilliz cloud版本
|
||||||
|
|
||||||
Milvus 的全托管服务,性能优于 Milvus 并提供 SLA,点击使用 [Zilliz Cloud](https://zilliz.com.cn/)。
|
暂不推荐,部分系统存在精度丢失,等待修复。
|
||||||
|
|
||||||
|
亿级以上向量首选。
|
||||||
|
|
||||||
由于向量库使用了 Cloud,无需占用本地资源,无需太关注。
|
由于向量库使用了 Cloud,无需占用本地资源,无需太关注。
|
||||||
|
|
||||||
@@ -134,16 +138,14 @@ curl -o docker-compose.yml https://raw.githubusercontent.com/labring/FastGPT/mai
|
|||||||
# curl -o docker-compose.yml https://raw.githubusercontent.com/labring/FastGPT/main/files/docker/docker-compose-zilliz.yml
|
# curl -o docker-compose.yml https://raw.githubusercontent.com/labring/FastGPT/main/files/docker/docker-compose-zilliz.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. 修改环境变量
|
### 2. 修改 docker-compose.yml 环境变量
|
||||||
|
|
||||||
找到 yml 文件中,fastgpt 容器的环境变量进行下面操作:
|
|
||||||
|
|
||||||
{{< tabs tabTotal="3" >}}
|
{{< tabs tabTotal="3" >}}
|
||||||
{{< tab tabName="PgVector版本" >}}
|
{{< tab tabName="PgVector版本" >}}
|
||||||
{{< markdownify >}}
|
{{< markdownify >}}
|
||||||
|
|
||||||
```
|
```
|
||||||
FE_DOMAIN=你的前端你访问地址,例如 http://192.168.0.1:3000;https://cloud.fastgpt.cn
|
无需操作
|
||||||
```
|
```
|
||||||
|
|
||||||
{{< /markdownify >}}
|
{{< /markdownify >}}
|
||||||
@@ -152,7 +154,7 @@ FE_DOMAIN=你的前端你访问地址,例如 http://192.168.0.1:3000;https://clo
|
|||||||
{{< markdownify >}}
|
{{< markdownify >}}
|
||||||
|
|
||||||
```
|
```
|
||||||
FE_DOMAIN=你的前端你访问地址,例如 http://192.168.0.1:3000;https://cloud.fastgpt.cn
|
无需操作
|
||||||
```
|
```
|
||||||
|
|
||||||
{{< /markdownify >}}
|
{{< /markdownify >}}
|
||||||
@@ -160,14 +162,11 @@ FE_DOMAIN=你的前端你访问地址,例如 http://192.168.0.1:3000;https://clo
|
|||||||
{{< tab tabName="Zilliz版本" >}}
|
{{< tab tabName="Zilliz版本" >}}
|
||||||
{{< markdownify >}}
|
{{< markdownify >}}
|
||||||
|
|
||||||
打开 [Zilliz Cloud](https://zilliz.com.cn/), 创建实例并获取相关秘钥。
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
{{% alert icon="🤖" context="success" %}}
|
{{% alert icon="🤖" context="success" %}}
|
||||||
|
|
||||||
1. 修改`MILVUS_ADDRESS`和`MILVUS_TOKEN`链接参数,分别对应 `zilliz` 的 `Public Endpoint` 和 `Api key`,记得把自己ip加入白名单。
|
修改`MILVUS_ADDRESS`和`MILVUS_TOKEN`链接参数,分别对应 `zilliz` 的 `Public Endpoint` 和 `Api key`,记得把自己ip加入白名单。
|
||||||
2. 修改FE_DOMAIN=你的前端你访问地址,例如 http://192.168.0.1:3000;https://cloud.fastgpt.cn
|
|
||||||
|
|
||||||
{{% /alert %}}
|
{{% /alert %}}
|
||||||
|
|
||||||
|
|||||||
@@ -132,11 +132,7 @@ OneAPI 的 API Key 配置错误,需要修改`OPENAI_API_KEY`环境变量,并
|
|||||||
|
|
||||||
4.8.10 版本新增了错误日志,报错时,会在日志中打印出实际发送的 Body 参数,可以复制该参数后,通过 curl 向 oneapi 发起请求测试。
|
4.8.10 版本新增了错误日志,报错时,会在日志中打印出实际发送的 Body 参数,可以复制该参数后,通过 curl 向 oneapi 发起请求测试。
|
||||||
|
|
||||||
由于 oneapi 在 stream 模式下,无法正确捕获错误,可以设置成 `stream=false` 后进行测试。可能的问题:
|
由于 oneapi 在 stream 模式下,无法正确捕获错误,可以设置成 `stream=false` 后进行测试。
|
||||||
|
|
||||||
1. 国内模型命中风控
|
|
||||||
2. 不支持的模型参数:只保留 messages 和必要参数来测试,删除其他参数测试。
|
|
||||||
3. 参数不符合模型要求:例如有的模型 temperature 不支持 0,有些不支持两位小数。max_tokens 超出,上下文超长等。
|
|
||||||
|
|
||||||
### 如何测试模型是否支持工具调用
|
### 如何测试模型是否支持工具调用
|
||||||
|
|
||||||
@@ -269,4 +265,4 @@ curl --location --request POST 'https://oneapi.xxxx/v1/chat/completions' \
|
|||||||
],
|
],
|
||||||
"tool_choice": "auto"
|
"tool_choice": "auto"
|
||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
@@ -25,7 +25,7 @@ FastGPT 的 API Key **有 2 类**,一类是全局通用的 key (无法直接
|
|||||||
|
|
||||||
| 通用key | 应用特定 key |
|
| 通用key | 应用特定 key |
|
||||||
| --------------------- | --------------------- |
|
| --------------------- | --------------------- |
|
||||||
|  |  |
|
|  |  |
|
||||||
|
|
||||||
## 基本配置
|
## 基本配置
|
||||||
|
|
||||||
|
|||||||
@@ -35,10 +35,9 @@ curl --location --request POST 'http://localhost:3000/api/v1/chat/completions' \
|
|||||||
--header 'Authorization: Bearer fastgpt-xxxxxx' \
|
--header 'Authorization: Bearer fastgpt-xxxxxx' \
|
||||||
--header 'Content-Type: application/json' \
|
--header 'Content-Type: application/json' \
|
||||||
--data-raw '{
|
--data-raw '{
|
||||||
"chatId": "my_chatId",
|
"chatId": "abcd",
|
||||||
"stream": false,
|
"stream": false,
|
||||||
"detail": false,
|
"detail": false,
|
||||||
"responseChatItemId": "my_responseChatItemId",
|
|
||||||
"variables": {
|
"variables": {
|
||||||
"uid": "asdfadsfasfd2323",
|
"uid": "asdfadsfasfd2323",
|
||||||
"name": "张三"
|
"name": "张三"
|
||||||
@@ -105,7 +104,6 @@ curl --location --request POST 'http://localhost:3000/api/v1/chat/completions' \
|
|||||||
- 为 `undefined` 时(不传入),不使用 FastGpt 提供的上下文功能,完全通过传入的 messages 构建上下文。 不会将你的记录存储到数据库中,你也无法在记录汇总中查阅到。
|
- 为 `undefined` 时(不传入),不使用 FastGpt 提供的上下文功能,完全通过传入的 messages 构建上下文。 不会将你的记录存储到数据库中,你也无法在记录汇总中查阅到。
|
||||||
- 为`非空字符串`时,意味着使用 chatId 进行对话,自动从 FastGpt 数据库取历史记录,并使用 messages 数组最后一个内容作为用户问题。请自行确保 chatId 唯一,长度小于250,通常可以是自己系统的对话框ID。
|
- 为`非空字符串`时,意味着使用 chatId 进行对话,自动从 FastGpt 数据库取历史记录,并使用 messages 数组最后一个内容作为用户问题。请自行确保 chatId 唯一,长度小于250,通常可以是自己系统的对话框ID。
|
||||||
- messages: 结构与 [GPT接口](https://platform.openai.com/docs/api-reference/chat/object) chat模式一致。
|
- messages: 结构与 [GPT接口](https://platform.openai.com/docs/api-reference/chat/object) chat模式一致。
|
||||||
- responseChatItemId: string | undefined 。如果传入,则会将该值作为本次对话的响应消息的 ID,FastGPT 会自动将该 ID 存入数据库。请确保,在当前`chatId`下,`responseChatItemId`是唯一的。
|
|
||||||
- detail: 是否返回中间值(模块状态,响应的完整结果等),`stream模式`下会通过`event`进行区分,`非stream模式`结果保存在`responseData`中。
|
- detail: 是否返回中间值(模块状态,响应的完整结果等),`stream模式`下会通过`event`进行区分,`非stream模式`结果保存在`responseData`中。
|
||||||
- variables: 模块变量,一个对象,会替换模块中,输入框内容里的`{{key}}`
|
- variables: 模块变量,一个对象,会替换模块中,输入框内容里的`{{key}}`
|
||||||
{{% /alert %}}
|
{{% /alert %}}
|
||||||
|
|||||||
@@ -31,6 +31,17 @@ curl --location --request POST 'http://localhost:3000/api/support/wallet/usage/c
|
|||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**x例子**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl --location --request POST 'http://localhost:3000/api/support/wallet/bill/createTrainingBill' \
|
||||||
|
--header 'Authorization: Bearer {{apikey}}' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--data-raw '{
|
||||||
|
"name": "可选,自定义订单名称,例如:文档训练-fastgpt.docx"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
{{< /markdownify >}}
|
{{< /markdownify >}}
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ curl --location --request POST 'https://{{host}}/api/admin/initv464' \
|
|||||||
4. 优化 - 历史记录模块。弃用旧的历史记录模块,直接在对应地方填写数值即可。
|
4. 优化 - 历史记录模块。弃用旧的历史记录模块,直接在对应地方填写数值即可。
|
||||||
5. 调整 - 知识库搜索模块 topk 逻辑,采用 MaxToken 计算,兼容不同长度的文本块
|
5. 调整 - 知识库搜索模块 topk 逻辑,采用 MaxToken 计算,兼容不同长度的文本块
|
||||||
6. 调整鉴权顺序,提高 apikey 的优先级,避免cookie抢占 apikey 的鉴权。
|
6. 调整鉴权顺序,提高 apikey 的优先级,避免cookie抢占 apikey 的鉴权。
|
||||||
7. 链接读取支持多选择器。参考[Web 站点同步用法](/docs/guide/knowledge_base/websync/)
|
7. 链接读取支持多选择器。参考[Web 站点同步用法](/docs/course/websync)
|
||||||
8. 修复 - 分享链接图片上传鉴权问题
|
8. 修复 - 分享链接图片上传鉴权问题
|
||||||
9. 修复 - Mongo 连接池未释放问题。
|
9. 修复 - Mongo 连接池未释放问题。
|
||||||
10. 修复 - Dataset Intro 无法更新
|
10. 修复 - Dataset Intro 无法更新
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ weight: 831
|
|||||||
|
|
||||||
## V4.6.5 功能介绍
|
## V4.6.5 功能介绍
|
||||||
|
|
||||||
1. 新增 - [问题优化模块](/docs/guide/workbench/workflow/coreferenceresolution/)
|
1. 新增 - [问题优化模块](/docs/workflow/modules/coreferenceresolution/)
|
||||||
2. 新增 - [文本编辑模块](/docs/guide/workbench/workflow/text_editor/)
|
2. 新增 - [文本编辑模块](/docs/workflow/modules/text_editor/)
|
||||||
3. 新增 - [判断器模块](/docs/guide/workbench/workflow/tfswitch//)
|
3. 新增 - [判断器模块](/docs/workflow/modules/tfswitch/)
|
||||||
4. 新增 - [自定义反馈模块](/docs/guide/workbench/workflow/custom_feedback/)
|
4. 新增 - [自定义反馈模块](/docs/workflow/modules/custom_feedback/)
|
||||||
5. 新增 - 【内容提取】模块支持选择模型,以及字段枚举
|
5. 新增 - 【内容提取】模块支持选择模型,以及字段枚举
|
||||||
6. 优化 - docx读取,兼容表格(表格转markdown)
|
6. 优化 - docx读取,兼容表格(表格转markdown)
|
||||||
7. 优化 - 高级编排连接线交互
|
7. 优化 - 高级编排连接线交互
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ weight: 830
|
|||||||
|
|
||||||
1. 查看 [FastGPT 2024 RoadMap](https://github.com/labring/FastGPT?tab=readme-ov-file#-%E5%9C%A8%E7%BA%BF%E4%BD%BF%E7%94%A8)
|
1. 查看 [FastGPT 2024 RoadMap](https://github.com/labring/FastGPT?tab=readme-ov-file#-%E5%9C%A8%E7%BA%BF%E4%BD%BF%E7%94%A8)
|
||||||
2. 新增 - Http 模块请求头支持 Json 编辑器。
|
2. 新增 - Http 模块请求头支持 Json 编辑器。
|
||||||
3. 新增 - [ReRank模型部署](/docs/development/custom-models/bge-rerank/)
|
3. 新增 - [ReRank模型部署](/docs/development/custom-models/reranker/)
|
||||||
4. 新增 - 搜索方式:分离向量语义检索,全文检索和重排,通过 RRF 进行排序合并。
|
4. 新增 - 搜索方式:分离向量语义检索,全文检索和重排,通过 RRF 进行排序合并。
|
||||||
5. 优化 - 问题分类提示词,id引导。测试国产商用 api 模型(百度阿里智谱讯飞)使用 Prompt 模式均可分类。
|
5. 优化 - 问题分类提示词,id引导。测试国产商用 api 模型(百度阿里智谱讯飞)使用 Prompt 模式均可分类。
|
||||||
6. UI 优化,未来将逐步替换新的UI设计。
|
6. UI 优化,未来将逐步替换新的UI设计。
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ curl --location --request POST 'https://{{host}}/api/init/v468' \
|
|||||||
|
|
||||||
1. 新增 - 知识库搜索合并模块。
|
1. 新增 - 知识库搜索合并模块。
|
||||||
2. 新增 - 新的 Http 模块,支持更加灵活的参数传入。同时支持了输入输出自动数据类型转化,例如:接口输出的 JSON 类型会自动转成字符串类型,直接给其他模块使用。此外,还补充了一些例子,可在文档中查看。
|
2. 新增 - 新的 Http 模块,支持更加灵活的参数传入。同时支持了输入输出自动数据类型转化,例如:接口输出的 JSON 类型会自动转成字符串类型,直接给其他模块使用。此外,还补充了一些例子,可在文档中查看。
|
||||||
3. 优化 - 内容补全。将内容补全内置到【知识库搜索】中,并实现了一次内容补全,即可完成“指代消除”和“问题扩展”。FastGPT知识库搜索详细流程可查看:[知识库搜索介绍](/docs/guide/workbench/workflow/dataset_search/)
|
3. 优化 - 内容补全。将内容补全内置到【知识库搜索】中,并实现了一次内容补全,即可完成“指代消除”和“问题扩展”。FastGPT知识库搜索详细流程可查看:[知识库搜索介绍](/docs/course/data_search/)
|
||||||
4. 优化 - LLM 模型配置,不再区分对话、分类、提取模型。同时支持模型的默认参数,避免不同模型参数冲突,可通过`defaultConfig`传入默认的配置。
|
4. 优化 - LLM 模型配置,不再区分对话、分类、提取模型。同时支持模型的默认参数,避免不同模型参数冲突,可通过`defaultConfig`传入默认的配置。
|
||||||
5. 优化 - 流响应,参考了`ChatNextWeb`的流,更加丝滑。此外,之前提到的乱码、中断,刷新后又正常了,可能会修复)
|
5. 优化 - 流响应,参考了`ChatNextWeb`的流,更加丝滑。此外,之前提到的乱码、中断,刷新后又正常了,可能会修复)
|
||||||
6. 修复 - 语音输入文件无法上传。
|
6. 修复 - 语音输入文件无法上传。
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: 'V4.8.13'
|
title: 'V4.8.13(进行中)'
|
||||||
description: 'FastGPT V4.8.13 更新说明'
|
description: 'FastGPT V4.8.13 更新说明'
|
||||||
icon: 'upgrade'
|
icon: 'upgrade'
|
||||||
draft: false
|
draft: false
|
||||||
@@ -13,17 +13,13 @@ weight: 811
|
|||||||
|
|
||||||
### 2. 修改镜像
|
### 2. 修改镜像
|
||||||
|
|
||||||
- 更新 FastGPT 镜像 tag: v4.8.13-fix
|
- 更新 FastGPT 镜像 tag: v4.8.13-alpha
|
||||||
- 更新 FastGPT 商业版镜像 tag: v4.8.13-fix (fastgpt-pro镜像)
|
- 更新 FastGPT 管理端镜像 tag: v4.8.13-alpha (fastgpt-pro镜像)
|
||||||
- Sandbox 镜像,可以不更新
|
- Sandbox 镜像,可以不更新
|
||||||
|
|
||||||
### 3. 添加环境变量
|
### 3. 调整文件上传编排
|
||||||
|
|
||||||
- 给 fastgpt 和 fastgpt-pro 镜像添加环境变量:`FE_DOMAIN=http://xx.com`,值为 fastgpt 前端访问地址,注意后面不要加`/`。可以自动补齐相对文件地址的前缀。
|
虽然依然兼容旧版的文件上传编排,但是未来两个版本内将会去除兼容代码,请尽快调整编排,以适应最新的文件上传逻辑。尤其是嵌套应用的文件传递,未来将不会自动传递,必须手动指定传递的文件。
|
||||||
|
|
||||||
### 4. 调整文件上传编排
|
|
||||||
|
|
||||||
虽然依然兼容旧版的文件上传编排,但是未来两个版本内将会去除兼容代码,请尽快调整编排,以适应最新的文件上传逻辑。尤其是嵌套应用的文件传递,未来将不会自动传递,必须手动指定传递的文件。具体内容可参考: [文件上传变更](/docs/guide/course/fileinput/#4813%E7%89%88%E6%9C%AC%E8%B5%B7%E5%85%B3%E4%BA%8E%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E7%9A%84%E6%9B%B4%E6%96%B0)
|
|
||||||
|
|
||||||
## 更新说明
|
## 更新说明
|
||||||
|
|
||||||
@@ -43,9 +39,6 @@ weight: 811
|
|||||||
14. 优化 - Markdown 组件自动空格,避免分割 url 中的中文。
|
14. 优化 - Markdown 组件自动空格,避免分割 url 中的中文。
|
||||||
15. 优化 - 工作流上下文拆分,性能优化。
|
15. 优化 - 工作流上下文拆分,性能优化。
|
||||||
16. 优化 - 语音播报,不支持 mediaSource 的浏览器可等待完全生成语音后输出。
|
16. 优化 - 语音播报,不支持 mediaSource 的浏览器可等待完全生成语音后输出。
|
||||||
17. 优化 - 对话引导 csv 读取,自动识别编码
|
17. 修复 - Dockerfile pnpm install 支持代理。
|
||||||
18. 优化 - csv 导入问题引导可能乱码
|
18. 修复 - BI 图表生成无法写入文件。同时优化其解析,支持数字类型数组。
|
||||||
19. 修复 - Dockerfile pnpm install 支持代理。。
|
19. 修复 - 分享链接首次加载时,标题显示不正确。
|
||||||
20. 修复 - Dockerfile pnpm install 支持代理。
|
|
||||||
21. 修复 - BI 图表生成无法写入文件。同时优化其解析,支持数字类型数组。
|
|
||||||
22. 修复 - 分享链接首次加载时,标题显示不正确。
|
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
---
|
|
||||||
title: 'V4.8.14'
|
|
||||||
description: 'FastGPT V4.8.14 更新说明'
|
|
||||||
icon: 'upgrade'
|
|
||||||
draft: false
|
|
||||||
toc: true
|
|
||||||
weight: 810
|
|
||||||
---
|
|
||||||
|
|
||||||
## 更新指南
|
|
||||||
|
|
||||||
### 1. 做好数据备份
|
|
||||||
|
|
||||||
### 2. 修改镜像
|
|
||||||
|
|
||||||
- 更新 FastGPT 镜像 tag: v4.8.14
|
|
||||||
- 更新 FastGPT 商业版镜像 tag: v4.8.14 (fastgpt-pro镜像)
|
|
||||||
- Sandbox 镜像,可以不更新
|
|
||||||
|
|
||||||
milvus版本使用:v4.8.14-milvus-fix 镜像。
|
|
||||||
|
|
||||||
## 新功能预览
|
|
||||||
|
|
||||||
### 自动触发工作流
|
|
||||||
|
|
||||||
可以允许你配置用户加载对话时,自动触发一次工作流。可以用于一些 CRM 系统,可以快速的引导用户使用,无需等待用户主动触发。
|
|
||||||
|
|
||||||
| | |
|
|
||||||
| --- | --- |
|
|
||||||
|  |  |
|
|
||||||
|
|
||||||
|
|
||||||
## 完整更新内容
|
|
||||||
|
|
||||||
1. 新增 - 工作流支持进入聊天框/点击开始对话后,自动触发一轮对话。
|
|
||||||
2. 新增 - 重写 chatContext,对话测试也会有日志,并且刷新后不会丢失对话。
|
|
||||||
3. 新增 - 分享链接支持配置是否允许查看原文。
|
|
||||||
4. 新增 - 新的 doc2x 插件。
|
|
||||||
5. 新增 - 繁体中文-台湾。
|
|
||||||
6. 新增 - 分析链接和 chat api 支持传入自定义 uid。
|
|
||||||
7. 商业版新增 - 微软 oauth 登录
|
|
||||||
8. 优化 - 工作流 ui 细节。
|
|
||||||
9. 优化 - 应用编辑记录采用 diff 存储,避免浏览器溢出。
|
|
||||||
10. 优化 - 代码入口,增加 register 入口,无需等待首次访问才执行。
|
|
||||||
11. 优化 - 工作流检查,增加更多缺失值检查。
|
|
||||||
12. 优化 - 增加知识库训练最大重试次数限制。
|
|
||||||
13. 优化 - 图片路径问题和示意图任务
|
|
||||||
14. 优化 - Milvus description
|
|
||||||
15. 修复 - 分块策略,四级标题会被丢失。 同时新增了五级标题的支持。
|
|
||||||
16. 修复 - MongoDB 知识库集合唯一索引。
|
|
||||||
17. 修复 - 反选知识库引用后可能会报错。
|
|
||||||
18. 修复 - 简易模式转工作流,不是使用最新编辑记录进行转移。
|
|
||||||
19. 修复 - 表单输入的说明文字不显示。
|
|
||||||
20. 修复 - API 无法使用 base64 图片。
|
|
||||||
@@ -66,7 +66,7 @@ Tips: 可以通过点击上下文按键查看完整的上下文组成,便于
|
|||||||
|
|
||||||
FastGPT 知识库采用 QA 对(不一定都是问答格式,仅代表两个变量)的格式存储,在转义成字符串时候会根据**引用模板**来进行格式化。知识库包含多个可用变量: q, a, sourceId(数据的ID), index(第n个数据), source(数据的集合名、文件名),score(距离得分,0-1) 可以通过 {{q}} {{a}} {{sourceId}} {{index}} {{source}} {{score}} 按需引入。下面一个模板例子:
|
FastGPT 知识库采用 QA 对(不一定都是问答格式,仅代表两个变量)的格式存储,在转义成字符串时候会根据**引用模板**来进行格式化。知识库包含多个可用变量: q, a, sourceId(数据的ID), index(第n个数据), source(数据的集合名、文件名),score(距离得分,0-1) 可以通过 {{q}} {{a}} {{sourceId}} {{index}} {{source}} {{score}} 按需引入。下面一个模板例子:
|
||||||
|
|
||||||
可以通过 [知识库结构讲解](/docs/guide/knowledge_base/dataset_engine/) 了解详细的知识库的结构。
|
可以通过 [知识库结构讲解](/docs/course/dataset_engine/) 了解详细的知识库的结构。
|
||||||
|
|
||||||
#### 引用模板
|
#### 引用模板
|
||||||
|
|
||||||
|
|||||||
@@ -30,5 +30,5 @@ weight: 232
|
|||||||
|
|
||||||
|
|
||||||
{{% alert icon="🍅" context="success" %}}
|
{{% alert icon="🍅" context="success" %}}
|
||||||
具体配置参数介绍可以参考: [AI参数配置说明](/docs/guide/course/ai_settings/)
|
具体配置参数介绍可以参考: [AI参数配置说明](/docs/course/ai_settings)
|
||||||
{{% /alert %}}
|
{{% /alert %}}
|
||||||
@@ -36,4 +36,4 @@ weight: 264
|
|||||||
|
|
||||||
## 示例
|
## 示例
|
||||||
|
|
||||||
- [接入谷歌搜索](/docs/use-cases/app-cases/google_search/)
|
- [接入谷歌搜索](/docs/workflow/examples/google_search/)
|
||||||
@@ -5,28 +5,4 @@ icon: "form_input"
|
|||||||
draft: false
|
draft: false
|
||||||
toc: true
|
toc: true
|
||||||
weight: 244
|
weight: 244
|
||||||
---
|
---
|
||||||
|
|
||||||
## 特点
|
|
||||||
|
|
||||||
- 用户交互
|
|
||||||
- 可重复添加
|
|
||||||
- 触发执行
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 功能
|
|
||||||
|
|
||||||
「表单输入」节点属于用户交互节点,当触发这个节点时,对话会进入“交互”状态,会记录工作流的状态,等用户完成交互后,继续向下执行工作流
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
比如上图中的例子,当触发表单输入节点时,对话框隐藏,对话进入“交互状态”
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
当用户填完必填的信息并点击提交后,节点能够收集用户填写的表单信息,传递到后续的节点中使用
|
|
||||||
|
|
||||||
## 作用
|
|
||||||
|
|
||||||
能够精准收集需要的用户信息,再根据用户信息进行后续操作
|
|
||||||
@@ -250,6 +250,6 @@ export default async function (ctx: FunctionContext) {
|
|||||||
|
|
||||||
## 相关示例
|
## 相关示例
|
||||||
|
|
||||||
- [谷歌搜索](/docs/use-cases/app-cases/google_search/)
|
- [谷歌搜索](/docs/workflow/examples/google_search/)
|
||||||
- [发送飞书webhook](/docs/use-cases/app-cases/feishu_webhook/)
|
- [发送飞书webhook](/docs/workflow/examples/feishu_webhook/)
|
||||||
- [实验室预约(操作数据库)](/docs/use-cases/app-cases/lab_appointment/)
|
- [实验室预约(操作数据库)](/docs/workflow/examples/lab_appointment/)
|
||||||
|
|||||||
@@ -29,4 +29,4 @@ weight: 246
|
|||||||
|
|
||||||
## 示例
|
## 示例
|
||||||
|
|
||||||
- [接入谷歌搜索](/docs/use-cases/app-cases/google_search/)
|
- [接入谷歌搜索](/docs/workflow/examples/google_search/)
|
||||||
@@ -7,21 +7,20 @@ toc: true
|
|||||||
weight: 236
|
weight: 236
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### **什么是工具**
|
## 什么是工具
|
||||||
|
|
||||||
工具可以是一个系统模块,例如:AI对话、知识库搜索、HTTP模块等。也可以是一个插件。
|
工具可以是一个系统模块,例如:AI对话、知识库搜索、HTTP模块等。也可以是一个插件。
|
||||||
|
|
||||||
工具调用可以让 LLM 更动态的决策流程,而不都是固定的流程。(当然,缺点就是费tokens)
|
工具调用可以让 LLM 更动态的决策流程,而不都是固定的流程。(当然,缺点就是费tokens)
|
||||||
|
|
||||||
### **工具的组成**
|
## 工具的组成
|
||||||
|
|
||||||
1. 工具介绍。通常是模块的介绍或插件的介绍,这个介绍会告诉LLM,这个工具的作用是什么。
|
1. 工具介绍。通常是模块的介绍或插件的介绍,这个介绍会告诉LLM,这个工具的作用是什么。
|
||||||
2. 工具参数。对于系统模块来说,工具参数已经是固定的,无需额外配置。对于插件来说,工具参数是一个可配置项。
|
2. 工具参数。对于系统模块来说,工具参数已经是固定的,无需额外配置。对于插件来说,工具参数是一个可配置项。
|
||||||
|
|
||||||
### **工具是如何运行的**
|
## 工具是如何运行的
|
||||||
|
|
||||||
要了解工具如何运行的,首先需要知道它的运行条件。
|
要了解工具如何运行的,首先需要知道它的运行条件。
|
||||||
|
|
||||||
@@ -30,57 +29,43 @@ weight: 236
|
|||||||
|
|
||||||
结合工具的介绍、参数介绍和参数是否必须,LLM会决定是否调用这个工具。有以下几种情况:
|
结合工具的介绍、参数介绍和参数是否必须,LLM会决定是否调用这个工具。有以下几种情况:
|
||||||
|
|
||||||
|
|
||||||
1. 无参数的工具:直接根据工具介绍,决定是否需要执行。例如:获取当前时间。
|
1. 无参数的工具:直接根据工具介绍,决定是否需要执行。例如:获取当前时间。
|
||||||
2. 有参数的工具:
|
2. 有参数的工具:
|
||||||
1. 无必须的参数:尽管上下文中,没有适合的参数,也可以调用该工具。但有时候,LLM会自己伪造一个参数。
|
1. 无必须的参数:尽管上下文中,没有适合的参数,也可以调用该工具。但有时候,LLM会自己伪造一个参数。
|
||||||
2. 有必须的参数:如果没有适合的参数,LLM可能不会调用该工具。可以通过提示词,引导用户提供参数。
|
2. 有必须的参数:如果没有适合的参数,LLM可能不会调用该工具。可以通过提示词,引导用户提供参数。
|
||||||
|
|
||||||
#### **工具调用逻辑**
|
|
||||||
|
|
||||||
在支持`函数调用`的模型中,可以一次性调用多个工具,调用逻辑如下:
|
在支持`函数调用`的模型中,可以一次性调用多个工具,调用逻辑如下:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### **怎么用**
|
## 怎么用
|
||||||
|
|
||||||
<div style="display: flex; gap: 10px;">
|
| 有工具调用模块 | 无工具调用模块 |
|
||||||
<img src="/imgs/flow-tool3.png" alt="工具调用模块示例 3" width="40%" />
|
| --- | --- |
|
||||||
<img src="/imgs/flow-tool4.png" alt="工具调用模块示例 4" width="60%" />
|
|  |  |
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ! -->
|
高级编排中,托动工具调用的连接点,可用的工具头部会出现一个菱形,可以将它与工具调用模块底部的菱形相连接。
|
||||||
|
|
||||||
高级编排中,拖动工具调用的连接点,可用的工具头部会出现一个菱形,可以将它与工具调用模块底部的菱形相连接。
|
被连接的工具,会自动分离工具输入与普通的输入,并且可以编辑`描述`,可以通过调整介绍,使得该工具调用时机更加精确。对于一些内置的节点,务必修改`描述`才能让模型正常调用。
|
||||||
|
|
||||||
被连接的工具,会自动分离工具输入与普通的输入,并且可以编辑`介绍`,可以通过调整介绍,使得该工具调用时机更加精确。
|
|
||||||
|
|
||||||
关于工具调用,如何调试仍然是一个玄学,所以建议,不要一次性增加太多工具,选择少量工具调优后再进一步尝试。
|
关于工具调用,如何调试仍然是一个玄学,所以建议,不要一次性增加太多工具,选择少量工具调优后再进一步尝试。
|
||||||
|
|
||||||
#### 用途
|
## 组合节点
|
||||||
|
|
||||||
默认清空下,工具调用节点,在决定调用工具后,会将工具运行的结果,返回给AI,让 AI 对工具运行的结果进行总结输出。有时候,如果你不需要 AI 进行进一步的总结输出,可以使用该节点,将其接入对于工具流程的末尾。
|
### 工具调用终止
|
||||||
|
|
||||||
如下图,在执行知识库搜索后,发送给了 HTTP 请求,搜索将不会返回搜索的结果给工具调用进行 AI 总结。
|
工具调用默认会把子流程运行的结果作为`工具结果`,返回给模型进行回答。有时候,你可能不希望模型做回答,你可以给对应子流程的末尾增加上一个`工具调用终止`节点,这样,子流程的结果就不会被返回给模型。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### 附加节点
|
|
||||||
|
|
||||||
当您使用了工具调用节点,同时就会出现工具调用终止节点和自定义变量节点,能够进一步提升工具调用的使用体验。
|
|
||||||
|
|
||||||
#### 工具调用终止
|
|
||||||
|
|
||||||
工具调用终止可用于结束本次调用,即可以接在某个工具后面,当工作流执行到这个节点时,便会强制结束本次工具调用,不再调用其他工具,也不会再调用 AI 针对工具调用结果回答问题。
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### 自定义工具变量
|
### 自定义工具变量
|
||||||
|
|
||||||
自定义变量可以扩展工具的变量输入,即对于一些未被视作工具参数或无法工具调用的节点,可以自定义工具变量,填上对应的参数描述,那么工具调用便会相对应的调用这个节点,进而调用其之后的工作流。
|
工具调用的子流程运行,有时候会依赖`AI`生成的一些变量,为了简化交互流程,我们给系统内置的节点都指定了`工具变量`。然而,有些时候,你需要的变量不仅是目标流程的`首个节点`的变量,而是需要更复杂的变量,此时你可以使用`自定义工具变量`。它允许你完全自定义该`工具流程`的变量。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### **相关示例**
|
## 相关示例
|
||||||
|
|
||||||
- [谷歌搜索](https://doc.fastgpt.in/docs/use-cases/app-cases/google_search/)
|
- [谷歌搜索](/docs/workflow/examples/google_search/)
|
||||||
- [发送飞书webhook](https://doc.fastgpt.in/docs/use-cases/app-cases/feishu_webhook/)
|
- [发送飞书webhook](/docs/workflow/examples/feishu_webhook/)
|
||||||
@@ -85,15 +85,16 @@ FastGPT 商业版软件根据不同的部署方式,分为 3 类收费模式。
|
|||||||
|
|
||||||
## QA
|
## QA
|
||||||
|
|
||||||
### 如何交付?
|
1. 如何交付?
|
||||||
|
|
||||||
完整版应用 = 开源版镜像 + 商业版镜像
|
完整版应用 = 开源版镜像 + 商业版镜像
|
||||||
|
|
||||||
我们会提供一个商业版镜像给你使用,该镜像需要一个 License 启动。
|
我们会提供一个商业版镜像给你使用,该镜像需要一个 License 启动。
|
||||||
|
|
||||||
### 二次开发如何操作?
|
2. 二次开发如何操作?
|
||||||
|
|
||||||
|
可自行修改开源版代码进行二次开发,不支持修改商业版镜像。
|
||||||
|
|
||||||
可以修改开源版部分代码,不支持修改商业版镜像。完整版本=开源版+商业版镜像,所以是可以修改部分内容的。但是如果二开了,后续则需要自己进行代码合并升级。
|
|
||||||
|
|
||||||
## Sealos 费用
|
## Sealos 费用
|
||||||
|
|
||||||
|
|||||||
@@ -39,53 +39,44 @@ weight: 506
|
|||||||
海外版用户(cloud.tryfastgpt.ai)可以填写下面的 IP 白名单:
|
海外版用户(cloud.tryfastgpt.ai)可以填写下面的 IP 白名单:
|
||||||
|
|
||||||
```
|
```
|
||||||
35.240.227.100
|
34.87.20.17
|
||||||
34.124.237.188
|
|
||||||
34.143.240.160
|
|
||||||
34.87.51.146
|
|
||||||
34.87.79.202
|
|
||||||
35.247.163.68
|
|
||||||
34.87.102.86
|
|
||||||
35.198.192.104
|
|
||||||
34.126.163.205
|
|
||||||
34.124.189.116
|
|
||||||
34.143.149.171
|
|
||||||
34.87.173.252
|
|
||||||
34.142.157.52
|
|
||||||
34.87.180.104
|
|
||||||
34.87.20.189
|
|
||||||
34.87.110.152
|
|
||||||
34.87.44.74
|
|
||||||
34.87.152.33
|
|
||||||
35.197.149.75
|
|
||||||
35.247.161.35
|
35.247.161.35
|
||||||
|
34.87.51.146
|
||||||
|
34.87.110.152
|
||||||
|
35.247.163.68
|
||||||
|
34.126.163.205
|
||||||
|
34.87.20.189
|
||||||
|
34.87.102.86
|
||||||
|
35.240.227.100
|
||||||
|
35.198.192.104
|
||||||
|
34.143.149.171
|
||||||
|
34.87.152.33
|
||||||
|
34.124.237.188
|
||||||
|
35.197.149.75
|
||||||
|
34.87.44.74
|
||||||
|
34.124.189.116
|
||||||
|
34.87.79.202
|
||||||
|
34.87.173.252
|
||||||
|
34.143.240.160
|
||||||
|
34.87.180.104
|
||||||
|
34.142.157.52
|
||||||
```
|
```
|
||||||
|
|
||||||
国内版用户(fastgpt.cn)可以填写下面的 IP 白名单:
|
国内版用户(fastgpt.cn)可以填写下面的 IP 白名单:
|
||||||
|
|
||||||
```
|
```
|
||||||
47.97.1.240
|
47.97.59.172
|
||||||
121.43.105.217
|
121.43.108.48
|
||||||
|
121.41.75.88
|
||||||
121.41.178.7
|
121.41.178.7
|
||||||
121.40.65.187
|
121.40.65.187
|
||||||
47.97.59.172
|
|
||||||
101.37.205.32
|
|
||||||
120.55.195.90
|
|
||||||
120.26.229.115
|
|
||||||
120.55.193.112
|
|
||||||
47.98.190.173
|
|
||||||
112.124.41.79
|
|
||||||
121.196.235.183
|
121.196.235.183
|
||||||
121.41.75.88
|
120.55.195.90
|
||||||
121.43.108.48
|
120.55.193.112
|
||||||
112.124.12.6
|
120.26.229.115
|
||||||
121.43.52.222
|
112.124.41.79
|
||||||
121.199.162.43
|
101.37.205.32
|
||||||
121.199.162.102
|
47.98.190.173
|
||||||
120.55.94.163
|
|
||||||
47.99.59.223
|
|
||||||
112.124.46.5
|
|
||||||
121.40.46.247
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 4. 获取AES Key,选择加密方式
|
## 4. 获取AES Key,选择加密方式
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ weight: 509
|
|||||||
|
|
||||||
[chatgpt-on-wechat GitHub 地址](https://github.com/zhayujie/chatgpt-on-wechat)
|
[chatgpt-on-wechat GitHub 地址](https://github.com/zhayujie/chatgpt-on-wechat)
|
||||||
|
|
||||||
由于 FastGPT 的 API 接口和 OpenAI 的规范一致,可以无需变更原来的应用即可使用 FastGPT 上编排好的应用。API 使用可参考 [这篇文章](/docs/use-cases/external-integration/openapi/)。编排示例,可参考 [高级编排介绍](/docs/workflow/intro)
|
由于 FastGPT 的 API 接口和 OpenAI 的规范一致,可以无需变更原来的应用即可使用 FastGPT 上编排好的应用。API 使用可参考 [这篇文章](/docs/course/openapi/)。编排示例,可参考 [高级编排介绍](/docs/workflow/intro)
|
||||||
|
|
||||||
## 1. 获取 OpenAPI 密钥
|
## 1. 获取 OpenAPI 密钥
|
||||||
|
|
||||||
|
|||||||
@@ -114,15 +114,15 @@ services:
|
|||||||
# fastgpt
|
# fastgpt
|
||||||
sandbox:
|
sandbox:
|
||||||
container_name: sandbox
|
container_name: sandbox
|
||||||
image: ghcr.io/labring/fastgpt-sandbox:v4.8.14 # git
|
image: ghcr.io/labring/fastgpt-sandbox:v4.8.11 # git
|
||||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.14 # 阿里云
|
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.11 # 阿里云
|
||||||
networks:
|
networks:
|
||||||
- fastgpt
|
- fastgpt
|
||||||
restart: always
|
restart: always
|
||||||
fastgpt:
|
fastgpt:
|
||||||
container_name: fastgpt
|
container_name: fastgpt
|
||||||
image: ghcr.io/labring/fastgpt:v4.8.14-milvus-fix # git
|
image: ghcr.io/labring/fastgpt:v4.8.11 # git
|
||||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.14-milvus-fix # 阿里云
|
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.11 # 阿里云
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
networks:
|
networks:
|
||||||
@@ -133,14 +133,14 @@ services:
|
|||||||
- sandbox
|
- sandbox
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
# 前端访问地址: http://localhost:3000
|
|
||||||
- FE_DOMAIN=
|
|
||||||
# root 密码,用户名为: root。如果需要修改 root 密码,直接修改这个环境变量,并重启即可。
|
# root 密码,用户名为: root。如果需要修改 root 密码,直接修改这个环境变量,并重启即可。
|
||||||
- DEFAULT_ROOT_PSW=1234
|
- DEFAULT_ROOT_PSW=1234
|
||||||
# AI模型的API地址哦。务必加 /v1。这里默认填写了OneApi的访问地址。
|
# AI模型的API地址哦。务必加 /v1。这里默认填写了OneApi的访问地址。
|
||||||
- OPENAI_BASE_URL=http://oneapi:3000/v1
|
- OPENAI_BASE_URL=http://oneapi:3000/v1
|
||||||
# AI模型的API Key。(这里默认填写了OneAPI的快速默认key,测试通后,务必及时修改)
|
# AI模型的API Key。(这里默认填写了OneAPI的快速默认key,测试通后,务必及时修改)
|
||||||
- CHAT_API_KEY=sk-fastgpt
|
- CHAT_API_KEY=sk-fastgpt
|
||||||
|
# 是否将图片转成 base64 传递给模型,本地开发和内网环境使用共有模型时候需要设置为 true
|
||||||
|
- MULTIPLE_DATA_TO_BASE64=false
|
||||||
# 数据库最大连接数
|
# 数据库最大连接数
|
||||||
- DB_MAX_LINK=30
|
- DB_MAX_LINK=30
|
||||||
# 登录凭证密钥
|
# 登录凭证密钥
|
||||||
@@ -156,6 +156,8 @@ services:
|
|||||||
- MILVUS_TOKEN=none
|
- MILVUS_TOKEN=none
|
||||||
# sandbox 地址
|
# sandbox 地址
|
||||||
- SANDBOX_URL=http://sandbox:3000
|
- SANDBOX_URL=http://sandbox:3000
|
||||||
|
# 前端地址: http://localhost:3000
|
||||||
|
- FE_DOMAIN=
|
||||||
# 日志等级: debug, info, warn, error
|
# 日志等级: debug, info, warn, error
|
||||||
- LOG_LEVEL=info
|
- LOG_LEVEL=info
|
||||||
- STORE_LOG_LEVEL=warn
|
- STORE_LOG_LEVEL=warn
|
||||||
|
|||||||
@@ -72,15 +72,15 @@ services:
|
|||||||
# fastgpt
|
# fastgpt
|
||||||
sandbox:
|
sandbox:
|
||||||
container_name: sandbox
|
container_name: sandbox
|
||||||
image: ghcr.io/labring/fastgpt-sandbox:v4.8.13 # git
|
image: ghcr.io/labring/fastgpt-sandbox:v4.8.11 # git
|
||||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.13 # 阿里云
|
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.11 # 阿里云
|
||||||
networks:
|
networks:
|
||||||
- fastgpt
|
- fastgpt
|
||||||
restart: always
|
restart: always
|
||||||
fastgpt:
|
fastgpt:
|
||||||
container_name: fastgpt
|
container_name: fastgpt
|
||||||
image: ghcr.io/labring/fastgpt:v4.8.14 # git
|
image: ghcr.io/labring/fastgpt:v4.8.11 # git
|
||||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.14 # 阿里云
|
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.11 # 阿里云
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
networks:
|
networks:
|
||||||
@@ -91,14 +91,14 @@ services:
|
|||||||
- sandbox
|
- sandbox
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
# 前端访问地址: http://localhost:3000
|
|
||||||
- FE_DOMAIN=
|
|
||||||
# root 密码,用户名为: root。如果需要修改 root 密码,直接修改这个环境变量,并重启即可。
|
# root 密码,用户名为: root。如果需要修改 root 密码,直接修改这个环境变量,并重启即可。
|
||||||
- DEFAULT_ROOT_PSW=1234
|
- DEFAULT_ROOT_PSW=1234
|
||||||
# AI模型的API地址哦。务必加 /v1。这里默认填写了OneApi的访问地址。
|
# AI模型的API地址哦。务必加 /v1。这里默认填写了OneApi的访问地址。
|
||||||
- OPENAI_BASE_URL=http://oneapi:3000/v1
|
- OPENAI_BASE_URL=http://oneapi:3000/v1
|
||||||
# AI模型的API Key。(这里默认填写了OneAPI的快速默认key,测试通后,务必及时修改)
|
# AI模型的API Key。(这里默认填写了OneAPI的快速默认key,测试通后,务必及时修改)
|
||||||
- CHAT_API_KEY=sk-fastgpt
|
- CHAT_API_KEY=sk-fastgpt
|
||||||
|
# 是否将图片转成 base64 传递给模型,本地开发和内网环境使用共有模型时候需要设置为 true
|
||||||
|
- MULTIPLE_DATA_TO_BASE64=false
|
||||||
# 数据库最大连接数
|
# 数据库最大连接数
|
||||||
- DB_MAX_LINK=30
|
- DB_MAX_LINK=30
|
||||||
# 登录凭证密钥
|
# 登录凭证密钥
|
||||||
@@ -113,6 +113,8 @@ services:
|
|||||||
- PG_URL=postgresql://username:password@pg:5432/postgres
|
- PG_URL=postgresql://username:password@pg:5432/postgres
|
||||||
# sandbox 地址
|
# sandbox 地址
|
||||||
- SANDBOX_URL=http://sandbox:3000
|
- SANDBOX_URL=http://sandbox:3000
|
||||||
|
# 前端地址: http://localhost:3000
|
||||||
|
- FE_DOMAIN=
|
||||||
# 日志等级: debug, info, warn, error
|
# 日志等级: debug, info, warn, error
|
||||||
- LOG_LEVEL=info
|
- LOG_LEVEL=info
|
||||||
- STORE_LOG_LEVEL=warn
|
- STORE_LOG_LEVEL=warn
|
||||||
|
|||||||
@@ -53,15 +53,15 @@ services:
|
|||||||
wait $$!
|
wait $$!
|
||||||
sandbox:
|
sandbox:
|
||||||
container_name: sandbox
|
container_name: sandbox
|
||||||
image: ghcr.io/labring/fastgpt-sandbox:v4.8.14 # git
|
image: ghcr.io/labring/fastgpt-sandbox:v4.8.11 # git
|
||||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.14 # 阿里云
|
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.11 # 阿里云
|
||||||
networks:
|
networks:
|
||||||
- fastgpt
|
- fastgpt
|
||||||
restart: always
|
restart: always
|
||||||
fastgpt:
|
fastgpt:
|
||||||
container_name: fastgpt
|
container_name: fastgpt
|
||||||
image: ghcr.io/labring/fastgpt:v4.8.14-milvus-fix # git
|
image: ghcr.io/labring/fastgpt:v4.8.11 # git
|
||||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.14-milvus-fix # 阿里云
|
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.11 # 阿里云
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
networks:
|
networks:
|
||||||
@@ -71,14 +71,14 @@ services:
|
|||||||
- sandbox
|
- sandbox
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
# 前端访问地址: http://localhost:3000
|
|
||||||
- FE_DOMAIN=
|
|
||||||
# root 密码,用户名为: root。如果需要修改 root 密码,直接修改这个环境变量,并重启即可。
|
# root 密码,用户名为: root。如果需要修改 root 密码,直接修改这个环境变量,并重启即可。
|
||||||
- DEFAULT_ROOT_PSW=1234
|
- DEFAULT_ROOT_PSW=1234
|
||||||
# AI模型的API地址哦。务必加 /v1。这里默认填写了OneApi的访问地址。
|
# AI模型的API地址哦。务必加 /v1。这里默认填写了OneApi的访问地址。
|
||||||
- OPENAI_BASE_URL=http://oneapi:3000/v1
|
- OPENAI_BASE_URL=http://oneapi:3000/v1
|
||||||
# AI模型的API Key。(这里默认填写了OneAPI的快速默认key,测试通后,务必及时修改)
|
# AI模型的API Key。(这里默认填写了OneAPI的快速默认key,测试通后,务必及时修改)
|
||||||
- CHAT_API_KEY=sk-fastgpt
|
- CHAT_API_KEY=sk-fastgpt
|
||||||
|
# 是否将图片转成 base64 传递给模型,本地开发和内网环境使用共有模型时候需要设置为 true
|
||||||
|
- MULTIPLE_DATA_TO_BASE64=false
|
||||||
# 数据库最大连接数
|
# 数据库最大连接数
|
||||||
- DB_MAX_LINK=30
|
- DB_MAX_LINK=30
|
||||||
# 登录凭证密钥
|
# 登录凭证密钥
|
||||||
@@ -94,6 +94,8 @@ services:
|
|||||||
- MILVUS_TOKEN=zilliz_cloud_token
|
- MILVUS_TOKEN=zilliz_cloud_token
|
||||||
# sandbox 地址
|
# sandbox 地址
|
||||||
- SANDBOX_URL=http://sandbox:3000
|
- SANDBOX_URL=http://sandbox:3000
|
||||||
|
# 前端地址: http://localhost:3000
|
||||||
|
- FE_DOMAIN=
|
||||||
# 日志等级: debug, info, warn, error
|
# 日志等级: debug, info, warn, error
|
||||||
- LOG_LEVEL=info
|
- LOG_LEVEL=info
|
||||||
- STORE_LOG_LEVEL=warn
|
- STORE_LOG_LEVEL=warn
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export const bucketNameMap = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ReadFileBaseUrl = `${process.env.FE_DOMAIN || ''}${process.env.NEXT_PUBLIC_BASE_URL || ''}/api/common/file/read`;
|
export const ReadFileBaseUrl = `${process.env.FE_DOMAIN || ''}${process.env.NEXT_PUBLIC_BASE_URL}/api/common/file/read`;
|
||||||
|
|
||||||
export const documentFileType = '.txt, .docx, .csv, .xlsx, .pdf, .md, .html, .pptx';
|
export const documentFileType = '.txt, .docx, .csv, .xlsx, .pdf, .md, .html, .pptx';
|
||||||
export const imageFileType =
|
export const imageFileType =
|
||||||
|
|||||||
@@ -12,7 +12,5 @@ export const fileImgs = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export function getFileIcon(name = '', defaultImg = 'file/fill/file') {
|
export function getFileIcon(name = '', defaultImg = 'file/fill/file') {
|
||||||
return (
|
return fileImgs.find((item) => new RegExp(item.suffix, 'gi').test(name))?.src || defaultImg;
|
||||||
fileImgs.find((item) => new RegExp(`\.${item.suffix}`, 'gi').test(name))?.src || defaultImg
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,11 +23,6 @@ export const parseUrlToFileType = (url: string): UserChatItemValueItemType['file
|
|||||||
const parseUrl = new URL(url, 'https://locaohost:3000');
|
const parseUrl = new URL(url, 'https://locaohost:3000');
|
||||||
|
|
||||||
const filename = (() => {
|
const filename = (() => {
|
||||||
// Check base64 image
|
|
||||||
if (url.startsWith('data:image/')) {
|
|
||||||
const mime = url.split(',')[0].split(':')[1].split(';')[0];
|
|
||||||
return `image.${mime.split('/')[1]}`;
|
|
||||||
}
|
|
||||||
// Old version file url: https://xxx.com/file/read?filename=xxx.pdf
|
// Old version file url: https://xxx.com/file/read?filename=xxx.pdf
|
||||||
const filenameQuery = parseUrl.searchParams.get('filename');
|
const filenameQuery = parseUrl.searchParams.get('filename');
|
||||||
if (filenameQuery) return filenameQuery;
|
if (filenameQuery) return filenameQuery;
|
||||||
|
|||||||
3
packages/global/common/file/type.d.ts
vendored
@@ -3,7 +3,6 @@ import { BucketNameEnum } from './constants';
|
|||||||
export type FileTokenQuery = {
|
export type FileTokenQuery = {
|
||||||
bucketName: `${BucketNameEnum}`;
|
bucketName: `${BucketNameEnum}`;
|
||||||
teamId: string;
|
teamId: string;
|
||||||
uid: string; // tmbId/ share uid/ teamChat uid
|
tmbId: string;
|
||||||
fileId: string;
|
fileId: string;
|
||||||
customExpireMinutes?: number;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { batchRun } from '../fn/utils';
|
import { batchRun } from '../fn/utils';
|
||||||
import { getNanoid, simpleText } from './tools';
|
import { simpleText } from './tools';
|
||||||
import type { ImageType } from '../../../service/worker/readFile/type';
|
|
||||||
|
|
||||||
/* Delete redundant text in markdown */
|
/* Delete redundant text in markdown */
|
||||||
export const simpleMarkdownText = (rawText: string) => {
|
export const simpleMarkdownText = (rawText: string) => {
|
||||||
@@ -93,25 +92,3 @@ export const markdownProcess = async ({
|
|||||||
|
|
||||||
return simpleMarkdownText(imageProcess);
|
return simpleMarkdownText(imageProcess);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const matchMdImgTextAndUpload = (text: string) => {
|
|
||||||
const base64Regex = /"(data:image\/[^;]+;base64[^"]+)"/g;
|
|
||||||
const imageList: ImageType[] = [];
|
|
||||||
const images = Array.from(text.match(base64Regex) || []);
|
|
||||||
for (const image of images) {
|
|
||||||
const uuid = `IMAGE_${getNanoid(12)}_IMAGE`;
|
|
||||||
const mime = image.split(';')[0].split(':')[1];
|
|
||||||
const base64 = image.split(',')[1];
|
|
||||||
text = text.replace(image, uuid);
|
|
||||||
imageList.push({
|
|
||||||
uuid,
|
|
||||||
base64,
|
|
||||||
mime
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
text,
|
|
||||||
imageList
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ ${mdSplitString}
|
|||||||
5. 标点分割:重叠
|
5. 标点分割:重叠
|
||||||
*/
|
*/
|
||||||
const commonSplit = (props: SplitProps): SplitResponse => {
|
const commonSplit = (props: SplitProps): SplitResponse => {
|
||||||
let { text = '', chunkLen, overlapRatio = 0.15, customReg = [] } = props;
|
let { text = '', chunkLen, overlapRatio = 0.2, customReg = [] } = props;
|
||||||
|
|
||||||
const splitMarker = 'SPLIT_HERE_SPLIT_HERE';
|
const splitMarker = 'SPLIT_HERE_SPLIT_HERE';
|
||||||
const codeBlockMarker = 'CODE_BLOCK_LINE_MARKER';
|
const codeBlockMarker = 'CODE_BLOCK_LINE_MARKER';
|
||||||
@@ -113,8 +113,6 @@ const commonSplit = (props: SplitProps): SplitResponse => {
|
|||||||
text = text.replace(/(\r?\n|\r){3,}/g, '\n\n\n');
|
text = text.replace(/(\r?\n|\r){3,}/g, '\n\n\n');
|
||||||
|
|
||||||
// The larger maxLen is, the next sentence is less likely to trigger splitting
|
// The larger maxLen is, the next sentence is less likely to trigger splitting
|
||||||
const markdownIndex = 4;
|
|
||||||
const forbidOverlapIndex = 8;
|
|
||||||
const stepReges: { reg: RegExp; maxLen: number }[] = [
|
const stepReges: { reg: RegExp; maxLen: number }[] = [
|
||||||
...customReg.map((text) => ({
|
...customReg.map((text) => ({
|
||||||
reg: new RegExp(`(${replaceRegChars(text)})`, 'g'),
|
reg: new RegExp(`(${replaceRegChars(text)})`, 'g'),
|
||||||
@@ -124,11 +122,9 @@ const commonSplit = (props: SplitProps): SplitResponse => {
|
|||||||
{ reg: /^(##\s[^\n]+\n)/gm, maxLen: chunkLen * 1.4 },
|
{ reg: /^(##\s[^\n]+\n)/gm, maxLen: chunkLen * 1.4 },
|
||||||
{ reg: /^(###\s[^\n]+\n)/gm, maxLen: chunkLen * 1.6 },
|
{ reg: /^(###\s[^\n]+\n)/gm, maxLen: chunkLen * 1.6 },
|
||||||
{ reg: /^(####\s[^\n]+\n)/gm, maxLen: chunkLen * 1.8 },
|
{ reg: /^(####\s[^\n]+\n)/gm, maxLen: chunkLen * 1.8 },
|
||||||
{ reg: /^(#####\s[^\n]+\n)/gm, maxLen: chunkLen * 1.8 },
|
|
||||||
|
|
||||||
{ reg: /([\n]([`~]))/g, maxLen: chunkLen * 4 }, // code block
|
{ reg: /([\n]([`~]))/g, maxLen: chunkLen * 4 }, // code block
|
||||||
{ reg: /([\n](?=\s*[0-9]+\.))/g, maxLen: chunkLen * 2 }, // 增大块,尽可能保证它是一个完整的段落。 (?![\*\-|>`0-9]): markdown special char
|
{ reg: /([\n](?!\s*[\*\-|>0-9]))/g, maxLen: chunkLen * 2 }, // 增大块,尽可能保证它是一个完整的段落。 (?![\*\-|>`0-9]): markdown special char
|
||||||
{ reg: /(\n{2,})/g, maxLen: chunkLen * 1.6 },
|
|
||||||
{ reg: /([\n])/g, maxLen: chunkLen * 1.2 },
|
{ reg: /([\n])/g, maxLen: chunkLen * 1.2 },
|
||||||
// ------ There's no overlap on the top
|
// ------ There's no overlap on the top
|
||||||
{ reg: /([。]|([a-zA-Z])\.\s)/g, maxLen: chunkLen * 1.2 },
|
{ reg: /([。]|([a-zA-Z])\.\s)/g, maxLen: chunkLen * 1.2 },
|
||||||
@@ -140,10 +136,8 @@ const commonSplit = (props: SplitProps): SplitResponse => {
|
|||||||
|
|
||||||
const customRegLen = customReg.length;
|
const customRegLen = customReg.length;
|
||||||
const checkIsCustomStep = (step: number) => step < customRegLen;
|
const checkIsCustomStep = (step: number) => step < customRegLen;
|
||||||
const checkIsMarkdownSplit = (step: number) =>
|
const checkIsMarkdownSplit = (step: number) => step >= customRegLen && step <= 3 + customRegLen;
|
||||||
step >= customRegLen && step <= markdownIndex + customRegLen;
|
const checkForbidOverlap = (step: number) => step <= 6 + customRegLen;
|
||||||
+customReg.length;
|
|
||||||
const checkForbidOverlap = (step: number) => step <= forbidOverlapIndex + customRegLen;
|
|
||||||
|
|
||||||
// if use markdown title split, Separate record title
|
// if use markdown title split, Separate record title
|
||||||
const getSplitTexts = ({ text, step }: { text: string; step: number }) => {
|
const getSplitTexts = ({ text, step }: { text: string; step: number }) => {
|
||||||
@@ -237,7 +231,7 @@ const commonSplit = (props: SplitProps): SplitResponse => {
|
|||||||
// use slice-chunkLen to split text
|
// use slice-chunkLen to split text
|
||||||
const chunks: string[] = [];
|
const chunks: string[] = [];
|
||||||
for (let i = 0; i < text.length; i += chunkLen - overlapLen) {
|
for (let i = 0; i < text.length; i += chunkLen - overlapLen) {
|
||||||
chunks.push(text.slice(i, i + chunkLen));
|
chunks.push(`${parentTitle}${text.slice(i, i + chunkLen)}`);
|
||||||
}
|
}
|
||||||
return chunks;
|
return chunks;
|
||||||
}
|
}
|
||||||
@@ -247,6 +241,7 @@ const commonSplit = (props: SplitProps): SplitResponse => {
|
|||||||
|
|
||||||
const maxLen = splitTexts.length > 1 ? stepReges[step].maxLen : chunkLen;
|
const maxLen = splitTexts.length > 1 ? stepReges[step].maxLen : chunkLen;
|
||||||
const minChunkLen = chunkLen * 0.7;
|
const minChunkLen = chunkLen * 0.7;
|
||||||
|
// console.log(splitTexts, stepReges[step].reg);
|
||||||
|
|
||||||
const chunks: string[] = [];
|
const chunks: string[] = [];
|
||||||
for (let i = 0; i < splitTexts.length; i++) {
|
for (let i = 0; i < splitTexts.length; i++) {
|
||||||
@@ -254,34 +249,12 @@ const commonSplit = (props: SplitProps): SplitResponse => {
|
|||||||
|
|
||||||
const lastTextLen = lastText.length;
|
const lastTextLen = lastText.length;
|
||||||
const currentText = item.text;
|
const currentText = item.text;
|
||||||
|
const currentTextLen = currentText.length;
|
||||||
const newText = lastText + currentText;
|
const newText = lastText + currentText;
|
||||||
const newTextLen = newText.length;
|
const newTextLen = lastTextLen + currentTextLen;
|
||||||
|
|
||||||
// Markdown 模式下,会强制向下拆分最小块,并再最后一个标题时候,给小块都补充上所有标题(包含父级标题)
|
|
||||||
if (isMarkdownStep) {
|
|
||||||
// split new Text, split chunks must will greater 1 (small lastText)
|
|
||||||
const innerChunks = splitTextRecursively({
|
|
||||||
text: newText,
|
|
||||||
step: step + 1,
|
|
||||||
lastText: '',
|
|
||||||
parentTitle: parentTitle + item.title
|
|
||||||
});
|
|
||||||
|
|
||||||
const lastChunk = innerChunks[innerChunks.length - 1];
|
|
||||||
if (!lastChunk) continue;
|
|
||||||
|
|
||||||
chunks.push(
|
|
||||||
...innerChunks.map(
|
|
||||||
(chunk) =>
|
|
||||||
step === markdownIndex + customRegLen ? `${parentTitle}${item.title}${chunk}` : chunk // 合并进 Markdown 分块时,需要补标题
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// newText is too large(now, The lastText must be smaller than chunkLen)
|
// newText is too large(now, The lastText must be smaller than chunkLen)
|
||||||
if (newTextLen > maxLen) {
|
if (newTextLen > maxLen || isMarkdownStep) {
|
||||||
// lastText greater minChunkLen, direct push it to chunks, not add to next chunk. (large lastText)
|
// lastText greater minChunkLen, direct push it to chunks, not add to next chunk. (large lastText)
|
||||||
if (lastTextLen > minChunkLen) {
|
if (lastTextLen > minChunkLen) {
|
||||||
chunks.push(lastText);
|
chunks.push(lastText);
|
||||||
@@ -305,6 +278,15 @@ const commonSplit = (props: SplitProps): SplitResponse => {
|
|||||||
|
|
||||||
if (!lastChunk) continue;
|
if (!lastChunk) continue;
|
||||||
|
|
||||||
|
if (forbidConcat) {
|
||||||
|
chunks.push(
|
||||||
|
...innerChunks.map(
|
||||||
|
(chunk) => (step === 3 + customRegLen ? `${parentTitle}${chunk}` : chunk) // 合并进 Markdown 分块时,需要补标题
|
||||||
|
)
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// last chunk is too small, concat it to lastText(next chunk start)
|
// last chunk is too small, concat it to lastText(next chunk start)
|
||||||
if (lastChunk.length < minChunkLen) {
|
if (lastChunk.length < minChunkLen) {
|
||||||
chunks.push(...innerChunks.slice(0, -1));
|
chunks.push(...innerChunks.slice(0, -1));
|
||||||
@@ -322,11 +304,11 @@ const commonSplit = (props: SplitProps): SplitResponse => {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// New text is small
|
// new text is small
|
||||||
|
|
||||||
// Not overlap
|
// Not overlap
|
||||||
if (forbidConcat) {
|
if (forbidConcat) {
|
||||||
chunks.push(item.text);
|
chunks.push(`${parentTitle}${item.title}${item.text}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ export type FastGPTFeConfigsType = {
|
|||||||
github?: string;
|
github?: string;
|
||||||
google?: string;
|
google?: string;
|
||||||
wechat?: string;
|
wechat?: string;
|
||||||
microsoft?: string;
|
|
||||||
};
|
};
|
||||||
limit?: {
|
limit?: {
|
||||||
exportDatasetLimitMinutes?: number;
|
exportDatasetLimitMinutes?: number;
|
||||||
|
|||||||
1
packages/global/core/ai/type.d.ts
vendored
@@ -49,7 +49,6 @@ export type ChatCompletionMessageParam = (
|
|||||||
| CustomChatCompletionAssistantMessageParam
|
| CustomChatCompletionAssistantMessageParam
|
||||||
) & {
|
) & {
|
||||||
dataId?: string;
|
dataId?: string;
|
||||||
hideInUI?: boolean;
|
|
||||||
};
|
};
|
||||||
export type SdkChatCompletionMessageParam = SdkChatCompletionMessageParam;
|
export type SdkChatCompletionMessageParam = SdkChatCompletionMessageParam;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
import {
|
import { AppTTSConfigType, AppFileSelectConfigType, AppWhisperConfigType } from './type';
|
||||||
AppTTSConfigType,
|
|
||||||
AppFileSelectConfigType,
|
|
||||||
AppWhisperConfigType,
|
|
||||||
AppAutoExecuteConfigType
|
|
||||||
} from './type';
|
|
||||||
|
|
||||||
export enum AppTypeEnum {
|
export enum AppTypeEnum {
|
||||||
folder = 'folder',
|
folder = 'folder',
|
||||||
@@ -17,11 +12,6 @@ export const AppFolderTypeList = [AppTypeEnum.folder, AppTypeEnum.httpPlugin];
|
|||||||
|
|
||||||
export const defaultTTSConfig: AppTTSConfigType = { type: 'web' };
|
export const defaultTTSConfig: AppTTSConfigType = { type: 'web' };
|
||||||
|
|
||||||
export const defaultAutoExecuteConfig: AppAutoExecuteConfigType = {
|
|
||||||
open: false,
|
|
||||||
defaultPrompt: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
export const defaultWhisperConfig: AppWhisperConfigType = {
|
export const defaultWhisperConfig: AppWhisperConfigType = {
|
||||||
open: false,
|
open: false,
|
||||||
autoSend: false,
|
autoSend: false,
|
||||||
|
|||||||
6
packages/global/core/app/type.d.ts
vendored
@@ -96,7 +96,6 @@ export type AppSimpleEditFormType = {
|
|||||||
export type AppChatConfigType = {
|
export type AppChatConfigType = {
|
||||||
welcomeText?: string;
|
welcomeText?: string;
|
||||||
variables?: VariableItemType[];
|
variables?: VariableItemType[];
|
||||||
autoExecute?: AppAutoExecuteConfigType;
|
|
||||||
questionGuide?: boolean;
|
questionGuide?: boolean;
|
||||||
ttsConfig?: AppTTSConfigType;
|
ttsConfig?: AppTTSConfigType;
|
||||||
whisperConfig?: AppWhisperConfigType;
|
whisperConfig?: AppWhisperConfigType;
|
||||||
@@ -159,11 +158,6 @@ export type AppScheduledTriggerConfigType = {
|
|||||||
timezone: string;
|
timezone: string;
|
||||||
defaultPrompt: string;
|
defaultPrompt: string;
|
||||||
};
|
};
|
||||||
// auto execute
|
|
||||||
export type AppAutoExecuteConfigType = {
|
|
||||||
open: boolean;
|
|
||||||
defaultPrompt: string;
|
|
||||||
};
|
|
||||||
// File
|
// File
|
||||||
export type AppFileSelectConfigType = {
|
export type AppFileSelectConfigType = {
|
||||||
canSelectFile: boolean;
|
canSelectFile: boolean;
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ export const chats2GPTMessages = ({
|
|||||||
|
|
||||||
results.push({
|
results.push({
|
||||||
dataId,
|
dataId,
|
||||||
hideInUI: item.hideInUI,
|
|
||||||
role: ChatCompletionRequestMessageRoleEnum.User,
|
role: ChatCompletionRequestMessageRoleEnum.User,
|
||||||
content: simpleUserContentPart(value)
|
content: simpleUserContentPart(value)
|
||||||
});
|
});
|
||||||
@@ -319,7 +318,6 @@ export const GPTMessages2Chats = (
|
|||||||
return {
|
return {
|
||||||
dataId: item.dataId,
|
dataId: item.dataId,
|
||||||
obj,
|
obj,
|
||||||
hideInUI: item.hideInUI,
|
|
||||||
value
|
value
|
||||||
} as ChatItemType;
|
} as ChatItemType;
|
||||||
})
|
})
|
||||||
|
|||||||
1
packages/global/core/chat/type.d.ts
vendored
@@ -56,7 +56,6 @@ export type UserChatItemValueItemType = {
|
|||||||
export type UserChatItemType = {
|
export type UserChatItemType = {
|
||||||
obj: ChatRoleEnum.Human;
|
obj: ChatRoleEnum.Human;
|
||||||
value: UserChatItemValueItemType[];
|
value: UserChatItemValueItemType[];
|
||||||
hideInUI?: boolean;
|
|
||||||
};
|
};
|
||||||
export type SystemChatItemValueItemType = {
|
export type SystemChatItemValueItemType = {
|
||||||
type: ChatItemValueTypeEnum.text;
|
type: ChatItemValueTypeEnum.text;
|
||||||
|
|||||||
@@ -78,15 +78,11 @@ export const getHistoryPreview = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const filterPublicNodeResponseData = ({
|
export const filterPublicNodeResponseData = ({
|
||||||
flowResponses = [],
|
flowResponses = []
|
||||||
responseDetail = false
|
|
||||||
}: {
|
}: {
|
||||||
flowResponses?: ChatHistoryItemResType[];
|
flowResponses?: ChatHistoryItemResType[];
|
||||||
responseDetail?: boolean;
|
|
||||||
}) => {
|
}) => {
|
||||||
const filedList = responseDetail
|
const filedList = ['quoteList', 'moduleType', 'pluginOutput', 'runningTime'];
|
||||||
? ['quoteList', 'moduleType', 'pluginOutput', 'runningTime']
|
|
||||||
: ['moduleType', 'pluginOutput', 'runningTime'];
|
|
||||||
const filterModuleTypeList: any[] = [
|
const filterModuleTypeList: any[] = [
|
||||||
FlowNodeTypeEnum.pluginModule,
|
FlowNodeTypeEnum.pluginModule,
|
||||||
FlowNodeTypeEnum.datasetSearchNode,
|
FlowNodeTypeEnum.datasetSearchNode,
|
||||||
@@ -101,7 +97,7 @@ export const filterPublicNodeResponseData = ({
|
|||||||
for (let key in item) {
|
for (let key in item) {
|
||||||
if (key === 'toolDetail' || key === 'pluginDetail') {
|
if (key === 'toolDetail' || key === 'pluginDetail') {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
obj[key] = filterPublicNodeResponseData({ flowResponses: item[key], responseDetail });
|
obj[key] = filterPublicNodeResponseData({ flowResponses: item[key] });
|
||||||
} else if (filedList.includes(key)) {
|
} else if (filedList.includes(key)) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
obj[key] = item[key];
|
obj[key] = item[key];
|
||||||
|
|||||||
3
packages/global/core/dataset/type.d.ts
vendored
@@ -204,8 +204,7 @@ export type DatasetFileSchema = {
|
|||||||
contentType: string;
|
contentType: string;
|
||||||
metadata: {
|
metadata: {
|
||||||
teamId: string;
|
teamId: string;
|
||||||
tmbId?: string;
|
tmbId: string;
|
||||||
uid: string;
|
|
||||||
encoding?: string;
|
encoding?: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -106,7 +106,6 @@ export enum NodeInputKeyEnum {
|
|||||||
variables = 'variables',
|
variables = 'variables',
|
||||||
scheduleTrigger = 'scheduleTrigger',
|
scheduleTrigger = 'scheduleTrigger',
|
||||||
chatInputGuide = 'chatInputGuide',
|
chatInputGuide = 'chatInputGuide',
|
||||||
autoExecute = 'autoExecute',
|
|
||||||
|
|
||||||
// plugin config
|
// plugin config
|
||||||
instruction = 'instruction',
|
instruction = 'instruction',
|
||||||
|
|||||||
@@ -95,10 +95,10 @@ export const DatasetSearchModule: FlowNodeTemplateType = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: NodeInputKeyEnum.collectionFilterMatch,
|
key: NodeInputKeyEnum.collectionFilterMatch,
|
||||||
renderTypeList: [FlowNodeInputTypeEnum.textarea, FlowNodeInputTypeEnum.reference],
|
renderTypeList: [FlowNodeInputTypeEnum.JSONEditor, FlowNodeInputTypeEnum.reference],
|
||||||
label: i18nT('workflow:collection_metadata_filter'),
|
label: i18nT('workflow:collection_metadata_filter'),
|
||||||
|
|
||||||
valueType: WorkflowIOValueTypeEnum.string,
|
valueType: WorkflowIOValueTypeEnum.object,
|
||||||
isPro: true,
|
isPro: true,
|
||||||
description: i18nT('workflow:filter_description')
|
description: i18nT('workflow:filter_description')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,12 +25,10 @@ import type {
|
|||||||
AppWhisperConfigType,
|
AppWhisperConfigType,
|
||||||
AppScheduledTriggerConfigType,
|
AppScheduledTriggerConfigType,
|
||||||
ChatInputGuideConfigType,
|
ChatInputGuideConfigType,
|
||||||
AppChatConfigType,
|
AppChatConfigType
|
||||||
AppAutoExecuteConfigType
|
|
||||||
} from '../app/type';
|
} from '../app/type';
|
||||||
import { EditorVariablePickerType } from '../../../web/components/common/Textarea/PromptEditor/type';
|
import { EditorVariablePickerType } from '../../../web/components/common/Textarea/PromptEditor/type';
|
||||||
import {
|
import {
|
||||||
defaultAutoExecuteConfig,
|
|
||||||
defaultChatInputGuideConfig,
|
defaultChatInputGuideConfig,
|
||||||
defaultTTSConfig,
|
defaultTTSConfig,
|
||||||
defaultWhisperConfig
|
defaultWhisperConfig
|
||||||
@@ -71,37 +69,34 @@ export const getGuideModule = (modules: StoreNodeItemType[]) =>
|
|||||||
);
|
);
|
||||||
export const splitGuideModule = (guideModules?: StoreNodeItemType) => {
|
export const splitGuideModule = (guideModules?: StoreNodeItemType) => {
|
||||||
const welcomeText: string =
|
const welcomeText: string =
|
||||||
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.welcomeText)?.value ?? '';
|
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.welcomeText)?.value || '';
|
||||||
|
|
||||||
const variables: VariableItemType[] =
|
const variables: VariableItemType[] =
|
||||||
guideModules?.inputs.find((item) => item.key === NodeInputKeyEnum.variables)?.value ?? [];
|
guideModules?.inputs.find((item) => item.key === NodeInputKeyEnum.variables)?.value || [];
|
||||||
|
|
||||||
const questionGuide: boolean =
|
const questionGuide: boolean =
|
||||||
!!guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.questionGuide)?.value ??
|
!!guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.questionGuide)?.value ||
|
||||||
false;
|
false;
|
||||||
|
|
||||||
const ttsConfig: AppTTSConfigType =
|
const ttsConfig: AppTTSConfigType =
|
||||||
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.tts)?.value ??
|
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.tts)?.value ||
|
||||||
defaultTTSConfig;
|
defaultTTSConfig;
|
||||||
|
|
||||||
const whisperConfig: AppWhisperConfigType =
|
const whisperConfig: AppWhisperConfigType =
|
||||||
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.whisper)?.value ??
|
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.whisper)?.value ||
|
||||||
defaultWhisperConfig;
|
defaultWhisperConfig;
|
||||||
|
|
||||||
const scheduledTriggerConfig: AppScheduledTriggerConfigType =
|
const scheduledTriggerConfig: AppScheduledTriggerConfigType = guideModules?.inputs?.find(
|
||||||
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.scheduleTrigger)?.value ??
|
(item) => item.key === NodeInputKeyEnum.scheduleTrigger
|
||||||
undefined;
|
)?.value;
|
||||||
|
|
||||||
const chatInputGuide: ChatInputGuideConfigType =
|
const chatInputGuide: ChatInputGuideConfigType =
|
||||||
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.chatInputGuide)?.value ??
|
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.chatInputGuide)?.value ||
|
||||||
defaultChatInputGuideConfig;
|
defaultChatInputGuideConfig;
|
||||||
|
|
||||||
|
// plugin
|
||||||
const instruction: string =
|
const instruction: string =
|
||||||
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.instruction)?.value ?? '';
|
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.instruction)?.value || '';
|
||||||
|
|
||||||
const autoExecute: AppAutoExecuteConfigType =
|
|
||||||
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.autoExecute)?.value ??
|
|
||||||
defaultAutoExecuteConfig;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
welcomeText,
|
welcomeText,
|
||||||
@@ -111,8 +106,7 @@ export const splitGuideModule = (guideModules?: StoreNodeItemType) => {
|
|||||||
whisperConfig,
|
whisperConfig,
|
||||||
scheduledTriggerConfig,
|
scheduledTriggerConfig,
|
||||||
chatInputGuide,
|
chatInputGuide,
|
||||||
instruction,
|
instruction
|
||||||
autoExecute
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -138,8 +132,7 @@ export const getAppChatConfig = ({
|
|||||||
whisperConfig,
|
whisperConfig,
|
||||||
scheduledTriggerConfig,
|
scheduledTriggerConfig,
|
||||||
chatInputGuide,
|
chatInputGuide,
|
||||||
instruction,
|
instruction
|
||||||
autoExecute
|
|
||||||
} = splitGuideModule(systemConfigNode);
|
} = splitGuideModule(systemConfigNode);
|
||||||
|
|
||||||
const config: AppChatConfigType = {
|
const config: AppChatConfigType = {
|
||||||
@@ -149,7 +142,6 @@ export const getAppChatConfig = ({
|
|||||||
scheduledTriggerConfig,
|
scheduledTriggerConfig,
|
||||||
chatInputGuide,
|
chatInputGuide,
|
||||||
instruction,
|
instruction,
|
||||||
autoExecute,
|
|
||||||
...chatConfig,
|
...chatConfig,
|
||||||
variables: storeVariables ?? chatConfig?.variables ?? variables,
|
variables: storeVariables ?? chatConfig?.variables ?? variables,
|
||||||
welcomeText: storeWelcomeText ?? chatConfig?.welcomeText ?? welcomeText
|
welcomeText: storeWelcomeText ?? chatConfig?.welcomeText ?? welcomeText
|
||||||
|
|||||||
@@ -14,6 +14,5 @@ export const userStatusMap = {
|
|||||||
export enum OAuthEnum {
|
export enum OAuthEnum {
|
||||||
github = 'github',
|
github = 'github',
|
||||||
google = 'google',
|
google = 'google',
|
||||||
wechat = 'wechat',
|
wechat = 'wechat'
|
||||||
microsoft = 'microsoft'
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,18 @@ import { cloneDeep } from 'lodash';
|
|||||||
import { WorkerNameEnum, runWorker } from '@fastgpt/service/worker/utils';
|
import { WorkerNameEnum, runWorker } from '@fastgpt/service/worker/utils';
|
||||||
|
|
||||||
// Run in main thread
|
// Run in main thread
|
||||||
const staticPluginList = ['getTime', 'fetchUrl', 'feishu', 'google', 'bing'];
|
const staticPluginList = [
|
||||||
|
'getTime',
|
||||||
|
'fetchUrl',
|
||||||
|
'Doc2X',
|
||||||
|
'Doc2X/URLPDF2text',
|
||||||
|
'Doc2X/URLImg2text',
|
||||||
|
`Doc2X/FilePDF2text`,
|
||||||
|
`Doc2X/FileImg2text`,
|
||||||
|
'feishu',
|
||||||
|
'google',
|
||||||
|
'bing'
|
||||||
|
];
|
||||||
// Run in worker thread (Have npm packages)
|
// Run in worker thread (Have npm packages)
|
||||||
const packagePluginList = [
|
const packagePluginList = [
|
||||||
'mathExprVal',
|
'mathExprVal',
|
||||||
@@ -17,9 +28,7 @@ const packagePluginList = [
|
|||||||
'drawing',
|
'drawing',
|
||||||
'drawing/baseChart',
|
'drawing/baseChart',
|
||||||
'wiki',
|
'wiki',
|
||||||
'databaseConnection',
|
'databaseConnection'
|
||||||
'Doc2X',
|
|
||||||
'Doc2X/PDF2text'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export const list = [...staticPluginList, ...packagePluginList];
|
export const list = [...staticPluginList, ...packagePluginList];
|
||||||
@@ -46,8 +55,6 @@ export const getCommunityPlugins = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getSystemPluginTemplates = () => {
|
export const getSystemPluginTemplates = () => {
|
||||||
if (!global.systemPlugins) return [];
|
|
||||||
|
|
||||||
const oldPlugins = global.communityPlugins ?? [];
|
const oldPlugins = global.communityPlugins ?? [];
|
||||||
return [...oldPlugins, ...cloneDeep(global.systemPlugins)];
|
return [...oldPlugins, ...cloneDeep(global.systemPlugins)];
|
||||||
};
|
};
|
||||||
@@ -89,3 +96,7 @@ export const getCommunityCb = async () => {
|
|||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getSystemPluginCb = async () => {
|
||||||
|
return global.systemPluginCb;
|
||||||
|
};
|
||||||
|
|||||||
172
packages/plugins/src/Doc2X/FileImg2text/index.ts
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
import { delay } from '@fastgpt/global/common/system/utils';
|
||||||
|
import { addLog } from '@fastgpt/service/common/system/log';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
apikey: string;
|
||||||
|
files: Array<string>;
|
||||||
|
img_correction: boolean;
|
||||||
|
formula: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Response = Promise<{
|
||||||
|
result: string;
|
||||||
|
failreason: string;
|
||||||
|
success: boolean;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
const main = async ({ apikey, files, img_correction, formula }: Props): Response => {
|
||||||
|
// Check the apikey
|
||||||
|
if (!apikey) {
|
||||||
|
return {
|
||||||
|
result: '',
|
||||||
|
failreason: `API key is required`,
|
||||||
|
success: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let real_api_key = apikey;
|
||||||
|
if (!apikey.startsWith('sk-')) {
|
||||||
|
const response = await fetch('https://api.doc2x.noedgeai.com/api/token/refresh', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${apikey}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (response.status !== 200) {
|
||||||
|
return {
|
||||||
|
result: '',
|
||||||
|
failreason: `Get token failed: ${await response.text()}`,
|
||||||
|
success: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
real_api_key = data.data.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
let final_result = '';
|
||||||
|
let fail_reason = '';
|
||||||
|
let flag = false;
|
||||||
|
//Process each file one by one
|
||||||
|
for await (const url of files) {
|
||||||
|
// Fetch the image and check its content type
|
||||||
|
const imageResponse = await fetch(url);
|
||||||
|
if (!imageResponse.ok) {
|
||||||
|
fail_reason += `\n---\nFile:${url} \n<Content>\nFailed to fetch image from URL\n</Content>\n`;
|
||||||
|
flag = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = imageResponse.headers.get('content-type');
|
||||||
|
const fileName = url.match(/read\?filename=([^&]+)/)?.[1] || 'unknown.png';
|
||||||
|
if (!contentType || !contentType.startsWith('image/')) {
|
||||||
|
fail_reason += `\n---\nFile:${url} \n<Content>\nThe provided URL does not point to an image: ${contentType}\n</Content>\n`;
|
||||||
|
flag = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await imageResponse.blob();
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', blob, fileName);
|
||||||
|
formData.append('img_correction', img_correction ? '1' : '0');
|
||||||
|
formData.append('equation', formula ? '1' : '0');
|
||||||
|
|
||||||
|
let upload_url = 'https://api.doc2x.noedgeai.com/api/platform/async/img';
|
||||||
|
if (real_api_key.startsWith('sk-')) {
|
||||||
|
upload_url = 'https://api.doc2x.noedgeai.com/api/v1/async/img';
|
||||||
|
}
|
||||||
|
|
||||||
|
let uuid;
|
||||||
|
let upload_flag = true;
|
||||||
|
const uploadAttempts = [1, 2, 3];
|
||||||
|
for await (const attempt of uploadAttempts) {
|
||||||
|
const upload_response = await fetch(upload_url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${real_api_key}`
|
||||||
|
},
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!upload_response.ok) {
|
||||||
|
// Rate limit, wait for 10s and retry at most 3 times
|
||||||
|
if (upload_response.status === 429 && attempt < 3) {
|
||||||
|
await delay(10000);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
fail_reason += `\n---\nFile:${fileName}\n<Content>\nFailed to upload file: ${await upload_response.text()}\n</Content>\n`;
|
||||||
|
flag = true;
|
||||||
|
upload_flag = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!upload_flag) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const upload_data = await upload_response.json();
|
||||||
|
uuid = upload_data.data.uuid;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the result by uuid
|
||||||
|
let result_url = 'https://api.doc2x.noedgeai.com/api/platform/async/status?uuid=' + uuid;
|
||||||
|
if (real_api_key.startsWith('sk-')) {
|
||||||
|
result_url = 'https://api.doc2x.noedgeai.com/api/v1/async/status?uuid=' + uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
let required_flag = true;
|
||||||
|
const maxAttempts = 100;
|
||||||
|
// Wait for the result, at most 100s
|
||||||
|
for await (const _ of Array(maxAttempts).keys()) {
|
||||||
|
const result_response = await fetch(result_url, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${real_api_key}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!result_response.ok) {
|
||||||
|
fail_reason += `\n---\nFile:${fileName}\n<Content>\nFailed to get result: ${await result_response.text()}\n</Content>\n`;
|
||||||
|
flag = true;
|
||||||
|
required_flag = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const result_data = await result_response.json();
|
||||||
|
if (['ready', 'processing'].includes(result_data.data.status)) {
|
||||||
|
await delay(1000);
|
||||||
|
} else if (result_data.data.status === 'pages limit exceeded') {
|
||||||
|
fail_reason += `\n---\nFile:${fileName}\n<Content>\nFailed to get result: pages limit exceeded\n</Content>\n`;
|
||||||
|
flag = true;
|
||||||
|
required_flag = false;
|
||||||
|
break;
|
||||||
|
} else if (result_data.data.status === 'success') {
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
result = result_data.data.result.pages[0].md;
|
||||||
|
result = result.replace(/\\[\(\)]/g, '$').replace(/\\[\[\]]/g, '$$');
|
||||||
|
} catch {
|
||||||
|
// no pages
|
||||||
|
final_result += `\n---\nFile:${fileName}\n<Content>\n \n</Content>\n`;
|
||||||
|
required_flag = false;
|
||||||
|
}
|
||||||
|
final_result += `\n---\nFile:${fileName}\n<Content>\n${result}\n</Content>\n`;
|
||||||
|
required_flag = false;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
fail_reason += `\n---\nFile:${fileName}\n<Content>\nFailed to get result: ${result_data.data.status}\n</Content>\n`;
|
||||||
|
flag = true;
|
||||||
|
required_flag = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (required_flag) {
|
||||||
|
fail_reason += `\n---\nFile:${fileName}\n<Content>\nTimeout waiting for result\n</Content>\n`;
|
||||||
|
flag = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: final_result,
|
||||||
|
failreason: fail_reason,
|
||||||
|
success: !flag
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default main;
|
||||||
500
packages/plugins/src/Doc2X/FileImg2text/template.json
Normal file
@@ -0,0 +1,500 @@
|
|||||||
|
{
|
||||||
|
"author": "Menghuan1918",
|
||||||
|
"version": "488",
|
||||||
|
"name": "Doc2X 图像(文件)识别",
|
||||||
|
"avatar": "plugins/doc2x",
|
||||||
|
"intro": "将上传的图片文件发送至Doc2X进行解析,返回带LaTeX公式的markdown格式的文本",
|
||||||
|
"courseUrl": "https://fael3z0zfze.feishu.cn/wiki/Rkc5witXWiJoi5kORd2cofh6nDg?fromScene=spaceOverview",
|
||||||
|
"showStatus": true,
|
||||||
|
"weight": 10,
|
||||||
|
|
||||||
|
"isTool": true,
|
||||||
|
"templateType": "tools",
|
||||||
|
|
||||||
|
"workflow": {
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"nodeId": "pluginConfig",
|
||||||
|
"name": "common:core.module.template.system_config",
|
||||||
|
"intro": "",
|
||||||
|
"avatar": "core/workflow/template/systemConfig",
|
||||||
|
"flowNodeType": "pluginConfig",
|
||||||
|
"position": {
|
||||||
|
"x": -90.53591960393504,
|
||||||
|
"y": -17.580286776561252
|
||||||
|
},
|
||||||
|
"version": "4811",
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nodeId": "pluginInput",
|
||||||
|
"name": "插件开始",
|
||||||
|
"intro": "可以配置插件需要哪些输入,利用这些输入来运行插件",
|
||||||
|
"avatar": "core/workflow/template/workflowStart",
|
||||||
|
"flowNodeType": "pluginInput",
|
||||||
|
"showStatus": false,
|
||||||
|
"position": {
|
||||||
|
"x": 368.6800424053505,
|
||||||
|
"y": -17.580286776561252
|
||||||
|
},
|
||||||
|
"version": "481",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"renderTypeList": ["input"],
|
||||||
|
"selectedTypeIndex": 0,
|
||||||
|
"valueType": "string",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "apikey",
|
||||||
|
"label": "apikey",
|
||||||
|
"description": "Doc2X的验证密匙,对于个人用户可以从Doc2X官网 - 个人信息 - 身份令牌获得",
|
||||||
|
"required": true,
|
||||||
|
"toolDescription": "",
|
||||||
|
"defaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"renderTypeList": ["reference"],
|
||||||
|
"selectedTypeIndex": 0,
|
||||||
|
"valueType": "arrayString",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "files",
|
||||||
|
"label": "files",
|
||||||
|
"description": "待处理图片文件",
|
||||||
|
"required": true,
|
||||||
|
"toolDescription": "待处理图片文件"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"renderTypeList": ["switch"],
|
||||||
|
"selectedTypeIndex": 0,
|
||||||
|
"valueType": "boolean",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "img_correction",
|
||||||
|
"label": "img_correction",
|
||||||
|
"description": "是否启用图形矫正功能",
|
||||||
|
"required": true,
|
||||||
|
"toolDescription": "",
|
||||||
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"renderTypeList": ["switch"],
|
||||||
|
"selectedTypeIndex": 0,
|
||||||
|
"valueType": "boolean",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "formula",
|
||||||
|
"label": "formula",
|
||||||
|
"description": "是否开启纯公式识别(仅适用于图片内容仅有公式时)",
|
||||||
|
"required": true,
|
||||||
|
"toolDescription": "",
|
||||||
|
"defaultValue": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"id": "apikey",
|
||||||
|
"valueType": "string",
|
||||||
|
"key": "apikey",
|
||||||
|
"label": "apikey",
|
||||||
|
"type": "hidden"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "url",
|
||||||
|
"valueType": "arrayString",
|
||||||
|
"key": "files",
|
||||||
|
"label": "files",
|
||||||
|
"type": "hidden"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "img_correction",
|
||||||
|
"valueType": "boolean",
|
||||||
|
"key": "img_correction",
|
||||||
|
"label": "img_correction",
|
||||||
|
"type": "hidden"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "formula",
|
||||||
|
"valueType": "boolean",
|
||||||
|
"key": "formula",
|
||||||
|
"label": "formula",
|
||||||
|
"type": "hidden"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nodeId": "pluginOutput",
|
||||||
|
"name": "插件输出",
|
||||||
|
"intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
|
||||||
|
"avatar": "core/workflow/template/pluginOutput",
|
||||||
|
"flowNodeType": "pluginOutput",
|
||||||
|
"showStatus": false,
|
||||||
|
"position": {
|
||||||
|
"x": 1796.2235867744578,
|
||||||
|
"y": 6.419713223438748
|
||||||
|
},
|
||||||
|
"version": "481",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"renderTypeList": ["reference"],
|
||||||
|
"valueType": "string",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "result",
|
||||||
|
"label": "result",
|
||||||
|
"description": "处理结果(或者是报错信息)",
|
||||||
|
"value": ["zHG5jJBkXmjB", "xWQuEf50F3mr"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"renderTypeList": ["reference"],
|
||||||
|
"valueType": "string",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "failreason",
|
||||||
|
"label": "failreason",
|
||||||
|
"description": "文件处理失败原因,由文件名以及报错组成,多个文件之间由横线分隔开",
|
||||||
|
"value": ["zHG5jJBkXmjB", "jbv4nVZvmFXm"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"renderTypeList": ["reference"],
|
||||||
|
"valueType": "boolean",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "success",
|
||||||
|
"label": "success",
|
||||||
|
"description": "是否全部文件都处理成功,如有没有处理成功的文件,失败原因将会输出在failreason中",
|
||||||
|
"value": ["zHG5jJBkXmjB", "k46cjNulVk5Y"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nodeId": "zHG5jJBkXmjB",
|
||||||
|
"name": "HTTP 请求",
|
||||||
|
"intro": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)",
|
||||||
|
"avatar": "core/workflow/template/httpRequest",
|
||||||
|
"flowNodeType": "httpRequest468",
|
||||||
|
"showStatus": true,
|
||||||
|
"position": {
|
||||||
|
"x": 1081.967607938733,
|
||||||
|
"y": -426.08028677656125
|
||||||
|
},
|
||||||
|
"version": "481",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"key": "system_addInputParam",
|
||||||
|
"renderTypeList": ["addInputParam"],
|
||||||
|
"valueType": "dynamic",
|
||||||
|
"label": "",
|
||||||
|
"required": false,
|
||||||
|
"description": "common:core.module.input.description.HTTP Dynamic Input",
|
||||||
|
"customInputConfig": {
|
||||||
|
"selectValueTypeList": [
|
||||||
|
"string",
|
||||||
|
"number",
|
||||||
|
"boolean",
|
||||||
|
"object",
|
||||||
|
"arrayString",
|
||||||
|
"arrayNumber",
|
||||||
|
"arrayBoolean",
|
||||||
|
"arrayObject",
|
||||||
|
"arrayAny",
|
||||||
|
"any",
|
||||||
|
"chatHistory",
|
||||||
|
"datasetQuote",
|
||||||
|
"dynamic",
|
||||||
|
"selectApp",
|
||||||
|
"selectDataset"
|
||||||
|
],
|
||||||
|
"showDescription": false,
|
||||||
|
"showDefaultValue": true
|
||||||
|
},
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "system_httpMethod",
|
||||||
|
"renderTypeList": ["custom"],
|
||||||
|
"valueType": "string",
|
||||||
|
"label": "",
|
||||||
|
"value": "POST",
|
||||||
|
"required": true,
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "system_httpTimeout",
|
||||||
|
"renderTypeList": ["custom"],
|
||||||
|
"valueType": "number",
|
||||||
|
"label": "",
|
||||||
|
"value": 30,
|
||||||
|
"min": 5,
|
||||||
|
"max": 600,
|
||||||
|
"required": true,
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "system_httpReqUrl",
|
||||||
|
"renderTypeList": ["hidden"],
|
||||||
|
"valueType": "string",
|
||||||
|
"label": "",
|
||||||
|
"description": "common:core.module.input.description.Http Request Url",
|
||||||
|
"placeholder": "https://api.ai.com/getInventory",
|
||||||
|
"required": false,
|
||||||
|
"value": "Doc2X/FileImg2text",
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "system_httpHeader",
|
||||||
|
"renderTypeList": ["custom"],
|
||||||
|
"valueType": "any",
|
||||||
|
"value": [],
|
||||||
|
"label": "",
|
||||||
|
"description": "common:core.module.input.description.Http Request Header",
|
||||||
|
"placeholder": "common:core.module.input.description.Http Request Header",
|
||||||
|
"required": false,
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "system_httpParams",
|
||||||
|
"renderTypeList": ["hidden"],
|
||||||
|
"valueType": "any",
|
||||||
|
"value": [],
|
||||||
|
"label": "",
|
||||||
|
"required": false,
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "system_httpJsonBody",
|
||||||
|
"renderTypeList": ["hidden"],
|
||||||
|
"valueType": "any",
|
||||||
|
"value": "{\n \"apikey\": \"{{apikey}}\",\n \"files\": {{files}},\n \"img_correction\": {{img_correction}},\n \"formula\": {{formula}}\n}",
|
||||||
|
"label": "",
|
||||||
|
"required": false,
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "system_httpFormBody",
|
||||||
|
"renderTypeList": ["hidden"],
|
||||||
|
"valueType": "any",
|
||||||
|
"value": [],
|
||||||
|
"label": "",
|
||||||
|
"required": false,
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "system_httpContentType",
|
||||||
|
"renderTypeList": ["hidden"],
|
||||||
|
"valueType": "string",
|
||||||
|
"value": "json",
|
||||||
|
"label": "",
|
||||||
|
"required": false,
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"renderTypeList": ["reference"],
|
||||||
|
"valueType": "string",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "apikey",
|
||||||
|
"label": "apikey",
|
||||||
|
"customInputConfig": {
|
||||||
|
"selectValueTypeList": [
|
||||||
|
"string",
|
||||||
|
"number",
|
||||||
|
"boolean",
|
||||||
|
"object",
|
||||||
|
"arrayString",
|
||||||
|
"arrayNumber",
|
||||||
|
"arrayBoolean",
|
||||||
|
"arrayObject",
|
||||||
|
"arrayAny",
|
||||||
|
"any",
|
||||||
|
"chatHistory",
|
||||||
|
"datasetQuote",
|
||||||
|
"dynamic",
|
||||||
|
"selectApp",
|
||||||
|
"selectDataset"
|
||||||
|
],
|
||||||
|
"showDescription": false,
|
||||||
|
"showDefaultValue": true
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"value": ["pluginInput", "apikey"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"renderTypeList": ["reference"],
|
||||||
|
"valueType": "arrayString",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "files",
|
||||||
|
"label": "files",
|
||||||
|
"customInputConfig": {
|
||||||
|
"selectValueTypeList": [
|
||||||
|
"string",
|
||||||
|
"number",
|
||||||
|
"boolean",
|
||||||
|
"object",
|
||||||
|
"arrayString",
|
||||||
|
"arrayNumber",
|
||||||
|
"arrayBoolean",
|
||||||
|
"arrayObject",
|
||||||
|
"arrayAny",
|
||||||
|
"any",
|
||||||
|
"chatHistory",
|
||||||
|
"datasetQuote",
|
||||||
|
"dynamic",
|
||||||
|
"selectApp",
|
||||||
|
"selectDataset"
|
||||||
|
],
|
||||||
|
"showDescription": false,
|
||||||
|
"showDefaultValue": true
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"value": ["pluginInput", "url"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"renderTypeList": ["reference"],
|
||||||
|
"valueType": "boolean",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "img_correction",
|
||||||
|
"label": "img_correction",
|
||||||
|
"customInputConfig": {
|
||||||
|
"selectValueTypeList": [
|
||||||
|
"string",
|
||||||
|
"number",
|
||||||
|
"boolean",
|
||||||
|
"object",
|
||||||
|
"arrayString",
|
||||||
|
"arrayNumber",
|
||||||
|
"arrayBoolean",
|
||||||
|
"arrayObject",
|
||||||
|
"arrayAny",
|
||||||
|
"any",
|
||||||
|
"chatHistory",
|
||||||
|
"datasetQuote",
|
||||||
|
"dynamic",
|
||||||
|
"selectApp",
|
||||||
|
"selectDataset"
|
||||||
|
],
|
||||||
|
"showDescription": false,
|
||||||
|
"showDefaultValue": true
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"value": ["pluginInput", "img_correction"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"renderTypeList": ["reference"],
|
||||||
|
"valueType": "boolean",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "formula",
|
||||||
|
"label": "formula",
|
||||||
|
"customInputConfig": {
|
||||||
|
"selectValueTypeList": [
|
||||||
|
"string",
|
||||||
|
"number",
|
||||||
|
"boolean",
|
||||||
|
"object",
|
||||||
|
"arrayString",
|
||||||
|
"arrayNumber",
|
||||||
|
"arrayBoolean",
|
||||||
|
"arrayObject",
|
||||||
|
"arrayAny",
|
||||||
|
"any",
|
||||||
|
"chatHistory",
|
||||||
|
"datasetQuote",
|
||||||
|
"dynamic",
|
||||||
|
"selectApp",
|
||||||
|
"selectDataset"
|
||||||
|
],
|
||||||
|
"showDescription": false,
|
||||||
|
"showDefaultValue": true
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"value": ["pluginInput", "formula"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"id": "error",
|
||||||
|
"key": "error",
|
||||||
|
"label": "workflow:request_error",
|
||||||
|
"description": "HTTP请求错误信息,成功时返回空",
|
||||||
|
"valueType": "object",
|
||||||
|
"type": "static"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "httpRawResponse",
|
||||||
|
"key": "httpRawResponse",
|
||||||
|
"required": true,
|
||||||
|
"label": "workflow:raw_response",
|
||||||
|
"description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
|
||||||
|
"valueType": "any",
|
||||||
|
"type": "static"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "system_addOutputParam",
|
||||||
|
"key": "system_addOutputParam",
|
||||||
|
"type": "dynamic",
|
||||||
|
"valueType": "dynamic",
|
||||||
|
"label": "",
|
||||||
|
"customFieldConfig": {
|
||||||
|
"selectValueTypeList": [
|
||||||
|
"string",
|
||||||
|
"number",
|
||||||
|
"boolean",
|
||||||
|
"object",
|
||||||
|
"arrayString",
|
||||||
|
"arrayNumber",
|
||||||
|
"arrayBoolean",
|
||||||
|
"arrayObject",
|
||||||
|
"any",
|
||||||
|
"chatHistory",
|
||||||
|
"datasetQuote",
|
||||||
|
"dynamic",
|
||||||
|
"selectApp",
|
||||||
|
"selectDataset"
|
||||||
|
],
|
||||||
|
"showDescription": false,
|
||||||
|
"showDefaultValue": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "xWQuEf50F3mr",
|
||||||
|
"valueType": "string",
|
||||||
|
"type": "dynamic",
|
||||||
|
"key": "result",
|
||||||
|
"label": "result"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "jbv4nVZvmFXm",
|
||||||
|
"valueType": "string",
|
||||||
|
"type": "dynamic",
|
||||||
|
"key": "failreason",
|
||||||
|
"label": "failreason"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "k46cjNulVk5Y",
|
||||||
|
"valueType": "boolean",
|
||||||
|
"type": "dynamic",
|
||||||
|
"key": "success",
|
||||||
|
"label": "success"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"edges": [
|
||||||
|
{
|
||||||
|
"source": "pluginInput",
|
||||||
|
"target": "zHG5jJBkXmjB",
|
||||||
|
"sourceHandle": "pluginInput-source-right",
|
||||||
|
"targetHandle": "zHG5jJBkXmjB-target-left"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "zHG5jJBkXmjB",
|
||||||
|
"target": "pluginOutput",
|
||||||
|
"sourceHandle": "zHG5jJBkXmjB-source-right",
|
||||||
|
"targetHandle": "pluginOutput-target-left"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
165
packages/plugins/src/Doc2X/FilePDF2text/index.ts
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import { delay } from '@fastgpt/global/common/system/utils';
|
||||||
|
import { addLog } from '@fastgpt/service/common/system/log';
|
||||||
|
import { result } from 'lodash';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
apikey: string;
|
||||||
|
files: Array<string>;
|
||||||
|
ocr: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Response type same as HTTP outputs
|
||||||
|
type Response = Promise<{
|
||||||
|
result: string;
|
||||||
|
failreason: string;
|
||||||
|
success: boolean;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
const main = async ({ apikey, files, ocr }: Props): Response => {
|
||||||
|
// Check the apikey
|
||||||
|
if (!apikey) {
|
||||||
|
return {
|
||||||
|
result: '',
|
||||||
|
failreason: `API key is required`,
|
||||||
|
success: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let real_api_key = apikey;
|
||||||
|
if (!apikey.startsWith('sk-')) {
|
||||||
|
const response = await fetch('https://api.doc2x.noedgeai.com/api/token/refresh', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${apikey}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (response.status !== 200) {
|
||||||
|
return {
|
||||||
|
result: '',
|
||||||
|
failreason: `Get token failed: ${await response.text()}`,
|
||||||
|
success: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
real_api_key = data.data.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
let final_result = '';
|
||||||
|
let fail_reason = '';
|
||||||
|
let flag = false;
|
||||||
|
//Process each file one by one
|
||||||
|
for await (const url of files) {
|
||||||
|
//Fetch the pdf and check its contene type
|
||||||
|
const PDFResponse = await fetch(url);
|
||||||
|
if (!PDFResponse.ok) {
|
||||||
|
fail_reason += `\n---\nFile:${url} \n<Content>\nFailed to fetch PDF from URL\n</Content>\n`;
|
||||||
|
flag = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = PDFResponse.headers.get('content-type');
|
||||||
|
const file_name = url.match(/read\?filename=([^&]+)/)?.[1] || 'unknown.pdf';
|
||||||
|
if (!contentType || !contentType.startsWith('application/pdf')) {
|
||||||
|
fail_reason += `\n---\nFile:${file_name}\n<Content>\nThe provided file does not point to a PDF: ${contentType}\n</Content>\n`;
|
||||||
|
flag = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await PDFResponse.blob();
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', blob, file_name);
|
||||||
|
formData.append('ocr', ocr ? '1' : '0');
|
||||||
|
|
||||||
|
let upload_url = 'https://api.doc2x.noedgeai.com/api/platform/async/pdf';
|
||||||
|
if (real_api_key.startsWith('sk-')) {
|
||||||
|
upload_url = 'https://api.doc2x.noedgeai.com/api/v1/async/pdf';
|
||||||
|
}
|
||||||
|
|
||||||
|
let uuid;
|
||||||
|
let upload_flag = true;
|
||||||
|
const uploadAttempts = [1, 2, 3];
|
||||||
|
for await (const attempt of uploadAttempts) {
|
||||||
|
const upload_response = await fetch(upload_url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${real_api_key}`
|
||||||
|
},
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
if (!upload_response.ok) {
|
||||||
|
// Rate limit, wait for 10s and retry at most 3 times
|
||||||
|
if (upload_response.status === 429 && attempt < 3) {
|
||||||
|
await delay(10000);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
fail_reason += `\n---\nFile:${file_name}\n<Content>\nFailed to upload file: ${await upload_response.text()}\n</Content>\n`;
|
||||||
|
flag = true;
|
||||||
|
upload_flag = false;
|
||||||
|
}
|
||||||
|
if (!upload_flag) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const upload_data = await upload_response.json();
|
||||||
|
uuid = upload_data.data.uuid;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the result by uuid
|
||||||
|
let result_url = 'https://api.doc2x.noedgeai.com/api/platform/async/status?uuid=' + uuid;
|
||||||
|
if (real_api_key.startsWith('sk-')) {
|
||||||
|
result_url = 'https://api.doc2x.noedgeai.com/api/v1/async/status?uuid=' + uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
let required_flag = true;
|
||||||
|
let result = '';
|
||||||
|
// Wait for the result, at most 100s
|
||||||
|
const maxAttempts = 100;
|
||||||
|
for await (const _ of Array(maxAttempts).keys()) {
|
||||||
|
const result_response = await fetch(result_url, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${real_api_key}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!result_response.ok) {
|
||||||
|
fail_reason += `\n---\nFile:${file_name}\n<Content>\nFailed to get result: ${await result_response.text()}\n</Content>\n`;
|
||||||
|
flag = true;
|
||||||
|
required_flag = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const result_data = await result_response.json();
|
||||||
|
if (['ready', 'processing'].includes(result_data.data.status)) {
|
||||||
|
await delay(1000);
|
||||||
|
} else if (result_data.data.status === 'pages limit exceeded') {
|
||||||
|
fail_reason += `\n---\nFile:${file_name}\n<Content>\nPages limit exceeded\n</Content>\n`;
|
||||||
|
flag = true;
|
||||||
|
required_flag = false;
|
||||||
|
break;
|
||||||
|
} else if (result_data.data.status === 'success') {
|
||||||
|
result = await Promise.all(
|
||||||
|
result_data.data.result.pages.map((page: { md: any }) => page.md)
|
||||||
|
).then((pages) => pages.join('\n'));
|
||||||
|
result = result.replace(/\\[\(\)]/g, '$').replace(/\\[\[\]]/g, '$$');
|
||||||
|
final_result += `\n---\nFile:${file_name}\n<Content>\n${result}\n</Content>\n`;
|
||||||
|
required_flag = false;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
fail_reason += `\n---\nFile:${file_name}\n<Content>\nFailed to get result: ${result_data.data.status}\n</Content>\n`;
|
||||||
|
flag = true;
|
||||||
|
required_flag = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (required_flag) {
|
||||||
|
fail_reason += `\n---\nFile:${file_name}\n<Content>\nTimeout after 100s for uuid ${uuid}\n</Content>\n`;
|
||||||
|
flag = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: final_result,
|
||||||
|
failreason: fail_reason,
|
||||||
|
success: !flag
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default main;
|
||||||
@@ -1,26 +1,42 @@
|
|||||||
{
|
{
|
||||||
"author": "Menghuan1918",
|
"author": "Menghuan1918",
|
||||||
"version": "488",
|
"version": "488",
|
||||||
"name": "PDF识别",
|
"name": "Doc2X PDF文件(文件)识别",
|
||||||
"avatar": "plugins/doc2x",
|
"avatar": "plugins/doc2x",
|
||||||
"intro": "将PDF文件发送至Doc2X进行解析,返回结构化的LaTeX公式的文本(markdown),支持传入String类型的URL或者流程输出中的文件链接变量",
|
"intro": "将上传的PDF文件发送至Doc2X进行解析,返回带LaTeX公式的markdown格式的文本",
|
||||||
"courseUrl": "https://fael3z0zfze.feishu.cn/wiki/Rkc5witXWiJoi5kORd2cofh6nDg?fromScene=spaceOverview",
|
"courseUrl": "https://fael3z0zfze.feishu.cn/wiki/Rkc5witXWiJoi5kORd2cofh6nDg?fromScene=spaceOverview",
|
||||||
"showStatus": true,
|
"showStatus": true,
|
||||||
"weight": 10,
|
"weight": 10,
|
||||||
|
|
||||||
"isTool": true,
|
"isTool": true,
|
||||||
"templateType": "tools",
|
"templateType": "tools",
|
||||||
|
|
||||||
"workflow": {
|
"workflow": {
|
||||||
"nodes": [
|
"nodes": [
|
||||||
|
{
|
||||||
|
"nodeId": "pluginConfig",
|
||||||
|
"name": "common:core.module.template.system_config",
|
||||||
|
"intro": "",
|
||||||
|
"avatar": "core/workflow/template/systemConfig",
|
||||||
|
"flowNodeType": "pluginConfig",
|
||||||
|
"position": {
|
||||||
|
"x": -30.474351356537454,
|
||||||
|
"y": -101.45216221730038
|
||||||
|
},
|
||||||
|
"version": "4811",
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"nodeId": "pluginInput",
|
"nodeId": "pluginInput",
|
||||||
"name": "自定义插件输入",
|
"name": "插件开始",
|
||||||
"intro": "可以配置插件需要哪些输入,利用这些输入来运行插件",
|
"intro": "可以配置插件需要哪些输入,利用这些输入来运行插件",
|
||||||
"avatar": "core/workflow/template/workflowStart",
|
"avatar": "core/workflow/template/workflowStart",
|
||||||
"flowNodeType": "pluginInput",
|
"flowNodeType": "pluginInput",
|
||||||
"showStatus": false,
|
"showStatus": false,
|
||||||
"position": {
|
"position": {
|
||||||
"x": -137.96875104510553,
|
"x": 407.2817920483865,
|
||||||
"y": -90.9968973555371
|
"y": -101.45216221730038
|
||||||
},
|
},
|
||||||
"version": "481",
|
"version": "481",
|
||||||
"inputs": [
|
"inputs": [
|
||||||
@@ -31,45 +47,33 @@
|
|||||||
"canEdit": true,
|
"canEdit": true,
|
||||||
"key": "apikey",
|
"key": "apikey",
|
||||||
"label": "apikey",
|
"label": "apikey",
|
||||||
"description": "Doc2X的API密匙,可以从Doc2X开放平台获得",
|
"description": "Doc2X的验证密匙,对于个人用户可以从Doc2X官网 - 个人信息 - 身份令牌获得",
|
||||||
"required": true,
|
"required": true,
|
||||||
"defaultValue": "",
|
"toolDescription": "",
|
||||||
"list": []
|
"defaultValue": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"renderTypeList": ["fileSelect"],
|
"renderTypeList": ["reference"],
|
||||||
"selectedTypeIndex": 0,
|
"selectedTypeIndex": 0,
|
||||||
"valueType": "arrayString",
|
"valueType": "arrayString",
|
||||||
"canEdit": true,
|
"canEdit": true,
|
||||||
"key": "files",
|
"key": "files",
|
||||||
"label": "files",
|
"label": "files",
|
||||||
"description": "需要处理的PDF地址",
|
"description": "待处理的PDF文件",
|
||||||
"required": true,
|
"required": true,
|
||||||
"list": [],
|
"toolDescription": "待处理的PDF文件"
|
||||||
"canSelectFile": true,
|
|
||||||
"canSelectImg": false,
|
|
||||||
"maxFiles": 14,
|
|
||||||
"defaultValue": ""
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"renderTypeList": ["switch", "reference"],
|
"renderTypeList": ["switch"],
|
||||||
"selectedTypeIndex": 0,
|
"selectedTypeIndex": 0,
|
||||||
"valueType": "boolean",
|
"valueType": "boolean",
|
||||||
"canEdit": true,
|
"canEdit": true,
|
||||||
"key": "HTMLtable",
|
"key": "ocr",
|
||||||
"label": "HTMLtable",
|
"label": "ocr",
|
||||||
"description": "是否以HTML格式输出表格。如果需要精确地输出表格,请打开此开关以使用HTML格式。关闭后,表格将转换为Markdown形式输出,但这可能会损失一些表格特性,如合并单元格。",
|
"description": "是否开启对PDF文件内图片的OCR识别,建议开启",
|
||||||
"defaultValue": false,
|
"required": true,
|
||||||
"list": [
|
"toolDescription": "",
|
||||||
{
|
"defaultValue": true
|
||||||
"label": "",
|
|
||||||
"value": ""
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"maxFiles": 5,
|
|
||||||
"canSelectFile": true,
|
|
||||||
"canSelectImg": true,
|
|
||||||
"required": true
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outputs": [
|
"outputs": [
|
||||||
@@ -88,24 +92,24 @@
|
|||||||
"type": "hidden"
|
"type": "hidden"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "htmltable",
|
"id": "formula",
|
||||||
"valueType": "boolean",
|
"valueType": "boolean",
|
||||||
"key": "HTMLtable",
|
"key": "ocr",
|
||||||
"label": "HTMLtable",
|
"label": "ocr",
|
||||||
"type": "hidden"
|
"type": "hidden"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"nodeId": "pluginOutput",
|
"nodeId": "pluginOutput",
|
||||||
"name": "自定义插件输出",
|
"name": "插件输出",
|
||||||
"intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
|
"intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
|
||||||
"avatar": "core/workflow/template/pluginOutput",
|
"avatar": "core/workflow/template/pluginOutput",
|
||||||
"flowNodeType": "pluginOutput",
|
"flowNodeType": "pluginOutput",
|
||||||
"showStatus": false,
|
"showStatus": false,
|
||||||
"position": {
|
"position": {
|
||||||
"x": 1505.494975310334,
|
"x": 1842.070888321717,
|
||||||
"y": -4.14668564643415
|
"y": -101.45216221730038
|
||||||
},
|
},
|
||||||
"version": "481",
|
"version": "481",
|
||||||
"inputs": [
|
"inputs": [
|
||||||
@@ -120,13 +124,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"renderTypeList": ["reference"],
|
"renderTypeList": ["reference"],
|
||||||
"valueType": "object",
|
"valueType": "string",
|
||||||
"canEdit": true,
|
"canEdit": true,
|
||||||
"key": "error",
|
"key": "failreason",
|
||||||
"label": "error",
|
"label": "failreason",
|
||||||
"description": "",
|
"description": "文件处理失败原因,由文件名以及报错组成,多个文件之间由横线分隔开",
|
||||||
"value": ["zHG5jJBkXmjB", "httpRawResponse"],
|
"value": ["zHG5jJBkXmjB", "yDxzW5CFalGw"]
|
||||||
"isToolOutput": true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"renderTypeList": ["reference"],
|
"renderTypeList": ["reference"],
|
||||||
@@ -135,8 +138,7 @@
|
|||||||
"key": "success",
|
"key": "success",
|
||||||
"label": "success",
|
"label": "success",
|
||||||
"description": "是否全部文件都处理成功,如有没有处理成功的文件,失败原因将会输出在failreason中",
|
"description": "是否全部文件都处理成功,如有没有处理成功的文件,失败原因将会输出在failreason中",
|
||||||
"value": ["zHG5jJBkXmjB", "m6CJJj7GFud5"],
|
"value": ["zHG5jJBkXmjB", "m6CJJj7GFud5"]
|
||||||
"isToolOutput": false
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outputs": []
|
"outputs": []
|
||||||
@@ -149,8 +151,8 @@
|
|||||||
"flowNodeType": "httpRequest468",
|
"flowNodeType": "httpRequest468",
|
||||||
"showStatus": true,
|
"showStatus": true,
|
||||||
"position": {
|
"position": {
|
||||||
"x": 619.0661933308237,
|
"x": 1077.7986740892777,
|
||||||
"y": -472.91377894611503
|
"y": -496.9521622173004
|
||||||
},
|
},
|
||||||
"version": "481",
|
"version": "481",
|
||||||
"inputs": [
|
"inputs": [
|
||||||
@@ -200,7 +202,7 @@
|
|||||||
"renderTypeList": ["custom"],
|
"renderTypeList": ["custom"],
|
||||||
"valueType": "number",
|
"valueType": "number",
|
||||||
"label": "",
|
"label": "",
|
||||||
"value": 300,
|
"value": 30,
|
||||||
"min": 5,
|
"min": 5,
|
||||||
"max": 600,
|
"max": 600,
|
||||||
"required": true,
|
"required": true,
|
||||||
@@ -215,7 +217,7 @@
|
|||||||
"description": "common:core.module.input.description.Http Request Url",
|
"description": "common:core.module.input.description.Http Request Url",
|
||||||
"placeholder": "https://api.ai.com/getInventory",
|
"placeholder": "https://api.ai.com/getInventory",
|
||||||
"required": false,
|
"required": false,
|
||||||
"value": "Doc2X/PDF2text",
|
"value": "Doc2X/FilePDF2text",
|
||||||
"debugLabel": "",
|
"debugLabel": "",
|
||||||
"toolDescription": ""
|
"toolDescription": ""
|
||||||
},
|
},
|
||||||
@@ -245,7 +247,7 @@
|
|||||||
"key": "system_httpJsonBody",
|
"key": "system_httpJsonBody",
|
||||||
"renderTypeList": ["hidden"],
|
"renderTypeList": ["hidden"],
|
||||||
"valueType": "any",
|
"valueType": "any",
|
||||||
"value": "{\n \"apikey\": \"{{apikey}}\",\n \"HTMLtable\": {{HTMLtable}},\n \"files\": {{files}}\n}",
|
"value": "{\n \"apikey\": \"{{apikey}}\",\n \"files\": {{files}},\n \"ocr\": {{ocr}}\n}",
|
||||||
"label": "",
|
"label": "",
|
||||||
"required": false,
|
"required": false,
|
||||||
"debugLabel": "",
|
"debugLabel": "",
|
||||||
@@ -329,14 +331,14 @@
|
|||||||
"showDefaultValue": true
|
"showDefaultValue": true
|
||||||
},
|
},
|
||||||
"required": true,
|
"required": true,
|
||||||
"value": [["pluginInput", "url"]]
|
"value": ["pluginInput", "url"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"renderTypeList": ["reference"],
|
"renderTypeList": ["reference"],
|
||||||
"valueType": "boolean",
|
"valueType": "boolean",
|
||||||
"canEdit": true,
|
"canEdit": true,
|
||||||
"key": "HTMLtable",
|
"key": "ocr",
|
||||||
"label": "HTMLtable",
|
"label": "ocr",
|
||||||
"customInputConfig": {
|
"customInputConfig": {
|
||||||
"selectValueTypeList": [
|
"selectValueTypeList": [
|
||||||
"string",
|
"string",
|
||||||
@@ -359,7 +361,7 @@
|
|||||||
"showDefaultValue": true
|
"showDefaultValue": true
|
||||||
},
|
},
|
||||||
"required": true,
|
"required": true,
|
||||||
"value": ["pluginInput", "htmltable"]
|
"value": ["pluginInput", "formula"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outputs": [
|
"outputs": [
|
||||||
@@ -420,42 +422,30 @@
|
|||||||
"type": "dynamic",
|
"type": "dynamic",
|
||||||
"key": "success",
|
"key": "success",
|
||||||
"label": "success"
|
"label": "success"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "yDxzW5CFalGw",
|
||||||
|
"valueType": "string",
|
||||||
|
"type": "dynamic",
|
||||||
|
"key": "failreason",
|
||||||
|
"label": "failreason"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"edges": [
|
"edges": [
|
||||||
{
|
|
||||||
"source": "zHG5jJBkXmjB",
|
|
||||||
"target": "pluginOutput",
|
|
||||||
"sourceHandle": "zHG5jJBkXmjB-source-right",
|
|
||||||
"targetHandle": "pluginOutput-target-left"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"source": "pluginInput",
|
"source": "pluginInput",
|
||||||
"target": "zHG5jJBkXmjB",
|
"target": "zHG5jJBkXmjB",
|
||||||
"sourceHandle": "pluginInput-source-right",
|
"sourceHandle": "pluginInput-source-right",
|
||||||
"targetHandle": "zHG5jJBkXmjB-target-left"
|
"targetHandle": "zHG5jJBkXmjB-target-left"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "zHG5jJBkXmjB",
|
||||||
|
"target": "pluginOutput",
|
||||||
|
"sourceHandle": "zHG5jJBkXmjB-source-right",
|
||||||
|
"targetHandle": "pluginOutput-target-left"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"chatConfig": {
|
|
||||||
"questionGuide": false,
|
|
||||||
"ttsConfig": {
|
|
||||||
"type": "web"
|
|
||||||
},
|
|
||||||
"whisperConfig": {
|
|
||||||
"open": false,
|
|
||||||
"autoSend": false,
|
|
||||||
"autoTTSResponse": false
|
|
||||||
},
|
|
||||||
"chatInputGuide": {
|
|
||||||
"open": false,
|
|
||||||
"textList": [],
|
|
||||||
"customUrl": ""
|
|
||||||
},
|
|
||||||
"instruction": "",
|
|
||||||
"variables": [],
|
|
||||||
"welcomeText": ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,256 +0,0 @@
|
|||||||
import { delay } from '@fastgpt/global/common/system/utils';
|
|
||||||
import axios from 'axios';
|
|
||||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
apikey: string;
|
|
||||||
HTMLtable: boolean;
|
|
||||||
files: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
// Response type same as HTTP outputs
|
|
||||||
type Response = Promise<{
|
|
||||||
result: string;
|
|
||||||
success: boolean;
|
|
||||||
error?: Record<string, any>;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
function processContent(content: string, HTMLtable: boolean): string {
|
|
||||||
if (HTMLtable) {
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
return content.replace(/<table>[\s\S]*?<\/table>/g, (htmlTable) => {
|
|
||||||
try {
|
|
||||||
// Clean up whitespace and newlines
|
|
||||||
const cleanHtml = htmlTable.replace(/\n\s*/g, '');
|
|
||||||
const rows = cleanHtml.match(/<tr>(.*?)<\/tr>/g);
|
|
||||||
if (!rows) return htmlTable;
|
|
||||||
|
|
||||||
// Parse table data
|
|
||||||
let tableData: string[][] = [];
|
|
||||||
let maxColumns = 0;
|
|
||||||
|
|
||||||
// Try to convert to markdown table
|
|
||||||
try {
|
|
||||||
rows.forEach((row, rowIndex) => {
|
|
||||||
if (!tableData[rowIndex]) {
|
|
||||||
tableData[rowIndex] = [];
|
|
||||||
}
|
|
||||||
let colIndex = 0;
|
|
||||||
const cells = row.match(/<td.*?>(.*?)<\/td>/g) || [];
|
|
||||||
|
|
||||||
cells.forEach((cell) => {
|
|
||||||
while (tableData[rowIndex][colIndex]) {
|
|
||||||
colIndex++;
|
|
||||||
}
|
|
||||||
const colspan = parseInt(cell.match(/colspan="(\d+)"/)?.[1] || '1');
|
|
||||||
const rowspan = parseInt(cell.match(/rowspan="(\d+)"/)?.[1] || '1');
|
|
||||||
const content = cell.replace(/<td.*?>|<\/td>/g, '').trim();
|
|
||||||
|
|
||||||
for (let i = 0; i < rowspan; i++) {
|
|
||||||
for (let j = 0; j < colspan; j++) {
|
|
||||||
if (!tableData[rowIndex + i]) {
|
|
||||||
tableData[rowIndex + i] = [];
|
|
||||||
}
|
|
||||||
tableData[rowIndex + i][colIndex + j] = i === 0 && j === 0 ? content : '^^';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
colIndex += colspan;
|
|
||||||
maxColumns = Math.max(maxColumns, colIndex);
|
|
||||||
});
|
|
||||||
|
|
||||||
for (let i = 0; i < maxColumns; i++) {
|
|
||||||
if (!tableData[rowIndex][i]) {
|
|
||||||
tableData[rowIndex][i] = ' ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const chunks: string[] = [];
|
|
||||||
|
|
||||||
const headerCells = tableData[0]
|
|
||||||
.slice(0, maxColumns)
|
|
||||||
.map((cell) => (cell === '^^' ? ' ' : cell || ' '));
|
|
||||||
const headerRow = '| ' + headerCells.join(' | ') + ' |';
|
|
||||||
chunks.push(headerRow);
|
|
||||||
|
|
||||||
const separator = '| ' + Array(headerCells.length).fill('---').join(' | ') + ' |';
|
|
||||||
chunks.push(separator);
|
|
||||||
|
|
||||||
tableData.slice(1).forEach((row) => {
|
|
||||||
const paddedRow = row
|
|
||||||
.slice(0, maxColumns)
|
|
||||||
.map((cell) => (cell === '^^' ? ' ' : cell || ' '));
|
|
||||||
while (paddedRow.length < maxColumns) {
|
|
||||||
paddedRow.push(' ');
|
|
||||||
}
|
|
||||||
chunks.push('| ' + paddedRow.join(' | ') + ' |');
|
|
||||||
});
|
|
||||||
|
|
||||||
return chunks.join('\n');
|
|
||||||
} catch (error) {
|
|
||||||
return htmlTable;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return htmlTable;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const main = async ({ apikey, files, HTMLtable }: Props): Response => {
|
|
||||||
// Check the apikey
|
|
||||||
if (!apikey) {
|
|
||||||
return Promise.reject(`API key is required`);
|
|
||||||
}
|
|
||||||
const successResult = [];
|
|
||||||
const failedResult = [];
|
|
||||||
|
|
||||||
const axiosInstance = axios.create({
|
|
||||||
timeout: 30000 // 30 seconds timeout
|
|
||||||
});
|
|
||||||
|
|
||||||
//Process each file one by one
|
|
||||||
for await (const url of files) {
|
|
||||||
try {
|
|
||||||
//Fetch the pdf and check its content type
|
|
||||||
const PDFResponse = await axios
|
|
||||||
.get(url, {
|
|
||||||
responseType: 'arraybuffer',
|
|
||||||
proxy: false,
|
|
||||||
timeout: 20000
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
throw new Error(`[Fetch PDF Error] Failed to fetch PDF: ${getErrText(error)}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (PDFResponse.status !== 200) {
|
|
||||||
throw new Error(
|
|
||||||
`[Fetch PDF Error] Failed with status ${PDFResponse.status}: ${PDFResponse.data}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const contentType = PDFResponse.headers['content-type'];
|
|
||||||
const file_name = url.match(/read\/([^?]+)/)?.[1] || 'unknown.pdf';
|
|
||||||
if (!contentType || !contentType.startsWith('application/pdf')) {
|
|
||||||
throw new Error(`The provided file does not point to a PDF: ${contentType}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const blob = new Blob([PDFResponse.data], { type: 'application/pdf' });
|
|
||||||
// Get pre-upload URL first
|
|
||||||
const preupload_response = await axiosInstance
|
|
||||||
.post('https://v2.doc2x.noedgeai.com/api/v2/parse/preupload', null, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${apikey}`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
throw new Error(`[Pre-upload Error] Failed to get pre-upload URL: ${getErrText(error)}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (preupload_response.status !== 200) {
|
|
||||||
throw new Error(`Failed to get pre-upload URL: ${preupload_response.data}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const preupload_data = preupload_response.data;
|
|
||||||
if (preupload_data.code !== 'success') {
|
|
||||||
throw new Error(`Failed to get pre-upload URL: ${JSON.stringify(preupload_data)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const upload_url = preupload_data.data.url;
|
|
||||||
const uid = preupload_data.data.uid;
|
|
||||||
// Upload file to pre-signed URL with binary stream
|
|
||||||
|
|
||||||
const response = await axiosInstance
|
|
||||||
.put(upload_url, blob, {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/pdf'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
throw new Error(`[Upload Error] Failed to upload file: ${getErrText(error)}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.status !== 200) {
|
|
||||||
throw new Error(`Upload failed with status ${response.status}: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the result by uid
|
|
||||||
|
|
||||||
// Wait for the result
|
|
||||||
const checkResult = async (retry = 20) => {
|
|
||||||
if (retry <= 0)
|
|
||||||
return Promise.reject(
|
|
||||||
`File:${file_name}\n<Content>\n[Parse Timeout Error] Failed to get result (uid: ${uid}): Process timeout\n</Content>`
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result_response = await axiosInstance
|
|
||||||
.get(`https://v2.doc2x.noedgeai.com/api/v2/parse/status?uid=${uid}`, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${apikey}`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
throw new Error(
|
|
||||||
`[Parse Status Error] Failed to get parse status: ${getErrText(error)}`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const result_data = result_response.data;
|
|
||||||
if (!['ok', 'success'].includes(result_data.code)) {
|
|
||||||
return Promise.reject(
|
|
||||||
`File:${file_name}\n<Content>\nFailed to get result (uid: ${uid}): ${JSON.stringify(result_data)}\n</Content>`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (['ready', 'processing'].includes(result_data.data.status)) {
|
|
||||||
await delay(4000);
|
|
||||||
return checkResult(retry - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result_data.data.status === 'success') {
|
|
||||||
const result = processContent(
|
|
||||||
await Promise.all(
|
|
||||||
result_data.data.result.pages.map((page: { md: any }) => page.md)
|
|
||||||
).then((pages) => pages.join('\n')),
|
|
||||||
HTMLtable
|
|
||||||
)
|
|
||||||
// Do some post-processing
|
|
||||||
.replace(/\\[\(\)]/g, '$')
|
|
||||||
.replace(/\\[\[\]]/g, '$$')
|
|
||||||
.replace(/<img\s+src="([^"]+)"(?:\s*\?[^>]*)?(?:\s*\/>|>)/g, '')
|
|
||||||
.replace(/<!-- Media -->/g, '')
|
|
||||||
.replace(/<!-- Footnote -->/g, '')
|
|
||||||
.replace(/\$(.+?)\s+\\tag\{(.+?)\}\$/g, '$$$1 \\qquad \\qquad ($2)$$')
|
|
||||||
.replace(/\\text\{([^}]*?)(\b\w+)_(\w+\b)([^}]*?)\}/g, '\\text{$1$2\\_$3$4}');
|
|
||||||
|
|
||||||
return `File:${file_name}\n<Content>\n${result}\n</Content>`;
|
|
||||||
}
|
|
||||||
return checkResult(retry - 1);
|
|
||||||
} catch (error) {
|
|
||||||
if (retry > 1) {
|
|
||||||
await delay(100);
|
|
||||||
return checkResult(retry - 1);
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await checkResult();
|
|
||||||
successResult.push(result);
|
|
||||||
} catch (error) {
|
|
||||||
failedResult.push(
|
|
||||||
`File:${url} \n<Content>\nFailed to fetch file from URL: ${getErrText(error)}\n</Content>`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
result: successResult.join('\n******\n'),
|
|
||||||
error: {
|
|
||||||
message: failedResult.join('\n******\n')
|
|
||||||
},
|
|
||||||
success: failedResult.length === 0
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default main;
|
|
||||||
166
packages/plugins/src/Doc2X/URLImg2text/index.ts
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
import { delay } from '@fastgpt/global/common/system/utils';
|
||||||
|
import { addLog } from '@fastgpt/service/common/system/log';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
apikey: string;
|
||||||
|
url: string;
|
||||||
|
img_correction: boolean;
|
||||||
|
formula: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Response = Promise<{
|
||||||
|
result: string;
|
||||||
|
success: boolean;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
const main = async ({ apikey, url, img_correction, formula }: Props): Response => {
|
||||||
|
// Check the apikey
|
||||||
|
if (!apikey) {
|
||||||
|
return {
|
||||||
|
result: `API key is required`,
|
||||||
|
success: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let real_api_key = apikey;
|
||||||
|
if (!apikey.startsWith('sk-')) {
|
||||||
|
const response = await fetch('https://api.doc2x.noedgeai.com/api/token/refresh', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${apikey}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (response.status !== 200) {
|
||||||
|
return {
|
||||||
|
result: `Get token failed: ${await response.text()}`,
|
||||||
|
success: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
real_api_key = data.data.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
let imageResponse;
|
||||||
|
// Fetch the image and check its content type
|
||||||
|
try {
|
||||||
|
imageResponse = await fetch(url);
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
result: `Failed to fetch image from URL: ${url} with error: ${e}`,
|
||||||
|
success: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!imageResponse.ok) {
|
||||||
|
return {
|
||||||
|
result: `Failed to fetch image from URL: ${url}`,
|
||||||
|
success: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = imageResponse.headers.get('content-type');
|
||||||
|
if (!contentType || !contentType.startsWith('image/')) {
|
||||||
|
return {
|
||||||
|
result: `The provided URL does not point to an image: ${contentType}`,
|
||||||
|
success: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await imageResponse.blob();
|
||||||
|
const formData = new FormData();
|
||||||
|
const fileName = url.split('/').pop()?.split('?')[0] || 'image';
|
||||||
|
formData.append('file', blob, fileName);
|
||||||
|
formData.append('img_correction', img_correction ? '1' : '0');
|
||||||
|
formData.append('equation', formula ? '1' : '0');
|
||||||
|
|
||||||
|
let upload_url = 'https://api.doc2x.noedgeai.com/api/platform/async/img';
|
||||||
|
if (real_api_key.startsWith('sk-')) {
|
||||||
|
upload_url = 'https://api.doc2x.noedgeai.com/api/v1/async/img';
|
||||||
|
}
|
||||||
|
|
||||||
|
let uuid;
|
||||||
|
const uploadAttempts = [1, 2, 3];
|
||||||
|
for await (const attempt of uploadAttempts) {
|
||||||
|
const upload_response = await fetch(upload_url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${real_api_key}`
|
||||||
|
},
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!upload_response.ok) {
|
||||||
|
// Rate limit, wait for 10s and retry at most 3 times
|
||||||
|
if (upload_response.status === 429 && attempt < 3) {
|
||||||
|
await delay(10000);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
result: `Failed to upload image: ${await upload_response.text()}`,
|
||||||
|
success: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const upload_data = await upload_response.json();
|
||||||
|
uuid = upload_data.data.uuid;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the result by uuid
|
||||||
|
let result_url = 'https://api.doc2x.noedgeai.com/api/platform/async/status?uuid=' + uuid;
|
||||||
|
if (real_api_key.startsWith('sk-')) {
|
||||||
|
result_url = 'https://api.doc2x.noedgeai.com/api/v1/async/status?uuid=' + uuid;
|
||||||
|
}
|
||||||
|
const maxAttempts = 100;
|
||||||
|
// Wait for the result, at most 100s
|
||||||
|
for await (const _ of Array(maxAttempts).keys()) {
|
||||||
|
const result_response = await fetch(result_url, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${real_api_key}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!result_response.ok) {
|
||||||
|
return {
|
||||||
|
result: `Failed to get result: ${await result_response.text()}`,
|
||||||
|
success: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const result_data = await result_response.json();
|
||||||
|
if (['ready', 'processing'].includes(result_data.data.status)) {
|
||||||
|
await delay(1000);
|
||||||
|
} else if (result_data.data.status === 'pages limit exceeded') {
|
||||||
|
return {
|
||||||
|
result: 'Doc2X Pages limit exceeded',
|
||||||
|
success: false
|
||||||
|
};
|
||||||
|
} else if (result_data.data.status === 'success') {
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
result = result_data.data.result.pages[0].md;
|
||||||
|
result = result.replace(/\\[\(\)]/g, '$').replace(/\\[\[\]]/g, '$$');
|
||||||
|
} catch {
|
||||||
|
// no pages
|
||||||
|
return {
|
||||||
|
result: '',
|
||||||
|
success: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
result: result,
|
||||||
|
success: true
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
result: `Failed to get result: ${await result_data.text()}`,
|
||||||
|
success: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: 'Timeout waiting for result',
|
||||||
|
success: false
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default main;
|
||||||
484
packages/plugins/src/Doc2X/URLImg2text/template.json
Normal file
@@ -0,0 +1,484 @@
|
|||||||
|
{
|
||||||
|
"author": "Menghuan1918",
|
||||||
|
"version": "488",
|
||||||
|
"name": "Doc2X 图像(URL)识别",
|
||||||
|
"avatar": "plugins/doc2x",
|
||||||
|
"intro": "从URL下载图片并发送至Doc2X进行解析,返回带LaTeX公式的markdown格式的文本",
|
||||||
|
"courseUrl": "https://fael3z0zfze.feishu.cn/wiki/Rkc5witXWiJoi5kORd2cofh6nDg?fromScene=spaceOverview",
|
||||||
|
"showStatus": true,
|
||||||
|
"weight": 10,
|
||||||
|
|
||||||
|
"isTool": true,
|
||||||
|
"templateType": "tools",
|
||||||
|
|
||||||
|
"workflow": {
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"nodeId": "pluginInput",
|
||||||
|
"name": "插件开始",
|
||||||
|
"intro": "可以配置插件需要哪些输入,利用这些输入来运行插件",
|
||||||
|
"avatar": "core/workflow/template/workflowStart",
|
||||||
|
"flowNodeType": "pluginInput",
|
||||||
|
"showStatus": false,
|
||||||
|
"position": {
|
||||||
|
"x": 353.91678143999377,
|
||||||
|
"y": -75.09744210499466
|
||||||
|
},
|
||||||
|
"version": "481",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"renderTypeList": ["input"],
|
||||||
|
"selectedTypeIndex": 0,
|
||||||
|
"valueType": "string",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "apikey",
|
||||||
|
"label": "apikey",
|
||||||
|
"description": "Doc2X的验证密匙,对于个人用户可以从Doc2X官网 - 个人信息 - 身份令牌获得",
|
||||||
|
"required": true,
|
||||||
|
"toolDescription": "",
|
||||||
|
"defaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"renderTypeList": ["reference"],
|
||||||
|
"selectedTypeIndex": 0,
|
||||||
|
"valueType": "string",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "url",
|
||||||
|
"label": "url",
|
||||||
|
"description": "待处理图片的URL",
|
||||||
|
"required": true,
|
||||||
|
"toolDescription": "待处理图片的URL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"renderTypeList": ["switch"],
|
||||||
|
"selectedTypeIndex": 0,
|
||||||
|
"valueType": "boolean",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "img_correction",
|
||||||
|
"label": "img_correction",
|
||||||
|
"description": "是否启用图形矫正功能",
|
||||||
|
"required": true,
|
||||||
|
"toolDescription": "",
|
||||||
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"renderTypeList": ["switch"],
|
||||||
|
"selectedTypeIndex": 0,
|
||||||
|
"valueType": "boolean",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "formula",
|
||||||
|
"label": "formula",
|
||||||
|
"description": "是否开启纯公式识别(仅适用于图片内容仅有公式时)",
|
||||||
|
"required": true,
|
||||||
|
"toolDescription": "",
|
||||||
|
"defaultValue": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"id": "apikey",
|
||||||
|
"valueType": "string",
|
||||||
|
"key": "apikey",
|
||||||
|
"label": "apikey",
|
||||||
|
"type": "hidden"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "url",
|
||||||
|
"valueType": "string",
|
||||||
|
"key": "url",
|
||||||
|
"label": "url",
|
||||||
|
"type": "hidden"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "img_correction",
|
||||||
|
"valueType": "boolean",
|
||||||
|
"key": "img_correction",
|
||||||
|
"label": "img_correction",
|
||||||
|
"type": "hidden"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "formula",
|
||||||
|
"valueType": "boolean",
|
||||||
|
"key": "formula",
|
||||||
|
"label": "formula",
|
||||||
|
"type": "hidden"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nodeId": "pluginOutput",
|
||||||
|
"name": "插件输出",
|
||||||
|
"intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
|
||||||
|
"avatar": "core/workflow/template/pluginOutput",
|
||||||
|
"flowNodeType": "pluginOutput",
|
||||||
|
"showStatus": false,
|
||||||
|
"position": {
|
||||||
|
"x": 1703.581616889916,
|
||||||
|
"y": -14.097442104994656
|
||||||
|
},
|
||||||
|
"version": "481",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"renderTypeList": ["reference"],
|
||||||
|
"valueType": "string",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "result",
|
||||||
|
"label": "result",
|
||||||
|
"description": "处理结果(或者是报错信息)",
|
||||||
|
"value": ["zHG5jJBkXmjB", "xWQuEf50F3mr"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"renderTypeList": ["reference"],
|
||||||
|
"valueType": "boolean",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "success",
|
||||||
|
"label": "success",
|
||||||
|
"description": "是否处理成功",
|
||||||
|
"value": ["zHG5jJBkXmjB", "m6CJJj7GFud5"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nodeId": "zHG5jJBkXmjB",
|
||||||
|
"name": "HTTP 请求",
|
||||||
|
"intro": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)",
|
||||||
|
"avatar": "core/workflow/template/httpRequest",
|
||||||
|
"flowNodeType": "httpRequest468",
|
||||||
|
"showStatus": true,
|
||||||
|
"position": {
|
||||||
|
"x": 1000.6685388413375,
|
||||||
|
"y": -457.0974421049947
|
||||||
|
},
|
||||||
|
"version": "481",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"key": "system_addInputParam",
|
||||||
|
"renderTypeList": ["addInputParam"],
|
||||||
|
"valueType": "dynamic",
|
||||||
|
"label": "",
|
||||||
|
"required": false,
|
||||||
|
"description": "common:core.module.input.description.HTTP Dynamic Input",
|
||||||
|
"customInputConfig": {
|
||||||
|
"selectValueTypeList": [
|
||||||
|
"string",
|
||||||
|
"number",
|
||||||
|
"boolean",
|
||||||
|
"object",
|
||||||
|
"arrayString",
|
||||||
|
"arrayNumber",
|
||||||
|
"arrayBoolean",
|
||||||
|
"arrayObject",
|
||||||
|
"arrayAny",
|
||||||
|
"any",
|
||||||
|
"chatHistory",
|
||||||
|
"datasetQuote",
|
||||||
|
"dynamic",
|
||||||
|
"selectApp",
|
||||||
|
"selectDataset"
|
||||||
|
],
|
||||||
|
"showDescription": false,
|
||||||
|
"showDefaultValue": true
|
||||||
|
},
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "system_httpMethod",
|
||||||
|
"renderTypeList": ["custom"],
|
||||||
|
"valueType": "string",
|
||||||
|
"label": "",
|
||||||
|
"value": "POST",
|
||||||
|
"required": true,
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "system_httpTimeout",
|
||||||
|
"renderTypeList": ["custom"],
|
||||||
|
"valueType": "number",
|
||||||
|
"label": "",
|
||||||
|
"value": 30,
|
||||||
|
"min": 5,
|
||||||
|
"max": 600,
|
||||||
|
"required": true,
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "system_httpReqUrl",
|
||||||
|
"renderTypeList": ["hidden"],
|
||||||
|
"valueType": "string",
|
||||||
|
"label": "",
|
||||||
|
"description": "common:core.module.input.description.Http Request Url",
|
||||||
|
"placeholder": "https://api.ai.com/getInventory",
|
||||||
|
"required": false,
|
||||||
|
"value": "Doc2X/URLImg2text",
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "system_httpHeader",
|
||||||
|
"renderTypeList": ["custom"],
|
||||||
|
"valueType": "any",
|
||||||
|
"value": [],
|
||||||
|
"label": "",
|
||||||
|
"description": "common:core.module.input.description.Http Request Header",
|
||||||
|
"placeholder": "common:core.module.input.description.Http Request Header",
|
||||||
|
"required": false,
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "system_httpParams",
|
||||||
|
"renderTypeList": ["hidden"],
|
||||||
|
"valueType": "any",
|
||||||
|
"value": [],
|
||||||
|
"label": "",
|
||||||
|
"required": false,
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "system_httpJsonBody",
|
||||||
|
"renderTypeList": ["hidden"],
|
||||||
|
"valueType": "any",
|
||||||
|
"value": "{\n \"apikey\": \"{{apikey}}\",\n \"url\": \"{{url}}\",\n \"img_correction\": {{img_correction}},\n \"formula\": {{formula}}\n}",
|
||||||
|
"label": "",
|
||||||
|
"required": false,
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "system_httpFormBody",
|
||||||
|
"renderTypeList": ["hidden"],
|
||||||
|
"valueType": "any",
|
||||||
|
"value": [],
|
||||||
|
"label": "",
|
||||||
|
"required": false,
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "system_httpContentType",
|
||||||
|
"renderTypeList": ["hidden"],
|
||||||
|
"valueType": "string",
|
||||||
|
"value": "json",
|
||||||
|
"label": "",
|
||||||
|
"required": false,
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"renderTypeList": ["reference"],
|
||||||
|
"valueType": "string",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "apikey",
|
||||||
|
"label": "apikey",
|
||||||
|
"customInputConfig": {
|
||||||
|
"selectValueTypeList": [
|
||||||
|
"string",
|
||||||
|
"number",
|
||||||
|
"boolean",
|
||||||
|
"object",
|
||||||
|
"arrayString",
|
||||||
|
"arrayNumber",
|
||||||
|
"arrayBoolean",
|
||||||
|
"arrayObject",
|
||||||
|
"arrayAny",
|
||||||
|
"any",
|
||||||
|
"chatHistory",
|
||||||
|
"datasetQuote",
|
||||||
|
"dynamic",
|
||||||
|
"selectApp",
|
||||||
|
"selectDataset"
|
||||||
|
],
|
||||||
|
"showDescription": false,
|
||||||
|
"showDefaultValue": true
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"value": ["pluginInput", "apikey"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"renderTypeList": ["reference"],
|
||||||
|
"valueType": "string",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "url",
|
||||||
|
"label": "url",
|
||||||
|
"customInputConfig": {
|
||||||
|
"selectValueTypeList": [
|
||||||
|
"string",
|
||||||
|
"number",
|
||||||
|
"boolean",
|
||||||
|
"object",
|
||||||
|
"arrayString",
|
||||||
|
"arrayNumber",
|
||||||
|
"arrayBoolean",
|
||||||
|
"arrayObject",
|
||||||
|
"arrayAny",
|
||||||
|
"any",
|
||||||
|
"chatHistory",
|
||||||
|
"datasetQuote",
|
||||||
|
"dynamic",
|
||||||
|
"selectApp",
|
||||||
|
"selectDataset"
|
||||||
|
],
|
||||||
|
"showDescription": false,
|
||||||
|
"showDefaultValue": true
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"value": ["pluginInput", "url"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"renderTypeList": ["reference"],
|
||||||
|
"valueType": "boolean",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "img_correction",
|
||||||
|
"label": "img_correction",
|
||||||
|
"customInputConfig": {
|
||||||
|
"selectValueTypeList": [
|
||||||
|
"string",
|
||||||
|
"number",
|
||||||
|
"boolean",
|
||||||
|
"object",
|
||||||
|
"arrayString",
|
||||||
|
"arrayNumber",
|
||||||
|
"arrayBoolean",
|
||||||
|
"arrayObject",
|
||||||
|
"arrayAny",
|
||||||
|
"any",
|
||||||
|
"chatHistory",
|
||||||
|
"datasetQuote",
|
||||||
|
"dynamic",
|
||||||
|
"selectApp",
|
||||||
|
"selectDataset"
|
||||||
|
],
|
||||||
|
"showDescription": false,
|
||||||
|
"showDefaultValue": true
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"value": ["pluginInput", "img_correction"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"renderTypeList": ["reference"],
|
||||||
|
"valueType": "boolean",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "formula",
|
||||||
|
"label": "formula",
|
||||||
|
"customInputConfig": {
|
||||||
|
"selectValueTypeList": [
|
||||||
|
"string",
|
||||||
|
"number",
|
||||||
|
"boolean",
|
||||||
|
"object",
|
||||||
|
"arrayString",
|
||||||
|
"arrayNumber",
|
||||||
|
"arrayBoolean",
|
||||||
|
"arrayObject",
|
||||||
|
"arrayAny",
|
||||||
|
"any",
|
||||||
|
"chatHistory",
|
||||||
|
"datasetQuote",
|
||||||
|
"dynamic",
|
||||||
|
"selectApp",
|
||||||
|
"selectDataset"
|
||||||
|
],
|
||||||
|
"showDescription": false,
|
||||||
|
"showDefaultValue": true
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"value": ["pluginInput", "formula"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"id": "error",
|
||||||
|
"key": "error",
|
||||||
|
"label": "workflow:request_error",
|
||||||
|
"description": "HTTP请求错误信息,成功时返回空",
|
||||||
|
"valueType": "object",
|
||||||
|
"type": "static"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "httpRawResponse",
|
||||||
|
"key": "httpRawResponse",
|
||||||
|
"required": true,
|
||||||
|
"label": "workflow:raw_response",
|
||||||
|
"description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
|
||||||
|
"valueType": "any",
|
||||||
|
"type": "static"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "system_addOutputParam",
|
||||||
|
"key": "system_addOutputParam",
|
||||||
|
"type": "dynamic",
|
||||||
|
"valueType": "dynamic",
|
||||||
|
"label": "",
|
||||||
|
"customFieldConfig": {
|
||||||
|
"selectValueTypeList": [
|
||||||
|
"string",
|
||||||
|
"number",
|
||||||
|
"boolean",
|
||||||
|
"object",
|
||||||
|
"arrayString",
|
||||||
|
"arrayNumber",
|
||||||
|
"arrayBoolean",
|
||||||
|
"arrayObject",
|
||||||
|
"any",
|
||||||
|
"chatHistory",
|
||||||
|
"datasetQuote",
|
||||||
|
"dynamic",
|
||||||
|
"selectApp",
|
||||||
|
"selectDataset"
|
||||||
|
],
|
||||||
|
"showDescription": false,
|
||||||
|
"showDefaultValue": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "xWQuEf50F3mr",
|
||||||
|
"valueType": "string",
|
||||||
|
"type": "dynamic",
|
||||||
|
"key": "result",
|
||||||
|
"label": "result"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "m6CJJj7GFud5",
|
||||||
|
"valueType": "boolean",
|
||||||
|
"type": "dynamic",
|
||||||
|
"key": "success",
|
||||||
|
"label": "success"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nodeId": "sWEDDSeuI9ar",
|
||||||
|
"name": "系统配置",
|
||||||
|
"intro": "",
|
||||||
|
"avatar": "core/workflow/template/systemConfig",
|
||||||
|
"flowNodeType": "pluginConfig",
|
||||||
|
"position": {
|
||||||
|
"x": -117.03701176267538,
|
||||||
|
"y": -75.09744210499466
|
||||||
|
},
|
||||||
|
"version": "4811",
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"edges": [
|
||||||
|
{
|
||||||
|
"source": "pluginInput",
|
||||||
|
"target": "zHG5jJBkXmjB",
|
||||||
|
"sourceHandle": "pluginInput-source-right",
|
||||||
|
"targetHandle": "zHG5jJBkXmjB-target-left"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "zHG5jJBkXmjB",
|
||||||
|
"target": "pluginOutput",
|
||||||
|
"sourceHandle": "zHG5jJBkXmjB-source-right",
|
||||||
|
"targetHandle": "pluginOutput-target-left"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
156
packages/plugins/src/Doc2X/URLPDF2text/index.ts
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
import { delay } from '@fastgpt/global/common/system/utils';
|
||||||
|
import { addLog } from '@fastgpt/service/common/system/log';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
apikey: string;
|
||||||
|
url: string;
|
||||||
|
ocr: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Response type same as HTTP outputs
|
||||||
|
type Response = Promise<{
|
||||||
|
result: string;
|
||||||
|
success: boolean;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
const main = async ({ apikey, url, ocr }: Props): Response => {
|
||||||
|
// Check the apikey
|
||||||
|
if (!apikey) {
|
||||||
|
return {
|
||||||
|
result: `API key is required`,
|
||||||
|
success: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let real_api_key = apikey;
|
||||||
|
if (!apikey.startsWith('sk-')) {
|
||||||
|
const response = await fetch('https://api.doc2x.noedgeai.com/api/token/refresh', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${apikey}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (response.status !== 200) {
|
||||||
|
return {
|
||||||
|
result: `Get token failed: ${await response.text()}`,
|
||||||
|
success: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
real_api_key = data.data.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Fetch the pdf and check its contene type
|
||||||
|
let PDFResponse;
|
||||||
|
try {
|
||||||
|
PDFResponse = await fetch(url);
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
result: `Failed to fetch PDF from URL: ${url} with error: ${e}`,
|
||||||
|
success: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!PDFResponse.ok) {
|
||||||
|
return {
|
||||||
|
result: `Failed to fetch PDF from URL: ${url}`,
|
||||||
|
success: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = PDFResponse.headers.get('content-type');
|
||||||
|
if (!contentType || !contentType.startsWith('application/pdf')) {
|
||||||
|
return {
|
||||||
|
result: `The provided URL does not point to a PDF: ${contentType}`,
|
||||||
|
success: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await PDFResponse.blob();
|
||||||
|
const formData = new FormData();
|
||||||
|
const fileName = url.split('/').pop()?.split('?')[0] || 'pdf';
|
||||||
|
formData.append('file', blob, fileName);
|
||||||
|
formData.append('ocr', ocr ? '1' : '0');
|
||||||
|
|
||||||
|
let upload_url = 'https://api.doc2x.noedgeai.com/api/platform/async/pdf';
|
||||||
|
if (real_api_key.startsWith('sk-')) {
|
||||||
|
upload_url = 'https://api.doc2x.noedgeai.com/api/v1/async/pdf';
|
||||||
|
}
|
||||||
|
|
||||||
|
let uuid;
|
||||||
|
const uploadAttempts = [1, 2, 3];
|
||||||
|
for await (const attempt of uploadAttempts) {
|
||||||
|
const upload_response = await fetch(upload_url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${real_api_key}`
|
||||||
|
},
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
if (!upload_response.ok) {
|
||||||
|
if (upload_response.status === 429 && attempt < 3) {
|
||||||
|
await delay(10000);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
result: `Failed to upload file: ${await upload_response.text()}`,
|
||||||
|
success: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const upload_data = await upload_response.json();
|
||||||
|
uuid = upload_data.data.uuid;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the result by uuid
|
||||||
|
let result_url = 'https://api.doc2x.noedgeai.com/api/platform/async/status?uuid=' + uuid;
|
||||||
|
if (real_api_key.startsWith('sk-')) {
|
||||||
|
result_url = 'https://api.doc2x.noedgeai.com/api/v1/async/status?uuid=' + uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = '';
|
||||||
|
// Wait for the result, at most 100s
|
||||||
|
const maxAttempts = 100;
|
||||||
|
for await (const _ of Array(maxAttempts).keys()) {
|
||||||
|
const result_response = await fetch(result_url, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${real_api_key}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!result_response.ok) {
|
||||||
|
return {
|
||||||
|
result: `Failed to get result: ${await result_response.text()}`,
|
||||||
|
success: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const result_data = await result_response.json();
|
||||||
|
if (['ready', 'processing'].includes(result_data.data.status)) {
|
||||||
|
await delay(1000);
|
||||||
|
} else if (result_data.data.status === 'pages limit exceeded') {
|
||||||
|
return {
|
||||||
|
result: 'Doc2X Pages limit exceeded',
|
||||||
|
success: false
|
||||||
|
};
|
||||||
|
} else if (result_data.data.status === 'success') {
|
||||||
|
result = await Promise.all(
|
||||||
|
result_data.data.result.pages.map((page: { md: any }) => page.md)
|
||||||
|
).then((pages) => pages.join('\n'));
|
||||||
|
result = result.replace(/\\[\(\)]/g, '$').replace(/\\[\[\]]/g, '$$');
|
||||||
|
return {
|
||||||
|
result: result,
|
||||||
|
success: true
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
result: `Failed to get result: ${await result_data.text()}`,
|
||||||
|
success: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: 'Timeout waiting for result',
|
||||||
|
success: false
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default main;
|
||||||
435
packages/plugins/src/Doc2X/URLPDF2text/template.json
Normal file
@@ -0,0 +1,435 @@
|
|||||||
|
{
|
||||||
|
"author": "Menghuan1918",
|
||||||
|
"version": "488",
|
||||||
|
"name": "Doc2X PDF文件(URL)识别",
|
||||||
|
"avatar": "plugins/doc2x",
|
||||||
|
"intro": "从URL下载PDF文件,并发送至Doc2X进行解析,返回带LaTeX公式的markdown格式的文本",
|
||||||
|
"courseUrl": "https://fael3z0zfze.feishu.cn/wiki/Rkc5witXWiJoi5kORd2cofh6nDg?fromScene=spaceOverview",
|
||||||
|
"showStatus": true,
|
||||||
|
"weight": 10,
|
||||||
|
|
||||||
|
"isTool": true,
|
||||||
|
"templateType": "tools",
|
||||||
|
|
||||||
|
"workflow": {
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"nodeId": "pluginInput",
|
||||||
|
"name": "插件开始",
|
||||||
|
"intro": "可以配置插件需要哪些输入,利用这些输入来运行插件",
|
||||||
|
"avatar": "core/workflow/template/workflowStart",
|
||||||
|
"flowNodeType": "pluginInput",
|
||||||
|
"showStatus": false,
|
||||||
|
"position": {
|
||||||
|
"x": 388.243055058894,
|
||||||
|
"y": -75.09744210499466
|
||||||
|
},
|
||||||
|
"version": "481",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"renderTypeList": ["input"],
|
||||||
|
"selectedTypeIndex": 0,
|
||||||
|
"valueType": "string",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "apikey",
|
||||||
|
"label": "apikey",
|
||||||
|
"description": "Doc2X的验证密匙,对于个人用户可以从Doc2X官网 - 个人信息 - 身份令牌获得",
|
||||||
|
"required": true,
|
||||||
|
"toolDescription": "",
|
||||||
|
"defaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"renderTypeList": ["reference"],
|
||||||
|
"selectedTypeIndex": 0,
|
||||||
|
"valueType": "string",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "url",
|
||||||
|
"label": "url",
|
||||||
|
"description": "待处理PDF文件的URL",
|
||||||
|
"required": true,
|
||||||
|
"toolDescription": "待处理PDF文件的URL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"renderTypeList": ["switch"],
|
||||||
|
"selectedTypeIndex": 0,
|
||||||
|
"valueType": "boolean",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "ocr",
|
||||||
|
"label": "ocr",
|
||||||
|
"description": "是否开启对PDF文件内图片的OCR识别,建议开启",
|
||||||
|
"required": true,
|
||||||
|
"toolDescription": "",
|
||||||
|
"defaultValue": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"id": "apikey",
|
||||||
|
"valueType": "string",
|
||||||
|
"key": "apikey",
|
||||||
|
"label": "apikey",
|
||||||
|
"type": "hidden"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "url",
|
||||||
|
"valueType": "string",
|
||||||
|
"key": "url",
|
||||||
|
"label": "url",
|
||||||
|
"type": "hidden"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "formula",
|
||||||
|
"valueType": "boolean",
|
||||||
|
"key": "ocr",
|
||||||
|
"label": "ocr",
|
||||||
|
"type": "hidden"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nodeId": "pluginOutput",
|
||||||
|
"name": "插件输出",
|
||||||
|
"intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
|
||||||
|
"avatar": "core/workflow/template/pluginOutput",
|
||||||
|
"flowNodeType": "pluginOutput",
|
||||||
|
"showStatus": false,
|
||||||
|
"position": {
|
||||||
|
"x": 1665.6420513111314,
|
||||||
|
"y": -40.597442104994656
|
||||||
|
},
|
||||||
|
"version": "481",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"renderTypeList": ["reference"],
|
||||||
|
"valueType": "string",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "result",
|
||||||
|
"label": "result",
|
||||||
|
"description": "处理结果(或者是报错信息)",
|
||||||
|
"value": ["zHG5jJBkXmjB", "xWQuEf50F3mr"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"renderTypeList": ["reference"],
|
||||||
|
"valueType": "boolean",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "success",
|
||||||
|
"label": "success",
|
||||||
|
"description": "是否处理成功",
|
||||||
|
"value": ["zHG5jJBkXmjB", "m6CJJj7GFud5"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nodeId": "zHG5jJBkXmjB",
|
||||||
|
"name": "HTTP 请求",
|
||||||
|
"intro": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)",
|
||||||
|
"avatar": "core/workflow/template/httpRequest",
|
||||||
|
"flowNodeType": "httpRequest468",
|
||||||
|
"showStatus": true,
|
||||||
|
"position": {
|
||||||
|
"x": 966.3422652224374,
|
||||||
|
"y": -446.5974421049947
|
||||||
|
},
|
||||||
|
"version": "481",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"key": "system_addInputParam",
|
||||||
|
"renderTypeList": ["addInputParam"],
|
||||||
|
"valueType": "dynamic",
|
||||||
|
"label": "",
|
||||||
|
"required": false,
|
||||||
|
"description": "common:core.module.input.description.HTTP Dynamic Input",
|
||||||
|
"customInputConfig": {
|
||||||
|
"selectValueTypeList": [
|
||||||
|
"string",
|
||||||
|
"number",
|
||||||
|
"boolean",
|
||||||
|
"object",
|
||||||
|
"arrayString",
|
||||||
|
"arrayNumber",
|
||||||
|
"arrayBoolean",
|
||||||
|
"arrayObject",
|
||||||
|
"arrayAny",
|
||||||
|
"any",
|
||||||
|
"chatHistory",
|
||||||
|
"datasetQuote",
|
||||||
|
"dynamic",
|
||||||
|
"selectApp",
|
||||||
|
"selectDataset"
|
||||||
|
],
|
||||||
|
"showDescription": false,
|
||||||
|
"showDefaultValue": true
|
||||||
|
},
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "system_httpMethod",
|
||||||
|
"renderTypeList": ["custom"],
|
||||||
|
"valueType": "string",
|
||||||
|
"label": "",
|
||||||
|
"value": "POST",
|
||||||
|
"required": true,
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "system_httpTimeout",
|
||||||
|
"renderTypeList": ["custom"],
|
||||||
|
"valueType": "number",
|
||||||
|
"label": "",
|
||||||
|
"value": 30,
|
||||||
|
"min": 5,
|
||||||
|
"max": 600,
|
||||||
|
"required": true,
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "system_httpReqUrl",
|
||||||
|
"renderTypeList": ["hidden"],
|
||||||
|
"valueType": "string",
|
||||||
|
"label": "",
|
||||||
|
"description": "common:core.module.input.description.Http Request Url",
|
||||||
|
"placeholder": "https://api.ai.com/getInventory",
|
||||||
|
"required": false,
|
||||||
|
"value": "Doc2X/URLPDF2text",
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "system_httpHeader",
|
||||||
|
"renderTypeList": ["custom"],
|
||||||
|
"valueType": "any",
|
||||||
|
"value": [],
|
||||||
|
"label": "",
|
||||||
|
"description": "common:core.module.input.description.Http Request Header",
|
||||||
|
"placeholder": "common:core.module.input.description.Http Request Header",
|
||||||
|
"required": false,
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "system_httpParams",
|
||||||
|
"renderTypeList": ["hidden"],
|
||||||
|
"valueType": "any",
|
||||||
|
"value": [],
|
||||||
|
"label": "",
|
||||||
|
"required": false,
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "system_httpJsonBody",
|
||||||
|
"renderTypeList": ["hidden"],
|
||||||
|
"valueType": "any",
|
||||||
|
"value": "{\n \"apikey\": \"{{apikey}}\",\n \"url\": \"{{url}}\",\n \"ocr\": {{ocr}}\n}",
|
||||||
|
"label": "",
|
||||||
|
"required": false,
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "system_httpFormBody",
|
||||||
|
"renderTypeList": ["hidden"],
|
||||||
|
"valueType": "any",
|
||||||
|
"value": [],
|
||||||
|
"label": "",
|
||||||
|
"required": false,
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "system_httpContentType",
|
||||||
|
"renderTypeList": ["hidden"],
|
||||||
|
"valueType": "string",
|
||||||
|
"value": "json",
|
||||||
|
"label": "",
|
||||||
|
"required": false,
|
||||||
|
"debugLabel": "",
|
||||||
|
"toolDescription": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"renderTypeList": ["reference"],
|
||||||
|
"valueType": "string",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "apikey",
|
||||||
|
"label": "apikey",
|
||||||
|
"customInputConfig": {
|
||||||
|
"selectValueTypeList": [
|
||||||
|
"string",
|
||||||
|
"number",
|
||||||
|
"boolean",
|
||||||
|
"object",
|
||||||
|
"arrayString",
|
||||||
|
"arrayNumber",
|
||||||
|
"arrayBoolean",
|
||||||
|
"arrayObject",
|
||||||
|
"arrayAny",
|
||||||
|
"any",
|
||||||
|
"chatHistory",
|
||||||
|
"datasetQuote",
|
||||||
|
"dynamic",
|
||||||
|
"selectApp",
|
||||||
|
"selectDataset"
|
||||||
|
],
|
||||||
|
"showDescription": false,
|
||||||
|
"showDefaultValue": true
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"value": ["pluginInput", "apikey"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"renderTypeList": ["reference"],
|
||||||
|
"valueType": "string",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "url",
|
||||||
|
"label": "url",
|
||||||
|
"customInputConfig": {
|
||||||
|
"selectValueTypeList": [
|
||||||
|
"string",
|
||||||
|
"number",
|
||||||
|
"boolean",
|
||||||
|
"object",
|
||||||
|
"arrayString",
|
||||||
|
"arrayNumber",
|
||||||
|
"arrayBoolean",
|
||||||
|
"arrayObject",
|
||||||
|
"arrayAny",
|
||||||
|
"any",
|
||||||
|
"chatHistory",
|
||||||
|
"datasetQuote",
|
||||||
|
"dynamic",
|
||||||
|
"selectApp",
|
||||||
|
"selectDataset"
|
||||||
|
],
|
||||||
|
"showDescription": false,
|
||||||
|
"showDefaultValue": true
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"value": ["pluginInput", "url"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"renderTypeList": ["reference"],
|
||||||
|
"valueType": "boolean",
|
||||||
|
"canEdit": true,
|
||||||
|
"key": "ocr",
|
||||||
|
"label": "ocr",
|
||||||
|
"customInputConfig": {
|
||||||
|
"selectValueTypeList": [
|
||||||
|
"string",
|
||||||
|
"number",
|
||||||
|
"boolean",
|
||||||
|
"object",
|
||||||
|
"arrayString",
|
||||||
|
"arrayNumber",
|
||||||
|
"arrayBoolean",
|
||||||
|
"arrayObject",
|
||||||
|
"arrayAny",
|
||||||
|
"any",
|
||||||
|
"chatHistory",
|
||||||
|
"datasetQuote",
|
||||||
|
"dynamic",
|
||||||
|
"selectApp",
|
||||||
|
"selectDataset"
|
||||||
|
],
|
||||||
|
"showDescription": false,
|
||||||
|
"showDefaultValue": true
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"value": ["pluginInput", "formula"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"id": "error",
|
||||||
|
"key": "error",
|
||||||
|
"label": "workflow:request_error",
|
||||||
|
"description": "HTTP请求错误信息,成功时返回空",
|
||||||
|
"valueType": "object",
|
||||||
|
"type": "static"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "httpRawResponse",
|
||||||
|
"key": "httpRawResponse",
|
||||||
|
"required": true,
|
||||||
|
"label": "workflow:raw_response",
|
||||||
|
"description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
|
||||||
|
"valueType": "any",
|
||||||
|
"type": "static"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "system_addOutputParam",
|
||||||
|
"key": "system_addOutputParam",
|
||||||
|
"type": "dynamic",
|
||||||
|
"valueType": "dynamic",
|
||||||
|
"label": "",
|
||||||
|
"customFieldConfig": {
|
||||||
|
"selectValueTypeList": [
|
||||||
|
"string",
|
||||||
|
"number",
|
||||||
|
"boolean",
|
||||||
|
"object",
|
||||||
|
"arrayString",
|
||||||
|
"arrayNumber",
|
||||||
|
"arrayBoolean",
|
||||||
|
"arrayObject",
|
||||||
|
"any",
|
||||||
|
"chatHistory",
|
||||||
|
"datasetQuote",
|
||||||
|
"dynamic",
|
||||||
|
"selectApp",
|
||||||
|
"selectDataset"
|
||||||
|
],
|
||||||
|
"showDescription": false,
|
||||||
|
"showDefaultValue": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "xWQuEf50F3mr",
|
||||||
|
"valueType": "string",
|
||||||
|
"type": "dynamic",
|
||||||
|
"key": "result",
|
||||||
|
"label": "result"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "m6CJJj7GFud5",
|
||||||
|
"valueType": "boolean",
|
||||||
|
"type": "dynamic",
|
||||||
|
"key": "success",
|
||||||
|
"label": "success"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nodeId": "rZmLfANEyyJe",
|
||||||
|
"name": "系统配置",
|
||||||
|
"intro": "",
|
||||||
|
"avatar": "core/workflow/template/systemConfig",
|
||||||
|
"flowNodeType": "pluginConfig",
|
||||||
|
"position": {
|
||||||
|
"x": -93.55061402342784,
|
||||||
|
"y": -55.907069101622824
|
||||||
|
},
|
||||||
|
"version": "4811",
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"edges": [
|
||||||
|
{
|
||||||
|
"source": "pluginInput",
|
||||||
|
"target": "zHG5jJBkXmjB",
|
||||||
|
"sourceHandle": "pluginInput-source-right",
|
||||||
|
"targetHandle": "zHG5jJBkXmjB-target-left"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "zHG5jJBkXmjB",
|
||||||
|
"target": "pluginOutput",
|
||||||
|
"sourceHandle": "zHG5jJBkXmjB-source-right",
|
||||||
|
"targetHandle": "pluginOutput-target-left"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,20 +32,18 @@ export function getGridBucket(bucket: `${BucketNameEnum}`) {
|
|||||||
export async function uploadFile({
|
export async function uploadFile({
|
||||||
bucketName,
|
bucketName,
|
||||||
teamId,
|
teamId,
|
||||||
uid,
|
tmbId,
|
||||||
path,
|
path,
|
||||||
filename,
|
filename,
|
||||||
contentType,
|
contentType,
|
||||||
encoding,
|
|
||||||
metadata = {}
|
metadata = {}
|
||||||
}: {
|
}: {
|
||||||
bucketName: `${BucketNameEnum}`;
|
bucketName: `${BucketNameEnum}`;
|
||||||
teamId: string;
|
teamId: string;
|
||||||
uid: string; // tmbId / outLinkUId
|
tmbId: string;
|
||||||
path: string;
|
path: string;
|
||||||
filename: string;
|
filename: string;
|
||||||
contentType?: string;
|
contentType?: string;
|
||||||
encoding: string;
|
|
||||||
metadata?: Record<string, any>;
|
metadata?: Record<string, any>;
|
||||||
}) {
|
}) {
|
||||||
if (!path) return Promise.reject(`filePath is empty`);
|
if (!path) return Promise.reject(`filePath is empty`);
|
||||||
@@ -54,11 +52,11 @@ export async function uploadFile({
|
|||||||
const stats = await fsp.stat(path);
|
const stats = await fsp.stat(path);
|
||||||
if (!stats.isFile()) return Promise.reject(`${path} is not a file`);
|
if (!stats.isFile()) return Promise.reject(`${path} is not a file`);
|
||||||
|
|
||||||
const readStream = fs.createReadStream(path);
|
const { stream: readStream, encoding } = await stream2Encoding(fs.createReadStream(path));
|
||||||
|
|
||||||
// Add default metadata
|
// Add default metadata
|
||||||
metadata.teamId = teamId;
|
metadata.teamId = teamId;
|
||||||
metadata.uid = uid;
|
metadata.tmbId = tmbId;
|
||||||
metadata.encoding = encoding;
|
metadata.encoding = encoding;
|
||||||
|
|
||||||
// create a gridfs bucket
|
// create a gridfs bucket
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import path from 'path';
|
|||||||
import { BucketNameEnum, bucketNameMap } from '@fastgpt/global/common/file/constants';
|
import { BucketNameEnum, bucketNameMap } from '@fastgpt/global/common/file/constants';
|
||||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||||
|
|
||||||
export type FileType = {
|
type FileType = {
|
||||||
fieldname: string;
|
fieldname: string;
|
||||||
originalname: string;
|
originalname: string;
|
||||||
encoding: string;
|
encoding: string;
|
||||||
@@ -41,7 +41,7 @@ export const getUploadModel = ({ maxSize = 500 }: { maxSize?: number }) => {
|
|||||||
})
|
})
|
||||||
}).single('file');
|
}).single('file');
|
||||||
|
|
||||||
async doUpload<T = any>(
|
async doUpload<T = Record<string, any>>(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
res: NextApiResponse,
|
res: NextApiResponse,
|
||||||
originBucketName?: `${BucketNameEnum}`
|
originBucketName?: `${BucketNameEnum}`
|
||||||
|
|||||||
@@ -4,17 +4,16 @@ import FormData from 'form-data';
|
|||||||
|
|
||||||
import { WorkerNameEnum, runWorker } from '../../../worker/utils';
|
import { WorkerNameEnum, runWorker } from '../../../worker/utils';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import { detectFileEncoding } from '@fastgpt/global/common/file/tools';
|
||||||
import type { ReadFileResponse } from '../../../worker/readFile/type';
|
import type { ReadFileResponse } from '../../../worker/readFile/type';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { addLog } from '../../system/log';
|
import { addLog } from '../../system/log';
|
||||||
import { batchRun } from '@fastgpt/global/common/fn/utils';
|
import { batchRun } from '@fastgpt/global/common/fn/utils';
|
||||||
import { addHours } from 'date-fns';
|
import { addHours } from 'date-fns';
|
||||||
import { matchMdImgTextAndUpload } from '@fastgpt/global/common/string/markdown';
|
|
||||||
|
|
||||||
export type readRawTextByLocalFileParams = {
|
export type readRawTextByLocalFileParams = {
|
||||||
teamId: string;
|
teamId: string;
|
||||||
path: string;
|
path: string;
|
||||||
encoding: string;
|
|
||||||
metadata?: Record<string, any>;
|
metadata?: Record<string, any>;
|
||||||
};
|
};
|
||||||
export const readRawTextByLocalFile = async (params: readRawTextByLocalFileParams) => {
|
export const readRawTextByLocalFile = async (params: readRawTextByLocalFileParams) => {
|
||||||
@@ -23,12 +22,13 @@ export const readRawTextByLocalFile = async (params: readRawTextByLocalFileParam
|
|||||||
const extension = path?.split('.')?.pop()?.toLowerCase() || '';
|
const extension = path?.split('.')?.pop()?.toLowerCase() || '';
|
||||||
|
|
||||||
const buffer = fs.readFileSync(path);
|
const buffer = fs.readFileSync(path);
|
||||||
|
const encoding = detectFileEncoding(buffer);
|
||||||
|
|
||||||
const { rawText } = await readRawContentByFileBuffer({
|
const { rawText } = await readRawContentByFileBuffer({
|
||||||
extension,
|
extension,
|
||||||
isQAImport: false,
|
isQAImport: false,
|
||||||
teamId: params.teamId,
|
teamId: params.teamId,
|
||||||
encoding: params.encoding,
|
encoding,
|
||||||
buffer,
|
buffer,
|
||||||
metadata: params.metadata
|
metadata: params.metadata
|
||||||
});
|
});
|
||||||
@@ -53,7 +53,6 @@ export const readRawContentByFileBuffer = async ({
|
|||||||
encoding: string;
|
encoding: string;
|
||||||
metadata?: Record<string, any>;
|
metadata?: Record<string, any>;
|
||||||
}) => {
|
}) => {
|
||||||
// Custom read file service
|
|
||||||
const customReadfileUrl = process.env.CUSTOM_READ_FILE_URL;
|
const customReadfileUrl = process.env.CUSTOM_READ_FILE_URL;
|
||||||
const customReadFileExtension = process.env.CUSTOM_READ_FILE_EXTENSION || '';
|
const customReadFileExtension = process.env.CUSTOM_READ_FILE_EXTENSION || '';
|
||||||
const ocrParse = process.env.CUSTOM_READ_FILE_OCR || 'false';
|
const ocrParse = process.env.CUSTOM_READ_FILE_OCR || 'false';
|
||||||
@@ -79,7 +78,6 @@ export const readRawContentByFileBuffer = async ({
|
|||||||
data: {
|
data: {
|
||||||
page: number;
|
page: number;
|
||||||
markdown: string;
|
markdown: string;
|
||||||
duration: number;
|
|
||||||
};
|
};
|
||||||
}>(customReadfileUrl, data, {
|
}>(customReadfileUrl, data, {
|
||||||
timeout: 600000,
|
timeout: 600000,
|
||||||
@@ -91,12 +89,10 @@ export const readRawContentByFileBuffer = async ({
|
|||||||
addLog.info(`Use custom read file service, time: ${Date.now() - start}ms`);
|
addLog.info(`Use custom read file service, time: ${Date.now() - start}ms`);
|
||||||
|
|
||||||
const rawText = response.data.markdown;
|
const rawText = response.data.markdown;
|
||||||
const { text, imageList } = matchMdImgTextAndUpload(rawText);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
rawText: text,
|
rawText,
|
||||||
formatText: rawText,
|
formatText: rawText
|
||||||
imageList
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -123,9 +119,6 @@ export const readRawContentByFileBuffer = async ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
rawText = rawText.replace(item.uuid, src);
|
rawText = rawText.replace(item.uuid, src);
|
||||||
if (formatText) {
|
|
||||||
formatText = formatText.replace(item.uuid, src);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +127,7 @@ export const readRawContentByFileBuffer = async ({
|
|||||||
if (isQAImport) {
|
if (isQAImport) {
|
||||||
rawText = rawText || '';
|
rawText = rawText || '';
|
||||||
} else {
|
} else {
|
||||||
rawText = formatText || rawText;
|
rawText = formatText || '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
import type { UserModelSchema } from '@fastgpt/global/support/user/type';
|
import type { UserModelSchema } from '@fastgpt/global/support/user/type';
|
||||||
import OpenAI from '@fastgpt/global/core/ai';
|
import OpenAI from '@fastgpt/global/core/ai';
|
||||||
import {
|
|
||||||
ChatCompletionCreateParamsNonStreaming,
|
|
||||||
ChatCompletionCreateParamsStreaming
|
|
||||||
} from '@fastgpt/global/core/ai/type';
|
|
||||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
|
||||||
import { addLog } from '../../common/system/log';
|
|
||||||
import { i18nT } from '../../../web/i18n/utils';
|
|
||||||
|
|
||||||
export const openaiBaseUrl = process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1';
|
export const openaiBaseUrl = process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1';
|
||||||
|
|
||||||
@@ -41,68 +34,3 @@ export const getAxiosConfig = (props?: { userKey?: UserModelSchema['openaiAccoun
|
|||||||
authorization: `Bearer ${apiKey}`
|
authorization: `Bearer ${apiKey}`
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
type CompletionsBodyType =
|
|
||||||
| ChatCompletionCreateParamsNonStreaming
|
|
||||||
| ChatCompletionCreateParamsStreaming;
|
|
||||||
type InferResponseType<T extends CompletionsBodyType> =
|
|
||||||
T extends ChatCompletionCreateParamsStreaming
|
|
||||||
? OpenAI.Chat.Completions.ChatCompletionChunk
|
|
||||||
: OpenAI.Chat.Completions.ChatCompletion;
|
|
||||||
|
|
||||||
export const createChatCompletion = async <T extends CompletionsBodyType>({
|
|
||||||
body,
|
|
||||||
userKey,
|
|
||||||
timeout,
|
|
||||||
options
|
|
||||||
}: {
|
|
||||||
body: T;
|
|
||||||
userKey?: UserModelSchema['openaiAccount'];
|
|
||||||
timeout?: number;
|
|
||||||
options?: OpenAI.RequestOptions;
|
|
||||||
}): Promise<{
|
|
||||||
response: InferResponseType<T>;
|
|
||||||
isStreamResponse: boolean;
|
|
||||||
getEmptyResponseTip: () => string;
|
|
||||||
}> => {
|
|
||||||
try {
|
|
||||||
const formatTimeout = timeout ? timeout : body.stream ? 60000 : 600000;
|
|
||||||
const ai = getAIApi({
|
|
||||||
userKey,
|
|
||||||
timeout: formatTimeout
|
|
||||||
});
|
|
||||||
const response = await ai.chat.completions.create(body, options);
|
|
||||||
|
|
||||||
const isStreamResponse =
|
|
||||||
typeof response === 'object' &&
|
|
||||||
response !== null &&
|
|
||||||
('iterator' in response || 'controller' in response);
|
|
||||||
|
|
||||||
const getEmptyResponseTip = () => {
|
|
||||||
addLog.warn(`LLM response empty`, {
|
|
||||||
baseUrl: userKey?.baseUrl,
|
|
||||||
requestBody: body
|
|
||||||
});
|
|
||||||
if (userKey?.baseUrl) {
|
|
||||||
return `您的 OpenAI key 没有响应: ${JSON.stringify(body)}`;
|
|
||||||
}
|
|
||||||
return i18nT('chat:LLM_model_response_empty');
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
response: response as InferResponseType<T>,
|
|
||||||
isStreamResponse,
|
|
||||||
getEmptyResponseTip
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
addLog.error(`LLM response error`, error);
|
|
||||||
addLog.warn(`LLM response error`, {
|
|
||||||
baseUrl: userKey?.baseUrl,
|
|
||||||
requestBody: body
|
|
||||||
});
|
|
||||||
if (userKey?.baseUrl) {
|
|
||||||
return Promise.reject(`您的 OpenAI key 出错了: ${getErrText(error)}`);
|
|
||||||
}
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export async function getVectorsByText({ model, input, type }: GetVectorProps) {
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addLog.error(`Embedding Error`, error);
|
console.log(`Embedding Error`, error);
|
||||||
|
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type.d';
|
import type { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type.d';
|
||||||
import { createChatCompletion } from '../config';
|
import { getAIApi } from '../config';
|
||||||
import { countGptMessagesTokens } from '../../../common/string/tiktoken/index';
|
import { countGptMessagesTokens } from '../../../common/string/tiktoken/index';
|
||||||
import { loadRequestMessages } from '../../chat/utils';
|
import { loadRequestMessages } from '../../chat/utils';
|
||||||
import { llmCompletionsBodyFormat } from '../utils';
|
import { llmCompletionsBodyFormat } from '../utils';
|
||||||
@@ -29,8 +29,11 @@ export async function createQuestionGuide({
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const { response: data } = await createChatCompletion({
|
const ai = getAIApi({
|
||||||
body: llmCompletionsBodyFormat(
|
timeout: 480000
|
||||||
|
});
|
||||||
|
const data = await ai.chat.completions.create(
|
||||||
|
llmCompletionsBodyFormat(
|
||||||
{
|
{
|
||||||
model,
|
model,
|
||||||
temperature: 0.1,
|
temperature: 0.1,
|
||||||
@@ -43,7 +46,7 @@ export async function createQuestionGuide({
|
|||||||
},
|
},
|
||||||
model
|
model
|
||||||
)
|
)
|
||||||
});
|
);
|
||||||
|
|
||||||
const answer = data.choices?.[0]?.message?.content || '';
|
const answer = data.choices?.[0]?.message?.content || '';
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { replaceVariable } from '@fastgpt/global/common/string/tools';
|
import { replaceVariable } from '@fastgpt/global/common/string/tools';
|
||||||
import { createChatCompletion } from '../config';
|
import { getAIApi } from '../config';
|
||||||
import { ChatItemType } from '@fastgpt/global/core/chat/type';
|
import { ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||||
import { countGptMessagesTokens } from '../../../common/string/tiktoken/index';
|
import { countGptMessagesTokens } from '../../../common/string/tiktoken/index';
|
||||||
|
import { ChatCompletion, ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type';
|
||||||
import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
|
import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
|
||||||
import { getLLMModel } from '../model';
|
import { getLLMModel } from '../model';
|
||||||
import { llmCompletionsBodyFormat } from '../utils';
|
import { llmCompletionsBodyFormat } from '../utils';
|
||||||
@@ -137,6 +138,10 @@ A: ${chatBg}
|
|||||||
|
|
||||||
const modelData = getLLMModel(model);
|
const modelData = getLLMModel(model);
|
||||||
|
|
||||||
|
const ai = getAIApi({
|
||||||
|
timeout: 480000
|
||||||
|
});
|
||||||
|
|
||||||
const messages = [
|
const messages = [
|
||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
@@ -145,19 +150,20 @@ A: ${chatBg}
|
|||||||
histories: concatFewShot
|
histories: concatFewShot
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
] as any;
|
] as ChatCompletionMessageParam[];
|
||||||
|
|
||||||
const { response: result } = await createChatCompletion({
|
const result = (await ai.chat.completions.create(
|
||||||
body: llmCompletionsBodyFormat(
|
llmCompletionsBodyFormat(
|
||||||
{
|
{
|
||||||
stream: false,
|
stream: false,
|
||||||
model: modelData.model,
|
model: modelData.model,
|
||||||
temperature: 0.01,
|
temperature: 0.01,
|
||||||
|
// @ts-ignore
|
||||||
messages
|
messages
|
||||||
},
|
},
|
||||||
modelData
|
modelData
|
||||||
)
|
)
|
||||||
});
|
)) as ChatCompletion;
|
||||||
|
|
||||||
let answer = result.choices?.[0]?.message?.content || '';
|
let answer = result.choices?.[0]?.message?.content || '';
|
||||||
if (!answer) {
|
if (!answer) {
|
||||||
|
|||||||
@@ -48,17 +48,14 @@ export const computedTemperature = ({
|
|||||||
type CompletionsBodyType =
|
type CompletionsBodyType =
|
||||||
| ChatCompletionCreateParamsNonStreaming
|
| ChatCompletionCreateParamsNonStreaming
|
||||||
| ChatCompletionCreateParamsStreaming;
|
| ChatCompletionCreateParamsStreaming;
|
||||||
type InferCompletionsBody<T> = T extends { stream: true }
|
|
||||||
? ChatCompletionCreateParamsStreaming
|
|
||||||
: ChatCompletionCreateParamsNonStreaming;
|
|
||||||
|
|
||||||
export const llmCompletionsBodyFormat = <T extends CompletionsBodyType>(
|
export const llmCompletionsBodyFormat = <T extends CompletionsBodyType>(
|
||||||
body: T,
|
body: T,
|
||||||
model: string | LLMModelItemType
|
model: string | LLMModelItemType
|
||||||
): InferCompletionsBody<T> => {
|
) => {
|
||||||
const modelData = typeof model === 'string' ? getLLMModel(model) : model;
|
const modelData = typeof model === 'string' ? getLLMModel(model) : model;
|
||||||
if (!modelData) {
|
if (!modelData) {
|
||||||
return body as InferCompletionsBody<T>;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestBody: T = {
|
const requestBody: T = {
|
||||||
@@ -84,5 +81,5 @@ export const llmCompletionsBodyFormat = <T extends CompletionsBodyType>(
|
|||||||
|
|
||||||
// console.log(requestBody);
|
// console.log(requestBody);
|
||||||
|
|
||||||
return requestBody as InferCompletionsBody<T>;
|
return requestBody;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ export const chatConfigType = {
|
|||||||
scheduledTriggerConfig: Object,
|
scheduledTriggerConfig: Object,
|
||||||
chatInputGuide: Object,
|
chatInputGuide: Object,
|
||||||
fileSelectConfig: Object,
|
fileSelectConfig: Object,
|
||||||
instruction: String,
|
instruction: String
|
||||||
autoExecute: Object
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// schema
|
// schema
|
||||||
|
|||||||
@@ -46,10 +46,6 @@ const ChatItemSchema = new Schema({
|
|||||||
type: Date,
|
type: Date,
|
||||||
default: () => new Date()
|
default: () => new Date()
|
||||||
},
|
},
|
||||||
hideInUI: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
obj: {
|
obj: {
|
||||||
// chat role
|
// chat role
|
||||||
type: String,
|
type: String,
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
import type { AIChatItemType, UserChatItemType } from '@fastgpt/global/core/chat/type.d';
|
import type {
|
||||||
|
AIChatItemType,
|
||||||
|
ChatItemType,
|
||||||
|
UserChatItemType
|
||||||
|
} from '@fastgpt/global/core/chat/type.d';
|
||||||
|
import axios from 'axios';
|
||||||
import { MongoApp } from '../app/schema';
|
import { MongoApp } from '../app/schema';
|
||||||
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
import {
|
||||||
|
ChatItemValueTypeEnum,
|
||||||
|
ChatRoleEnum,
|
||||||
|
ChatSourceEnum
|
||||||
|
} from '@fastgpt/global/core/chat/constants';
|
||||||
import { MongoChatItem } from './chatItemSchema';
|
import { MongoChatItem } from './chatItemSchema';
|
||||||
import { MongoChat } from './chatSchema';
|
import { MongoChat } from './chatSchema';
|
||||||
import { addLog } from '../../common/system/log';
|
import { addLog } from '../../common/system/log';
|
||||||
@@ -124,15 +133,21 @@ export async function saveChat({
|
|||||||
export const updateInteractiveChat = async ({
|
export const updateInteractiveChat = async ({
|
||||||
chatId,
|
chatId,
|
||||||
appId,
|
appId,
|
||||||
|
teamId,
|
||||||
|
tmbId,
|
||||||
userInteractiveVal,
|
userInteractiveVal,
|
||||||
aiResponse,
|
aiResponse,
|
||||||
newVariables
|
newVariables,
|
||||||
|
newTitle
|
||||||
}: {
|
}: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
appId: string;
|
appId: string;
|
||||||
|
teamId: string;
|
||||||
|
tmbId: string;
|
||||||
userInteractiveVal: string;
|
userInteractiveVal: string;
|
||||||
aiResponse: AIChatItemType & { dataId?: string };
|
aiResponse: AIChatItemType & { dataId?: string };
|
||||||
newVariables?: Record<string, any>;
|
newVariables?: Record<string, any>;
|
||||||
|
newTitle: string;
|
||||||
}) => {
|
}) => {
|
||||||
if (!chatId) return;
|
if (!chatId) return;
|
||||||
|
|
||||||
@@ -217,6 +232,7 @@ export const updateInteractiveChat = async ({
|
|||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
variables: newVariables,
|
variables: newVariables,
|
||||||
|
title: newTitle,
|
||||||
updateTime: new Date()
|
updateTime: new Date()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export const loadRequestMessages = async ({
|
|||||||
}
|
}
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
messages.map(async (item) => {
|
messages.map(async (item) => {
|
||||||
if (item.type === 'image_url') {
|
if (item.type === 'image_url' && process.env.MULTIPLE_DATA_TO_BASE64 === 'true') {
|
||||||
// Remove url origin
|
// Remove url origin
|
||||||
const imgUrl = (() => {
|
const imgUrl = (() => {
|
||||||
if (origin && item.image_url.url.startsWith(origin)) {
|
if (origin && item.image_url.url.startsWith(origin)) {
|
||||||
@@ -118,11 +118,6 @@ export const loadRequestMessages = async ({
|
|||||||
return item.image_url.url;
|
return item.image_url.url;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// base64 image
|
|
||||||
if (imgUrl.startsWith('data:image/')) {
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// If imgUrl is a local path, load image from local, and set url to base64
|
// If imgUrl is a local path, load image from local, and set url to base64
|
||||||
if (imgUrl.startsWith('/')) {
|
if (imgUrl.startsWith('/')) {
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export async function createOneCollection({
|
|||||||
|
|
||||||
fileId,
|
fileId,
|
||||||
rawLink,
|
rawLink,
|
||||||
...(externalFileId ? { externalFileId } : {}),
|
externalFileId,
|
||||||
externalFileUrl,
|
externalFileUrl,
|
||||||
|
|
||||||
rawTextLength,
|
rawTextLength,
|
||||||
|
|||||||
@@ -118,10 +118,7 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
|
|||||||
let createTimeCollectionIdList: string[] | undefined = undefined;
|
let createTimeCollectionIdList: string[] | undefined = undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const jsonMatch =
|
const jsonMatch = json5.parse(collectionFilterMatch);
|
||||||
typeof collectionFilterMatch === 'object'
|
|
||||||
? collectionFilterMatch
|
|
||||||
: json5.parse(collectionFilterMatch);
|
|
||||||
|
|
||||||
// Tag
|
// Tag
|
||||||
let andTags = jsonMatch?.tags?.$and as (string | null)[] | undefined;
|
let andTags = jsonMatch?.tags?.$and as (string | null)[] | undefined;
|
||||||
@@ -350,7 +347,7 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
|
|||||||
teamId: new Types.ObjectId(teamId),
|
teamId: new Types.ObjectId(teamId),
|
||||||
datasetId: new Types.ObjectId(id),
|
datasetId: new Types.ObjectId(id),
|
||||||
$text: { $search: jiebaSplit({ text: query }) },
|
$text: { $search: jiebaSplit({ text: query }) },
|
||||||
...(filterCollectionIdList
|
...(filterCollectionIdList && filterCollectionIdList.length > 0
|
||||||
? {
|
? {
|
||||||
collectionId: {
|
collectionId: {
|
||||||
$in: filterCollectionIdList.map((id) => new Types.ObjectId(id))
|
$in: filterCollectionIdList.map((id) => new Types.ObjectId(id))
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export async function pushDataListToTrainingQueue({
|
|||||||
|
|
||||||
if (trainingMode === TrainingModeEnum.chunk) {
|
if (trainingMode === TrainingModeEnum.chunk) {
|
||||||
return {
|
return {
|
||||||
maxToken: vectorModelData.maxToken * 1.5,
|
maxToken: vectorModelData.maxToken * 1.3,
|
||||||
model: vectorModelData.model,
|
model: vectorModelData.model,
|
||||||
weight: vectorModelData.weight
|
weight: vectorModelData.weight
|
||||||
};
|
};
|
||||||
@@ -125,7 +125,10 @@ export async function pushDataListToTrainingQueue({
|
|||||||
|
|
||||||
const text = item.q + item.a;
|
const text = item.q + item.a;
|
||||||
|
|
||||||
if (text.length > maxToken) {
|
// count q token
|
||||||
|
const token = item.q.length;
|
||||||
|
|
||||||
|
if (token > maxToken) {
|
||||||
filterResult.overToken.push(item);
|
filterResult.overToken.push(item);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,11 +51,6 @@ const TrainingDataSchema = new Schema({
|
|||||||
type: Date,
|
type: Date,
|
||||||
default: () => new Date('2000/1/1')
|
default: () => new Date('2000/1/1')
|
||||||
},
|
},
|
||||||
retryCount: {
|
|
||||||
type: Number,
|
|
||||||
default: 5
|
|
||||||
},
|
|
||||||
|
|
||||||
model: {
|
model: {
|
||||||
// ai model
|
// ai model
|
||||||
type: String,
|
type: String,
|
||||||
@@ -102,7 +97,7 @@ try {
|
|||||||
// lock training data(teamId); delete training data
|
// lock training data(teamId); delete training data
|
||||||
TrainingDataSchema.index({ teamId: 1, datasetId: 1 });
|
TrainingDataSchema.index({ teamId: 1, datasetId: 1 });
|
||||||
// get training data and sort
|
// get training data and sort
|
||||||
TrainingDataSchema.index({ mode: 1, retryCount: 1, lockTime: 1, weight: -1 });
|
TrainingDataSchema.index({ mode: 1, lockTime: 1, weight: -1 });
|
||||||
TrainingDataSchema.index({ expireAt: 1 }, { expireAfterSeconds: 7 * 24 * 60 * 60 }); // 7 days
|
TrainingDataSchema.index({ expireAt: 1 }, { expireAfterSeconds: 7 * 24 * 60 * 60 }); // 7 days
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
|
|||||||
import { countMessagesTokens } from '../../../../common/string/tiktoken/index';
|
import { countMessagesTokens } from '../../../../common/string/tiktoken/index';
|
||||||
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
|
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
|
||||||
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||||
import { createChatCompletion } from '../../../ai/config';
|
import { getAIApi } from '../../../ai/config';
|
||||||
import type { ClassifyQuestionAgentItemType } from '@fastgpt/global/core/workflow/template/system/classifyQuestion/type';
|
import type { ClassifyQuestionAgentItemType } from '@fastgpt/global/core/workflow/template/system/classifyQuestion/type';
|
||||||
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||||
@@ -120,8 +120,13 @@ const completions = async ({
|
|||||||
useVision: false
|
useVision: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const { response: data } = await createChatCompletion({
|
const ai = getAIApi({
|
||||||
body: llmCompletionsBodyFormat(
|
userKey: user.openaiAccount,
|
||||||
|
timeout: 480000
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await ai.chat.completions.create(
|
||||||
|
llmCompletionsBodyFormat(
|
||||||
{
|
{
|
||||||
model: cqModel.model,
|
model: cqModel.model,
|
||||||
temperature: 0.01,
|
temperature: 0.01,
|
||||||
@@ -129,9 +134,8 @@ const completions = async ({
|
|||||||
stream: false
|
stream: false
|
||||||
},
|
},
|
||||||
cqModel
|
cqModel
|
||||||
),
|
)
|
||||||
userKey: user.openaiAccount
|
);
|
||||||
});
|
|
||||||
const answer = data.choices?.[0].message?.content || '';
|
const answer = data.choices?.[0].message?.content || '';
|
||||||
|
|
||||||
// console.log(JSON.stringify(chats2GPTMessages({ messages, reserveId: false }), null, 2));
|
// console.log(JSON.stringify(chats2GPTMessages({ messages, reserveId: false }), null, 2));
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
countGptMessagesTokens
|
countGptMessagesTokens
|
||||||
} from '../../../../common/string/tiktoken/index';
|
} from '../../../../common/string/tiktoken/index';
|
||||||
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||||
import { createChatCompletion } from '../../../ai/config';
|
import { getAIApi } from '../../../ai/config';
|
||||||
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/workflow/template/system/contextExtract/type';
|
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/workflow/template/system/contextExtract/type';
|
||||||
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||||
@@ -222,8 +222,13 @@ const toolChoice = async (props: ActionProps) => {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const { response } = await createChatCompletion({
|
const ai = getAIApi({
|
||||||
body: llmCompletionsBodyFormat(
|
userKey: user.openaiAccount,
|
||||||
|
timeout: 480000
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await ai.chat.completions.create(
|
||||||
|
llmCompletionsBodyFormat(
|
||||||
{
|
{
|
||||||
model: extractModel.model,
|
model: extractModel.model,
|
||||||
temperature: 0.01,
|
temperature: 0.01,
|
||||||
@@ -232,9 +237,8 @@ const toolChoice = async (props: ActionProps) => {
|
|||||||
tool_choice: { type: 'function', function: { name: agentFunName } }
|
tool_choice: { type: 'function', function: { name: agentFunName } }
|
||||||
},
|
},
|
||||||
extractModel
|
extractModel
|
||||||
),
|
)
|
||||||
userKey: user.openaiAccount
|
);
|
||||||
});
|
|
||||||
|
|
||||||
const arg: Record<string, any> = (() => {
|
const arg: Record<string, any> = (() => {
|
||||||
try {
|
try {
|
||||||
@@ -268,8 +272,13 @@ const functionCall = async (props: ActionProps) => {
|
|||||||
const { agentFunction, filterMessages } = await getFunctionCallSchema(props);
|
const { agentFunction, filterMessages } = await getFunctionCallSchema(props);
|
||||||
const functions: ChatCompletionCreateParams.Function[] = [agentFunction];
|
const functions: ChatCompletionCreateParams.Function[] = [agentFunction];
|
||||||
|
|
||||||
const { response } = await createChatCompletion({
|
const ai = getAIApi({
|
||||||
body: llmCompletionsBodyFormat(
|
userKey: user.openaiAccount,
|
||||||
|
timeout: 480000
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await ai.chat.completions.create(
|
||||||
|
llmCompletionsBodyFormat(
|
||||||
{
|
{
|
||||||
model: extractModel.model,
|
model: extractModel.model,
|
||||||
temperature: 0.01,
|
temperature: 0.01,
|
||||||
@@ -280,9 +289,8 @@ const functionCall = async (props: ActionProps) => {
|
|||||||
functions
|
functions
|
||||||
},
|
},
|
||||||
extractModel
|
extractModel
|
||||||
),
|
)
|
||||||
userKey: user.openaiAccount
|
);
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const arg = JSON.parse(response?.choices?.[0]?.message?.function_call?.arguments || '');
|
const arg = JSON.parse(response?.choices?.[0]?.message?.function_call?.arguments || '');
|
||||||
@@ -350,8 +358,12 @@ Human: ${content}`
|
|||||||
useVision: false
|
useVision: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const { response: data } = await createChatCompletion({
|
const ai = getAIApi({
|
||||||
body: llmCompletionsBodyFormat(
|
userKey: user.openaiAccount,
|
||||||
|
timeout: 480000
|
||||||
|
});
|
||||||
|
const data = await ai.chat.completions.create(
|
||||||
|
llmCompletionsBodyFormat(
|
||||||
{
|
{
|
||||||
model: extractModel.model,
|
model: extractModel.model,
|
||||||
temperature: 0.01,
|
temperature: 0.01,
|
||||||
@@ -359,9 +371,8 @@ Human: ${content}`
|
|||||||
stream: false
|
stream: false
|
||||||
},
|
},
|
||||||
extractModel
|
extractModel
|
||||||
),
|
)
|
||||||
userKey: user.openaiAccount
|
);
|
||||||
});
|
|
||||||
const answer = data.choices?.[0].message?.content || '';
|
const answer = data.choices?.[0].message?.content || '';
|
||||||
|
|
||||||
// parse response
|
// parse response
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createChatCompletion } from '../../../../ai/config';
|
import { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
||||||
|
import { getAIApi } from '../../../../ai/config';
|
||||||
import { filterGPTMessageByMaxTokens, loadRequestMessages } from '../../../../chat/utils';
|
import { filterGPTMessageByMaxTokens, loadRequestMessages } from '../../../../chat/utils';
|
||||||
import {
|
import {
|
||||||
ChatCompletion,
|
ChatCompletion,
|
||||||
@@ -21,12 +22,13 @@ import { DispatchFlowResponse, WorkflowResponseType } from '../../type';
|
|||||||
import { countGptMessagesTokens } from '../../../../../common/string/tiktoken/index';
|
import { countGptMessagesTokens } from '../../../../../common/string/tiktoken/index';
|
||||||
import { getNanoid, sliceStrStartEnd } from '@fastgpt/global/common/string/tools';
|
import { getNanoid, sliceStrStartEnd } from '@fastgpt/global/common/string/tools';
|
||||||
import { AIChatItemType } from '@fastgpt/global/core/chat/type';
|
import { AIChatItemType } from '@fastgpt/global/core/chat/type';
|
||||||
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
|
import { chats2GPTMessages, GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
|
||||||
import { formatToolResponse, initToolCallEdges, initToolNodes } from './utils';
|
import { formatToolResponse, initToolCallEdges, initToolNodes } from './utils';
|
||||||
import { computedMaxToken, llmCompletionsBodyFormat } from '../../../../ai/utils';
|
import { computedMaxToken, llmCompletionsBodyFormat } from '../../../../ai/utils';
|
||||||
import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants';
|
import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants';
|
||||||
import { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
import { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||||
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
|
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||||
|
import { i18nT } from '../../../../../../web/i18n/utils';
|
||||||
|
|
||||||
type FunctionRunResponseType = {
|
type FunctionRunResponseType = {
|
||||||
toolRunResponse: DispatchFlowResponse;
|
toolRunResponse: DispatchFlowResponse;
|
||||||
@@ -43,7 +45,7 @@ export const runToolWithFunctionCall = async (
|
|||||||
requestOrigin,
|
requestOrigin,
|
||||||
runtimeNodes,
|
runtimeNodes,
|
||||||
runtimeEdges,
|
runtimeEdges,
|
||||||
user,
|
node,
|
||||||
stream,
|
stream,
|
||||||
workflowStreamResponse,
|
workflowStreamResponse,
|
||||||
params: { temperature = 0, maxToken = 4000, aiChatVision }
|
params: { temperature = 0, maxToken = 4000, aiChatVision }
|
||||||
@@ -215,22 +217,17 @@ export const runToolWithFunctionCall = async (
|
|||||||
|
|
||||||
// console.log(JSON.stringify(requestMessages, null, 2));
|
// console.log(JSON.stringify(requestMessages, null, 2));
|
||||||
/* Run llm */
|
/* Run llm */
|
||||||
const {
|
const ai = getAIApi({
|
||||||
response: aiResponse,
|
timeout: 480000
|
||||||
isStreamResponse,
|
});
|
||||||
getEmptyResponseTip
|
const aiResponse = await ai.chat.completions.create(requestBody, {
|
||||||
} = await createChatCompletion({
|
headers: {
|
||||||
body: requestBody,
|
Accept: 'application/json, text/plain, */*'
|
||||||
userKey: user.openaiAccount,
|
|
||||||
options: {
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json, text/plain, */*'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const { answer, functionCalls } = await (async () => {
|
const { answer, functionCalls } = await (async () => {
|
||||||
if (res && isStreamResponse) {
|
if (res && stream) {
|
||||||
return streamResponse({
|
return streamResponse({
|
||||||
res,
|
res,
|
||||||
toolNodes,
|
toolNodes,
|
||||||
@@ -259,9 +256,6 @@ export const runToolWithFunctionCall = async (
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
if (!answer && functionCalls.length === 0) {
|
|
||||||
return Promise.reject(getEmptyResponseTip());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the selected tool.
|
// Run the selected tool.
|
||||||
const toolsRunResponse = (
|
const toolsRunResponse = (
|
||||||
@@ -555,5 +549,9 @@ async function streamResponse({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!textAnswer && functionCalls.length === 0) {
|
||||||
|
return Promise.reject(i18nT('chat:LLM_model_response_empty'));
|
||||||
|
}
|
||||||
|
|
||||||
return { answer: textAnswer, functionCalls };
|
return { answer: textAnswer, functionCalls };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import { getFileContentFromLinks, getHistoryFileLinks } from '../../tools/readFi
|
|||||||
import { parseUrlToFileType } from '@fastgpt/global/common/file/tools';
|
import { parseUrlToFileType } from '@fastgpt/global/common/file/tools';
|
||||||
import { Prompt_DocumentQuote } from '@fastgpt/global/core/ai/prompt/AIChat';
|
import { Prompt_DocumentQuote } from '@fastgpt/global/core/ai/prompt/AIChat';
|
||||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||||
import { postTextCensor } from '../../../../../common/api/requestPlusApi';
|
|
||||||
|
|
||||||
type Response = DispatchNodeResultType<{
|
type Response = DispatchNodeResultType<{
|
||||||
[NodeOutputKeyEnum.answerText]: string;
|
[NodeOutputKeyEnum.answerText]: string;
|
||||||
@@ -46,7 +45,6 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
|||||||
requestOrigin,
|
requestOrigin,
|
||||||
chatConfig,
|
chatConfig,
|
||||||
runningAppInfo: { teamId },
|
runningAppInfo: { teamId },
|
||||||
user,
|
|
||||||
params: {
|
params: {
|
||||||
model,
|
model,
|
||||||
systemPrompt,
|
systemPrompt,
|
||||||
@@ -152,15 +150,6 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
|||||||
return value;
|
return value;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// censor model and system key
|
|
||||||
if (toolModel.censor && !user.openaiAccount?.key) {
|
|
||||||
await postTextCensor({
|
|
||||||
text: `${systemPrompt}
|
|
||||||
${userChatInput}
|
|
||||||
`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
toolWorkflowInteractiveResponse,
|
toolWorkflowInteractiveResponse,
|
||||||
dispatchFlowResponse, // tool flow response
|
dispatchFlowResponse, // tool flow response
|
||||||
@@ -228,14 +217,13 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
|||||||
tokens: toolNodeTokens,
|
tokens: toolNodeTokens,
|
||||||
modelType: ModelTypeEnum.llm
|
modelType: ModelTypeEnum.llm
|
||||||
});
|
});
|
||||||
const toolAIUsage = user.openaiAccount?.key ? 0 : totalPoints;
|
|
||||||
|
|
||||||
// flat child tool response
|
// flat child tool response
|
||||||
const childToolResponse = dispatchFlowResponse.map((item) => item.flowResponses).flat();
|
const childToolResponse = dispatchFlowResponse.map((item) => item.flowResponses).flat();
|
||||||
|
|
||||||
// concat tool usage
|
// concat tool usage
|
||||||
const totalPointsUsage =
|
const totalPointsUsage =
|
||||||
toolAIUsage +
|
totalPoints +
|
||||||
dispatchFlowResponse.reduce((sum, item) => {
|
dispatchFlowResponse.reduce((sum, item) => {
|
||||||
const childrenTotal = item.flowUsages.reduce((sum, item) => sum + item.totalPoints, 0);
|
const childrenTotal = item.flowUsages.reduce((sum, item) => sum + item.totalPoints, 0);
|
||||||
return sum + childrenTotal;
|
return sum + childrenTotal;
|
||||||
@@ -252,7 +240,6 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
|||||||
.join(''),
|
.join(''),
|
||||||
[DispatchNodeResponseKeyEnum.assistantResponses]: previewAssistantResponses,
|
[DispatchNodeResponseKeyEnum.assistantResponses]: previewAssistantResponses,
|
||||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||||
// 展示的积分消耗
|
|
||||||
totalPoints: totalPointsUsage,
|
totalPoints: totalPointsUsage,
|
||||||
toolCallTokens: toolNodeTokens,
|
toolCallTokens: toolNodeTokens,
|
||||||
childTotalPoints: flatUsages.reduce((sum, item) => sum + item.totalPoints, 0),
|
childTotalPoints: flatUsages.reduce((sum, item) => sum + item.totalPoints, 0),
|
||||||
@@ -267,14 +254,12 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
|||||||
mergeSignId: nodeId
|
mergeSignId: nodeId
|
||||||
},
|
},
|
||||||
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
|
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
|
||||||
// 工具调用本身的积分消耗
|
|
||||||
{
|
{
|
||||||
moduleName: name,
|
moduleName: name,
|
||||||
totalPoints: toolAIUsage,
|
totalPoints,
|
||||||
model: modelName,
|
model: modelName,
|
||||||
tokens: toolNodeTokens
|
tokens: toolNodeTokens
|
||||||
},
|
},
|
||||||
// 工具的消耗
|
|
||||||
...flatUsages
|
...flatUsages
|
||||||
],
|
],
|
||||||
[DispatchNodeResponseKeyEnum.interactive]: toolWorkflowInteractiveResponse
|
[DispatchNodeResponseKeyEnum.interactive]: toolWorkflowInteractiveResponse
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createChatCompletion } from '../../../../ai/config';
|
import { getAIApi } from '../../../../ai/config';
|
||||||
import { filterGPTMessageByMaxTokens, loadRequestMessages } from '../../../../chat/utils';
|
import { filterGPTMessageByMaxTokens, loadRequestMessages } from '../../../../chat/utils';
|
||||||
import {
|
import {
|
||||||
ChatCompletion,
|
ChatCompletion,
|
||||||
@@ -29,6 +29,7 @@ import { WorkflowResponseType } from '../../type';
|
|||||||
import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants';
|
import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants';
|
||||||
import { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
import { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||||
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
|
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
|
||||||
|
import { i18nT } from '../../../../../../web/i18n/utils';
|
||||||
|
|
||||||
type FunctionCallCompletion = {
|
type FunctionCallCompletion = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -51,7 +52,7 @@ export const runToolWithPromptCall = async (
|
|||||||
requestOrigin,
|
requestOrigin,
|
||||||
runtimeNodes,
|
runtimeNodes,
|
||||||
runtimeEdges,
|
runtimeEdges,
|
||||||
user,
|
node,
|
||||||
stream,
|
stream,
|
||||||
workflowStreamResponse,
|
workflowStreamResponse,
|
||||||
params: { temperature = 0, maxToken = 4000, aiChatVision }
|
params: { temperature = 0, maxToken = 4000, aiChatVision }
|
||||||
@@ -224,19 +225,18 @@ export const runToolWithPromptCall = async (
|
|||||||
|
|
||||||
// console.log(JSON.stringify(requestMessages, null, 2));
|
// console.log(JSON.stringify(requestMessages, null, 2));
|
||||||
/* Run llm */
|
/* Run llm */
|
||||||
const {
|
const ai = getAIApi({
|
||||||
response: aiResponse,
|
timeout: 480000
|
||||||
isStreamResponse,
|
});
|
||||||
getEmptyResponseTip
|
const aiResponse = await ai.chat.completions.create(requestBody, {
|
||||||
} = await createChatCompletion({
|
headers: {
|
||||||
body: requestBody,
|
Accept: 'application/json, text/plain, */*'
|
||||||
userKey: user.openaiAccount,
|
|
||||||
options: {
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json, text/plain, */*'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const isStreamResponse =
|
||||||
|
typeof aiResponse === 'object' &&
|
||||||
|
aiResponse !== null &&
|
||||||
|
('iterator' in aiResponse || 'controller' in aiResponse);
|
||||||
|
|
||||||
const answer = await (async () => {
|
const answer = await (async () => {
|
||||||
if (res && isStreamResponse) {
|
if (res && isStreamResponse) {
|
||||||
@@ -254,11 +254,8 @@ export const runToolWithPromptCall = async (
|
|||||||
return result.choices?.[0]?.message?.content || '';
|
return result.choices?.[0]?.message?.content || '';
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
const { answer: replaceAnswer, toolJson } = parseAnswer(answer);
|
|
||||||
if (!answer && !toolJson) {
|
|
||||||
return Promise.reject(getEmptyResponseTip());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const { answer: replaceAnswer, toolJson } = parseAnswer(answer);
|
||||||
// No tools
|
// No tools
|
||||||
if (!toolJson) {
|
if (!toolJson) {
|
||||||
if (replaceAnswer === ERROR_TEXT) {
|
if (replaceAnswer === ERROR_TEXT) {
|
||||||
@@ -540,6 +537,9 @@ async function streamResponse({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!textAnswer) {
|
||||||
|
return Promise.reject(i18nT('chat:LLM_model_response_empty'));
|
||||||
|
}
|
||||||
return { answer: textAnswer.trim() };
|
return { answer: textAnswer.trim() };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createChatCompletion } from '../../../../ai/config';
|
import { getAIApi } from '../../../../ai/config';
|
||||||
import { filterGPTMessageByMaxTokens, loadRequestMessages } from '../../../../chat/utils';
|
import { filterGPTMessageByMaxTokens, loadRequestMessages } from '../../../../chat/utils';
|
||||||
import {
|
import {
|
||||||
ChatCompletion,
|
ChatCompletion,
|
||||||
@@ -92,7 +92,6 @@ export const runToolWithToolChoice = async (
|
|||||||
runtimeNodes,
|
runtimeNodes,
|
||||||
runtimeEdges,
|
runtimeEdges,
|
||||||
stream,
|
stream,
|
||||||
user,
|
|
||||||
workflowStreamResponse,
|
workflowStreamResponse,
|
||||||
params: { temperature = 0, maxToken = 4000, aiChatVision }
|
params: { temperature = 0, maxToken = 4000, aiChatVision }
|
||||||
} = workflowProps;
|
} = workflowProps;
|
||||||
@@ -272,272 +271,277 @@ export const runToolWithToolChoice = async (
|
|||||||
);
|
);
|
||||||
// console.log(JSON.stringify(requestBody, null, 2), '==requestBody');
|
// console.log(JSON.stringify(requestBody, null, 2), '==requestBody');
|
||||||
/* Run llm */
|
/* Run llm */
|
||||||
const {
|
const ai = getAIApi({
|
||||||
response: aiResponse,
|
timeout: 480000
|
||||||
isStreamResponse,
|
});
|
||||||
getEmptyResponseTip
|
|
||||||
} = await createChatCompletion({
|
try {
|
||||||
body: requestBody,
|
const aiResponse = await ai.chat.completions.create(requestBody, {
|
||||||
userKey: user.openaiAccount,
|
|
||||||
options: {
|
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json, text/plain, */*'
|
Accept: 'application/json, text/plain, */*'
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
const isStreamResponse =
|
||||||
|
typeof aiResponse === 'object' &&
|
||||||
|
aiResponse !== null &&
|
||||||
|
('iterator' in aiResponse || 'controller' in aiResponse);
|
||||||
|
|
||||||
const { answer, toolCalls } = await (async () => {
|
const { answer, toolCalls } = await (async () => {
|
||||||
if (res && isStreamResponse) {
|
if (res && isStreamResponse) {
|
||||||
return streamResponse({
|
return streamResponse({
|
||||||
res,
|
res,
|
||||||
workflowStreamResponse,
|
workflowStreamResponse,
|
||||||
toolNodes,
|
toolNodes,
|
||||||
stream: aiResponse
|
stream: aiResponse
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const result = aiResponse as ChatCompletion;
|
const result = aiResponse as ChatCompletion;
|
||||||
const calls = result.choices?.[0]?.message?.tool_calls || [];
|
const calls = result.choices?.[0]?.message?.tool_calls || [];
|
||||||
const answer = result.choices?.[0]?.message?.content || '';
|
const answer = result.choices?.[0]?.message?.content || '';
|
||||||
|
|
||||||
// 加上name和avatar
|
// 加上name和avatar
|
||||||
const toolCalls = calls.map((tool) => {
|
const toolCalls = calls.map((tool) => {
|
||||||
const toolNode = toolNodes.find((item) => item.nodeId === tool.function?.name);
|
const toolNode = toolNodes.find((item) => item.nodeId === tool.function?.name);
|
||||||
return {
|
return {
|
||||||
...tool,
|
...tool,
|
||||||
toolName: toolNode?.name || '',
|
toolName: toolNode?.name || '',
|
||||||
toolAvatar: toolNode?.avatar || ''
|
toolAvatar: toolNode?.avatar || ''
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// 不支持 stream 模式的模型的流失响应
|
// 不支持 stream 模式的模型的流失响应
|
||||||
toolCalls.forEach((tool) => {
|
toolCalls.forEach((tool) => {
|
||||||
workflowStreamResponse?.({
|
workflowStreamResponse?.({
|
||||||
event: SseResponseEventEnum.toolCall,
|
event: SseResponseEventEnum.toolCall,
|
||||||
data: {
|
data: {
|
||||||
tool: {
|
tool: {
|
||||||
id: tool.id,
|
id: tool.id,
|
||||||
toolName: tool.toolName,
|
toolName: tool.toolName,
|
||||||
toolAvatar: tool.toolAvatar,
|
toolAvatar: tool.toolAvatar,
|
||||||
functionName: tool.function.name,
|
functionName: tool.function.name,
|
||||||
params: tool.function?.arguments ?? '',
|
params: tool.function?.arguments ?? '',
|
||||||
response: ''
|
response: ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
if (answer) {
|
|
||||||
workflowStreamResponse?.({
|
|
||||||
event: SseResponseEventEnum.fastAnswer,
|
|
||||||
data: textAdaptGptResponse({
|
|
||||||
text: answer
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
answer,
|
|
||||||
toolCalls: toolCalls
|
|
||||||
};
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
if (!answer && toolCalls.length === 0) {
|
|
||||||
return Promise.reject(getEmptyResponseTip());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the selected tool by LLM.
|
|
||||||
const toolsRunResponse = (
|
|
||||||
await Promise.all(
|
|
||||||
toolCalls.map(async (tool) => {
|
|
||||||
const toolNode = toolNodes.find((item) => item.nodeId === tool.function?.name);
|
|
||||||
|
|
||||||
if (!toolNode) return;
|
|
||||||
|
|
||||||
const startParams = (() => {
|
|
||||||
try {
|
|
||||||
return json5.parse(tool.function.arguments);
|
|
||||||
} catch (error) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
initToolNodes(runtimeNodes, [toolNode.nodeId], startParams);
|
|
||||||
const toolRunResponse = await dispatchWorkFlow({
|
|
||||||
...workflowProps,
|
|
||||||
isToolCall: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const stringToolResponse = formatToolResponse(toolRunResponse.toolResponses);
|
|
||||||
|
|
||||||
const toolMsgParams: ChatCompletionToolMessageParam = {
|
|
||||||
tool_call_id: tool.id,
|
|
||||||
role: ChatCompletionRequestMessageRoleEnum.Tool,
|
|
||||||
name: tool.function.name,
|
|
||||||
content: stringToolResponse
|
|
||||||
};
|
|
||||||
|
|
||||||
workflowStreamResponse?.({
|
|
||||||
event: SseResponseEventEnum.toolResponse,
|
|
||||||
data: {
|
|
||||||
tool: {
|
|
||||||
id: tool.id,
|
|
||||||
toolName: '',
|
|
||||||
toolAvatar: '',
|
|
||||||
params: '',
|
|
||||||
response: sliceStrStartEnd(stringToolResponse, 5000, 5000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
if (answer) {
|
||||||
|
workflowStreamResponse?.({
|
||||||
|
event: SseResponseEventEnum.fastAnswer,
|
||||||
|
data: textAdaptGptResponse({
|
||||||
|
text: answer
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
toolRunResponse,
|
answer,
|
||||||
toolMsgParams
|
toolCalls: toolCalls
|
||||||
};
|
};
|
||||||
})
|
|
||||||
)
|
|
||||||
).filter(Boolean) as ToolRunResponseType;
|
|
||||||
|
|
||||||
const flatToolsResponseData = toolsRunResponse.map((item) => item.toolRunResponse).flat();
|
|
||||||
// concat tool responses
|
|
||||||
const dispatchFlowResponse = response
|
|
||||||
? response.dispatchFlowResponse.concat(flatToolsResponseData)
|
|
||||||
: flatToolsResponseData;
|
|
||||||
|
|
||||||
if (toolCalls.length > 0 && !res?.closed) {
|
|
||||||
// Run the tool, combine its results, and perform another round of AI calls
|
|
||||||
const assistantToolMsgParams: ChatCompletionAssistantMessageParam[] = [
|
|
||||||
...(answer
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
role: ChatCompletionRequestMessageRoleEnum.Assistant as 'assistant',
|
|
||||||
content: answer
|
|
||||||
}
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
{
|
|
||||||
role: ChatCompletionRequestMessageRoleEnum.Assistant,
|
|
||||||
tool_calls: toolCalls
|
|
||||||
}
|
}
|
||||||
];
|
})();
|
||||||
|
|
||||||
/*
|
// Run the selected tool by LLM.
|
||||||
|
const toolsRunResponse = (
|
||||||
|
await Promise.all(
|
||||||
|
toolCalls.map(async (tool) => {
|
||||||
|
const toolNode = toolNodes.find((item) => item.nodeId === tool.function?.name);
|
||||||
|
|
||||||
|
if (!toolNode) return;
|
||||||
|
|
||||||
|
const startParams = (() => {
|
||||||
|
try {
|
||||||
|
return json5.parse(tool.function.arguments);
|
||||||
|
} catch (error) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
initToolNodes(runtimeNodes, [toolNode.nodeId], startParams);
|
||||||
|
const toolRunResponse = await dispatchWorkFlow({
|
||||||
|
...workflowProps,
|
||||||
|
isToolCall: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const stringToolResponse = formatToolResponse(toolRunResponse.toolResponses);
|
||||||
|
|
||||||
|
const toolMsgParams: ChatCompletionToolMessageParam = {
|
||||||
|
tool_call_id: tool.id,
|
||||||
|
role: ChatCompletionRequestMessageRoleEnum.Tool,
|
||||||
|
name: tool.function.name,
|
||||||
|
content: stringToolResponse
|
||||||
|
};
|
||||||
|
|
||||||
|
workflowStreamResponse?.({
|
||||||
|
event: SseResponseEventEnum.toolResponse,
|
||||||
|
data: {
|
||||||
|
tool: {
|
||||||
|
id: tool.id,
|
||||||
|
toolName: '',
|
||||||
|
toolAvatar: '',
|
||||||
|
params: '',
|
||||||
|
response: sliceStrStartEnd(stringToolResponse, 5000, 5000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
toolRunResponse,
|
||||||
|
toolMsgParams
|
||||||
|
};
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).filter(Boolean) as ToolRunResponseType;
|
||||||
|
|
||||||
|
const flatToolsResponseData = toolsRunResponse.map((item) => item.toolRunResponse).flat();
|
||||||
|
// concat tool responses
|
||||||
|
const dispatchFlowResponse = response
|
||||||
|
? response.dispatchFlowResponse.concat(flatToolsResponseData)
|
||||||
|
: flatToolsResponseData;
|
||||||
|
|
||||||
|
if (toolCalls.length > 0 && !res?.closed) {
|
||||||
|
// Run the tool, combine its results, and perform another round of AI calls
|
||||||
|
const assistantToolMsgParams: ChatCompletionAssistantMessageParam[] = [
|
||||||
|
...(answer
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
role: ChatCompletionRequestMessageRoleEnum.Assistant as 'assistant',
|
||||||
|
content: answer
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
{
|
||||||
|
role: ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||||
|
tool_calls: toolCalls
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
/*
|
||||||
...
|
...
|
||||||
user
|
user
|
||||||
assistant: tool data
|
assistant: tool data
|
||||||
*/
|
*/
|
||||||
const concatToolMessages = [
|
const concatToolMessages = [
|
||||||
...requestMessages,
|
...requestMessages,
|
||||||
...assistantToolMsgParams
|
...assistantToolMsgParams
|
||||||
] as ChatCompletionMessageParam[];
|
] as ChatCompletionMessageParam[];
|
||||||
|
|
||||||
// Only toolCall tokens are counted here, Tool response tokens count towards the next reply
|
// Only toolCall tokens are counted here, Tool response tokens count towards the next reply
|
||||||
const tokens = await countGptMessagesTokens(concatToolMessages, tools);
|
const tokens = await countGptMessagesTokens(concatToolMessages, tools);
|
||||||
/*
|
/*
|
||||||
...
|
...
|
||||||
user
|
user
|
||||||
assistant: tool data
|
assistant: tool data
|
||||||
tool: tool response
|
tool: tool response
|
||||||
*/
|
*/
|
||||||
const completeMessages = [
|
const completeMessages = [
|
||||||
...concatToolMessages,
|
...concatToolMessages,
|
||||||
...toolsRunResponse.map((item) => item?.toolMsgParams)
|
...toolsRunResponse.map((item) => item?.toolMsgParams)
|
||||||
];
|
];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Get tool node assistant response
|
Get tool node assistant response
|
||||||
history assistant
|
history assistant
|
||||||
current tool assistant
|
current tool assistant
|
||||||
tool child assistant
|
tool child assistant
|
||||||
*/
|
*/
|
||||||
const toolNodeAssistant = GPTMessages2Chats([
|
const toolNodeAssistant = GPTMessages2Chats([
|
||||||
...assistantToolMsgParams,
|
...assistantToolMsgParams,
|
||||||
...toolsRunResponse.map((item) => item?.toolMsgParams)
|
...toolsRunResponse.map((item) => item?.toolMsgParams)
|
||||||
])[0] as AIChatItemType;
|
])[0] as AIChatItemType;
|
||||||
const toolChildAssistants = flatToolsResponseData
|
const toolChildAssistants = flatToolsResponseData
|
||||||
.map((item) => item.assistantResponses)
|
.map((item) => item.assistantResponses)
|
||||||
.flat()
|
.flat()
|
||||||
.filter((item) => item.type !== ChatItemValueTypeEnum.interactive); // 交互节点留着下次记录
|
.filter((item) => item.type !== ChatItemValueTypeEnum.interactive); // 交互节点留着下次记录
|
||||||
const toolNodeAssistants = [
|
const toolNodeAssistants = [
|
||||||
...assistantResponses,
|
...assistantResponses,
|
||||||
...toolNodeAssistant.value,
|
...toolNodeAssistant.value,
|
||||||
...toolChildAssistants
|
...toolChildAssistants
|
||||||
];
|
];
|
||||||
|
|
||||||
const runTimes =
|
const runTimes =
|
||||||
(response?.runTimes || 0) +
|
(response?.runTimes || 0) +
|
||||||
flatToolsResponseData.reduce((sum, item) => sum + item.runTimes, 0);
|
flatToolsResponseData.reduce((sum, item) => sum + item.runTimes, 0);
|
||||||
const toolNodeTokens = response ? response.toolNodeTokens + tokens : tokens;
|
const toolNodeTokens = response ? response.toolNodeTokens + tokens : tokens;
|
||||||
|
|
||||||
// Check stop signal
|
// Check stop signal
|
||||||
const hasStopSignal = flatToolsResponseData.some(
|
const hasStopSignal = flatToolsResponseData.some(
|
||||||
(item) => !!item.flowResponses?.find((item) => item.toolStop)
|
(item) => !!item.flowResponses?.find((item) => item.toolStop)
|
||||||
);
|
);
|
||||||
// Check interactive response(Only 1 interaction is reserved)
|
// Check interactive response(Only 1 interaction is reserved)
|
||||||
const workflowInteractiveResponseItem = toolsRunResponse.find(
|
const workflowInteractiveResponseItem = toolsRunResponse.find(
|
||||||
(item) => item.toolRunResponse.workflowInteractiveResponse
|
(item) => item.toolRunResponse.workflowInteractiveResponse
|
||||||
);
|
);
|
||||||
if (hasStopSignal || workflowInteractiveResponseItem) {
|
if (hasStopSignal || workflowInteractiveResponseItem) {
|
||||||
// Get interactive tool data
|
// Get interactive tool data
|
||||||
const workflowInteractiveResponse =
|
const workflowInteractiveResponse =
|
||||||
workflowInteractiveResponseItem?.toolRunResponse.workflowInteractiveResponse;
|
workflowInteractiveResponseItem?.toolRunResponse.workflowInteractiveResponse;
|
||||||
|
|
||||||
// Flashback traverses completeMessages, intercepting messages that know the first user
|
// Flashback traverses completeMessages, intercepting messages that know the first user
|
||||||
const firstUserIndex = completeMessages.findLastIndex((item) => item.role === 'user');
|
const firstUserIndex = completeMessages.findLastIndex((item) => item.role === 'user');
|
||||||
const newMessages = completeMessages.slice(firstUserIndex + 1);
|
const newMessages = completeMessages.slice(firstUserIndex + 1);
|
||||||
|
|
||||||
const toolWorkflowInteractiveResponse: WorkflowInteractiveResponseType | undefined =
|
const toolWorkflowInteractiveResponse: WorkflowInteractiveResponseType | undefined =
|
||||||
workflowInteractiveResponse
|
workflowInteractiveResponse
|
||||||
? {
|
? {
|
||||||
...workflowInteractiveResponse,
|
...workflowInteractiveResponse,
|
||||||
toolParams: {
|
toolParams: {
|
||||||
entryNodeIds: workflowInteractiveResponse.entryNodeIds,
|
entryNodeIds: workflowInteractiveResponse.entryNodeIds,
|
||||||
toolCallId: workflowInteractiveResponseItem?.toolMsgParams.tool_call_id,
|
toolCallId: workflowInteractiveResponseItem?.toolMsgParams.tool_call_id,
|
||||||
memoryMessages: newMessages
|
memoryMessages: newMessages
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
: undefined;
|
||||||
: undefined;
|
|
||||||
|
return {
|
||||||
|
dispatchFlowResponse,
|
||||||
|
toolNodeTokens,
|
||||||
|
completeMessages,
|
||||||
|
assistantResponses: toolNodeAssistants,
|
||||||
|
runTimes,
|
||||||
|
toolWorkflowInteractiveResponse
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return runToolWithToolChoice(
|
||||||
|
{
|
||||||
|
...props,
|
||||||
|
maxRunToolTimes: maxRunToolTimes - 1,
|
||||||
|
messages: completeMessages
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dispatchFlowResponse,
|
||||||
|
toolNodeTokens,
|
||||||
|
assistantResponses: toolNodeAssistants,
|
||||||
|
runTimes
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// No tool is invoked, indicating that the process is over
|
||||||
|
const gptAssistantResponse: ChatCompletionAssistantMessageParam = {
|
||||||
|
role: ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||||
|
content: answer
|
||||||
|
};
|
||||||
|
const completeMessages = filterMessages.concat(gptAssistantResponse);
|
||||||
|
const tokens = await countGptMessagesTokens(completeMessages, tools);
|
||||||
|
|
||||||
|
// concat tool assistant
|
||||||
|
const toolNodeAssistant = GPTMessages2Chats([gptAssistantResponse])[0] as AIChatItemType;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dispatchFlowResponse,
|
dispatchFlowResponse: response?.dispatchFlowResponse || [],
|
||||||
toolNodeTokens,
|
toolNodeTokens: response ? response.toolNodeTokens + tokens : tokens,
|
||||||
completeMessages,
|
completeMessages,
|
||||||
assistantResponses: toolNodeAssistants,
|
assistantResponses: [...assistantResponses, ...toolNodeAssistant.value],
|
||||||
runTimes,
|
runTimes: (response?.runTimes || 0) + 1
|
||||||
toolWorkflowInteractiveResponse
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
return runToolWithToolChoice(
|
console.log(error);
|
||||||
{
|
addLog.warn(`LLM response error`, {
|
||||||
...props,
|
requestBody
|
||||||
maxRunToolTimes: maxRunToolTimes - 1,
|
});
|
||||||
messages: completeMessages
|
return Promise.reject(error);
|
||||||
},
|
|
||||||
{
|
|
||||||
dispatchFlowResponse,
|
|
||||||
toolNodeTokens,
|
|
||||||
assistantResponses: toolNodeAssistants,
|
|
||||||
runTimes
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// No tool is invoked, indicating that the process is over
|
|
||||||
const gptAssistantResponse: ChatCompletionAssistantMessageParam = {
|
|
||||||
role: ChatCompletionRequestMessageRoleEnum.Assistant,
|
|
||||||
content: answer
|
|
||||||
};
|
|
||||||
const completeMessages = filterMessages.concat(gptAssistantResponse);
|
|
||||||
const tokens = await countGptMessagesTokens(completeMessages, tools);
|
|
||||||
|
|
||||||
// concat tool assistant
|
|
||||||
const toolNodeAssistant = GPTMessages2Chats([gptAssistantResponse])[0] as AIChatItemType;
|
|
||||||
|
|
||||||
return {
|
|
||||||
dispatchFlowResponse: response?.dispatchFlowResponse || [],
|
|
||||||
toolNodeTokens: response ? response.toolNodeTokens + tokens : tokens,
|
|
||||||
completeMessages,
|
|
||||||
assistantResponses: [...assistantResponses, ...toolNodeAssistant.value],
|
|
||||||
runTimes: (response?.runTimes || 0) + 1
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -652,5 +656,9 @@ async function streamResponse({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!textAnswer && toolCalls.length === 0) {
|
||||||
|
return Promise.reject(i18nT('chat:LLM_model_response_empty'));
|
||||||
|
}
|
||||||
|
|
||||||
return { answer: textAnswer, toolCalls };
|
return { answer: textAnswer, toolCalls };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { ChatItemType, UserChatItemValueItemType } from '@fastgpt/global/co
|
|||||||
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||||
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
|
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||||
import { createChatCompletion } from '../../../ai/config';
|
import { getAIApi } from '../../../ai/config';
|
||||||
import type { ChatCompletion, StreamChatType } from '@fastgpt/global/core/ai/type.d';
|
import type { ChatCompletion, StreamChatType } from '@fastgpt/global/core/ai/type.d';
|
||||||
import { formatModelChars2Points } from '../../../../support/wallet/usage/utils';
|
import { formatModelChars2Points } from '../../../../support/wallet/usage/utils';
|
||||||
import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
||||||
@@ -33,13 +33,15 @@ import { getLLMModel, ModelTypeEnum } from '../../../ai/model';
|
|||||||
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
||||||
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||||
import { checkQuoteQAValue, getHistories } from '../utils';
|
import { getHistories } from '../utils';
|
||||||
import { filterSearchResultsByMaxChars } from '../../utils';
|
import { filterSearchResultsByMaxChars } from '../../utils';
|
||||||
import { getHistoryPreview } from '@fastgpt/global/core/chat/utils';
|
import { getHistoryPreview } from '@fastgpt/global/core/chat/utils';
|
||||||
|
import { addLog } from '../../../../common/system/log';
|
||||||
import { computedMaxToken, llmCompletionsBodyFormat } from '../../../ai/utils';
|
import { computedMaxToken, llmCompletionsBodyFormat } from '../../../ai/utils';
|
||||||
import { WorkflowResponseType } from '../type';
|
import { WorkflowResponseType } from '../type';
|
||||||
import { formatTime2YMDHM } from '@fastgpt/global/common/string/time';
|
import { formatTime2YMDHM } from '@fastgpt/global/common/string/time';
|
||||||
import { AiChatQuoteRoleType } from '@fastgpt/global/core/workflow/template/system/aiChat/type';
|
import { AiChatQuoteRoleType } from '@fastgpt/global/core/workflow/template/system/aiChat/type';
|
||||||
|
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||||
import { getFileContentFromLinks, getHistoryFileLinks } from '../tools/readFiles';
|
import { getFileContentFromLinks, getHistoryFileLinks } from '../tools/readFiles';
|
||||||
import { parseUrlToFileType } from '@fastgpt/global/common/file/tools';
|
import { parseUrlToFileType } from '@fastgpt/global/common/file/tools';
|
||||||
import { i18nT } from '../../../../../web/i18n/utils';
|
import { i18nT } from '../../../../../web/i18n/utils';
|
||||||
@@ -91,7 +93,6 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
|||||||
stream = stream && isResponseAnswerText;
|
stream = stream && isResponseAnswerText;
|
||||||
|
|
||||||
const chatHistories = getHistories(history, histories);
|
const chatHistories = getHistories(history, histories);
|
||||||
quoteQA = checkQuoteQAValue(quoteQA);
|
|
||||||
|
|
||||||
const modelConstantsData = getLLMModel(model);
|
const modelConstantsData = getLLMModel(model);
|
||||||
if (!modelConstantsData) {
|
if (!modelConstantsData) {
|
||||||
@@ -137,6 +138,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
|||||||
if (modelConstantsData.censor && !user.openaiAccount?.key) {
|
if (modelConstantsData.censor && !user.openaiAccount?.key) {
|
||||||
return postTextCensor({
|
return postTextCensor({
|
||||||
text: `${systemPrompt}
|
text: `${systemPrompt}
|
||||||
|
${datasetQuoteText}
|
||||||
${userChatInput}
|
${userChatInput}
|
||||||
`
|
`
|
||||||
});
|
});
|
||||||
@@ -168,91 +170,109 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
|||||||
modelConstantsData
|
modelConstantsData
|
||||||
);
|
);
|
||||||
// console.log(JSON.stringify(requestBody, null, 2), '===');
|
// console.log(JSON.stringify(requestBody, null, 2), '===');
|
||||||
const { response, isStreamResponse, getEmptyResponseTip } = await createChatCompletion({
|
try {
|
||||||
body: requestBody,
|
const ai = getAIApi({
|
||||||
userKey: user.openaiAccount,
|
userKey: user.openaiAccount,
|
||||||
options: {
|
timeout: 480000
|
||||||
|
});
|
||||||
|
const response = await ai.chat.completions.create(requestBody, {
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json, text/plain, */*'
|
Accept: 'application/json, text/plain, */*'
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const { answerText } = await (async () => {
|
const isStreamResponse =
|
||||||
if (res && isStreamResponse) {
|
typeof response === 'object' &&
|
||||||
// sse response
|
response !== null &&
|
||||||
const { answer } = await streamResponse({
|
('iterator' in response || 'controller' in response);
|
||||||
res,
|
|
||||||
stream: response,
|
|
||||||
workflowStreamResponse
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
const { answerText } = await (async () => {
|
||||||
answerText: answer
|
if (res && isStreamResponse) {
|
||||||
};
|
// sse response
|
||||||
} else {
|
const { answer } = await streamResponse({
|
||||||
const unStreamResponse = response as ChatCompletion;
|
res,
|
||||||
const answer = unStreamResponse.choices?.[0]?.message?.content || '';
|
stream: response,
|
||||||
|
workflowStreamResponse
|
||||||
if (stream) {
|
|
||||||
// Some models do not support streaming
|
|
||||||
workflowStreamResponse?.({
|
|
||||||
event: SseResponseEventEnum.fastAnswer,
|
|
||||||
data: textAdaptGptResponse({
|
|
||||||
text: answer
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!answer) {
|
||||||
|
return Promise.reject(i18nT('chat:LLM_model_response_empty'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
answerText: answer
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const unStreamResponse = response as ChatCompletion;
|
||||||
|
const answer = unStreamResponse.choices?.[0]?.message?.content || '';
|
||||||
|
|
||||||
|
if (stream) {
|
||||||
|
// Some models do not support streaming
|
||||||
|
workflowStreamResponse?.({
|
||||||
|
event: SseResponseEventEnum.fastAnswer,
|
||||||
|
data: textAdaptGptResponse({
|
||||||
|
text: answer
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
answerText: answer
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
return {
|
const completeMessages = requestMessages.concat({
|
||||||
answerText: answer
|
role: ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||||
};
|
content: answerText
|
||||||
}
|
});
|
||||||
})();
|
const chatCompleteMessages = GPTMessages2Chats(completeMessages);
|
||||||
|
|
||||||
if (!answerText) {
|
const tokens = await countMessagesTokens(chatCompleteMessages);
|
||||||
return Promise.reject(getEmptyResponseTip());
|
const { totalPoints, modelName } = formatModelChars2Points({
|
||||||
}
|
model,
|
||||||
|
|
||||||
const completeMessages = requestMessages.concat({
|
|
||||||
role: ChatCompletionRequestMessageRoleEnum.Assistant,
|
|
||||||
content: answerText
|
|
||||||
});
|
|
||||||
const chatCompleteMessages = GPTMessages2Chats(completeMessages);
|
|
||||||
|
|
||||||
const tokens = await countMessagesTokens(chatCompleteMessages);
|
|
||||||
const { totalPoints, modelName } = formatModelChars2Points({
|
|
||||||
model,
|
|
||||||
tokens,
|
|
||||||
modelType: ModelTypeEnum.llm
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
answerText,
|
|
||||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
|
||||||
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
|
|
||||||
model: modelName,
|
|
||||||
tokens,
|
tokens,
|
||||||
query: `${userChatInput}`,
|
modelType: ModelTypeEnum.llm
|
||||||
maxToken: max_tokens,
|
});
|
||||||
historyPreview: getHistoryPreview(
|
|
||||||
chatCompleteMessages,
|
return {
|
||||||
10000,
|
answerText,
|
||||||
modelConstantsData.vision && aiChatVision
|
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||||
),
|
|
||||||
contextTotalLen: completeMessages.length
|
|
||||||
},
|
|
||||||
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
|
|
||||||
{
|
|
||||||
moduleName: name,
|
|
||||||
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
|
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
|
||||||
model: modelName,
|
model: modelName,
|
||||||
tokens
|
tokens,
|
||||||
}
|
query: `${userChatInput}`,
|
||||||
],
|
maxToken: max_tokens,
|
||||||
[DispatchNodeResponseKeyEnum.toolResponses]: answerText,
|
historyPreview: getHistoryPreview(
|
||||||
history: chatCompleteMessages
|
chatCompleteMessages,
|
||||||
};
|
10000,
|
||||||
|
modelConstantsData.vision && aiChatVision
|
||||||
|
),
|
||||||
|
contextTotalLen: completeMessages.length
|
||||||
|
},
|
||||||
|
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
|
||||||
|
{
|
||||||
|
moduleName: name,
|
||||||
|
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
|
||||||
|
model: modelName,
|
||||||
|
tokens
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[DispatchNodeResponseKeyEnum.toolResponses]: answerText,
|
||||||
|
history: chatCompleteMessages
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
addLog.warn(`LLM response error`, {
|
||||||
|
baseUrl: user.openaiAccount?.baseUrl,
|
||||||
|
requestBody
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user.openaiAccount?.baseUrl) {
|
||||||
|
return Promise.reject(`您的 OpenAI key 出错了: ${getErrText(error)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async function filterDatasetQuote({
|
async function filterDatasetQuote({
|
||||||
|
|||||||
@@ -65,17 +65,7 @@ export async function dispatchDatasetSearch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!userChatInput) {
|
if (!userChatInput) {
|
||||||
return {
|
return Promise.reject(i18nT('common:core.chat.error.User input empty'));
|
||||||
quoteQA: [],
|
|
||||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
|
||||||
totalPoints: 0,
|
|
||||||
query: '',
|
|
||||||
limit,
|
|
||||||
searchMode
|
|
||||||
},
|
|
||||||
nodeDispatchUsages: [],
|
|
||||||
[DispatchNodeResponseKeyEnum.toolResponses]: []
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// query extension
|
// query extension
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
textAdaptGptResponse,
|
textAdaptGptResponse,
|
||||||
replaceEditorVariable
|
replaceEditorVariable
|
||||||
} from '@fastgpt/global/core/workflow/runtime/utils';
|
} from '@fastgpt/global/core/workflow/runtime/utils';
|
||||||
|
import { getSystemPluginCb } from '../../../../../plugins/register';
|
||||||
import { ContentTypes } from '@fastgpt/global/core/workflow/constants';
|
import { ContentTypes } from '@fastgpt/global/core/workflow/constants';
|
||||||
import { uploadFileFromBase64Img } from '../../../../common/file/gridfs/controller';
|
import { uploadFileFromBase64Img } from '../../../../common/file/gridfs/controller';
|
||||||
import { ReadFileBaseUrl } from '@fastgpt/global/common/file/constants';
|
import { ReadFileBaseUrl } from '@fastgpt/global/common/file/constants';
|
||||||
@@ -208,7 +209,7 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { formatResponse, rawResponse } = await (async () => {
|
const { formatResponse, rawResponse } = await (async () => {
|
||||||
const systemPluginCb = global.systemPluginCb;
|
const systemPluginCb = await getSystemPluginCb();
|
||||||
if (systemPluginCb[httpReqUrl]) {
|
if (systemPluginCb[httpReqUrl]) {
|
||||||
const pluginResult = await replaceSystemPluginResponse({
|
const pluginResult = await replaceSystemPluginResponse({
|
||||||
response: await systemPluginCb[httpReqUrl](requestBody),
|
response: await systemPluginCb[httpReqUrl](requestBody),
|
||||||
@@ -394,7 +395,7 @@ async function replaceSystemPluginResponse({
|
|||||||
response[key] = `${ReadFileBaseUrl}/${filename}?token=${await createFileToken({
|
response[key] = `${ReadFileBaseUrl}/${filename}?token=${await createFileToken({
|
||||||
bucketName: 'chat',
|
bucketName: 'chat',
|
||||||
teamId,
|
teamId,
|
||||||
uid: tmbId,
|
tmbId,
|
||||||
fileId
|
fileId
|
||||||
})}`;
|
})}`;
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import { responseWrite } from '../../../common/response';
|
|||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||||
import { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
|
||||||
|
|
||||||
export const getWorkflowResponseWrite = ({
|
export const getWorkflowResponseWrite = ({
|
||||||
res,
|
res,
|
||||||
@@ -88,6 +87,27 @@ export const filterToolNodeIdByEdges = ({
|
|||||||
.map((edge) => edge.target);
|
.map((edge) => edge.target);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// export const checkTheModuleConnectedByTool = (
|
||||||
|
// modules: StoreNodeItemType[],
|
||||||
|
// node: StoreNodeItemType
|
||||||
|
// ) => {
|
||||||
|
// let sign = false;
|
||||||
|
// const toolModules = modules.filter((item) => item.flowNodeType === FlowNodeTypeEnum.tools);
|
||||||
|
|
||||||
|
// toolModules.forEach((item) => {
|
||||||
|
// const toolOutput = item.outputs.find(
|
||||||
|
// (output) => output.key === NodeOutputKeyEnum.selectedTools
|
||||||
|
// );
|
||||||
|
// toolOutput?.targets.forEach((target) => {
|
||||||
|
// if (target.moduleId === node.moduleId) {
|
||||||
|
// sign = true;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
// return sign;
|
||||||
|
// };
|
||||||
|
|
||||||
export const getHistories = (history?: ChatItemType[] | number, histories: ChatItemType[] = []) => {
|
export const getHistories = (history?: ChatItemType[] | number, histories: ChatItemType[] = []) => {
|
||||||
if (!history) return [];
|
if (!history) return [];
|
||||||
|
|
||||||
@@ -129,17 +149,6 @@ export const valueTypeFormat = (value: any, type?: WorkflowIOValueTypeEnum) => {
|
|||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const checkQuoteQAValue = (quoteQA?: SearchDataResponseItemType[]) => {
|
|
||||||
if (!quoteQA) return undefined;
|
|
||||||
if (quoteQA.length === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
if (quoteQA.some((item) => !item.q || !item.datasetId)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return quoteQA;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* remove system variable */
|
/* remove system variable */
|
||||||
export const removeSystemVariable = (variables: Record<string, any>) => {
|
export const removeSystemVariable = (variables: Record<string, any>) => {
|
||||||
const copyVariables = { ...variables };
|
const copyVariables = { ...variables };
|
||||||
|
|||||||
@@ -48,8 +48,7 @@ const OutLinkSchema = new Schema({
|
|||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
showNodeStatus: {
|
showNodeStatus: {
|
||||||
type: Boolean,
|
type: Boolean
|
||||||
default: true
|
|
||||||
},
|
},
|
||||||
showRawSource: {
|
showRawSource: {
|
||||||
type: Boolean
|
type: Boolean
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
|||||||
import { OwnerPermissionVal, ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
import { OwnerPermissionVal, ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||||
import { Permission } from '@fastgpt/global/support/permission/controller';
|
import { Permission } from '@fastgpt/global/support/permission/controller';
|
||||||
|
|
||||||
export const authCollectionFile = async ({
|
export async function authFile({
|
||||||
fileId,
|
fileId,
|
||||||
per = OwnerPermissionVal,
|
per = OwnerPermissionVal,
|
||||||
...props
|
...props
|
||||||
@@ -17,7 +17,7 @@ export const authCollectionFile = async ({
|
|||||||
AuthResponseType & {
|
AuthResponseType & {
|
||||||
file: DatasetFileSchema;
|
file: DatasetFileSchema;
|
||||||
}
|
}
|
||||||
> => {
|
> {
|
||||||
const authRes = await parseHeaderCert(props);
|
const authRes = await parseHeaderCert(props);
|
||||||
const { teamId, tmbId } = authRes;
|
const { teamId, tmbId } = authRes;
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ export const authCollectionFile = async ({
|
|||||||
|
|
||||||
const permission = new Permission({
|
const permission = new Permission({
|
||||||
per: ReadPermissionVal,
|
per: ReadPermissionVal,
|
||||||
isOwner: file.metadata?.uid === tmbId || file.metadata?.tmbId === tmbId
|
isOwner: file.metadata?.tmbId === tmbId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!permission.checkPer(per)) {
|
if (!permission.checkPer(per)) {
|
||||||
@@ -45,4 +45,4 @@ export const authCollectionFile = async ({
|
|||||||
permission,
|
permission,
|
||||||
file
|
file
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -413,8 +413,7 @@ export const createFileToken = (data: FileTokenQuery) => {
|
|||||||
return Promise.reject('System unset FILE_TOKEN_KEY');
|
return Promise.reject('System unset FILE_TOKEN_KEY');
|
||||||
}
|
}
|
||||||
|
|
||||||
const expireMinutes =
|
const expireMinutes = bucketNameMap[data.bucketName].previewExpireMinutes;
|
||||||
data.customExpireMinutes ?? bucketNameMap[data.bucketName].previewExpireMinutes;
|
|
||||||
const expiredTime = Math.floor(addMinutes(new Date(), expireMinutes).getTime() / 1000);
|
const expiredTime = Math.floor(addMinutes(new Date(), expireMinutes).getTime() / 1000);
|
||||||
|
|
||||||
const key = (process.env.FILE_TOKEN_KEY as string) ?? 'filetoken';
|
const key = (process.env.FILE_TOKEN_KEY as string) ?? 'filetoken';
|
||||||
@@ -436,14 +435,14 @@ export const authFileToken = (token?: string) =>
|
|||||||
const key = (process.env.FILE_TOKEN_KEY as string) ?? 'filetoken';
|
const key = (process.env.FILE_TOKEN_KEY as string) ?? 'filetoken';
|
||||||
|
|
||||||
jwt.verify(token, key, function (err, decoded: any) {
|
jwt.verify(token, key, function (err, decoded: any) {
|
||||||
if (err || !decoded.bucketName || !decoded?.teamId || !decoded?.fileId) {
|
if (err || !decoded.bucketName || !decoded?.teamId || !decoded?.tmbId || !decoded?.fileId) {
|
||||||
reject(ERROR_ENUM.unAuthFile);
|
reject(ERROR_ENUM.unAuthFile);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
resolve({
|
resolve({
|
||||||
bucketName: decoded.bucketName,
|
bucketName: decoded.bucketName,
|
||||||
teamId: decoded.teamId,
|
teamId: decoded.teamId,
|
||||||
uid: decoded.uid,
|
tmbId: decoded.tmbId,
|
||||||
fileId: decoded.fileId
|
fileId: decoded.fileId
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -62,14 +62,14 @@ export async function authOutLinkValid<T extends OutlinkAppType = undefined>({
|
|||||||
if (!shareId) {
|
if (!shareId) {
|
||||||
return Promise.reject(OutLinkErrEnum.linkUnInvalid);
|
return Promise.reject(OutLinkErrEnum.linkUnInvalid);
|
||||||
}
|
}
|
||||||
const outLinkConfig = (await MongoOutLink.findOne({ shareId }).lean()) as OutLinkSchema<T>;
|
const shareChat = (await MongoOutLink.findOne({ shareId }).lean()) as OutLinkSchema<T>;
|
||||||
|
|
||||||
if (!outLinkConfig) {
|
if (!shareChat) {
|
||||||
return Promise.reject(OutLinkErrEnum.linkUnInvalid);
|
return Promise.reject(OutLinkErrEnum.linkUnInvalid);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
appId: outLinkConfig.appId,
|
appId: shareChat.appId,
|
||||||
outLinkConfig
|
shareChat
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Permission } from '@fastgpt/global/support/permission/controller';
|
|||||||
import { ApiRequestProps } from '../../type/next';
|
import { ApiRequestProps } from '../../type/next';
|
||||||
import type { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
import type { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||||
import { RequireAtLeastOne } from '@fastgpt/global/common/type/utils';
|
import { RequireAtLeastOne } from '@fastgpt/global/common/type/utils';
|
||||||
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
|
|
||||||
|
|
||||||
export type ReqHeaderAuthType = {
|
export type ReqHeaderAuthType = {
|
||||||
cookie?: string;
|
cookie?: string;
|
||||||
|
|||||||
3
packages/service/type.d.ts
vendored
@@ -23,6 +23,9 @@ declare global {
|
|||||||
var whisperModel: WhisperModelType;
|
var whisperModel: WhisperModelType;
|
||||||
var reRankModels: ReRankModelItemType[];
|
var reRankModels: ReRankModelItemType[];
|
||||||
|
|
||||||
|
var systemLoadedGlobalVariables: boolean;
|
||||||
|
var systemLoadedGlobalConfig: boolean;
|
||||||
|
|
||||||
var workerPoll: Record<WorkerNameEnum, WorkerPool>;
|
var workerPoll: Record<WorkerNameEnum, WorkerPool>;
|
||||||
var appMarketTemplates: TemplateMarketItemType[];
|
var appMarketTemplates: TemplateMarketItemType[];
|
||||||
}
|
}
|
||||||
|
|||||||