ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从一句用户问题到 MCP Tool:商城 Agent 的 Java 路由治理

从一句用户问题到 MCP Tool:商城 Agent 的 Java 路由治理 很多 Agent 项目最容易跑通的做法是把一组工具直接交给大模型模型判断要不要调用工具、调用哪个工具再生成参数交给程序执行。做 Demo 时这条链路很顺。但当工具后面连接的是真实订单、物流、设备和售后数据时问题就不再只是“模型能不能选对工具”而是这次调用究竟该不该发生模型判断错了以后系统有没有机会把它拦下来我在改造商城问答系统时逐渐把这个问题落到了 Java 路由治理上。模型可以参与理解用户意图但模型输出不能直接变成业务执行指令。在自然语言和真实业务能力之间需要有一层确定性的 Java 代码负责判断请求能去哪里、能访问什么资源以及什么时候应该停下来。不过真正做完以后我发现“路由”并不是一次简单的 Intent 分类。用户回复“第二个”时要先恢复上一轮待办“你能做什么”没必要调用模型一个复合问题需要先拆分模型给出的 Intent 还要经过置信度、白名单和资源契约检查最后进入知识库或 MCP Tool 后又有各自的执行路径。因此这个项目里的路由更像一条分层的决策链待办状态恢复 → 预路由 → 问题改写与拆分 → Intent候选识别 → Java路由治理 →资源契约授权→ 知识库或MCP Tool执行这篇文章就沿着这条链路展开。参数补全、实体消歧、身份绑定和权限控制虽然会在架构中出现但只说明它们与路由的边界具体实现留到下一篇。一、路由治理为什么会出现这个商城项目最初是一个 RAG 问答系统擅长回答商品介绍、退换货政策、配送规则等稳定知识。但实际用户还会问我上个月买的手表还在保吗换屏多少钱要回答这个问题系统需要找到用户购买的设备再查询实时保修状态和备件价格。这些数据不会长期静态地放在知识库里也不能让模型根据训练记忆自由生成。于是系统中出现了两类数据来源知识库负责稳定、公开的非结构化知识业务工具负责实时、结构化或者与用户身份绑定的权威数据。更准确的说法是RAG 负责稳定、公开的非结构化知识Tool 负责实时、结构化或与用户身份绑定的权威数据。当两种数据源同时存在系统首先要解决的就不是“怎么检索”而是“这个问题应该交给谁”。二、先分清四个容易混淆的概念在看架构之前需要先分清 Intent、Route、Tool 和 Resource Contract。1. Intent用户想完成什么Intent 属于业务语义层例如ORDER_QUERY 查询订单 LOGISTICS_QUERY 查询物流 WARRANTY_STATUS 查询保修状态 RETURN_POLICY 了解退货政策Intent 不是为了给句子贴一个好看的标签而是为了给后面的业务分发提供依据。2. Route系统准备走哪类处理链路Route 属于流程决策层。同一个用户问题最终可能进入指定知识库检索MCP Tool 调用系统固定处理补充信息公共知识库兜底超出范围拒绝。3. Tool系统真正具备的执行能力Tool 属于执行层例如getOrder getLogistics getWarrantyStatus getSparePartPrice4. Resource Contract允许访问什么资源资源契约就是这层映射关系。它把某个 Intent 转换成系统允许访问的资源Intent → ResourceKindKB / MCP / SYSTEM → ResourceNamecollectionName / mcpToolId / systemAction例如Intent资源类型授权资源RETURN_POLICYKBshopping-policy-kbORDER_QUERYMCPgetOrderWARRANTY_STATUSMCPgetWarrantyStatusORDER_CHANGESYSTEM官方订单修改指引这张表实际上就是商城 Agent 的能力边界。模型可以理解用户“像是想查物流”但只有资源契约中存在且启用的 Intent才能被转换成可执行路径。三、完整路由架构整个请求进入系统后大致经过下面这些层次客户端请求 ↓ Sa-Token登录校验并建立UserContext ↓ 待办状态路由 ├─ 恢复工具依赖选择 ├─ 恢复实体候选选择 └─ 恢复缺失参数补充 ↓ 没有待办任务 PreRoutingPolicy预路由 ├─ AUTH_CONTEXT ├─ SYSTEM └─ 普通商城问题继续 ↓ 会话记忆 Query Rewrite 多问题拆分 ↓ IntentResolver输出候选Intent与置信度 ↓ RoutingGovernanceService进行最终治理 ↓ ResourceContractGuard生成AllowedRoute ↓ RetrievalEngine执行授权资源 ├─ 指定知识库 ├─ 公共知识库兜底 ├─ MCP Tool └─ SYSTEM处理 ↓ 组合KB Evidence与MCP Result ↓ 生成最终回答并记录Trace这条链路里最值得注意的不是类名而是每一层只回答一个问题层次回答的问题Pending-State Routing这句话是不是在接着完成上一轮任务PreRoutingPolicy这个问题是否可以不经过模型直接处理IntentResolver这句话可能属于哪些业务意图RoutingGovernanceService这些候选是否可信应该继续、兜底还是拒绝ResourceContractGuard最终允许访问哪个具体资源RetrievalEngine已经授权以后具体怎么检索或调用把这些职责拆开以后路由错误才有可能被定位而不是所有问题最后都归结为一句“模型选错工具了”。四、第一层不是 Intent而是待办状态路由正常情况下我们会习惯性地把新输入送去意图识别。但在多轮任务中有些输入根本不是一个完整的新问题。例如上一轮系统说找到两台符合条件的手表请选择第一台或第二台。用户下一轮只回复第二个。如果把“第二个”重新送去做 Intent 分类模型很难知道它在说什么。它真正的含义保存在上一轮的待确认任务里。因此StreamChatPipeline在正常路由之前会先尝试恢复几类 Pending StateDependencyExecutionCoordinator.resume() → 是否正在等待工具依赖候选确认 ​ EntitySelectionClosedLoopService.resume() → 是否正在等待实体选择 ​ MissingParameterClosedLoopService.resume() → 是否正在等待缺失参数只有不存在待处理状态时请求才会继续进入普通问题路由。这一步可以称为“会话状态路由”它判断的不是用户属于哪个业务 Intent而是当前输入属于一个新任务还是上一轮任务的后续动作。五、预路由有些问题不值得调用模型通过待办状态检查以后系统进入PreRoutingPolicy。这一层使用纯 Java 规则处理少量明确而稳定的问题。例如“我的业务用户编号是什么” → AUTH_CONTEXT → 从服务端登录上下文回答 ​ “你是谁”“你能做什么” → SYSTEM → 返回固定能力说明这类问题的答案已经存在于系统上下文中让模型重新分类、检索甚至调用工具不仅浪费资源还可能让模型生成错误身份信息。不过预路由不应该无限扩张成一套关键词系统。它适合处理数量少、规则稳定、结果确定的特殊场景。普通的商城语义仍然交给后面的 IntentResolver。六、Query Rewrite 的任务是整理问题不是决定资源进入正常路由以后系统会结合会话记忆做 Query Rewrite并把复合问题拆成相对独立的子问题。例如查一下我的订单再告诉我怎么退货。可以拆成子问题1查询我的订单 子问题2退货政策是什么子问题 1 最终可能进入getOrder子问题 2 则进入shopping-policy-kb。如果不拆分只给整句话分配一个 Intent系统往往只能满足其中一个目标。但 Query Rewrite 也有风险改写后的句子可能偏离用户原始表达。为此后面的资源治理不能只看改写结果还需要保留原始问题作为纠偏依据。七、IntentResolver只负责寻找候选商城 Agent 先根据商品、订单、物流、售后和设备等业务领域梳理能力再细化成 24 个叶子 Intent。Intent 的划分依据不是表面词义而是问题背后的数据来源和处理链路。例如“支持哪些配送方式”和“我的订单到哪里了”都与物流有关但前者查询稳定政策后者查询实时物流因此会落到不同 Intent 和资源中。运行时IntentResolver把启用的叶子 Intent 提供给大模型让模型针对每个子问题输出候选 Intent 和置信度[ {id: LOGISTICS_QUERY, score: 0.93}, {id: ORDER_QUERY, score: 0.42} ]模型结果回来以后Java 还会执行几项基础检查丢弃系统中不存在的 Intent ID丢弃低于配置阈值的候选限制单个子问题保留的候选数量区分正常解析、没有可信候选和分类异常。最终得到的IntentResolutionResult大致有三类状态RESOLVED 得到可信候选 NO_TRUSTED 没有候选通过门槛 ERROR 分类过程发生异常到这里仍然只是“候选识别”。IntentResolver 回答的是“这句话像什么”并没有给模型最终工具调用权。八、RoutingGovernanceService不是重新分类而是最终裁决RoutingGovernanceService接收 IntentResolver 的候选结果回答另一个问题当前候选是否可信系统最终应该继续执行、进入公共知识库兜底还是明确拒绝它首先处理几类确定性情况。1. 表达明显不完整例如用户只说“查一下”“看一下”系统虽然知道他可能想办理某项业务却无法判断具体目标。此时应该进入NEED_CLARIFICATION而不是选择分数最高的工具碰运气。2. 分类模型异常模型调用失败、输出无法解析等情况会进入ROUTING_ERROR。技术异常不能伪装成用户问题也不应该自动放宽路由条件。3. 候选进入资源契约检查剩余候选会交给ResourceContractGuard转换成系统真正允许执行的AllowedRoute。治理结束后业务上主要有三种结果ROUTED → 存在可信且已授权的资源 GLOBAL_KB_FALLBACK → 没有叶子Intent但仍可能是商城公共知识 OUT_OF_SCOPE → 超出系统承诺的能力范围这里体现了一个很重要的设计原则低置信度时系统宁愿补问、受限兜底或拒绝也不会为了给出答案而强行匹配一个业务工具。九、ResourceContractGuard把语义候选收敛成授权路由ResourceContractGuard负责把最终 Intent 转换为AllowedRoutepublic record AllowedRoute( String subQuestion, String intentId, ResourceKind kind, String resourceName ) {}其中kind → KB / MCP / SYSTEM resourceName → collectionName / mcpToolId / systemAction对于同一个子问题它会从候选中收敛出一个授权 Intent 和一个资源而不是把所有候选都交给后续执行器。这一层还保留少量确定性纠偏规则用原始问题修正模型分类或 Query Rewrite 的漂移。例如“订单到哪了” → LOGISTICS_QUERY “订单详情、金额” → ORDER_QUERY “搜索不到、配对失败” → CONNECTION_NETWORK “单耳无声、频繁重启” → TROUBLESHOOTING “怎么设置、怎么开启” → USAGE_GUIDE这些规则不是为了取代大模型重新实现一套分类器而是针对业务中已经确认的高风险混淆点做确定性修正。因此几个核心组件可以这样概括IntentResolver 找候选这个问题像什么 RoutingGovernanceService 做决策候选是否可信系统要不要继续 ResourceContractGuard 做授权最终允许访问哪个资源 RetrievalEngine 做执行具体怎么查知识库或调用工具十、24 类 Intent 最终被路由到哪里当前 24 个叶子 Intent 并不是 24 个工具而是分成 15 个知识库 Intent、8 个 MCP Tool Intent 和 1 个 SYSTEM Intent。1. 15 个知识库 Intent知识库Intentproduct-kbPRODUCT_INFO、PRODUCT_COMPARE、PRODUCT_RECOMMEND_KB、PRODUCT_COMPATIBILITYshopping-policy-kbCOUPON_POLICY、INVOICE_POLICY、DELIVERY_POLICY、RETURN_POLICYafter-sales-kbWARRANTY_POLICY、REPAIR_SERVICE、SERVICE_CENTERsupport-kbUSAGE_GUIDE、TROUBLESHOOTING、CONNECTION_NETWORK、CHARGING_BATTERY这些 Intent 处理的是稳定、公开、适合通过文档检索的知识。2. 8 个 MCP Tool IntentIntentMCP Tool数据特点PRODUCT_RECOMMEND_TOOLsearchProducts动态、结构化PRODUCT_AVAILABILITYgetProductAvailability实时库存COUPON_ELIGIBILITYgetCouponEligibility用户私有数据ORDER_QUERYgetOrder用户私有数据LOGISTICS_QUERYgetLogistics实时且与用户绑定WARRANTY_STATUSgetWarrantyStatus设备实时状态SPARE_PART_PRICEgetSparePartPrice动态、结构化REPAIR_PROGRESSgetRepairProgress用户私有数据这 8 个工具又可以分成两类公开但动态、结构化的数据 ├─ searchProducts ├─ getProductAvailability └─ getSparePartPrice 与当前用户身份绑定的私有数据 ├─ getCouponEligibility ├─ getOrder ├─ getLogistics ├─ getWarrantyStatus └─ getRepairProgress3. 1 个 SYSTEM IntentORDER_CHANGE → 不调用修改订单工具 → 引导用户进入官方订单修改入口这个设计明确表达了系统边界当前 Agent 可以提供订单修改指引但并没有获得修改真实订单的能力。需要注意的是这里的 SYSTEM Intent 和前面PreRoutingPolicy处理的“你是谁”并不完全相同。前者是 24 类业务能力中的一个正式资源路由后者则是在正常分类之前处理的系统快捷路径。十一、RetrievalEngine获得授权以后才真正执行完成路由治理后RetrievalEngine根据AllowedRoute执行具体资源。1. 指定知识库路由如果某个子问题被授权为 KB 资源会进入 Intent Directed Search只检索当前 Intent 绑定的 Collection。RETURN_POLICY → shopping-policy-kb → 向量召回 → 去重与重排 → 返回KBEvidence先做 Intent 定向再检索指定知识库可以减少商品知识、售后政策和设备操作文档之间的相互干扰。2. 公共知识库兜底如果治理结果是GLOBAL_KB_FALLBACK系统不会访问任意资源而只在四个公共知识库白名单内并行检索product-kb shopping-policy-kb after-sales-kb support-kb兜底的含义不是取消边界而是在受限公共知识范围内给系统一次检索机会。订单、优惠券、维修进度等私有工具不会因为 Intent 没识别出来就被兜底调用。3. MCP Tool 路由如果AllowedRoute的类型为 MCP执行器会根据 Intent 节点中固定的mcpToolId到本地McpToolRegistry中精确查找工具。WARRANTY_STATUS → mcpToolId getWarrantyStatus → McpToolRegistry精确查找 → 获取该工具的输入Schema → 进入参数抽取与执行前门禁 → MCP Client发起tools/call模型不能临时发明一个工具名也不会在所有 MCP 工具中自由探索。Java 在进入执行层之前已经把可见工具限制为当前授权资源。这也解释了 Java 路由和 MCP 的关系Java 路由负责决定这次请求能不能调用工具、允许调用哪个工具MCP 负责工具的描述、发现和远程调用。MCP 是工具协议不是业务路由器。十二、复合问题怎样组合 KB 和 Tool当前实现中一个子问题基本只会对应一个资源一个知识库、一个工具或一个 SYSTEM 处理器。系统之所以能够处理“既需要知识库又需要工具”的问题主要依赖 Query Rewrite 先把复合问题拆开。例如“查一下我的订单并告诉我怎么退货” ↓ 子问题1查询我的订单 → ORDER_QUERY → getOrder 子问题2退货政策是什么 → RETURN_POLICY → shopping-policy-kb ↓ 组合MCP Result和KB Evidence生成最终回答这是一种可控的混合路由但它不是让一个不可拆分的子问题同时自由访问多个知识库和工具。如果将来出现真正需要同一子问题多资源协同的场景还需要显式扩展路由模型而不能把当前能力说成通用的自由混合路由。十三、工具依赖路由不是重新做 Intent 分类有些工具在执行时缺少的参数可以由另一个工具提供。例如“上个月买的手表还在保吗” ↓ getOrder → 找到购买记录和设备SN ↓ getWarrantyStatus → SN只从上游可信结果绑定这时的DependencyExecutionCoordinator做的是工具依赖规划根据已经确定的业务目标决定先调用哪个上游工具再把可信结果传给下游工具。它并没有重新判断用户属于什么 Intent因此更准确地说这是执行阶段的“依赖路由”而不是语义分类路由。如果上游找到多条可能的购买记录系统会进入待确认状态用户下一轮回复“第二个”时又会回到最前面的 Pending-State Routing 恢复任务。这样整条链路形成了一个闭环而不是一次性的工具调用。十四、哪些东西不应该都叫“路由”项目中还有一批组件经常和路由混在一起ExactBusinessParameterFilter → 过滤不符合格式或来源要求的业务参数 MissingParameterGate → 检查必填参数是否完整 EntityResolver → 从可信业务数据中绑定实体并处理歧义 HuaweiMallRuntimeArguments → 删除模型生成的身份字段注入服务端用户身份 McpToolRegistry → 只允许执行已经注册的工具这些组件发生在工具已经选定以后解决的是“这次调用是否具备安全执行条件”因此更适合统称为工具执行门禁或参数与权限治理而不是意图路由。也就是说完整链路可以被切成两个问题路由治理 → 该走知识库、工具、系统处理还是拒绝 工具执行治理 → 工具已经确定后参数和身份是否允许执行本文讨论的是前半段后半段会单独展开。十五、模型供应商路由也不是业务路由项目中还存在RoutingLLMService、RoutingEmbeddingService和RoutingRerankService。它们名字里同样带有 Routing但解决的是另一个层次的问题Chat / Embedding / Rerank请求 → 选择首选Provider和Model → 检查健康状态 → 失败时切换备用模型这是基础设施层的模型容灾路由不负责判断用户要查订单、物流还是知识库。区分这两种 Routing 很重要业务路由决定请求访问什么业务资源模型路由决定某次 AI 能力由哪个供应商或模型承载。十六、为什么不直接让模型 Function CallingFunction Calling 解决的是模型如何用结构化格式表达“我想调用这个函数”。它非常有价值但它本身不等于完整的业务治理。如果把全部工具直接交给模型模型通常同时负责理解用户意图 → 选择工具 → 生成参数开发速度很快但三类错误也会耦合在一起。一旦调用失败很难判断究竟是意图识别错了、工具描述不清楚还是参数生成出了问题。当前项目把链路拆成模型识别候选Intent → Java判断候选是否可信 → 资源契约确定唯一授权资源 → 模型针对指定Schema抽取普通参数 → Java完成参数与权限门禁 → MCP执行tools/call它牺牲了一部分模型自由度但换来了清晰的能力边界、可解释的调用路径和更容易回归的业务行为。因此这个商城系统更准确的定位是受控业务 Agent或者 Agentic Workflow而不是强自治的 ReAct Agent。十七、路由必须留下 Trace最终回答错误并不能说明错误一定发生在生成阶段。它可能来自 Query Rewrite 漂移、Intent 判断错误、资源契约配置错误甚至公共知识库兜底范围不合理。因此一次路由至少应该记录原始问题和改写结果拆分出的子问题候选 Intent 与置信度IntentResolver 的解析状态RoutingGovernanceService 的最终决策AllowedRoute 中的资源类型和资源名称是否发生公共知识库兜底进入澄清、拒绝或错误处理的原因。例如用户询问物流却调用了订单详情工具可以沿着 Trace 逐层定位模型是否把它识别为ORDER_QUERY → ResourceContractGuard是否发生错误纠偏 → LOGISTICS_QUERY是否被错误映射到getOrder → RetrievalEngine是否取错了toolId这也是为什么 Agent 评测不能只看最终回答。对路由层来说更重要的断言是Intent 是否可信、资源是否正确、该补问时有没有停止执行以及超出能力范围的问题有没有误调用工具。十八、当前实现需要诚实说明的边界工程设计不能只介绍理想结构还要说明当前版本实际做到哪里。1. IntentGuidanceService 当前不在有效主链上它原本希望在同一子问题存在多个 KB 候选时引导用户确认具体意图。但当前顺序大致是IntentResolver → RoutingGovernanceService → ResourceContractGuard将每个子问题收敛成一个候选 → IntentGuidanceService再检查多候选等执行到 Guidance 时多候选已经被收敛因此这一模块在主链中很难真正触发。若要让它生效更合理的顺序应该是IntentResolver → IntentGuidanceService → 用户确认 → ResourceContractGuard或者专门保留一份未经裁剪的候选集合。2. 单个子问题目前基本是单资源路由当前ResourceContractGuard对一个子问题只保留一个 Intent因此一个子问题基本对应一个 KB、一个 Tool 或一个 SYSTEM 资源。系统能够处理 KB 与 Tool 的组合主要依靠 Query Rewrite 拆分多个子问题而不是让同一个子问题自由访问多个资源。这种方式更容易控制但能力边界也需要如实说明。3. 确定性纠偏规则需要持续维护“到哪了”映射物流、“详情和金额”映射订单这类规则对高风险混淆场景很有效但它们依赖业务经验。如果规则不断膨胀ResourceContractGuard也可能演变成另一个难以维护的分类器。更合适的做法是只保留少量稳定、高价值的纠偏规则并通过 Trace 和业务评测决定哪些规则值得进入主链。十九、总结回头看这套路由架构它并不是在大模型外面简单加一个switch而是把一次模糊的自然语言请求逐步收敛成一条确定、授权并可追踪的执行路径。Pending-State Routing → 判断是不是上一轮任务的继续 PreRoutingPolicy → 处理无需模型参与的确定性问题 IntentResolver → 识别候选用户的问题像什么 RoutingGovernanceService → 最终裁决候选是否可信系统是否继续 ResourceContractGuard → 资源授权允许访问哪个KB、Tool或SYSTEM资源 RetrievalEngine → 执行授权真正检索或调用整套设计最终可以收束成一句话模型负责理解Java 负责裁决资源契约划定能力边界MCP 负责连接工具但只有通过路由治理的请求才有资格进入工具调用链路。下一篇将从McpToolRegistry继续向下工具已经确定以后如何根据 Schema 抽取参数怎样区分意图澄清和缺参补问怎样完成实体消歧、上游参数绑定与服务端身份注入以及为什么模型生成的订单号、SN 和 userId 不能被直接信任。
返回列表