1. Agent 时代RAG 的定位到底变了什么RAG 这个词从 2023 年火到现在几乎成了大模型应用落地的标配。但如果你最近还在用“文档切块 → 向量化 → 余弦相似度召回 → 塞进 Prompt”这套经典流程去应对 Agent 场景大概率会遇到一个尴尬的局面召回的内容看起来相关Agent 用起来却总是答非所问或者干脆在工具调用链里把检索结果丢在一边不用。问题不在于 RAG 本身失效了而在于Agent 对 RAG 的需求和传统问答机器人完全不同。传统 RAG 是“一问一答”检索是终点Agent 场景下检索是中间步骤是 Agent 决策链上的一环。Agent 可能先检索一次拿到线索调用工具验证发现信息不够再检索第二次甚至需要跨多个知识源做交叉比对。这种“多轮、动态、带反馈”的检索模式就是现在圈子里讨论很多的Agentic RAG。我拿一个实际项目举例。之前做过一个内部技术文档助手最初用的是标准 RAG 流程用户问“LangChain4j 里 Easy RAG 怎么配置多路召回”系统能召回相关段落回答也还行。但后来把它接入 Agent 框架让 Agent 自己决定什么时候检索、检索什么、要不要二次检索效果反而变差了——因为 Agent 拿到第一次召回结果后经常直接开始编答案而不是判断“这些内容够不够”。这说明Agent 时代的 RAG核心矛盾从“能不能召回”变成了“Agent 会不会用召回结果”。所以这篇内容想聊的不是“RAG 是什么”这种入门科普而是在 Agent 架构下RAG 该怎么选型、怎么设计、怎么和 Agent 的决策循环配合。适合已经在做 Agent 项目、或者正准备把现有 RAG 系统升级到 Agent 架构的开发者。如果你还在纠结“RAG 和微调选哪个”那说明你还没进入 Agent 场景这篇可以先收藏等真正开始搭 Agent 的时候再翻出来看。2. Agentic RAG 的核心设计思路拆解2.1 为什么传统 RAG 在 Agent 里容易“水土不服”传统 RAG 的隐含假设是检索是一次性的且召回内容一定和问题相关。这个假设在单轮问答里成立因为用户问题本身就是检索 query语义空间对齐得比较好。但 Agent 场景下检索 query 往往不是用户原始问题而是 Agent 在推理过程中生成的子问题。比如用户问“帮我对比一下 LangChain4j 和 Spring AI 的 RAG 实现差异”Agent 可能会拆成“LangChain4j RAG 流程”“Spring AI RAG 流程”“两者向量存储选型差异”三个子查询分别检索后再汇总。这时候如果还用传统 RAG 的“一次召回 Top-K”策略就会出现两个问题。第一子查询的语义空间和原始文档块不一定对齐比如“向量存储选型差异”这个查询可能召回的是“如何配置向量存储”的文档而不是“选型对比”的文档。第二Agent 没有机制判断召回内容是否足够它只能选择“用”或者“不用”而不能选择“再检索一次”或者“换个角度检索”。我踩过的一个坑是Agent 在调用 RAG 工具后如果召回内容里没有直接答案它倾向于用已有知识编一个看起来合理的回答而不是说“我没找到”。这在内部文档场景里很危险因为编造的技术参数可能误导用户。后来我在 RAG 工具返回结果里加了一个字段confidence让 Agent 根据置信度决定是否二次检索情况才好转。2.2 Agentic RAG 的三种典型架构目前业界讨论比较多的 Agentic RAG 架构大致可以归为三类每类适合不同的场景和团队规模。第一类Agent 主导型。Agent 拥有完全的检索决策权自己生成查询、调用检索工具、判断结果、决定是否继续检索。这种架构最灵活但对 Agent 的推理能力要求很高而且容易陷入“无限检索循环”。适合 Agent 框架成熟、有明确终止条件的场景比如代码助手、研究助手。第二类RAG 主导型。RAG 模块内部实现多轮检索和重排序对外暴露一个“智能检索”接口Agent 只需要调用一次。这种架构对 Agent 友好但 RAG 模块本身复杂度高需要实现查询改写、多路召回、重排序、置信度评估等逻辑。适合 Agent 能力较弱、或者希望把检索逻辑收敛到一处的团队。第三类混合编排型。Agent 和 RAG 各自承担一部分决策比如 Agent 负责生成查询和判断结果RAG 负责执行检索和返回置信度。这种架构平衡了灵活性和可控性但需要定义清晰的接口协议。我目前项目里用的就是这种Agent 生成查询后RAG 模块会返回results和confidenceAgent 根据置信度决定下一步。选哪种架构核心看两个因素Agent 框架的成熟度和团队对检索链路的掌控能力。如果用的是 Agentscope 2.0 这类较新的框架Agent 主导型可能更顺手如果用的是 LangChain4j 的 Easy RAG 这类封装度高的库RAG 主导型改造成本更低。2.3 检索粒度与 Agent 决策粒度的匹配传统 RAG 通常按固定长度切块比如 512 token 一块重叠 50 token。这个粒度在单轮问答里够用但在 Agent 场景下经常出问题。Agent 的决策粒度可能是“一个函数签名”“一个配置项”“一个错误码”而文档块可能包含多个不相关的信息导致 Agent 提取关键信息时被噪声干扰。我的做法是按语义单元切块而不是按固定长度。比如技术文档里一个配置项说明、一个 API 签名、一个错误码解释各自作为一个独立块。这样 Agent 检索到“LangChain4j Easy RAG 配置”时召回的就是配置项本身而不是包含配置项的一大段文字。实现上可以用 Markdown 标题层级、代码块边界、列表项边界作为切分依据比纯按 token 切效果好很多。另一个细节是给每个块加上元数据比如来源文件、章节路径、最后更新时间。Agent 在判断召回内容是否可信时可以参考这些元数据。比如召回内容来自“草稿”目录Agent 就应该降低置信度来自“正式发布”目录就可以提高置信度。这个机制在内部知识库场景里特别有用因为内部文档经常有多个版本。3. 核心细节解析与实操要点3.1 查询改写Agent 生成查询的三种策略Agent 生成检索查询的方式直接决定了召回质量。我试过三种策略各有优劣。第一种直接使用用户原始问题。最简单但效果最差。因为用户问题往往包含多个意图直接检索会召回一堆不相关的内容。比如“LangChain4j 和 Spring AI 的 RAG 实现差异”直接检索可能召回两边的文档但无法保证覆盖“差异”这个关键点。第二种Agent 拆解子问题。让 Agent 把用户问题拆成多个子查询分别检索后汇总。这种策略召回覆盖率高但容易产生冗余而且子查询之间的顺序和依赖关系需要 Agent 自己维护。我在项目里用 LangChain4j 的QueryTransformer做过类似实现效果不错但需要给 Agent 明确的拆解规则比如“每个子查询不超过 15 个字”“子查询之间用 AND 关系”。第三种查询改写 扩展。Agent 不仅拆解问题还对每个子查询做同义词扩展和上下文补全。比如“Easy RAG 配置”扩展成“LangChain4j Easy RAG 配置方法”“Easy RAG 多路召回配置”“Easy RAG 向量存储配置”。这种策略召回率最高但需要维护同义词表和领域词典成本也最高。实测下来第二种策略性价比最高适合大多数 Agent 项目。第三种策略适合领域术语多、用户表达差异大的场景比如医疗、法律。第一种策略只适合原型验证阶段。3.2 多路召回与重排序的工程实现Agent 场景下单路召回往往不够因为不同检索方式擅长的查询类型不同。向量检索擅长语义相似关键词检索擅长精确匹配图检索擅长关系推理。多路召回就是把多种检索结果合并再统一重排序。我目前项目里用的是向量检索 BM25 关键词检索 元数据过滤三路召回。向量检索用 Ollama 本地部署的 embedding 模型BM25 用 Lucene 实现元数据过滤用简单的字段匹配。三路结果合并后用一个轻量级重排序模型比如 BGE Reranker做精排取 Top-5 返回给 Agent。这里有个细节重排序模型的输入格式要和 Agent 的查询意图对齐。比如 Agent 查询是“LangChain4j Easy RAG 配置”重排序时应该把包含“Easy RAG”和“配置”的文档排前面而不是单纯按语义相似度排。我的做法是在重排序前给每个文档算一个“关键词命中率”分数和重排序分数加权求和。权重根据场景调技术文档场景下关键词命中率权重可以高一些因为技术术语精确匹配很重要。另一个坑是多路召回的延迟。三路召回并行执行总延迟取决于最慢的一路。如果向量检索用远程 API延迟可能到 500ms 以上拖累整体响应。我的做法是给每路召回设超时比如 300ms超时就用已有结果继续不阻塞整体流程。这个策略在 Agent 场景下特别重要因为 Agent 可能连续调用多次检索每次延迟累积起来很可观。3.3 置信度评估让 Agent 知道“该不该信”Agent 拿到召回结果后需要判断“这些内容够不够回答用户问题”。这个判断机制就是置信度评估。没有置信度评估Agent 要么盲目相信召回内容要么完全不用两种都不可取。我用的置信度评估方法比较简单但实测有效。第一看召回结果的分数分布。如果 Top-1 分数远高于 Top-2说明召回内容聚焦置信度高如果 Top-1 到 Top-5 分数差不多说明召回内容分散置信度低。第二看召回结果和查询的关键词重叠度。重叠度高置信度高。第三看召回结果的元数据。来自权威来源的置信度高。这三个信号加权求和得到一个 0 到 1 的置信度分数。Agent 根据这个分数决定下一步高于 0.8直接用0.5 到 0.8可以尝试回答但标注“信息可能不完整”低于 0.5触发二次检索或告知用户“没找到”。这里有个经验置信度阈值不要设太高。我一开始设 0.8结果 Agent 频繁触发二次检索响应时间翻倍。后来降到 0.6效果好很多。因为 Agent 本身有一定的推理能力即使召回内容不完美它也能从中提取有用信息。阈值太高反而浪费了 Agent 的能力。3.4 工具调用与 RAG 的接口设计Agent 调用 RAG 工具时接口设计很关键。我见过一些项目把 RAG 工具设计成“输入查询返回文本”这种接口对 Agent 不友好因为 Agent 无法判断返回文本的质量也无法控制检索参数。我的做法是把 RAG 工具设计成“输入查询和参数返回结构化结果”。参数包括top_k、confidence_threshold、source_filter等Agent 可以根据场景调整。返回结果包括results列表每项含content、score、metadata、confidence整体置信度、suggestions建议的后续查询。这样 Agent 不仅能拿到内容还能拿到判断依据和下一步建议。接口设计还有一个细节支持流式返回。Agent 场景下检索可能耗时较长如果等全部结果返回再给 Agent响应时间会很长。我的做法是检索到一批就返回一批Agent 可以边接收边处理。这个在 LangChain4j 里可以用StreamingChatLanguageModel配合实现但需要自己管理流式状态。4. 实操过程与核心环节实现4.1 环境准备与依赖选型先说我目前项目里的技术栈供参考。Agent 框架用的是 LangChain4j因为团队 Java 背景强而且 LangChain4j 的 Easy RAG 封装度合适改造成本低。向量存储用的是 Ollama 本地部署的nomic-embed-text模型配合内存向量库做原型生产环境换成了 PostgreSQL pgvector。关键词检索用 Lucene图检索暂时没上因为项目里关系推理需求不多。依赖清单大致如下dependency groupIddev.langchain4j/groupId artifactIdlangchain4j/artifactId version0.35.0/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-easy-rag/artifactId version0.35.0/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-pgvector/artifactId version0.35.0/version /dependencyOllama 本地部署 embedding 模型的命令ollama pull nomic-embed-text ollama serve这里有个坑Ollama 默认端口是 11434如果本地有其他服务占用需要改端口。改端口后LangChain4j 的配置也要同步改否则连接失败。我一开始没注意排查了半天。4.2 文档切块与元数据注入文档切块我写了一个自定义的DocumentSplitter按 Markdown 标题层级和代码块边界切分。核心逻辑是遇到##或###标题新起一块遇到代码块开始标记新起一块遇到列表项如果列表项超过 3 个每个列表项独立成块。public class MarkdownSplitter implements DocumentSplitter { Override public ListDocument split(Document document) { String content document.text(); ListDocument chunks new ArrayList(); // 按标题和代码块边界切分 String[] sections content.split((?##|###|)); for (String section : sections) { if (section.trim().isEmpty()) continue; Document chunk Document.from(section.trim()); chunk.metadata().put(source, document.metadata().get(source)); chunk.metadata().put(section, extractSectionTitle(section)); chunks.add(chunk); } return chunks; } }元数据注入包括source来源文件、section章节路径、updated_at更新时间、authority权威级别。authority是我自己加的字段取值high、medium、low对应正式文档、草稿、临时笔记。Agent 在置信度评估时会参考这个字段。4.3 多路召回与重排序的代码实现多路召回的核心是并行执行三路检索然后合并结果。我用CompletableFuture实现并行public ListRetrievalResult multiRetrieve(String query, int topK) { CompletableFutureListRetrievalResult vectorFuture CompletableFuture.supplyAsync(() - vectorRetrieve(query, topK)); CompletableFutureListRetrievalResult bm25Future CompletableFuture.supplyAsync(() - bm25Retrieve(query, topK)); CompletableFutureListRetrievalResult metaFuture CompletableFuture.supplyAsync(() - metaRetrieve(query, topK)); ListRetrievalResult merged new ArrayList(); try { merged.addAll(vectorFuture.get(300, TimeUnit.MILLISECONDS)); merged.addAll(bm25Future.get(300, TimeUnit.MILLISECONDS)); merged.addAll(metaFuture.get(300, TimeUnit.MILLISECONDS)); } catch (TimeoutException e) { // 超时就用已有结果 } return rerank(query, merged, topK); }重排序用 BGE Reranker输入是查询和文档对输出是相关性分数。这里有个细节重排序前要去重因为三路召回可能有重复文档。去重按文档 ID 或内容哈希保留分数最高的那条。4.4 置信度评估与 Agent 决策循环置信度评估的实现public double evaluateConfidence(String query, ListRetrievalResult results) { if (results.isEmpty()) return 0.0; // 分数分布信号 double top1 results.get(0).score(); double top2 results.size() 1 ? results.get(1).score() : 0.0; double distributionScore top1 0 ? (top1 - top2) / top1 : 0.0; // 关键词重叠信号 SetString queryTerms tokenize(query); double overlapScore results.stream() .mapToDouble(r - overlapRatio(queryTerms, tokenize(r.content()))) .average().orElse(0.0); // 元数据信号 double authorityScore results.stream() .mapToDouble(r - authorityWeight(r.metadata().get(authority))) .average().orElse(0.0); return 0.4 * distributionScore 0.3 * overlapScore 0.3 * authorityScore; }Agent 决策循环里根据置信度决定下一步double confidence evaluateConfidence(query, results); if (confidence 0.6) { return generateAnswer(query, results); } else if (confidence 0.3) { // 二次检索换个查询角度 String newQuery rewriteQuery(query, results); return multiRetrieve(newQuery, topK); } else { return 没找到相关信息建议换个问法或提供更多上下文; }这个循环最多执行三次避免无限检索。三次后如果置信度还是低就返回“没找到”。5. 常见问题与排查技巧实录5.1 召回内容相关但 Agent 不用怎么排查这是最常见的问题。召回内容明明相关Agent 却忽略它自己编答案。排查思路分三步。第一步检查 Prompt 里有没有明确要求 Agent 使用召回内容。很多 Agent 的 System Prompt 只写了“你可以使用检索工具”但没写“必须优先使用检索结果”。Agent 会倾向于用自己的知识回答因为这样更快。我的做法是在 Prompt 里加一句“如果检索结果包含答案必须基于检索结果回答并标注来源”。第二步检查召回内容的格式是否对 Agent 友好。如果召回内容是一大段文字Agent 可能提取不到关键信息。我的做法是把召回内容格式化成“来源xxx\n内容xxx\n置信度xxx”让 Agent 一眼看到关键信息。第三步检查 Agent 的推理链是否被截断。有些 Agent 框架对推理链长度有限制如果召回内容太长Agent 可能还没读到关键部分就被截断了。我的做法是控制单次召回内容长度比如不超过 2000 token超长的分多次召回。5.2 检索延迟高Agent 响应慢怎么优化Agent 场景下检索延迟是敏感指标因为 Agent 可能连续调用多次。优化思路分三层。第一层减少检索次数。通过置信度评估让 Agent 在置信度高时直接回答不触发二次检索。我实测下来置信度阈值从 0.8 降到 0.6检索次数减少 40%响应时间减少 30%。第二层并行化检索。多路召回并行执行总延迟取决于最慢的一路。给每路设超时超时就用已有结果。这个策略在向量检索用远程 API 时特别有效。第三层缓存检索结果。相同查询的检索结果缓存起来下次直接返回。缓存用 LRU 策略容量根据内存定。我项目里缓存命中率大概 20%响应时间减少 15%。5.3 常见问题速查表问题现象可能原因排查方法解决方案Agent 忽略召回内容Prompt 未强制使用检查 System Prompt加“必须基于检索结果回答”召回内容不相关查询改写不到位打印 Agent 生成的查询优化查询拆解规则检索延迟高多路召回串行执行检查检索代码改并行执行设超时置信度评估不准信号权重不合理人工标注验证调整权重重新校准Agent 陷入检索循环无终止条件检查决策循环设最大检索次数召回内容重复多路召回未去重检查合并逻辑按文档 ID 去重元数据过滤失效元数据未注入检查切块代码补全元数据字段5.4 几个踩过的坑和独家技巧坑一Ollama 本地 embedding 模型首次调用特别慢。因为要加载模型到内存首次调用可能等 10 秒以上。我的做法是服务启动时预热一次调一个空查询让模型加载好。坑二pgvector 的索引类型选错导致检索慢。pgvector 支持 IVFFlat 和 HNSW 两种索引IVFFlat 建索引快但检索慢HNSW 建索引慢但检索快。生产环境用 HNSW原型阶段用 IVFFlat。我一开始用 IVFFlat检索延迟 200ms换 HNSW 后降到 50ms。坑三Agent 生成的查询包含特殊字符导致检索失败。比如查询里有引号、括号BM25 检索会报错。我的做法是在检索前对查询做清洗去掉特殊字符只保留字母、数字、中文和空格。技巧一给 Agent 提供“检索建议”。在 RAG 工具返回结果里加一个suggestions字段告诉 Agent“可以尝试查询 xxx”。Agent 在二次检索时可以参考这些建议提高检索效率。技巧二用“检索历史”避免重复检索。Agent 在决策循环里把已检索的查询和结果记下来下次生成查询时先检查是否已检索过。这个在 LangChain4j 里可以用ChatMemory实现但需要自己管理检索历史的存储和读取。技巧三置信度评估加“时间衰减”。如果召回内容更新时间较久置信度应该降低。我的做法是给updated_at算一个衰减因子超过 6 个月的内容衰减 0.1超过 1 年衰减 0.2。这个在技术文档场景里特别有用因为技术更新快旧文档可能已过时。6. 不同 Agent 框架下的 RAG 选型建议6.1 LangChain4j Easy RAG 的适用场景与改造点LangChain4j 的 Easy RAG 封装度很高几行代码就能搭一个 RAG 流程。但它的默认配置是“单路召回 固定切块”在 Agent 场景下需要改造。改造点包括替换默认切块器为语义切块器、增加多路召回、增加置信度评估、把检索接口改成结构化返回。Easy RAG 的优势是上手快适合原型验证。劣势是灵活性差深度改造需要读源码。我的建议是原型阶段用 Easy RAG生产阶段换成手动组装的 RAG 流程。因为 Easy RAG 的抽象层在 Agent 场景下反而成了负担很多参数没法直接调。6.2 Agentscope 2.0 的 RAG as Service 模式Agentscope 2.0 提出的 RAG as Service 模式思路是把 RAG 封装成一个独立服务Agent 通过标准接口调用。这种模式的好处是 RAG 逻辑和 Agent 逻辑解耦可以独立迭代。坏处是增加了网络开销而且接口设计需要仔细考虑否则 Agent 拿到的信息不够用。我试过用 Agentscope 2.0 搭了一个原型感觉它的优势在多 Agent 协作场景。多个 Agent 共享同一个 RAG 服务检索结果可以复用减少重复检索。如果项目里只有一个 Agent用 Agentscope 2.0 可能有点重。6.3 自研 RAG 模块的取舍如果团队有精力自研 RAG 模块是最灵活的选择。自研的核心工作包括文档切块、向量化、多路召回、重排序、置信度评估、接口设计。每部分都可以根据场景定制。自研的代价是开发成本高而且需要持续维护。我的建议是如果项目对检索质量要求极高或者场景特殊比如图检索、本体检索自研值得投入如果只是常规文档问答用现成框架改造更划算。6.4 选型决策表场景推荐方案理由原型验证LangChain4j Easy RAG上手快几行代码跑通单 Agent 生产环境LangChain4j 手动组装灵活可控多 Agent 协作Agentscope 2.0 RAG as Service检索结果可复用图检索/本体检索自研 Neo4j现成框架不支持本地知识库Ollama pgvector数据不出本地高并发场景自研 缓存 并行召回性能可控7. 检索质量评估与持续优化7.1 怎么量化 RAG 的检索质量检索质量评估不能只看“召回内容看起来对不对”要有量化指标。我用的指标包括Hit RateTop-K 里包含正确答案的比例、MRR平均倒数排名、NDCG归一化折损累计增益。这三个指标在信息检索领域是标准计算方式网上都有不展开。评估数据集怎么来我的做法是从历史查询里采样人工标注正确答案。比如从日志里抽 100 个查询每个查询标注哪些文档块是相关的。这个标注工作量大但值得做因为有了评估集才能量化优化效果。7.2 持续优化的三个方向方向一查询改写规则迭代。根据评估结果看哪些查询召回质量差针对性优化查询拆解规则。比如发现“对比类”查询召回差就加一条规则“对比类查询拆成两个子查询分别检索”。方向二切块粒度调整。如果评估发现召回内容经常包含无关信息说明切块粒度太粗需要切细。如果召回内容经常不完整说明切块粒度太细需要合并。方向三重排序模型微调。如果有足够的标注数据可以微调重排序模型让它更适应领域场景。这个成本较高适合长期项目。7.3 一个实际优化案例我项目里最初 Hit Rate 只有 0.6优化后到 0.85。优化过程分三步。第一步把固定切块改成语义切块Hit Rate 到 0.7。第二步加多路召回和重排序Hit Rate 到 0.8。第三步加查询改写规则Hit Rate 到 0.85。每步都有评估数据支撑不是拍脑袋改。这个案例说明RAG 优化是系统工程单点优化效果有限。切块、召回、重排序、查询改写每个环节都做好整体效果才能上去。8. 一些个人体会和后续扩展方向Agent 时代的 RAG核心变化是从“检索一次”变成“检索多次”从“召回内容”变成“召回内容 置信度 建议”。这个变化对 RAG 模块的设计提出了更高要求但也让 RAG 在 Agent 架构里有了更重要的位置。我个人在实际操作中的体会是不要追求一步到位先跑通再优化。我一开始想直接上多路召回 重排序 置信度评估结果复杂度太高调试困难。后来改成先跑通单路召回再加多路再加置信度每步都验证效果反而更快。后续扩展方向我目前在探索两个。一是 GraphRAG用图结构表示文档间关系支持关系推理类查询。二是 Ontology RAG用本体描述领域知识支持更精确的语义检索。这两个方向在技术文档、医疗、法律等场景有潜力但工程复杂度高需要评估投入产出比。最后分享一个小技巧给 Agent 的 RAG 工具加一个“调试模式”开启后返回详细的检索日志包括查询改写结果、每路召回结果、重排序分数、置信度计算过程。这个在排查问题时特别有用能快速定位是哪个环节出了问题。生产环境关掉调试模式避免日志过多。