☰
AI Agent知识获取管道:从RAG原理到落地避坑指南
2026/9/29 18:10:15 网站建设 项目流程

如果你跟我一样,最近一直在折腾 AI Agent,应该迟早会撞上同一个问题:模型再聪明,也架不住一问三不知。我去年接手的第一个 Agent 项目,客户要做企业内部客服助手,用的是当时最强的通用对话能力,prompt 写得再花哨,一遇到公司最新的产品价格、库存状态、售后政策就满嘴跑火车。那几天我一度怀疑是不是提示工程没做好,后来才意识到,真正缺的是一个“知识获取管道”——也就是 RAG。

这篇是“走进 AI Agent”系列的第四篇,前几篇把 Agent 的规划、记忆、工具调用都聊过了,唯独知识这块一直没展开。看后台留言问得最多的也是它:知识库到底怎么建?检索怎么做才能准?为什么我搭出来的 RAG 回答质量还不如直接问大模型?所以这一篇就专门把知识获取管道讲透,从最基本的原理到能落地的代码,再到我实际踩过的坑,一次说清楚。适合正在从 0 到 1 搭建 AI Agent、或者已经在用 LangChain / Spring AI 做知识库问答但效果不理想的人。

1. 为什么 AI Agent 必须补上“知识获取”这一课

1.1 大模型的天花板不在推理,在知识边界

先聊一个很多人不愿意面对的事实:无论你的模型多大、多贵,它本质上是一个“记忆压缩器”。训练数据里出现过的东西,它记得很牢;训练数据里没有的、出现过已经被遗忘的、或者最近才更新的,它就全靠猜。更麻烦的是,它“猜”的时候完全不知道自己在猜,回答起来底气十足,这就是我们常说的幻觉。

我那个客服项目里最典型的问题是:客户问“A 型号的保修期是不是从 2026 年 1 月改成了两年”,模型不知道 2026 年 1 月的政策变化,于是基于 2023 年的老数据开始推断,然后一本正经地给了个错误答案。用户信了,后面产生纠纷,全公司的人来找我。

这不是模型质量问题,是知识边界问题。大模型的知识是有截止日期的,企业内部的知识是动态变化的,两者天然存在一条断层。AI Agent 想在一个具体行业、具体业务里真正有用,就必须自己想办法跨过这条断层。

1.2 知识获取管道到底是个什么东西

所谓知识获取管道,英文叫 Knowledge Acquisition Pipeline,简单说就是一套把外部的、非结构化的信息,经过处理变成 AI Agent 可以检索、可以引用、可以信任的资源池。它一般包括三个环节:

  • 索引(Indexing):把文档切块、向量化、存入向量数据库,让知识变成可以被“搜”的状态。
  • 检索(Retrieval):根据用户的问题,从向量库里找出最相关的知识片段。
  • 生成(Generation):把检索到的片段连同问题一起交给大模型,让模型基于这些材料作答。

早先大家习惯叫它“RAG 链路”,现在 Agent 火起来了,这个概念被统一纳入了知识获取管道,因为对 Agent 来说,这不仅仅是问答,而是它所有决策和行动的材料来源。你可以把 RAG 理解成 Agent 的“外部大脑”——平时把知识存在库里,用的时候才临时取出来,用完不占模型脑子。

我特别想强调一点:管道这个词很重要。它不是一次性的查询,而是一条流水线。进来的原材料是乱七八糟的文档,经过清洗、切分、编码、索引,最终出来的是结构化、可检索、按相关性排序的有效信息。管道设计得好不好,直接决定 Agent 回答问题时的“底料”好不好。

1.3 为什么第一步是 RAG,而不是微调

每次聊到知识获取,一定会有人问:那我微调一个行业模型不行吗?我自己的判断是:能用 RAG 解决的问题,尽量别碰微调。两者不是竞争关系,而是分工关系。

对比维度RAG 知识库微调
知识更新换文档、重建索引,几分钟内生效重新训练或持续训练,耗时耗钱
可解释性可以明确指出依据来自哪份文档知识混入参数,无法追溯
成本主要是一次性的索引和向量库存储GPU 训练、数据标注、人工调参
幻觉抑制有检索片段做约束,相对容易控制模型仍可能自由发挥
工程门槛中低,基本流程清晰高,需要 ML 工程能力
适合场景私有知识、动态知识、需要可追溯的场景风格迁移、特定指令遵循、行业术语固话

