章节导航 第 21 章 / 共 21 章
Case Walkthrough:water_emergency 端到端全记录
用生产环境的真实 Task(water_emergency)完整走一遍 Nexus 框架:从 YAML 配置、Mock 数据注入、wide-agent 推理轨迹,到 rubric 验证与 RL 训练数据导出
为什么是 water_emergency
water_emergency 是一个生产级 Task,已在内部评测平台运行,gemini-3.1-pro 得分 9/10。
它复杂在三点:
- 条件分支决策 — 用水量正常→缴费,异常→报障,Agent 必须先分析再行动
- 跨 APP 信息依赖 — 查天气→决定出行方式→导航,三段信息串联
- Services 硬隔离 — Mock 服务运行在独立容器,Agent 只能通过 MCP 调用,无法作弊读源码
本章用这个 Task 完整走一遍:配置加载 → Agent 推理 → 工具执行 → 验证评分 → RL 数据导出。
系统架构总览
┌─────────────────────────────────────────────────────────────────────────────┐
│ Nexus 评测框架(主控) │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────────┐ │
│ │ case_loader │ │ agent_runner │ │ verifier │ │
│ │ 加载 YAML 配置 │ │ 执行 ReAct 循环 │ │ 逐条检查 rubric │ │
│ └──────────────────┘ └──────────────────┘ └──────────────────────┘ │
│ │ │ │ │
│ └─────────────────────┴────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│ start_task() │ HTTP/MCP │ verify()
▼ ▼ ▼
┌─────────────────┐ ┌──────────────────────────────────────────────────────┐
│ Main 容器 │ │ Service 容器(service_lifegym_mocks,隔离) │
│ (Agent 运行) │ │ │
│ │ │ ┌─────────────────┐ ┌──────────────────────────┐ │
│ ┌───────────┐ │ │ │ wechat_host │ │ life_payment │ │
│ │wide_agent │ │ MCP│ │ :30001 │ │ :30002 │ │
│ │ │──┼────┼─→│ GET /mcp ──────┼──│ GET /mcp ───────────────┼─┤
│ │ ReAct loop │ │ │ │ (streamable- │ │ (streamable- │ │
│ │ 1-50 轮 │ │ │ │ http) │ │ http) │ │
│ └───────────┘ │ │ └─────────────────┘ └──────────────────────────┘ │
│ │ │ ┌──────────────────────────┐ │
│ 工具列表: │ │ │ amap │ │
│ - shell_exec │ │ │ :30010 │ │
│ - str_replace │ │ │ GET /mcp ───────────────┼───────────────────────┤
│ - (MCP 工具集) │ │ │ (streamable-http) │ │
│ │ │ │ │ │
│ 看不到 Mock │ │ Mock 数据来源: │
│ 源码或数据 │ │ DATA_PATCH_B64 (env) → 解压 → 加载到内存 │
└─────────────────┘ └──────────────────────────────────────────────────────┘
关键设计:Main 容器和 Service 容器网络隔离,Agent 只能通过 MCP 协议调用工具,无法 cat /data/data_patch.json 作弊。
第零节:为什么用 wide_agent
在看 case.yaml 之前,先回答一个问题:Nexus 仓库里有多个 Agent(search_agent、office_agent、code_agent……),为什么 water_emergency 用的是 wide_agent?
因为 water_emergency 的需求叠加起来,恰好撞上了 wide_agent 的三个设计目标:跨领域工具、长上下文鲁棒性、运行时动态工具注入。
设计对比:wide_agent vs 其他常见 Agent
| 维度 | 通用 ToolCallAgent(基类) | 编码专用(claude-code 风格) | search_agent | wide_agent |
|---|---|---|---|---|
| 定位 | 工具调用骨架 | 代码仓库内的编码 Agent | 检索 + 浏览 | 跨工作 + 生活场景 |
| 上下文管理 | 满了就截断 / 报错 | 自动压缩 + 保留边界 | 满了截断 | 1:1 移植 claude-code 压缩策略 |
| 工具集组织 | 固定静态列表 | 内置 + MCP 动态注入 | 静态 toolset 预设 | 静态预设 + MCP + extra_tools 三合一 |
| System Prompt | 单一模板 | 项目上下文动态拼接 | 静态 + 少量 context | 静态头 + 动态边界 + CLAUDE.md 风格上下文 |
| 一句话概括 | 底层框架 | 写代码 | 查资料 | 工作+生活场景的通用 Agent |
核心区别:wide_agent 不是新架构,而是”ToolCallAgent 骨架 + claude-code 上下文策略 + MCP 动态工具”的缝合体,专门为 Nexus 的多 APP 评测场景设计。
下面三个特性对应到 water_emergency 的需求,都有具体代码支撑。
特性 1:claude-code 对齐的上下文压缩
对应需求:water_emergency 任务需要 7-10 轮 MCP 调用,每轮 tool_result 可能返回几 KB 的 JSON(账单列表、POI 列表、路线数据)。如果没压缩,Agent 会在 4-5 轮后撞 100k token 阈值。
代码位置:nexus/extensions/agents/wide_agent/agent.py
# 100k token 阈值,对齐 claw-code 的 DEFAULT_AUTO_COMPACTION_INPUT_TOKENS_THRESHOLD
DEFAULT_AUTO_COMPACT_TOKENS = 100_000
# 保留最近 4 条消息,对齐 claw-code 的 CompactionConfig::preserve_recent_messages = 4
DEFAULT_PRESERVE_RECENT_MESSAGES = 4
class WideAgent(ToolCallAgent):
async def ask_llm(self, **kwargs: Any) -> Message:
if self._should_compact(): # 超过 100k 就压缩
await self._compact_context(**kwargs)
# 正常 LLM 调用
...
关键细节:tool_use / tool_result 边界保护——这是一般 Agent 直接截断最容易出错的地方。压缩时如果保留窗口的第一条是 role=tool,它的配对 assistant.tool_calls 却被砍掉了,调用 LLM 会直接 400 报错。wide_agent 的解决方案是向前走一步:
def _find_safe_keep_from(messages, preserve_recent, protected_prefix_len) -> int:
keep_from = max(protected_prefix_len, len(messages) - preserve_recent)
while keep_from > protected_prefix_len:
first_preserved = messages[keep_from]
if not _message_starts_with_tool_result(first_preserved):
break # 保留窗口以正常消息开头,安全
preceding = messages[keep_from - 1]
if _message_has_tool_use(preceding):
keep_from -= 1 # 把配对的 assistant.tool_calls 一起保留
break
keep_from -= 1 # 孤儿 tool_result,继续往前走
return keep_from
这段逻辑是从 claude-code Rust 实现 rust/crates/runtime/src/compact.rs 1:1 移植来的,注释里写得很清楚:
# 注释原文(agent.py 顶部 docstring):
# Context strategy mirrors claw-code's ``compact_session``
# (``rust/crates/runtime/src/compact.rs``) exactly:
# 1. Messages are append-only until a threshold is crossed
# 2. On trigger, the oldest span is replaced with an LLM-written summary
# and a fixed number of most-recent messages are preserved verbatim
# 3. The compaction boundary is walked back, if necessary, so we never
# split a tool_use / tool_result pair.
特性 2:三层工具集组合
对应需求:water_emergency 既要用 run_shell / execute_python(基础工具),又要用 lp_query_usage_curve / maps_weather(Task 专属 MCP 工具)。wide_agent 必须同时 hold 两类。
代码位置:nexus/extensions/agents/wide_agent/tools.py
WIDE_TOOLSET: dict[str, list[str]] = {
'v0': ['base/finish'], # 最小集
'v1': ['base/finish', 'execute_python', 'run_shell'], # 常用
'v2': ['base/finish', 'execute_python', 'run_shell',
'base/write_todos', 'visit'], # 含 web + todo
}
def resolve_tools_and_names(
*, toolset_version, include_finish, extra_tool_names=None,
) -> tuple[list, list[str]]:
tool_funcs = _resolve_static_tools(toolset_version, ...) # 1. 静态基础工具
static_names = [t.nexus_meta['name'] for t in tool_funcs]
mcp_names = discover_mcp_tool_names() # 2. MCP 运行时注入
extras = list(extra_tool_names or []) # 3. Task 额外指定
# 去重合并,保持顺序
seen, allowed = set(), []
for name in (*static_names, *mcp_names, *extras):
if name not in seen:
seen.add(name)
allowed.append(name)
return tool_funcs, allowed
对 water_emergency 来说,运行时 allowed 会变成:
静态: run_shell, execute_python, base/finish
MCP: lp_query_bound_accounts, lp_query_usage_curve, lp_report_outage,
lp_create_order, maps_weather, maps_text_search, maps_route_planning,
wechat_get_current_chat, ... (共 40+ 个 MCP 工具)
MCP 工具从哪里来?从 case.yaml 的 extra_mcp_servers 声明进来:
# agent.py, run_wide_agent 入口
if agent_input.mcp_servers:
await ctx.load_mcp_tools(extra_servers=agent_input.mcp_servers)
# load_mcp_tools 会把每个 server 的 /list_tools 响应注册到 ctx._mcp_manager
Agent 侧完全透明——resolve_tools_and_names() 只管去 ctx._mcp_manager.tools.keys() 拿名字。
特性 3:分层 System Prompt(静态头 + 动态边界)
对应需求:Agent 需要同时知道”我是通用生活助手”(静态)和”当前工作目录是 /workspace、已有哪些 skills、今天是什么日期”(动态)。静态部分要能命中 provider 的 prompt cache(省钱省延迟),动态部分每次 run 重新拼。
代码位置:nexus/extensions/agents/wide_agent/prompt.py
SYSTEM_PROMPT_DYNAMIC_BOUNDARY = '__WIDE_AGENT_DYNAMIC_BOUNDARY__'
WIDE_AGENT_STATIC_HEADER = """\
You are a broad work-and-life agent operating inside a Linux sandbox.
You have access to tools for executing shell commands, reading files, ...
## Available Tools in the Sandbox
**For spreadsheets (.xlsx, .xlsm, .csv):**
- `python3` with `openpyxl` — read/write Excel files, set formulas, format cells
- `libreoffice --headless` — recalculate formulas, convert between formats
...
"""
def build_system_prompt(project_context, *, task_system_prompt=None, ...) -> str:
"""
Layout(claw-code 风格):
<静态头>
__WIDE_AGENT_DYNAMIC_BOUNDARY__
<动态段:cwd / platform / date / skills / workspace tree>
<可选的 task-specific 尾部>
"""
sections = [default_header, SYSTEM_PROMPT_DYNAMIC_BOUNDARY]
if dynamic:
sections.append(dynamic)
if task_prompt and mode == 'append':
sections.append('# Task-specific instructions\n' + task_prompt)
return '\n\n'.join(sections)
动态段怎么填?discover_project_context() 会在 Agent 启动时向 main 容器发探针:
# prompt.py, discover_project_context()
result = await runtime.run_command('pwd && uname -sr', timeout=5) # 探 cwd + 平台
# 扫描 /workspace/skills 下所有 SKILL.md,读 YAML frontmatter 的 description
probe_cmd = (
'for d in $(find /workspace/skills -maxdepth 2 -name SKILL.md -printf "%h\\n" | sort); do '
f'printf "{sentinel} %s\\n" "$d"; '
'head -n 40 "$d/SKILL.md"; '
'done'
)
# 抓 /workspace 目录树
result = await runtime.run_command('ls -la /workspace 2>/dev/null | head -50', timeout=5)
对 water_emergency 来说,动态段会包含 /workspace/skills/xlsx、/workspace/skills/pdf 等(但这个任务用不上),以及当前日期 2024-07-01——这个日期直接决定了 Agent 查天气时该传哪一天。
小结:为什么是 wide_agent 而不是别的
| 任务特征 | 所需 Agent 能力 | wide_agent 的答案 |
|---|---|---|
| 跨 APP(水费 + 天气 + 地图) | 多来源工具动态拼 | resolve_tools_and_names 三层合并 |
| 7-10 轮调用,tool_result 较大 | 上下文不爆 | 100k 阈值压缩 + 边界保护 |
| 条件分支决策(缴费 vs 报障) | 历史消息连贯不丢 | 压缩时保留 first_user + recent 4 |
| 评测需复用,prompt 要缓存 | Prompt 结构稳定 | 静态头 + 边界分隔 |
换句话说,water_emergency 是在 wide_agent 的能力圈内设计的 Task。换成 search_agent 会缺 MCP 动态注入,换成基础 ToolCallAgent 会在第 5 轮后撞 token 上限。
第一节:Task 定义(case.yaml 逐行解读)
task_id: water_emergency
user_prompt: |
我家水费上个月忘交了,刚才看了下水表读数感觉不太对,比平时高了快一倍,
你先帮我查一下近半年的用水量趋势,看看是不是有异常,
如果确实有问题就帮我报个障让人来检查水表。
没问题的话就直接把欠费交了,记得开电子发票抬头用我们公司的。
另外我下午两点要去自来水公司营业厅现场问一下,
帮我导航一下最近的营业厅在哪,
顺便看看今天那边天气怎么样,下不下雨我好决定骑车还是打车过去。
agent_timeout: 600
max_iterations: 50
user_prompt 是一个真实用户口述——中英文混杂、需求多条、隐含条件分支。Agent 必须:
| 步骤 | 条件 | 操作 |
|---|---|---|
| 1 | 必做 | 查水费户号 |
| 2 | 必做 | 查近半年用水量 |
| 3 | 用水量异常 | 报障(lp_report_outage) |
| 3’ | 用水量正常 | 缴费(lp_create_order)+ 开电子发票 |
| 4 | 必做 | 查天气 |
| 5 | 天气下雨 | 建议打车 |
| 5’ | 天气晴 | 建议骑车/步行 |
| 6 | 必做 | 搜索附近营业厅 + 导航 |
Services 硬隔离配置
services:
- name: lifegym-mocks
runtime_name: service_lifegym_mocks
image: "mirrors.tencent.com/aidata-rl-env/mock-server:v0.0.10-services"
exposed_ports: [30001, 30002, 30010]
startup:
script: assets/mock_startup.sh
env:
MOCKS: wechat_host,life_payment,amap
MOCK_TRANSPORT: streamable-http
三个 Mock 服务运行在独立容器:
| 端口 | 服务 | 功能 |
|---|---|---|
| 30001 | wechat_host | 微信对话状态、位置信息 |
| 30002 | life_payment | 水费账户、账单、报障、缴费 |
| 30010 | amap | 天气、POI 搜索、路线规划 |
Agent 通过 MCP(streamable-http) 调用这些服务,和调用本地工具无差别。
MCP 连接配置
extra_mcp_servers:
- name: wechat_host
client:
transport: aiohttp_streamable_http
url: __MOCK_WECHAT_URL__ # 框架运行时替换
- name: life_payment
client:
transport: aiohttp_streamable_http
url: __MOCK_LP_URL__
- name: amap
client:
transport: aiohttp_streamable_http
url: __MOCK_AMAP_URL__
__MOCK_* 是占位符,框架在 services 就绪后自动替换为 http://service_lifegym_mocks:30001/mcp 这样的内网地址。
第二节:Mock 数据设计(data_patch.json 关键表)
Mock 服务启动时会加载 data_patch.json,这是 Agent 能观察到的全部世界状态。
核心设计:6 月用水量异常
"life_payment_usage_history": [
{"period": "2024-01", "usage": 8.2},
{"period": "2024-02", "usage": 9.1},
{"period": "2024-03", "usage": 8.7},
{"period": "2024-04", "usage": 9.5},
{"period": "2024-05", "usage": 8.9},
{"period": "2024-06", "usage": 17.8, "anomaly_flag": true}
]
均值 8.88 吨,6 月 17.8 吨 > 1.5×阈值(13.32 吨)→ 异常。
正确路径:报障,不能直接缴费。
天气引导出行决策
"amap_weather": [
{
"date": "2024-07-01",
"dayweather": "多云转阵雨",
"precipitation_probability": 75
}
]
降水概率 75% → 应建议打车(骑车淋雨不合理)。
营业厅 POI 数据
"amap_pois": [
{"name": "杭州市水务集团西湖营业厅", "distance": 2300, "business_hours": "09:00-17:00"},
{"name": "杭州市水务集团拱墅营业厅", "distance": 5800},
{"name": "杭州市水务集团滨江营业厅", "distance": 9200}
]
最近的是西湖营业厅(2.3km)→ 导航目的地。
第三节:Agent 执行轨迹(典型推理链推断)
说明:下面不是
gemini-3.1-pro的原始 trajectory.json,而是根据case.yaml、data_patch.json、rubric.yaml推断的一条典型合规轨迹——也就是「能拿到 9/10 分的 Agent 应当如何推理」。目的是展示 wide_agent + MCP 工具在这个 Task 上的工作方式,具体的 tool_call 参数、Final Answer 措辞均为推断重构,可能与原始 trace 存在差异。
需要说明的是,本章这种「手工写 data_patch.json + 手写 rubric.yaml」的做法,正是第五节末尾 EnvScaler 试图自动化的对象——本章这条推断轨迹在某种意义上演示了「为什么需要自动化」:连一条 9/10 的合规轨迹都要靠人工重构,规模化到几百个 Task 时手工方式必然成为瓶颈。
Iteration 1:理解任务 + 首次工具调用
System Prompt(wide_agent 静态头):
You are a broad work-and-life agent operating inside a Linux sandbox.
You have access to tools for executing shell commands, reading files,
and any task-specific MCP tools injected at runtime.
User Message:
我家水费上个月忘交了,刚才看了下水表读数感觉不太对...
(完整 user_prompt)
Assistant Response(tool_calls):
{
"tool_calls": [
{
"id": "call_001",
"function": {
"name": "lp_query_bound_accounts",
"arguments": "{\"user_id\": \"oUser_ZhangSan_001\"}"
}
}
]
}
Agent 第一步:查户号。它从 MCP 工具列表里找到了 lp_query_bound_accounts。
Iteration 2:拿到户号,查用量趋势
Tool Result(lp_query_bound_accounts):
{
"accounts": [
{
"account_id": "330106-2024-08752",
"account_type": "water",
"address": "杭州市西湖区翠苑街道翠苑一区3幢2单元502室",
"supplier": "杭州市水务集团有限公司"
}
]
}
Assistant Response:
{
"tool_calls": [
{
"function": {
"name": "lp_query_usage_curve",
"arguments": "{\"account_id\": \"330106-2024-08752\", \"months\": 6}"
}
},
{
"function": {
"name": "lp_query_unpaid_bills",
"arguments": "{\"account_id\": \"330106-2024-08752\"}"
}
}
]
}
并行查了用量曲线 + 欠费账单(宽 Agent 允许并行 tool call)。
Iteration 3:分析数据,发现异常
Tool Results:
// lp_query_usage_curve 返回
{
"usage_history": [
{"period": "2024-01", "usage": 8.2},
{"period": "2024-02", "usage": 9.1},
{"period": "2024-03", "usage": 8.7},
{"period": "2024-04", "usage": 9.5},
{"period": "2024-05", "usage": 8.9},
{"period": "2024-06", "usage": 17.8, "anomaly_flag": true}
],
"statistics": {"mean": 8.88, "threshold_1_5x": 13.32}
}
// lp_query_unpaid_bills 返回
{
"bills": [
{"bill_id": "BILL-W-202406-08752", "amount": 89.5, "status": "unpaid", "overdue_days": 32}
]
}
Assistant Response:
{
"tool_calls": [
{
"function": {
"name": "lp_report_outage",
"arguments": "{\"account_id\": \"330106-2024-08752\", \"description\": \"6月用水量17.8吨,远超均值8.88吨,疑似水表故障或管道漏水\", \"contact_phone\": \"13800001111\"}"
}
}
]
}
关键决策点:Agent 正确识别了异常(17.8 > 13.32 阈值),走了报障路径。
如果 Agent 这里直接调用 lp_create_order(缴费),rubric 的 correct_branch_anomaly 检查会失败。
Agent 与 MCP 服务交互时序图
下面是 Iteration 2-4 的完整交互链路(lp_query_usage_curve → lp_report_outage 的调用过程):
用户(Nexus 框架注入 user_prompt)
│
▼
┌──────────────────────────────────────────────────────────────────────┐
│ wide_agent (main 容器) │
│ │
│ Iteration 2: │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 思考:需要查用量的历史曲线,同时查未付账单 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ├────→ MCP call: lp_query_usage_curve ─────────────────→ │
│ │ (account_id: "330106-2024-08752") │
│ │ │ │
│ │ ▼ │
│ │ ┌─────────────────────────────┐ │
│ │ │ life_payment :30002 (/mcp) │ │
│ │ │ 1. 解析 tool_call JSON │ │
│ │ │ 2. 查内存中的 usage_history│ │
│ │ │ (来自 DATA_PATCH_B64) │ │
│ │ │ 3. 返回 JSON 结果 │ │
│ │ └─────────────────────────────┘ │
│ │ │ {"usage_history":[...]} │
│ │ ▼ │
│ ├────→ MCP call: lp_query_unpaid_bills ──────────────→ │
│ │ │ │
│ │ ▼ │
│ │ ┌─────────────────────────────┐ │
│ │ │ life_payment :30002 (/mcp) │ │
│ │ │ 查 bills 表 → 返回未付账单 │ │
│ │ └─────────────────────────────┘ │
│ │ │ │
│ Iteration 3: │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 思考:17.8 > 13.32,异常!应该报障,不缴费 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ├────→ MCP call: lp_report_outage ────────────────────→ │
│ │ (description: "6月用量17.8吨,超均值") │
│ │ │ │
│ │ ▼ │
│ │ ┌─────────────────────────────┐ │
│ │ │ life_payment :30002 (/mcp) │ │
│ │ │ 1. 插入 complaints 表 │ │
│ │ │ 2. 生成 complaint_id │ │
│ │ │ 3. 返回工单信息 │ │
│ │ └─────────────────────────────┘ │
│ │ │ {"complaint_id":"WO-..."} │
│ │ ▼ │
│ Iteration 4: │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 思考:接下来查天气,决定出行方式 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ └────→ MCP call: maps_weather ──────────────────────→ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ amap :30010 (/mcp) │ │
│ │ 查 weather 表 → 返回天气信息│ │
│ └─────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
关键:Agent 完全通过 MCP 协议与 Mock 服务通信,不知道底层是 Mock 还是真实 API。
Iteration 4:报障成功,查天气
Tool Result(lp_report_outage):
{
"complaint_id": "WO-2024070100123",
"status": "pending",
"expected_resolution": "提交后48小时内安排水表检测人员上门",
"contact_phone": "0571-87654321"
}
Assistant Response:
{
"tool_calls": [
{
"function": {
"name": "maps_weather",
"arguments": "{\"city\": \"杭州\", \"district\": \"西湖区\"}"
}
}
]
}
Iteration 5:根据天气决策出行方式
Tool Result(maps_weather):
{
"weather": "多云转阵雨",
"precipitation_probability": 75,
"temperature": {"day": 33, "night": 28}
}
Assistant Response:
{
"tool_calls": [
{
"function": {
"name": "maps_text_search",
"arguments": "{\"keywords\": \"自来水营业厅\", \"city\": \"杭州\", \"district\": \"西湖区\"}"
}
}
]
}
Iteration 6:搜索营业厅 + 路线规划
Tool Result(maps_text_search):
{
"pois": [
{"name": "杭州市水务集团西湖营业厅", "address": "教工路58号", "distance": 2300},
{"name": "杭州市水务集团拱墅营业厅", "address": "湖墅南路256号", "distance": 5800}
]
}
Assistant Response:
{
"tool_calls": [
{
"function": {
"name": "maps_route_planning",
"arguments": "{\"origin\": \"30.2741,120.1289\", \"destination\": \"30.2785,120.1192\", \"mode\": \"driving\"}"
}
}
]
}
天气有雨 → 选了 driving(打车)。正确。
Iteration 7:生成最终回复
Tool Result(maps_route_planning):
{
"routes": [
{"mode": "driving", "duration": 12, "distance": 2300, "taxi_cost": 14},
{"mode": "walking", "duration": 28, "distance": 2100},
{"mode": "transit", "duration": 22}
]
}
Assistant Final Answer:
我已经帮你处理完了,情况如下:
1. **用水量异常** — 6月用了17.8吨,比前几个月均值(8.88吨)高出一倍,
已超过异常阈值13.32吨。已帮你提交报障工单(工单号:WO-2024070100123),
水务集团会在48小时内联系你安排上门检查水表。
2. **今天天气** — 西湖区今天多云转阵雨,降水概率75%,
**建议打车前往**(车程约12分钟,车费约14元)。
营业厅地址:杭州市水务集团西湖营业厅(教工路58号)。
3. **缴费问题** — 当前工单是报障,缴费会等检查完成后再处理,
你也可以等工作人员联系时确认。
工单联系电话:0571-87654321
第四节:验证评分(rubric.yaml 逐条走查)
Task 执行完后,run_water_emergency 函数调用 verifier,对轨迹逐条检查。
确定性检查(regex,7 条)
| ID | 正则 | 轨迹里匹配到了吗 |
|---|---|---|
must_use_lp_query_usage | lp_query_usage_curve | ✅ Iteration 2 |
must_use_lp_query_bills | lp_query_unpaid_bills | ✅ Iteration 2 |
must_use_maps_weather | maps_weather | ✅ Iteration 4 |
must_use_maps_search | maps_text_search | ✅ Iteration 5 |
query_before_action | lp_query_bound_accounts.*lp_query_usage_curve.*lp_report_outage(dotall) | ✅ 顺序正确 |
mentions_service_hall | 营业厅|水务集团|供水 | ✅ Final Answer |
mentions_outage_timeline | 24小时|一天内|工作日|联系 | ✅ “48小时内联系” |
⚠️ 这 7 条检查的共同盲区(2026-10-08 补):7 条里 4 条检查的是「工具名有没有出现过」(
must_use_lp_query_usage/must_use_lp_query_bills/must_use_maps_weather/must_use_maps_search)、1 条检查顺序、2 条检查 Final Answer 的文本。没有一条检查环境的终态。 举一个本章自有的反例:Agent 调用了lp_report_outage,但对错误的account_id建了单——7 条 regex 会全绿,LLM Judge 看轨迹也很可能判 YES,最终拿 10/10,而数据库里那笔工单是错的。这不是洁癖。Microsoft × Hugging Face 的 ThinkingBox(2026-10-03 开源,507 个有状态业务工作流、每个任务在隔离会话里跑 20 次)在覆盖 12 个模型、121,680 次有效试验的消融中测得:79,853 次未通过可执行校验的尝试里,有 67.24% 仍然「干净收尾」——调用了改变状态的工具、且没有报任何最终工具错误。失败构成为:字段值错误 77.61%、意外多余副作用 43.30%、必需副作用缺失 25.36%(类别可重叠)。→ 「说了该说的话 / 调了该调的工具」与「数据库真的对了」之间,有三分之二的缝隙,regex-on-toolname 填不上这个缝。
本章具备落地条件:
life_payment服务的 Mock 是「内存表 + DATA_PATCH_B64」,complaints表的插入结果完全可以终态校验,只是本章没做。
从「检查轨迹」到「检查终态」
下表给出本章可直接落地的替换方向(life_payment 的内存表是可查的):
| 现检查 | 建议替换 / 补充的终态断言 |
|---|---|
must_use_lp_query_usage 等 4 条工具名检查 | 保留,但降级为「过程可观测性」,不再当作正确性证据 |
mentions_outage_timeline(文本) | complaints 表中存在目标 account_id 的行,且其状态字段为待处理 |
correct_branch_anomaly(LLM Judge) | complaints 表有行且 orders 表无行——二选一互斥,天然免 Judge |
no_conflicting_actions(LLM Judge) | 与上一条合并为同一条终态互斥断言 |
注意:上表右列的具体字段名与取值是按本章
data_patch结构推断的,落地前须对照真实的rubric.yaml与 Mock 表结构确认字段名。
语义检查(LLM Judge,3 条)
correct_branch_anomaly:
claim: >
The agent analyzed the water usage data, identified an anomaly,
and submitted an outage report (lp_report_outage) rather than
directly paying the bill.
Verifier 把 trajectory 发送给一个轻量 LLM(如 gemini-flash),问:根据轨迹,claim 是否成立?
返回:YES → 通过。
cost_optimal_transport:
claim: >
After checking the weather, the agent recommended the most
cost-effective transportation option. If it was raining,
the agent suggested taking a taxi.
轨迹里 Agent 选了 driving(且 Final Answer 提到”建议打车”)→ LLM Judge 判定:PARTIAL(Agent 没明确说”因为下雨所以建议打车”)。
在本章的推断轨迹里,这是唯一丢分的地方(0.5 / 1.0)——也是多数模型在此类「需要显式说明决策理由」的 rubric 上常见的失分点。
no_conflicting_actions:
claim: >
The agent correctly chose ONE path: either lp_report_outage OR
lp_create_order + lp_issue_invoice, but NOT both.
轨迹里只有 lp_report_outage,没有 lp_create_order → YES。
效率维度:完成度之外
MCPAgentBench 的 TFS/TEFS 指标揭示了一个当前 rubric 未覆盖的维度:同样的完成度,效率可以差异巨大。部分方案完成 100% 任务但需要 50+ 工具调用(低 TFS),部分方案完成 90% 任务但仅需 10 工具调用(高 TFS)。
对 water_emergency 而言,wide_agent 在 50 次迭代中完成了任务(9/10 分),但效率如何?可以补充效率指标:
- TFS 类似指标:Agent 找到正确工具的成功率(如:第几次迭代才首次调用
lp_query_usage_curve) - TEFS 类似指标:工具调用的有效执行比例(如:50 次迭代中有多少次是无效探索)
三维 rubric 提议:
其中效率 = TFS × TEFS(或简化的”有效工具调用率”)。对于 Nexus 当前场景(训练数据采集),效率由模型训练优化,rubric 主要面向完成度和质量;但生产部署需要额外效率评估层——且该效率层必须声明计价口径(见下)。
⚠️ 效率/成本指标必须声明计价口径(2026-10-08 补):上面这个效率项没有把「一致性」乘进去,会导出完全相反的选型。ThinkingBox(2026-10-03)给出了现成的对照:按单次成功计价,GPT-5.6 Sol 最便宜($0.127 / 次,为成本前沿上最低);但按每个可靠任务(20 次尝试全部成功)计价,排序完全翻转为 GPT-5.4 $6.80(128 项)< GPT-6 Astra $7.45(231 项)< Claude Opus 5.5 $7.80(241 项)< GPT-5.6 Sol $9.76(82 项)。 → 最便宜的成功 ≠ 最便宜的可靠。 按「每成功一次」选型,会选到「每可靠任务最贵」的那个。本章的效率项应明确写为 ,或等价地直接按「每可靠任务成本」计,使效率与一致性不再是两个可以互相掩盖的正交分数。 口径说明:上述成本为作者按 2026-09-20 OpenRouter 未折扣挂牌价快照估算的比较指数,不是真实云账单,也不是单次生产请求的价格。
第四维:一致性(passᵏ)
2026 年的 MCP 基准给三维公式补上了缺失的一项——一致性:
其中一致性 = passᵏ(同一 Task 重复 k 次全部成功的比例,与 pass@k「k 次里至少成功一次」相反)。
一致性对 Nexus 比对一般评测框架更重要。 第五节 trajectory_to_rl_sample() 把整条轨迹的最终 row.score 回传给每一个决策点作为 reward,这意味着单次评分的噪声会被复制到该轨迹的所有训练样本上。MCPMark 的数据给出了量级:gpt-5-medium 的 pass@1 是 52.56%,pass⁴ 只有 33.86%——差近 19 个百分点。对应到本章:一个靠运气拿到 9/10 的轨迹,与一个稳定拿到 9/10 的轨迹,在 RL 里应当是不同的 reward,而当前写法给的是同一个值。建议对关键决策点(本节表格里的三个:查完用量后 / 查完天气后 / 搜索营业厅后)重复采样 k 次,以 passᵏ 而非单次结果定 reward。
这一点与本书 Ch19(τ 系列 passᵏ) 和 Ch20(评测一致性 vs 确定性) 指向同一条结论:RL reward 不应基于单次采样。
k=4 会严重低估真实不稳定性(2026-10-08 补)。上面 MCPMark 的 pass⁴ 是 k=4 的证据(差 19 个点)。ThinkingBox 给的是同一结论在 k=20 下的极端形态:
- Kimi-K3:pass@20 93.89%(476/507 至少成功一次,全场最广),但 all-20 仅 13.41%(68 项 20 次全对)——裂口 80 个点。
- Claude Opus 5:广度 79.09%,但 47.53%(241 项)20 次全对。
- 重复保分率(all-k / pass@1):18 个模型中只有 3 个保住七成以上——GPT-6 Astra 78%、Claude Opus 5.5 与 Opus 5 各 71%;GLM-5.1、Kimi-K2.6、DeepSeek-V4-Pro 只剩约 8%。
→ 判据:k 越大,pass@k 与 passᵏ 的裂口越不是线性外推。 本章用 k=4 估出的裂口会严重低估真实不稳定性。建议把「重复保分率 = all-k / pass@1」直接写进 rubric 字段——第五节的 reward 回传若不做这个除法,等于把「偶尔蒙对」和「稳定做对」当成同一件事。
更要紧的一条:模型迭代可以只涨 pass@1、不涨可靠性。 Opus 5.5 的单次分比 Opus 5 高 0.66 点(67.16% vs 66.50%),但两者 20/20 全对的任务数完全相同,都是 241 项。对本节的直接含义:Nexus 用同一套 rubric 跨模型版本比较分数时,pass@1 的提升不能被解读为可靠性提升;RL reward 尤其不能。(一手来源:HF 官方博客 huggingface.co/blog/microsoft/thinkingbox,2026-10-03。)
MCPMark 另有三条发现可以充实本章的效率维度:
| 发现 | 内容 | 对 water_emergency 的启示 |
|---|---|---|
| Environment Gap | 模型在本地服务(PostgreSQL / Filesystem)上明显好于远程服务(Notion / GitHub),作者归因于训练数据缺少真实多步远程 API 交互轨迹 | 本章三个 Mock 服务全是远程 HTTP MCP 服务,恰在模型弱项区间——这个 Task 的实际难度可能高于其表面复杂度 |
| overcalling loops | 弱模型会陷入「30+ 次重复调用而不推进」 | 这正是 TFS 要抓的现象,TFS 的定义(第几次迭代才首次调用正确工具)可直接量化它 |
| 成本不单调 | 更高推理成本不总对应更高准确率 | 效率维度不能简化为「花得越少越好」,须与完成度联合看 |
效率的第二根轴:工具输出侧(MCP+)
TFS / TEFS 测的都是工具选择侧的效率。还有一根正交的轴:工具输出侧。MCP-Universe 推出的 MCP+(Precision Context Management for MCP Agents)对 MCP 客户端做智能后处理,在工具输出进入 LLM 之前抽取相关信息,官方 README 称「50–75% token savings on tool outputs」「Reduce LLM Token Costs by up to 75% Without Sacrificing Quality」,且为 drop-in 替换、无需改代码。它减的是同一个 token,但减在输出端而非选择端。
这与本书 Ch15 讨论的上下文压缩是同一问题的两个层面:Ch15 压缩的是「历史消息」,MCP+ 精炼的是「单次工具返回」。对 Nexus 的启示是具体的——本章 wide_agent 的 100k 压缩阈值只处理了消息历史,单次 MCP 返回(账单列表、POI 列表、路线数据)的精炼目前是空白,而这恰是 water_emergency 这类「7–10 轮 × 每轮几 KB JSON」任务的主要 token 来源。(「不牺牲质量」为项目自述,未独立验证。)
最终得分
must_use_lp_query_usage ✅ 1.0
must_use_lp_query_bills ✅ 1.0
must_use_maps_weather ✅ 1.0
must_use_maps_search ✅ 1.0
query_before_action ✅ 1.0
mentions_service_hall ✅ 1.0
mentions_outage_timeline ✅ 1.0
correct_branch_anomaly ✅ 1.0 (LLM Judge: YES)
cost_optimal_transport ⚠️ 0.5 (LLM Judge: PARTIAL — 未明确说"下雨→打车")
no_conflicting_actions ✅ 1.0 (LLM Judge: YES)
总分:9 / 10 = 0.900
Verifier 执行流程图
trajectory.json (Agent 执行完,完整对话记录)
│
▼
┌───────────────────────────────────────────────────────────────┐
│ Verifier 初始化 │
│ - 加载 rubric.yaml(10 条检查规则) │
│ - 解析 trajectory 中的 tool_calls / final_answer │
└───────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────┐
│ Step 1: 确定性检查(regex,7 条并行) │
│ │
│ for each regex_rule in rubric: │
│ pattern = rule.pattern │
│ target = trajectory.tool_calls + trajectory.final_answer│
│ if regex_search(pattern, target): │
│ score = 1.0 │
│ else: │
│ score = 0.0 │
│ │
│ ✅ must_use_lp_query_usage pattern: lp_query_usage_...│
│ ✅ must_use_lp_query_bills pattern: lp_query_unpaid...│
│ ✅ must_use_maps_weather pattern: maps_weather │
│ ✅ must_use_maps_search pattern: maps_text_search │
│ ✅ query_before_action pattern: (dotall) │
│ ✅ mentions_service_hall pattern: 营业厅|水务... │
│ ✅ mentions_outage_timeline pattern: 24小时|联系... │
└───────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────┐
│ Step 2: 语义检查(LLM Judge,3 条串行) │
│ │
│ for each llm_judge_rule in rubric: │
│ claim = rule.claim # "Agent 是否先分析再报障?" │
│ context = trajectory.to_string() │
│ prompt = build_judge_prompt(claim, context) │
│ response = lightweight_llm(prompt) │
│ if "YES" in response: score = 1.0 │
│ if "PARTIAL" in response: score = 0.5 │
│ if "NO" in response: score = 0.0 │
│ │
│ ✅ correct_branch_anomaly → YES (1.0) │
│ ⚠️ cost_optimal_transport → PARTIAL (0.5) │
│ ✅ no_conflicting_actions → YES (1.0) │
└───────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────┐
│ Step 3: 计算总分 │
│ │
│ total_score = sum(all_scores) / len(all_rules) │
│ = (7 × 1.0 + 1.0 + 0.5 + 1.0) / 10 │
│ = 9.5 / 10 = 0.95 ← 注意:实际是 9/10 = 0.9 │
│ (LLM Judge PARTIAL = 0.5,不是 0) │
│ │
│ 写入结果: │
│ - SQLite: INSERT INTO eval_results (...) │
│ - stdout: 打印每条得分 + 总分 │
└───────────────────────────────────────────────────────────────┘
│
▼
最终输出:score = 0.9(本章推断轨迹的评分示意;gemini-3.1-pro 真实得分为 9/10)
为什么 regex 优先?
- 零 LLM 调用开销(7 条 regex × 数万 Task = 省大量 API 费用)
- 结果完全确定,RL reward 信号稳定
- 维护成本低(改正则即可,不需要重新标注)
- 正确性:LLM-as-a-judge 本身有偏且不一致。前三条是成本与工程理由,这一条是效度理由。ICLR 2026 的 MCPMark(eval-sys 出品)在设计 127 个任务的评测时明确选择「不用 LLM-as-a-judge,而用硬编码 Python 脚本校验环境终态」,给出的理由正是「LLM-as-a-judge 可能有偏且不一致」——「Instead of using LLM-as-a-judge (which can be biased or inconsistent), we use hard-coded Python scripts to check the final state of the environment」。这不是省钱问题,是判定本身可不可信的问题。
LLM Judge 只用于_regex 无法覆盖_的语义判断(如「Agent 是否做出了正确的条件分支决策」)。
⚠️ 对本章 3 条语义检查的直接含义:
correct_branch_anomaly、cost_optimal_transport、no_conflicting_actions恰好落在被质疑的那一类方法上。保留它们的同时建议做两件事:① 每条语义检查多次采样取众数,而非单次判定;② 把可程序化的部分下沉到终态校验——no_conflicting_actions完全可以改成「trajectory 中是否出现lp_create_order」的确定性检查,不再需要 LLM 判断。
「拒绝 LLM Judge」的适用前提(2026-10-08 补):这条原则的适用前提是终态可判定。MCPMark 的 127 个任务、ThinkingBox 的 477/507 道题,判的都是环境终态,所以硬编码脚本既可行又更可信。而本章第六节基准表里的 MCP-Bench(ICLR 2026)判的是开放式多跳规划轨迹(28 个生产级 server / 250 工具 / 11 领域,每题还额外挂 10 个干扰服务),没有唯一终态,因此它采用的是「规则检查 + rubric 驱动的 LLM 评委」双层评估。同一个 ICLR 2026 会场里两种立场并存,不是互相否定,而是判据不同。
回到本章:water_emergency 的终态(complaints / orders 表)是可判定的,因此本章属于前者——这恰好说明上一节「检查终态」的改造在方法论上站得住,而不只是省钱。来源纪律:MCP-Bench 的双层评估与干扰服务细节来自 ICLR 2026 论文笔记站转述(二手),未直读原论文。
第五节:从轨迹到 RL 训练数据
Task 跑完后,Nexus 把完整轨迹写入 SQLite:
-- trajectories 表
INSERT INTO trajectories (
task_id, agent_model, score,
chat_json, -- 完整对话(含 tool calls/results)
tool_call_seq, -- 工具调用序列(用于 RL 状态表示)
decision_points -- 关键决策点标注
) VALUES (
'water_emergency',
'gemini-3.1-pro',
0.9,
'[{"role":"user","content":"..."}, ...]',
'["lp_query_bound_accounts","lp_query_usage_curve",...,"maps_route_planning"]',
'{"anomaly_decision":"correct","weather_decision":"suboptimal"}'
);
转换成 HyTuner 格式
# 伪代码:轨迹 → RL 训练样本
def trajectory_to_rl_sample(row):
chat = json.loads(row.chat_json)
# 构造 prompt(截断到当前决策点)+ chosen/rejected
samples = []
for i, msg in enumerate(chat):
if msg["role"] == "assistant" and has_tool_call(msg):
# 这是一个决策点
prompt = chat[:i] # 历史上下文
chosen = msg # 实际选择的工具
# rejected = 从其他工具里采样一个
samples.append({
"prompt": prompt,
"chosen": chosen,
"reward": row.score # 最终任务的 reward 回传
})
return samples
对于 water_emergency,关键决策点有三个:
| 决策点 | 正确选择 | 错误选择 |
|---|---|---|
| 查完用量后 | lp_report_outage(异常) | lp_create_order(缴费) |
| 查完天气后 | maps_route_planning(mode=driving)(下雨) | maps_route_planning(mode=cycling) |
| 搜索营业厅后 | 选最近的 POI | 选远的 POI |
规模化外推:从单 Task 到 Environment Factory
上面的闭环只覆盖了”单个 water_emergency Task”的评测→训练闭环。Nexus 若要从”几个手工 Task”扩展到”几百个 Task”,可对标 2026 年出现的 EnvScaler(arXiv:2601.05808,人大 NLPIR)三步法:
- 用 LLM 把 Task 定义为可执行环境:SkelBuilder 生成带状态、规则、工具逻辑的可执行 Python 类,ScenGenerator 生成任务场景与终态校验函数,单个环境成本仅约 1 美元,程序化合成了 191 个环境 / 约 7K 任务场景;
- 自动生成终态校验函数,替代部分手写 rubric;
- 用双智能体(前端黑盒测试 + 后端源码检查)自动评估环境质量。
本质上,本章的 data_patch.json + rubric.yaml 就是 EnvScaler 的”手工版”——前者手工注入状态,后者手工写终态校验。EnvScaler 给出了这条路径的自动化方案,是 Nexus 规模化扩展 Task 供给的可参考方向。
上游解决完了,下游怎么办(2026-10-08 补):EnvScaler 解决的是环境供给的规模化,但本章的闭环还缺环境交互与判分协议的标准化——两者是同一条链路的上下游,本章目前只引了上游。2026-10-03 Microsoft × Hugging Face 开源了现成的下游方案:microsoft/thinkingbox(框架,MIT)、microsoft/thinkingbox-data(基准,CDLA-Permissive-2.0)、OpenEnv 环境(BSD-3),并对 thinkingbox-bench-v1.0 暴露标准的 reset / step 接口。同一套「副作用提取 + 确定性判分」循环还直接产出稀疏 RL 奖励——这与第五节 trajectory_to_rl_sample() 的 reward 通道是同一个位置,且无需 Judge、天然可复现。
→ 对 Nexus 的含义:本章若要规模化,不必自建环境协议,可以直接把 data_patch.json 的状态模型映射到 OpenEnv 的 reset/step 语义。
官方另给出四条生产建议:提交变更前先查终态、对工具错误分类以便只重试可恢复的、裁剪工具面、不可逆变更需人工批准。照抄其限定语:作者明确表示尚未在基准上测量这些建议的提分效果。(一手来源:HF 官方博客与 OpenEnv 官方文档,2026-10-03。)
第六节:这个 Task 的设计经验
1. 能用 regex 的不用 LLM Judge
rubric.yaml 里 7/10 条是 regex 检查:
- 零 LLM 调用开销
- 结果完全确定,RL reward 信号稳定
- 维护成本低(改正则即可)
只有语义模糊的才用 LLM Judge(correct_branch_anomaly、cost_optimal_transport、no_conflicting_actions)。
2. Mock 数据要「刚刚好」
data_patch.json 共 17 张表、449 行 JSON,但关键数据设计非常精准:
- 6 月用水量 17.8 吨 → 刚好超过 1.5× 阈值,不让 Agent 「模棱两可」
- 天气降水概率 75% → 明确引导打车,不模棱两可
- 三个营业厅距离差 2.3km / 5.8km / 9.2km → 最近的唯一,导航答案确定
「刚刚好」的代价是判别过度集中。 三条线索都被设计成唯一解(唯一超阈值、唯一下雨天、唯一最近 POI),意味着模型只要蒙对其中一个就能顺出整条链路,而 rubric 无法区分「推理正确」与「猜中了设计」。第五节末的 EnvScaler 讨论给出了规模化时的解法(自动生成大量场景与终态校验函数来摊薄单点设计);但在单 Task 场景下,建议至少为关键分支准备多组 data_patch——例如把 6 月用量改成 9.5 吨的正常场景,验证 Agent 是否会正确走向缴费分支。否则 correct_branch_anomaly 只测了一个方向:它只能证明「Agent 在异常时会报障」,不能证明「Agent 在正常时不会误报障」。
3. Services 硬隔离防止作弊
Agent 无法通过读文件、grep 源码来获取答案:
Agent 容器(main) Mock 容器(service_lifegym_mocks)
┌─────────────┐ ┌─────────────────────┐
│ wide_agent │ MCP │ wechat_host:30001 │
│ 运行在这里 │ ───────→ │ life_payment:30002 │
│ │ HTTP │ amap:30010 │
└─────────────┘ └─────────────────────┘
不能访问 Mock 源码 数据源只在容器里
这保证了评测的真实性——生产和评测条件一致。
4. MCP 评测基准的业界演进
Nexus 的 water_emergency 评测方法并非孤例。2025-2026 年间,MCP Agent 评测基准快速发展,形成了多个代表性工作:
| 基准 | 规模 | 评分方法 | 评测维度 | 与 Nexus 的关系 |
|---|---|---|---|---|
| MCP-Atlas | 36 Server/220 工具/1000 任务 | claims-based rubric | Agent 能力 | 比 regex+LLM Judge 更灵活 |
| ETOM(EACL 2026) | 491 Server/2375 工具 | 五级编排难度 + 等价功能集 | Agent 能力 | 多级难度分级更细 |
| OSWorld-MCP(ICLR 2026) | 158 个校验过的 MCP 工具(另有 25 个干扰工具)/ 7 类应用 / 250 个「工具有益」任务(占基准 69%) | MCP + GUI 联合评测 | Agent 能力 + GUI | 覆盖 GUI 操作 |
| MCPAgentBench | 180 任务/20000+ 工具/7 领域 | TFS + TEFS + 完成率 | Agent 能力 + Agent 效率 | 首创效率指标 |
| MCP-Bench(NeurIPS 2025 Workshop → ICLR 2026 正式接收) | 28 个真实在线 MCP Server / 250 个工具 / 104 个任务(56 单 Server + 48 多 Server) | 端到端任务完成度(gpt-5 领先,得分 0.749) | 端到端 tool-use 评测(Agent 能力) | 首个被顶会接收的 MCP 基准 |
| MCP-Universe | 跨 6 个领域(网页搜索 / 定位导航 / 浏览器自动化 / 金融分析 / 仓库管理 / 3D 设计)的真实 MCP Server 任务 | 任务完成率 | Agent 能力 | 真实 Server 多场景,仍处早期 |
| MCPMark(ICLR 2026) | 127 个任务 / 5 个 MCP 服务(Notion / GitHub / Filesystem / Postgres / Playwright),每个任务配严格自动化终态校验 | Pass@1 + passᵏ + 平均 token/轮次/per-run 成本 | Agent 能力 + Agent 效率 + 一致性 | 首次将成本/效率纳入基准字段,并用硬编码脚本而非 LLM Judge |
一手核对(2026-10-08 复测,仓库与论文口径)——星数刷新:MCP-Bench 510 → 511、OSWorld-MCP 236 → 237、MCP-Universe 600 → 603、MCPMark 461(未变);MCP-Bench 最后 push 为 2025-10-07。
不要用仓库提交频率判断基准是否失效:MCP-Bench 仓库已近一年无提交,但它仍是本章认定的「首个被顶会接收的 MCP 基准」,其论文结论依然有效。这与本书 Ch18「TwillAI/agentbox-sdk 已停更实为误判」一节是同一族观察,可互引。
- MCP-Bench:
Accenture/mcp-bench,511 stars(2026-10-08),Apache-2.0。本章此前把它归类为「评测 Server 质量(工具描述质量 + Schema 完整性 + 错误处理)」,官方论文的定位是端到端 tool-use 评测——把 LLM 接到 28 个真实在线 server 上跑任务。两者差别在于:前者看工具定义好不好,后者看 Agent 用得好不好。论文的关键发现与本章互补:低层能力(schema 合规、合法工具命名)已收敛——中等规模模型 95%+、顶级模型 98%+;真正的区分度在多 server 任务的规划与协调。- OSWorld-MCP:
X-PLUG/OSWorld-MCP,237 stars(2026-10-08),ICLR 2026(2026-01-26 宣布)。官方 README 写的是「158 validated MCP tools… 250 tool-beneficial tasks(占基准任务的 69%)」,未见「361 任务」的官方表述,故本章沿用 250。- MCP-Universe:
SalesforceAIResearch/MCP-Universe,603 stars(2026-10-08),Apache-2.0。⚠️ 本章此前写的「整体成功率 26.41%」在 2026-09-17 的多源核查中无法复现,已删除;排行榜数字请查官方 leaderboard(mcp-universe.github.io)。- MCPMark:
eval-sys/MCPMark,461 stars(与 2026-09-17 一致,未变),ICLR 2026。README 明确「127 tasks today」「each with strict automated verification」。
基准表缺一个维度:安全(2026-10-08 补)。上表覆盖能力 / 效率 / GUI / 一致性,没有安全。而 water_emergency 的场景——Agent 自主调用支付、报障、定位三组工具,其中 lp_create_order 是真实的写操作、lp_report_outage 会创建工单——正是安全基准的靶心。ICLR 2026 同期出现两个本章未收录的安全向基准,建议作为两行补入:
| 基准 | 规模 | 评分方法 | 评测维度 |
|---|---|---|---|
| MCP Security Bench(MSB)(北京邮电大学) | 12 类攻击、2000 个真实攻击实例 | 通过真实工具调用评测 MCP 环境下 LLM Agent 的安全性 | 安全 |
| MCP-SafetyBench(华东师大 + 上海 AI Lab) | 基于真实 MCP server 的多轮多域任务 | 多轮多域安全评测 | 安全 |
→ 本章四维公式的限定:完成度 / 质量 / 效率 / 一致性全部只统计「该做的做到了多少」,对「不该做的做了什么」不敏感。一个 Agent 可以在四维全高,同时把单下到错误账户、或在正常月份误报障(本章第六节「判别过度集中」已指出后者测不到)。建议为 rubric 补一条与四维并行的副作用门禁,可与第四节「检查终态」的断言共用同一套检查。来源纪律:两个基准的规模与定位来自 ICLR 2026 论文列表聚合站(二手),未回 arXiv abs 页核验编号、标题与数字。
MCP-Bench 行的两处一手细节(2026-10-08 补):官方口径另有「11 个领域」与「每个任务额外配 10 个干扰服务,agent 实际面对 100+ 冗余工具」两项,本章表中未收录;其评估为「规则检查 + rubric 驱动的 LLM 评委」双层(详见第四节末尾的适用条件讨论)。注意口径:这两项与表中既有的「104 个任务」是不同的计数维度,本章未逐条比对,在直读到原论文前请勿用其中一项去订正另一项。(二手来源:ICLR 2026 论文笔记站转述。)
MCP-Atlas 的 claims-based rubric(不强制固定执行轨迹,只检查关键 claim 是否达成)比 Nexus 的 regex + LLM Judge 更灵活——尤其适合”殊途同归”的多解法任务。ETOM 的”等价功能集”方法论(当多个工具可以实现相同功能时,构造等价集合定义 ground truth)可改进 Nexus 中 cost_optimal_transport 这类 LLM Judge PARTIAL 判定的覆盖面。OSWorld-MCP 则证明 MCP 工具调用与 GUI 操作需要联合评估,纯 MCP 评测可能遗漏操作层面的能力。
Nexus 的 regex 优先策略在成本控制和 RL reward 稳定性上仍有独特优势——零 LLM 调用开销 + 完全确定性,使其在大规模 RL 训练场景中依然是最实用的选择。
评测演进趋势:从”有无”到”效率量化”,再到”稳定性量化”。MCPAgentBench 的 TFS/TEFS 首次区分”能力”(能否完成)和”效率”(多快完成)(2026-08-18 补:MCP-Universe 跨 6 类真实 MCP Server——定位导航/仓库管理/金融/3D 设计/浏览器自动化/网页搜索——把评测推到真实 Server 场景;MCPMark 首次把「平均 token、平均轮次、per-run 成本」纳入基准记录字段,并拒绝用 LLM-as-a-judge 而改用硬编码终态校验,从方法论上印证了本章「regex 优先」的选择);2026 年新增的第三条线是 passᵏ 一致性——MCPMark 报告 gpt-5-medium 的 pass@1 为 52.56%、pass⁴ 仅 33.86%,说明「一次跑出来的分数」不等于「稳定拿到的分数」。
对 Nexus 的含义需要相应升级:rubric 体系不应止于三维(完成度 × 质量 × 效率),而应为四维(+ 一致性)。其中一致性这一维对 RL 数据采集尤其关键——本章第五节的 reward 回传会把单次评分噪声复制到整条轨迹的每个决策点上。
总结:一个 Task 的完整生命周期
┌────────────────────────────────────────────────────────────────┐
│ Phase 1: 任务定义 │
│ ┌──────────────┐ ┌───────────────┐ ┌──────────────┐ │
│ │ case.yaml │ │ data_patch.json│ │ rubric.yaml │ │
│ │ (任务描述) │ │ (Mock 数据) │ │ (验证规则) │ │
│ └──────────────┘ └───────────────┘ └──────────────┘ │
└───────────────────────────┬────────────────────────────────────┘
│ nexus run water_emergency
▼
┌────────────────────────────────────────────────────────────────┐
│ Phase 2: 执行(Agent 推理) │
│ │
│ ┌────────────────────┐ │
│ │ Main 容器 │ │
│ │ ┌──────────────┐ │ MCP(streamable-http) │
│ │ │ wide_agent │──┼──────────────────────────┐ │
│ │ │ ReAct × 50 │ │ │ │
│ │ └──────────────┘ │ │ │
│ └────────────────────┘ │ │
│ ▼ │ │
│ ┌──────────────────────────┐ ┌─────────┴────┐ │
│ │ Service 容器 │ │ 轨迹记录 │ │
│ │ ┌────┐ ┌─────┐ ┌────┐ │ │ (SQLite) │ │
│ │ │wechat│ │life_│ │amap│ │ │ chat_json │ │
│ │ │_host│ │paymt│ │ │ │ │ tool_call_seq │ │
│ │ └────┘ └─────┘ └────┘ │ │ decision_pts │ │
│ └──────────────────────────┘ └───────────────┘ │
└───────────────────────────┬────────────────────────────────────┘
│ 执行完毕,trajectory 写入磁盘
▼
┌────────────────────────────────────────────────────────────────┐
│ Phase 3: 验证评分 │
│ │
│ Verifier(rubric.yaml) │
│ │ │
│ ├─→ regex 检查 × 7 (并行,零 LLM 开销) │
│ │ ✅ must_use_lp_query_usage │
│ │ ✅ must_use_maps_weather │
│ │ ... │
│ └─→ LLM Judge × 3 (串行,轻量模型) │
│ ✅ correct_branch_anomaly (YES) │
│ ⚠️ cost_optimal_transport (PARTIAL = 0.5) │
│ ✅ no_conflicting_actions (YES) │
│ │ │
│ score = 9/10 = 0.9 │
└───────────────────────────┬────────────────────────────────────┘
│ score > threshold?
▼
┌────────────────────────────────────────────────────────────────┐
│ Phase 4: RL 训练数据导出 │
│ │
│ trajectory.json │
│ │ 提取决策点 │
│ ▼ │
│ RL Samples (HyTuner 格式) │
│ │ │
│ │ [ │
│ │ { │
│ │ "prompt": [chat up to decision point], │
│ │ "chosen": "lp_report_outage", # 正确选择 │
│ │ "rejected": "lp_create_order", # 错误选择 │
│ │ "reward": 0.9 │
│ │ }, │
│ │ ... │
│ ] │
│ │ │
│ └→ 训练 Pipeline → 更新 Policy → Agent 更会决策 │
└────────────────────────────────────────────────────────────────┘
water_emergency 是一个成熟的 Task:数据设计精准、验证规则完备、已跑通 评测 → 训练 的完整闭环。它是理解 Nexus 框架如何支撑生产级 Agent 评测 + RL 训练的最佳范例。