.agents/design/core/dataset/embedding-token-limit-guard-功能开发文档.md
知识库入库、检索和 query extension 等链路都会调用 embedding 模型。此前部分链路只按字符长度或已有分块规则控制文本大小,但 embedding provider 实际按 token 限制输入长度,因此会出现以下问题:
maxToken,导致建索引失败。本次优化目标是:让知识库文本索引按 token 预算分块,并保证最终进入 embedding 的文本不超过模型 maxToken。原始数据内容保持不变,索引数量和粒度可能随 token 计数结果变化。
本次优化遵循以下原则:
DatasetData.q / DatasetData.a 的原文内容。getVectors 是最后一道硬兜底:所有 text input 在请求 provider 前按模型 maxToken 截断;image input 不参与文本截断。文件:
packages/service/common/string/textSplitter.ts
packages/service/worker/text2Chunks/index.ts
packages/service/worker/function.ts
职责:
splitText2Chunks 放在 packages/service,前端导入预览通过后端接口调用,不再直接依赖分块实现。splitText2Chunks 新增可选 lengthUnit。默认仍按字符长度执行,token 模式使用统一的 o200k_base tokenizer。maxChunks 为预览等不可信输入提供工作量上限;空自定义分隔符、非法 overlap 和非正 chunk size 会直接报错。text2Chunks worker 承载 tokenizer 常驻内存,主 API 进程只提交可序列化的 lengthUnit 参数。边界:
packages/global,避免 global 包承担后端运行时依赖。fileCustom 分块预览改为调用后端 rawText 预览接口,因此 textSplitter.ts 可以整体迁到 service。文件:
projects/app/src/service/core/dataset/data/dataIndex.ts
新增核心流程:
原始 q/a/index text
-> 系统 q/a index 直接调用 text2Chunks 的 token 模式
-> 按 min(indexSize, embedding maxToken - prefixTokens) 生成 index
-> 外部 index 未超限时保持原文,超限时按同一 token 模式拆分
-> 再写入向量库
对应 helper:
splitIndexTextByTokenLimit
buildEmbeddingSafeIndexTexts
影响范围:
getSystemIndexes:系统默认索引直接按 token 模式和 indexSize 生成。formatIndexes:系统索引复用上一步的安全结果;外部文本索引只在超过 embedding 上限时拆分。imageEmbedding 类型索引不参与文本 token 拆分。注意:
indexes[].text,不会改 q/a 原文。indexPrefix,先拼出最终索引文本,再走 token 模式分块,保证最终送 embedding 的文本不超过模型上限。dataId。文件:
packages/service/core/dataset/search/defaultRecall/embeddingRecall.ts
处理对象:
textQueriesimageCaptionQueries处理流程:
query
-> trim/filter
-> 调用 getVectors
-> getVectors 内部按 embedding maxToken 截断 text input
注意:
文件:
packages/service/core/ai/hooks/useTextCosine.ts
处理对象:
originalTextcandidates[]处理流程:
originalText / candidates
-> 过滤空文本
-> 调用 getVectors 计算向量
-> getVectors 内部按 embedding maxToken 截断 text input
-> 继续执行原 lazy greedy selection 逻辑
注意:
selectedData 仍是原始候选文本的 trim 结果,不因为 embedding 截断改变展示内容。k <= 0,直接返回空结果和 embeddingTokens = 0。文件:
packages/service/core/ai/embedding/index.ts
处理流程:
getVectors inputs
-> 校验输入结构和空输入
-> 批量统计 text input token 数
-> 只对超限 text input 按 model.maxToken 截断
-> 按 text / image 组装 provider 请求
-> 调用 embedding provider
-> provider 返回错误时按原错误链路抛出
作用:
getVectors 只作为最后硬兜底。注意:
getVectors 只截断 text input,不会把单条输入拆成多条。image_url 结构,不走文本 token 统计。测试文件:
projects/app/test/service/core/dataset/data/dataIndex.test.ts
建议验证:
q 会生成多条 token-safe 默认索引。a 会生成多条 token-safe 默认索引。imageEmbedding index 不参与文本拆分。indexPrefix 时,按“前缀 + 正文”的最终文本计算 token。验收标准:
每条最终写入向量库的文本 index token <= 当前 embedding model.maxToken
测试文件:
packages/service/test/core/dataset/search/defaultRecall.test.ts
建议验证:
textQueries 不会在检索阶段扩增为多条 query。imageCaptionQueries 不会在检索阶段扩增为多条 query。验收标准:
检索阶段把有效文本 query 原样交给 getVectors,最终截断由 getVectors 统一兜底
测试文件:
packages/service/test/core/ai/hooks/useTextCosine.test.ts
建议验证:
originalText 超长时不会在相似度阶段扩增为多条输入。candidates[] 中超长候选不会在相似度阶段扩增为多条输入。selectedData 仍使用原候选文本。k <= 0 或无有效文本时,不调用 embedding。验收标准:
useTextCosine 只做 trim/filter 和选择逻辑,最终截断由 getVectors 统一兜底
测试文件:
packages/service/test/core/ai/embedding/index.test.ts
建议验证:
model.maxToken 截断。验收标准:
getVectors 是 embedding 超长文本的最后硬兜底;provider 错误仍能正常透出
测试文件:
projects/app/test/pages/api/core/dataset/file/getRawTextPreviewChunks.test.ts
packages/service/test/common/string/textSplitter.test.ts
建议验证:
| 或连续 ||。overlapRatio、chunkSize 必须落在安全业务范围。maxChunks 的工作量会在继续分配大数组前终止。因为本次改动在服务端逻辑,手工测试前需要:
1. 重启 app 服务。
2. 确认运行的是包含本次改动的分支。
3. 对已有数据测试时,需要重新触发更新索引或重新上传文件。
如果服务未重启,或仍查看旧数据索引,页面可能仍显示旧逻辑生成的索引数量。
操作:
1. 创建或选择知识库。
2. 上传包含超长文本的文件。
3. 等待训练任务完成。
4. 打开数据详情,查看数据索引。
预期:
1. 训练任务不应因为 embedding max-token 超限失败。
2. 数据可以正常完成索引生成。
3. 超长 index 可能被拆成多条默认索引。
4. 拆分结果不保证保持 Markdown 表格头、代码块边界或语义完整性。
操作:
1. 在知识库检索测试或应用对话中输入超长 query。
2. 触发语义检索。
预期:
1. 不应因为 query embedding 超长导致请求失败。
2. 检索会基于截断后的 query 执行。
3. 不会因为一个超长 query 扩增成多个 query。
本次不解决以下问题:
如果后续要优化索引质量,应单独设计“知识库分块质量优化”方案,不应混在 embedding max-token 兜底里。
packages/service/core/ai/embedding/tokenLimit.ts
packages/service/core/ai/embedding/index.ts
packages/service/core/ai/hooks/useTextCosine.ts
packages/service/core/dataset/search/defaultRecall/embeddingRecall.ts
packages/service/common/string/textSplitter.ts
packages/service/worker/function.ts
packages/service/worker/text2Chunks/index.ts
projects/app/src/service/core/dataset/data/dataIndex.ts
packages/global/openapi/core/dataset/file/api.ts
packages/global/openapi/core/dataset/file/index.ts
packages/global/core/dataset/training/utils.ts
projects/app/src/pages/api/core/dataset/file/getPreviewChunks.ts
projects/app/src/pages/api/core/dataset/file/getRawTextPreviewChunks.ts
projects/app/src/web/core/dataset/api/file.ts
projects/app/src/pageComponents/dataset/detail/Import/commonProgress/PreviewData.tsx
test/mocks/core/ai/embedding.ts
packages/service/test/core/ai/embedding/index.test.ts
packages/service/test/core/ai/hooks/useTextCosine.test.ts
packages/service/test/core/dataset/search/defaultRecall.test.ts
packages/service/test/common/string/textSplitter.test.ts
packages/service/test/worker/function.test.ts
projects/app/test/pages/api/core/dataset/file/getPreviewChunks.test.ts
projects/app/test/pages/api/core/dataset/file/getRawTextPreviewChunks.test.ts
projects/app/test/service/core/dataset/data/dataIndex.test.ts
本次功能验收只看一个核心结果:
FastGPT 已知上游入口会尽量把 text input 控制在对应 embedding model.maxToken 内;最终是否超限以 provider 返回为准。
入库阶段允许拆分为多条 index;检索和相似度阶段只截断,不扩增 query 数量;getVectors 批量预判 token 数并只截断超限文本。