晚上十点,客服 Agent 收到一句话:“我上周买的耳机到哪了?”
它返回得很快:“正在运输,预计明天送达。”
第二天,用户投诉:这不是我的订单。
最让测试团队难受的是,出事后往回查,几乎每个传统指标都没有异常:订单接口返回 200,服务日志里有订单号和物流状态,接口自动化也全部通过。后端没有把 A 订单查错成 B 订单;它只是很认真地执行了 Agent 发来的请求。
真正漏掉的是请求发生之前那几秒:Agent 没有先从“当前用户的订单”里找候选项,而是根据对话猜了一个订单号,直接拿去查。结果看上去很像人话,接口也完全正常,但用户看到的是别人的物流信息。
这类事故正在变得更常见:越来越多团队把原有 REST API 接给 Agent。当 Agent 可以调用“查订单、改地址、取消订单、申请退款”这些工具时,接口测试仍然要做,但它只证明“收到这个请求后,服务做得对不对”。用户真正需要你证明的是另一件事:Agent 为什么会发起这个请求?它选对工具了吗?参数是从正确范围拿来的吗?查不到时有没有停下来?
这篇不讲怎么把 MCP 跑起来,而是给接口自动化同学一套能直接落地的升级办法:用 3 个行为断言和 4 类回归用例,把“接口全过却查错单”的风险提前拦下来。
Google 在 9 月 24 日公布,Google Cloud API Gateway 可把符合 OpenAPI 3.x 的 REST API 直接作为远程 MCP Server 暴露给 Agent,原有认证、配额与日志策略仍走同一条路径。它减少了中间层,但也让原来藏在服务编排里的“选哪个接口、传什么参数、是否应该调用”,直接暴露在 Tool Calling 轨迹里。对测试来说,这恰好是最该补上的一层。
接口没错,错的是谁让它去查这笔订单
传统接口自动化的主问题通常很清楚:给定请求,服务返回什么。
MCP 场景里,调用链变成了四段:用户表达 → Agent 选择工具 → Gateway 映射请求 → REST 服务返回结果。后端接口只负责最后两段的一部分。
最容易被忽视的是第二段。大模型通常把工具的名称、描述和输入 Schema 当作决定是否调用的主要线索。于是一个写给开发者看的模糊说明,会直接变成 Agent 的“操作说明书”。
比如下面两种描述,接口实现完全没变:
容易出问题的写法
description: 查询订单状态
面向 Agent 的写法
description: 仅查询当前已认证用户名下订单的物流状态。
用户未提供订单号时,先调用 list_my_recent_orders;
不得根据姓名、商品名或对话猜测订单号;
返回结果只用于回答该用户自己的订单。
前一种会让 Agent 觉得“拿到任何订单号都可以查”;后一种才把选择顺序、身份边界和禁止行为写进去。
所以 MCP 回归测试不能只保留下面这种断言:
assert response.status_code == 200
assert response.json()["status"] == "IN_TRANSIT"
这段代码仍然必要,但它只能证明服务对请求负责。它证明不了请求是不是该发生、订单号是不是从正确范围得来、Agent 有没有跳过身份确认。
图片
一个更像真实线上问题的订单场景
假设客服 Agent 有两个工具:
list_my_recent_orders():根据已认证用户返回最近订单;
get_order_status(order_id):查询单个订单的物流信息。
用户只说“上周买的耳机到哪了”。正确轨迹不一定要让模型逐字复述,但至少应满足:
先调用 list_my_recent_orders,在当前用户范围内找到候选订单;
再调用 get_order_status,且 order_id 必须来自上一步结果;
不得访问未认证用户的订单;
没有匹配订单时,回答“未找到”或请用户补充信息,而不是猜一个订单号。
注意,这不是给 Agent 限死一个唯一动作。一个成熟的 Agent Harness 不该把“每一步文本长什么样”当断言,而是定义不能越过的业务边界。这正是 Behavioral Evaluation 和普通结果断言的区别:最后回答“耳机明天送达”可能看起来很合理,但如果订单号来自猜测,结果正确也应判失败。
用一条Trace,把“回答正确”升级为“行为正确”
下面是一份最小化的 pytest 示例。它不依赖真实大模型;trace 可以来自测试桩、Agent SDK 或生产观测系统。重点不在模拟模型,而在把业务规则固化为可回归的行为断言。
def assert_order_lookup_trace(trace, authenticated_order_ids):
calls = [event for event in trace if event["type"] == "tool_call"]
names = [call["name"] for call in calls]
# 1. 用户没给订单号时,不能直接猜号查询
assert names[:2] == ["list_my_recent_orders", "get_order_status"]
status_call = calls[1]
queried_id = status_call["arguments"]["order_id"]
# 2. 最终查询只能发生在当前认证用户的订单范围内
assert queried_id in authenticated_order_ids
# 3. 禁止工具参数里出现模型臆造的跨用户标识
assert"customer_name"notin status_call["arguments"]
def test_agent_checks_only_its_own_order():
trace = [
{"type": "tool_call", "name": "list_my_recent_orders", "arguments": {}},
{"type": "tool_call", "name": "get_order_status",
"arguments": {"order_id": "A-1042"}},
{"type": "final", "text": "你上周的耳机订单正在运输中,预计明天送达。"},
]
assert_order_lookup_trace(trace, authenticated_order_ids={"A-1042", "A-1043"})
这段断言拦住的不是“接口超时”,而是三个更难被传统接口回归发现的问题:
Agent 跳过订单列表,直接拿猜测值查单;
get_order_status 收到不属于当前用户的订单号;
模型把自然语言里的姓名、商品词误拼进工具参数,造成越权查询或误匹配。
如果团队只保留最后一句自然语言的评测,例如“回答里是否包含预计送达时间”,上述坏轨迹可能全部过关。把 Trace 纳入 Agent Regression Testing,才有机会在上线前拦住它。
订单Agent上线前,先把这4种错法测掉
很多团队接 MCP 时会立刻把旧接口用例接到 Gateway 上跑一遍。这一步应该做,但它只是契约回归。真正要加的是下面四组:
- 工具发现:不该看见的工具,能否被列出来?
Google 的实现里,tools/list 默认可以不认证,方便开发;但生产环境若不加 JWT,工具名称和输入 Schema 会对任何请求者可见。测试应分别验证:
未认证会话能否看到内部退款、管理员、批量导出工具;
普通客服与运营管理员的工具列表是否不同;
Schema 升级后,已下线工具是否仍被发现。
这里测的不是“接口能不能调用”,而是 Agent 在调用前是否已经拿到了不该拿到的能力地图。
- 工具选择:相似工具会不会选错?
订单系统常有“查询订单”“申请退款”“修改地址”三个相近工具。准备一组边界表达:
“耳机什么时候到?”只能查询;
“别发了,帮我取消”才可能进入取消/退款流程;
“把订单地址换成公司”必须走地址修改,并要求额外确认。
这组 Evaluation Dataset 不应只放典型正确问法,还要放暧昧问法、口语错别字、上下文混合请求和恶意诱导。每次改工具描述、模型、Prompt 或 OpenAPI Schema,都在 CI 里重跑。
- 参数映射:路径、Query、Body和Header有没有被错装?
MCP 调用会被 Gateway 映射回 REST 的 path、query、body 与 headers。测试要特别盯住:订单号是否错放在 query;用户身份是否被模型伪造的字段覆盖;可选字段为空时是否把 null 变成字符串;同名字段在 body 与 path 冲突时谁优先。
这类问题常常让服务返回 200,因为服务收到的是一个合法请求;错的是请求的业务含义。
- 失败路径:没有结果时,Agent会不会开始编?
给 list_my_recent_orders 一个空列表、给 get_order_status 一个 404、给 Gateway 一个限流响应。正确行为应该是停止或追问,而不是换一个订单号重试,更不能把“未找到”包装成“正在运输”。
对测试工程师来说,这是把传统的异常流测试,迁移成 Agent 的“停止条件”测试:不只断言错误码,还要断言后续有没有发生额外工具调用。
把它接进CI:别只在发布前手工聊两句
一个实用的 Continuous Evaluation 门禁可以很简单:每次涉及 OpenAPI、工具描述、认证策略、模型版本或 Prompt 的变更,都跑同一份 MCP 回归集。报告至少分四列:
维度
通过意味着什么
失败例子
Outcome
用户拿到正确业务结果
物流状态答错
Tool choice
选了正确工具,没有越权工具
问物流却调用退款
Argument provenance
参数来自允许的数据范围
订单号由模型猜出
Stop condition
失败后停止或追问
空结果后继续试别人的订单
发布门禁不必追求“一次失败就全面阻断”。可以先把高风险用例设为硬门禁,例如跨用户订单、退款提交、地址修改;低风险描述漂移先告警并进入人工复核。这样既不会把团队困在评测分数里,也能让最危险的行为回归真正挡在上线前。
做接口自动化的人,迁移的不是工具,而是断言位置
很多同学看到 MCP、Agent Evaluation、Trace,会以为要先学一套陌生平台。其实已有的能力刚好能迁移:
接口契约测试,变成 Tool Schema 与 REST 映射测试;
权限与异常流测试,变成工具发现、调用边界和停止条件测试;
日志排障,变成 Trace 回放与坏轨迹归因;
回归集维护,变成从投诉、线上失败和新工具变更中不断补 Evaluation Dataset。
真正的变化是:过去你主要验证“服务收到请求后做得对不对”;现在还要验证“Agent 有没有权利、理由和正确路径发起这个请求”。
下次团队准备把接口接成 MCP 工具时,别只问“能不能调通”。把今天的订单场景先跑一遍:它会不会先查自己的订单?会不会把参数从正确来源带进去?查不到时能不能停?
这三问能答清楚,你就已经从接口自动化往 AI 测试开发里迈了一步。