diff --git a/.vscode/settings.json b/.vscode/settings.json index c674d0733..bba595f12 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -27,7 +27,5 @@ }, "markdown.copyFiles.destination": { "/docSite/content/**/*": "${documentWorkspaceFolder}/docSite/assets/imgs/" - }, - "markdown.copyFiles.overwriteBehavior": "nameIncrementally", - "markdown.copyFiles.transformPath": "const filename = uri.path.split('/').pop(); return `/imgs/${filename}`;" + } } \ No newline at end of file diff --git a/README.md b/README.md index dfe0a8387..f40777117 100644 --- a/README.md +++ b/README.md @@ -115,16 +115,6 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b # -## 🏘️ 社区交流群 - -扫码加入飞书话题群: - -![](https://oss.laf.run/otnvvf-imgs/fastgpt-feishu1.png) - - - # - - ## 🏘️ 加入我们 我们正在寻找志同道合的小伙伴,加速 FastGPT 的发展。你可以通过 [FastGPT 2025 招聘](https://fael3z0zfze.feishu.cn/wiki/P7FOwEmPziVcaYkvVaacnVX1nvg)了解 FastGPT 的招聘信息。 @@ -135,17 +125,25 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b - [Sealos:快速部署集群应用](https://github.com/labring/sealos) - [AI Proxy API调用地址](https://sealos.run/aiproxy/?k=fastgpt-github/) - [One API:多模型管理,支持 Azure、文心一言等](https://github.com/songquanpeng/one-api) -- [TuShan:5 分钟搭建后台管理系统](https://github.com/msgbyte/tushan) # - ## 🌿 第三方生态 -- [COW 个人微信/企微机器人](https://doc.tryfastgpt.ai/docs/use-cases/external-integration/onwechat/) - [SiliconCloud (硅基流动) —— 开源模型在线体验平台](https://cloud.siliconflow.cn/i/TR9Ym0c4) +- [COW 个人微信/企微机器人](https://doc.tryfastgpt.ai/docs/use-cases/external-integration/onwechat/) + + + # + + +## 🏘️ 社区交流群 + +扫码加入飞书话题群: + +![](https://oss.laf.run/otnvvf-imgs/fastgpt-feishu1.png) # diff --git a/deploy/docker/docker-compose-milvus.yml b/deploy/docker/docker-compose-milvus.yml index 2914ab87a..2e30c9f23 100644 --- a/deploy/docker/docker-compose-milvus.yml +++ b/deploy/docker/docker-compose-milvus.yml @@ -141,10 +141,9 @@ services: - AIPROXY_API_ENDPOINT=http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY - AIPROXY_API_TOKEN=aiproxy - # AI模型的API地址哦。务必加 /v1。这里默认填写了OneApi的访问地址。 - - OPENAI_BASE_URL=http://oneapi:3000/v1 - # AI模型的API Key。(这里默认填写了OneAPI的快速默认key,测试通后,务必及时修改) - - CHAT_API_KEY=sk-fastgpt + # 模型中转地址(如果用了 AI Proxy,下面 2 个就不需要了,旧版 OneAPI 用户,使用下面的变量) + # - OPENAI_BASE_URL=http://oneapi:3000/v1 + # - CHAT_API_KEY=sk-fastgpt # 数据库最大连接数 - DB_MAX_LINK=30 # 登录凭证密钥 @@ -180,32 +179,37 @@ services: container_name: aiproxy restart: unless-stopped depends_on: - pgsql: + aiproxy_pg: condition: service_healthy ports: - - '3002:3000/tcp' + - '3002:3000' networks: - fastgpt environment: - - ADMIN_KEY=aiproxy # 对应 fastgpt 里的AIPROXY_API_TOKEN - - LOG_DETAIL_STORAGE_HOURS=1 # 日志详情保存时间(小时) - - TZ=Asia/Shanghai - - SQL_DSN=postgres://postgres:aiproxy@pgsql:5432/aiproxy + # 对应 fastgpt 里的AIPROXY_API_TOKEN + - ADMIN_KEY=aiproxy + # 错误日志详情保存时间(小时) + - LOG_DETAIL_STORAGE_HOURS=1 + # 数据库连接地址 + - SQL_DSN=postgres://postgres:aiproxy@aiproxy_pg:5432/aiproxy + # 最大重试次数 + - RetryTimes=3 + # 不需要计费 + - BILLING_ENABLED=false + # 不需要严格检测模型 - DISABLE_MODEL_CONFIG=true healthcheck: test: ['CMD', 'curl', '-f', 'http://localhost:3000/api/status'] interval: 5s timeout: 5s retries: 10 - - # AI Proxy - pgsql: - # image: "postgres:latest" - image: registry.cn-hangzhou.aliyuncs.com/fastgpt/pgvector:v0.7.0 # 阿里云 + aiproxy_pg: + # image: pgvector/pgvector:0.8.0-pg15 # docker hub + image: registry.cn-hangzhou.aliyuncs.com/fastgpt/pgvector:v0.8.0-pg15 # 阿里云 restart: unless-stopped - container_name: pgsql + container_name: aiproxy_pg volumes: - - ./pgsql:/var/lib/postgresql/data + - ./aiproxy_pg:/var/lib/postgresql/data networks: - fastgpt environment: diff --git a/deploy/docker/docker-compose-pgvector.yml b/deploy/docker/docker-compose-pgvector.yml index c403741bf..aee2bebcb 100644 --- a/deploy/docker/docker-compose-pgvector.yml +++ b/deploy/docker/docker-compose-pgvector.yml @@ -11,8 +11,8 @@ services: # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/pgvector:v0.8.0-pg15 # 阿里云 container_name: pg restart: always - ports: # 生产环境建议不要暴露 - - 5432:5432 + # ports: # 生产环境建议不要暴露 + # - 5432:5432 networks: - fastgpt environment: @@ -99,10 +99,9 @@ services: - AIPROXY_API_ENDPOINT=http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY - AIPROXY_API_TOKEN=aiproxy - # AI模型的API地址哦。务必加 /v1。这里默认填写了OneApi的访问地址。 - - OPENAI_BASE_URL=http://oneapi:3000/v1 - # AI模型的API Key。(这里默认填写了OneAPI的快速默认key,测试通后,务必及时修改) - - CHAT_API_KEY=sk-fastgpt + # 模型中转地址(如果用了 AI Proxy,下面 2 个就不需要了,旧版 OneAPI 用户,使用下面的变量) + # - OPENAI_BASE_URL=http://oneapi:3000/v1 + # - CHAT_API_KEY=sk-fastgpt # 数据库最大连接数 - DB_MAX_LINK=30 # 登录凭证密钥 @@ -137,32 +136,37 @@ services: container_name: aiproxy restart: unless-stopped depends_on: - pgsql: + aiproxy_pg: condition: service_healthy ports: - - '3002:3000/tcp' + - '3002:3000' networks: - fastgpt environment: - - ADMIN_KEY=aiproxy # 对应 fastgpt 里的AIPROXY_API_TOKEN - - LOG_DETAIL_STORAGE_HOURS=1 # 日志详情保存时间(小时) - - TZ=Asia/Shanghai - - SQL_DSN=postgres://postgres:aiproxy@pgsql:5432/aiproxy + # 对应 fastgpt 里的AIPROXY_API_TOKEN + - ADMIN_KEY=aiproxy + # 错误日志详情保存时间(小时) + - LOG_DETAIL_STORAGE_HOURS=1 + # 数据库连接地址 + - SQL_DSN=postgres://postgres:aiproxy@aiproxy_pg:5432/aiproxy + # 最大重试次数 + - RetryTimes=3 + # 不需要计费 + - BILLING_ENABLED=false + # 不需要严格检测模型 - DISABLE_MODEL_CONFIG=true healthcheck: test: ['CMD', 'curl', '-f', 'http://localhost:3000/api/status'] interval: 5s timeout: 5s retries: 10 - - # AI Proxy - pgsql: - # image: "postgres:latest" - image: registry.cn-hangzhou.aliyuncs.com/fastgpt/pgvector:v0.7.0 # 阿里云 + aiproxy_pg: + # image: pgvector/pgvector:0.8.0-pg15 # docker hub + image: registry.cn-hangzhou.aliyuncs.com/fastgpt/pgvector:v0.8.0-pg15 # 阿里云 restart: unless-stopped - container_name: pgsql + container_name: aiproxy_pg volumes: - - ./pgsql:/var/lib/postgresql/data + - ./aiproxy_pg:/var/lib/postgresql/data networks: - fastgpt environment: diff --git a/deploy/docker/docker-compose-zilliz.yml b/deploy/docker/docker-compose-zilliz.yml index ffdb623c1..e2bad44d8 100644 --- a/deploy/docker/docker-compose-zilliz.yml +++ b/deploy/docker/docker-compose-zilliz.yml @@ -79,10 +79,9 @@ services: - AIPROXY_API_ENDPOINT=http://aiproxy:3000 # AI Proxy 的 Admin Token,与 AI Proxy 中的环境变量 ADMIN_KEY - AIPROXY_API_TOKEN=aiproxy - # AI模型的API地址哦。务必加 /v1。这里默认填写了OneApi的访问地址。 - - OPENAI_BASE_URL=http://oneapi:3000/v1 - # AI模型的API Key。(这里默认填写了OneAPI的快速默认key,测试通后,务必及时修改) - - CHAT_API_KEY=sk-fastgpt + # 模型中转地址(如果用了 AI Proxy,下面 2 个就不需要了,旧版 OneAPI 用户,使用下面的变量) + # - OPENAI_BASE_URL=http://oneapi:3000/v1 + # - CHAT_API_KEY=sk-fastgpt # 数据库最大连接数 - DB_MAX_LINK=30 # 登录凭证密钥 @@ -118,32 +117,37 @@ services: container_name: aiproxy restart: unless-stopped depends_on: - pgsql: + aiproxy_pg: condition: service_healthy ports: - - '3002:3000/tcp' + - '3002:3000' networks: - fastgpt environment: - - ADMIN_KEY=aiproxy # 对应 fastgpt 里的AIPROXY_API_TOKEN - - LOG_DETAIL_STORAGE_HOURS=1 # 日志详情保存时间(小时) - - TZ=Asia/Shanghai - - SQL_DSN=postgres://postgres:aiproxy@pgsql:5432/aiproxy + # 对应 fastgpt 里的AIPROXY_API_TOKEN + - ADMIN_KEY=aiproxy + # 错误日志详情保存时间(小时) + - LOG_DETAIL_STORAGE_HOURS=1 + # 数据库连接地址 + - SQL_DSN=postgres://postgres:aiproxy@aiproxy_pg:5432/aiproxy + # 最大重试次数 + - RetryTimes=3 + # 不需要计费 + - BILLING_ENABLED=false + # 不需要严格检测模型 - DISABLE_MODEL_CONFIG=true healthcheck: test: ['CMD', 'curl', '-f', 'http://localhost:3000/api/status'] interval: 5s timeout: 5s retries: 10 - - # AI Proxy - pgsql: - # image: "postgres:latest" - image: registry.cn-hangzhou.aliyuncs.com/fastgpt/pgvector:v0.7.0 # 阿里云 + aiproxy_pg: + # image: pgvector/pgvector:0.8.0-pg15 # docker hub + image: registry.cn-hangzhou.aliyuncs.com/fastgpt/pgvector:v0.8.0-pg15 # 阿里云 restart: unless-stopped - container_name: pgsql + container_name: aiproxy_pg volumes: - - ./pgsql:/var/lib/postgresql/data + - ./aiproxy_pg:/var/lib/postgresql/data networks: - fastgpt environment: diff --git a/docSite/assets/imgs/aiproxy1.png b/docSite/assets/imgs/aiproxy1.png new file mode 100644 index 000000000..3905df329 Binary files /dev/null and b/docSite/assets/imgs/aiproxy1.png differ diff --git a/docSite/content/zh-cn/docs/development/docker.md b/docSite/content/zh-cn/docs/development/docker.md index 0ef3b1dcc..9a5962a04 100644 --- a/docSite/content/zh-cn/docs/development/docker.md +++ b/docSite/content/zh-cn/docs/development/docker.md @@ -30,7 +30,7 @@ weight: 707 ### PgVector版本 -非常轻量,适合数据量在 5000 万以下。 +非常轻量,适合知识库索引量在 5000 万以下。 {{< table "table-hover table-striped-columns" >}} | 环境 | 最低配置(单节点) | 推荐配置 | @@ -149,18 +149,14 @@ curl -o docker-compose.yml https://raw.githubusercontent.com/labring/FastGPT/mai {{< tab tabName="PgVector版本" >}} {{< markdownify >}} -``` -FE_DOMAIN=你的前端你访问地址,例如 http://192.168.0.1:3000;https://cloud.fastgpt.cn -``` +无需操作 {{< /markdownify >}} {{< /tab >}} {{< tab tabName="Milvus版本" >}} {{< markdownify >}} -``` -FE_DOMAIN=你的前端你访问地址,例如 http://192.168.0.1:3000;https://cloud.fastgpt.cn -``` +无需操作 {{< /markdownify >}} {{< /tab >}} @@ -174,7 +170,6 @@ FE_DOMAIN=你的前端你访问地址,例如 http://192.168.0.1:3000;https://clo {{% alert icon="🤖" context="success" %}} 1. 修改`MILVUS_ADDRESS`和`MILVUS_TOKEN`链接参数,分别对应 `zilliz` 的 `Public Endpoint` 和 `Api key`,记得把自己ip加入白名单。 -2. 修改FE_DOMAIN=你的前端你访问地址,例如 http://192.168.0.1:3000;https://cloud.fastgpt.cn {{% /alert %}} @@ -189,30 +184,28 @@ FE_DOMAIN=你的前端你访问地址,例如 http://192.168.0.1:3000;https://clo ```bash # 启动容器 docker-compose up -d -# 等待10s,OneAPI第一次总是要重启几次才能连上Mysql -sleep 10 -# 重启一次oneapi(由于OneAPI的默认Key有点问题,不重启的话会提示找不到渠道,临时手动重启一次解决,等待作者修复) -docker restart oneapi ``` ### 4. 访问 FastGPT -目前可以通过 `ip:3000` 直接访问(注意防火墙)。登录用户名为 `root`,密码为`docker-compose.yml`环境变量里设置的 `DEFAULT_ROOT_PSW`。 +目前可以通过 `ip:3000` 直接访问(注意开放防火墙)。登录用户名为 `root`,密码为`docker-compose.yml`环境变量里设置的 `DEFAULT_ROOT_PSW`。 如果需要域名访问,请自行安装并配置 Nginx。 -首次运行,会自动初始化 root 用户,密码为 `1234`(与环境变量中的`DEFAULT_ROOT_PSW`一致),日志里会提示一次`MongoServerError: Unable to read from a snapshot due to pending collection catalog changes;`可忽略。 +首次运行,会自动初始化 root 用户,密码为 `1234`(与环境变量中的`DEFAULT_ROOT_PSW`一致),日志可能会提示一次`MongoServerError: Unable to read from a snapshot due to pending collection catalog changes;`可忽略。 ### 5. 配置模型 -登录FastGPT后,进入“模型提供商”页面,首先配置模型渠道,[点击查看相关教程](/docs/development/modelconfig/ai-proxy) - -然后配置具体模型,务必先配置至少一个语言模型和一个向量模型,否则系统无法正常使用。 - -[点击查看模型配置教程](/docs/development/modelConfig/intro/) +- 首次登录FastGPT后,系统会提示未配置`语言模型`和`索引模型`,并自动跳转模型配置页面。系统必须至少有这两类模型才能正常使用。 +- 如果系统未正常跳转,可以在`账号-模型提供商`页面,进行模型配置。[点击查看相关教程](/docs/development/modelconfig/ai-proxy) +- 目前已知可能问题:首次进入系统后,整个浏览器 tab 无法响应。此时需要删除该tab,重新打开一次即可。 ## FAQ +### 登录系统后,浏览器无法响应 + +无法点击任何内容,刷新也无效。此时需要删除该tab,重新打开一次即可。 + ### Mongo 副本集自动初始化失败 最新的 docker-compose 示例优化 Mongo 副本集初始化,实现了全自动。目前在 unbuntu20,22 centos7, wsl2, mac, window 均通过测试。仍无法正常启动,大部分是因为 cpu 不支持 AVX 指令集,可以切换 Mongo4.x 版本。 diff --git a/docSite/content/zh-cn/docs/development/modelConfig/ai-proxy.md b/docSite/content/zh-cn/docs/development/modelConfig/ai-proxy.md index 70e58881f..5d86c0957 100644 --- a/docSite/content/zh-cn/docs/development/modelConfig/ai-proxy.md +++ b/docSite/content/zh-cn/docs/development/modelConfig/ai-proxy.md @@ -7,7 +7,7 @@ toc: true weight: 744 --- -从 FastGPT 4.8.23 版本开始,引入 AI Proxy 来进一步方便模型的配置。 +从 `FastGPT 4.8.23` 版本开始,引入 AI Proxy 来进一步方便模型的配置。 AI Proxy 与 One API 类似,也是作为一个 OpenAI 接口管理 & 分发系统,可以通过标准的 OpenAI API 格式访问所有的大模型,开箱即用。 @@ -15,13 +15,29 @@ AI Proxy 与 One API 类似,也是作为一个 OpenAI 接口管理 & 分发系 ### Docker 版本 -`docker-compose.yml` 文件已加入了 AI Proxy 配置,可直接使用。 +`docker-compose.yml` 文件已加入了 AI Proxy 配置,可直接使用。[点击查看最新的 yml 配置](https://raw.githubusercontent.com/labring/FastGPT/main/deploy/docker/docker-compose-pgvector.yml) -## 基础使用 +从旧版升级的用户,可以复制 yml 里,ai proxy 的配置,加入到旧的 yml 文件中。 + +## 运行原理 + +AI proxy 核心模块: + +1. 渠道管理:管理各家模型提供商的 API Key 和可用模型列表。 +2. 模型调用:根据请求的模型,选中对应的渠道;根据渠道的 API 格式,构造请求体,发送请求;格式化响应体成标准格式返回。 +3. 调用日志:详细记录模型调用的日志,并在错误时候可以记录其入参和报错信息,方便排查。 + +运行流程: + +![aiproxy12](/imgs/aiproxy1.png) + +## 在 FastGPT 中使用 + +AI proxy 相关功能,可以在`账号-模型提供商`页面找到。 ### 1. 创建渠道 -如果 FastGPT 的环境变量中,设置了 AIPROXY_API_ENDPOINT 的值,那么在“模型提供商”的配置页面,会多出两个 tab,可以直接在 FastGPT 平台上配置模型渠道,以及查看模型实际调用日志。 +在`模型提供商`的配置页面,点击`模型渠道`,进入渠道配置页面 ![aiproxy1](/imgs/aiproxy-1.png) @@ -36,9 +52,18 @@ AI Proxy 与 One API 类似,也是作为一个 OpenAI 接口管理 & 分发系 1. 渠道名:展示在外部的渠道名称,仅作标识; 2. 厂商:模型对应的厂商,不同厂商对应不同的默认地址和 API 密钥格式; 3. 模型:当前渠道具体可以使用的模型,系统内置了主流的一些模型,如果下拉框中没有想要的选项,可以点击“新增模型”,[增加自定义模型](/docs/development/modelconfig/intro/#新增自定义模型); -4. 模型映射:将 FastGPT 请求的模型,映射到具体提供的模型上; -5. 代理地址:具体请求的地址,系统给每个主流渠道配置了默认的地址,如果无需改动则不用填 -6. API 密钥:从模型厂商处获取的 API 凭证 +4. 模型映射:将 FastGPT 请求的模型,映射到具体提供的模型上。例如: + +```json +{ + "gpt-4o-test": "gpt-4o", +} +``` + +FatGPT 中的模型为 `gpt-4o-test`,向 AI Proxy 发起请求时也是 `gpt-4o-test`。AI proxy 在向上游发送请求时,实际的`model`为 `gpt-4o`。 + +5. 代理地址:具体请求的地址,系统给每个主流渠道配置了默认的地址,如果无需改动则不用填。 +6. API 密钥:从模型厂商处获取的 API 凭证。注意部分厂商需要提供多个密钥组合,可以根据提示进行输入。 最后点击“新增”,就能在“模型渠道”下看到刚刚配置的渠道 @@ -60,16 +85,15 @@ AI Proxy 与 One API 类似,也是作为一个 OpenAI 接口管理 & 分发系 ### 3. 启用模型 -最后在“模型配置”中,可以选择启用对应的模型,这样就能在平台中使用了 +最后在`模型配置`中,可以选择启用对应的模型,这样就能在平台中使用了,更多模型配置可以参考[模型配置](/docs/development/modelconfig/intro) ![aiproxy8](/imgs/aiproxy-8.png) - -## 渠道设置 +## 其他功能介绍 ### 优先级 -在 FastGPT 中,可以给渠道设置优先级,对于同样的模型,优先级越高的渠道会越优先请求 +范围1~100。数值越大,越容易被优先选中。 ![aiproxy9](/imgs/aiproxy-9.png) @@ -81,13 +105,15 @@ AI Proxy 与 One API 类似,也是作为一个 OpenAI 接口管理 & 分发系 ### 调用日志 -在 “调用日志” 页面,会展示发送到模型处的请求记录,包括具体的输入输出 tokens、请求时间、请求耗时、请求地址等等 +在 `调用日志` 页面,会展示发送到模型处的请求记录,包括具体的输入输出 tokens、请求时间、请求耗时、请求地址等等。错误的请求,则会详细的入参和错误信息,方便排查,但仅会保留 1 小时(环境变量里可配置)。 ![aiproxy11](/imgs/aiproxy-11.png) -## 如何从 OneAPI 迁移到 AI Proxy +## 从 OneAPI 迁移到 AI Proxy -可以从任意终端,发起 1 个 HTTP 请求。其中 {{host}} 替换成 AI Proxy 地址,{{admin_key}} 替换成 AI Proxy 中 ADMIN_KEY 的值,参数 dsn 为 OneAPI 的 mysql 连接串 +可以从任意终端,发起 1 个 HTTP 请求。其中 `{{host}}` 替换成 AI Proxy 地址,`{{admin_key}}` 替换成 AI Proxy 中 `ADMIN_KEY` 的值。 + +Body 参数 `dsn` 为 OneAPI 的 mysql 连接串。 ```bash curl --location --request POST '{{host}}/api/channels/import/oneapi' \ @@ -100,4 +126,4 @@ curl --location --request POST '{{host}}/api/channels/import/oneapi' \ 执行成功的情况下会返回 "success": true -脚本目前不是完全准,可能会有部分渠道遗漏,还需要手动再检查下 \ No newline at end of file +脚本目前不是完全准,仅是简单的做数据映射,主要是迁移`代理地址`、`模型`和`API 密钥`,建议迁移后再进行手动检查。 \ No newline at end of file diff --git a/docSite/content/zh-cn/docs/development/upgrading/490.md b/docSite/content/zh-cn/docs/development/upgrading/490.md index 7836521d5..4857f7068 100644 --- a/docSite/content/zh-cn/docs/development/upgrading/490.md +++ b/docSite/content/zh-cn/docs/development/upgrading/490.md @@ -46,6 +46,7 @@ curl --location --request POST 'https://{{host}}/api/admin/initv490' \ 1. 知识库数据不再限制索引数量,可无限自定义。同时可自动更新输入文本的索引,不影响自定义索引。 2. Markdown 解析,增加链接后中文标点符号检测,增加空格。 +3. Prompt 模式工具调用,支持思考模型。同时优化其格式检测,减少空输出的概率。 ## 🐛 修复 diff --git a/packages/global/core/chat/adapt.ts b/packages/global/core/chat/adapt.ts index 4c4d65101..4302ca757 100644 --- a/packages/global/core/chat/adapt.ts +++ b/packages/global/core/chat/adapt.ts @@ -1,8 +1,11 @@ import type { + AIChatItemValueItemType, ChatItemType, ChatItemValueItemType, RuntimeUserPromptType, - UserChatItemType + SystemChatItemValueItemType, + UserChatItemType, + UserChatItemValueItemType } from '../../core/chat/type.d'; import { ChatFileTypeEnum, ChatItemValueTypeEnum, ChatRoleEnum } from '../../core/chat/constants'; import type { @@ -174,137 +177,24 @@ export const GPTMessages2Chats = ( ): ChatItemType[] => { const chatMessages = messages .map((item) => { - const value: ChatItemType['value'] = []; const obj = GPT2Chat[item.role]; - if ( - obj === ChatRoleEnum.System && - item.role === ChatCompletionRequestMessageRoleEnum.System - ) { - if (Array.isArray(item.content)) { - item.content.forEach((item) => [ - value.push({ - type: ChatItemValueTypeEnum.text, - text: { - content: item.text - } - }) - ]); - } else { - value.push({ - type: ChatItemValueTypeEnum.text, - text: { - content: item.content - } - }); - } - } else if ( - obj === ChatRoleEnum.Human && - item.role === ChatCompletionRequestMessageRoleEnum.User - ) { - if (typeof item.content === 'string') { - value.push({ - type: ChatItemValueTypeEnum.text, - text: { - content: item.content - } - }); - } else if (Array.isArray(item.content)) { - item.content.forEach((item) => { - if (item.type === 'text') { + const value = (() => { + if ( + obj === ChatRoleEnum.System && + item.role === ChatCompletionRequestMessageRoleEnum.System + ) { + const value: SystemChatItemValueItemType[] = []; + + if (Array.isArray(item.content)) { + item.content.forEach((item) => [ value.push({ type: ChatItemValueTypeEnum.text, text: { content: item.text } - }); - } else if (item.type === 'image_url') { - value.push({ - //@ts-ignore - type: ChatItemValueTypeEnum.file, - file: { - type: ChatFileTypeEnum.image, - name: '', - url: item.image_url.url - } - }); - } else if (item.type === 'file_url') { - value.push({ - // @ts-ignore - type: ChatItemValueTypeEnum.file, - file: { - type: ChatFileTypeEnum.file, - name: item.name, - url: item.url - } - }); - } - }); - } - } else if ( - obj === ChatRoleEnum.AI && - item.role === ChatCompletionRequestMessageRoleEnum.Assistant - ) { - if (item.tool_calls && reserveTool) { - // save tool calls - const toolCalls = item.tool_calls as ChatCompletionMessageToolCall[]; - value.push({ - //@ts-ignore - type: ChatItemValueTypeEnum.tool, - tools: toolCalls.map((tool) => { - let toolResponse = - messages.find( - (msg) => - msg.role === ChatCompletionRequestMessageRoleEnum.Tool && - msg.tool_call_id === tool.id - )?.content || ''; - toolResponse = - typeof toolResponse === 'string' ? toolResponse : JSON.stringify(toolResponse); - - return { - id: tool.id, - toolName: tool.toolName || '', - toolAvatar: tool.toolAvatar || '', - functionName: tool.function.name, - params: tool.function.arguments, - response: toolResponse as string - }; - }) - }); - } else if (item.function_call && reserveTool) { - const functionCall = item.function_call as ChatCompletionMessageFunctionCall; - const functionResponse = messages.find( - (msg) => - msg.role === ChatCompletionRequestMessageRoleEnum.Function && - msg.name === item.function_call?.name - ) as ChatCompletionFunctionMessageParam; - - if (functionResponse) { - value.push({ - //@ts-ignore - type: ChatItemValueTypeEnum.tool, - tools: [ - { - id: functionCall.id || '', - toolName: functionCall.toolName || '', - toolAvatar: functionCall.toolAvatar || '', - functionName: functionCall.name, - params: functionCall.arguments, - response: functionResponse.content || '' - } - ] - }); - } - } else if (item.interactive) { - value.push({ - //@ts-ignore - type: ChatItemValueTypeEnum.interactive, - interactive: item.interactive - }); - } else if (typeof item.content === 'string') { - const lastValue = value[value.length - 1]; - if (lastValue && lastValue.type === ChatItemValueTypeEnum.text && lastValue.text) { - lastValue.text.content += item.content; + }) + ]); } else { value.push({ type: ChatItemValueTypeEnum.text, @@ -313,8 +203,145 @@ export const GPTMessages2Chats = ( } }); } + return value; + } else if ( + obj === ChatRoleEnum.Human && + item.role === ChatCompletionRequestMessageRoleEnum.User + ) { + const value: UserChatItemValueItemType[] = []; + + if (typeof item.content === 'string') { + value.push({ + type: ChatItemValueTypeEnum.text, + text: { + content: item.content + } + }); + } else if (Array.isArray(item.content)) { + item.content.forEach((item) => { + if (item.type === 'text') { + value.push({ + type: ChatItemValueTypeEnum.text, + text: { + content: item.text + } + }); + } else if (item.type === 'image_url') { + value.push({ + //@ts-ignore + type: ChatItemValueTypeEnum.file, + file: { + type: ChatFileTypeEnum.image, + name: '', + url: item.image_url.url + } + }); + } else if (item.type === 'file_url') { + value.push({ + // @ts-ignore + type: ChatItemValueTypeEnum.file, + file: { + type: ChatFileTypeEnum.file, + name: item.name, + url: item.url + } + }); + } + }); + } + return value; + } else if ( + obj === ChatRoleEnum.AI && + item.role === ChatCompletionRequestMessageRoleEnum.Assistant + ) { + const value: AIChatItemValueItemType[] = []; + + if (typeof item.reasoning_text === 'string') { + value.push({ + type: ChatItemValueTypeEnum.reasoning, + reasoning: { + content: item.reasoning_text + } + }); + } + if (item.tool_calls && reserveTool) { + // save tool calls + const toolCalls = item.tool_calls as ChatCompletionMessageToolCall[]; + value.push({ + //@ts-ignore + type: ChatItemValueTypeEnum.tool, + tools: toolCalls.map((tool) => { + let toolResponse = + messages.find( + (msg) => + msg.role === ChatCompletionRequestMessageRoleEnum.Tool && + msg.tool_call_id === tool.id + )?.content || ''; + toolResponse = + typeof toolResponse === 'string' ? toolResponse : JSON.stringify(toolResponse); + + return { + id: tool.id, + toolName: tool.toolName || '', + toolAvatar: tool.toolAvatar || '', + functionName: tool.function.name, + params: tool.function.arguments, + response: toolResponse as string + }; + }) + }); + } + if (item.function_call && reserveTool) { + const functionCall = item.function_call as ChatCompletionMessageFunctionCall; + const functionResponse = messages.find( + (msg) => + msg.role === ChatCompletionRequestMessageRoleEnum.Function && + msg.name === item.function_call?.name + ) as ChatCompletionFunctionMessageParam; + + if (functionResponse) { + value.push({ + //@ts-ignore + type: ChatItemValueTypeEnum.tool, + tools: [ + { + id: functionCall.id || '', + toolName: functionCall.toolName || '', + toolAvatar: functionCall.toolAvatar || '', + functionName: functionCall.name, + params: functionCall.arguments, + response: functionResponse.content || '' + } + ] + }); + } + } + if (item.interactive) { + value.push({ + //@ts-ignore + type: ChatItemValueTypeEnum.interactive, + interactive: item.interactive + }); + } + if (typeof item.content === 'string') { + const lastValue = value[value.length - 1]; + if (lastValue && lastValue.type === ChatItemValueTypeEnum.text && lastValue.text) { + lastValue.text.content += item.content; + } else { + value.push({ + type: ChatItemValueTypeEnum.text, + text: { + content: item.content + } + }); + } + } + + return value; } - } + + return []; + })(); return { dataId: item.dataId, diff --git a/packages/global/core/chat/type.d.ts b/packages/global/core/chat/type.d.ts index 4e010c68f..8837075e8 100644 --- a/packages/global/core/chat/type.d.ts +++ b/packages/global/core/chat/type.d.ts @@ -77,6 +77,7 @@ export type AIChatItemValueItemType = { | ChatItemValueTypeEnum.reasoning | ChatItemValueTypeEnum.tool | ChatItemValueTypeEnum.interactive; + text?: { content: string; }; diff --git a/packages/global/core/workflow/template/system/aiChat/index.ts b/packages/global/core/workflow/template/system/aiChat/index.ts index 8d6aa6cd0..db5df3d6d 100644 --- a/packages/global/core/workflow/template/system/aiChat/index.ts +++ b/packages/global/core/workflow/template/system/aiChat/index.ts @@ -55,7 +55,7 @@ export const AiChatModule: FlowNodeTemplateType = { showStatus: true, isTool: true, courseUrl: '/docs/guide/workbench/workflow/ai_chat/', - version: '4813', + version: '490', inputs: [ Input_Template_SettingAiModel, // --- settings modal diff --git a/packages/global/core/workflow/template/system/tools.ts b/packages/global/core/workflow/template/system/tools.ts index 8ef75d9f5..672deaffa 100644 --- a/packages/global/core/workflow/template/system/tools.ts +++ b/packages/global/core/workflow/template/system/tools.ts @@ -58,6 +58,13 @@ export const ToolModule: FlowNodeTemplateType = { valueType: WorkflowIOValueTypeEnum.boolean, value: true }, + { + key: NodeInputKeyEnum.aiChatReasoning, + renderTypeList: [FlowNodeInputTypeEnum.hidden], + label: '', + valueType: WorkflowIOValueTypeEnum.boolean, + value: true + }, { key: NodeInputKeyEnum.aiChatTopP, renderTypeList: [FlowNodeInputTypeEnum.hidden], diff --git a/packages/service/common/file/read/utils.ts b/packages/service/common/file/read/utils.ts index 406c9f32f..2c943c6b1 100644 --- a/packages/service/common/file/read/utils.ts +++ b/packages/service/common/file/read/utils.ts @@ -245,7 +245,7 @@ export const readRawContentByFileBuffer = async ({ if (result_data.data.status === 'success') { const result = result_data.data.result.pages .map((page) => page.md) - .join('\n') + .join('') // Do some post-processing .replace(/\\[\(\)]/g, '$') .replace(/\\[\[\]]/g, '$$') diff --git a/packages/service/core/ai/config/provider/Moonshot.json b/packages/service/core/ai/config/provider/Moonshot.json index 796c529b8..f33b09ebe 100644 --- a/packages/service/core/ai/config/provider/Moonshot.json +++ b/packages/service/core/ai/config/provider/Moonshot.json @@ -75,6 +75,81 @@ "showTopP": true, "showStopSign": true, "responseFormatList": ["text", "json_object"] + }, + { + "model": "moonshot-v1-8k-vision-preview", + "name": "moonshot-v1-8k-vision-preview", + "maxContext": 8000, + "maxResponse": 4000, + "quoteMaxToken": 6000, + "maxTemperature": 1, + "vision": true, + "toolChoice": true, + "functionCall": false, + "defaultSystemChatPrompt": "", + "datasetProcess": true, + "usedInClassify": true, + "customCQPrompt": "", + "usedInExtractFields": true, + "usedInQueryExtension": true, + "customExtractPrompt": "", + "usedInToolCall": true, + "defaultConfig": {}, + "fieldMap": {}, + "type": "llm", + "showTopP": true, + "showStopSign": true, + "responseFormatList": ["text", "json_object"] + }, + { + "model": "moonshot-v1-32k-vision-preview", + "name": "moonshot-v1-32k-vision-preview", + "maxContext": 32000, + "maxResponse": 4000, + "quoteMaxToken": 32000, + "maxTemperature": 1, + "vision": true, + "toolChoice": true, + "functionCall": false, + "defaultSystemChatPrompt": "", + "datasetProcess": true, + "usedInClassify": true, + "customCQPrompt": "", + "usedInExtractFields": true, + "usedInQueryExtension": true, + "customExtractPrompt": "", + "usedInToolCall": true, + "defaultConfig": {}, + "fieldMap": {}, + "type": "llm", + "showTopP": true, + "showStopSign": true, + "responseFormatList": ["text", "json_object"] + }, + { + "model": "moonshot-v1-128k-vision-preview", + "name": "moonshot-v1-128k-vision-preview", + "maxContext": 128000, + "maxResponse": 4000, + "quoteMaxToken": 60000, + "maxTemperature": 1, + "vision": true, + "toolChoice": true, + "functionCall": false, + "defaultSystemChatPrompt": "", + "datasetProcess": true, + "usedInClassify": true, + "customCQPrompt": "", + "usedInExtractFields": true, + "usedInQueryExtension": true, + "customExtractPrompt": "", + "usedInToolCall": true, + "defaultConfig": {}, + "fieldMap": {}, + "type": "llm", + "showTopP": true, + "showStopSign": true, + "responseFormatList": ["text", "json_object"] } ] } diff --git a/packages/service/core/app/templates/templateSchema.ts b/packages/service/core/app/templates/templateSchema.ts index 2485f535f..826437412 100644 --- a/packages/service/core/app/templates/templateSchema.ts +++ b/packages/service/core/app/templates/templateSchema.ts @@ -9,41 +9,23 @@ const AppTemplateSchema = new Schema({ type: String, required: true }, - name: { - type: String - }, - intro: { - type: String - }, - avatar: { - type: String - }, - author: { - type: String - }, + name: String, + intro: String, + avatar: String, + author: String, tags: { type: [String], default: undefined }, - type: { - type: String - }, - isActive: { - type: Boolean - }, - userGuide: { - type: Object - }, - isQuickTemplate: { - type: Boolean - }, + type: String, + isActive: Boolean, + userGuide: Object, + isQuickTemplate: Boolean, order: { type: Number, default: -1 }, - workflow: { - type: Object - } + workflow: Object }); AppTemplateSchema.index({ templateId: 1 }); diff --git a/packages/service/core/workflow/dispatch/agent/runTool/index.ts b/packages/service/core/workflow/dispatch/agent/runTool/index.ts index d0f99583f..b10b57eac 100644 --- a/packages/service/core/workflow/dispatch/agent/runTool/index.ts +++ b/packages/service/core/workflow/dispatch/agent/runTool/index.ts @@ -55,7 +55,8 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise< userChatInput, history = 6, fileUrlList: fileLinks, - aiChatVision + aiChatVision, + aiChatReasoning } } = props; @@ -63,6 +64,9 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise< const useVision = aiChatVision && toolModel.vision; const chatHistories = getHistories(history, histories); + props.params.aiChatVision = aiChatVision && toolModel.vision; + props.params.aiChatReasoning = aiChatReasoning && toolModel.reasoning; + const toolNodeIds = filterToolNodeIdByEdges({ nodeId, edges: runtimeEdges }); // Gets the module to which the tool is connected diff --git a/packages/service/core/workflow/dispatch/agent/runTool/promptCall.ts b/packages/service/core/workflow/dispatch/agent/runTool/promptCall.ts index 58e95a059..c5c0cb4bd 100644 --- a/packages/service/core/workflow/dispatch/agent/runTool/promptCall.ts +++ b/packages/service/core/workflow/dispatch/agent/runTool/promptCall.ts @@ -24,7 +24,12 @@ import { import { AIChatItemType } from '@fastgpt/global/core/chat/type'; import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt'; import { formatToolResponse, initToolCallEdges, initToolNodes } from './utils'; -import { computedMaxToken, llmCompletionsBodyFormat } from '../../../../ai/utils'; +import { + computedMaxToken, + llmCompletionsBodyFormat, + parseReasoningContent, + parseReasoningStreamContent +} from '../../../../ai/utils'; import { WorkflowResponseType } from '../../type'; import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants'; import { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type'; @@ -58,6 +63,7 @@ export const runToolWithPromptCall = async ( temperature, maxToken, aiChatVision, + aiChatReasoning, aiChatTopP, aiChatStopSign, aiChatResponseFormat, @@ -216,7 +222,7 @@ export const runToolWithPromptCall = async ( const [requestMessages] = await Promise.all([ loadRequestMessages({ messages: filterMessages, - useVision: toolModel.vision && aiChatVision, + useVision: aiChatVision, origin: requestOrigin }) ]); @@ -251,22 +257,46 @@ export const runToolWithPromptCall = async ( } }); - const answer = await (async () => { + const { answer, reasoning } = await (async () => { if (res && isStreamResponse) { - const { answer } = await streamResponse({ + const { answer, reasoning } = await streamResponse({ res, toolNodes, stream: aiResponse, - workflowStreamResponse + workflowStreamResponse, + aiChatReasoning }); - return answer; + return { answer, reasoning }; } else { - const result = aiResponse as ChatCompletion; + const content = aiResponse.choices?.[0]?.message?.content || ''; + const reasoningContent: string = aiResponse.choices?.[0]?.message?.reasoning_content || ''; - return result.choices?.[0]?.message?.content || ''; + // API already parse reasoning content + if (reasoningContent || !aiChatReasoning) { + return { + answer: content, + reasoning: reasoningContent + }; + } + + const [think, answer] = parseReasoningContent(content); + return { + answer, + reasoning: think + }; } })(); + + if (stream && !isStreamResponse && aiChatReasoning && reasoning) { + workflowStreamResponse?.({ + event: SseResponseEventEnum.fastAnswer, + data: textAdaptGptResponse({ + reasoning_content: reasoning + }) + }); + } + const { answer: replaceAnswer, toolJson } = parseAnswer(answer); if (!answer && !toolJson) { return Promise.reject(getEmptyResponseTip()); @@ -294,11 +324,16 @@ export const runToolWithPromptCall = async ( } // No tool is invoked, indicating that the process is over - const gptAssistantResponse: ChatCompletionAssistantMessageParam = { + const gptAssistantResponse: ChatCompletionMessageParam = { role: ChatCompletionRequestMessageRoleEnum.Assistant, - content: replaceAnswer + content: replaceAnswer, + reasoning_text: reasoning }; - const completeMessages = filterMessages.concat(gptAssistantResponse); + const completeMessages = filterMessages.concat({ + ...gptAssistantResponse, + reasoning_text: undefined + }); + const inputTokens = await countGptMessagesTokens(requestMessages); const outputTokens = await countGptMessagesTokens([gptAssistantResponse]); @@ -379,9 +414,10 @@ export const runToolWithPromptCall = async ( })(); // 合并工具调用的结果,使用 functionCall 格式存储。 - const assistantToolMsgParams: ChatCompletionAssistantMessageParam = { + const assistantToolMsgParams: ChatCompletionMessageParam = { role: ChatCompletionRequestMessageRoleEnum.Assistant, - function_call: toolJson + function_call: toolJson, + reasoning_text: reasoning }; // Only toolCall tokens are counted here, Tool response tokens count towards the next reply @@ -502,12 +538,14 @@ ANSWER: `; async function streamResponse({ res, stream, - workflowStreamResponse + workflowStreamResponse, + aiChatReasoning }: { res: NextApiResponse; toolNodes: ToolNodeItemType[]; stream: StreamChatType; workflowStreamResponse?: WorkflowResponseType; + aiChatReasoning?: boolean; }) { const write = responseWriteController({ res, @@ -515,7 +553,9 @@ async function streamResponse({ }); let startResponseWrite = false; - let textAnswer = ''; + let answer = ''; + let reasoning = ''; + const { parsePart, getStartTagBuffer } = parseReasoningStreamContent(); for await (const part of stream) { if (res.closed) { @@ -523,13 +563,21 @@ async function streamResponse({ break; } - const responseChoice = part.choices?.[0]?.delta; - // console.log(responseChoice, '---==='); + const [reasoningContent, content] = parsePart(part, aiChatReasoning); + answer += content; + reasoning += reasoningContent; - if (responseChoice?.content) { - const content = responseChoice?.content || ''; - textAnswer += content; + if (aiChatReasoning && reasoningContent) { + workflowStreamResponse?.({ + write, + event: SseResponseEventEnum.answer, + data: textAdaptGptResponse({ + reasoning_content: reasoningContent + }) + }); + } + if (content) { if (startResponseWrite) { workflowStreamResponse?.({ write, @@ -538,18 +586,20 @@ async function streamResponse({ text: content }) }); - } else if (textAnswer.length >= 3) { - textAnswer = textAnswer.trim(); - if (textAnswer.startsWith('0')) { + } else if (answer.length >= 3) { + answer = answer.trimStart(); + if (/0(:|:)/.test(answer)) { startResponseWrite = true; + // find first : index - const firstIndex = textAnswer.indexOf(':'); - textAnswer = textAnswer.substring(firstIndex + 1).trim(); + const firstIndex = + answer.indexOf('0:') !== -1 ? answer.indexOf('0:') : answer.indexOf('0:'); + answer = answer.substring(firstIndex + 2).trim(); workflowStreamResponse?.({ write, event: SseResponseEventEnum.answer, data: textAdaptGptResponse({ - text: textAnswer + text: answer }) }); } @@ -557,7 +607,23 @@ async function streamResponse({ } } - return { answer: textAnswer.trim() }; + if (answer === '') { + answer = getStartTagBuffer(); + if (/0(:|:)/.test(answer)) { + // find first : index + const firstIndex = answer.indexOf('0:') !== -1 ? answer.indexOf('0:') : answer.indexOf('0:'); + answer = answer.substring(firstIndex + 2).trim(); + workflowStreamResponse?.({ + write, + event: SseResponseEventEnum.answer, + data: textAdaptGptResponse({ + text: answer + }) + }); + } + } + + return { answer, reasoning }; } const parseAnswer = ( @@ -568,8 +634,7 @@ const parseAnswer = ( } => { str = str.trim(); // 首先,使用正则表达式提取TOOL_ID和TOOL_ARGUMENTS - const prefixReg = /^1(:|:)/; - const answerPrefixReg = /^0(:|:)/; + const prefixReg = /1(:|:)/; if (prefixReg.test(str)) { const toolString = sliceJsonStr(str); @@ -585,13 +650,21 @@ const parseAnswer = ( } }; } catch (error) { - return { - answer: ERROR_TEXT - }; + if (/^1(:|:)/.test(str)) { + return { + answer: ERROR_TEXT + }; + } else { + return { + answer: str + }; + } } } else { + const firstIndex = str.indexOf('0:') !== -1 ? str.indexOf('0:') : str.indexOf('0:'); + const answer = str.substring(firstIndex + 2).trim(); return { - answer: str.replace(answerPrefixReg, '') + answer }; } }; diff --git a/packages/service/core/workflow/dispatch/agent/runTool/type.d.ts b/packages/service/core/workflow/dispatch/agent/runTool/type.d.ts index 64ecd79fc..61cb6b217 100644 --- a/packages/service/core/workflow/dispatch/agent/runTool/type.d.ts +++ b/packages/service/core/workflow/dispatch/agent/runTool/type.d.ts @@ -22,6 +22,7 @@ export type DispatchToolModuleProps = ModuleDispatchProps<{ [NodeInputKeyEnum.aiChatTemperature]: number; [NodeInputKeyEnum.aiChatMaxToken]: number; [NodeInputKeyEnum.aiChatVision]?: boolean; + [NodeInputKeyEnum.aiChatReasoning]?: boolean; [NodeInputKeyEnum.aiChatTopP]?: number; [NodeInputKeyEnum.aiChatStopSign]?: string; [NodeInputKeyEnum.aiChatResponseFormat]?: string; diff --git a/packages/service/core/workflow/dispatch/chat/oneapi.ts b/packages/service/core/workflow/dispatch/chat/oneapi.ts index 41df8e31d..07035a826 100644 --- a/packages/service/core/workflow/dispatch/chat/oneapi.ts +++ b/packages/service/core/workflow/dispatch/chat/oneapi.ts @@ -563,6 +563,15 @@ async function streamResponse({ // if answer is empty, try to get value from startTagBuffer. (Cause: The response content is too short to exceed the minimum parse length) if (answer === '') { answer = getStartTagBuffer(); + if (isResponseAnswerText && answer) { + workflowStreamResponse?.({ + write, + event: SseResponseEventEnum.answer, + data: textAdaptGptResponse({ + text: answer + }) + }); + } } return { answer, reasoning }; diff --git a/packages/service/worker/readFile/index.ts b/packages/service/worker/readFile/index.ts index 625191a39..45092ed72 100644 --- a/packages/service/worker/readFile/index.ts +++ b/packages/service/worker/readFile/index.ts @@ -27,7 +27,9 @@ parentPort?.on('message', async (props: ReadRawTextProps) => { case 'csv': return readCsvRawText(params); default: - return Promise.reject('Only support .txt, .md, .html, .pdf, .docx, pptx, .csv, .xlsx'); + return Promise.reject( + `Only support .txt, .md, .html, .pdf, .docx, pptx, .csv, .xlsx. "${params.extension}" is not supported.` + ); } }; diff --git a/projects/app/src/pageComponents/account/model/Channel/index.tsx b/projects/app/src/pageComponents/account/model/Channel/index.tsx index 0f6ed2142..795cfd521 100644 --- a/projects/app/src/pageComponents/account/model/Channel/index.tsx +++ b/projects/app/src/pageComponents/account/model/Channel/index.tsx @@ -139,14 +139,14 @@ const ChannelTable = ({ Tab }: { Tab: React.ReactNode }) => { { const val = (() => { - if (!e) return 0; + if (!e) return 1; return e; })(); updateChannel({ diff --git a/projects/app/src/web/core/ai/channel.ts b/projects/app/src/web/core/ai/channel.ts index b7a10061b..fd4534a3f 100644 --- a/projects/app/src/web/core/ai/channel.ts +++ b/projects/app/src/web/core/ai/channel.ts @@ -130,7 +130,8 @@ export const postCreateChannel = (data: CreateChannelProps) => base_url: data.base_url, models: data.models, model_mapping: data.model_mapping, - key: data.key + key: data.key, + priority: 1 }); export const putChannelStatus = (id: number, status: ChannelStatusEnum) => @@ -146,7 +147,7 @@ export const putChannel = (data: ChannelInfoType) => model_mapping: data.model_mapping, key: data.key, status: data.status, - priority: data.priority + priority: data.priority ? Math.max(data.priority, 1) : undefined }); export const deleteChannel = (id: number) => DELETE(`/channel/${id}`);