我见过太多团队一上来就准备微调,问他们为什么要微调,回答是“因为网上说 RAG 不够准”。但实际上大部分“不够准”都是索引和检索没做好,而不是 RAG 这套思路有问题。先把管道建好,再把检索调准,绝大多数业务问题已经能解决掉。微调可以作为后续增强的手段,而不是第一步的取舍。

2. 知识管道的入口:文档接入与索引构建

2.1 接料这一步,决定后续的天花板

管道的第一站是数据接入。这一步看起来平平无奇,其实是最容易翻车的地方,而且翻车了往往要到很后面才会暴露。

我接手过一套旧的知识库系统,里面全是 PDF 格式的产品手册。PDF 看起来是标准格式,但内部结构千差万别:常见的如印刷版扫描件、双栏排版、带表格的说明书、嵌套在图片里的标题。如果不做预处理就直接往文本切分器里丢,出来的文本会是混乱的,比如双栏 PDF 被从上到下、从左到右读,把两栏内容搅成一锅粥;带表格的内容被拆成一行行的碎文本,数字和表头对应不上。

我有一个不算夸张的判断:RAG 效果的上限是由文档解析质量决定的。后面检索、重排做得再好,也救不回来已经被切乱的原文。

现在常见的解析工具链大概是这么个思路:先做格式识别,判断是文本型 PDF 还是扫描件;扫描件先走 OCR,中文场景推荐 PaddleOCR 这一类的工具;然后做版面分析,识别标题、正文、表格、图片区域;最后按语义结构抽取文本。Python 生态里上有 Unstructured、PyMuPDF 可以做粗解析,复杂的表格可以叠加表格识别模型。我之前图省事直接用某个通用 PDF 库一把梭,结果表格数据全丢了,客户问“这个规格在哪个表格里”就抓瞎,老老实实回头补了解析层。

还有一个很多人忽略的点:非文本类的知识怎么进管道。比如企业内部的知识经常藏在 Excel、数据库甚至企业微信聊天记录里。我的做法是先把这些数据统一转成标准文本或 Markdown,再进索引。比如 Excel 表格,我会先转成“表名+列名+行数据”的语义化描述,而不是让切分器直接啃原始的 CSV。这个习惯让我后面做检索省了很多事。

2.2 分块策略是索引的灵魂

材料清好之后,要做的第一件事不是急着向量化,而是切块。分块(chunking)可能是整个 RAG 工程里对最终效果影响最大、却最容易被随便对付的环节。

为什么要切块?因为 embedding 模型有一个上下文上限,而且向量检索的粒度需要和用户的问题粒度大致匹配。你把整本手册 300 页塞进一个向量里,检索出来的是一个巨大的、语义稀释的“大杂烩”,相关细节被平均掉了;你切得太碎,比如一句话一个 chunk,检索出来的又只是一句孤零零的话,缺少上下文,大模型也答不出所以然。

关于 chunk 大小,我给出一个实测下来的参考区间:

场景chunk 大小(字符数)overlap说明
常见问答、FAQ300 ~ 50050 ~ 80问题和答案通常较短
产品手册、操作文档500 ~ 80080 ~ 100段落本身较长,保留上下文
长报告、合同、研究论文800 ~ 1200100 ~ 150需要保留章节内逻辑
表格数据按行/按语义块切10 ~ 20表格不能硬按字符切

还有一点非常关键:overlap(重叠)不能省。我最早做 demo 的时候觉得 overlap 浪费 token,把重叠区域设成 0,结果检索出来的 chunk 经常是“上一段的结尾带着下一段的开头”,语义断裂。加了重叠之后,关键边界信息就不会被切断在相邻两个 chunk 的缝隙里。

进阶一点的做法是父子分块(Parent-Child Chunking):检索按小的、精确的子块匹配,但真正交给大模型的是包含该子块的完整父段落。这样既保证召回精度,又保证上下文完整。我现在的默认配置基本就是这个思路。

2.3 向量化与向量库选型

