
1. 从能查到会想增强版智能知识库到底增强了什么做过 RAG 知识库的人大概都有过这种体验第一版 demo 跑通的时候特别兴奋文档切一切、向量一存、问题一搜答案就出来了。但真正丢给业务方用上一周吐槽就来了——它答得对但答得太死、问一个跨文档的问题它就懵了、明明知识库里有它就是找不到。这就是基础 RAG 的天花板它本质上还是个高级搜索检索到什么就答什么不会追问、不会拆解、不会多步推理。增强版智能知识库要解决的正是这个天花板问题。它把 Agent 的规划-执行-反思能力叠加到 RAG 的检索-增强-生成链路上让知识库从一个被动的问答接口变成一个能主动拆解问题、多轮检索、交叉验证、必要时调用外部工具的智能体。关键词里的Agent、LangChain、RAG、向量数据库这四个词基本就勾勒出了这套系统的骨架LangChain 负责编排RAG 负责知识注入向量数据库负责语义召回Agent 负责决策调度。这套东西适合谁如果你已经跑通过一个最基础的 RAG demo但被检索不准、答非所问、无法处理复杂问题卡住了那这篇就是给你写的。如果你是完全零基础也能看懂因为我会把每个环节的为什么讲清楚但实操部分建议先补一下 LangChain 和向量检索的基础概念。整套方案我在实际项目里迭代过几轮踩过的坑、调过的参数、换过的组件都会如实写出来不藏私。需要先明确一个认知增强版不是把组件堆得越多越好。我见过有人一上来就上多路召回、重排序、图谱、多 Agent 协作结果系统复杂度爆炸调试成本高到离谱效果还不如一个调好的基础版。增强要按需增强先定位瓶颈在哪再针对性加能力。下面我会按整体设计思路 → 核心组件拆解 → 完整实操 → 问题排查的顺序展开你可以对照自己的项目情况选择性吸收。2. 整体架构设计与技术选型思路2.1 为什么是Agent RAG而不是纯 RAG纯 RAG 的流程是线性的用户提问 → 向量检索 Top-K → 拼进 Prompt → LLM 生成。这条链路的问题在于它假设用户的问题和知识库里的答案之间是一次检索就能对齐的。但现实里大量问题是这样的我们去年 Q3 和今年 Q1 的退货政策有什么变化——这需要先检索去年 Q3 的政策再检索今年 Q1 的政策然后对比。一次 Top-K 检索根本覆盖不了这种跨时间、跨文档的对比需求。Agent 的介入本质上是把一次检索变成一个可以循环的决策过程。Agent 拿到问题后先判断这个问题需要几步、需要检索什么、检索结果够不够、不够要不要换个 query 再检、要不要调用计算器或外部 API。这个判断-执行-评估-再执行的循环就是 Agent 相对纯 RAG 的核心增量。LangChain 里的 AgentExecutor 或者 LangGraph 的状态机就是用来承载这个循环的。但要注意Agent 不是免费的。每多一轮循环就多一次 LLM 调用延迟和成本都会上升。所以我的原则是能用一次检索解决的绝不走 Agent 循环。具体做法是在入口加一个轻量路由简单问题走快速通道直接 RAG复杂问题才进 Agent 循环。这个路由本身可以用一个小模型或者规则来做成本极低。2.2 向量数据库选型别被最强带偏向量数据库这块市面上的选择太多了Milvus、Qdrant、Weaviate、Chroma、pgvector、Faiss……新手最容易犯的错是直接选功能最全的那个结果发现运维成本高得吓人。我的选型逻辑是按数据规模和团队能力分档场景推荐方案理由个人项目 / 原型验证数据 10 万条Chroma 或 Faiss零运维进程内运行改代码即生效中小团队数据 10 万 ~ 千万级Qdrant 或 pgvectorQdrant 性能好、API 干净pgvector 复用现有 PG省一套运维大规模生产亿级以上Milvus分布式、可水平扩展但运维复杂度高我自己的项目数据量在百万级最后选了 Qdrant。原因很实际它的过滤filter能力和向量检索结合得很好支持在检索时带元数据条件过滤这对知识库场景太重要了——你经常需要只在某个部门、某个时间段的文档里检索。pgvector 也能做但数据量上去之后性能衰减比较明显。Milvus 功能强但我们团队没有专职的中间件运维扛不住。提示向量数据库的选型不要只看 benchmark 上的 QPS。知识库场景的瓶颈往往不在检索速度而在召回质量和过滤灵活性。选型时优先看它的元数据过滤、混合检索向量 关键词支持得怎么样。2.3 文档处理链路知识库质量的地基很多人把精力全花在 Agent 编排上却忽略了文档处理。我可以负责任地说知识库效果差八成问题出在文档处理环节。切分策略、元数据设计、清洗规则这三样决定了检索的上限Agent 再聪明也救不回来。切分chunking不是简单地按字数切。我试过固定 512 token 切分结果把一张表格从中间劈开检索出来半张表LLM 直接答错。后来改成按语义结构切Markdown 按标题层级切PDF 按段落和表格边界切代码文档按函数切。LangChain 里的RecursiveCharacterTextSplitter配合自定义分隔符能覆盖大部分场景。切分大小我一般设 300~500 token重叠 50~80 token这个区间在召回精度和上下文完整性之间比较平衡。元数据设计是另一个被低估的点。每条 chunk 至少要带上来源文档、章节标题、更新时间、文档类型、权限标签。这些字段在检索时能做过滤在生成时能作为引用来源在权限控制时能拦住越权访问。我见过有人只存了文本和向量后期想加权限控制只能全量重建索引血泪教训。3. 核心组件深度拆解与实操要点3.1 检索增强从单路召回走向混合检索基础 RAG 只用向量检索问题是它对精确匹配不敏感。比如用户问错误码 E5021 怎么解决向量检索可能召回一堆语义相近但错误码不同的文档。这时候关键词检索BM25就补上了——它能精确命中E5021这个 token。混合检索就是把向量召回和关键词召回的结果融合常用的融合算法是 RRFReciprocal Rank Fusion。RRF 的逻辑很朴素对每个文档把它在各路召回里的排名取倒数再求和得分高的排前面。公式是score Σ 1/(k rank_i)k 一般取 60。它不需要归一化不同召回路的分数工程上很省心。我在 Qdrant 里用它的原生混合检索或者用 LangChain 的EnsembleRetriever把两个 retriever 组合起来效果比单路向量召回明显好尤其是含专有名词、编号、代码的查询。重排序rerank是第二层增强。混合检索召回 Top-20再用一个 cross-encoder 模型比如 bge-reranker对这 20 条精排取 Top-5 喂给 LLM。cross-encoder 把 query 和 document 拼在一起过模型精度比双塔的向量相似度高不少代价是慢。所以它只用在精排阶段不用于全量召回。实测下来加了 rerank 之后答案准确率能提升 10~15 个百分点这个投入非常值。3.2 Agent 编排用 LangGraph 管住循环早期我用 LangChain 的AgentExecutor简单场景够用但一旦涉及多步、条件分支、人工介入它的黑盒感就很强调试困难。后来换到 LangGraph用状态图StateGraph显式定义节点和边整个流程一目了然。每个节点是一个函数边是条件跳转状态在节点间传递。这种显式编排对知识库 Agent 特别合适因为检索流程本身就是有明确步骤的。我的 Agent 状态图大致是这样几个节点问题理解判断问题类型、拆解子问题→检索规划决定检索哪些库、用什么 query→执行检索调 retriever→结果评估检索结果是否足够→ 分支足够则生成答案不足则改写 query 回到检索。评估节点是关键它决定了循环什么时候停。我用 LLM 做评估prompt 里明确让它输出sufficient或insufficient加理由避免无限循环。循环次数一定要设上限。我设的是最多 3 轮检索超过就强制生成并在答案里标注信息可能不完整。不设上限的 Agent 在生产环境是灾难我见过因为评估节点判断失误导致死循环一次请求烧掉几十万 token 的案例。3.3 向量化模型中文场景别照搬英文方案embedding 模型的选择直接决定召回质量。英文场景 OpenAI 的 text-embedding-3 系列很稳但中文场景我强烈建议用国产开源模型比如 BGE 系列bge-large-zh、bge-m3或者 M3E。原因很简单中文的语义分布和英文差异大英文模型在中文上的表现经常不如专门训练的中文模型。bge-m3 还支持多语言和长文本对混合中英文的知识库很友好。维度方面不是越高越好。1024 维和 768 维在实际召回效果上差距不大但存储和检索成本差不少。百万级数据下1024 维的索引体积和内存占用明显更高。我一般选 768 或 1024够用就行。另外query 和 document 必须用同一个模型编码这个看似废话但我真见过有人 query 用 A 模型、document 用 B 模型检索结果一塌糊涂还找不到原因。3.4 生成环节Prompt 设计与引用溯源生成环节的 Prompt 设计核心是两条约束 LLM 只用检索到的内容回答以及要求它标注引用来源。第一条防止幻觉第二条让用户能验证。我的 system prompt 大致是这样组织的先声明角色你是知识库助手再给约束只基于提供的上下文回答上下文没有就说不知道不要编造最后给格式要求答案末尾用 [1][2] 标注引用了哪几条。引用溯源在工程上要配合元数据。检索回来的每条 chunk 都带 id 和来源生成时把这些信息一起给 LLM让它引用。前端展示时把 [1] 渲染成可点击的链接跳转到原文对应位置。这个体验对知识库产品太重要了用户信任度直接拉满。注意不要指望 LLM 100% 遵守只用上下文回答的约束。它偶尔还是会脑补。所以关键场景比如法务、医疗一定要加一层后置校验用规则或另一个模型检查答案里的关键实体是否都出现在检索结果里。4. 完整实操从零搭一个增强版知识库4.1 环境准备与依赖安装先把环境搭起来。我用 Python 3.10依赖管理用 uv 或 pip 都行。核心依赖包括langchain、langchain-community、langgraph、qdrant-client、sentence-transformers跑本地 embedding 和 rerank、以及一个 LLM 客户端。如果你用本地模型可以接 Ollama用云端 API 就装对应的 SDK。pip install langchain langchain-community langgraph qdrant-client sentence-transformers pip install pypdf markdown beautifulsoup4 # 文档解析用Qdrant 我用 Docker 起一个本地实例方便调试docker run -p 6333:6333 -v $(pwd)/qdrant_storage:/qdrant/storage qdrant/qdrant启动后访问 6333 端口能看到 dashboard说明起来了。生产环境记得配持久化和鉴权本地调试无所谓。4.2 文档入库解析、切分、向量化、写入这一步是整个系统的地基我拆成四小步。第一步解析PDF 用 pypdfMarkdown 直接读HTML 用 BeautifulSoup 去标签。解析时顺手把元数据抽出来文件名、标题、更新时间。第二步切分用RecursiveCharacterTextSplitter中文场景分隔符要加上中文标点from langchain.text_splitter import RecursiveCharacterTextSplitter splitter RecursiveCharacterTextSplitter( chunk_size400, chunk_overlap60, separators[\n\n, \n, 。, , , , , , ] )第三步向量化用 bge-m3from sentence_transformers import SentenceTransformer model SentenceTransformer(BAAI/bge-m3) vectors model.encode(chunks, normalize_embeddingsTrue)注意normalize_embeddingsTrue归一化后余弦相似度计算更快更稳。第四步写入 Qdrant每条 point 的 payload 带上文本和元数据from qdrant_client import QdrantClient from qdrant_client.models import PointStruct, VectorParams, Distance client QdrantClient(hostlocalhost, port6333) client.recreate_collection( collection_nameknowledge, vectors_configVectorParams(size1024, distanceDistance.COSINE) ) points [ PointStruct(idi, vectorvectors[i], payload{text: chunks[i], **metas[i]}) for i in range(len(chunks)) ] client.upsert(collection_nameknowledge, pointspoints)bge-m3 的输出维度是 1024所以size1024。如果你换模型这里要跟着改维度对不上会直接报错。4.3 混合检索与重排序的落地检索这块我封装成一个函数输入 query输出精排后的 Top-K。先做向量召回和关键词召回再用 RRF 融合最后 rerank。向量召回直接调 Qdrant关键词召回可以用 Qdrant 的全文索引或者本地用 rank_bm25 建一个。为了简化我用 Qdrant 的混合能力from qdrant_client.models import Filter, FieldCondition, MatchValue def hybrid_retrieve(query, top_k20): q_vec model.encode(query, normalize_embeddingsTrue) hits client.search( collection_nameknowledge, query_vectorq_vec, limittop_k ) return hits然后接 rerank。bge-reranker 用 cross-encoder 的方式from sentence_transformers import CrossEncoder reranker CrossEncoder(BAAI/bge-reranker-v2-m3) def rerank(query, hits, top_n5): pairs [(query, h.payload[text]) for h in hits] scores reranker.predict(pairs) ranked sorted(zip(hits, scores), keylambda x: x[1], reverseTrue) return [h for h, s in ranked[:top_n]]实测下来召回 20 条精排到 5 条是精度和延迟的甜点区。召回太多 rerank 慢召回太少可能漏掉正确答案。4.4 用 LangGraph 编排 Agent 循环这是整套系统的大脑。我用 StateGraph 定义状态和节点。状态里放 query、子问题列表、检索结果、循环次数。节点包括plan拆解问题、retrieve执行检索、evaluate评估充分性、generate生成答案。边用条件函数控制evaluate 返回 sufficient 就走 generate否则回 retrieve。from langgraph.graph import StateGraph, END from typing import TypedDict, List class AgentState(TypedDict): query: str sub_queries: List[str] docs: List[str] loop_count: int answer: str def plan_node(state): # 用 LLM 拆解问题简单问题直接返回原 query ... return {sub_queries: [...], loop_count: 0} def retrieve_node(state): docs [] for q in state[sub_queries]: hits hybrid_retrieve(q) docs.extend(rerank(q, hits)) return {docs: docs, loop_count: state[loop_count] 1} def evaluate_node(state): # LLM 判断检索结果是否足够 ... def route_after_eval(state): if state[loop_count] 3: return generate return generate if is_sufficient else retrieve graph StateGraph(AgentState) graph.add_node(plan, plan_node) graph.add_node(retrieve, retrieve_node) graph.add_node(evaluate, evaluate_node) graph.add_node(generate, generate_node) graph.set_entry_point(plan) graph.add_edge(plan, retrieve) graph.add_edge(retrieve, evaluate) graph.add_conditional_edges(evaluate, route_after_eval, {retrieve: retrieve, generate: generate}) graph.add_edge(generate, END) app graph.compile()这个图跑起来之后简单问题一轮就出结果复杂问题会自动多轮检索。loop_count的硬上限是保命的千万别省。4.5 生成与引用溯源的实现生成节点把精排后的 docs 拼成上下文加上 system prompt 调 LLM。我要求 LLM 在答案里用 [1][2] 标注引用然后前端解析这些标记映射回 docs 的元数据来源、链接。这样用户点一下就能看到原文。Prompt 里我会明确列出每条上下文的编号让 LLM 引用时有据可依以下是检索到的资料 [1] {doc1_text} [2] {doc2_text} ... 请只基于以上资料回答并在引用处标注编号。生成完再做一次后置校验把答案里的关键实体抽出来检查是否都在 docs 里出现过。没出现的标记为可能幻觉前端给个提示。这层校验不完美但能拦住大部分明显的编造。5. 常见问题与排查技巧实录5.1 检索不准先查切分再查模型检索不准是最常见的问题排查顺序我总结成一张表现象可能原因排查方法明明有答案却检索不到切分把答案切碎了打印 chunk 内容看答案是否完整检索到无关内容embedding 模型不匹配中文换 bge-m3 或 bge-large-zh 重试专有名词检索不到纯向量召回对 token 不敏感加 BM25 混合检索排序靠后缺 rerank加 cross-encoder 精排我的经验是先怀疑切分再怀疑模型最后怀疑检索策略。切分问题占了检索不准的一大半。有个快速验证方法把用户问的问题原文直接去知识库里 grep如果能 grep 到但检索不到那基本就是切分或向量化的问题。5.2 Agent 死循环与成本失控Agent 循环最怕两件事死循环和成本爆炸。死循环的根因通常是 evaluate 节点判断不准一直认为不够一直检索。解法是硬上限 评估 prompt 优化。硬上限前面说了评估 prompt 要明确告诉 LLM如果连续两轮检索结果高度重合就判定为 sufficient避免它在同一个问题上反复打转。成本控制方面我做了两件事一是入口路由简单问题不走 Agent二是缓存相同 query 的检索结果缓存起来短时间内重复问直接命中缓存。实测下来这两招能把平均成本降一半以上。5.3 并发扛不住怎么办AI Agent 怎么扛并发是个高频问题。知识库 Agent 的并发瓶颈通常不在检索向量库能扛而在 LLM 调用。LLM API 有速率限制并发一高就排队。我的做法是检索层用异步Qdrant 客户端支持 asyncLLM 调用加一个信号量控制并发数超出的请求进队列。另外把 rerank 这种 CPU 密集的操作放到独立的 worker 里别阻塞主流程。如果并发量真的很大考虑把 embedding 和 rerank 换成更小的模型或者用 GPU 加速。bge-m3 在 CPU 上跑单条要几百毫秒GPU 上能到几十毫秒差距很大。预算允许的话给 embedding 和 rerank 单独配一张卡收益立竿见影。5.4 知识库里的图片怎么处理RAG 知识库能存储图片吗这个问题问的人很多。答案是能但要分情况。如果图片里有文字比如截图、扫描件先用 OCR 把文字抽出来当成文本入库图片本身存对象存储元数据里记 URL。如果图片是纯图形比如流程图、架构图可以用多模态模型生成图片描述把描述文本入库检索时命中描述返回时带上原图。纯图形图片的检索效果目前还不太理想别抱太高期望。5.5 几个我踩过的坑第一个坑元数据没设计好后期加权限控制要重建索引。这个前面提过血的教训一开始就把权限标签加上。第二个坑embedding 模型换了但没重建索引。换模型必须全量重新向量化否则新旧向量不在一个空间里检索结果完全乱套。我建议把模型版本写进 collection 名字里比如knowledge_bge_m3_v1换模型就换 collection避免混淆。第三个坑rerank 模型和 embedding 模型不匹配。虽然理论上可以混用但同系列比如都用 BGE的模型配合起来效果更稳。我试过 embedding 用 bge、rerank 用别的效果反而不如都用 bge。第四个坑Prompt 里上下文太长导致 LLM 忽略中间内容。这是著名的lost in the middle现象。解法是把最相关的文档放上下文开头和结尾中间放次要的或者干脆减少上下文条数靠 rerank 保证质量。6. 性能调优与扩展方向6.1 延迟优化的几个抓手知识库 Agent 的延迟主要花在三处检索、rerank、LLM 生成。检索和 rerank 是可控的LLM 生成取决于模型和输出长度。优化顺序我建议先优化 rerank换小模型或减候选数再优化检索加缓存、用更快的向量库最后才动 LLM换更快的模型或流式输出。流式输出对体感延迟改善巨大。用户不用等完整答案生成完第一个 token 出来就能看到心理上觉得快很多。LangChain 的 streaming 支持很完善接上就行。6.2 从 RAG 到 GraphRAG 的演进当知识库里的实体关系很复杂时比如人物关系、组织架构、因果链纯向量 RAG 就力不从心了。这时候可以考虑 GraphRAG把知识抽成实体-关系图检索时沿着图遍历。代价是构建成本高需要实体抽取和关系抽取而且图的质量直接决定效果。我的建议是先用向量 RAG 跑起来确认瓶颈确实在关系推理上再上 GraphRAG。别一上来就搞图谱容易陷进去出不来。6.3 多知识库路由实际项目里往往不止一个知识库可能有产品文档库、客服话术库、内部制度库。这时候需要一个路由层根据问题判断该查哪个库。路由可以用 LLM 做也可以用规则关键词匹配。我一般先用规则兜底规则覆盖不了的再用 LLM 判断这样成本和准确率都兼顾。路由做得好检索范围就小精度和速度都上去了。这是被很多人忽略的一个优化点——与其在一个巨大的库里大海捞针不如先分库再检索。7. 一些掏心窝子的实操心得这套增强版知识库我前后迭代了大半年最大的体会是别追求一步到位按瓶颈迭代。第一版就老老实实做基础 RAG跑通、上线、收集反馈。等用户开始抱怨答不了复杂问题了再加 Agent 循环等发现专有名词检索不准了再加混合检索等发现排序不好了再加 rerank。每一步都有明确的动机而不是为了用某个技术而用。另一个心得是评估体系要早建。没有评估你根本不知道改动是变好还是变坏。我建了一个小规模的评测集几十条真实问题和标准答案每次改动都跑一遍看准确率和召回率的变化。这个评测集不用很大但必须真实从用户实际提问里挑。有了它调参才有方向不然就是瞎调。最后说个容易被忽略的点日志要打全。每次请求的 query、检索到的 docs、rerank 分数、Agent 循环次数、最终答案全部记下来。出问题的时候这些日志就是你的救命稻草。我靠日志定位过好几次诡异问题比如某类 query 总是触发多轮循环一看日志发现是评估节点的 prompt 对这类问题判断有偏差改完就好了。没有日志这种问题能查到你怀疑人生。这套方案不是银弹它解决的是基础 RAG 不够用的问题代价是复杂度和成本上升。如果你的场景简单问题占绝大多数基础 RAG 加个好的 rerank 可能就够了不必上 Agent。技术选型永远要看场景别被热词带着跑。