☰
从朴素RAG到Agent知识库:LangChain+FAISS+HyDE增强实战
2026/10/6 5:05:40 网站建设 项目流程

1. 从"能查"到"会想":增强版知识库到底增强了什么

做过RAG的人大概都有过这种体验:搭一个能跑通的知识库问答,一个下午就够了——文档切一切、向量化、塞进FAISS、检索Top-K、拼进Prompt丢给大模型。Demo阶段效果惊艳,老板看了直点头。可一旦真上线,用户问几个稍微绕一点的问题,系统立刻露馅:问"这个方案和上个季度那版比改了什么",它答非所问;问"为什么当时选了这个技术路线",它把文档里八竿子打不着的段落拼在一起硬凑答案。

问题出在哪?出在朴素RAG本质上只是个"关键词匹配的搬运工",它不会改写问题、不会判断检索质量、不会多轮追问、更不会在检索不到的时候承认自己不知道。所谓"增强版智能知识库",增强的从来不是向量库本身,而是在检索前后加了一整套"会思考"的Agent逻辑。

这篇内容我想聊的,就是怎么把LangChain、FAISS、HyDE这几样东西捏成一个真正能扛住真实提问的Agent知识库。它适合已经跑通过最基础RAG、但被实际效果折磨过的开发者;也适合刚接触Agent、想知道"Agent到底比普通链强在哪"的入门者。我不会只给你一段能跑的代码,而是把每个设计决策背后的"为什么"讲清楚——为什么用HyDE、为什么FAISS要这么配、为什么检索要做多路召回、Agent的记忆和工具该怎么编排。这些才是决定你的知识库是"玩具"还是"工具"的分水岭。

先说结论:一个增强版知识库的核心能力,可以拆成四层——查询理解层、检索增强层、结果校验层、编排决策层。普通RAG只有中间那半层,而Agent的价值就在于把另外三层补齐。下面我按这个思路,一层层拆给你看。

2. 朴素RAG为什么在真实提问面前频频翻车

2.1 用户的问题和文档的表述,天然对不上

这是最根本的矛盾。用户提问用的是口语、是意图、是上下文省略后的短句;而知识库里的文档是书面语、是完整陈述、是专业术语。举个我实际遇到的例子:用户问"上次那个卡顿的问题解决没",文档里写的是"2024年Q2性能优化专项:针对高并发场景下的响应延迟问题完成根因定位与修复"。这两句话在向量空间里的距离,可能比你想的远得多。

朴素RAG的做法是:把用户这句话直接向量化,然后去FAISS里找最近的K个。结果就是检索回来的东西要么不相关,要么只沾一点边。模型拿到这种上下文,只能靠"编"来补全,幻觉就这么来的。

2.2 Top-K固定,是另一个隐形杀手

大部分教程里K都写死成4或者5。但真实场景里,有的问题一个段落就能答,有的问题需要跨三四个文档综合。固定K的后果是:简单问题召回一堆噪声干扰模型,复杂问题又漏掉关键信息。更麻烦的是,你根本不知道这次检索到底"够不够"——朴素RAG没有自我评估的能力。

2.3 没有"检索不到"这个选项

这是最致命的。朴素RAG的流程是线性的:检索→拼接→生成,中间没有任何判断节点。哪怕FAISS返回的全是无关内容,它也会照单全收塞给模型。模型面对一堆垃圾上下文,要么硬答,要么答非所问,就是不会说"我不知道"。而一个靠谱的知识库,"拒答"和"回答"同样重要。

2.4 单轮检索,无法处理需要追问的问题

真实提问经常是模糊的。"帮我看看那个配置怎么改"——哪个配置?什么场景下的?朴素RAG拿到这种问题只能瞎猜。而Agent可以先把问题澄清、或者基于对话历史补全指代,再去检索。这一步的差距,就是"能用"和"好用"的差距。

理解了这些翻车点,你就明白增强版要往哪个方向使劲了:让系统在检索前会改写问题,检索时会多路并进,检索后会自我校验,检索不到时敢于承认。接下来逐层落地。

3. 查询理解层:让系统先"听懂"再去找

3.1 查询改写:把口语翻译成"文档语言"

查询改写的核心思路是:在真正检索之前,先用一次LLM调用把用户问题"翻译"成更接近文档表述的形式。这一步成本很低(一次轻量调用),但收益极大。