分块完成之后进入向量化环节。这一步的核心是 embedding 模型——把一段文本映射成一串高维向量,语义相近的文本向量距离也更近。

中文场景下我的选择顺序大致是这样:开源模型里,BAAI 的 bge-m3、bge-large-zh 是我用的最多的;英文为主可以加 e5-mistral、OpenAI 的 text-embedding-3 系列。如果你的数据以垂直领域为主,比如医疗、法律,建议在通用模型之上做领域适配,或者用领域语料微调一个小的 embedding 模型——这一步能显著提升领域术语的检索命中率。

embedding 维度也要留意。bge-m3 输出 1024 维,维度越高,虽然表达能力越强,但存储和检索开销也跟着涨。向量检索的时候距离函数一般用余弦相似度(Cosine)或点积,两者在你没有特殊需求的时候差别不大,我习惯用 Cosine,因为对归一化后的向量有天然的理论优势。

向量数据库的选择,我整理了一张表供参考:

方案特点适合场景
Chroma轻量,本地文件,API 简单个人项目、原型验证
FAISS高性能向量索引库,无服务端对库有控制力、纯本地
Milvus / Zilliz分布式,支持元数据过滤、混合检索生产级、海量数据、高并发
Elasticsearch + 向量插件复用现有 ES 基础设施,支持全文+向量混合已有 ES 的企业
PGVectorPostgreSQL 扩展数据量不大、不想引入新组件

我这里多说一句:别盲目上分布式向量库。我见过几个项目,数据量还不到 100 万条向量,却上了三节点的分布式架构,运维成本和问题排查难度翻了好几倍。起步阶段用 Chroma 或 PGVector 完全够用,等量级真上来了再迁移不迟。

3. 检索不是“查一下”那么简单

3.1 相似度检索的局限,你迟早会撞上

管道走到检索这一步,很多人以为把用户问题向量化、去库里算相似度、取 top_k 就完事了。真实业务里,这一步恰恰是命中率的“分水岭”。

纯向量检索的逻辑是“语义相似”,但用户问问题的方式是千奇百怪的。同一个意思能有好几种问法,而且有时候关键词的重要性远超语义。举个我们实际遇到过的场景:用户问“这款笔记本的重量是多少”,文档里写的是“产品净重:1.36kg(含电池)”。向量检索可以匹配“重量”和“净重”,但如果用户问的是“带适配器多重”,纯向量检索可能就把那条“适配器:0.4kg”的文本排在很后面了,因为整句语义和“笔记本重量”整体并不完全同构。

更典型的问题是简称和全称:用户搜“CRM”,文档里全是“客户关系管理系统”;用户搜“GSM”,文档里写“全球移动通信系统”。向量模型在这类稀疏词、缩写词上经常表现得不稳定。

所以现在做知识管道的,基本共识是:单一检索方式是有瓶颈的,需要混合。

3.2 混合检索:关键词和语义两条腿走路

混合检索的做法很简单:同时跑两个通道,一个走向量相似度,一个走 BM25(经典的关键词匹配算法),然后把两个通道的结果合并、去重、统一排分。

BM25 跟向量检索是互补的。向量擅长“语义相近但表述不同”,BM25 擅长“字面精确命中”。一个问句里如果出现了明显的关键词,比如型号、编号、人名,BM25 有很大概率直接命中;如果问的是含蓄的自然语言问题,向量检索又能兜住语义关系。

合并排序我比较常用的是 RRF(Reciprocal Rank Fusion)算法,给两个通道的名次取倒数求和,而不是直接比相似度分数。原因是两个通道的分数尺度不一样,放一块儿比是不公平的,RRF 只关心名次,天然规避了这个问题。

def rrf_fusion(vector_results, bm25_results, k=60): fused_scores = {} for rank, doc_id in enumerate(vector_results): fused_scores[doc_id] = fused_scores.get(doc_id, 0) + 1 / (k + rank + 1) for rank, doc_id in enumerate(bm25_results): fused_scores[doc_id] = fused_scores.get(doc_id, 0) + 1 / (k + rank + 1) return sorted(fused_scores.items(), key=lambda x: x[1], reverse=True)

