这篇我拖了挺久。前面几篇聊 Agent 的记忆、工具调用和规划,都是围绕“决策”和“行动”展开的。但到了真实项目里,我发现一个特别容易被忽略的环节:Agent 的知识到底从哪儿来?你总不能指望大模型脑子里那点通用知识,把你公司几百页产品手册、客户的私有文档、业务系统的实时数据全部装进去吧。这时候就需要一条能让 Agent 主动获取外部知识的管道。这就是本期要说的 RAG 基础,也就是检索增强生成。
我先说一句大白话定位:RAG 就是给大模型配一个“图书管理员”。模型回答问题前,先让管理员去资料库查资料,把查到的相关内容塞进模型的上下文,模型再基于这些材料组织答案。就这么个机制,但真要把它做扎实,从文档清洗、文本切分、向量化到检索排序,每一步都有讲究。这篇既适合正在从零搭 Agent、卡在“不知道怎么把私有知识喂给模型”的开发者,也适合想彻底搞明白 RAG 到底解决什么问题、和微调有什么区别的初学者。我会把“为什么这么做”讲透,把参数和坑也一并交待。
1. 为什么 AI Agent 需要一条“知识获取管道”
1.1 大模型的知识边界,先得认清
我见过很多人犯同一个直觉性错误:以为大模型啥都知道。真不是这样。大模型的知识是预训练阶段“冻结”的,它记住的是训练数据截止日之前的世界。你的产品上周刚上线的新功能,它不知道;你公司内部那份三百页的运维规范,它没读过;你数据库里今天刚更新的库存数字,它更不可能知道。你要求一个只知道“通用世界知识”的模型回答这些具体问题,它唯一能做的就是胡编。
这个“胡编”的问题在 AI 圈有个专有名词叫幻觉。模型不是故意撒谎,而是它的知识结构里压根没有对应信息,它只能通过联想把最接近的词组合起来生成一个听起来合理的答案。在聊天场景里这没什么,大家哈哈一乐就过去了。但放到 Agent 要对接业务、要回答客户问题、要写代码的具体场景里,幻觉就是事故源头。
所以 RAG 首先要解决的就是“知识边界”这个问题。它做的不是在模型脑子里硬塞知识,而是把知识存放在模型外部的数据库里,在需要时按需取用。这里有个很关键的思想转变:与其努力让模型“记住”,不如让模型学会“查”。
1.2 RAG 的本质:把“翻资料”变成 Agent 的固定一环
我自己理解 RAG,喜欢把它拆成一个词:检索增强。核心不是“生成”,而是“增强”。增强的对象是大模型的回答能力,增强的手段是外部知识检索。
你如果把 Agent 的整个执行过程拆开看,会发现它本质上是一个感知到认知再到行动的回路。Agent 接收用户问题,这是感知;它决定下一步干什么,是调用工具还是直接回答,这是认知;它执行动作,这是行动。RAG 在这个回路里的位置特别有意思:它既是感知的一部分,也是认知的一部分。因为 Agent 得先知道“我有哪些知识可以用”,然后才能决定“我要不要为了解决这个问题去查一下资料”。
我把它称作“知识获取管道”,而不是简单叫“知识库”,核心原因就在这里。RAG 不是一个静态的存储容器,它是一条管道:知识源从一端流入,经过清洗、切分、向量化,变成可检索的索引,检索时再经过召回、排序、组装,最终流入模型的上下文。这个过程是流动的,每一步都影响最终结果。你把管道某一段堵了,或者用了不合适的参数,模型的回答质量就会明显下降。
1.3 为什么不用微调?先分清两个工具的边界
聊 RAG 的时候几乎必然会被问到:“既然要让模型拥有领域知识,为什么不直接微调?”我的回答是:微调和 RAG 根本不站在同一个赛道上,它们是解决两种不同问题的工具,甚至是互补关系。
微调是修改模型本身的行为。你的目标是让模型学会一种“表达方式”或“行为模式”,比如让它按固定格式输出 JSON、让它的语气更幽默、让它学会某种代码风格,这时候微调是有效的。但如果你要微调的目标是“记住知识”,问题就来了:第一,知识更新迭代快,每改一次文档内容你就得重新微调一次,这个成本在真实业务里根本扛不住;第二,微调是让模型把所有知识“背”进参数里,这会产生严重的知识混淆,模型很难分清楚它记住的到底是哪个版本的文档;第三,微调并不能根除幻觉,它只是让模型在熟悉的知识范围内少犯一点错,超出范围照旧瞎编。
RAG 的方式则完全绕开了上面三个问题。知识存在外部数据库里,更新内容只需要重新灌一次索引,不需要动模型;模型每次回答前都能拿到当前最新的材料,它检索到什么就基于什么回答;因为它不是靠记忆而是靠查证,幻觉的概率也会明显下降。所以我的经验是:先搭 RAG,等基础能力跑通之后,如果你发现模型在“格式规范”“工具调用方式”这类行为层面需要定制,再考虑用微调去补。
2. RAG 整体拆解:先建好检索用的索引
2.1 知识源加载与清洗:管道质量的第一道关
RAG 这个管道的第一步,是把知识源加载进来。这一步看起来不起眼,但它决定了后续所有环节的上限。你加载进来的文档如果是垃圾,后面切分、向量化做得再好也没用。
知识源的形态五花八门:PDF、Word、Markdown、HTML、Notion 导出、数据库记录,都可能成为知识来源。不同格式的加载方式完全不一样,我用过的方案里,干净规整的 Markdown 和纯文本最好处理,直接读取就行;HTML 要先去标签,只保留正文;PDF 是最麻烦的,特别是那种扫描版,需要 OCR 识别,还有各种复杂的表格和页眉页脚。
这里我想特别提醒一个坑:PDF 的逻辑结构。很多 PDF 文件表面上看着排版正常,但按文本流提取出来之后,句子顺序是乱的,表格数据粘连在一起,代码块里的缩进全丢了。我第一次跑一个开源 PDF 加载器处理一份混合了文字和代码块的文档,结果切分出的 chunk 里大段代码被拦腰截断,检索命中率惨不忍睹。后来换了保留布局信息的解析方案,用 OCR 辅助处理扫描页,情况才好转。
清洗阶段的核心任务有四个:去噪、去重、标准化、去敏感信息。去噪是去掉无用字符,比如页眉页脚、水印、多余的空格和换行;去重是防止同一份知识被多次加载,不然检索时同一答案会出现多份,浪费上下文窗口;标准化是把不同来源的文档统一成一种格式,比如日期格式、URL 格式、术语写法;去敏感信息更关键,你要让 RAG 回答业务问题,但绝不想让模型把内部机密信息也检索出来输出给用户,所以越权访问控制最好在这一步就开始考虑。
2.2 文本切分:整条管道里最容易翻车的环节
加载清洗之后,文档往往很长。你不能把整篇文档都塞给模型——上下文窗口有限且宝贵。所以你先把文档切成若干小段,每一段叫做一个 chunk。模型最终能引用的知识单位,就是这些 chunk。
很多人对切分不以为然,觉得不就是按字数切开嘛。实际上这是 RAG 里最容易翻车的环节。切得太碎,一个完整的概念会被拆成两半,检索时只能命中一半,模型拿到的信息不完整;切得太粗,一个 chunk 里混了多个主题,检索时命中但噪声太大,模型分不清哪个信息才是当前问题的答案。
我见过一种最常见也最不推荐的做法:直接按固定字符数硬切,比如每 512 个字符一刀切下去。这样切出来的 chunk 完全无视文本的语义边界,经常把一句话、一个段落、一段代码从中间拦腰斩断。我自己早期调 RAG 的时候就这么干过,结果检索来的 doc 片段读起来前言不搭后语,模型的回答质量和命中率都上不去。
推荐的做法是按“语义边界”切分。最朴素的办法是按段落切,因为段落天然表达一个相对完整的语义单元;进阶一点的做法是按句子边界、列表项、代码块边界等自然结构切,让每个 chunk 尽量完整表达一个含义。目前主流的 RAG 框架都内置了这类递归式切分器,它会先按大分隔符切,发现 chunk 还是太长,再按更细的分隔符递归切下去,直到满足长度要求。我在实际项目中常用的参数是单个 chunk 300 到 500 字,chunk 之间保留 30 到 80 字的重叠。这个重叠非常重要,它能保证切分边界附近的上下文不丢失。
2.3 向量化:把文本变成数字
chunk 切好之后,接下来的一步是把这些文本变成向量。这背后有一个重要的思想:文本之间有没有相关性,模型无法直接比较,但数字可以。你可以把每个 chunk 通过嵌入模型换算成一个高维向量,语义相近的文本在向量空间里距离也更近。所谓“语义相近”,不光是关键词重合,还包括意思相近。一句话“今天上海天气怎么样”和“上海今天气温情况”, 关键词差异不小,但它们表达的意思几乎一样,在向量空间里就会彼此靠近。
选择嵌入模型是个值得花时间的决策。不同模型的向量维度、语义能力、语言支持都有差异。英文场景主流的选择就那几个,中文场景我建议优先考虑专门针对中文优化的模型。国内有几个开源的嵌入模型,比如 BGE 系列,在中文语义理解上实测效果不错。你需要重点关注的指标有两个:一是向量维度,维度越高一般表达能力越强,但存储和检索的代价也相应增大;二是最大输入长度,如果嵌入模型最长支持 512 个 token,那你的 chunk 就尽量不要超过这个长度,否则超出的部分会被截断丢掉。
向量化还有一个我建议新手先知道的工程经验:批处理不要一次性把全部文本丢给嵌入接口。不是技术限制问题,是稳定性问题。几千个 chunk 一次性发给嵌入服务,超时和限流会让你头皮发麻。我平时习惯分成每批 64 或 128 个请求,还要做失败重试。这个习惯看着琐碎,但在构建真实规模的知识库时能帮你省下大多数不必要的重跑时间。
2.4 向量存储与索引:选型没有最优,只有适合
向量化完成之后,你需要一个地方存放这些向量,并且支持按相似度快速检索。这就轮到了向量数据库登场。市面上选择很多,我梳理下来大概分三类。
第一类叫轻量嵌入式方案,代表是 Chroma 和 FAISS, 它们更适合原型验证和中小规模知识库,不用额外部署服务,代码里直接调用即可。我用 Chroma 做过不少练手项目,省心。
第二类叫独立向量数据库,代表是 Milvus 和 Qdrant, 定位是大规模生产环境,支持分布式部署、高并发访问、复杂元数据过滤。如果你的知识库体量到了千万级向量、每天有海量查询请求,这类是更稳妥的选择。
第三类是多模数据库内置的向量能力,代表是 pgvector 和 Elasticsearch 的向量检索。如果你的项目已经在用 PostgreSQL 存储业务数据,直接在现有库上装一个 pgvector 扩展就能兼做向量检索,少维护一个组件,对中小项目很友好。
表格对比一下会更直观:
| 方案类型 | 代表产品 | 适合场景 | 优点 | 代价 |
|---|---|---|---|---|
| 轻量嵌入式 | Chroma、FAISS | 原型验证、单机小库 | 部署简单、开发效率高 | 大规模并发较弱 |
| 独立向量库 | Milvus、Qdrant | 生产环境、海量数据 | 性能强、扩展性好 | 需要额外部署运维 |
| 数据库扩展 | pgvector、Elasticsearch | 已有数据库/搜索引擎 | 组件少、复用现成系统 | 大规模检索需调优 |
这里我想强调一句自己的体会:不要为了用向量数据库而用向量数据库。如果只是几百上千个 chunk,你甚至可以用内存列表暴力算相似度,效果一样不错。上向量数据库应该是为了应对规模扩大和并发提升,而不是因为它是 RAG 的标配。选型前先估算一下知识库的量级和查询频率,很多团队在这上面过度设计,导致架构里平白多出一个需要维护的组件。
建索引的时候还有两个配置很容易被忽略。第一个是索引类型,向量数据库通常支持精确检索和近似最近邻检索两种类型,精确检索结果最准但速度慢,近似检索速度快但有一定的精度损失。数据量大到一定规模,用近似检索是必然选择,但你最好理解它带来的召回损失,并在评估阶段记录基线。第二个是元数据字段。每个 chunk 在入库时除了向量本身,还应该带着文件名、章节号、更新时间、权限级别这样的元数据。检索时你可以先用元数据过滤缩小范围,再在过滤范围内做向量检索,速度和精度都会有明显提升。
3. 检索与生成:让管道转起来
3.1 查询改写:让检索更准的隐藏环节
索引建好了,但检索这一步不是把用户问题原封不动丢给向量库就行。用户提问的表达方式千差万别,而知识库里的文本是另一种表达方式。两边对不上,召回质量就会很受影响。
我举个例子。用户问“这个产品的保价政策是什么”,但知识库里对应文档的标题写的是“售后服务条款中关于价格保护的规定”。从语义上看它们确实是相关的,如果只做向量检索,大概率也能召回到,但相似度分数未必理想。如果你先把用户问题改写成“价格保护 售后服务 规定”或者“保价政策 条款 适用条件”,检索效果会好很多。
这种改写操作可以交给大模型完成。在用户问题进入检索器之前,先用一个轻量级 Prompt 让模型把问题重写为适合检索的形式。它可以做三件事:提炼关键词、把口语化表达转成书面表达、补全问题里隐含的条件。还有一类更高级的玩法叫 HyDE, 它不只是改写问题,还会先用一个模型根据问题生成一段假想的答案文本,再用这段文本去检索。因为假想答案和库内真实文本的行文风格更接近,向量的相似度往往比用问题原文检索更高。我在实际项目里用过这个方案,确实能提升召回率,代价是多一次模型调用,响应耗时会略微增加。
这里你要做一个权衡:查询改写对单轮问答的提升可能不明显,但对多轮对话场景作用显著。用户上一轮提到“那个基金”,这一轮问“它最近表现怎么样”,如果你不把“它”指代的内容替换回完整名词,检索器根本不知道要查什么。Agent 场景里多轮对话很常见,所以我的建议是:只要你的 Agent 支持连续对话,查询改写这一步就不该省。
3.2 相似度检索的关键参数
进入检索阶段之后,需要调的核心参数主要有三个:top_k、相似度阈值和检索策略。
top_k 是取回多少个相关的 chunk。这个参数直接决定上下文的质量和体量。调小了,相关信息可能漏掉;调大了,不相关的信息会被塞进上下文,既浪费 token 又污染生成结果。我一般从 4 开始调,再根据问题复杂度和 chunk 颗粒度上下浮动。简单问题 3 就够,需要跨多个文档综合回答的问题可能要到 8。有一个经验可以参考:如果多个相关的答案分散在不同 chunk 里,top_k 不够会让模型漏答;如果单个 chunk 已经足够完整,top_k 就没必要设置很大。
相似度阈值是过滤低质量检索结果的一道闸门。向量检索返回的每个结果都有一个相似度分数,比如用余弦相似度计算时分数范围在 -1 到 1 之间。你设定一个阈值,低于阈值的 chunk 直接丢弃,宁可模型说“我不知道”,也不要给它不相关的噪声信息再用它胡编乱造。阈值通常在 0.5 到 0.8 之间,具体数值取决于你用的嵌入模型和分数计算方式。这里我给不了你万能值,但能告诉你调法:先从 0.7 起步,多试几条查询,观察召回的结果里有多少是明显不相关的,如果噪声多就往上提,如果漏检多就往下降。有时候换个嵌入模型,分数分布整体变化,之前的阈值就失效了,这个要做记录。
还有一个很多教程不提的细节:相似度分数的分布不均匀时,阈值一刀切会误伤有效结果。我自己的做法是结合 top_k 的截断机制:先按相似度分数从高到低排序,取前 2 倍左右的候选数,再用阈值过滤。这样既避免了阈值过严导致一条不剩,也避免了 top_k 过大把噪声全部带入。说白了,检索不是非黑即白,参数组合调优比单独调一个参数更有效。
3.3 混合检索与重排:从“能用”到“好用”的两个增强手段
这里我必须先讲一个知识:向量检索并不是万能的。它擅长理解语义相似,但在关键词精确匹配和罕见词匹配上反而会翻车。举个例子,你想检索一段包含“AGPL-3.0 许可证”字样的文档,如果知识库里的文本提到的是“GNU Affero 通用公共许可证”,向量检索能找到;但如果你想精确匹配代码里某个函数名“convertToCamelCase”,向量检索的语义模型未必能把这个名字放在突出位置,传统的关键词检索反而一找一个准。
所以实践中可靠性比较高的方案是混合检索。用关键词检索和向量检索并行,分别召回候选结果,再合并。关键词检索的经典算法是 BM25, 它基于词频和逆文档频率给文本打分,对精确词匹配效果非常好。现在很多向量数据库已经内置了混合检索能力,也有框架自带 BM25 实现,接入并不复杂。我遇到的绝大多数真实业务场景里,混合检索的命中率都是明显高于单纯向量检索的,这一点几乎没有什么悬念,值得你优先落地。
检索完合并之后还有一步叫重排。前期的粗粒度召回可能混着大量不相关结果,重排模型会把候选结果重新打分排序,把最相关的放最前面。常用的做法是使用专门的交叉编码器模型,它会把查询和每个文档片段当成一对整体输入模型,计算两者的相关性分数,粒度比向量相似度精细很多。你可以只取重排后的前三名作为最终上下文。
重排这步的价值在知识库稍大时就会显现出来。向量检索第一步召回的前几名,和真实最相关的片段往往并不完全一致,多多少少有偏差。你不重排直接让模型基于这些结果回答,上下文靠前的可能是中等相关的片段,反而压制了真正的答案。重排的意义就是把最好的材料挑到最前面,让模型第一眼就看到它。代价是多一次推理调用,但对回答准确率的提升绝对值回这个成本。检索参数调整建议用表格整理好,方便后期排查:
| 参数 | 建议起始值 | 调整信号 | 调整方向 |
|---|---|---|---|
| chunk_size | 300-500 字 | 检索片段语义不完整 | 调大;若片段太杂则调小 |
| chunk_overlap | 30-80 字 | 边界信息丢失 | 调大重叠 |
| top_k | 4 | 召回不足/噪声过多 | 不足调大,噪声大调小 |
| 相似度阈值 | 0.7 | 噪声多提高/漏检降低 | 观察分数分布后微调 |
| 重排截断数 | 3 | 上下文过长/精度不足 | 平衡 token 与精度 |
3.4 Prompt 组装与答案生成:最后一步同样不能随便来
索引构建好了,检索结果也拿到了,但任务还没完。你还需要把用户问题、检索到的相关资料、系统指令组装成一个合适的 Prompt 送给大模型,这一步直接决定最终回答形式。有些开发者在这里偷懒,直接把所有检索片段拼在问题前面,期望模型自己挑重点,结果就是生成内容混乱、格式不统一,甚至回答里根本引用不到关键材料。
我建议 Prompt 模板至少要包含四个部分:系统指令、检索资料、用户问题、输出约束。系统指令要告诉模型你的角色和任务,比如“你是企业知识助手,请严格依据下方资料回答问题”这类定位。检索资料部分要做成有清晰标识的块状结构,用特殊标记区分每一条来源,这样模型能明确知道哪些是参考材料。输出约束部分要明确要求,比如“当资料不足以回答问题时,直接回答不知道,禁止编造”“回答尽量引用资料中的原话”。这四个部分的顺序和措辞,直接影响回答质量。
还有一个容易忽略的点:引文与来源。如果你希望 Agent 的回答可追溯、可审计,那么 Prompt 里应该要求模型在回答中标注它依据的是哪一块资料。实现方式一般是在资料块前面加上编号或文档名,然后要求模型在回答的对应位置用括号标注来源。这一步的作用不只是“看起来规范”,更重要的是当你发现回答质量有问题时,可以顺着引文快速定位是哪个环节出了问题:是检索没召回到位,还是生成时理解错了资料。
组装完 Prompt 之后,生成阶段同样有几个要点。温度参数建议调低,知识问答场景温度太高模型会自由发挥,温度设置在 0.2 以下比较稳妥。生成的最大长度要留足,但也不能无限大,否则模型会在材料不足时开始车轱辘话。更重要的是,不要让模型在生成时进行多步推理和事实判断之外的过度扩展,知识问答要的是“克制”而非“创造”。
4. 常见问题与排查技巧实录
4.1 先判断:检索挂了还是生成挂了
几乎所有人调 RAG 都会遇到同样的问题:回答质量差。但我发现大家的第一反应往往是去改 Prompt,甚至是换大模型。这个排查思路是反的。RAG 的质量由管道全链路决定,很多问题根本不在生成阶段,而是在检索阶段就已经埋下了。
我自己的排查顺序很固定。先看检索到的资料到底对不对。你可以把最终送进 Prompt 的那几份 chunk 单独拿出来读一遍,如果它们本身就和用户问题无关,那不管 Prompt 写得再好、模型再强,答案都不可能正确。这叫检索问题。反过来说,如果检索到的资料明显相关,模型却没答好,那才是生成问题,这时候才需要调整 Prompt 或生成参数。这一步判断很关键,因为它能把排查范围直接缩小一半。
我强烈建议你在开发阶段就做一个调试页,把每次问答的检索结果可视化出来,而不是只看到最终答案。这样你能一眼看到系统到底检索了什么、评分是多少、相关度如何。缺少这个调试手段,你排查 RAG 问题会非常痛苦,因为“黑盒”状态下你完全无法判断问题在哪一环。
4.2 切分不合理的典型症状
切分问题最隐蔽,因为它的症状往往不是“检索不到”,而是“检索到了但答案不完整”。比如用户问“退货政策是什么”,系统召回的片段里有一句“支持七天无理由退换货”,但具体到“哪些商品不参与”的那句话躺在下一个 chunk 里,模型只看到了前半句,给出一个片面甚至误导性的回答。
这种问题在面向管理者的视角看就是“答非所问”“客服出错”,实际上根源是切分切碎了知识的完整性。解决办法前面提过:一是按语义边界切分而非固定字符截断,二是设置足够的重叠区间。还有一个更进阶的方案叫父子分块:父级 chunk 大一些保证上下文完整,子级 chunk 小一些方便精准检索,命中子级时把父级内容一并送给模型。这种方法效果好,但复杂度也上来了,建议先把基础切分调通之后再去尝试。
另一个切分相关的坑是不同文档类型混合切分。产品文档、代码文档、合同文档,它们的语义单位完全不同,硬要用同一套切分参数处理所有类型,必然造成某些类型的效果崩溃。我的做法是分门别类处理:代码类文档按函数和类切,不用按段落;合同类文档按条款切,保留条款编号;长文本报告按章节和标题级层次切。这听起来繁琐,但能显著提升最终检索质量。
4.3 信息过载与上下文污染
有一类问题是“检索到的内容太多太杂”。有时候 top_k 设置得过大,或者检索策略的召回太激进,最终塞进 Prompt 的片段里只有一两份是真正相关的,其余全是噪声。模型面对一堆不相关材料,很容易被带偏,反而忽略真正的答案。
上下文污染还有一个来源是历史对话。多轮会话的 Agent 通常会把历史对话摘要也放进 Prompt,如果历史摘要里包含错误的中间结论,这些错误信息会像污染源一样传染给当前回合的生成结果。我遇到过不止一次:单轮问答表现良好,一进入多轮对话质量立刻下降,最后排查发现就是历史摘要里的过时信息在捣鬼。
应对信息过载的办法,一个是严格限制 top_k 和阈值,宁缺毋滥。另一个是分步筛选:先粗召回较多候选,再用重排模型精挑细选,只把质量最高的几份送进上下文。多轮对话场景则建议弱化历史信息在 Prompt 中的权重,给当前检索到的资料更高的优先级,必要时可以只保留对话摘要而丢弃逐字逐句的原始历史记录。
4.4 用 hit rate 量化评估:别靠感觉调参
没有评估指标的 RAG 优化,都是凭感觉碰运气。你改了切分参数,怎么知道是好是坏?改了阈值,怎么知道有没有提升?这都需要一套量化评估机制。最简单有效的一个指标叫命中率,英文就叫 hit rate。
计算方式不复杂:准备一批测试问题,每个问题都标注好它最应该命中的知识片段是哪一个;跑完检索后,看这些问题里有多大比例确实召回到了目标片段。比如准备了 100 个测试问题,检索结果命中了 80 个,那 hit rate 就是 80%。这个指标衡量的是你的索引和检索策略“有没有把正确的材料找出来”,是 RAG 最基本的能力指标。有了它,你就能用数据而不是感觉来判断每次参数调整是否有效。更专业的评估框架,比如 RAGAS, 还会从上下文精准度、答案忠实度、答案相关性等多个维度给出综合评分,功能更全但指标也更多。我建议新手先做一套自己的 hit rate 测试集,把所有评估基础打好,再去碰这些复杂框架。
常见问题速查表
| 症状 | 可能原因 | 首选排查方法 |
|---|---|---|
| 回答内容与问题无关 | 检索召回不相关 | 查看检索片段,调高相似度阈值 |
| 回答内容片面、信息缺失 | chunk 切碎了完整知识 | 改按语义边界切分、增大重叠 |
| 答案存在明显幻觉 | 生成阶段自由发挥 | 降低温度、在 Prompt 中加入禁止编造约束 |
| 相同问题不同回答差异大 | 检索结果不稳定 | 检查向量库索引一致性、固定生成参数 |
| 多轮对话后质量下降 | 历史摘要污染 | 降低历史权重,仅保留必要摘要 |
| 检索命中但答案答不到点 | 重排不到位 | 引入重排模型,精筛前 3 条 |
5. 从“基础 RAG”到“Agentic RAG”的一点心得
到这里,RAG 的基础管道算是完整铺开了。不过如果把它放进 Agent 的语境里,我觉得还要多聊几句进阶的思考,因为这个帖子毕竟是在“AI Agent”系列下写的。
基础 RAG 的问题在于流程是固定的。用户问题来了,系统就去检索,检索完了就生成,每次都这么做,不管这个问题需不需要翻资料、需要翻几次资料、需不需要翻完再顺着线索继续查。这在复杂问题面前就显得笨。比如用户问“对比一下 A 方案和 B 方案在售后支持上的差异”,一条固定检索管线可能只召回 A 文档或者只召回 B 文档,很难同时覆盖两个维度的对比,而它又不会主动去查两轮。Agentic RAG 就是要把这种“何时检索”“检索几轮”“检索结果不够时怎么办”的决策权交给智能体本身。智能体根据问题规划检索步数,每次检索后评估信息是否充足,不够就改写查询继续检索,直到足够才开始生成。这个能力模型上叫 agentic loop, 工程上则需要你提前把 RAG 封装成可调用的工具,再配给 Agent 的规划模块。它比基础 RAG 灵活,但也意味着你要处理更复杂的错误传播和控制逻辑。
还有一个演进方向是图结构增强的 RAG, 常见的是 GraphRAG 一类方案。传统 RAG 把知识切成互相孤立的 block, 丢失了知识之间丰富的关联关系;图结构则把实体、概念、关系显式建模,特别适合回答“哪些因素影响了 X”“A 和 B 之间有什么联系”这类需要跨段落综合的问题。代价是构建和存储的复杂度明显上了一个台阶。我对初学者的建议是:先把基础 RAG 做扎实,hit rate 稳定在理想区间后再去引入图结构,不要一开始就给自己上难度。
毕竟知识获取管道这个东西,复杂度是随着真实需求层层长出来的。我个人的实操经验是:先跑通最小闭环,再逐步替换薄弱环节。拿我最早做的那个文档问答 Agent 来说,第一版就只是简单的向量检索加拼接 Prompt, 效果一般但能跑;后来逐步加了查询改写、混合检索、重排、评估指标,每一步都是因为遇到了真实的问题才被逼着做的。这比起一开始就照着大而全的架构去搭,要实在得多。
最后再分享一个小技巧,也是我这段时间最深的体会:RAG 的效果瓶颈往往不在模型,而在你对知识源的理解程度。花时间搞清楚你的文档有哪些类型、哪些字段是关键的元数据、哪类问题用户问得最多,比调任何参数都更能提升最终效果。工具是固定的,知识是流动的,你先把它理顺了,后边的工程实现才谈得上从容。