第 21 章 第三部分:完整案例

Case Walkthrough:water_emergency 端到端全记录

用生产环境的真实 Task(water_emergency)完整走一遍 Nexus 框架:从 YAML 配置、Mock 数据注入、wide-agent 推理轨迹,到 rubric 验证与 RL 训练数据导出

Nexus Case Study water_emergency wide-agent RL训练数据

为什么是 water_emergency

water_emergency 是一个生产级 Task,已在内部评测平台运行,gemini-3.1-pro 得分 9/10。

它复杂在三点:

  1. 条件分支决策 — 用水量正常→缴费,异常→报障,Agent 必须先分析再行动
  2. 跨 APP 信息依赖 — 查天气→决定出行方式→导航,三段信息串联
  3. 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_agentwide_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 服务运行在独立容器:

端口服务功能
30001wechat_host微信对话状态、位置信息
30002life_payment水费账户、账单、报障、缴费
30010amap天气、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_usagelp_query_usage_curve✅ Iteration 2
must_use_lp_query_billslp_query_unpaid_bills✅ Iteration 2
must_use_maps_weathermaps_weather✅ Iteration 4
must_use_maps_searchmaps_text_search✅ Iteration 5
query_before_actionlp_query_bound_accounts.*lp_query_usage_curve.*lp_report_outage(dotall)✅ 顺序正确
mentions_service_hall营业厅|水务集团|供水✅ Final Answer
mentions_outage_timeline24小时|一天内|工作日|联系✅ “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 提议:

Score=w1⋅完成度+w2⋅质量+w3⋅效率\text{Score} = w_1 \cdot \text{完成度} + w_2 \cdot \text{质量} + w_3 \cdot \text{效率}

其中效率 = 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 项)。 → 最便宜的成功 ≠ 最便宜的可靠。 按「每成功一次」选型,会选到「每可靠任务最贵」的那个。本章的效率项应明确写为 效率=TFS×TEFS×重复保分率\text{效率} = \text{TFS} \times \text{TEFS} \times \text{重复保分率},或等价地直接按「每可靠任务成本」计,使效率与一致性不再是两个可以互相掩盖的正交分数。 口径说明:上述成本为作者按 2026-09-20 OpenRouter 未折扣挂牌价快照估算的比较指数,不是真实云账单,也不是单次生产请求的价格。

第四维:一致性(passᵏ)

2026 年的 MCP 基准给三维公式补上了缺失的一项——一致性:

Score=w1⋅完成度+w2⋅质量+w3⋅效率+w4⋅一致性\text{Score} = w_1 \cdot \text{完成度} + w_2 \cdot \text{质量} + w_3 \cdot \text{效率} + w_4 \cdot \text{一致性}

其中一致性 = 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)三步法:

  1. 用 LLM 把 Task 定义为可执行环境:SkelBuilder 生成带状态、规则、工具逻辑的可执行 Python 类,ScenGenerator 生成任务场景与终态校验函数,单个环境成本仅约 1 美元,程序化合成了 191 个环境 / 约 7K 任务场景;
  2. 自动生成终态校验函数,替代部分手写 rubric;
  3. 用双智能体(前端黑盒测试 + 后端源码检查)自动评估环境质量。

本质上,本章的 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-Atlas36 Server/220 工具/1000 任务claims-based rubricAgent 能力比 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 操作
MCPAgentBench180 任务/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 训练的最佳范例。