另外,如果检索目标本身带较强的结构化约束(比如“查一下订单编号为 SO20260101 的状态”),向量检索前最好先做一层规则抽取,把编号、日期、金额这些字段抽出来单独过滤。这其实已经在往 Agentic RAG 的方向走了,后面细说。

3.3 重排:从“还凑合”到“精准”

混合检索出来的结果集,相关性往往还是“毛坯”。因为无论向量模型还是 BM25,它们对“语义相关性”的理解都比较粗糙,与“能不能回答用户的这个问题”并不是一回事。这时候需要一个重排(Reranking)环节。

重排模型做的事和向量检索不一样:向量检索是一次性的粗筛,把海量候选压到几百个;重排模型对(问题,候选片段)逐对精细打分,再输出排序。常用的开源重排模型有 bge-reranker-base、bge-reranker-large 等。

我实际项目里的配置是:检索阶段 top_k 取 20,重排后只取前 5 个片段注入提示词。别小看这个动作,同一个知识库,不加重排的答案正确率大概在 70%,加了重排能到 85% 以上,提升非常直观。

有一个容易踩的坑:重排是“问题+候选”两两组合进去的,推理成本比向量检索高一个量级,所以千万不要让重排去做全库匹配,它只负责精排。粗召回靠向量+BM25,精排靠重排模型,分工明确,效率才稳。

3.4 用指标盯着知识库的命中率

做 Agent 的人最怕“感觉变好了”。知识管道的检索质量必须有客观指标兜底,最常见的两个是:

  • Hit Rate(命中率):在所有测试问题中,正确答案对应的文档片段是否出现在检索返回的 top_k 结果里。只要出现在里面,就算命中,它衡量的是“召回能力”。
  • MRR(平均倒数排名):正确答案在检索结果里排第几位,排序越靠前数值越大。

我在项目里落地评测集的方法是:从用户历史真实问题里抽 100~200 条,每条人工标注一个标准答案片段。改完分块策略、换了 embedding 模型、加了重排,都跑一遍这个评测集,看 hit rate 和 MRR 的变化。有了这个数据集,你才敢说“这次改动是有效的”。

现在也有人用 RAGAS 这类框架做自动化评测,用生成式的 LLM 给“忠实度”“相关性”打分。我的建议是自动化评测可以辅助,但人工标注的评测集依然不能丢,尤其是业务强相关的领域,机器打分的稳定性不足以替代人工判断。

4. 从静态管道到 Agentic RAG:知识获取如何变成 Agent 能力

4.1 基础 RAG 的三问

聊完了基础链路,必须往前看一步。传统 RAG 有几个让开发者抓狂的死穴:

  • 它对所有问题都执行一模一样的流程:先检索、再生成。
  • 它不会判断问题是否需要外部知识,也不判断知识库里到底有没有相关内容。
  • 它不能结合多个数据源,也不能在检索结果不足时“自救”。

如果你只是做一个简单的知识问答 demo,这些都不是事;但一旦把 RAG 接到 Agent 身上,作为一个“知识获取能力”来用,这些死穴就会无限放大。因为 Agent 是自主决策的,它的下一步行动依赖上一步拿到什么信息,如果知识获取这步是僵硬的一刀切逻辑,Agent 整个决策链路都会跟着翻车。

4.2 Agentic RAG 的三板斧

所谓 Agentic RAG,就是把检索过程本身交给 Agent 来“规划”。具体怎么理解?我拆成三层:

第一层:查询改写。用户的问题经常是模糊的、口语化的,甚至上下文缺失的。Agent 先调用大模型把问题改写成适合检索的形式,比如“这个怎么办理”改写成“XX 业务的办理流程和材料清单”,命中率立刻不一样。

第二层:路由选择。Agent 判断问题属于哪个领域、该查哪类数据源。比如“帮我查一下物流单号状态”走订单 API,“这个产品有什么售后政策”走知识库,“这两个配置有什么区别”可能要同时查产品手册和技术文档。不是所有问题都该走同一个知识库,也不是所有问题都需要检索。

第三层:多步检索与反思。Agent 可以多次检索,第一次检索的结果可能只回答了一半,它可以根据中间结果判断“还缺信息”,再次发起检索。这个问题在代码上表现为一个循环体:Agent 观察当前检索结果是否满足需求,不满足就继续检索或换检索策略。