具体怎么做?我常用的策略是让模型输出多个改写版本,而不是一个。比如用户问"卡顿问题解决没",模型可能输出:

  • 高并发场景响应延迟问题的修复进展
  • 性能优化专项 根因定位 修复状态
  • 响应延迟 高并发 解决方案

这三个版本分别侧重不同表述,一起丢给检索器做多路召回,命中率比单版本高出一截。这就是所谓的多查询检索(Multi-Query Retrieval)。

from langchain.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI rewrite_prompt = ChatPromptTemplate.from_template( """你是知识库检索助手。请把用户问题改写成3个不同表述的检索查询, 分别侧重:专业术语版、关键词版、同义扩展版。 只输出查询,每行一个,不要编号。 用户问题:{question}""" ) llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) rewrite_chain = rewrite_prompt | llm queries = rewrite_chain.invoke({"question": user_q}).content.split("\n")

注意:改写这一步的temperature一定要调低(0或0.1),否则模型会"发挥创意",改出一些偏离原意的查询,反而拉低召回质量。

3.2 HyDE:用"假答案"去钓"真文档"

HyDE(Hypothetical Document Embeddings)是我个人最喜欢的一个技巧,思路非常反直觉但极其有效:与其拿问题去检索,不如先让模型"编"一个假想的答案,再拿这个假答案去检索。

为什么有效?因为答案和文档在表述风格上是同源的——都是陈述句、都包含专业术语、都在描述事实。而问题和文档是异源的。用假答案去检索,向量空间里的距离天然更近。

hyde_prompt = ChatPromptTemplate.from_template( """请针对下面的问题,写一段200字左右的假设性答案。 不需要准确,只需要在表述风格和术语上接近技术文档。 问题:{question}""" ) hyde_doc = (hyde_prompt | llm).invoke({"question": user_q}).content # 用 hyde_doc 去向量化检索,而不是用 user_q

实测下来,HyDE在专业术语密集的知识库上提升尤其明显。但它有个前提:你的知识库领域要相对聚焦。如果知识库横跨十几个不相关领域,模型编出来的假答案可能跑偏到别的领域去,这时候HyDE反而有害。所以我的建议是,HyDE适合垂直领域知识库,通用知识库慎用,或者配合多路召回一起用,让HyDE只是其中一路。

3.3 指代消解:多轮对话里最容易忽略的一环

如果知识库支持多轮对话,"它""那个""上面说的"这类指代必须处理。做法很简单:把对话历史和当前问题一起丢给模型,让它输出一个"自包含"的完整问题。

condense_prompt = ChatPromptTemplate.from_template( """根据对话历史,把用户最新问题改写成一个不依赖上下文、可独立理解的完整问题。 对话历史:{history} 最新问题:{question} 只输出改写后的问题。""" )

这一步不做,多轮对话的检索质量会断崖式下跌。我见过太多项目在单轮测试时表现良好,一上多轮就崩,根因往往就在这里。

4. 检索增强层:FAISS不是塞进去就完事

4.1 FAISS索引选型:IndexFlat还是IndexIVF

很多人用FAISS就是FAISS.from_documents()一把梭,默认给你建的是IndexFlatL2——暴力检索,精度最高但速度随数据量线性下降。数据量小(几万条以内)无所谓,但一旦上到几十万、上百万向量,检索延迟会让你怀疑人生。

这时候要换IndexIVFFlat,它把向量空间聚类成nlist个桶,检索时只查最近的几个桶(nprobe),速度提升几十倍。代价是精度略有损失,但通过调nprobe可以找平衡。

索引类型适用规模检索速度精度内存
IndexFlatL2< 10万慢100%高
IndexIVFFlat10万-千万快可调(95%-99%)中
IndexHNSWFlat百万级极快高高
IndexIVFPQ千万级以上极快有损低

我的经验是:10万条以下别折腾,Flat就够;10万到百万用IVF,nlist设成sqrt(N),nprobe设成nlist的5%-10%;再往上考虑HNSW或PQ。选型错了,后面再怎么优化Prompt都是白搭。

4.2 分块策略:切得好,检索就成功了一半

分块(Chunking)是RAG里最被低估的环节。默认的RecursiveCharacterTextSplitter按字符数硬切,经常把一句话、一个表格、一段代码从中间劈开,检索回来的是半截内容,模型看了也懵。

我的做法是按文档结构切,而不是按字符数切。Markdown按标题层级切,代码按函数切,PDF按段落切。切完之后再做一层"语义合并":如果相邻两块语义高度相关(可以用向量相似度判断),就合并成一块。

