斗破Google ADK Java苍穹-第9境斗尊-掌空穿梭-MCP与A2A
斗尊境:MCP 与 A2A 协同,让数字人跃出单 JVM 边界
工具硬编码、Agent 同进程,接一个新系统就要改一次主工程。
[读者] 已跑通 ADK Java 数字人、准备对接外部系统的后端与 Agent 工程师
[痛点] 工具写死在本地方法里,Agent 只能同进程部署,接一个系统改一次主工程
[现在读] 订单、工单、知识库分属不同团队,能力还要被别的 Agent 反向复用
[读完] 会用 MCP 接外部工具、用 A2A 调远程 Agent,并能划清四类能力边界
[旧方案] 工具就是本地 Java 方法,Agent 与工具同 JVM 部署
|
v
[新需求] 工具由外部团队维护,数字人的能力要被远程 Agent 调用与复用
|
v
[冲突] 进程内的方法调用表达不了跨组织、跨语言、独立发版的边界
|
v
[后果] 每次接入都改主工程、重新发版,还要替别人的可用性背锅
我是老李,带一个做企业数字人的小组。从斗之气一路走到斗宗,这个数字人已经有 Runner、Event、Session、Memory、Workflow 和 HITL,能多轮续聊、能记住用户、能跑确定性流程,也能在危险动作前暂停等审批。
但它有一个越跑越明显的毛病:所有能力都长在同一棵树上。工具是一个个本地 Java 方法,写在数字人工程里;要接一个新系统,就得在数字人工程里新建一个 Tool 类、注入对方的 SDK、再重新发版。业务接得越多,主工程越像一根越缠越紧的线。
最近订单团队找过来,说订单查询能力可以接给数字人,但提了三个条件:订单 SDK 不对外发、鉴权走他们自己的 token、以后他们自己改自己的接口。紧接着工单团队和知识库团队也提了同样的要求,还有另一个团队想把数字人的能力反过来接进他们的 Agent 里。
同一条数字人进程,还能继续既跑业务流程、又跑工具适配、又跑别人的 Agent 吗?
是把工具继续硬编码成本地 Tool,还是让数字人通过 MCP 去调工具、通过 A2A 去调远程 Agent?
选错的话,代价是每接一个系统都要改数字人主工程、重新发版,所有外部系统的抖动都会顺着进程内调用直接砸到数字人身上?
01、故事
上周四下午,订单团队的三个人坐在我们会议室,白板上写着他们的诉求:订单中心的查询接口不打算对外开放 SDK,只提供一套 MCP Server;鉴权用他们签发的机器人 token,权限按项目号收敛;他们的接口版本会自己演进,不承诺向后兼容超过一个大版本。
我原来的做法很直接。新建一个 OrderQueryTool 类,注入订单团队的 HTTP 客户端,把 queryOrder 和 getStatus 两个方法包成 ADK 的 FunctionTool,注册进 LlmAgent 的 tools 列表,然后再写一层 try-catch 把异常转成友好话术。整个过程半小时,改完提交,重新发版。
这次的任务是:不改数字人主工程,把订单查询接进来,并且让订单能力以后由订单团队自己发版。
第一次听到这个要求,我下意识觉得多此一举。工具就是方法,方法就是代码,代码就在我工程里,有什么不好?直到我翻了一下最近三个月的提交记录:数字人主工程里,有 40% 的改动来自外部系统的接口适配,而不是数字人本身的逻辑。我们发版的节奏,被别人的接口节奏牵着走。
更麻烦的是第二件事。另一个团队来找我,说他们的客服 Agent 想直接调用我们的数字人,用来回答产品使用问题。按老办法,他们得把我们的 jar 引过去、把我们的 Session Service 一起部署,然后我们一起发版。他们的技术栈是 Python。
现场那台机器上,跑着一个 JVM、一个数字人、六套外部系统的适配代码,还有三个别的团队想要的地图。这条路,再往前走一步就走不动了。
旧法钉在进程
新需跨出边界
一人难扛四方
先问路在何处
02、问题
旧方案失效得很具体:数字人主工程同时承担了三件本不该由它承担的事——业务编排、外部系统适配、以及别人 Agent 的运行时。
业务影响有两层。对外,每接一套系统平均要走一次完整的开发、测试、发版流程,接入周期不由我们决定;对内,主工程的编译依赖越来越重,一次订单 SDK 的升级会连带触发整个数字人的回归。
技术表现上,问题集中在四点。第一,工具是编译期固定的,运行期无法按项目、按租户增减。第二,工具与 Agent 在同一个进程里,外部系统的超时、OOM、线程阻塞会直接传导成数字人的故障。第三,模型能看到的工具集合是全集,没有白名单概念,一个写了退款能力的工具,只要注册进去,模型就有可能调用它。第四,数字人无法作为独立能力被别的 Agent 调用,只能靠共享 jar 和共享部署。
我们需要的完成标准是可验证的,而不是口号式的:
- 新增一个外部工具时,数字人主工程代码零改动,只改配置。
- 未在白名单内的工具,模型在本次会话中不可见、不可调用。
- 单次 MCP 调用有明确超时上限,超时后返回可诊断的错误事件,而不是让整个 Run 挂住。
- 订单能力可拆成独立部署的远程 Agent,数字人通过 A2A 调用它,双方可独立发版。
- 以上每一条,都有能在同一个数字人项目里跑出来的测试结果。
这五条里,第四条最容易被忽略,也最容易被做歪——很多人以为把 Agent 拆出去只是换个调用地址,实际上它是把一次函数调用换成了一次带契约、带版本、带失败语义的跨进程协作。
改动落在主干
发版绑住他人
标准写在纸上
才算可验可测
03、原理
这一章只需要两个概念,但要分清它们各自的边界。
MCP 是 Agent 与工具之间的协议。它的定位是让模型侧的调用方,能够发现并调用一个外部进程暴露出来的工具,而不需要把工具的实现代码搬到调用方进程里。在 ADK Java 里,这件事由 com.google.adk.tools.mcp 包下的 McpToolset 承担:它把远程 MCP Server 提供的工具,适配成 ADK 侧的 Tool,注册进 LlmAgent 的 tools。对 LlmAgent 来说,MCP 工具和本地 FunctionTool 在调用形态上是同一类东西。
A2A 是 Agent 与 Agent 之间的协作协议。它解决的不是“怎么调一个函数”,而是“怎么让一个独立的、可能由另一个团队用另一种语言实现的 Agent,被当作一个可寻址、可发现的协作者来使用”。ADK Java 的 a2a 模块负责这一侧的适配:把远程 Agent 包装成本地可调用的 Agent 形态。
反直觉判断: 接入 MCP 并不会让系统变简单,它只是把复杂度从一个地方搬到了另一个地方。本地方法调用被替换成远程协议调用之后,网络语义会立刻注入进来:连接、鉴权、超时、重试、序列化、版本协商、错误映射,这些以前不存在的东西,现在一个都不能少。真正的收益不是“代码更少”,而是“边界更清、发版更独立”。如果奔着省事去用 MCP,用不了多久就会在排查现场还债。
基于这个判断,数字人的能力可以划成四类边界,这个分类后面架构、排查、优化都会反复用到:
| 能力类型 | 执行位置 | 典型场景 | 主要代价 |
|---|---|---|---|
| Local Tool | 同进程 | 纯计算、本地数据读取、无外部依赖 | 无网络开销,但改一次发一次版 |
| MCP Tool | 外部进程,Agent 调工具 | 订单、工单、知识库等外部系统能力 | 多出协议层、鉴权与超时治理 |
| Local SubAgent | 同进程 | 内部拆分的专职子 Agent | 隔离性弱,共享同一份资源 |
| Remote A2A Agent | 独立服务 | 跨团队、跨语言的 Agent 复用 | 多出契约版本与跨进程失败语义 |
这四类不是替代关系,而是分层关系。判断标准只有一条:谁来决定它的发版节奏。发版节奏由我们决定,就放进 Local;由别人决定,就推到 MCP 或 A2A。
协议非为省事
只是换岗值守
调用跨出进程
语义随之外扩
04、架构
[输入] 用户消息 / Workflow Step
|
v
[模块] LlmAgent + 四类能力边界路由
Local Tool | MCP Tool | Local SubAgent | Remote A2A Agent
|
v
[数据/状态] InvocationContext / Session State / Project 凭证上下文
|
v
[处理] 白名单判定 -> 能力选择 -> 跨进程调用(超时/熔断)-> 结果归一
|
v
[输出] Event 流 / Artifact / 远端结果回填
这个架构的关键不在多出来的两个协议,而在于把“能力选择”从编译期提到了运行期。LlmAgent 依然只面对一份工具列表,但这份列表是按项目、按凭证、按配置动态拼装出来的。
边界上要守住三条。第一条,MCP Toolset 只做适配,不做业务判断,业务规则留在外部系统的 MCP Server 里,数字人不复制一份。第二条,凭证不进模型上下文,也不进 Session State,只以引用的形式存在于调用链路里。第三条,A2A 的远程 Agent 对本地来说是一个黑盒,本地不假设它的内部结构,只依赖它的 Agent Card 与返回契约。
收益是清晰的:接入新能力从“改代码 + 发版”变成“改配置 + 重启”,外部系统的抖动被进程边界和超时策略挡在外面,数字人本身的能力也能以远程 Agent 的形式被别的团队复用。
代价同样清晰:一次本地调用变成一次网络调用,延迟必然上升;协议层引入新的失败模式,排查链路变长;工具的白名单、凭证的作用域、契约的版本,都需要人来维护,无人维护的白名单很快就会退化成全量放开。
适用条件也很明确:当工具的维护方与 Agent 的维护方不是同一批人,或者 Agent 需要被别的系统复用,这套架构才划算。如果工具就是我们自己写的、三个月都不改一次,硬编码成本地 Tool 反而更稳。
一条请求进来
四类边界分流
状态留在上下
结果回到事件
05、实战一次
先做最小的一步:只用 MCP Toolset 把订单能力接进来,A2A 留到优化一章再拆。
环境与版本:JDK 21;Maven 3.9+;Google ADK Java 1.11.0;订单侧由订单团队提供 MCP Server,暴露 SSE 端点。以下代码按 adk-java 仓库 core/src/main/java/com/google/adk/tools/mcp 与 a2a/README.md 的组织方式整理,具体类名、builder 方法与参数名可能随版本变化,落地前请按当前官方文档核验。
依赖:
<dependency>
<groupId>com.google.adk</groupId>
<artifactId>google-adk</artifactId>
<version>1.11.0</version>
</dependency>
如果 MCP Toolset 与 A2A 分别位于独立模块,需要额外引入对应模块坐标,具体 artifactId 需按当前官方 Release 核验。
配置:
digitalhuman:
mcp:
order:
url: https://mcp.order.internal/sse
credential-ref: vault://order-mcp/robot-token
project:
id: proj-dh-001
核心实现:
package com.example.dh.agent;
import com.google.adk.agents.LlmAgent;
import com.google.adk.tools.mcp.McpToolset;
import com.google.adk.tools.mcp.SseServerParameters;
import java.util.List;
import java.util.Map;
public final class OrderDeskAgent {
private OrderDeskAgent() {}
/**
* 组装只会用订单能力的数字人 Agent。
* token 由凭证中心按 project 下发,不落盘、不进 Session State。
*/
public static LlmAgent create(String token) {
SseServerParameters params =
SseServerParameters.builder()
.url("https://mcp.order.internal/sse")
.headers(Map.of("Authorization", "Bearer " + token))
.build();
McpToolset orderTools =
McpToolset.builder()
.serverParameters(params)
.toolFilter(List.of("order_query", "order_status"))
.build();
return LlmAgent.builder()
.name("digital_human")
.model("gemini-2.0-flash")
.description("企业数字人:订单业务助手")
.instruction(
"你是企业数字人。查询订单只能使用已授权的 order_query 与 order_status;"
+ "工具返回失败时如实告知用户,禁止猜测订单状态。")
.tools(orderTools)
.build();
}
}
启动:
mvn -q -DskipTests package
java -jar target/digital-human-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev
请求:
curl -s -X POST http://localhost:8080/api/chat \
-H 'Content-Type: application/json' \
-d '{"userId":"u-1001","sessionId":"s-001","message":"订单 A10086 现在什么状态?"}'
未在当前环境实测,以下为预期结果。
示例输出:
{
"events": [
{"author": "digital_human", "type": "tool_call", "tool": "order_query", "args": {"orderId": "A10086"}},
{"author": "digital_human", "type": "tool_result", "tool": "order_query", "status": "OK"},
{"author": "digital_human", "type": "text", "text": "订单 A10086 已发货,预计明天送达。"}
]
}
验证的落点是三个:Event 流里出现 tool_call 与 tool_result;调用链路上只有 order_query 与 order_status;把 token 换成无订单权限的凭证后,调用应当被拒绝而不是静默成功。
V1 到这里是能跑的,但它只解决了“接得上”,没有解决“接得稳”。
先接一个工具
跑通一次调用
白名单尚未立
隐患留在后面
06、排查
第一条链路:模型叫了一个不该存在的工具。
现象:联调日志里出现工具名 order_cancel_all,而我们的意图里根本没有涉及取消订单。
怀疑:是不是模型幻觉出了一个不存在的工具名。
检查:打开 MCP Toolset 的注册日志,对照 MCP Server 的 tools/list 返回。
证据:
10:12:31 WARN McpToolset - registered tools=[order_query, order_status, order_cancel_all,
order_refund, order_export, user_list, ...] count=12
订单侧暴露了 12 个工具,我们的 toolFilter 当时并没有生效,模型可见的是全集。
根因:白名单只在客户端做了一半,且服务端没有按 token 的 scope 做 ACL。工具可见性是一个安全边界问题,不是提示词工程问题。
修复:客户端 toolFilter 收紧到两个工具;同时要求订单侧按 robot token 的 scope 过滤 tools/list,双端各拦一道。
错误尝试: 在 instruction 里写“严禁调用退款、取消、导出类工具”。
为什么错:这是把安全边界交给模型的自律。模型可能在多轮对话、长上下文、或者被用户诱导的情况下越过提示词约束;而工具一旦被调用,副作用已经发生在订单系统里了。白名单必须由代码和授权系统保证,提示词只能作为最后一层补充,不能作为第一层防线。
第二条链路:一次 MCP 调用挂住整个 Run。
现象:用户发了消息后,Runner 长时间没有产生新的 Event,HTTP 请求一直不返回,也没有任何异常抛出。
怀疑:先是怀疑模型侧卡住,换了模型后现象依旧;再怀疑是 Runner 线程池不够。
检查:间隔 30 秒抓两次线程栈,对比阻塞位置。
证据:
"adk-runner-3" #31 prio=5 os_prio=0 tid=0x... nid=0x... runnable
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:171)
- locked <0x...> (a java.net.SocketInputStream)
线程停在 SSE 的 socket read 上,没有读超时,连接来自共享的连接池。
根因:SSE 连接没有配置读超时,MCP 工具调用使用的是全局共享连接,一个慢响应会把 Runner 线程一起拖住。
修复:给 MCP 连接加上明确的读超时(本章取的 3 秒是一个可配置的工程取值,不是压测结论);为 MCP 单独配置连接池,避免与业务 HTTP 共用;在 Toolset 外层加熔断,连续失败后短期快速失败。
错误尝试: 把 HTTP 客户端线程池从 20 调到 200。
为什么错:线程池放大的是承受能力,不是修复能力。慢响应依旧存在,只是把“一个请求卡住”变成“两百个请求一起卡住”,并且在故障时更快耗尽内存。问题的根因是没有超时,加线程只是把爆炸半径扩大。
日志露出真名
栈里卡住等待
疑心先放一边
证据才定根因
07、优化
两条排查链给出了两个根因:工具可见性没有边界,跨进程调用没有时间边界。V2 就围绕这两个根因改。
修改一:把工具可见性收成三层。
第一层是 MCP Server 侧按 token scope 过滤 tools/list,没有权限的工具根本不下发;第二层是 ADK 侧 toolFilter 白名单,只放 order_query 与 order_status;第三层是服务端的 Project ACL,按项目号限制可访问的订单范围。
为什么要三层:任何一层都可能因为配置漂移失效,三层互为兜底,且失效时是“调用被拒绝”,而不是“调用成功但越权”。
新行为:模型可见工具稳定为 2 个;换用无权限凭证时,tools/list 返回空集,LlmAgent 无工具可调,直接走兜底话术。
修改二:把凭证从代码里赶出去。
public interface McpCredentialProvider {
/** 按项目号换取短期凭证,进程内不缓存明文。 */
String orderMcpToken(String projectId);
}
凭证由凭证中心按项目号下发短时 token,代码里只持有 provider 引用,不落配置文件、不进 Session State、不进模型上下文。理由很直接:MCP 的鉴权信息一旦进了上下文,就可能被日志、被模型输出、被对话历史带出去。
修改三:给跨进程调用加时间边界与熔断。
给出两个可配置的取值:MCP 读超时 3 秒,A2A 调用超时 5 秒;连续失败达到阈值后进入熔断窗口,窗口内直接快速失败并把错误映射成可读事件。这两个数值是工程上的默认起点,需按实际链路的官方文档与线上表现调整,不是压测结论。
修改四:把订单 Agent 拆成远程 A2A 服务。
订单团队把订单 Agent 独立部署,暴露 Agent Card;数字人侧只保留一个远程 Agent 引用。远程 Agent 的包装类名与 builder 形态需按当前 a2a 模块 README 核验,以下按该模块的组织方式示意:
// 类名与 builder 方法需按当前 adk-java a2a 模块核验
RemoteA2aAgent orderRemoteAgent =
RemoteA2aAgent.builder()
.name("order_remote_agent")
.description("订单中心远程 Agent:负责订单查询与状态解释")
.agentCardUrl("https://order-agent.internal/.well-known/agent-card.json")
.build();
拆分之后,订单 Agent 的模型、提示词、工具、发版节奏全部归订单团队,数字人只依赖它的 Agent Card 与返回契约。
验证:跨进程契约要先能看见。
curl -s https://order-agent.internal/.well-known/agent-card.json
示例输出:
{"name":"order-agent","version":"1.2","capabilities":["order.query","order.status"]}
版本兼容的处理办法是:数字人侧记录依赖的 Agent Card 版本,不兼容时拒绝调用并给出明确事件;远程 Agent 侧声明自身支持的能力集合,新增能力不改变已有契约。
错误传播的处理办法是:远端失败不吞异常,映射成带来源、带原因、带可重试标记的事件,让用户看到“订单服务暂时不可用”,让运维在事件流里看到是哪一跳断了。
白名单收窄口
超时划出底线
拆出一座远岛
各修各的城墙
08、演进
[同一输入:订单 A10086 现在什么状态?]
|
+--[V1] 本地工具 + 全量 MCP 工具
| 行为:调用成功,但工具集不可控、无超时、凭证混用
| 代价:接入快,隐患留到线上才暴露
|
+--[V2] MCP 白名单 + Project ACL + 凭证隔离 + 超时熔断 + 远程 A2A Agent
| 行为:可见工具收敛、失败可诊断、订单能力独立发版
| 代价:多出协议层、治理配置与跨进程排查成本
|
[Trade-off]
得到:能力解耦、发版独立、跨团队与跨语言复用
失去:本地调用的低延迟与简单链路
适用边界:工具维护方与 Agent 维护方不是同一批人,或 Agent 需要被外部复用
正确性上,V2 明显更强。V1 把工具可见性交给模型自律,V2 把它交给三层授权,越权调用从“可能发生”变成“结构性不可达”。
稳定性上,V2 用超时和熔断把外部故障挡在进程外,V1 则把外部故障直接转成自家的线程阻塞。这一条在排查现场是最有说服力的证据。
复杂度上,V2 明显更高。多了一层的代价是真实的:配置项变多、排查链路变长、本地调试需要先起一个远程服务。V2 不是无脑更优,它是在接入方变多之后才变得更优。
成本上,V2 增加的主要是运维与人力成本,而不是机器成本:白名单要人维护,Agent Card 版本要人跟进,凭证有效期要人巡检。这些如果没人管,V2 会退化回 V1,而且是更难排查的 V1。
适用范围上,只有当我们自己写工具、并且短期内不打算对外复用 Agent 时,V1 才是更合理的选择。反过来,只要有一个外部团队要维护自己的能力,V2 的账就开始划算。
遗留问题有三个:MCP 与 A2A 的链路目前还没有统一的观测面板,跨进程耗时只能靠日志拼;白名单目前是静态配置,还没有做成按租户动态下发;远程 Agent 的灰度与回滚机制还没建。这三条留给后面的境界继续补。
同是一句问话
旧路改骨换筋
新路只换接线
取舍各在心头
09、洞见
9.1 能力边界比能力数量更重要
接入 MCP 之后,数字人能调用的工具数量会迅速膨胀,因为接入成本被压得很低。但系统真正的安全性来自“少”,不是“多”。工具越容易接,越需要提前定义哪些工具根本不允许出现在模型面前。本章第一条排查链就是这句话的反面教材:一个有退款能力的工具,仅仅因为它被注册了,就进入了模型的可选集合。
反直觉判断:接入能力的门槛越低,治理的优先级就应该越高,而不是越低。低门槛会让人误以为不需要审批、不需要白名单、不需要凭证隔离,而这三件事恰恰是低门槛带来的风险放大器。
9.2 协议解决的是演进问题,不是性能问题
MCP 与 A2A 不会让调用更快。跨进程一定比进程内慢,协议层一定比方法调用重。它们换来的是别的东西:订单团队可以改自己的接口,工单团队可以换自己的语言,数字人主工程不用动一行代码。
反直觉判断:如果用性能指标去评价 MCP 与 A2A 的收益,几乎一定会得出“不值得”的结论;这两个协议的收益体现在发布节奏上,而不是延迟上。选型时问的问题应该是“这套能力以后由谁发版”,而不是“这样调用快不快”。
9.3 错误传播是跨进程 Agent 的第一等公民
进程内调用失败,异常栈就在眼前;跨进程调用失败,异常有可能被序列化成一个空字符串,或者被吞在一层 try-catch 里。第二种情况在排查现场几乎是灾难:现象是没有回复,日志里什么都没有。
可落地的做法是给错误定义统一结构:来源(哪一跳)、原因(超时/拒绝/契约不符)、可重试标记、以及一段给用户看的话。这一层做不做,决定了这套架构在生产上能不能被维护。
9.4 四类能力边界的心智模型
Local Tool 解决快速,MCP Tool 解决解耦,Local SubAgent 解决分工,Remote A2A Agent 解决复用。四者不是替代关系,判断标准是发版节奏归谁。
一个容易犯的错误是:把本地 SubAgent 当成远程 Agent 的替代,理由是“反正都在我们工程里”。但一旦对方的团队、语言、发版节奏和我们不同,本地化就意味着我们替他们背上了运行时责任,这在组织上是错位的。
能力不在多寡
边界才定生死
协议解耦演进
不是替人省事
10、系统落地
原来有什么:一个单 JVM 的数字人,工具是本地 Java 方法,外部能力靠硬编码适配,每次接入都要改主工程、重新发版,外部系统故障会直接传导进来。
本篇新增了什么:MCP Toolset 接入外部工具;工具白名单、Project ACL 与凭证隔离三层治理;MCP 连接的读超时、专用连接池与熔断;订单 Agent 拆成独立部署的远程 A2A Agent;A2A 侧的版本兼容判定与统一错误传播结构。四类能力边界成为团队内部的通用语言。
现在能做什么:新增一个外部工具,主工程代码零改动;模型可见的工具集合由授权决定,而不是由代码决定;单次跨进程调用有明确时间上限;订单能力的发版节奏归订单团队,数字人不再被牵着走;数字人的能力可以被别的团队以远程 Agent 方式复用。
还缺什么:跨进程链路的统一观测面板还没有,MCP 与 A2A 的耗时、失败率目前只能从日志里拼;白名单还是静态配置,没有按租户动态下发;远程 Agent 的灰度发布与快速回滚没有建立。
下一步如何演进:把这一境的跨进程调用纳入统一治理层——超时、熔断、限流不再各写一遍,而是集中在 Runtime 侧统一下发;同时给 MCP 与 A2A 调用补上可追踪的链路标识,让一次跨进程协作可以被完整还原。这一条会在后面的境界里,和 Workflow、Live、Eval 一起合流。
主工程不再改
外系统各自飞
还缺观测闭环
留给后来补全
11、小结
Q1 → 不行。单进程同时背业务编排、外部适配和别人的 Agent 运行时,已经到边界了。
Q2 → 工具走 MCP,Agent 走 A2A。前者解决 Agent 调工具,后者解决 Agent 与远程 Agent 协作。
Q3 → 代价是主工程被别人的接口节奏绑架,外部故障直接传导进来,且能力无法被复用。
状态 → 数字人跃出单 JVM 边界,工具可插拔、能力可远程、调用有边界,并有测试结果。
这一境真正交出去的东西,不是两段接入代码,而是一套判断规则:一个能力该放在本地、该走 MCP、还是该拆成远程 Agent,取决于它的发版节奏归谁。
三问皆有回应
四界各有归属
状态停在此境
可跑可测可换
12、作业
12.1 理解题:MCP 与 A2A 各自解决什么问题,为什么不能互相替代?
参考答案:MCP 解决的是 Agent 与工具之间的调用关系,把外部进程的能力适配成可被模型调用的工具,调用方是 Agent,被调用方是工具服务;A2A 解决的是 Agent 与 Agent 之间的协作关系,被调用方本身是一个有模型、有提示词、有自己工具集的独立 Agent。前者替换的是函数调用,后者替换的是跨团队的能力复用。二者不能互替的根因在于语义层级不同:工具没有意图,Agent 有意图;工具返回数据,Agent 返回判断。把远程 Agent 硬塞进 MCP 会丢掉它对自身领域规则的封装,把工具硬塞进 A2A 则会凭空多出一层没有必要的推理开销。
12.2 实战题:给数字人接一个知识库 MCP Server,并保证模型只能看到被授权的工具。
参考答案:分三步落地。第一步在知识库侧按 robot token 的 scope 过滤 tools/list,只下发 kb_search 与 kb_doc_meta;第二步在 ADK 侧用 McpToolset 的 toolFilter 显式声明这两个工具名,形成客户端白名单;第三步为 Project ACL 建立项目号到知识库分区的一一映射,并在测试中用一个无权限凭证跑一次,断言 tools/list 返回空集、LlmAgent 走兜底话术而不报未捕获异常。三步都做完才算完成,只做第二步只是客户端自律,服务端仍可能下发越权工具。
12.3 排障题:数字人调用 MCP 工具时偶发长时间无响应,日志里没有异常,怎么定位?
参考答案:按证据链走。先确认现象是“无 Event 产生”还是“Event 产生但很慢”,两者根因不同;再抓两次线程栈对比,若线程阻塞在 socket read 上且没有读超时,根因基本锁定在连接层;接着检查 MCP 连接是否与业务 HTTP 共用连接池,共用会放大阻塞;最后检查 MCP Server 侧是否有慢查询或长事务。修复要落在读超时、独立连接池与熔断三件事上,而不是加大线程池。同时补上错误事件,让超时能被用户和运维同时看到。
12.4 架构判断题:订单团队的接口很稳定,三年没改过,是否也要拆成远程 A2A Agent?
参考答案:不一定,要看发版节奏归谁,而不是看接口稳不稳定。如果订单能力由订单团队维护、他们有自己的发布流程、未来还要被其他业务复用,那么即使当前接口稳定,拆出去仍然划算,因为拆的是责任边界而不是接口本身;如果订单能力实际上由数字人团队自己维护、也没有对外复用需求,那么拆出去只是凭空增加一跳网络与一套契约维护成本,用 Local Tool 更合适。判断的关键问题是:下一次订单能力变更时,是谁改代码、谁发版、谁背线上责任。
题在纸上易答
手在键上难行
先做一遍最小
再谈架构高低
13、思考
这一境的核心冲突,其实不是“要不要用 MCP 和 A2A”,而是“哪些东西不该再长在数字人自己身上”。工具硬编码与同进程部署之所以能撑到斗宗,是因为前八个境界里所有能力都归我们自己发版;一旦维护方变成别人,原来的做法就从“高效”变成了“绑架”。
可复用的工程判断有三条。第一,能力放在哪里,取决于发版节奏归谁,而不是取决于接入方便不方便。第二,跨进程之后,时间边界和授权边界必须先于功能落地,否则接得越快,故障来得越早。第三,协议的收益体现在演进能力上,用延迟和吞吐去评价它,会得出完全错误的结论。
从斗之气到斗尊,这个数字人一直在做同一件事:把不属于自己的能力,从身上摘出去。摘得越干净,它自己就越稳。
一境换一番骨
一界定一条路
一拆得一分活
一约守一生契

浙公网安备 33010602011771号