系列文章
本篇是 AI Agent 系列的第 13 篇。以下是已发布的全部文章:
- 理解 AI Agent 的大脑:ReAct 模式从入门到实战
- AI Agent 的记忆系统:从 ChatMemory 到持久化记忆的 Java 实战
- AI Agent 的记忆力是怎么实现的——LangChain4j Memory 机制深度解析
- AI Agent 的工具箱:深入理解 Tool Use 与 Spring AI Function Calling 实战
- MCP 模型上下文协议:AI 的万能接口与 MCP Server 实战
- AI Agent 的灵魂对话:Prompt Engineering 系统提示词设计的艺术与工程
- AI Agent 的规划大脑:从任务分解到自适应执行策略
- 让 AI 学会说人话:Spring AI 结构化输出实战
- 从零理解 RAG:检索增强生成完整指南
- Embedding 向量化的魔法:从文本到向量的数学之旅与 Java 实战
- 当 RAG 遇上知识图谱:GraphRAG 原理与 Java 实战
- AI Agent 团队协作:多 Agent 系统架构设计与 Java 实战
- 当 RAG 遇到 Agent:Agentic RAG 的架构设计与 Java 实战(本文)
引子:一个让人头疼的客服场景
假设你正在为一家电商公司构建 AI 客服系统。用户问了这么一个问题:
“我上周买的蓝牙耳机,左边没声音了,你们之前说的 30 天无理由退换还有 3 天到期,能帮我走退换流程吗?另外我之前有个订单的积分好像没到账,顺便查一下。”
如果用传统 RAG 来处理,系统会把这个问题整段扔给向量数据库做相似度检索,然后把检索结果塞给 LLM 生成回答。结果呢?
- 向量检索出来的可能是”蓝牙耳机使用指南”,而不是退换货政策
- 积分问题完全被忽略了,因为混合查询的向量表示模糊
- 退换货的时间约束(”还有 3 天”)没有被识别和验证
传统 RAG 就像一个只会用搜索引擎的人——你把问题甩给它,它就去搜,搜到什么就用什么,搜不到就算了。它不会思考”这个问题应该怎么拆”,不会判断”搜到的信息够不够用”,更不会”再搜一次”。
这就是 Agentic RAG 要解决的问题。
从”被动检索”到”主动编排”
传统 RAG 的三大瓶颈
在之前的系列文章中,我们详细讨论过 RAG 的原理(从零理解 RAG)、Embedding 技术(Embedding 向量化)以及 GraphRAG(当 RAG 遇上知识图谱)。传统 RAG 的核心流程是:
1
| 用户查询 → 向量化 → 相似度检索 → 拼接上下文 → LLM 生成回答
|
这个流程有三个根本性的局限:
第一,查询理解的缺失。 用户的自然语言查询往往包含多个意图、隐含条件、上下文依赖。直接把原始查询做 Embedding 检索,就像拿着一封投诉信去图书馆找书——你可能找到的是”如何写投诉信”的书,而不是用户真正想解决的问题。
第二,检索策略的僵化。 传统 RAG 的检索逻辑是固定的:取 Top-K 最相似的文档片段。但现实问题千变万化——有些问题需要精确匹配(”退货政策第几条”),有些需要语义理解(”耳机坏了怎么办”),有些需要跨文档关联。一刀切的 Top-K 策略无法适应这种多样性。
第三,缺乏自省和迭代。 检索到的内容不够用怎么办?信息矛盾怎么办?传统 RAG 没有”回头看一眼”的机制。它拿到检索结果就直接生成,不管结果是好是坏。
Agentic RAG 的核心思想
Agentic RAG 的本质是:用 Agent 的决策能力来编排 RAG 的检索流程。
这不是简单地把 Agent 和 RAG 拼在一起,而是让 Agent 成为检索流程的”大脑”:
- 查询分析:在检索之前,Agent 先理解用户到底在问什么,拆解成多个子查询
- 动态路由:根据查询类型,选择不同的检索策略和数据源
- 迭代优化:如果第一次检索结果不满意,Agent 可以调整策略重新检索
- 结果验证:Agent 检查检索结果是否真的回答了用户的问题
- 多源融合:从不同数据源(向量库、知识图谱、数据库、API)获取信息并综合
用一个类比:传统 RAG 是自动售货机,投币出货;Agentic RAG 是一个有经验的店员,会先问你要什么、帮你找、找不到还会建议替代品。
Agentic RAG 的四种架构模式
模式一:Query Decomposition(查询分解)
这是最基础也最实用的模式。核心思想是:复杂查询拆解为多个简单子查询,分别检索,最后综合。
回到开头的客服场景,Agent 会这样处理:
1 2 3 4 5 6 7 8
| 原始查询:"我上周买的蓝牙耳机,左边没声音了,退换货还有 3 天到期..." ↓ Agent 分析 子查询 1:"蓝牙耳机退换货政策" → 检索退货政策文档 子查询 2:"退换货流程和时间限制" → 检索流程文档 子查询 3:"订单积分查询" → 调用订单 API 子查询 4:"耳机故障保修条款" → 检索保修文档 ↓ Agent 综合 最终回答:整合所有子查询结果,生成完整回答
|
模式二:Adaptive Retrieval(自适应检索)
这种模式的核心是:Agent 根据查询特征决定是否需要检索、从哪里检索。
不是所有问题都需要检索。”今天天气怎么样”可能需要调用天气 API,”帮我写个邮件”可能直接用 LLM 就行,”公司报销政策是什么”才需要检索知识库。
Agent 的决策流程:
1 2 3 4 5 6 7 8 9 10
| 用户查询 ↓ Agent 判断:这个问题需要检索吗? ├── 不需要 → 直接用 LLM 知识回答(如常识性问题) ├── 需要 → 选择检索源 │ ├── 向量数据库(语义搜索) │ ├── 关系数据库(精确查询) │ ├── 知识图谱(关系推理) │ └── 外部 API(实时数据) └── 不确定 → 先尝试回答,如果置信度低再检索
|
模式三:Self-RAG(自反思检索)
这个模式来自论文《Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection》。核心思想是:LLM 在生成过程中自我评估,决定是否需要额外检索来验证或补充。
流程:
1 2 3 4
| 检索文档 → 生成初步回答 → 自我评估: ├── "这个回答有依据吗?" → 如果没有 → 重新检索 ├── "检索到的信息相关吗?" → 如果不相关 → 调整查询重试 └── "回答是否完整?" → 如果不完整 → 补充检索
|
Self-RAG 的关键创新在于引入了特殊标记(reflection tokens),让 LLM 在生成过程中插入自我评估的判断点。在实际工程中,我们通常用两次 LLM 调用来模拟:第一次生成,第二次评估。
模式四:Corrective RAG(纠正式检索)
这种模式是 Self-RAG 的变体,增加了外部验证环节。Agent 不仅自我反思,还会调用外部工具(搜索引擎、知识库、API)来验证生成内容的准确性。
1 2 3 4
| 检索 → 生成 → 验证 ├── 知识库验证:生成的内容和知识库一致吗? ├── 搜索引擎验证:有外部信息支持吗? └── 逻辑验证:推理链条自洽吗?
|
实战:用 Spring AI 实现 Agentic RAG
接下来,我们用 Spring AI 框架实现一个完整的 Agentic RAG 系统,采用 Query Decomposition + Adaptive Retrieval 的组合模式。
项目结构与依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-chat</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-vector-store-pgvector</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-client</artifactId> </dependency> </dependencies>
|
核心组件:QueryAnalyzer(查询分析器)
这是 Agentic RAG 的”大脑入口”。它负责分析用户查询,决定检索策略:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| @Service public class QueryAnalyzer {
private final ChatClient chatClient;
public QueryAnalyzer(ChatClient.Builder chatClientBuilder) { this.chatClient = chatClientBuilder.build(); }
public QueryPlan analyze(String userQuery) { String prompt = """ 你是一个查询分析专家。请分析用户查询,输出 JSON 格式的检索计划。 用户查询:{query} 请输出以下 JSON: { "intent": "查询意图(问答/操作/闲聊)", "subQueries": ["拆解后的子查询列表"], "retrievalStrategy": "检索策略(semantic/exact/hybrid)", "needsRetrieval": true/false, "dataSources": ["向量库/数据库/API等"] } """;
return chatClient.prompt() .user(u -> u.text(prompt).param("query", userQuery)) .call() .entity(QueryPlan.class); } }
|
QueryPlan 是一个简单的 POJO:
1 2 3 4 5 6 7
| public record QueryPlan( String intent, List<String> subQueries, String retrievalStrategy, boolean needsRetrieval, List<String> dataSources ) {}
|
核心组件:AdaptiveRetriever(自适应检索器)
这个组件根据 QueryPlan 执行不同的检索策略:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| @Service public class AdaptiveRetriever {
private final VectorStore vectorStore; private final ChatClient chatClient;
public AdaptiveRetriever(VectorStore vectorStore, ChatClient.Builder chatClientBuilder) { this.vectorStore = vectorStore; this.chatClient = chatClientBuilder.build(); }
public RetrievalResult execute(QueryPlan plan) { if (!plan.needsRetrieval()) { return RetrievalResult.empty("无需检索,直接用 LLM 知识回答"); }
List<Document> allResults = new ArrayList<>();
for (String subQuery : plan.subQueries()) { List<Document> results = switch (plan.retrievalStrategy()) { case "semantic" -> semanticSearch(subQuery, 5); case "exact" -> exactSearch(subQuery); case "hybrid" -> hybridSearch(subQuery, 3); default -> semanticSearch(subQuery, 5); }; allResults.addAll(results); }
List<Document> deduplicated = deduplicate(allResults);
return new RetrievalResult(deduplicated, plan); }
private List<Document> semanticSearch(String query, int topK) { SearchRequest request = SearchRequest.query(query) .withTopK(topK) .withSimilarityThreshold(0.7); return vectorStore.similaritySearch(request); }
private List<Document> exactSearch(String query) { SearchRequest request = SearchRequest.query(query) .withTopK(10) .withSimilarityThreshold(0.5); return vectorStore.similaritySearch(request); }
private List<Document> hybridSearch(String query, int topK) { SearchRequest semanticReq = SearchRequest.query(query) .withTopK(topK) .withSimilarityThreshold(0.6); return vectorStore.similaritySearch(semanticReq); }
private List<Document> deduplicate(List<Document> docs) { return docs.stream() .collect(Collectors.toMap( doc -> doc.getText().substring(0, Math.min(100, doc.getText().length())), doc -> doc, (d1, d2) -> d1 )) .values().stream() .toList(); } }
|
核心组件:AnswerSynthesizer(答案综合器)
检索完成后,需要把多个子查询的结果综合成一个连贯的回答:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| @Service public class AnswerSynthesizer {
private final ChatClient chatClient;
public AnswerSynthesizer(ChatClient.Builder chatClientBuilder) { this.chatClient = chatClientBuilder.build(); }
public String synthesize(String userQuery, RetrievalResult retrievalResult) { String context = retrievalResult.documents().stream() .map(Document::getText) .collect(Collectors.joining("\n---\n"));
String prompt = """ 你是一个专业的问答助手。根据检索到的参考资料回答用户问题。 规则: 1. 只基于提供的参考资料回答,不要编造信息 2. 如果参考资料不足以回答问题,明确告知用户 3. 回答要自然流畅,不要逐条罗列检索结果 4. 引用信息来源时,说明出自哪份资料 用户问题:{query} 参考资料: {context} 请回答: """;
return chatClient.prompt() .user(u -> u.text(prompt) .param("query", userQuery) .param("context", context)) .call() .content(); } }
|
串联一切:AgenticRagService
最后,把所有组件串联成完整的 Agentic RAG 流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| @Service public class AgenticRagService {
private final QueryAnalyzer queryAnalyzer; private final AdaptiveRetriever retriever; private final AnswerSynthesizer synthesizer; private final ChatClient chatClient;
private static final int MAX_RETRIES = 2;
public AgenticRagService(QueryAnalyzer queryAnalyzer, AdaptiveRetriever retriever, AnswerSynthesizer synthesizer, ChatClient.Builder chatClientBuilder) { this.queryAnalyzer = queryAnalyzer; this.retriever = retriever; this.synthesizer = synthesizer; this.chatClient = chatClientBuilder.build(); }
public AgenticRagResponse process(String userQuery) { QueryPlan plan = queryAnalyzer.analyze(userQuery);
RetrievalResult result = retriever.execute(plan);
String answer = synthesizer.synthesize(userQuery, result);
for (int attempt = 0; attempt < MAX_RETRIES; attempt++) { ValidationReport validation = validate(userQuery, answer, result); if (validation.isAdequate()) { break; } result = retriever.execute(validation.revisedPlan()); answer = synthesizer.synthesize(userQuery, result); }
return new AgenticRagResponse(answer, plan, result); }
private ValidationReport validate(String query, String answer, RetrievalResult result) { String prompt = """ 请评估以下回答的质量: 用户问题:{query} 生成的回答:{answer} 检索到的文档数量:{docCount} 评估标准: 1. 回答是否完整覆盖了用户问题的所有方面? 2. 回答中的信息是否有检索到的资料支撑? 3. 是否存在编造或推测的内容? 输出 JSON: { "adequate": true/false, "score": 0-100, "issues": ["问题列表"], "suggestedRevisions": ["改进建议"] } """;
ValidationReport report = chatClient.prompt() .user(u -> u.text(prompt) .param("query", query) .param("answer", answer) .param("docCount", result.documents().size())) .call() .entity(ValidationReport.class);
return report; } }
|
Controller 接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @RestController @RequestMapping("/api/rag") public class AgenticRagController {
private final AgenticRagService ragService;
public AgenticRagController(AgenticRagService ragService) { this.ragService = ragService; }
@PostMapping("/ask") public AgenticRagResponse ask(@RequestBody QuestionRequest request) { return ragService.process(request.question()); } }
|
源码解析:Spring AI 的检索机制
要真正理解 Agentic RAG,需要搞清楚 Spring AI 的 VectorStore 和 SearchRequest 背后发生了什么。
VectorStore.similaritySearch 的内部流程
Spring AI 的 VectorStore 接口定义了统一的检索抽象:
1 2 3 4 5 6
| public interface VectorStore extends DocumentWriter { default List<Document> similaritySearch(String query) { ... } default List<Document> similaritySearch(SearchRequest request) { ... } void write(List<Document> documents); void delete(List<String> idList); }
|
以 PgVector 实现为例,similaritySearch 的核心流程是:
1 2 3 4 5 6
| 1. 调用 EmbeddingModel 将查询文本向量化 2. 构造 SQL:SELECT * FROM vector_store ORDER BY embedding <=> $query_vector LIMIT $topK 3. 过滤相似度低于 threshold 的结果 4. 封装为 Document 对象返回
|
关键代码片段(简化):
1 2 3 4 5 6 7 8 9 10 11 12
| private float getSimilarity(Document doc, float[] queryEmbedding) { float[] docEmbedding = doc.getEmbedding(); float dotProduct = 0, normA = 0, normB = 0; for (int i = 0; i < queryEmbedding.length; i++) { dotProduct += queryEmbedding[i] * docEmbedding[i]; normA += queryEmbedding[i] * queryEmbedding[i]; normB += docEmbedding[i] * docEmbedding[i]; } return dotProduct / (float)(Math.sqrt(normA) * Math.sqrt(normB)); }
|
为什么理解这个很重要? 因为 Agentic RAG 的”自适应检索”策略就是基于对底层检索机制的理解来设计的。比如你知道精确匹配(政策编号)不适合用余弦相似度,就会选择不同的检索策略。
SearchRequest 的链式构建
Spring AI 的 SearchRequest 使用 Builder 模式,支持灵活的检索参数配置:
1 2 3 4 5 6 7 8 9 10 11
| SearchRequest request = SearchRequest.query("退货政策") .withTopK(5) .withSimilarityThreshold(0.7) .withFilterExpression("category == '退货'");
SearchRequest filtered = SearchRequest.query("耳机保修") .withTopK(10) .withSimilarityThreshold(0.5) .withFilterExpression("docType == 'policy' AND year >= 2025");
|
在 Agentic RAG 中,withFilterExpression 尤其重要——Agent 可以根据查询分析的结果,动态构造过滤条件,把搜索范围精确缩小到相关文档类型。
横向对比:三种 RAG 方案的差异
| 维度 |
传统 RAG |
GraphRAG |
Agentic RAG |
| 查询处理 |
直接 Embedding |
子图提取 |
Agent 分析 + 拆解 |
| 检索策略 |
固定 Top-K |
图遍历 + 社区摘要 |
动态选择策略 |
| 多轮迭代 |
❌ 不支持 |
❌ 不支持 |
✅ Self-RAG 验证 |
| 多源融合 |
❌ 单一向量库 |
❌ 知识图谱 |
✅ 向量库 + DB + API |
| 适用场景 |
简单问答 |
复杂关系推理 |
复杂多意图查询 |
| 实现复杂度 |
低 |
高 |
中高 |
| 延迟 |
低(1 次检索) |
中(图查询) |
高(多次 LLM 调用) |
| 成本 |
低 |
中 |
高 |
选型建议:
- 简单 FAQ、文档问答 → 传统 RAG 就够了,不要过度设计
- 知识密集型、需要推理关系 → GraphRAG(参考 当 RAG 遇上知识图谱)
- 复杂业务场景、多意图查询、需要精确控制 → Agentic RAG
- 企业级系统 → 通常是 Agentic RAG + GraphRAG 的混合方案
生产环境的挑战与最佳实践
挑战一:延迟控制
Agentic RAG 的延迟是传统 RAG 的 3-5 倍。因为每次查询可能涉及:
- 1 次查询分析 LLM 调用
- N 次向量检索
- 1 次答案生成 LLM 调用
- 1 次验证 LLM 调用(如果触发重试)
优化策略:
1 2 3 4 5 6 7 8 9 10
| List<CompletableFuture<List<Document>>> futures = plan.subQueries().stream() .map(sq -> CompletableFuture.supplyAsync(() -> retriever.search(sq))) .toList(); List<Document> allResults = futures.stream() .flatMap(f -> f.join().stream()) .toList();
|
挑战二:成本控制
多次 LLM 调用意味着 token 消耗翻倍。一个 Agentic RAG 查询可能消耗 2000-4000 tokens,而传统 RAG 只需要 500-1000 tokens。
优化策略:
- 短路优化:简单查询(如”今天星期几”)直接跳过检索,用 LLM 回答
- 缓存:相同或相似查询的结果缓存 5-10 分钟
- 模型分级:查询分析和验证用小模型(如 GPT-4o-mini),答案生成用大模型
1 2 3 4 5 6 7 8
| private boolean isSimpleQuery(String query) { return query.length() < 50 && !query.contains("另外") && !query.contains("顺便") && !query.contains("还有"); }
|
挑战三:检索质量评估
Agentic RAG 的价值在于”检索质量提升”,但怎么量化?
关键指标:
- 检索召回率(Recall@K):Top-K 结果中包含正确答案的比例
- 答案准确率:最终回答是否正确
- 幻觉率:回答中有多少内容是检索结果中没有的
- 首次检索成功率:不需要重试就通过验证的比例
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Service public class RagMetrics { private final MeterRegistry registry;
public void recordQuery(QueryPlan plan, boolean retryNeeded, long latencyMs) { registry.counter("rag.query.total", "strategy", plan.retrievalStrategy(), "retry", String.valueOf(retryNeeded)) .increment(); registry.timer("rag.query.latency").record(latencyMs, TimeUnit.MILLISECONDS); } }
|
挑战四:错误处理与降级
Agentic RAG 有多次 LLM 调用,任何一次都可能失败。生产环境中必须有降级策略:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public AgenticRagResponse processWithFallback(String userQuery) { try { return process(userQuery); } catch (Exception e) { log.warn("Agentic RAG failed, falling back to simple RAG", e); return simpleRag(userQuery); } }
private AgenticRagResponse simpleRag(String query) { List<Document> docs = retriever.semanticSearch(query, 5); String answer = synthesizer.synthesize(query, new RetrievalResult(docs, null)); return new AgenticRagResponse(answer, null, new RetrievalResult(docs, null)); }
|
扩展:与 Agent 生态的融合
Agentic RAG 不是孤立存在的,它可以和其他 Agent 组件深度集成:
- 与 Memory 系统集成:将检索历史存入 Agent 的记忆系统,实现”上一次检索的结果这次不用再查”
- 与 Tool Use 集合:将外部 API(订单查询、库存查询)注册为 Agent 的工具,在检索之外还能执行操作
- 与 MCP 协议集成:通过 MCP 接入企业内部的各种数据源
- 与 Planning 集成:对于超复杂的查询,让 规划引擎 先制定完整的执行计划,再逐步检索
什么时候不该用 Agentic RAG
这是一个很重要但经常被忽略的问题。Agentic RAG 不是银弹,以下场景不适合:
- 简单 FAQ 系统:问题和答案都是固定的,传统 RAG + 精确匹配就够了
- 延迟敏感的实时场景:每次 3-5 秒的延迟在实时聊天中不可接受
- 知识库很小(< 100 篇文档):检索空间太小,Agent 的分析和迭代没有意义
- 预算有限:多次 LLM 调用的成本是传统 RAG 的 3-5 倍
- 团队缺乏 Agent 开发经验:Agentic RAG 的调试和维护比传统 RAG 复杂得多
实用建议:先用传统 RAG 跑起来,收集用户反馈和检索失败的 case,再有针对性地引入 Agentic 能力。不要一上来就搞最复杂的架构。
总结
Agentic RAG 是 RAG 技术演进的必然方向。当传统 RAG 的”一搜了之”无法满足复杂业务需求时,Agent 的决策能力就成为了检索流程的关键补充。
核心要点回顾:
- Agentic RAG 的本质是用 Agent 编排检索流程,而不是简单拼接
- 四种架构模式:查询分解、自适应检索、Self-RAG、纠正式检索
- 实现关键:QueryAnalyzer(查询分析)→ AdaptiveRetriever(自适应检索)→ AnswerSynthesizer(答案综合)→ Validator(自验证)
- 生产挑战:延迟、成本、错误处理都需要精心设计
- 不要过度设计:简单场景用简单方案,复杂场景才需要 Agentic 能力
从 传统 RAG 到 GraphRAG,再到 Agentic RAG,我们看到的是同一条演进线索:**让 AI 不仅能”找到信息”,更能”理解信息、判断信息、运用信息”**。这也正是 AI Agent 这个系列一直在探讨的核心命题。