资讯动态

LangChain实战:RAG与Agent开发中的向量检索与增强检索全解析

发布时间:2026/10/19 13:18:27 来源:尧图企业网站定制
做RAG开发有段时间了从最早的文档问答Demo到后来相对完整的Agent智能体应用这条路踩过的坑确实不少。这篇把基于LangChain做RAG与Agent开发的主线完整整理一遍重点放在向量存储、向量检索、以及RAG增强检索实现这三块。适合已经能调通基础大模型接口、想自己搭一套知识库问答或智能助手的朋友。我会先把整体设计思路讲透再给出可以直接落地参考的代码与参数配置最后把实际开发中常见的故障和排查方法一并聊清楚尽量让这篇能当一份实用手册用。1. 项目整体设计为什么选择RAG Agent这条技术路线1.1 RAG要解决的核心问题先聊一个最基本的问题大模型看起来什么都知道为什么还需要RAG我在项目里最直观的感受是只要问题涉及企业私有数据模型几乎必然开始“一本正经地编”。比如你问它“我们公司的年假制度是什么”它能给你生成一份看起来完全合理的答案但内容全是幻觉。这类问题的根源在于模型训练时根本没见过这些数据它的知识边界和你的业务边界是两条线。RAG解决的正是这个“不知道但必须答”的困境。思路也很简单在模型回答之前先从外部知识库里检索出和问题最相关的那些片段把这些片段拼进上下文窗口让模型基于这些材料作答。相当于从闭卷考试改成开卷考试不改变模型参数却能在回答瞬间赋予它原本没有的知识。和微调相比RAG在知识更新、成本、可解释性上的优势很明显。微调一次需要准备大量带标签的数据、话费不少训练资源而且知识一旦变化就得重新微调RAG不需要改模型只要把最新的文档灌进向量库问题拿到手就能用新知识回答整个过程可以按小时级甚至分钟级完成迭代。所以我给自己定了一条选型原则凡是知识内容变化频繁、答案需要引据的业务场景优先考虑RAG而不是微调。1.2 LangChain在整个架构里的定位LangChain是我选定的编排层框架。它把RAG全链路抽象成了几个标准模块加载器负责从各种来源读文档文本分割器负责切片Embedding模型负责把文本向量化向量库负责存储和检索检索器提供统一接口最后再通过提示模板和语言模型组装成问答链路。这套抽象最大的价值在“替换性”。今天你用的是本地嵌入式向量库明天数据量上来了要切分布式向量库只要是LangChain支持的实现业务代码基本不用动换一个类、改几行初始化逻辑就行。类似的Embedding模型从开源小模型换成长文本大模型也只需要替换对象实例。这种可替换性在项目初期帮助很大因为你没法一开始就预测数据规模会涨到多少、线上并发会到什么程度。当然LangChain也有它的毛病比如版本迭代快、抽象层多、报错信息有时不够直观。我在项目里会固定版本升级前先跑一遍回归测试避免被链式调用API的变化打个措手不及。这里额外说一句框架只是工具核心还是要理解底层原理不然换个版本或者换个组件你会连问题都定位不准。1.3 Agent智能体与RAG的分工配合普通RAG是固定流程每个问题进来都无条件执行“检索→拼接→生成”这一套动作。但现实场景里用户问的问题很杂有些需要检索知识库有些其实只需要常识对话有些甚至要查结构化数据并做计算。这时候把RAG硬套在每个问题上既浪费资源又容易答非所问。Agent智能体的引入改变了这个模式。Agent的核心是让模型自己决定“下一步做什么”判断这个问题需不需要检索、需要检索哪一个知识库、要不要调用计算工具、如果有多个工具结果是否要交叉验证。RAG在Agent架构里不再是一个固定串联的流程而是一个可以被动态调用的“查资料能力”。举个例子用户问“这款产品的退货率在最近三个月是上升还是下降”。如果走普通RAG你会从文档里查出一堆关于产品介绍的文字然后让模型猜个答案。但如果走AgentRAG模型可以先调用一个退货率统计工具拿真实数据需要补充产品背景时再调用产品知识库检索工具最后把两部分结果综合成结论。这种分工方式让RAG的定位更清晰它负责提供静态知识而Agent负责调度这些知识源去解决复杂问题。2. 向量化与向量存储原理拆解、选型对比与核心参数2.1 Embedding到底做了什么向量化的本质是把一段文本映射成一个高维空间的坐标点也就是一个浮点数组。比如一个384维的Embedding就是把文本变成了384个浮点数。这串数字本身没有直观语义但它有一个重要性质语义相近的文本在向量空间里的距离也相近。“今天天气不错”和“今天天气很好”这两句话向量距离会很近而“今天天气不错”和“Python闭包怎么写”这两句距离就会很远。这个性质是向量检索的地基。检索时系统把用户问题也转成向量然后在向量库里找与它最接近的那些向量对应的原始文本就是召回结果。整个过程不需要死板的关键词匹配所以哪怕用户问题和文档原文用词不完全一致只要语义相近也能被找出来。实际开发中要注意的是Embedding模型有输入长度限制。绝大多数模型对单条文本的长度有上限可能是256个token也可能是8192个token。超出的文本必须先切分再向量化。这也是为什么向量化之前必须先做切片切片做不好后面一切检索效果都是空中楼阁。另外Embedding的维度不是越高越好高维度表达能力理论上更强但存储成本、检索耗时都会上涨。选模型时要结合数据量、硬件和场景综合判断。2.2 主流向量存储方案的选型逻辑向量存到哪里是项目里第一个要做的技术决策。按我自己的经验可以从数据规模、并发量、运维成本三个维度去筛选。这里把常见的几类方案做一个对照方案类型典型定位数据规模并发能力运维成本适用场景嵌入式单机库进程内运行随应用启动百万级以下低受单进程限制极低适合原型验证个人项目、Demo、内部小工具内存级索引库纯内存或本地持久化百万级到千万级中等偏上低中小规模生产环境分布式向量库服务化部署多节点扩展千万级以上高支持水平扩展高需要独立运维生产环境、多租户、高并发检索关系库扩展在已有数据库上增加向量字段和索引百万级到千万级取决于原库能力低依托现有运维体系存量业务已有关系库不想多维护一套系统我个人的选型习惯是项目还没跑明白、数据量在几十万条以内时优先用嵌入式单机库把精力放在验证检索质量和产品逻辑上等确认数据量要增长、并发要求也上来之后再迁移到分布式方案。千万别一上来就上重型组件架构先行有时反而拖慢项目节奏。2.3 相似度计算与检索参数设置向量库里最常用的相似度计算有三种余弦相似度、点积、欧氏距离。余弦相似度衡量的是两个向量的方向一致性范围在-1到1之间越接近1表示越相似。点积则同时受方向和大小的双重影响如果向量在入库前都做了归一化处理即模长化为1那么点积计算等价于余弦相似度而且计算起来更快。欧氏距离反映的是点与点在空间中的直线距离距离越小越相似。选哪一种没有绝对标准关键看Embedding模型的使用说明。很多模型官方推荐用余弦相似度也有的建议用点积。我在项目里的做法是先看模型文档推荐文档没说就搭建时用一小批人工标注的相似问题对分别用三种方式算一遍分数和召回率哪个效果好就用哪个。别迷信“大家都用XX”在向量这块实测数据最可靠。检索参数里最常见的是TopK也就是返回多少条相关片段。TopK太小比如只取1到2条漏召回的概率很大TopK太大比如取20条又会把一堆不相关的噪声塞进上下文不仅浪费token还可能干扰模型判断。我的常规起点是4到6条然后根据问答效果做微调。复杂问题可以适当提高到8条同时结合重排序模块来控制最终进入上下文的内容质量。2.4 图索引与搜索参数的理解想深入一点的同学会接触到图索引尤其是HNSW这类算法。HNSW把向量组织成多层图结构上层稀疏、连接稀疏的节点用于快速定位大方向下层稠密、连接更细用于精细查找。搜索时从顶层快速下降到底层在保证速度的同时尽量不牺牲召回精度。HNSW有三个核心参数值得记一下M控制每个节点的最大连接数M越大索引越精细但内存占用更高efConstruction控制建索引时的动态列表大小越大建索引越慢但质量越好efSearch控制查询时的搜索宽度越大召回质量越高但延迟越高。我的建议是建索引阶段先把质量做上去M取16到32efConstruction取100到200线上查询阶段再根据延迟预算调整efSearch一般从50开始试。这个参数调优过程有点像给地图做分层路修得越密导航越准但地图文件越大。实际项目里如果你发现检索经常漏结果不要急着换Embedding模型先把HNSW的efSearch调大试试往往会有立竿见影的效果。3. RAG增强检索全流程实现从文档加载到完整链路3.1 文档加载与清洗这些坑必须提前避开做过RAG的人都知道文档是整个系统的粮食粮食坏了后面的加工环节做得再好也白搭。我处理过的文档类型包括PDF、Word、网页、数据库导出的文本每一类都有自己的坑。PDF是最麻烦的。很多PDF看起来是文字实际内部是扫描图片直接提取只能得到一堆乱码或者空内容。这种必须先过一遍OCR。还有些PDF的排版是多栏布局按阅读顺序提取时会左右穿插导致语义断裂。Word文档相对好一点但要警惕表格内容被拆得七零八落。网页文件抓下来之后导航栏、页脚、广告这些噪音信息混在正文里不去掉会让每个切片向量都“掺沙子”严重拉低检索质量。清洗环节我一般做四件事去重、去噪、统一编码、修正结构。去重针对的是同一份文档在多个位置重复出现去噪是把无关页眉页脚和模板内容清理掉统一编码是确保中文不乱码修正结构是把标题和正文的关系保留下来方便后面按结构切片。这些工作看着琐碎但省掉的每一步都会在后面变成一个个奇怪的bug。3.2 切片策略chunk size和overlap怎么定切片的粒度直接影响检索效果。切片太大一个片段里包含多个主题检索时命中可能只和其中一小段相关却把其他无关内容也带进上下文稀释注意力。切片太小单个片段语义信息不完整比如一句话被截断后半句丢了向量化后表达的含义可能完全变味。我常用的核心参数是chunk_size每片大小和chunk_overlap相邻切片的重叠量。在中文场景我习惯按token数而不是字符数来设置因为Embedding模型和大语言模型都按token计费、按token限长。初始值一般设在400到800 token之间。overlap通常取chunk_size的10%到20%也就是40到80 token。overlap的作用是防止关键句刚好被切在边界处——有了重叠那句被截断的信息能在相邻两个切片的任何一个里完整出现。切片方式上固定长度硬切是最后的选择。更好的方式是按结构切比如按文档标题层级、按段落、按句子树状递归分割。LangChain里的递归字符分割器就是这种思路它按从大到小的分隔符逐级寻找切分点保持段落和句子的完整性。更进一步还可以用语义切分即判断相邻句子语义是否明显变化在语义转折处切开。效果更好但开销也更大。3.3 Embedding模型选型与效果验证Embedding模型决定向量检索的上限。模型选不好后续所有优化都是在弥补选型错误。中文场景下我优先考虑对中文支持好、且能在中英文混合查询下保持稳定的模型。本地部署时我通常选择参数量适中、向量维度较低的开源小模型比如384维或768维的版本兼顾效果和资源占用。选模型的一个重要参考是MTEB中文榜单但不能唯榜单论。榜单得分反映的是通用场景的平均水平而你的业务领域可能有大量专业术语比如医疗、法律、工业制造这些词汇在通用语料里少见模型表现会打折扣。所以我的流程是选定两三个候选模型准备一份覆盖业务典型问题的测试集对每个问题手工标出应召回的文档和片段然后分别跑一遍召回计算RecallK指标用数据决定最终用哪个。这里说一个容易忽略的点Embedding模型和大语言模型的“语言风格”最好匹配。如果问题端和文档端的语言风格差异很大比如用户口语化提问、文档却全是正式书面语向量检索的表现会明显下降。针对这种情况可以在切片阶段做“问题改写”也可以训练一个适配业务语料的模型但这是后话初版先用通用模型跑通闭环更重要。3.4 从入库到检索的完整链路下面给出一段浓缩的完整链路代码对应“加载→切片→向量化→入库→检索→生成”的全过程。这里用的是抽象的类名实际项目按你选定的组件替换成对应实现即可。import os # 1. 加载文档按目录批量读取 from langchain_community.document_loaders import DirectoryLoader loader DirectoryLoader(./docs, glob**/*.md) docs loader.load() print(f加载文档数: {len(docs)}) # 2. 切片递归按结构切分 from langchain_text_splitters import RecursiveCharacterTextSplitter splitter RecursiveCharacterTextSplitter( chunk_size500, # 目标切片大小按token估算 chunk_overlap80, # 重叠量 separators[\n\n, \n, 。, , , , ], ) chunks splitter.split_documents(docs) print(f切片总数: {len(chunks)}) # 3. 向量化使用本地Embedding模型封装 # 不同Embedding模型的加载方式不同这里替换为你实际的模型封装即可 embed_model LocalEmbeddingModel() # 本地开源中文向量模型 # 4. 写入向量库并构建索引 vector_store MyVectorStore.from_documents(chunks, embed_model) # 5. 转为检索器 retriever vector_store.as_retriever( search_typesimilarity, search_kwargs{k: 5} ) # 6. 检索测试 question 公司年假制度中工作满一年的员工可以休几天 hits retriever.invoke(question) for i, doc in enumerate(hits): print(f[{i}] {doc.page_content[:100]})检索器有两种常用模式。默认的是相似度检索直接按向量距离返回最接近的K条。另一种是MMR最大边际相关性它在相关性和多样性之间做平衡返回的结果不会全部集中在某一个语义角落适合内容有重叠的场景。代价是检索速度稍慢一些。我的习惯是初版先用相似度检索跑通等遇到“结果都来自同一段落、覆盖面变窄”的问题时再切MMR。生成阶段把检索结果拼进提示词让大模型基于这些材料作答from langchain_core.prompts import ChatPromptTemplate prompt ChatPromptTemplate.from_messages([ (system, 你是一个企业内部知识库助手。你只能根据给定的上下文回答问题。 如果上下文中没有相关信息请明确回答不知道不要编造内容。), (human, 上下文\n{context}\n\n问题{question}), ]) def generate_answer(question: str): docs retriever.invoke(question) context \n\n---\n\n.join(d.page_content for d in docs) messages prompt.format_messages(contextcontext, questionquestion) # 这里的 llm 是你项目里已配置好的大模型实例 response llm.invoke(messages) return response.content这里最关键的一条经验是生成阶段如果发现答案不满意不要只顾着调提示词和模型参数先回去看检索阶段返回的内容是否靠谱。RAG这条链路里百分之八十的错误都出在“没找到对的材料”而不是“模型不会答”。3.5 增强检索的进阶手段多路召回、重排与父文档召回搞定了基础链路之后会发现效果到达一个瓶颈。这时候可以考虑进阶的增强检索策略我实际用过且见效明显的有三种。第一多路召回。不只用向量检索一路而是同时用传统的关键词检索比如BM25两者结果合并去重后再统一排序。向量检索擅长语义模糊匹配关键词检索擅长精确命中专有名词和编号。很多业务问题里客户会直接报出“编号”“型号”这样的精确词单纯向量检索反而容易忽略这类精确匹配。双路召回之后整体召回率会明显上升。第二重排序。多路召回或单路召回的结果集会比较杂向量相似度排序不一定符合用户真实意图。做法是引入一个重排序模型对候选的几十条片段再做一轮精细打分然后取分数最高的几段进上下文。我实测的体感是重排序之后生成答案的准确率提升非常显著而且TopK可以适当放大因为后面的“垃圾”会被排在前面。第三父文档召回。切片过细会导致单个片段丢失上下文。父文档召回策略是检索时先找到最相关的小片段然后返回它所归属的更大父片段。比如按句子切分用于向量检索但最终喂给大模型的是这段句子所在的整个段落甚至整个章节。这样既保留了细粒度检索的精度又保证了上下文完整性。这三招不用一次全上我建议按“重排序→父文档召回→多路召回”的顺序逐步叠加每一步都通过人工检查案例来评估收益避免过度复杂化。4. Agent智能体与增强检索的融合实战4.1 Agent的核心工作方式从ReAct到工具调用Agent之所以能“智能”是因为它把“思考”和“行动”做成了一个循环。以经典的ReAct模式为例模型每一步先推理当前局面判断需要什么信息然后调用某个工具拿到工具返回结果后继续推理直到认为信息充足给出最终答案。在LangChain里这个机制通常体现为工具调用。模型在生成文本的同时可能输出一个结构化的工具调用请求里面包含要调用的函数名和参数。框架解析到这个请求后执行对应函数把返回值再塞回模型上下文让模型继续处理。整个过程并不神秘本质上是让模型掌握了几把“钥匙”它来决定何时用哪一把。和固定流程的Chain相比Agent的好处是灵活。比如一个知识库问答应用普通RAG每个问题都先检索再生成Agent则先判断问题是否需要检索如果需要继续分析应该查哪个文档组还可能需要反复用不同关键词查多次。这种动态决策在高复杂度的问答场景里优势明显。4.2 把Retriever封装成Agent工具在Agent中接入RAG就是把检索器包装成一个工具函数并给这个工具一个清晰的名字和描述。下面是一段示例from langchain_core.tools import tool tool def search_company_policy(query: str) - str: 在企业制度文档中检索信息。 当用户询问考勤、请假、报销、薪酬、福利等制度相关内容时必须调用本工具。 docs retriever_invoke_with_query(query) if not docs: return 未检索到相关内容请如实告知用户知识库中暂无相关信息。 return \n\n---\n\n.join(d.page_content for d in docs)这段代码里真正决定Agent会不会正确使用工具的是那段描述文字。描述写得越具体模型的判断越准。如果只是写成“查询文档”Agent就可能在回答常识问题时也调它白白增加延迟写成“当用户询问考勤、请假等内容时必须调用”Agent才能精准触发。检索无结果的场景也要在这个工具里兜底处理。我见过很多案例是Agent调用了检索工具但结果为空然后模型不知道该怎么回答开始胡编。明确返回“未检索到相关内容”这句话模型就知道数据缺失是一个事实应当如实告知用户。4.3 多工具协同处理复杂问题真实业务里只挂一个检索工具往往不够。我做过的一个资产管理项目里Agent同时挂了三类工具制度文档检索、产品参数检索、资产状态查询。这三类数据的存储方式不同切分策略不同Embedding模型也可能不同但通过Agent统一调度对用户来说就是一个入口。多工具协同的最大收益在于组合查询。比如用户问“我想申请购买一台支持视频编辑的笔记本预算范围内怎么选”。这个问题要拆成两步先从产品知识库检索哪些型号支持视频编辑再从资产系统查询当前库存与价格最后把两部分信息汇总后生成建议。纯RAG链路做不了这种跨库组合但Agent完全可以做到——它会依次调用两个工具自己完成信息的交叉整合。使用多工具时要注意设好边界防止模型进入无限循环。我的做法是给Agent设置最大迭代次数比如6到8轮。一旦超过次数没有产出最终答案就强制结束并返回已收集的信息。实践中还要设计好兜底回答策略当用户问题不匹配任何工具时让Agent走普通对话分支而不是硬调用某个不相关的工具。5. 常见问题、排查方法与避坑经验总结5.1 召回质量差问题到底出在哪一环遇到“搜回来的东西牛头不对马嘴”我的排查顺序是固定的。第一步看切片。把返回的片段打印出来人眼扫一遍如果发现片段本身语义完整但没有命中问题要点多半是切片粒度和主题划分的问题。第二步看Embedding模型。提取几个已知相似问题的向量直接计算一下相似度分数如果分数低得离谱说明模型对业务词汇的表达能力不足考虑换模型或做领域适配。第三步看检索参数。同样的查询把TopK从5调到20看召回结果里是否出现了正确片段如果出现了但排得太靠后说明排序逻辑有问题考虑引入重排序。还有个很隐蔽的问题元数据过滤。如果向量库里既有制度文档又有产品手册检索时不加过滤条件模型会同时召回两个领域的片段互相干扰。给每个文档打上来源、类型、部门标签检索时按标签过滤能显著提升命中质量。5.2 生成结果乱编与上下文超限的处置生成幻觉大多不是模型问题而是上下文里根本没有正确信息。排查思路是把检索结果打印出来人工读一遍看看有没有能直接支撑答案的片段。如果没有问题在检索如果有而模型仍然答错问题在提示词或模型指令。我常用的一条提示词约束是“只能基于上下文回答如果上下文中没有明确依据直接回答不知道。”这句话能很大程度抑制幻觉。同时要让模型在输出中标注引用了哪些片段不仅能追溯也倒逼模型不乱编。上下文超限的问题更常见。处理办法有几类降低TopK、缩小chunk_size、压缩检索结果、或者再增加一层“预筛选”。我曾在项目里把TopK从8降到4再配合重排序效果损失很小token消耗大幅下降。还有一个技巧把检索结果按来源文档聚合只保留每个文档里最相关的片段而不是把重复内容全部塞进上下文。5.3 性能与成本优化从入库到查询的提速向量入库慢通常是Embedding环节卡住。逐条调用模型显然慢正确做法是批量Embedding一次处理几十条甚至上百条文本再并行写入向量库。我实测批量比逐条快了近一个数量级而且更省网络请求开销。查询端性能优化最直接有效的是加缓存。把用户问题和检索结果做一层映射缓存遇到相同或相似问题直接命中不再重复走向量查询。对于高峰期查询量大的场景我还会把embedding模型和向量库单独拆成独立服务与应用主服务分离部署避免相互拖累。读取频繁的索引段还可以做内存预热把冷数据提前载入内存减少首次查询的延迟。成本方面也要精打细算。Embedding调用费看着单价不高量一大就不可忽视。切片阶段做一次去重能省掉大量重复向量的计算和存储开支。另外对不活跃的历史数据可以做冷热分层热数据走性能路径冷数据走廉价的宽松存储需要时再激活入索引。5.4 数据更新与一致性问题容易被忽视的坑RAG上线后知识库一定是动态更新的。最常见的故障是文档更新了但旧版本的向量还留在库里新老内容同时被检索到回答自相矛盾。这个问题在初版系统里几乎必踩。我的做法是给每条切片的元数据打上文档版本号或更新时间戳。检索时按版本过滤只查询最新版本。做文档更新时先按文档ID批量删除旧的分片再写入新分片。有的向量库支持直接按元数据字段删除效率很高。如果删除操作不方便也可以采用“懒删除”策略检索时通过版本过滤把旧内容排除在结果之外定时任务再清理废弃向量。还要注意写入可见性。如果边写边检索可能出现索引尚未刷新、检索不到刚写入数据的情况。业务上对强一致有要求时入库后要等索引刷新完成再返回成功或者引入状态标识把“可检索”与“不可检索”的新数据区分对待。写在最后的个人体会做一个可用的RAG系统难点从来不在调用几个接口而在于切片、向量化、存储、检索、生成每一个环节的细节取舍。初版不要追求复杂架构先用一套简单链路跑通再针对暴露出来的问题逐项优化。我最常用的一套配置是递归切分chunk_size在500 token左右overlap取80开源中文小模型做向量化嵌入式向量库存储TopK取5结合重排序模型精排。这套组合在绝大多数业务场景下都能作为稳定可靠的开局方案后续再根据数据量和并发逐步演进。希望这篇把链路拆清楚之后能帮你少走一些我走过的弯路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