Agentic RAG 自主决策检索系统深度实践:从单轮问答到生产级智能检索控制系统
对许多团队来说,RAG 的初始阶段目标仅仅是“让模型具备检索能力”;然而,当系统真正投入生产环境后,挑战会迅速演变为“让系统懂得该查什么、查几次、查哪里、何时停止、如何兜底、怎样审计”。
此时,你需要的已不再是一条简单的检索增强问答链路,而是一套集规划、执行、反思、治理与可观测能力于一体的 Agentic RAG 系统。
一、为什么传统 RAG 很快会撞到天花板
传统 RAG 的经典链路是:
用户问题 -> Query Embedding -> TopK 检索 -> 上下文拼接 -> LLM 生成答案
这个模型在 Demo 阶段非常有效,因为它足够简单、成本可控、实现速度快。但在真实生产场景里,它往往会在三个阶段出现明显失效。
1.1 第一类失效:问题并不是“查一次就能答”
例如下面几类问题:
- “某客户昨天升级后为什么调用风控接口一直超时,和网关限流策略是否相关?”
- “我们当前合同审批链路里,哪些节点和上季度的合规要求冲突?”
- “比较 A 版本与 B 版本知识库中关于退款策略的差异,并给出当前生效结论。”
这类问题天然具备以下特征:
- 需要多跳推理,不是单段文档能直接回答。
- 需要跨源整合,既要查制度文档,又要查变更记录、FAQ、工单、甚至运行日志摘要。
- 需要判断证据是否充分,而不是盲目把 TopK 拼给大模型。
传统 RAG 没有“决策闭环”,只能机械执行一次检索,因此很容易出现“检索命中但答案错误”的假象。
1.2 第二类失效:系统不知道该查谁
企业知识从来不是一个干净统一的向量库,它通常分散在多个域:
- 产品文档库
- API 参考手册
- 运维 Runbook
- 工单系统
- CRM / ERP / OA
- 结构化数据库
- 图谱或关系网络
- 外部检索源
如果系统不能判断“当前问题应优先走哪个知识源、哪个召回策略、哪个排序器”,那它检索出来的内容再多,也只是噪声堆积。
1.3 第三类失效:系统没有自我修正能力
传统 RAG 默认一次检索就足够,但线上真实情况是:
- 查询词可能表述含糊。
- 用户问题可能缺少上下文。
- 文档切分可能破坏语义边界。
- Embedding 召回可能遗漏关键术语。
- 最新事实可能只存在于结构化事件或工单记录中。
如果系统不会在“证据不足”时自动改写查询、拆解子任务、切换检索工具或回退到澄清提问,那么它只能在低质量上下文上生成看似完整、实则不可靠的答案。
1.4 本质原因:传统 RAG 是检索管道,不是决策系统
传统 RAG 的核心是“静态流程”:
固定输入 -> 固定召回 -> 固定拼接 -> 固定生成
而复杂知识问答需要的是“动态控制”:
理解意图 -> 制定计划 -> 选择工具 -> 执行检索 -> 评估证据 -> 再决策 -> 输出答案
这就是 Agentic RAG 的出发点。
二、Agentic RAG 的本质:把“检索增强”升级为“决策增强”
2.1 什么是 Agentic RAG
Agentic RAG 并不只是“多轮检索”,也不只是“在 RAG 上加一个 Agent”。
它的本质是:让系统围绕回答目标,自主完成规划、检索、评估、修正和生成的闭环控制。
一个成熟的 Agentic RAG,至少应具备五个能力:
- 任务理解:判断问题类型、风险级别、是否需要外部知识。
- 检索规划:决定使用哪些工具、哪些数据源、采用什么召回策略。
- 证据执行:并发调用检索器、数据库、图谱、缓存或工作流。
- 结果评估:判断当前证据是否充分、是否冲突、是否过时。
- 输出治理:对答案进行引用标注、置信度控制、安全审查和审计留痕。
2.2 它和几种常见形态的边界
很多团队会把几个概念混在一起,导致架构设计一开始就偏了。
| 形态 | 核心特征 | 是否等于 Agentic RAG |
|---|---|---|
| 传统 RAG | 单轮检索 + 生成 | 否 |
| Workflow RAG | 固定流程编排,多步但不自主决策 | 否 |
| GraphRAG | 借助图结构做关系检索与组织 | 否,但可成为 Agentic RAG 的检索能力之一 |
| Tool Calling | 模型能调工具 | 否,只是基础能力 |
| Multi-Agent | 多角色协作 | 不一定,可能只是复杂化 |
| Agentic RAG | 围绕回答目标做检索决策闭环 | 是 |
一个常见误区是:
“我已经能 function calling 了,所以我做的是 Agentic RAG。”
这是不准确的。
Function Calling 只是“能动手”,而 Agentic RAG 强调的是“何时动手、怎么动手、动几次、失败后如何换路”。
2.3 Agentic RAG 的闭环模型
可以把它抽象为一个 OODA 风格的检索控制回路:
- Observe:感知问题、租户上下文、历史会话、工具状态。
- Orient:识别意图、分类问题、评估风险与复杂度。
- Decide:选择检索计划、排序器、工具链路和停止条件。
- Act:执行查询、聚合证据、验证冲突、生成答案。
与传统问答不同,Agentic RAG 的核心价值不在“调用了多少次模型”,而在“每次调用是否在减少不确定性”。
2.4 决策闭环里最关键的四个判断
生产系统里,最有价值的不是“让模型自由发挥”,而是把以下四个判断做实:
- 是否需要检索
并非所有问题都要查库。定义类、常识类、流程解释类问题,可能直接回答更稳、更快、更便宜。 - 应该检索哪些源
技术问题优先产品文档和 API 手册;异常排查类问题优先日志摘要、工单、变更记录;制度类问题优先规章和版本生效记录。 - 当前证据是否够用
不是 TopK 达到阈值就算够,而是要看是否覆盖关键实体、关键时间、关键约束和关键结论。 - 什么时候必须停止
不能无限循环检索。系统必须有最大步数、最大 Token、最大耗时和置信度阈值。
三、生产级 Agentic RAG 的总体架构
3.1 从“问答链”转向“控制面 + 数据面”
做生产系统时,最重要的架构跃迁是把 Agentic RAG 拆成两个面:
- 控制面
负责任务理解、策略路由、工具编排、权限控制、成本治理、可观测与灰度发布。 - 数据面
负责离线数据摄取、解析切分、索引构建、在线检索、重排、缓存、结果聚合和证据返回。
这两个面分离后,系统才能真正具备可扩展性。
3.2 整体分层架构
┌─────────────────────────────┐ │ Client Layer │ │ Web / API / Bot / Workflow │ └──────────────┬──────────────┘ │ ┌──────────────▼──────────────┐ │ Access Gateway │ │ Auth / RateLimit / Audit │ └──────────────┬──────────────┘ │ ┌─────────────────────────▼─────────────────────────┐ │ Agent Control Plane │ │ Intent Router / Planner / Policy / State / Eval │ └──────────────┬──────────────────────┬─────────────┘ │ │ ┌──────────────▼────────────┐ ┌──────▼──────────────┐ │ Tool Orchestration │ │ Memory Layer │ │ Search / SQL / Graph / KB │ │ Session / Cache / LT │ └──────────────┬────────────┘ └──────┬──────────────┘ │ │ ┌────────────────────────▼──────────────────────▼────────────────────────┐ │ Data Plane │ │ Vector Search / BM25 / Graph Query / Rerank / Metadata Filter / SQL │ └────────────────────────┬──────────────────────┬────────────────────────┘ │ │ ┌──────────────────▼────────────┐ ┌──────▼────────────────────────┐ │ Offline Index Pipeline │ │ Online Observability │ │ Parse / Chunk / Embed / Build │ │ Metrics / Traces / Feedback │ └─────────────────────────────────┘ └────────────────────────────────┘
3.3 核心模块说明
3.3.1 Access Gateway
统一处理:
- 身份认证与租户识别
- 限流、熔断和并发配额
- 敏感字段脱敏
- 请求级审计与追踪 ID 注入
3.3.2 Agent Control Plane
这是 Agentic RAG 的“大脑”,负责:
- 意图识别与问题分类
- 检索计划生成
- 工具路由与工具权限控制
- 多轮状态机推进
- 结果评估与停止决策
- 模型版本、Prompt 版本、策略版本灰度切换
3.3.3 Tool Orchestration
这里不是简单“把工具列表暴露给模型”,而是要做可治理的工具层:
- 工具注册中心
- 输入 Schema 校验
- 工具级 ACL
- 超时、重试、熔断、隔离舱
- 幂等控制与调用审计
3.3.4 Data Plane
这是检索实际发生的地方,通常包括:
- 稠密向量检索
- 稀疏全文检索
- 图谱检索
- 元数据过滤
- Cross Encoder 重排
- 上下文压缩与证据去重
3.3.5 Offline Index Pipeline
生产级知识系统最容易被忽略的,其实不是在线问答,而是离线建库质量。
没有高质量索引,就不会有高质量 Agent 决策。
典型离线链路:
文档接入 -> 解析清洗 -> 结构化抽取 -> Chunk 切分 -> 标签补齐 -> Embedding -> 索引构建 -> 版本发布
对于高质量场景,通常还要补齐:
- 文档血缘
- 生效时间与失效时间
- 文档可信级别
- 所属租户与部门域
- 文档版本与审批状态
四、Agentic RAG 的核心执行模型
4.1 最小可用状态机
一个生产级 Agentic RAG,建议至少具备如下状态:
INIT -> ROUTE -> PLAN -> RETRIEVE -> EVALUATE -> REFLECT -> ANSWER -> SAFE_GUARD -> DONE
其中最关键的是三个环节:
PLAN:生成可执行检索计划,而不是直接“让模型自己搜”。EVALUATE:判断证据是否足够,不足则进入下一轮。SAFE_GUARD:在输出前执行风险控制,包括引用完整性、越权检测、敏感内容规避和低置信度兜底。
4.2 检索计划不应只是字符串
很多实现里,Planner 的输出只是“改写后的 query”。
这太弱了。
生产系统里,Planner 输出应是结构化计划,至少包含:
- 原始问题
- 子问题列表
- 目标知识域
- 工具选择
- 过滤条件
- 排序策略
- 截止条件
- 风险等级
例如:
{ "intent": "incident_analysis", "risk_level": "high", "sub_queries": [ "风控接口超时的直接原因", "过去24小时相关网关策略变更", "是否存在租户级限流命中" ], "tools": [ "runbook_search", "change_log_search", "ticket_search" ], "filters": { "tenantId": "t-001", "timeRange": "last_24h", "env": "prod" }, "stop_condition": { "max_steps": 4, "max_latency_ms": 4500, "min_confidence": 0.76 }}
4.3 证据评估要看“覆盖度”,不是只看分数
传统做法常常用向量相似度或 rerank score 决定“检索质量是否足够”。
这在线上非常危险。
真正应该关注的是:
- 实体覆盖度:关键对象是否都被召回,例如系统名、租户名、版本号、时间区间。
- 结论覆盖度:关键问题是否都有证据支撑,而不是只有背景说明。
- 时间有效性:证据是否来自当前生效版本。
- 冲突程度:不同知识源是否给出相互矛盾的结果。
- 引用完备性:最终答案中的关键结论是否都能溯源到证据。
4.4 Reflection 不是“无限重试”,而是“受控修正”
Reflection 阶段的目标是降低不确定性,而不是让模型不停重复。
建议只允许有限的几种修正动作:
- 改写查询词
- 扩大/收窄过滤条件
- 切换知识源
- 拆解子问题
- 请求用户补充关键上下文
凡是不能明确降低不确定性的反思动作,都不应该继续消耗 Token。
五、离线建库:决定上限的不是 Agent,而是知识质量
5.1 文档接入不是“丢进向量库”这么简单
如果离线链路粗糙,在线 Agent 再聪明也没用。
离线建库至少要完成以下步骤:
- 文档采集:从对象存储、知识库、Git 仓库、数据库、工单系统接入原始数据。
- 内容解析:解析 PDF、Word、Markdown、HTML、表格、图片 OCR。
- 结构重建:识别标题层级、代码块、表格、流程步骤、章节边界。
- 元数据打标:租户、业务域、文档类型、作者、审批状态、生效时间、版本号。
- Chunk 切分:按语义边界切分,而不是机械按字数。
- 双路索引:同时构建向量索引和全文索引。
- 版本发布:以“知识版本”的形式发布到在线系统。
5.2 为什么一定要做双路索引
纯向量检索适合语义相近,但对以下内容很容易失真:
- 错误码
- 接口路径
- 版本号
- 英文缩写
- 配置项
- SQL 字段名
因此生产环境通常采用 Hybrid Retrieval:
FinalScore = a * DenseScore + b * BM25Score + c * FreshnessScore + d * AuthorityScore
其中:
DenseScore负责语义召回BM25Score负责关键词精确命中FreshnessScore负责时间新鲜度AuthorityScore负责可信源优先
5.3 Chunk 切分的工程原则
最忌讳的就是按固定 500 字、带 50 字 overlap 直接切。
更合理的原则是:
- 优先基于标题层级切分
- 流程步骤要完整保留
- 表格与表头不可拆断
- 代码块必须整体保留
- FAQ 问答对要成组保存
- 对高价值术语建立别名映射
5.4 元数据过滤在企业场景中极其关键
没有元数据过滤,系统就无法保证:
- 多租户隔离
- 版本生效控制
- 区域法规差异
- 环境区分
- 文档可信级别控制
典型过滤维度包括:
| 维度 | 示例 |
|---|---|
tenant_id |
finance-cn-01 |
domain |
payment / risk / crm |
doc_type |
runbook / manual / api / faq |
env |
prod / staging |
version |
v2.4.3 |
effective_from |
生效时间 |
authority_level |
官方 / 草稿 / 社区 |
六、在线链路设计:高并发场景下如何跑稳
6.1 一条生产请求是怎么走的
一个高并发 Agentic RAG 请求,推荐按如下链路执行:
接入鉴权-> 意图分类-> 读取租户策略-> 读取会话状态-> 生成检索计划-> 并发召回-> 证据聚合与重排-> 结果评估-> 必要时进入反思回路-> 输出答案-> 写入审计与反馈事件
这条链路的关键不在于“步骤多”,而在于“每一步都要可控、可追踪、可降级”。
6.2 高并发场景必须做的五件事
第一,限流
限流不能只做 API 入口限流,还要做:
- 租户级并发限制
- 模型调用配额
- 检索工具并发控制
- 外部数据源熔断
第二,缓存
缓存至少分三层:
- Query Result Cache
对标准化问题和高频问法缓存检索结果。 - Retrieval Fragment Cache
对热点片段、热点 rerank 结果做短 TTL 缓存。 - Session State Cache
缓存多轮会话上下文和执行状态,降低状态存储压力。
第三,超时切片
不能把整个链路交给一个总超时。
建议为每一层设置独立预算:
- 路由 100ms
- 计划 300ms
- 检索 1200ms
- 重排 300ms
- 生成 1500ms
- 审计 200ms
如果某一层超时,应该走降级策略,而不是让整条链路雪崩。
第四,降级
推荐设计明确的降级顺序:
- 关闭低价值外部检索源
- 降低召回路数
- 跳过成本高的 rerank
- 退化为单轮 Hybrid RAG
- 最后输出“证据不足”的受控答复
第五,异步化
不是所有动作都应该在同步链路完成。
像下面这些动作更适合异步:
- 用户反馈写入
- 长文本摘要预热
- 热点问题缓存构建
- 低优先级知识对齐
- 离线评估样本沉淀
6.3 控制面与数据面的容量解耦
很多系统一开始把 Planner、Retriever、Reranker、LLM Gateway 全部堆在一个服务里。
这在 QPS 上来之后很快就会崩。
推荐拆分为:
agent-gatewayagent-orchestratorretrieval-servicererank-servicellm-gatewaysession-state-serviceindex-pipeline-service
好处有三个:
- 各层可以按瓶颈独立扩缩容
- 可以在检索层与模型层之间做熔断隔离
- 可以清晰核算各层成本
七、生产级代码骨架设计
下面给出一套偏企业 Java 风格的实现骨架。
重点不在于把所有代码写满,而在于明确生产系统应有哪些边界对象、状态流和治理钩子。
7.1 核心领域对象
package com.example.agenticrag.domain;import java.time.Instant;import java.util.List;import java.util.Map;public record RagRequest( String requestId, String tenantId, String sessionId, String userId, String question, Map<String, Object> attributes, Instant requestTime) {}public record RetrievalPlan( String intent, RiskLevel riskLevel, List<SubQuery> subQueries, List<ToolSelection> tools, FilterCondition filterCondition, StopCondition stopCondition) {}public record SubQuery( String id, String question, QueryPurpose purpose) {}public record ToolSelection( String toolName, int topK, boolean rerank, int timeoutMs) {}public record FilterCondition( String tenantId, String domain, String version, String env, Map<String, Object> extraFilters) {}public record StopCondition( int maxSteps, int maxPromptTokens, long maxLatencyMs, double minConfidence) {}public record Evidence( String sourceId, String sourceType, String title, String content, double score, double freshnessScore, double authorityScore, Map<String, Object> metadata) {}public record RagAnswer( String requestId, String answer, double confidence, List<Citation> citations, boolean fallback, String fallbackReason) {}public record Citation( String sourceId, String title, String locator) {}public enum RiskLevel { LOW, MEDIUM, HIGH}public enum QueryPurpose { FACT, PROCEDURE, DIAGNOSIS, COMPARISON, DECISION}
7.2 状态机上下文
package com.example.agenticrag.runtime;import com.example.agenticrag.domain.Evidence;import com.example.agenticrag.domain.RagRequest;import com.example.agenticrag.domain.RetrievalPlan;import java.time.Instant;import java.util.ArrayList;import java.util.List;import java.util.concurrent.atomic.AtomicInteger;public class AgentExecutionContext { private final RagRequest request; private final Instant startTime = Instant.now(); private final AtomicInteger currentStep = new AtomicInteger(0); private RetrievalPlan plan; private final List<Evidence> evidences = new ArrayList<>(); private final List<String> reflections = new ArrayList<>(); private boolean sufficient; private boolean fallback; private String fallbackReason; public AgentExecutionContext(RagRequest request) { this.request = request; } public RagRequest request() { return request; } public int nextStep() { return currentStep.incrementAndGet(); } public int currentStep() { return currentStep.get(); } public RetrievalPlan plan() { return plan; } public void plan(RetrievalPlan plan) { this.plan = plan; } public List<Evidence> evidences() { return evidences; } public List<String> reflections() { return reflections; } public boolean sufficient() { return sufficient; } public void sufficient(boolean sufficient) { this.sufficient = sufficient; } public boolean fallback() { return fallback; } public void fallback(boolean fallback) { this.fallback = fallback; } public String fallbackReason() { return fallbackReason; } public void fallbackReason(String fallbackReason) { this.fallbackReason = fallbackReason; } public Instant startTime() { return startTime; }}
7.3 检索工具统一抽象
package com.example.agenticrag.tool;import com.example.agenticrag.domain.Evidence;import com.example.agenticrag.domain.SubQuery;import com.example.agenticrag.runtime.AgentExecutionContext;import java.util.List;public interface RetrievalTool { String name(); boolean supports(String intent); List<Evidence> retrieve(SubQuery subQuery, AgentExecutionContext context);}
7.4 Planner:输出结构化计划,而不是自由文本
package com.example.agenticrag.agent;import com.example.agenticrag.domain.FilterCondition;import com.example.agenticrag.domain.QueryPurpose;import com.example.agenticrag.domain.RiskLevel;import com.example.agenticrag.domain.StopCondition;import com.example.agenticrag.domain.SubQuery;import com.example.agenticrag.domain.ToolSelection;import com.example.agenticrag.domain.RetrievalPlan;import com.example.agenticrag.runtime.AgentExecutionContext;import com.example.agenticrag.service.IntentClassifier;import com.example.agenticrag.service.PolicyService;import java.util.List;public class PlannerAgent { private final IntentClassifier intentClassifier; private final PolicyService policyService; public PlannerAgent(IntentClassifier intentClassifier, PolicyService policyService) { this.intentClassifier = intentClassifier; this.policyService = policyService; } public RetrievalPlan plan(AgentExecutionContext context) { String intent = intentClassifier.classify(context.request().question()); var policy = policyService.loadTenantPolicy(context.request().tenantId(), intent); List<SubQuery> subQueries = switch (intent) { case "incident_analysis" -> List.of( new SubQuery("q1", "定位直接异常现象及影响范围", QueryPurpose.DIAGNOSIS), new SubQuery("q2", "查询最近变更、发布与配置调整", QueryPurpose.FACT), new SubQuery("q3", "确认当前可执行恢复动作", QueryPurpose.PROCEDURE) ); case "policy_compare" -> List.of( new SubQuery("q1", "抽取当前版本结论", QueryPurpose.FACT), new SubQuery("q2", "抽取历史版本差异", QueryPurpose.COMPARISON) ); default -> List.of( new SubQuery("q1", context.request().question(), QueryPurpose.FACT) ); }; return new RetrievalPlan( intent, policy.highRisk() ? RiskLevel.HIGH : RiskLevel.MEDIUM, subQueries, policy.recommendedTools(), new FilterCondition( context.request().tenantId(), policy.domain(), policy.knowledgeVersion(), policy.environment(), policy.extraFilters() ), new StopCondition(4, 12000, 4500, 0.78D) ); }}
7.5 Executor:并发调度检索工具
package com.example.agenticrag.agent;import com.example.agenticrag.domain.Evidence;import com.example.agenticrag.domain.SubQuery;import com.example.agenticrag.runtime.AgentExecutionContext;import com.example.agenticrag.tool.RetrievalTool;import java.util.ArrayList;import java.util.Comparator;import java.util.List;import java.util.Map;import java.util.concurrent.CompletableFuture;import java.util.concurrent.Executor;import java.util.stream.Collectors;public class RetrievalExecutor { private final Map<String, RetrievalTool> toolRegistry; private final Executor executor; private final EvidenceReranker reranker; public RetrievalExecutor(Map<String, RetrievalTool> toolRegistry, Executor executor, EvidenceReranker reranker) { this.toolRegistry = toolRegistry; this.executor = executor; this.reranker = reranker; } public List<Evidence> execute(AgentExecutionContext context) { List<CompletableFuture<List<Evidence>>> futures = new ArrayList<>(); for (SubQuery subQuery : context.plan().subQueries()) { for (var selection : context.plan().tools()) { RetrievalTool tool = toolRegistry.get(selection.toolName()); if (tool == null || !tool.supports(context.plan().intent())) { continue; } futures.add(CompletableFuture.supplyAsync( () -> tool.retrieve(subQuery, context), executor )); } } List<Evidence> merged = futures.stream() .map(CompletableFuture::join) .flatMap(List::stream) .collect(Collectors.toList()); List<Evidence> reranked = reranker.rerank(context.request().question(), merged); return reranked.stream() .sorted(Comparator.comparingDouble(Evidence::score).reversed()) .limit(12) .toList(); }}
7.6 Evaluator:判断当前证据是否足够
package com.example.agenticrag.agent;import com.example.agenticrag.domain.Evidence;import com.example.agenticrag.runtime.AgentExecutionContext;import java.util.List;import java.util.Set;import java.util.stream.Collectors;public class EvidenceEvaluator { public EvaluationResult evaluate(AgentExecutionContext context, List<Evidence> evidences) { double avgScore = evidences.stream() .mapToDouble(Evidence::score) .average() .orElse(0D); boolean hasFreshEvidence = evidences.stream() .anyMatch(e -> e.freshnessScore() >= 0.7D); Set<String> sourceTypes = evidences.stream() .map(Evidence::sourceType) .collect(Collectors.toSet()); boolean enoughSourceDiversity = sourceTypes.size() >= 2; boolean sufficient = avgScore >= context.plan().stopCondition().minConfidence() && hasFreshEvidence && enoughSourceDiversity; String reason = sufficient ? "evidence_sufficient" : "low_confidence_or_low_coverage"; return new EvaluationResult(sufficient, avgScore, reason); } public record EvaluationResult(boolean sufficient, double score, String reason) {}}
7.7 Reflector:只做受控修正
package com.example.agenticrag.agent;import com.example.agenticrag.domain.RetrievalPlan;import com.example.agenticrag.domain.ToolSelection;import com.example.agenticrag.runtime.AgentExecutionContext;import java.util.ArrayList;public class ReflectionAgent { public RetrievalPlan reflect(AgentExecutionContext context, String reason) { context.reflections().add(reason); var oldPlan = context.plan(); var newTools = new ArrayList<>(oldPlan.tools()); newTools.add(new ToolSelection("graph_search", 5, true, 400)); return new RetrievalPlan( oldPlan.intent(), oldPlan.riskLevel(), oldPlan.subQueries(), newTools, oldPlan.filterCondition(), oldPlan.stopCondition() ); }}
7.8 统一编排入口
package com.example.agenticrag.agent;import com.example.agenticrag.domain.RagAnswer;import com.example.agenticrag.domain.RagRequest;import com.example.agenticrag.runtime.AgentExecutionContext;import com.example.agenticrag.service.AnswerComposer;import com.example.agenticrag.service.AuditService;public class AgenticRagOrchestrator { private final PlannerAgent plannerAgent; private final RetrievalExecutor retrievalExecutor; private final EvidenceEvaluator evidenceEvaluator; private final ReflectionAgent reflectionAgent; private final AnswerComposer answerComposer; private final AuditService auditService; public AgenticRagOrchestrator(PlannerAgent plannerAgent, RetrievalExecutor retrievalExecutor, EvidenceEvaluator evidenceEvaluator, ReflectionAgent reflectionAgent, AnswerComposer answerComposer, AuditService auditService) { this.plannerAgent = plannerAgent; this.retrievalExecutor = retrievalExecutor; this.evidenceEvaluator = evidenceEvaluator; this.reflectionAgent = reflectionAgent; this.answerComposer = answerComposer; this.auditService = auditService; } public RagAnswer handle(RagRequest request) { AgentExecutionContext context = new AgentExecutionContext(request); context.plan(plannerAgent.plan(context)); while (context.currentStep() < context.plan().stopCondition().maxSteps()) { context.nextStep(); var evidences = retrievalExecutor.execute(context); context.evidences().clear(); context.evidences().addAll(evidences); var evaluation = evidenceEvaluator.evaluate(context, evidences); if (evaluation.sufficient()) { context.sufficient(true); break; } if (context.currentStep() >= context.plan().stopCondition().maxSteps()) { context.fallback(true); context.fallbackReason("max_steps_exceeded"); break; } context.plan(reflectionAgent.reflect(context, evaluation.reason())); } RagAnswer answer = answerComposer.compose(context); auditService.record(context, answer); return answer; }}
7.9 AnswerComposer:生成必须带引用和兜底策略
package com.example.agenticrag.service;import com.example.agenticrag.domain.Citation;import com.example.agenticrag.domain.RagAnswer;import com.example.agenticrag.runtime.AgentExecutionContext;import java.util.List;public class AnswerComposer { private final LlmGateway llmGateway; public AnswerComposer(LlmGateway llmGateway) { this.llmGateway = llmGateway; } public RagAnswer compose(AgentExecutionContext context) { if (context.fallback()) { return new RagAnswer( context.request().requestId(), "当前证据不足以给出高可信结论,建议补充时间范围、系统名称或具体异常现象后重试。", 0.42D, List.of(), true, context.fallbackReason() ); } String prompt = PromptTemplates.answerPrompt( context.request().question(), context.evidences() ); String answer = llmGateway.chat(prompt); List<Citation> citations = context.evidences().stream() .limit(5) .map(e -> new Citation(e.sourceId(), e.title(), "section:" + e.metadata().get("section"))) .toList(); return new RagAnswer( context.request().requestId(), answer, 0.86D, citations, false, "" ); }}
7.10 检索工具示例:Hybrid Search
package com.example.agenticrag.tool.impl;import com.example.agenticrag.domain.Evidence;import com.example.agenticrag.domain.SubQuery;import com.example.agenticrag.runtime.AgentExecutionContext;import com.example.agenticrag.tool.RetrievalTool;import org.opensearch.client.opensearch.OpenSearchClient;import java.util.List;import java.util.Map;public class HybridKnowledgeSearchTool implements RetrievalTool { private final OpenSearchClient openSearchClient; public HybridKnowledgeSearchTool(OpenSearchClient openSearchClient) { this.openSearchClient = openSearchClient; } @Override public String name() { return "knowledge_search"; } @Override public boolean supports(String intent) { return true; } @Override public List<Evidence> retrieve(SubQuery subQuery, AgentExecutionContext context) { String tenantId = context.plan().filterCondition().tenantId(); // 真实项目里,这里应同时执行向量召回、BM25 召回和元数据过滤, // 然后在服务端完成融合排序,而不是完全交给模型判断。 return List.of( new Evidence( "doc-1001", "knowledge_base", "支付服务限流治理手册", "当下游风控接口持续超时超过 3s 时,应先检查网关租户级限流与最近发布记录。", 0.91D, 0.88D, 0.95D, Map.of("tenantId", tenantId, "section", "4.2") ) ); }}
7.11 Spring Boot 配置示例
server: port: 8080spring: application: name: agentic-rag-service data: redis: host: redis-cluster port: 6379 kafka: bootstrap-servers: kafka-1:9092,kafka-2:9092,kafka-3:9092agentic-rag: default-timeout-ms: 4500 max-steps: 4 max-prompt-tokens: 12000 tools: knowledge-search: enabled: true timeout-ms: 400 top-k: 8 graph-search: enabled: true timeout-ms: 300 top-k: 5 ticket-search: enabled: true timeout-ms: 250 top-k: 6 fallback: enable-safe-answer: true min-confidence: 0.78
7.12 事件审计与异步反馈
package com.example.agenticrag.service;import com.example.agenticrag.domain.RagAnswer;import com.example.agenticrag.runtime.AgentExecutionContext;import org.springframework.kafka.core.KafkaTemplate;import java.util.Map;public class AuditService { private final KafkaTemplate<String, Object> kafkaTemplate; public AuditService(KafkaTemplate<String, Object> kafkaTemplate) { this.kafkaTemplate = kafkaTemplate; } public void record(AgentExecutionContext context, RagAnswer answer) { Map<String, Object> event = Map.of( "requestId", context.request().requestId(), "tenantId", context.request().tenantId(), "question", context.request().question(), "planIntent", context.plan().intent(), "stepCount", context.currentStep(), "evidenceCount", context.evidences().size(), "fallback", answer.fallback(), "confidence", answer.confidence() ); kafkaTemplate.send("agentic-rag-audit", context.request().requestId(), event); }}
这段代码的价值不只是“把日志打出去”,而是为后续离线评估、问题回放、成本分析和 Prompt 版本对比沉淀证据。
八、多源检索策略设计:不是多加几个工具就叫生产级
8.1 常见工具类型
一个企业级 Agentic RAG 通常会接入以下工具:
-
knowledge_search
面向文档库、FAQ、制度手册。
-
api_search
面向接口定义、字段说明、错误码文档。
-
graph_search
面向实体关系、多跳路径、因果关联。
-
sql_lookup
面向结构化事实和维度过滤。
-
ticket_search
面向历史故障、工单处理与经验案例。
-
change_log_search
面向发布记录、配置变更、灰度说明。
-
web_search
面向公共信息补充,但企业内场景通常要默认受限。
8.2 不同问题应采用不同检索模式
| 问题类型 | 推荐模式 |
|---|---|
| 定义解释 | 直接回答或单轮 Hybrid RAG |
| 步骤指南 | 文档检索 + FAQ 检索 + 引用生成 |
| 版本差异 | 文档版本过滤 + 结构化对比 |
| 故障诊断 | 工单 + 变更记录 + Runbook + 图谱 |
| 决策建议 | 检索 + 规则约束 + 置信度控制 |
8.3 GraphRAG 在 Agentic RAG 中的正确位置
GraphRAG 很强,但它不是所有问题的默认路径。
它更适合:
-
- 多实体关联问题
-
- 根因追踪问题
-
- 关系补全问题
-
- 多跳路径推理问题
不适合一上来就对所有问题强行走图谱,否则会导致:
-
- 建图成本高
-
- 延迟上升
-
- 命中收益不明显
-
- 关系噪声影响回答
正确做法是把 GraphRAG 作为可选检索策略,由 Planner 在满足条件时动态启用。
九、真实案例:从“客户支付失败”到“自主诊断检索”
9.1 业务背景
某支付 SaaS 平台服务 3000+ 商户,知识源包括:
-
- 产品使用文档
-
- API 接口手册
-
- 历史故障工单
-
- 发布变更记录
-
- 风控策略说明
-
- 运维 Runbook
用户提问:
某华东商户昨晚 22:00 后支付失败率突然升高,是通道故障、风控策略升级,还是网关限流导致?
如果用传统 RAG,大概率会召回“支付失败常见原因”“限流机制介绍”“风控系统说明”等泛化文档,最后输出一段正确但无用的套话。
9.2 Agentic RAG 的计划拆解
Planner 应把问题拆成三个子任务:
- 识别现象:支付失败率升高发生在什么时间段、影响哪些商户、主要错误码是什么。
- 定位变化:22:00 前后是否有通道、风控或网关相关变更。
- 给出处置:当前最可能原因是什么,推荐先执行哪一步排查或恢复动作。
9.3 工具路由方案
推荐调用:
ticket_searchchange_log_searchrunbook_searchknowledge_search
如果系统发现“商户、通道、风控规则、网关策略”之间存在复杂关联,再按需补调用 graph_search。
9.4 评估标准
只有当以下问题都被回答时,证据才算充分:
- 有无异常现象证据
- 有无时间窗口证据
- 有无变更证据
- 有无恢复路径证据
- 最终结论是否可引用
9.5 最终输出示例
系统最终不应只回答“可能是限流”,而应给出结构化结果:
结论:当前更可能是网关租户级限流策略与新风控回调模式叠加导致,而不是支付通道整体故障。证据:1. 22:03 起,商户 tenant-hd-17 的 gateway_limit_exceeded 错误显著增加。2. 21:55 发布记录显示风控回调并发阈值由 200 提升为 800。3. Runbook 4.2 节明确指出,当风控回调并发抬升后,若租户级限流阈值未同步调整,会出现支付受理成功但后续确认失败。建议动作:1. 暂时提升该商户网关限流阈值。2. 回看 21:55 发布批次是否只影响华东通道。3. 若 5 分钟内失败率未回落,再切换到备用通道。
这才是企业真正要买单的价值:
不是“像人一样说话”,而是“像一个有经验的值班专家一样先检索、再判断、最后给行动建议”。
十、多租户、权限与安全治理
10.1 多租户隔离不能只靠索引名称
很多团队一开始做法是“每个租户一个索引”或者“查询时带个 tenantId filter”。
这还不够。
生产环境里建议至少做四层隔离:
- 请求鉴权隔离
入口层校验租户、用户、角色、场景。 - 检索过滤隔离
所有检索工具强制注入 tenant filter,不能信任模型自行传参。 - 引用输出隔离
最终答案中不可引用越权证据,即使该证据在检索阶段被意外召回。 - 审计回放隔离
运营人员回放执行链路时也要遵守权限约束。
10.2 工具层必须做最小权限控制
模型不应直接拥有任意查询数据库、任意访问外部系统的能力。
正确做法是:
- 为每个工具定义严格输入 Schema
- 对每个工具设置可访问范围
- 对高风险工具实行白名单和审批策略
- 对结构化查询生成结果做二次检查
例如 sql_lookup 不应让模型自由写 SQL,而应采用:
- 预定义查询模板
- 参数化占位符
- 列级脱敏
- 行级过滤
10.3 输出安全
输出前至少要做以下检查:
- 是否包含敏感信息
- 是否包含越权知识
- 是否缺少关键引用
- 是否在低置信度时误导性过强
- 是否给出了未被证据支持的操作建议
十一、可观测性:没有观测,就没有迭代
11.1 你必须监控的不只是响应时间
Agentic RAG 的观测对象比普通 API 多得多。
至少需要监控以下指标:
| 指标 | 含义 |
|---|---|
request_qps |
请求吞吐 |
p95_latency_ms |
端到端时延 |
planner_latency_ms |
计划生成耗时 |
retrieval_latency_ms |
检索耗时 |
llm_tokens_input |
输入 Token |
llm_tokens_output |
输出 Token |
reflection_rate |
进入反思回路比例 |
fallback_rate |
兜底回答比例 |
citation_coverage |
答案关键结论引用覆盖率 |
tenant_isolation_violation |
租户隔离违规次数 |
11.2 Trace 应该长什么样
一次请求的 Trace 最好能回放出以下信息:
- 用户问题
- 意图分类结果
- 检索计划
- 工具调用顺序
- 每个工具的耗时与结果量
- 证据评分
- 反思动作
- 最终答案与引用
- 成本统计
这样一来,当用户说“你这次为什么答错了”时,团队可以定位是:
- 问题路由错了
- 数据没召回
- 重排错了
- 反思没生效
- 生成时幻觉了
- 输出安全策略过度收缩
11.3 反馈闭环
真正成熟的系统,会把用户反馈转成后续优化数据:
- “答案不准” -> 检索召回评估样本
- “引用不全” -> 引用覆盖率问题样本
- “回答太慢” -> 延迟分析样本
- “该回答不该看到” -> 权限问题样本
这些样本可沉淀到离线评估平台,推动以下改进:
- Prompt 版本优化
- 检索策略优化
- rerank 模型替换
- 知识库清洗
- 工具路由策略更新
十二、缓存、熔断与降级策略
12.1 缓存设计
建议把缓存拆成四类:
- Query Rewrite Cache
缓存标准化问法与检索计划。 - Evidence Cache
缓存热点问题的证据集合。 - Citation Cache
缓存高频问题对应的引用片段。 - Answer Cache
只对低风险、低时效问题使用,且需要绑定知识版本。
12.2 熔断策略
不同下游要有不同熔断策略:
| 下游 | 熔断后动作 |
|---|---|
| 向量检索服务 | 切换到 BM25 |
| Rerank 服务 | 使用基础融合排序 |
| 图谱服务 | 跳过图谱路径 |
| 工单系统 | 返回文档级证据并提示时效有限 |
| 外部 Web 检索 | 直接禁用 |
12.3 降级原则
降级不是“回答质量变差”这么简单,而是要保证:
- 系统仍可回答
- 风险不失控
- 用户知道当前答案的边界
因此低配模式下,宁可回答:
当前仅基于内部文档证据给出结论,外部变更与实时工单未纳入本次判断。
也不要假装自己看到了所有证据。
十三、评估体系:别只看主观体验
13.1 评估至少分三层
第一层:检索层
关注:
- Recall@K
- MRR
- NDCG
- 过滤命中率
- 新鲜度覆盖率
第二层:Agent 层
关注:
- 意图分类准确率
- 工具选择正确率
- 反思有效率
- 无效循环率
- 计划执行完成率
第三层:答案层
关注:
- 正确率
- 引用完整率
- 幻觉率
- 用户反馈满意度
13.2 线上评估不能只靠人工抽样
建议建设 A/B 评估能力,对比:
- 不同 Planner Prompt
- 不同检索路由策略
- 不同 rerank 模型
- 不同最大步数
- 是否启用 GraphRAG
关键是把实验结果落到指标,而不是只听“感觉更聪明了”。
十四、常见失败模式与反模式
14.1 反模式一:把 Agent 当成万能自动驾驶
让模型自由选择任意工具、无限循环、自主生成 SQL、自主访问外部系统,这种做法上线后大概率会变成事故放大器。
生产级 Agentic RAG 应是“有限自主”,而不是“无限自由”。
14.2 反模式二:所有问题都走最复杂链路
很多团队希望每个问题都经过规划、检索、反思、重排、引用、复核。
这会直接把时延和成本打爆。
正确做法是按问题复杂度分层:
- 简单问题直接回答
- 中等问题走单轮 Hybrid RAG
- 复杂问题再进入 Agent 回路
14.3 反模式三:把多智能体当成高级感
多智能体不是越多越好。
如果单 Agent 状态机就能解决问题,没必要拆成一堆“路由 Agent、分析 Agent、总结 Agent、批判 Agent”。
工程上更应该追求:
- 职责清晰
- 状态明确
- 成本可控
- 可回放
- 可降级
14.4 反模式四:忽视离线知识版本治理
很多团队只关注在线问答,忽视了:
- 哪份知识已生效
- 哪份知识已过期
- 哪份知识仍是草稿
- 答案引用的是哪个版本
没有版本治理,线上答案就无法审计。
十五、从 0 到 1 的落地路径
15.1 第一阶段:先做可控的 Hybrid RAG
目标不是一步到位做多智能体,而是先把下面这些基础能力打稳:
- 离线建库质量
- Hybrid Retrieval
- 元数据过滤
- 引用输出
- 基础监控
15.2 第二阶段:增加 Planner 和受控 Reflection
这一步开始引入 Agentic 能力,但不要贪大:
- 先限定工具集合
- 先限定最大步数
- 先限定问题范围
- 先限定租户范围
15.3 第三阶段:扩展为多源知识与复杂推理
当系统已经能稳定处理文档检索后,再逐步引入:
- 图谱检索
- 工单检索
- 结构化查询
- 变更记录分析
- 策略与规则系统
15.4 第四阶段:构建反馈驱动优化平台
只有走到这一步,系统才真正形成长期竞争力:
- 用户反馈沉淀
- 线上回放与标注
- 检索层评估
- Agent 决策评估
- 模型与 Prompt A/B 实验
十六、部署建议:一套更接近生产的基础设施组合
16.1 推荐组件组合
如果面向企业内部或 ToB 场景,比较稳妥的一组技术组合是:
| 层 | 推荐实现 |
|---|---|
| 接入层 | API Gateway / Ingress |
| 编排层 | Spring Boot / 状态机编排服务 |
| 模型网关 | 统一 LLM Gateway |
| 向量检索 | OpenSearch / Milvus |
| 全文检索 | OpenSearch |
| 图谱 | Neo4j |
| 缓存 | Redis Cluster |
| 事件流 | Kafka |
| 对象存储 | OSS / S3 |
| 监控 | Prometheus + Grafana + Tempo/Jaeger |
16.2 为什么要有统一模型网关
不要让业务服务直接调用不同模型供应商。
统一模型网关至少有三个价值:
- 屏蔽模型差异
- 统一做限流、审计和成本核算
- 实现模型灰度切换和故障回退
16.3 为什么要有事件流
Kafka 这类事件流中间件对 Agentic RAG 的价值不只是“解耦”:
- 可以异步写审计事件
- 可以回放执行样本
- 可以构建离线评估数据集
- 可以触发缓存预热与知识修复任务
十七、什么时候该上 Agentic RAG,什么时候不要上
17.1 适合上的场景
- 企业知识源多且异构
- 问题复杂,需要多跳推理
- 对证据可追溯要求高
- 答案需要行动建议而非单纯解释
- 有持续优化和治理能力
17.2 不适合上的场景
- 知识规模很小
- 问题大多是固定 FAQ
- 时延预算极低
- 团队尚未把基础 RAG 做稳
- 没有评估、审计与治理资源
一句话总结:
如果你连“文档质量、切分质量、过滤质量、引用质量”都还没做稳,
那 Agentic RAG 往往不会帮你掩盖问题,只会把问题放大。
十八、架构师视角下的最终结论
Agentic RAG 的真正价值,不是让系统“更像人”,而是让系统在复杂知识环境中具备受控决策能力。
它解决的不是“模型不知道答案”,而是:
- 系统不知道该去哪找答案
- 不知道当前证据够不够
- 不知道何时应该停
- 不知道怎样在成本、时延、准确率和安全之间做平衡
因此,建设 Agentic RAG 时,建议始终坚持四个原则:
- 把 Agent 看成控制系统,不是聊天外壳。
- 把检索看成数据工程,不是向量 API 调用。
- 把回答看成受控输出,不是自然语言表演。
- 把优化看成长期治理,不是一次性 Prompt 调优。
如果用一句最工程化的话收尾:
传统 RAG 解决的是“查得到”,Agentic RAG 解决的是“查得对、查得稳、查得可审计、查得能扩展”。
这也是它从实验室走向企业生产的分水岭。
学AI大模型的正确顺序,千万不要搞错了
🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!
有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!
就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋

📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇
学习路线:
✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经
以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!
我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~
这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】

更多推荐
所有评论(0)