我把这三层叠加后的效果说直白一点:静态 RAG 是“一把梭”,Agentic RAG 是“看情况打”,后者的优势是处理复杂问题时的稳定性大幅提升,代价是链路变长、token 消耗变多、调试难度上升。

4.3 Skill、工具与知识管道的协同

前面有几次提到“知识库不是唯一的获取途径”,这里展开讲。在 Agent 架构里,获取信息的手段至少有三类:RAG 知识库(面向静态文档)、API 工具(面向实时系统)、代码执行(面向计算逻辑)。这三者怎么配合,是 Agent 设计的关键。

我一般会在 Agent 的规划层做意图分流,伪代码大概长这样:

def route_agent_query(query): """ 输入: 用户问题 输出: (action_type, params) """ if extract_order_id(query): # 有订单号,直接走业务API return "call_api", {"api": "order_status"} if need_external_compute(query): # 需要计算、比对、统计 return "run_code", {"mode": "python"} # 其余情况,走知识库检索 return "rag_search", {"query_rewrite": rewrite_query(query)}

“Skill 怎么和 RAG 结合起来”是最近留言区经常被问到的一个点。我的理解是:Skill 是 Agent 的可复用原子能力,把 RAG 封装成一个 Skill,好处是同一个知识检索能力可以被多个 Agent 任务复用,比如答疑、写方案、生成报表,都能调用同一个知识检索 Skill,而且可以在 Skill 内部叠加路由、改写、重排逻辑。

4.4 本地知识库和本体 RAG 的延伸

在热点词里看到名人提出“ontology rag”和“GraphRAG”。简单说,这是给文档里的实体和关系建了一张图:人、产品、公司、项目之间的关联不是孤立的向量相似度,而是明确的图谱边。

我个人的看法是:如果你的知识库涉及大量“谁和谁有什么关系”“哪个版本依赖哪个版本”“哪些模块影响哪些模块”这类强关系型问题,纯文本向量检索效果会很勉强。这时候可以考虑在 RAG 链路上叠加一层知识图谱:实体链接(把文本中的实体映射到图谱节点) + 图检索(沿着边找相关实体)。这属于 RAG 的进阶玩法,但思考路径是共通的:先问自己“我的用户问题到底依赖哪种知识形态”,再决定管道怎么搭。

5. 拿得出手的最小 RAG 管道实操

5.1 一个贴合业务的场景选择

光讲原理不落地都是耍流氓。我以之前做过的一个“本地 ERP + 产品检索”场景为例,给出一套可直接复用的最小实现。

业务需求大概是这样的:企业内部有一个 ERP 系统,里面有产品档案、价格、库存信息,同时还有大量 Word 产品说明书 PDF。老板要求做一个内部问答工具,支持员工问“这个零件的单价是多少”“某某型号的维护周期多长”。这类问题的特点是:一部分需要查结构化数据(价格、库存),一部分需要查非结构化文档(维护周期、使用说明)。

为了演示一个最小管道,我先做非结构化文档的 RAG 部分,结构化数据可以通过 SQL 查询或 API 补充进来。

5.2 显式代码一步步搭

我选用的是 Python + LangChain 生态。为什么用 LangChain?因为它把文档加载、切分、向量化、检索、重排这些环节都抽象成了标准组件,降低了我拼装管道的成本。如果你用 Java 技术栈,Spring AI 也有对应的 RAG 链路,比如TikaDocumentReader、TokenTextSplitter、VectorStore这一套,思路完全一致。

第一步,加载文档并清洗:

from langchain_community.document_loaders import PyPDFLoader, TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter # 加载PDF产品手册 loader = PyPDFLoader("data/product_manual.pdf") docs = loader.load() # 如果有OCR需求的扫描件,先用PaddleOCR转成文本再走TextLoader

这里要注意:PyPDFLoader 对文本型 PDF 有效,扫描件出了乱码就要先走 OCR,OCR 这块我不展开代码了,但思路别漏。

第二步,分块。我选择父子分块,先粗切出父块,再细分子块:

