Compare commits
51 Commits
v4.8-previ
...
v4.8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59fd94384d | ||
|
|
ee8cb0915e | ||
|
|
8cf643d972 | ||
|
|
26f4c92124 | ||
|
|
f351d4ea68 | ||
|
|
d70efe1d6f | ||
|
|
435b2fba25 | ||
|
|
d61de17df2 | ||
|
|
08a310c41f | ||
|
|
50716ff782 | ||
|
|
5e250b2f65 | ||
|
|
434af56abd | ||
|
|
6463427d93 | ||
|
|
af4c732d93 | ||
|
|
d4169bf066 | ||
|
|
afe5039cd3 | ||
|
|
2155489be3 | ||
|
|
eb36b71ac3 | ||
|
|
2230bc40c5 | ||
|
|
917e4e9262 | ||
|
|
3c6e5a6e00 | ||
|
|
7b75a99ba2 | ||
|
|
2e468fc8ca | ||
|
|
caa0755d9a | ||
|
|
fef1a1702b | ||
|
|
2a99e46353 | ||
|
|
8f9203c053 | ||
|
|
2053bbdb1b | ||
|
|
9e192c6d11 | ||
|
|
eef609a063 | ||
|
|
5bb9c550f6 | ||
|
|
db1c27cdc7 | ||
|
|
8863337606 | ||
|
|
59bd2a47b6 | ||
|
|
d057ba29f0 | ||
|
|
b500631a4d | ||
|
|
bf6084da69 | ||
|
|
b5f0ac3e1d | ||
|
|
1529c1e991 | ||
|
|
db6fc53840 | ||
|
|
a0c1320d47 | ||
|
|
5ca4049757 | ||
|
|
59ece446a2 | ||
|
|
d407e87dd9 | ||
|
|
c8412e7dc9 | ||
|
|
f6247fe11d | ||
|
|
613699fe59 | ||
|
|
c56c28be23 | ||
|
|
89ab17ea2e | ||
|
|
c608f86146 | ||
|
|
0a8b104bd7 |
2
.github/ISSUE_TEMPLATE/bugs.md
vendored
@@ -21,7 +21,7 @@ assignees: ''
|
||||
- [ ] 公有云版本
|
||||
- [ ] 私有部署版本, 具体版本号:
|
||||
|
||||
**问题描述**
|
||||
**问题描述, 日志截图**
|
||||
|
||||
**复现步骤**
|
||||
|
||||
|
||||
2
.github/workflows/fastgpt-image.yml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
- 'projects/app/**'
|
||||
- 'packages/**'
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
- 'v*'
|
||||
jobs:
|
||||
build-fastgpt-images:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
43
.vscode/i18n-ally-custom-framework.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# .vscode/i18n-ally-custom-framework.yml
|
||||
|
||||
# An array of strings which contain Language Ids defined by VS Code
|
||||
# You can check available language ids here: https://code.visualstudio.com/docs/languages/identifiers
|
||||
languageIds:
|
||||
- javascript
|
||||
- typescript
|
||||
- javascriptreact
|
||||
- typescriptreact
|
||||
|
||||
# An array of RegExes to find the key usage. **The key should be captured in the first match group**.
|
||||
# You should unescape RegEx strings in order to fit in the YAML file
|
||||
# To help with this, you can use https://www.freeformatter.com/json-escape.html
|
||||
usageMatchRegex:
|
||||
# The following example shows how to detect `t("your.i18n.keys")`
|
||||
# the `{key}` will be placed by a proper keypath matching regex,
|
||||
# you can ignore it and use your own matching rules as well
|
||||
- "[^\\w\\d]t\\(['\"`]({key})['\"`]"
|
||||
- "[^\\w\\d]commonT\\(['\"`]({key})['\"`]"
|
||||
# 支持 appT("your.i18n.keys")
|
||||
- "[^\\w\\d]appT\\(['\"`]({key})['\"`]"
|
||||
# 支持 datasetT("your.i18n.keys")
|
||||
- "[^\\w\\d]datasetT\\(['\"`]({key})['\"`]"
|
||||
- "[^\\w\\d]fileT\\(['\"`]({key})['\"`]"
|
||||
- "[^\\w\\d]publishT\\(['\"`]({key})['\"`]"
|
||||
|
||||
# A RegEx to set a custom scope range. This scope will be used as a prefix when detecting keys
|
||||
# and works like how the i18next framework identifies the namespace scope from the
|
||||
# useTranslation() hook.
|
||||
# You should unescape RegEx strings in order to fit in the YAML file
|
||||
# To help with this, you can use https://www.freeformatter.com/json-escape.html
|
||||
scopeRangeRegex: "useTranslation\\(\\s*\\[?\\s*['\"`](.*?)['\"`]"
|
||||
|
||||
# An array of strings containing refactor templates.
|
||||
# The "$1" will be replaced by the keypath specified.
|
||||
# Optional: uncomment the following two lines to use
|
||||
|
||||
# refactorTemplates:
|
||||
# - i18n.get("$1")
|
||||
|
||||
|
||||
# If set to true, only enables this custom framework (will disable all built-in frameworks)
|
||||
monopoly: true
|
||||
29
.vscode/nextapi.code-snippets
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
// Place your FastGPT 工作区 snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
|
||||
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
|
||||
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
|
||||
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
|
||||
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
|
||||
// Placeholders with the same ids are connected.
|
||||
// Example:
|
||||
"Next api template": {
|
||||
"scope": "javascript,typescript",
|
||||
"prefix": "nextapi",
|
||||
"body": [
|
||||
"import type { NextApiRequest, NextApiResponse } from 'next';",
|
||||
"import { NextAPI } from '@/service/middle/entry';",
|
||||
"",
|
||||
"type Props = {};",
|
||||
"",
|
||||
"type Response = {};",
|
||||
"",
|
||||
"async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<Response> {",
|
||||
" $1",
|
||||
" return {}",
|
||||
"}",
|
||||
"",
|
||||
"export default NextAPI(handler);"
|
||||
],
|
||||
"description": "FastGPT Next API template"
|
||||
}
|
||||
}
|
||||
8
.vscode/settings.json
vendored
@@ -4,12 +4,12 @@
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"prettier.prettierPath": "",
|
||||
"i18n-ally.localesPaths": [
|
||||
"projects/app/public/locales",
|
||||
"projects/app/i18n",
|
||||
],
|
||||
"i18n-ally.enabledParsers": ["json"],
|
||||
"i18n-ally.enabledParsers": ["json", "yaml", "js", "ts"],
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.sortKeys": true,
|
||||
"i18n-ally.keepFulfilled": true,
|
||||
"i18n-ally.keepFulfilled": false,
|
||||
"i18n-ally.sourceLanguage": "zh", // 根据此语言文件翻译其他语言文件的变量和内容
|
||||
"i18n-ally.displayLanguage": "zh", // 显示语言
|
||||
"i18n-ally.displayLanguage": "zh" // 显示语言
|
||||
}
|
||||
@@ -122,7 +122,7 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b
|
||||
|
||||
wx 扫一下加入:
|
||||
|
||||

|
||||

