LangChain与向量库集成:Document Loaders与Text Splitters

张开发
2026/4/6 3:35:32 15 分钟阅读

分享文章

LangChain与向量库集成:Document Loaders与Text Splitters
上周三凌晨两点我被一个奇怪的召回问题卡住了明明在PDF里写得很清楚的配置项用相似问题去查向量库总是返回一些边缘内容。打开调试日志一看发现切出来的文本片段里前半段是某个章节的结尾后半段才是下一个章节的开头——两个完全不相关的上下文被硬生生拼在一起召回质量能好才怪。这就是今天要聊的核心问题如何把文档合理地“喂”给向量数据库。很多人以为LangChain里load_and_split()调一下就行结果上线后效果稀烂。今天咱们就拆开看看Document Loaders和Text Splitters这两个看似简单、实则暗坑不少的家伙。Document Loaders别以为只是读文件先看Loader。LangChain提供了几十种文档加载器从txt、pdf到notion、github都有。但新手最容易栽在“默认参数”上。# 新手常见写法要出问题的fromlangchain.document_loadersimportPyPDFLoader loaderPyPDFLoader(spec.pdf)docsloader.load()# 坑就在这里这么写的话PDF里的表格、分栏、图片注释全混在一起文本顺序都可能错乱。我的经验是永远不要直接用默认参数处理生产文档。# 调优后的写法loaderPyPDFLoader(spec.pdf,extract_imagesFalse,# 除非做OCR否则先关掉header_footer_filterTrue,# 页眉页脚经常是噪声concatenate_pagesFalse# 先不分页后面统一处理)raw_pagesloader.load()# 看一眼元数据print(f总页数:{len(raw_pages)})print(f第一页字符数:{len(raw_pages[0].page_content)})print(f元数据keys:{raw_pages[0].metadata.keys()})每个Loader都有自己特有的参数。比如UnstructuredFileLoader处理Word时可以指定modeelements来保留标题层级CSVLoader可以设置source_column指定哪一列是文本主体。关键一步加载完先抽样检查别急着往下走。Text Splitters这里踩的坑最多文本拆分是向量化质量的关键。最经典的错误就是直接按固定长度切# 灾难性切法千万别这样写fromlangchain.text_splitterimportCharacterTextSplitter splitterCharacterTextSplitter(chunk_size1000,chunk_overlap200)chunkssplitter.split_documents(docs)这样切出来的片段大概率在句子中间、单词中间甚至代码中间断开。召回时这些“断肢残臂”的向量根本匹配不上完整的问题。实战推荐的组合拳fromlangchain.text_splitterimport(RecursiveCharacterTextSplitter,Language,MarkdownHeaderTextSplitter)# 场景1通用技术文档混合代码和说明splitterRecursiveCharacterTextSplitter.from_language(languageLanguage.MARKDOWN,# 按markdown语法感知chunk_size800,# 比想象中小一点召回更准chunk_overlap150,separators[\n## ,\n### ,\n\n,\n, ]# 按标题优先切)# 场景2API文档保留层级headers_to_split_on[(#,Header1),(##,Header2),(###,Header3),]markdown_splitterMarkdownHeaderTextSplitter(headers_to_split_onheaders_to_split_on,strip_headersFalse# 保留标题在内容里增强上下文)# 场景3源代码文件code_splitterRecursiveCharacterTextSplitter.from_language(languageLanguage.PYTHON,chunk_size600,# 代码可以小一点chunk_overlap100,separators[\nclass ,\ndef ,\n\n,\n, ]# 按类/函数边界切)重叠overlap不是随便设的。我的一般原则技术文档重叠150-200字符确保关键段落不被切断对话记录重叠300因为一句话可能跨行代码重叠100左右主要保证函数签名完整元数据被低估的召回增强器拆分时保留的元数据后面可以用于过滤或加权。很多人只存个source字段浪费了大好机会。# 好的元数据设计chunk_with_metadata{page_content:具体文本内容...,metadata:{source:spec.pdf,page:42,section:性能指标,# 手动或自动提取的章节doc_type:API参考,last_updated:2024-03,importance:0.8# 人工标注的重要度}}在向量检索时可以用section做预过滤只查相关章节用importance做后排序加权用doc_type区分处理标准文档和FAQ调试流水线眼见为实我必用的调试代码片段defdebug_splitting(docs,splitter,sample_num3):肉眼看看切得对不对chunkssplitter.split_documents(docs)print(f总片段数:{len(chunks)})print(-*50)foriinrange(min(sample_num,len(chunks))):print(f片段 #{i1}:)print(f长度:{len(chunks[i].page_content)})print(f前100字符:{chunks[i].page_content[:100]}...)print(f后100字符:{...chunks[i].page_content[-100:]})print(f元数据:{chunks[i].metadata})print(*50)# 检查边界情况print(\n⚠️ 检查切碎情况:)short_chunks[cforcinchunksiflen(c.page_content)50]print(f过短片段(50字符):{len(short_chunks)}个)returnchunks跑一下这个你可能会发现有些片段只有标点符号需要过滤表格数据被拆得支离破碎需要换专用处理器代码注释和正文分家了需要调整分隔符个人经验包先分析文档结构再选方案。技术手册、会议记录、源代码它们的合理切法完全不同。拿10%的样本做拆分实验比盲目调参强。chunk_size不是越大越好。512-1024是常见范围但具体要看你的嵌入模型。比如有些模型对256-512的输入优化更好。测试时用召回率评估而不是感觉。重叠区域要包含完整语义单元。检查overlap区域是否在句子结尾开始、在句子开头结束。我常写个检查函数确保重叠部分至少包含一个完整句子。保留层级信息。把章节标题、列表前缀塞进片段内容里比如“## 安装步骤\n1. 先装依赖…”这样向量化时会带上结构信息。拆分阶段就考虑召回策略。如果你打算用元数据过滤拆分时就要标记清楚如果想做多向量召回比如同时查标题和正文拆分时就可以准备两个版本。监控生产数据。上线后统计片段长度分布、召回命中率、用户点击反馈。发现某些文档类型效果差就针对性优化拆分策略。最后说一句文档加载和拆分看着像脏活累活但这是Agent知识库的“地基”。地基歪了后面用再好的模型、再 fancy 的检索算法都白搭。花两天时间把这块调稳比后面折腾两个月召回算法都值。

更多文章