parent_splitter = RecursiveCharacterTextSplitter( chunk_size=800, chunk_overlap=100, separators=["\n\n", "\n", "。", ";", ",", " "], ) child_splitter = RecursiveCharacterTextSplitter( chunk_size=300, chunk_overlap=50, ) parent_docs = parent_splitter.split_documents(docs) child_docs = [] for parent in parent_docs: child_docs.extend(child_splitter.split_documents([parent]))

分块之后把父子关系保存下来(比如给 child 挂一个parent_id元数据),生成阶段才能按子块找到父块。

第三步,向量化并写入向量库:

from sentence_transformers import SentenceTransformer from langchain_community.embeddings import HuggingFaceEmbeddings # 使用bge-m3模型,对中文更友好 embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-m3") from langchain_community.vectorstores import Chroma vectorstore = Chroma.from_documents( documents=child_docs, embedding=embeddings, persist_directory="./chroma_db" )

第四步,混合检索 + 重排。这里我用 BM25 做关键词通道,再用 bge-reranker 精排:

from langchain_community.retrievers import BM25Retriever from langchain.retrievers import EnsembleRetriever from langchain_community.cross_encoders import HuggingFaceCrossEncoder bm25_retriever = BM25Retriever.from_documents(child_docs) bm25_retriever.k = 20 vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 20}) hybrid_retriever = EnsembleRetriever( retrievers=[bm25_retriever, vector_retriever], weights=[0.4, 0.6], ) # 初筛 initial_results = hybrid_retriever.invoke(query) # 重排 cross_encoder = HuggingFaceCrossEncoder(model_name="BAAI/bge-reranker-base") pairs = [[query, doc.page_content] for doc in initial_results] scores = cross_encoder.score(pairs) ranked = sorted(zip(initial_results, scores), key=lambda x: x[1], reverse=True) top_results = [doc for doc, score in ranked[:5]]

第五步,组装提示词并生成答案。这是生成阶段的关键,必须要求模型严格依据检索片段作答,并且标明引用来源:

from langchain_core.prompts import ChatPromptTemplate template = """你是一个企业内部的智能助手。请严格依据以下资料片段回答用户问题。 如果资料片段中没有信息可以回答问题,请直接回答“我无法从现有资料中找到答案”,不要编造。 资料片段: {context} 用户问题:{question} 请用中文回答,并在回答末尾列出引用的文档来源。""" prompt = ChatPromptTemplate.from_template(template) formatted_prompt = prompt.format( context="\n\n".join([doc.page_content for doc in top_results]), question="A型号产品的维护周期是多少?" )

这一套下来就是一个基础 RAG 管道的全貌。

5.3 关键参数和调优思路

很多读者问:我照抄了代码,为什么效果还是一般?大概率是参数没对上业务。

我分享几个高频调优点:

  • temperature:生成阶段设 0.1~0.3 比较稳,太高会让模型在片段之外自由发挥,幻觉概率上升。
  • top_k 和重排后数量:初筛 20、精排 5 是通用配置,但如果知识库段落特别长,可以精排后取 8~10 个,给模型更多上下文线索。
  • 分块大小:不要无脑用默认值。中文场景 500~800 字符是经验区,还要看你的文档风格,纯技术文档可以适当放大。
  • 检索阈值:向量相似度分数低于某个阈值(如 0.65)时,宁可告诉用户“没找到”,也不要硬拼答案进去。这一步能显著降低幻觉。

6. 我从这些坑里爬出来的经验

6.1 检索不到、命中率低的排查清单

这东西网上能搜到不少通用建议,但大多数停留在“换个模型试试”“调大 chunk”这种话术上。我把自己真实查过的 case 整理成一份可对照的速查表:

现象常见原因排查方向
问题里的关键词一个都没命中分词太碎 / embedding 不认识领域缩写在检索链路加同义词扩展,或领域微调 embedding
检索结果相关,但回答依然错误分块把关键上下文切断了加 overlap 或改父子分块
文档内容很相关但检索不到PDF 解析乱序 / 表格结构丢失检查解析层文本输出,先修解析
相似度分数普遍很高却答非所问embedding 模型领域适应差换垂直领域模型,或收集领域语料微调
top_k 结果太杂,前后段落来自不同章节切分没有保留章节层级给检索结果按来源文档做过滤或聚簇
回答内容新鲜但引文观点陈旧知识库更新没进索引建立文档版本和增量索引机制

