Compare commits

...

26 Commits

Author SHA1 Message Date
Archer
59177d307e fix: http (#3596) 2025-01-15 12:09:12 +08:00
Archer
bfb598d686 doc (#3590) 2025-01-14 17:43:05 +08:00
heheer
b780fbd6a7 fix lexical editor space (#3586)
* fix lexical editor space

* delete console
2025-01-14 17:24:16 +08:00
Archer
7861229325 4.8.18 test (#3589)
* update doc

* add subpath
2025-01-14 17:10:58 +08:00
Archer
5b0516c28b update doc (#3588) 2025-01-14 16:59:59 +08:00
Archer
740e8eb30c update doc (#3587) 2025-01-14 16:53:52 +08:00
Archer
05a357dffe fix: http variable (#3585) 2025-01-14 13:45:45 +08:00
Archer
781fd45e39 update doc (#3581) 2025-01-14 09:57:26 +08:00
Archer
80c8897e10 fix: create plugin by curl (#3580) 2025-01-13 18:27:31 +08:00
heheer
e933dacb05 curl create plugin with inputs (#3573) 2025-01-13 17:37:27 +08:00
Archer
cdf4f0e67d faq (#3574) 2025-01-13 14:08:35 +08:00
Archer
4dfeb21da3 4.8.18 test (#3571)
* rename

* update doc

* doc

* doc

* perf: intro
2025-01-13 10:43:38 +08:00
Archer
d0d1a2cae8 perf: http body;perf: create by json;perf: create by curl (#3570)
* perf: http body

* feat: create app by json (#3557)

* feat: create app by json

* fix build

* perf: create by json;perf: create by curl

* fix: ts

---------

Co-authored-by: heheer <heheer@sealos.io>
2025-01-12 22:49:03 +08:00
Archer
f1f0ae2839 fix: per;fix: invite user un refresh;perf: loginout (#3566)
* perf: loginout

* fix: invite user un refresh

* fix: per

* fix: dockerfile

* perf: docker file
2025-01-11 17:16:17 +08:00
Archer
10d8c56e23 V4.8.18 feature (#3565)
* feat: org CRUD (#3380)

* feat: add org schema

* feat: org manage UI

* feat: OrgInfoModal

* feat: org tree view

* feat: org management

* fix: init root org

* feat: org permission for app

* feat: org support for dataset

* fix: disable org role control

* styles: opt type signatures

* fix: remove unused permission

* feat: delete org collaborator

* perf: Team org ui (#3499)

* perf: org ui

* perf: org ui

* feat: org auth for app & dataset (#3498)

* feat: auth org resource permission

* feat: org auth support for app & dataset

* perf: org permission check (#3500)

* i18n (#3501)

* name

* i18n

* feat: support dataset changeOwner (#3483)

* feat: support dataset changeOwner

* chore: update dataset change owner api

* feat: permission manage UI for org (#3503)

* perf: password check;perf: image upload check;perf: sso login check (#3509)

* perf: password check

* perf: image upload check

* perf: sso login check

* force show update notification modal & fix login page text (#3512)

* fix login page English text

* update notification modal

* perf: notify account (#3515)

* perf(plugin): improve searXNG empty result handling and documentation (#3507)

* perf(plugin): improve searXNG empty result handling and documentation

* 修改了文档和代码部分无搜索的结果的反馈

* refactor: org pathId (#3516)

* optimize payment process (#3517)

* feat: support wecom sso (#3518)

* feat: support wecom sso

* chore: remove unused wecom js-sdk dependency

* fix qrcode script (#3520)

* fix qrcode script

* i18n

* perf: full text collection and search code;perf: rename function (#3519)

* perf: full text collection and search code

* perf: rename function

* perf: notify modal

* remove invalid code

* perf: sso login

* perf: pay process

* 4.8.18 test (#3524)

* perf: remove local token

* perf: index

* perf: file encoding;perf: leave team code;@c121914yu perf: full text search code (#3528)

* perf: text encoding

* perf: leave team code

* perf: full text search code

* fix: http status

* perf: embedding search and vector avatar

* perf: async read file (#3531)

* refactor: team permission  manager (#3535)

* perf: classify org, group and member

* refactor: team per manager

* fix: missing functions

* 4.8.18 test (#3543)

* perf: login check

* doc

* perf: llm model config

* perf: team clb config

* fix: MemberModal UI (#3553)

* fix: adapt MemberModal title and icon

* fix: adapt member modal

* fix: search input placeholder

* fix: add button text

* perf: org permission (#3556)

* docs:用户答疑的官方文档补充 (#3540)

* docs:用户答疑的官方文档补充

* 问题回答的内容修补

* share link random avatar (#3541)

* share link random avatar

* fix

* delete unused code

* share page avatar (#3558)

* feat: init 4818

* share page avatar

* feat: tmp upgrade code (#3559)

* feat: tmp upgrade code

* fulltext search test

* update action

* full text tmp code (#3561)

* full text tmp code

* fix: init

* fix: init

* remove tmp code

* remove tmp code

* 4818-alpha

* 4.8.18 test (#3562)

* full text tmp code

* fix: init

* upgrade code

* account log

* account log

* perf: dockerfile

* upgrade code

* chore: update docs app template submission (#3564)

---------

Co-authored-by: a.e. <49438478+I-Info@users.noreply.github.com>
Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
Co-authored-by: heheer <heheer@sealos.io>
Co-authored-by: Jiangween <145003935+Jiangween@users.noreply.github.com>
2025-01-11 15:15:38 +08:00
Archer
bb669ca3ff fix: plugin cost (#3533) 2025-01-06 12:43:04 +08:00
Archer
72ed72e595 fix: charts plugins (#3530) 2025-01-05 14:41:34 +08:00
Archer
e5735fddd1 Update 4817.md (#3523) 2025-01-03 16:39:55 +08:00
Archer
fa92ef86b9 fix: system title (#3522) 2025-01-03 15:30:46 +08:00
Archer
f39ea04178 feat: new provider (#3513) 2025-01-02 15:38:06 +08:00
Archer
bd4893ced9 更新 README.md (#3511) 2025-01-02 10:31:25 +08:00
Archer
b75e807f24 fix: tool choice run same tool will error (#3502) 2024-12-31 10:58:52 +08:00
Archer
b2fdefdc0c update config.json (#3497) 2024-12-30 13:53:12 +08:00
Archer
896fec4b82 update doc (#3496) 2024-12-30 13:51:05 +08:00
Archer
50bf7f9a3b V4.8.17 feature (#3493)
* split tokens into input and output (#3477)

* split tokens into input and output

* query extension & tool call & question guide

* fix

* perf: input and output tokens

* perf: tool call if else

* perf: remove code

* fix: extract usage count

* fix: qa usage count

---------

Co-authored-by: heheer <heheer@sealos.io>
2024-12-30 10:13:25 +08:00
Archer
da2831b948 update doc (#3488) 2024-12-27 20:21:08 +08:00
304 changed files with 7775 additions and 3491 deletions

View File

@@ -173,3 +173,86 @@ jobs:
-t ${Docker_Hub_Tag} \ -t ${Docker_Hub_Tag} \
-t ${Docker_Hub_Latest} \ -t ${Docker_Hub_Latest} \
. .
build-fastgpt-images-sub-route-gchat:
runs-on: ubuntu-20.04
steps:
# install env
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 1
- name: Install Dependencies
run: |
sudo apt update && sudo apt install -y nodejs npm
- name: Set up QEMU (optional)
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:
driver-opts: network=host
- name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
# login docker
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GH_PAT }}
- name: Login to Ali Hub
uses: docker/login-action@v2
with:
registry: registry.cn-hangzhou.aliyuncs.com
username: ${{ secrets.ALI_HUB_USERNAME }}
password: ${{ secrets.ALI_HUB_PASSWORD }}
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_NAME }}
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
# Set tag
- name: Set image name and tag
run: |
if [[ "${{ github.ref_name }}" == "main" ]]; then
echo "Git_Tag=ghcr.io/${{ github.repository_owner }}/fastgpt-sub-route-gchat:latest" >> $GITHUB_ENV
echo "Git_Latest=ghcr.io/${{ github.repository_owner }}/fastgpt-sub-route-gchat:latest" >> $GITHUB_ENV
echo "Ali_Tag=${{ secrets.ALI_IMAGE_NAME }}/fastgpt-sub-route-gchat:latest" >> $GITHUB_ENV
echo "Ali_Latest=${{ secrets.ALI_IMAGE_NAME }}/fastgpt-sub-route-gchat:latest" >> $GITHUB_ENV
echo "Docker_Hub_Tag=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-sub-route-gchat:latest" >> $GITHUB_ENV
echo "Docker_Hub_Latest=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-sub-route-gchat:latest" >> $GITHUB_ENV
else
echo "Git_Tag=ghcr.io/${{ github.repository_owner }}/fastgpt-sub-route-gchat:${{ github.ref_name }}" >> $GITHUB_ENV
echo "Git_Latest=ghcr.io/${{ github.repository_owner }}/fastgpt-sub-route-gchat:latest" >> $GITHUB_ENV
echo "Ali_Tag=${{ secrets.ALI_IMAGE_NAME }}/fastgpt-sub-route-gchat:${{ github.ref_name }}" >> $GITHUB_ENV
echo "Ali_Latest=${{ secrets.ALI_IMAGE_NAME }}/fastgpt-sub-route-gchat:latest" >> $GITHUB_ENV
echo "Docker_Hub_Tag=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-sub-route-gchat:${{ github.ref_name }}" >> $GITHUB_ENV
echo "Docker_Hub_Latest=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-sub-route-gchat:latest" >> $GITHUB_ENV
fi
- name: Build and publish image for main branch or tag push event
env:
DOCKER_REPO_TAGGED: ${{ env.DOCKER_REPO_TAGGED }}
run: |
docker buildx build \
-f projects/app/Dockerfile \
--platform linux/amd64,linux/arm64 \
--build-arg base_url=/gchat \
--label "org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/FastGPT" \
--label "org.opencontainers.image.description=fastgpt-sub-route-gchat image" \
--push \
--cache-from=type=local,src=/tmp/.buildx-cache \
--cache-to=type=local,dest=/tmp/.buildx-cache \
-t ${Git_Tag} \
-t ${Git_Latest} \
-t ${Ali_Tag} \
-t ${Ali_Latest} \
-t ${Docker_Hub_Tag} \
-t ${Docker_Hub_Latest} \
.

View File

@@ -36,7 +36,7 @@ jobs:
password: ${{ secrets.GH_PAT }} password: ${{ secrets.GH_PAT }}
- name: Set DOCKER_REPO_TAGGED based on branch or tag - name: Set DOCKER_REPO_TAGGED based on branch or tag
run: | run: |
echo "DOCKER_REPO_TAGGED=ghcr.io/${{ github.repository_owner }}/fastgpt-pr:${{ github.event.pull_request.number }}" >> $GITHUB_ENV echo "DOCKER_REPO_TAGGED=ghcr.io/${{ github.repository_owner }}/fastgpt-pr:${{ github.event.pull_request.head.sha }}" >> $GITHUB_ENV
- name: Build image for PR - name: Build image for PR
env: env:
DOCKER_REPO_TAGGED: ${{ env.DOCKER_REPO_TAGGED }} DOCKER_REPO_TAGGED: ${{ env.DOCKER_REPO_TAGGED }}
@@ -44,13 +44,30 @@ jobs:
docker buildx build \ docker buildx build \
-f projects/app/Dockerfile \ -f projects/app/Dockerfile \
--label "org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/FastGPT" \ --label "org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/FastGPT" \
--label "org.opencontainers.image.description=fastgpt-pr imae" \ --label "org.opencontainers.image.description=fastgpt-pr image" \
--label "org.opencontainers.image.licenses=Apache" \ --label "org.opencontainers.image.licenses=Apache" \
--push \ --push \
--cache-from=type=local,src=/tmp/.buildx-cache \ --cache-from=type=local,src=/tmp/.buildx-cache \
--cache-to=type=local,dest=/tmp/.buildx-cache \ --cache-to=type=local,dest=/tmp/.buildx-cache \
-t ${DOCKER_REPO_TAGGED} \ -t ${DOCKER_REPO_TAGGED} \
. .
# Add write md step after build
- name: Write md
run: |
echo "# 🤖 Generated by deploy action" > report.md
echo "📦 Preview Image: \`${DOCKER_REPO_TAGGED}\`" >> report.md
cat report.md
- name: Gh Rebot for Sealos
uses: labring/gh-rebot@v0.0.6
if: ${{ (github.event_name == 'pull_request_target') }}
with:
version: v0.0.6
env:
GH_TOKEN: '${{ secrets.GH_PAT }}'
SEALOS_TYPE: 'pr_comment'
SEALOS_FILENAME: 'report.md'
SEALOS_REPLACE_TAG: 'DEFAULT_REPLACE_DEPLOY'
helm-check: helm-check:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04

View File

@@ -215,4 +215,4 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b
1. 允许作为后台服务直接商用,但不允许提供 SaaS 服务。 1. 允许作为后台服务直接商用,但不允许提供 SaaS 服务。
2. 未经商业授权,任何形式的商用服务均需保留相关版权信息。 2. 未经商业授权,任何形式的商用服务均需保留相关版权信息。
3. 完整请查看 [FastGPT Open Source License](./LICENSE) 3. 完整请查看 [FastGPT Open Source License](./LICENSE)
4. 联系方式Dennis@sealos.io[点击查看商业版定价策略](https://doc.tryfastgpt.ai/docs/commercial) 4. 联系方式Dennis@sealos.io[点击查看商业版定价策略](https://doc.tryfastgpt.ai/docs/shopping_cart/intro/)

2
dev.md
View File

@@ -1,6 +1,6 @@
## Premise ## Premise
Since FastGPT is managed in the same way as monorepo, it is recommended to install 'make' first during development. Since FastGPT is managed in the same way as monorepo, it is recommended to install make first during development.
monorepo Project Name: monorepo Project Name:

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -7,7 +7,7 @@ toc: true
weight: 1210 weight: 1210
--- ---
FastGPT 项目在 Apache License 2.0 许可下开源,同时包含以下附加条件: FastGPT 项目在 Apache License 2.0 许可下开源,包含以下附加条件:
+ FastGPT 允许被用于商业化,例如作为其他应用的“后端即服务”使用,或者作为应用开发平台提供给企业。然而,当满足以下条件时,必须联系作者获得商业许可: + FastGPT 允许被用于商业化,例如作为其他应用的“后端即服务”使用,或者作为应用开发平台提供给企业。然而,当满足以下条件时,必须联系作者获得商业许可:

View File

@@ -9,9 +9,9 @@ weight: 707
由于环境变量不利于配置复杂的内容,新版 FastGPT 采用了 ConfigMap 的形式挂载配置文件,你可以在 `projects/app/data/config.json` 看到默认的配置文件。可以参考 [docker-compose 快速部署](/docs/development/docker/) 来挂载配置文件。 由于环境变量不利于配置复杂的内容,新版 FastGPT 采用了 ConfigMap 的形式挂载配置文件,你可以在 `projects/app/data/config.json` 看到默认的配置文件。可以参考 [docker-compose 快速部署](/docs/development/docker/) 来挂载配置文件。
**开发环境下**,你需要将示例配置文件 `config.json` 复制成 `config.local.json` 文件才会生效。 **开发环境下**,你需要将示例配置文件 `config.json` 复制成 `config.local.json` 文件才会生效。
这个配置文件中包含了系统参数和各个模型配置: 下面配置文件示例中包含了系统参数和各个模型配置:
## 4.6.8+ 版本新配置文件示例 ## 4.6.8+ 版本新配置文件示例
@@ -168,6 +168,7 @@ weight: 707
"reRankModels": [], "reRankModels": [],
"audioSpeechModels": [ "audioSpeechModels": [
{ {
"provider": "OpenAI",
"model": "tts-1", "model": "tts-1",
"name": "OpenAI TTS1", "name": "OpenAI TTS1",
"charsPointsPrice": 0, "charsPointsPrice": 0,
@@ -182,6 +183,7 @@ weight: 707
} }
], ],
"whisperModel": { "whisperModel": {
"provider": "OpenAI",
"model": "whisper-1", "model": "whisper-1",
"name": "Whisper1", "name": "Whisper1",
"charsPointsPrice": 0 "charsPointsPrice": 0
@@ -201,7 +203,9 @@ weight: 707
- OpenAI - OpenAI
- Claude - Claude
- Gemini - Gemini
- Meta
- MistralAI - MistralAI
- AliCloud - 阿里云
- Qwen - 通义千问 - Qwen - 通义千问
- Doubao - 豆包 - Doubao - 豆包
- ChatGLM - 智谱 - ChatGLM - 智谱
@@ -213,7 +217,10 @@ weight: 707
- Baichuan - 百川 - Baichuan - 百川
- Yi - 零一万物 - Yi - 零一万物
- Ernie - 文心一言 - Ernie - 文心一言
- StepFun - 阶跃星辰
- Ollama - Ollama
- BAAI - 智源研究院
- FishAudio
- Other - 其他 - Other - 其他

View File

@@ -19,6 +19,10 @@ images: []
## 二、通用问题 ## 二、通用问题
### 本地部署的限制
具体内容参考https://fael3z0zfze.feishu.cn/wiki/OFpAw8XzAi36Guk8dfucrCKUnjg。
### 能否纯本地运行 ### 能否纯本地运行
可以。需要准备好向量模型和LLM模型。 可以。需要准备好向量模型和LLM模型。
@@ -41,31 +45,6 @@ images: []
1. 问题补全需要经过一轮AI生成。 1. 问题补全需要经过一轮AI生成。
2. 会进行3~5轮的查询如果数据库性能不足会有明显影响。 2. 会进行3~5轮的查询如果数据库性能不足会有明显影响。
### 对话接口报错或返回为空(core.chat.Chat API is error or undefined)
1. 检查 AI 的 key 问题:通过 curl 请求看是否正常。务必用 stream=true 模式。并且 maxToken 等相关参数尽量一致。
2. 如果是国内模型,可能是命中风控了。
3. 查看模型请求日志,检查出入参数是否异常。
```sh
# curl 例子。
curl --location --request POST 'https://xxx.cn/v1/chat/completions' \
--header 'Authorization: Bearer sk-xxxx' \
--header 'Content-Type: application/json' \
--data-raw '{
"model": "gpt-3.5-turbo",
"stream": true,
"temperature": 1,
"max_tokens": 3000,
"messages": [
{
"role": "user",
"content": "你是谁"
}
]
}'
```
### 页面中可以正常回复API 报错 ### 页面中可以正常回复API 报错
页面中是用 stream=true 模式所以API也需要设置 stream=true 来进行测试。部分模型接口(国产居多)非 Stream 的兼容有点垃圾。 页面中是用 stream=true 模式所以API也需要设置 stream=true 来进行测试。部分模型接口(国产居多)非 Stream 的兼容有点垃圾。
@@ -111,6 +90,13 @@ FastGPT 模型配置文件中的 model 必须与 OneAPI 渠道中的模型对应
如果OneAPI中没有配置对应的模型`config.json`中也不要配置,否则容易报错。 如果OneAPI中没有配置对应的模型`config.json`中也不要配置,否则容易报错。
### 点击模型测试失败
OneAPI 只会测试渠道的第一个模型,并且只会测试对话模型,向量模型无法自动测试,需要手动发起请求进行测试。[查看测试模型命令示例](/docs/development/faq/#如何检查模型问题)
### get request url failed: Post "https://xxx dial tcp: xxxx
OneAPI 与模型网络不通,需要检查网络配置。
### Incorrect API key provided: sk-xxxx.You can find your api Key at xxx ### Incorrect API key provided: sk-xxxx.You can find your api Key at xxx
OneAPI 的 API Key 配置错误,需要修改`OPENAI_API_KEY`环境变量,并重启容器(先 docker-compose down 然后再 docker-compose up -d 运行一次)。 OneAPI 的 API Key 配置错误,需要修改`OPENAI_API_KEY`环境变量,并重启容器(先 docker-compose down 然后再 docker-compose up -d 运行一次)。
@@ -126,6 +112,112 @@ OneAPI 的 API Key 配置错误,需要修改`OPENAI_API_KEY`环境变量,并
## 四、常见模型问题 ## 四、常见模型问题
### 如何检查模型问题
1. 私有部署模型,先确认部署的模型是否正常。
2. 通过 CURL 请求,直接测试上游模型是否正常运行(云端模型或私有模型均进行测试)
3. 通过 CURL 请求,请求 OneAPI 去测试模型是否正常。
4. 在 FastGPT 中使用该模型进行测试。
下面是几个测试 CURL 示例:
{{< tabs tabTotal="5" >}}
{{< tab tabName="LLM模型" >}}
{{< markdownify >}}
```bash
curl https://api.openai.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "gpt-4o",
"messages": [
{
"role": "system",
"content": "You are a helpful assistant."
},
{
"role": "user",
"content": "Hello!"
}
]
}'
```
{{< /markdownify >}}
{{< /tab >}}
{{< tab tabName="Embedding模型" >}}
{{< markdownify >}}
```bash
curl https://api.openai.com/v1/embeddings \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "The food was delicious and the waiter...",
"model": "text-embedding-ada-002",
"encoding_format": "float"
}'
```
{{< /markdownify >}}
{{< /tab >}}
{{< tab tabName="Rerank 模型" >}}
{{< markdownify >}}
```bash
curl --location --request POST 'https://xxxx.com/api/v1/rerank' \
--header 'Authorization: Bearer {{ACCESS_TOKEN}}' \
--header 'Content-Type: application/json' \
--data-raw '{
"model": "bge-rerank-m3",
"query": "导演是谁",
"documents": [
"你是谁?\n我是电影《铃芽之旅》助手"
]
}'
```
{{< /markdownify >}}
{{< /tab >}}
{{< tab tabName="TTS 模型" >}}
{{< markdownify >}}
```bash
curl https://api.openai.com/v1/audio/speech \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "tts-1",
"input": "The quick brown fox jumped over the lazy dog.",
"voice": "alloy"
}' \
--output speech.mp3
```
{{< /markdownify >}}
{{< /tab >}}
{{< tab tabName="Whisper 模型" >}}
{{< markdownify >}}
```bash
curl https://api.openai.com/v1/audio/transcriptions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: multipart/form-data" \
-F file="@/path/to/file/audio.mp3" \
-F model="whisper-1"
```
{{< /markdownify >}}
{{< /tab >}}
{{< /tabs >}}
### 报错 - 模型响应为空/模型报错 ### 报错 - 模型响应为空/模型报错
该错误是由于 stream 模式下oneapi 直接结束了流请求,并且未返回任何内容导致。 该错误是由于 stream 模式下oneapi 直接结束了流请求,并且未返回任何内容导致。
@@ -165,7 +257,7 @@ curl --location --request POST 'https://api.openai.com/v1/chat/completions' \
需要模型提供商和 oneapi 同时支持工具调用才可使用,测试方法如下: 需要模型提供商和 oneapi 同时支持工具调用才可使用,测试方法如下:
1. 通过 `curl``oneapi` 发起第一轮 stream 模式的 tool 测试。 ##### 1. 通过 `curl` 向 `oneapi` 发起第一轮 stream 模式的 tool 测试。
```bash ```bash
curl --location --request POST 'https://oneapi.xxx/v1/chat/completions' \ curl --location --request POST 'https://oneapi.xxx/v1/chat/completions' \
@@ -200,7 +292,7 @@ curl --location --request POST 'https://oneapi.xxx/v1/chat/completions' \
}' }'
``` ```
2. 检查响应参数 ##### 2. 检查响应参数
如果能正常调用工具,会返回对应 `tool_calls` 参数。 如果能正常调用工具,会返回对应 `tool_calls` 参数。
@@ -238,7 +330,7 @@ curl --location --request POST 'https://oneapi.xxx/v1/chat/completions' \
} }
``` ```
3. 通过 `curl``oneapi` 发起第二轮 stream 模式的 tool 测试。 ##### 3. 通过 `curl` 向 `oneapi` 发起第二轮 stream 模式的 tool 测试。
第二轮请求是把工具结果发送给模型。发起后会得到模型回答的结果。 第二轮请求是把工具结果发送给模型。发起后会得到模型回答的结果。

View File

@@ -148,7 +148,7 @@ FastGPT 在`pnpm i`后会执行`postinstall`脚本,用于自动生成`ChakraUI
## 加入社区 ## 加入社区
遇到困难了吗?有任何问题吗? 加入微信群与开发者和用户保持沟通。 遇到困难了吗?有任何问题吗? 加入飞书群与开发者和用户保持沟通。
<img width="400px" src="https://oss.laf.run/otnvvf-imgs/fastgpt-feishu1.png" class="medium-zoom-image" /> <img width="400px" src="https://oss.laf.run/otnvvf-imgs/fastgpt-feishu1.png" class="medium-zoom-image" />

View File

@@ -1175,7 +1175,7 @@ curl --location --request POST 'http://localhost:3000/api/core/dataset/data/v2/l
}' }'
``` ```
**4.6.7+** **4.6.7-(即将弃用)**
```bash ```bash
curl --location --request POST 'http://localhost:3000/api/core/dataset/data/list' \ curl --location --request POST 'http://localhost:3000/api/core/dataset/data/list' \
@@ -1197,8 +1197,7 @@ curl --location --request POST 'http://localhost:3000/api/core/dataset/data/list
{{% alert icon=" " context="success" %}} {{% alert icon=" " context="success" %}}
- pageNum: 偏移量(选填) - offset: 偏移量(选填)
- pageNum: 页码(选填)
- pageSize: 每页数量最大30选填 - pageSize: 每页数量最大30选填
- collectionId: 集合的ID必填 - collectionId: 集合的ID必填
- searchText: 模糊搜索词(选填) - searchText: 模糊搜索词(选填)
@@ -1218,9 +1217,7 @@ curl --location --request POST 'http://localhost:3000/api/core/dataset/data/list
"statusText": "", "statusText": "",
"message": "", "message": "",
"data": { "data": {
"pageNum": 1, "list": [
"pageSize": 10,
"data": [
{ {
"_id": "65abd4b29d1448617cba61db", "_id": "65abd4b29d1448617cba61db",
"datasetId": "65abc9bd9d1448617cba5e6c", "datasetId": "65abc9bd9d1448617cba5e6c",

View File

@@ -1,5 +1,5 @@
--- ---
title: 'V4.8.17(进行中)' title: 'V4.8.17(包含升级脚本)'
description: 'FastGPT V4.8.17 更新说明' description: 'FastGPT V4.8.17 更新说明'
icon: 'upgrade' icon: 'upgrade'
draft: false draft: false
@@ -7,7 +7,16 @@ toc: true
weight: 807 weight: 807
--- ---
## 运行升级脚本 ## 更新指南
### 1. 更新镜像:
- 更新 fastgpt 镜像 tag: v4.8.17-fix-title
- 更新 fastgpt-pro 商业版镜像 tag: v4.8.17
- Sandbox 镜像无需更新
### 2. 运行升级脚本
从任意终端,发起 1 个 HTTP 请求。其中 {{rootkey}} 替换成环境变量里的 `rootkey`{{host}} 替换成**FastGPT 域名**。 从任意终端,发起 1 个 HTTP 请求。其中 {{rootkey}} 替换成环境变量里的 `rootkey`{{host}} 替换成**FastGPT 域名**。
@@ -19,6 +28,11 @@ curl --location --request POST 'https://{{host}}/api/admin/initv4817' \
会将用户绑定的 OpenAI 账号移动到团队中。 会将用户绑定的 OpenAI 账号移动到团队中。
## 调整 completions 接口返回值
/api/v1/chat/completions 接口返回值调整,对话节点、工具节点等使用到模型的节点,将不再返回 `tokens` 字段,改为返回 `inputTokens``outputTokens` 字段,分别表示输入和输出的 Token 数量。
## 完整更新内容 ## 完整更新内容
1. 新增 - 简易模式工具调用支持数组类型插件。 1. 新增 - 简易模式工具调用支持数组类型插件。
@@ -27,10 +41,12 @@ curl --location --request POST 'https://{{host}}/api/admin/initv4817' \
4. 新增 - 商业版支持后台配置模板市场。 4. 新增 - 商业版支持后台配置模板市场。
5. 新增 - 商业版支持后台配置自定义工作流变量,用于与业务系统鉴权打通。 5. 新增 - 商业版支持后台配置自定义工作流变量,用于与业务系统鉴权打通。
6. 新增 - 搜索测试接口支持问题优化。 6. 新增 - 搜索测试接口支持问题优化。
7. 优化 - Markdown 大小测试,超出 20 万字符不使用 Markdown 组件,避免崩溃 7. 新增 - 工作流中 Input Token 和 Output Token 分开记录展示。并修复部分请求未记录输出 Token 计费问题
8. 优化 - 知识库搜索参数,滑动条支持输入模式,可以更精准的控制 8. 优化 - Markdown 大小测试,超出 20 万字符不使用 Markdown 组件,避免崩溃
9. 优化 - 可用模型展示 9. 优化 - 知识库搜索参数,滑动条支持输入模式,可以更精准的控制。
10. 优化 - Mongo 查询语句,增加 virtual 字段 10. 优化 - 可用模型展示UI
11. 修复 - 文件返回接口缺少 Content-Length 头,导致通过非同源文件上传时,阿里 vision 模型无法识别图片 11. 优化 - Mongo 查询语句,增加 virtual 字段
12. 修复 - 去除判断器两端字符串隐藏换行符,避免判断器失效 12. 修复 - 文件返回接口缺少 Content-Length 头,导致通过非同源文件上传时,阿里 vision 模型无法识别图片
13. 修复 - 变量更新节点,手动输入更新内容时候,非字符串类型数据类型无法自动转化。 13. 修复 - 去除判断器两端字符串隐藏换行符,避免判断器失效。
14. 修复 - 变量更新节点,手动输入更新内容时候,非字符串类型数据类型无法自动转化。
15. 修复 - 豆包模型无法工具调用。

View File

@@ -0,0 +1,47 @@
---
title: 'V4.8.18'
description: 'FastGPT V4.8.18 更新说明'
icon: 'upgrade'
draft: false
toc: true
weight: 806
---
## 更新指南
### 1. 更新镜像:
- 更新 fastgpt 镜像 tag: v4.8.18-fix
- 更新 fastgpt-pro 商业版镜像 tag: v4.8.18-fix
- Sandbox 镜像无需更新
### 2. 运行升级脚本
从任意终端,发起 1 个 HTTP 请求。其中 {{rootkey}} 替换成环境变量里的 `rootkey`{{host}} 替换成**FastGPT 域名**。
```bash
curl --location --request POST 'https://{{host}}/api/admin/initv4818' \
--header 'rootkey: {{rootkey}}' \
--header 'Content-Type: application/json'
```
会迁移全文检索表,时间较长,迁移期间全文检索会失效,日志中会打印已经迁移的数据长度。
## 完整更新内容
1. 新增 - 支持通过 JSON 配置直接创建应用。
2. 新增 - 支持通过 CURL 脚本快速创建 HTTP 插件。
3. 新增 - 商业版支持部门架构权限模式。
4. 新增 - 支持配置自定跨域安全策略,默认全开。
5. 新增 - 补充私有部署,模型问题排查文档。
6. 优化 - HTTP Body 增加特殊处理,解决字符串变量带换行时无法解析问题。
7. 优化 - 分享链接随机生成用户头像。
8. 优化 - 图片上传安全校验。并增加头像图片唯一存储,确保不会累计存储。
9. 优化 - Mongo 全文索引表分离。
10. 优化 - 知识库检索查询语句合并,同时减少查库数量。
11. 优化 - 文件编码检测,减少 CSV 文件乱码概率。
12. 优化 - 异步读取文件内容,减少进程阻塞。
13. 优化 - 文件阅读HTML 直接下载,不允许在线阅读。
14. 修复 - HTML 文件上传base64 图片无法自动转图片链接。
15. 修复 - 插件计费错误。

View File

@@ -7,10 +7,34 @@ toc: true
weight: 908 weight: 908
--- ---
## 工作流中多轮对话场景中如何使连续问题被问题分类节点正确的归类 ## 多轮对话中如何使连续问题被问题分类节点正确的归类
问题分类节点具有获取上下文信息的能力,当处理两个关联性较大的问题时,模型的判断准确性往往依赖于这两个问题之间的联系和模型的能力。例如,当用户先问“我该如何使用这个功能?”接着又询问“这个功能有什么限制?”时,模型借助上下文信息,就能够更精准地理解并响应。 问题分类节点具有获取上下文信息的能力,当处理两个关联性较大的问题时,模型的判断准确性往往依赖于这两个问题之间的联系和模型的能力。例如,当用户先问“我该如何使用这个功能?”接着又询问“这个功能有什么限制?”时,模型借助上下文信息,就能够更精准地理解并响应。
但是,当连续问题之间的关联性较小,模型判断的准确度可能会受到限制。在这种情况下,我们可以引入全局变量的概念来记录分类结果。在后续的问题分类阶段,首先检查全局变量是否存有分类结果。如果有,那么直接沿用该结果;若没有,则让模型自行判断。 但是,当连续问题之间的关联性较小,模型判断的准确度可能会受到限制。在这种情况下,我们可以引入全局变量的概念来记录分类结果。在后续的问题分类阶段,首先检查全局变量是否存有分类结果。如果有,那么直接沿用该结果;若没有,则让模型自行判断。
建议:构建批量运行脚本进行测试,评估问题分类的准确性。 建议:构建批量运行脚本进行测试,评估问题分类的准确性。
## 定时执行的时机问题
系统编排配置中的定时执行,如果用户打开分享的连接,停留在那个页面,定时执行触发问题:
定时执行会在应用发布后生效,会在后台生效。
## AI对话回答要求中的Markdown语法取消
修改知识库默认提示词, 默认用的是标准模板提示词,会要求按 Markdown 输出,可以去除该要求:
| | |
| --- | --- |
| ![](/imgs/image-83.png) | ![](/imgs/image-84.png) |
## 应用在不同来源效果不一致
Q: 应用在调试和正式发布后,效果不一致;在 API 调用时,效果不一致。
A: 通常是由于上下文不一致导致,可以在对话日志中,找到对应的记录,并查看运行详情来进行比对。
| | | |
| --- | --- | --- |
| ![](/imgs/image-85.png) | ![](/imgs/image-86.png) | ![](/imgs/image-87.png) |

View File

@@ -14,4 +14,51 @@ weight: 910
## 知识库配置里的文件处理模型是什么?与索引模型有什么区别? ## 知识库配置里的文件处理模型是什么?与索引模型有什么区别?
* **文件处理模型**:用于数据处理的【增强处理】和【问答拆分】。在【增强处理】中,生成相关问题和摘要,在【问答拆分】中执行问答对生成。 * **文件处理模型**:用于数据处理的【增强处理】和【问答拆分】。在【增强处理】中,生成相关问题和摘要,在【问答拆分】中执行问答对生成。
* **索引模型**:用于向量化,即通过对文本数据进行处理和组织,构建出一个能够快速查询的数据结构。 * **索引模型**:用于向量化,即通过对文本数据进行处理和组织,构建出一个能够快速查询的数据结构。
## 基于知识库的查询但是问题相关的答案过多。ai回答到一半就不继续回答。
FastGPT回复长度计算公式:
最大回复=min(配置的最大回复(内置的限制),最大上下文(输入和输出的总和)-历史记录)
18K模型->输入与输出的和
输出增多->输入减小
所以可以:
1. 检查配置的最大回复(回复上限)
2. 减小输入来增大输出,即减小历史记录,在工作流其实也就是“聊天记录”
配置的最大回复:
![](/imgs/dataset1.png)
![](/imgs/dataset2.png)
1. 私有化部署的时候,后台配模型参数,可以在配置最大上文时候,预留一些空间,比如 128000 的模型,可以只配置 120000, 剩余的空间后续会被安排给输出
## 受到模型上下文的限制,有时候达不到聊天记录的轮次,连续对话字数过多就会报上下文不够的错误。
FastGPT回复长度计算公式:
最大回复=min(配置的最大回复(内置的限制),最大上下文(输入和输出的总和)-历史记录)
18K模型->输入与输出的和
输出增多->输入减小
所以可以:
1. 检查配置的最大回复(回复上限)
2. 减小输入来增大输出,即减小历史记录,在工作流其实也就是“聊天记录”
配置的最大回复:
![](/imgs/dataset1.png)
![](/imgs/dataset2.png)
1. 私有化部署的时候,后台配模型参数,可以在配置最大上文时候,预留一些空间,比如 128000 的模型,可以只配置 120000, 剩余的空间后续会被安排给输出

View File

@@ -160,6 +160,18 @@ default_doi_resolver: 'oadoi.org'
} }
``` ```
* 搜索结果为空时会返回友好提示:
```Bash
{
"result": "[]",
"error": {
"message": "No search results",
"code": 500
}
}
```
* 失败时通过 Promise.reject 可能返回错误信息: * 失败时通过 Promise.reject 可能返回错误信息:
```Bash ```Bash

View File

@@ -54,7 +54,7 @@ FastGPT 对外的 API 接口对齐了 OpenAI 官方接口,可以直接接入
1. **项目开源** 1. **项目开源**
FastGPT 遵循附加条件 Apache License 2.0 开源协议,你可以 [Fork](https://github.com/labring/FastGPT/fork) 之后进行二次开发和发布。FastGPT 社区版将保留核心功能,商业版仅在社区版基础上使用 API 的形式进行扩展,不影响学习使用。 FastGPT 遵循**附加条件 Apache License 2.0 开源协议**,你可以 [Fork](https://github.com/labring/FastGPT/fork) 之后进行二次开发和发布。FastGPT 社区版将保留核心功能,商业版仅在社区版基础上使用 API 的形式进行扩展,不影响学习使用。
2. **独特的 QA 结构** 2. **独特的 QA 结构**

View File

@@ -53,8 +53,9 @@ FastGPT 商业版软件根据不同的部署方式,分为 3 类收费模式。
{{< table "table-hover table-striped-columns" >}} {{< table "table-hover table-striped-columns" >}}
| 部署方式 | 特有服务 | 上线时长 | 标品价格 | | 部署方式 | 特有服务 | 上线时长 | 标品价格 |
| ---- | ---- | ---- | ---- | | ---- | ---- | ---- | ---- |
| Sealos全托管 | 1. 有效期内免费升级。<br>2. 免运维服务&数据库。 | 半天 | 6000元起/月3个月起<br>或<br>60000元起/年 | | Sealos全托管 | 1. 有效期内免费升级。<br>2. 免运维服务&数据库。 | 半天 | 10000元起/月3个月起<br>或<br>120000元起/年<br>8C32G 资源,额外资源另外收费。 |
| 自有服务器部署 | 1. 6个版本免费升级支持。 | 14天内 | 具体价格可[联系咨询](https://fael3z0zfze.feishu.cn/share/base/form/shrcnRxj3utrzjywsom96Px4sud) | | Sealos全托管多节点 | 1. 有效期内免费升级。<br>2. 免运维服务&数据库。 | 半天 | 22000元起/月3个月起<br>或<br>264000元起/年<br>32C128G 资源,额外资源另外收费。 |
| 自有服务器部署 | 1. 6个版本免费升级支持。 | 14天内 | 具体价格和优惠可[联系咨询](https://fael3z0zfze.feishu.cn/share/base/form/shrcnRxj3utrzjywsom96Px4sud) |
{{< /table >}} {{< /table >}}
{{% alert icon="🤖 " context="success" %}} {{% alert icon="🤖 " context="success" %}}

View File

@@ -5,8 +5,9 @@ icon: 'currency_yen'
draft: false draft: false
toc: true toc: true
weight: 1102 weight: 1102
type: redirect
target: https://cloud.tryfastgpt.ai/price
--- ---
线上版价格请查看:[https://cloud.tryfastgpt.ai/price](https://cloud.tryfastgpt.ai/price) 线上版价格按套餐订阅模式,具体价格和计费请查看(请正确选择版本,账号不互通):
- [海外版](https://cloud.tryfastgpt.ai/price)
- [国内版](https://cloud.fastgpt.cn/price)

View File

@@ -32,11 +32,11 @@ weight: 602
1. ### 创建应用模板 1. ### 创建应用模板
应用模板配置以及相关资源,都会在 **projects/app/public/appMarketTemplates** 目录下。 应用模板配置以及相关资源,都会在 **packages/templates/src** 目录下。
![](/imgs/template_submission2.png) ![](/imgs/template_submission2.png)
1. **projects/app/public/appMarketTemplates** 目录下,创建一个文件夹,名称为模板对应的 id。 1. 在**packages/templates/src** 目录下,创建一个文件夹,名称为模板对应的 id。
2. 在刚刚创建的文件夹中,再创建一个 **template.json** 文件,复制粘贴并填写如下配置: 2. 在刚刚创建的文件夹中,再创建一个 **template.json** 文件,复制粘贴并填写如下配置:
```JSON ```JSON
@@ -83,4 +83,4 @@ weight: 602
- 写清楚模板的介绍和功能 - 写清楚模板的介绍和功能
- 配上模板运行的效果图 - 配上模板运行的效果图
- 模板参数填写说明,需要在 PR 中写清楚。例如,有些模板需要去某个提供商申请 key需要附上对应的地址和教程后续我们会加入到文档中。 - 模板参数填写说明,需要在 PR 中写清楚。例如,有些模板需要去某个提供商申请 key需要附上对应的地址和教程后续我们会加入到文档中。

View File

@@ -114,15 +114,15 @@ services:
# fastgpt # fastgpt
sandbox: sandbox:
container_name: sandbox container_name: sandbox
image: ghcr.io/labring/fastgpt-sandbox:v4.8.16 # git image: ghcr.io/labring/fastgpt-sandbox:v4.8.17 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.16 # 阿里云 # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.17 # 阿里云
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
fastgpt: fastgpt:
container_name: fastgpt container_name: fastgpt
image: ghcr.io/labring/fastgpt:v4.8.16 # git image: ghcr.io/labring/fastgpt:v4.8.17 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.16 # 阿里云 # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.17 # 阿里云
ports: ports:
- 3000:3000 - 3000:3000
networks: networks:
@@ -159,6 +159,14 @@ services:
# 日志等级: debug, info, warn, error # 日志等级: debug, info, warn, error
- LOG_LEVEL=info - LOG_LEVEL=info
- STORE_LOG_LEVEL=warn - STORE_LOG_LEVEL=warn
# 工作流最大运行次数
- WORKFLOW_MAX_RUN_TIMES=1000
# 批量执行节点,最大输入长度
- WORKFLOW_MAX_LOOP_TIMES=100
# 自定义跨域,不配置时,默认都允许跨域(多个域名通过逗号分割)
- ALLOWED_ORIGINS=
# 是否开启IP限制默认不开启
- USE_IP_LIMIT=false
volumes: volumes:
- ./config.json:/app/data/config.json - ./config.json:/app/data/config.json

View File

@@ -72,15 +72,15 @@ services:
# fastgpt # fastgpt
sandbox: sandbox:
container_name: sandbox container_name: sandbox
image: ghcr.io/labring/fastgpt-sandbox:v4.8.16 # git image: ghcr.io/labring/fastgpt-sandbox:v4.8.17 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.16 # 阿里云 # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.17 # 阿里云
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
fastgpt: fastgpt:
container_name: fastgpt container_name: fastgpt
image: ghcr.io/labring/fastgpt:v4.8.16 # git image: ghcr.io/labring/fastgpt:v4.8.17 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.16 # 阿里云 # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.17 # 阿里云
ports: ports:
- 3000:3000 - 3000:3000
networks: networks:
@@ -116,6 +116,14 @@ services:
# 日志等级: debug, info, warn, error # 日志等级: debug, info, warn, error
- LOG_LEVEL=info - LOG_LEVEL=info
- STORE_LOG_LEVEL=warn - STORE_LOG_LEVEL=warn
# 工作流最大运行次数
- WORKFLOW_MAX_RUN_TIMES=1000
# 批量执行节点,最大输入长度
- WORKFLOW_MAX_LOOP_TIMES=100
# 自定义跨域,不配置时,默认都允许跨域(多个域名通过逗号分割)
- ALLOWED_ORIGINS=
# 是否开启IP限制默认不开启
- USE_IP_LIMIT=false
volumes: volumes:
- ./config.json:/app/data/config.json - ./config.json:/app/data/config.json

View File

@@ -53,15 +53,15 @@ services:
wait $$! wait $$!
sandbox: sandbox:
container_name: sandbox container_name: sandbox
image: ghcr.io/labring/fastgpt-sandbox:v4.8.16 # git image: ghcr.io/labring/fastgpt-sandbox:v4.8.17 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.16 # 阿里云 # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.17 # 阿里云
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
fastgpt: fastgpt:
container_name: fastgpt container_name: fastgpt
image: ghcr.io/labring/fastgpt:v4.8.16 # git image: ghcr.io/labring/fastgpt:v4.8.17 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.16 # 阿里云 # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.17 # 阿里云
ports: ports:
- 3000:3000 - 3000:3000
networks: networks:
@@ -97,6 +97,14 @@ services:
# 日志等级: debug, info, warn, error # 日志等级: debug, info, warn, error
- LOG_LEVEL=info - LOG_LEVEL=info
- STORE_LOG_LEVEL=warn - STORE_LOG_LEVEL=warn
# 工作流最大运行次数
- WORKFLOW_MAX_RUN_TIMES=1000
# 批量执行节点,最大输入长度
- WORKFLOW_MAX_LOOP_TIMES=100
# 自定义跨域,不配置时,默认都允许跨域(多个域名通过逗号分割)
- ALLOWED_ORIGINS=
# 是否开启IP限制默认不开启
- USE_IP_LIMIT=false
volumes: volumes:
- ./config.json:/app/data/config.json - ./config.json:/app/data/config.json

View File

@@ -1,14 +1,20 @@
import { i18nT } from '../../../../web/i18n/utils';
import { ErrType } from '../errorCode'; import { ErrType } from '../errorCode';
/* dataset: 507000 */ /* dataset: 507000 */
const startCode = 507000; const startCode = 507000;
export enum CommonErrEnum { export enum CommonErrEnum {
invalidParams = 'invalidParams',
fileNotFound = 'fileNotFound', fileNotFound = 'fileNotFound',
unAuthFile = 'unAuthFile', unAuthFile = 'unAuthFile',
missingParams = 'missingParams', missingParams = 'missingParams',
inheritPermissionError = 'inheritPermissionError' inheritPermissionError = 'inheritPermissionError'
} }
const datasetErr = [ const datasetErr = [
{
statusText: CommonErrEnum.fileNotFound,
message: i18nT('common:error.invalid_params')
},
{ {
statusText: CommonErrEnum.fileNotFound, statusText: CommonErrEnum.fileNotFound,
message: 'error.fileNotFound' message: 'error.fileNotFound'

View File

@@ -1,9 +1,11 @@
import { ErrType } from '../errorCode';
import { i18nT } from '../../../../web/i18n/utils'; import { i18nT } from '../../../../web/i18n/utils';
import type { ErrType } from '../errorCode';
/* team: 500000 */ /* team: 500000 */
export enum TeamErrEnum { export enum TeamErrEnum {
notUser = 'notUser',
teamOverSize = 'teamOverSize', teamOverSize = 'teamOverSize',
unAuthTeam = 'unAuthTeam', unAuthTeam = 'unAuthTeam',
teamMemberOverSize = 'teamMemberOverSize',
aiPointsNotEnough = 'aiPointsNotEnough', aiPointsNotEnough = 'aiPointsNotEnough',
datasetSizeNotEnough = 'datasetSizeNotEnough', datasetSizeNotEnough = 'datasetSizeNotEnough',
datasetAmountNotEnough = 'datasetAmountNotEnough', datasetAmountNotEnough = 'datasetAmountNotEnough',
@@ -14,11 +16,22 @@ export enum TeamErrEnum {
groupNameEmpty = 'groupNameEmpty', groupNameEmpty = 'groupNameEmpty',
groupNameDuplicate = 'groupNameDuplicate', groupNameDuplicate = 'groupNameDuplicate',
groupNotExist = 'groupNotExist', groupNotExist = 'groupNotExist',
orgMemberNotExist = 'orgMemberNotExist',
orgMemberDuplicated = 'orgMemberDuplicated',
orgNotExist = 'orgNotExist',
orgParentNotExist = 'orgParentNotExist',
cannotMoveToSubPath = 'cannotMoveToSubPath',
cannotModifyRootOrg = 'cannotModifyRootOrg',
cannotDeleteNonEmptyOrg = 'cannotDeleteNonEmptyOrg',
cannotDeleteDefaultGroup = 'cannotDeleteDefaultGroup', cannotDeleteDefaultGroup = 'cannotDeleteDefaultGroup',
userNotActive = 'userNotActive' userNotActive = 'userNotActive'
} }
const teamErr = [ const teamErr = [
{
statusText: TeamErrEnum.notUser,
message: i18nT('common:code_error.team_error.not_user')
},
{ {
statusText: TeamErrEnum.teamOverSize, statusText: TeamErrEnum.teamOverSize,
message: i18nT('common:code_error.team_error.over_size') message: i18nT('common:code_error.team_error.over_size')
@@ -71,6 +84,34 @@ const teamErr = [
{ {
statusText: TeamErrEnum.userNotActive, statusText: TeamErrEnum.userNotActive,
message: i18nT('common:code_error.team_error.user_not_active') message: i18nT('common:code_error.team_error.user_not_active')
},
{
statusText: TeamErrEnum.orgMemberNotExist,
message: i18nT('common:code_error.team_error.org_member_not_exist')
},
{
statusText: TeamErrEnum.orgMemberDuplicated,
message: i18nT('common:code_error.team_error.org_member_duplicated')
},
{
statusText: TeamErrEnum.orgNotExist,
message: i18nT('common:code_error.team_error.org_not_exist')
},
{
statusText: TeamErrEnum.orgParentNotExist,
message: i18nT('common:code_error.team_error.org_parent_not_exist')
},
{
statusText: TeamErrEnum.cannotMoveToSubPath,
message: i18nT('common:code_error.team_error.cannot_move_to_sub_path')
},
{
statusText: TeamErrEnum.cannotModifyRootOrg,
message: i18nT('common:code_error.team_error.cannot_modify_root_org')
},
{
statusText: TeamErrEnum.cannotDeleteNonEmptyOrg,
message: i18nT('common:code_error.team_error.cannot_delete_non_empty_org')
} }
]; ];

View File

@@ -2,25 +2,16 @@ import { ErrType } from '../errorCode';
import { i18nT } from '../../../../web/i18n/utils'; import { i18nT } from '../../../../web/i18n/utils';
/* team: 503000 */ /* team: 503000 */
export enum UserErrEnum { export enum UserErrEnum {
unAuthUser = 'unAuthUser',
unAuthRole = 'unAuthRole', unAuthRole = 'unAuthRole',
binVisitor = 'binVisitor', account_psw_error = 'account_psw_error',
balanceNotEnough = 'balanceNotEnough', balanceNotEnough = 'balanceNotEnough',
unAuthSso = 'unAuthSso' unAuthSso = 'unAuthSso'
} }
const errList = [ const errList = [
{ {
statusText: UserErrEnum.unAuthUser, statusText: UserErrEnum.account_psw_error,
message: i18nT('common:code_error.user_error.un_auth_user') message: i18nT('common:code_error.account_error')
}, },
{
statusText: UserErrEnum.binVisitor,
message: i18nT('common:code_error.user_error.bin_visitor')
}, // 身份校验未通过
{
statusText: UserErrEnum.binVisitor,
message: i18nT('common:code_error.user_error.bin_visitor_guest')
}, // 游客身份
{ {
statusText: UserErrEnum.balanceNotEnough, statusText: UserErrEnum.balanceNotEnough,
message: i18nT('common:code_error.user_error.balance_not_enough') message: i18nT('common:code_error.user_error.balance_not_enough')

View File

@@ -5,6 +5,6 @@ export const getErrText = (err: any, def = ''): any => {
typeof err === 'string' typeof err === 'string'
? err ? err
: err?.response?.data?.message || err?.response?.message || err?.message || def; : err?.response?.data?.message || err?.response?.message || err?.message || def;
msg && console.log('error =>', msg); // msg && console.log('error =>', msg);
return replaceSensitiveText(msg); return replaceSensitiveText(msg);
}; };

View File

@@ -1,10 +1,7 @@
import { MongoImageTypeEnum } from './image/constants';
import { OutLinkChatAuthProps } from '../../support/permission/chat.d'; import { OutLinkChatAuthProps } from '../../support/permission/chat.d';
export type preUploadImgProps = OutLinkChatAuthProps & { export type preUploadImgProps = OutLinkChatAuthProps & {
type: `${MongoImageTypeEnum}`; // expiredTime?: Date;
expiredTime?: Date;
metadata?: Record<string, any>; metadata?: Record<string, any>;
}; };
export type UploadImgProps = preUploadImgProps & { export type UploadImgProps = preUploadImgProps & {

View File

@@ -1,61 +1,5 @@
export const imageBaseUrl = '/api/system/img/'; export const imageBaseUrl = '/api/system/img/';
export enum MongoImageTypeEnum {
systemAvatar = 'systemAvatar',
appAvatar = 'appAvatar',
pluginAvatar = 'pluginAvatar',
datasetAvatar = 'datasetAvatar',
userAvatar = 'userAvatar',
teamAvatar = 'teamAvatar',
groupAvatar = 'groupAvatar',
chatImage = 'chatImage',
collectionImage = 'collectionImage'
}
export const mongoImageTypeMap = {
[MongoImageTypeEnum.systemAvatar]: {
label: 'appAvatar',
unique: true
},
[MongoImageTypeEnum.appAvatar]: {
label: 'appAvatar',
unique: true
},
[MongoImageTypeEnum.pluginAvatar]: {
label: 'pluginAvatar',
unique: true
},
[MongoImageTypeEnum.datasetAvatar]: {
label: 'datasetAvatar',
unique: true
},
[MongoImageTypeEnum.userAvatar]: {
label: 'userAvatar',
unique: true
},
[MongoImageTypeEnum.teamAvatar]: {
label: 'teamAvatar',
unique: true
},
[MongoImageTypeEnum.groupAvatar]: {
label: 'groupAvatar',
unique: true
},
[MongoImageTypeEnum.chatImage]: {
label: 'chatImage',
unique: false
},
[MongoImageTypeEnum.collectionImage]: {
label: 'collectionImage',
unique: false
}
};
export const uniqueImageTypeList = Object.entries(mongoImageTypeMap)
.filter(([key, value]) => value.unique)
.map(([key]) => key as `${MongoImageTypeEnum}`);
export const FolderIcon = 'file/fill/folder'; export const FolderIcon = 'file/fill/folder';
export const FolderImgUrl = '/imgs/files/folder.svg'; export const FolderImgUrl = '/imgs/files/folder.svg';
export const HttpPluginImgUrl = '/imgs/app/httpPluginFill.svg'; export const HttpPluginImgUrl = '/imgs/app/httpPluginFill.svg';

View File

@@ -1,12 +1,8 @@
import { MongoImageTypeEnum } from './constants';
export type MongoImageSchemaType = { export type MongoImageSchemaType = {
_id: string; _id: string;
teamId: string; teamId: string;
binary: Buffer; binary: Buffer;
createTime: Date;
expiredTime?: Date; expiredTime?: Date;
type: `${MongoImageTypeEnum}`;
metadata?: { metadata?: {
mime?: string; // image mime type. mime?: string; // image mime type.

View File

@@ -2,6 +2,7 @@ import { detect } from 'jschardet';
import { documentFileType, imageFileType } from './constants'; import { documentFileType, imageFileType } from './constants';
import { ChatFileTypeEnum } from '../../core/chat/constants'; import { ChatFileTypeEnum } from '../../core/chat/constants';
import { UserChatItemValueItemType } from '../../core/chat/type'; import { UserChatItemValueItemType } from '../../core/chat/type';
import * as fs from 'fs';
export const formatFileSize = (bytes: number): string => { export const formatFileSize = (bytes: number): string => {
if (bytes === 0) return '0 B'; if (bytes === 0) return '0 B';
@@ -16,6 +17,22 @@ export const formatFileSize = (bytes: number): string => {
export const detectFileEncoding = (buffer: Buffer) => { export const detectFileEncoding = (buffer: Buffer) => {
return detect(buffer.slice(0, 200))?.encoding?.toLocaleLowerCase(); return detect(buffer.slice(0, 200))?.encoding?.toLocaleLowerCase();
}; };
export const detectFileEncodingByPath = async (path: string) => {
// Get 64KB file head
const MAX_BYTES = 64 * 1024;
const buffer = Buffer.alloc(MAX_BYTES);
const fd = await fs.promises.open(path, 'r');
try {
// Read file head
const { bytesRead } = await fd.read(buffer, 0, MAX_BYTES, 0);
const actualBuffer = buffer.slice(0, bytesRead);
return detect(actualBuffer)?.encoding?.toLocaleLowerCase();
} finally {
await fd.close();
}
};
// Url => user upload file type // Url => user upload file type
export const parseUrlToFileType = (url: string): UserChatItemValueItemType['file'] | undefined => { export const parseUrlToFileType = (url: string): UserChatItemValueItemType['file'] | undefined => {

View File

@@ -0,0 +1,44 @@
import parse from '@bany/curl-to-json';
type RequestMethod = 'get' | 'post' | 'put' | 'delete' | 'patch';
const methodMap: { [K in RequestMethod]: string } = {
get: 'GET',
post: 'POST',
put: 'PUT',
delete: 'DELETE',
patch: 'PATCH'
};
export const parseCurl = (curlContent: string) => {
const parsed = parse(curlContent);
if (!parsed.url) {
throw new Error('url not found');
}
const newParams = Object.keys(parsed.params || {}).map((key) => ({
key,
value: parsed.params?.[key],
type: 'string'
}));
const newHeaders = Object.keys(parsed.header || {}).map((key) => ({
key,
value: parsed.header?.[key],
type: 'string'
}));
const newBody = JSON.stringify(parsed.data, null, 2);
const bodyArray = Object.keys(parsed.data || {}).map((key) => ({
key,
value: parsed.data?.[key],
type: 'string'
}));
return {
url: parsed.url,
method: methodMap[parsed.method?.toLowerCase() as RequestMethod] || 'GET',
params: newParams,
headers: newHeaders,
body: newBody,
bodyArray
};
};

View File

@@ -25,17 +25,22 @@ export const simpleText = (text = '') => {
return text; return text;
}; };
/* export const valToStr = (val: any) => {
replace {{variable}} to value if (val === undefined) return 'undefined';
*/ if (val === null) return 'null';
if (typeof val === 'object') return JSON.stringify(val);
return String(val);
};
// replace {{variable}} to value
export function replaceVariable(text: any, obj: Record<string, string | number>) { export function replaceVariable(text: any, obj: Record<string, string | number>) {
if (typeof text !== 'string') return text; if (typeof text !== 'string') return text;
for (const key in obj) { for (const key in obj) {
const val = obj[key]; const val = obj[key];
const formatVal = typeof val === 'object' ? JSON.stringify(val) : String(val); const formatVal = valToStr(val);
text = text.replace(new RegExp(`{{(${key})}}`, 'g'), () => formatVal);
text = text.replace(new RegExp(`{{(${key})}}`, 'g'), formatVal);
} }
return text || ''; return text || '';
} }

View File

@@ -1,6 +1,9 @@
export const HUMAN_ICON = `/icon/human.svg`; export const HUMAN_ICON = `/icon/human.svg`;
export const LOGO_ICON = `/icon/logo.svg`; export const LOGO_ICON = `/icon/logo.svg`;
export const HUGGING_FACE_ICON = `/imgs/model/huggingface.svg`; export const HUGGING_FACE_ICON = `/imgs/model/huggingface.svg`;
export const DEFAULT_TEAM_AVATAR = `/imgs/avatar/defaultTeamAvatar.svg`; export const DEFAULT_TEAM_AVATAR = `/imgs/avatar/defaultTeamAvatar.svg`;
export const DEFAULT_ORG_AVATAR = '/imgs/avatar/defaultOrgAvatar.svg';
export const DEFAULT_USER_AVATAR = '/imgs/avatar/BlueAvatar.svg';
export const isProduction = process.env.NODE_ENV === 'production'; export const isProduction = process.env.NODE_ENV === 'production';

View File

@@ -73,6 +73,11 @@ export type FastGPTFeConfigsType = {
google?: string; google?: string;
wechat?: string; wechat?: string;
dingtalk?: string; dingtalk?: string;
wecom?: {
corpid?: string;
agentid?: string;
secret?: string;
};
microsoft?: { microsoft?: {
clientId?: string; clientId?: string;
tenantId?: string; tenantId?: string;

View File

@@ -1,6 +1,13 @@
import type { ModelProviderIdType } from './provider'; import type { ModelProviderIdType } from './provider';
export type LLMModelItemType = { type PriceType = {
charsPointsPrice?: number; // 1k chars=n points; 60s=n points;
// If inputPrice is set, the input-output charging scheme is adopted
inputPrice?: number; // 1k tokens=n points
outputPrice?: number; // 1k tokens=n points
};
export type LLMModelItemType = PriceType & {
provider: ModelProviderIdType; provider: ModelProviderIdType;
model: string; model: string;
name: string; name: string;
@@ -10,8 +17,6 @@ export type LLMModelItemType = {
quoteMaxToken: number; quoteMaxToken: number;
maxTemperature: number; maxTemperature: number;
charsPointsPrice: number; // 1k chars=n points
censor?: boolean; censor?: boolean;
vision?: boolean; vision?: boolean;
@@ -33,13 +38,12 @@ export type LLMModelItemType = {
fieldMap?: Record<string, string>; fieldMap?: Record<string, string>;
}; };
export type VectorModelItemType = { export type VectorModelItemType = PriceType & {
provider: ModelProviderIdType; provider: ModelProviderIdType;
model: string; // model name model: string; // model name
name: string; // show name name: string; // show name
avatar?: string; avatar?: string;
defaultToken: number; // split text default token defaultToken: number; // split text default token
charsPointsPrice: number; // 1k tokens=n points
maxToken: number; // model max token maxToken: number; // model max token
weight: number; // training weight weight: number; // training weight
hidden?: boolean; // Disallow creation hidden?: boolean; // Disallow creation
@@ -48,25 +52,23 @@ export type VectorModelItemType = {
queryConfig?: Record<string, any>; // Custom parameters for query queryConfig?: Record<string, any>; // Custom parameters for query
}; };
export type ReRankModelItemType = { export type ReRankModelItemType = PriceType & {
provider: ModelProviderIdType;
model: string; model: string;
name: string; name: string;
charsPointsPrice: number;
requestUrl: string; requestUrl: string;
requestAuth: string; requestAuth: string;
}; };
export type AudioSpeechModelType = { export type AudioSpeechModelType = PriceType & {
provider: ModelProviderIdType; provider: ModelProviderIdType;
model: string; model: string;
name: string; name: string;
charsPointsPrice: number;
voices: { label: string; value: string; bufferId: string }[]; voices: { label: string; value: string; bufferId: string }[];
}; };
export type STTModelType = { export type STTModelType = PriceType & {
provider: ModelProviderIdType; provider: ModelProviderIdType;
model: string; model: string;
name: string; name: string;
charsPointsPrice: number; // 60s = n points
}; };

View File

@@ -4,20 +4,25 @@ export type ModelProviderIdType =
| 'OpenAI' | 'OpenAI'
| 'Claude' | 'Claude'
| 'Gemini' | 'Gemini'
| 'Meta'
| 'MistralAI' | 'MistralAI'
| 'Groq' | 'Groq'
| 'AliCloud'
| 'Qwen' | 'Qwen'
| 'Doubao' | 'Doubao'
| 'ChatGLM' | 'ChatGLM'
| 'DeepSeek' | 'DeepSeek'
| 'Ernie'
| 'Moonshot' | 'Moonshot'
| 'MiniMax' | 'MiniMax'
| 'SparkDesk' | 'SparkDesk'
| 'Hunyuan' | 'Hunyuan'
| 'Baichuan' | 'Baichuan'
| 'StepFun'
| 'Yi' | 'Yi'
| 'Ernie'
| 'Ollama' | 'Ollama'
| 'BAAI'
| 'FishAudio'
| 'Other'; | 'Other';
export type ModelProviderType = { export type ModelProviderType = {
@@ -42,6 +47,11 @@ export const ModelProviderList: ModelProviderType[] = [
name: 'Gemini', name: 'Gemini',
avatar: 'model/gemini' avatar: 'model/gemini'
}, },
{
id: 'Meta',
name: 'Meta',
avatar: 'model/meta'
},
{ {
id: 'MistralAI', id: 'MistralAI',
name: 'MistralAI', name: 'MistralAI',
@@ -52,6 +62,11 @@ export const ModelProviderList: ModelProviderType[] = [
name: 'Groq', name: 'Groq',
avatar: 'model/groq' avatar: 'model/groq'
}, },
{
id: 'AliCloud',
name: i18nT('common:model_alicloud'),
avatar: 'model/alicloud'
},
{ {
id: 'Qwen', id: 'Qwen',
name: i18nT('common:model_qwen'), name: i18nT('common:model_qwen'),
@@ -67,6 +82,11 @@ export const ModelProviderList: ModelProviderType[] = [
name: i18nT('common:model_chatglm'), name: i18nT('common:model_chatglm'),
avatar: 'model/chatglm' avatar: 'model/chatglm'
}, },
{
id: 'Ernie',
name: i18nT('common:model_ernie'),
avatar: 'model/ernie'
},
{ {
id: 'DeepSeek', id: 'DeepSeek',
name: 'DeepSeek', name: 'DeepSeek',
@@ -97,21 +117,32 @@ export const ModelProviderList: ModelProviderType[] = [
name: i18nT('common:model_baichuan'), name: i18nT('common:model_baichuan'),
avatar: 'model/baichuan' avatar: 'model/baichuan'
}, },
{
id: 'StepFun',
name: i18nT('common:model_stepfun'),
avatar: 'model/stepfun'
},
{ {
id: 'Yi', id: 'Yi',
name: i18nT('common:model_yi'), name: i18nT('common:model_yi'),
avatar: 'model/yi' avatar: 'model/yi'
}, },
{
id: 'Ernie',
name: i18nT('common:model_ernie'),
avatar: 'model/ernie'
},
{ {
id: 'Ollama', id: 'Ollama',
name: 'Ollama', name: 'Ollama',
avatar: 'model/ollama' avatar: 'model/ollama'
}, },
{
id: 'BAAI',
name: i18nT('common:model_baai'),
avatar: 'model/BAAI'
},
{
id: 'FishAudio',
name: 'FishAudio',
avatar: 'model/fishaudio'
},
{ {
id: 'Other', id: 'Other',
name: i18nT('common:model_other'), name: i18nT('common:model_other'),

View File

@@ -1,6 +1,6 @@
import { RequireOnlyOne } from '../../common/type/utils'; import type { RequireOnlyOne } from '../../common/type/utils';
import { import {
UpdateClbPermissionProps, type UpdateClbPermissionProps,
UpdatePermissionBody UpdatePermissionBody
} from '../../support/permission/collaborator'; } from '../../support/permission/collaborator';
import { PermissionValueType } from '../../support/permission/type'; import { PermissionValueType } from '../../support/permission/type';
@@ -14,4 +14,5 @@ export type AppCollaboratorDeleteParams = {
} & RequireOnlyOne<{ } & RequireOnlyOne<{
tmbId: string; tmbId: string;
groupId: string; groupId: string;
orgId: string;
}>; }>;

View File

@@ -5,6 +5,8 @@ import type { FlowNodeInputItemType } from '../workflow/type/io.d';
import { getAppChatConfig } from '../workflow/utils'; import { getAppChatConfig } from '../workflow/utils';
import { StoreNodeItemType } from '../workflow/type/node'; import { StoreNodeItemType } from '../workflow/type/node';
import { DatasetSearchModeEnum } from '../dataset/constants'; import { DatasetSearchModeEnum } from '../dataset/constants';
import { WorkflowTemplateBasicType } from '../workflow/type';
import { AppTypeEnum } from './constants';
export const getDefaultAppForm = (): AppSimpleEditFormType => { export const getDefaultAppForm = (): AppSimpleEditFormType => {
return { return {
@@ -127,3 +129,20 @@ export const appWorkflow2Form = ({
return defaultAppForm; return defaultAppForm;
}; };
export const getAppType = (config?: WorkflowTemplateBasicType | AppSimpleEditFormType) => {
if (!config) return '';
if ('aiSettings' in config) {
return AppTypeEnum.simple;
}
if (!('nodes' in config)) return '';
if (config.nodes.some((node) => node.flowNodeType === 'workflowStart')) {
return AppTypeEnum.workflow;
}
if (config.nodes.some((node) => node.flowNodeType === 'pluginInput')) {
return AppTypeEnum.plugin;
}
return '';
};

View File

@@ -11,4 +11,5 @@ export type DatasetCollaboratorDeleteParams = {
} & RequireOnlyOne<{ } & RequireOnlyOne<{
tmbId: string; tmbId: string;
groupId: string; groupId: string;
orgId: string;
}>; }>;

View File

@@ -112,6 +112,15 @@ export type DatasetDataSchemaType = {
rebuilding?: boolean; rebuilding?: boolean;
}; };
export type DatasetDataTextSchemaType = {
_id: string;
teamId: string;
datasetId: string;
collectionId: string;
dataId: string;
fullTextToken: string;
};
export type DatasetTrainingSchemaType = { export type DatasetTrainingSchemaType = {
_id: string; _id: string;
userId: string; userId: string;

View File

@@ -107,7 +107,9 @@ export type DispatchNodeResponseType = {
mergeSignId?: string; mergeSignId?: string;
// bill // bill
tokens?: number; tokens?: number; // deprecated
inputTokens?: number;
outputTokens?: number;
model?: string; model?: string;
contextTotalLen?: number; contextTotalLen?: number;
totalPoints?: number; totalPoints?: number;
@@ -157,6 +159,8 @@ export type DispatchNodeResponseType = {
// tool // tool
toolCallTokens?: number; toolCallTokens?: number;
toolCallInputTokens?: number;
toolCallOutputTokens?: number;
toolDetail?: ChatHistoryItemResType[]; toolDetail?: ChatHistoryItemResType[];
toolStop?: boolean; toolStop?: boolean;

View File

@@ -9,7 +9,7 @@ import { isValidReferenceValueFormat } from '../utils';
import { FlowNodeOutputItemType, ReferenceValueType } from '../type/io'; import { FlowNodeOutputItemType, ReferenceValueType } from '../type/io';
import { ChatItemType, NodeOutputItemType } from '../../../core/chat/type'; import { ChatItemType, NodeOutputItemType } from '../../../core/chat/type';
import { ChatItemValueTypeEnum, ChatRoleEnum } from '../../../core/chat/constants'; import { ChatItemValueTypeEnum, ChatRoleEnum } from '../../../core/chat/constants';
import { replaceVariable } from '../../../common/string/tools'; import { replaceVariable, valToStr } from '../../../common/string/tools';
export const getMaxHistoryLimitFromNodes = (nodes: StoreNodeItemType[]): number => { export const getMaxHistoryLimitFromNodes = (nodes: StoreNodeItemType[]): number => {
let limit = 10; let limit = 10;
@@ -339,15 +339,12 @@ export function replaceEditorVariable({
const output = node.outputs.find((output) => output.id === id); const output = node.outputs.find((output) => output.id === id);
if (output) return formatVariableValByType(output.value, output.valueType); if (output) return formatVariableValByType(output.value, output.valueType);
// Use the node's input as the variable value(Example: HTTP data will reference its own dynamic input)
const input = node.inputs.find((input) => input.key === id); const input = node.inputs.find((input) => input.key === id);
if (input) return getReferenceVariableValue({ value: input.value, nodes, variables }); if (input) return getReferenceVariableValue({ value: input.value, nodes, variables });
})(); })();
const formatVal = (() => { const formatVal = valToStr(variableVal);
if (variableVal === undefined) return 'undefined';
if (variableVal === null) return 'null';
return typeof variableVal === 'object' ? JSON.stringify(variableVal) : String(variableVal);
})();
const regex = new RegExp(`\\{\\{\\$(${nodeId}\\.${id})\\$\\}\\}`, 'g'); const regex = new RegExp(`\\{\\{\\$(${nodeId}\\.${id})\\$\\}\\}`, 'g');
text = text.replace(regex, () => formatVal); text = text.replace(regex, () => formatVal);

View File

@@ -49,9 +49,10 @@ export type FlowNodeInputItemType = InputComponentPropsType & {
debugLabel?: string; debugLabel?: string;
description?: string; // field desc description?: string; // field desc
required?: boolean; required?: boolean;
toolDescription?: string; // If this field is not empty, it is entered as a tool
enum?: string; enum?: string;
toolDescription?: string; // If this field is not empty, it is entered as a tool
// render components params // render components params
canEdit?: boolean; // dynamic inputs canEdit?: boolean; // dynamic inputs
isPro?: boolean; // Pro version field isPro?: boolean; // Pro version field

View File

@@ -13,7 +13,9 @@
"next": "14.2.5", "next": "14.2.5",
"openai": "4.61.0", "openai": "4.61.0",
"openapi-types": "^12.1.3", "openapi-types": "^12.1.3",
"timezones-list": "^3.0.2" "json5": "^2.2.3",
"timezones-list": "^3.0.2",
"@bany/curl-to-json": "^1.2.8"
}, },
"devDependencies": { "devDependencies": {
"@types/js-yaml": "^4.0.9", "@types/js-yaml": "^4.0.9",

View File

@@ -10,22 +10,18 @@ export type CollaboratorItemType = {
} & RequireOnlyOne<{ } & RequireOnlyOne<{
tmbId: string; tmbId: string;
groupId: string; groupId: string;
orgId: string;
}>; }>;
export type UpdateClbPermissionProps = { export type UpdateClbPermissionProps = {
members?: string[]; members?: string[];
groups?: string[]; groups?: string[];
orgs?: string[];
permission: PermissionValueType; permission: PermissionValueType;
}; };
export type DeleteClbPermissionProps = RequireOnlyOne<{ export type DeletePermissionQuery = RequireOnlyOne<{
tmbId: string; tmbId?: string;
groupId: string; groupId?: string;
}>; orgId?: string;
export type UpdatePermissionBody = {
permission: PermissionValueType;
} & RequireOnlyOne<{
memberId: string;
groupId: string;
}>; }>;

View File

@@ -1,8 +1,9 @@
import { UserModelSchema } from '../user/type'; import { UserModelSchema } from '../user/type';
import { RequireOnlyOne } from '../../common/type/utils'; import { RequireOnlyOne } from '../../common/type/utils';
import { TeamMemberSchema } from '../user/team/type'; import { TeamMemberSchema } from '../user/team/type';
import { AuthUserTypeEnum, PermissionKeyEnum, PerResourceTypeEnum } from './constant';
import { MemberGroupSchemaType } from './memberGroup/type'; import { MemberGroupSchemaType } from './memberGroup/type';
import type { TeamMemberWithUserSchema } from '../user/team/type';
import { AuthUserTypeEnum, type PermissionKeyEnum, type PerResourceTypeEnum } from './constant';
// PermissionValueType, the type of permission's value is a number, which is a bit field actually. // PermissionValueType, the type of permission's value is a number, which is a bit field actually.
// It is spired by the permission system in Linux. // It is spired by the permission system in Linux.
@@ -29,6 +30,7 @@ export type ResourcePermissionType = {
} & RequireOnlyOne<{ } & RequireOnlyOne<{
tmbId: string; tmbId: string;
groupId: string; groupId: string;
orgId: string;
}>; }>;
export type ResourcePerWithTmbWithUser = Omit<ResourcePermissionType, 'tmbId'> & { export type ResourcePerWithTmbWithUser = Omit<ResourcePermissionType, 'tmbId'> & {

View File

@@ -17,5 +17,6 @@ export enum OAuthEnum {
wechat = 'wechat', wechat = 'wechat',
microsoft = 'microsoft', microsoft = 'microsoft',
dingtalk = 'dingtalk', dingtalk = 'dingtalk',
wecom = 'wecom',
sso = 'sso' sso = 'sso'
} }

View File

@@ -0,0 +1,3 @@
export function checkIsWecomTerminal() {
return /wxwork/i.test(navigator.userAgent);
}

View File

@@ -0,0 +1,32 @@
export type postCreateOrgData = {
name: string;
parentId: string;
description?: string;
avatar?: string;
};
export type putUpdateOrgMembersData = {
orgId: string;
members: {
tmbId: string;
// role: `${OrgMemberRole}`;
}[];
};
export type putUpdateOrgData = {
orgId: string;
name?: string;
avatar?: string;
description?: string;
};
export type putMoveOrgType = {
orgId: string;
targetOrgId: string;
};
// type putChnageOrgOwnerData = {
// orgId: string;
// tmbId: string;
// toAdmin?: boolean;
// };

View File

@@ -0,0 +1,12 @@
import { OrgSchemaType } from './type';
export const OrgCollectionName = 'team_orgs';
export const OrgMemberCollectionName = 'team_org_members';
export const getOrgChildrenPath = (org: OrgSchemaType) => `${org.path}/${org.pathId}`;
// export enum OrgMemberRole {
// owner = 'owner',
// admin = 'admin',
// member = 'member'
// }

View File

@@ -0,0 +1,25 @@
import type { TeamPermission } from 'support/permission/user/controller';
import { ResourcePermissionType } from '../type';
type OrgSchemaType = {
_id: string;
teamId: string;
pathId: string;
path: string;
name: string;
avatar?: string;
description?: string;
updateTime: Date;
};
type OrgMemberSchemaType = {
teamId: string;
orgId: string;
tmbId: string;
};
type OrgType = Omit<OrgSchemaType, 'avatar'> & {
avatar: string;
members: OrgMemberSchemaType[];
permission: TeamPermission;
};

View File

@@ -55,10 +55,11 @@ export type TeamMemberWithTeamAndUserSchema = TeamMemberSchema & {
export type TeamTmbItemType = { export type TeamTmbItemType = {
userId: string; userId: string;
teamId: string; teamId: string;
teamAvatar?: string;
teamName: string; teamName: string;
memberName: string; memberName: string;
avatar: string; avatar: string;
balance: number; balance?: number;
tmbId: string; tmbId: string;
teamDomain: string; teamDomain: string;
defaultTeam: boolean; defaultTeam: boolean;

View File

@@ -0,0 +1,16 @@
export const getRandomUserAvatar = () => {
const defaultAvatars = [
'/imgs/avatar/RoyalBlueAvatar.svg',
'/imgs/avatar/PurpleAvatar.svg',
'/imgs/avatar/AdoraAvatar.svg',
'/imgs/avatar/OrangeAvatar.svg',
'/imgs/avatar/RedAvatar.svg',
'/imgs/avatar/GrayModernAvatar.svg',
'/imgs/avatar/TealAvatar.svg',
'/imgs/avatar/GreenAvatar.svg',
'/imgs/avatar/BrightBlueAvatar.svg',
'/imgs/avatar/BlueAvatar.svg'
];
return defaultAvatars[Math.floor(Math.random() * defaultAvatars.length)];
};

View File

@@ -23,7 +23,8 @@ export type BillSchemaType = {
}; };
export type ChatNodeUsageType = { export type ChatNodeUsageType = {
tokens?: number; inputTokens?: number;
outputTokens?: number;
totalPoints: number; totalPoints: number;
moduleName: string; moduleName: string;
model?: string; model?: string;

View File

@@ -2,9 +2,13 @@ import { CreateUsageProps } from './api';
import { UsageSourceEnum } from './constants'; import { UsageSourceEnum } from './constants';
export type UsageListItemCountType = { export type UsageListItemCountType = {
tokens?: number; inputTokens?: number;
outputTokens?: number;
charsLength?: number; charsLength?: number;
duration?: number; duration?: number;
// deprecated
tokens?: number;
}; };
export type UsageListItemType = UsageListItemCountType & { export type UsageListItemType = UsageListItemCountType & {
moduleName: string; moduleName: string;

View File

@@ -35,24 +35,26 @@ export const list = [...staticPluginList, ...packagePluginList];
/* Get plugins */ /* Get plugins */
export const getCommunityPlugins = () => { export const getCommunityPlugins = () => {
return list.map<SystemPluginTemplateItemType>((name) => { return Promise.all(
const config = require(`./src/${name}/template.json`); list.map<Promise<SystemPluginTemplateItemType>>(async (name) => {
const config = (await import(`./src/${name}/template.json`))?.default;
const isFolder = list.find((item) => item.startsWith(`${name}/`)); const isFolder = list.find((item) => item.startsWith(`${name}/`));
const parentIdList = name.split('/').slice(0, -1); const parentIdList = name.split('/').slice(0, -1);
const parentId = const parentId =
parentIdList.length > 0 ? `${PluginSourceEnum.community}-${parentIdList.join('/')}` : null; parentIdList.length > 0 ? `${PluginSourceEnum.community}-${parentIdList.join('/')}` : null;
return { return {
...config, ...config,
id: `${PluginSourceEnum.community}-${name}`, id: `${PluginSourceEnum.community}-${name}`,
isFolder, isFolder,
parentId, parentId,
isActive: true, isActive: true,
isOfficial: true isOfficial: true
}; };
}); })
);
}; };
export const getSystemPluginTemplates = () => { export const getSystemPluginTemplates = () => {

View File

@@ -4,8 +4,8 @@ import { SystemPluginSpecialResponse } from '../../../type.d';
type Props = { type Props = {
title: string; title: string;
xAxis: string; xAxis: string[];
yAxis: string; yAxis: string[];
chartType: string; chartType: string;
}; };
@@ -27,7 +27,12 @@ type Option = {
series: SeriesData[]; // 使用定义的类型 series: SeriesData[]; // 使用定义的类型
}; };
const generateChart = async (title: string, xAxis: string, yAxis: string, chartType: string) => { const generateChart = async (
title: string,
xAxis: string[],
yAxis: string[],
chartType: string
) => {
// @ts-ignore 无法使用dom如使用jsdom会出现生成图片无法正常展示有高手可以帮忙解决 // @ts-ignore 无法使用dom如使用jsdom会出现生成图片无法正常展示有高手可以帮忙解决
const chart = echarts.init(undefined, undefined, { const chart = echarts.init(undefined, undefined, {
renderer: 'svg', // 必须使用 SVG 模式 renderer: 'svg', // 必须使用 SVG 模式
@@ -36,21 +41,11 @@ const generateChart = async (title: string, xAxis: string, yAxis: string, chartT
height: 300 height: 300
}); });
let parsedXAxis: string[] = [];
let parsedYAxis: number[] = [];
try {
parsedXAxis = json5.parse(xAxis);
parsedYAxis = json5.parse(yAxis);
} catch (error: any) {
console.error('解析数据时出错:', error);
return Promise.reject('Data error');
}
const option: Option = { const option: Option = {
backgroundColor: '#f5f5f5', backgroundColor: '#f5f5f5',
title: { text: title }, title: { text: title },
tooltip: {}, tooltip: {},
xAxis: { data: parsedXAxis }, xAxis: { data: xAxis },
yAxis: {}, yAxis: {},
series: [] // 初始化为空数组 series: [] // 初始化为空数组
}; };
@@ -58,18 +53,18 @@ const generateChart = async (title: string, xAxis: string, yAxis: string, chartT
// 根据 chartType 生成不同的图表 // 根据 chartType 生成不同的图表
switch (chartType) { switch (chartType) {
case '柱状图': case '柱状图':
option.series.push({ name: 'Sample', type: 'bar', data: parsedYAxis }); option.series.push({ name: 'Sample', type: 'bar', data: yAxis.map(Number) });
break; break;
case '折线图': case '折线图':
option.series.push({ name: 'Sample', type: 'line', data: parsedYAxis }); option.series.push({ name: 'Sample', type: 'line', data: yAxis.map(Number) });
break; break;
case '饼图': case '饼图':
option.series.push({ option.series.push({
name: 'Sample', name: 'Sample',
type: 'pie', type: 'pie',
data: parsedYAxis.map((value, index) => ({ data: yAxis.map((value, index) => ({
value, value: Number(value),
name: parsedXAxis[index] // 使用 xAxis 作为饼图的名称 name: xAxis[index] // 使用 xAxis 作为饼图的名称
})) }))
}); });
break; break;

View File

@@ -1,6 +1,6 @@
{ {
"author": "silencezhang", "author": "silencezhang",
"version": "4812", "version": "4817",
"name": "基础图表", "name": "基础图表",
"avatar": "core/workflow/template/baseChart", "avatar": "core/workflow/template/baseChart",
"intro": "根据数据生成图表可根据chartType生成柱状图折线图饼图", "intro": "根据数据生成图表可根据chartType生成柱状图折线图饼图",
@@ -68,7 +68,7 @@
"canEdit": true, "canEdit": true,
"key": "yAxis", "key": "yAxis",
"label": "yAxis", "label": "yAxis",
"description": "y轴数据例如['1', '2', '3']", "description": "y轴数据例如[1,2,3]",
"defaultValue": "", "defaultValue": "",
"list": [ "list": [
{ {
@@ -77,7 +77,7 @@
} }
], ],
"required": true, "required": true,
"toolDescription": "y轴数据例如['1', '2', '3']" "toolDescription": "y轴数据例如[1,2,3]"
}, },
{ {
"renderTypeList": ["select", "reference"], "renderTypeList": ["select", "reference"],
@@ -145,8 +145,8 @@
"flowNodeType": "pluginOutput", "flowNodeType": "pluginOutput",
"showStatus": false, "showStatus": false,
"position": { "position": {
"x": 2122.252754006148, "x": 2128.8138851197145,
"y": -63.5218674613718 "y": -63.52186746137181
}, },
"version": "481", "version": "481",
"inputs": [ "inputs": [
@@ -154,10 +154,12 @@
"renderTypeList": ["reference"], "renderTypeList": ["reference"],
"valueType": "string", "valueType": "string",
"canEdit": true, "canEdit": true,
"key": "相对路径URL", "key": "图表 url",
"label": "相对路径URL", "label": "图表 url",
"description": "可用使用markdown格式展示图片![图片](url)", "description": "可用使用markdown格式展示图片![图片](url)",
"value": ["ws0DFKJnCPhk", "bzaYjKyQFOw2"] "value": ["ws0DFKJnCPhk", "bzaYjKyQFOw2"],
"isToolOutput": true,
"required": true
} }
], ],
"outputs": [] "outputs": []
@@ -170,8 +172,8 @@
"flowNodeType": "httpRequest468", "flowNodeType": "httpRequest468",
"showStatus": true, "showStatus": true,
"position": { "position": {
"x": 1216.5166647574395, "x": 1264.2009472531117,
"y": -206.30162946606856 "y": -455.0773486762623
}, },
"version": "481", "version": "481",
"inputs": [ "inputs": [
@@ -275,7 +277,7 @@
"key": "system_httpJsonBody", "key": "system_httpJsonBody",
"renderTypeList": ["hidden"], "renderTypeList": ["hidden"],
"valueType": "any", "valueType": "any",
"value": "{\r\n \"title\": \"{{title-plugin}}\",\r\n \"xAxis\": \"{{xAxis-plugin}}\",\r\n \"yAxis\": \"{{yAxis-plugin}}\",\r\n \"chartType\": \"{{chartType-plugin}}\"\r\n}", "value": "{\r\n \"title\": \"{{$pluginInput.title$}}\",\r\n \"xAxis\": {{$pluginInput.xAxis$}},\r\n \"yAxis\": {{$pluginInput.yAxis$}},\r\n \"chartType\": \"{{$pluginInput.chartType$}}\"\r\n}",
"label": "", "label": "",
"required": false, "required": false,
"valueDesc": "", "valueDesc": "",
@@ -306,126 +308,6 @@
"description": "", "description": "",
"debugLabel": "", "debugLabel": "",
"toolDescription": "" "toolDescription": ""
},
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "title-plugin",
"label": "title-plugin",
"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", "title"]
},
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "xAxis-plugin",
"label": "xAxis-plugin",
"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", "xAxis"]
},
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "yAxis-plugin",
"label": "yAxis-plugin",
"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", "yAxis"]
},
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "chartType-plugin",
"label": "chartType-plugin",
"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", "chartType"]
} }
], ],
"outputs": [ "outputs": [

View File

@@ -2,7 +2,7 @@
"author": "", "author": "",
"version": "488", "version": "488",
"name": "飞书 webhook", "name": "飞书 webhook",
"avatar": "/appMarketTemplates/plugin-feishu/avatar.svg", "avatar": "core/app/templates/plugin-feishu",
"intro": "向飞书机器人发起 webhook 请求。", "intro": "向飞书机器人发起 webhook 请求。",
"courseUrl": "https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot#f62e72d5", "courseUrl": "https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot#f62e72d5",
"showStatus": false, "showStatus": false,

View File

@@ -48,6 +48,16 @@ const main = async (props: Props, retry = 3): Response => {
}); });
}); });
if (results.length === 0) {
return {
result: JSON.stringify([]),
error: {
message: 'No search results',
code: 500
}
};
}
return { return {
result: JSON.stringify(results.slice(0, 10)) result: JSON.stringify(results.slice(0, 10))
}; };

View File

@@ -4,7 +4,7 @@ import fsp from 'fs/promises';
import fs from 'fs'; import fs from 'fs';
import { DatasetFileSchema } from '@fastgpt/global/core/dataset/type'; import { DatasetFileSchema } from '@fastgpt/global/core/dataset/type';
import { MongoChatFileSchema, MongoDatasetFileSchema } from './schema'; import { MongoChatFileSchema, MongoDatasetFileSchema } from './schema';
import { detectFileEncoding } from '@fastgpt/global/common/file/tools'; import { detectFileEncoding, detectFileEncodingByPath } from '@fastgpt/global/common/file/tools';
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common'; import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
import { MongoRawTextBuffer } from '../../buffer/rawText/schema'; import { MongoRawTextBuffer } from '../../buffer/rawText/schema';
import { readRawContentByFileBuffer } from '../read/utils'; import { readRawContentByFileBuffer } from '../read/utils';
@@ -36,7 +36,6 @@ export async function uploadFile({
path, path,
filename, filename,
contentType, contentType,
encoding,
metadata = {} metadata = {}
}: { }: {
bucketName: `${BucketNameEnum}`; bucketName: `${BucketNameEnum}`;
@@ -45,7 +44,6 @@ export async function uploadFile({
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`);
@@ -59,7 +57,7 @@ export async function uploadFile({
// Add default metadata // Add default metadata
metadata.teamId = teamId; metadata.teamId = teamId;
metadata.uid = uid; metadata.uid = uid;
metadata.encoding = encoding; metadata.encoding = await detectFileEncodingByPath(path);
// create a gridfs bucket // create a gridfs bucket
const bucket = getGridBucket(bucketName); const bucket = getGridBucket(bucketName);

View File

@@ -1,43 +1,92 @@
import { UploadImgProps } from '@fastgpt/global/common/file/api'; import { UploadImgProps } from '@fastgpt/global/common/file/api';
import { imageBaseUrl } from '@fastgpt/global/common/file/image/constants'; import { imageBaseUrl } from '@fastgpt/global/common/file/image/constants';
import { MongoImage } from './schema'; import { MongoImage } from './schema';
import { ClientSession } from '../../../common/mongo'; import { ClientSession, Types } from '../../../common/mongo';
import { guessBase64ImageType } from '../utils'; import { guessBase64ImageType } from '../utils';
import { readFromSecondary } from '../../mongo/utils'; import { readFromSecondary } from '../../mongo/utils';
import { addHours } from 'date-fns';
export const maxImgSize = 1024 * 1024 * 12; export const maxImgSize = 1024 * 1024 * 12;
const base64MimeRegex = /data:image\/([^\)]+);base64/; const base64MimeRegex = /data:image\/([^\)]+);base64/;
export async function uploadMongoImg({ export async function uploadMongoImg({
type,
base64Img, base64Img,
teamId, teamId,
expiredTime,
metadata, metadata,
shareId shareId,
forever = false
}: UploadImgProps & { }: UploadImgProps & {
teamId: string; teamId: string;
forever?: Boolean;
}) { }) {
if (base64Img.length > maxImgSize) { if (base64Img.length > maxImgSize) {
return Promise.reject('Image too large'); return Promise.reject('Image too large');
} }
const [base64Mime, base64Data] = base64Img.split(','); const [base64Mime, base64Data] = base64Img.split(',');
// Check if mime type is valid
if (!base64MimeRegex.test(base64Mime)) {
return Promise.reject('Invalid image mime type');
}
const mime = `image/${base64Mime.match(base64MimeRegex)?.[1] ?? 'image/jpeg'}`; const mime = `image/${base64Mime.match(base64MimeRegex)?.[1] ?? 'image/jpeg'}`;
const binary = Buffer.from(base64Data, 'base64'); const binary = Buffer.from(base64Data, 'base64');
const extension = mime.split('/')[1]; const extension = mime.split('/')[1];
const { _id } = await MongoImage.create({ const { _id } = await MongoImage.create({
type,
teamId, teamId,
binary, binary,
expiredTime,
metadata: Object.assign({ mime }, metadata), metadata: Object.assign({ mime }, metadata),
shareId shareId,
expiredTime: forever ? undefined : addHours(new Date(), 1)
}); });
return `${process.env.FE_DOMAIN || ''}${process.env.NEXT_PUBLIC_BASE_URL || ''}${imageBaseUrl}${String(_id)}.${extension}`; return `${process.env.FE_DOMAIN || ''}${process.env.NEXT_PUBLIC_BASE_URL || ''}${imageBaseUrl}${String(_id)}.${extension}`;
} }
const getIdFromPath = (path?: string) => {
if (!path) return;
const paths = path.split('/');
const name = paths[paths.length - 1];
if (!name) return;
const id = name.split('.')[0];
if (!id || !Types.ObjectId.isValid(id)) return;
return id;
};
// 删除旧的头像,新的头像去除过期时间
export const refreshSourceAvatar = async (
path?: string,
oldPath?: string,
session?: ClientSession
) => {
const newId = getIdFromPath(path);
const oldId = getIdFromPath(oldPath);
if (!newId) return;
await MongoImage.updateOne({ _id: newId }, { $unset: { expiredTime: 1 } }, { session });
if (oldId) {
await MongoImage.deleteOne({ _id: oldId }, { session });
}
};
export const removeImageByPath = (path?: string, session?: ClientSession) => {
if (!path) return;
const paths = path.split('/');
const name = paths[paths.length - 1];
if (!name) return;
const id = name.split('.')[0];
if (!id || !Types.ObjectId.isValid(id)) return;
return MongoImage.deleteOne({ _id: id }, { session });
};
export async function readMongoImg({ id }: { id: string }) { export async function readMongoImg({ id }: { id: string }) {
const formatId = id.replace(/\.[^/.]+$/, ''); const formatId = id.replace(/\.[^/.]+$/, '');

View File

@@ -1,8 +1,7 @@
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant'; import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
import { connectionMongo, getMongoModel, type Model } from '../../mongo'; import { connectionMongo, getMongoModel } from '../../mongo';
import { MongoImageSchemaType } from '@fastgpt/global/common/file/image/type.d'; import { MongoImageSchemaType } from '@fastgpt/global/common/file/image/type.d';
import { mongoImageTypeMap } from '@fastgpt/global/common/file/image/constants'; const { Schema } = connectionMongo;
const { Schema, model, models } = connectionMongo;
const ImageSchema = new Schema({ const ImageSchema = new Schema({
teamId: { teamId: {
@@ -14,27 +13,15 @@ const ImageSchema = new Schema({
type: Date, type: Date,
default: () => new Date() default: () => new Date()
}, },
expiredTime: { expiredTime: Date,
type: Date binary: Buffer,
}, metadata: Object
binary: {
type: Buffer
},
type: {
type: String,
enum: Object.keys(mongoImageTypeMap),
required: true
},
metadata: {
type: Object
}
}); });
try { try {
// tts expired60 Minutes // tts expired60 Minutes
ImageSchema.index({ expiredTime: 1 }, { expireAfterSeconds: 60 * 60 }); ImageSchema.index({ expiredTime: 1 }, { expireAfterSeconds: 60 * 60 });
ImageSchema.index({ type: 1 }); ImageSchema.index({ type: 1 });
ImageSchema.index({ createTime: 1 });
// delete related img // delete related img
ImageSchema.index({ teamId: 1, 'metadata.relatedId': 1 }); ImageSchema.index({ teamId: 1, 'metadata.relatedId': 1 });
} catch (error) { } catch (error) {

View File

@@ -1,5 +1,4 @@
import { uploadMongoImg } from '../image/controller'; import { uploadMongoImg } from '../image/controller';
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
import FormData from 'form-data'; import FormData from 'form-data';
import { WorkerNameEnum, runWorker } from '../../../worker/utils'; import { WorkerNameEnum, runWorker } from '../../../worker/utils';
@@ -8,7 +7,6 @@ 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 { matchMdImgTextAndUpload } from '@fastgpt/global/common/string/markdown'; import { matchMdImgTextAndUpload } from '@fastgpt/global/common/string/markdown';
export type readRawTextByLocalFileParams = { export type readRawTextByLocalFileParams = {
@@ -22,7 +20,7 @@ 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 = await fs.promises.readFile(path);
const { rawText } = await readRawContentByFileBuffer({ const { rawText } = await readRawContentByFileBuffer({
extension, extension,
@@ -114,10 +112,9 @@ export const readRawContentByFileBuffer = async ({
if (imageList) { if (imageList) {
await batchRun(imageList, async (item) => { await batchRun(imageList, async (item) => {
const src = await uploadMongoImg({ const src = await uploadMongoImg({
type: MongoImageTypeEnum.collectionImage,
base64Img: `data:${item.mime};base64,${item.base64}`, base64Img: `data:${item.mime};base64,${item.base64}`,
teamId, teamId,
expiredTime: addHours(new Date(), 1), // expiredTime: addHours(new Date(), 1),
metadata: { metadata: {
...metadata, ...metadata,
mime: item.mime mime: item.mime

View File

@@ -3,10 +3,13 @@ import NextCors from 'nextjs-cors';
export async function withNextCors(req: NextApiRequest, res: NextApiResponse) { export async function withNextCors(req: NextApiRequest, res: NextApiResponse) {
const methods = ['GET', 'eHEAD', 'PUT', 'PATCH', 'POST', 'DELETE']; const methods = ['GET', 'eHEAD', 'PUT', 'PATCH', 'POST', 'DELETE'];
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',');
const origin = req.headers.origin; const origin = req.headers.origin;
await NextCors(req, res, { await NextCors(req, res, {
methods, methods,
origin: origin, origin: allowedOrigins || origin,
optionsSuccessStatus: 200 optionsSuccessStatus: 200
}); });
} }

View File

@@ -9,10 +9,10 @@ import { jsonRes } from '../response';
// unit: times/s // unit: times/s
// how to use? // how to use?
// export default NextAPI(useQPSLimit(10), handler); // limit 10 times per second for a ip // export default NextAPI(useQPSLimit(10), handler); // limit 10 times per second for a ip
export function useReqFrequencyLimit(seconds: number, limit: number) { export function useReqFrequencyLimit(seconds: number, limit: number, force = false) {
return async (req: ApiRequestProps, res: NextApiResponse) => { return async (req: ApiRequestProps, res: NextApiResponse) => {
const ip = requestIp.getClientIp(req); const ip = requestIp.getClientIp(req);
if (!ip || process.env.USE_IP_LIMIT !== 'true') { if (!ip || (process.env.USE_IP_LIMIT !== 'true' && !force)) {
return; return;
} }
try { try {
@@ -22,10 +22,9 @@ export function useReqFrequencyLimit(seconds: number, limit: number) {
expiredTime: addSeconds(new Date(), seconds) expiredTime: addSeconds(new Date(), seconds)
}); });
} catch (_) { } catch (_) {
res.status(429);
jsonRes(res, { jsonRes(res, {
code: 429, code: 429,
message: ERROR_ENUM.tooManyRequest error: ERROR_ENUM.tooManyRequest
}); });
} }
}; };

View File

@@ -33,7 +33,7 @@ export const jsonRes = <T = any>(
addLog.error(`Api response error: ${url}`, ERROR_RESPONSE[errResponseKey]); addLog.error(`Api response error: ${url}`, ERROR_RESPONSE[errResponseKey]);
return res.json(ERROR_RESPONSE[errResponseKey]); return res.status(code).json(ERROR_RESPONSE[errResponseKey]);
} }
// another error // another error

View File

@@ -15,6 +15,9 @@ export const initFastGPTConfig = (config?: FastGPTConfigFileType) => {
global.subPlans = config.subPlans; global.subPlans = config.subPlans;
global.llmModels = config.llmModels; global.llmModels = config.llmModels;
global.llmModelPriceType = global.llmModels.some((item) => typeof item.inputPrice === 'number')
? 'IO'
: 'Tokens';
global.vectorModels = config.vectorModels; global.vectorModels = config.vectorModels;
global.audioSpeechModels = config.audioSpeechModels; global.audioSpeechModels = config.audioSpeechModels;
global.whisperModel = config.whisperModel; global.whisperModel = config.whisperModel;

View File

@@ -0,0 +1,11 @@
{
"provider": "OpenAI",
"model": "text-embedding-ada-002",
"name": "text-embedding-ada-002",
"defaultToken": 512, // 默认分块 token
"maxToken": 3000, // 最大分块 token
"weight": 0, // 权重
"charsPointsPrice": 0 // 积分/1k token
}

View File

@@ -0,0 +1,33 @@
{
"provider": "OpenAI",
"model": "gpt-4o-mini",
"name": "GPT-4o-mini", // alias
"maxContext": 125000, // 最大上下文
"maxResponse": 16000, // 最大回复
"quoteMaxToken": 60000, // 最大引用
"maxTemperature": 1.2, // 最大温度
"presencePenaltyRange": [-2, 2], // 惩罚系数范围
"frequencyPenaltyRange": [-2, 2], // 频率惩罚系数范围
"responseFormatList": ["text", "json_object", "json_schema"], // 响应格式
"showStopSign": true, // 是否显示停止符号
"vision": true, // 是否支持图片识别
"toolChoice": true, // 是否支持工具调用
"functionCall": false, // 是否支持函数调用(一般都可以 false 了,基本不用了)
"defaultSystemChatPrompt": "", // 默认系统提示
"datasetProcess": true, // 用于知识库文本处理
"usedInClassify": true, // 用于问题分类
"customCQPrompt": "", // 自定义问题分类提示
"usedInExtractFields": true, // 用于提取字段
"customExtractPrompt": "", // 自定义提取提示
"usedInToolCall": true, // 用于工具调用
"usedInQueryExtension": true, // 用于问题优化
"defaultConfig": {}, // 额外的自定义 body
"fieldMap": {}, // body 字段映射
"censor": false, // 是否开启敏感词过滤
"charsPointsPrice": 0 // n 积分/1k token
}

View File

@@ -0,0 +1,6 @@
{
"provider": "BAAI",
"model": "bge-reranker-v2-m3",
"name": "bge-reranker-v2-m3",
"charsPointsPrice": 0
}

View File

@@ -0,0 +1,6 @@
{
"provider": "OpenAI",
"model": "whisper-1",
"name": "whisper-1",
"charsPointsPrice": 0
}

View File

@@ -0,0 +1,32 @@
{
"provider": "OpenAI",
"model": "tts-1",
"name": "TTS1",
"charsPointsPrice": 0,
"voices": [
{
"label": "Alloy",
"value": "alloy"
},
{
"label": "Echo",
"value": "echo"
},
{
"label": "Fable",
"value": "fable"
},
{
"label": "Onyx",
"value": "onyx"
},
{
"label": "Nova",
"value": "nova"
},
{
"label": "Shimmer",
"value": "shimmer"
}
]
}

View File

@@ -1,6 +1,6 @@
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 { createChatCompletion } from '../config';
import { countGptMessagesTokens } from '../../../common/string/tiktoken/index'; import { countGptMessagesTokens, countPromptTokens } from '../../../common/string/tiktoken/index';
import { loadRequestMessages } from '../../chat/utils'; import { loadRequestMessages } from '../../chat/utils';
import { llmCompletionsBodyFormat } from '../utils'; import { llmCompletionsBodyFormat } from '../utils';
import { import {
@@ -20,7 +20,8 @@ export async function createQuestionGuide({
customPrompt?: string; customPrompt?: string;
}): Promise<{ }): Promise<{
result: string[]; result: string[];
tokens: number; inputTokens: number;
outputTokens: number;
}> { }> {
const concatMessages: ChatCompletionMessageParam[] = [ const concatMessages: ChatCompletionMessageParam[] = [
...messages, ...messages,
@@ -29,6 +30,10 @@ export async function createQuestionGuide({
content: `${customPrompt || PROMPT_QUESTION_GUIDE}\n${PROMPT_QUESTION_GUIDE_FOOTER}` content: `${customPrompt || PROMPT_QUESTION_GUIDE}\n${PROMPT_QUESTION_GUIDE_FOOTER}`
} }
]; ];
const requestMessages = await loadRequestMessages({
messages: concatMessages,
useVision: false
});
const { response: data } = await createChatCompletion({ const { response: data } = await createChatCompletion({
body: llmCompletionsBodyFormat( body: llmCompletionsBodyFormat(
@@ -36,10 +41,7 @@ export async function createQuestionGuide({
model, model,
temperature: 0.1, temperature: 0.1,
max_tokens: 200, max_tokens: 200,
messages: await loadRequestMessages({ messages: requestMessages,
messages: concatMessages,
useVision: false
}),
stream: false stream: false
}, },
model model
@@ -51,13 +53,15 @@ export async function createQuestionGuide({
const start = answer.indexOf('['); const start = answer.indexOf('[');
const end = answer.lastIndexOf(']'); const end = answer.lastIndexOf(']');
const tokens = await countGptMessagesTokens(concatMessages); const inputTokens = await countGptMessagesTokens(requestMessages);
const outputTokens = await countPromptTokens(answer);
if (start === -1 || end === -1) { if (start === -1 || end === -1) {
addLog.warn('Create question guide error', { answer }); addLog.warn('Create question guide error', { answer });
return { return {
result: [], result: [],
tokens: 0 inputTokens: 0,
outputTokens: 0
}; };
} }
@@ -69,14 +73,16 @@ export async function createQuestionGuide({
try { try {
return { return {
result: json5.parse(jsonStr), result: json5.parse(jsonStr),
tokens inputTokens,
outputTokens
}; };
} catch (error) { } catch (error) {
console.log(error); console.log(error);
return { return {
result: [], result: [],
tokens: 0 inputTokens: 0,
outputTokens: 0
}; };
} }
} }

View File

@@ -1,7 +1,7 @@
import { replaceVariable } from '@fastgpt/global/common/string/tools'; import { replaceVariable } from '@fastgpt/global/common/string/tools';
import { createChatCompletion } from '../config'; import { createChatCompletion } 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, countPromptTokens } from '../../../common/string/tiktoken/index';
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';
@@ -121,7 +121,8 @@ export const queryExtension = async ({
rawQuery: string; rawQuery: string;
extensionQueries: string[]; extensionQueries: string[];
model: string; model: string;
tokens: number; inputTokens: number;
outputTokens: number;
}> => { }> => {
const systemFewShot = chatBg const systemFewShot = chatBg
? `Q: 对话背景。 ? `Q: 对话背景。
@@ -166,7 +167,8 @@ A: ${chatBg}
rawQuery: query, rawQuery: query,
extensionQueries: [], extensionQueries: [],
model, model,
tokens: 0 inputTokens: 0,
outputTokens: 0
}; };
} }
@@ -181,7 +183,8 @@ A: ${chatBg}
rawQuery: query, rawQuery: query,
extensionQueries: Array.isArray(queries) ? queries : [], extensionQueries: Array.isArray(queries) ? queries : [],
model, model,
tokens: await countGptMessagesTokens(messages) inputTokens: await countGptMessagesTokens(messages),
outputTokens: await countPromptTokens(answer)
}; };
} catch (error) { } catch (error) {
addLog.error(`Query extension error`, error); addLog.error(`Query extension error`, error);
@@ -189,7 +192,8 @@ A: ${chatBg}
rawQuery: query, rawQuery: query,
extensionQueries: [], extensionQueries: [],
model, model,
tokens: 0 inputTokens: 0,
outputTokens: 0
}; };
} }
}; };

View File

@@ -4,6 +4,7 @@ export const getLLMModel = (model?: string) => {
global.llmModels[0] global.llmModels[0]
); );
}; };
export const getDatasetModel = (model?: string) => { export const getDatasetModel = (model?: string) => {
return ( return (
global.llmModels global.llmModels

View File

@@ -1,11 +1,15 @@
import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type'; import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type';
import { PluginRuntimeType } from '@fastgpt/global/core/plugin/type'; import { PluginRuntimeType } from '@fastgpt/global/core/plugin/type';
import { splitCombinePluginId } from './controller';
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
/* /*
Plugin points calculation: Plugin points calculation:
1. Return 0 if error 1. 系统插件/商业版插件:
2. Add configured points if commercial plugin - 有错误:返回 0
3. Add sum of child nodes points - 无错误:返回 单次积分 + 子流程积分(可配置)
2. 个人插件
- 返回 子流程积分
*/ */
export const computedPluginUsage = async ({ export const computedPluginUsage = async ({
plugin, plugin,
@@ -16,13 +20,16 @@ export const computedPluginUsage = async ({
childrenUsage: ChatNodeUsageType[]; childrenUsage: ChatNodeUsageType[];
error?: boolean; error?: boolean;
}) => { }) => {
if (error) { const { source } = await splitCombinePluginId(plugin.id);
return 0; const childrenUsages = childrenUsage.reduce((sum, item) => sum + (item.totalPoints || 0), 0);
if (source !== PluginSourceEnum.personal) {
if (error) return 0;
const pluginCurrentCost = plugin.currentCost ?? 0;
return plugin.hasTokenFee ? pluginCurrentCost + childrenUsages : pluginCurrentCost;
} }
const childrenIUsages = childrenUsage.reduce((sum, item) => sum + (item.totalPoints || 0), 0); return childrenUsages;
const pluginCurrentCose = plugin.currentCost ?? 0;
return plugin.hasTokenFee ? pluginCurrentCose + childrenIUsages : pluginCurrentCose;
}; };

View File

@@ -86,24 +86,21 @@ const ChatItemSchema = new Schema({
}); });
try { try {
ChatItemSchema.index({ dataId: 1 }, { background: true }); ChatItemSchema.index({ dataId: 1 });
/* delete by app; /* delete by app;
delete by chat id; delete by chat id;
get chat list; get chat list;
get chat logs; get chat logs;
close custom feedback; close custom feedback;
*/ */
ChatItemSchema.index({ appId: 1, chatId: 1, dataId: 1 }, { background: true }); ChatItemSchema.index({ appId: 1, chatId: 1, dataId: 1 });
// admin charts // admin charts
ChatItemSchema.index({ time: -1, obj: 1 }, { background: true }); ChatItemSchema.index({ time: -1, obj: 1 });
// timer, clear history // timer, clear history
ChatItemSchema.index({ teamId: 1, time: -1 }, { background: true }); ChatItemSchema.index({ teamId: 1, time: -1 });
// Admin charts // Admin charts
ChatItemSchema.index( ChatItemSchema.index({ obj: 1, time: -1 }, { partialFilterExpression: { obj: 'Human' } });
{ obj: 1, time: -1 },
{ background: true, partialFilterExpression: { obj: 'Human' } }
);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }

View File

@@ -81,19 +81,19 @@ const ChatSchema = new Schema({
}); });
try { try {
ChatSchema.index({ chatId: 1 }, { background: true }); ChatSchema.index({ chatId: 1 });
// get user history // get user history
ChatSchema.index({ tmbId: 1, appId: 1, top: -1, updateTime: -1 }, { background: true }); ChatSchema.index({ tmbId: 1, appId: 1, top: -1, updateTime: -1 });
// delete by appid; clear history; init chat; update chat; auth chat; get chat; // delete by appid; clear history; init chat; update chat; auth chat; get chat;
ChatSchema.index({ appId: 1, chatId: 1 }, { background: true }); ChatSchema.index({ appId: 1, chatId: 1 });
// get chat logs; // get chat logs;
ChatSchema.index({ teamId: 1, appId: 1, updateTime: -1 }, { background: true }); ChatSchema.index({ teamId: 1, appId: 1, updateTime: -1 });
// get share chat history // get share chat history
ChatSchema.index({ shareId: 1, outLinkUid: 1, updateTime: -1 }, { background: true }); ChatSchema.index({ shareId: 1, outLinkUid: 1, updateTime: -1 });
// timer, clear history // timer, clear history
ChatSchema.index({ teamId: 1, updateTime: -1 }, { background: true }); ChatSchema.index({ teamId: 1, updateTime: -1 });
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }

View File

@@ -24,6 +24,7 @@ import { pushDataListToTrainingQueue } from '../training/controller';
import { MongoImage } from '../../../common/file/image/schema'; import { MongoImage } from '../../../common/file/image/schema';
import { hashStr } from '@fastgpt/global/common/string/tools'; import { hashStr } from '@fastgpt/global/common/string/tools';
import { addDays } from 'date-fns'; import { addDays } from 'date-fns';
import { MongoDatasetDataText } from '../data/dataTextSchema';
export const createCollectionAndInsertData = async ({ export const createCollectionAndInsertData = async ({
dataset, dataset,
@@ -240,12 +241,12 @@ export const delCollectionRelatedSource = async ({
.map((item) => item?.metadata?.relatedImgId || '') .map((item) => item?.metadata?.relatedImgId || '')
.filter(Boolean); .filter(Boolean);
// delete files // Delete files
await delFileByFileIdList({ await delFileByFileIdList({
bucketName: BucketNameEnum.dataset, bucketName: BucketNameEnum.dataset,
fileIdList fileIdList
}); });
// delete images // Delete images
await delImgByRelatedId({ await delImgByRelatedId({
teamId, teamId,
relateIds: relatedImageIds, relateIds: relatedImageIds,
@@ -273,7 +274,7 @@ export async function delCollection({
const datasetIds = Array.from(new Set(collections.map((item) => String(item.datasetId)))); const datasetIds = Array.from(new Set(collections.map((item) => String(item.datasetId))));
const collectionIds = collections.map((item) => String(item._id)); const collectionIds = collections.map((item) => String(item._id));
// delete training data // Delete training data
await MongoDatasetTraining.deleteMany({ await MongoDatasetTraining.deleteMany({
teamId, teamId,
datasetIds: { $in: datasetIds }, datasetIds: { $in: datasetIds },
@@ -285,11 +286,16 @@ export async function delCollection({
await delCollectionRelatedSource({ collections, session }); await delCollectionRelatedSource({ collections, session });
} }
// delete dataset.datas // Delete dataset_datas
await MongoDatasetData.deleteMany( await MongoDatasetData.deleteMany(
{ teamId, datasetIds: { $in: datasetIds }, collectionId: { $in: collectionIds } }, { teamId, datasetIds: { $in: datasetIds }, collectionId: { $in: collectionIds } },
{ session } { session }
); );
// Delete dataset_data_texts
await MongoDatasetDataText.deleteMany(
{ teamId, datasetIds: { $in: datasetIds }, collectionId: { $in: collectionIds } },
{ session }
);
// delete collections // delete collections
await MongoDatasetCollection.deleteMany( await MongoDatasetCollection.deleteMany(

View File

@@ -6,6 +6,7 @@ import { ClientSession } from '../../common/mongo';
import { MongoDatasetTraining } from './training/schema'; import { MongoDatasetTraining } from './training/schema';
import { MongoDatasetData } from './data/schema'; import { MongoDatasetData } from './data/schema';
import { deleteDatasetDataVector } from '../../common/vectorStore/controller'; import { deleteDatasetDataVector } from '../../common/vectorStore/controller';
import { MongoDatasetDataText } from './data/dataTextSchema';
/* ============= dataset ========== */ /* ============= dataset ========== */
/* find all datasetId by top datasetId */ /* find all datasetId by top datasetId */
@@ -92,7 +93,7 @@ export async function delDatasetRelevantData({
{ session } { session }
).lean(); ).lean();
// image and file // Delete Image and file
await delCollectionRelatedSource({ collections, session }); await delCollectionRelatedSource({ collections, session });
// delete collections // delete collections
@@ -101,9 +102,15 @@ export async function delDatasetRelevantData({
datasetId: { $in: datasetIds } datasetId: { $in: datasetIds }
}).session(session); }).session(session);
// delete dataset.datas(Not need session) // No session delete:
// Delete dataset_data_texts
await MongoDatasetDataText.deleteMany({
teamId,
datasetId: { $in: datasetIds }
});
// delete dataset_datas
await MongoDatasetData.deleteMany({ teamId, datasetId: { $in: datasetIds } }); await MongoDatasetData.deleteMany({ teamId, datasetId: { $in: datasetIds } });
// no session delete: delete files, vector data // Delete vector data
await deleteDatasetDataVector({ teamId, datasetIds }); await deleteDatasetDataVector({ teamId, datasetIds });
} }

View File

@@ -0,0 +1,45 @@
import { connectionMongo, getMongoModel } from '../../../common/mongo';
const { Schema } = connectionMongo;
import { DatasetDataSchemaType } from '@fastgpt/global/core/dataset/type.d';
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
import { DatasetCollectionName } from '../schema';
import { DatasetColCollectionName } from '../collection/schema';
import { DatasetDataCollectionName } from './schema';
export const DatasetDataTextCollectionName = 'dataset_data_texts';
const DatasetDataTextSchema = new Schema({
teamId: {
type: Schema.Types.ObjectId,
ref: TeamCollectionName,
required: true
},
datasetId: {
type: Schema.Types.ObjectId,
ref: DatasetCollectionName,
required: true
},
collectionId: {
type: Schema.Types.ObjectId,
ref: DatasetColCollectionName,
required: true
},
dataId: {
type: Schema.Types.ObjectId,
ref: DatasetDataCollectionName,
required: true
},
fullTextToken: String
});
try {
DatasetDataTextSchema.index({ teamId: 1, datasetId: 1, fullTextToken: 'text' });
DatasetDataTextSchema.index({ dataId: 1 }, { unique: true });
} catch (error) {
console.log(error);
}
export const MongoDatasetDataText = getMongoModel<DatasetDataSchemaType>(
DatasetDataTextCollectionName,
DatasetDataTextSchema
);

View File

@@ -1,4 +1,4 @@
import { connectionMongo, getMongoModel, type Model } from '../../../common/mongo'; import { connectionMongo, getMongoModel } from '../../../common/mongo';
const { Schema, model, models } = connectionMongo; const { Schema, model, models } = connectionMongo;
import { DatasetDataSchemaType } from '@fastgpt/global/core/dataset/type.d'; import { DatasetDataSchemaType } from '@fastgpt/global/core/dataset/type.d';
import { import {
@@ -39,10 +39,6 @@ const DatasetDataSchema = new Schema({
type: String, type: String,
default: '' default: ''
}, },
fullTextToken: {
type: String,
default: ''
},
indexes: { indexes: {
type: [ type: [
{ {
@@ -71,17 +67,11 @@ const DatasetDataSchema = new Schema({
type: Number, type: Number,
default: 0 default: 0
}, },
inited: { rebuilding: Boolean,
type: Boolean
},
rebuilding: Boolean
});
DatasetDataSchema.virtual('collection', { // Abandon
ref: DatasetColCollectionName, fullTextToken: String,
localField: 'collectionId', initFullText: Boolean
foreignField: '_id',
justOne: true
}); });
try { try {
@@ -93,13 +83,15 @@ try {
chunkIndex: 1, chunkIndex: 1,
updateTime: -1 updateTime: -1
}); });
// full text index // FullText tmp full text index
DatasetDataSchema.index({ teamId: 1, datasetId: 1, fullTextToken: 'text' }); // DatasetDataSchema.index({ teamId: 1, datasetId: 1, fullTextToken: 'text' });
// Recall vectors after data matching // Recall vectors after data matching
DatasetDataSchema.index({ teamId: 1, datasetId: 1, collectionId: 1, 'indexes.dataId': 1 }); DatasetDataSchema.index({ teamId: 1, datasetId: 1, collectionId: 1, 'indexes.dataId': 1 });
DatasetDataSchema.index({ updateTime: 1 }); DatasetDataSchema.index({ updateTime: 1 });
// rebuild data // rebuild data
DatasetDataSchema.index({ rebuilding: 1, teamId: 1, datasetId: 1 }); DatasetDataSchema.index({ rebuilding: 1, teamId: 1, datasetId: 1 });
DatasetDataSchema.index({ initFullText: 1 });
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }

View File

@@ -8,8 +8,8 @@ import { getVectorsByText } from '../../ai/embedding';
import { getVectorModel } from '../../ai/model'; import { getVectorModel } from '../../ai/model';
import { MongoDatasetData } from '../data/schema'; import { MongoDatasetData } from '../data/schema';
import { import {
DatasetCollectionSchemaType,
DatasetDataSchemaType, DatasetDataSchemaType,
DatasetDataTextSchemaType,
SearchDataResponseItemType SearchDataResponseItemType
} from '@fastgpt/global/core/dataset/type'; } from '@fastgpt/global/core/dataset/type';
import { MongoDatasetCollection } from '../collection/schema'; import { MongoDatasetCollection } from '../collection/schema';
@@ -23,6 +23,7 @@ import { Types } from '../../../common/mongo';
import json5 from 'json5'; import json5 from 'json5';
import { MongoDatasetCollectionTags } from '../tag/schema'; import { MongoDatasetCollectionTags } from '../tag/schema';
import { readFromSecondary } from '../../../common/mongo/utils'; import { readFromSecondary } from '../../../common/mongo/utils';
import { MongoDatasetDataText } from '../data/dataTextSchema';
type SearchDatasetDataProps = { type SearchDatasetDataProps = {
teamId: string; teamId: string;
@@ -266,57 +267,60 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
filterCollectionIdList filterCollectionIdList
}); });
// get q and a // Get data and collections
const dataList = await MongoDatasetData.find( const collectionIdList = Array.from(new Set(results.map((item) => item.collectionId)));
{ const [dataList, collections] = await Promise.all([
teamId, MongoDatasetData.find(
datasetId: { $in: datasetIds }, {
collectionId: { $in: Array.from(new Set(results.map((item) => item.collectionId))) }, teamId,
'indexes.dataId': { $in: results.map((item) => item.id?.trim()) } datasetId: { $in: datasetIds },
}, collectionId: { $in: collectionIdList },
'datasetId collectionId updateTime q a chunkIndex indexes' 'indexes.dataId': { $in: results.map((item) => item.id?.trim()) }
) },
.populate<{ collection: DatasetCollectionSchemaType }>( '_id datasetId collectionId updateTime q a chunkIndex indexes',
'collection', { ...readFromSecondary }
'name fileId rawLink externalFileId externalFileUrl' ).lean(),
) MongoDatasetCollection.find(
.lean(); {
_id: { $in: collectionIdList }
},
'_id name fileId rawLink externalFileId externalFileUrl',
{ ...readFromSecondary }
).lean()
]);
// add score to data(It's already sorted. The first one is the one with the most points) const formatResult = results
const concatResults = dataList.map((data) => { .map((item, index) => {
const dataIdList = data.indexes.map((item) => item.dataId); const collection = collections.find((col) => String(col._id) === String(item.collectionId));
if (!collection) {
console.log('Collection is not found', item);
return;
}
const data = dataList.find((data) =>
data.indexes.some((index) => index.dataId === item.id)
);
if (!data) {
console.log('Data is not found', item);
return;
}
const maxScoreResult = results.find((item) => { const score = item?.score || 0;
return dataIdList.includes(item.id);
});
return { const result: SearchDataResponseItemType = {
...data, id: String(data._id),
score: maxScoreResult?.score || 0 updateTime: data.updateTime,
}; q: data.q,
}); a: data.a,
chunkIndex: data.chunkIndex,
datasetId: String(data.datasetId),
collectionId: String(data.collectionId),
...getCollectionSourceData(collection),
score: [{ type: SearchScoreTypeEnum.embedding, value: score, index }]
};
concatResults.sort((a, b) => b.score - a.score); return result;
})
const formatResult = concatResults.map((data, index) => { .filter(Boolean) as SearchDataResponseItemType[];
if (!data.collectionId) {
console.log('Collection is not found', data);
}
const result: SearchDataResponseItemType = {
id: String(data._id),
updateTime: data.updateTime,
q: data.q,
a: data.a,
chunkIndex: data.chunkIndex,
datasetId: String(data.datasetId),
collectionId: String(data.collectionId),
...getCollectionSourceData(data.collection),
score: [{ type: SearchScoreTypeEnum.embedding, value: data.score, index }]
};
return result;
});
return { return {
embeddingRecallResults: formatResult, embeddingRecallResults: formatResult,
@@ -344,88 +348,224 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
}; };
} }
let searchResults = ( const searchResults = (
await Promise.all( await Promise.all(
datasetIds.map(async (id) => { datasetIds.map(async (id) => {
return MongoDatasetData.aggregate([ return MongoDatasetData.aggregate(
{ [
$match: { {
teamId: new Types.ObjectId(teamId), $match: {
datasetId: new Types.ObjectId(id), teamId: new Types.ObjectId(teamId),
$text: { $search: jiebaSplit({ text: query }) }, datasetId: new Types.ObjectId(id),
...(filterCollectionIdList $text: { $search: jiebaSplit({ text: query }) },
? { ...(filterCollectionIdList
collectionId: { ? {
$in: filterCollectionIdList.map((id) => new Types.ObjectId(id)) collectionId: {
$in: filterCollectionIdList.map((id) => new Types.ObjectId(id))
}
} }
} : {}),
: {}), ...(forbidCollectionIdList && forbidCollectionIdList.length > 0
...(forbidCollectionIdList && forbidCollectionIdList.length > 0 ? {
? { collectionId: {
collectionId: { $nin: forbidCollectionIdList.map((id) => new Types.ObjectId(id))
$nin: forbidCollectionIdList.map((id) => new Types.ObjectId(id)) }
} }
} : {})
: {}) }
},
{
$sort: {
score: { $meta: 'textScore' }
}
},
{
$limit: limit
},
{
$project: {
_id: 1,
datasetId: 1,
collectionId: 1,
updateTime: 1,
q: 1,
a: 1,
chunkIndex: 1,
score: { $meta: 'textScore' }
}
} }
}, ],
{ {
$addFields: { ...readFromSecondary
score: { $meta: 'textScore' }
}
},
{
$sort: {
score: { $meta: 'textScore' }
}
},
{
$limit: limit
},
{
$project: {
_id: 1,
datasetId: 1,
collectionId: 1,
updateTime: 1,
q: 1,
a: 1,
chunkIndex: 1,
score: 1
}
} }
]); );
}) })
) )
).flat() as (DatasetDataSchemaType & { score: number })[]; ).flat() as (DatasetDataSchemaType & { score: number })[];
// resort // Get data and collections
searchResults.sort((a, b) => b.score - a.score);
searchResults.slice(0, limit);
const collections = await MongoDatasetCollection.find( const collections = await MongoDatasetCollection.find(
{ {
_id: { $in: searchResults.map((item) => item.collectionId) } _id: { $in: searchResults.map((item) => item.collectionId) }
}, },
'_id name fileId rawLink' '_id name fileId rawLink externalFileId externalFileUrl',
); { ...readFromSecondary }
).lean();
return { return {
fullTextRecallResults: searchResults.map((item, index) => { fullTextRecallResults: searchResults
const collection = collections.find((col) => String(col._id) === String(item.collectionId)); .map((data, index) => {
return { const collection = collections.find(
id: String(item._id), (col) => String(col._id) === String(data.collectionId)
datasetId: String(item.datasetId), );
collectionId: String(item.collectionId), if (!collection) {
updateTime: item.updateTime, console.log('Collection is not found', data);
...getCollectionSourceData(collection), return;
q: item.q, }
a: item.a,
chunkIndex: item.chunkIndex, return {
indexes: item.indexes, id: String(data._id),
score: [{ type: SearchScoreTypeEnum.fullText, value: item.score, index }] datasetId: String(data.datasetId),
}; collectionId: String(data.collectionId),
}), updateTime: data.updateTime,
q: data.q,
a: data.a,
chunkIndex: data.chunkIndex,
indexes: data.indexes,
...getCollectionSourceData(collection),
score: [{ type: SearchScoreTypeEnum.fullText, value: data.score ?? 0, index }]
};
})
.filter(Boolean) as SearchDataResponseItemType[],
tokenLen: 0
};
};
const fullTextRecall2 = async ({
query,
limit,
filterCollectionIdList,
forbidCollectionIdList
}: {
query: string;
limit: number;
filterCollectionIdList?: string[];
forbidCollectionIdList: string[];
}): Promise<{
fullTextRecallResults: SearchDataResponseItemType[];
tokenLen: number;
}> => {
if (limit === 0) {
return {
fullTextRecallResults: [],
tokenLen: 0
};
}
const searchResults = (
await Promise.all(
datasetIds.map(async (id) => {
return MongoDatasetDataText.aggregate(
[
{
$match: {
teamId: new Types.ObjectId(teamId),
datasetId: new Types.ObjectId(id),
$text: { $search: jiebaSplit({ text: query }) },
...(filterCollectionIdList
? {
collectionId: {
$in: filterCollectionIdList.map((id) => new Types.ObjectId(id))
}
}
: {}),
...(forbidCollectionIdList && forbidCollectionIdList.length > 0
? {
collectionId: {
$nin: forbidCollectionIdList.map((id) => new Types.ObjectId(id))
}
}
: {})
}
},
{
$sort: {
score: { $meta: 'textScore' }
}
},
{
$limit: limit
},
{
$project: {
_id: 1,
collectionId: 1,
dataId: 1,
score: { $meta: 'textScore' }
}
}
],
{
...readFromSecondary
}
);
})
)
).flat() as (DatasetDataTextSchemaType & { score: number })[];
// Get data and collections
const [dataList, collections] = await Promise.all([
MongoDatasetData.find(
{
_id: { $in: searchResults.map((item) => item.dataId) }
},
'_id datasetId collectionId updateTime q a chunkIndex indexes',
{ ...readFromSecondary }
).lean(),
MongoDatasetCollection.find(
{
_id: { $in: searchResults.map((item) => item.collectionId) }
},
'_id name fileId rawLink externalFileId externalFileUrl',
{ ...readFromSecondary }
).lean()
]);
return {
fullTextRecallResults: searchResults
.map((item, index) => {
const collection = collections.find(
(col) => String(col._id) === String(item.collectionId)
);
if (!collection) {
console.log('Collection is not found', item);
return;
}
const data = dataList.find((data) => String(data._id) === String(item.dataId));
if (!data) {
console.log('Data is not found', item);
return;
}
return {
id: String(data._id),
datasetId: String(data.datasetId),
collectionId: String(data.collectionId),
updateTime: data.updateTime,
q: data.q,
a: data.a,
chunkIndex: data.chunkIndex,
indexes: data.indexes,
...getCollectionSourceData(collection),
score: [
{
type: SearchScoreTypeEnum.fullText,
value: item.score || 0,
index
}
]
};
})
.filter(Boolean) as SearchDataResponseItemType[],
tokenLen: 0 tokenLen: 0
}; };
}; };
@@ -496,7 +636,8 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
forbidCollectionIdList, forbidCollectionIdList,
filterCollectionIdList filterCollectionIdList
}), }),
fullTextRecall({ // FullText tmp
fullTextRecall2({
query, query,
limit: fullTextLimit, limit: fullTextLimit,
filterCollectionIdList, filterCollectionIdList,

View File

@@ -1,5 +1,8 @@
import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt'; import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
import { countMessagesTokens } from '../../../../common/string/tiktoken/index'; import {
countGptMessagesTokens,
countPromptTokens
} 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 { createChatCompletion } from '../../../ai/config';
@@ -49,7 +52,7 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
const chatHistories = getHistories(history, histories); const chatHistories = getHistories(history, histories);
const { arg, tokens } = await completions({ const { arg, inputTokens, outputTokens } = await completions({
...props, ...props,
histories: chatHistories, histories: chatHistories,
cqModel cqModel
@@ -59,7 +62,8 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
const { totalPoints, modelName } = formatModelChars2Points({ const { totalPoints, modelName } = formatModelChars2Points({
model: cqModel.model, model: cqModel.model,
tokens, inputTokens: inputTokens,
outputTokens: outputTokens,
modelType: ModelTypeEnum.llm modelType: ModelTypeEnum.llm
}); });
@@ -72,7 +76,8 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints, totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
model: modelName, model: modelName,
query: userChatInput, query: userChatInput,
tokens, inputTokens: inputTokens,
outputTokens: outputTokens,
cqList: agents, cqList: agents,
cqResult: result.value, cqResult: result.value,
contextTotalLen: chatHistories.length + 2 contextTotalLen: chatHistories.length + 2
@@ -82,7 +87,8 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
moduleName: name, moduleName: name,
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints, totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
model: modelName, model: modelName,
tokens inputTokens: inputTokens,
outputTokens: outputTokens
} }
] ]
}; };
@@ -148,7 +154,8 @@ const completions = async ({
} }
return { return {
tokens: await countMessagesTokens(messages), inputTokens: await countGptMessagesTokens(requestMessages),
outputTokens: await countPromptTokens(answer),
arg: { type: id } arg: { type: id }
}; };
}; };

View File

@@ -3,7 +3,8 @@ import { filterGPTMessageByMaxTokens, loadRequestMessages } from '../../../chat/
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d'; import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import { import {
countMessagesTokens, countMessagesTokens,
countGptMessagesTokens countGptMessagesTokens,
countPromptTokens
} 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 { createChatCompletion } from '../../../ai/config';
@@ -59,7 +60,7 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
const extractModel = getLLMModel(model); const extractModel = getLLMModel(model);
const chatHistories = getHistories(history, histories); const chatHistories = getHistories(history, histories);
const { arg, tokens } = await (async () => { const { arg, inputTokens, outputTokens } = await (async () => {
if (extractModel.toolChoice) { if (extractModel.toolChoice) {
return toolChoice({ return toolChoice({
...props, ...props,
@@ -114,7 +115,8 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
const { totalPoints, modelName } = formatModelChars2Points({ const { totalPoints, modelName } = formatModelChars2Points({
model: extractModel.model, model: extractModel.model,
tokens, inputTokens: inputTokens,
outputTokens: outputTokens,
modelType: ModelTypeEnum.llm modelType: ModelTypeEnum.llm
}); });
@@ -126,7 +128,8 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints, totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
model: modelName, model: modelName,
query: content, query: content,
tokens, inputTokens,
outputTokens,
extractDescription: description, extractDescription: description,
extractResult: arg, extractResult: arg,
contextTotalLen: chatHistories.length + 2 contextTotalLen: chatHistories.length + 2
@@ -136,7 +139,8 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
moduleName: name, moduleName: name,
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints, totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
model: modelName, model: modelName,
tokens inputTokens,
outputTokens
} }
] ]
}; };
@@ -249,15 +253,18 @@ const toolChoice = async (props: ActionProps) => {
} }
})(); })();
const completeMessages: ChatCompletionMessageParam[] = [ const AIMessages: ChatCompletionMessageParam[] = [
...filterMessages,
{ {
role: ChatCompletionRequestMessageRoleEnum.Assistant, role: ChatCompletionRequestMessageRoleEnum.Assistant,
tool_calls: response.choices?.[0]?.message?.tool_calls tool_calls: response.choices?.[0]?.message?.tool_calls
} }
]; ];
const inputTokens = await countGptMessagesTokens(filterMessages, tools);
const outputTokens = await countGptMessagesTokens(AIMessages);
return { return {
tokens: await countGptMessagesTokens(completeMessages, tools), inputTokens,
outputTokens,
arg arg
}; };
}; };
@@ -286,17 +293,21 @@ const functionCall = async (props: ActionProps) => {
try { try {
const arg = JSON.parse(response?.choices?.[0]?.message?.function_call?.arguments || ''); const arg = JSON.parse(response?.choices?.[0]?.message?.function_call?.arguments || '');
const completeMessages: ChatCompletionMessageParam[] = [
...filterMessages, const AIMessages: ChatCompletionMessageParam[] = [
{ {
role: ChatCompletionRequestMessageRoleEnum.Assistant, role: ChatCompletionRequestMessageRoleEnum.Assistant,
function_call: response.choices?.[0]?.message?.function_call function_call: response.choices?.[0]?.message?.function_call
} }
]; ];
const inputTokens = await countGptMessagesTokens(filterMessages, undefined, functions);
const outputTokens = await countGptMessagesTokens(AIMessages);
return { return {
arg, arg,
tokens: await countGptMessagesTokens(completeMessages, undefined, functions) inputTokens,
outputTokens
}; };
} catch (error) { } catch (error) {
console.log(response.choices?.[0]?.message); console.log(response.choices?.[0]?.message);
@@ -305,7 +316,8 @@ const functionCall = async (props: ActionProps) => {
return { return {
arg: {}, arg: {},
tokens: 0 inputTokens: 0,
outputTokens: 0
}; };
} }
}; };
@@ -370,7 +382,8 @@ Human: ${content}`
if (!jsonStr) { if (!jsonStr) {
return { return {
rawResponse: answer, rawResponse: answer,
tokens: await countMessagesTokens(messages), inputTokens: await countMessagesTokens(messages),
outputTokens: await countPromptTokens(answer),
arg: {} arg: {}
}; };
} }
@@ -378,7 +391,8 @@ Human: ${content}`
try { try {
return { return {
rawResponse: answer, rawResponse: answer,
tokens: await countMessagesTokens(messages), inputTokens: await countMessagesTokens(messages),
outputTokens: await countPromptTokens(answer),
arg: json5.parse(jsonStr) as Record<string, any> arg: json5.parse(jsonStr) as Record<string, any>
}; };
} catch (error) { } catch (error) {
@@ -386,7 +400,8 @@ Human: ${content}`
console.log(error); console.log(error);
return { return {
rawResponse: answer, rawResponse: answer,
tokens: await countMessagesTokens(messages), inputTokens: await countMessagesTokens(messages),
outputTokens: await countPromptTokens(answer),
arg: {} arg: {}
}; };
} }

View File

@@ -109,7 +109,8 @@ export const runToolWithFunctionCall = async (
return { return {
dispatchFlowResponse: [toolRunResponse], dispatchFlowResponse: [toolRunResponse],
toolNodeTokens: 0, toolNodeInputTokens: 0,
toolNodeOutputTokens: 0,
completeMessages: requestMessages, completeMessages: requestMessages,
assistantResponses: toolRunResponse.assistantResponses, assistantResponses: toolRunResponse.assistantResponses,
runTimes: toolRunResponse.runTimes, runTimes: toolRunResponse.runTimes,
@@ -126,7 +127,8 @@ export const runToolWithFunctionCall = async (
}, },
{ {
dispatchFlowResponse: [toolRunResponse], dispatchFlowResponse: [toolRunResponse],
toolNodeTokens: 0, toolNodeInputTokens: 0,
toolNodeOutputTokens: 0,
assistantResponses: toolRunResponse.assistantResponses, assistantResponses: toolRunResponse.assistantResponses,
runTimes: toolRunResponse.runTimes runTimes: toolRunResponse.runTimes
} }
@@ -340,7 +342,9 @@ export const runToolWithFunctionCall = async (
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, undefined, functions); // const tokens = await countGptMessagesTokens(concatToolMessages, undefined, functions);
const inputTokens = await countGptMessagesTokens(requestMessages, undefined, functions);
const outputTokens = await countGptMessagesTokens([assistantToolMsgParams]);
/* /*
... ...
user user
@@ -375,7 +379,12 @@ export const runToolWithFunctionCall = async (
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?.toolNodeTokens ? response.toolNodeTokens + tokens : tokens; const toolNodeInputTokens = response?.toolNodeInputTokens
? response.toolNodeInputTokens + inputTokens
: inputTokens;
const toolNodeOutputTokens = response?.toolNodeOutputTokens
? response.toolNodeOutputTokens + outputTokens
: outputTokens;
// Check stop signal // Check stop signal
const hasStopSignal = flatToolsResponseData.some( const hasStopSignal = flatToolsResponseData.some(
@@ -408,7 +417,8 @@ export const runToolWithFunctionCall = async (
return { return {
dispatchFlowResponse, dispatchFlowResponse,
toolNodeTokens, toolNodeInputTokens,
toolNodeOutputTokens,
completeMessages, completeMessages,
assistantResponses: toolNodeAssistants, assistantResponses: toolNodeAssistants,
runTimes, runTimes,
@@ -423,7 +433,8 @@ export const runToolWithFunctionCall = async (
}, },
{ {
dispatchFlowResponse, dispatchFlowResponse,
toolNodeTokens, toolNodeInputTokens,
toolNodeOutputTokens,
assistantResponses: toolNodeAssistants, assistantResponses: toolNodeAssistants,
runTimes runTimes
} }
@@ -435,7 +446,8 @@ export const runToolWithFunctionCall = async (
content: answer content: answer
}; };
const completeMessages = filterMessages.concat(gptAssistantResponse); const completeMessages = filterMessages.concat(gptAssistantResponse);
const tokens = await countGptMessagesTokens(completeMessages, undefined, functions); const inputTokens = await countGptMessagesTokens(requestMessages, undefined, functions);
const outputTokens = await countGptMessagesTokens([gptAssistantResponse]);
// console.log(tokens, 'response token'); // console.log(tokens, 'response token');
// concat tool assistant // concat tool assistant
@@ -443,7 +455,12 @@ export const runToolWithFunctionCall = async (
return { return {
dispatchFlowResponse: response?.dispatchFlowResponse || [], dispatchFlowResponse: response?.dispatchFlowResponse || [],
toolNodeTokens: response?.toolNodeTokens ? response.toolNodeTokens + tokens : tokens, toolNodeInputTokens: response?.toolNodeInputTokens
? response.toolNodeInputTokens + inputTokens
: inputTokens,
toolNodeOutputTokens: response?.toolNodeOutputTokens
? response.toolNodeOutputTokens + outputTokens
: outputTokens,
completeMessages, completeMessages,
assistantResponses: [...assistantResponses, ...toolNodeAssistant.value], assistantResponses: [...assistantResponses, ...toolNodeAssistant.value],
runTimes: (response?.runTimes || 0) + 1 runTimes: (response?.runTimes || 0) + 1

View File

@@ -165,6 +165,8 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
toolWorkflowInteractiveResponse, toolWorkflowInteractiveResponse,
dispatchFlowResponse, // tool flow response dispatchFlowResponse, // tool flow response
toolNodeTokens, toolNodeTokens,
toolNodeInputTokens,
toolNodeOutputTokens,
completeMessages = [], // The actual message sent to AI(just save text) completeMessages = [], // The actual message sent to AI(just save text)
assistantResponses = [], // FastGPT system store assistant.value response assistantResponses = [], // FastGPT system store assistant.value response
runTimes runTimes
@@ -225,7 +227,8 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
const { totalPoints, modelName } = formatModelChars2Points({ const { totalPoints, modelName } = formatModelChars2Points({
model, model,
tokens: toolNodeTokens, inputTokens: toolNodeInputTokens,
outputTokens: toolNodeOutputTokens,
modelType: ModelTypeEnum.llm modelType: ModelTypeEnum.llm
}); });
const toolAIUsage = externalProvider.openaiAccount?.key ? 0 : totalPoints; const toolAIUsage = externalProvider.openaiAccount?.key ? 0 : totalPoints;
@@ -255,6 +258,8 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
// 展示的积分消耗 // 展示的积分消耗
totalPoints: totalPointsUsage, totalPoints: totalPointsUsage,
toolCallTokens: toolNodeTokens, toolCallTokens: toolNodeTokens,
toolCallInputTokens: toolNodeInputTokens,
toolCallOutputTokens: toolNodeOutputTokens,
childTotalPoints: flatUsages.reduce((sum, item) => sum + item.totalPoints, 0), childTotalPoints: flatUsages.reduce((sum, item) => sum + item.totalPoints, 0),
model: modelName, model: modelName,
query: userChatInput, query: userChatInput,
@@ -270,9 +275,10 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
// 工具调用本身的积分消耗 // 工具调用本身的积分消耗
{ {
moduleName: name, moduleName: name,
totalPoints: toolAIUsage,
model: modelName, model: modelName,
tokens: toolNodeTokens totalPoints: toolAIUsage,
inputTokens: toolNodeInputTokens,
outputTokens: toolNodeOutputTokens
}, },
// 工具的消耗 // 工具的消耗
...flatUsages ...flatUsages

View File

@@ -115,7 +115,8 @@ export const runToolWithPromptCall = async (
return { return {
dispatchFlowResponse: [toolRunResponse], dispatchFlowResponse: [toolRunResponse],
toolNodeTokens: 0, toolNodeInputTokens: 0,
toolNodeOutputTokens: 0,
completeMessages: concatMessages, completeMessages: concatMessages,
assistantResponses: toolRunResponse.assistantResponses, assistantResponses: toolRunResponse.assistantResponses,
runTimes: toolRunResponse.runTimes, runTimes: toolRunResponse.runTimes,
@@ -131,7 +132,8 @@ export const runToolWithPromptCall = async (
}, },
{ {
dispatchFlowResponse: [toolRunResponse], dispatchFlowResponse: [toolRunResponse],
toolNodeTokens: 0, toolNodeInputTokens: 0,
toolNodeOutputTokens: 0,
assistantResponses: toolRunResponse.assistantResponses, assistantResponses: toolRunResponse.assistantResponses,
runTimes: toolRunResponse.runTimes runTimes: toolRunResponse.runTimes
} }
@@ -286,15 +288,20 @@ export const runToolWithPromptCall = async (
content: replaceAnswer content: replaceAnswer
}; };
const completeMessages = filterMessages.concat(gptAssistantResponse); const completeMessages = filterMessages.concat(gptAssistantResponse);
const tokens = await countGptMessagesTokens(completeMessages, undefined); const inputTokens = await countGptMessagesTokens(requestMessages);
// console.log(tokens, 'response token'); const outputTokens = await countGptMessagesTokens([gptAssistantResponse]);
// concat tool assistant // concat tool assistant
const toolNodeAssistant = GPTMessages2Chats([gptAssistantResponse])[0] as AIChatItemType; const toolNodeAssistant = GPTMessages2Chats([gptAssistantResponse])[0] as AIChatItemType;
return { return {
dispatchFlowResponse: response?.dispatchFlowResponse || [], dispatchFlowResponse: response?.dispatchFlowResponse || [],
toolNodeTokens: response?.toolNodeTokens ? response.toolNodeTokens + tokens : tokens, toolNodeInputTokens: response?.toolNodeInputTokens
? response.toolNodeInputTokens + inputTokens
: inputTokens,
toolNodeOutputTokens: response?.toolNodeOutputTokens
? response.toolNodeOutputTokens + outputTokens
: outputTokens,
completeMessages, completeMessages,
assistantResponses: [...assistantResponses, ...toolNodeAssistant.value], assistantResponses: [...assistantResponses, ...toolNodeAssistant.value],
runTimes: (response?.runTimes || 0) + 1 runTimes: (response?.runTimes || 0) + 1
@@ -366,17 +373,9 @@ export const runToolWithPromptCall = async (
function_call: toolJson function_call: toolJson
}; };
/*
...
user
assistant: tool data
*/
const concatToolMessages = [
...requestMessages,
assistantToolMsgParams
] 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, undefined); const inputTokens = await countGptMessagesTokens(requestMessages);
const outputTokens = await countGptMessagesTokens([assistantToolMsgParams]);
/* /*
... ...
@@ -437,7 +436,12 @@ ANSWER: `;
} }
const runTimes = (response?.runTimes || 0) + toolsRunResponse.toolResponse.runTimes; const runTimes = (response?.runTimes || 0) + toolsRunResponse.toolResponse.runTimes;
const toolNodeTokens = response?.toolNodeTokens ? response.toolNodeTokens + tokens : tokens; const toolNodeInputTokens = response?.toolNodeInputTokens
? response.toolNodeInputTokens + inputTokens
: inputTokens;
const toolNodeOutputTokens = response?.toolNodeOutputTokens
? response.toolNodeOutputTokens + outputTokens
: outputTokens;
// Check stop signal // Check stop signal
const hasStopSignal = toolsRunResponse.toolResponse.flowResponses.some((item) => !!item.toolStop); const hasStopSignal = toolsRunResponse.toolResponse.flowResponses.some((item) => !!item.toolStop);
@@ -460,7 +464,8 @@ ANSWER: `;
return { return {
dispatchFlowResponse, dispatchFlowResponse,
toolNodeTokens, toolNodeInputTokens,
toolNodeOutputTokens,
completeMessages: filterMessages, completeMessages: filterMessages,
assistantResponses: toolNodeAssistants, assistantResponses: toolNodeAssistants,
runTimes, runTimes,
@@ -475,7 +480,8 @@ ANSWER: `;
}, },
{ {
dispatchFlowResponse, dispatchFlowResponse,
toolNodeTokens, toolNodeInputTokens,
toolNodeOutputTokens,
assistantResponses: toolNodeAssistants, assistantResponses: toolNodeAssistants,
runTimes runTimes
} }

View File

@@ -27,9 +27,10 @@ import { getNanoid, sliceStrStartEnd } from '@fastgpt/global/common/string/tools
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 { getErrText } from '@fastgpt/global/common/error/utils';
type ToolRunResponseType = { type ToolRunResponseType = {
toolRunResponse: DispatchFlowResponse; toolRunResponse?: DispatchFlowResponse;
toolMsgParams: ChatCompletionToolMessageParam; toolMsgParams: ChatCompletionToolMessageParam;
}[]; }[];
@@ -158,7 +159,8 @@ export const runToolWithToolChoice = async (
return { return {
dispatchFlowResponse: [toolRunResponse], dispatchFlowResponse: [toolRunResponse],
toolNodeTokens: 0, toolNodeInputTokens: 0,
toolNodeOutputTokens: 0,
completeMessages: requestMessages, completeMessages: requestMessages,
assistantResponses: toolRunResponse.assistantResponses, assistantResponses: toolRunResponse.assistantResponses,
runTimes: toolRunResponse.runTimes, runTimes: toolRunResponse.runTimes,
@@ -176,7 +178,8 @@ export const runToolWithToolChoice = async (
}, },
{ {
dispatchFlowResponse: [toolRunResponse], dispatchFlowResponse: [toolRunResponse],
toolNodeTokens: 0, toolNodeInputTokens: 0,
toolNodeOutputTokens: 0,
assistantResponses: toolRunResponse.assistantResponses, assistantResponses: toolRunResponse.assistantResponses,
runTimes: toolRunResponse.runTimes runTimes: toolRunResponse.runTimes
} }
@@ -207,7 +210,7 @@ export const runToolWithToolChoice = async (
properties[item.key] = { properties[item.key] = {
...jsonSchema, ...jsonSchema,
description: item.toolDescription || '', description: item.toolDescription || '',
enum: item.enum?.split('\n').filter(Boolean) || [] enum: item.enum?.split('\n').filter(Boolean) || undefined
}; };
}); });
@@ -224,6 +227,7 @@ export const runToolWithToolChoice = async (
} }
}; };
}); });
// Filter histories by maxToken // Filter histories by maxToken
const filterMessages = ( const filterMessages = (
await filterGPTMessageByMaxTokens({ await filterGPTMessageByMaxTokens({
@@ -342,59 +346,87 @@ export const runToolWithToolChoice = async (
return Promise.reject(getEmptyResponseTip()); return Promise.reject(getEmptyResponseTip());
} }
// Run the selected tool by LLM. /* Run the selected tool by LLM.
const toolsRunResponse = ( Since only reference parameters are passed, if the same tool is run in parallel, it will get the same run parameters
await Promise.all( */
toolCalls.map(async (tool) => { const toolsRunResponse: ToolRunResponseType = [];
const toolNode = toolNodes.find((item) => item.nodeId === tool.function?.name); for await (const tool of toolCalls) {
try {
const toolNode = toolNodes.find((item) => item.nodeId === tool.function?.name);
if (!toolNode) return; if (!toolNode) continue;
const startParams = (() => { const startParams = (() => {
try { try {
return json5.parse(tool.function.arguments); return json5.parse(tool.function.arguments);
} catch (error) { } catch (error) {
return {}; 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)
} }
})(); }
});
initToolNodes(runtimeNodes, [toolNode.nodeId], startParams); toolsRunResponse.push({
const toolRunResponse = await dispatchWorkFlow({ toolRunResponse,
...workflowProps, toolMsgParams
isToolCall: true });
}); } catch (error) {
const err = getErrText(error);
workflowStreamResponse?.({
event: SseResponseEventEnum.toolResponse,
data: {
tool: {
id: tool.id,
toolName: '',
toolAvatar: '',
params: '',
response: sliceStrStartEnd(err, 5000, 5000)
}
}
});
const stringToolResponse = formatToolResponse(toolRunResponse.toolResponses); toolsRunResponse.push({
toolRunResponse: undefined,
const toolMsgParams: ChatCompletionToolMessageParam = { toolMsgParams: {
tool_call_id: tool.id, tool_call_id: tool.id,
role: ChatCompletionRequestMessageRoleEnum.Tool, role: ChatCompletionRequestMessageRoleEnum.Tool,
name: tool.function.name, name: tool.function.name,
content: stringToolResponse content: sliceStrStartEnd(err, 5000, 5000)
}; }
});
}
}
workflowStreamResponse?.({ const flatToolsResponseData = toolsRunResponse
event: SseResponseEventEnum.toolResponse, .map((item) => item.toolRunResponse)
data: { .flat()
tool: { .filter(Boolean) as DispatchFlowResponse[];
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 // concat tool responses
const dispatchFlowResponse = response const dispatchFlowResponse = response
? response.dispatchFlowResponse.concat(flatToolsResponseData) ? response.dispatchFlowResponse.concat(flatToolsResponseData)
@@ -428,24 +460,26 @@ export const runToolWithToolChoice = async (
] 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 inputTokens = await countGptMessagesTokens(requestMessages, tools);
const outputTokens = await countGptMessagesTokens(assistantToolMsgParams);
/* /*
... ...
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)
@@ -463,7 +497,10 @@ export const runToolWithToolChoice = async (
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 toolNodeInputTokens = response ? response.toolNodeInputTokens + inputTokens : inputTokens;
const toolNodeOutputTokens = response
? response.toolNodeOutputTokens + outputTokens
: outputTokens;
// Check stop signal // Check stop signal
const hasStopSignal = flatToolsResponseData.some( const hasStopSignal = flatToolsResponseData.some(
@@ -471,12 +508,12 @@ export const runToolWithToolChoice = async (
); );
// 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');
@@ -496,7 +533,8 @@ export const runToolWithToolChoice = async (
return { return {
dispatchFlowResponse, dispatchFlowResponse,
toolNodeTokens, toolNodeInputTokens,
toolNodeOutputTokens,
completeMessages, completeMessages,
assistantResponses: toolNodeAssistants, assistantResponses: toolNodeAssistants,
runTimes, runTimes,
@@ -512,7 +550,8 @@ export const runToolWithToolChoice = async (
}, },
{ {
dispatchFlowResponse, dispatchFlowResponse,
toolNodeTokens, toolNodeInputTokens,
toolNodeOutputTokens,
assistantResponses: toolNodeAssistants, assistantResponses: toolNodeAssistants,
runTimes runTimes
} }
@@ -524,14 +563,17 @@ export const runToolWithToolChoice = async (
content: answer content: answer
}; };
const completeMessages = filterMessages.concat(gptAssistantResponse); const completeMessages = filterMessages.concat(gptAssistantResponse);
const tokens = await countGptMessagesTokens(completeMessages, tools); const inputTokens = await countGptMessagesTokens(requestMessages, tools);
const outputTokens = await countGptMessagesTokens([gptAssistantResponse]);
// concat tool assistant // concat tool assistant
const toolNodeAssistant = GPTMessages2Chats([gptAssistantResponse])[0] as AIChatItemType; const toolNodeAssistant = GPTMessages2Chats([gptAssistantResponse])[0] as AIChatItemType;
return { return {
dispatchFlowResponse: response?.dispatchFlowResponse || [], dispatchFlowResponse: response?.dispatchFlowResponse || [],
toolNodeTokens: response ? response.toolNodeTokens + tokens : tokens, toolNodeInputTokens: response ? response.toolNodeInputTokens + inputTokens : inputTokens,
toolNodeOutputTokens: response ? response.toolNodeOutputTokens + outputTokens : outputTokens,
completeMessages, completeMessages,
assistantResponses: [...assistantResponses, ...toolNodeAssistant.value], assistantResponses: [...assistantResponses, ...toolNodeAssistant.value],
runTimes: (response?.runTimes || 0) + 1 runTimes: (response?.runTimes || 0) + 1
@@ -578,7 +620,8 @@ async function streamResponse({
text: content text: content
}) })
}); });
} else if (responseChoice?.tool_calls?.[0]) { }
if (responseChoice?.tool_calls?.[0]) {
const toolCall: ChatCompletionMessageToolCall = responseChoice.tool_calls[0]; const toolCall: ChatCompletionMessageToolCall = responseChoice.tool_calls[0];
// In a stream response, only one tool is returned at a time. If have id, description is executing a tool // In a stream response, only one tool is returned at a time. If have id, description is executing a tool
if (toolCall.id || callingTool) { if (toolCall.id || callingTool) {

View File

@@ -31,7 +31,9 @@ export type DispatchToolModuleProps = ModuleDispatchProps<{
export type RunToolResponse = { export type RunToolResponse = {
dispatchFlowResponse: DispatchFlowResponse[]; dispatchFlowResponse: DispatchFlowResponse[];
toolNodeTokens: number; toolNodeTokens?: number; // deprecated
toolNodeInputTokens: number;
toolNodeOutputTokens: number;
completeMessages?: ChatCompletionMessageParam[]; completeMessages?: ChatCompletionMessageParam[];
assistantResponses?: AIChatItemValueItemType[]; assistantResponses?: AIChatItemValueItemType[];
toolWorkflowInteractiveResponse?: WorkflowInteractiveResponseType; toolWorkflowInteractiveResponse?: WorkflowInteractiveResponseType;

View File

@@ -5,13 +5,17 @@ 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 { createChatCompletion } from '../../../ai/config';
import type { ChatCompletion, StreamChatType } from '@fastgpt/global/core/ai/type.d'; import type {
ChatCompletion,
ChatCompletionMessageParam,
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';
import { postTextCensor } from '../../../../common/api/requestPlusApi'; import { postTextCensor } from '../../../../common/api/requestPlusApi';
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants'; import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants';
import type { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type'; import type { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
import { countMessagesTokens } from '../../../../common/string/tiktoken/index'; import { countGptMessagesTokens } from '../../../../common/string/tiktoken/index';
import { import {
chats2GPTMessages, chats2GPTMessages,
chatValue2RuntimePrompt, chatValue2RuntimePrompt,
@@ -214,16 +218,23 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
return Promise.reject(getEmptyResponseTip()); return Promise.reject(getEmptyResponseTip());
} }
const completeMessages = requestMessages.concat({ const AIMessages: ChatCompletionMessageParam[] = [
role: ChatCompletionRequestMessageRoleEnum.Assistant, {
content: answerText role: ChatCompletionRequestMessageRoleEnum.Assistant,
}); content: answerText
}
];
const completeMessages = [...requestMessages, ...AIMessages];
const chatCompleteMessages = GPTMessages2Chats(completeMessages); const chatCompleteMessages = GPTMessages2Chats(completeMessages);
const tokens = await countMessagesTokens(chatCompleteMessages); const inputTokens = await countGptMessagesTokens(requestMessages);
const outputTokens = await countGptMessagesTokens(AIMessages);
const { totalPoints, modelName } = formatModelChars2Points({ const { totalPoints, modelName } = formatModelChars2Points({
model, model,
tokens, inputTokens,
outputTokens,
modelType: ModelTypeEnum.llm modelType: ModelTypeEnum.llm
}); });
@@ -232,7 +243,9 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
[DispatchNodeResponseKeyEnum.nodeResponse]: { [DispatchNodeResponseKeyEnum.nodeResponse]: {
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints, totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
model: modelName, model: modelName,
tokens, tokens: inputTokens + outputTokens,
inputTokens: inputTokens,
outputTokens: outputTokens,
query: `${userChatInput}`, query: `${userChatInput}`,
maxToken: max_tokens, maxToken: max_tokens,
historyPreview: getHistoryPreview( historyPreview: getHistoryPreview(
@@ -247,7 +260,8 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
moduleName: name, moduleName: name,
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints, totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
model: modelName, model: modelName,
tokens inputTokens: inputTokens,
outputTokens: outputTokens
} }
], ],
[DispatchNodeResponseKeyEnum.toolResponses]: answerText, [DispatchNodeResponseKeyEnum.toolResponses]: answerText,

View File

@@ -120,14 +120,14 @@ export async function dispatchDatasetSearch(
// vector // vector
const { totalPoints, modelName } = formatModelChars2Points({ const { totalPoints, modelName } = formatModelChars2Points({
model: vectorModel.model, model: vectorModel.model,
tokens, inputTokens: tokens,
modelType: ModelTypeEnum.vector modelType: ModelTypeEnum.vector
}); });
const responseData: DispatchNodeResponseType & { totalPoints: number } = { const responseData: DispatchNodeResponseType & { totalPoints: number } = {
totalPoints, totalPoints,
query: concatQueries.join('\n'), query: concatQueries.join('\n'),
model: modelName, model: modelName,
tokens, inputTokens: tokens,
similarity: usingSimilarityFilter ? similarity : undefined, similarity: usingSimilarityFilter ? similarity : undefined,
limit, limit,
searchMode, searchMode,
@@ -139,19 +139,21 @@ export async function dispatchDatasetSearch(
totalPoints, totalPoints,
moduleName: node.name, moduleName: node.name,
model: modelName, model: modelName,
tokens inputTokens: tokens
} }
]; ];
if (aiExtensionResult) { if (aiExtensionResult) {
const { totalPoints, modelName } = formatModelChars2Points({ const { totalPoints, modelName } = formatModelChars2Points({
model: aiExtensionResult.model, model: aiExtensionResult.model,
tokens: aiExtensionResult.tokens, inputTokens: aiExtensionResult.inputTokens,
outputTokens: aiExtensionResult.outputTokens,
modelType: ModelTypeEnum.llm modelType: ModelTypeEnum.llm
}); });
responseData.totalPoints += totalPoints; responseData.totalPoints += totalPoints;
responseData.tokens = aiExtensionResult.tokens; responseData.inputTokens = aiExtensionResult.inputTokens;
responseData.outputTokens = aiExtensionResult.outputTokens;
responseData.extensionModel = modelName; responseData.extensionModel = modelName;
responseData.extensionResult = responseData.extensionResult =
aiExtensionResult.extensionQueries?.join('\n') || aiExtensionResult.extensionQueries?.join('\n') ||
@@ -161,7 +163,8 @@ export async function dispatchDatasetSearch(
totalPoints, totalPoints,
moduleName: 'core.module.template.Query extension', moduleName: 'core.module.template.Query extension',
model: modelName, model: modelName,
tokens: aiExtensionResult.tokens inputTokens: aiExtensionResult.inputTokens,
outputTokens: aiExtensionResult.outputTokens
}); });
} }

Some files were not shown because too many files have changed in this diff Show More