from langchain.text_splitter import MarkdownHeaderTextSplitter headers_to_split = [ ("#", "h1"), ("##", "h2"), ("###", "h3"), ] splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split) chunks = splitter.split_text(md_content)

另外,chunk_size和overlap没有万能值。技术文档我一般用500-800字符、overlap 100;对话记录用300-500;法律合同这种长句密集的用1000以上。别信"512是黄金值"这种说法,一定要拿你自己的数据实测。

4.3 多路召回 + 重排:把召回率和准确率分开优化

单路检索永远在召回率和准确率之间纠结。解法是多路召回、统一重排:用不同策略各召回一批(向量检索、BM25关键词检索、HyDE检索、多查询检索),合并去重后,再用一个重排模型(Reranker)统一打分排序,取Top-N。

# 伪代码示意多路召回 results = [] results += vector_retriever.invoke(query) # 向量路 results += bm25_retriever.invoke(query) # 关键词路 results += hyde_retriever.invoke(hyde_doc) # HyDE路 # 去重后交给 reranker reranked = reranker.rerank(query, dedup(results), top_n=5)

重排模型我常用的是bge-reranker系列,中文场景表现稳定。这一步的收益非常直观:召回阶段可以放宽(多召回一些),重排阶段再收紧,两阶段各司其职,整体效果比单路调参好得多。

提示:多路召回会增加延迟,如果对响应时间敏感,可以把BM25和向量检索并行跑,HyDE那一路因为要先调LLM,可以异步或者只在向量路召回质量差时才触发。

5. 结果校验层:让知识库学会说"我不知道"

5.1 相关性打分:检索回来的东西到底能不能用

检索完不能直接信。我习惯加一个"相关性裁判"节点:把用户问题和召回内容一起丢给模型,让它给每段内容打一个0-10的相关性分,低于阈值的直接丢弃。

grade_prompt = ChatPromptTemplate.from_template( """判断下面这段内容与问题的相关程度,输出0-10的分数,只输出数字。 问题:{question} 内容:{doc}""" )

这个节点看起来多花了一次调用,但它能挡掉大量噪声。实测中,大约20%-30%的召回内容会被判为不相关,把它们剔除后,最终答案的准确率提升非常明显。

5.2 幻觉检测:答案里的每句话都要有出处

生成完答案后,再做一次校验:把答案拆成若干陈述句,逐句检查是否能在召回内容里找到支撑。找不到支撑的句子,要么删掉,要么标注"此点未在知识库中找到依据"。

这一步是"可信知识库"和"聊天机器人"的分界线。用户信任你的知识库,是因为它说的每句话都能溯源。一旦它开始编,信任就崩了。

5.3 拒答机制:检索不到时的正确姿势

当所有召回内容的相关性分都低于阈值时,系统应该明确拒答,而不是硬凑。拒答话术也有讲究,不能冷冰冰一句"我不知道",而要给出建设性引导:

"这个问题我在现有知识库里没有找到明确依据。你可以尝试换个说法,或者补充一下具体场景,我再帮你找找。"

这种拒答既诚实,又给了用户下一步动作,体验比硬答好太多。

6. 编排决策层:用LangGraph把上面这些串成Agent

6.1 为什么是LangGraph而不是普通Chain

普通Chain是线性的,A→B→C走完就结束。但增强版知识库的流程是带分支和循环的:检索质量差要重试、问题模糊要追问、相关性不够要换策略。这种"会拐弯"的流程,用LangGraph这种图结构来表达最自然。

LangGraph的核心概念是状态(State)+ 节点(Node)+ 边(Edge)。状态在节点间流转,边决定下一步走哪。你可以把它理解成一张流程图,每个节点是一个处理步骤,边是条件判断。

6.2 一个可落地的图结构设计

我实际项目里的图大概长这样:

  • 入口节点:接收用户问题
  • 指代消解节点:多轮场景下补全问题
  • 查询改写节点:生成多路查询
  • 检索节点:多路召回 + 重排
  • 相关性校验节点:打分过滤
  • 条件边:如果有效内容不足,走"重试"或"拒答"分支;如果足够,走"生成"分支
  • 生成节点:基于有效内容生成答案
  • 幻觉校验节点:逐句溯源
  • 出口节点:返回答案
