diff --git a/.dockerignore b/.dockerignore index c7fff2624cca..a237c00be6e9 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,4 +8,5 @@ README.md .yalc/ yalc.lock -testApi/ \ No newline at end of file +testApi/ +*.local.* \ No newline at end of file diff --git a/.gitignore b/.gitignore index e5ba8debf908..600bf57d86d5 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,5 @@ dist/ **/.hugo_build.lock docSite/public/ docSite/resources/_gen/ -docSite/.vercel \ No newline at end of file +docSite/.vercel +*.local.* \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index b3d82c07b4ac..46ee0db93229 100644 --- a/Dockerfile +++ b/Dockerfile @@ -76,6 +76,11 @@ COPY --from=builder --chown=nextjs:nodejs /app/projects/$name/.next/static /app/ COPY --from=builder /app/projects/$name/package.json ./package.json # copy woker COPY --from=workerDeps /app/worker /app/worker +# copy config +COPY ./projects/$name/data/config.json /app/data/config.json +COPY ./projects/$name/data/pluginTemplates /app/data/pluginTemplates +COPY ./projects/$name/data/simpleTemplates /app/data/simpleTemplates + ENV NODE_ENV production ENV NEXT_TELEMETRY_DISABLED 1 diff --git a/docSite/assets/imgs/coreferenceResolution1.png b/docSite/assets/imgs/coreferenceResolution1.png new file mode 100644 index 000000000000..df9b8771b2de Binary files /dev/null and b/docSite/assets/imgs/coreferenceResolution1.png differ diff --git a/docSite/assets/imgs/coreferenceResolution2.png b/docSite/assets/imgs/coreferenceResolution2.png new file mode 100644 index 000000000000..34c1c65ad7e6 Binary files /dev/null and b/docSite/assets/imgs/coreferenceResolution2.png differ diff --git a/docSite/assets/imgs/coreferenceResolution3.png b/docSite/assets/imgs/coreferenceResolution3.png new file mode 100644 index 000000000000..3b3fd4e05a48 Binary files /dev/null and b/docSite/assets/imgs/coreferenceResolution3.png differ diff --git a/docSite/assets/imgs/demo-appointment1.png b/docSite/assets/imgs/demo-appointment1.png index 58554a84db11..4c1c74b27743 100644 Binary files a/docSite/assets/imgs/demo-appointment1.png and b/docSite/assets/imgs/demo-appointment1.png differ diff --git a/docSite/assets/imgs/demo-appointment2.png b/docSite/assets/imgs/demo-appointment2.png index a5a60b23dbe4..e596f96ad287 100644 Binary files a/docSite/assets/imgs/demo-appointment2.png and b/docSite/assets/imgs/demo-appointment2.png differ diff --git a/docSite/assets/imgs/demo-appointment3.png b/docSite/assets/imgs/demo-appointment3.png index 4ef179f378c7..0e5e9ee437c0 100644 Binary files a/docSite/assets/imgs/demo-appointment3.png and b/docSite/assets/imgs/demo-appointment3.png differ diff --git a/docSite/assets/imgs/demo-appointment4.png b/docSite/assets/imgs/demo-appointment4.png index 198043925c25..096002d0c5f9 100644 Binary files a/docSite/assets/imgs/demo-appointment4.png and b/docSite/assets/imgs/demo-appointment4.png differ diff --git a/docSite/assets/imgs/demo-appointment5.png b/docSite/assets/imgs/demo-appointment5.png index 9492702ab917..b0f7a6ebe96a 100644 Binary files a/docSite/assets/imgs/demo-appointment5.png and b/docSite/assets/imgs/demo-appointment5.png differ diff --git a/docSite/assets/imgs/demo-appointment6.png b/docSite/assets/imgs/demo-appointment6.png index 779d142b55c3..4fd3f4f32125 100644 Binary files a/docSite/assets/imgs/demo-appointment6.png and b/docSite/assets/imgs/demo-appointment6.png differ diff --git a/docSite/assets/imgs/demo-appointment7.png b/docSite/assets/imgs/demo-appointment7.png index e78678d93293..3533f4c5fa5a 100644 Binary files a/docSite/assets/imgs/demo-appointment7.png and b/docSite/assets/imgs/demo-appointment7.png differ diff --git a/docSite/assets/imgs/demo-appointment8.png b/docSite/assets/imgs/demo-appointment8.png new file mode 100644 index 000000000000..7afefe26d884 Binary files /dev/null and b/docSite/assets/imgs/demo-appointment8.png differ diff --git a/docSite/assets/imgs/demo-appointment9.png b/docSite/assets/imgs/demo-appointment9.png new file mode 100644 index 000000000000..e4aac6d89d6b Binary files /dev/null and b/docSite/assets/imgs/demo-appointment9.png differ diff --git a/docSite/assets/imgs/demo_op_question1.png b/docSite/assets/imgs/demo_op_question1.png deleted file mode 100644 index 6122719978f1..000000000000 Binary files a/docSite/assets/imgs/demo_op_question1.png and /dev/null differ diff --git a/docSite/assets/imgs/demo_op_question2.png b/docSite/assets/imgs/demo_op_question2.png deleted file mode 100644 index 2ce07fa403ed..000000000000 Binary files a/docSite/assets/imgs/demo_op_question2.png and /dev/null differ diff --git a/docSite/assets/imgs/demo_op_question3.png b/docSite/assets/imgs/demo_op_question3.png deleted file mode 100644 index 0855aab9bf3d..000000000000 Binary files a/docSite/assets/imgs/demo_op_question3.png and /dev/null differ diff --git a/docSite/assets/imgs/google_search_1.png b/docSite/assets/imgs/google_search_1.png index 76c448a35949..9b96319b6f0e 100644 Binary files a/docSite/assets/imgs/google_search_1.png and b/docSite/assets/imgs/google_search_1.png differ diff --git a/docSite/assets/imgs/google_search_2.png b/docSite/assets/imgs/google_search_2.png index 25dde0073886..9e4d3b29bca1 100644 Binary files a/docSite/assets/imgs/google_search_2.png and b/docSite/assets/imgs/google_search_2.png differ diff --git a/docSite/content/docs/development/configuration.md b/docSite/content/docs/development/configuration.md index 09870a1563d4..02421b2556a9 100644 --- a/docSite/content/docs/development/configuration.md +++ b/docSite/content/docs/development/configuration.md @@ -92,7 +92,7 @@ weight: 708 "maxContext": 16000, "maxResponse": 4000, "price": 0, - "functionCall": true, // 是否支持function call, 不支持的模型需要设置为 false,会走提示词生成 + "toolChoice": true, // 是否支持openai的 toolChoice, 不支持的模型需要设置为 false,会走提示词生成 "functionPrompt": "" }, { @@ -101,7 +101,7 @@ weight: 708 "maxContext": 8000, "maxResponse": 8000, "price": 0, - "functionCall": true, + "toolChoice": true, "functionPrompt": "" } ], @@ -112,7 +112,7 @@ weight: 708 "maxContext": 16000, "maxResponse": 4000, "price": 0, - "functionCall": true, + "toolChoice": true, "functionPrompt": "" } ], diff --git a/docSite/content/docs/development/upgrading/465.md b/docSite/content/docs/development/upgrading/465.md new file mode 100644 index 000000000000..cefeb912a81e --- /dev/null +++ b/docSite/content/docs/development/upgrading/465.md @@ -0,0 +1,31 @@ +--- +title: 'V4.6.5(需要改配置文件)' +description: 'FastGPT V4.6.5' +icon: 'upgrade' +draft: false +toc: true +weight: 831 +--- + +## 配置文件变更 + +由于 openai 已开始启用 function call,改为 toolChoice。FastGPT 同步的修改了对于的配置和调用方式,需要对配置文件做一些修改: + +[点击查看最新的配置文件](/docs/development/configuration/) + +主要是修改模型的`functionCall`字段,改成`toolChoice`即可。设置为`true`的模型,会默认走 openai 的 tools 模式;未设置或设置为`false`的,会走提示词生成模式。 + +问题补全模型与内容提取模型使用同一组配置。 + +## V4.6.5 功能介绍 + +1. 新增 - [问题补全模块](/docs/workflow/modules/coreferenceresolution/) +2. 新增 - [文本编辑模块](/docs/workflow/modules/text_editor/) +3. 新增 - [判断器模块](/docs/workflow/modules/tfswitch/) +4. 新增 - [自定义反馈模块](/docs/workflow/modules/custom_feedback/) +5. 新增 - 【内容提取】模块支持选择模型,以及字段枚举 +6. 优化 - docx读取,兼容表格(表格转markdown) +7. 优化 - 高级编排连接线交互 +8. 优化 - 由于 html2md 导致的 cpu密集计算,阻断线程问题 +9. 修复 - 高级编排提示词提取描述 + diff --git a/docSite/content/docs/workflow/examples/google_search.md b/docSite/content/docs/workflow/examples/google_search.md index 681fed73ee48..9568872fde79 100644 --- a/docSite/content/docs/workflow/examples/google_search.md +++ b/docSite/content/docs/workflow/examples/google_search.md @@ -1,17 +1,18 @@ --- -title: '联网 GPT' -description: '将 FastGPT 外接搜索引擎' +title: '接入谷歌搜索' +description: '将 FastGPT 接入谷歌搜索' icon: 'search' draft: false toc: true weight: 402 --- -![](/imgs/google_search_1.png) +| | | +| --------------------- | --------------------- | +| ![](/imgs/google_search_1.png) | ![](/imgs/google_search_2.png) | -![](/imgs/google_search_2.png) -如上图,利用 HTTP 模块,你可以轻松的外接一个搜索引擎。这里以调用 Google Search API 为例。注意:本文主要是为了介绍 HTTP 模型,具体的搜索效果需要依赖提示词和搜索引擎,这两部分可能需要更多的调试。 +如上图,利用 HTTP 模块,你可以外接一个搜索引擎作为AI回复的参考资料。这里以调用 Google Search API 为例。注意:本文主要是为了介绍 HTTP 模型,具体的搜索效果需要依赖提示词和搜索引擎,尤其是【搜索引擎】,简单的搜索引擎无法获取更详细的内容,这部分可能需要更多的调试。 ## 注册 Google Search API @@ -21,20 +22,28 @@ weight: 402 这里用 [Laf](https://laf.dev/) 快速实现一个接口,即写即发布,无需部署。务必打开 POST 请求方式。 +{{% details title="Laf 谷歌搜索Demo" closed="true" %}} + ```ts -import cloud from '@lafjs/cloud'; +import cloud from '@lafjs/cloud' + +const googleSearchKey = "" +const googleCxId = "" +const baseurl = "https://www.googleapis.com/customsearch/v1" -const googleSearchKey = ''; -const googleCxId = ''; -const baseurl = 'https://www.googleapis.com/customsearch/v1'; +type RequestType = { + data: { + searchKey: string + } +} export default async function (ctx: FunctionContext) { - const { searchKey } = ctx.body; + const { data: { searchKey } } = ctx.body as RequestType if (!searchKey) { return { - prompt: '' - }; + prompt: "" + } } try { @@ -45,21 +54,27 @@ export default async function (ctx: FunctionContext) { key: googleSearchKey, c2coff: 1, start: 1, - end: 5, - dateRestrict: 'm[1]' + end: 10, + dateRestrict: 'm[1]', } - }); + }) + // 获取搜索结果 const result = data.items.map((item) => item.snippet).join('\n'); - return { prompt: `搜索词: ${searchKey};google 搜索结果: ${result}` }; + + return { prompt: result } } catch (err) { - console.log(err); + console.log(err) + ctx.response.status(500) return { - prompt: '' - }; + message: "异常" + } } } + ``` +{{% /details %}} + ## 模块编排 复制下面配置,点击「高级编排」右上角的导入按键,导入该配置,导入后将接口地址复制到「HTTP 模块」。 @@ -71,17 +86,21 @@ export default async function (ctx: FunctionContext) { { "moduleId": "userChatInput", "name": "用户问题(对话入口)", + "avatar": "/imgs/module/userChatInput.png", "flowType": "questionInput", "position": { - "x": 464.32198615344566, - "y": 1602.2698463081606 + "x": -210.28388868386423, + "y": 1577.7262770270404 }, "inputs": [ { "key": "userChatInput", "type": "systemInput", + "valueType": "string", "label": "用户问题", - "connected": true + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false } ], "outputs": [ @@ -92,76 +111,42 @@ export default async function (ctx: FunctionContext) { "valueType": "string", "targets": [ { - "moduleId": "6g2075", - "key": "content" - }, - { - "moduleId": "aijmbb", + "moduleId": "p9h459", "key": "userChatInput" } ] } ] }, - { - "moduleId": "history", - "name": "聊天记录", - "flowType": "historyNode", - "position": { - "x": 452.5466249541586, - "y": 1276.3930310334215 - }, - "inputs": [ - { - "key": "maxContext", - "type": "numberInput", - "label": "最长记录数", - "value": 6, - "min": 0, - "max": 50, - "connected": true - }, - { - "key": "history", - "type": "hidden", - "label": "聊天记录", - "connected": true - } - ], - "outputs": [ - { - "key": "history", - "label": "聊天记录", - "valueType": "chatHistory", - "type": "source", - "targets": [ - { - "moduleId": "6g2075", - "key": "history" - }, - { - "moduleId": "aijmbb", - "key": "history" - } - ] - } - ] - }, { "moduleId": "6g2075", "name": "文本内容提取", + "avatar": "/imgs/module/extract.png", "flowType": "contentExtract", "showStatus": true, "position": { - "x": 971.5119545668634, - "y": 1118.186021718385 + "x": 787.652411398441, + "y": 1168.747396089701 }, "inputs": [ { "key": "switch", "type": "target", - "label": "触发器", + "label": "core.module.input.label.switch", "valueType": "any", + "showTargetInApp": true, + "showTargetInPlugin": true, + "connected": false + }, + { + "key": "model", + "type": "selectExtractModel", + "valueType": "string", + "label": "提取模型", + "required": true, + "showTargetInApp": false, + "showTargetInPlugin": false, + "value": "gpt-3.5-turbo", "connected": false }, { @@ -169,18 +154,26 @@ export default async function (ctx: FunctionContext) { "type": "textarea", "valueType": "string", "label": "提取要求描述", - "description": "写一段提取要求,告诉 AI 需要提取哪些内容", + "description": "给AI一些对应的背景知识或要求描述,引导AI更好的完成任务", "required": true, - "placeholder": "例如: \n1. 你是一个实验室预约助手。根据用户问题,提取出姓名、实验室号和预约时间", - "value": "你是谷歌搜索机器人,可以生成搜索词。你需要自行判断是否需要生成搜索词,如果不需要则返回空字符串。", - "connected": true + "placeholder": "例如: \n1. 你是一个实验室预约助手,你的任务是帮助用户预约实验室。\n2. 你是谷歌搜索助手,需要从文本中提取出合适的搜索词。", + "showTargetInApp": true, + "showTargetInPlugin": true, + "value": "你是谷歌搜索机器人,根据当前问题和对话记录生成搜索词,当前时间是: {{cTime}}。\n你需要自行判断是否需要进行网络实时查询:\n- 如果需查询则生成搜索词。\n- 如果不需要查询则返回空字符串。", + "connected": false }, { "key": "history", - "type": "target", - "label": "聊天记录", + "type": "numberInput", + "label": "core.module.input.label.chat history", + "required": true, + "min": 0, + "max": 30, "valueType": "chatHistory", - "connected": true + "value": 2, + "showTargetInApp": true, + "showTargetInPlugin": true, + "connected": false }, { "key": "content", @@ -188,21 +181,27 @@ export default async function (ctx: FunctionContext) { "label": "需要提取的文本", "required": true, "valueType": "string", + "showTargetInApp": true, + "showTargetInPlugin": true, "connected": true }, { "key": "extractKeys", "type": "custom", "label": "目标字段", + "valueType": "any", "description": "由 '描述' 和 'key' 组成一个目标字段,可提取多个目标字段", "value": [ { "desc": "搜索词", "key": "searchKey", - "required": false + "required": false, + "enum": "" } ], - "connected": true + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false } ], "outputs": [ @@ -220,7 +219,7 @@ export default async function (ctx: FunctionContext) { "type": "source", "targets": [ { - "moduleId": "aijmbb", + "moduleId": "5jtdwx", "key": "switch" } ] @@ -241,7 +240,7 @@ export default async function (ctx: FunctionContext) { "type": "source", "targets": [ { - "moduleId": "5fk9ru", + "moduleId": "ee1kxy", "key": "searchKey" } ] @@ -249,101 +248,214 @@ export default async function (ctx: FunctionContext) { ] }, { - "moduleId": "5fk9ru", + "moduleId": "ee1kxy", "name": "HTTP模块", + "avatar": "/imgs/module/http.png", "flowType": "httpRequest", "showStatus": true, "position": { - "x": 1481.5339897373183, - "y": 1290.2958964143072 + "x": 1608.5495771387305, + "y": 1844.976739172803 }, "inputs": [ { - "key": "url", - "value": "https://d8dns0.laf.dev/google_web_search", - "type": "input", - "label": "请求地址", - "description": "请求目标地址", - "placeholder": "https://api.fastgpt.run/getInventory", + "key": "switch", + "type": "target", + "label": "core.module.input.label.switch", + "valueType": "any", + "showTargetInApp": true, + "showTargetInPlugin": true, + "connected": false + }, + { + "key": "system_httpMethod", + "type": "select", + "valueType": "string", + "label": "core.module.input.label.Http Request Method", + "value": "POST", + "list": [ + { + "label": "GET", + "value": "GET" + }, + { + "label": "POST", + "value": "POST" + } + ], "required": true, - "connected": true + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false }, { - "key": "switch", + "key": "system_httpReqUrl", + "type": "input", + "valueType": "string", + "label": "core.module.input.label.Http Request Url", + "description": "core.module.input.description.Http Request Url", + "placeholder": "https://api.ai.com/getInventory", + "required": false, + "showTargetInApp": false, + "showTargetInPlugin": false, + "value": "", + "connected": false + }, + { + "key": "system_httpHeader", + "type": "textarea", + "valueType": "string", + "label": "core.module.input.label.Http Request Header", + "description": "core.module.input.description.Http Request Header", + "placeholder": "core.module.input.description.Http Request Header", + "required": false, + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false + }, + { + "key": "DYNAMIC_INPUT_KEY", "type": "target", - "label": "触发器", "valueType": "any", + "label": "core.module.inputType.dynamicTargetInput", + "description": "core.module.input.description.dynamic input", + "required": false, + "showTargetInApp": false, + "showTargetInPlugin": true, + "hideInApp": true, "connected": false }, { + "key": "searchKey", "valueType": "string", - "type": "target", "label": "搜索词", - "edit": true, - "key": "searchKey", + "type": "target", "required": true, + "description": "", + "edit": true, + "editField": { + "key": true, + "name": true, + "description": true, + "required": true, + "dataType": true + }, "connected": true + }, + { + "key": "system_addInputParam", + "type": "addInputParam", + "valueType": "any", + "label": "", + "required": false, + "showTargetInApp": false, + "showTargetInPlugin": false, + "editField": { + "key": true, + "name": true, + "description": true, + "required": true, + "dataType": true + }, + "defaultEditField": { + "label": "", + "key": "", + "description": "", + "inputType": "target", + "valueType": "string", + "required": true + }, + "connected": false } ], "outputs": [ { - "label": "搜索词", - "valueType": "string", + "key": "finish", + "label": "core.module.output.label.running done", + "description": "core.module.output.description.running done", + "valueType": "boolean", "type": "source", - "edit": true, + "targets": [] + }, + { + "key": "system_addOutputParam", + "type": "addOutputParam", + "valueType": "any", + "label": "", "targets": [], - "key": "searchKey" + "editField": { + "key": true, + "name": true, + "description": true, + "dataType": true + }, + "defaultEditField": { + "label": "", + "key": "", + "description": "", + "outputType": "source", + "valueType": "string" + } }, { - "label": "搜索结果", - "valueType": "string", "type": "source", + "valueType": "string", + "label": "搜索结果", + "description": "", "edit": true, + "editField": { + "key": true, + "name": true, + "description": true, + "dataType": true + }, "targets": [ { - "moduleId": "aijmbb", - "key": "systemPrompt" + "moduleId": "bwhh0x", + "key": "response" } ], "key": "prompt" - }, - { - "key": "finish", - "label": "请求结束", - "valueType": "boolean", - "type": "source", - "targets": [ - { - "moduleId": "aijmbb", - "key": "switch" - } - ] } ] }, { - "moduleId": "aijmbb", + "moduleId": "r8ckxe", "name": "AI 对话", + "avatar": "/imgs/module/AI.png", "flowType": "chatNode", "showStatus": true, "position": { - "x": 2086.6387991825745, - "y": 1090.812798225035 + "x": 2739.8508590056117, + "y": 1804.8613188888335 }, "inputs": [ + { + "key": "switch", + "type": "target", + "label": "core.module.input.label.switch", + "valueType": "any", + "showTargetInApp": true, + "showTargetInPlugin": true, + "connected": false + }, { "key": "model", - "type": "custom", + "type": "selectChatModel", "label": "对话模型", - "value": "gpt-3.5-turbo-16k", - "list": [], - "connected": true + "required": true, + "valueType": "string", + "showTargetInApp": false, + "showTargetInPlugin": false, + "value": "gpt-3.5-turbo", + "connected": false }, { "key": "temperature", - "type": "slider", + "type": "hidden", "label": "温度", "value": 0, + "valueType": "number", "min": 0, "max": 10, "step": 1, @@ -357,13 +469,16 @@ export default async function (ctx: FunctionContext) { "value": 10 } ], - "connected": true + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false }, { "key": "maxToken", - "type": "custom", + "type": "hidden", "label": "回复上限", - "value": 8000, + "value": 2000, + "valueType": "number", "min": 100, "max": 4000, "step": 50, @@ -377,75 +492,588 @@ export default async function (ctx: FunctionContext) { "value": 4000 } ], - "connected": true + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false + }, + { + "key": "isResponseAnswerText", + "type": "hidden", + "label": "返回AI内容", + "value": true, + "valueType": "boolean", + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false + }, + { + "key": "quoteTemplate", + "type": "hidden", + "label": "引用内容模板", + "valueType": "string", + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false + }, + { + "key": "quotePrompt", + "type": "hidden", + "label": "引用内容提示词", + "valueType": "string", + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false + }, + { + "key": "aiSettings", + "type": "aiSettings", + "label": "", + "valueType": "any", + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false }, { "key": "systemPrompt", "type": "textarea", "label": "系统提示词", + "max": 300, "valueType": "string", "description": "模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}", "placeholder": "模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}", - "value": "", + "showTargetInApp": true, + "showTargetInPlugin": true, + "connected": false + }, + { + "key": "history", + "type": "numberInput", + "label": "core.module.input.label.chat history", + "required": true, + "min": 0, + "max": 30, + "valueType": "chatHistory", + "value": 6, + "showTargetInApp": true, + "showTargetInPlugin": true, + "connected": false + }, + { + "key": "quoteQA", + "type": "target", + "label": "引用内容", + "description": "对象数组格式,结构:\n [{q:'问题',a:'回答'}]", + "valueType": "datasetQuote", + "showTargetInApp": true, + "showTargetInPlugin": true, + "connected": false + }, + { + "key": "userChatInput", + "type": "target", + "label": "core.module.input.label.user question", + "required": true, + "valueType": "string", + "showTargetInApp": true, + "showTargetInPlugin": true, "connected": true + } + ], + "outputs": [ + { + "key": "history", + "label": "新的上下文", + "description": "将本次回复内容拼接上历史记录,作为新的上下文返回", + "valueType": "chatHistory", + "type": "source", + "targets": [] + }, + { + "key": "answerText", + "label": "AI回复", + "description": "将在 stream 回复完毕后触发", + "valueType": "string", + "type": "source", + "targets": [] + }, + { + "key": "finish", + "label": "core.module.output.label.running done", + "description": "core.module.output.description.running done", + "valueType": "boolean", + "type": "source", + "targets": [] + } + ] + }, + { + "moduleId": "bwhh0x", + "name": "core.module.template.textEditor", + "avatar": "/imgs/module/textEditor.svg", + "flowType": "pluginModule", + "showStatus": false, + "position": { + "x": 2191.3365552198184, + "y": 2050.00737644673 + }, + "inputs": [ + { + "key": "pluginId", + "type": "hidden", + "label": "pluginId", + "value": "community-textEditor", + "valueType": "string", + "connected": false, + "showTargetInApp": false, + "showTargetInPlugin": false }, { - "key": "limitPrompt", + "key": "switch", + "type": "target", + "label": "core.module.input.label.switch", + "valueType": "any", + "showTargetInApp": true, + "showTargetInPlugin": true, + "connected": false + }, + { + "key": "textarea", + "valueType": "string", + "label": "core.module.input.label.textEditor textarea", "type": "textarea", + "required": true, + "description": "core.module.input.description.textEditor textarea", + "edit": false, + "editField": { + "key": true, + "name": true, + "description": true, + "required": true, + "dataType": true, + "inputType": true + }, + "connected": false, + "placeholder": "core.module.input.description.textEditor textarea", + "value": "谷歌搜索结果:\n\"\"\"\n{{response}}\n\"\"\"\n\n请根据谷歌搜索结果和历史记录来回答我的问题,遵循以下要求:\n- 使用对话的语气回答问题。\n- 不要提及你是从谷歌搜索和历史记录获取的结果。\n- 使用与问题相同的语言回答。\n\n我的问题:“{{q}}”" + }, + { + "key": "response", "valueType": "string", - "label": "限定词", - "description": "限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。可使用变量,例如 {{language}}。引导例子:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 \"Laf\" 无关内容,直接回复: \"我不知道\"。\n2. 你仅回答关于 \"xxx\" 的问题,其他问题回复: \"xxxx\"", - "placeholder": "限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。可使用变量,例如 {{language}}。引导例子:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 \"Laf\" 无关内容,直接回复: \"我不知道\"。\n2. 你仅回答关于 \"xxx\" 的问题,其他问题回复: \"xxxx\"", - "value": "上文是谷歌搜索的结果,你可以提供实时信息,根据搜索结果回答问题。当前时间是{{cTime}}。", + "label": "搜索结果", + "type": "target", + "required": true, + "description": "", + "edit": true, + "editField": { + "key": true, + "name": true, + "description": true, + "required": true, + "dataType": true, + "inputType": false + }, "connected": true }, + { + "key": "q", + "valueType": "string", + "label": "问题", + "type": "target", + "required": true, + "description": "", + "edit": true, + "editField": { + "key": true, + "name": true, + "description": true, + "required": true, + "dataType": true, + "inputType": false + }, + "connected": true + }, + { + "key": "DYNAMIC_INPUT_KEY", + "valueType": "any", + "label": "字符串变量", + "type": "addInputParam", + "required": false, + "description": "可动态的添加字符串类型变量,在文本编辑中通过 {{key}} 使用变量。", + "edit": false, + "editField": { + "key": true, + "name": true, + "description": true, + "required": true, + "dataType": true, + "inputType": false + }, + "defaultEditField": { + "label": "", + "key": "", + "description": "", + "inputType": "target", + "valueType": "string", + "required": true + }, + "connected": false + } + ], + "outputs": [ + { + "key": "text", + "valueType": "string", + "label": "core.module.output.label.text", + "type": "source", + "edit": false, + "targets": [ + { + "moduleId": "r8ckxe", + "key": "userChatInput" + } + ] + } + ] + }, + { + "moduleId": "lxubmw", + "name": "用户问题(入口)", + "avatar": "/imgs/module/userChatInput.png", + "flowType": "questionInput", + "position": { + "x": 1756.2023030545522, + "y": 2638.357914585682 + }, + "inputs": [ + { + "key": "userChatInput", + "type": "systemInput", + "valueType": "string", + "label": "用户问题", + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false + } + ], + "outputs": [ + { + "key": "userChatInput", + "label": "用户问题", + "type": "source", + "valueType": "string", + "targets": [ + { + "moduleId": "bwhh0x", + "key": "q" + } + ] + } + ] + }, + { + "moduleId": "se8tz2", + "name": "用户问题(对话入口)", + "avatar": "/imgs/module/userChatInput.png", + "flowType": "questionInput", + "position": { + "x": 1265.7020997254251, + "y": 1651.8948902038671 + }, + "inputs": [ + { + "key": "userChatInput", + "type": "systemInput", + "valueType": "string", + "label": "用户问题", + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false + } + ], + "outputs": [ + { + "key": "userChatInput", + "label": "用户问题", + "type": "source", + "valueType": "string", + "targets": [ + { + "moduleId": "5jtdwx", + "key": "userChatInput" + } + ] + } + ] + }, + { + "moduleId": "5jtdwx", + "name": "AI 对话", + "avatar": "/imgs/module/AI.png", + "flowType": "chatNode", + "showStatus": true, + "position": { + "x": 1589.1965513432344, + "y": 1018.248906699934 + }, + "inputs": [ { "key": "switch", "type": "target", - "label": "触发器", + "label": "core.module.input.label.switch", "valueType": "any", + "showTargetInApp": true, + "showTargetInPlugin": true, "connected": true }, { - "key": "quoteQA", - "type": "target", - "label": "引用内容", - "valueType": "datasetQuote", + "key": "model", + "type": "selectChatModel", + "label": "对话模型", + "required": true, + "valueType": "string", + "showTargetInApp": false, + "showTargetInPlugin": false, + "value": "gpt-3.5-turbo", + "connected": false + }, + { + "key": "temperature", + "type": "hidden", + "label": "温度", + "value": 0, + "valueType": "number", + "min": 0, + "max": 10, + "step": 1, + "markList": [ + { + "label": "严谨", + "value": 0 + }, + { + "label": "发散", + "value": 10 + } + ], + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false + }, + { + "key": "maxToken", + "type": "hidden", + "label": "回复上限", + "value": 2000, + "valueType": "number", + "min": 100, + "max": 4000, + "step": 50, + "markList": [ + { + "label": "100", + "value": 100 + }, + { + "label": "4000", + "value": 4000 + } + ], + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false + }, + { + "key": "isResponseAnswerText", + "type": "hidden", + "label": "返回AI内容", + "value": true, + "valueType": "boolean", + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false + }, + { + "key": "quoteTemplate", + "type": "hidden", + "label": "引用内容模板", + "valueType": "string", + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false + }, + { + "key": "quotePrompt", + "type": "hidden", + "label": "引用内容提示词", + "valueType": "string", + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false + }, + { + "key": "aiSettings", + "type": "aiSettings", + "label": "", + "valueType": "any", + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false + }, + { + "key": "systemPrompt", + "type": "textarea", + "label": "系统提示词", + "max": 300, + "valueType": "string", + "description": "模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}", + "placeholder": "模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}", + "showTargetInApp": true, + "showTargetInPlugin": true, "connected": false }, { "key": "history", - "type": "target", - "label": "聊天记录", + "type": "numberInput", + "label": "core.module.input.label.chat history", + "required": true, + "min": 0, + "max": 30, "valueType": "chatHistory", - "connected": true + "value": 6, + "showTargetInApp": true, + "showTargetInPlugin": true, + "connected": false + }, + { + "key": "quoteQA", + "type": "target", + "label": "引用内容", + "description": "对象数组格式,结构:\n [{q:'问题',a:'回答'}]", + "valueType": "datasetQuote", + "showTargetInApp": true, + "showTargetInPlugin": true, + "connected": false }, { "key": "userChatInput", "type": "target", - "label": "用户问题", + "label": "core.module.input.label.user question", "required": true, "valueType": "string", + "showTargetInApp": true, + "showTargetInPlugin": true, "connected": true } ], "outputs": [ + { + "key": "history", + "label": "新的上下文", + "description": "将本次回复内容拼接上历史记录,作为新的上下文返回", + "valueType": "chatHistory", + "type": "source", + "targets": [] + }, { "key": "answerText", "label": "AI回复", - "description": "直接响应,无需配置", - "type": "hidden", + "description": "将在 stream 回复完毕后触发", + "valueType": "string", + "type": "source", "targets": [] }, { "key": "finish", - "label": "回复结束", - "description": "AI 回复完成后触发", + "label": "core.module.output.label.running done", + "description": "core.module.output.description.running done", "valueType": "boolean", "type": "source", "targets": [] } ] + }, + { + "moduleId": "p9h459", + "name": "core.module.template.cfr", + "avatar": "/imgs/module/cfr.svg", + "flowType": "cfr", + "showStatus": true, + "position": { + "x": 184.26897486756246, + "y": 1372.7983698162132 + }, + "inputs": [ + { + "key": "switch", + "type": "target", + "label": "core.module.input.label.switch", + "valueType": "any", + "showTargetInApp": true, + "showTargetInPlugin": true, + "connected": false + }, + { + "key": "model", + "type": "selectExtractModel", + "label": "core.module.input.label.aiModel", + "required": true, + "valueType": "string", + "showTargetInApp": false, + "showTargetInPlugin": false, + "value": "gpt-4", + "connected": false + }, + { + "key": "systemPrompt", + "type": "textarea", + "label": "core.module.input.label.cfr background", + "max": 300, + "valueType": "string", + "description": "core.module.input.description.cfr background", + "placeholder": "core.module.input.placeholder.cfr background", + "showTargetInApp": true, + "showTargetInPlugin": true, + "value": "", + "connected": false + }, + { + "key": "history", + "type": "numberInput", + "label": "core.module.input.label.chat history", + "required": true, + "min": 0, + "max": 30, + "valueType": "chatHistory", + "value": 6, + "showTargetInApp": true, + "showTargetInPlugin": true, + "connected": false + }, + { + "key": "userChatInput", + "type": "target", + "label": "core.module.input.label.user question", + "required": true, + "valueType": "string", + "showTargetInApp": true, + "showTargetInPlugin": true, + "connected": true + } + ], + "outputs": [ + { + "key": "system_text", + "label": "core.module.output.label.cfr result", + "valueType": "string", + "type": "source", + "targets": [ + { + "moduleId": "6g2075", + "key": "content" + } + ] + } + ] } ] ``` @@ -454,7 +1082,8 @@ export default async function (ctx: FunctionContext) { ## 流程说明 -1. 提取模块将用户的问题提取成搜索关键词。 -2. 将搜索关键词传入 HTTP 模块。 -3. HTTP 模块调用谷歌搜索接口,返回搜索内容。 -4. 将搜索内容传入【AI 对话】的提示词,引导模型进行回答。 +1. 利用【问题补全】模块,优化用户的问题,明确主体对象。 +2. 利用【内容提取】模块,将用户的问题提取成搜索关键词。 +3. 将搜索关键词传入【HTTP模块】,执行谷歌搜索。 +4. 利用【文本编辑模块】组合搜索结果和问题,生成一个适合模型回答的问题。 +5. 将新的问题发给【AI模块】,回答搜索结果。 diff --git a/docSite/content/docs/workflow/examples/lab_appointment.md b/docSite/content/docs/workflow/examples/lab_appointment.md index 19f6887719de..2ada07a319df 100644 --- a/docSite/content/docs/workflow/examples/lab_appointment.md +++ b/docSite/content/docs/workflow/examples/lab_appointment.md @@ -7,11 +7,12 @@ toc: true weight: 403 --- -![](/imgs/demo-appointment1.png) +| | | +| --------------------- | --------------------- | +| ![](/imgs/demo-appointment1.png) | ![](/imgs/demo-appointment2.png) | +| ![](/imgs/demo-appointment3.png) | ![](/imgs/demo-appointment4.png) | -![](/imgs/demo-appointment2.png) -![](/imgs/demo-appointment3.png) 本示例演示了利用问题分类、内容提取和 HTTP 模块实现数据库的 CRUD 操作。以一个实验室预约为例,用户可以通过对话系统预约、取消、修改预约和查询预约记录。 @@ -19,41 +20,48 @@ weight: 403 编排 Tips:**从左往右编辑流程;尽量不要使线交叉**。 -## 1. 问题分类 +## 1. 全局变量使用 -![](/imgs/demo-appointment4.png) +通过设计一个全局变量,让用户输入姓名,模拟用户身份信息。实际使用过程中,通常是直接通过嵌入 Token 来标记用户身份。 + +## 2. 问题分类 + +![](/imgs/demo-appointment5.png) 如上图,用户问题作为对话的起点,流入【问题分类模块】,根据用户问题的内容,判断用户是询问实验室相关问题、预约实验室或其他问题。如果用户询问的是非实验问题,会直接拒绝回复内容。再根据问题是属于询问实验室相关/预约类问题,执行不同的流程。 {{% alert icon="🤗" context="warning" %}} -**Tips:** 这里需要增加适当的上下文,方便模型更好的判断属于哪个类别。 不过由于是使用了 GPT-3.5 模型进行判断,有时候会抽风~ +**Tips:** 这里需要增加适当的上下文,方便模型更好的判断属于哪个类别~ {{% /alert %}} -## 2. 知识库搜索 +## 3. 实验室介绍的知识库搜索 -![](/imgs/demo-appointment5.png) -这里不多介绍,标准的走了一套实验室介绍的知识库搜索。 +这里不多介绍,标准的走了一套知识库搜索流程。 -## 3. 内容提取 +## 4. 内容提取 -![](/imgs/demo-appointment6.png) +| | | | +| --------------------- | --------------------- |--------------------- | +| ![](/imgs/demo-appointment6.png) | ![](/imgs/demo-appointment7.png) | ![](/imgs/demo-appointment8.png) | -内容提取是 AI 带来革命性的能力,可以从自然语言中提取出结构化的数据,从而方便进行逻辑处理。这里用了 2 个提取模块,一个用于提取姓名、时间和实验室名称;一个用于提取预约行为。 +内容提取是 LLM 带来的十分重要的能力,可以从自然语言中提取出结构化的数据,从而方便进行逻辑处理。 -提取姓名、时间和实验室名称时候,需要注意把必填关掉,否则模型可能会伪造一些内容,同时再对数据处理时候,需要进行判空处理。 +这里用了 2 个提取模块,一个用于提取预约时间和实验室名称;一个用于提取预约行为。 + +提取时间和实验室名称时候,需要注意把必填关掉,否则模型可能会伪造一些内容,同时再对数据处理时候,需要进行判空处理。 最后将两个提取的结果,通过 HTTP 模块发送到后端进行数据库的操作。 -## 4. HTTP +## 5. HTTP模块执行预约操作 -HTTP 模块允许你调用任意 POST 类型的 HTTP 接口,从而实验一些复杂的业务逻辑。这里我们调用了一个预约实验室的接口,传入的是内容提取模块输出的 2 个提取结果。 +HTTP 模块允许你调用任意 GET/POST 类型的 HTTP 接口,从而实现一些复杂的业务逻辑。这里我们调用了一个预约实验室的接口,传入的是信息提取模块的结果和预约行为。 -![](/imgs/demo-appointment7.png) +![](/imgs/demo-appointment9.png) -从日志可以看出,提取的内容中包含了 2 个**字符串数组**,注意是字符串,所以需要进行一次额外的 parse 操作才能拿到里面的对象。具体逻辑可以参考[附件里的 Laf 代码](/docs/workflow/examples/lab_appointment/#laf-云函数代码)。 +具体的入参结构可以参考[HTTP模块](/docs/workflow/modules/http/),实在不行在接口里多打印 Debug。 -响应值也很简单,只需要返回一个 **JSON 对象**即可,注意,是对象,不是字符串。 +响应值也很简单,只需要返回一个 **JSON 对象** 即可。注意!是对象,不是字符串。 # 总结 @@ -61,6 +69,11 @@ HTTP 模块允许你调用任意 POST 类型的 HTTP 接口,从而实验一些 2. 可以通过内容提取模块,实现自然语言转结构化数据,从而实现复杂的逻辑操作。 3. 内容提取 + HTTP 模块允许你无限扩展。 +**难点** + +1. 模型对连续对话的分类和提取能力不足 + + # 附件 ## 编排配置 @@ -74,6 +87,7 @@ HTTP 模块允许你调用任意 POST 类型的 HTTP 接口,从而实验一些 { "moduleId": "userChatInput", "name": "用户问题(对话入口)", + "avatar": "/imgs/module/userChatInput.png", "flowType": "questionInput", "position": { "x": 309.7143912167367, @@ -83,8 +97,11 @@ HTTP 模块允许你调用任意 POST 类型的 HTTP 接口,从而实验一些 { "key": "userChatInput", "type": "systemInput", + "valueType": "string", "label": "用户问题", - "connected": true + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false } ], "outputs": [ @@ -102,80 +119,62 @@ HTTP 模块允许你调用任意 POST 类型的 HTTP 接口,从而实验一些 } ] }, - { - "moduleId": "history", - "name": "聊天记录", - "flowType": "historyNode", - "position": { - "x": 266.7681439415004, - "y": 1152.956322172662 - }, - "inputs": [ - { - "key": "maxContext", - "type": "numberInput", - "label": "最长记录数", - "value": 16, - "min": 0, - "max": 50, - "connected": true - }, - { - "key": "history", - "type": "hidden", - "label": "聊天记录", - "connected": true - } - ], - "outputs": [ - { - "key": "history", - "label": "聊天记录", - "valueType": "chatHistory", - "type": "source", - "targets": [ - { - "moduleId": "hlw67t", - "key": "history" - } - ] - } - ] - }, { "moduleId": "98xq69", "name": "文本内容提取", + "avatar": "/imgs/module/extract.png", "flowType": "contentExtract", "showStatus": true, "position": { - "x": 1990.50096174463, - "y": 1162.2928248187695 + "x": 2025.8337531196155, + "y": 1104.8374776004466 }, "inputs": [ { "key": "switch", "type": "target", - "label": "触发器", + "label": "core.module.input.label.switch", "valueType": "any", + "showTargetInApp": true, + "showTargetInPlugin": true, "connected": true }, + { + "key": "model", + "type": "selectExtractModel", + "valueType": "string", + "label": "提取模型", + "required": true, + "showTargetInApp": false, + "showTargetInPlugin": false, + "value": "gpt-3.5-turbo", + "connected": false + }, { "key": "description", "type": "textarea", "valueType": "string", - "value": "你是实验室预约助手,从文本中提取出: 用户的姓名、预约时间和实验室名称。当前时间 {{cTime}}", "label": "提取要求描述", - "description": "写一段提取要求,告诉 AI 需要提取哪些内容", + "description": "给AI一些对应的背景知识或要求描述,引导AI更好的完成任务", "required": true, - "placeholder": "例如: \n1. 你是一个实验室预约助手。根据用户问题,提取出姓名、实验室号和预约时间", - "connected": true + "placeholder": "例如: \n1. 你是一个实验室预约助手,你的任务是帮助用户预约实验室。\n2. 你是谷歌搜索助手,需要从文本中提取出合适的搜索词。", + "showTargetInApp": true, + "showTargetInPlugin": true, + "value": "系统参数:\n- 当前时间:{{cTime}}\n\n你是实验室预约助手,请从对话中获取相关预约信息:\n\n1. 用户期望预约时间\n2. 实验室名称", + "connected": false }, { "key": "history", - "type": "target", - "label": "聊天记录", + "type": "numberInput", + "label": "core.module.input.label.chat history", + "required": true, + "min": 0, + "max": 30, "valueType": "chatHistory", - "connected": true + "value": 8, + "showTargetInApp": true, + "showTargetInPlugin": true, + "connected": false }, { "key": "content", @@ -183,23 +182,22 @@ HTTP 模块允许你调用任意 POST 类型的 HTTP 接口,从而实验一些 "label": "需要提取的文本", "required": true, "valueType": "string", + "showTargetInApp": true, + "showTargetInPlugin": true, "connected": true }, { "key": "extractKeys", "type": "custom", "label": "目标字段", + "valueType": "any", "description": "由 '描述' 和 'key' 组成一个目标字段,可提取多个目标字段", "value": [ { - "desc": "姓名", - "key": "name", - "required": false - }, - { - "desc": "时间(YYYY/MM/DD HH:mm格式)", + "desc": "预约时间 (YYYY/MM/DD HH:mm 格式)", "key": "time", - "required": false + "required": false, + "enum": "" }, { "desc": "实验室名", @@ -207,7 +205,9 @@ HTTP 模块允许你调用任意 POST 类型的 HTTP 接口,从而实验一些 "required": false } ], - "connected": true + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false } ], "outputs": [ @@ -233,22 +233,14 @@ HTTP 模块允许你调用任意 POST 类型的 HTTP 接口,从而实验一些 "type": "source", "targets": [ { - "moduleId": "ux0wk1", - "key": "appointment" + "moduleId": "xznuym", + "key": "info" } ] }, - { - "key": "name", - "label": "提取结果-姓名", - "description": "无法提取时不会返回", - "valueType": "string", - "type": "source", - "targets": [] - }, { "key": "time", - "label": "提取结果-时间(YYYY/MM/DD HH:mm格式)", + "label": "提取结果-预约时间 (YYYY/MM/DD HH:mm 格式)", "description": "无法提取时不会返回", "valueType": "string", "type": "source", @@ -264,106 +256,43 @@ HTTP 模块允许你调用任意 POST 类型的 HTTP 接口,从而实验一些 } ] }, - { - "moduleId": "ux0wk1", - "name": "HTTP模块", - "flowType": "httpRequest", - "showStatus": true, - "position": { - "x": 2708.3795785896, - "y": 1751.695782003616 - }, - "inputs": [ - { - "key": "url", - "value": "", - "type": "input", - "label": "请求地址", - "description": "请求目标地址", - "placeholder": "https://api.fastgpt.run/getInventory", - "required": true, - "connected": true - }, - { - "key": "switch", - "type": "target", - "label": "触发器", - "valueType": "any", - "connected": false - }, - { - "valueType": "string", - "type": "target", - "label": "提取的字段", - "edit": true, - "key": "appointment", - "required": true, - "connected": true - }, - { - "valueType": "string", - "type": "target", - "label": "预约行为", - "edit": true, - "key": "action", - "required": true, - "connected": true - } - ], - "outputs": [ - { - "key": "finish", - "label": "请求结束", - "valueType": "boolean", - "type": "source", - "targets": [] - }, - { - "label": "提取结果", - "valueType": "string", - "type": "source", - "edit": true, - "targets": [ - { - "moduleId": "eg5upi", - "key": "text" - } - ], - "key": "response" - } - ] - }, { "moduleId": "eg5upi", "name": "指定回复", + "avatar": "/imgs/module/reply.png", "flowType": "answerNode", "position": { - "x": 3437.5642119438417, - "y": 1941.2730515095657 + "x": 3273.0448927780258, + "y": 2339.4574906500184 }, "inputs": [ { "key": "switch", "type": "target", - "label": "触发器", + "label": "core.module.input.label.switch", "valueType": "any", + "showTargetInApp": true, + "showTargetInPlugin": true, "connected": false }, { "key": "text", "type": "textarea", - "valueType": "string", - "value": "", + "valueType": "any", "label": "回复的内容", - "description": "可以使用 \\n 来实现换行。也可以通过外部模块输入实现回复,外部模块输入时会覆盖当前填写的内容", + "description": "可以使用 \\n 来实现连续换行。\n可以通过外部模块输入实现回复,外部模块输入时会覆盖当前填写的内容。\n如传入非字符串类型数据将会自动转成字符串", + "placeholder": "可以使用 \\n 来实现连续换行。\n可以通过外部模块输入实现回复,外部模块输入时会覆盖当前填写的内容。\n如传入非字符串类型数据将会自动转成字符串", + "showTargetInApp": true, + "showTargetInPlugin": true, + "value": "", "connected": true } ], "outputs": [ { "key": "finish", - "label": "回复结束", - "description": "回复完成后触发", + "label": "core.module.output.label.running done", + "description": "core.module.output.description.running done", "valueType": "boolean", "type": "source", "targets": [] @@ -373,18 +302,65 @@ HTTP 模块允许你调用任意 POST 类型的 HTTP 接口,从而实验一些 { "moduleId": "kge59i", "name": "用户引导", + "avatar": "/imgs/module/userGuide.png", "flowType": "userGuide", "position": { - "x": 278.3025954454602, - "y": 879.3568006623397 + "x": 271.18826350548954, + "y": 777.38470952276 }, "inputs": [ { "key": "welcomeText", - "type": "input", + "type": "hidden", + "valueType": "string", "label": "开场白", + "showTargetInApp": false, + "showTargetInPlugin": false, "value": "你好,我是实验室助手,请问有什么可以帮助你的么?如需预约或修改预约实验室,请提供姓名、时间和实验室名称。\n[实验室介绍]\n[开放时间]\n[预约]", - "connected": true + "connected": false + }, + { + "key": "variables", + "type": "hidden", + "valueType": "any", + "label": "对话框变量", + "value": [ + { + "id": "nzpco0", + "key": "name", + "label": "姓名", + "type": "input", + "required": true, + "maxLen": 50, + "enums": [ + { + "value": "" + } + ] + } + ], + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false + }, + { + "key": "questionGuide", + "valueType": "boolean", + "type": "switch", + "label": "问题引导", + "showTargetInApp": false, + "showTargetInPlugin": false, + "value": false, + "connected": false + }, + { + "key": "tts", + "type": "hidden", + "valueType": "any", + "label": "语音播报", + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false } ], "outputs": [] @@ -392,6 +368,7 @@ HTTP 模块允许你调用任意 POST 类型的 HTTP 接口,从而实验一些 { "moduleId": "hlw67t", "name": "问题分类", + "avatar": "/imgs/module/cq.png", "flowType": "classifyQuestion", "showStatus": true, "position": { @@ -399,34 +376,65 @@ HTTP 模块允许你调用任意 POST 类型的 HTTP 接口,从而实验一些 "y": 1164.1601096928105 }, "inputs": [ + { + "key": "switch", + "type": "target", + "label": "core.module.input.label.switch", + "valueType": "any", + "showTargetInApp": true, + "showTargetInPlugin": true, + "connected": false + }, + { + "key": "model", + "type": "selectCQModel", + "valueType": "string", + "label": "分类模型", + "required": true, + "showTargetInApp": false, + "showTargetInPlugin": false, + "value": "gpt-4", + "connected": false + }, { "key": "systemPrompt", "type": "textarea", "valueType": "string", - "value": "你是实验室助手,判断用户是询问实验室相关问题、预约实验室或其他问题", - "label": "系统提示词", + "label": "背景知识", "description": "你可以添加一些特定内容的介绍,从而更好的识别用户的问题类型。这个内容通常是给模型介绍一个它不知道的内容。", - "placeholder": "例如: \n1. Laf 是一个云函数开发平台……\n2. Sealos 是一个集群操作系统", - "connected": true + "placeholder": "例如: \n1. AIGC(人工智能生成内容)是指使用人工智能技术自动或半自动地生成数字内容,如文本、图像、音乐、视频等。\n2. AIGC技术包括但不限于自然语言处理、计算机视觉、机器学习和深度学习。这些技术可以创建新内容或修改现有内容,以满足特定的创意、教育、娱乐或信息需求。", + "showTargetInApp": true, + "showTargetInPlugin": true, + "value": "实验室是由浙江工业大学主导的人工智能实验室,请判断用户的问题是属于询问实验室介绍,或是预约实验室。", + "connected": false }, { "key": "history", - "type": "target", - "label": "聊天记录", + "type": "numberInput", + "label": "core.module.input.label.chat history", + "required": true, + "min": 0, + "max": 30, "valueType": "chatHistory", - "connected": true + "value": 12, + "showTargetInApp": true, + "showTargetInPlugin": true, + "connected": false }, { "key": "userChatInput", "type": "target", - "label": "用户问题", + "label": "core.module.input.label.user question", "required": true, "valueType": "string", + "showTargetInApp": true, + "showTargetInPlugin": true, "connected": true }, { "key": "agents", "type": "custom", + "valueType": "any", "label": "", "value": [ { @@ -438,11 +446,13 @@ HTTP 模块允许你调用任意 POST 类型的 HTTP 接口,从而实验一些 "key": "fqsw" }, { - "value": "其他问题", + "value": "一般聊天", "key": "sq32" } ], - "connected": true + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false } ], "outputs": [ @@ -465,6 +475,10 @@ HTTP 模块允许你调用任意 POST 类型的 HTTP 接口,从而实验一些 { "moduleId": "98xq69", "key": "switch" + }, + { + "moduleId": "mhw4md", + "key": "switch" } ] }, @@ -484,40 +498,64 @@ HTTP 模块允许你调用任意 POST 类型的 HTTP 接口,从而实验一些 "label": "", "type": "hidden", "targets": [] + }, + { + "key": "wqre", + "label": "", + "type": "hidden", + "targets": [] + }, + { + "key": "sdfa", + "label": "", + "type": "hidden", + "targets": [] + }, + { + "key": "agex", + "label": "", + "type": "hidden", + "targets": [] } ] }, { "moduleId": "l5xe4u", "name": "指定回复", + "avatar": "/imgs/module/reply.png", "flowType": "answerNode", "position": { - "x": 777.8362177291783, - "y": 1954.8053341919722 + "x": 1094.059515373104, + "y": 2184.2930987678496 }, "inputs": [ { "key": "switch", "type": "target", - "label": "触发器", + "label": "core.module.input.label.switch", "valueType": "any", + "showTargetInApp": true, + "showTargetInPlugin": true, "connected": true }, { "key": "text", "type": "textarea", - "valueType": "string", - "value": "对不起,我不太理解你的问题,请更详细描述关于实验室问题。", + "valueType": "any", "label": "回复的内容", - "description": "可以使用 \\n 来实现换行。也可以通过外部模块输入实现回复,外部模块输入时会覆盖当前填写的内容", - "connected": true + "description": "可以使用 \\n 来实现连续换行。\n可以通过外部模块输入实现回复,外部模块输入时会覆盖当前填写的内容。\n如传入非字符串类型数据将会自动转成字符串", + "placeholder": "可以使用 \\n 来实现连续换行。\n可以通过外部模块输入实现回复,外部模块输入时会覆盖当前填写的内容。\n如传入非字符串类型数据将会自动转成字符串", + "showTargetInApp": true, + "showTargetInPlugin": true, + "value": "对不起,我不太理解你的问题,请更详细描述关于实验室问题。", + "connected": false } ], "outputs": [ { "key": "finish", - "label": "回复结束", - "description": "回复完成后触发", + "label": "core.module.output.label.running done", + "description": "core.module.output.description.running done", "valueType": "boolean", "type": "source", "targets": [] @@ -527,86 +565,96 @@ HTTP 模块允许你调用任意 POST 类型的 HTTP 接口,从而实验一些 { "moduleId": "zltb5l", "name": "知识库搜索", + "avatar": "/imgs/module/db.png", "flowType": "datasetSearchNode", "showStatus": true, "position": { - "x": 1634.995464753433, - "y": 108.17018849334033 + "x": 1573.0026778213864, + "y": 17.56534605419546 }, "inputs": [ { - "key": "kbList", - "type": "custom", + "key": "switch", + "type": "target", + "label": "core.module.input.label.switch", + "valueType": "any", + "showTargetInApp": true, + "showTargetInPlugin": true, + "connected": true + }, + { + "key": "datasets", + "type": "selectDataset", "label": "关联的知识库", - "value": [ - { - "kbId": "64f585865ae84cf2f223e8bd", - "vectorModel": { - "model": "text-embedding-ada-002", - "name": "Embedding-2", - "price": 0.2, - "defaultToken": 500, - "maxToken": 3000 - } - } - ], + "value": [], + "valueType": "selectDataset", "list": [], - "connected": true + "required": true, + "showTargetInApp": false, + "showTargetInPlugin": true, + "connected": false }, { "key": "similarity", - "type": "slider", - "label": "相似度", + "type": "hidden", + "label": "最低相关性", "value": 0.69, + "valueType": "number", "min": 0, "max": 1, "step": 0.01, "markList": [ { - "label": "100", - "value": 100 + "label": "0", + "value": 0 }, { "label": "1", "value": 1 } ], - "connected": true + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false }, { "key": "limit", - "type": "slider", - "label": "单次搜索上限", - "description": "最多取 n 条记录作为本次问题引用", + "type": "hidden", + "label": "引用上限", + "description": "单次搜索最大的 Tokens 数量,中文约1字=1.7Tokens,英文约1字=1Tokens", "value": 2, - "min": 1, - "max": 20, - "step": 1, - "markList": [ - { - "label": "1", - "value": 1 - }, - { - "label": "20", - "value": 20 - } - ], - "connected": true + "valueType": "number", + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false }, { - "key": "switch", - "type": "target", - "label": "触发器", + "key": "searchMode", + "type": "hidden", + "label": "core.dataset.search.Mode", + "valueType": "string", + "showTargetInApp": false, + "showTargetInPlugin": false, + "value": "embedding", + "connected": false + }, + { + "key": "datasetParamsModal", + "type": "selectDatasetParamsModal", + "label": "", "valueType": "any", - "connected": true + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false }, { "key": "userChatInput", "type": "target", - "label": "用户问题", + "label": "core.module.input.label.user question", "required": true, "valueType": "string", + "showTargetInApp": true, + "showTargetInPlugin": true, "connected": true } ], @@ -637,12 +685,21 @@ HTTP 模块允许你调用任意 POST 类型的 HTTP 接口,从而实验一些 "key": "quoteQA" } ] + }, + { + "key": "finish", + "label": "core.module.output.label.running done", + "description": "core.module.output.description.running done", + "valueType": "boolean", + "type": "source", + "targets": [] } ] }, { "moduleId": "bjfklc", "name": "AI 对话", + "avatar": "/imgs/module/AI.png", "flowType": "chatNode", "showStatus": true, "position": { @@ -651,18 +708,31 @@ HTTP 模块允许你调用任意 POST 类型的 HTTP 接口,从而实验一些 }, "inputs": [ { - "key": "model", - "type": "custom", - "label": "对话模型", - "value": "gpt-3.5-turbo", - "list": [], - "connected": true + "key": "switch", + "type": "target", + "label": "core.module.input.label.switch", + "valueType": "any", + "showTargetInApp": true, + "showTargetInPlugin": true, + "connected": false + }, + { + "key": "model", + "type": "selectChatModel", + "label": "对话模型", + "required": true, + "valueType": "string", + "showTargetInApp": false, + "showTargetInPlugin": false, + "value": "gpt-3.5-turbo", + "connected": false }, { "key": "temperature", - "type": "slider", + "type": "hidden", "label": "温度", "value": 0, + "valueType": "number", "min": 0, "max": 10, "step": 1, @@ -676,13 +746,16 @@ HTTP 模块允许你调用任意 POST 类型的 HTTP 接口,从而实验一些 "value": 10 } ], - "connected": true + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false }, { "key": "maxToken", - "type": "custom", + "type": "hidden", "label": "回复上限", "value": 550, + "valueType": "number", "min": 100, "max": 4000, "step": 50, @@ -696,71 +769,127 @@ HTTP 模块允许你调用任意 POST 类型的 HTTP 接口,从而实验一些 "value": 4000 } ], - "connected": true + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false }, { - "key": "systemPrompt", - "type": "textarea", - "label": "系统提示词", + "key": "isResponseAnswerText", + "type": "hidden", + "label": "返回AI内容", + "value": true, + "valueType": "boolean", + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false + }, + { + "key": "quoteTemplate", + "type": "hidden", + "label": "引用内容模板", "valueType": "string", - "description": "模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}", - "placeholder": "模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}", + "showTargetInApp": false, + "showTargetInPlugin": false, "value": "", - "connected": true + "connected": false }, { - "key": "limitPrompt", - "type": "textarea", + "key": "quotePrompt", + "type": "hidden", + "label": "引用内容提示词", "valueType": "string", - "label": "限定词", - "description": "限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。可使用变量,例如 {{language}}。引导例子:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 \"Laf\" 无关内容,直接回复: \"我不知道\"。\n2. 你仅回答关于 \"xxx\" 的问题,其他问题回复: \"xxxx\"", - "placeholder": "限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。可使用变量,例如 {{language}}。引导例子:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 \"Laf\" 无关内容,直接回复: \"我不知道\"。\n2. 你仅回答关于 \"xxx\" 的问题,其他问题回复: \"xxxx\"", + "showTargetInApp": false, + "showTargetInPlugin": false, "value": "", - "connected": true + "connected": false }, { - "key": "switch", - "type": "target", - "label": "触发器", + "key": "aiSettings", + "type": "aiSettings", + "label": "", "valueType": "any", + "showTargetInApp": false, + "showTargetInPlugin": false, "connected": false }, { - "key": "quoteQA", - "type": "target", - "label": "引用内容", - "valueType": "datasetQuote", - "connected": true + "key": "systemPrompt", + "type": "textarea", + "label": "系统提示词", + "max": 300, + "valueType": "string", + "description": "模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}", + "placeholder": "模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}", + "showTargetInApp": true, + "showTargetInPlugin": true, + "value": "", + "connected": false }, { "key": "history", - "type": "target", - "label": "聊天记录", + "type": "numberInput", + "label": "core.module.input.label.chat history", + "required": true, + "min": 0, + "max": 30, "valueType": "chatHistory", + "value": 4, + "showTargetInApp": true, + "showTargetInPlugin": true, + "connected": false + }, + { + "key": "quoteQA", + "type": "target", + "label": "引用内容", + "description": "对象数组格式,结构:\n [{q:'问题',a:'回答'}]", + "valueType": "datasetQuote", + "showTargetInApp": true, + "showTargetInPlugin": true, "connected": true }, { "key": "userChatInput", "type": "target", - "label": "用户问题", + "label": "core.module.input.label.user question", "required": true, "valueType": "string", + "showTargetInApp": true, + "showTargetInPlugin": true, "connected": true + }, + { + "key": "limitPrompt", + "type": "textarea", + "valueType": "string", + "label": "限定词", + "description": "限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。可使用变量,例如 {{language}}。引导例子:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 \"Laf\" 无关内容,直接回复: \"我不知道\"。\n2. 你仅回答关于 \"xxx\" 的问题,其他问题回复: \"xxxx\"", + "placeholder": "限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。可使用变量,例如 {{language}}。引导例子:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 \"Laf\" 无关内容,直接回复: \"我不知道\"。\n2. 你仅回答关于 \"xxx\" 的问题,其他问题回复: \"xxxx\"", + "value": "", + "connected": false } ], "outputs": [ { "key": "answerText", - "label": "模型AI回复回复", + "label": "AI回复", "description": "将在 stream 回复完毕后触发", "valueType": "string", "type": "source", "targets": [] }, + { + "key": "history", + "label": "新的上下文", + "description": "将本次回复内容拼接上历史记录,作为新的上下文返回", + "valueType": "chatHistory", + "type": "source", + "targets": [] + }, { "key": "finish", - "label": "回复结束", - "description": "AI 回复完成后触发", + "label": "core.module.output.label.running done", + "description": "core.module.output.description.running done", "valueType": "boolean", "type": "source", "targets": [] @@ -770,17 +899,21 @@ HTTP 模块允许你调用任意 POST 类型的 HTTP 接口,从而实验一些 { "moduleId": "ee1fo3", "name": "用户问题(对话入口)", + "avatar": "/imgs/module/userChatInput.png", "flowType": "questionInput", "position": { - "x": 1133.7087158919899, - "y": 638.1461154935015 + "x": 1252.9256138382332, + "y": 704.9075783433977 }, "inputs": [ { "key": "userChatInput", "type": "systemInput", + "valueType": "string", "label": "用户问题", - "connected": true + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false } ], "outputs": [ @@ -802,80 +935,62 @@ HTTP 模块允许你调用任意 POST 类型的 HTTP 接口,从而实验一些 } ] }, - { - "moduleId": "14dsss", - "name": "聊天记录", - "flowType": "historyNode", - "position": { - "x": 1670.1688237345365, - "y": 785.0835604459131 - }, - "inputs": [ - { - "key": "maxContext", - "type": "numberInput", - "label": "最长记录数", - "value": 6, - "min": 0, - "max": 50, - "connected": true - }, - { - "key": "history", - "type": "hidden", - "label": "聊天记录", - "connected": true - } - ], - "outputs": [ - { - "key": "history", - "label": "聊天记录", - "valueType": "chatHistory", - "type": "source", - "targets": [ - { - "moduleId": "bjfklc", - "key": "history" - } - ] - } - ] - }, { "moduleId": "mhw4md", "name": "文本内容提取", + "avatar": "/imgs/module/extract.png", "flowType": "contentExtract", "showStatus": true, "position": { - "x": 1955.3493020276055, - "y": 2135.4407620304137 + "x": 2035.4759582500983, + "y": 2140.0194281002705 }, "inputs": [ { "key": "switch", "type": "target", - "label": "触发器", + "label": "core.module.input.label.switch", "valueType": "any", + "showTargetInApp": true, + "showTargetInPlugin": true, + "connected": true + }, + { + "key": "model", + "type": "selectExtractModel", + "valueType": "string", + "label": "提取模型", + "required": true, + "showTargetInApp": false, + "showTargetInPlugin": false, + "value": "gpt-3.5-turbo", "connected": false }, { "key": "description", "type": "textarea", "valueType": "string", - "value": "请根据我们的对话,判断我是需要预约、取消预约还是修改预约实验室。", "label": "提取要求描述", - "description": "写一段提取要求,告诉 AI 需要提取哪些内容", + "description": "给AI一些对应的背景知识或要求描述,引导AI更好的完成任务", "required": true, - "placeholder": "例如: \n1. 你是一个实验室预约助手。根据用户问题,提取出姓名、实验室号和预约时间", - "connected": true + "placeholder": "例如: \n1. 你是一个实验室预约助手,你的任务是帮助用户预约实验室。\n2. 你是谷歌搜索助手,需要从文本中提取出合适的搜索词。", + "showTargetInApp": true, + "showTargetInPlugin": true, + "value": "判断我的行为:查询预约,新增预约、取消预约或者修改预约实验室。", + "connected": false }, { "key": "history", - "type": "target", - "label": "聊天记录", + "type": "numberInput", + "label": "core.module.input.label.chat history", + "required": true, + "min": 0, + "max": 30, "valueType": "chatHistory", - "connected": true + "value": 4, + "showTargetInApp": true, + "showTargetInPlugin": true, + "connected": false }, { "key": "content", @@ -883,36 +998,27 @@ HTTP 模块允许你调用任意 POST 类型的 HTTP 接口,从而实验一些 "label": "需要提取的文本", "required": true, "valueType": "string", + "showTargetInApp": true, + "showTargetInPlugin": true, "connected": true }, { "key": "extractKeys", "type": "custom", "label": "目标字段", + "valueType": "any", "description": "由 '描述' 和 'key' 组成一个目标字段,可提取多个目标字段", "value": [ { - "desc": "预约实验室", - "key": "post", - "required": false - }, - { - "desc": "取消预约", - "key": "remove", - "required": false - }, - { - "desc": "修改预约", - "key": "put", - "required": false - }, - { - "desc": "查询预约记录", - "key": "get", - "required": false + "desc": "行为", + "key": "action", + "required": true, + "enum": "post\ndelete\nput\nget" } ], - "connected": true + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false } ], "outputs": [ @@ -936,121 +1042,246 @@ HTTP 模块允许你调用任意 POST 类型的 HTTP 接口,从而实验一些 "description": "一个 JSON 字符串,例如:{\"name:\":\"YY\",\"Time\":\"2023/7/2 18:00\"}", "valueType": "string", "type": "source", - "targets": [ - { - "moduleId": "ux0wk1", - "key": "action" - } - ] - }, - { - "key": "post", - "label": "提取结果-预约实验室", - "description": "无法提取时不会返回", - "valueType": "string", - "type": "source", - "targets": [] - }, - { - "key": "put", - "label": "提取结果-修改预约", - "description": "无法提取时不会返回", - "valueType": "string", - "type": "source", "targets": [] }, { - "key": "remove", - "label": "提取结果-取消预约", - "description": "无法提取时不会返回", - "valueType": "string", - "type": "source", - "targets": [] - }, - { - "key": "get", - "label": "提取结果-查询预约记录", + "key": "action", + "label": "提取结果-行为", "description": "无法提取时不会返回", "valueType": "string", "type": "source", - "targets": [] + "targets": [ + { + "moduleId": "xznuym", + "key": "action" + } + ] } ] }, { - "moduleId": "643ik3", - "name": "聊天记录", - "flowType": "historyNode", + "moduleId": "x3ymlc", + "name": "用户问题(对话入口)", + "avatar": "/imgs/module/userChatInput.png", + "flowType": "questionInput", "position": { - "x": 1402.5447731090367, - "y": 1933.5935888119106 + "x": 1482.787362456553, + "y": 1763.0754750794902 }, "inputs": [ { - "key": "maxContext", - "type": "numberInput", - "label": "最长记录数", - "value": 16, - "min": 0, - "max": 50, - "connected": true - }, - { - "key": "history", - "type": "hidden", - "label": "聊天记录", - "connected": true + "key": "userChatInput", + "type": "systemInput", + "valueType": "string", + "label": "用户问题", + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false } ], "outputs": [ { - "key": "history", - "label": "聊天记录", - "valueType": "chatHistory", + "key": "userChatInput", + "label": "用户问题", "type": "source", + "valueType": "string", "targets": [ { "moduleId": "98xq69", - "key": "history" + "key": "content" }, { "moduleId": "mhw4md", - "key": "history" + "key": "content" } ] } ] }, { - "moduleId": "x3ymlc", - "name": "用户问题(对话入口)", - "flowType": "questionInput", + "moduleId": "xznuym", + "name": "HTTP模块", + "avatar": "/imgs/module/http.png", + "flowType": "httpRequest", + "showStatus": true, "position": { - "x": 1457.4894986450388, - "y": 1763.0754750794902 + "x": 2751.575624241899, + "y": 1976.1556611102292 }, "inputs": [ { - "key": "userChatInput", - "type": "systemInput", - "label": "用户问题", + "key": "switch", + "type": "target", + "label": "core.module.input.label.switch", + "valueType": "any", + "showTargetInApp": true, + "showTargetInPlugin": true, + "connected": false + }, + { + "key": "system_httpMethod", + "type": "select", + "valueType": "string", + "label": "core.module.input.label.Http Request Method", + "value": "POST", + "list": [ + { + "label": "GET", + "value": "GET" + }, + { + "label": "POST", + "value": "POST" + } + ], + "required": true, + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false + }, + { + "key": "system_httpReqUrl", + "type": "input", + "valueType": "string", + "label": "core.module.input.label.Http Request Url", + "description": "core.module.input.description.Http Request Url", + "placeholder": "https://api.ai.com/getInventory", + "required": false, + "showTargetInApp": false, + "showTargetInPlugin": false, + "value": "", + "connected": false + }, + { + "key": "system_httpHeader", + "type": "textarea", + "valueType": "string", + "label": "core.module.input.label.Http Request Header", + "description": "core.module.input.description.Http Request Header", + "placeholder": "core.module.input.description.Http Request Header", + "required": false, + "showTargetInApp": false, + "showTargetInPlugin": false, + "connected": false + }, + { + "key": "DYNAMIC_INPUT_KEY", + "type": "target", + "valueType": "any", + "label": "core.module.inputType.dynamicTargetInput", + "description": "core.module.input.description.dynamic input", + "required": false, + "showTargetInApp": false, + "showTargetInPlugin": true, + "hideInApp": true, + "connected": false + }, + { + "key": "info", + "valueType": "string", + "label": "资料提取结果", + "type": "target", + "required": true, + "description": "", + "edit": true, + "editField": { + "key": true, + "name": true, + "description": true, + "required": true, + "dataType": true + }, + "connected": true + }, + { + "key": "action", + "valueType": "string", + "label": "预约行为", + "type": "target", + "required": true, + "description": "", + "edit": true, + "editField": { + "key": true, + "name": true, + "description": true, + "required": true, + "dataType": true + }, "connected": true + }, + { + "key": "system_addInputParam", + "type": "addInputParam", + "valueType": "any", + "label": "", + "required": false, + "showTargetInApp": false, + "showTargetInPlugin": false, + "editField": { + "key": true, + "name": true, + "description": true, + "required": true, + "dataType": true + }, + "defaultEditField": { + "label": "", + "key": "", + "description": "", + "inputType": "target", + "valueType": "string", + "required": true + }, + "connected": false } ], "outputs": [ { - "key": "userChatInput", - "label": "用户问题", + "key": "finish", + "label": "core.module.output.label.running done", + "description": "core.module.output.description.running done", + "valueType": "boolean", + "type": "source", + "targets": [] + }, + { + "key": "system_addOutputParam", + "type": "addOutputParam", + "valueType": "any", + "label": "", + "targets": [], + "editField": { + "key": true, + "name": true, + "description": true, + "dataType": true + }, + "defaultEditField": { + "label": "", + "key": "", + "description": "", + "outputType": "source", + "valueType": "string" + } + }, + { "type": "source", "valueType": "string", + "key": "result", + "label": "结果", + "description": "", + "edit": true, + "editField": { + "key": true, + "name": true, + "description": true, + "dataType": true + }, "targets": [ { - "moduleId": "98xq69", - "key": "content" - }, - { - "moduleId": "mhw4md", - "key": "content" + "moduleId": "eg5upi", + "key": "text" } ] } @@ -1067,179 +1298,172 @@ HTTP 模块允许你调用任意 POST 类型的 HTTP 接口,从而实验一些 {{% details title="函数代码" closed="true" %}} -```typescript -import cloud from '@lafjs/cloud'; -const db = cloud.database(); +```ts +import cloud from '@lafjs/cloud' +const db = cloud.database() + +type RequestType = { + variables: { + name: string; + } + data: { + info: string; + action: 'post' | 'delete' | 'put' | 'get' + } +} +type recordType = { + name?: string; + time?: string; + labname?: string; +} export default async function (ctx: FunctionContext) { try { - const { appointment, action } = ctx.body; - console.log(appointment, action); - const parseBody = JSON.parse(appointment); - const { get, post, put, remove } = JSON.parse(action); + const { variables: { name }, data: { info, action } } = ctx.body as RequestType + + const parseBody = { name, ...JSON.parse(info) } as recordType - if (!!get) { - return await getRecord(parseBody); + if (action === 'get') { + return await getRecord(parseBody) } - if (!!post) { - return await createRecord(parseBody); + if (action === 'post') { + return await createRecord(parseBody) } - if (!!put) { - return await putRecord(parseBody); + if (action === 'put') { + return await putRecord(parseBody) } - if (!!remove) { - return await removeRecord(parseBody); + if (action === 'delete') { + return await removeRecord(parseBody) } + return { - response: '异常' - }; + result: "异常" + } } catch (err) { return { - response: '异常' - }; + result: "异常" + } } } -async function putRecord({ name, time, labname }) { - const missData = []; - if (!name) missData.push('你的姓名'); +async function putRecord({ name, time, labname }: recordType) { + const missData = [] + if (!name) missData.push("你的姓名") if (missData.length > 0) { return { - response: `请提供: ${missData.join('、')}` - }; + result: `请提供: ${missData.join("、")}` + } } - const { data: record } = await db - .collection('LabAppointment') - .where({ - name, - status: 'unStart' - }) - .getOne(); + const { data: record } = await db.collection("LabAppointment").where({ + name, status: "unStart" + }).getOne() if (!record) { return { - response: `${name} 还没有预约记录` - }; + result: `${name} 还没有预约记录` + } } const updateWhere = { name, time: time || record.time, labname: labname || record.labname - }; + } - await db - .collection('LabAppointment') - .where({ - name, - status: 'unStart' - }) - .update(updateWhere); + await db.collection("LabAppointment").where({ + name, status: "unStart" + }).update(updateWhere) return { - response: `修改预约成功。 - 姓名:${name} + result: `修改预约成功。 + 姓名:${name}· 时间: ${updateWhere.time} - 实验室: ${updateWhere.labname} - ` - }; + 实验室名: ${updateWhere.labname} + ` } } -async function getRecord({ name }) { + +async function getRecord({ name }: recordType) { if (!name) { return { - response: '请提供你的姓名' - }; + result: "请提供你的姓名" + } } - const { data } = await db - .collection('LabAppointment') - .where({ name, status: 'unStart' }) - .getOne(); + const { data } = await db.collection('LabAppointment').where({ name, status: "unStart" }).getOne() if (!data) { return { - response: `${name} 没有预约中的记录` - }; + result: `${name} 没有预约中的记录` + } } return { - response: `${name} 有一条预约记录: + result: `${name} 有一条预约记录: 姓名:${data.name} 时间: ${data.time} -实验室: ${data.labname} +实验室名: ${data.labname} ` - }; + } } -async function removeRecord({ name }) { +async function removeRecord({ name }: recordType) { if (!name) { return { - response: '请提供你的姓名' - }; + result: "请提供你的姓名" + } } - const { deleted } = await db - .collection('LabAppointment') - .where({ name, status: 'unStart' }) - .remove(); + const { deleted } = await db.collection('LabAppointment').where({ name, status: "unStart" }).remove() if (deleted > 0) { return { - response: `取消预约记录成功: ${name}` - }; + result: `取消预约记录成功: ${name}` + } } return { - response: ` ${name} 没有预约中的记录` - }; + result: ` ${name} 没有预约中的记录` + } } -async function createRecord({ name, time, labname }) { - const missData = []; - if (!name) missData.push('你的姓名'); - if (!time) missData.push('需要预约的时间'); - if (!labname) missData.push('实验室名称'); +async function createRecord({ name, time, labname }: recordType) { + const missData = [] + if (!name) missData.push("你的姓名") + if (!time) missData.push("需要预约的时间") + if (!labname) missData.push("实验室名名称") if (missData.length > 0) { return { - response: `请提供: ${missData.join('、')}` - }; + result: `请提供: ${missData.join("、")}` + } } - const { data: record } = await db - .collection('LabAppointment') - .where({ - name, - status: 'unStart' - }) - .getOne(); + const { data: record } = await db.collection("LabAppointment").where({ + name, status: "unStart" + }).getOne() if (record) { return { - response: `您已经有一个预约记录了: + result: `您已经有一个预约记录了: 姓名:${record.name} 时间: ${record.time} -实验室: ${record.labname} +实验室名: ${record.labname} -每人仅能同时预约一个实验室。 +每人仅能同时预约一个实验室名。 ` - }; + } } - await db.collection('LabAppointment').add({ - name, - time, - labname, - status: 'unStart' - }); + await db.collection("LabAppointment").add({ + name, time, labname, status: "unStart" + }) return { - response: `预约成功。 -姓名:${name} -时间: ${time} -实验室: ${labname} - ` - }; + result: `预约成功。 + 姓名:${name} + 时间: ${time} + 实验室名: ${labname} + ` } } ``` diff --git a/docSite/content/docs/workflow/examples/op_question.md b/docSite/content/docs/workflow/examples/op_question.md deleted file mode 100644 index bca96ebba6eb..000000000000 --- a/docSite/content/docs/workflow/examples/op_question.md +++ /dev/null @@ -1,512 +0,0 @@ ---- -title: '优化知识库搜索词' -description: '利用 GPT 优化和完善知识库搜索词,实现上下文关联搜索' -icon: 'search' -draft: false -toc: true -weight: 404 ---- - -![](/imgs/demo_op_question1.png) - -| 优化前 | 优化后 | -| --------------------- | --------------------- | -| ![](/imgs/demo_op_question3.png) | ![](/imgs/demo_op_question2.png) | - -如上图,优化后的搜索可以针对【自动数据预处理】进行搜索,从而找到其相关的内容,一定程度上弥补了向量搜索的上下文缺失问题。 - -## 模块编排 - -复制下面配置,点击「高级编排」右上角的导入按键,导入该配置。 - -{{% details title="编排配置" closed="true" %}} - -```json -[ - { - "moduleId": "userChatInput", - "name": "用户问题(对话入口)", - "flowType": "questionInput", - "position": { - "x": 585.750318069507, - "y": 1597.4127130315183 - }, - "inputs": [ - { - "key": "userChatInput", - "type": "systemInput", - "label": "用户问题", - "connected": true - } - ], - "outputs": [ - { - "key": "userChatInput", - "label": "用户问题", - "type": "source", - "valueType": "string", - "targets": [ - { - "moduleId": "ssdd86", - "key": "content" - } - ] - } - ] - }, - { - "moduleId": "history", - "name": "聊天记录", - "flowType": "historyNode", - "position": { - "x": 567.49877916803, - "y": 1289.3453864378014 - }, - "inputs": [ - { - "key": "maxContext", - "type": "numberInput", - "label": "最长记录数", - "value": 6, - "min": 0, - "max": 50, - "connected": true - }, - { - "key": "history", - "type": "hidden", - "label": "聊天记录", - "connected": true - } - ], - "outputs": [ - { - "key": "history", - "label": "聊天记录", - "valueType": "chatHistory", - "type": "source", - "targets": [ - { - "moduleId": "ssdd86", - "key": "history" - } - ] - } - ] - }, - { - "moduleId": "nkxlso", - "name": "知识库搜索", - "flowType": "datasetSearchNode", - "showStatus": true, - "position": { - "x": 1542.6434554710224, - "y": 1153.7853815737192 - }, - "inputs": [ - { - "key": "kbList", - "type": "custom", - "label": "关联的知识库", - "value": [], - "list": [], - "connected": true - }, - { - "key": "similarity", - "type": "slider", - "label": "相似度", - "value": 0.8, - "min": 0, - "max": 1, - "step": 0.01, - "markList": [ - { - "label": "100", - "value": 100 - }, - { - "label": "1", - "value": 1 - } - ], - "connected": true - }, - { - "key": "limit", - "type": "slider", - "label": "单次搜索上限", - "description": "最多取 n 条记录作为本次问题引用", - "value": 7, - "min": 1, - "max": 20, - "step": 1, - "markList": [ - { - "label": "1", - "value": 1 - }, - { - "label": "20", - "value": 20 - } - ], - "connected": true - }, - { - "key": "switch", - "type": "target", - "label": "触发器", - "valueType": "any", - "connected": false - }, - { - "key": "userChatInput", - "type": "target", - "label": "用户问题", - "required": true, - "valueType": "string", - "connected": true - } - ], - "outputs": [ - { - "key": "isEmpty", - "label": "搜索结果为空", - "type": "source", - "valueType": "boolean", - "targets": [] - }, - { - "key": "unEmpty", - "label": "搜索结果不为空", - "type": "source", - "valueType": "boolean", - "targets": [] - }, - { - "key": "quoteQA", - "label": "引用内容", - "description": "始终返回数组,如果希望搜索结果为空时执行额外操作,需要用到上面的两个输入以及目标模块的触发器", - "type": "source", - "valueType": "datasetQuote", - "targets": [ - { - "moduleId": "ol82hp", - "key": "quoteQA" - } - ] - } - ] - }, - { - "moduleId": "ol82hp", - "name": "AI 对话", - "flowType": "chatNode", - "showStatus": true, - "position": { - "x": 2207.4577044902126, - "y": 1079.6308003796544 - }, - "inputs": [ - { - "key": "model", - "type": "custom", - "label": "对话模型", - "value": "gpt-3.5-turbo", - "list": [], - "connected": true - }, - { - "key": "temperature", - "type": "slider", - "label": "温度", - "value": 0, - "min": 0, - "max": 10, - "step": 1, - "markList": [ - { - "label": "严谨", - "value": 0 - }, - { - "label": "发散", - "value": 10 - } - ], - "connected": true - }, - { - "key": "maxToken", - "type": "custom", - "label": "回复上限", - "value": 2000, - "min": 100, - "max": 4000, - "step": 50, - "markList": [ - { - "label": "100", - "value": 100 - }, - { - "label": "4000", - "value": 4000 - } - ], - "connected": true - }, - { - "key": "systemPrompt", - "type": "textarea", - "label": "系统提示词", - "max": 300, - "valueType": "string", - "description": "模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}", - "placeholder": "模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}", - "value": "我会向你询问三引号引用中提及的内容,你仅使用提供的引用内容来回答我的问题,不要做额外的扩展补充。", - "connected": true - }, - { - "key": "limitPrompt", - "type": "textarea", - "valueType": "string", - "label": "限定词", - "max": 500, - "description": "限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。不建议内容太长,会影响上下文,可使用变量,例如 {{language}}。可在文档中找到对应的限定例子", - "placeholder": "限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。不建议内容太长,会影响上下文,可使用变量,例如 {{language}}。可在文档中找到对应的限定例子", - "value": "", - "connected": true - }, - { - "key": "switch", - "type": "target", - "label": "触发器", - "valueType": "any", - "connected": false - }, - { - "key": "quoteQA", - "type": "target", - "label": "引用内容", - "description": "对象数组格式,结构:\n [{q:'问题',a:'回答'}]", - "valueType": "datasetQuote", - "connected": true - }, - { - "key": "history", - "type": "target", - "label": "聊天记录", - "valueType": "chatHistory", - "connected": true - }, - { - "key": "userChatInput", - "type": "target", - "label": "用户问题", - "required": true, - "valueType": "string", - "connected": true - } - ], - "outputs": [ - { - "key": "answerText", - "label": "AI回复", - "description": "将在 stream 回复完毕后触发", - "valueType": "string", - "type": "source", - "targets": [] - }, - { - "key": "finish", - "label": "回复结束", - "description": "AI 回复完成后触发", - "valueType": "boolean", - "type": "source", - "targets": [] - } - ] - }, - { - "moduleId": "o62kns", - "name": "用户问题(对话入口)", - "flowType": "questionInput", - "position": { - "x": 1696.5940057372968, - "y": 2270.5070479742435 - }, - "inputs": [ - { - "key": "userChatInput", - "type": "systemInput", - "label": "用户问题", - "connected": true - } - ], - "outputs": [ - { - "key": "userChatInput", - "label": "用户问题", - "type": "source", - "valueType": "string", - "targets": [ - { - "moduleId": "ol82hp", - "key": "userChatInput" - } - ] - } - ] - }, - { - "moduleId": "he7013", - "name": "聊天记录", - "flowType": "historyNode", - "position": { - "x": 1636.793907221069, - "y": 1952.7122387165764 - }, - "inputs": [ - { - "key": "maxContext", - "type": "numberInput", - "label": "最长记录数", - "value": 6, - "min": 0, - "max": 50, - "connected": true - }, - { - "key": "history", - "type": "hidden", - "label": "聊天记录", - "connected": true - } - ], - "outputs": [ - { - "key": "history", - "label": "聊天记录", - "valueType": "chatHistory", - "type": "source", - "targets": [ - { - "moduleId": "ol82hp", - "key": "history" - } - ] - } - ] - }, - { - "moduleId": "ssdd86", - "name": "文本内容提取", - "flowType": "contentExtract", - "showStatus": true, - "position": { - "x": 1031.822028231947, - "y": 1231.9793566344022 - }, - "inputs": [ - { - "key": "switch", - "type": "target", - "label": "触发器", - "valueType": "any", - "connected": false - }, - { - "key": "description", - "type": "textarea", - "valueType": "string", - "value": "结合上下文,优化用户的问题,要求不能包含\"它\"、\"第几个\"等代名词,需将他们替换成具体的名词。", - "label": "提取要求描述", - "description": "写一段提取要求,告诉 AI 需要提取哪些内容", - "required": true, - "placeholder": "例如: \n1. 你是一个实验室预约助手。根据用户问题,提取出姓名、实验室号和预约时间", - "connected": true - }, - { - "key": "history", - "type": "target", - "label": "聊天记录", - "valueType": "chatHistory", - "connected": true - }, - { - "key": "content", - "type": "target", - "label": "需要提取的文本", - "required": true, - "valueType": "string", - "connected": true - }, - { - "key": "extractKeys", - "type": "custom", - "label": "目标字段", - "description": "由 '描述' 和 'key' 组成一个目标字段,可提取多个目标字段", - "value": [ - { - "desc": "优化后的问题", - "key": "q", - "required": true - } - ], - "connected": true - } - ], - "outputs": [ - { - "key": "success", - "label": "字段完全提取", - "valueType": "boolean", - "type": "source", - "targets": [] - }, - { - "key": "failed", - "label": "提取字段缺失", - "valueType": "boolean", - "type": "source", - "targets": [] - }, - { - "key": "fields", - "label": "完整提取结果", - "description": "一个 JSON 字符串,例如:{\"name:\":\"YY\",\"Time\":\"2023/7/2 18:00\"}", - "valueType": "string", - "type": "source", - "targets": [] - }, - { - "key": "q", - "label": "提取结果-优化后的问题", - "description": "无法提取时不会返回", - "valueType": "string", - "type": "source", - "targets": [ - { - "moduleId": "nkxlso", - "key": "userChatInput" - } - ] - } - ] - } -] -``` - -{{% /details %}} - -## 流程说明 - -1. 利用内容提取模块,将用户的问题进行优化。 -2. 将优化后的问题传递到知识库搜索模块进行搜索。 -3. 搜索内容传递到 AI 对话模块,进行回答。 - -## Tips - -内容提取模块可以将自然语言提取成结构化数据,可以使用其进行一些神奇的操作。 \ No newline at end of file diff --git a/docSite/content/docs/workflow/modules/_index.md b/docSite/content/docs/workflow/modules/_index.md index c19b3f0fefd9..a5d5551e064e 100644 --- a/docSite/content/docs/workflow/modules/_index.md +++ b/docSite/content/docs/workflow/modules/_index.md @@ -5,4 +5,6 @@ description: "介绍 FastGPT 的常用模块" icon: "apps" draft: false images: [] ---- \ No newline at end of file +--- + + \ No newline at end of file diff --git a/docSite/content/docs/workflow/modules/coreferenceResolution.md b/docSite/content/docs/workflow/modules/coreferenceResolution.md new file mode 100644 index 000000000000..5fe3ae801df8 --- /dev/null +++ b/docSite/content/docs/workflow/modules/coreferenceResolution.md @@ -0,0 +1,39 @@ +--- +title: "问题补全" +description: "问题补全模块介绍和使用" +icon: "input" +draft: false +toc: true +weight: 364 +--- + +## 特点 + +- 可重复添加 +- 有外部输入 +- 触发执行 + +![](/imgs/coreferenceResolution1.png) + +## 背景 + +在 RAG 中,我们需要根据输入的问题去数据库里执行 embedding 搜索,查找相关的内容,从而查找到相似的内容(简称知识库搜索)。 + +在搜索的过程中,尤其是连续对话的搜索,我们通常会发现后续的问题难以搜索到合适的内容,其中一个原因是知识库搜索只会使用“当前”的问题去执行。看下面的例子: + +![](/imgs/coreferenceResolution2.png) + +用户在提问“第二点是什么”的时候,只会去知识库里查找“第二点是什么”,压根查不到内容。实际上需要查询的是“QA结构是什么”。因此我们需要引入一个【问题补全】模块,来对用户当前的问题进行补全,从而使得知识库搜索能够搜索到合适的内容。使用补全后效果如下: + +![](/imgs/coreferenceResolution3.png) + + +## 功能 + +调用 AI 去对用户当前的问题进行补全。目前主要是补全“指代”词,使得检索词更加的完善可靠,从而增强上下文连续对话的知识库搜索能力。 + +遇到最大的难题在于:模型对于【补全】的概念可能不清晰,且对于长上下文往往无法准确的知道应该如何补全。 + +## 示例 + +- [接入谷歌搜索](/docs/workflow/examples/google_search/) \ No newline at end of file diff --git a/docSite/content/docs/workflow/modules/kb_search.md b/docSite/content/docs/workflow/modules/dataset_search.md similarity index 100% rename from docSite/content/docs/workflow/modules/kb_search.md rename to docSite/content/docs/workflow/modules/dataset_search.md diff --git a/docSite/content/docs/workflow/modules/question.md b/docSite/content/docs/workflow/modules/question_classify.md similarity index 100% rename from docSite/content/docs/workflow/modules/question.md rename to docSite/content/docs/workflow/modules/question_classify.md diff --git a/docSite/content/docs/workflow/modules/string.md b/docSite/content/docs/workflow/modules/text_editor.md similarity index 88% rename from docSite/content/docs/workflow/modules/string.md rename to docSite/content/docs/workflow/modules/text_editor.md index 51197756d3e7..4a851245b0d4 100644 --- a/docSite/content/docs/workflow/modules/string.md +++ b/docSite/content/docs/workflow/modules/text_editor.md @@ -26,3 +26,7 @@ weight: 363 ## 作用 给任意模块输入自定格式文本,或处理 AI 模块系统提示词。 + +## 示例 + +- [接入谷歌搜索](/docs/workflow/examples/google_search/) \ No newline at end of file diff --git a/docSite/content/docs/workflow/modules/judgement.md b/docSite/content/docs/workflow/modules/tfswitch.md similarity index 92% rename from docSite/content/docs/workflow/modules/judgement.md rename to docSite/content/docs/workflow/modules/tfswitch.md index 809bf09321d6..ef1e9d97eb91 100644 --- a/docSite/content/docs/workflow/modules/judgement.md +++ b/docSite/content/docs/workflow/modules/tfswitch.md @@ -25,4 +25,5 @@ weight: 362 ## 作用 -适用场景有:让大模型做判断后输出固定内容,根据大模型回复内容判断是否触发后续模块。 \ No newline at end of file +适用场景有:让大模型做判断后输出固定内容,根据大模型回复内容判断是否触发后续模块。 + diff --git a/packages/global/common/string/markdown.ts b/packages/global/common/string/markdown.ts index c29987adea20..4753e0565697 100644 --- a/packages/global/common/string/markdown.ts +++ b/packages/global/common/string/markdown.ts @@ -28,7 +28,7 @@ export const simpleMarkdownText = (rawText: string) => { ['####', '###', '##', '#', '```', '~~~'].forEach((item, i) => { const reg = new RegExp(`\\n\\s*${item}`, 'g'); if (reg.test(rawText)) { - rawText = rawText.replace(new RegExp(`(\\n)\\s*(${item})`, 'g'), '$1$2'); + rawText = rawText.replace(new RegExp(`(\\n)( *)(${item})`, 'g'), '$1$3'); } }); diff --git a/packages/global/core/ai/model.d.ts b/packages/global/core/ai/model.d.ts index 0fb7e90d33bc..2411fddf4afb 100644 --- a/packages/global/core/ai/model.d.ts +++ b/packages/global/core/ai/model.d.ts @@ -14,7 +14,7 @@ export type ChatModelItemType = LLMModelItemType & { }; export type FunctionModelItemType = LLMModelItemType & { - functionCall: boolean; + toolChoice: boolean; functionPrompt: string; }; diff --git a/packages/global/core/ai/model.ts b/packages/global/core/ai/model.ts index d3040cec4eb2..c5bbd5665a32 100644 --- a/packages/global/core/ai/model.ts +++ b/packages/global/core/ai/model.ts @@ -1,63 +1,5 @@ -import type { - LLMModelItemType, - ChatModelItemType, - FunctionModelItemType, - VectorModelItemType, - AudioSpeechModelType, - WhisperModelType, - ReRankModelItemType -} from './model.d'; +import type { LLMModelItemType, VectorModelItemType } from './model.d'; -export const defaultChatModels: ChatModelItemType[] = [ - { - model: 'gpt-3.5-turbo-1106', - name: 'GPT35-1106', - price: 0, - maxContext: 16000, - maxResponse: 4000, - quoteMaxToken: 2000, - maxTemperature: 1.2, - censor: false, - vision: false, - defaultSystemChatPrompt: '' - }, - { - model: 'gpt-3.5-turbo-16k', - name: 'GPT35-16k', - maxContext: 16000, - maxResponse: 16000, - price: 0, - quoteMaxToken: 8000, - maxTemperature: 1.2, - censor: false, - vision: false, - defaultSystemChatPrompt: '' - }, - { - model: 'gpt-4', - name: 'GPT4-8k', - maxContext: 8000, - maxResponse: 8000, - price: 0, - 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: '' - } -]; export const defaultQAModels: LLMModelItemType[] = [ { model: 'gpt-3.5-turbo-16k', @@ -67,46 +9,6 @@ export const defaultQAModels: LLMModelItemType[] = [ price: 0 } ]; -export const defaultCQModels: FunctionModelItemType[] = [ - { - model: 'gpt-3.5-turbo-1106', - name: 'GPT35-1106', - maxContext: 16000, - maxResponse: 4000, - price: 0, - functionCall: true, - functionPrompt: '' - }, - { - model: 'gpt-4', - name: 'GPT4-8k', - maxContext: 8000, - maxResponse: 8000, - price: 0, - functionCall: true, - functionPrompt: '' - } -]; -export const defaultExtractModels: FunctionModelItemType[] = [ - { - model: 'gpt-3.5-turbo-1106', - name: 'GPT35-1106', - maxContext: 16000, - maxResponse: 4000, - price: 0, - functionCall: true, - functionPrompt: '' - } -]; -export const defaultQGModels: LLMModelItemType[] = [ - { - model: 'gpt-3.5-turbo-1106', - name: 'GPT35-1106', - maxContext: 1600, - maxResponse: 4000, - price: 0 - } -]; export const defaultVectorModels: VectorModelItemType[] = [ { @@ -117,27 +19,3 @@ export const defaultVectorModels: VectorModelItemType[] = [ maxToken: 3000 } ]; - -export const defaultReRankModels: ReRankModelItemType[] = []; - -export const defaultAudioSpeechModels: AudioSpeechModelType[] = [ - { - model: 'tts-1', - name: 'OpenAI TTS1', - 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 -}; diff --git a/packages/global/core/app/type.d.ts b/packages/global/core/app/type.d.ts index 9851b4703514..da64ed9b78bd 100644 --- a/packages/global/core/app/type.d.ts +++ b/packages/global/core/app/type.d.ts @@ -67,6 +67,9 @@ export type AppSimpleEditFormType = { searchMode: `${DatasetSearchModeEnum}`; searchEmptyText: string; }; + cfr: { + background: string; + }; userGuide: { welcomeText: string; variables: { @@ -111,6 +114,9 @@ export type AppSimpleEditConfigTemplateType = { searchMode: `${DatasetSearchModeEnum}`; searchEmptyText?: boolean; }; + cfr?: { + background?: boolean; + }; userGuide?: { welcomeText?: boolean; variables?: boolean; diff --git a/packages/global/core/app/utils.ts b/packages/global/core/app/utils.ts index 1ab676443b1f..e65511e58df9 100644 --- a/packages/global/core/app/utils.ts +++ b/packages/global/core/app/utils.ts @@ -3,23 +3,23 @@ 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, + model: 'gpt-3.5-turbo', systemPrompt: '', temperature: 0, isResponseAnswerText: true, quotePrompt: '', quoteTemplate: '', - maxToken: defaultChatModel ? defaultChatModel.maxResponse / 2 : 4000 + maxToken: 4000 + }, + cfr: { + background: '' }, dataset: { datasets: [], @@ -116,6 +116,11 @@ export const appModules2Form = ({ questionGuide: questionGuide, tts: ttsConfig }; + } else if (module.flowType === FlowNodeTypeEnum.cfr) { + const value = module.inputs.find((item) => item.key === ModuleInputKeyEnum.aiSystemPrompt); + if (value) { + defaultAppForm.cfr.background = value.value; + } } }); diff --git a/packages/global/core/chat/type.d.ts b/packages/global/core/chat/type.d.ts index 719bf5636a6f..f29271d29819 100644 --- a/packages/global/core/chat/type.d.ts +++ b/packages/global/core/chat/type.d.ts @@ -93,6 +93,7 @@ export type moduleDispatchResType = { model?: string; query?: string; contextTotalLen?: number; + textOutput?: string; // chat temperature?: number; @@ -119,9 +120,7 @@ export type moduleDispatchResType = { // plugin output pluginOutput?: Record; - - // text editor - textOutput?: string; + pluginDetail?: ChatHistoryItemResType[]; // tf switch tfSwitchResult?: boolean; diff --git a/packages/global/core/module/node/constant.ts b/packages/global/core/module/node/constant.ts index c23ab3043a92..65a017710a13 100644 --- a/packages/global/core/module/node/constant.ts +++ b/packages/global/core/module/node/constant.ts @@ -38,7 +38,6 @@ export enum FlowNodeOutputTypeEnum { } export enum FlowNodeTypeEnum { - empty = 'empty', userGuide = 'userGuide', questionInput = 'questionInput', historyNode = 'historyNode', @@ -52,10 +51,10 @@ export enum FlowNodeTypeEnum { pluginModule = 'pluginModule', pluginInput = 'pluginInput', pluginOutput = 'pluginOutput', - textEditor = 'textEditor', + cfr = 'cfr', // abandon variable = 'variable' } -export const EDGE_TYPE = 'smoothstep'; +export const EDGE_TYPE = 'default'; diff --git a/packages/global/core/module/template/system/aiChat.ts b/packages/global/core/module/template/system/aiChat.ts index b667b274be8a..24a304b1c4a8 100644 --- a/packages/global/core/module/template/system/aiChat.ts +++ b/packages/global/core/module/template/system/aiChat.ts @@ -141,7 +141,7 @@ export const AiChatModule: FlowModuleTemplateType = { }, { key: ModuleOutputKeyEnum.answerText, - label: 'AI回复', + label: 'AI回复内容', description: '将在 stream 回复完毕后触发', valueType: ModuleIOValueTypeEnum.string, type: FlowNodeOutputTypeEnum.source, diff --git a/packages/global/core/module/template/system/coreferenceResolution.ts b/packages/global/core/module/template/system/coreferenceResolution.ts new file mode 100644 index 000000000000..aed23d475483 --- /dev/null +++ b/packages/global/core/module/template/system/coreferenceResolution.ts @@ -0,0 +1,61 @@ +import { + FlowNodeInputTypeEnum, + FlowNodeOutputTypeEnum, + FlowNodeTypeEnum +} from '../../node/constant'; +import { FlowModuleTemplateType } from '../../type.d'; +import { + ModuleIOValueTypeEnum, + ModuleInputKeyEnum, + ModuleOutputKeyEnum, + ModuleTemplateTypeEnum +} from '../../constants'; +import { + Input_Template_History, + Input_Template_Switch, + Input_Template_UserChatInput +} from '../input'; + +export const AiCFR: FlowModuleTemplateType = { + id: FlowNodeTypeEnum.chatNode, + templateType: ModuleTemplateTypeEnum.tools, + flowType: FlowNodeTypeEnum.cfr, + avatar: '/imgs/module/cfr.svg', + name: 'core.module.template.cfr', + intro: 'core.module.template.cfr intro', + showStatus: true, + inputs: [ + Input_Template_Switch, + { + key: ModuleInputKeyEnum.aiModel, + type: FlowNodeInputTypeEnum.selectExtractModel, + label: 'core.module.input.label.aiModel', + required: true, + valueType: ModuleIOValueTypeEnum.string, + showTargetInApp: false, + showTargetInPlugin: false + }, + { + key: ModuleInputKeyEnum.aiSystemPrompt, + type: FlowNodeInputTypeEnum.textarea, + label: 'core.module.input.label.cfr background', + max: 300, + valueType: ModuleIOValueTypeEnum.string, + description: 'core.module.input.description.cfr background', + placeholder: 'core.module.input.placeholder.cfr background', + showTargetInApp: true, + showTargetInPlugin: true + }, + Input_Template_History, + Input_Template_UserChatInput + ], + outputs: [ + { + key: ModuleOutputKeyEnum.text, + label: 'core.module.output.label.cfr result', + valueType: ModuleIOValueTypeEnum.string, + type: FlowNodeOutputTypeEnum.source, + targets: [] + } + ] +}; diff --git a/packages/global/core/module/template/system/empty.ts b/packages/global/core/module/template/system/empty.ts deleted file mode 100644 index 5f3089eb75f7..000000000000 --- a/packages/global/core/module/template/system/empty.ts +++ /dev/null @@ -1,14 +0,0 @@ -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: [] -}; diff --git a/packages/global/core/module/type.d.ts b/packages/global/core/module/type.d.ts index 8690b5c49c53..6728bbecbec8 100644 --- a/packages/global/core/module/type.d.ts +++ b/packages/global/core/module/type.d.ts @@ -38,7 +38,7 @@ export type ModuleItemType = { outputs: FlowNodeOutputItemType[]; }; -/* function type */ +/* --------------- function type -------------------- */ // variable export type VariableItemType = { id: string; @@ -74,3 +74,46 @@ export type ContextExtractAgentItemType = { required: boolean; enum?: string; }; + +/* -------------- running module -------------- */ +export type RunningModuleItemType = { + name: ModuleItemType['name']; + moduleId: ModuleItemType['moduleId']; + flowType: ModuleItemType['flowType']; + showStatus?: ModuleItemType['showStatus']; +} & { + inputs: { + key: string; + value?: any; + }[]; + outputs: { + key: string; + answer?: boolean; + response?: boolean; + value?: any; + targets: { + moduleId: string; + key: string; + }[]; + }[]; +}; + +export type ChatDispatchProps = { + res: NextApiResponse; + mode: 'test' | 'chat'; + teamId: string; + tmbId: string; + user: UserType; + appId: string; + chatId?: string; + responseChatItemId?: string; + histories: ChatItemType[]; + variables: Record; + stream: boolean; + detail: boolean; // response detail +}; + +export type ModuleDispatchProps = ChatDispatchProps & { + outputs: RunningModuleItemType['outputs']; + inputs: T; +}; diff --git a/packages/global/core/plugin/type.d.ts b/packages/global/core/plugin/type.d.ts index ebc14cae8f6e..bec98a79988c 100644 --- a/packages/global/core/plugin/type.d.ts +++ b/packages/global/core/plugin/type.d.ts @@ -15,14 +15,19 @@ export type PluginItemSchema = { }; /* plugin template */ -export type PluginTemplateType = { +export type PluginTemplateType = PluginRuntimeType & { author?: string; id: string; source: `${PluginSourceEnum}`; templateType: FlowModuleTemplateType['templateType']; + intro: string; + modules: ModuleItemType[]; +}; + +export type PluginRuntimeType = { + teamId?: string; name: string; avatar: string; - intro: string; showStatus?: boolean; modules: ModuleItemType[]; }; diff --git a/packages/service/common/mongo/init.ts b/packages/service/common/mongo/init.ts index ea653397b75b..f6d2b6c824c1 100644 --- a/packages/service/common/mongo/init.ts +++ b/packages/service/common/mongo/init.ts @@ -20,11 +20,12 @@ export async function connectMongo({ console.log('mongo start connect'); try { mongoose.set('strictQuery', true); + const maxConnecting = Math.max(20, Number(process.env.DB_MAX_LINK || 20)); await mongoose.connect(process.env.MONGODB_URI as string, { bufferCommands: true, - maxConnecting: Number(process.env.DB_MAX_LINK || 5), - maxPoolSize: Number(process.env.DB_MAX_LINK || 5), - minPoolSize: Math.min(10, Number(process.env.DB_MAX_LINK || 10)), + maxConnecting: maxConnecting, + maxPoolSize: maxConnecting, + minPoolSize: Math.max(5, Math.round(Number(process.env.DB_MAX_LINK || 5) * 0.1)), connectTimeoutMS: 60000, waitQueueTimeoutMS: 60000, socketTimeoutMS: 60000, diff --git a/packages/service/core/ai/audio/speech.ts b/packages/service/core/ai/audio/speech.ts index ee79fe2a87a5..fe805d1513a6 100644 --- a/packages/service/core/ai/audio/speech.ts +++ b/packages/service/core/ai/audio/speech.ts @@ -1,14 +1,12 @@ import type { NextApiResponse } from 'next'; import { getAIApi } from '../config'; -import { defaultAudioSpeechModels } from '../../../../global/core/ai/model'; -import { UserModelSchema } from '@fastgpt/global/support/user/type'; export async function text2Speech({ res, onSuccess, onError, input, - model = defaultAudioSpeechModels[0].model, + model, voice, speed = 1 }: { diff --git a/packages/service/core/ai/functions/queryExtension.ts b/packages/service/core/ai/functions/queryExtension.ts new file mode 100644 index 000000000000..771c76e0df31 --- /dev/null +++ b/packages/service/core/ai/functions/queryExtension.ts @@ -0,0 +1,59 @@ +import { replaceVariable } from '@fastgpt/global/common/string/tools'; +import { getAIApi } from '../config'; + +const prompt = ` +您的任务是生成根据用户问题,从不同角度,生成两个不同版本的问题,以便可以从矢量数据库检索相关文档。例如: +问题: FastGPT如何使用? +OUTPUT: ["FastGPT使用教程。","怎么使用FastGPT?"] +------------------- +问题: FastGPT如何收费? +OUTPUT: ["FastGPT收费标准。","FastGPT是如何计费的?"] +------------------- +问题: 怎么FastGPT部署? +OUTPUT: ["FastGPT的部署方式。","如何部署FastGPT?"] +------------------- +问题 question: {{q}} +OUTPUT: +`; + +export const searchQueryExtension = async ({ query, model }: { query: string; model: string }) => { + const ai = getAIApi(undefined, 480000); + + const result = await ai.chat.completions.create({ + model, + temperature: 0, + messages: [ + { + role: 'user', + content: replaceVariable(prompt, { q: query }) + } + ], + stream: false + }); + + const answer = result.choices?.[0]?.message?.content || ''; + if (!answer) { + return { + queries: [query], + model, + inputTokens: 0, + responseTokens: 0 + }; + } + + try { + return { + queries: JSON.parse(answer) as string[], + model, + inputTokens: result.usage?.prompt_tokens || 0, + responseTokens: result.usage?.completion_tokens || 0 + }; + } catch (error) { + return { + queries: [query], + model, + inputTokens: 0, + responseTokens: 0 + }; + } +}; diff --git a/packages/service/core/plugin/controller.ts b/packages/service/core/plugin/controller.ts index 60a4e72ff865..18e59fa8c4e8 100644 --- a/packages/service/core/plugin/controller.ts +++ b/packages/service/core/plugin/controller.ts @@ -3,7 +3,7 @@ import { FlowModuleTemplateType } from '@fastgpt/global/core/module/type'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant'; import { plugin2ModuleIO } from '@fastgpt/global/core/module/utils'; import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants'; -import type { PluginTemplateType } from '@fastgpt/global/core/plugin/type.d'; +import type { PluginRuntimeType, PluginTemplateType } from '@fastgpt/global/core/plugin/type.d'; import { ModuleTemplateTypeEnum } from '@fastgpt/global/core/module/constants'; /* @@ -41,6 +41,7 @@ const getPluginTemplateById = async (id: string): Promise => if (!item) return Promise.reject('plugin not found'); return { id: String(item._id), + teamId: String(item.teamId), name: item.name, avatar: item.avatar, intro: item.intro, @@ -74,16 +75,14 @@ export async function getPluginPreviewModule({ } /* run plugin time */ -export async function getPluginRuntimeById(id: string): Promise { +export async function getPluginRuntimeById(id: string): Promise { const plugin = await getPluginTemplateById(id); return { - id: plugin.id, - source: plugin.source, - templateType: plugin.templateType, + teamId: plugin.teamId, name: plugin.name, avatar: plugin.avatar, - intro: plugin.intro, + showStatus: plugin.showStatus, modules: plugin.modules }; } diff --git a/packages/web/common/string/markdown.ts b/packages/web/common/string/markdown.ts new file mode 100644 index 000000000000..03a97cae03d6 --- /dev/null +++ b/packages/web/common/string/markdown.ts @@ -0,0 +1,35 @@ +import TurndownService from 'turndown'; +// @ts-ignore +import * as turndownPluginGfm from 'joplin-turndown-plugin-gfm'; + +const turndownService = new TurndownService({ + headingStyle: 'atx', + bulletListMarker: '-', + codeBlockStyle: 'fenced', + fence: '```', + emDelimiter: '_', + strongDelimiter: '**', + linkStyle: 'inlined', + linkReferenceStyle: 'full' +}); +export const htmlStr2Md = (html: string) => { + // 浏览器,html字符串转dom + const parser = new DOMParser(); + const dom = parser.parseFromString(html, 'text/html'); + + turndownService.remove(['i', 'script', 'iframe']); + turndownService.addRule('codeBlock', { + filter: 'pre', + replacement(_, node) { + const content = node.textContent?.trim() || ''; + // @ts-ignore + const codeName = node?._attrsByQName?.class?.data?.trim() || ''; + + return `\n\`\`\`${codeName}\n${content}\n\`\`\`\n`; + } + }); + + turndownService.use(turndownPluginGfm.gfm); + + return turndownService.turndown(dom); +}; diff --git a/packages/web/package.json b/packages/web/package.json index fda00320fb49..7e5515a70dfb 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,6 +1,11 @@ { "name": "@fastgpt/web", "version": "1.0.0", - "dependencies": {}, - "devDependencies": {} + "dependencies": { + "joplin-turndown-plugin-gfm": "^1.0.12", + "turndown": "^7.1.2" + }, + "devDependencies": { + "@types/turndown": "^5.0.4" + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c85d34ac7698..942dd6e2180e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -128,7 +128,18 @@ importers: specifier: ^0.0.4 version: registry.npmmirror.com/@types/tunnel@0.0.4 - packages/web: {} + packages/web: + dependencies: + joplin-turndown-plugin-gfm: + specifier: ^1.0.12 + version: registry.npmmirror.com/joplin-turndown-plugin-gfm@1.0.12 + turndown: + specifier: ^7.1.2 + version: registry.npmmirror.com/turndown@7.1.2 + devDependencies: + '@types/turndown': + specifier: ^5.0.4 + version: registry.npmmirror.com/@types/turndown@5.0.4 projects/app: dependencies: @@ -4850,6 +4861,12 @@ packages: '@types/node': registry.npmmirror.com/@types/node@20.8.5 dev: true + registry.npmmirror.com/@types/turndown@5.0.4: + resolution: {integrity: sha512-28GI33lCCkU4SGH1GvjDhFgOVr+Tym4PXGBIU1buJUa6xQolniPArtUT+kv42RR2N9MsMLInkr904Aq+ESHBJg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/turndown/-/turndown-5.0.4.tgz} + name: '@types/turndown' + version: 5.0.4 + dev: true + registry.npmmirror.com/@types/unist@2.0.10: resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/unist/-/unist-2.0.10.tgz} name: '@types/unist' @@ -6756,6 +6773,12 @@ packages: domelementtype: registry.npmmirror.com/domelementtype@2.3.0 dev: false + registry.npmmirror.com/domino@2.1.6: + resolution: {integrity: sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/domino/-/domino-2.1.6.tgz} + name: domino + version: 2.1.6 + dev: false + registry.npmmirror.com/dompurify@3.0.3: resolution: {integrity: sha512-axQ9zieHLnAnHh0sfAamKYiqXMJAVwu+LM/alQ7WDagoWessyWvMSFyW65CqF3owufNu8HBcE4cM2Vflu7YWcQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/dompurify/-/dompurify-3.0.3.tgz} name: dompurify @@ -8772,6 +8795,12 @@ packages: set-function-name: registry.npmmirror.com/set-function-name@2.0.1 dev: true + registry.npmmirror.com/joplin-turndown-plugin-gfm@1.0.12: + resolution: {integrity: sha512-qL4+1iycQjZ1fs8zk3jSRk7cg3ROBUHk7GKtiLAQLFzLPKErnILUvz5DLszSQvz3s1sTjPbywLDISVUtBY6HaA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/joplin-turndown-plugin-gfm/-/joplin-turndown-plugin-gfm-1.0.12.tgz} + name: joplin-turndown-plugin-gfm + version: 1.0.12 + dev: false + registry.npmmirror.com/js-sdsl@4.4.2: resolution: {integrity: sha512-dwXFwByc/ajSV6m5bcKAPwe4yDDF6D614pxmIi5odytzxRlwqF6nwoiCek80Ixc7Cvma5awClxrzFtxCQvcM8w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/js-sdsl/-/js-sdsl-4.4.2.tgz} name: js-sdsl @@ -12166,6 +12195,14 @@ packages: engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} dev: false + registry.npmmirror.com/turndown@7.1.2: + resolution: {integrity: sha512-ntI9R7fcUKjqBP6QU8rBK2Ehyt8LAzt3UBT9JR9tgo6GtuKvyUzpayWmeMKJw1DPdXzktvtIT8m2mVXz+bL/Qg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/turndown/-/turndown-7.1.2.tgz} + name: turndown + version: 7.1.2 + dependencies: + domino: registry.npmmirror.com/domino@2.1.6 + dev: false + registry.npmmirror.com/type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz} name: type-check diff --git a/projects/app/data/config.json b/projects/app/data/config.json index 0162a314b8c9..d5a0032940a4 100644 --- a/projects/app/data/config.json +++ b/projects/app/data/config.json @@ -7,10 +7,10 @@ }, "ChatModels": [ { - "model": "gpt-3.5-turbo-1106", - "name": "GPT35-1106", + "model": "gpt-3.5-turbo", + "name": "GPT35", "price": 0, - "maxContext": 16000, + "maxContext": 4000, "maxResponse": 4000, "quoteMaxToken": 2000, "maxTemperature": 1.2, @@ -71,7 +71,7 @@ "maxContext": 4000, "maxResponse": 4000, "price": 0, - "functionCall": true, + "toolChoice": true, "functionPrompt": "" }, { @@ -80,7 +80,7 @@ "maxContext": 8000, "maxResponse": 8000, "price": 0, - "functionCall": true, + "toolChoice": true, "functionPrompt": "" } ], @@ -91,7 +91,7 @@ "maxContext": 16000, "maxResponse": 4000, "price": 0, - "functionCall": true, + "toolChoice": true, "functionPrompt": "" } ], diff --git a/projects/app/public/pluginTemplates/README.md b/projects/app/data/pluginTemplates/README.md similarity index 100% rename from projects/app/public/pluginTemplates/README.md rename to projects/app/data/pluginTemplates/README.md diff --git a/projects/app/public/pluginTemplates/customFeedback.json b/projects/app/data/pluginTemplates/customFeedback.json similarity index 100% rename from projects/app/public/pluginTemplates/customFeedback.json rename to projects/app/data/pluginTemplates/customFeedback.json diff --git a/projects/app/public/pluginTemplates/textEditor.json b/projects/app/data/pluginTemplates/textEditor.json similarity index 100% rename from projects/app/public/pluginTemplates/textEditor.json rename to projects/app/data/pluginTemplates/textEditor.json diff --git a/projects/app/public/pluginTemplates/tfSwitch.json b/projects/app/data/pluginTemplates/tfSwitch.json similarity index 100% rename from projects/app/public/pluginTemplates/tfSwitch.json rename to projects/app/data/pluginTemplates/tfSwitch.json diff --git a/projects/app/public/simpleTemplates/fastgpt-simple.json b/projects/app/data/simpleTemplates/fastgpt-simple.json similarity index 92% rename from projects/app/public/simpleTemplates/fastgpt-simple.json rename to projects/app/data/simpleTemplates/fastgpt-simple.json index bdc3fd09c375..3acb6ad2f0a6 100644 --- a/projects/app/public/simpleTemplates/fastgpt-simple.json +++ b/projects/app/data/simpleTemplates/fastgpt-simple.json @@ -10,6 +10,9 @@ "quoteTemplate": false, "quotePrompt": false }, + "cfr": { + "background": true + }, "dataset": { "datasets": true, "similarity": false, diff --git a/projects/app/public/docs/versionIntro.md b/projects/app/public/docs/versionIntro.md index 1fd0a8037cfd..201481aad4a6 100644 --- a/projects/app/public/docs/versionIntro.md +++ b/projects/app/public/docs/versionIntro.md @@ -1,13 +1,14 @@ -### Fast GPT V4.6.4 +### Fast GPT V4.6.5 -1. 重写 - 分享链接身份逻辑,采用 localID 记录用户的ID。 -2. 商业版新增 - 分享链接 SSO 方案,通过`身份鉴权`地址,仅需`3个接口`即可完全接入已有用户系统。具体参考[分享链接身份鉴权](https://doc.fastgpt.in/docs/development/openapi/share/) -3. 新增 - 分享链接更多嵌入方式提示,更多DIY方式。 -4. 优化 - 历史记录模块。弃用旧的历史记录模块,直接在对应地方填写数值即可。 -5. 调整 - 知识库搜索模块 topk 逻辑,采用 MaxToken 计算,兼容不同长度的文本块 -6. 链接读取支持多选择器。参考[Web 站点同步用法](https://doc.fastgpt.in/docs/course/websync) -7. [知识库结构详解](https://doc.fastgpt.in/docs/use-cases/datasetengine/) -8. [知识库提示词详解](https://doc.fastgpt.in/docs/use-cases/ai_settings/#引用模板--引用提示词) -9. [使用文档](https://doc.fastgpt.in/docs/intro/) -10. [点击查看高级编排介绍文档](https://doc.fastgpt.in/docs/workflow) -11. [点击查看商业版](https://doc.fastgpt.in/docs/commercial/) +1. 新增 - [问题补全模块](https://doc.fastgpt.in/docs/workflow/modules/coreferenceresolution/) +2. 新增 - [文本编辑模块](https://doc.fastgpt.in/docs/workflow/modules/text_editor/) +3. 新增 - [判断器模块](https://doc.fastgpt.in/docs/workflow/modules/tfswitch/) +4. 新增 - [自定义反馈模块](https://doc.fastgpt.in/docs/workflow/modules/custom_feedback/) +5. 新增 - 【内容提取】模块支持选择模型,以及字段枚举 +6. 优化 - docx读取,兼容表格(表格转markdown) +7. 优化 - 高级编排连接线交互 +8. 优化 - 由于 html2md 导致的 cpu密集计算,阻断线程问题 +9. 修复 - 高级编排提示词提取描述 +10. [使用文档](https://doc.fastgpt.in/docs/intro/) +11. [点击查看高级编排介绍文档](https://doc.fastgpt.in/docs/workflow) +12. [点击查看商业版](https://doc.fastgpt.in/docs/commercial/) diff --git a/projects/app/public/imgs/module/cfr.svg b/projects/app/public/imgs/module/cfr.svg new file mode 100644 index 000000000000..da46b4de6b11 --- /dev/null +++ b/projects/app/public/imgs/module/cfr.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/projects/app/public/locales/en/common.json b/projects/app/public/locales/en/common.json index 56851fc1c028..36bbf2a8be39 100644 --- a/projects/app/public/locales/en/common.json +++ b/projects/app/public/locales/en/common.json @@ -245,7 +245,9 @@ "Out Ad Edit": "You are about to exit the Advanced orchestration page, please confirm", "Prompt Editor": "Prompt Editor", "Save and out": "Save out", - "UnSave": "UnSave" + "UnSave": "UnSave", + "cfr background prompt": "Question completion - Chat background description", + "cfr background tip": "Describing the scope of the current conversation makes it easier for AI to complete first or vague questions, thereby enhancing the knowledge base's ability to continue conversations. \nIf is empty, the problem completion function is not used." }, "feedback": { "Custom feedback": "Custom feedback", @@ -295,7 +297,10 @@ "Stop Speak": "Stop Speak", "Type a message": "Input problem", "error": { - "Messages empty": "Interface content is empty, maybe the text is too long ~" + "Messages empty": "Interface content is empty, maybe the text is too long ~", + "Select dataset empty": "You didn't choose any dataset.", + "user input empty": "User question is empty", + "Chat error": "Chat error" }, "feedback": { "Close User Good Feedback": "", @@ -315,6 +320,7 @@ "Read Source": "Read Source" }, "response": { + "Plugin Resonse Detail": "Plugin Detail", "context total length": "Context Length", "module cq": "Question classification list", "module cq result": "Classification Result", @@ -541,6 +547,7 @@ "Http Request Url": "", "TFSwitch textarea": "", "anyInput": "", + "cfr background": "The background knowledge of the current conversation makes it easy to complete the first question and the fuzzy question, and only needs to briefly describe the scope of the current conversation.", "dynamic input": "", "textEditor textarea": "The passed variable can be referenced by {{key}}." }, @@ -549,11 +556,16 @@ "Http Request Method": "", "Http Request Url": "", "TFSwitch textarea": "", + "aiModel": "", "anyInput": "", + "cfr background": "Background", "chat history": "chat history", "switch": "Switch", "textEditor textarea": "Text Edit", "user question": "User question" + }, + "placeholder": { + "cfr background": "Questions about the introduction and use of python. \nThe current dialogue is related to the game GTA5." } }, "inputType": { @@ -574,6 +586,7 @@ "running done": "Triggered when the module call ends" }, "label": { + "cfr result": "", "result false": "", "result true": "", "running done": "End of module call ", @@ -584,6 +597,8 @@ "TFSwitch": "", "TFSwitch intro": "", "UnKnow Module": "UnKnow Module", + "cfr": "", + "cfr intro": "Refine the current issue based on history, making it more conducive to knowledge base search, while improving continuous conversation capabilities.", "textEditor": "Text Editor", "textEditor intro": "Output of fixed or incoming text after edit" }, diff --git a/projects/app/public/locales/zh/common.json b/projects/app/public/locales/zh/common.json index f8fbb336b25a..d09d0987e792 100644 --- a/projects/app/public/locales/zh/common.json +++ b/projects/app/public/locales/zh/common.json @@ -239,13 +239,17 @@ "TTS": "语音播报", "TTS Tip": "开启后,每次对话后可使用语音播放功能。使用该功能可能产生额外费用。", "Welcome Text": "对话开场白", + "Chat Variable": "对话框变量", + "Question Guide": "问题引导", "create app": "创建属于你的 AI 应用", "edit": { "Confirm Save App Tip": "该应用可能为高级编排模式,保存后将会覆盖高级编排配置,请确认!", "Out Ad Edit": "您即将退出高级编排页面,请确认", "Prompt Editor": "提示词编辑", "Save and out": "保存并退出", - "UnSave": "不保存" + "UnSave": "不保存", + "cfr background prompt": "问题补全 - 对话背景描述", + "cfr background tip": "描述当前对话的范围,便于AI补全首次问题或模糊的问题,从而增强知识库连续对话的能力。\n为空时,表示不使用问题补全功能。" }, "feedback": { "Custom feedback": "自定义反馈", @@ -295,7 +299,10 @@ "Stop Speak": "停止录音", "Type a message": "输入问题", "error": { - "Messages empty": "接口内容为空,可能文本超长了~" + "Messages empty": "接口内容为空,可能文本超长了~", + "Select dataset empty": "你没有选择知识库", + "user input empty": "传入的用户问题为空", + "Chat error": "对话出现异常" }, "feedback": { "Close User Good Feedback": "", @@ -315,6 +322,7 @@ "Read Source": "查看来源" }, "response": { + "Plugin Resonse Detail": "插件详情", "context total length": "上下文总长度", "module cq": "问题分类列表", "module cq result": "分类结果", @@ -479,7 +487,7 @@ "embeddingReRank": "增强语义检索", "embeddingReRank desc": "超额进行向量 topk 查询后再使用 Rerank 进行排序,相关度通常差异明显。" }, - "search mode": "检索模式" + "search mode": "搜索模式" }, "status": { "active": "已就绪", @@ -541,6 +549,7 @@ "Http Request Url": "新的HTTP请求地址。如果出现两个“请求地址”,可以删除该模块重新加入,会拉取最新的模块配置。", "TFSwitch textarea": "允许定义一些字符串来实现 false 匹配,每行一个,支持正则表达式。", "anyInput": "可传入任意内容", + "cfr background": "描述当前对话的范围,便于AI补全首次问题或模糊的问题,从而增强知识库连续对话的能力。\n为空时,表示【首次对话】不使用问题补全功能。", "dynamic input": "接收用户动态添加的参数,会在运行时将这些参数平铺传入", "textEditor textarea": "可以通过 {{key}} 的方式引用传入的变量。变量仅支持字符串或数字。" }, @@ -549,11 +558,16 @@ "Http Request Method": "请求方式", "Http Request Url": "请求地址", "TFSwitch textarea": "自定义 False 匹配规则", + "aiModel": "AI 模型", "anyInput": "任意内容输入", + "cfr background": "背景知识", "chat history": "聊天记录", "switch": "触发器", "textEditor textarea": "文本编辑", "user question": "用户问题" + }, + "placeholder": { + "cfr background": "关于 python 的介绍和使用等问题。\n当前对话与游戏《GTA5》有关。" } }, "inputType": { @@ -574,6 +588,7 @@ "running done": "模块调用结束时触发" }, "label": { + "cfr result": "补全结果", "result false": "False", "result true": "True", "running done": "模块调用结束", @@ -584,6 +599,8 @@ "TFSwitch": "判断器", "TFSwitch intro": "根据传入的内容进行 True False 输出。默认情况下,当传入的内容为 false, undefined, null, 0, none 时,会输出 false。你也可以增加一些自定义的字符串来补充输出 false 的内容。", "UnKnow Module": "未知模块", + "cfr": "问题补全", + "cfr intro": "根据历史记录,完善当前问题,使其更利于知识库搜索,同时提高连续对话能力。", "textEditor": "文本加工", "textEditor intro": "可对固定或传入的文本进行加工后输出" }, diff --git a/projects/app/src/components/ChatBox/QuoteModal.tsx b/projects/app/src/components/ChatBox/QuoteModal.tsx index 41d9e2f61151..8583f56bf7e4 100644 --- a/projects/app/src/components/ChatBox/QuoteModal.tsx +++ b/projects/app/src/components/ChatBox/QuoteModal.tsx @@ -26,6 +26,43 @@ const QuoteModal = ({ isShare: boolean; }) => { const { t } = useTranslation(); + + return ( + <> + + {t('core.chat.Quote Amount', { amount: rawSearch.length })} + + {t('core.chat.quote.Quote Tip')} + + + } + > + + + + + + ); +}; + +export default QuoteModal; + +export const QuoteList = React.memo(function QuoteList({ + rawSearch = [], + isShare +}: { + rawSearch: SearchDataResponseItemType[]; + isShare: boolean; +}) { + const { t } = useTranslation(); const { isPc } = useSystemStore(); const theme = useTheme(); const { toast } = useToast(); @@ -60,124 +97,104 @@ const QuoteModal = ({ return ( <> - - {t('core.chat.Quote Amount', { amount: rawSearch.length })} - - {t('core.chat.quote.Quote Tip')} - - - } - > - - {rawSearch.map((item, i) => ( - - - - - {!isShare && ( - - {t('core.dataset.Go Dataset')} - - - )} - + {rawSearch.map((item, i) => ( + + + + + {!isShare && ( + + {t('core.dataset.Go Dataset')} + + + )} + - {item.q} - {item.a} - {!isShare && ( - - {isPc && ( - - - # {item.id} - - - )} - - - - {item.q.length + (item.a?.length || 0)} - - - {!isShare && item.score && ( - - - - - {item.score.toFixed(4)} - - - )} - - {item.id && ( - - - onclickEdit(item)} - /> - - - )} + {item.q} + {item.a} + {!isShare && ( + + {isPc && ( + + + # {item.id} + + + )} + + + + {item.q.length + (item.a?.length || 0)} + + {!isShare && item.score && ( + + + + + {item.score.toFixed(4)} + + )} - - ))} - - - + + {item.id && ( + + + onclickEdit(item)} + /> + + + )} + + )} + + ))} {editInputData && editInputData.id && ( setEditInputData(undefined)} @@ -191,8 +208,7 @@ const QuoteModal = ({ collectionId={editInputData.collectionId} /> )} + ); -}; - -export default QuoteModal; +}); diff --git a/projects/app/src/components/ChatBox/ResponseTags.tsx b/projects/app/src/components/ChatBox/ResponseTags.tsx index f5b3eb8e3f1e..087e075ce344 100644 --- a/projects/app/src/components/ChatBox/ResponseTags.tsx +++ b/projects/app/src/components/ChatBox/ResponseTags.tsx @@ -222,7 +222,11 @@ const ResponseTags = ({ setContextModalData(undefined)} /> )} {isOpenWholeModal && ( - + )} diff --git a/projects/app/src/components/ChatBox/WholeResponseModal.tsx b/projects/app/src/components/ChatBox/WholeResponseModal.tsx index 6536b8187882..60e51bed9dd2 100644 --- a/projects/app/src/components/ChatBox/WholeResponseModal.tsx +++ b/projects/app/src/components/ChatBox/WholeResponseModal.tsx @@ -3,13 +3,14 @@ import { Box, useTheme, Flex, Image } from '@chakra-ui/react'; import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d'; import { useTranslation } from 'next-i18next'; import { moduleTemplatesFlat } from '@/web/core/modules/template/system'; -import Tabs from '../Tabs'; +import Tabs from '../Tabs'; import MyModal from '../MyModal'; import MyTooltip from '../MyTooltip'; import { QuestionOutlineIcon } from '@chakra-ui/icons'; import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools'; import Markdown from '../Markdown'; +import { QuoteList } from './QuoteModal'; import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constant'; function Row({ @@ -37,7 +38,9 @@ function Row({ fontSize={'sm'} {...(isCodeBlock ? { transform: 'translateY(-3px)' } - : { px: 3, py: 1, border: theme.borders.base })} + : value + ? { px: 3, py: 1, border: theme.borders.base } + : {})} > {value && } {rawDom} @@ -48,13 +51,51 @@ function Row({ const WholeResponseModal = ({ response, + isShare, onClose }: { response: ChatHistoryItemResType[]; + isShare: boolean; onClose: () => void; }) => { const { t } = useTranslation(); + return ( + + {t('chat.Complete Response')} + + + + + } + > + + + + + ); +}; + +export default WholeResponseModal; + +const ResponseBox = React.memo(function ResponseBox({ + response, + isShare +}: { + response: ChatHistoryItemResType[]; + isShare: boolean; +}) { + const theme = useTheme(); + const { t } = useTranslation(); + const list = useMemo( () => response.map((item, i) => ({ @@ -83,145 +124,129 @@ const WholeResponseModal = ({ const activeModule = useMemo(() => response[Number(currentTab)], [currentTab, response]); return ( - - {t('chat.Complete Response')} - - - - - } - > - - - - - - - {activeModule?.price !== undefined && ( - - )} + <> + + + + + + {activeModule?.price !== undefined && ( - - - + )} + + + + + + + {/* ai chat */} + + + + {activeModule.historyPreview?.map((item, i) => ( + + {item.obj} + {item.value} + + ))} + + ) : ( + '' + ) + } + /> + {activeModule.quoteList && activeModule.quoteList.length > 0 && ( } /> + )} - {/* ai chat */} + {/* dataset search */} + {activeModule?.searchMode && ( - + )} + + + + {/* classify question */} + { + if (!activeModule?.cqList) return ''; + return activeModule.cqList.map((item) => `* ${item.value}`).join('\n'); + })()} + /> + + + {/* extract */} + + {activeModule?.extractResult && ( - {activeModule.historyPreview?.map((item, i) => ( - - {item.obj} - {item.value} - - ))} - - ) : ( - '' - ) - } + label={t('core.chat.response.module extract result')} + value={`~~~json\n${JSON.stringify(activeModule?.extractResult, null, 2)}`} /> - {activeModule.quoteList && activeModule.quoteList.length > 0 && ( - - )} - - {/* dataset search */} - {activeModule?.searchMode && ( - - )} - - + )} - {/* classify question */} + {/* http */} + {activeModule?.body && ( { - if (!activeModule?.cqList) return ''; - return activeModule.cqList.map((item) => `* ${item.value}`).join('\n'); - })()} + label={t('core.chat.response.module http body')} + value={`~~~json\n${JSON.stringify(activeModule?.body, null, 2)}`} /> - - - {/* extract */} + )} + {activeModule?.httpResult && ( - {activeModule?.extractResult && ( - - )} + )} - {/* http */} - {activeModule?.body && ( - - )} - {activeModule?.httpResult && ( - - )} - - {/* plugin */} - {activeModule?.pluginOutput && ( - - )} + {/* plugin */} + {activeModule?.pluginDetail && activeModule?.pluginDetail.length > 0 && ( + } + /> + )} + {activeModule?.pluginOutput && ( + + )} - {/* text editor */} - - - - + {/* text output */} + + + ); -}; - -export default WholeResponseModal; +}); diff --git a/projects/app/src/components/ChatBox/index.tsx b/projects/app/src/components/ChatBox/index.tsx index d69cce768679..4355a84f0fb7 100644 --- a/projects/app/src/components/ChatBox/index.tsx +++ b/projects/app/src/components/ChatBox/index.tsx @@ -349,7 +349,13 @@ const ChatBox = ( responseText, isNewChat = false } = await onStartChat({ - chatList: newChatList, + chatList: newChatList.map((item) => ({ + dataId: item.dataId, + obj: item.obj, + value: item.value, + status: item.status, + moduleName: item.moduleName + })), messages, controller: abortSignal, generatingMessage, @@ -386,7 +392,7 @@ const ChatBox = ( }, 100); } catch (err: any) { toast({ - title: getErrText(err, '聊天出错了~'), + title: t(getErrText(err, 'core.chat.error.Chat error')), status: 'error', duration: 5000, isClosable: true @@ -419,7 +425,8 @@ const ChatBox = ( generatingMessage, createQuestionGuide, generatingScroll, - isPc + isPc, + t ] ); diff --git a/projects/app/src/components/Layout/index.tsx b/projects/app/src/components/Layout/index.tsx index c5ebb79c2efb..b4fce4f1f3a3 100644 --- a/projects/app/src/components/Layout/index.tsx +++ b/projects/app/src/components/Layout/index.tsx @@ -39,7 +39,7 @@ const Layout = ({ children }: { children: JSX.Element }) => { const router = useRouter(); const { colorMode, setColorMode } = useColorMode(); const { Loading } = useLoading(); - const { loading, setScreenWidth, isPc, loadGitStar } = useSystemStore(); + const { loading, setScreenWidth, isPc } = useSystemStore(); const { userInfo } = useUserStore(); const isChatPage = useMemo( @@ -61,12 +61,11 @@ const Layout = ({ children }: { children: JSX.Element }) => { window.addEventListener('resize', resize); resize(); - loadGitStar(); return () => { window.removeEventListener('resize', resize); }; - }, [loadGitStar, setScreenWidth]); + }, [setScreenWidth]); const { data: unread = 0 } = useQuery(['getUnreadCount'], getUnreadCount, { enabled: !!userInfo && !!feConfigs.isPlus, diff --git a/projects/app/src/components/Markdown/index.module.scss b/projects/app/src/components/Markdown/index.module.scss index 428f42ca1711..7823fece1a2b 100644 --- a/projects/app/src/components/Markdown/index.module.scss +++ b/projects/app/src/components/Markdown/index.module.scss @@ -346,7 +346,7 @@ width: 100%; * { - word-break: break-all; + word-break: break-word; } pre { diff --git a/projects/app/src/components/common/Textarea/PromptTextarea/index.tsx b/projects/app/src/components/common/Textarea/PromptTextarea/index.tsx index 063ea87d4996..c101b4f51d78 100644 --- a/projects/app/src/components/common/Textarea/PromptTextarea/index.tsx +++ b/projects/app/src/components/common/Textarea/PromptTextarea/index.tsx @@ -19,49 +19,51 @@ type Props = TextareaProps & { // variables: string[]; }; -const PromptTextarea = (props: Props) => { - const ModalTextareaRef = useRef(null); - const TextareaRef = useRef(null); +const PromptTextarea = React.forwardRef( + function PromptTextarea(props, ref) { + const ModalTextareaRef = useRef(null); + const TextareaRef = useRef(null); - const { t } = useTranslation(); - const { title = t('core.app.edit.Prompt Editor'), value, ...childProps } = props; + const { t } = useTranslation(); + const { title = t('core.app.edit.Prompt Editor'), ...childProps } = props; - const { isOpen, onOpen, onClose } = useDisclosure(); + const { isOpen, onOpen, onClose } = useDisclosure(); - return ( - <> - - {isOpen && ( - - - - - - - - - )} - - ); -}; + onClose(); + }} + > + {t('common.Confirm')} + + + + )} + + ); + } +); -export default PromptTextarea; +export default React.memo(PromptTextarea); const Editor = React.memo(function Editor({ onOpenModal, @@ -75,7 +77,7 @@ const Editor = React.memo(function Editor({ return ( -