|
||||
|
||||
<a href="#readme">
|
||||
<img src="https://img.shields.io/badge/-返回顶部-7d09f1.svg" alt="#" align="right">
|
||||
|
||||
|
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 206 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 225 KiB |
|
Before Width: | Height: | Size: 181 KiB After Width: | Height: | Size: 285 KiB |
|
Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 293 KiB |
|
Before Width: | Height: | Size: 184 KiB After Width: | Height: | Size: 281 KiB |
|
Before Width: | Height: | Size: 205 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 186 KiB After Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 206 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 173 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 190 KiB After Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 180 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 48 KiB |
@@ -7,7 +7,7 @@ toc: true
|
||||
weight: 101
|
||||
---
|
||||
|
||||
更多使用技巧,[查看视屏教程](https://www.bilibili.com/video/BV1n34y1A7Bo/?spm_id_from=333.337.search-card.all.click&vd_source=903c2b09b7412037c2eddc6a8fb9828b)
|
||||
更多使用技巧,[查看视屏教程](https://www.bilibili.com/video/BV1sH4y1T7s9)
|
||||
|
||||
## 知识库
|
||||
|
||||
|
||||
@@ -257,6 +257,13 @@ PG 数据库没有连接上/初始化失败,可以查看日志。FastGPT 会
|
||||
2. 非 docker 部署的,需要手动安装 pg vector 插件
|
||||
3. 查看 fastgpt 日志,有没有相关报错
|
||||
|
||||
### Illegal instruction
|
||||
|
||||
可能原因:
|
||||
|
||||
1. arm架构。需要使用 Mongo 官方镜像: mongo:5.0.18
|
||||
2. cpu 不支持 AVX,无法用 mongo5,需要换成 mongo4.x。把 mongo 的 image 换成: mongo:4.4.29
|
||||
|
||||
### Operation `auth_codes.findOne()` buffering timed out after 10000ms
|
||||
|
||||
mongo连接失败,查看mongo的运行状态对应日志。
|
||||
|
||||
@@ -13,7 +13,8 @@ images: []
|
||||
|
||||
1. `docker ps -a` 查看所有容器运行状态,检查是否全部 running,如有异常,尝试`docker logs 容器名`查看对应日志。
|
||||
2. 容器都运行正常的,`docker logs 容器名` 查看报错日志
|
||||
3. 无法解决时,可以找找[Issue](https://github.com/labring/FastGPT/issues),或新提 Issue,私有部署错误,务必提供详细的日志,否则很难排查。
|
||||
3. 带有`requestId`的,都是 OneAPI 提示错误,大部分都是因为模型接口报错。
|
||||
4. 无法解决时,可以找找[Issue](https://github.com/labring/FastGPT/issues),或新提 Issue,私有部署错误,务必提供详细的日志,否则很难排查。
|
||||
|
||||
|
||||
## 二、通用问题
|
||||
@@ -22,10 +23,6 @@ images: []
|
||||
|
||||
可以。需要准备好向量模型和LLM模型。
|
||||
|
||||
### 页面中可以正常回复,API 报错
|
||||
|
||||
页面中是用 stream=true 模式,所以API也需要设置 stream=true 来进行测试。部分模型接口(国产居多)非 Stream 的兼容有点垃圾。
|
||||
|
||||
### 其他模型没法进行问题分类/内容提取
|
||||
|
||||
1. 看日志。如果提示 JSON invalid,not support tool 之类的,说明该模型不支持工具调用或函数调用,需要设置`toolChoice=false`和`functionCall=false`,就会默认走提示词模式。目前内置提示词仅针对了商业模型API进行测试。问题分类基本可用,内容提取不太行。
|
||||
@@ -42,12 +39,36 @@ images: []
|
||||
1. 问题补全需要经过一轮AI生成。
|
||||
2. 会进行3~5轮的查询,如果数据库性能不足,会有明显影响。
|
||||
|
||||
### 模型响应为空(core.chat.Chat API is error or undefined)
|
||||
### 对话接口报错或返回为空(core.chat.Chat API is error or undefined)
|
||||
|
||||
1. 检查 key 问题。
|
||||
1. 检查 AI 的 key 问题:通过 curl 请求看是否正常。务必用 stream=true 模式。并且 maxToken 等相关参数尽量一致。
|
||||
2. 如果是国内模型,可能是命中风控了。
|
||||
3. 查看模型请求日志,检查出入参数是否异常。
|
||||
|
||||
```sh
|
||||
# curl 例子。
|
||||
curl --location --request POST 'https://xxx.cn/v1/chat/completions' \
|
||||
--header 'Authorization: Bearer sk-xxxx' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"model": "gpt-3.5-turbo",
|
||||
"stream": true,
|
||||
"temperature": 1,
|
||||
"max_tokens": 3000,
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "你是谁"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
### 页面中可以正常回复,API 报错
|
||||
|
||||
页面中是用 stream=true 模式,所以API也需要设置 stream=true 来进行测试。部分模型接口(国产居多)非 Stream 的兼容有点垃圾。
|
||||
和上一个问题一样,curl 测试。
|
||||
|
||||
### 知识库索引没有进度/索引很慢
|
||||
|
||||
先看日志报错信息。有以下几种情况:
|
||||
@@ -76,12 +97,14 @@ images: []
|
||||
|
||||
OneAPI 账号的余额不足,默认 root 用户只有 200 刀,可以手动修改。
|
||||
|
||||
路径:打开OneAPI -> 用户 -> root用户右边的编辑 -> 剩余余额调大
|
||||
|
||||
### xxx渠道找不到
|
||||
|
||||
FastGPT 模型配置文件中的 model 必须与 OneAPI 渠道中的模型对应上,否则就会提示这个错误。可检查下面内容:
|
||||
|
||||
1. OneAPI 中没有配置该模型渠道,或者被禁用了。
|
||||
2. 修改了 FastGPT 配置文件中一部分的模型,但没有全部修改,仍有模型是 OneAPI 没配置的。
|
||||
2. FastGPT 配置文件有 OneAPI 没有配置的模型。如果 OneAPI 没有配置对应模型的,配置文件中也不要写。
|
||||
3. 使用旧的向量模型创建了知识库,后又更新了向量模型。这时候需要删除以前的知识库,重建。
|
||||
|
||||
如果OneAPI中,没有配置对应的模型,`config.json`中也不要配置,否则容易报错。
|
||||
@@ -90,4 +113,9 @@ FastGPT 模型配置文件中的 model 必须与 OneAPI 渠道中的模型对应
|
||||
|
||||
OneAPI 的 API Key 配置错误,需要修改`OPENAI_API_KEY`环境变量,并重启容器(先 docker-compose down 然后再 docker-compose up -d 运行一次)。
|
||||
|
||||
可以`exec`进入容器,`env`查看环境变量是否生效。
|
||||
可以`exec`进入容器,`env`查看环境变量是否生效。
|
||||
|
||||
### bad_response_status_code bad response status code 503
|
||||
|
||||
1. 模型服务不可用
|
||||
2. ....
|
||||
187
docSite/content/docs/development/migration/ docker_mongo.md
Normal file
@@ -0,0 +1,187 @@
|
||||
---
|
||||
weight: 762
|
||||
title: "Docker Mongo迁移(dump模式)"
|
||||
description: "FastGPT Docker Mongo迁移"
|
||||
icon: database
|
||||
draft: false
|
||||
images: []
|
||||
---
|
||||
|
||||
## 作者
|
||||
|
||||
[https://github.com/samqin123](https://github.com/samqin123)
|
||||
|
||||
[相关PR。有问题可打开这里与作者交流](https://github.com/labring/FastGPT/pull/1426)
|
||||
|
||||
## 介绍
|
||||
|
||||
如何使用Mongodump来完成从A环境到B环境的Fastgpt的mongodb迁移
|
||||
|
||||
前提说明:
|
||||
|
||||
A环境:我在阿里云上部署的fastgpt,现在需要迁移到B环境。
|
||||
B环境:是新环境比如腾讯云新部署的fastgpt,更特殊一点的是,NAS(群晖或者QNAP)部署了fastgpt,mongo必须改成4.2或者4.4版本(其实云端更方便,支持fastgpt mongo默认版本)
|
||||
C环境:妥善考虑,用本地电脑作为C环境过渡,保存相关文件并分离操作
|
||||
|
||||
|
||||
## 1. 环境准备:进入 docker mongo 【A环境】
|
||||
```
|
||||
docker exec -it mongo sh
|
||||
mongo -u 'username' -p 'password'
|
||||
>> show dbs
|
||||
```
|
||||
看到fastgpt数据库,以及其它几个,确定下导出数据库名称
|
||||
准备:
|
||||
检查数据库,容器和宿主机都创建一下 backup 目录 【A环境 + C环境】
|
||||
|
||||
##### 准备:
|
||||
|
||||
检查数据库,容器和宿主机都创建一下“数据导出导入”临时目录 ,比如data/backup 【A环境建目录 + C环境建目录用于同步到容器中】
|
||||
|
||||
#### 先在【A环境】创建文件目录,用于dump导出操作
|
||||
容器:(先进入fastgpt docker容器)
|
||||
```
|
||||
docker exec -it fastgpt sh
|
||||
mkdir -p /data/backup
|
||||
```
|
||||
|
||||
建好后,未来导出mongo的数据,会在A环境本地fastgpt的安装目录/Data/下看到自动同步好的目录,数据会在data\backup中,然后可以衔接后续的压缩和下载转移动作。如果没有同步到本地,也可以手动建一下,配合docker cp 把文件拷到本地用(基本不会发生)
|
||||
|
||||
#### 然后,【C环境】宿主机目录类似操作,用于把上传的文件自动同步到C环境部署的fastgpt容器里。
|
||||
到fastgpt目录,进入mongo目录,有data目录,下面建backup
|
||||
```
|
||||
mkdir -p /fastgpt/data/backup
|
||||
```
|
||||
准备好后,后续上传
|
||||
```
|
||||
### 新fastgpt环境【B】中也需要建一个,比如/fastgpt/mongobackup目录,注意不要在fastgpt/data目录下建立目录
|
||||
```
|
||||
mkdir -p /fastgpt/mongobackup
|
||||
```
|
||||
|
||||
###2. 正题开始,从fastgpt老环境【A】中导出数据
|
||||
进入A环境,使用mongodump 导出mongo数据库。
|
||||
|
||||
#### 2.1 导出
|
||||
可以使用mongodump在源头容器中导出数据文件, 导出路径为上面指定临时目录,即"data\backup"
|
||||
|
||||
[导出的文件在代码中指定为/data/backup,因为fastgpt配置文件已经建立了data的持久化,所以会同步到容器所在环境本地fast/mongo/data应该就能看到这个导出的目录:backup,里面有文件]
|
||||
|
||||
一行指令导出代码,在服务器本地环境运行,不需要进入容器。
|
||||
```
|
||||
docker exec -it mongo bash -c "mongodump --db fastgpt -u 'username' -p 'password' --authenticationDatabase admin --out /data/backup"
|
||||
```
|
||||
|
||||
也可以进入环境,熟手可以结合建目录,一次性完成建导出目录,以及使用mongodump导出数据到该目录
|
||||
```
|
||||
1.docker exec -it fastgpt sh
|
||||
|
||||
2.mkdir -p /data/backup
|
||||
|
||||
3. mongodump --host 127.0.0.1:27017 --db fastgpt -u "username" -p "password" --authenticationDatabase admin --out /data/backup
|
||||
|
||||
|
||||
##### 补充:万一没自动同步,也可以将mongodump导出的文件,手工导出到宿主机【A环境】,备用指令如下:
|
||||
```
|
||||
docker cp mongo:/data/backup <A环境本地fastgpt目录>:/fastgpt/data/backup>
|
||||
```
|
||||
|
||||
2.2 对新手,建议稳妥起见,压缩这个文件目录,并将压缩文件下载到本地过渡环境【A环境 -> C环境】;原因是因为留存一份,并且检查文件数量是否一致。
|
||||
|
||||
熟手可以直接复制到新部署服务器(腾讯云或者NAS)【A环境-> B环境】
|
||||
|
||||
|
||||
2.2.1 先进入 【A环境】源头系统的本地环境 fastgpt/mongo/data 目录
|
||||
|
||||
```
|
||||
cd /usr/fastgpt/mongo/data
|
||||
```
|
||||
|
||||
#执行,压缩文件命令
|
||||
```
|
||||
tar -czvf ../fastgpt-mongo-backup-$(date +%Y-%m-%d).tar.gz ./ 【A环境】
|
||||
```
|
||||
#接下来,把压缩包下载到本地 【A环境-> C环境】,以便于检查和留存版本。熟手,直接将该压缩包同步到B环境中新fastgpt目录data目录下备用。
|
||||
|
||||
```
|
||||
scp -i /Users/path/<user.pem换成你自己的pem文件链接> root@<fastgpt所在云服务器地址>:/usr/fastgpt/mongo/fastgptbackup-2024-05-03.tar.gz /<本地电脑路径>/Downloads/fastgpt
|
||||
|
||||
```
|
||||
熟手直接换成新环境地址
|
||||
|
||||
```
|
||||
scp -i /Users/path/<user.pem换成你自己的pem文件链接> root@<老环境fastgpt服务器地址>:/usr/fastgpt/mongo/fastgptbackup-2024-05-03.tar.gz root@<新环境fastgpt服务器地址>:/Downloads/fastgpt2
|
||||
|
||||
```
|
||||
|
||||
2.2 【C环境】检查压缩文件是否完整,如果不完整,重新导出。事实上,我也出现过问题,因为跨环境scp会出现丢数据的情况。
|
||||
|
||||
压缩数据包导入到C环境本地后,可以考虑在宿主机目录解压缩,放在一个自定义目录比如. < user/fastgpt/mongobackup/data>
|
||||
|
||||
```
|
||||
tar -xvzf fastgptbackup-2024-05-03.tar.gz -C user/fastgpt/mongobackup/data
|
||||
```
|
||||
解压缩后里面是bson文件,这里可以检查下,压缩文件数量是否一致。如果不一致,后续启动新环境的fastgpt容器,也不会有任何数据。
|
||||
|
||||
<img width="1561" alt="image" src="https://github.com/labring/FastGPT/assets/103937568/cbb8a93c-5834-4a0d-be6c-c45c701f593e">
|
||||
|
||||
|
||||
如果没问题,准备进入下一步,将压缩包文件上传到B环境,也就是新fastgpt环境里的指定目录,比如/fastgpt/mongobackup, 注意不要放到fastgpt/data目录下,因为下面会先清空一次这个目录,否则导入会报错。
|
||||
```
|
||||
scp -rfv <本地电脑路径>/Downloads/fastgpt/fastgptbackup-2024-05-03.tar.gz root@<新环境fastgpt服务器地址>:/Downloads/fastgpt/backup
|
||||
```
|
||||
|
||||
## 3 导入恢复: 实际恢复和导入步骤
|
||||
|
||||
### 3.1. 进入新fastgpt本地环境的安装目录后,找到迁移的压缩文件包fastgptbackup-2024-05-03.tar.gz,解压缩到指定目录
|
||||
|
||||
```
|
||||
tar -xvzf fastgptbackup-2024-05-03.tar.gz -C user/fastgpt/mongobackup/data
|
||||
```
|
||||
再次核对文件数量,和上面对比一下。
|
||||
|
||||
熟手可以用tar指令检查文件完整性,上面是给新手准备的,便于比对核查。
|
||||
|
||||
|
||||
### 3.2 手动上传新fastgpt docker容器里备用 【C环境】
|
||||
说明:因为没有放在data里,所以不会自动同步到容器里。而且要确保容器的data目录被清理干净,否则导入时会报错。
|
||||
|
||||
```
|
||||
docker cp user/fastgpt/mongobackup/data mongo:/tmp/backup
|
||||
```
|
||||
|
||||
### 3.3 建议初始化一次docker compose ,运行后建立新的 mongo/data 持久化目录
|
||||
如果不是初始化的 mongo/db 目录, mongorestore 导入可能会报错。如果报错,建议尝试初始化mongo。
|
||||
|
||||
操作指令
|
||||
```
|
||||
cd /fastgpt安装目录/mongo/data
|
||||
rm -rf *
|
||||
```
|
||||
|
||||
|
||||
4.恢复: mongorestore 恢复 [C环境】
|
||||
简单一点,退回到本地环境,用 docker 命令一键导入,当然你也可以在容器里操作
|
||||
|
||||
```
|
||||
docker exec -it mongo mongorestore -u "username" -p "password" --authenticationDatabase admin /tmp/backup/ --db fastgpt
|
||||
```
|
||||
<img width="1668" alt="image" src="https://github.com/labring/FastGPT/assets/103937568/32c2cdb8-bf80-4d31-9269-4bf3909cf04e">
|
||||
注意:导入文件数量量级太少,大概率是没导入成功的表现。如果导入不成功,新环境fastgpt可以登入,但是一片空白。
|
||||
|
||||
|
||||
5.重启容器 【C环境】
|
||||
```
|
||||
docker compose restart
|
||||
docker logs -f mongo **强烈建议先检查mongo运行情况,在去做登陆动作,如果mongo报错,访问web也会报错”
|
||||
```
|
||||
|
||||
如果mongo启动正常,显示的是类似这样的,而不是 “mongo is restarting”,后者就是错误
|
||||
<img width="1736" alt="iShot_2024-05-09_19 21 26" src="https://github.com/labring/FastGPT/assets/103937568/94ee00db-43de-48bd-a1fc-22dfe86aaa90">
|
||||
|
||||
报错情况
|
||||
<img width="508" alt="iShot_2024-05-09_19 23 13" src="https://github.com/labring/FastGPT/assets/103937568/2e2afc9f-484c-4b63-93ee-1c14aef03de0">
|
||||
|
||||
|
||||
6. 启动fastgpt容器服务后,登陆新fastgpt web,能看到原来的数据库内容完整显示,说明已经导入系统了。
|
||||
<img width="1728" alt="iShot_2024-05-09_19 23 51" src="https://github.com/labring/FastGPT/assets/103937568/846b6157-6b6a-4468-a1d9-c44d681ebf7c">
|
||||
9
docSite/content/docs/development/migration/_index.md
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
weight: 960
|
||||
title: "迁移&备份"
|
||||
description: "FastGPT 迁移&备份"
|
||||
icon: settings_backup_restore
|
||||
draft: false
|
||||
images: []
|
||||
---
|
||||
<!-- 960~970 -->
|
||||
15
docSite/content/docs/development/migration/docker_db.md
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
weight: 762
|
||||
title: "Docker 数据库迁移(无脑操作)"
|
||||
description: "FastGPT Docker 数据库备份和迁移"
|
||||
icon: database
|
||||
draft: false
|
||||
images: []
|
||||
---
|
||||
|
||||
## Copy文件
|
||||
|
||||
Docker 部署数据库都会通过 volume 挂载本地的目录进入容器,如果要迁移,直接复制这些目录即可。
|
||||
|
||||
`PG 数据`: pg/data
|
||||
`Mongo 数据`: mongo/data
|
||||
@@ -106,6 +106,7 @@ FastGPT 商业版共包含了2个应用(fastgpt, fastgpt-plus)和2个数据
|
||||
|
||||
```
|
||||
SYSTEM_NAME=FastGPT
|
||||
SYSTEM_DESCRIPTION=
|
||||
SYSTEM_FAVICON=/favicon.ico
|
||||
HOME_URL=/app/list
|
||||
```
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.8(进行中)'
|
||||
title: 'V4.8(开发中)'
|
||||
description: 'FastGPT V4.8 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
@@ -11,15 +11,45 @@ weight: 824
|
||||
|
||||
FastGPT workflow V2上线,支持更加简洁的工作流模式。
|
||||
|
||||
**由于工作流差异较大,需要手动重新构建。**
|
||||
|
||||
{{% alert icon="🤖 " context="success" %}}
|
||||
**由于工作流差异较大,不少地方需要手动重新构建。请依次重建插件和应用**
|
||||
|
||||
简易尽快更新工作流,避免未来持续迭代后导致无法兼容。
|
||||
{{% /alert %}}
|
||||
|
||||
|
||||
给应用和插件增加了 version 的字段,用于标识是旧工作流还是新工作流。当你更新 4.8 后,保存和新建的工作流均为新版,旧版工作流会有一个重置的弹窗提示。并且,如果是通过 API 和 分享链接 调用的工作流,仍可以正常使用,直到你下次保存它们。
|
||||
|
||||
## 商业版配置更新
|
||||
|
||||
商业版用户如果配置了邮件验证码,需要在管理端 -> 项目配置 -> 登录配置 -> 邮箱登录配置 -> 修改 **邮箱服务SMTP地址**,之前只能配置别名,现在可以配置自定义的地址。下面是一组别名和实际地址关系:
|
||||
|
||||
qq: smtp.qq.com
|
||||
gmail: smtp.gmail.com
|
||||
|
||||
## V4.8 更新说明
|
||||
|
||||
1. 重构 - 工作流
|
||||
2. 新增 - 工作流 Debug 模式,可以调试单个节点或者逐步调试工作流。
|
||||
3. 新增 - 定时执行应用。可轻松实现定时任务。
|
||||
4. 新增 - 插件自定义输入优化,可以渲染输入组件。
|
||||
5. 优化 - 工作流连线,可以四向连接,方便构建循环工作流。
|
||||
6. 优化 - worker进程管理,并将计算 Token 任务分配给 worker 进程。
|
||||
2. 新增 - 判断器。支持 if elseIf else 判断。 @newfish-cmyk (preview版本的if else节点需要删除重建)
|
||||
3. 新增 - 变量更新节点。支持更新运行中工作流输出变量,或更新全局变量。@newfish-cmyk
|
||||
4. 新增 - 工作流自动保存和版本管理。
|
||||
5. 新增 - 工作流 Debug 模式,可以调试单个节点或者逐步调试工作流。
|
||||
6. 新增 - 定时执行应用。可轻松实现定时任务。
|
||||
7. 新增 - 插件自定义输入优化,可以渲染输入组件。
|
||||
8. 新增 - 分享链接发送对话前 hook https://github.com/labring/FastGPT/pull/1252 @gaord
|
||||
9. 优化 - 工作流连线,可以四向连接,方便构建循环工作流。
|
||||
10. 优化 - 工作流上下文传递,性能🚀。
|
||||
11. 优化 - ctrl和alt+enter换行,换行符位置不正确。
|
||||
12. 优化 - chat中存储变量配置。避免修改变量后,影响旧的对话。
|
||||
13. 优化 - 简易模式,更新配置后自动更新调试框内容,无需保存。
|
||||
14. 优化 - worker进程管理,并将计算 Token 任务分配给 worker 进程。
|
||||
15. 优化 - 工具调用支持指定字段数据类型(string, boolean, number) https://github.com/labring/FastGPT/issues/1236
|
||||
16. 优化 - completions接口size限制 https://github.com/labring/FastGPT/issues/1241
|
||||
17. 优化 - Node api 中间件。优化 api 端代码。@c121914yu
|
||||
18. 优化 - 对话记录保持为偶数进行截取,避免部分模型不支持奇数的历史记录,最大长度增加到50轮。 https://github.com/labring/FastGPT/issues/1384
|
||||
19. 优化 - HTTP节点错误后终止进程 https://github.com/labring/FastGPT/issues/1290
|
||||
20. 修复 - 工具调用时候,name不能是数字开头(随机数有概率数字开头)@c121914yu
|
||||
21. 修复 - 分享链接, query 全局变量会被缓存。 @c121914yu
|
||||
22. 修复 - 工具调用字段兼容。 https://github.com/labring/FastGPT/issues/1253
|
||||
23. 修复 - HTTP 模块url光标问题 https://github.com/labring/FastGPT/issues/1334 @maquannene
|
||||
@@ -17,16 +17,14 @@ FastGPT 从 V4 版本开始采用新的交互方式来构建 AI 应用。使用
|
||||
|
||||
在程序中,节点可以理解为一个个 Function 或者接口。可以理解为它就是一个**步骤**。将多个节点一个个拼接起来,即可一步步的去实现最终的 AI 输出。
|
||||
|
||||
如下图,这是一个最简单的 AI 对话。它由用户输入的问题、聊天记录以及 AI 对话节点组成。
|
||||
如下图,这是一个最简单的 AI 对话。它由用流程开始和 AI 对话节点组成。
|
||||
|
||||

|
||||
|
||||
执行流程如下:
|
||||
|
||||
1. 用户输入问题后,会向服务器发送一个请求,并携带问题。从而得到【用户问题】节点的输出。
|
||||
2. 根据设置的【最长记录数】来获取数据库中的记录数,从而得到【聊天记录】节点的输出。
|
||||
经过上面两个流程,就得到了左侧两个蓝色点的结果。结果会被注入到右侧的【AI】对话节点。
|
||||
3. 【AI 对话】节点根据传入的聊天记录和用户问题,调用对话接口,从而实现回答。(这里的对话结果输出隐藏了起来,默认只要触发了对话节点,就会往客户端输出内容)
|
||||
1. 用户输入问题后,【流程开始】节点执行,用户问题被保存。
|
||||
2. 【AI 对话】节点执行,此节点有两个必填参数“聊天记录” “用户问题”,聊天记录的值是默认输入的6条,表示此模块上下文长度。用户问题选择的是【流程开始】模块中保存的用户问题。
|
||||
3. 【AI 对话】节点根据传入的聊天记录和用户问题,调用对话接口,从而实现回答。
|
||||
|
||||
### 节点分类
|
||||
|
||||
@@ -37,52 +35,46 @@ FastGPT 从 V4 版本开始采用新的交互方式来构建 AI 应用。使用
|
||||
|
||||
### 节点的组成
|
||||
|
||||
每个节点会包含 3 个核心部分:固定参数、外部输入(左边有个圆圈)和输出(右边有个圆圈)。
|
||||
每个节点会包含 3 个核心部分:输入、输出和触发器。
|
||||
|
||||

|
||||
|
||||
- 对话模型、温度、回复上限、系统提示词和限定词为固定参数,同时系统提示词和限定词也可以作为外部输入,意味着如果你有输入流向了系统提示词,那么原本填写的内容就会被**覆盖**。
|
||||
- 触发器、引用内容、聊天记录和用户问题则为外部输入,需要从其他节点的输出流入。
|
||||
- 回复结束则为该节点的输出。
|
||||
- AI模型、提示词、聊天记录、用户问题,知识库引用为输入,节点的输入可以是手动输入也可以是变量引用,变量引用的范围包括“全局变量”和之前任意一个节点的输出。
|
||||
- 新的上下文和AI回复内容为输出,输出可以被之后任意节点变量引用。
|
||||
- 节点的上下左右有四个“触发器”可以被用来连接,被连接的节点按顺序决定是否执行。
|
||||
|
||||
## 重点 - 工作流是如何运行的
|
||||
|
||||
与单出入口的工作流不同,FastGPT的工作流可以指定**不同的入口**,并且没有**固定的出口**,而是以节点运行结束作为出口,如果在一个轮调用中,所有节点都不再允许,则工作流结束。
|
||||
FastGPT的工作流从【流程开始】节点开始执行,可以理解为从用户输入问题开始,没有**固定的出口**,是以节点运行结束作为出口,如果在一个轮调用中,所有节点都不再允许,则工作流结束。
|
||||
|
||||
不过为了方便阅读,大部分时候,我们仍是设置一个模块作为入口,在工作流中,它被叫做`对话入口`。下面我们来看下,工作流是如何运行的,以及每个节点何时被触发执行。
|
||||
下面我们来看下,工作流是如何运行的,以及每个节点何时被触发执行。
|
||||
|
||||
记住3个**节点可执行**的原则:
|
||||

|
||||
|
||||
1. 仅关心**已连接的**外部输入,即左边的圆圈被连接了参数。
|
||||
2. 当**已连接的**内容都被赋值的时候触发。(这个地方经常会遇到,连接了很多根输入线,但是只要有一个输入没有值,这个节点也不会执行)
|
||||
3. 可以多个输出连接到一个输入,后续的值会覆盖前面的值。
|
||||
如上图所示节点会“被连接”也会“连接其他节点”,我们称“被连接”的那根线为前置线,“连接其他节点的线”为后置线。上图例子中【知识库搜索】模块左侧有一根前置线,右侧有一根后置线。而【AI对话】节点只有左侧一根前置线。
|
||||
|
||||

|
||||
FastGPT工作流中的线有以下几种状态:
|
||||
- `waiting`:被连接的节点等待执行。
|
||||
- `active`:被连接的节点可以执行。
|
||||
- `skip`:被连接的节点不需要执行跳过。
|
||||
|
||||
### 示例 1:
|
||||
节点执行的原则:
|
||||
|
||||
聊天记录节点会自动执行,因此聊天记录输入会自动赋值。当用户发送问题时,【用户问题】节点会输出值,此时【AI 对话】节点的用户问题输入也会被赋值。两个连接的输入都被赋值后,会执行 【AI 对话】节点。
|
||||
1. 判断前置线中有没有状态为 `waiting` 的,如果有则等待。
|
||||
2. 判断前置线中状态有没有状态为 `active` 如果有则执行。
|
||||
3. 如果前置线中状态即没有 `waiting` 也没有 `active` 则认为此节点需要跳过。
|
||||
4. 节点执行完毕后,需要根据实际情况更改后置线的状态为`active`或`skip`并且更改前置线状态为`waiting`等待下一轮执行。
|
||||
|
||||

|
||||
|
||||
### 例子 2:
|
||||
|
||||
下图是一个知识库搜索例子。
|
||||
|
||||
1. 历史记录会流入【AI 对话】节点。
|
||||
2. 用户的问题会流入【知识库搜索】和【AI 对话】节点,由于【AI 对话】节点的触发器和引用内容还是空,此时不会执行。
|
||||
3. 【知识库搜索】节点仅一个外部输入,并且被赋值,开始执行。
|
||||
4. 【知识库搜索】结果为空时,“搜索结果不为空”的值为空,不会输出,因此【AI 对话】节点会因为触发器没有赋值而无法执行。而“搜索结果为空”会有输出,流向指定回复的触发器,因此【指定回复】节点进行输出。
|
||||
5. 【知识库搜索】结果不为空时,“搜索结果不为空”和“引用内容”都有输出,会流向【AI 对话】,此时【AI 对话】的 4 个外部输入都被赋值,开始执行。
|
||||
|
||||

|
||||
让我们看一下上面例子的执行过程:
|
||||
1. 【流程开始】节点执行完毕,更改后置线为`active`。
|
||||
2. 【知识库搜索】节点判断前置线状态为`active`开始执行,执行完毕后更改后置线状态为`active` 前置线状态为`waiting`。
|
||||
3. 【AI对话】节点判断前置线状态为`active`开始执行,流程执行结束。
|
||||
|
||||
## 如何连接节点
|
||||
|
||||
1. 为了方便识别不同输入输出的类型,FastGPT 给每个节点的输入输出连接点赋予不同的颜色,你可以把相同颜色的连接点连接起来。其中,灰色代表任意类型,可以随意连接。
|
||||
2. 位于左侧的连接点为输入,右侧的为输出,连接只能将一个输入和输出连接起来,不能连接“输入和输入”或者“输出和输出”。
|
||||
3. 可以点击连接线中间的 x 来删除连接线。
|
||||
4. 可以左键点击选中连接线
|
||||
1. 为了方便连接,FastGPT 每个节点的上下左右都有连接点,左和上是前置线连接点,右和下是后置线连接点。
|
||||
2. 可以点击连接线中间的 x 来删除连接线。
|
||||
3. 可以左键点击选中连接线
|
||||
|
||||
## 如何阅读?
|
||||
|
||||
@@ -98,7 +90,4 @@ FastGPT 从 V4 版本开始采用新的交互方式来构建 AI 应用。使用
|
||||
2. 知识库搜索合并,可以合并多个知识库搜索结果
|
||||
3. 其他结果,无法直接合并,可以考虑传入到`HTTP`节点中进行合并,使用`[Laf](https://laf.run/)`可以快速实现一个无服务器HTTP接口。
|
||||
|
||||
### 节点为什么有2个用户问题
|
||||
|
||||
左侧的`用户问题`是指该节点所需的输入。右侧的`用户问题`是为了方便后续的连线,输出的值和传入的用户问题一样。
|
||||
|
||||
|
||||
@@ -10,8 +10,6 @@ weight: 351
|
||||
## 特点
|
||||
|
||||
- 可重复添加
|
||||
- 有外部输入
|
||||
- 有静态配置
|
||||
- 触发执行
|
||||
- 核心模块
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ weight: 352
|
||||
## 特点
|
||||
|
||||
- 可重复添加
|
||||
- 有外部输入
|
||||
- 需要手动配置
|
||||
- 触发执行
|
||||
- function_call 模块
|
||||
@@ -54,7 +53,5 @@ weight: 352
|
||||
|
||||
## 输出介绍
|
||||
|
||||
- **字段完全提取**:说明用户的问题中包含需要提取的所有内容。
|
||||
- **提取字段缺失**:与 “字段完全提取” 对立,有缺失提取的字段时触发。
|
||||
- **完整提取结果**: 一个 JSON 字符串,包含所有字段的提取结果。
|
||||
- **目标字段提取结果**:类型均为字符串。
|
||||
@@ -29,16 +29,6 @@ weight: 357
|
||||
|
||||
[点击查看参数介绍](/docs/course/data_search/#搜索参数)
|
||||
|
||||
### 输出 - 搜索结果
|
||||
|
||||
输出部分给了两个 boolean 类型的搜索结果,以便根据搜索结果进行不同的处理,通常会有下方两个处理方式:
|
||||
|
||||
| 直接回复特定内容 | 对接普通的 gpt |
|
||||
| ----------------------------- | ----------------------------- |
|
||||
|  |  |
|
||||
|
||||
当然,你也可以连接到 HTTP 模块,从而实现无法从知识搜索到内容时,去进行联网搜索或者维基百科搜索。
|
||||
|
||||
### 输出 - 引用内容
|
||||
|
||||
以数组格式输出引用,长度可以为 0。意味着,即使没有搜索到内容,这个输出链路也会走通。
|
||||
|
||||
@@ -10,7 +10,6 @@ weight: 355
|
||||
## 特点
|
||||
|
||||
- 可重复添加
|
||||
- 有外部输入
|
||||
- 手动配置
|
||||
- 触发执行
|
||||
- 核中核模块
|
||||
@@ -23,10 +22,11 @@ HTTP 模块会向对应的地址发送一个 `HTTP` 请求,实际操作与 Pos
|
||||
|
||||
- Params 为路径请求参数,GET请求中用的居多。
|
||||
- Body 为请求体,POST/PUT请求中用的居多。
|
||||
- Headers 为请求头,用于传递一些特殊的信息。
|
||||
- Headers 为请求头,用于传递一些特殊的信息。
|
||||
- 自定义变量中可以接收前方节点的输出作为变量
|
||||
- 3 种数据中均可以通过 `{{}}` 来引用变量。
|
||||
- url 也可以通过 `{{}}` 来引用变量。
|
||||
- 变量来自于`全局变量`、`系统变量`、`局部传入`
|
||||
- 变量来自于`全局变量`、`系统变量`、`前方节点输出`
|
||||
|
||||
## 参数结构
|
||||
|
||||
|
||||
@@ -9,9 +9,8 @@ weight: 356
|
||||
|
||||
## 特点
|
||||
|
||||
- 可重复添加(防止复杂编排时线太乱,重复添加可以更美观)
|
||||
- 无外部输入
|
||||
- 流程入口
|
||||
- 无输入
|
||||
- 自动执行
|
||||
|
||||

|
||||
@@ -14,19 +14,13 @@ weight: 359
|
||||
- 可外部输入
|
||||
- 会输出结果给客户端
|
||||
|
||||
制定回复模块通常用户特殊状态回复,当然你也可以像图 2 一样,实现一些比较骚的操作~ 触发逻辑非常简单:
|
||||
指定回复模块通常用户特殊状态回复,回复内容有两种:
|
||||
|
||||
1. 一种是写好回复内容,通过触发器触发。
|
||||
2. 一种是不写回复内容,直接由外部输入触发,并回复输入的内容。
|
||||
1. 一种是手动输入固定内容。
|
||||
2. 一种是通过变量引用。
|
||||
|
||||
{{< figure
|
||||
src="/imgs/specialreply.png"
|
||||
alt=""
|
||||
caption="图 1"
|
||||
>}}
|
||||
|
||||
{{< figure
|
||||
src="/imgs/specialreply2.png"
|
||||
alt=""
|
||||
caption="图 2"
|
||||
>}}
|
||||
@@ -20,7 +20,7 @@ weight: 363
|
||||
## 功能
|
||||
对输入文本进行固定加工处理,入参仅支持字符串和数字格式,入参以变量形式使用在文本编辑区域。
|
||||
|
||||
根据上方示例图的处理方式,对任何输入都会在前面拼接“我的问题是:”。
|
||||
根据上方示例图的处理方式,对任何输入都会在前面拼接“用户的问题是:”。
|
||||
|
||||
|
||||
## 作用
|
||||
|
||||
@@ -17,44 +17,13 @@ weight: 362
|
||||
|
||||
## 功能
|
||||
|
||||
对任意输入内容进行 True False 输出,默认情况下,当传入的内容为 false, undefined, null,0,none 时,会输出 false。
|
||||
对任意变量进行`IF`判断,若满足条件则执行`IF`分支,不满足条件执行`ELSE`分支。
|
||||
|
||||
也可以增加自定义规则来补充输出 false 的内容,每行代表一个匹配规则,支持正则表达式。
|
||||
|
||||
**例子1**
|
||||
上述例子中若「知识库引用」变量的长度等于0则执行`IF`分支,否则执行`ELSE`分支。
|
||||
|
||||
不填写任何自定义 False 规则。
|
||||
|
||||
| 输入 | 输出 |
|
||||
| --- | --- |
|
||||
| 123 | true |
|
||||
| 这是一段文本 | true |
|
||||
| false | false |
|
||||
| 0 | false |
|
||||
| null | false |
|
||||
|
||||
**例子2**
|
||||
|
||||
自定义 False 规则:
|
||||
|
||||
```
|
||||
123
|
||||
你好
|
||||
aa
|
||||
/dd/
|
||||
```
|
||||
|
||||
| 输入 | 输出 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 123 | false | 命中自定义 false 规则 |
|
||||
| 这是一段文本 | true | 未命中 |
|
||||
| false | false | 命中自定义 内置 规则 |
|
||||
| 0 | false | 命中自定义 内置 规则 |
|
||||
| null | false | 命中自定义 内置 规则 |
|
||||
| aa | false | 命中自定义 false 规则 |
|
||||
| aaa | true | 未命中 |
|
||||
| bb | false | 命中自定义 false 规则 |
|
||||
| bbb | false | 命中自定义 false 规则(正则匹配通过) |
|
||||
支持增加更多的判断条件和分支,同编程语言中的`IF`语句逻辑相同。
|
||||
|
||||
## 作用
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ weight: 356
|
||||
| --- | --- |
|
||||
|  |  |
|
||||
|
||||
高级编排中,一旦有了工具调用模块,可用的工具头部会出现一个菱形,可以将它与工具调用模块底部的菱形相连接。
|
||||
高级编排中,托动工具调用的连接点,可用的工具头部会出现一个菱形,可以将它与工具调用模块底部的菱形相连接。
|
||||
|
||||
被连接的工具,会自动分离工具输入与普通的输入,并且可以编辑`介绍`,可以通过调整介绍,使得该工具调用时机更加精确。
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
title: "触发器"
|
||||
description: "FastGPT 触发器模块介绍"
|
||||
icon: "work_history"
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 360
|
||||
---
|
||||
|
||||
细心的同学可以发现,在每个功能模块里都会有一个叫【触发器】的外部输入,并且是 any 类型。
|
||||
|
||||
它的**核心作用**就是控制模块的执行时机,以下图两个知识库搜索中的【AI 对话】模块为例子:
|
||||
|
||||
| 图 1 | 图 2 |
|
||||
| ---------------------------- | ---------------------------- |
|
||||
|  |  |
|
||||
|
||||
【知识库搜索】模块中,由于**引用内容**始终会有输出,会导致【AI 对话】模块的**引用内容**输入无论有没有搜到内容都会被赋值。如果此时不连接触发器(图 2),在搜索结束后必定会执行【AI 对话】模块。
|
||||
|
||||
有时候,你可能希望空搜索时候进行额外处理,例如:回复固定内容、调用其他提示词的 GPT、发送一个 HTTP 请求…… 此时就需要用到触发器,需要将 **搜索结果不为空** 和 **触发器** 连接起来。
|
||||
|
||||
当搜索结果为空时,【知识库搜索】模块不会输出 **搜索结果不为空** 的结果,因此 【AI 对话】 模块的触发器始终为空,便不会执行。
|
||||
|
||||
总之,记住模块执行的逻辑就可以灵活的使用触发器:**外部输入字段(有连接的才有效)全部被赋值时才会被执行**。
|
||||
@@ -1,12 +1,13 @@
|
||||
# 数据库的默认账号和密码仅首次运行时设置有效
|
||||
# 如果修改了账号密码,记得改数据库和项目连接参数,别只改一处~
|
||||
# 该配置文件只是给快速启动,测试使用。正式使用,记得务必修改账号密码,以及调整合适的知识库参数,共享内存等。
|
||||
# 如何无法访问 dockerhub 和 git,可以用阿里云(阿里云没有arm包)
|
||||
|
||||
version: '3.3'
|
||||
services:
|
||||
pg:
|
||||
image: ankane/pgvector:v0.5.0 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/pgvector:v0.5.0 # 阿里云
|
||||
image: pgvector/pgvector:0.7.0-pg15 # docker hub
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/pgvector:v0.7.0 # 阿里云
|
||||
container_name: pg
|
||||
restart: always
|
||||
ports: # 生产环境建议不要暴露
|
||||
@@ -21,7 +22,9 @@ services:
|
||||
volumes:
|
||||
- ./pg/data:/var/lib/postgresql/data
|
||||
mongo:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/mongo:5.0.18
|
||||
image: mongo:5.0.18 # dockerhub
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/mongo:5.0.18 # 阿里云
|
||||
# image: mongo:4.4.29 # cpu不支持AVX时候使用
|
||||
container_name: mongo
|
||||
restart: always
|
||||
ports:
|
||||
@@ -66,8 +69,8 @@ services:
|
||||
wait $$!
|
||||
fastgpt:
|
||||
container_name: fastgpt
|
||||
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.7 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.7 # 阿里云
|
||||
image: ghcr.io/labring/fastgpt:v4.8 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8 # 阿里云
|
||||
ports:
|
||||
- 3000:3000
|
||||
networks:
|
||||
|
||||
@@ -99,13 +99,41 @@ data:
|
||||
}
|
||||
],
|
||||
"vectorModels": [
|
||||
{
|
||||
"model": "text-embedding-3-large",
|
||||
"name": "Embedding-2",
|
||||
"avatar": "/imgs/model/openai.svg",
|
||||
"charsPointsPrice": 0,
|
||||
"defaultToken": 512,
|
||||
"maxToken": 3000,
|
||||
"weight": 100,
|
||||
"dbConfig": {},
|
||||
"queryConfig": {},
|
||||
"defaultConfig": {
|
||||
"dimensions": 1024
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "text-embedding-3-small",
|
||||
"name": "Embedding-2",
|
||||
"avatar": "/imgs/model/openai.svg",
|
||||
"charsPointsPrice": 0,
|
||||
"defaultToken": 512,
|
||||
"maxToken": 3000,
|
||||
"weight": 100,
|
||||
"dbConfig": {},
|
||||
"queryConfig": {}
|
||||
},
|
||||
{
|
||||
"model": "text-embedding-ada-002",
|
||||
"name": "Embedding-2",
|
||||
"avatar": "/imgs/model/openai.svg",
|
||||
"charsPointsPrice": 0,
|
||||
"defaultToken": 700,
|
||||
"defaultToken": 512,
|
||||
"maxToken": 3000,
|
||||
"weight": 100
|
||||
"weight": 100,
|
||||
"dbConfig": {},
|
||||
"queryConfig": {}
|
||||
}
|
||||
],
|
||||
"reRankModels": [],
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ErrType } from '../errorCode';
|
||||
|
||||
/* dataset: 502000 */
|
||||
export enum AppErrEnum {
|
||||
unExist = 'unExist',
|
||||
unExist = 'appUnExist',
|
||||
unAuthApp = 'unAuthApp'
|
||||
}
|
||||
const appErrList = [
|
||||
|
||||
@@ -2,8 +2,8 @@ import { ErrType } from '../errorCode';
|
||||
|
||||
/* dataset: 506000 */
|
||||
export enum OpenApiErrEnum {
|
||||
unExist = 'unExist',
|
||||
unAuth = 'unAuth'
|
||||
unExist = 'openapiUnExist',
|
||||
unAuth = 'openapiUnAuth'
|
||||
}
|
||||
const errList = [
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ErrType } from '../errorCode';
|
||||
|
||||
/* dataset: 505000 */
|
||||
export enum OutLinkErrEnum {
|
||||
unExist = 'unExist',
|
||||
unExist = 'outlinkUnExist',
|
||||
unAuthLink = 'unAuthLink',
|
||||
linkUnInvalid = 'linkUnInvalid',
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import { ErrType } from '../errorCode';
|
||||
|
||||
/* dataset: 507000 */
|
||||
export enum PluginErrEnum {
|
||||
unExist = 'unExist',
|
||||
unAuth = 'unAuth'
|
||||
unExist = 'pluginUnExist',
|
||||
unAuth = 'pluginUnAuth'
|
||||
}
|
||||
const errList = [
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ export enum BucketNameEnum {
|
||||
}
|
||||
export const bucketNameMap = {
|
||||
[BucketNameEnum.dataset]: {
|
||||
label: 'common.file.bucket.dataset'
|
||||
label: 'file.bucket.dataset'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -13,36 +13,36 @@ export enum MongoImageTypeEnum {
|
||||
}
|
||||
export const mongoImageTypeMap = {
|
||||
[MongoImageTypeEnum.systemAvatar]: {
|
||||
label: 'common.file.type.appAvatar',
|
||||
label: 'appAvatar',
|
||||
unique: true
|
||||
},
|
||||
[MongoImageTypeEnum.appAvatar]: {
|
||||
label: 'common.file.type.appAvatar',
|
||||
label: 'appAvatar',
|
||||
unique: true
|
||||
},
|
||||
[MongoImageTypeEnum.pluginAvatar]: {
|
||||
label: 'common.file.type.pluginAvatar',
|
||||
label: 'pluginAvatar',
|
||||
unique: true
|
||||
},
|
||||
[MongoImageTypeEnum.datasetAvatar]: {
|
||||
label: 'common.file.type.datasetAvatar',
|
||||
label: 'datasetAvatar',
|
||||
unique: true
|
||||
},
|
||||
[MongoImageTypeEnum.userAvatar]: {
|
||||
label: 'common.file.type.userAvatar',
|
||||
label: 'userAvatar',
|
||||
unique: true
|
||||
},
|
||||
[MongoImageTypeEnum.teamAvatar]: {
|
||||
label: 'common.file.type.teamAvatar',
|
||||
label: 'teamAvatar',
|
||||
unique: true
|
||||
},
|
||||
|
||||
[MongoImageTypeEnum.chatImage]: {
|
||||
label: 'common.file.type.chatImage',
|
||||
label: 'chatImage',
|
||||
unique: false
|
||||
},
|
||||
[MongoImageTypeEnum.collectionImage]: {
|
||||
label: 'common.file.type.collectionImage',
|
||||
label: 'collectionImage',
|
||||
unique: false
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,24 +1,91 @@
|
||||
import { getErrText } from '../error/utils';
|
||||
import { replaceRegChars } from './tools';
|
||||
|
||||
/**
|
||||
* text split into chunks
|
||||
* chunkLen - one chunk len. max: 3500
|
||||
* overlapLen - The size of the before and after Text
|
||||
* chunkLen > overlapLen
|
||||
* markdown
|
||||
*/
|
||||
export const splitText2Chunks = (props: {
|
||||
export const CUSTOM_SPLIT_SIGN = '-----CUSTOM_SPLIT_SIGN-----';
|
||||
|
||||
type SplitProps = {
|
||||
text: string;
|
||||
chunkLen: number;
|
||||
overlapRatio?: number;
|
||||
customReg?: string[];
|
||||
}): {
|
||||
};
|
||||
|
||||
type SplitResponse = {
|
||||
chunks: string[];
|
||||
chars: number;
|
||||
overlapRatio?: number;
|
||||
} => {
|
||||
};
|
||||
|
||||
// 判断字符串是否为markdown的表格形式
|
||||
const strIsMdTable = (str: string) => {
|
||||
// 检查是否包含表格分隔符 |
|
||||
if (!str.includes('|')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const lines = str.split('\n');
|
||||
|
||||
// 检查表格是否至少有两行
|
||||
if (lines.length < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查表头行是否包含 |
|
||||
const headerLine = lines[0].trim();
|
||||
if (!headerLine.startsWith('|') || !headerLine.endsWith('|')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查分隔行是否由 | 和 - 组成
|
||||
const separatorLine = lines[1].trim();
|
||||
const separatorRegex = /^(\|[\s:]*-+[\s:]*)+\|$/;
|
||||
if (!separatorRegex.test(separatorLine)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查数据行是否包含 |
|
||||
for (let i = 2; i < lines.length; i++) {
|
||||
const dataLine = lines[i].trim();
|
||||
if (dataLine && (!dataLine.startsWith('|') || !dataLine.endsWith('|'))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const markdownTableSplit = (props: SplitProps): SplitResponse => {
|
||||
let { text = '', chunkLen } = props;
|
||||
const splitText2Lines = text.split('\n');
|
||||
const header = splitText2Lines[0];
|
||||
const headerSize = header.split('|').length - 2;
|
||||
|
||||
const mdSplitString = `| ${new Array(headerSize > 0 ? headerSize : 1)
|
||||
.fill(0)
|
||||
.map(() => '---')
|
||||
.join(' | ')} |`;
|
||||
|
||||
const chunks: string[] = [];
|
||||
let chunk = `${header}
|
||||
${mdSplitString}
|
||||
`;
|
||||
|
||||
for (let i = 2; i < splitText2Lines.length; i++) {
|
||||
if (chunk.length + splitText2Lines[i].length > chunkLen * 1.2) {
|
||||
chunks.push(chunk);
|
||||
chunk = `${header}
|
||||
${mdSplitString}
|
||||
`;
|
||||
}
|
||||
chunk += `${splitText2Lines[i]}\n`;
|
||||
}
|
||||
|
||||
return {
|
||||
chunks,
|
||||
chars: chunks.reduce((sum, chunk) => sum + chunk.length, 0)
|
||||
};
|
||||
};
|
||||
|
||||
const commonSplit = (props: SplitProps): SplitResponse => {
|
||||
let { text = '', chunkLen, overlapRatio = 0.2, customReg = [] } = props;
|
||||
|
||||
const splitMarker = 'SPLIT_HERE_SPLIT_HERE';
|
||||
const codeBlockMarker = 'CODE_BLOCK_LINE_MARKER';
|
||||
const overlapLen = Math.round(chunkLen * overlapRatio);
|
||||
@@ -253,3 +320,29 @@ export const splitText2Chunks = (props: {
|
||||
throw new Error(getErrText(err));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* text split into chunks
|
||||
* chunkLen - one chunk len. max: 3500
|
||||
* overlapLen - The size of the before and after Text
|
||||
* chunkLen > overlapLen
|
||||
* markdown
|
||||
*/
|
||||
export const splitText2Chunks = (props: SplitProps): SplitResponse => {
|
||||
let { text = '' } = props;
|
||||
|
||||
const splitWithCustomSign = text.split(CUSTOM_SPLIT_SIGN);
|
||||
|
||||
const splitResult = splitWithCustomSign.map((item) => {
|
||||
if (strIsMdTable(item)) {
|
||||
return markdownTableSplit(props);
|
||||
}
|
||||
|
||||
return commonSplit(props);
|
||||
});
|
||||
|
||||
return {
|
||||
chunks: splitResult.map((item) => item.chunks).flat(),
|
||||
chars: splitResult.reduce((sum, item) => sum + item.chars, 0)
|
||||
};
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ import cronParser from 'cron-parser';
|
||||
export const formatTime2YMDHM = (time?: Date) =>
|
||||
time ? dayjs(time).format('YYYY-MM-DD HH:mm') : '';
|
||||
export const formatTime2YMD = (time?: Date) => (time ? dayjs(time).format('YYYY-MM-DD') : '');
|
||||
export const formatTime2HM = (time: Date = new Date()) => dayjs(time).format('HH:mm');
|
||||
|
||||
/* cron time parse */
|
||||
export const cronParser2Fields = (cronString: string) => {
|
||||
|
||||
@@ -50,8 +50,18 @@ export const replaceSensitiveText = (text: string) => {
|
||||
return text;
|
||||
};
|
||||
|
||||
/* Make sure the first letter is definitely lowercase */
|
||||
export const getNanoid = (size = 12) => {
|
||||
return customAlphabet('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890', size)();
|
||||
const firstChar = customAlphabet('abcdefghijklmnopqrstuvwxyz', 1)();
|
||||
|
||||
if (size === 1) return firstChar;
|
||||
|
||||
const randomsStr = customAlphabet(
|
||||
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890',
|
||||
size - 1
|
||||
)();
|
||||
|
||||
return `${firstChar}${randomsStr}`;
|
||||
};
|
||||
|
||||
export const replaceRegChars = (text: string) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
|
||||
@@ -37,6 +37,7 @@ export type FastGPTFeConfigsType = {
|
||||
chatbotUrl?: string;
|
||||
openAPIDocUrl?: string;
|
||||
systemTitle?: string;
|
||||
systemDescription?: string;
|
||||
googleClientVerKey?: string;
|
||||
isPlus?: boolean;
|
||||
show_phoneLogin?: boolean;
|
||||
|
||||
@@ -23,7 +23,7 @@ export const defaultQAModels: LLMModelItemType[] = [
|
||||
|
||||
export const defaultVectorModels: VectorModelItemType[] = [
|
||||
{
|
||||
model: 'text-embedding-ada-002',
|
||||
model: 'text-embedding-3-small',
|
||||
name: 'Embedding-2',
|
||||
charsPointsPrice: 0,
|
||||
defaultToken: 500,
|
||||
|
||||
22
packages/global/core/app/api.d.ts
vendored
@@ -1,22 +0,0 @@
|
||||
import type { LLMModelItemType } from '../ai/model.d';
|
||||
import { AppTypeEnum } from './constants';
|
||||
import { AppSchema } from './type';
|
||||
|
||||
export type CreateAppParams = {
|
||||
name?: string;
|
||||
avatar?: string;
|
||||
type?: `${AppTypeEnum}`;
|
||||
modules: AppSchema['modules'];
|
||||
edges?: AppSchema['edges'];
|
||||
};
|
||||
|
||||
export interface AppUpdateParams {
|
||||
name?: string;
|
||||
type?: `${AppTypeEnum}`;
|
||||
avatar?: string;
|
||||
intro?: string;
|
||||
modules?: AppSchema['modules'];
|
||||
edges?: AppSchema['edges'];
|
||||
permission?: AppSchema['permission'];
|
||||
teamTags?: AppSchema['teamTags'];
|
||||
}
|
||||
3
packages/global/core/app/type.d.ts
vendored
@@ -6,7 +6,7 @@ import { VariableInputEnum } from '../workflow/constants';
|
||||
import { SelectedDatasetType } from '../workflow/api';
|
||||
import { DatasetSearchModeEnum } from '../dataset/constants';
|
||||
import { TeamTagSchema as TeamTagsSchemaType } from '@fastgpt/global/support/user/team/type.d';
|
||||
import { StoreEdgeItemType } from 'core/workflow/type/edge';
|
||||
import { StoreEdgeItemType } from '../workflow/type/edge';
|
||||
|
||||
export interface AppSchema {
|
||||
_id: string;
|
||||
@@ -18,6 +18,7 @@ export interface AppSchema {
|
||||
avatar: string;
|
||||
intro: string;
|
||||
updateTime: number;
|
||||
|
||||
modules: StoreNodeItemType[];
|
||||
edges: StoreEdgeItemType[];
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ export const appWorkflow2Form = ({ nodes }: { nodes: StoreNodeItemType[] }) => {
|
||||
} else if (node.flowNodeType === FlowNodeTypeEnum.systemConfig) {
|
||||
const {
|
||||
welcomeText,
|
||||
variableModules,
|
||||
variableNodes,
|
||||
questionGuide,
|
||||
ttsConfig,
|
||||
whisperConfig,
|
||||
@@ -114,7 +114,7 @@ export const appWorkflow2Form = ({ nodes }: { nodes: StoreNodeItemType[] }) => {
|
||||
|
||||
defaultAppForm.userGuide = {
|
||||
welcomeText: welcomeText,
|
||||
variables: variableModules,
|
||||
variables: variableNodes,
|
||||
questionGuide: questionGuide,
|
||||
tts: ttsConfig,
|
||||
whisper: whisperConfig,
|
||||
@@ -125,6 +125,7 @@ export const appWorkflow2Form = ({ nodes }: { nodes: StoreNodeItemType[] }) => {
|
||||
|
||||
defaultAppForm.selectedTools.push({
|
||||
id: node.pluginId,
|
||||
pluginId: node.pluginId,
|
||||
name: node.name,
|
||||
avatar: node.avatar,
|
||||
intro: node.intro || '',
|
||||
|
||||
10
packages/global/core/app/version.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import { StoreNodeItemType } from '../workflow/type';
|
||||
import { StoreEdgeItemType } from '../workflow/type/edge';
|
||||
|
||||
export type AppVersionSchemaType = {
|
||||
_id: string;
|
||||
appId: string;
|
||||
time: Date;
|
||||
nodes: StoreNodeItemType[];
|
||||
edges: StoreEdgeItemType[];
|
||||
};
|
||||
10
packages/global/core/chat/type.d.ts
vendored
@@ -10,7 +10,7 @@ import {
|
||||
import { FlowNodeTypeEnum } from '../workflow/node/constant';
|
||||
import { NodeOutputKeyEnum } from '../workflow/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '../workflow/runtime/constants';
|
||||
import { AppSchema } from '../app/type';
|
||||
import { AppSchema, VariableItemType } from '../app/type';
|
||||
import type { AppSchema as AppType } from '@fastgpt/global/core/app/type.d';
|
||||
import { DatasetSearchModeEnum } from '../dataset/constants';
|
||||
import { ChatBoxInputType } from '../../../../projects/app/src/components/ChatBox/type';
|
||||
@@ -27,11 +27,13 @@ export type ChatSchema = {
|
||||
title: string;
|
||||
customTitle: string;
|
||||
top: boolean;
|
||||
variables: Record<string, any>;
|
||||
source: `${ChatSourceEnum}`;
|
||||
shareId?: string;
|
||||
outLinkUid?: string;
|
||||
content: ChatItemType[];
|
||||
|
||||
variableList?: VariableItemType[];
|
||||
welcomeText?: string;
|
||||
variables: Record<string, any>;
|
||||
metadata?: Record<string, any>;
|
||||
};
|
||||
|
||||
@@ -155,6 +157,6 @@ export type ToolModuleResponseItemType = {
|
||||
|
||||
/* dispatch run time */
|
||||
export type RuntimeUserPromptType = {
|
||||
files?: UserChatItemValueItemType['file'][];
|
||||
files: UserChatItemValueItemType['file'][];
|
||||
text: string;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DispatchNodeResponseType } from '../workflow/runtime/type';
|
||||
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '../workflow/node/constant';
|
||||
import { FlowNodeTypeEnum } from '../workflow/node/constant';
|
||||
import { ChatItemValueTypeEnum, ChatRoleEnum } from './constants';
|
||||
import { ChatHistoryItemResType, ChatItemType } from './type.d';
|
||||
import { ChatHistoryItemResType, ChatItemType, UserChatItemValueItemType } from './type.d';
|
||||
|
||||
export const getChatTitleFromChatMessage = (message?: ChatItemType, defaultValue = '新对话') => {
|
||||
// @ts-ignore
|
||||
@@ -77,3 +77,15 @@ export const filterPublicNodeResponseData = ({
|
||||
return obj as ChatHistoryItemResType;
|
||||
});
|
||||
};
|
||||
|
||||
export const removeEmptyUserInput = (input: UserChatItemValueItemType[]) => {
|
||||
return input.filter((item) => {
|
||||
if (item.type === ChatItemValueTypeEnum.text && !item.text?.content?.trim()) {
|
||||
return false;
|
||||
}
|
||||
if (item.type === ChatItemValueTypeEnum.file && !item.file?.url) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@ import { CreateOnePluginParams } from '../controller';
|
||||
import { StoreNodeItemType } from '../../workflow/type';
|
||||
import { HttpImgUrl } from '../../../common/file/image/constants';
|
||||
import SwaggerParser from '@apidevtools/swagger-parser';
|
||||
import { getHandleId } from '../../../core/workflow/utils';
|
||||
|
||||
export const str2OpenApiSchema = async (yamlStr = ''): Promise<OpenApiJsonSchema> => {
|
||||
try {
|
||||
@@ -51,13 +52,35 @@ export const str2OpenApiSchema = async (yamlStr = ''): Promise<OpenApiJsonSchema
|
||||
})
|
||||
.flat()
|
||||
.filter(Boolean) as OpenApiJsonSchema['pathData'];
|
||||
|
||||
return { pathData, serverPath };
|
||||
} catch (err) {
|
||||
throw new Error('Invalid Schema');
|
||||
}
|
||||
};
|
||||
|
||||
export const getType = (schema: { type: string; items?: { type: string } }) => {
|
||||
const typeMap: { [key: string]: WorkflowIOValueTypeEnum } = {
|
||||
string: WorkflowIOValueTypeEnum.arrayString,
|
||||
number: WorkflowIOValueTypeEnum.arrayNumber,
|
||||
integer: WorkflowIOValueTypeEnum.arrayNumber,
|
||||
boolean: WorkflowIOValueTypeEnum.arrayBoolean,
|
||||
object: WorkflowIOValueTypeEnum.arrayObject
|
||||
};
|
||||
|
||||
if (schema?.type === 'integer') {
|
||||
return WorkflowIOValueTypeEnum.number;
|
||||
}
|
||||
|
||||
if (schema?.type === 'array' && schema?.items) {
|
||||
const itemType = typeMap[schema.items.type];
|
||||
if (itemType) {
|
||||
return itemType;
|
||||
}
|
||||
}
|
||||
|
||||
return schema?.type as WorkflowIOValueTypeEnum;
|
||||
};
|
||||
|
||||
export const httpApiSchema2Plugins = async ({
|
||||
parentId,
|
||||
apiSchemaStr = '',
|
||||
@@ -86,7 +109,7 @@ export const httpApiSchema2Plugins = async ({
|
||||
...(item.params?.map((param: any) => {
|
||||
return {
|
||||
key: param.name,
|
||||
valueType: param.schema.type,
|
||||
valueType: getType(param.schema),
|
||||
label: param.name,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference],
|
||||
required: param.required,
|
||||
@@ -108,7 +131,7 @@ export const httpApiSchema2Plugins = async ({
|
||||
const prop = properties[key];
|
||||
return {
|
||||
key,
|
||||
valueType: prop.type,
|
||||
valueType: getType(prop),
|
||||
label: key,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference],
|
||||
required: false,
|
||||
@@ -135,7 +158,7 @@ export const httpApiSchema2Plugins = async ({
|
||||
return {
|
||||
id,
|
||||
key: param.name,
|
||||
valueType: param.schema.type,
|
||||
valueType: getType(param.schema),
|
||||
label: param.name,
|
||||
type: FlowNodeOutputTypeEnum.source
|
||||
};
|
||||
@@ -146,7 +169,7 @@ export const httpApiSchema2Plugins = async ({
|
||||
return {
|
||||
id,
|
||||
key,
|
||||
valueType: properties[key].type,
|
||||
valueType: getType(properties[key]),
|
||||
label: key,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
edit: true
|
||||
@@ -158,7 +181,7 @@ export const httpApiSchema2Plugins = async ({
|
||||
...(item.params?.map((param: any) => {
|
||||
return {
|
||||
key: param.name,
|
||||
valueType: param.schema.type,
|
||||
valueType: getType(param.schema),
|
||||
label: param.name,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference],
|
||||
canEdit: true,
|
||||
@@ -172,7 +195,7 @@ export const httpApiSchema2Plugins = async ({
|
||||
...(propsKeys?.map((key) => {
|
||||
return {
|
||||
key,
|
||||
valueType: properties[key].type,
|
||||
valueType: getType(properties[key]),
|
||||
label: key,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference],
|
||||
canEdit: true,
|
||||
@@ -196,7 +219,7 @@ export const httpApiSchema2Plugins = async ({
|
||||
if (param.in === 'header') {
|
||||
httpNodeHeaders.push({
|
||||
key: param.name,
|
||||
type: param.schema?.type || WorkflowIOValueTypeEnum.string,
|
||||
type: getType(param.schema) || WorkflowIOValueTypeEnum.string,
|
||||
value: `{{${param.name}}}`
|
||||
});
|
||||
} else if (param.in === 'body') {
|
||||
@@ -208,7 +231,7 @@ export const httpApiSchema2Plugins = async ({
|
||||
} else if (param.in === 'query') {
|
||||
httpNodeParams.push({
|
||||
key: param.name,
|
||||
type: param.schema?.type || WorkflowIOValueTypeEnum.string,
|
||||
type: getType(param.schema) || WorkflowIOValueTypeEnum.string,
|
||||
value: `{{${param.name}}}`
|
||||
});
|
||||
}
|
||||
@@ -378,14 +401,14 @@ export const httpApiSchema2Plugins = async ({
|
||||
{
|
||||
source: pluginInputId,
|
||||
target: httpId,
|
||||
sourcePort: `${pluginInputId}-source-right`,
|
||||
targetPort: `${httpId}-target-left`
|
||||
sourceHandle: getHandleId(pluginInputId, 'source', 'right'),
|
||||
targetHandle: getHandleId(httpId, 'target', 'left')
|
||||
},
|
||||
{
|
||||
source: httpId,
|
||||
target: pluginOutputId,
|
||||
sourcePort: `${httpId}-source-right`,
|
||||
targetPort: `${pluginOutputId}-target-left`
|
||||
sourceHandle: getHandleId(httpId, 'source', 'right'),
|
||||
targetHandle: getHandleId(pluginOutputId, 'target', 'left')
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -14,18 +14,21 @@ export enum WorkflowIOValueTypeEnum {
|
||||
string = 'string',
|
||||
number = 'number',
|
||||
boolean = 'boolean',
|
||||
object = 'object',
|
||||
arrayString = 'arrayString',
|
||||
arrayNumber = 'arrayNumber',
|
||||
arrayBoolean = 'arrayBoolean',
|
||||
arrayObject = 'arrayObject',
|
||||
any = 'any',
|
||||
|
||||
chatHistory = 'chatHistory',
|
||||
datasetQuote = 'datasetQuote',
|
||||
|
||||
dynamic = 'dynamic',
|
||||
|
||||
// plugin special type
|
||||
selectApp = 'selectApp',
|
||||
selectDataset = 'selectDataset',
|
||||
|
||||
// tool
|
||||
tools = 'tools'
|
||||
selectDataset = 'selectDataset'
|
||||
}
|
||||
|
||||
/* reg: modulename key */
|
||||
@@ -34,7 +37,6 @@ export enum NodeInputKeyEnum {
|
||||
welcomeText = 'welcomeText',
|
||||
switch = 'switch', // a trigger switch
|
||||
history = 'history',
|
||||
userChatInput = 'userChatInput',
|
||||
answerText = 'text',
|
||||
|
||||
// system config
|
||||
@@ -44,6 +46,10 @@ export enum NodeInputKeyEnum {
|
||||
variables = 'variables',
|
||||
scheduleTrigger = 'scheduleTrigger',
|
||||
|
||||
// entry
|
||||
userChatInput = 'userChatInput',
|
||||
inputFiles = 'inputFiles',
|
||||
|
||||
agents = 'agents', // cq agent key
|
||||
|
||||
// latest
|
||||
@@ -98,7 +104,10 @@ export enum NodeInputKeyEnum {
|
||||
|
||||
// if else
|
||||
condition = 'condition',
|
||||
ifElseList = 'ifElseList'
|
||||
ifElseList = 'ifElseList',
|
||||
|
||||
// variable update
|
||||
updateList = 'updateList'
|
||||
}
|
||||
|
||||
export enum NodeOutputKeyEnum {
|
||||
@@ -132,15 +141,14 @@ export enum NodeOutputKeyEnum {
|
||||
// plugin
|
||||
pluginStart = 'pluginStart',
|
||||
|
||||
if = 'IF',
|
||||
else = 'ELSE'
|
||||
ifElseResult = 'ifElseResult'
|
||||
}
|
||||
|
||||
export enum VariableInputEnum {
|
||||
input = 'input',
|
||||
textarea = 'textarea',
|
||||
select = 'select',
|
||||
external = 'external'
|
||||
custom = 'custom'
|
||||
}
|
||||
export const variableMap = {
|
||||
[VariableInputEnum.input]: {
|
||||
@@ -158,10 +166,10 @@ export const variableMap = {
|
||||
title: 'core.module.variable.select type',
|
||||
desc: ''
|
||||
},
|
||||
[VariableInputEnum.external]: {
|
||||
[VariableInputEnum.custom]: {
|
||||
icon: 'core/app/variable/external',
|
||||
title: 'core.module.variable.External type',
|
||||
desc: '可以通过API接口或分享链接的Query传递变量。增加该类型变量的主要目的是用于变量提示。使用例子: 你可以通过分享链接Query中拼接Token,来实现内部系统身份鉴权。'
|
||||
title: 'core.module.variable.Custom type',
|
||||
desc: '可以定义一个无需用户填写的全局变量。\n该变量的值可以来自于 API 接口,分享链接的 Query 或通过【变量更新】模块进行赋值。'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -173,3 +181,5 @@ export enum RuntimeEdgeStatusEnum {
|
||||
'active' = 'active',
|
||||
'skipped' = 'skipped'
|
||||
}
|
||||
|
||||
export const VARIABLE_NODE_ID = 'VARIABLE_NODE_ID';
|
||||
|
||||
@@ -112,7 +112,8 @@ export enum FlowNodeTypeEnum {
|
||||
tools = 'tools',
|
||||
stopTool = 'stopTool',
|
||||
lafModule = 'lafModule',
|
||||
ifElseNode = 'ifElseNode'
|
||||
ifElseNode = 'ifElseNode',
|
||||
variableUpdate = 'variableUpdate'
|
||||
}
|
||||
|
||||
export const EDGE_TYPE = 'default';
|
||||
|
||||
@@ -9,7 +9,8 @@ export enum SseResponseEventEnum {
|
||||
toolCall = 'toolCall', // tool start
|
||||
toolParams = 'toolParams', // tool params return
|
||||
toolResponse = 'toolResponse', // tool response return
|
||||
flowResponses = 'flowResponses' // sse response request
|
||||
flowResponses = 'flowResponses', // sse response request
|
||||
updateVariables = 'updateVariables'
|
||||
}
|
||||
|
||||
export enum DispatchNodeResponseKeyEnum {
|
||||
|
||||
@@ -75,7 +75,7 @@ export type DispatchNodeResponseType = {
|
||||
pluginDetail?: ChatHistoryItemResType[];
|
||||
|
||||
// if-else
|
||||
ifElseResult?: 'IF' | 'ELSE';
|
||||
ifElseResult?: string;
|
||||
|
||||
// tool
|
||||
toolCallTokens?: number;
|
||||
|
||||
@@ -4,7 +4,9 @@ import { FlowNodeTypeEnum } from '../node/constant';
|
||||
import { StoreNodeItemType } from '../type';
|
||||
import { StoreEdgeItemType } from '../type/edge';
|
||||
import { RuntimeEdgeItemType, RuntimeNodeItemType } from './type';
|
||||
import { VARIABLE_NODE_ID } from '../../../../../projects/app/src/web/core/workflow/constants/index';
|
||||
import { VARIABLE_NODE_ID } from '../constants';
|
||||
import { isReferenceValue } from '../utils';
|
||||
import { ReferenceValueProps } from '../type/io';
|
||||
|
||||
export const initWorkflowEdgeStatus = (edges: StoreEdgeItemType[]): RuntimeEdgeItemType[] => {
|
||||
return (
|
||||
@@ -138,16 +140,11 @@ export const getReferenceVariableValue = ({
|
||||
nodes,
|
||||
variables
|
||||
}: {
|
||||
value: [string, string];
|
||||
value: ReferenceValueProps;
|
||||
nodes: RuntimeNodeItemType[];
|
||||
variables: Record<string, any>;
|
||||
}) => {
|
||||
if (
|
||||
!Array.isArray(value) ||
|
||||
value.length !== 2 ||
|
||||
typeof value[0] !== 'string' ||
|
||||
typeof value[1] !== 'string'
|
||||
) {
|
||||
if (!isReferenceValue(value)) {
|
||||
return value;
|
||||
}
|
||||
const sourceNodeId = value[0];
|
||||
|
||||
@@ -18,10 +18,10 @@ import { PluginOutputModule } from './system/pluginOutput';
|
||||
import { RunPluginModule } from './system/runPlugin';
|
||||
import { AiQueryExtension } from './system/queryExtension';
|
||||
|
||||
import type { FlowNodeTemplateType, nodeTemplateListType } from '../type';
|
||||
import { FlowNodeTemplateTypeEnum } from '../../workflow/constants';
|
||||
import { lafModule } from './system/laf';
|
||||
import { ifElseNode } from './system/ifElse/index';
|
||||
import type { FlowNodeTemplateType } from '../type';
|
||||
import { LafModule } from './system/laf';
|
||||
import { IfElseNode } from './system/ifElse/index';
|
||||
import { VariableUpdateNode } from './system/variableUpdate';
|
||||
|
||||
/* app flow module templates */
|
||||
export const appSystemModuleTemplates: FlowNodeTemplateType[] = [
|
||||
@@ -38,8 +38,9 @@ export const appSystemModuleTemplates: FlowNodeTemplateType[] = [
|
||||
ContextExtractModule,
|
||||
HttpModule468,
|
||||
AiQueryExtension,
|
||||
lafModule,
|
||||
ifElseNode
|
||||
LafModule,
|
||||
IfElseNode,
|
||||
VariableUpdateNode
|
||||
];
|
||||
/* plugin flow module templates */
|
||||
export const pluginSystemModuleTemplates: FlowNodeTemplateType[] = [
|
||||
@@ -56,8 +57,9 @@ export const pluginSystemModuleTemplates: FlowNodeTemplateType[] = [
|
||||
ContextExtractModule,
|
||||
HttpModule468,
|
||||
AiQueryExtension,
|
||||
lafModule,
|
||||
ifElseNode
|
||||
LafModule,
|
||||
IfElseNode,
|
||||
VariableUpdateNode
|
||||
];
|
||||
|
||||
/* all module */
|
||||
@@ -80,44 +82,7 @@ export const moduleTemplatesFlat: FlowNodeTemplateType[] = [
|
||||
PluginOutputModule,
|
||||
RunPluginModule,
|
||||
AiQueryExtension,
|
||||
lafModule,
|
||||
ifElseNode
|
||||
];
|
||||
|
||||
export const moduleTemplatesList: nodeTemplateListType = [
|
||||
{
|
||||
type: FlowNodeTemplateTypeEnum.systemInput,
|
||||
label: 'core.module.template.System input module',
|
||||
list: []
|
||||
},
|
||||
{
|
||||
type: FlowNodeTemplateTypeEnum.textAnswer,
|
||||
label: 'core.module.template.Response module',
|
||||
list: []
|
||||
},
|
||||
{
|
||||
type: FlowNodeTemplateTypeEnum.functionCall,
|
||||
label: 'core.module.template.Function module',
|
||||
list: []
|
||||
},
|
||||
{
|
||||
type: FlowNodeTemplateTypeEnum.tools,
|
||||
label: 'core.module.template.Tool module',
|
||||
list: []
|
||||
},
|
||||
{
|
||||
type: FlowNodeTemplateTypeEnum.externalCall,
|
||||
label: 'core.module.template.External module',
|
||||
list: []
|
||||
},
|
||||
{
|
||||
type: FlowNodeTemplateTypeEnum.personalPlugin,
|
||||
label: '',
|
||||
list: []
|
||||
},
|
||||
{
|
||||
type: FlowNodeTemplateTypeEnum.other,
|
||||
label: '其他',
|
||||
list: []
|
||||
}
|
||||
LafModule,
|
||||
IfElseNode,
|
||||
VariableUpdateNode
|
||||
];
|
||||
|
||||
@@ -9,9 +9,10 @@ export const Input_Template_History: FlowNodeInputItemType = {
|
||||
renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference],
|
||||
valueType: WorkflowIOValueTypeEnum.chatHistory,
|
||||
label: 'core.module.input.label.chat history',
|
||||
description: '最多携带多少轮对话记录',
|
||||
required: true,
|
||||
min: 0,
|
||||
max: 30,
|
||||
max: 50,
|
||||
value: 6
|
||||
};
|
||||
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeTypeEnum
|
||||
} from '../../node/constant';
|
||||
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '../../node/constant';
|
||||
import { FlowNodeTemplateType } from '../../type/index.d';
|
||||
import {
|
||||
WorkflowIOValueTypeEnum,
|
||||
NodeInputKeyEnum,
|
||||
FlowNodeTemplateTypeEnum,
|
||||
NodeOutputKeyEnum
|
||||
FlowNodeTemplateTypeEnum
|
||||
} from '../../constants';
|
||||
import { getHandleConfig } from '../utils';
|
||||
|
||||
@@ -26,7 +21,7 @@ export const AssignedAnswerModule: FlowNodeTemplateType = {
|
||||
{
|
||||
key: NodeInputKeyEnum.answerText,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.textarea, FlowNodeInputTypeEnum.reference],
|
||||
valueType: WorkflowIOValueTypeEnum.string,
|
||||
valueType: WorkflowIOValueTypeEnum.any,
|
||||
label: 'core.module.input.label.Response content',
|
||||
description: 'core.module.input.description.Response content',
|
||||
placeholder: 'core.module.input.description.Response content'
|
||||
|
||||
@@ -10,16 +10,26 @@ import {
|
||||
NodeOutputKeyEnum,
|
||||
FlowNodeTemplateTypeEnum
|
||||
} from '../../constants';
|
||||
import { Input_Template_Dataset_Quote } from '../input';
|
||||
import { getNanoid } from '../../../../common/string/tools';
|
||||
import { getHandleConfig } from '../utils';
|
||||
import { FlowNodeInputItemType } from '../../type/io.d';
|
||||
|
||||
export const getOneQuoteInputTemplate = (key = getNanoid()): FlowNodeInputItemType => ({
|
||||
...Input_Template_Dataset_Quote,
|
||||
const defaultQuoteKey = 'defaultQuoteKey';
|
||||
|
||||
export const getOneQuoteInputTemplate = ({
|
||||
key = getNanoid(),
|
||||
index
|
||||
}: {
|
||||
key?: string;
|
||||
index: number;
|
||||
}): FlowNodeInputItemType => ({
|
||||
key,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.custom],
|
||||
description: ''
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference],
|
||||
label: `引用${index}`,
|
||||
debugLabel: '知识库引用',
|
||||
canEdit: key !== defaultQuoteKey,
|
||||
description: '',
|
||||
valueType: WorkflowIOValueTypeEnum.datasetQuote
|
||||
});
|
||||
|
||||
export const DatasetConcatModule: FlowNodeTemplateType = {
|
||||
@@ -37,7 +47,7 @@ export const DatasetConcatModule: FlowNodeTemplateType = {
|
||||
key: NodeInputKeyEnum.datasetMaxTokens,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.custom],
|
||||
label: '最大 Tokens',
|
||||
value: 1500,
|
||||
value: 3000,
|
||||
valueType: WorkflowIOValueTypeEnum.number
|
||||
},
|
||||
{
|
||||
@@ -45,7 +55,7 @@ export const DatasetConcatModule: FlowNodeTemplateType = {
|
||||
renderTypeList: [FlowNodeInputTypeEnum.custom],
|
||||
label: ''
|
||||
},
|
||||
getOneQuoteInputTemplate()
|
||||
getOneQuoteInputTemplate({ key: defaultQuoteKey, index: 1 })
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FlowNodeTemplateTypeEnum, WorkflowIOValueTypeEnum } from '../../constants';
|
||||
import { getHandleConfig } from '../utils';
|
||||
import { FlowNodeTypeEnum } from '../../node/constant';
|
||||
import { FlowNodeOutputTypeEnum, FlowNodeTypeEnum } from '../../node/constant';
|
||||
import { VariableItemType } from '../../../app/type';
|
||||
import { FlowNodeTemplateType } from '../../type';
|
||||
|
||||
@@ -25,6 +25,7 @@ export const getGlobalVariableNode = ({
|
||||
id: item.key,
|
||||
key: item.key,
|
||||
valueType: WorkflowIOValueTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.static,
|
||||
label: item.label
|
||||
}))
|
||||
};
|
||||
|
||||
@@ -20,6 +20,11 @@ export enum VariableConditionEnum {
|
||||
lengthLessThan = 'lengthLessThan',
|
||||
lengthLessThanOrEqualTo = 'lengthLessThanOrEqualTo'
|
||||
}
|
||||
export enum IfElseResultEnum {
|
||||
IF = 'IF',
|
||||
ELSE = 'ELSE',
|
||||
ELSE_IF = 'ELSE IF'
|
||||
}
|
||||
|
||||
export const stringConditionList = [
|
||||
{ label: '为空', value: VariableConditionEnum.isEmpty },
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import { FlowNodeTemplateType } from '../../../type';
|
||||
import { getHandleConfig } from '../../utils';
|
||||
|
||||
export const ifElseNode: FlowNodeTemplateType = {
|
||||
export const IfElseNode: FlowNodeTemplateType = {
|
||||
id: FlowNodeTypeEnum.ifElseNode,
|
||||
templateType: FlowNodeTemplateTypeEnum.tools,
|
||||
flowNodeType: FlowNodeTypeEnum.ifElseNode,
|
||||
@@ -23,14 +23,6 @@ export const ifElseNode: FlowNodeTemplateType = {
|
||||
intro: '根据一定的条件,执行不同的分支。',
|
||||
showStatus: true,
|
||||
inputs: [
|
||||
{
|
||||
key: NodeInputKeyEnum.condition,
|
||||
valueType: WorkflowIOValueTypeEnum.string,
|
||||
label: '',
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
required: false,
|
||||
value: 'AND' // AND, OR
|
||||
},
|
||||
{
|
||||
key: NodeInputKeyEnum.ifElseList,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
@@ -38,27 +30,25 @@ export const ifElseNode: FlowNodeTemplateType = {
|
||||
label: '',
|
||||
value: [
|
||||
{
|
||||
variable: undefined,
|
||||
condition: undefined,
|
||||
value: undefined
|
||||
condition: 'AND', // AND, OR
|
||||
list: [
|
||||
{
|
||||
variable: undefined,
|
||||
condition: undefined,
|
||||
value: undefined
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
id: NodeOutputKeyEnum.if,
|
||||
key: NodeOutputKeyEnum.if,
|
||||
label: 'IF',
|
||||
valueType: WorkflowIOValueTypeEnum.any,
|
||||
type: FlowNodeOutputTypeEnum.source
|
||||
},
|
||||
{
|
||||
id: NodeOutputKeyEnum.else,
|
||||
key: NodeOutputKeyEnum.else,
|
||||
label: 'ELSE',
|
||||
valueType: WorkflowIOValueTypeEnum.any,
|
||||
type: FlowNodeOutputTypeEnum.source
|
||||
id: NodeOutputKeyEnum.ifElseResult,
|
||||
key: NodeOutputKeyEnum.ifElseResult,
|
||||
label: '判断结果',
|
||||
valueType: WorkflowIOValueTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.static
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -2,8 +2,12 @@ import { ReferenceValueProps } from 'core/workflow/type/io';
|
||||
import { VariableConditionEnum } from './constant';
|
||||
|
||||
export type IfElseConditionType = 'AND' | 'OR';
|
||||
export type IfElseListItemType = {
|
||||
export type ConditionListItemType = {
|
||||
variable?: ReferenceValueProps;
|
||||
condition?: VariableConditionEnum;
|
||||
value?: string;
|
||||
};
|
||||
export type IfElseListItemType = {
|
||||
condition: IfElseConditionType;
|
||||
list: ConditionListItemType[];
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ import { Input_Template_DynamicInput } from '../input';
|
||||
import { Output_Template_AddOutput } from '../output';
|
||||
import { getHandleConfig } from '../utils';
|
||||
|
||||
export const lafModule: FlowNodeTemplateType = {
|
||||
export const LafModule: FlowNodeTemplateType = {
|
||||
id: FlowNodeTypeEnum.lafModule,
|
||||
templateType: FlowNodeTemplateTypeEnum.externalCall,
|
||||
flowNodeType: FlowNodeTypeEnum.lafModule,
|
||||
|
||||
@@ -12,7 +12,7 @@ export const PluginInputModule: FlowNodeTemplateType = {
|
||||
unique: true,
|
||||
forbidDelete: true,
|
||||
avatar: '/imgs/workflow/input.png',
|
||||
name: '定义插件输入',
|
||||
name: '自定义插件输入',
|
||||
intro: '自定义配置外部输入,使用插件时,仅暴露自定义配置的输入',
|
||||
showStatus: false,
|
||||
inputs: [],
|
||||
|
||||
@@ -12,7 +12,7 @@ export const PluginOutputModule: FlowNodeTemplateType = {
|
||||
unique: true,
|
||||
forbidDelete: true,
|
||||
avatar: '/imgs/workflow/output.png',
|
||||
name: '定义插件输出',
|
||||
name: '自定义插件输出',
|
||||
intro: '自定义配置外部输出,使用插件时,仅暴露自定义配置的输出',
|
||||
showStatus: false,
|
||||
inputs: [],
|
||||
|
||||
@@ -27,7 +27,7 @@ export const ToolModule: FlowNodeTemplateType = {
|
||||
sourceHandle: getHandleConfig(true, true, false, true),
|
||||
targetHandle: getHandleConfig(true, true, false, true),
|
||||
avatar: '/imgs/workflow/tool.svg',
|
||||
name: '工具调用(实验)',
|
||||
name: '工具调用(实验)',
|
||||
intro: '通过AI模型自动选择一个或多个功能块进行调用,也可以对插件进行调用。',
|
||||
showStatus: true,
|
||||
inputs: [
|
||||
@@ -64,5 +64,14 @@ export const ToolModule: FlowNodeTemplateType = {
|
||||
Input_Template_History,
|
||||
Input_Template_UserChatInput
|
||||
],
|
||||
outputs: []
|
||||
outputs: [
|
||||
{
|
||||
id: NodeOutputKeyEnum.answerText,
|
||||
key: NodeOutputKeyEnum.answerText,
|
||||
label: 'core.module.output.label.Ai response content',
|
||||
description: 'core.module.output.description.Ai response content',
|
||||
valueType: WorkflowIOValueTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.static
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '../../../node/constant';
|
||||
import { FlowNodeTemplateType } from '../../../type/index.d';
|
||||
import {
|
||||
FlowNodeTemplateTypeEnum,
|
||||
NodeInputKeyEnum,
|
||||
WorkflowIOValueTypeEnum
|
||||
} from '../../../constants';
|
||||
import { getHandleConfig } from '../../utils';
|
||||
|
||||
export const VariableUpdateNode: FlowNodeTemplateType = {
|
||||
id: FlowNodeTypeEnum.variableUpdate,
|
||||
templateType: FlowNodeTemplateTypeEnum.tools,
|
||||
flowNodeType: FlowNodeTypeEnum.variableUpdate,
|
||||
sourceHandle: getHandleConfig(true, true, true, true),
|
||||
targetHandle: getHandleConfig(true, true, true, true),
|
||||
avatar: '/imgs/workflow/variable.png',
|
||||
name: '变量更新',
|
||||
intro: '可以更新指定节点的输出值或更新全局变量',
|
||||
showStatus: true,
|
||||
isTool: false,
|
||||
inputs: [
|
||||
{
|
||||
key: NodeInputKeyEnum.updateList,
|
||||
valueType: WorkflowIOValueTypeEnum.any,
|
||||
label: '',
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
editField: {
|
||||
key: true,
|
||||
valueType: true
|
||||
},
|
||||
value: [
|
||||
{
|
||||
variable: ['', ''],
|
||||
value: ['', ''],
|
||||
valueType: WorkflowIOValueTypeEnum.string,
|
||||
renderType: FlowNodeInputTypeEnum.input
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
outputs: []
|
||||
};
|
||||
10
packages/global/core/workflow/template/system/variableUpdate/type.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import { FlowNodeInputTypeEnum } from '../../../node/constant';
|
||||
import { ReferenceValueProps } from '../../..//type/io';
|
||||
import { WorkflowIOValueTypeEnum } from '../../../constants';
|
||||
|
||||
export type TUpdateListItem = {
|
||||
variable?: ReferenceValueProps;
|
||||
value: ReferenceValueProps;
|
||||
valueType?: WorkflowIOValueTypeEnum;
|
||||
renderType: FlowNodeInputTypeEnum.input | FlowNodeInputTypeEnum.reference;
|
||||
};
|
||||
@@ -40,7 +40,7 @@ export type FlowNodeCommonType = {
|
||||
};
|
||||
|
||||
export type FlowNodeTemplateType = FlowNodeCommonType & {
|
||||
id: string; // module id, unique
|
||||
id: string; // node id, unique
|
||||
templateType: `${FlowNodeTemplateTypeEnum}`;
|
||||
|
||||
// show handle
|
||||
@@ -132,11 +132,12 @@ export type ChatDispatchProps = {
|
||||
chatId?: string;
|
||||
responseChatItemId?: string;
|
||||
histories: ChatItemType[];
|
||||
variables: Record<string, any>;
|
||||
inputFiles?: UserChatItemValueItemType['file'][];
|
||||
variables: Record<string, any>; // global variable
|
||||
query: UserChatItemValueItemType[]; // trigger query
|
||||
stream: boolean;
|
||||
detail: boolean; // response detail
|
||||
maxRunTimes: number;
|
||||
isToolCall?: boolean;
|
||||
};
|
||||
|
||||
export type ModuleDispatchProps<T> = ChatDispatchProps & {
|
||||
|
||||
@@ -15,6 +15,7 @@ import type {
|
||||
} from '../app/type';
|
||||
import { EditorVariablePickerType } from '../../../web/components/common/Textarea/PromptEditor/type';
|
||||
import { defaultWhisperConfig } from '../app/constants';
|
||||
import { IfElseResultEnum } from './template/system/ifElse/constant';
|
||||
|
||||
export const getHandleId = (nodeId: string, type: 'source' | 'target', key: string) => {
|
||||
return `${nodeId}-${type}-${key}`;
|
||||
@@ -35,13 +36,17 @@ export const checkInputIsReference = (input: FlowNodeInputItemType) => {
|
||||
|
||||
/* node */
|
||||
export const getGuideModule = (modules: StoreNodeItemType[]) =>
|
||||
modules.find((item) => item.flowNodeType === FlowNodeTypeEnum.systemConfig);
|
||||
|
||||
modules.find(
|
||||
(item) =>
|
||||
item.flowNodeType === FlowNodeTypeEnum.systemConfig ||
|
||||
// @ts-ignore (adapt v1)
|
||||
item.flowType === FlowNodeTypeEnum.systemConfig
|
||||
);
|
||||
export const splitGuideModule = (guideModules?: StoreNodeItemType) => {
|
||||
const welcomeText: string =
|
||||
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.welcomeText)?.value || '';
|
||||
|
||||
const variableModules: VariableItemType[] =
|
||||
const variableNodes: VariableItemType[] =
|
||||
guideModules?.inputs.find((item) => item.key === NodeInputKeyEnum.variables)?.value || [];
|
||||
|
||||
const questionGuide: boolean =
|
||||
@@ -62,13 +67,43 @@ export const splitGuideModule = (guideModules?: StoreNodeItemType) => {
|
||||
|
||||
return {
|
||||
welcomeText,
|
||||
variableModules,
|
||||
variableNodes,
|
||||
questionGuide,
|
||||
ttsConfig,
|
||||
whisperConfig,
|
||||
scheduledTriggerConfig
|
||||
};
|
||||
};
|
||||
export const replaceAppChatConfig = ({
|
||||
node,
|
||||
variableList,
|
||||
welcomeText
|
||||
}: {
|
||||
node?: StoreNodeItemType;
|
||||
variableList?: VariableItemType[];
|
||||
welcomeText?: string;
|
||||
}): StoreNodeItemType | undefined => {
|
||||
if (!node) return;
|
||||
return {
|
||||
...node,
|
||||
inputs: node.inputs.map((input) => {
|
||||
if (input.key === NodeInputKeyEnum.variables && variableList) {
|
||||
return {
|
||||
...input,
|
||||
value: variableList
|
||||
};
|
||||
}
|
||||
if (input.key === NodeInputKeyEnum.welcomeText && welcomeText) {
|
||||
return {
|
||||
...input,
|
||||
value: welcomeText
|
||||
};
|
||||
}
|
||||
|
||||
return input;
|
||||
})
|
||||
};
|
||||
};
|
||||
|
||||
export const getOrInitModuleInputValue = (input: FlowNodeInputItemType) => {
|
||||
if (input.value !== undefined || !input.valueType) return input.value;
|
||||
@@ -132,3 +167,11 @@ export const formatEditorVariablePickerIcon = (
|
||||
icon: item.type ? variableMap[item.type]?.icon : variableMap['input'].icon
|
||||
}));
|
||||
};
|
||||
|
||||
export const isReferenceValue = (value: any): boolean => {
|
||||
return Array.isArray(value) && value.length === 2 && typeof value[0] === 'string';
|
||||
};
|
||||
|
||||
export const getElseIFLabel = (i: number) => {
|
||||
return i === 0 ? IfElseResultEnum.IF : `${IfElseResultEnum.ELSE_IF} ${i}`;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export enum OutLinkTypeEnum {
|
||||
export enum PublishChannelEnum {
|
||||
share = 'share',
|
||||
iframe = 'iframe',
|
||||
apikey = 'apikey'
|
||||
apikey = 'apikey',
|
||||
feishu = 'feishu'
|
||||
}
|
||||
|
||||
60
packages/global/support/outLink/type.d.ts
vendored
@@ -1,31 +1,79 @@
|
||||
import { AppSchema } from 'core/app/type';
|
||||
import { OutLinkTypeEnum } from './constant';
|
||||
import { PublishChannelEnum } from './constant';
|
||||
|
||||
export type OutLinkSchema = {
|
||||
// Feishu Config interface
|
||||
export interface FeishuType {
|
||||
appId: string;
|
||||
appSecret: string;
|
||||
// Encrypt config
|
||||
// refer to: https://open.feishu.cn/document/server-docs/event-subscription-guide/event-subscription-configure-/configure-encrypt-key
|
||||
encryptKey?: string; // no secret if null
|
||||
// Token Verification
|
||||
// refer to: https://open.feishu.cn/document/server-docs/event-subscription-guide/event-subscription-configure-/encrypt-key-encryption-configuration-case
|
||||
verificationToken: string;
|
||||
}
|
||||
|
||||
// TODO: Unused
|
||||
export interface WecomType {
|
||||
ReplyLimit: Boolean;
|
||||
defaultResponse: string;
|
||||
immediateResponse: boolean;
|
||||
WXWORK_TOKEN: string;
|
||||
WXWORK_AESKEY: string;
|
||||
WXWORK_SECRET: string;
|
||||
WXWORD_ID: string;
|
||||
}
|
||||
|
||||
export type OutLinkSchema<T = void> = {
|
||||
_id: string;
|
||||
shareId: string;
|
||||
teamId: string;
|
||||
tmbId: string;
|
||||
appId: string;
|
||||
// teamId: Schema.Types.ObjectId;
|
||||
// tmbId: Schema.Types.ObjectId;
|
||||
// appId: Schema.Types.ObjectId;
|
||||
name: string;
|
||||
usagePoints: number;
|
||||
lastTime: Date;
|
||||
type: `${OutLinkTypeEnum}`;
|
||||
type: PublishChannelEnum;
|
||||
|
||||
// whether the response content is detailed
|
||||
responseDetail: boolean;
|
||||
|
||||
// response when request
|
||||
immediateResponse?: string;
|
||||
// response when error or other situation
|
||||
defaultResponse?: string;
|
||||
|
||||
limit?: {
|
||||
expiredTime?: Date;
|
||||
// Questions per minute
|
||||
QPM: number;
|
||||
maxUsagePoints: number;
|
||||
// Verification message hook url
|
||||
hookUrl?: string;
|
||||
};
|
||||
|
||||
app?: T;
|
||||
};
|
||||
|
||||
// to handle MongoDB querying
|
||||
export type OutLinkWithAppType = Omit<OutLinkSchema, 'appId'> & {
|
||||
appId: AppSchema;
|
||||
};
|
||||
|
||||
export type OutLinkEditType = {
|
||||
// Edit the Outlink
|
||||
export type OutLinkEditType<T = void> = {
|
||||
_id?: string;
|
||||
name: string;
|
||||
responseDetail: OutLinkSchema['responseDetail'];
|
||||
limit: OutLinkSchema['limit'];
|
||||
responseDetail: OutLinkSchema<T>['responseDetail'];
|
||||
// response when request
|
||||
immediateResponse?: string;
|
||||
// response when error or other situation
|
||||
defaultResponse?: string;
|
||||
limit?: OutLinkSchema<T>['limit'];
|
||||
|
||||
// config for specific platform
|
||||
app?: T;
|
||||
};
|
||||
|
||||