Compare commits
25 Commits
v4.6
...
v4.6.3-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c77dfbddd | ||
|
|
7fc05af09e | ||
|
|
a9ae270335 | ||
|
|
b916183848 | ||
|
|
007fce2deb | ||
|
|
abc1e576b7 | ||
|
|
a74e1d7166 | ||
|
|
e765c3bf95 | ||
|
|
933c3fdfd6 | ||
|
|
f818260711 | ||
|
|
3acbf1ab17 | ||
|
|
9cb4280a16 | ||
|
|
60f752629f | ||
|
|
0558379ddb | ||
|
|
9c4eabfc9e | ||
|
|
b05dd0fde1 | ||
|
|
0df5152202 | ||
|
|
e044d3583d | ||
|
|
c5664c7e90 | ||
|
|
70f3373246 | ||
|
|
af16817a4a | ||
|
|
4358b6de4d | ||
|
|
f6aea484ce | ||
|
|
fbe1d8cfed | ||
|
|
16103029f5 |
@@ -1,7 +1,7 @@
|
||||
# Install dependencies only when needed
|
||||
FROM node:18.15-alpine AS deps
|
||||
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
||||
RUN apk add --no-cache libc6-compat && npm install -g pnpm
|
||||
RUN apk add libc6-compat && npm install -g pnpm
|
||||
WORKDIR /app
|
||||
|
||||
ARG name
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
|
||||
<p align="center">
|
||||
<a href="./README_en.md">English</a> |
|
||||
<a href="./README.md">简体中文</a>
|
||||
<a href="./README.md">简体中文</a> |
|
||||
<a href="./README_ja.md">日语</a>
|
||||
</p>
|
||||
|
||||
FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开箱即用的数据处理、模型调用等能力。同时可以通过 Flow 可视化进行工作流编排,从而实现复杂的问答场景!
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
|
||||
<p align="center">
|
||||
<a href="./README_en.md">English</a> |
|
||||
<a href="./README.md">简体中文</a>
|
||||
<a href="./README.md">简体中文</a> |
|
||||
<a href="./README_ja.md">日语</a>
|
||||
</p>
|
||||
|
||||
FastGPT is a knowledge-based Q&A system built on the LLM, offers out-of-the-box data processing and model invocation capabilities, allows for workflow orchestration through Flow visualization!
|
||||
|
||||
135
README_ja.md
Normal file
@@ -0,0 +1,135 @@
|
||||
<div align="center">
|
||||
|
||||
<a href="https://fastgpt.run/"><img src="/.github/imgs/logo.svg" width="120" height="120" alt="fastgpt logo"></a>
|
||||
|
||||
# FastGPT
|
||||
|
||||
<p align="center">
|
||||
<a href="./README_en.md">English</a> |
|
||||
<a href="./README.md">简体中文</a> |
|
||||
<a href="./README_ja.md">日语</a>
|
||||
</p>
|
||||
|
||||
FastGPT は、LLM 上 に 構築 された 知識 ベースの Q&A システムで、すぐに 使 えるデータ 処理 とモデル 呼 び 出 し 機能 を 提供 し、Flow の 可視化 を 通 じてワークフローのオーケストレーションを 可能 にします!
|
||||
|
||||
</div>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://fastgpt.run/">
|
||||
<img height="21" src="https://img.shields.io/badge/在线使用-d4eaf7?style=flat-square&logo=spoj&logoColor=7d09f1" alt="cloud">
|
||||
</a>
|
||||
<a href="https://doc.fastgpt.run/docs/intro">
|
||||
<img height="21" src="https://img.shields.io/badge/相关文档-7d09f1?style=flat-square" alt="document">
|
||||
</a>
|
||||
<a href="https://doc.fastgpt.run/docs/development">
|
||||
<img height="21" src="https://img.shields.io/badge/本地开发-%23d4eaf7?style=flat-square&logo=xcode&logoColor=7d09f1" alt="development">
|
||||
</a>
|
||||
<a href="/#-%E7%9B%B8%E5%85%B3%E9%A1%B9%E7%9B%AE">
|
||||
<img height="21" src="https://img.shields.io/badge/相关项目-7d09f1?style=flat-square" alt="project">
|
||||
</a>
|
||||
<a href="https://github.com/labring/FastGPT/blob/main/LICENSE">
|
||||
<img height="21" src="https://img.shields.io/badge/License-Apache--2.0-ffffff?style=flat-square&labelColor=d4eaf7&color=7d09f1" alt="license">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409bd33f6d4
|
||||
|
||||
## 🛸 クラウドサービスの 利用
|
||||
|
||||
[fastgpt.run](https://fastgpt.run/)
|
||||
| | |
|
||||
| ---------------------------------- | ---------------------------------- |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|
||||
## 💡 機能
|
||||
|
||||
1. パワフルなビジュアルワークフロー:AI アプリケーションを 簡単 に 作成
|
||||
|
||||
- [x] デッキのシンプルモード - マニュアルアレンジ 不要
|
||||
- [x] ユーザ 対話事前 ガイダンス
|
||||
- [x] グローバル 変数
|
||||
- [x] ナレッジベース 検索
|
||||
- [x] 複数 の LLM モデルによる 対話
|
||||
- [x] テキストマジック - 構造化 データへの 変換
|
||||
- [x] HTTP による 拡張
|
||||
- [ ] on-the-fly HTTP モジュールのための 埋 め 込 みLaf
|
||||
- [x] 次 の 対話 ステップへの 指示
|
||||
- [x] ソースファイル 参照 の 追跡
|
||||
- [ ] カスタムファイルリーダー
|
||||
- [ ] モジュールをプラグインにパッケージして 再利用 する
|
||||
|
||||
2. 広範 なナレッジベースの 前処理
|
||||
|
||||
- [x] 複数 のナレッジベースの 再利用 と 混合
|
||||
- [x] チャンクの 変更 と 削除 を 追跡
|
||||
- [x] 手動入力、直接分割、QA 分割 インポートをサポート
|
||||
- [x] URL フェッチとバッチ CSV インポートをサポート
|
||||
- [x] ナレッジベースにユニークなベクトルモデルを 設定可能
|
||||
- [x] オリジナルファイルの 保存
|
||||
- [ ] ファイル 学習 エージェント
|
||||
|
||||
3. 複数 の 効果測定 チャンネル
|
||||
|
||||
- [x] シングルポイントナレッジベース 検索 テスト
|
||||
- [x] 対話中 のフィードバック 参照 と 修正 ・ 削除機能
|
||||
- [x] 完全 なコンテキストの 提示
|
||||
- [ ] 完全 なモジュール 中間値提示
|
||||
|
||||
4. OpenAPI
|
||||
|
||||
- [x] 補完 インターフェイス (GPT インターフェイスに 合 わせる)
|
||||
- [ ] ナレッジベース CRUD
|
||||
|
||||
5. オペレーション 機能
|
||||
|
||||
- [x] ログイン 不要 の 共有 ウィンドウ
|
||||
- [x] Iframe によるワンクリック 埋 め 込 み
|
||||
- [ ] 対話記録 への 統一 されたアクセス
|
||||
|
||||
## 👨💻 開発
|
||||
|
||||
プロジェクトの 技術 スタック:NextJs + TS + ChakraUI + Mongo + Postgres (Vector プラグイン)
|
||||
|
||||
- **⚡ デプロイ**
|
||||
|
||||
[](https://cloud.sealos.io/?openapp=system-fastdeploy%3FtemplateName%3Dfastgpt)
|
||||
|
||||
デプロイ 後、データベースをセットアップするので、2~4分待 ってください。基本設定 を 使 っているので、最初 は 少 し 遅 いかもしれません。
|
||||
|
||||
- [ローカル 開発入門](https://doc.fastgpt.run/docs/development)
|
||||
- [FastGPT のデプロイ](https://doc.fastgpt.run/docs/installation)
|
||||
- [システム 設定 ガイド](https://doc.fastgpt.run/docs/installation/reference)
|
||||
- [複数 モデルの 設定](https://doc.fastgpt.run/docs/installation/reference/models)
|
||||
- [バージョン 更新 とアップグレード](https://doc.fastgpt.run/docs/installation/upgrading)
|
||||
|
||||
<!-- ## :point_right: ロードマップ
|
||||
- [FastGPT ロードマップ](https://kjqvjse66l.feishu.cn/docx/RVUxdqE2WolDYyxEKATcM0XXnte) -->
|
||||
|
||||
<!-- ## 🏘️ コミュニティ
|
||||
|
||||
| コミュニティグループ | アシスタント |
|
||||
| ------------------------------------------------- | ---------------------------------------------- |
|
||||
|  |  | -->
|
||||
|
||||
## 👀 その 他
|
||||
|
||||
- [FastGPT FAQ](https://kjqvjse66l.feishu.cn/docx/HtrgdT0pkonP4kxGx8qcu6XDnGh)
|
||||
- [Docker 導入 チュートリアル 動画](https://www.bilibili.com/video/BV1jo4y147fT/)
|
||||
- [公式 アカウント 統合 ビデオチュートリアル](https://www.bilibili.com/video/BV1xh4y1t7fy/)
|
||||
- [FastGPT ナレッジベースデモ](https://www.bilibili.com/video/BV1Wo4y1p7i1/)
|
||||
|
||||
## 💪 関連 プロジェクト
|
||||
|
||||
- [Laf:サードパーティ 製 アプリケーションに 3 分 でクイックアクセス](https://github.com/labring/laf)
|
||||
- [Sealos:クラスタアプリケーションの 迅速 な 展開](https://github.com/labring/sealos)
|
||||
- [One API:マルチモデル 管理、Azure、Wenxin Yiyuan などをサポートします。](https://github.com/songquanpeng/one-api)
|
||||
- [TuShan:5 分 でバックエンド 管理 システムを 構築](https://github.com/msgbyte/tushan)
|
||||
|
||||
## 🤝 サードパーティエコシステム
|
||||
|
||||
- [luolinAI:すぐに 使 える 企業向 け WeChat ボット](https://github.com/luolin-ai/FastGPT-Enterprise-WeChatbot)
|
||||
|
||||
## 🌟 Star History
|
||||
|
||||
[](https://star-history.com/#labring/FastGPT&Date)
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"preset": "default",
|
||||
"rules": {
|
||||
"adjustedFullWidthPunctuation": ""
|
||||
}
|
||||
}
|
||||
"preset": "default",
|
||||
"rules": {
|
||||
"adjustedFullWidthPunctuation": ""
|
||||
}
|
||||
}
|
||||
BIN
docSite/assets/imgs/fastgpt-api-baseurl.png
Normal file
|
After Width: | Height: | Size: 186 KiB |
BIN
docSite/assets/imgs/share-auth1.jpg
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
docSite/assets/imgs/share-auth2.png
Normal file
|
After Width: | Height: | Size: 340 KiB |
BIN
docSite/assets/imgs/share-setlink.jpg
Normal file
|
After Width: | Height: | Size: 216 KiB |
BIN
docSite/assets/imgs/wechat1.png
Normal file
|
After Width: | Height: | Size: 210 KiB |
BIN
docSite/assets/imgs/wechat10.png
Normal file
|
After Width: | Height: | Size: 326 KiB |
BIN
docSite/assets/imgs/wechat2.png
Normal file
|
After Width: | Height: | Size: 344 KiB |
BIN
docSite/assets/imgs/wechat3.png
Normal file
|
After Width: | Height: | Size: 269 KiB |
BIN
docSite/assets/imgs/wechat4.png
Normal file
|
After Width: | Height: | Size: 209 KiB |
BIN
docSite/assets/imgs/wechat5.png
Normal file
|
After Width: | Height: | Size: 270 KiB |
BIN
docSite/assets/imgs/wechat6.png
Normal file
|
After Width: | Height: | Size: 261 KiB |
BIN
docSite/assets/imgs/wechat7.png
Normal file
|
After Width: | Height: | Size: 157 KiB |
BIN
docSite/assets/imgs/wechat8.png
Normal file
|
After Width: | Height: | Size: 227 KiB |
BIN
docSite/assets/imgs/wechat9.png
Normal file
|
After Width: | Height: | Size: 324 KiB |
@@ -59,7 +59,7 @@ Authorization 为 sk-aaabbbcccdddeeefffggghhhiiijjjkkk。model 为刚刚在 One
|
||||
|
||||
## 接入 FastGPT
|
||||
|
||||
修改 config.json 配置文件,在 VectorModels 中加入 chatglm2 和 M3E 模型:
|
||||
修改 config.json 配置文件,在 ChatModels 中加入 chatglm2, 在 VectorModels 中加入 M3E 模型:
|
||||
|
||||
```json
|
||||
"ChatModels": [
|
||||
|
||||
@@ -99,7 +99,7 @@ Authorization 为 sk-aaabbbcccdddeeefffggghhhiiijjjkkk。model 为刚刚在 One
|
||||
|
||||
## 接入 FastGPT
|
||||
|
||||
修改 config.json 配置文件,在 VectorModels 中加入 chatglm2 模型:
|
||||
修改 config.json 配置文件,在 ChatModels 中加入 chatglm2 模型:
|
||||
|
||||
```json
|
||||
"ChatModels": [
|
||||
@@ -107,10 +107,11 @@ Authorization 为 sk-aaabbbcccdddeeefffggghhhiiijjjkkk。model 为刚刚在 One
|
||||
{
|
||||
"model": "chatglm2",
|
||||
"name": "chatglm2",
|
||||
"maxToken": 8000,
|
||||
"price": 0,
|
||||
"quoteMaxToken": 4000,
|
||||
"maxTemperature": 1.2,
|
||||
"maxContext": 4000,
|
||||
"maxResponse": 4000,
|
||||
"quoteMaxToken": 2000,
|
||||
"maxTemperature": 1,
|
||||
"vision": false,
|
||||
"defaultSystemChatPrompt": ""
|
||||
}
|
||||
]
|
||||
|
||||
@@ -26,7 +26,7 @@ weight: 520
|
||||
"qaMaxProcess": 15, // QA 生成最大进程,结合数据库性能和 key 来设置
|
||||
"pgHNSWEfSearch": 100 // pg vector 索引参数,越大精度高但速度慢
|
||||
},
|
||||
"ChatModels": [
|
||||
"ChatModels": [ // 对话模型
|
||||
{
|
||||
"model": "gpt-3.5-turbo-1106",
|
||||
"name": "GPT35-1106",
|
||||
@@ -36,6 +36,7 @@ weight: 520
|
||||
"quoteMaxToken": 2000, // 最大引用内容长度
|
||||
"maxTemperature": 1.2, // 最大温度值
|
||||
"censor": false, // 是否开启敏感词过滤(商业版)
|
||||
"vision": false, // 支持图片输入
|
||||
"defaultSystemChatPrompt": ""
|
||||
},
|
||||
{
|
||||
@@ -47,6 +48,7 @@ weight: 520
|
||||
"quoteMaxToken": 8000,
|
||||
"maxTemperature": 1.2,
|
||||
"censor": false,
|
||||
"vision": false,
|
||||
"defaultSystemChatPrompt": ""
|
||||
},
|
||||
{
|
||||
@@ -58,10 +60,23 @@ weight: 520
|
||||
"quoteMaxToken": 4000,
|
||||
"maxTemperature": 1.2,
|
||||
"censor": false,
|
||||
"vision": false,
|
||||
"defaultSystemChatPrompt": ""
|
||||
},
|
||||
{
|
||||
"model": "gpt-4-vision-preview",
|
||||
"name": "GPT4-Vision",
|
||||
"maxContext": 128000,
|
||||
"maxResponse": 4000,
|
||||
"price": 0,
|
||||
"quoteMaxToken": 100000,
|
||||
"maxTemperature": 1.2,
|
||||
"censor": false,
|
||||
"vision": true,
|
||||
"defaultSystemChatPrompt": ""
|
||||
}
|
||||
],
|
||||
"QAModels": [
|
||||
"QAModels": [ // QA 生成模型
|
||||
{
|
||||
"model": "gpt-3.5-turbo-16k",
|
||||
"name": "GPT35-16k",
|
||||
@@ -70,14 +85,14 @@ weight: 520
|
||||
"price": 0
|
||||
}
|
||||
],
|
||||
"CQModels": [
|
||||
"CQModels": [ // 问题分类模型
|
||||
{
|
||||
"model": "gpt-3.5-turbo-1106",
|
||||
"name": "GPT35-1106",
|
||||
"maxContext": 16000,
|
||||
"maxResponse": 4000,
|
||||
"price": 0,
|
||||
"functionCall": true,
|
||||
"functionCall": true, // 是否支持function call, 不支持的模型需要设置为 false,会走提示词生成
|
||||
"functionPrompt": ""
|
||||
},
|
||||
{
|
||||
@@ -90,7 +105,7 @@ weight: 520
|
||||
"functionPrompt": ""
|
||||
}
|
||||
],
|
||||
"ExtractModels": [
|
||||
"ExtractModels": [ // 内容提取模型
|
||||
{
|
||||
"model": "gpt-3.5-turbo-1106",
|
||||
"name": "GPT35-1106",
|
||||
@@ -101,7 +116,7 @@ weight: 520
|
||||
"functionPrompt": ""
|
||||
}
|
||||
],
|
||||
"QGModels": [
|
||||
"QGModels": [ // 生成下一步指引
|
||||
{
|
||||
"model": "gpt-3.5-turbo-1106",
|
||||
"name": "GPT35-1106",
|
||||
@@ -110,7 +125,7 @@ weight: 520
|
||||
"price": 0
|
||||
}
|
||||
],
|
||||
"VectorModels": [
|
||||
"VectorModels": [ // 向量模型
|
||||
{
|
||||
"model": "text-embedding-ada-002",
|
||||
"name": "Embedding-2",
|
||||
@@ -123,13 +138,23 @@ weight: 520
|
||||
{
|
||||
"model": "tts-1",
|
||||
"name": "OpenAI TTS1",
|
||||
"price": 0
|
||||
},
|
||||
{
|
||||
"model": "tts-1-hd",
|
||||
"name": "OpenAI TTS1HD",
|
||||
"price": 0
|
||||
"price": 0,
|
||||
"baseUrl": "",
|
||||
"key": "",
|
||||
"voices": [
|
||||
{ "label": "Alloy", "value": "alloy", "bufferId": "openai-Alloy" },
|
||||
{ "label": "Echo", "value": "echo", "bufferId": "openai-Echo" },
|
||||
{ "label": "Fable", "value": "fable", "bufferId": "openai-Fable" },
|
||||
{ "label": "Onyx", "value": "onyx", "bufferId": "openai-Onyx" },
|
||||
{ "label": "Nova", "value": "nova", "bufferId": "openai-Nova" },
|
||||
{ "label": "Shimmer", "value": "shimmer", "bufferId": "openai-Shimmer" }
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"WhisperModel": {
|
||||
"model": "whisper-1",
|
||||
"name": "Whisper1",
|
||||
"price": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -54,11 +54,11 @@ git clone git@github.com:<github_username>/FastGPT.git
|
||||
|
||||
**环境变量**
|
||||
|
||||
复制.env.template 文件,在同级目录下生成一个.env.local 文件,修改.env.local 里内容才是有效的变量。变量说明见 .env.template
|
||||
复制`.env.template`文件,在同级目录下生成一个`.env.local` 文件,修改`.env.local` 里内容才是有效的变量。变量说明见 .env.template
|
||||
|
||||
**config 配置文件**
|
||||
|
||||
复制 data/config.json 文件,生成一个 data/config.local.json 配置文件,具体配置参数说明,可参考 [config 配置说明](/docs/development/configuration)
|
||||
复制 `data/config.json` 文件,生成一个 `data/config.local.json` 配置文件,具体配置参数说明,可参考 [config 配置说明](/docs/development/configuration)
|
||||
|
||||
**注意:json 配置文件不能包含注释,介绍中为了方便看才加入的注释**
|
||||
|
||||
|
||||
@@ -1,546 +0,0 @@
|
||||
---
|
||||
title: 'OpenAPI 使用(API Key 使用)'
|
||||
description: 'FastGPT OpenAPI 文档'
|
||||
icon: 'api'
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 512
|
||||
---
|
||||
|
||||
# 基本配置
|
||||
```
|
||||
baseUrl: "https://fastgpt.run/api"
|
||||
headers: {
|
||||
Authorization: "Bearer apikey"
|
||||
}
|
||||
```
|
||||
|
||||
# 如何获取 API Key
|
||||
|
||||
FastGPT 的 API Key 有 2 类,一类是全局通用的 key;一类是携带了 AppId 也就是有应用标记的 key。
|
||||
|
||||
| 通用key | 应用特定 key |
|
||||
| --------------------- | --------------------- |
|
||||
|  |  |
|
||||
|
||||
# 接口
|
||||
|
||||
## 发起对话
|
||||
|
||||
{{% alert icon="🤖 " context="success" %}}
|
||||
该接口 API Key 需使用应用特定的 key,否则会报错。
|
||||
|
||||
有些包的 BaseUrl 需要添加 `v1` 路径,有些不需要,建议都试一下。
|
||||
{{% /alert %}}
|
||||
|
||||
|
||||
对话接口兼容`GPT`的接口!如果你的项目使用的是标准的`GPT`官方接口,可以直接通过修改 `BaseUrl` 和 `Authorization` 来访问 FastGpt 应用。
|
||||
|
||||
请求参数说明
|
||||
- headers.Authorization: Bearer apikey
|
||||
- chatId: string | undefined 。
|
||||
- 为 undefined 时(不传入),不使用 FastGpt 提供的上下文功能,完全通过传入的 messages 构建上下文。 不会将你的记录存储到数据库中,你也无法在记录汇总中查阅到。
|
||||
- 为非空字符串时,意味着使用 chatId 进行对话,自动从 FastGpt 数据库取历史记录,并使用 messages 数组最后一个内容作为用户问题。(请自行确保 chatId 唯一,长度不限制)
|
||||
- messages: 结构与 [GPT接口](https://platform.openai.com/docs/api-reference/chat/object) 完全一致。
|
||||
- detail: 是否返回详细值(模块状态,响应的完整结果),`stream模式`下会通过event进行区分,`非stream模式`结果保存在responseData中。
|
||||
- variables: 变量内容,一个对象,会替换`{{key}}`变量。在`HTTP`模块中会发给接口,可作为身份凭证等标识。
|
||||
|
||||
**请求示例:**
|
||||
|
||||
```bash
|
||||
curl --location --request POST 'https://fastgpt.run/api/v1/chat/completions' \
|
||||
--header 'Authorization: Bearer apikey' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"chatId":"111",
|
||||
"stream":false,
|
||||
"detail": false,
|
||||
"variables": {
|
||||
"cTime": "2022/2/2 22:22"
|
||||
},
|
||||
"messages": [
|
||||
{
|
||||
"content": "导演是谁",
|
||||
"role": "user"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
|
||||
{{< tabs tabTotal="3" >}}
|
||||
{{< tab tabName="detail=false 响应" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
```bash
|
||||
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":""},"index":0,"finish_reason":null}]}
|
||||
|
||||
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":"电"},"index":0,"finish_reason":null}]}
|
||||
|
||||
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":"影"},"index":0,"finish_reason":null}]}
|
||||
|
||||
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":"《"},"index":0,"finish_reason":null}]}
|
||||
```
|
||||
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab tabName="detail=true 响应" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
```bash
|
||||
event: answer
|
||||
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":""},"index":0,"finish_reason":null}]}
|
||||
|
||||
event: answer
|
||||
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":"电"},"index":0,"finish_reason":null}]}
|
||||
|
||||
event: answer
|
||||
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":"影"},"index":0,"finish_reason":null}]}
|
||||
|
||||
event: answer
|
||||
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":"《"},"index":0,"finish_reason":null}]}
|
||||
|
||||
event: answer
|
||||
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":"铃"},"index":0,"finish_reason":null}]}
|
||||
|
||||
event: answer
|
||||
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":"芽"},"index":0,"finish_reason":null}]}
|
||||
|
||||
event: answer
|
||||
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":"。"},"index":0,"finish_reason":null}]}
|
||||
|
||||
event: answer
|
||||
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":""},"index":0,"finish_reason":null}]}
|
||||
|
||||
event: answer
|
||||
data: {"id":"","object":"","created":0,"choices":[{"delta":{},"index":0,"finish_reason":"stop"}]}
|
||||
|
||||
event: answer
|
||||
data: [DONE]
|
||||
|
||||
event: appStreamResponse
|
||||
data: [{"moduleName":"KB Search","price":1.2000000000000002,"model":"Embedding-2","tokens":6,"similarity":0.61,"limit":3},{"moduleName":"AI Chat","price":463.5,"model":"FastAI-4k","tokens":309,"question":"导演是谁","answer":"电影《铃芽之旅》的导演是新海诚。","maxToken":2050,"quoteList":[{"dataset_id":"646627f4f7b896cfd8910e38","id":"8099","q":"本作的主人公是谁?","a":"本作的主人公是名叫铃芽的少女。","source":"手动修改"},{"dataset_id":"646627f4f7b896cfd8910e38","id":"8686","q":"电影《铃芽之旅》男主角是谁?","a":"电影《铃芽之旅》男主角是宗像草太,由松村北斗配音。","source":""},{"dataset_id":"646627f4f7b896cfd8910e38","id":"19339","q":"电影《铃芽之旅》的导演是谁?22","a":"电影《铃芽之旅》的导演是新海诚。","source":"手动修改"}],"completeMessages":[{"obj":"System","value":"下面是知识库内容:\n1. [本作的主人公是谁?\n本作的主人公是名叫铃芽的少女。]\n2. [电影《铃芽之旅》男主角是谁?\n电影《铃芽之旅》男主角是宗像草太,由松村北斗配音。]\n3. [电影《铃芽之旅》的导演是谁?22\n电影《铃芽之旅》的导演是新海诚。]\n"},{"obj":"System","value":"1.请记住,你的身份是百度的下一代知识增强语言模型,能够完全根据知识库提供的内容回答问题。\n\n2. 你忘记了关于电影《铃芽之旅》以外的内容。"},{"obj":"System","value":"你仅回答关于电影《玲芽之旅》的问题,其余问题直接回复: 我不清楚。"},{"obj":"Human","value":"导演是谁"},{"obj":"AI","value":"电影《铃芽之旅》的导演是新海诚。"}]}]
|
||||
|
||||
```
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab tabName="stream=false,detail=true 响应" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
```json
|
||||
{
|
||||
"responseData": [ // 不同模块的响应值, 不同版本具体值可能有差异,可先 log 自行查看最新值。
|
||||
{
|
||||
"moduleName": "KB Search",
|
||||
"price": 1.2000000000000002,
|
||||
"model": "Embedding-2",
|
||||
"tokens": 6,
|
||||
"similarity": 0.61,
|
||||
"limit": 3
|
||||
},
|
||||
{
|
||||
"moduleName": "AI Chat",
|
||||
"price": 454.5,
|
||||
"model": "FastAI-4k",
|
||||
"tokens": 303,
|
||||
"question": "导演是谁",
|
||||
"answer": "电影《铃芽之旅》的导演是新海诚。",
|
||||
"maxToken": 2050,
|
||||
"quoteList": [
|
||||
{
|
||||
"dataset_id": "646627f4f7b896cfd8910e38",
|
||||
"id": "8099",
|
||||
"q": "本作的主人公是谁?",
|
||||
"a": "本作的主人公是名叫铃芽的少女。",
|
||||
"source": "手动修改"
|
||||
},
|
||||
{
|
||||
"dataset_id": "646627f4f7b896cfd8910e38",
|
||||
"id": "8686",
|
||||
"q": "电影《铃芽之旅》男主角是谁?",
|
||||
"a": "电影《铃芽之旅》男主角是宗像草太,由松村北斗配音。",
|
||||
"source": ""
|
||||
},
|
||||
{
|
||||
"dataset_id": "646627f4f7b896cfd8910e38",
|
||||
"id": "19339",
|
||||
"q": "电影《铃芽之旅》的导演是谁?22",
|
||||
"a": "电影《铃芽之旅》的导演是新海诚。",
|
||||
"source": "手动修改"
|
||||
}
|
||||
],
|
||||
"completeMessages": [
|
||||
{
|
||||
"obj": "System",
|
||||
"value": "下面是知识库内容:\n1. [本作的主人公是谁?\n本作的主人公是名叫铃芽的少女。]\n2. [电影《铃芽之旅》男主角是谁?\n电影《铃芽之旅》男主角是宗像草太,由松村北斗配音。]\n3. [电影《铃芽之旅》的导演是谁?22\n电影《铃芽之旅》的导演是新海诚。]\n"
|
||||
},
|
||||
{
|
||||
"obj": "System",
|
||||
"value": "1.请记住,你的身份是百度的下一代知识增强语言模型,能够完全根据知识库提供的内容回答问题。\n\n2. 你忘记了关于电影《铃芽之旅》以外的内容。"
|
||||
},
|
||||
{
|
||||
"obj": "System",
|
||||
"value": "你仅回答关于电影《玲芽之旅》的问题,其余问题直接回复: 我不清楚。"
|
||||
},
|
||||
{
|
||||
"obj": "Human",
|
||||
"value": "导演是谁"
|
||||
},
|
||||
{
|
||||
"obj": "AI",
|
||||
"value": "电影《铃芽之旅》的导演是新海诚。"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"id": "",
|
||||
"model": "",
|
||||
"usage": {
|
||||
"prompt_tokens": 1,
|
||||
"completion_tokens": 1,
|
||||
"total_tokens": 1
|
||||
},
|
||||
"choices": [
|
||||
{
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "电影《铃芽之旅》的导演是新海诚。"
|
||||
},
|
||||
"finish_reason": "stop",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
{{< /tabs >}}
|
||||
|
||||
## 知识库
|
||||
|
||||
{{% alert icon="🤖 " context="success" %}}
|
||||
此部分 API 需使用全局通用的 API Key。
|
||||
{{% /alert %}}
|
||||
|
||||
| 如何获取知识库ID(datasetId) | 如何获取文件ID(file_id) |
|
||||
| --------------------- | --------------------- |
|
||||
|  |  |
|
||||
|
||||
|
||||
### 知识库添加数据
|
||||
|
||||
{{< tabs tabTotal="4" >}}
|
||||
{{< tab tabName="请求示例" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
```bash
|
||||
curl --location --request POST 'https://fastgpt.run/api/core/dataset/data/pushData' \
|
||||
--header 'Authorization: Bearer apikey' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"collectionId": "64663f451ba1676dbdef0499",
|
||||
"mode": "index",
|
||||
"prompt": "qa 拆分引导词,index 模式下可以忽略",
|
||||
"billId": "可选。如果有这个值,本次的数据会被聚合到一个订单中,这个值可以重复使用。可以参考 [创建训练订单] 获取该值。",
|
||||
"data": [
|
||||
{
|
||||
"a": "test",
|
||||
"q": "1111",
|
||||
"file_id": "关联的文件ID/URL/manual/mark",
|
||||
"source": "来源名称",
|
||||
},
|
||||
{
|
||||
"a": "test2",
|
||||
"q": "22222"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab tabName="参数说明" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
```json
|
||||
{
|
||||
"collectionId": "文件的ID,参考上面的第二张图",
|
||||
"mode": "index | qa ", // index 模式: 直接将 q 转成向量存起来,a 直接入库。qa 模式: 只关注 data 里的 q,将 q 丢给大模型,让其根据 prompt 拆分成 qa 问答对。
|
||||
"prompt": "拆分提示词,需严格按照模板,建议不要传入。",
|
||||
"data": [
|
||||
{
|
||||
"q": "生成索引的内容,index 模式下最大 tokens 为3000,建议不超过 1000",
|
||||
"a": "预期回答/补充",
|
||||
"file_id": "如果推送数据到手动录入,这里可以留空; 如果希望关联到某个文件中,需要填写对应文件的ID; 如果希望加入到手动标注中,可设置为: mark",
|
||||
},
|
||||
{
|
||||
"q": "生成索引的内容,qa 模式下最大 tokens 为10000,建议 8000 左右",
|
||||
"a": "预期回答/补充"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab tabName="响应例子" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"statusText": "",
|
||||
"data": {
|
||||
"insertLen": 1, // 最终插入成功的数量
|
||||
"overToken": [], // 超出 token 的
|
||||
"fileIdInvalid": [ // file_id 无效的
|
||||
{
|
||||
"a": "飞飞dsaf飞",
|
||||
"q": "测试是32否收到",
|
||||
"file_id": "32dwe"
|
||||
}
|
||||
],
|
||||
"error": [] // 其他错误
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab tabName="QA Prompt 模板" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
{{theme}} 里的内容可以换成数据的主题。默认为:它们可能包含多个主题内容
|
||||
|
||||
```
|
||||
我会给你一段文本,{{theme}},学习它们,并整理学习成果,要求为:
|
||||
1. 提出最多 25 个问题。
|
||||
2. 给出每个问题的答案。
|
||||
3. 答案要详细完整,答案可以包含普通文字、链接、代码、表格、公示、媒体链接等 markdown 元素。
|
||||
4. 按格式返回多个问题和答案:
|
||||
|
||||
Q1: 问题。
|
||||
A1: 答案。
|
||||
Q2:
|
||||
A2:
|
||||
……
|
||||
|
||||
我的文本:"""{{text}}"""
|
||||
```
|
||||
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
|
||||
{{< /tabs >}}
|
||||
|
||||
|
||||
### 搜索测试
|
||||
|
||||
{{< tabs tabTotal="2" >}}
|
||||
{{< tab tabName="请求示例" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
```bash
|
||||
curl --location --request POST 'https://fastgpt.run/api/core/dataset/searchTest' \
|
||||
--header 'Authorization: Bearer apiKey' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"datasetId": "知识库的ID",
|
||||
"text": "导演是谁"
|
||||
}'
|
||||
```
|
||||
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab tabName="响应示例" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
返回 top12 结果
|
||||
|
||||
```bash
|
||||
{
|
||||
"code": 200,
|
||||
"statusText": "",
|
||||
"data": [
|
||||
{
|
||||
"id": "5613327",
|
||||
"q": "该人有获奖情况吗?",
|
||||
"a": "该人获得过2020/07全国大学生服务外包大赛国家一等奖和2021/05国家创新创业计划立项的获奖情况。",
|
||||
"source": "余金隆简历.pdf",
|
||||
"score": 0.41556452839298963
|
||||
},
|
||||
......
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
|
||||
{{< /tabs >}}
|
||||
|
||||
## 订单
|
||||
|
||||
### 创建训练订单
|
||||
|
||||
**请求示例**
|
||||
|
||||
```bash
|
||||
curl --location --request POST 'https://fastgpt.run/api/support/wallet/bill/createTrainingBill' \
|
||||
--header 'Authorization: Bearer {{apikey}}' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw ''
|
||||
```
|
||||
|
||||
**响应结果**
|
||||
|
||||
data 为 billId,可用于 api 添加数据时进行账单聚合。
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"statusText": "",
|
||||
"message": "",
|
||||
"data": "65112ab717c32018f4156361"
|
||||
}
|
||||
```
|
||||
|
||||
## 免登录分享链接校验(内测中)
|
||||
|
||||
免登录链接配置中,增加了`凭证校验服务器`后,使用分享链接时会向服务器发起请求,校验链接是否可用,并在每次对话结束后,向服务器发送对话结果。下面以`host`来表示`凭证校验服务器`。服务器接口仅需返回是否校验成功即可,不需要返回其他数据,格式如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "错误提示"
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
### 分享链接中增加额外 query
|
||||
|
||||
增加一个 query: authToken。例如:
|
||||
|
||||
原始的链接:https://fastgpt.run/chat/share?shareId=648aaf5ae121349a16d62192
|
||||
完整链接: https://fastgpt.run/chat/share?shareId=648aaf5ae121349a16d62192&authToken=userid12345
|
||||
|
||||
发出校验请求时候,会在`body`中携带 token={{authToken}} 的参数。
|
||||
|
||||
### 初始化校验
|
||||
|
||||
**FastGPT 发出的请求**
|
||||
|
||||
```bash
|
||||
curl --location --request POST '{{host}}/shareAuth/init' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"token": "sintdolore"
|
||||
}'
|
||||
```
|
||||
|
||||
### 对话前校验
|
||||
|
||||
**FastGPT 发出的请求**
|
||||
|
||||
```bash
|
||||
curl --location --request POST '{{host}}/shareAuth/start' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"token": "sintdolore",
|
||||
"question": "用户问题",
|
||||
}'
|
||||
```
|
||||
|
||||
### 对话结果上报
|
||||
|
||||
**FastGPT 发出的请求**
|
||||
|
||||
```bash
|
||||
curl --location --request POST '{{host}}/shareAuth/finish' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"token": "sint dolore",
|
||||
"responseData": [
|
||||
{
|
||||
"moduleName": "KB Search",
|
||||
"price": 1.2000000000000002,
|
||||
"model": "Embedding-2",
|
||||
"tokens": 6,
|
||||
"similarity": 0.61,
|
||||
"limit": 3
|
||||
},
|
||||
{
|
||||
"moduleName": "AI Chat",
|
||||
"price": 454.5,
|
||||
"model": "FastAI-4k",
|
||||
"tokens": 303,
|
||||
"question": "导演是谁",
|
||||
"answer": "电影《铃芽之旅》的导演是新海诚。",
|
||||
"maxToken": 2050,
|
||||
"quoteList": [
|
||||
{
|
||||
"dataset_id": "646627f4f7b896cfd8910e38",
|
||||
"id": "8099",
|
||||
"q": "本作的主人公是谁?",
|
||||
"a": "本作的主人公是名叫铃芽的少女。",
|
||||
"source": "手动修改"
|
||||
},
|
||||
{
|
||||
"dataset_id": "646627f4f7b896cfd8910e38",
|
||||
"id": "8686",
|
||||
"q": "电影《铃芽之旅》男主角是谁?",
|
||||
"a": "电影《铃芽之旅》男主角是宗像草太,由松村北斗配音。",
|
||||
"source": ""
|
||||
},
|
||||
{
|
||||
"dataset_id": "646627f4f7b896cfd8910e38",
|
||||
"id": "19339",
|
||||
"q": "电影《铃芽之旅》的导演是谁?22",
|
||||
"a": "电影《铃芽之旅》的导演是新海诚。",
|
||||
"source": "手动修改"
|
||||
}
|
||||
],
|
||||
"completeMessages": [
|
||||
{
|
||||
"obj": "System",
|
||||
"value": "下面是知识库内容:\n1. [本作的主人公是谁?\n本作的主人公是名叫铃芽的少女。]\n2. [电影《铃芽之旅》男主角是谁?\n电影《铃芽之旅》男主角是宗像草太,由松村北斗配音。]\n3. [电影《铃芽之旅》的导演是谁?22\n电影《铃芽之旅》的导演是新海诚。]\n"
|
||||
},
|
||||
{
|
||||
"obj": "System",
|
||||
"value": "1.请记住,你的身份是百度的下一代知识增强语言模型,能够完全根据知识库提供的内容回答问题。\n\n2. 你忘记了关于电影《铃芽之旅》以外的内容。"
|
||||
},
|
||||
{
|
||||
"obj": "System",
|
||||
"value": "你仅回答关于电影《玲芽之旅》的问题,其余问题直接回复: 我不清楚。"
|
||||
},
|
||||
{
|
||||
"obj": "Human",
|
||||
"value": "导演是谁"
|
||||
},
|
||||
{
|
||||
"obj": "AI",
|
||||
"value": "电影《铃芽之旅》的导演是新海诚。"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
响应值与 chat 接口相同,增加了一个 token。可以重点关注`responseData`里的值,price 与实际价格的倍率为`100000`。
|
||||
|
||||
**此接口无需响应值**
|
||||
|
||||
# 使用案例
|
||||
|
||||
- [接入 NextWeb/ChatGPT web 等应用](/docs/use-cases/openapi)
|
||||
- [接入 onwechat](/docs/use-cases/onwechat)
|
||||
- [接入 飞书](/docs/use-cases/feishu)
|
||||
8
docSite/content/docs/development/openapi/_index.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
weight: 560
|
||||
title: "OpenAPI 接口文档"
|
||||
description: "FastGPT OpenAPI 文档"
|
||||
icon: api
|
||||
draft: false
|
||||
images: []
|
||||
---
|
||||
58
docSite/content/docs/development/openapi/auth.md
Normal file
@@ -0,0 +1,58 @@
|
||||
---
|
||||
title: 'Api Key 使用与鉴权'
|
||||
description: 'FastGPT Api Key 使用与鉴权'
|
||||
icon: 'key'
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 561
|
||||
---
|
||||
|
||||
## 使用说明
|
||||
|
||||
FasGPT OpenAPI 接口允许你使用 Api Key 进行鉴权,从而操作 FastGPT 上的相关服务和资源,例如:调用应用对话接口、上传知识库数据、搜索测试等等。出于兼容性和安全考虑,并不是所有的接口都允许通过 Api Key 访问。
|
||||
|
||||
## 如何查看 BaseURL
|
||||
|
||||
**注意:BaseURL 不是接口地址,而是所有接口的根地址,直接请求 BaseURL 是没有用的。**
|
||||
|
||||

|
||||
|
||||
## 如何获取 Api Key
|
||||
|
||||
FastGPT 的 API Key **有 2 类**,一类是全局通用的 key (无法直接调用应用对话);一类是携带了 AppId 也就是有应用标记的 key (可直接调用应用对话)。
|
||||
|
||||
我们建议,仅操作应用或者对话的相关接口使用 `应用特定key`,其他接口使用 `通用key`。
|
||||
|
||||
| 通用key | 应用特定 key |
|
||||
| --------------------- | --------------------- |
|
||||
|  |  |
|
||||
|
||||
## 基本配置
|
||||
|
||||
OpenAPI 中,所有的接口都通过 Header.Authorization 进行鉴权。
|
||||
|
||||
```
|
||||
baseUrl: "https://fastgpt.run/api"
|
||||
headers: {
|
||||
Authorization: "Bearer {{apikey}}"
|
||||
}
|
||||
```
|
||||
|
||||
**发起应用对话示例**
|
||||
|
||||
```sh
|
||||
curl --location --request POST 'https://fastgpt.run/api/v1/chat/completions' \
|
||||
--header 'Authorization: Bearer fastgpt-xxxxxx' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"chatId": "111",
|
||||
"stream": false,
|
||||
"detail": false,
|
||||
"messages": [
|
||||
{
|
||||
"content": "导演是谁",
|
||||
"role": "user"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
254
docSite/content/docs/development/openapi/chat.md
Normal file
@@ -0,0 +1,254 @@
|
||||
---
|
||||
title: '对话接口'
|
||||
description: 'FastGPT OpenAPI 对话接口'
|
||||
icon: 'chat'
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 562
|
||||
---
|
||||
|
||||
## 发起对话
|
||||
|
||||
{{% alert icon="🤖 " context="success" %}}
|
||||
该接口的 API Key 需使用`应用特定的 key`,否则会报错。
|
||||
|
||||
有些包调用时,`BaseUrl`需要添加`v1`路径,有些不需要,如果出现404情况,可补充`v1`重试。
|
||||
{{% /alert %}}
|
||||
|
||||
|
||||
**对话接口兼容`GPT`的接口!如果你的项目使用的是标准的`GPT`官方接口,可以直接通过修改`BaseUrl`和 `Authorization`来访问 FastGpt 应用。**
|
||||
|
||||
## 请求
|
||||
|
||||
{{< tabs tabTotal="2" >}}
|
||||
{{< tab tabName="请求示例" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
```bash
|
||||
curl --location --request POST 'https://fastgpt.run/api/v1/chat/completions' \
|
||||
--header 'Authorization: Bearer fastgpt-xxxxxx' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"chatId": "abcd",
|
||||
"stream": false,
|
||||
"detail": false,
|
||||
"variables": {
|
||||
"uid": "asdfadsfasfd2323",
|
||||
"name": "张三"
|
||||
},
|
||||
"messages": [
|
||||
{
|
||||
"content": "导演是谁",
|
||||
"role": "user"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab tabName="detail=true 响应" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
{{% alert context="info" %}}
|
||||
- headers.Authorization: Bearer {{apikey}}
|
||||
- chatId: string | undefined 。
|
||||
- 为 `undefined` 时(不传入),不使用 FastGpt 提供的上下文功能,完全通过传入的 messages 构建上下文。 不会将你的记录存储到数据库中,你也无法在记录汇总中查阅到。
|
||||
- 为`非空字符串`时,意味着使用 chatId 进行对话,自动从 FastGpt 数据库取历史记录,并使用 messages 数组最后一个内容作为用户问题。请自行确保 chatId 唯一,长度小于250,通常可以是自己系统的对话框ID。
|
||||
- messages: 结构与 [GPT接口](https://platform.openai.com/docs/api-reference/chat/object) 完全一致。
|
||||
- detail: 是否返回中间值(模块状态,响应的完整结果等),`stream模式`下会通过`event`进行区分,`非stream模式`结果保存在`responseData`中。
|
||||
- variables: 模块变量,一个对象,会替换模块中,输入框内容里的`{{key}}`
|
||||
{{% /alert %}}
|
||||
|
||||
|
||||
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
{{< /tabs >}}
|
||||
|
||||
## 响应
|
||||
|
||||
{{< tabs tabTotal="4" >}}
|
||||
{{< tab tabName="detail=false,stream=false 响应" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "adsfasf",
|
||||
"model": "",
|
||||
"usage": {
|
||||
"prompt_tokens": 1,
|
||||
"completion_tokens": 1,
|
||||
"total_tokens": 1
|
||||
},
|
||||
"choices": [
|
||||
{
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "电影《铃芽之旅》的导演是新海诚。"
|
||||
},
|
||||
"finish_reason": "stop",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab tabName="detail=false,stream=true 响应" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
|
||||
```bash
|
||||
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":""},"index":0,"finish_reason":null}]}
|
||||
|
||||
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":"电"},"index":0,"finish_reason":null}]}
|
||||
|
||||
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":"影"},"index":0,"finish_reason":null}]}
|
||||
|
||||
data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":"《"},"index":0,"finish_reason":null}]}
|
||||
```
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab tabName="detail=true,stream=false 响应" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
```json
|
||||
{
|
||||
"responseData": [ // 不同模块的响应值, 不同版本具体值可能有差异,可先 log 自行查看最新值。
|
||||
{
|
||||
"moduleName": "Dataset Search",
|
||||
"price": 1.2000000000000002,
|
||||
"model": "Embedding-2",
|
||||
"tokens": 6,
|
||||
"similarity": 0.61,
|
||||
"limit": 3
|
||||
},
|
||||
{
|
||||
"moduleName": "AI Chat",
|
||||
"price": 454.5,
|
||||
"model": "FastAI-4k",
|
||||
"tokens": 303,
|
||||
"question": "导演是谁",
|
||||
"answer": "电影《铃芽之旅》的导演是新海诚。",
|
||||
"maxToken": 2050,
|
||||
"quoteList": [
|
||||
{
|
||||
"dataset_id": "646627f4f7b896cfd8910e38",
|
||||
"id": "8099",
|
||||
"q": "本作的主人公是谁?",
|
||||
"a": "本作的主人公是名叫铃芽的少女。",
|
||||
"source": "手动修改"
|
||||
},
|
||||
{
|
||||
"dataset_id": "646627f4f7b896cfd8910e38",
|
||||
"id": "8686",
|
||||
"q": "电影《铃芽之旅》男主角是谁?",
|
||||
"a": "电影《铃芽之旅》男主角是宗像草太,由松村北斗配音。",
|
||||
"source": ""
|
||||
},
|
||||
{
|
||||
"dataset_id": "646627f4f7b896cfd8910e38",
|
||||
"id": "19339",
|
||||
"q": "电影《铃芽之旅》的导演是谁?22",
|
||||
"a": "电影《铃芽之旅》的导演是新海诚。",
|
||||
"source": "手动修改"
|
||||
}
|
||||
],
|
||||
"completeMessages": [
|
||||
{
|
||||
"obj": "System",
|
||||
"value": "下面是知识库内容:\n1. [本作的主人公是谁?\n本作的主人公是名叫铃芽的少女。]\n2. [电影《铃芽之旅》男主角是谁?\n电影《铃芽之旅》男主角是宗像草太,由松村北斗配音。]\n3. [电影《铃芽之旅》的导演是谁?22\n电影《铃芽之旅》的导演是新海诚。]\n"
|
||||
},
|
||||
{
|
||||
"obj": "System",
|
||||
"value": "1.请记住,你的身份是百度的下一代知识增强语言模型,能够完全根据知识库提供的内容回答问题。\n\n2. 你忘记了关于电影《铃芽之旅》以外的内容。"
|
||||
},
|
||||
{
|
||||
"obj": "System",
|
||||
"value": "你仅回答关于电影《玲芽之旅》的问题,其余问题直接回复: 我不清楚。"
|
||||
},
|
||||
{
|
||||
"obj": "Human",
|
||||
"value": "导演是谁"
|
||||
},
|
||||
{
|
||||
"obj": "AI",
|
||||
"value": "电影《铃芽之旅》的导演是新海诚。"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"id": "",
|
||||
"model": "",
|
||||
"usage": {
|
||||
"prompt_tokens": 1,
|
||||
"completion_tokens": 1,
|
||||
"total_tokens": 1
|
||||
},
|
||||
"choices": [
|
||||
{
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "电影《铃芽之旅》的导演是新海诚。"
|
||||
},
|
||||
"finish_reason": "stop",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
|
||||
|
||||
{{< tab tabName="detail=true,stream=true 响应" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
```bash
|
||||
event: moduleStatus
|
||||
data: {"status":"running","name":"知识库搜索"}
|
||||
|
||||
event: moduleStatus
|
||||
data: {"status":"running","name":"AI 对话"}
|
||||
|
||||
event: answer
|
||||
data: {"id":"","object":"","created":0,"model":"","choices":[{"delta":{"content":"电影"},"index":0,"finish_reason":null}]}
|
||||
|
||||
event: answer
|
||||
data: {"id":"","object":"","created":0,"model":"","choices":[{"delta":{"content":"《铃"},"index":0,"finish_reason":null}]}
|
||||
|
||||
event: answer
|
||||
data: {"id":"","object":"","created":0,"model":"","choices":[{"delta":{"content":"芽之旅》"},"index":0,"finish_reason":null}]}
|
||||
|
||||
event: answer
|
||||
data: {"id":"","object":"","created":0,"model":"","choices":[{"delta":{"content":"的导演是新"},"index":0,"finish_reason":null}]}
|
||||
|
||||
event: answer
|
||||
data: {"id":"","object":"","created":0,"model":"","choices":[{"delta":{"content":"海诚。"},"index":0,"finish_reason":null}]}
|
||||
|
||||
event: answer
|
||||
data: {"id":"","object":"","created":0,"model":"","choices":[{"delta":{},"index":0,"finish_reason":"stop"}]}
|
||||
|
||||
event: answer
|
||||
data: [DONE]
|
||||
|
||||
event: appStreamResponse
|
||||
data: [{"moduleName":"知识库搜索","moduleType":"datasetSearchNode","runningTime":1.78},{"question":"导演是谁","quoteList":[{"id":"654f2e49b64caef1d9431e8b","q":"电影《铃芽之旅》的导演是谁?","a":"电影《铃芽之旅》的导演是新海诚!","indexes":[{"type":"qa","dataId":"3515487","text":"电影《铃芽之旅》的导演是谁?","_id":"654f2e49b64caef1d9431e8c","defaultIndex":true}],"datasetId":"646627f4f7b896cfd8910e38","collectionId":"653279b16cd42ab509e766e8","sourceName":"data (81).csv","sourceId":"64fd3b6423aa1307b65896f6","score":0.8935586214065552},{"id":"6552e14c50f4a2a8e632af11","q":"导演是谁?","a":"电影《铃芽之旅》的导演是新海诚。","indexes":[{"defaultIndex":true,"type":"qa","dataId":"3644565","text":"导演是谁?\n电影《铃芽之旅》的导演是新海诚。","_id":"6552e14dde5cc7ba3954e417"}],"datasetId":"646627f4f7b896cfd8910e38","collectionId":"653279b16cd42ab509e766e8","sourceName":"data (81).csv","sourceId":"64fd3b6423aa1307b65896f6","score":0.8890955448150635},{"id":"654f34a0b64caef1d946337e","q":"本作的主人公是谁?","a":"本作的主人公是名叫铃芽的少女。","indexes":[{"type":"qa","dataId":"3515541","text":"本作的主人公是谁?","_id":"654f34a0b64caef1d946337f","defaultIndex":true}],"datasetId":"646627f4f7b896cfd8910e38","collectionId":"653279b16cd42ab509e766e8","sourceName":"data (81).csv","sourceId":"64fd3b6423aa1307b65896f6","score":0.8738770484924316},{"id":"654f3002b64caef1d944207a","q":"电影《铃芽之旅》男主角是谁?","a":"电影《铃芽之旅》男主角是宗像草太,由松村北斗配音。","indexes":[{"type":"qa","dataId":"3515538","text":"电影《铃芽之旅》男主角是谁?","_id":"654f3002b64caef1d944207b","defaultIndex":true}],"datasetId":"646627f4f7b896cfd8910e38","collectionId":"653279b16cd42ab509e766e8","sourceName":"data (81).csv","sourceId":"64fd3b6423aa1307b65896f6","score":0.8607980012893677},{"id":"654f2fc8b64caef1d943fd46","q":"电影《铃芽之旅》的编剧是谁?","a":"新海诚是本片的编剧。","indexes":[{"defaultIndex":true,"type":"qa","dataId":"3515550","text":"电影《铃芽之旅》的编剧是谁?22","_id":"654f2fc8b64caef1d943fd47"}],"datasetId":"646627f4f7b896cfd8910e38","collectionId":"653279b16cd42ab509e766e8","sourceName":"data (81).csv","sourceId":"64fd3b6423aa1307b65896f6","score":0.8468944430351257}],"moduleName":"AI 对话","moduleType":"chatNode","runningTime":1.86}]
|
||||
```
|
||||
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
{{< /tabs >}}
|
||||
|
||||
|
||||
## 使用案例
|
||||
|
||||
- [接入 NextWeb/ChatGPT web 等应用](/docs/use-cases/openapi)
|
||||
- [接入 onwechat](/docs/use-cases/onwechat)
|
||||
- [接入 飞书](/docs/use-cases/feishu)
|
||||
216
docSite/content/docs/development/openapi/dataset.md
Normal file
@@ -0,0 +1,216 @@
|
||||
---
|
||||
title: '知识库接口'
|
||||
description: 'FastGPT OpenAPI 知识库接口'
|
||||
icon: 'dataset'
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 563
|
||||
---
|
||||
|
||||
| 如何获取知识库ID(datasetId) | 如何获取文件集合ID(collection_id) |
|
||||
| --------------------- | --------------------- |
|
||||
|  |  |
|
||||
|
||||
|
||||
|
||||
## 创建训练订单
|
||||
|
||||
**请求示例**
|
||||
|
||||
```bash
|
||||
curl --location --request POST 'https://fastgpt.run/api/support/wallet/bill/createTrainingBill' \
|
||||
--header 'Authorization: Bearer {{apikey}}' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"name": "可选,自定义订单名称,例如:文档训练-fastgpt.docx"
|
||||
}'
|
||||
```
|
||||
|
||||
**响应结果**
|
||||
|
||||
data 为 billId,可用于添加知识库数据时进行账单聚合。
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"statusText": "",
|
||||
"message": "",
|
||||
"data": "65112ab717c32018f4156361"
|
||||
}
|
||||
```
|
||||
|
||||
## 知识库添加数据
|
||||
|
||||
{{< tabs tabTotal="4" >}}
|
||||
{{< tab tabName="请求示例" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
```bash
|
||||
curl --location --request POST 'https://fastgpt.run/api/core/dataset/data/pushData' \
|
||||
--header 'Authorization: Bearer apikey' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"collectionId": "64663f451ba1676dbdef0499",
|
||||
"mode": "chunk",
|
||||
"prompt": "可选。qa 拆分引导词,chunk 模式下忽略",
|
||||
"billId": "可选。如果有这个值,本次的数据会被聚合到一个订单中,这个值可以重复使用。可以参考 [创建训练订单] 获取该值。",
|
||||
"data": [
|
||||
{
|
||||
"q": "你是谁?",
|
||||
"a": "我是FastGPT助手"
|
||||
},
|
||||
{
|
||||
"q": "你会什么?",
|
||||
"a": "我什么都会",
|
||||
"indexes": [{
|
||||
"type":"custom",
|
||||
"text":"你好"
|
||||
}]
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab tabName="参数说明" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
需要先了解 FastGPT 的多路索引概念:
|
||||
|
||||
在 FastGPT 中,你可以为一组数据创建多个索引,如果不指定索引,则系统会自动取对应的 chunk 作为索引。例如前面的请求示例中:
|
||||
|
||||
`q:你是谁?a:我是FastGPT助手` 它的`indexes`属性为空,意味着不自定义索引,而是使用默认的索引(你是谁?\n我是FastGPT助手)。
|
||||
|
||||
在第二组数据中`q:你会什么?a:我什么都会`指定了一个`你好`的索引,因此这组数据的索引为`你好`。
|
||||
|
||||
```json
|
||||
{
|
||||
"collectionId": "文件集合的ID,参考上面的第二张图",
|
||||
"mode": "chunk | qa ", // chunk 模式: 可自定义索引。qa 模型:无法自定义索引,会自动取 data 中的 q 作为数据,让模型自动生成问答对和索引。
|
||||
"prompt": "QA 拆分提示词,需严格按照模板,建议不要传入。",
|
||||
"data": [
|
||||
{
|
||||
"q": "生成索引的内容,index 模式下最大 tokens 为3000,建议不超过 1000",
|
||||
"a": "预期回答/补充",
|
||||
"indexes": "自定义索引",
|
||||
},
|
||||
{
|
||||
"q": "xxx",
|
||||
"a": "xxxx"
|
||||
}
|
||||
],
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab tabName="响应例子" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"statusText": "",
|
||||
"data": {
|
||||
"insertLen": 1, // 最终插入成功的数量
|
||||
"overToken": [], // 超出 token 的
|
||||
|
||||
"repeat": [], // 重复的数量
|
||||
"error": [] // 其他错误
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab tabName="QA Prompt 模板" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
{{theme}} 里的内容可以换成数据的主题。默认为:它们可能包含多个主题内容
|
||||
|
||||
```
|
||||
我会给你一段文本,{{theme}},学习它们,并整理学习成果,要求为:
|
||||
1. 提出最多 25 个问题。
|
||||
2. 给出每个问题的答案。
|
||||
3. 答案要详细完整,答案可以包含普通文字、链接、代码、表格、公示、媒体链接等 markdown 元素。
|
||||
4. 按格式返回多个问题和答案:
|
||||
|
||||
Q1: 问题。
|
||||
A1: 答案。
|
||||
Q2:
|
||||
A2:
|
||||
……
|
||||
|
||||
我的文本:"""{{text}}"""
|
||||
```
|
||||
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
|
||||
{{< /tabs >}}
|
||||
|
||||
|
||||
## 搜索测试
|
||||
|
||||
{{< tabs tabTotal="2" >}}
|
||||
{{< tab tabName="请求示例" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
```bash
|
||||
curl --location --request POST 'https://fastgpt.run/api/core/dataset/searchTest' \
|
||||
--header 'Authorization: Bearer fastgpt-xxxxx' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"datasetId": "知识库的ID",
|
||||
"text": "导演是谁",
|
||||
"rarank": true,
|
||||
"limit": 20
|
||||
}'
|
||||
```
|
||||
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab tabName="响应示例" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
返回 top limit 结果
|
||||
|
||||
```bash
|
||||
{
|
||||
"code": 200,
|
||||
"statusText": "",
|
||||
"data": [
|
||||
{
|
||||
"id": "65599c54a5c814fb803363cb",
|
||||
"q": "你是谁",
|
||||
"a": "我是FastGPT助手",
|
||||
"indexes": [
|
||||
{
|
||||
"defaultIndex": true,
|
||||
"type": "qa",
|
||||
"dataId": "3645952",
|
||||
"text": "你是谁\n我是FastGPT助手",
|
||||
"_id": "65599c5588271af95b019862"
|
||||
}
|
||||
],
|
||||
"datasetId": "6554684f7f9ed18a39a4d15c",
|
||||
"collectionId": "6556cd795e4b663e770bb66d",
|
||||
"sourceName": "GBT 15104-2021 装饰单板贴面人造板.pdf",
|
||||
"sourceId": "6556cd775e4b663e770bb65c",
|
||||
"score": 0.8050316572189331
|
||||
},
|
||||
......
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
|
||||
{{< /tabs >}}
|
||||
257
docSite/content/docs/development/openapi/share.md
Normal file
@@ -0,0 +1,257 @@
|
||||
---
|
||||
title: '分享链接鉴权'
|
||||
description: 'FastGPT 分享链接鉴权'
|
||||
icon: 'share'
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 564
|
||||
---
|
||||
|
||||
## 使用说明
|
||||
|
||||
分享链接鉴权设计的目的在于,将 FastGPT 的对话框安全的接入你现有的系统中。
|
||||
|
||||
免登录链接配置中,增加了`凭证校验服务器`后,使用分享链接时会向服务器发起请求,校验链接是否可用,并在每次对话结束后,向服务器发送对话结果。下面以`host`来表示`凭证校验服务器`。服务器接口仅需返回是否校验成功即可,不需要返回其他数据,格式如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "错误提示",
|
||||
"msg": "同message, 错误提示"
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
## 配置校验地址和校验token
|
||||
|
||||
### 1. 配置校验地址的`BaseURL`、
|
||||
|
||||

|
||||
|
||||
配置校验地址后,在每次分享链接使用时,都会向对应的地址发起校验和上报请求。
|
||||
|
||||
### 2. 分享链接中增加额外 query
|
||||
|
||||
在分享链接的地址中,增加一个额外的参数: authToken。例如:
|
||||
|
||||
原始的链接:https://fastgpt.run/chat/share?shareId=648aaf5ae121349a16d62192
|
||||
完整链接: https://fastgpt.run/chat/share?shareId=648aaf5ae121349a16d62192&authToken=userid12345
|
||||
|
||||
这个`token`通常是你系统生成的,在发出校验请求时,FastGPT 会在`body`中携带 token={{authToken}} 的参数。
|
||||
|
||||
## 聊天初始化校验
|
||||
|
||||
**FastGPT 发出的请求**
|
||||
|
||||
```bash
|
||||
curl --location --request POST '{{host}}/shareAuth/init' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"token": "sintdolore"
|
||||
}'
|
||||
```
|
||||
|
||||
**响应示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "分享链接无效",
|
||||
}
|
||||
```
|
||||
|
||||
## 对话前校验
|
||||
|
||||
**FastGPT 发出的请求**
|
||||
|
||||
```bash
|
||||
curl --location --request POST '{{host}}/shareAuth/start' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"token": "sintdolore",
|
||||
"question": "用户问题",
|
||||
}'
|
||||
```
|
||||
|
||||
**响应示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
## 对话结果上报
|
||||
|
||||
```bash
|
||||
curl --location --request POST '{{host}}/shareAuth/finish' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"token": "sint dolore",
|
||||
"responseData": [
|
||||
{
|
||||
"moduleName": "KB Search",
|
||||
"price": 1.2000000000000002,
|
||||
"model": "Embedding-2",
|
||||
"tokens": 6,
|
||||
"similarity": 0.61,
|
||||
"limit": 3
|
||||
},
|
||||
{
|
||||
"moduleName": "AI Chat",
|
||||
"price": 454.5,
|
||||
"model": "FastAI-4k",
|
||||
"tokens": 303,
|
||||
"question": "导演是谁",
|
||||
"answer": "电影《铃芽之旅》的导演是新海诚。",
|
||||
"maxToken": 2050,
|
||||
"quoteList": [
|
||||
{
|
||||
"dataset_id": "646627f4f7b896cfd8910e38",
|
||||
"id": "8099",
|
||||
"q": "本作的主人公是谁?",
|
||||
"a": "本作的主人公是名叫铃芽的少女。",
|
||||
"source": "手动修改"
|
||||
},
|
||||
{
|
||||
"dataset_id": "646627f4f7b896cfd8910e38",
|
||||
"id": "8686",
|
||||
"q": "电影《铃芽之旅》男主角是谁?",
|
||||
"a": "电影《铃芽之旅》男主角是宗像草太,由松村北斗配音。",
|
||||
"source": ""
|
||||
},
|
||||
{
|
||||
"dataset_id": "646627f4f7b896cfd8910e38",
|
||||
"id": "19339",
|
||||
"q": "电影《铃芽之旅》的导演是谁?22",
|
||||
"a": "电影《铃芽之旅》的导演是新海诚。",
|
||||
"source": "手动修改"
|
||||
}
|
||||
],
|
||||
"completeMessages": [
|
||||
{
|
||||
"obj": "System",
|
||||
"value": "下面是知识库内容:\n1. [本作的主人公是谁?\n本作的主人公是名叫铃芽的少女。]\n2. [电影《铃芽之旅》男主角是谁?\n电影《铃芽之旅》男主角是宗像草太,由松村北斗配音。]\n3. [电影《铃芽之旅》的导演是谁?22\n电影《铃芽之旅》的导演是新海诚。]\n"
|
||||
},
|
||||
{
|
||||
"obj": "System",
|
||||
"value": "1.请记住,你的身份是百度的下一代知识增强语言模型,能够完全根据知识库提供的内容回答问题。\n\n2. 你忘记了关于电影《铃芽之旅》以外的内容。"
|
||||
},
|
||||
{
|
||||
"obj": "System",
|
||||
"value": "你仅回答关于电影《玲芽之旅》的问题,其余问题直接回复: 我不清楚。"
|
||||
},
|
||||
{
|
||||
"obj": "Human",
|
||||
"value": "导演是谁"
|
||||
},
|
||||
{
|
||||
"obj": "AI",
|
||||
"value": "电影《铃芽之旅》的导演是新海诚。"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
响应值与 chat 接口相同,增加了一个 token。可以重点关注`responseData`里的值,price 与实际价格的倍率为`100000`。
|
||||
|
||||
**此接口无需响应值**
|
||||
|
||||
## 使用示例
|
||||
|
||||
我们以[Laf作为服务器为例](https://laf.dev/),展示这 3 个接口的使用方式。
|
||||
|
||||
### 1. 创建3个Laf接口
|
||||
|
||||

|
||||
|
||||
{{< tabs tabTotal="3" >}}
|
||||
{{< tab tabName="/shareAuth/init" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
这个接口中,我们设置了`token`必须等于`fastgpt`才能通过校验。(实际生产中不建议固定写死)
|
||||
|
||||
```ts
|
||||
import cloud from '@lafjs/cloud'
|
||||
|
||||
export default async function (ctx: FunctionContext) {
|
||||
const { token } = ctx.body
|
||||
|
||||
if (token === 'fastgpt') {
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
return { success: false,message: "身份错误" }
|
||||
}
|
||||
```
|
||||
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab tabName="/shareAuth/start" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
这个接口中,我们设置了`token`必须等于`fastgpt`才能通过校验。并且如果问题中包含了`你`字,则会报错,用于模拟敏感校验。
|
||||
|
||||
```ts
|
||||
import cloud from '@lafjs/cloud'
|
||||
|
||||
export default async function (ctx: FunctionContext) {
|
||||
const { token, question } = ctx.body
|
||||
console.log(token, question, 'start')
|
||||
|
||||
if (token !== 'fastgpt') {
|
||||
return { success: false, message: "身份错误" }
|
||||
|
||||
}
|
||||
|
||||
if(question.includes("你")){
|
||||
return { success: false, message: "内容不合规" }
|
||||
}
|
||||
|
||||
return { success: true }
|
||||
}
|
||||
```
|
||||
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab tabName="/shareAuth/finish" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
结果上报接口可自行进行逻辑处理。
|
||||
|
||||
```ts
|
||||
import cloud from '@lafjs/cloud'
|
||||
|
||||
export default async function (ctx: FunctionContext) {
|
||||
const { token, responseData } = ctx.body
|
||||
console.log(token,responseData,'=====')
|
||||
return { }
|
||||
}
|
||||
```
|
||||
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
{{< /tabs >}}
|
||||
|
||||
|
||||
### 2. 配置校验地址
|
||||
|
||||
我们随便复制3个地址中一个接口:https://d8dns0.laf.dev/shareAuth/finish , 去除 /shareAuth/finish 后填入 FastGPT 中: https://d8dns0.laf.dev
|
||||
|
||||

|
||||
|
||||
### 3. 修改分享链接参数
|
||||
|
||||
源分享链接:[https://fastgpt.run/chat/share?shareId=64be36376a438af0311e599c](https://fastgpt.run/chat/share?shareId=64be36376a438af0311e599c)
|
||||
|
||||
修改后:[https://fastgpt.run/chat/share?shareId=64be36376a438af0311e599c&authToken=fastgpt](https://fastgpt.run/chat/share?shareId=64be36376a438af0311e599c&authToken=fastgpt)
|
||||
|
||||
### 4. 测试效果
|
||||
|
||||
1. 打开源链接或者`authToken`不等于 `fastgpt`的链接会提示身份错误。
|
||||
2. 发送内容中包含你字,会提示内容不合规。
|
||||
@@ -86,7 +86,7 @@ curl -O https://raw.githubusercontent.com/labring/FastGPT/main/projects/app/data
|
||||
|
||||
## 三、启动容器
|
||||
|
||||
修改`docker-compose.yml`中的`OPENAI_BASE_URL`和`CHAT_API_KEY`即可,对应为 API 的地址和 key。
|
||||
修改`docker-compose.yml`中的`OPENAI_BASE_URL`和`CHAT_API_KEY`即可,对应为 API 的地址(别忘记加/v1)和 key。
|
||||
|
||||
```bash
|
||||
# 在 docker-compose.yml 同级目录下执行
|
||||
|
||||
@@ -46,7 +46,6 @@ SqlLite 版本不支持多实例,适合个人小流量使用,但是价格非
|
||||
|
||||
```
|
||||
SESSION_SECRET=SESSION_SECRET
|
||||
CHANNEL_TEST_FREQUENCY=30
|
||||
POLLING_INTERVAL=60
|
||||
BATCH_UPDATE_ENABLED=true
|
||||
BATCH_UPDATE_INTERVAL=60
|
||||
@@ -72,7 +71,7 @@ BATCH_UPDATE_INTERVAL=60
|
||||
|
||||
### 3. 修改 FastGPT 的环境变量
|
||||
|
||||
有了 One API 令牌后,FastGPT 可以通过修改 baseurl 和 key 去请求到 One API,再由 One API 去请求不同的模型。修改下面两个环境变量:
|
||||
有了 One API 令牌后,FastGPT 可以通过修改 `baseurl` 和 `key` 去请求到 One API,再由 One API 去请求不同的模型。修改下面两个环境变量:
|
||||
|
||||
```bash
|
||||
# 下面的地址是 Sealos 提供的,务必写上 v1, 两个项目都在 sealos 部署时候,https://xxxx.cloud.sealos.io 可以改用内网地址
|
||||
@@ -104,6 +103,7 @@ CHAT_API_KEY=sk-xxxxxx
|
||||
// 例如:文心一言的中英文 token 基本是 1:1,而 GPT 的中文 Token 是 2:1,如果文心一言官方最大 Token 是 4000,那么这里就可以填 8000,保险点就填 7000.
|
||||
"quoteMaxToken": 2000, // 引用知识库的最大 Token
|
||||
"maxTemperature": 1, // 最大温度
|
||||
"vision": false, // 是否开启图片识别
|
||||
"defaultSystemChatPrompt": "" // 默认的系统提示词
|
||||
}
|
||||
...
|
||||
|
||||
@@ -7,7 +7,7 @@ toc: true
|
||||
weight: 847
|
||||
---
|
||||
|
||||
私有部署,如果添加了配置文件,需要在配置文件中修改 `VectorModels` 字段。增加 defaultToken 和 maxToken,分别对应直接分段时的默认 token 数量和该模型支持的 token 上限(通常不建议超过 3000)
|
||||
私有部署,如果添加了配置文件,需要在配置文件中修改 `VectorModels` 字段。增加 defaultToken 和 maxToken,分别对应直接分段时的默认 token 数量和该模型支持的 token 上限 (通常不建议超过 3000)
|
||||
|
||||
```json
|
||||
"VectorModels": [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: '升级到 V4.3'
|
||||
title: '升级到 V4.3(需要初始化)'
|
||||
description: 'FastGPT 从旧版本升级到 V4.3 操作指南'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
@@ -9,7 +9,7 @@ weight: 846
|
||||
|
||||
## 执行初始化 API
|
||||
|
||||
发起 1 个 HTTP 请求(记得携带 `headers.rootkey`,这个值是环境变量里的)
|
||||
发起 1 个 HTTP 请求 (记得携带 `headers.rootkey`,这个值是环境变量里的)
|
||||
|
||||
1. https://xxxxx/api/admin/initv43
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: '升级到 V4.4'
|
||||
title: '升级到 V4.4(需要初始化)'
|
||||
description: 'FastGPT 从旧版本升级到 V4.4 操作指南'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
@@ -9,7 +9,7 @@ weight: 845
|
||||
|
||||
## 执行初始化 API
|
||||
|
||||
发起 1 个 HTTP 请求(记得携带 `headers.rootkey`,这个值是环境变量里的)
|
||||
发起 1 个 HTTP 请求 (记得携带 `headers.rootkey`,这个值是环境变量里的)
|
||||
|
||||
1. https://xxxxx/api/admin/initv44
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: '升级到 V4.4.1'
|
||||
title: '升级到 V4.4.1(需要初始化)'
|
||||
description: 'FastGPT 从旧版本升级到 V4.4.1 操作指南'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: '升级到 V4.4.2'
|
||||
title: '升级到 V4.4.2(需要初始化)'
|
||||
description: 'FastGPT 从旧版本升级到 V4.4.2 操作指南'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
@@ -9,7 +9,7 @@ weight: 843
|
||||
|
||||
## 执行初始化 API
|
||||
|
||||
发起 1 个 HTTP 请求(记得携带 `headers.rootkey`,这个值是环境变量里的)
|
||||
发起 1 个 HTTP 请求 (记得携带 `headers.rootkey`,这个值是环境变量里的)
|
||||
|
||||
1. https://xxxxx/api/admin/initv442
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: 'V4.4.5'
|
||||
description: 'FastGPT V4.4.5 更新(需执行升级脚本)'
|
||||
title: 'V4.4.5(需要初始化)'
|
||||
description: 'FastGPT V4.4.5 更新'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
toc: true
|
||||
|
||||
@@ -7,20 +7,20 @@ toc: true
|
||||
weight: 836
|
||||
---
|
||||
|
||||
# V4.6 版本加入了简单的团队功能,可以邀请其他用户进来管理资源。该版本升级后无法执行旧的升级脚本,且无法回退。
|
||||
**V4.6 版本加入了简单的团队功能,可以邀请其他用户进来管理资源。该版本升级后无法执行旧的升级脚本,且无法回退。**
|
||||
|
||||
# 1. 更新镜像并变更配置文件
|
||||
## 1。更新镜像并变更配置文件
|
||||
|
||||
更新镜像至 latest 或者 v4.6 版本。商业版镜像更新至 V0.2.1
|
||||
|
||||
最新配置可参考: [V46版本最新 config.json](/docs/development/configuration),商业镜像配置文件也更新,参考最新的飞书文档。
|
||||
最新配置可参考:[V46 版本最新 config.json](/docs/development/configuration),商业镜像配置文件也更新,参考最新的飞书文档。
|
||||
|
||||
|
||||
# 2. 执行初始化 API
|
||||
## 2。执行初始化 API
|
||||
|
||||
发起 2 个 HTTP 请求({{rootkey}} 替换成环境变量里的`rootkey`,{{host}}替换成自己域名)
|
||||
发起 2 个 HTTP 请求 ({{rootkey}} 替换成环境变量里的 `rootkey`,{{host}} 替换成自己域名)
|
||||
|
||||
**该初始化接口可能速度很慢,返回超时不用管,注意看日志即可,需要注意的是,需确保initv46成功后,在执行initv46-2**
|
||||
**该初始化接口可能速度很慢,返回超时不用管,注意看日志即可,需要注意的是,需确保 initv46 成功后,在执行 initv46-2**
|
||||
|
||||
1. https://xxxxx/api/admin/initv46
|
||||
|
||||
@@ -39,17 +39,29 @@ curl --location --request POST 'https://{{host}}/api/admin/initv46-2' \
|
||||
```
|
||||
|
||||
初始化内容:
|
||||
1. 创建默认团队
|
||||
2. 初始化 Mongo 所有资源的团队字段
|
||||
3. 初始化 Pg 的字段
|
||||
4. 初始化 Mongo Data
|
||||
1。创建默认团队
|
||||
2。初始化 Mongo 所有资源的团队字段
|
||||
3。初始化 Pg 的字段
|
||||
4。初始化 Mongo Data
|
||||
|
||||
|
||||
# V4.6功能介绍
|
||||
## V4.6 功能介绍
|
||||
|
||||
1. 新增 - 团队空间
|
||||
2. 新增 - 多路向量(多个向量映射一组数据)
|
||||
3. 新增 - tts语音
|
||||
2. 新增 - 多路向量 (多个向量映射一组数据)
|
||||
3. 新增 - tts 语音
|
||||
4. 新增 - 支持知识库配置文本预处理模型
|
||||
5. 线上环境新增 - ReRank向量召回,提高召回精度
|
||||
5. 线上环境新增 - ReRank 向量召回,提高召回精度
|
||||
6. 优化 - 知识库导出,可直接触发流下载,无需等待转圈圈
|
||||
|
||||
## 4.6 缺陷修复
|
||||
|
||||
旧的 4.6 版本由于缺少一个字段,导致文件导入时知识库数据无法显示,可执行下面的脚本:
|
||||
|
||||
https://xxxxx/api/admin/initv46-fix
|
||||
|
||||
```bash
|
||||
curl --location --request POST 'https://{{host}}/api/admin/initv46-fix' \
|
||||
--header 'rootkey: {{rootkey}}' \
|
||||
--header 'Content-Type: application/json'
|
||||
```
|
||||
16
docSite/content/docs/installation/upgrading/461.md
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: 'V4.6.1'
|
||||
description: 'FastGPT V4.6 .1'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 835
|
||||
---
|
||||
|
||||
|
||||
## V4.6.1 功能介绍
|
||||
|
||||
1. 新增 - GPT4-v 模型支持
|
||||
2. 新增 - whisper 语音输入
|
||||
3. 优化 - TTS 流传输
|
||||
4. 优化 - TTS 缓存
|
||||
31
docSite/content/docs/installation/upgrading/462.md
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
title: 'V4.6.2(需要初始化)'
|
||||
description: 'FastGPT V4.6.2'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 834
|
||||
---
|
||||
|
||||
## 1。执行初始化 API
|
||||
|
||||
发起 1 个 HTTP 请求 ({{rootkey}} 替换成环境变量里的 `rootkey`,{{host}} 替换成自己域名)
|
||||
|
||||
1. https://xxxxx/api/admin/initv462
|
||||
|
||||
```bash
|
||||
curl --location --request POST 'https://{{host}}/api/admin/initv462' \
|
||||
--header 'rootkey: {{rootkey}}' \
|
||||
--header 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
初始化说明:
|
||||
1. 初始化全文索引
|
||||
|
||||
## V4.6.2 功能介绍
|
||||
|
||||
1. 新增 - 全文索引(需配合 Rerank 模型,在看怎么放到开源版,模型接口比较特殊)
|
||||
2. 新增 - 插件来源(预计4.7/4.8版本会正式使用)
|
||||
3. 优化 - PDF读取
|
||||
4. 优化 - docx文件读取,转成 markdown 并保留其图片内容
|
||||
5. 修复和优化 TextSplitter 函数
|
||||
33
docSite/content/docs/installation/upgrading/463.md
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: 'V4.6.3(需要初始化)'
|
||||
description: 'FastGPT V4.6.3'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 833
|
||||
---
|
||||
|
||||
## 1。执行初始化 API
|
||||
|
||||
发起 1 个 HTTP 请求 ({{rootkey}} 替换成环境变量里的 `rootkey`,{{host}} 替换成自己域名)
|
||||
|
||||
1. https://xxxxx/api/admin/initv463
|
||||
|
||||
```bash
|
||||
curl --location --request POST 'https://{{host}}/api/admin/initv463' \
|
||||
--header 'rootkey: {{rootkey}}' \
|
||||
--header 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
初始化说明:
|
||||
1. 初始化Mongo 中 dataset,collection 和 data 的部分字段
|
||||
|
||||
## V4.6.3 功能介绍
|
||||
|
||||
1. 商业版新增 - web站点同步
|
||||
2. 新增 - 集合元数据记录
|
||||
3. 优化 - url 读取内容
|
||||
4. 优化 - 流读取文件,防止内存溢出
|
||||
5. 优化 - 4v模型自动将 url 转 base64,本地也可调试
|
||||
6. 优化 - 图片压缩等级
|
||||
7. 修复 - 图片压缩失败报错,防止文件读取过程卡死。
|
||||
@@ -9,23 +9,23 @@ weight: 310
|
||||
|
||||
在 FastGPT 的 AI 对话模块中,有一个 AI 高级配置,里面包含了 AI 模型的参数配置,本文详细介绍这些配置的含义。
|
||||
|
||||
# 返回AI内容
|
||||
## 返回AI内容
|
||||
|
||||
这是一个开关,打开的时候,当 AI 对话模块运行时,会将其输出的内容返回到浏览器(API响应);如果关闭,AI 输出的内容不会返回到浏览器,但是生成的内容仍可以通过【AI回复】进行输出。你可以将【AI回复】连接到其他模块中。
|
||||
|
||||
# 温度
|
||||
## 温度
|
||||
|
||||
可选范围0-10,约大代表生成的内容约自由扩散,越小代表约严谨。调节能力有限,知识库问答场景通常设置为0。
|
||||
|
||||
# 回复上限
|
||||
## 回复上限
|
||||
|
||||
控制 AI 回复的最大 Tokens,较小的值可以一定程度上减少 AI 的废话,但也可能导致 AI 回复不完整。
|
||||
|
||||
# 引用模板 & 引用提示词
|
||||
## 引用模板 & 引用提示词
|
||||
|
||||
这两个参数与知识库问答场景相关,可以控制知识库相关的提示词。
|
||||
|
||||
## AI 对话消息组成
|
||||
### AI 对话消息组成
|
||||
|
||||
想使用明白这两个变量,首先要了解传递传递给 AI 模型的消息格式。它是一个数组,FastGPT 中这个数组的组成形式为:
|
||||
|
||||
@@ -42,7 +42,7 @@ weight: 310
|
||||
Tips: 可以通过点击上下文按键查看完整的上下文组成,便于调试。
|
||||
{{% /alert %}}
|
||||
|
||||
## 引用模板和提示词设计
|
||||
### 引用模板和提示词设计
|
||||
|
||||
引用模板和引用提示词通常是成对出现,引用提示词依赖引用模板。
|
||||
|
||||
@@ -50,7 +50,7 @@ FastGPT 知识库采用 QA 对(不一定都是问答格式,仅代表两个变
|
||||
|
||||
可以通过 [知识库结构讲解](/docs/use-cases/datasetEngine/) 了解详细的知识库的结构。
|
||||
|
||||
### 引用模板
|
||||
#### 引用模板
|
||||
|
||||
```
|
||||
{instruction:"{{q}}",output:"{{a}}",source:"{{source}}"}
|
||||
@@ -64,7 +64,7 @@ FastGPT 知识库采用 QA 对(不一定都是问答格式,仅代表两个变
|
||||
{instruction:"电影《铃芽之旅》的编剧是谁?22",output:"新海诚是本片的编剧。",source:"手动输入"}
|
||||
```
|
||||
|
||||
### 引用提示词
|
||||
#### 引用提示词
|
||||
|
||||
引用模板需要和引用提示词一起使用,提示词中可以写引用模板的格式说明以及对话的要求等。可以使用 {{quote}} 来使用 **引用模板**,使用 {{question}} 来引入问题。例如:
|
||||
|
||||
@@ -95,15 +95,15 @@ FastGPT 知识库采用 QA 对(不一定都是问答格式,仅代表两个变
|
||||
我的问题是:"{{question}}"
|
||||
```
|
||||
|
||||
### 总结
|
||||
#### 总结
|
||||
|
||||
引用模板规定了搜索出来的内容如何组成一句话,其由 q,a,index,source 多个变量组成。
|
||||
|
||||
引用提示词由`引用模板`和`提示词`组成,提示词通常是对引用模板的一个描述,加上对模型的要求。
|
||||
|
||||
## 引用模板和提示词设计 示例
|
||||
### 引用模板和提示词设计 示例
|
||||
|
||||
### 通用模板与问答模板对比
|
||||
#### 通用模板与问答模板对比
|
||||
|
||||
我们通过一组`你是谁`的手动数据,对通用模板与问答模板的效果进行对比。此处特意打了个搞笑的答案,通用模板下 GPT35 就变得不那么听话了,而问答模板下 GPT35 依然能够回答正确。这是由于结构化的提示词,在大语言模型中具有更强的引导作用。
|
||||
|
||||
@@ -117,7 +117,7 @@ Tips: 建议根据不同的场景,每种知识库仅选择1类数据类型,
|
||||
|  |  |
|
||||
|  |  |
|
||||
|
||||
### 严格模板
|
||||
#### 严格模板
|
||||
|
||||
使用非严格模板,我们随便询问一个不在知识库中的内容,模型通常会根据其自身知识进行回答。
|
||||
|
||||
@@ -125,7 +125,7 @@ Tips: 建议根据不同的场景,每种知识库仅选择1类数据类型,
|
||||
| --- | --- | --- |
|
||||
|  |  | |
|
||||
|
||||
### 提示词设计思路
|
||||
#### 提示词设计思路
|
||||
|
||||
1. 使用序号进行不同要求描述。
|
||||
2. 使用首先、然后、最后等词语进行描述。
|
||||
|
||||
@@ -7,7 +7,7 @@ toc: true
|
||||
weight: 311
|
||||
---
|
||||
|
||||
# 理解向量
|
||||
## 理解向量
|
||||
|
||||
FastGPT 采用了 RAG 中的 Embedding 方案构建知识库,要使用好 FastGPT 需要简单的理解`Embedding`向量是如何工作的及其特点。
|
||||
|
||||
@@ -21,7 +21,7 @@ FastGPT 采用了 RAG 中的 Embedding 方案构建知识库,要使用好 Fast
|
||||
|
||||
检索器的精度比较容易解决,向量模型的训练略复杂,因此数据和检索词质量优化成了一个重要的环节。
|
||||
|
||||
# FastGPT 中向量的结构设计
|
||||
## FastGPT 中向量的结构设计
|
||||
|
||||
FastGPT 采用了 `PostgresSQL` 的 `PG Vector` 插件作为向量检索器,索引为`HNSW`。且`PostgresSQL`仅用于向量检索,`MongoDB`用于其他数据的存取。
|
||||
|
||||
@@ -29,13 +29,13 @@ FastGPT 采用了 `PostgresSQL` 的 `PG Vector` 插件作为向量检索器,
|
||||
|
||||

|
||||
|
||||
## 多向量的目的和使用方式
|
||||
### 多向量的目的和使用方式
|
||||
|
||||
在一组数据中,如果我们希望它尽可能长,但语义又要在向量中尽可能提现,则没有办法通过一组向量来表示。因此,我们采用了多向量映射的方式,将一组数据映射到多组向量中,从而保障数据的完整性和语义的提现。
|
||||
在一组向量中,内容的长度和语义的丰富度通常是矛盾的,无法兼得。因此,FastGPT 采用了多向量映射的方式,将一组数据映射到多组向量中,从而保障数据的完整性和语义的丰富度。
|
||||
|
||||
你可以为一组较长的文本,添加多组向量,从而在检索时,只要其中一组向量被检索到,该数据也将被召回。
|
||||
|
||||
## 提高向量搜索精度的方法
|
||||
### 提高向量搜索精度的方法
|
||||
|
||||
1. 更好分词分段:当一段话的结构和语义是完整的,并且是单一的,精度也会提高。因此,许多系统都会优化分词器,尽可能的保障每组数据的完整性。
|
||||
2. 精简`index`的内容,减少向量内容的长度:当`index`的内容更少,更准确时,检索精度自然会提高。但与此同时,会牺牲一定的检索范围,适合答案较为严格的场景。
|
||||
@@ -43,7 +43,7 @@ FastGPT 采用了 `PostgresSQL` 的 `PG Vector` 插件作为向量检索器,
|
||||
4. 优化检索词:在实际使用过程中,用户的问题通常是模糊的或是缺失的,并不一定是完整清晰的问题。因此优化用户的问题(检索词)很大程度上也可以提高精度。
|
||||
5. 微调向量模型:由于市面上直接使用的向量模型都是通用型模型,在特定领域的检索精度并不高,因此微调向量模型可以很大程度上提高专业领域的检索效果。
|
||||
|
||||
# FastGPT 构建知识库方案
|
||||
## FastGPT 构建知识库方案
|
||||
|
||||
在 FastGPT 中,整个知识库由库、集合和数据 3 部分组成。集合可以简单理解为一个`文件`。一个`库`中可以包含多个`集合`,一个`集合`中可以包含多组`数据`。最小的搜索单位是`库`,也就是说,知识库搜索时,是对整个`库`进行搜索,而集合仅是为了对数据进行分类管理,与搜索效果无关。(起码目前还是)
|
||||
|
||||
@@ -51,7 +51,7 @@ FastGPT 采用了 `PostgresSQL` 的 `PG Vector` 插件作为向量检索器,
|
||||
| --- | --- | --- |
|
||||
|  |  |  |
|
||||
|
||||
## 导入数据方案1 - 直接分段导入
|
||||
### 导入数据方案1 - 直接分段导入
|
||||
|
||||
选择文件导入时,可以选择直接分段方案。直接分段会利用`句子分词器`对文本进行一定长度拆分,最终分割中多组的`q`。如果使用了直接分段方案,我们建议在`应用`设置`引用提示词`时,使用`通用模板`即可,无需选择`问答模板`。
|
||||
|
||||
@@ -60,7 +60,7 @@ FastGPT 采用了 `PostgresSQL` 的 `PG Vector` 插件作为向量检索器,
|
||||
|  |  |
|
||||
|
||||
|
||||
## 导入数据方案2 - QA导入
|
||||
### 导入数据方案2 - QA导入
|
||||
|
||||
选择文件导入时,可以选择QA拆分方案。仍然需要使用到`句子分词器`对文本进行拆分,但长度比直接分段大很多。在导入后,会先调用`大模型`对分段进行学习,并给出一些`问题`和`答案`,最终问题和答案会一起被存储到`q`中。注意,新版的 FastGPT 为了提高搜索的范围,不再将问题和答案分别存储到 qa 中。
|
||||
|
||||
@@ -68,7 +68,7 @@ FastGPT 采用了 `PostgresSQL` 的 `PG Vector` 插件作为向量检索器,
|
||||
| --- | --- |
|
||||
|  |  |
|
||||
|
||||
## 导入数据方案3 - 手动录入
|
||||
### 导入数据方案3 - 手动录入
|
||||
|
||||
在 FastGPT 中,你可以在任何一个`集合`中点击右上角的`插入`手动录入知识点,或者使用`标注`功能手动录入。被搜索的内容为`q`,补充内容(可选)为`a`。
|
||||
|
||||
@@ -76,16 +76,16 @@ FastGPT 采用了 `PostgresSQL` 的 `PG Vector` 插件作为向量检索器,
|
||||
| --- | --- | --- |
|
||||
|  |  |  |
|
||||
|
||||
## 导入数据方案4 - CSV录入
|
||||
### 导入数据方案4 - CSV录入
|
||||
|
||||
有些数据较为独特,可能需要单独的进行预处理分割后再导入 FastGPT,此时可以选择 csv 导入,可批量的将处理好的数据导入。
|
||||
|
||||

|
||||
|
||||
## 导入数据方案5 - API导入
|
||||
### 导入数据方案5 - API导入
|
||||
|
||||
参考[FastGPT OpenAPI使用](/docs/development/openapi/#知识库添加数据)。
|
||||
|
||||
# QA的组合与引用提示词构建
|
||||
## QA的组合与引用提示词构建
|
||||
|
||||
参考[引用模板与引用提示词示例](/docs/use-cases/ai_settings/#示例)
|
||||
|
||||
78
docSite/content/docs/use-cases/wechat.md
Normal file
@@ -0,0 +1,78 @@
|
||||
---
|
||||
title: " 接入微信和企业微信 "
|
||||
description: "FastGPT 接入微信和企业微信 "
|
||||
icon: "chat"
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 322
|
||||
---
|
||||
|
||||
# FastGPT 三分钟接入微信/企业微信
|
||||
私人微信和企业微信接入的方式基本一样,不同的地方会刻意指出。
|
||||
[查看视频教程](https://www.bilibili.com/video/BV1cu411F7FN/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=903c2b09b7412037c2eddc6a8fb9828b)
|
||||
## 创建APIKey
|
||||
首先找到我们需要接入的应用,然后点击「外部使用」->「API访问」创建一个APIKey并保存。
|
||||
|
||||

|
||||
|
||||
## 配置微秘书
|
||||
|
||||
打开[微秘书](https://wechat.aibotk.com?r=zWLnZK) 注册登陆后找到菜单栏「基础配置」->「智能配置」,按照下图配置。
|
||||
|
||||

|
||||
|
||||
继续往下看到 `apikey` 和`服务器根地址`,这里`apikey`填写我们在 FastGPT 应用外部访问中创建的 APIkey,服务器根地址填写官方地址或者私有化部署的地址,这里用官方地址示例,注意要添加`/v1`后缀,填写完毕后保存。
|
||||
|
||||

|
||||
|
||||
## sealos部署服务
|
||||
|
||||
[访问sealos](https://cloud.sealos.io/) 登陆进来之后打开「应用管理」-> 「新建应用」。
|
||||
- 应用名:称随便填写
|
||||
- 镜像名:私人微信填写 aibotk/wechat-assistant 企业微信填写 aibotk/worker-assistant
|
||||
- cpu和内存建议 1c1g
|
||||
|
||||

|
||||
|
||||
往下翻页找到「高级配置」-> 「编辑环境变量」
|
||||
|
||||

|
||||
|
||||
这里需要填写四个环境变量:
|
||||
```
|
||||
AIBOTK_KEY=微秘书 APIKEY
|
||||
AIBOTK_SECRET=微秘书 APISECRET
|
||||
WORK_PRO_TOKEN=你申请的企微 token (企业微信需要填写,私人微信不需要)
|
||||
WECHATY_PUPPET_SERVICE_AUTHORITY=token-service-discovery-test.juzibot.com(企业微信需要填写,私人微信不需要)
|
||||
```
|
||||
|
||||
这里最后两个变量只有部署企业微信才需要,私人微信只需要填写前两个即可。
|
||||
|
||||

|
||||
|
||||
这里环境变量我们介绍下如何填写:
|
||||
|
||||
`AIBOTK_KEY` 和 `AIBOTK_SECRET` 我们需要回到[微秘书](https://wechat.aibotk.com?r=zWLnZK)找到「个人中心」,这里的 APIKEY 对应 AIBOTK_KEY ,APISECRET 对应 `AIBOTK_SECRET`。
|
||||
|
||||

|
||||
|
||||
`WORK_PRO_TOKEN` [点击这里](https://tss.juzibot.com?aff=aibotk)申请 token 然后填入即可。
|
||||
|
||||
`WECHATY_PUPPET_SERVICE_AUTHORITY`的值复制过去就可以。
|
||||
|
||||
填写完毕后点右上角「部署」,等待应用状态变为运行中。
|
||||
|
||||

|
||||
|
||||
返回[微秘书](https://wechat.aibotk.com?r=zWLnZK) 找到「首页」,扫码登陆需要接入的微信号。
|
||||
|
||||

|
||||
|
||||
## 测试
|
||||
只需要发送信息,或者拉入群聊@登陆的微信就会回复信息啦。
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
8
packages/global/common/file/api.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
export type UrlFetchParams = {
|
||||
urlList: string[];
|
||||
selector?: string;
|
||||
};
|
||||
export type UrlFetchResponse = {
|
||||
url: string;
|
||||
content: string;
|
||||
}[];
|
||||
@@ -1,3 +1,8 @@
|
||||
import axios from 'axios';
|
||||
import { UrlFetchParams, UrlFetchResponse } from './api.d';
|
||||
import { htmlToMarkdown } from '../string/markdown';
|
||||
import * as cheerio from 'cheerio';
|
||||
|
||||
export const formatFileSize = (bytes: number): string => {
|
||||
if (bytes === 0) return '0 B';
|
||||
|
||||
@@ -7,3 +12,84 @@ export const formatFileSize = (bytes: number): string => {
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
};
|
||||
|
||||
export const cheerioToHtml = ({
|
||||
fetchUrl,
|
||||
$,
|
||||
selector
|
||||
}: {
|
||||
fetchUrl: string;
|
||||
$: cheerio.CheerioAPI;
|
||||
selector?: string;
|
||||
}) => {
|
||||
// get origin url
|
||||
const originUrl = new URL(fetchUrl).origin;
|
||||
|
||||
// remove i element
|
||||
$('i,script').remove();
|
||||
|
||||
// remove empty a element
|
||||
$('a')
|
||||
.filter((i, el) => {
|
||||
return $(el).text().trim() === '' && $(el).children().length === 0;
|
||||
})
|
||||
.remove();
|
||||
|
||||
// if link,img startWith /, add origin url
|
||||
$('a').each((i, el) => {
|
||||
const href = $(el).attr('href');
|
||||
if (href && href.startsWith('/')) {
|
||||
$(el).attr('href', originUrl + href);
|
||||
}
|
||||
});
|
||||
$('img').each((i, el) => {
|
||||
const src = $(el).attr('src');
|
||||
if (src && src.startsWith('/')) {
|
||||
$(el).attr('src', originUrl + src);
|
||||
}
|
||||
});
|
||||
|
||||
return $(selector || 'body').html();
|
||||
};
|
||||
export const urlsFetch = async ({
|
||||
urlList,
|
||||
selector
|
||||
}: UrlFetchParams): Promise<UrlFetchResponse> => {
|
||||
urlList = urlList.filter((url) => /^(http|https):\/\/[^ "]+$/.test(url));
|
||||
|
||||
const response = (
|
||||
await Promise.all(
|
||||
urlList.map(async (url) => {
|
||||
try {
|
||||
const fetchRes = await axios.get(url, {
|
||||
timeout: 30000
|
||||
});
|
||||
|
||||
const $ = cheerio.load(fetchRes.data);
|
||||
|
||||
const md = htmlToMarkdown(
|
||||
cheerioToHtml({
|
||||
fetchUrl: url,
|
||||
$,
|
||||
selector
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
url,
|
||||
content: md
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error, 'fetch error');
|
||||
|
||||
return {
|
||||
url,
|
||||
content: ''
|
||||
};
|
||||
}
|
||||
})
|
||||
)
|
||||
).filter((item) => item.content);
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
export type FetchResultItem = {
|
||||
url: string;
|
||||
content: string;
|
||||
};
|
||||
1508
packages/global/common/string/jieba.ts
Normal file
97
packages/global/common/string/markdown.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { simpleText } from './tools';
|
||||
import { NodeHtmlMarkdown } from 'node-html-markdown';
|
||||
|
||||
/* Delete redundant text in markdown */
|
||||
export const simpleMarkdownText = (rawText: string) => {
|
||||
rawText = simpleText(rawText);
|
||||
|
||||
// Remove a line feed from a hyperlink or picture
|
||||
rawText = rawText.replace(/\[([^\]]+)\]\((.+?)\)/g, (match, linkText, url) => {
|
||||
const cleanedLinkText = linkText.replace(/\n/g, ' ').trim();
|
||||
|
||||
if (!url) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return `[${cleanedLinkText}](${url})`;
|
||||
});
|
||||
|
||||
// replace special \.* ……
|
||||
const reg1 = /\\([-.!`_(){}\[\]])/g;
|
||||
if (reg1.test(rawText)) {
|
||||
rawText = rawText.replace(/\\([`!*()+-_\[\]{}\\.])/g, '$1');
|
||||
}
|
||||
|
||||
// replace \\n
|
||||
rawText = rawText.replace(/\\\\n/g, '\\n');
|
||||
|
||||
// Remove headings and code blocks front spaces
|
||||
['####', '###', '##', '#', '```', '~~~'].forEach((item) => {
|
||||
const reg = new RegExp(`\\n\\s*${item}`, 'g');
|
||||
if (reg.test(rawText)) {
|
||||
rawText = rawText.replace(new RegExp(`\\n\\s*(${item})`, 'g'), '\n$1');
|
||||
}
|
||||
});
|
||||
|
||||
return rawText.trim();
|
||||
};
|
||||
|
||||
/* html string to markdown */
|
||||
export const htmlToMarkdown = (html?: string | null) => {
|
||||
if (!html) return '';
|
||||
|
||||
const surround = (source: string, surroundStr: string) => `${surroundStr}${source}${surroundStr}`;
|
||||
|
||||
const nhm = new NodeHtmlMarkdown(
|
||||
{
|
||||
codeFence: '```',
|
||||
codeBlockStyle: 'fenced',
|
||||
ignore: ['i', 'script']
|
||||
},
|
||||
{
|
||||
code: ({ node, parent, options: { codeFence, codeBlockStyle }, visitor }) => {
|
||||
const isCodeBlock = ['PRE', 'WRAPPED-PRE'].includes(parent?.tagName!);
|
||||
|
||||
if (!isCodeBlock) {
|
||||
return {
|
||||
spaceIfRepeatingChar: true,
|
||||
noEscape: true,
|
||||
postprocess: ({ content }) => {
|
||||
// Find longest occurring sequence of running backticks and add one more (so content is escaped)
|
||||
const delimiter =
|
||||
'`' + (content.match(/`+/g)?.sort((a, b) => b.length - a.length)?.[0] || '');
|
||||
const padding = delimiter.length > 1 ? ' ' : '';
|
||||
|
||||
return surround(surround(content, padding), delimiter);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/* Handle code block */
|
||||
if (codeBlockStyle === 'fenced') {
|
||||
const language =
|
||||
node.getAttribute('class')?.match(/language-(\S+)/)?.[1] ||
|
||||
parent?.getAttribute('class')?.match(/language-(\S+)/)?.[1] ||
|
||||
'';
|
||||
|
||||
return {
|
||||
noEscape: true,
|
||||
prefix: `${codeFence}${language}\n`,
|
||||
postfix: `\n${codeFence}\n`,
|
||||
childTranslators: visitor.instance.codeBlockTranslators
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
noEscape: true,
|
||||
postprocess: ({ content }) => content.replace(/^/gm, ' '),
|
||||
childTranslators: visitor.instance.codeBlockTranslators
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const markdown = nhm.translate(html).trim();
|
||||
|
||||
return simpleMarkdownText(markdown);
|
||||
};
|
||||
@@ -3,121 +3,218 @@ import { countPromptTokens } from './tiktoken';
|
||||
|
||||
/**
|
||||
* text split into chunks
|
||||
* maxLen - one chunk len. max: 3500
|
||||
* chunkLen - one chunk len. max: 3500
|
||||
* overlapLen - The size of the before and after Text
|
||||
* maxLen > overlapLen
|
||||
* chunkLen > overlapLen
|
||||
* markdown
|
||||
*/
|
||||
export const splitText2Chunks = (props: { text: string; maxLen: number; overlapLen?: number }) => {
|
||||
const { text = '', maxLen, overlapLen = Math.floor(maxLen * 0.2) } = props;
|
||||
const tempMarker = 'SPLIT_HERE_SPLIT_HERE';
|
||||
export const splitText2Chunks = (props: {
|
||||
text: string;
|
||||
chunkLen: number;
|
||||
overlapRatio?: number;
|
||||
}): {
|
||||
chunks: string[];
|
||||
tokens: number;
|
||||
overlapRatio?: number;
|
||||
} => {
|
||||
let { text = '', chunkLen, overlapRatio = 0.2 } = props;
|
||||
const splitMarker = 'SPLIT_HERE_SPLIT_HERE';
|
||||
const codeBlockMarker = 'CODE_BLOCK_LINE_MARKER';
|
||||
const overlapLen = Math.round(chunkLen * overlapRatio);
|
||||
|
||||
const stepReg: Record<number, RegExp> = {
|
||||
0: /^(#\s[^\n]+)\n/gm,
|
||||
1: /^(##\s[^\n]+)\n/gm,
|
||||
2: /^(###\s[^\n]+)\n/gm,
|
||||
3: /^(####\s[^\n]+)\n/gm,
|
||||
// replace code block all \n to codeBlockMarker
|
||||
text = text.replace(/(```[\s\S]*?```|~~~[\s\S]*?~~~)/g, function (match) {
|
||||
return match.replace(/\n/g, codeBlockMarker);
|
||||
});
|
||||
|
||||
4: /(\n\n)/g,
|
||||
5: /([\n])/g,
|
||||
6: /[。]|(?!<[^a-zA-Z])\.\s/g,
|
||||
7: /([!?]|!\s|\?\s)/g,
|
||||
8: /([;]|;\s)/g,
|
||||
9: /([,]|,\s)/g
|
||||
// The larger maxLen is, the next sentence is less likely to trigger splitting
|
||||
const stepReges: { reg: RegExp; maxLen: number }[] = [
|
||||
{ reg: /^(#\s[^\n]+)\n/gm, maxLen: chunkLen * 1.4 },
|
||||
{ reg: /^(##\s[^\n]+)\n/gm, maxLen: chunkLen * 1.4 },
|
||||
{ reg: /^(###\s[^\n]+)\n/gm, maxLen: chunkLen * 1.4 },
|
||||
{ reg: /^(####\s[^\n]+)\n/gm, maxLen: chunkLen * 1.4 },
|
||||
|
||||
{ reg: /([\n](`))/g, maxLen: chunkLen * 4 }, // code block
|
||||
{ reg: /([\n](?![\*\-|>0-9]))/g, maxLen: chunkLen * 1.8 }, // (?![\*\-|>`0-9]): markdown special char
|
||||
{ reg: /([\n])/g, maxLen: chunkLen * 1.4 },
|
||||
|
||||
{ reg: /([。]|([a-zA-Z])\.\s)/g, maxLen: chunkLen * 1.4 },
|
||||
{ reg: /([!]|!\s)/g, maxLen: chunkLen * 1.4 },
|
||||
{ reg: /([?]|\?\s)/g, maxLen: chunkLen * 1.6 },
|
||||
{ reg: /([;]|;\s)/g, maxLen: chunkLen * 1.8 },
|
||||
{ reg: /([,]|,\s)/g, maxLen: chunkLen * 2 }
|
||||
];
|
||||
|
||||
// if use markdown title split, Separate record title title
|
||||
const getSplitTexts = ({ text, step }: { text: string; step: number }) => {
|
||||
if (step >= stepReges.length) {
|
||||
return [
|
||||
{
|
||||
text,
|
||||
title: ''
|
||||
}
|
||||
];
|
||||
}
|
||||
const isMarkdownSplit = step <= 3;
|
||||
const { reg } = stepReges[step];
|
||||
|
||||
const splitTexts = text
|
||||
.replace(reg, isMarkdownSplit ? `${splitMarker}$1` : `$1${splitMarker}`)
|
||||
.split(`${splitMarker}`)
|
||||
.filter((part) => part.trim());
|
||||
|
||||
return splitTexts
|
||||
.map((text) => {
|
||||
const matchTitle = isMarkdownSplit ? text.match(reg)?.[0] || '' : '';
|
||||
|
||||
return {
|
||||
text: isMarkdownSplit ? text.replace(matchTitle, '') : text,
|
||||
title: matchTitle
|
||||
};
|
||||
})
|
||||
.filter((item) => item.text.trim());
|
||||
};
|
||||
|
||||
const getOneTextOverlapText = ({ text, step }: { text: string; step: number }): string => {
|
||||
const forbidOverlap = step <= 6;
|
||||
const maxOverlapLen = chunkLen * 0.4;
|
||||
|
||||
// step >= stepReges.length: Do not overlap incomplete sentences
|
||||
if (forbidOverlap || overlapLen === 0 || step >= stepReges.length) return '';
|
||||
|
||||
const splitTexts = getSplitTexts({ text, step });
|
||||
let overlayText = '';
|
||||
|
||||
for (let i = splitTexts.length - 1; i >= 0; i--) {
|
||||
const currentText = splitTexts[i].text;
|
||||
const newText = currentText + overlayText;
|
||||
const newTextLen = newText.length;
|
||||
|
||||
if (newTextLen > overlapLen) {
|
||||
if (newTextLen > maxOverlapLen) {
|
||||
const text = getOneTextOverlapText({ text: newText, step: step + 1 });
|
||||
return text || overlayText;
|
||||
}
|
||||
return newText;
|
||||
}
|
||||
|
||||
overlayText = newText;
|
||||
}
|
||||
return overlayText;
|
||||
};
|
||||
|
||||
const splitTextRecursively = ({
|
||||
text = '',
|
||||
step,
|
||||
lastChunk,
|
||||
overlayChunk
|
||||
lastText,
|
||||
mdTitle = ''
|
||||
}: {
|
||||
text: string;
|
||||
step: number;
|
||||
lastChunk: string;
|
||||
overlayChunk: string;
|
||||
}) => {
|
||||
if (text.length <= maxLen) {
|
||||
lastText: string;
|
||||
mdTitle: string;
|
||||
}): string[] => {
|
||||
const isMarkdownSplit = step <= 3;
|
||||
|
||||
// mini text
|
||||
if (text.length <= chunkLen) {
|
||||
return [text];
|
||||
}
|
||||
const reg = stepReg[step];
|
||||
const isMarkdownSplit = step < 4;
|
||||
|
||||
if (!reg) {
|
||||
// use slice-maxLen to split text
|
||||
// oversize
|
||||
if (step >= stepReges.length) {
|
||||
if (text.length < chunkLen * 3) {
|
||||
return [text];
|
||||
}
|
||||
// use slice-chunkLen to split text
|
||||
const chunks: string[] = [];
|
||||
let chunk = '';
|
||||
for (let i = 0; i < text.length; i += maxLen - overlapLen) {
|
||||
chunk = text.slice(i, i + maxLen);
|
||||
chunks.push(chunk);
|
||||
for (let i = 0; i < text.length; i += chunkLen - overlapLen) {
|
||||
chunks.push(`${mdTitle}${text.slice(i, i + chunkLen)}`);
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
|
||||
const { maxLen } = stepReges[step];
|
||||
const minChunkLen = chunkLen * 0.7;
|
||||
|
||||
// split text by special char
|
||||
const splitTexts = text
|
||||
.replace(reg, isMarkdownSplit ? `${tempMarker}$1` : `$1${tempMarker}`)
|
||||
.split(`${tempMarker}`)
|
||||
.filter((part) => part);
|
||||
const splitTexts = getSplitTexts({ text, step });
|
||||
|
||||
let chunks: string[] = [];
|
||||
const chunks: string[] = [];
|
||||
for (let i = 0; i < splitTexts.length; i++) {
|
||||
let text = splitTexts[i];
|
||||
let chunkToken = lastChunk.length;
|
||||
const textToken = text.length;
|
||||
const item = splitTexts[i];
|
||||
const currentTitle = `${mdTitle}${item.title}`;
|
||||
|
||||
// next chunk is too large / new chunk is too large(The current chunk must be smaller than maxLen)
|
||||
if (textToken >= maxLen || chunkToken + textToken > maxLen * 1.4) {
|
||||
// last chunk is too large, push it to chunks, not add to next chunk
|
||||
if (chunkToken > maxLen * 0.7) {
|
||||
chunks.push(lastChunk);
|
||||
lastChunk = '';
|
||||
overlayChunk = '';
|
||||
const currentText = item.text;
|
||||
const currentTextLen = currentText.length;
|
||||
const lastTextLen = lastText.length;
|
||||
const newText = lastText + currentText;
|
||||
const newTextLen = lastTextLen + currentTextLen;
|
||||
|
||||
// newText is too large(now, The lastText must be smaller than chunkLen)
|
||||
if (newTextLen > maxLen) {
|
||||
// lastText greater minChunkLen, direct push it to chunks, not add to next chunk. (large lastText)
|
||||
if (lastTextLen > minChunkLen) {
|
||||
chunks.push(`${currentTitle}${lastText}`);
|
||||
lastText = getOneTextOverlapText({ text: lastText, step }); // next chunk will start with overlayText
|
||||
i--;
|
||||
|
||||
continue;
|
||||
}
|
||||
// chunk is small, insert to next chunks
|
||||
|
||||
// split new Text, split chunks must will greater 1 (small lastText)
|
||||
const innerChunks = splitTextRecursively({
|
||||
text,
|
||||
text: newText,
|
||||
step: step + 1,
|
||||
lastChunk,
|
||||
overlayChunk
|
||||
lastText: '',
|
||||
mdTitle: currentTitle
|
||||
});
|
||||
if (innerChunks.length === 0) continue;
|
||||
chunks = chunks.concat(innerChunks);
|
||||
lastChunk = '';
|
||||
overlayChunk = '';
|
||||
const lastChunk = innerChunks[innerChunks.length - 1];
|
||||
// last chunk is too small, concat it to lastText
|
||||
if (!isMarkdownSplit && lastChunk.length < minChunkLen) {
|
||||
chunks.push(...innerChunks.slice(0, -1));
|
||||
lastText = lastChunk;
|
||||
} else {
|
||||
chunks.push(...innerChunks);
|
||||
// compute new overlapText
|
||||
lastText = getOneTextOverlapText({
|
||||
text: lastChunk,
|
||||
step
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// size less than maxLen, push text to last chunk
|
||||
lastChunk += text;
|
||||
chunkToken += textToken; // Definitely less than 1.4 * maxLen
|
||||
// size less than chunkLen, push text to last chunk. now, text definitely less than maxLen
|
||||
lastText = newText;
|
||||
|
||||
// size over lapLen, push it to next chunk
|
||||
if (
|
||||
overlapLen !== 0 &&
|
||||
!isMarkdownSplit &&
|
||||
chunkToken >= maxLen - overlapLen &&
|
||||
textToken < overlapLen
|
||||
) {
|
||||
overlayChunk += text;
|
||||
}
|
||||
if (chunkToken >= maxLen) {
|
||||
chunks.push(lastChunk);
|
||||
lastChunk = overlayChunk;
|
||||
overlayChunk = '';
|
||||
// markdown paragraph block: Direct addition; If the chunk size reaches, add a chunk
|
||||
if (isMarkdownSplit || newTextLen >= chunkLen) {
|
||||
chunks.push(`${currentTitle}${lastText}`);
|
||||
|
||||
lastText = isMarkdownSplit ? '' : getOneTextOverlapText({ text: lastText, step });
|
||||
}
|
||||
}
|
||||
|
||||
/* If the last chunk is independent, it needs to be push chunks. */
|
||||
if (lastChunk && chunks[chunks.length - 1] && !chunks[chunks.length - 1].endsWith(lastChunk)) {
|
||||
chunks.push(lastChunk);
|
||||
if (lastText && chunks[chunks.length - 1] && !chunks[chunks.length - 1].endsWith(lastText)) {
|
||||
if (lastText.length < chunkLen * 0.4) {
|
||||
chunks[chunks.length - 1] = chunks[chunks.length - 1] + lastText;
|
||||
} else {
|
||||
chunks.push(`${mdTitle}${lastText}`);
|
||||
}
|
||||
}
|
||||
|
||||
return chunks;
|
||||
};
|
||||
|
||||
try {
|
||||
const chunks = splitTextRecursively({ text, step: 0, lastChunk: '', overlayChunk: '' });
|
||||
const chunks = splitTextRecursively({
|
||||
text,
|
||||
step: 0,
|
||||
lastText: '',
|
||||
mdTitle: ''
|
||||
}).map((chunk) => chunk.replaceAll(codeBlockMarker, '\n')); // restore code block
|
||||
|
||||
const tokens = chunks.reduce((sum, chunk) => sum + countPromptTokens(chunk, 'system'), 0);
|
||||
|
||||
|
||||
3
packages/global/common/string/time.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export const formatTime2YMDHM = (time: Date) => dayjs(time).format('YYYY-MM-DD HH:mm');
|
||||
@@ -13,7 +13,8 @@ export const hashStr = (str: string) => {
|
||||
};
|
||||
|
||||
/* simple text, remove chinese space and extra \n */
|
||||
export const simpleText = (text: string) => {
|
||||
export const simpleText = (text = '') => {
|
||||
text = text.trim();
|
||||
text = text.replace(/([\u4e00-\u9fa5])[\s&&[^\n]]+([\u4e00-\u9fa5])/g, '$1$2');
|
||||
text = text.replace(/\r\n|\r/g, '\n');
|
||||
text = text.replace(/\n{3,}/g, '\n\n');
|
||||
@@ -24,7 +25,7 @@ export const simpleText = (text: string) => {
|
||||
};
|
||||
|
||||
/*
|
||||
replace {{variable}} to value
|
||||
replace {{variable}} to value
|
||||
*/
|
||||
export function replaceVariable(text: string, obj: Record<string, string | number>) {
|
||||
for (const key in obj) {
|
||||
|
||||
@@ -2,17 +2,15 @@ export type FeConfigsType = {
|
||||
show_emptyChat?: boolean;
|
||||
show_register?: boolean;
|
||||
show_appStore?: boolean;
|
||||
show_contact?: boolean;
|
||||
show_git?: boolean;
|
||||
show_doc?: boolean;
|
||||
show_pay?: boolean;
|
||||
show_openai_account?: boolean;
|
||||
show_promotion?: boolean;
|
||||
hide_app_flow?: boolean;
|
||||
concatMd?: string;
|
||||
docUrl?: string;
|
||||
openAPIDocUrl?: string;
|
||||
systemTitle?: string;
|
||||
authorText?: string;
|
||||
googleClientVerKey?: string;
|
||||
isPlus?: boolean;
|
||||
oauth?: {
|
||||
|
||||
6
packages/global/common/system/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export const delay = (ms: number) =>
|
||||
new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve('');
|
||||
}, ms);
|
||||
});
|
||||
2
packages/global/core/ai/api.d.ts
vendored
@@ -2,4 +2,4 @@ export type PostReRankProps = {
|
||||
query: string;
|
||||
inputs: { id: string; text: string }[];
|
||||
};
|
||||
export type PostReRankResponse = { id: string; score: number }[];
|
||||
export type PostReRankResponse = { id: string; score?: number }[];
|
||||
|
||||
16
packages/global/core/ai/model.d.ts
vendored
@@ -9,6 +9,7 @@ export type ChatModelItemType = LLMModelItemType & {
|
||||
quoteMaxToken: number;
|
||||
maxTemperature: number;
|
||||
censor?: boolean;
|
||||
vision?: boolean;
|
||||
defaultSystemChatPrompt?: string;
|
||||
};
|
||||
|
||||
@@ -25,8 +26,23 @@ export type VectorModelItemType = {
|
||||
maxToken: number;
|
||||
};
|
||||
|
||||
export type ReRankModelItemType = {
|
||||
model: string;
|
||||
name: string;
|
||||
price: number;
|
||||
requestUrl?: string;
|
||||
requestAuth?: string;
|
||||
};
|
||||
|
||||
export type AudioSpeechModelType = {
|
||||
model: string;
|
||||
name: string;
|
||||
price: number;
|
||||
voices: { label: string; value: string; bufferId: string }[];
|
||||
};
|
||||
|
||||
export type WhisperModelType = {
|
||||
model: string;
|
||||
name: string;
|
||||
price: number;
|
||||
};
|
||||
|
||||
@@ -3,7 +3,9 @@ import type {
|
||||
ChatModelItemType,
|
||||
FunctionModelItemType,
|
||||
VectorModelItemType,
|
||||
AudioSpeechModelType
|
||||
AudioSpeechModelType,
|
||||
WhisperModelType,
|
||||
ReRankModelItemType
|
||||
} from './model.d';
|
||||
|
||||
export const defaultChatModels: ChatModelItemType[] = [
|
||||
@@ -16,6 +18,7 @@ export const defaultChatModels: ChatModelItemType[] = [
|
||||
quoteMaxToken: 2000,
|
||||
maxTemperature: 1.2,
|
||||
censor: false,
|
||||
vision: false,
|
||||
defaultSystemChatPrompt: ''
|
||||
},
|
||||
{
|
||||
@@ -27,6 +30,7 @@ export const defaultChatModels: ChatModelItemType[] = [
|
||||
quoteMaxToken: 8000,
|
||||
maxTemperature: 1.2,
|
||||
censor: false,
|
||||
vision: false,
|
||||
defaultSystemChatPrompt: ''
|
||||
},
|
||||
{
|
||||
@@ -38,6 +42,19 @@ export const defaultChatModels: ChatModelItemType[] = [
|
||||
quoteMaxToken: 4000,
|
||||
maxTemperature: 1.2,
|
||||
censor: false,
|
||||
vision: false,
|
||||
defaultSystemChatPrompt: ''
|
||||
},
|
||||
{
|
||||
model: 'gpt-4-vision-preview',
|
||||
name: 'GPT4-Vision',
|
||||
maxContext: 128000,
|
||||
maxResponse: 4000,
|
||||
price: 0,
|
||||
quoteMaxToken: 100000,
|
||||
maxTemperature: 1.2,
|
||||
censor: false,
|
||||
vision: true,
|
||||
defaultSystemChatPrompt: ''
|
||||
}
|
||||
];
|
||||
@@ -101,15 +118,26 @@ export const defaultVectorModels: VectorModelItemType[] = [
|
||||
}
|
||||
];
|
||||
|
||||
export const defaultReRankModels: ReRankModelItemType[] = [];
|
||||
|
||||
export const defaultAudioSpeechModels: AudioSpeechModelType[] = [
|
||||
{
|
||||
model: 'tts-1',
|
||||
name: 'OpenAI TTS1',
|
||||
price: 0
|
||||
},
|
||||
{
|
||||
model: 'tts-1-hd',
|
||||
name: 'OpenAI TTS1',
|
||||
price: 0
|
||||
price: 0,
|
||||
voices: [
|
||||
{ label: 'Alloy', value: 'Alloy', bufferId: 'openai-Alloy' },
|
||||
{ label: 'Echo', value: 'Echo', bufferId: 'openai-Echo' },
|
||||
{ label: 'Fable', value: 'Fable', bufferId: 'openai-Fable' },
|
||||
{ label: 'Onyx', value: 'Onyx', bufferId: 'openai-Onyx' },
|
||||
{ label: 'Nova', value: 'Nova', bufferId: 'openai-Nova' },
|
||||
{ label: 'Shimmer', value: 'Shimmer', bufferId: 'openai-Shimmer' }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export const defaultWhisperModel: WhisperModelType = {
|
||||
model: 'whisper-1',
|
||||
name: 'Whisper1',
|
||||
price: 0
|
||||
};
|
||||
|
||||
8
packages/global/core/ai/speech/api.d.ts
vendored
@@ -1,8 +0,0 @@
|
||||
import { Text2SpeechVoiceEnum } from './constant';
|
||||
|
||||
export type Text2SpeechProps = {
|
||||
model?: string;
|
||||
voice?: `${Text2SpeechVoiceEnum}`;
|
||||
input: string;
|
||||
speed?: number;
|
||||
};
|
||||
@@ -1,17 +0,0 @@
|
||||
export enum Text2SpeechVoiceEnum {
|
||||
alloy = 'alloy',
|
||||
echo = 'echo',
|
||||
fable = 'fable',
|
||||
onyx = 'onyx',
|
||||
nova = 'nova',
|
||||
shimmer = 'shimmer'
|
||||
}
|
||||
export const openaiTTSList = [
|
||||
Text2SpeechVoiceEnum.alloy,
|
||||
Text2SpeechVoiceEnum.echo,
|
||||
Text2SpeechVoiceEnum.fable,
|
||||
Text2SpeechVoiceEnum.onyx,
|
||||
Text2SpeechVoiceEnum.nova,
|
||||
Text2SpeechVoiceEnum.shimmer
|
||||
];
|
||||
export const openaiTTSModel = 'tts-1';
|
||||
6
packages/global/core/ai/type.d.ts
vendored
@@ -5,12 +5,14 @@ import type {
|
||||
ChatCompletionMessageParam,
|
||||
ChatCompletionContentPart
|
||||
} from 'openai/resources';
|
||||
|
||||
export type ChatCompletionContentPart = ChatCompletionContentPart;
|
||||
export type ChatCompletionCreateParams = ChatCompletionCreateParams;
|
||||
export type ChatMessageItemType = Omit<ChatCompletionMessageParam> & {
|
||||
export type ChatMessageItemType = Omit<ChatCompletionMessageParam, 'name'> & {
|
||||
name?: any;
|
||||
dataId?: string;
|
||||
content: any;
|
||||
};
|
||||
} & any;
|
||||
|
||||
export type ChatCompletion = ChatCompletion;
|
||||
export type StreamChatType = Stream<ChatCompletionChunk>;
|
||||
|
||||
10
packages/global/core/app/api.d.ts
vendored
@@ -1,5 +1,6 @@
|
||||
import type { ChatModelItemType } from '../ai/model.d';
|
||||
import { AppTypeEnum } from './constants';
|
||||
import { AppSchema } from './type';
|
||||
import { AppSchema, AppSimpleEditFormType } from './type';
|
||||
|
||||
export type CreateAppParams = {
|
||||
name?: string;
|
||||
@@ -11,8 +12,15 @@ export type CreateAppParams = {
|
||||
export interface AppUpdateParams {
|
||||
name?: string;
|
||||
type?: `${AppTypeEnum}`;
|
||||
simpleTemplateId?: string;
|
||||
avatar?: string;
|
||||
intro?: string;
|
||||
modules?: AppSchema['modules'];
|
||||
permission?: AppSchema['permission'];
|
||||
}
|
||||
|
||||
export type FormatForm2ModulesProps = {
|
||||
formData: AppSimpleEditFormType;
|
||||
chatModelMaxToken: number;
|
||||
chatModelList: ChatModelItemType[];
|
||||
};
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
export enum AppTypeEnum {
|
||||
basic = 'basic',
|
||||
simple = 'simple',
|
||||
advanced = 'advanced'
|
||||
}
|
||||
export const AppTypeMap = {
|
||||
[AppTypeEnum.simple]: {
|
||||
label: 'simple'
|
||||
},
|
||||
[AppTypeEnum.advanced]: {
|
||||
label: 'advanced'
|
||||
}
|
||||
};
|
||||
|
||||
93
packages/global/core/app/type.d.ts
vendored
@@ -1,7 +1,10 @@
|
||||
import { ModuleItemType } from '../module/type';
|
||||
import type { AppTTSConfigType, ModuleItemType, VariableItemType } from '../module/type.d';
|
||||
import { AppTypeEnum } from './constants';
|
||||
import { PermissionTypeEnum } from '../../support/permission/constant';
|
||||
import { Text2SpeechVoiceEnum } from '../ai/speech/constant';
|
||||
import type { AIChatModuleProps, DatasetModuleProps } from '../module/node/type.d';
|
||||
import { VariableInputEnum } from '../module/constants';
|
||||
import { SelectedDatasetType } from '../module/api';
|
||||
import { DatasetSearchModeEnum } from '../dataset/constant';
|
||||
|
||||
export interface AppSchema {
|
||||
_id: string;
|
||||
@@ -10,11 +13,13 @@ export interface AppSchema {
|
||||
tmbId: string;
|
||||
name: string;
|
||||
type: `${AppTypeEnum}`;
|
||||
simpleTemplateId: string;
|
||||
avatar: string;
|
||||
intro: string;
|
||||
updateTime: number;
|
||||
modules: ModuleItemType[];
|
||||
permission: `${PermissionTypeEnum}`;
|
||||
inited?: boolean;
|
||||
}
|
||||
|
||||
export type AppListItemType = {
|
||||
@@ -30,3 +35,87 @@ export type AppDetailType = AppSchema & {
|
||||
isOwner: boolean;
|
||||
canWrite: boolean;
|
||||
};
|
||||
|
||||
// export type AppSimpleEditFormType = {
|
||||
// aiSettings: AIChatModuleProps;
|
||||
// dataset: DatasetModuleProps & {
|
||||
// searchEmptyText: string;
|
||||
// };
|
||||
// userGuide: {
|
||||
// welcomeText: string;
|
||||
// variables: VariableItemType[];
|
||||
// questionGuide: boolean;
|
||||
// tts: AppTTSConfigType;
|
||||
// };
|
||||
// };
|
||||
// Since useform cannot infer enumeration types, all enumeration keys can only be undone manually
|
||||
export type AppSimpleEditFormType = {
|
||||
templateId: string;
|
||||
aiSettings: {
|
||||
model: string;
|
||||
systemPrompt?: string | undefined;
|
||||
temperature: number;
|
||||
maxToken: number;
|
||||
isResponseAnswerText: boolean;
|
||||
quoteTemplate?: string | undefined;
|
||||
quotePrompt?: string | undefined;
|
||||
};
|
||||
dataset: {
|
||||
datasets: SelectedDatasetType;
|
||||
similarity: number;
|
||||
limit: number;
|
||||
searchMode: `${DatasetSearchModeEnum}`;
|
||||
searchEmptyText: string;
|
||||
};
|
||||
userGuide: {
|
||||
welcomeText: string;
|
||||
variables: {
|
||||
id: string;
|
||||
key: string;
|
||||
label: string;
|
||||
type: `${VariableInputEnum}`;
|
||||
required: boolean;
|
||||
maxLen: number;
|
||||
enums: {
|
||||
value: string;
|
||||
}[];
|
||||
}[];
|
||||
questionGuide: boolean;
|
||||
tts: {
|
||||
type: 'none' | 'web' | 'model';
|
||||
model?: string | undefined;
|
||||
voice?: string | undefined;
|
||||
speed?: number | undefined;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/* simple mode template*/
|
||||
export type AppSimpleEditConfigTemplateType = {
|
||||
id: string;
|
||||
name: string;
|
||||
desc: string;
|
||||
systemForm: {
|
||||
aiSettings?: {
|
||||
model?: boolean;
|
||||
systemPrompt?: boolean;
|
||||
temperature?: boolean;
|
||||
maxToken?: boolean;
|
||||
quoteTemplate?: boolean;
|
||||
quotePrompt?: boolean;
|
||||
};
|
||||
dataset?: {
|
||||
datasets?: boolean;
|
||||
similarity?: boolean;
|
||||
limit?: boolean;
|
||||
searchMode: `${DatasetSearchModeEnum}`;
|
||||
searchEmptyText?: boolean;
|
||||
};
|
||||
userGuide?: {
|
||||
welcomeText?: boolean;
|
||||
variables?: boolean;
|
||||
questionGuide?: boolean;
|
||||
tts?: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
123
packages/global/core/app/utils.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import type { AppSimpleEditFormType } from '../app/type';
|
||||
import { FlowNodeTypeEnum } from '../module/node/constant';
|
||||
import { ModuleOutputKeyEnum, ModuleInputKeyEnum } from '../module/constants';
|
||||
import type { FlowNodeInputItemType } from '../module/node/type.d';
|
||||
import { getGuideModule, splitGuideModule } from '../module/utils';
|
||||
import { defaultChatModels } from '../ai/model';
|
||||
import { ModuleItemType } from '../module/type.d';
|
||||
import { DatasetSearchModeEnum } from '../dataset/constant';
|
||||
|
||||
export const getDefaultAppForm = (templateId = 'fastgpt-universal'): AppSimpleEditFormType => {
|
||||
const defaultChatModel = defaultChatModels[0];
|
||||
|
||||
return {
|
||||
templateId,
|
||||
aiSettings: {
|
||||
model: defaultChatModel?.model,
|
||||
systemPrompt: '',
|
||||
temperature: 0,
|
||||
isResponseAnswerText: true,
|
||||
quotePrompt: '',
|
||||
quoteTemplate: '',
|
||||
maxToken: defaultChatModel ? defaultChatModel.maxResponse / 2 : 4000
|
||||
},
|
||||
dataset: {
|
||||
datasets: [],
|
||||
similarity: 0.4,
|
||||
limit: 5,
|
||||
searchEmptyText: '',
|
||||
searchMode: DatasetSearchModeEnum.embedding
|
||||
},
|
||||
userGuide: {
|
||||
welcomeText: '',
|
||||
variables: [],
|
||||
questionGuide: false,
|
||||
tts: {
|
||||
type: 'web'
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/* format app modules to edit form */
|
||||
export const appModules2Form = ({
|
||||
templateId,
|
||||
modules
|
||||
}: {
|
||||
modules: ModuleItemType[];
|
||||
templateId: string;
|
||||
}) => {
|
||||
const defaultAppForm = getDefaultAppForm(templateId);
|
||||
|
||||
const findInputValueByKey = (inputs: FlowNodeInputItemType[], key: string) => {
|
||||
return inputs.find((item) => item.key === key)?.value;
|
||||
};
|
||||
|
||||
modules.forEach((module) => {
|
||||
if (module.flowType === FlowNodeTypeEnum.chatNode) {
|
||||
defaultAppForm.aiSettings.model = findInputValueByKey(
|
||||
module.inputs,
|
||||
ModuleInputKeyEnum.aiModel
|
||||
);
|
||||
defaultAppForm.aiSettings.systemPrompt = findInputValueByKey(
|
||||
module.inputs,
|
||||
ModuleInputKeyEnum.aiSystemPrompt
|
||||
);
|
||||
defaultAppForm.aiSettings.temperature = findInputValueByKey(
|
||||
module.inputs,
|
||||
ModuleInputKeyEnum.aiChatTemperature
|
||||
);
|
||||
defaultAppForm.aiSettings.maxToken = findInputValueByKey(
|
||||
module.inputs,
|
||||
ModuleInputKeyEnum.aiChatMaxToken
|
||||
);
|
||||
defaultAppForm.aiSettings.quoteTemplate = findInputValueByKey(
|
||||
module.inputs,
|
||||
ModuleInputKeyEnum.aiChatQuoteTemplate
|
||||
);
|
||||
defaultAppForm.aiSettings.quotePrompt = findInputValueByKey(
|
||||
module.inputs,
|
||||
ModuleInputKeyEnum.aiChatQuotePrompt
|
||||
);
|
||||
} else if (module.flowType === FlowNodeTypeEnum.datasetSearchNode) {
|
||||
defaultAppForm.dataset.datasets = findInputValueByKey(
|
||||
module.inputs,
|
||||
ModuleInputKeyEnum.datasetSelectList
|
||||
);
|
||||
defaultAppForm.dataset.similarity = findInputValueByKey(
|
||||
module.inputs,
|
||||
ModuleInputKeyEnum.datasetSimilarity
|
||||
);
|
||||
defaultAppForm.dataset.limit = findInputValueByKey(
|
||||
module.inputs,
|
||||
ModuleInputKeyEnum.datasetLimit
|
||||
);
|
||||
defaultAppForm.dataset.searchMode =
|
||||
findInputValueByKey(module.inputs, ModuleInputKeyEnum.datasetSearchMode) ||
|
||||
DatasetSearchModeEnum.embedding;
|
||||
|
||||
// empty text
|
||||
const emptyOutputs =
|
||||
module.outputs.find((item) => item.key === ModuleOutputKeyEnum.datasetIsEmpty)?.targets ||
|
||||
[];
|
||||
const emptyOutput = emptyOutputs[0];
|
||||
if (emptyOutput) {
|
||||
const target = modules.find((item) => item.moduleId === emptyOutput.moduleId);
|
||||
defaultAppForm.dataset.searchEmptyText =
|
||||
target?.inputs?.find((item) => item.key === ModuleInputKeyEnum.answerText)?.value || '';
|
||||
}
|
||||
} else if (module.flowType === FlowNodeTypeEnum.userGuide) {
|
||||
const { welcomeText, variableModules, questionGuide, ttsConfig } = splitGuideModule(
|
||||
getGuideModule(modules)
|
||||
);
|
||||
defaultAppForm.userGuide = {
|
||||
welcomeText: welcomeText,
|
||||
variables: variableModules,
|
||||
questionGuide: questionGuide,
|
||||
tts: ttsConfig
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return defaultAppForm;
|
||||
};
|
||||
1
packages/global/core/chat/api.d.ts
vendored
@@ -30,5 +30,4 @@ export type InitChatResponse = {
|
||||
export type ChatHistoryItemResType = moduleDispatchResType & {
|
||||
moduleType: `${FlowNodeTypeEnum}`;
|
||||
moduleName: string;
|
||||
moduleLogo?: string;
|
||||
};
|
||||
|
||||
@@ -5,13 +5,6 @@ export enum ChatRoleEnum {
|
||||
Function = 'Function',
|
||||
Tool = 'Tool'
|
||||
}
|
||||
|
||||
export enum TaskResponseKeyEnum {
|
||||
'answerText' = 'answerText', // answer module text key
|
||||
'responseData' = 'responseData',
|
||||
'history' = 'history'
|
||||
}
|
||||
|
||||
export const ChatRoleMap = {
|
||||
[ChatRoleEnum.System]: {
|
||||
name: '系统提示词'
|
||||
@@ -36,7 +29,6 @@ export enum ChatSourceEnum {
|
||||
share = 'share',
|
||||
api = 'api'
|
||||
}
|
||||
|
||||
export const ChatSourceMap = {
|
||||
[ChatSourceEnum.test]: {
|
||||
name: 'chat.logs.test'
|
||||
@@ -54,3 +46,6 @@ export const ChatSourceMap = {
|
||||
|
||||
export const HUMAN_ICON = `/icon/human.svg`;
|
||||
export const LOGO_ICON = `/icon/logo.svg`;
|
||||
|
||||
export const IMG_BLOCK_KEY = 'img-block';
|
||||
export const FILE_BLOCK_KEY = 'file-block';
|
||||
|
||||
21
packages/global/core/chat/type.d.ts
vendored
@@ -1,8 +1,10 @@
|
||||
import { ClassifyQuestionAgentItemType } from '../module/type';
|
||||
import { SearchDataResponseItemType } from '../dataset/type';
|
||||
import { ChatRoleEnum, ChatSourceEnum, TaskResponseKeyEnum } from './constants';
|
||||
import { ChatRoleEnum, ChatSourceEnum } from './constants';
|
||||
import { FlowNodeTypeEnum } from '../module/node/constant';
|
||||
import { AppSchema } from 'core/app/type';
|
||||
import { ModuleOutputKeyEnum } from '../module/constants';
|
||||
import { AppSchema } from '../app/type';
|
||||
import { DatasetSearchModeEnum } from '../dataset/constant';
|
||||
|
||||
export type ChatSchema = {
|
||||
_id: string;
|
||||
@@ -38,8 +40,7 @@ export type ChatItemSchema = {
|
||||
value: string;
|
||||
userFeedback?: string;
|
||||
adminFeedback?: AdminFbkType;
|
||||
[TaskResponseKeyEnum.responseData]?: ChatHistoryItemResType[];
|
||||
tts?: Buffer;
|
||||
[ModuleOutputKeyEnum.responseData]?: ChatHistoryItemResType[];
|
||||
};
|
||||
|
||||
export type AdminFbkType = {
|
||||
@@ -56,14 +57,14 @@ export type ChatItemType = {
|
||||
value: any;
|
||||
userFeedback?: string;
|
||||
adminFeedback?: ChatItemSchema['feedback'];
|
||||
[TaskResponseKeyEnum.responseData]?: ChatItemSchema[TaskResponseKeyEnum.responseData];
|
||||
[ModuleOutputKeyEnum.responseData]?: ChatHistoryItemResType[];
|
||||
};
|
||||
|
||||
export type ChatSiteItemType = {
|
||||
export type ChatSiteItemType = ChatItemType & {
|
||||
status: 'loading' | 'running' | 'finish';
|
||||
moduleName?: string;
|
||||
ttsBuffer?: Buffer;
|
||||
} & ChatItemType;
|
||||
ttsBuffer?: Uint8Array;
|
||||
};
|
||||
|
||||
export type HistoryItemType = {
|
||||
chatId: string;
|
||||
@@ -78,13 +79,14 @@ export type ChatHistoryItemType = HistoryItemType & {
|
||||
|
||||
// response data
|
||||
export type moduleDispatchResType = {
|
||||
moduleLogo?: string;
|
||||
price: number;
|
||||
runningTime?: number;
|
||||
tokens?: number;
|
||||
model?: string;
|
||||
query?: string;
|
||||
|
||||
// chat
|
||||
question?: string;
|
||||
temperature?: number;
|
||||
maxToken?: number;
|
||||
quoteList?: SearchDataResponseItemType[];
|
||||
@@ -93,6 +95,7 @@ export type moduleDispatchResType = {
|
||||
// dataset search
|
||||
similarity?: number;
|
||||
limit?: number;
|
||||
searchMode?: `${DatasetSearchModeEnum}`;
|
||||
|
||||
// cq
|
||||
cqList?: ClassifyQuestionAgentItemType[];
|
||||
|
||||
6
packages/global/core/chat/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { IMG_BLOCK_KEY, FILE_BLOCK_KEY } from './constants';
|
||||
|
||||
export function chatContentReplaceBlock(content: string = '') {
|
||||
const regex = new RegExp(`\`\`\`(${IMG_BLOCK_KEY})\\n([\\s\\S]*?)\`\`\``, 'g');
|
||||
return content.replace(regex, '').trim();
|
||||
}
|
||||
31
packages/global/core/dataset/api.d.ts
vendored
@@ -1,8 +1,32 @@
|
||||
import { DatasetDataIndexItemType } from './type';
|
||||
import { DatasetDataIndexItemType, DatasetSchemaType } from './type';
|
||||
import { DatasetCollectionTrainingModeEnum, DatasetCollectionTypeEnum } from './constant';
|
||||
import type { LLMModelItemType } from '../ai/model.d';
|
||||
|
||||
/* ================= dataset ===================== */
|
||||
export type DatasetUpdateBody = {
|
||||
id: string;
|
||||
parentId?: string;
|
||||
tags?: string[];
|
||||
name?: string;
|
||||
avatar?: string;
|
||||
permission?: DatasetSchemaType['permission'];
|
||||
agentModel?: LLMModelItemType;
|
||||
websiteConfig?: DatasetSchemaType['websiteConfig'];
|
||||
status?: DatasetSchemaType['status'];
|
||||
};
|
||||
|
||||
/* ================= collection ===================== */
|
||||
export type CreateDatasetCollectionParams = {
|
||||
datasetId: string;
|
||||
parentId?: string;
|
||||
name: string;
|
||||
type: `${DatasetCollectionTypeEnum}`;
|
||||
trainingType?: `${DatasetCollectionTrainingModeEnum}`;
|
||||
chunkSize?: number;
|
||||
fileId?: string;
|
||||
rawLink?: string;
|
||||
metadata?: Record<string, any>;
|
||||
};
|
||||
|
||||
/* ================= data ===================== */
|
||||
export type PgSearchRawType = {
|
||||
@@ -18,3 +42,8 @@ export type PushDatasetDataChunkProps = {
|
||||
a?: string; // bonus content
|
||||
indexes?: Omit<DatasetDataIndexItemType, 'dataId'>[];
|
||||
};
|
||||
|
||||
export type PostWebsiteSyncParams = {
|
||||
datasetId: string;
|
||||
billId: string;
|
||||
};
|
||||
|
||||
@@ -1,41 +1,81 @@
|
||||
export const PgDatasetTableName = 'modeldata';
|
||||
|
||||
/* ------------ dataset -------------- */
|
||||
export enum DatasetTypeEnum {
|
||||
folder = 'folder',
|
||||
dataset = 'dataset'
|
||||
dataset = 'dataset',
|
||||
websiteDataset = 'websiteDataset' // depp link
|
||||
}
|
||||
|
||||
export const DatasetTypeMap = {
|
||||
[DatasetTypeEnum.folder]: {
|
||||
name: 'folder'
|
||||
icon: 'core/dataset/folderDataset',
|
||||
label: 'core.dataset.Folder Dataset',
|
||||
collectionLabel: 'common.Folder'
|
||||
},
|
||||
[DatasetTypeEnum.dataset]: {
|
||||
name: 'dataset'
|
||||
icon: 'core/dataset/commonDataset',
|
||||
label: 'core.dataset.Common Dataset',
|
||||
collectionLabel: 'common.File'
|
||||
},
|
||||
[DatasetTypeEnum.websiteDataset]: {
|
||||
icon: 'core/dataset/websiteDataset',
|
||||
label: 'core.dataset.Website Dataset',
|
||||
collectionLabel: 'common.Website'
|
||||
}
|
||||
};
|
||||
|
||||
export enum DatasetStatusEnum {
|
||||
active = 'active',
|
||||
syncing = 'syncing'
|
||||
}
|
||||
export const DatasetStatusMap = {
|
||||
[DatasetStatusEnum.active]: {
|
||||
label: 'core.dataset.status.active'
|
||||
},
|
||||
[DatasetStatusEnum.syncing]: {
|
||||
label: 'core.dataset.status.syncing'
|
||||
}
|
||||
};
|
||||
|
||||
/* ------------ collection -------------- */
|
||||
export enum DatasetCollectionTypeEnum {
|
||||
file = 'file',
|
||||
folder = 'folder',
|
||||
link = 'link',
|
||||
file = 'file',
|
||||
link = 'link', // one link
|
||||
virtual = 'virtual'
|
||||
}
|
||||
|
||||
export const DatasetCollectionTypeMap = {
|
||||
[DatasetCollectionTypeEnum.file]: {
|
||||
name: 'dataset.file'
|
||||
},
|
||||
[DatasetCollectionTypeEnum.folder]: {
|
||||
name: 'dataset.folder'
|
||||
name: 'core.dataset.folder'
|
||||
},
|
||||
[DatasetCollectionTypeEnum.file]: {
|
||||
name: 'core.dataset.file'
|
||||
},
|
||||
[DatasetCollectionTypeEnum.link]: {
|
||||
name: 'dataset.link'
|
||||
name: 'core.dataset.link'
|
||||
},
|
||||
[DatasetCollectionTypeEnum.virtual]: {
|
||||
name: 'dataset.Virtual File'
|
||||
name: 'core.dataset.Virtual File'
|
||||
}
|
||||
};
|
||||
export enum DatasetCollectionTrainingModeEnum {
|
||||
manual = 'manual',
|
||||
chunk = 'chunk',
|
||||
qa = 'qa'
|
||||
}
|
||||
export const DatasetCollectionTrainingTypeMap = {
|
||||
[DatasetCollectionTrainingModeEnum.manual]: {
|
||||
label: 'core.dataset.collection.training.type manual'
|
||||
},
|
||||
[DatasetCollectionTrainingModeEnum.chunk]: {
|
||||
label: 'core.dataset.collection.training.type chunk'
|
||||
},
|
||||
[DatasetCollectionTrainingModeEnum.qa]: {
|
||||
label: 'core.dataset.collection.training.type qa'
|
||||
}
|
||||
};
|
||||
|
||||
/* ------------ data -------------- */
|
||||
export enum DatasetDataIndexTypeEnum {
|
||||
chunk = 'chunk',
|
||||
qa = 'qa',
|
||||
@@ -61,29 +101,47 @@ export const DatasetDataIndexTypeMap = {
|
||||
}
|
||||
};
|
||||
|
||||
/* ------------ training -------------- */
|
||||
export enum TrainingModeEnum {
|
||||
'chunk' = 'chunk',
|
||||
'qa' = 'qa'
|
||||
// 'hypothetical' = 'hypothetical',
|
||||
// 'summary' = 'summary',
|
||||
// 'multipleIndex' = 'multipleIndex'
|
||||
chunk = 'chunk',
|
||||
qa = 'qa'
|
||||
}
|
||||
|
||||
export const TrainingTypeMap = {
|
||||
[TrainingModeEnum.chunk]: {
|
||||
name: 'chunk'
|
||||
label: 'core.dataset.training.type chunk'
|
||||
},
|
||||
[TrainingModeEnum.qa]: {
|
||||
name: 'qa'
|
||||
label: 'core.dataset.training.type qa'
|
||||
}
|
||||
};
|
||||
|
||||
/* ------------ search -------------- */
|
||||
export enum DatasetSearchModeEnum {
|
||||
embedding = 'embedding',
|
||||
embeddingReRank = 'embeddingReRank',
|
||||
embFullTextReRank = 'embFullTextReRank'
|
||||
}
|
||||
|
||||
export const DatasetSearchModeMap = {
|
||||
[DatasetSearchModeEnum.embedding]: {
|
||||
icon: 'core/dataset/modeEmbedding',
|
||||
title: 'core.dataset.search.mode.embedding',
|
||||
desc: 'core.dataset.search.mode.embedding desc',
|
||||
value: DatasetSearchModeEnum.embedding
|
||||
},
|
||||
[DatasetSearchModeEnum.embeddingReRank]: {
|
||||
icon: 'core/dataset/modeEmbeddingRerank',
|
||||
title: 'core.dataset.search.mode.embeddingReRank',
|
||||
desc: 'core.dataset.search.mode.embeddingReRank desc',
|
||||
value: DatasetSearchModeEnum.embeddingReRank
|
||||
},
|
||||
[DatasetSearchModeEnum.embFullTextReRank]: {
|
||||
icon: 'core/dataset/modeEmbFTRerank',
|
||||
title: 'core.dataset.search.mode.embFullTextReRank',
|
||||
desc: 'core.dataset.search.mode.embFullTextReRank desc',
|
||||
value: DatasetSearchModeEnum.embFullTextReRank
|
||||
}
|
||||
// [TrainingModeEnum.hypothetical]: {
|
||||
// name: 'hypothetical'
|
||||
// },
|
||||
// [TrainingModeEnum.summary]: {
|
||||
// name: 'summary'
|
||||
// },
|
||||
// [TrainingModeEnum.multipleIndex]: {
|
||||
// name: 'multipleIndex'
|
||||
// }
|
||||
};
|
||||
|
||||
export const FolderAvatarSrc = '/imgs/files/folder.svg';
|
||||
|
||||
1
packages/global/core/dataset/controller.d.ts
vendored
@@ -5,6 +5,7 @@ export type CreateDatasetDataProps = {
|
||||
tmbId: string;
|
||||
datasetId: string;
|
||||
collectionId: string;
|
||||
chunkIndex?: number;
|
||||
q: string;
|
||||
a?: string;
|
||||
indexes?: Omit<DatasetDataIndexItemType, 'dataId'>[];
|
||||
|
||||
42
packages/global/core/dataset/type.d.ts
vendored
@@ -4,6 +4,7 @@ import { PushDatasetDataChunkProps } from './api';
|
||||
import {
|
||||
DatasetCollectionTypeEnum,
|
||||
DatasetDataIndexTypeEnum,
|
||||
DatasetStatusEnum,
|
||||
DatasetTypeEnum,
|
||||
TrainingModeEnum
|
||||
} from './constant';
|
||||
@@ -20,26 +21,31 @@ export type DatasetSchemaType = {
|
||||
name: string;
|
||||
vectorModel: string;
|
||||
agentModel: string;
|
||||
tags: string[];
|
||||
intro: string;
|
||||
type: `${DatasetTypeEnum}`;
|
||||
status: `${DatasetStatusEnum}`;
|
||||
permission: `${PermissionTypeEnum}`;
|
||||
websiteConfig?: {
|
||||
url: string;
|
||||
selector: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type DatasetCollectionSchemaType = {
|
||||
_id: string;
|
||||
userId: string;
|
||||
teamId: string;
|
||||
tmbId: string;
|
||||
datasetId: string;
|
||||
parentId?: string;
|
||||
name: string;
|
||||
type: `${DatasetCollectionTypeEnum}`;
|
||||
createTime: Date;
|
||||
updateTime: Date;
|
||||
metadata: {
|
||||
fileId?: string;
|
||||
rawLink?: string;
|
||||
pgCollectionId?: string;
|
||||
};
|
||||
trainingType: `${TrainingModeEnum}`;
|
||||
chunkSize: number;
|
||||
fileId?: string;
|
||||
rawLink?: string;
|
||||
metadata?: Record<string, any>;
|
||||
};
|
||||
|
||||
export type DatasetDataIndexItemType = {
|
||||
@@ -57,8 +63,11 @@ export type DatasetDataSchemaType = {
|
||||
collectionId: string;
|
||||
datasetId: string;
|
||||
collectionId: string;
|
||||
chunkIndex: number;
|
||||
updateTime: Date;
|
||||
q: string; // large chunks or question
|
||||
a: string; // answer or custom content
|
||||
fullTextToken: string;
|
||||
indexes: DatasetDataIndexItemType[];
|
||||
};
|
||||
|
||||
@@ -77,14 +86,30 @@ export type DatasetTrainingSchemaType = {
|
||||
prompt: string;
|
||||
q: string;
|
||||
a: string;
|
||||
chunkIndex: number;
|
||||
indexes: Omit<DatasetDataIndexItemType, 'dataId'>[];
|
||||
};
|
||||
|
||||
export type CollectionWithDatasetType = Omit<DatasetCollectionSchemaType, 'datasetId'> & {
|
||||
datasetId: DatasetSchemaType;
|
||||
};
|
||||
export type DatasetDataWithCollectionType = Omit<DatasetDataSchemaType, 'collectionId'> & {
|
||||
collectionId: DatasetCollectionSchemaType;
|
||||
};
|
||||
|
||||
/* ================= dataset ===================== */
|
||||
export type DatasetListItemType = {
|
||||
_id: string;
|
||||
parentId: string;
|
||||
avatar: string;
|
||||
name: string;
|
||||
intro: string;
|
||||
type: `${DatasetTypeEnum}`;
|
||||
isOwner: boolean;
|
||||
canWrite: boolean;
|
||||
permission: `${PermissionTypeEnum}`;
|
||||
vectorModel: VectorModelItemType;
|
||||
};
|
||||
export type DatasetItemType = Omit<DatasetSchemaType, 'vectorModel' | 'agentModel'> & {
|
||||
vectorModel: VectorModelItemType;
|
||||
agentModel: LLMModelItemType;
|
||||
@@ -97,6 +122,7 @@ export type DatasetCollectionItemType = CollectionWithDatasetType & {
|
||||
canWrite: boolean;
|
||||
sourceName: string;
|
||||
sourceId?: string;
|
||||
file?: DatasetFileSchema;
|
||||
};
|
||||
|
||||
/* ================= data ===================== */
|
||||
@@ -130,6 +156,6 @@ export type DatasetFileSchema = {
|
||||
};
|
||||
|
||||
/* ============= search =============== */
|
||||
export type SearchDataResponseItemType = DatasetDataItemType & {
|
||||
export type SearchDataResponseItemType = Omit<DatasetDataItemType, 'isOwner' | 'canWrite'> & {
|
||||
score: number;
|
||||
};
|
||||
|
||||
104
packages/global/core/module/constants.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
export enum ModuleTemplateTypeEnum {
|
||||
userGuide = 'userGuide',
|
||||
systemInput = 'systemInput',
|
||||
textAnswer = 'textAnswer',
|
||||
dataset = 'dataset',
|
||||
functionCall = 'functionCall',
|
||||
externalCall = 'externalCall',
|
||||
|
||||
personalPlugin = 'personalPlugin',
|
||||
communityPlugin = 'communityPlugin',
|
||||
commercialPlugin = 'commercialPlugin',
|
||||
|
||||
other = 'other'
|
||||
}
|
||||
|
||||
export enum ModuleDataTypeEnum {
|
||||
string = 'string',
|
||||
number = 'number',
|
||||
boolean = 'boolean',
|
||||
chatHistory = 'chatHistory',
|
||||
datasetQuote = 'datasetQuote',
|
||||
any = 'any',
|
||||
|
||||
// plugin special type
|
||||
selectApp = 'selectApp',
|
||||
selectDataset = 'selectDataset'
|
||||
}
|
||||
|
||||
/* reg: modulename key */
|
||||
export enum ModuleInputKeyEnum {
|
||||
// old
|
||||
welcomeText = 'welcomeText',
|
||||
variables = 'variables',
|
||||
switch = 'switch', // a trigger switch
|
||||
history = 'history',
|
||||
userChatInput = 'userChatInput',
|
||||
questionGuide = 'questionGuide',
|
||||
tts = 'tts',
|
||||
answerText = 'text',
|
||||
agents = 'agents', // cq agent key
|
||||
|
||||
// latest
|
||||
// common
|
||||
aiModel = 'model',
|
||||
aiSystemPrompt = 'systemPrompt',
|
||||
description = 'description',
|
||||
|
||||
// history
|
||||
historyMaxAmount = 'maxContext',
|
||||
|
||||
// ai chat
|
||||
aiChatTemperature = 'temperature',
|
||||
aiChatMaxToken = 'maxToken',
|
||||
aiChatSettingModal = 'aiSettings',
|
||||
aiChatIsResponseText = 'isResponseAnswerText',
|
||||
aiChatQuoteTemplate = 'quoteTemplate',
|
||||
aiChatQuotePrompt = 'quotePrompt',
|
||||
aiChatDatasetQuote = 'quoteQA',
|
||||
|
||||
// dataset
|
||||
datasetSelectList = 'datasets',
|
||||
datasetSimilarity = 'similarity',
|
||||
datasetLimit = 'limit',
|
||||
datasetSearchMode = 'searchMode',
|
||||
datasetParamsModal = 'datasetParamsModal',
|
||||
|
||||
// context extract
|
||||
contextExtractInput = 'content',
|
||||
extractKeys = 'extractKeys',
|
||||
|
||||
// http
|
||||
httpUrl = 'url',
|
||||
|
||||
// app
|
||||
runAppSelectApp = 'app',
|
||||
|
||||
// plugin
|
||||
pluginId = 'pluginId'
|
||||
}
|
||||
|
||||
export enum ModuleOutputKeyEnum {
|
||||
// common
|
||||
userChatInput = 'userChatInput',
|
||||
finish = 'finish',
|
||||
responseData = 'responseData',
|
||||
history = 'history',
|
||||
answerText = 'answerText', // answer module text key
|
||||
success = 'success',
|
||||
failed = 'failed',
|
||||
|
||||
// dataset
|
||||
datasetIsEmpty = 'isEmpty',
|
||||
datasetUnEmpty = 'unEmpty',
|
||||
datasetQuoteQA = 'quoteQA',
|
||||
|
||||
// context extract
|
||||
contextExtractFields = 'fields'
|
||||
}
|
||||
|
||||
export enum VariableInputEnum {
|
||||
input = 'input',
|
||||
textarea = 'textarea',
|
||||
select = 'select'
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
export enum FlowNodeInputTypeEnum {
|
||||
systemInput = 'systemInput', // history, userChatInput, variableInput
|
||||
|
||||
input = 'input', // one line input
|
||||
textarea = 'textarea',
|
||||
numberInput = 'numberInput',
|
||||
@@ -8,14 +9,15 @@ export enum FlowNodeInputTypeEnum {
|
||||
custom = 'custom',
|
||||
target = 'target', // data input
|
||||
switch = 'switch',
|
||||
chatInput = 'chatInput',
|
||||
selectApp = 'selectApp',
|
||||
// chat special input
|
||||
aiSettings = 'aiSettings',
|
||||
maxToken = 'maxToken',
|
||||
// maxToken = 'maxToken',
|
||||
selectChatModel = 'selectChatModel',
|
||||
// dataset special input
|
||||
selectDataset = 'selectDataset',
|
||||
selectDatasetParamsModal = 'selectDatasetParamsModal',
|
||||
|
||||
hidden = 'hidden'
|
||||
}
|
||||
|
||||
@@ -27,7 +29,6 @@ export enum FlowNodeOutputTypeEnum {
|
||||
|
||||
export enum FlowNodeTypeEnum {
|
||||
empty = 'empty',
|
||||
variable = 'variable',
|
||||
userGuide = 'userGuide',
|
||||
questionInput = 'questionInput',
|
||||
historyNode = 'historyNode',
|
||||
@@ -40,20 +41,8 @@ export enum FlowNodeTypeEnum {
|
||||
runApp = 'app',
|
||||
pluginModule = 'pluginModule',
|
||||
pluginInput = 'pluginInput',
|
||||
pluginOutput = 'pluginOutput'
|
||||
}
|
||||
pluginOutput = 'pluginOutput',
|
||||
|
||||
export enum FlowNodeSpecialInputKeyEnum {
|
||||
'answerText' = 'text',
|
||||
'agents' = 'agents', // cq agent key
|
||||
'pluginId' = 'pluginId'
|
||||
}
|
||||
|
||||
export enum FlowNodeValTypeEnum {
|
||||
'string' = 'string',
|
||||
'number' = 'number',
|
||||
'boolean' = 'boolean',
|
||||
'chatHistory' = 'chatHistory',
|
||||
'datasetQuote' = 'datasetQuote',
|
||||
'any' = 'any'
|
||||
// abandon
|
||||
variable = 'variable'
|
||||
}
|
||||
|
||||
60
packages/global/core/module/node/type.d.ts
vendored
@@ -1,9 +1,6 @@
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeValTypeEnum,
|
||||
FlowNodeTypeEnum
|
||||
} from './constant';
|
||||
import { FlowNodeInputTypeEnum, FlowNodeOutputTypeEnum, FlowNodeTypeEnum } from './constant';
|
||||
import { ModuleDataTypeEnum, ModuleInputKeyEnum, ModuleOutputKeyEnum } from '../constants';
|
||||
import { SelectedDatasetType } from '../api';
|
||||
|
||||
export type FlowNodeChangeProps = {
|
||||
moduleId: string;
|
||||
@@ -23,23 +20,27 @@ export type FlowNodeChangeProps = {
|
||||
};
|
||||
|
||||
export type FlowNodeInputItemType = {
|
||||
key: string; // 字段名
|
||||
key: `${ModuleInputKeyEnum}` | string;
|
||||
type: `${FlowNodeInputTypeEnum}`; // Decide on a render style
|
||||
value?: any;
|
||||
valueType?: `${FlowNodeValTypeEnum}`;
|
||||
type: `${FlowNodeInputTypeEnum}`;
|
||||
valueType?: `${ModuleDataTypeEnum}`; // data type
|
||||
label: string;
|
||||
edit?: boolean;
|
||||
connected?: boolean;
|
||||
description?: string;
|
||||
placeholder?: string;
|
||||
required?: boolean;
|
||||
edit?: boolean; // Whether to allow editing
|
||||
connected?: boolean; // unConnected field will be deleted
|
||||
|
||||
showTargetInApp?: boolean;
|
||||
showTargetInPlugin?: boolean;
|
||||
|
||||
placeholder?: string; // input,textarea
|
||||
list?: { label: string; value: any }[]; // select
|
||||
step?: number; // slider max?: number;
|
||||
max?: number;
|
||||
min?: number;
|
||||
step?: number;
|
||||
required?: boolean;
|
||||
list?: { label: string; value: any }[];
|
||||
markList?: { label: string; value: any }[];
|
||||
customData?: () => any;
|
||||
valueCheck?: (value: any) => boolean;
|
||||
markList?: { label: string; value: any }[]; // slider
|
||||
|
||||
plusField?: boolean; // plus system will show
|
||||
};
|
||||
|
||||
export type FlowNodeOutputTargetItemType = {
|
||||
@@ -47,11 +48,30 @@ export type FlowNodeOutputTargetItemType = {
|
||||
key: string;
|
||||
};
|
||||
export type FlowNodeOutputItemType = {
|
||||
key: string; // 字段名
|
||||
key: `${ModuleOutputKeyEnum}` | string;
|
||||
label?: string;
|
||||
edit?: boolean;
|
||||
description?: string;
|
||||
valueType?: `${FlowNodeValTypeEnum}`;
|
||||
valueType?: `${ModuleDataTypeEnum}`;
|
||||
type?: `${FlowNodeOutputTypeEnum}`;
|
||||
targets: FlowNodeOutputTargetItemType[];
|
||||
};
|
||||
|
||||
/* ------------- item type --------------- */
|
||||
/* ai chat modules props */
|
||||
export type AIChatModuleProps = {
|
||||
[ModuleInputKeyEnum.aiModel]: string;
|
||||
[ModuleInputKeyEnum.aiSystemPrompt]?: string;
|
||||
[ModuleInputKeyEnum.aiChatTemperature]: number;
|
||||
[ModuleInputKeyEnum.aiChatMaxToken]: number;
|
||||
[ModuleInputKeyEnum.aiChatIsResponseText]: boolean;
|
||||
[ModuleInputKeyEnum.aiChatQuoteTemplate]?: string;
|
||||
[ModuleInputKeyEnum.aiChatQuotePrompt]?: string;
|
||||
};
|
||||
|
||||
export type DatasetModuleProps = {
|
||||
[ModuleInputKeyEnum.datasetSelectList]: SelectedDatasetType;
|
||||
[ModuleInputKeyEnum.datasetSimilarity]: number;
|
||||
[ModuleInputKeyEnum.datasetLimit]: number;
|
||||
[ModuleInputKeyEnum.datasetStartReRank]: boolean;
|
||||
};
|
||||
|
||||
32
packages/global/core/module/template/input.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { FlowNodeInputItemType } from '../node/type.d';
|
||||
import { ModuleInputKeyEnum } from '../constants';
|
||||
import { FlowNodeInputTypeEnum } from '../node/constant';
|
||||
import { ModuleDataTypeEnum } from '../constants';
|
||||
|
||||
export const Input_Template_TFSwitch: FlowNodeInputItemType = {
|
||||
key: ModuleInputKeyEnum.switch,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: 'core.module.input.label.switch',
|
||||
valueType: ModuleDataTypeEnum.any,
|
||||
showTargetInApp: true,
|
||||
showTargetInPlugin: true
|
||||
};
|
||||
|
||||
export const Input_Template_History: FlowNodeInputItemType = {
|
||||
key: ModuleInputKeyEnum.history,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: 'core.module.input.label.chat history',
|
||||
valueType: ModuleDataTypeEnum.chatHistory,
|
||||
showTargetInApp: true,
|
||||
showTargetInPlugin: true
|
||||
};
|
||||
|
||||
export const Input_Template_UserChatInput: FlowNodeInputItemType = {
|
||||
key: ModuleInputKeyEnum.userChatInput,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: 'core.module.input.label.user question',
|
||||
required: true,
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
showTargetInApp: true,
|
||||
showTargetInPlugin: true
|
||||
};
|
||||
13
packages/global/core/module/template/output.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { FlowNodeOutputItemType } from '../node/type';
|
||||
import { ModuleOutputKeyEnum } from '../constants';
|
||||
import { FlowNodeOutputTypeEnum } from '../node/constant';
|
||||
import { ModuleDataTypeEnum } from '../constants';
|
||||
|
||||
export const Output_Template_Finish: FlowNodeOutputItemType = {
|
||||
key: ModuleOutputKeyEnum.finish,
|
||||
label: 'core.module.output.label.running done',
|
||||
description: 'core.module.output.description.running done',
|
||||
valueType: ModuleDataTypeEnum.boolean,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
};
|
||||
157
packages/global/core/module/template/system/aiChat.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeTypeEnum
|
||||
} from '../../node/constant';
|
||||
import { FlowModuleTemplateType } from '../../type.d';
|
||||
import {
|
||||
ModuleDataTypeEnum,
|
||||
ModuleInputKeyEnum,
|
||||
ModuleOutputKeyEnum,
|
||||
ModuleTemplateTypeEnum
|
||||
} from '../../constants';
|
||||
import {
|
||||
Input_Template_History,
|
||||
Input_Template_TFSwitch,
|
||||
Input_Template_UserChatInput
|
||||
} from '../input';
|
||||
import { chatNodeSystemPromptTip } from '../tip';
|
||||
import { Output_Template_Finish } from '../output';
|
||||
|
||||
export const AiChatModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.chatNode,
|
||||
templateType: ModuleTemplateTypeEnum.textAnswer,
|
||||
flowType: FlowNodeTypeEnum.chatNode,
|
||||
avatar: '/imgs/module/AI.png',
|
||||
name: 'AI 对话',
|
||||
intro: 'AI 大模型对话',
|
||||
showStatus: true,
|
||||
inputs: [
|
||||
Input_Template_TFSwitch,
|
||||
{
|
||||
key: ModuleInputKeyEnum.aiModel,
|
||||
type: FlowNodeInputTypeEnum.selectChatModel,
|
||||
label: '对话模型',
|
||||
required: true,
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
},
|
||||
// --- settings modal
|
||||
{
|
||||
key: ModuleInputKeyEnum.aiChatTemperature,
|
||||
type: FlowNodeInputTypeEnum.hidden, // Set in the pop-up window
|
||||
label: '温度',
|
||||
value: 0,
|
||||
valueType: ModuleDataTypeEnum.number,
|
||||
min: 0,
|
||||
max: 10,
|
||||
step: 1,
|
||||
markList: [
|
||||
{ label: '严谨', value: 0 },
|
||||
{ label: '发散', value: 10 }
|
||||
],
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.aiChatMaxToken,
|
||||
type: FlowNodeInputTypeEnum.hidden, // Set in the pop-up window
|
||||
label: '回复上限',
|
||||
value: 2000,
|
||||
valueType: ModuleDataTypeEnum.number,
|
||||
min: 100,
|
||||
max: 4000,
|
||||
step: 50,
|
||||
markList: [
|
||||
{ label: '100', value: 100 },
|
||||
{
|
||||
label: `${4000}`,
|
||||
value: 4000
|
||||
}
|
||||
],
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.aiChatIsResponseText,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: '返回AI内容',
|
||||
value: true,
|
||||
valueType: ModuleDataTypeEnum.boolean,
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.aiChatQuoteTemplate,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: '引用内容模板',
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
value: '',
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.aiChatQuotePrompt,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: '引用内容提示词',
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
value: '',
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.aiChatSettingModal,
|
||||
type: FlowNodeInputTypeEnum.aiSettings,
|
||||
label: '',
|
||||
connected: false,
|
||||
valueType: ModuleDataTypeEnum.any,
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
},
|
||||
// settings modal ---
|
||||
{
|
||||
key: ModuleInputKeyEnum.aiSystemPrompt,
|
||||
type: FlowNodeInputTypeEnum.textarea,
|
||||
label: '系统提示词',
|
||||
max: 300,
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
description: chatNodeSystemPromptTip,
|
||||
placeholder: chatNodeSystemPromptTip,
|
||||
value: '',
|
||||
showTargetInApp: true,
|
||||
showTargetInPlugin: true
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.aiChatDatasetQuote,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: '引用内容',
|
||||
description: "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
|
||||
valueType: ModuleDataTypeEnum.datasetQuote,
|
||||
connected: false,
|
||||
showTargetInApp: true,
|
||||
showTargetInPlugin: true
|
||||
},
|
||||
Input_Template_History,
|
||||
Input_Template_UserChatInput
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: ModuleOutputKeyEnum.history,
|
||||
label: '新的上下文',
|
||||
description: '将本次回复内容拼接上历史记录,作为新的上下文返回',
|
||||
valueType: ModuleDataTypeEnum.chatHistory,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: ModuleOutputKeyEnum.answerText,
|
||||
label: 'AI回复',
|
||||
description: '将在 stream 回复完毕后触发',
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
},
|
||||
Output_Template_Finish
|
||||
]
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '../../node/constant';
|
||||
import { FlowModuleTemplateType } from '../../type.d';
|
||||
import { ModuleDataTypeEnum, ModuleInputKeyEnum, ModuleTemplateTypeEnum } from '../../constants';
|
||||
import { Input_Template_TFSwitch } from '../input';
|
||||
import { Output_Template_Finish } from '../output';
|
||||
|
||||
export const AssignedAnswerModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.answerNode,
|
||||
templateType: ModuleTemplateTypeEnum.textAnswer,
|
||||
flowType: FlowNodeTypeEnum.answerNode,
|
||||
avatar: '/imgs/module/reply.png',
|
||||
name: '指定回复',
|
||||
intro: '该模块可以直接回复一段指定的内容。常用于引导、提示',
|
||||
inputs: [
|
||||
Input_Template_TFSwitch,
|
||||
{
|
||||
key: ModuleInputKeyEnum.answerText,
|
||||
type: FlowNodeInputTypeEnum.textarea,
|
||||
valueType: ModuleDataTypeEnum.any,
|
||||
value: '',
|
||||
label: '回复的内容',
|
||||
description:
|
||||
'可以使用 \\n 来实现连续换行。\n\n可以通过外部模块输入实现回复,外部模块输入时会覆盖当前填写的内容。\n\n如传入非字符串类型数据将会自动转成字符串',
|
||||
showTargetInApp: true,
|
||||
showTargetInPlugin: true
|
||||
}
|
||||
],
|
||||
outputs: [Output_Template_Finish]
|
||||
};
|
||||
@@ -0,0 +1,96 @@
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeTypeEnum
|
||||
} from '../../node/constant';
|
||||
import { FlowModuleTemplateType } from '../../type.d';
|
||||
import { ModuleDataTypeEnum, ModuleInputKeyEnum, ModuleTemplateTypeEnum } from '../../constants';
|
||||
import {
|
||||
Input_Template_History,
|
||||
Input_Template_TFSwitch,
|
||||
Input_Template_UserChatInput
|
||||
} from '../input';
|
||||
|
||||
export const ClassifyQuestionModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.classifyQuestion,
|
||||
templateType: ModuleTemplateTypeEnum.functionCall,
|
||||
flowType: FlowNodeTypeEnum.classifyQuestion,
|
||||
avatar: '/imgs/module/cq.png',
|
||||
name: '问题分类',
|
||||
intro: `根据用户的历史记录和当前问题判断该次提问的类型。可以添加多组问题类型,下面是一个模板例子:
|
||||
类型1: 打招呼
|
||||
类型2: 关于商品“使用”问题
|
||||
类型3: 关于商品“购买”问题
|
||||
类型4: 其他问题`,
|
||||
showStatus: true,
|
||||
inputs: [
|
||||
Input_Template_TFSwitch,
|
||||
{
|
||||
key: ModuleInputKeyEnum.aiModel,
|
||||
type: FlowNodeInputTypeEnum.selectChatModel,
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
label: '分类模型',
|
||||
required: true,
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.aiSystemPrompt,
|
||||
type: FlowNodeInputTypeEnum.textarea,
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
value: '',
|
||||
label: '背景知识',
|
||||
description:
|
||||
'你可以添加一些特定内容的介绍,从而更好的识别用户的问题类型。这个内容通常是给模型介绍一个它不知道的内容。',
|
||||
placeholder:
|
||||
'例如: \n1. AIGC(人工智能生成内容)是指使用人工智能技术自动或半自动地生成数字内容,如文本、图像、音乐、视频等。\n2. AIGC技术包括但不限于自然语言处理、计算机视觉、机器学习和深度学习。这些技术可以创建新内容或修改现有内容,以满足特定的创意、教育、娱乐或信息需求。',
|
||||
showTargetInApp: true,
|
||||
showTargetInPlugin: true
|
||||
},
|
||||
Input_Template_History,
|
||||
Input_Template_UserChatInput,
|
||||
{
|
||||
key: ModuleInputKeyEnum.agents,
|
||||
type: FlowNodeInputTypeEnum.custom,
|
||||
valueType: ModuleDataTypeEnum.any,
|
||||
label: '',
|
||||
value: [
|
||||
{
|
||||
value: '打招呼',
|
||||
key: 'fasw'
|
||||
},
|
||||
{
|
||||
value: '关于 xxx 的问题',
|
||||
key: 'fqsw'
|
||||
},
|
||||
{
|
||||
value: '其他问题',
|
||||
key: 'fesw'
|
||||
}
|
||||
],
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
// custom output
|
||||
{
|
||||
key: 'fasw',
|
||||
label: '',
|
||||
type: FlowNodeOutputTypeEnum.hidden,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: 'fqsw',
|
||||
label: '',
|
||||
type: FlowNodeOutputTypeEnum.hidden,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: 'fesw',
|
||||
label: '',
|
||||
type: FlowNodeOutputTypeEnum.hidden,
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeTypeEnum
|
||||
} from '../../node/constant';
|
||||
import { FlowModuleTemplateType } from '../../type.d';
|
||||
import {
|
||||
ModuleDataTypeEnum,
|
||||
ModuleInputKeyEnum,
|
||||
ModuleOutputKeyEnum,
|
||||
ModuleTemplateTypeEnum
|
||||
} from '../../constants';
|
||||
import { Input_Template_History, Input_Template_TFSwitch } from '../input';
|
||||
|
||||
export const ContextExtractModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.contentExtract,
|
||||
templateType: ModuleTemplateTypeEnum.functionCall,
|
||||
flowType: FlowNodeTypeEnum.contentExtract,
|
||||
avatar: '/imgs/module/extract.png',
|
||||
name: '文本内容提取',
|
||||
intro: '可从文本中提取指定的数据,例如:sql语句、搜索关键词、代码等',
|
||||
showStatus: true,
|
||||
inputs: [
|
||||
Input_Template_TFSwitch,
|
||||
{
|
||||
key: ModuleInputKeyEnum.description,
|
||||
type: FlowNodeInputTypeEnum.textarea,
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
value: '',
|
||||
label: '提取要求描述',
|
||||
description: '写一段提取要求,告诉 AI 需要提取哪些内容',
|
||||
required: true,
|
||||
placeholder:
|
||||
'例如: \n1. 你是一个实验室预约助手。根据用户问题,提取出姓名、实验室号和预约时间',
|
||||
showTargetInApp: true,
|
||||
showTargetInPlugin: true
|
||||
},
|
||||
Input_Template_History,
|
||||
{
|
||||
key: ModuleInputKeyEnum.contextExtractInput,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: '需要提取的文本',
|
||||
required: true,
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
showTargetInApp: true,
|
||||
showTargetInPlugin: true
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.extractKeys,
|
||||
type: FlowNodeInputTypeEnum.custom,
|
||||
label: '目标字段',
|
||||
valueType: ModuleDataTypeEnum.any,
|
||||
description: "由 '描述' 和 'key' 组成一个目标字段,可提取多个目标字段",
|
||||
value: [], // {desc: string; key: string; required: boolean;}[]
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: ModuleOutputKeyEnum.success,
|
||||
label: '字段完全提取',
|
||||
valueType: ModuleDataTypeEnum.boolean,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: ModuleOutputKeyEnum.failed,
|
||||
label: '提取字段缺失',
|
||||
valueType: ModuleDataTypeEnum.boolean,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: ModuleOutputKeyEnum.contextExtractFields,
|
||||
label: '完整提取结果',
|
||||
description: '一个 JSON 字符串,例如:{"name:":"YY","Time":"2023/7/2 18:00"}',
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
};
|
||||
117
packages/global/core/module/template/system/datasetSearch.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeTypeEnum
|
||||
} from '../../node/constant';
|
||||
import { FlowModuleTemplateType } from '../../type.d';
|
||||
import {
|
||||
ModuleDataTypeEnum,
|
||||
ModuleInputKeyEnum,
|
||||
ModuleOutputKeyEnum,
|
||||
ModuleTemplateTypeEnum
|
||||
} from '../../constants';
|
||||
import { Input_Template_TFSwitch, Input_Template_UserChatInput } from '../input';
|
||||
import { Output_Template_Finish } from '../output';
|
||||
import { DatasetSearchModeEnum } from '../../../dataset/constant';
|
||||
|
||||
export const DatasetSearchModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.datasetSearchNode,
|
||||
templateType: ModuleTemplateTypeEnum.dataset,
|
||||
flowType: FlowNodeTypeEnum.datasetSearchNode,
|
||||
avatar: '/imgs/module/db.png',
|
||||
name: '知识库搜索',
|
||||
intro: '去知识库中搜索对应的答案。可作为 AI 对话引用参考。',
|
||||
showStatus: true,
|
||||
inputs: [
|
||||
Input_Template_TFSwitch,
|
||||
{
|
||||
key: ModuleInputKeyEnum.datasetSelectList,
|
||||
type: FlowNodeInputTypeEnum.selectDataset,
|
||||
label: '关联的知识库',
|
||||
value: [],
|
||||
valueType: ModuleDataTypeEnum.selectDataset,
|
||||
list: [],
|
||||
required: true,
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: true
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.datasetSimilarity,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: '最低相关性',
|
||||
value: 0.4,
|
||||
valueType: ModuleDataTypeEnum.number,
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
markList: [
|
||||
{ label: '0', value: 0 },
|
||||
{ label: '1', value: 1 }
|
||||
],
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.datasetLimit,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: '单次搜索上限',
|
||||
description: '最多取 n 条记录作为本次问题引用',
|
||||
value: 5,
|
||||
valueType: ModuleDataTypeEnum.number,
|
||||
min: 1,
|
||||
max: 20,
|
||||
step: 1,
|
||||
markList: [
|
||||
{ label: '1', value: 1 },
|
||||
{ label: '20', value: 20 }
|
||||
],
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.datasetSearchMode,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: 'core.dataset.search.Mode',
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false,
|
||||
value: DatasetSearchModeEnum.embedding
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.datasetParamsModal,
|
||||
type: FlowNodeInputTypeEnum.selectDatasetParamsModal,
|
||||
label: '',
|
||||
connected: false,
|
||||
valueType: ModuleDataTypeEnum.any,
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
},
|
||||
Input_Template_UserChatInput
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: ModuleOutputKeyEnum.datasetIsEmpty,
|
||||
label: '搜索结果为空',
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
valueType: ModuleDataTypeEnum.boolean,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: ModuleOutputKeyEnum.datasetUnEmpty,
|
||||
label: '搜索结果不为空',
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
valueType: ModuleDataTypeEnum.boolean,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: ModuleOutputKeyEnum.datasetQuoteQA,
|
||||
label: '引用内容',
|
||||
description:
|
||||
'始终返回数组,如果希望搜索结果为空时执行额外操作,需要用到上面的两个输入以及目标模块的触发器',
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
valueType: ModuleDataTypeEnum.datasetQuote,
|
||||
targets: []
|
||||
},
|
||||
Output_Template_Finish
|
||||
]
|
||||
};
|
||||
14
packages/global/core/module/template/system/empty.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { ModuleTemplateTypeEnum } from '../../constants';
|
||||
import { FlowNodeTypeEnum } from '../../node/constant';
|
||||
import { FlowModuleTemplateType } from '../../type.d';
|
||||
|
||||
export const EmptyModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.empty,
|
||||
templateType: ModuleTemplateTypeEnum.other,
|
||||
flowType: FlowNodeTypeEnum.empty,
|
||||
avatar: '/imgs/module/cq.png',
|
||||
name: '该模块已被移除',
|
||||
intro: '',
|
||||
inputs: [],
|
||||
outputs: []
|
||||
};
|
||||
48
packages/global/core/module/template/system/history.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeTypeEnum
|
||||
} from '../../node/constant';
|
||||
import { FlowModuleTemplateType } from '../../type.d';
|
||||
import { ModuleDataTypeEnum, ModuleInputKeyEnum, ModuleTemplateTypeEnum } from '../../constants';
|
||||
|
||||
export const HistoryModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.historyNode,
|
||||
templateType: ModuleTemplateTypeEnum.systemInput,
|
||||
flowType: FlowNodeTypeEnum.historyNode,
|
||||
avatar: '/imgs/module/history.png',
|
||||
name: '聊天记录',
|
||||
intro: '用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。',
|
||||
inputs: [
|
||||
{
|
||||
key: ModuleInputKeyEnum.historyMaxAmount,
|
||||
type: FlowNodeInputTypeEnum.numberInput,
|
||||
label: '最长记录数',
|
||||
description:
|
||||
'该记录数不代表模型可接收这么多的历史记录,具体可接收多少历史记录,取决于模型的能力,通常建议不要超过20条。',
|
||||
value: 6,
|
||||
valueType: ModuleDataTypeEnum.number,
|
||||
min: 0,
|
||||
max: 100,
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.history,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
valueType: ModuleDataTypeEnum.chatHistory,
|
||||
label: '聊天记录',
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: ModuleInputKeyEnum.history,
|
||||
label: '聊天记录',
|
||||
valueType: ModuleDataTypeEnum.chatHistory,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
};
|
||||
31
packages/global/core/module/template/system/http.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '../../node/constant';
|
||||
import { FlowModuleTemplateType } from '../../type.d';
|
||||
import { ModuleDataTypeEnum, ModuleInputKeyEnum, ModuleTemplateTypeEnum } from '../../constants';
|
||||
import { Input_Template_TFSwitch } from '../input';
|
||||
import { Output_Template_Finish } from '../output';
|
||||
|
||||
export const HttpModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.httpRequest,
|
||||
templateType: ModuleTemplateTypeEnum.externalCall,
|
||||
flowType: FlowNodeTypeEnum.httpRequest,
|
||||
avatar: '/imgs/module/http.png',
|
||||
name: 'HTTP模块',
|
||||
intro: '可以发出一个 HTTP POST 请求,实现更为复杂的操作(联网搜索、数据库查询等)',
|
||||
showStatus: true,
|
||||
inputs: [
|
||||
Input_Template_TFSwitch,
|
||||
{
|
||||
key: ModuleInputKeyEnum.httpUrl,
|
||||
value: '',
|
||||
type: FlowNodeInputTypeEnum.input,
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
label: '请求地址',
|
||||
description: '请求目标地址',
|
||||
placeholder: 'https://api.fastgpt.run/getInventory',
|
||||
required: true,
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
}
|
||||
],
|
||||
outputs: [Output_Template_Finish]
|
||||
};
|
||||
15
packages/global/core/module/template/system/pluginInput.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ModuleTemplateTypeEnum } from '../../constants';
|
||||
import { FlowNodeTypeEnum } from '../../node/constant';
|
||||
import { FlowModuleTemplateType } from '../../type.d';
|
||||
|
||||
export const PluginInputModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.pluginInput,
|
||||
templateType: ModuleTemplateTypeEnum.systemInput,
|
||||
flowType: FlowNodeTypeEnum.pluginInput,
|
||||
avatar: '/imgs/module/input.png',
|
||||
name: '定义插件输入',
|
||||
intro: '自定义配置外部输入,使用插件时,仅暴露自定义配置的输入',
|
||||
showStatus: false,
|
||||
inputs: [],
|
||||
outputs: []
|
||||
};
|
||||
15
packages/global/core/module/template/system/pluginOutput.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ModuleTemplateTypeEnum } from '../../constants';
|
||||
import { FlowNodeTypeEnum } from '../../node/constant';
|
||||
import { FlowModuleTemplateType } from '../../type.d';
|
||||
|
||||
export const PluginOutputModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.pluginOutput,
|
||||
templateType: ModuleTemplateTypeEnum.systemInput,
|
||||
flowType: FlowNodeTypeEnum.pluginOutput,
|
||||
avatar: '/imgs/module/output.png',
|
||||
name: '定义插件输出',
|
||||
intro: '自定义配置外部输出,使用插件时,仅暴露自定义配置的输出',
|
||||
showStatus: false,
|
||||
inputs: [],
|
||||
outputs: []
|
||||
};
|
||||
62
packages/global/core/module/template/system/runApp.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeTypeEnum
|
||||
} from '../../node/constant';
|
||||
import { FlowModuleTemplateType } from '../../type.d';
|
||||
import {
|
||||
ModuleDataTypeEnum,
|
||||
ModuleInputKeyEnum,
|
||||
ModuleOutputKeyEnum,
|
||||
ModuleTemplateTypeEnum
|
||||
} from '../../constants';
|
||||
import {
|
||||
Input_Template_History,
|
||||
Input_Template_TFSwitch,
|
||||
Input_Template_UserChatInput
|
||||
} from '../input';
|
||||
import { Output_Template_Finish } from '../output';
|
||||
|
||||
export const RunAppModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.runApp,
|
||||
templateType: ModuleTemplateTypeEnum.externalCall,
|
||||
flowType: FlowNodeTypeEnum.runApp,
|
||||
avatar: '/imgs/module/app.png',
|
||||
name: '应用调用',
|
||||
intro: '可以选择一个其他应用进行调用',
|
||||
showStatus: true,
|
||||
inputs: [
|
||||
Input_Template_TFSwitch,
|
||||
{
|
||||
key: ModuleInputKeyEnum.runAppSelectApp,
|
||||
type: FlowNodeInputTypeEnum.selectApp,
|
||||
valueType: ModuleDataTypeEnum.selectApp,
|
||||
label: '选择一个应用',
|
||||
description: '选择一个其他应用进行调用',
|
||||
required: true,
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
},
|
||||
Input_Template_History,
|
||||
Input_Template_UserChatInput
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: ModuleOutputKeyEnum.history,
|
||||
label: '新的上下文',
|
||||
description: '将该应用回复内容拼接到历史记录中,作为新的上下文返回',
|
||||
valueType: ModuleDataTypeEnum.chatHistory,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: ModuleOutputKeyEnum.answerText,
|
||||
label: 'AI回复',
|
||||
description: '将在应用完全结束后触发',
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
},
|
||||
Output_Template_Finish
|
||||
]
|
||||
};
|
||||
15
packages/global/core/module/template/system/runPlugin.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ModuleTemplateTypeEnum } from '../../constants';
|
||||
import { FlowNodeTypeEnum } from '../../node/constant';
|
||||
import { FlowModuleTemplateType } from '../../type.d';
|
||||
|
||||
export const RunPluginModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.pluginModule,
|
||||
templateType: ModuleTemplateTypeEnum.externalCall,
|
||||
flowType: FlowNodeTypeEnum.pluginModule,
|
||||
avatar: '/imgs/module/custom.png',
|
||||
intro: '',
|
||||
name: '自定义模块',
|
||||
showStatus: false,
|
||||
inputs: [], // [{key:'pluginId'},...]
|
||||
outputs: []
|
||||
};
|
||||
49
packages/global/core/module/template/system/userGuide.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '../../node/constant';
|
||||
import { FlowModuleTemplateType } from '../../type.d';
|
||||
import { userGuideTip } from '../tip';
|
||||
import { ModuleDataTypeEnum, ModuleInputKeyEnum, ModuleTemplateTypeEnum } from '../../constants';
|
||||
|
||||
export const UserGuideModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.userGuide,
|
||||
templateType: ModuleTemplateTypeEnum.userGuide,
|
||||
flowType: FlowNodeTypeEnum.userGuide,
|
||||
avatar: '/imgs/module/userGuide.png',
|
||||
name: '用户引导',
|
||||
intro: userGuideTip,
|
||||
inputs: [
|
||||
{
|
||||
key: ModuleInputKeyEnum.welcomeText,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
label: '开场白',
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.variables,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
valueType: ModuleDataTypeEnum.any,
|
||||
label: '对话框变量',
|
||||
value: [],
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.questionGuide,
|
||||
valueType: ModuleDataTypeEnum.boolean,
|
||||
type: FlowNodeInputTypeEnum.switch,
|
||||
label: '问题引导',
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.tts,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
valueType: ModuleDataTypeEnum.any,
|
||||
label: '语音播报',
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
}
|
||||
],
|
||||
outputs: []
|
||||
};
|
||||
40
packages/global/core/module/template/system/userInput.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeTypeEnum
|
||||
} from '../../node/constant';
|
||||
import { FlowModuleTemplateType } from '../../type.d';
|
||||
import {
|
||||
ModuleDataTypeEnum,
|
||||
ModuleInputKeyEnum,
|
||||
ModuleOutputKeyEnum,
|
||||
ModuleTemplateTypeEnum
|
||||
} from '../../constants';
|
||||
|
||||
export const UserInputModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.questionInput,
|
||||
templateType: ModuleTemplateTypeEnum.systemInput,
|
||||
flowType: FlowNodeTypeEnum.questionInput,
|
||||
avatar: '/imgs/module/userChatInput.png',
|
||||
name: '用户问题(入口)',
|
||||
intro: '用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。',
|
||||
inputs: [
|
||||
{
|
||||
key: ModuleInputKeyEnum.userChatInput,
|
||||
type: FlowNodeInputTypeEnum.systemInput,
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
label: '用户问题',
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: ModuleOutputKeyEnum.userChatInput,
|
||||
label: '用户问题',
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
};
|
||||
7
packages/global/core/module/template/tip.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const chatNodeSystemPromptTip =
|
||||
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}';
|
||||
export const userGuideTip = '可以在对话前设置引导语,设置全局变量,设置下一步指引';
|
||||
export const welcomeTextTip =
|
||||
'每次对话开始前,发送一个初始内容。支持标准 Markdown 语法,可使用的额外标记:\n[快捷按键]: 用户点击后可以直接发送该问题';
|
||||
export const variableTip =
|
||||
'可以在对话开始前,要求用户填写一些内容作为本轮对话的特定变量。该模块位于开场引导之后。\n变量可以通过 {{变量key}} 的形式注入到其他模块 string 类型的输入中,例如:提示词、限定词等';
|
||||
34
packages/global/core/module/type.d.ts
vendored
@@ -1,13 +1,14 @@
|
||||
import { FlowNodeTypeEnum, FlowNodeValTypeEnum } from './node/constant';
|
||||
import { FlowNodeTypeEnum } from './node/constant';
|
||||
import { ModuleDataTypeEnum, ModuleTemplateTypeEnum, VariableInputEnum } from './constants';
|
||||
import { FlowNodeInputItemType, FlowNodeOutputItemType } from './node/type';
|
||||
|
||||
export type FlowModuleTemplateType = {
|
||||
id: string;
|
||||
templateType: `${ModuleTemplateTypeEnum}`;
|
||||
flowType: `${FlowNodeTypeEnum}`; // unique
|
||||
logo?: string;
|
||||
avatar?: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
intro?: string;
|
||||
intro: string; // template list intro
|
||||
showStatus?: boolean; // chatting response step status
|
||||
inputs: FlowNodeInputItemType[];
|
||||
outputs: FlowNodeOutputItemType[];
|
||||
@@ -15,16 +16,17 @@ export type FlowModuleTemplateType = {
|
||||
export type FlowModuleItemType = FlowModuleTemplateType & {
|
||||
moduleId: string;
|
||||
};
|
||||
export type SystemModuleTemplateType = {
|
||||
export type moduleTemplateListType = {
|
||||
type: `${ModuleTemplateTypeEnum}`;
|
||||
label: string;
|
||||
list: FlowModuleTemplateType[];
|
||||
}[];
|
||||
|
||||
// store module type
|
||||
export type ModuleItemType = {
|
||||
name: string;
|
||||
logo?: string;
|
||||
avatar?: string;
|
||||
intro?: string;
|
||||
description?: string;
|
||||
moduleId: string;
|
||||
position?: {
|
||||
x: number;
|
||||
@@ -37,6 +39,24 @@ export type ModuleItemType = {
|
||||
};
|
||||
|
||||
/* function type */
|
||||
// variable
|
||||
export type VariableItemType = {
|
||||
id: string;
|
||||
key: string;
|
||||
label: string;
|
||||
type: `${VariableInputEnum}`;
|
||||
required: boolean;
|
||||
maxLen: number;
|
||||
enums: { value: string }[];
|
||||
};
|
||||
// tts
|
||||
export type AppTTSConfigType = {
|
||||
type: 'none' | 'web' | 'model';
|
||||
model?: string;
|
||||
voice?: string;
|
||||
speed?: number;
|
||||
};
|
||||
|
||||
export type SelectAppItemType = {
|
||||
id: string;
|
||||
name: string;
|
||||
|
||||
@@ -1,28 +1,54 @@
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeSpecialInputKeyEnum,
|
||||
FlowNodeTypeEnum
|
||||
} from './node/constant';
|
||||
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from './node/constant';
|
||||
import { ModuleDataTypeEnum, ModuleInputKeyEnum } from './constants';
|
||||
import { FlowNodeInputItemType, FlowNodeOutputItemType } from './node/type';
|
||||
import { ModuleItemType } from './type';
|
||||
import { AppTTSConfigType, ModuleItemType, VariableItemType } from './type';
|
||||
|
||||
export const getGuideModule = (modules: ModuleItemType[]) =>
|
||||
modules.find((item) => item.flowType === FlowNodeTypeEnum.userGuide);
|
||||
|
||||
export const splitGuideModule = (guideModules?: ModuleItemType) => {
|
||||
const welcomeText: string =
|
||||
guideModules?.inputs?.find((item) => item.key === ModuleInputKeyEnum.welcomeText)?.value || '';
|
||||
|
||||
const variableModules: VariableItemType[] =
|
||||
guideModules?.inputs.find((item) => item.key === ModuleInputKeyEnum.variables)?.value || [];
|
||||
|
||||
const questionGuide: boolean =
|
||||
!!guideModules?.inputs?.find((item) => item.key === ModuleInputKeyEnum.questionGuide)?.value ||
|
||||
false;
|
||||
|
||||
const ttsConfig: AppTTSConfigType = guideModules?.inputs?.find(
|
||||
(item) => item.key === ModuleInputKeyEnum.tts
|
||||
)?.value || { type: 'web' };
|
||||
|
||||
export function getPluginTemplatePluginIdInput(pluginId: string) {
|
||||
return {
|
||||
key: FlowNodeSpecialInputKeyEnum.pluginId,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: 'pluginId',
|
||||
value: pluginId,
|
||||
connected: true
|
||||
welcomeText,
|
||||
variableModules,
|
||||
questionGuide,
|
||||
ttsConfig
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export function formatPluginIOModules(
|
||||
export function formatPluginToPreviewModule(
|
||||
pluginId: string,
|
||||
modules: ModuleItemType[]
|
||||
): {
|
||||
inputs: FlowNodeInputItemType[];
|
||||
outputs: FlowNodeOutputItemType[];
|
||||
} {
|
||||
function getPluginTemplatePluginIdInput(pluginId: string): FlowNodeInputItemType {
|
||||
return {
|
||||
key: ModuleInputKeyEnum.pluginId,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: 'pluginId',
|
||||
value: pluginId,
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
connected: true,
|
||||
showTargetInApp: false,
|
||||
showTargetInPlugin: false
|
||||
};
|
||||
}
|
||||
|
||||
const pluginInput = modules.find((module) => module.flowType === FlowNodeTypeEnum.pluginInput);
|
||||
const customOutput = modules.find((module) => module.flowType === FlowNodeTypeEnum.pluginOutput);
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { ModuleTemplateTypeEnum } from '../module/constants';
|
||||
import { ModuleItemType } from '../module/type';
|
||||
|
||||
export const defaultModules: ModuleItemType[] = [
|
||||
{
|
||||
moduleId: 'fph4s3',
|
||||
moduleId: 'custom-output',
|
||||
name: '自定义输出',
|
||||
flowType: 'pluginOutput',
|
||||
showStatus: false,
|
||||
@@ -14,7 +15,7 @@ export const defaultModules: ModuleItemType[] = [
|
||||
outputs: []
|
||||
},
|
||||
{
|
||||
moduleId: 'w09v30',
|
||||
moduleId: 'custom-input',
|
||||
name: '自定义输入',
|
||||
flowType: 'pluginInput',
|
||||
showStatus: false,
|
||||
@@ -26,3 +27,14 @@ export const defaultModules: ModuleItemType[] = [
|
||||
outputs: []
|
||||
}
|
||||
];
|
||||
|
||||
export enum PluginTypeEnum {
|
||||
personal = 'personal',
|
||||
community = 'community',
|
||||
commercial = 'commercial'
|
||||
}
|
||||
export const PluginType2TemplateTypeMap = {
|
||||
[PluginTypeEnum.personal]: ModuleTemplateTypeEnum.personalPlugin,
|
||||
[PluginTypeEnum.community]: ModuleTemplateTypeEnum.communityPlugin,
|
||||
[PluginTypeEnum.commercial]: ModuleTemplateTypeEnum.commercialPlugin
|
||||
};
|
||||
|
||||
13
packages/global/core/plugin/type.d.ts
vendored
@@ -1,4 +1,6 @@
|
||||
import { ModuleTemplateTypeEnum } from 'core/module/constants';
|
||||
import type { ModuleItemType } from '../module/type.d';
|
||||
import { PluginTypeEnum } from './constants';
|
||||
|
||||
export type PluginItemSchema = {
|
||||
_id: string;
|
||||
@@ -11,3 +13,14 @@ export type PluginItemSchema = {
|
||||
updateTime: Date;
|
||||
modules: ModuleItemType[];
|
||||
};
|
||||
|
||||
/* plugin template */
|
||||
export type PluginTemplateType = {
|
||||
id: string;
|
||||
type: `${PluginTypeEnum}`;
|
||||
name: string;
|
||||
avatar: string;
|
||||
intro: string;
|
||||
modules: ModuleItemType[];
|
||||
templateType?: `${ModuleTemplateTypeEnum}`;
|
||||
};
|
||||
|
||||
@@ -3,13 +3,16 @@
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"axios": "^1.5.1",
|
||||
"timezones-list": "^3.0.2",
|
||||
"cheerio": "1.0.0-rc.12",
|
||||
"dayjs": "^1.11.7",
|
||||
"encoding": "^0.1.13",
|
||||
"js-tiktoken": "^1.0.7",
|
||||
"node-html-markdown": "^1.3.0",
|
||||
"openai": "^4.16.1",
|
||||
"js-tiktoken": "^1.0.7"
|
||||
"timezones-list": "^3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.8.5"
|
||||
"@types/node": "^20.8.5",
|
||||
"@types/turndown": "^5.0.4"
|
||||
}
|
||||
}
|
||||
|
||||
5
packages/global/support/user/api.d.ts
vendored
@@ -13,3 +13,8 @@ export type OauthLoginProps = {
|
||||
inviterId?: string;
|
||||
tmbId?: string;
|
||||
};
|
||||
|
||||
export type FastLoginProps = {
|
||||
token: string;
|
||||
code: string;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* bill common */
|
||||
import { PRICE_SCALE } from './constants';
|
||||
import { BillItemType, BillSchema } from './type';
|
||||
import { BillSourceEnum } from './constants';
|
||||
import { AuthUserTypeEnum } from '../../permission/constant';
|
||||
|
||||
/**
|
||||
* dataset price / PRICE_SCALE = real price
|
||||
@@ -8,3 +9,15 @@ import { BillItemType, BillSchema } from './type';
|
||||
export const formatPrice = (val = 0, multiple = 1) => {
|
||||
return Number(((val / PRICE_SCALE) * multiple).toFixed(10));
|
||||
};
|
||||
|
||||
export const getBillSourceByAuthType = ({
|
||||
shareId,
|
||||
authType
|
||||
}: {
|
||||
shareId?: string;
|
||||
authType?: `${AuthUserTypeEnum}`;
|
||||
}) => {
|
||||
if (shareId) return BillSourceEnum.shareLink;
|
||||
if (authType === AuthUserTypeEnum.apikey) return BillSourceEnum.api;
|
||||
return BillSourceEnum.fastgpt;
|
||||
};
|
||||
|
||||
@@ -101,18 +101,18 @@ export function request(url: string, data: any, config: ConfigType, method: Meth
|
||||
* @param {Object} config
|
||||
* @returns
|
||||
*/
|
||||
export function GET<T>(url: string, params = {}, config: ConfigType = {}): Promise<T> {
|
||||
export function GET<T = undefined>(url: string, params = {}, config: ConfigType = {}): Promise<T> {
|
||||
return request(url, params, config, 'GET');
|
||||
}
|
||||
|
||||
export function POST<T>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
|
||||
export function POST<T = undefined>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
|
||||
return request(url, data, config, 'POST');
|
||||
}
|
||||
|
||||
export function PUT<T>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
|
||||
export function PUT<T = undefined>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
|
||||
return request(url, data, config, 'PUT');
|
||||
}
|
||||
|
||||
export function DELETE<T>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
|
||||
export function DELETE<T = undefined>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
|
||||
return request(url, data, config, 'DELETE');
|
||||
}
|
||||
|
||||