最让我印象深刻的 case 是某个客户知识库检索命中率只有 35%,我查了两天,最后发现他们在文档接入时用了一个不支持中文字体的 PDF 解析器,导致所有中文内容乱码,向量化出来的全是噪声。这再次验证:解析层的质量是管道的地基。

6.2 幻觉的根源往往不是模型,而是检索

很多团队把幻觉归罪于“模型不行”,总想换更大的模型。但我的排查经验里,一半以上的幻觉病例,真相是:

  • 检索出来的片段根本不能回答用户的问题,模型只能“自由发挥”。
  • 检索片段里有正确信息和错误信息混在一起,模型分不清该信哪句。
  • 提示词没有强制约束“不能使用片段之外的信息”,模型就没有戒律。

所以我在项目里有一套强制做法:生成之前,先单独检验检索片段的质量。最简单的方式是让一个评估 LLM 先判断“这些片段是否足够回答用户问题”,如果判断“不够”就重新检索,而不是直接生成答案。

def check_sufficiency(query, retrieved_docs): judge_prompt = f""" 请判断以下资料是否足以回答用户的问题。 如果资料与问题密切相关且信息完整,输出 YES;否则输出 NO。 问题:{query} 资料:{retrieved_docs} """ # 调用LLM判断 return judge_prompt

还有一点很重要:提示词里明确写清楚“如果资料片段中没有信息可以直接回答,请回答无法找到答案”。这句话看起来简单,实际效果立竿见影,能把幻觉率砍掉一大截。

6.3 数据更新与权限:企业场景绕不过的两个课题

如果你的 Agent 只是个人 demo,可以跳过这一节。但一旦进入企业内部,更新和权限就不是可选项了。

知识库的数据是活的,产品手册会改版,政策会更新,员工离职了权限也要跟着变。我最开始只建了一个全局向量库,后来发现产品变更后旧文档依旧被检索到,回答处在新旧政策之间摇摆,用户完全不信任。后来做了三件事:

  • 版本管控:每份文档记录版本号、生效时间,建索引时把这些信息写入元数据字段。
  • 增量更新:监控文档目录变化,有新文件进来只重建它对应的索引,不做全量重刷。
  • 元数据过滤:检索时根据当前用户身份和业务域,在向量库的元数据上加过滤条件,比如“仅检索时效范围内且权限允许的文档”。

向量库如果不支持元数据过滤,做这些会非常吃力,这也是我建议生产环境用支持过滤的 Milvus 或 ES 的原因。

6.4 性能优化:让知识管道跑得不那么贵

RAG 链路的性能问题有两个面:一是延迟,二是成本。前者用户能感知,后者老板能感知。

我常用的优化手段包括:检索结果缓存(相同问题不重复检索)、embedding 结果的缓存复用、把重排模型量化加速、以及并行检索多个数据源。如果你的用户量上来了,考虑把向量库和重排模型做分布式部署,但这需要压测数据支撑,不要拍脑袋上。

成本这块更要精打细算:top_k 取多少、每个片段多长、重排模型用 base 还是 large,这些都直接影响 token 消耗。我一般先控住生成阶段的 token 上限,再逐步放宽,观察服务质量变化。知识管道本身的设计,本质上是在“回答质量、延迟、成本”三个指标之间做平衡,没有绝对的最优,只有最适合你当前业务场景的配置。

最后说点个人体会。RAG 这条路走下来,最难的不是把 demo 跑通,而是让它在业务里稳定不掉链子。我见过很多项目死在两个地方:一是文档没人清理,脏数据进了索引,检索出来谁看了都头大;二是没有评测集,改了一个 embedding 模型,效果是变好还是变坏全靠体感。所以如果你想从 0 到 1 搭一个 Agent,我的建议是,把知识获取管道当成数据工程来做,先花时间定分块规则、建离线评测、观察 badcase,再回头调模型和参数。模型可以随时换,管道和数据的质量才是真正决定上限的东西。

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

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

立即咨询