拓冰建站拓冰建站
首页 / 资讯中心 / 正文

LangChain.js @langchain/textsplitters 详解:RAG 文本切分器的实现原理、参数调优与包开发指南

LangChain.js langchain/textsplitters 详解RAG 文本切分器的实现原理、参数调优与包开发指南【免费下载链接】langchainjsThe agent engineering platform项目地址: https://gitcode.com/GitHub_Trending/la/langchainjs本篇基于仓库内 libs/langchain-textsplitters/README.md 及配套源码展开讲解 LangChain.js 官方文本切分包的安装与开发流程、六类切分器的 API 与默认参数、mergeSplits/递归切分等核心算法的实现细节以及如何在本仓库中构建、测试并扩展该包。读完你可以独立完成 RAG 管道中的文档切分选型与参数调优并按仓库规范对该包进行二次开发。一、包的定位与安装README 开篇说明langchain/textsplitters包含 LangChain.js 中各类文本切分器Text Splitter的实现最常见的用途是作为 RAG检索增强生成管道的文档预处理组件把长文档切分为适合嵌入模型与检索系统处理的片段。安装命令README 原文npm install langchain/textsplitters langchain/core需要同时安装langchain/core并非偶然。从 package.json 可以看到peerDependencies声明了langchain/core: ^1.0.0即langchain/core是同级依赖切分器基类直接继承其中的BaseDocumentTransformer运行依赖只有一个js-tiktoken^1.0.12供TokenTextSplitter按真实 tokenizer 计长运行环境要求node 20type: module产物同时提供 ESMdist/index.js与 CJSdist/index.cjs两套入口。包对外只暴露单一入口src/index.ts 仅有一行export * from ./text_splitter.js所有可导出的类都集中在 src/text_splitter.ts 中。二、核心 API切分器家族与通用参数2.1 抽象基类 TextSplitter 及其默认参数所有切分器继承抽象类TextSplittertext_splitter.ts L20-L228它在构造时接受TextSplitterParams参数类型默认值说明chunkSizenumber1000每个片段的目标长度上限chunkOverlapnumber200相邻片段间的重叠长度keepSeparatorbooleanfalse切分时是否保留分隔符本体lengthFunction(text) number \| Promisenumber(text) text.length自定义长度计算函数三个要点直接从源码得到印证参数校验是硬性约束构造函数中if (this.chunkOverlap this.chunkSize) throw new Error(Cannot have chunkOverlap chunkSize)L43-L45。仓库测试Test invalid argumentstext_splitter.test.ts L77-L84即用chunkSize: 2, chunkOverlap: 4验证该异常。长度函数可自定义默认按字符数text.length计长传入 tiktoken 计数函数或按词计数函数即可改变“1000”的含义mergeSplits中所有长度计算都走this.lengthFunction因此异步长度函数也被支持。切分器即文档转换器基类继承langchain/core的BaseDocumentTransformertransformDocuments(documents, chunkHeaderOptions)直接委托给splitDocumentsL48-L53因此切分器可以像其他 DocumentTransformer 一样被串进langchain/core的 Runnable 文档管道。2.2 CharacterTextSplitter单分隔符切分最基础的实现围绕一个固定separator默认\n\n即按空行切段工作import { CharacterTextSplitter } from langchain/textsplitters; const splitter new CharacterTextSplitter({ separator: , chunkSize: 7, chunkOverlap: 3, }); const output await splitter.splitText(foo bar baz 123); // [foo bar, bar baz, baz 123]上面的参数与期望输出取自单元测试Test splitting by character counttext_splitter.test.ts L16-L27。其splitText实现是“先按分隔符朴素拆分再交给mergeSplits合并”L249-L253。2.3 RecursiveCharacterTextSplitter最常用的递归切分器默认参数L282-L296separators: [\n\n, \n, , ]——按“段落 → 行 → 词 → 字符”的粒度从粗到细降级keepSeparator默认被构造函数覆盖为true注意与基类默认的false不同这是该切分器的刻意设计。典型用法与 examples 中的 recursive_text_splitter 示例 一致import { RecursiveCharacterTextSplitter } from langchain/textsplitters; const text Hi.\n\nIm Harrison.\n\nHow? Are? You?\nOkay then f f f f. ...; const splitter new RecursiveCharacterTextSplitter({ chunkSize: 10, chunkOverlap: 1, }); const output await splitter.createDocuments([text]); console.log(output); // Document 数组每个 chunk 带 loc.lines 元数据单测Test iterative text splitterL180-L210验证了chunkSize: 10, chunkOverlap: 1下的逐词降级行为例如Okay then f f f f.被切为Okay then与f f f f.。fromLanguage16 种语言的预置分隔符静态方法RecursiveCharacterTextSplitter.fromLanguage(language, options)可按语言生成切分器L351-L360。SupportedTextSplitterLanguages常量L260-L277列出了受支持语言cpp、go、java、js、php、proto、python、rst、ruby、rust、scala、swift、markdown、latex、html、sol。getSeparatorsForLanguageL362-L709为每种语言定义了“结构优先”的分隔符序列例如python先按\nclass、\ndef、\n\tdef切类与函数定义再降级到\n\n、\n、、markdown先按二级到六级标题\n##…\n######、代码块结束\n\n、水平线\n\n***\n\n等切分最后才降级到普通行与词solSolidity按pragma、contract、interface、function、event等 Solidity 结构关键字切分。源码注释中还明确了 markdown 分隔符表的局限不支持“标题下划线”式的二级标题语法也不处理用 3 个以上*/-/_定义的水平线L597-L607。2.4 TokenTextSplitter按真实 token 切分TokenTextSplitterL721-L772让chunkSize直接对应 tokenizer 的 token 数参数如下L712-L716参数默认值说明encodingNamegpt2tiktoken 编码名TiktokenEncoding类型如gpt2、r50k_base等allowedSpecial[]允许保留的特殊 tokendisallowedSpecialall禁止的特殊 token实现上有两个值得注意的细节tokenizer 惰性初始化首次splitText时才通过langchain/core的getEncoding(this.encodingName)加载L746-L748避免构造期同步加载 tiktoken 数据token 级滑动窗口先encode整段文本得到 token id 数组再以chunkSize为步长、chunkOverlap为回退量做窗口切分最后decode回文本L758-L768。因为回退发生在 id 层面窗口会从前一个窗口的中途开始所以重叠片段的开头可能带半截词——单测Token text splitter overlap when last chunk is large的期望输出 baz a aL368-L379正体现了这一现象。import { TokenTextSplitter } from langchain/textsplitters; const splitter new TokenTextSplitter({ encodingName: r50k_base, chunkSize: 3, chunkOverlap: 0, }); const output await splitter.splitText(foo bar baz a a); // [foo bar b, az a a]2.5 MarkdownTextSplitter 与 LatexTextSplitterMarkdownTextSplitterL776-L787与LatexTextSplitterL791-L802是RecursiveCharacterTextSplitter的薄封装构造函数把用户传入的参数与对应的语言分隔符表合并后透传给父类。也就是说它们等价于RecursiveCharacterTextSplitter.fromLanguage(markdown | latex, options)但提供了语义更清晰的类名。两者的实际切分效果有对应单测背书MarkdownL381-L408chunkSize: 100下一级标题与副标题被合并为一个 chunk## Quick Install及其 bash 代码块主体保持完整收尾单独成块——验证了“代码块结束符作为分隔边界”的设计意图LaTeXL410-L439\section{Quick Install}独立成块verbatim环境整体保留不被打断。三、实战操作splitText、createDocuments 与 chunk 头部切分器提供三个层级的输出接口由 text_splitter.ts 中同名方法实现splitText(text): Promisestring[]——纯文本进、字符串数组出适合先快速验证切分效果抽象方法各子类实现createDocuments(texts, metadatas?, chunkHeaderOptions?): PromiseDocument[]——文本数组进、Document数组出L75-L160。metadatas缺省会为每个文本生成空对象每个产出的Document的metadata.loc.lines会被自动写入该片段在原文中的起止行号{ from, to }行号统计基于切分前文本中的\n计数numberOfNewLinesL162-L165因此 RAG 返回检索片段时可以直接给出“原文出处”的行级定位transformDocuments(documents, chunkHeaderOptions?): PromiseDocument[]——Document数组进、Document数组出L48-L53保留原文档 metadata 并叠加loc.lines是接入BaseDocumentTransformer管道的标准入口。chunkHeaderOptions为每个片段注入来源头createDocuments/splitDocuments第三个参数TextSplitterChunkHeaderOptionsL14-L18选项默认值说明chunkHeader追加到每个片段开头的头文本典型用途是写来源文件名chunkOverlapHeader(contd) 续片段的额外头仅在开启appendChunkOverlapHeader时生效appendChunkOverlapHeaderfalse是否给同一段源的后续片段追加chunkOverlapHeader单测create documents ... with metadata and an added chunk headerL126-L157展示了完整效果chunkHeader: SOURCE NAME: testing\n-----\nappendChunkOverlapHeader: true时三段输出依次为...testing\n-----\nfoo、...testing\n-----\n(contd) bar、...testing\n-----\nbaz——首片段加来源头续片段再加(contd)前缀不同文本baz来自第二个源重新从chunkHeader开始。四、源码级机制splitOnSeparator 与 mergeSplits4.1 splitOnSeparator分隔符的三种切法protected splitOnSeparator(text, separator)L57-L73根据keepSeparator走三条路径keepSeparator true先把分隔符中的正则元字符转义再用前瞻断言new RegExp((? 转义分隔符 ))拆分——切点落在分隔符之前从而保留分隔符本体keepSeparator false直接text.split(separator)分隔符被丢弃separator为空字符串text.split()逐字符拆分这是递归降级到最细粒度时的兜底。最后filter((s) s ! )过滤空串保证后续合并不会产生空文档。4.2 mergeSplits合并、重叠与越界警告mergeSplits(splits, separator)L184-L227是所有字符类切分器共用的“装箱”算法核心逻辑累加total当前候选片段的长度和且计入分隔符占位判断条件为total _len currentDoc.length * separator.length chunkSize。从源码结构看keepSeparator: false时合并用的separator是非空分隔符本身因此即使分隔符已被丢弃其长度仍被计入预算——单测Separator length is considered correctly for chunk sizeL342-L353专门验证了这一点chunkSize: 7下aa ab ac ba bb切成[aa ab, ab ac, ac ba, ba bb]当currentDoc非空且预算超限时先joinDocsjoin(separator).trim()纯空白结果返回null被丢弃产出当前片段然后用 while 循环弹出队首元素直到total chunkOverlap或继续加入会再次超预算——这就是片段重叠的实现方式弹出后剩余元素成为下一个片段的开头若单个 split 本身就超过chunkSizetotal this.chunkSize时会console.warn提示“Created a chunk of size ... longer than the specified ...即超长词/超长行无法再被该层切分器打断只能靠递归切分器降级到更细的分隔符解决。4.3 递归切分器如何选分隔符RecursiveCharacterTextSplitter._splitTextL298-L345的算法是“选分隔符 装箱 递归”三合一顺序扫描separators取第一个在文本中出现的分隔符遇到空串则直接锁定因为它是终极粒度后续分隔符存为newSeparators按该分隔符拆分后逐段判断长度 chunkSize的进入goodSplits缓冲最终统一mergeSplits长度 ≥chunkSize的段先把已缓冲的goodSplits落盘然后若无更细分隔符则该段原样输出超长警告场景否则递归调用_splitText(段, newSeparators)用更细的粒度重试。这解释了 2.3 节测试中为什么f f f f.6 个字符 句号在chunkSize: 10下保持整词不拆、而更长的句群会先按空格拆、再按字符兜底。五、开发工作流在本仓库中构建、测试与扩展以下内容继承自 README 的 Development 一节并结合当前 package.json 的实际 scripts 做了核对。5.1 安装依赖与构建pnpm install pnpm build或者从仓库根目录按包过滤构建pnpm build --filter langchain/textsplitterspackage.json中的build脚本实际是turbo build:compile --filter langchain/textsplitters --output-logs new-only而build:compile调用 tsdown.config.ts 里的getBuildConfig({ entry: [./src/index.ts], plugins: [cjsCompatPlugin(...)] })——即单入口src/index.ts由langchain/build的工具链同时产出 ESM/CJS 双产物及类型声明与exports字段中./dist/index.cjs、./dist/index.js的映射一一对应。5.2 测试约定README 规定测试文件放在src/下的tests/目录单元测试以.test.ts结尾集成测试以.int.test.ts结尾运行方式为pnpm test pnpm test:int需要说明的是当前 package.json 的scripts中实际定义了testvitest --run与test:watchvitest --watch两个脚本未定义test:int从现有测试布局src/tests/text_splitter.test.ts 与code_text_splitter.test.ts均为.test.ts看当前所有用例都是可离线运行的单元测试。跑测试时以pnpm test为准。测试文件对关键行为的覆盖可作为行为规格参考CharacterTextSplitter的 5 组切分用例含超长词、短词在前、不产生空文档、非法参数抛错、createDocuments的 metadata 透传与loc.lines注入、chunk header 拼接、RecursiveCharacterTextSplitter的迭代切分/重叠/含空行文档的行号统计、TokenTextSplitter的两种场景以及 Markdown、LaTeX、HTMLfromLanguage(html)的端到端切分text_splitter.test.ts全文件 516 行。5.3 Lint 与格式化pnpm lint pnpm format5.4 新增导出入口README 给出的扩展方式如果导入了新文件要么在 src/index.ts 中 import 并 re-export要么把它加进package.json的exports字段然后运行pnpm build生成新入口。当前包只有.与./package.json两个导出面package.json L44-L57因此新增子路径导出时exports与filesdist/、CHANGELOG.md、README.md、LICENSE的配套修改都应一并检查。六、选型速查结合上述实现与测试证据可按如下标准选型通用正文/混合文本RecursiveCharacterTextSplitter默认chunkSize: 1000, chunkOverlap: 200, keepSeparator: true需要按语言优化结构边界时用fromLanguage(...)Markdown 文档MarkdownTextSplitter标题、代码块边界优先LaTeX 论文LatexTextSplittersection/environment边界优先需要严格贴合模型 token 预算TokenTextSplitter注意chunkSize此时是 token 数且重叠窗口可能从词中间开始固定格式文本CharacterTextSplitter显式指定separator。无论选哪个都建议用createDocuments而非裸splitText产出片段以获得loc.lines行级溯源与 metadata 透传把chunkOverlap控制在明显小于chunkSize的范围内两者相等会直接抛错并通过 examples/src/langchain-classic/indexes/recursive_text_splitter.ts 这类最小脚本先打印实际切分结果再确定chunkSize/chunkOverlap的取值。【免费下载链接】langchainjsThe agent engineering platform项目地址: https://gitcode.com/GitHub_Trending/la/langchainjs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门