From 22fc7dddfbf9ae04a71fad41d3558109c7018290 Mon Sep 17 00:00:00 2001 From: Archer <545436317@qq.com> Date: Thu, 17 Apr 2025 19:08:04 +0800 Subject: [PATCH] perf: text splitter (#4584) * perf: text splitter * update doc --- .../zh-cn/docs/development/upgrading/496.md | 2 + packages/global/common/string/textSplitter.ts | 42 +- packages/global/common/string/utils.ts | 3 + .../dataset/detail/DataCard.tsx | 3 +- .../service/core/dataset/data/controller.ts | 1 + .../packages/global/common/string/chunks.json | 5 + .../global/common/string/textSplitter.test.ts | 520 ++---------------- 7 files changed, 103 insertions(+), 473 deletions(-) create mode 100644 packages/global/common/string/utils.ts create mode 100644 test/cases/function/packages/global/common/string/chunks.json diff --git a/docSite/content/zh-cn/docs/development/upgrading/496.md b/docSite/content/zh-cn/docs/development/upgrading/496.md index fe42aaf6f..31242aa31 100644 --- a/docSite/content/zh-cn/docs/development/upgrading/496.md +++ b/docSite/content/zh-cn/docs/development/upgrading/496.md @@ -28,6 +28,8 @@ weight: 794 4. 知识库工具调用结果,自动补充图片域名。 5. Github action runner 升级成 unbuntu24 6. 去除飞书、公众号等三方渠道,回复时,可能前后多一个换行的问题。 +7. 调整分块策略,大表格时,不进行超大块合并,而是独立拆块。 +8. Iframe 嵌套组件,内置允许麦克风声明。 ## 🐛 修复 diff --git a/packages/global/common/string/textSplitter.ts b/packages/global/common/string/textSplitter.ts index 32863dbb6..78d1e71be 100644 --- a/packages/global/common/string/textSplitter.ts +++ b/packages/global/common/string/textSplitter.ts @@ -1,5 +1,6 @@ import { defaultMaxChunkSize } from '../../core/dataset/training/utils'; import { getErrText } from '../error/utils'; +import { getTextValidLength } from './utils'; export const CUSTOM_SPLIT_SIGN = '-----CUSTOM_SPLIT_SIGN-----'; @@ -73,7 +74,11 @@ ${mdSplitString} `; for (let i = 2; i < splitText2Lines.length; i++) { - if (chunk.length + splitText2Lines[i].length > chunkSize * 1.2) { + const chunkLength = getTextValidLength(chunk); + const nextLineLength = getTextValidLength(splitText2Lines[i]); + + // Over size + if (chunkLength + nextLineLength > chunkSize) { chunks.push(chunk); chunk = `${header} ${mdSplitString} @@ -112,10 +117,28 @@ const commonSplit = (props: SplitProps): SplitResponse => { const codeBlockMarker = 'CODE_BLOCK_LINE_MARKER'; const overlapLen = Math.round(chunkSize * overlapRatio); + // 特殊模块处理 + // 1. 代码块处理 - 去除空字符 // replace code block all \n to codeBlockMarker text = text.replace(/(```[\s\S]*?```|~~~[\s\S]*?~~~)/g, function (match) { return match.replace(/\n/g, codeBlockMarker); }); + // 2. 表格处理 - 单独提取表格出来,进行表头合并 + const tableReg = + /(\n\|(?:(?:[^\n|]+\|){1,})\n\|(?:[:\-\s]+\|){1,}\n(?:\|(?:[^\n|]+\|)*\n?)*)(?:\n|$)/g; + const tableDataList = text.match(tableReg); + if (tableDataList) { + tableDataList.forEach((tableData) => { + const { chunks } = markdownTableSplit({ + text: tableData.trim(), + chunkSize + }); + + const splitText = chunks.join('\n'); + text = text.replace(tableData, `\n${splitText}\n`); + }); + } + // replace invalid \n text = text.replace(/(\r?\n|\r){3,}/g, '\n\n\n'); @@ -137,7 +160,7 @@ const commonSplit = (props: SplitProps): SplitResponse => { { reg: /([\n](```[\s\S]*?```|~~~[\s\S]*?~~~))/g, maxLen: maxSize }, // code block { reg: /(\n\|(?:(?:[^\n|]+\|){1,})\n\|(?:[:\-\s]+\|){1,}\n(?:\|(?:[^\n|]+\|)*\n)*)/g, - maxLen: maxSize + maxLen: Math.min(chunkSize * 1.5, maxSize) }, // Table 尽可能保证完整性 { reg: /(\n{2,})/g, maxLen: chunkSize }, { reg: /([\n])/g, maxLen: chunkSize }, @@ -230,7 +253,7 @@ const commonSplit = (props: SplitProps): SplitResponse => { for (let i = splitTexts.length - 1; i >= 0; i--) { const currentText = splitTexts[i].text; const newText = currentText + overlayText; - const newTextLen = newText.length; + const newTextLen = getTextValidLength(newText); if (newTextLen > overlapLen) { if (newTextLen > maxOverlapLen) { @@ -259,15 +282,16 @@ const commonSplit = (props: SplitProps): SplitResponse => { const isMarkdownStep = checkIsMarkdownSplit(step); const isCustomStep = checkIsCustomStep(step); const forbidConcat = isCustomStep; // forbid=true时候,lastText肯定为空 + const textLength = getTextValidLength(text); // Over step if (step >= stepReges.length) { - if (text.length < maxSize) { + if (textLength < maxSize) { return [text]; } // use slice-chunkSize to split text const chunks: string[] = []; - for (let i = 0; i < text.length; i += chunkSize - overlapLen) { + for (let i = 0; i < textLength; i += chunkSize - overlapLen) { chunks.push(text.slice(i, i + chunkSize)); } return chunks; @@ -282,10 +306,10 @@ const commonSplit = (props: SplitProps): SplitResponse => { const maxLen = item.chunkMaxSize; // 当前块最大长度 - const lastTextLen = lastText.length; + const lastTextLen = getTextValidLength(lastText); const currentText = item.text; const newText = lastText + currentText; - const newTextLen = newText.length; + const newTextLen = getTextValidLength(newText); // Markdown 模式下,会强制向下拆分最小块,并再最后一个标题深度,给小块都补充上所有标题(包含父级标题) if (isMarkdownStep) { @@ -349,7 +373,7 @@ const commonSplit = (props: SplitProps): SplitResponse => { if (!lastChunk) continue; // last chunk is too small, concat it to lastText(next chunk start) - if (lastChunk.length < minChunkLen) { + if (getTextValidLength(lastChunk) < minChunkLen) { chunks.push(...innerChunks.slice(0, -1)); lastText = lastChunk; continue; @@ -378,7 +402,7 @@ const commonSplit = (props: SplitProps): SplitResponse => { /* If the last chunk is independent, it needs to be push chunks. */ if (lastText && chunks[chunks.length - 1] && !chunks[chunks.length - 1].endsWith(lastText)) { - if (lastText.length < chunkSize * 0.4) { + if (getTextValidLength(lastText) < chunkSize * 0.4) { chunks[chunks.length - 1] = chunks[chunks.length - 1] + lastText; } else { chunks.push(lastText); diff --git a/packages/global/common/string/utils.ts b/packages/global/common/string/utils.ts new file mode 100644 index 000000000..7b155e7a1 --- /dev/null +++ b/packages/global/common/string/utils.ts @@ -0,0 +1,3 @@ +export const getTextValidLength = (chunk: string) => { + return chunk.replaceAll(/[\s\n]/g, '').length; +}; diff --git a/projects/app/src/pageComponents/dataset/detail/DataCard.tsx b/projects/app/src/pageComponents/dataset/detail/DataCard.tsx index 08af77875..5bf1a091a 100644 --- a/projects/app/src/pageComponents/dataset/detail/DataCard.tsx +++ b/projects/app/src/pageComponents/dataset/detail/DataCard.tsx @@ -31,6 +31,7 @@ import { TabEnum } from './NavBar'; import { ImportDataSourceEnum } from '@fastgpt/global/core/dataset/constants'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import TrainingStates from './CollectionCard/TrainingStates'; +import { getTextValidLength } from '@fastgpt/global/common/string/utils'; const DataCard = () => { const theme = useTheme(); @@ -327,7 +328,7 @@ const DataCard = () => { w={'14px'} mr={1} /> - {item.q.length + (item.a?.length || 0)} + {getTextValidLength(item.q + item.a || '')} {canWrite && ( { const oldIndex = indexes!.find((index) => index.text === item.text); if (oldIndex) { diff --git a/test/cases/function/packages/global/common/string/chunks.json b/test/cases/function/packages/global/common/string/chunks.json new file mode 100644 index 000000000..1b9a525b4 --- /dev/null +++ b/test/cases/function/packages/global/common/string/chunks.json @@ -0,0 +1,5 @@ +[ + "测试的呀,第一个表格\n\n| 序号 | 姓名 | 年龄 | 职业 | 城市 |\n| --- | --- | --- | --- | --- |\n| 1 | 张三 | 25 | 工程师 | 北京 |\n| 2 | 李四 | 30 | 教师 | 上海 |\n| 3 | 王五 | 28 | 医生 | 广州 |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 4 | 赵六 | 35 | 律师 | 深圳 |\n| 5 | 孙七 | 27 | 设计师 | 杭州 |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 7 | 吴九 | 29 | 销售 | 武汉 |\n| 8 | 郑十 | 31 | 记者 | 南京 |\n| 9 | 刘一 | 33 | 建筑师 | 天津 |\n| 10 | 陈二 | 26 | 程序员 | 重庆 |\n| 1000 | 黄末 | 28 | 作家 | 厦门 |\n| 1001 | 杨一 | 34 | 程序员 | 厦门 |\n| 1002 | 杨二 | 34 | 程序员 | 厦门 |\n| 1003 | 杨三 | 34 | 程序员 | 厦门 |", + "| 序号 | 姓名 | 年龄 | 职业 | 城市 |\n| --- | --- | --- | --- | --- |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 1004 | 杨四 | 34 | 程序员 | 厦门 |\n| 1005 | 杨五 | 34 | 程序员 | 厦门 |\n| 1000 | 黄末 | 28 | 作家 | 厦门 |\n| 1000 | 黄末 | 28 | 作家 | 厦门 |\n| 1000 | 黄末 | 28 | 作家 | 厦门 |\n| 9 | 刘一 | 33 | 建筑师 | 天津 |\n| 10 | 陈二 | 26 | 程序员 | 重庆 |\n| 1000 | 黄末 | 28 | 作家 | 厦门 |\n| 1001 | 杨一 | 34 | 程序员 | 厦门 |\n| 1002 | 杨二 | 34 | 程序员 | 厦门 |\n| 1003 | 杨三 | 34 | 程序员 | 厦门 |\n| 1004 | 杨四 | 34 | 程序员 | 厦门 |\n| 1005 | 杨五 | 34 | 程序员 | 厦门 |\n\n| 序号 | 姓名 | 年龄 | 职业 | 城市 |\n| --- | --- | --- | --- | --- |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 1000 | 黄末 | 28 | 作家 | 厦门 |\n| 1000 | 黄末 | 28 | 作家 | 厦门 |\n| 1000 | 黄末 | 28 | 作家 | 厦门 |", + "这是第二段了,第二表格\n\n| 序号 | 姓名 | 年龄 | 职业 | 城市 |\n| --- | --- | --- | --- | --- |\n| 1 | 张三 | 25 | 工程师 | 北京 |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 2 | 李四 | 30 | 教师 | 上海 |\n| 3 | 王五 | 28 | 医生 | 广州 |\n| 4 | 赵六 | 35 | 律师 | 深圳 |\n| 5 | 孙七 | 27 | 设计师 | 杭州 |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 7 | 吴九 | 29 | 销售 | 武汉 |\n| 8 | 郑十 | 31 | 记者 | 南京 |\n| 9 | 刘一 | 33 | 建筑师 | 天津 |\n| 10 | 陈二 | 26 | 程序员 | 重庆 |\n| 10004 | 黄末 | 28 | 作家 | 厦门 |\n| 10013 | 杨一 | 34 | 程序员 | 厦门 |\n\n\n结束了\n\n| 序号22 | 姓名 | 年龄 | 职业 | 城市 |\n| --- | --- | --- | --- | --- |\n| 1 | 张三 | 25 | 工程师 | 北京 |\n| 2 | 李四 | 30 | 教师 | 上海 |\n| 3 | 王五 | 28 | 医生 | 广州 |\n| 4 | 赵六 | 35 | 律师 | 深圳 |\n| 5 | 孙七 | 27 | 设计师 | 杭州 |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 7 | 吴九 | 29 | 销售 | 武汉 |\n| 8 | 郑十 | 31 | 记者 | 南京 |\n| 9 | 刘一 | 33 | 建筑师 | 天津 |\n| 10 | 陈二 | 26 | 程序员 | 重庆 |\n| 10002 | 黄末 | 28 | 作家 | 厦门 |\n| 10012 | 杨一 | 34 | 程序员 | 厦门 |" +] \ No newline at end of file diff --git a/test/cases/function/packages/global/common/string/textSplitter.test.ts b/test/cases/function/packages/global/common/string/textSplitter.test.ts index baac2475d..2437bddf3 100644 --- a/test/cases/function/packages/global/common/string/textSplitter.test.ts +++ b/test/cases/function/packages/global/common/string/textSplitter.test.ts @@ -227,140 +227,11 @@ FastGPT 知识库采用 QA 对(不一定都是问答格式,仅代表两个变 通用模板与问答模板对比 我们通过一组你是谁的手动数据,对通用模板与问答模板的效果进行对比。此处特意打了个搞笑的答案,通用模板下 GPT35 就变得不那么听话了,`, result: [ - `快速了解 FastGPT -FastGPT 的能力与优势 - -FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开箱即用的数据处理、模型调用等能力。同时可以通过 Flow 可视化进行工作流编排,从而实现复杂的问答场景! - -FastGPT 在线使用:https://tryfastgpt.ai - -FastGPT 能力 -1. 专属 AI 客服 -通过导入文档或已有问答对进行训练,让 AI 模型能根据你的文档以交互式对话方式回答问题。 - -2. 简单易用的可视化界面 -FastGPT 采用直观的可视化界面设计,为各种应用场景提供了丰富实用的功能。通过简洁易懂的操作步骤,可以轻松完成 AI 客服的创建和训练流程。 - -3. 自动数据预处理 -提供手动输入、直接分段、LLM 自动处理和 CSV 等多种数据导入途径,其中“直接分段”支持通过 PDF、WORD、Markdown 和 CSV 文档内容作为上下文。FastGPT 会自动对文本数据进行预处理、向量化和 QA 分割,节省手动训练时间,提升效能。 - -4. 工作流编排 -基于 Flow 模块的工作流编排,可以帮助你设计更加复杂的问答流程。例如查询数据库、查询库存、预约实验室等。`, - `5. 强大的 API 集成 -FastGPT 对外的 API 接口对齐了 OpenAI 官方接口,可以直接接入现有的 GPT 应用,也可以轻松集成到企业微信、公众号、飞书等平台。 - -FastGPT 特点 -项目开源 - -FastGPT 遵循附加条件 Apache License 2.0 开源协议,你可以 Fork 之后进行二次开发和发布。FastGPT 社区版将保留核心功能,商业版仅在社区版基础上使用 API 的形式进行扩展,不影响学习使用。 - -独特的 QA 结构 - -针对客服问答场景设计的 QA 结构,提高在大量数据场景中的问答准确性。 - -可视化工作流 - -通过 Flow 模块展示了从问题输入到模型输出的完整流程,便于调试和设计复杂流程。 - -无限扩展 - -基于 API 进行扩展,无需修改 FastGPT 源码,也可快速接入现有的程序中。 - -便于调试 - -提供搜索测试、引用修改、完整对话预览等多种调试途径。 - -支持多种模型 - -支持 GPT、Claude、文心一言等多种 LLM 模型,未来也将支持自定义的向量模型。 - -知识库核心流程 - -FastGPT AI 相关参数配置说明 - -在 FastGPT 的 AI 对话模块中,有一个 AI 高级配置,里面包含了 AI 模型的参数配置,本文详细介绍这些配置的含义。`, - `返回AI内容(高级编排特有) -这是一个开关,打开的时候,当 AI 对话模块运行时,会将其输出的内容返回到浏览器(API响应);如果关闭,AI 输出的内容不会返回到浏览器,但是生成的内容仍可以通过【AI回复】进行输出。你可以将【AI回复】连接到其他模块中。 - -最大上下文 -代表模型最多容纳的文字数量。 - -函数调用 -支持函数调用的模型,在使用工具时更加准确。 - -温度 -越低回答越严谨,少废话(实测下来,感觉差别不大) - -回复上限 -最大回复 token 数量。注意,是回复的Tokens!不是上下文 tokens。 - -系统提示词 -被放置在上下文数组的最前面,role 为 system,用于引导模型。 - -引用模板 & 引用提示词 -这两个参数与知识库问答场景相关,可以控制知识库相关的提示词。 - -AI 对话消息组成 -想使用明白这两个变量,首先要了解传递传递给 AI 模型的消息格式。它是一个数组,FastGPT 中这个数组的组成形式为: - -[ -内置提示词(config.json 配置,一般为空) -系统提示词 (用户输入的提示词) -历史记录 -问题(由引用提示词、引用模板和用户问题组成) -] -🍅`, - `Tips: 可以通过点击上下文按键查看完整的上下文组成,便于调试。 - -引用模板和提示词设计 -简易模式已移除该功能,仅在工作流中可配置,可点击工作流中AI对话节点内,知识库引用旁边的setting icon进行配置。随着模型的增强,这部分功能将逐步弱化。 - -引用模板和引用提示词通常是成对出现,引用提示词依赖引用模板。 - -FastGPT 知识库采用 QA 对(不一定都是问答格式,仅代表两个变量)的格式存储,在转义成字符串时候会根据引用模板来进行格式化。知识库包含多个可用变量: q, a, sourceId(数据的ID), index(第n个数据), source(数据的集合名、文件名),score(距离得分,0-1) 可以通过 {{q}} {{a}} {{sourceId}} {{index}} {{source}} {{score}} 按需引入。下面一个模板例子: - -可以通过 知识库结构讲解 了解详细的知识库的结构。 - -引用模板 -{instruction:"{{q}}",output:"{{a}}",source:"{{source}}"} -搜索到的知识库,会自动将 q,a,source 替换成对应的内容。每条搜索到的内容,会通过 - 隔开。例如:`, - `{instruction:"电影《铃芽之旅》的导演是谁?",output:"电影《铃芽之旅》的导演是新海诚。",source:"手动输入"} -{instruction:"本作的主人公是谁?",output:"本作的主人公是名叫铃芽的少女。",source:""} -{instruction:"电影《铃芽之旅》男主角是谁?",output:"电影《铃芽之旅》男主角是宗像草太,由松村北斗配音。",source:""} -{instruction:"电影《铃芽之旅》的编剧是谁?22",output:"新海诚是本片的编剧。",source:"手动输入"} -引用提示词 -引用模板需要和引用提示词一起使用,提示词中可以写引用模板的格式说明以及对话的要求等。可以使用 {{quote}} 来使用 引用模板,使用 {{question}} 来引入问题。例如: - -你的背景知识: -""" -{{quote}} -""" -对话要求: -1. 背景知识是最新的,其中 instruction 是相关介绍,output 是预期回答或补充。 -2. 使用背景知识回答问题。 -3. 背景知识无法回答问题时,你可以礼貌的的回答用户问题。 -我的问题是:"{{question}}" -转义后则为:`, - `你的背景知识: -""" -{instruction:"电影《铃芽之旅》的导演是谁?",output:"电影《铃芽之旅》的导演是新海诚。",source:"手动输入"} -{instruction:"本作的主人公是谁?",output:"本作的主人公是名叫铃芽的少女。",source:""} -{instruction:"电影《铃芽之旅》男主角是谁?",output:"电影《铃芽之旅》男主角是宗像草太,由松村北斗配音} -""" -对话要求: -1. 背景知识是最新的,其中 instruction 是相关介绍,output 是预期回答或补充。 -2. 使用背景知识回答问题。 -3. 背景知识无法回答问题时,你可以礼貌的的回答用户问题。 -我的问题是:"{{question}}" -总结 -引用模板规定了搜索出来的内容如何组成一句话,其由 q,a,index,source 多个变量组成。 - -引用提示词由引用模板和提示词组成,提示词通常是对引用模板的一个描述,加上对模型的要求。 - -引用模板和提示词设计 示例 -通用模板与问答模板对比 -我们通过一组你是谁的手动数据,对通用模板与问答模板的效果进行对比。此处特意打了个搞笑的答案,通用模板下 GPT35 就变得不那么听话了,` + '快速了解 FastGPT\nFastGPT 的能力与优势\n\nFastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开箱即用的数据处理、模型调用等能力。同时可以通过 Flow 可视化进行工作流编排,从而实现复杂的问答场景!\n\nFastGPT 在线使用:https://tryfastgpt.ai\n\nFastGPT 能力 \n1. 专属 AI 客服 \n通过导入文档或已有问答对进行训练,让 AI 模型能根据你的文档以交互式对话方式回答问题。\n\n2. 简单易用的可视化界面 \nFastGPT 采用直观的可视化界面设计,为各种应用场景提供了丰富实用的功能。通过简洁易懂的操作步骤,可以轻松完成 AI 客服的创建和训练流程。\n\n3. 自动数据预处理 \n提供手动输入、直接分段、LLM 自动处理和 CSV 等多种数据导入途径,其中“直接分段”支持通过 PDF、WORD、Markdown 和 CSV 文档内容作为上下文。FastGPT 会自动对文本数据进行预处理、向量化和 QA 分割,节省手动训练时间,提升效能。\n\n4. 工作流编排 \n基于 Flow 模块的工作流编排,可以帮助你设计更加复杂的问答流程。例如查询数据库、查询库存、预约实验室等。\n\n5. 强大的 API 集成 \nFastGPT 对外的 API 接口对齐了 OpenAI 官方接口,可以直接接入现有的 GPT 应用,也可以轻松集成到企业微信、公众号、飞书等平台。', + 'FastGPT 特点 \n项目开源\n\nFastGPT 遵循附加条件 Apache License 2.0 开源协议,你可以 Fork 之后进行二次开发和发布。FastGPT 社区版将保留核心功能,商业版仅在社区版基础上使用 API 的形式进行扩展,不影响学习使用。\n\n独特的 QA 结构\n\n针对客服问答场景设计的 QA 结构,提高在大量数据场景中的问答准确性。\n\n可视化工作流\n\n通过 Flow 模块展示了从问题输入到模型输出的完整流程,便于调试和设计复杂流程。\n\n无限扩展\n\n基于 API 进行扩展,无需修改 FastGPT 源码,也可快速接入现有的程序中。\n\n便于调试\n\n提供搜索测试、引用修改、完整对话预览等多种调试途径。\n\n支持多种模型\n\n支持 GPT、Claude、文心一言等多种 LLM 模型,未来也将支持自定义的向量模型。\n\n知识库核心流程\n\nFastGPT AI 相关参数配置说明\n\n在 FastGPT 的 AI 对话模块中,有一个 AI 高级配置,里面包含了 AI 模型的参数配置,本文详细介绍这些配置的含义。\n\n返回AI内容(高级编排特有) \n这是一个开关,打开的时候,当 AI 对话模块运行时,会将其输出的内容返回到浏览器(API响应);如果关闭,AI 输出的内容不会返回到浏览器,但是生成的内容仍可以通过【AI回复】进行输出。你可以将【AI回复】连接到其他模块中。', + '最大上下文 \n代表模型最多容纳的文字数量。\n\n函数调用 \n支持函数调用的模型,在使用工具时更加准确。\n\n温度 \n越低回答越严谨,少废话(实测下来,感觉差别不大)\n\n回复上限 \n最大回复 token 数量。注意,是回复的Tokens!不是上下文 tokens。\n\n系统提示词 \n被放置在上下文数组的最前面,role 为 system,用于引导模型。\n\n引用模板 & 引用提示词 \n这两个参数与知识库问答场景相关,可以控制知识库相关的提示词。\n\nAI 对话消息组成 \n想使用明白这两个变量,首先要了解传递传递给 AI 模型的消息格式。它是一个数组,FastGPT 中这个数组的组成形式为:\n\n[\n内置提示词(config.json 配置,一般为空)\n系统提示词 (用户输入的提示词)\n历史记录\n问题(由引用提示词、引用模板和用户问题组成)\n]\n🍅\n\nTips: 可以通过点击上下文按键查看完整的上下文组成,便于调试。\n\n引用模板和提示词设计 \n简易模式已移除该功能,仅在工作流中可配置,可点击工作流中AI对话节点内,知识库引用旁边的setting icon进行配置。随着模型的增强,这部分功能将逐步弱化。\n\n引用模板和引用提示词通常是成对出现,引用提示词依赖引用模板。', + 'FastGPT 知识库采用 QA 对(不一定都是问答格式,仅代表两个变量)的格式存储,在转义成字符串时候会根据引用模板来进行格式化。知识库包含多个可用变量: q, a, sourceId(数据的ID), index(第n个数据), source(数据的集合名、文件名),score(距离得分,0-1) 可以通过 {{q}} {{a}} {{sourceId}} {{index}} {{source}} {{score}} 按需引入。下面一个模板例子:\n\n可以通过 知识库结构讲解 了解详细的知识库的结构。\n\n引用模板 \n{instruction:"{{q}}",output:"{{a}}",source:"{{source}}"}\n搜索到的知识库,会自动将 q,a,source 替换成对应的内容。每条搜索到的内容,会通过 \n 隔开。例如:\n\n{instruction:"电影《铃芽之旅》的导演是谁?",output:"电影《铃芽之旅》的导演是新海诚。",source:"手动输入"}\n{instruction:"本作的主人公是谁?",output:"本作的主人公是名叫铃芽的少女。",source:""}\n{instruction:"电影《铃芽之旅》男主角是谁?",output:"电影《铃芽之旅》男主角是宗像草太,由松村北斗配音。",source:""}\n{instruction:"电影《铃芽之旅》的编剧是谁?22",output:"新海诚是本片的编剧。",source:"手动输入"}\n引用提示词 \n引用模板需要和引用提示词一起使用,提示词中可以写引用模板的格式说明以及对话的要求等。可以使用 {{quote}} 来使用 引用模板,使用 {{question}} 来引入问题。例如:', + '你的背景知识:\n"""\n{{quote}}\n"""\n对话要求:\n1. 背景知识是最新的,其中 instruction 是相关介绍,output 是预期回答或补充。\n2. 使用背景知识回答问题。\n3. 背景知识无法回答问题时,你可以礼貌的的回答用户问题。\n我的问题是:"{{question}}"\n转义后则为:\n\n你的背景知识:\n"""\n{instruction:"电影《铃芽之旅》的导演是谁?",output:"电影《铃芽之旅》的导演是新海诚。",source:"手动输入"}\n{instruction:"本作的主人公是谁?",output:"本作的主人公是名叫铃芽的少女。",source:""}\n{instruction:"电影《铃芽之旅》男主角是谁?",output:"电影《铃芽之旅》男主角是宗像草太,由松村北斗配音}\n"""\n对话要求:\n1. 背景知识是最新的,其中 instruction 是相关介绍,output 是预期回答或补充。\n2. 使用背景知识回答问题。\n3. 背景知识无法回答问题时,你可以礼貌的的回答用户问题。\n我的问题是:"{{question}}"\n总结 \n引用模板规定了搜索出来的内容如何组成一句话,其由 q,a,index,source 多个变量组成。\n\n引用提示词由引用模板和提示词组成,提示词通常是对引用模板的一个描述,加上对模型的要求。\n\n引用模板和提示词设计 示例 \n通用模板与问答模板对比 \n我们通过一组你是谁的手动数据,对通用模板与问答模板的效果进行对比。此处特意打了个搞笑的答案,通用模板下 GPT35 就变得不那么听话了,' ] }; @@ -437,7 +308,7 @@ FastGPT是一款基于大语言模型(LLM)的智能问答系统,专为提 }); // 自定义分隔符测试:换行符号 -it(`Test splitText2Chunks 1`, () => { +it(`Test splitText2Chunks 7`, () => { const mock = { text: `111 222 @@ -456,7 +327,7 @@ it(`Test splitText2Chunks 1`, () => { }); // 长代码块分割 -it(`Test splitText2Chunks 7`, () => { +it(`Test splitText2Chunks 8`, () => { const mock = { text: `这是一个测试的内容,包含代码块 @@ -665,211 +536,9 @@ FastGPT AI 相关参数配置说明 最大上下文 代表模型最多容纳的文字数量。`, result: [ - `这是一个测试的内容,包含代码块 - -快速了解 FastGPT -FastGPT 的能力与优势 - -FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开箱即用的数据处理、模型调用等能力。同时可以通过 Flow 可视化进行工作流编排,从而实现复杂的问答场景! - -FastGPT 在线使用:https://tryfastgpt.ai - -FastGPT 能力 -1. 专属 AI 客服 -通过导入文档或已有问答对进行训练,让 AI 模型能根据你的文档以交互式对话方式回答问题。 - -2. 简单易用的可视化界面 -FastGPT 采用直观的可视化界面设计,为各种应用场景提供了丰富实用的功能。通过简洁易懂的操作步骤,可以轻松完成 AI 客服的创建和训练流程。 - -~~~js -import { defaultMaxChunkSize } from '../../core/dataset/training/utils'; -import { getErrText } from '../error/utils'; - -const getOneTextOverlapText = ({ text, step }: { text: string; step: number }): string => { - const forbidOverlap = checkForbidOverlap(step); - const maxOverlapLen = chunkSize * 0.4; - - // step >= stepReges.length: Do not overlap incomplete sentences - if (forbidOverlap || overlapLen === 0 || step >= stepReges.length) return ''; - - const splitTexts = getSplitTexts({ text, step }); - let overlayText = ''; - - for (let i = splitTexts.length - 1; i >= 0; i--) { - const currentText = splitTexts[i].text; - const newText = currentText + overlayText; - const newTextLen = newText.length; - - if (newTextLen > overlapLen) { - if (newTextLen > maxOverlapLen) { - const text = getOneTextOverlapText({ text: newText, step: step + 1 }); - return text || overlayText; - } - return newText; - } - - overlayText = newText; - } - return overlayText; - }; - - const getOneTextOverlapText = ({ text, step }: { text: string; step: number }): string => { - const forbidOverlap = checkForbidOverlap(step); - const maxOverlapLen = chunkSize * 0.4; - - // step >= stepReges.length: Do not overlap incomplete sentences - if (forbidOverlap || overlapLen === 0 || step >= stepReges.length) return ''; - - const splitTexts = getSplitTexts({ text, step }); - let overlayText = ''; - - for (let i = splitTexts.length - 1; i >= 0; i--) { - const currentText = splitTexts[i].text; - const newText = currentText + overlayText; - const newTextLen = newText.length; - - if (newTextLen > overlapLen) { - if (newTextLen > maxOverlapLen) { - const text = getOneTextOverlapText({ text: newText, step: step + 1 }); - return text || overlayText; - } - return newText; - } - - overlayText = newText; - } - return overlayText; - }; - - const getOneTextOverlapText = ({ text, step }: { text: string; step: number }): string => { - const forbidOverlap = checkForbidOverlap(step); - const maxOverlapLen = chunkSize * 0.4; - - // step >= stepReges.length: Do not overlap incomplete sentences - if (forbidOverlap || overlapLen === 0 || step >= stepReges.length) return ''; - - const splitTexts = getSplitTexts({ text, step }); - let overlayText = ''; - - for (let i = splitTexts.length - 1; i >= 0; i--) { - const currentText = splitTexts[i].text; - const newText = currentText + overlayText; - const newTextLen = newText.length; - - if (newTextLen > overlapLen) { - if (newTextLen > maxOverlapLen) { - const text = getOneTextOverlapText({ text: newText, step: step + 1 }); - return text || overlayText; - } - return newText; - } - - overlayText = newText; - } - return overlayText; - }; - - const getOneTextOverlapText = ({ text, step }: { text: string; step: number }): string => { - const forbidOverlap = checkForbidOverlap(step); - const maxOverlapLen = chunkSize * 0.4; - - // step >= stepReges.length: Do not overlap incomplete sentences - if (forbidOverlap || overlapLen === 0 || step >= stepReges.length) return ''; - - const splitTexts = getSplitTexts({ text, step }); - let overlayText = ''; - - for (let i = splitTexts.length - 1; i >= 0; i--) { - const currentText = splitTexts[i].text; - const newText = currentText + overlayText; - const newTextLen = newText.length; - - if (newTextLen > overlapLen) { - if (newTextLen > maxOverlapLen) { - const text = getOneTextOverlapText({ text: newText, step: step + 1 }); - return text || overlayText; - } - return newText; - } - - overlayText = newText; - } - return overlayText; - }; - - const getOneTextOverlapText = ({ text, step }: { text: string; step: number }): string => { - const forbidOverlap = checkForbidOverlap(step); - const maxOverlapLen = chunkSize * 0.4; - - // step >= stepReges.length: Do not overlap incomplete sentences - if (forbidOverlap || overlapLen === 0 || step >= stepReges.length) return ''; - - const splitTexts = getSplitTexts({ text, step }); - let overlayText = ''; - - for (let i = splitTexts.length - 1; i >= 0; i--) { - const currentText = splitTexts[i].text; - const newText = currentText + overlayText; - const newTextLen = newText.length; - - if (newTextLen > overlapLen) { - if (newTextLen > maxOverlapLen) { - const text = getOneTextOverlapText({ text: newText, step: step + 1 }); - return text || overlayText; - } - return newText; - } - - overlayText = newText; - } - return overlayText; - }; -~~~`, - `3. 自动数据预处理 -提供手动输入、直接分段、LLM 自动处理和 CSV 等多种数据导入途径,其中“直接分段”支持通过 PDF、WORD、Markdown 和 CSV 文档内容作为上下文。FastGPT 会自动对文本数据进行预处理、向量化和 QA 分割,节省手动训练时间,提升效能。 - -4. 工作流编排 -基于 Flow 模块的工作流编排,可以帮助你设计更加复杂的问答流程。例如查询数据库、查询库存、预约实验室等。 - -5. 强大的 API 集成 -FastGPT 对外的 API 接口对齐了 OpenAI 官方接口,可以直接接入现有的 GPT 应用,也可以轻松集成到企业微信、公众号、飞书等平台。 - -FastGPT 特点 -项目开源 - -FastGPT 遵循附加条件 Apache License 2.0 开源协议,你可以 Fork 之后进行二次开发和发布。FastGPT 社区版将保留核心功能,商业版仅在社区版基础上使用 API 的形式进行扩展,不影响学习使用。 - -独特的 QA 结构 - -针对客服问答场景设计的 QA 结构,提高在大量数据场景中的问答准确性。 - -可视化工作流 - -通过 Flow 模块展示了从问题输入到模型输出的完整流程,便于调试和设计复杂流程。 -`, - `无限扩展 - -基于 API 进行扩展,无需修改 FastGPT 源码,也可快速接入现有的程序中。 - -便于调试 - -提供搜索测试、引用修改、完整对话预览等多种调试途径。 - -支持多种模型 - -支持 GPT、Claude、文心一言等多种 LLM 模型,未来也将支持自定义的向量模型。 - -知识库核心流程 - -FastGPT AI 相关参数配置说明 - -在 FastGPT 的 AI 对话模块中,有一个 AI 高级配置,里面包含了 AI 模型的参数配置,本文详细介绍这些配置的含义。 - -返回AI内容(高级编排特有) -这是一个开关,打开的时候,当 AI 对话模块运行时,会将其输出的内容返回到浏览器(API响应);如果关闭,AI 输出的内容不会返回到浏览器,但是生成的内容仍可以通过【AI回复】进行输出。你可以将【AI回复】连接到其他模块中。 - -最大上下文 -代表模型最多容纳的文字数量。` + "这是一个测试的内容,包含代码块\n\n快速了解 FastGPT\nFastGPT 的能力与优势\n\nFastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开箱即用的数据处理、模型调用等能力。同时可以通过 Flow 可视化进行工作流编排,从而实现复杂的问答场景!\n\nFastGPT 在线使用:https://tryfastgpt.ai\n\nFastGPT 能力 \n1. 专属 AI 客服 \n通过导入文档或已有问答对进行训练,让 AI 模型能根据你的文档以交互式对话方式回答问题。\n\n2. 简单易用的可视化界面 \nFastGPT 采用直观的可视化界面设计,为各种应用场景提供了丰富实用的功能。通过简洁易懂的操作步骤,可以轻松完成 AI 客服的创建和训练流程。\n\n~~~js\nimport { defaultMaxChunkSize } from '../../core/dataset/training/utils';\nimport { getErrText } from '../error/utils';\n\nconst getOneTextOverlapText = ({ text, step }: { text: string; step: number }): string => {\n const forbidOverlap = checkForbidOverlap(step);\n const maxOverlapLen = chunkSize * 0.4;\n\n // step >= stepReges.length: Do not overlap incomplete sentences\n if (forbidOverlap || overlapLen === 0 || step >= stepReges.length) return '';\n\n const splitTexts = getSplitTexts({ text, step });\n let overlayText = '';\n\n for (let i = splitTexts.length - 1; i >= 0; i--) {\n const currentText = splitTexts[i].text;\n const newText = currentText + overlayText;\n const newTextLen = newText.length;\n\n if (newTextLen > overlapLen) {\n if (newTextLen > maxOverlapLen) {\n const text = getOneTextOverlapText({ text: newText, step: step + 1 });\n return text || overlayText;\n }\n return newText;\n }\n\n overlayText = newText;\n }\n return overlayText;\n };\n\n const getOneTextOverlapText = ({ text, step }: { text: string; step: number }): string => {\n const forbidOverlap = checkForbidOverlap(step);\n const maxOverlapLen = chunkSize * 0.4;\n\n // step >= stepReges.length: Do not overlap incomplete sentences\n if (forbidOverlap || overlapLen === 0 || step >= stepReges.length) return '';\n\n const splitTexts = getSplitTexts({ text, step });\n let overlayText = '';\n\n for (let i = splitTexts.length - 1; i >= 0; i--) {\n const currentText = splitTexts[i].text;\n const newText = currentText + overlayText;\n const newTextLen = newText.length;\n\n if (newTextLen > overlapLen) {\n if (newTextLen > maxOverlapLen) {\n const text = getOneTextOverlapText({ text: newText, step: step + 1 });\n return text || overlayText;\n }\n return newText;\n }\n\n overlayText = newText;\n }\n return overlayText;\n };\n\n const getOneTextOverlapText = ({ text, step }: { text: string; step: number }): string => {\n const forbidOverlap = checkForbidOverlap(step);\n const maxOverlapLen = chunkSize * 0.4;\n\n // step >= stepReges.length: Do not overlap incomplete sentences\n if (forbidOverlap || overlapLen === 0 || step >= stepReges.length) return '';\n\n const splitTexts = getSplitTexts({ text, step });\n let overlayText = '';\n\n for (let i = splitTexts.length - 1; i >= 0; i--) {\n const currentText = splitTexts[i].text;\n const newText = currentText + overlayText;\n const newTextLen = newText.length;\n\n if (newTextLen > overlapLen) {\n if (newTextLen > maxOverlapLen) {\n const text = getOneTextOverlapText({ text: newText, step: step + 1 });\n return text || overlayText;\n }\n return newText;\n }\n\n overlayText = newText;\n }\n return overlayText;\n };\n\n const getOneTextOverlapText = ({ text, step }: { text: string; step: number }): string => {\n const forbidOverlap = checkForbidOverlap(step);\n const maxOverlapLen = chunkSize * 0.4;\n\n // step >= stepReges.length: Do not overlap incomplete sentences\n if (forbidOverlap || overlapLen === 0 || step >= stepReges.length) return '';\n\n const splitTexts = getSplitTexts({ text, step });\n let overlayText = '';\n\n for (let i = splitTexts.length - 1; i >= 0; i--) {\n const currentText = splitTexts[i].text;\n const newText = currentText + overlayText;\n const newTextLen = newText.length;\n\n if (newTextLen > overlapLen) {\n if (newTextLen > maxOverlapLen) {\n const text = getOneTextOverlapText({ text: newText, step: step + 1 });\n return text || overlayText;\n }\n return newText;\n }\n\n overlayText = newText;\n }\n return overlayText;\n };\n\n const getOneTextOverlapText = ({ text, step }: { text: string; step: number }): string => {\n const forbidOverlap = checkForbidOverlap(step);\n const maxOverlapLen = chunkSize * 0.4;\n\n // step >= stepReges.length: Do not overlap incomplete sentences\n if (forbidOverlap || overlapLen === 0 || step >= stepReges.length) return '';\n\n const splitTexts = getSplitTexts({ text, step });\n let overlayText = '';\n\n for (let i = splitTexts.length - 1; i >= 0; i--) {\n const currentText = splitTexts[i].text;\n const newText = currentText + overlayText;\n const newTextLen = newText.length;\n\n if (newTextLen > overlapLen) {\n if (newTextLen > maxOverlapLen) {\n const text = getOneTextOverlapText({ text: newText, step: step + 1 });\n return text || overlayText;\n }\n return newText;\n }\n\n overlayText = newText;\n }\n return overlayText;\n };\n~~~", + '3. 自动数据预处理 \n提供手动输入、直接分段、LLM 自动处理和 CSV 等多种数据导入途径,其中“直接分段”支持通过 PDF、WORD、Markdown 和 CSV 文档内容作为上下文。FastGPT 会自动对文本数据进行预处理、向量化和 QA 分割,节省手动训练时间,提升效能。\n\n4. 工作流编排 \n基于 Flow 模块的工作流编排,可以帮助你设计更加复杂的问答流程。例如查询数据库、查询库存、预约实验室等。\n\n5. 强大的 API 集成 \nFastGPT 对外的 API 接口对齐了 OpenAI 官方接口,可以直接接入现有的 GPT 应用,也可以轻松集成到企业微信、公众号、飞书等平台。\n\nFastGPT 特点 \n项目开源\n\nFastGPT 遵循附加条件 Apache License 2.0 开源协议,你可以 Fork 之后进行二次开发和发布。FastGPT 社区版将保留核心功能,商业版仅在社区版基础上使用 API 的形式进行扩展,不影响学习使用。\n\n独特的 QA 结构\n\n针对客服问答场景设计的 QA 结构,提高在大量数据场景中的问答准确性。\n\n可视化工作流\n\n通过 Flow 模块展示了从问题输入到模型输出的完整流程,便于调试和设计复杂流程。\n\n无限扩展\n\n基于 API 进行扩展,无需修改 FastGPT 源码,也可快速接入现有的程序中。', + '便于调试\n\n提供搜索测试、引用修改、完整对话预览等多种调试途径。\n\n支持多种模型\n\n支持 GPT、Claude、文心一言等多种 LLM 模型,未来也将支持自定义的向量模型。\n\n知识库核心流程\n\nFastGPT AI 相关参数配置说明\n\n在 FastGPT 的 AI 对话模块中,有一个 AI 高级配置,里面包含了 AI 模型的参数配置,本文详细介绍这些配置的含义。\n\n返回AI内容(高级编排特有) \n这是一个开关,打开的时候,当 AI 对话模块运行时,会将其输出的内容返回到浏览器(API响应);如果关闭,AI 输出的内容不会返回到浏览器,但是生成的内容仍可以通过【AI回复】进行输出。你可以将【AI回复】连接到其他模块中。\n\n最大上下文 \n代表模型最多容纳的文字数量。' ] }; @@ -887,18 +556,20 @@ FastGPT AI 相关参数配置说明 }); // 表格分割测试 - 不超出maxSize -it(`Test splitText2Chunks 1`, () => { +it(`Test splitText2Chunks 9`, () => { const mock = { - text: `测试的呀 + text: `测试的呀,第一个表格 | 序号 | 姓名 | 年龄 | 职业 | 城市 | |------|------|------|------|------| | 1 | 张三 | 25 | 工程师 | 北京 | | 2 | 李四 | 30 | 教师 | 上海 | | 3 | 王五 | 28 | 医生 | 广州 | +| 6 | 周八 | 32 | 会计 | 成都 | | 4 | 赵六 | 35 | 律师 | 深圳 | | 5 | 孙七 | 27 | 设计师 | 杭州 | | 6 | 周八 | 32 | 会计 | 成都 | +| 6 | 周八 | 32 | 会计 | 成都 | | 7 | 吴九 | 29 | 销售 | 武汉 | | 8 | 郑十 | 31 | 记者 | 南京 | | 9 | 刘一 | 33 | 建筑师 | 天津 | @@ -907,6 +578,7 @@ it(`Test splitText2Chunks 1`, () => { | 1001 | 杨一 | 34 | 程序员 | 厦门 | | 1002 | 杨二 | 34 | 程序员 | 厦门 | | 1003 | 杨三 | 34 | 程序员 | 厦门 | +| 6 | 周八 | 32 | 会计 | 成都 | | 1004 | 杨四 | 34 | 程序员 | 厦门 | | 1005 | 杨五 | 34 | 程序员 | 厦门 | | 1000 | 黄末 | 28 | 作家 | 厦门 | @@ -920,134 +592,56 @@ it(`Test splitText2Chunks 1`, () => { | 1003 | 杨三 | 34 | 程序员 | 厦门 | | 1004 | 杨四 | 34 | 程序员 | 厦门 | | 1005 | 杨五 | 34 | 程序员 | 厦门 | +| 6 | 周八 | 32 | 会计 | 成都 | | 1000 | 黄末 | 28 | 作家 | 厦门 | | 1000 | 黄末 | 28 | 作家 | 厦门 | | 1000 | 黄末 | 28 | 作家 | 厦门 | -这是第二段了`, +这是第二段了,第二表格 + +| 序号 | 姓名 | 年龄 | 职业 | 城市 | +|------|------|------|------|------| +| 1 | 张三 | 25 | 工程师 | 北京 | +| 6 | 周八 | 32 | 会计 | 成都 | +| 2 | 李四 | 30 | 教师 | 上海 | +| 3 | 王五 | 28 | 医生 | 广州 | +| 4 | 赵六 | 35 | 律师 | 深圳 | +| 5 | 孙七 | 27 | 设计师 | 杭州 | +| 6 | 周八 | 32 | 会计 | 成都 | +| 7 | 吴九 | 29 | 销售 | 武汉 | +| 8 | 郑十 | 31 | 记者 | 南京 | +| 9 | 刘一 | 33 | 建筑师 | 天津 | +| 10 | 陈二 | 26 | 程序员 | 重庆 | +| 10004 | 黄末 | 28 | 作家 | 厦门 | +| 10013 | 杨一 | 34 | 程序员 | 厦门 | + + +结束了 + +| 序号22 | 姓名 | 年龄 | 职业 | 城市 | +|------|------|------|------|------| +| 1 | 张三 | 25 | 工程师 | 北京 | +| 2 | 李四 | 30 | 教师 | 上海 | +| 3 | 王五 | 28 | 医生 | 广州 | +| 4 | 赵六 | 35 | 律师 | 深圳 | +| 5 | 孙七 | 27 | 设计师 | 杭州 | +| 6 | 周八 | 32 | 会计 | 成都 | +| 6 | 周八 | 32 | 会计 | 成都 | +| 7 | 吴九 | 29 | 销售 | 武汉 | +| 8 | 郑十 | 31 | 记者 | 南京 | +| 9 | 刘一 | 33 | 建筑师 | 天津 | +| 10 | 陈二 | 26 | 程序员 | 重庆 | +| 10002 | 黄末 | 28 | 作家 | 厦门 | +| 10012 | 杨一 | 34 | 程序员 | 厦门 | +`, result: [ - `测试的呀 - -| 序号 | 姓名 | 年龄 | 职业 | 城市 | -|------|------|------|------|------| -| 1 | 张三 | 25 | 工程师 | 北京 | -| 2 | 李四 | 30 | 教师 | 上海 | -| 3 | 王五 | 28 | 医生 | 广州 | -| 4 | 赵六 | 35 | 律师 | 深圳 | -| 5 | 孙七 | 27 | 设计师 | 杭州 | -| 6 | 周八 | 32 | 会计 | 成都 | -| 7 | 吴九 | 29 | 销售 | 武汉 | -| 8 | 郑十 | 31 | 记者 | 南京 | -| 9 | 刘一 | 33 | 建筑师 | 天津 | -| 10 | 陈二 | 26 | 程序员 | 重庆 | -| 1000 | 黄末 | 28 | 作家 | 厦门 | -| 1001 | 杨一 | 34 | 程序员 | 厦门 | -| 1002 | 杨二 | 34 | 程序员 | 厦门 | -| 1003 | 杨三 | 34 | 程序员 | 厦门 | -| 1004 | 杨四 | 34 | 程序员 | 厦门 | -| 1005 | 杨五 | 34 | 程序员 | 厦门 | -| 1000 | 黄末 | 28 | 作家 | 厦门 | -| 1000 | 黄末 | 28 | 作家 | 厦门 | -| 1000 | 黄末 | 28 | 作家 | 厦门 | -| 9 | 刘一 | 33 | 建筑师 | 天津 | -| 10 | 陈二 | 26 | 程序员 | 重庆 | -| 1000 | 黄末 | 28 | 作家 | 厦门 | -| 1001 | 杨一 | 34 | 程序员 | 厦门 | -| 1002 | 杨二 | 34 | 程序员 | 厦门 | -| 1003 | 杨三 | 34 | 程序员 | 厦门 | -| 1004 | 杨四 | 34 | 程序员 | 厦门 | -| 1005 | 杨五 | 34 | 程序员 | 厦门 | -| 1000 | 黄末 | 28 | 作家 | 厦门 | -| 1000 | 黄末 | 28 | 作家 | 厦门 | -| 1000 | 黄末 | 28 | 作家 | 厦门 | - -这是第二段了` + '测试的呀,第一个表格\n\n| 序号 | 姓名 | 年龄 | 职业 | 城市 |\n| --- | --- | --- | --- | --- |\n| 1 | 张三 | 25 | 工程师 | 北京 |\n| 2 | 李四 | 30 | 教师 | 上海 |\n| 3 | 王五 | 28 | 医生 | 广州 |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 4 | 赵六 | 35 | 律师 | 深圳 |\n| 5 | 孙七 | 27 | 设计师 | 杭州 |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 7 | 吴九 | 29 | 销售 | 武汉 |\n| 8 | 郑十 | 31 | 记者 | 南京 |\n| 9 | 刘一 | 33 | 建筑师 | 天津 |\n| 10 | 陈二 | 26 | 程序员 | 重庆 |\n| 1000 | 黄末 | 28 | 作家 | 厦门 |\n| 1001 | 杨一 | 34 | 程序员 | 厦门 |\n| 1002 | 杨二 | 34 | 程序员 | 厦门 |\n| 1003 | 杨三 | 34 | 程序员 | 厦门 |', + '| 序号 | 姓名 | 年龄 | 职业 | 城市 |\n| --- | --- | --- | --- | --- |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 1004 | 杨四 | 34 | 程序员 | 厦门 |\n| 1005 | 杨五 | 34 | 程序员 | 厦门 |\n| 1000 | 黄末 | 28 | 作家 | 厦门 |\n| 1000 | 黄末 | 28 | 作家 | 厦门 |\n| 1000 | 黄末 | 28 | 作家 | 厦门 |\n| 9 | 刘一 | 33 | 建筑师 | 天津 |\n| 10 | 陈二 | 26 | 程序员 | 重庆 |\n| 1000 | 黄末 | 28 | 作家 | 厦门 |\n| 1001 | 杨一 | 34 | 程序员 | 厦门 |\n| 1002 | 杨二 | 34 | 程序员 | 厦门 |\n| 1003 | 杨三 | 34 | 程序员 | 厦门 |\n| 1004 | 杨四 | 34 | 程序员 | 厦门 |\n| 1005 | 杨五 | 34 | 程序员 | 厦门 |\n\n| 序号 | 姓名 | 年龄 | 职业 | 城市 |\n| --- | --- | --- | --- | --- |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 1000 | 黄末 | 28 | 作家 | 厦门 |\n| 1000 | 黄末 | 28 | 作家 | 厦门 |\n| 1000 | 黄末 | 28 | 作家 | 厦门 |', + '这是第二段了,第二表格\n\n| 序号 | 姓名 | 年龄 | 职业 | 城市 |\n| --- | --- | --- | --- | --- |\n| 1 | 张三 | 25 | 工程师 | 北京 |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 2 | 李四 | 30 | 教师 | 上海 |\n| 3 | 王五 | 28 | 医生 | 广州 |\n| 4 | 赵六 | 35 | 律师 | 深圳 |\n| 5 | 孙七 | 27 | 设计师 | 杭州 |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 7 | 吴九 | 29 | 销售 | 武汉 |\n| 8 | 郑十 | 31 | 记者 | 南京 |\n| 9 | 刘一 | 33 | 建筑师 | 天津 |\n| 10 | 陈二 | 26 | 程序员 | 重庆 |\n| 10004 | 黄末 | 28 | 作家 | 厦门 |\n| 10013 | 杨一 | 34 | 程序员 | 厦门 |\n\n\n结束了\n\n| 序号22 | 姓名 | 年龄 | 职业 | 城市 |\n| --- | --- | --- | --- | --- |\n| 1 | 张三 | 25 | 工程师 | 北京 |\n| 2 | 李四 | 30 | 教师 | 上海 |\n| 3 | 王五 | 28 | 医生 | 广州 |\n| 4 | 赵六 | 35 | 律师 | 深圳 |\n| 5 | 孙七 | 27 | 设计师 | 杭州 |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 6 | 周八 | 32 | 会计 | 成都 |\n| 7 | 吴九 | 29 | 销售 | 武汉 |\n| 8 | 郑十 | 31 | 记者 | 南京 |\n| 9 | 刘一 | 33 | 建筑师 | 天津 |\n| 10 | 陈二 | 26 | 程序员 | 重庆 |\n| 10002 | 黄末 | 28 | 作家 | 厦门 |\n| 10012 | 杨一 | 34 | 程序员 | 厦门 |' ] }; - const { chunks } = splitText2Chunks({ text: mock.text, chunkSize: 2000 }); - expect(chunks).toEqual(mock.result); -}); -// 表格分割测试 - 超出maxSize -it(`Test splitText2Chunks 1`, () => { - const mock = { - text: `测试的呀 - -| 序号 | 姓名 | 年龄 | 职业 | 城市 | -|------|------|------|------|------| -| 1 | 张三 | 25 | 工程师 | 北京 | -| 2 | 李四 | 30 | 教师 | 上海 | -| 3 | 王五 | 28 | 医生 | 广州 | -| 4 | 赵六 | 35 | 律师 | 深圳 | -| 5 | 孙七 | 27 | 设计师 | 杭州 | -| 6 | 周八 | 32 | 会计 | 成都 | -| 7 | 吴九 | 29 | 销售 | 武汉 | -| 8 | 郑十 | 31 | 记者 | 南京 | -| 9 | 刘一 | 33 | 建筑师 | 天津 | -| 10 | 陈二 | 26 | 程序员 | 重庆 | -| 1000 | 黄末 | 28 | 作家 | 厦门 | -| 1001 | 杨一 | 34 | 程序员 | 厦门 | -| 1002 | 杨二 | 34 | 程序员 | 厦门 | -| 1003 | 杨三 | 34 | 程序员 | 厦门 | -| 1004 | 杨四 | 34 | 程序员 | 厦门 | -| 1005 | 杨五 | 34 | 程序员 | 厦门 | -| 1000 | 黄末 | 28 | 作家 | 厦门 | -| 1000 | 黄末 | 28 | 作家 | 厦门 | -| 1000 | 黄末 | 28 | 作家 | 厦门 | -| 9 | 刘一 | 33 | 建筑师 | 天津 | -| 10 | 陈二 | 26 | 程序员 | 重庆 | -| 1000 | 黄末 | 28 | 作家 | 厦门 | -| 1001 | 杨一 | 34 | 程序员 | 厦门 | -| 1002 | 杨二 | 34 | 程序员 | 厦门 | -| 1003 | 杨三 | 34 | 程序员 | 厦门 | -| 1004 | 杨四 | 34 | 程序员 | 厦门 | -| 1005 | 杨五 | 34 | 程序员 | 厦门 | -| 1000 | 黄末 | 28 | 作家 | 厦门 | -| 1000 | 黄末 | 28 | 作家 | 厦门 | -| 1000 | 黄末 | 28 | 作家 | 厦门 | - -这是第二段了`, - result: [ - `测试的呀 - -| 序号 | 姓名 | 年龄 | 职业 | 城市 | -|------|------|------|------|------| -| 1 | 张三 | 25 | 工程师 | 北京 | -| 2 | 李四 | 30 | 教师 | 上海 | -| 3 | 王五 | 28 | 医生 | 广州 | -| 4 | 赵六 | 35 | 律师 | 深圳 | -| 5 | 孙七 | 27 | 设计师 | 杭州 | -| 6 | 周八 | 32 | 会计 | 成都 | -| 7 | 吴九 | 29 | 销售 | 武汉 | -| 8 | 郑十 | 31 | 记者 | 南京 | -| 9 | 刘一 | 33 | 建筑师 | 天津 | -| 10 | 陈二 | 26 | 程序员 | 重庆 | -| 1000 | 黄末 | 28 | 作家 | 厦门 | -| 1001 | 杨一 | 34 | 程序员 | 厦门 | -| 1002 | 杨二 | 34 | 程序员 | 厦门 | -| 1003 | 杨三 | 34 | 程序员 | 厦门 | -| 1004 | 杨四 | 34 | 程序员 | 厦门 | -| 1005 | 杨五 | 34 | 程序员 | 厦门 |`, - `| 1000 | 黄末 | 28 | 作家 | 厦门 | -| 1000 | 黄末 | 28 | 作家 | 厦门 | -| 1000 | 黄末 | 28 | 作家 | 厦门 | -| 9 | 刘一 | 33 | 建筑师 | 天津 | -| 10 | 陈二 | 26 | 程序员 | 重庆 | -| 1000 | 黄末 | 28 | 作家 | 厦门 | -| 1001 | 杨一 | 34 | 程序员 | 厦门 | -| 1002 | 杨二 | 34 | 程序员 | 厦门 | -| 1003 | 杨三 | 34 | 程序员 | 厦门 | -| 1004 | 杨四 | 34 | 程序员 | 厦门 | -| 1005 | 杨五 | 34 | 程序员 | 厦门 | -| 1000 | 黄末 | 28 | 作家 | 厦门 | -| 1000 | 黄末 | 28 | 作家 | 厦门 | -| 1000 | 黄末 | 28 | 作家 | 厦门 | - -这是第二段了` - ] - }; - - const { chunks } = splitText2Chunks({ text: mock.text, chunkSize: 512, maxSize: 512 }); + const { chunks } = splitText2Chunks({ text: mock.text, chunkSize: 300 }); expect(chunks).toEqual(mock.result); });