from langgraph.graph import StateGraph, END workflow = StateGraph(AgentState) workflow.add_node("condense", condense_node) workflow.add_node("rewrite", rewrite_node) workflow.add_node("retrieve", retrieve_node) workflow.add_node("grade", grade_node) workflow.add_node("generate", generate_node) workflow.add_node("verify", verify_node) workflow.set_entry_point("condense") workflow.add_edge("condense", "rewrite") workflow.add_edge("rewrite", "retrieve") workflow.add_edge("retrieve", "grade") workflow.add_conditional_edges( "grade", decide_next, # 返回 "generate" 或 "retry" 或 "reject" {"generate": "generate", "retry": "rewrite", "reject": END} ) workflow.add_edge("generate", "verify") workflow.add_edge("verify", END)

这个结构的关键在于条件边——它让系统有了"判断力"。检索质量不行就回去重试(换个查询策略),重试几次还不行就拒答。这就是Agent和普通RAG的本质区别。

6.3 记忆管理:短期对话 + 长期知识

Agent的记忆分两层。短期记忆是当前对话的历史,用来做指代消解和上下文理解,一般存在内存或Redis里,会话结束就清。长期记忆是跨会话的用户偏好、常见问题等,可以存进向量库,下次检索时一起召回。

我踩过的一个坑是:把长期记忆和知识库文档混在同一个FAISS索引里,结果检索时经常召回一堆用户历史提问,干扰了真正的知识检索。后来改成两个独立索引,分别检索后合并,问题就解决了。记忆和知识,物理上要隔离。

7. 实测中那些文档不会告诉你的坑

7.1 向量模型选型:中文场景别迷信英文榜单

MTEB榜单上排名靠前的模型,很多是英文优化的,直接拿来跑中文知识库,效果可能还不如一些国产模型。中文场景我实测下来,bge-large-zh、m3e、text-embedding-3-large(配合中文语料微调)表现都比较稳。选型时一定要拿自己的数据跑一遍召回率,别只看榜单。

7.2 并发问题:LLM调用是瓶颈,不是FAISS

很多人担心FAISS扛不住并发,其实FAISS检索是内存操作,单机每秒几千次没问题。真正的瓶颈是LLM调用——查询改写、相关性打分、生成、幻觉校验,一次问答可能触发4-5次LLM调用,每次几百毫秒到几秒。高并发下,LLM的QPS限制会先崩。

解法有三个:一是能并行的并行,多路召回、多段打分都可以并发;二是能缓存的缓存,相同问题的改写结果、打分结果缓存起来;三是分级调用,简单问题走轻量模型,复杂问题才上大模型。

7.3 成本控制:不是每一步都值得用大模型

增强版流程步骤多,如果每步都用GPT-4,成本会失控。我的做法是分级用模型:查询改写、相关性打分这种"判断类"任务用mini模型就够;只有最终生成和幻觉校验用大模型。实测下来,整体成本能降60%以上,效果几乎无损。

7.4 评估:没有评估集,你就是在盲调

这是最容易被忽略的一点。很多人调RAG全靠"感觉好像好了一点",没有量化指标。我的建议是建一个50-100条的小评估集,覆盖简单问题、复杂问题、模糊问题、知识库外问题四类,每次改动后跑一遍,看召回率、准确率、拒答率的变化。没有这个,你的所有优化都是玄学。

问题类型考察点期望行为
简单事实基础召回准确回答
跨文档综合多路召回综合多段回答
模糊提问查询改写澄清或合理猜测
知识库外拒答机制明确拒答

8. 写在最后的一点个人体会

这套增强版知识库我前后迭代了大概两个月,最大的感受是:RAG的难点从来不在"检索"本身,而在"判断"。判断问题该怎么理解、判断检索结果能不能用、判断答案有没有依据、判断什么时候该说不知道。这些判断,才是Agent真正带来的价值。

FAISS、HyDE、LangChain这些都只是工具,工具本身不难学。难的是想清楚每一步"为什么要这么做",以及在没有标准答案的时候,怎么用评估集给自己一个方向。我见过太多人把精力花在换模型、调参数上,却从来没建过一个评估集,最后调来调去也不知道到底有没有变好。

如果你正准备做类似的东西,我的建议是:先把最朴素的RAG跑通,然后建评估集,再一层层往上加增强。每加一层,用评估集验证一次。别一上来就堆满所有技巧,那样你根本不知道是哪个环节起了作用,出了问题也无从排查。慢就是快,这句话在RAG上尤其成立。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询