从 MCP 到多智能体:企业级 Agent 工程化落地指南
客户提了一个需求:「把 Agent 接到我们的 CRM 里,让它自己查订单、自己发退款。」听起来简单,真正动手时工程团队会卡在几个具体的地方:工具定义散在业务代码里,每接一个新系统就要改一遍 Agent 的 prompt;退款这种写操作该不该让模型自己决定;线上跑挂的 case 没法复现,改了 prompt 也不知道是变好还是变坏。
这几个问题在 2026 年有相对成熟的解法。MCP 负责工具和数据源的标准化接入,A2A 负责跨服务的 Agent 协作,权限、评测、成本、部署需要自己搭。
1. MCP、A2A 与多智能体架构选型
MCP 解决的是「Agent 怎么用工具」
Model Context Protocol 由 Anthropic 在 2024 年 11 月开源,基于 JSON-RPC 2.0,传输层支持 stdio 和 Streamable HTTP 两种。它解决的问题很窄:宿主应用和工具提供方之间,用什么格式交换能力描述和调用结果。
Server 侧暴露三类东西——tools(可调用的动作)、resources(可读取的数据)、prompts(预置的提示模板)。Client 侧把 sampling、roots 和 elicitation 暴露给 server。elicitation 是 2025-06-18 那版规范加进来的,允许工具在调用中途向用户追问缺失的参数,不用把整轮对话推倒重来。规范从 2025-03-26 那版开始支持基于 OAuth 2.1 的授权,Streamable HTTP 也是那一版引入的,替代了早先的 HTTP+SSE 方案。
协议本身不绑定模型,Claude、GPT、开源模型都能接。真正的收益在于解耦:工具代码只写一遍,换模型、换宿主、加新工具都不用动 Agent 主逻辑。内部有十个系统要接,每个写成独立 MCP Server,宿主侧只维护一份配置。
A2A 解决的是「Agent 怎么找 Agent」
Google 在 2025 年 4 月发布 Agent2Agent 协议,2025 年 6 月把项目捐给了 Linux 基金会。它同样跑在 JSON-RPC 2.0 上,用 SSE 推流。核心概念是 Agent Card——发布方在约定路径放一份描述,写清楚这个 Agent 能做什么、端点在哪、怎么鉴权。调用方读到 Card 之后发起任务,双方不需要共享代码,也不需要知道对方内部怎么实现。
判断要不要用 A2A,看一个条件:协作的双方是否在同一个代码仓库、同一个部署单元里。如果采购了第三方的客服 Agent,或者集团内两个事业部各自维护自己的 Agent,A2A 是合适的粘合层。如果只是 Planner 调两个 Worker,用框架内置的编排就够了。
选型对照
| 场景 | 用什么 | 原因 |
|---|---|---|
| Agent 读数据库、调内部 API | MCP Server | 工具描述和鉴权从 Agent 代码里剥离,加系统不用重发版 |
| 接入采购的第三方 Agent | A2A | 双方不共享代码和运行时,靠 Agent Card 协商能力 |
| 同进程内 Planner 调 Worker | 框架内置编排(LangGraph、OpenAI Agents SDK 的 handoff 等) | 拆成两个网络服务只增加延迟和故障点,没有换来任何隔离收益 |
| 跨事业部、跨云的 Agent 协作 | A2A | 组织边界清晰,各自演进互不影响 |
别把三者混着用。见过一个项目把内部两个 Worker 也写成 A2A 服务,结果每次任务多两跳网络调用,p95 延迟从 1.8 秒涨到 6 秒,排查了两周才发现是架构选错了。
2. 权限、沙箱与审计设计
给每个 Agent 一个独立身份
第一次上线 Agent 时,很多团队直接复用后端的 service account。出事之后翻审计日志,只能看到「某个服务调了退款接口」,看不出是哪个用户、哪次对话触发的。
正确做法是每个 Agent 部署实例拿一个独立身份,调用链上同时携带 Agent 身份和最终用户身份,两者都要落进日志。MCP Server 作为 Resource Server 必须校验 token 里的 audience,拒绝拿 A 服务的 token 去调 B 服务——这一步不做,水平越权在微服务架构里几乎必然发生。
权限切在工具粒度
- 读操作和写操作分成不同 scope,订单查询和发起退款是两条独立权限
- 参数在 Server 端重新校验,模型给的订单号、金额、租户号一律不信任
- 跨租户访问单独拦一道,按 tenant_id 过滤,不依赖模型自觉
- 金额阈值以上的写操作强制走人工审批,阈值写死在配置里,不写在 prompt 里
要执行代码就上真沙箱
Agent 如果需要在环境里跑测试、清洗数据、验证修复方案,容器加权限限制不算是沙箱,内核共享带来的逃逸风险是真实存在的。
- 用 gVisor 或 Firecracker 做隔离,别直接跑在宿主机容器里
- 网络出口做白名单,默认拒绝全部出站
- 文件系统只读挂载,只有 /tmp 可写,并且设大小上限
- CPU、内存、墙钟时间都设上限,超时强杀
- 一次任务一个实例,用完销毁,不复用
审计日志至少要留这些字段
| 字段 | 示例 | 为什么需要 |
|---|---|---|
| trace_id | 01JQ8X... | 把 LLM 调用、工具调用、最终回复串成一条链 |
| agent_id | cs-agent-prod-v3 | 定位是哪个版本的 Agent 做的决策 |
| principal | user:12345 | 最终责任人,日志里不能只有服务账号 |
| tool | crm.refund.create | 具体调了什么 |
| args_hash | sha256:9f3a... | 可追溯又不落敏感原文 |
| decision | human:678 approved | 审批人是谁,还是策略自动放行 |
| latency_ms / cost_usd | 2140 / 0.037 | 单次调用的性能和成本基线 |
工具返回的内容按不可信数据处理
prompt injection 最常见的入口不是用户输入,是工具返回的网页正文、邮件内容、工单描述。模型不是安全边界——所有工具调用在真正执行之前都要过一遍服务端策略检查,模型说「可以执行」不算数。客服场景里,用户上传的截图、转发的邮件都属于这一类。
3. 评测、可观测与灰度发布
评测集从线上日志来
手工编 50 条测试用例的问题在于,编出来的往往是团队已经想到的情况,漏掉的正好是那些让人半夜爬起来修的情况。
上线跑两周之后,从生产日志里采样 300 到 500 条真实请求,按意图、难度、是否涉及写操作分层。每条标注期望的工具组合和期望结果。之后每次改 prompt、换模型版本、增删工具,都跑一遍这个集子,看指标变化。
要盯的指标:
- 端到端任务成功率,不是单步准确率
- 工具选择正确率(选错工具比填错参数更致命)
- 参数正确率(工具选对了但参数填错,实际更常见)
- 平均对话轮数(轮数涨说明模型在原地打转)
- p95 延迟和单次任务平均成本
埋点用 OpenTelemetry,别自己发明字段
OpenTelemetry 的 GenAI semantic conventions 定义了 gen_ai.* 属性集。按这个埋点,后端从 Langfuse 换到 LangSmith 或者自建,业务代码不用动。Langfuse 支持自托管,数据不能出内网的团队可以部署在自己机房。
一次 Agent 运行的 span 结构大致是这样:
agent.run(顶层)llm.chat(第一次规划)tool.call(crm.order.get)llm.chat(根据结果决定下一步)tool.call(logistics.track)llm.chat(生成回复)
tool.call 都要带 trace_id 和 args_hash,这样才能从一条用户投诉反查到完整的执行链路。
LLM-as-judge 先校准再用
拿模型当评委打分能省人工,但必须先校准:挑 200 条已经人工标注的样本,让评委跑一遍,看它和人的一致率。一致率不达标就别拿它做发布门禁,只能当参考信号。校准数据要覆盖边界情况,比如「用户表达含糊时该转人工还是该追问」,这类判断评委很容易给出和人不一致的结论。
灰度分阶段推进
- 影子模式:新版本全量跑,输出不返回给用户,只对比和线上版本的差异
- 1% 流量,只放行读操作工具
- 10% 流量,放开写操作但保留人工审批
- 50% 流量,观察一周
- 全量
回滚要能一键切。prompt 版本、工具 schema 版本、模型版本都做成配置项,别硬编码在代码里。出事的时候改配置比发版快得多。
4. 推理成本与部署方案
成本大头往往不在对话本身
一次 Agent 请求的 token 消耗来自四块:系统提示加工具定义(每次请求都发)、对话历史、工具返回结果、模型输出。系统提示和工具定义是固定开销,工具越多这项越大——接了 20 个工具,光工具定义就可能占掉几千 token,用户还没说一句话。
三件最有效的事:
Prompt caching。 Anthropic 的缓存写入按基础输入价的 1.25 倍计费,命中按 0.1 倍;OpenAI 的 cached input 是基础价的 5 折。把系统提示和工具定义放在缓存断点之前,命中率能上去。具体价格以各家官方定价页为准,别拿旧数字做全年预算。
模型路由。 意图分类、参数抽取这类任务用便宜的小模型,只有真正需要多步推理的环节才调大模型。路由判断本身也可以是一个小模型。实践中能砍掉一半以上的支出,代价是要维护两套 prompt。
截断工具返回。 一个 CRM 查询返回 8000 字符的 JSON,模型真正需要的就 5 个字段。在 Server 端做字段投影,别把原始响应整个塞进上下文。这一步不做,上下文窗口会很快被无关数据填满,成本和延迟一起涨。
离线批处理场景(夜间跑报表、批量工单分类)用 Batch API,OpenAI 和 Anthropic 都是 50% 折扣,代价是延迟从秒级变成小时级。
部署形态怎么选
| 形态 | 适合 | 代价 |
|---|---|---|
| 云 API | 数据可以出网,要快速上线验证 | 成本随调用量线性增长,模型版本受供应商节奏控制 |
| 私有化 vLLM / SGLang | 数据不出内网 | 自己管 GPU,显存要按参数量 × 2 字节(fp16)估权重,再加 KV cache 和激活开销 |
| 混合 | 入口用云模型做规划和意图理解,敏感数据走内网模型 | 跨网调用要做脱敏和审计,链路变长 |
5. 三个场景的具体做法
客服:读操作放开,写操作卡住
场景:用户在 App 里问「我上周的订单怎么还没发货」,Agent 要查订单、查物流,然后决定是直接回答还是转人工。
工具按风险拆开:
order.get(读,自动放行)logistics.track(读,自动放行)ticket.escalate(写,自动放行,但强制记审计)refund.create(写,必须人工审批)
研发:沙箱里跑,别碰生产
场景:CI 挂了,Agent 去读日志、定位相关提交、给出修复建议,必要时在沙箱里跑一遍测试验证。
repo.read_file/repo.search:只读,指向代码仓库快照,不是生产代码ci.get_logs:只读sandbox.run:在 gVisor 隔离环境执行,无网络出口,5 分钟超时
金融:所有输出可追溯
场景:对账异常排查,从一堆流水里找出不平的那几笔并给出解释。
这类场景里,留痕比准确率更硬。每条结论要能回到原始凭证,做法是让所有工具返回都带 source_id,最终输出强制引用这些 ID,没引用到来源的结论一律不展示给业务人员。
禁止事项写死在服务端策略里:不能自动下单,不能修改客户账户信息,不能把明细数据发给外部模型。三条都要有对应的代码检查点,不能只靠文档约定。
这周能做的三件事
如果手上已经有一个跑在测试环境的 Agent:
- 把工具定义从业务代码里抽出来,先挑两个只读接口写成 MCP Server,验证一下宿主侧能不能正常加载。
- 给每次工具调用加上 trace_id 和 args_hash 日志,跑一天,攒出第一批评测样本。
- 检查 Agent 现在用的是不是和别的服务共用的 API key,如果是,单独申请一个身份。