APEX-Agents 深度解剖:真实职场数据 + MCP 模拟层如何构建专业 Agent 评测
从 World 数据结构到 MCP Server 源码,完整拆解 APEX-Agents 的架构设计——Google Takeout 真实数据、9 大 MCP 工具服务、Rubric 评分机制与 Leaderboard 分析
概览
APEX-Agents(Mercor, 2026.01)是当前最接近真实专业工作场景的 Agent 评测基准。它不用合成数据,不用模拟对话——而是直接把真实投行分析师、管理咨询顾问、公司律师的 Google Workspace 工作环境打包成评测环境,让 AI Agent 在里面完成他们的日常工作。
480 个任务,33 个虚拟工作世界,平均每个任务需要人类专家 1.82 小时完成。当前最强模型(GPT-5.2)的 Pass@1 仅 23%。
本报告基于对 APEX-Agents 数据集的实际解压分析、Archipelago 框架源码阅读、以及 MCP Server 接口文档的系统梳理,完整拆解其架构设计。
构建方法论:从调研到评测的四步流水线
APEX-Agents 的构建不是随意拼凑的。Mercor 设计了一套严谨的四步方法论,每一步都有明确的输入和产出。
第一步:行业调研(Surveys)
Mercor 调研了数百名专业服务领域的专家,受访者来自顶级机构——Goldman Sachs(高盛)、McKinsey(麦肯锡)、Cravath(克拉华律所,全美顶级律所之一)。
调研的核心问题不是”AI 能做什么”,而是“你们每天的时间花在什么任务上,哪些任务你最希望 AI 来做”。这决定了 benchmark 评测的任务分布——不是研究者想象的”典型任务”,而是从业者真实的痛点。
第二步:场景搭建(Scenarios)——Box 与 Harvey AI 合作
这是最核心的环节。Mercor 的内部专家团队——包括副总裁(VP)、董事总经理(MD)和经理(Manager)级别,拥有顶级公司 5-10 年经验的从业者——在 Google Workspace 中亲自模拟协作流程。
比如:为一家虚构的欧洲油气公司进行为期一周的咨询项目,聚焦成本削减措施。专家们真的在 Gmail 里通邮件、在 Google Sheets 里建财务模型、在 Google Chat 里讨论进展——然后通过 Google Takeout 导出全部数据。
两个外部合作方在这一步发挥了关键作用:
Box(box.com,企业级云端内容管理平台,NYSE 上市公司)与 Mercor 合作定义真实世界文件系统的样貌。Box 是企业文件管理领域的头部玩家(客户包括摩根士丹利、博通等),他们的核心贡献是确保 APEX-Agents 中的文件结构不是简化的测试数据,而是真正模拟了专业人士日常工作中的数据室(dataroom)环境——多层级目录、跨格式文件、不完整的命名约定、混合的版本管理。双方将严格的、领域特定的挑战映射到了覆盖多个行业的复杂文件结构中。
这个合作的价值在于:文件导航是 Agent 最容易失败的地方之一(详见下方失败模式分析)。一个过于整洁的文件系统会高估 Agent 的能力,Box 帮助确保了评测环境的真实复杂度。
Harvey AI(harvey.ai,AI 法律助手,估值超 30 亿美元的法律 AI 独角兽)对企业法务领域的评测世界提供了早期反馈——覆盖设计、范围和真实性三个维度。Harvey AI 确认了任务的复杂性反映了财富 500 强企业和律所中顶级律师的真实工作。
Harvey AI 的角色是验证者和质量把关者——确保法律领域的评测任务不是”玩具级”的合同分析,而是需要理解多方利益博弈、跨条款推理、合规风险评估的真实法务工作。
第三步:任务创建(Task Creation)
基于第二步搭建的场景,专家定义了480 个具体任务和1,948 条精确的评分标准。
关键细节:评分标准(rubric)的制定标准是“客户是否会接受这个交付物”——不是学术上的”答案是否正确”,而是商业上的”这个分析能不能呈给客户”。每个任务有 1-10 个 pass/fail 判定标准。
第四步:评估(Evals)
使用开源框架 Archipelago,将 Agent 部署到模拟世界中,通过 MCP 协议与环境交互,快照对比评分。
Agent 失败模式分析
APEX-Agents 博客文章明确指出,Agent 的失败不是因为”不够聪明”,而是因为三个看似基础但极其关键的能力缺失:
失败模式一:无法管理模糊性(Ambiguity)
现实工作中的指令往往不完整、含糊。比如经理在 Chat 里说”帮我更新一下那个模型”——“那个”指哪个模型?需要从邮件上下文、最近的会议记录、文件修改历史中推理出来。
大部分 Agent 在遇到模糊指令时要么瞎猜、要么停滞,缺乏主动澄清或上下文推理的能力。
失败模式二:找不到正确的文件
在复杂的文件系统中导航失败——这正是 Box 合作要解决的问题。真实的数据室可能有几十个子目录、上百个文件,命名不规范(Q3_Final_v2_CLEAN.xlsx vs q3-analysis-backup.xlsx),Agent 需要理解文件名语义、目录结构语义、以及邮件中对文件的间接引用。
失败模式三:无法在长流程中保持上下文
APEX-Agents 的平均任务耗时 1.82 小时(人类),Agent 需要几十步工具调用。在这个过程中,Agent 需要持续追踪多个信息源(邮件说了什么、Excel 里的数字是多少、之前的计算结果是什么),并在整个工作流中保持一致。
当前 Agent 的上下文窗口虽然已经很大(128K+ tokens),但有效利用率极低——大量 token 被工具输出占据,真正重要的信息被淹没。
更深层的启示
这三个失败模式指向同一个根本问题:当前的 Agent 架构是为”单次问答”设计的,不是为”多小时的项目型工作”设计的。职场场景中,信息是杂乱的、不完整的,分散在文档和聊天记录中。大部分基准测试用的是整洁的、自包含的 prompt——它们无法检测出这种能力缺失。
APEX 基准测试家族
APEX-Agents 不是孤立的产品。它加入了 Mercor 的评测生态系统:
| 基准测试 | 定位 | 评测维度 |
|---|---|---|
| APEX v1 | 无工具评估 | 纯推理/知识能力(无 Agent 工具调用) |
| ACE | 消费者应用 | 日常消费级应用场景 |
| APEX-Agents | 专业 Agent | 投行/咨询/法务的多步骤工具使用 |
| APEX-SWE | 软件工程 | 代码修复/开发类任务 |
四个基准覆盖了从”纯推理”到”复杂 Agent”的完整能力谱。
第一章 · 数据集全景
1.1 基本规模
| 维度 | 数值 |
|---|---|
| 总任务数 | 480 |
| 总世界数 | 33 |
| 领域分布 | 投行 160 / 咨询 160 / 法务 160 |
| 评分标准总数 | 1,948 条(平均 4.1 条/任务) |
| 平均完成时间(人类专家) | 1.82 小时 |
| 数据集大小 | ~8.5 GB |
| 许可证 | CC-BY 4.0(仅评测,禁止训练) |
1.2 任务输出类型分布
| 输出类型 | 数量 | 占比 |
|---|---|---|
| 文本回复(message_in_console) | 422 | 87.9% |
| 新建文档(make_new_doc) | 20 | 4.2% |
| 编辑表格(edit_existing_sheet) | 16 | 3.3% |
| 新建表格(make_new_sheet) | 14 | 2.9% |
| 新建演示文稿(make_new_slide_deck) | 5 | 1.0% |
| 编辑文档(edit_existing_doc) | 2 | 0.4% |
| 编辑演示文稿(edit_existing_slide_deck) | 1 | 0.2% |
87.9% 的任务要求文本回复,12.1% 要求产出文件——这意味着大部分任务考察的是信息检索 + 分析推理能力,少部分考察文档生成能力。
1.3 Rubric 评分标准
每个任务有 1-10 条二值评分标准(Met / Not Met),由 Judge 模型独立评估每条。
示例(投行任务):
任务: 计算 Kenvue 交易的 FY24 税前目标倍数
Rubric:
✓ States Deal value / EBIT is 24.9x
✓ States Deal value / EBITDA is 18.6x
✓ Deal value / OpFCF is 34.5x
示例(法务任务):
任务: 供应商能否基于关税援引不可抗力条款?
Rubric:
✓ 正确识别 Template 1 的 Section 14 为不可抗力条款
✓ 指出 25% 关税阈值
✓ 确认 35% > 25%,供应商可以援引
✓ 引用了伊拉克原材料来源这一关键事实
这种设计让评测可以精确衡量 Agent 的推理链是否完整——不只是最终答案对不对,还要看关键推理步骤有没有覆盖。
第二章 · World 数据结构:真实的 Google Takeout 导出
2.1 核心发现
解压 World 文件后,发现数据不是合成的,而是真实的 Google Workspace 数据导出。证据:
- 邮件格式:标准 MBOX 文件,含完整的 DKIM 签名、SMTP 路由头、SPF 验证记录
- 日历格式:标准 iCalendar (.ics),
PRODID:-//Google Inc//Google Calendar 70.9054//EN - 聊天格式:Google Chat 的 JSON 导出,含 message_id、topic_id、reactions(emoji + reactor 列表)
- 文件系统:真实的 PDF 年报(AB InBev、Kellanova、PepsiCo)、Excel 财务模型、Word 文档、PPT
2.2 目录结构
world_xxx/
├── .apps_data/ ← 应用数据
│ ├── calendar/
│ │ └── Calendar/
│ │ ├── user1@c-mercor.com.ics ← iCalendar 标准格式
│ │ └── user2@c-mercor.com.ics
│ ├── chat/
│ │ └── Google Chat/
│ │ ├── Groups/
│ │ │ ├── DM xxx/messages.json ← 私聊
│ │ │ └── Space xxx/messages.json ← 群组
│ │ └── Users/
│ │ └── User xxx/
│ │ ├── user_info.json ← 身份 + 群组成员
│ │ └── unsentmessages.json
│ └── mail/
│ └── Mail/
│ └── All mail Including Spam and Trash.mbox ← Gmail 导出
└── filesystem/ ← 工作文件
└── 4. Research/
├── Data_Tracker.xlsx
├── Assumptions_Validation.docx
└── Company background and research/
├── Anheuser-Busch/ ← 12 个 PDF/Excel(年报+财报+股价)
├── Kellanova/ ← 15 个文件
└── Pepsi/ ← 10 个文件
2.3 数据格式实例
邮件(MBOX)——完整的 RFC 2822 格式,含路由头:
From 1848045044579534667@xxx Thu Nov 06 12:44:54 +0000 2025
X-GM-THRID: 1848045044579534667
X-Gmail-Labels: Inbox,Opened,Category Updates
Delivered-To: world113.consulting@c-mercor.com
DKIM-Signature: v=1; a=rsa-sha256; d=google.com; ...
Subject: Your Google data is ready to download
From: Google Takeout <noreply@google.com>
日历(ICS)——标准 VEVENT:
BEGIN:VEVENT
DTSTART:20251030T160000Z
DTEND:20251030T170000Z
ORGANIZER;CN=nile.amethyst@c-mercor.com
SUMMARY:Project Alignment Meeting
ATTENDEES:...
END:VEVENT
聊天(JSON)——Google Chat 导出格式:
{
"messages": [
{
"creator": {
"name": "joss amethyst",
"email": "joss.amethyst@c-mercor.com",
"user_type": "Human"
},
"created_date": "Wednesday, November 19, 2025 at 10:28:24 AM UTC",
"text": "Hi team, thanks for joining the space. I shared a short meeting invite...",
"reactions": [
{
"emoji": { "unicode": "\ud83d\udc4d" },
"reactor_emails": ["torin.amethyst@c-mercor.com", "jeran.amethyst@c-mercor.com"]
}
]
}
]
}
2.4 虚拟公司设计
所有用户使用 @c-mercor.com 域名,人名采用颜色代号(amethyst、amber 等)。这是匿名化处理——真实的专业人士在 Google Workspace 中创建了完整的工作场景,然后通过 Google Takeout 导出。
第三章 · Archipelago 框架:MCP 驱动的 Agent 执行环境
3.1 整体架构
┌───────────────────────────────────────────────────────┐
│ Agent(被评测的 LLM) │
│ 通过 MCP 协议与环境交互 │
└────────────────────────┬──────────────────────────────┘
│ MCP Protocol (JSON-RPC 2.0)
┌────────────────────────▼──────────────────────────────┐
│ Environment Container (Docker) │
│ port 8080 │
│ ┌──────────────────────────────────────────────────┐ │
│ │ MCP Gateway(热插拔路由) │ │
│ │ POST /apps → 动态配置 MCP Server │ │
│ │ GET /mcp → 统一 MCP 端点 │ │
│ └───────────┬──────────────────────────────────────┘ │
│ │ stdio │
│ ┌───────────▼──────────────────────────────────────┐ │
│ │ 9 个 MCP Server 进程 │ │
│ │ mail | chat | calendar | filesystem | code │ │
│ │ spreadsheets | documents | pdfs | presentations │ │
│ └───────────┬──────────────────────────────────────┘ │
│ │ │
│ ┌───────────▼──────────────────────────────────────┐ │
│ │ 数据层(World 快照) │ │
│ │ /.apps_data/ → mail/.mbox, calendar/.ics, ... │ │
│ │ /filesystem/ → PDF, Excel, Word, PPT │ │
│ └──────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────┘
3.2 MCP Server 配置方式
每个 World 启动时,通过 POST 请求动态配置 MCP Server:
config = {
"mcpServers": {
"mail_server": {
"transport": "stdio",
"command": "python",
"args": ["main.py"],
"cwd": "./mcp_servers/mail_server"
},
"calendar_server": {
"transport": "stdio",
"command": "python",
"args": ["main.py"],
"cwd": "./mcp_servers/calendar_server"
},
# ... 其他 server
}
}
requests.post("http://localhost:8080/apps", json=config)
配置完成后,Agent 通过 http://localhost:8080/mcp/ 统一端点访问所有工具。
3.3 评分机制
任务开始 → 创建初始快照(tar.gz)
↓
Agent 执行(多步 MCP 工具调用)
↓
任务结束 → 创建最终快照
↓
Grading 系统:
1. 对比初始/最终快照的 diff
2. 提取 Agent 的文本输出
3. Judge 模型逐条评估 rubric 标准
4. 汇总二值评分
第四章 · 源码级验证:MCP Server 如何加载真实数据
以下分析基于 Archipelago 开源仓库(github.com/Mercor-Intelligence/archipelago)的实际源码,逐一验证每个关键环节。
4.1 Mail Server:Python mailbox 解析 .mbox
数据加载路径(utils/config.py):
# 数据根目录默认指向 /.apps_data/mail
_apps_data_root = os.getenv("APP_APPS_DATA_ROOT", "/.apps_data")
MAIL_DATA_ROOT = os.getenv("APP_MAIL_DATA_ROOT") or os.path.join(
_apps_data_root, "mail"
)
MBOX_FILENAME = os.getenv("APP_MAIL_MBOX_FILENAME", "sent.mbox")
自动发现 .mbox 文件(utils/path.py):
def get_mbox_path() -> str:
mail_dir = resolve_mail_path("")
if os.path.exists(mail_dir):
for root, _, files in os.walk(mail_dir):
mbox_files = [f for f in files if f.endswith(".mbox")]
if mbox_files:
return os.path.join(root, mbox_files[0]) # 返回第一个找到的 .mbox
return os.path.join(mail_dir, MBOX_FILENAME) # 回退到默认文件名
启动时递归扫描 /.apps_data/mail/ 目录,找到 Google Takeout 导出的 All mail Including Spam and Trash.mbox 文件。
解析邮件(utils/mbox_utils.py)——使用 Python 标准库 email 模块:
def parse_message_to_dict(message) -> dict:
to_list = parse_email_list(message.get("To", ""))
cc_list = parse_email_list(message.get("Cc", "")) or None
# 提取正文:优先 text/plain,其次 text/html
body = ""
if message.is_multipart():
for part in message.walk():
if part.get_content_type() == "text/plain":
body = part.get_payload(decode=True).decode("utf-8", errors="ignore")
break
else:
body = message.get_payload(decode=True).decode("utf-8", errors="ignore")
# 提取线程信息
thread_id = message.get("X-Thread-ID", None)
in_reply_to = message.get("In-Reply-To", None)
references = message.get("References", "").split() if message.get("References") else None
return {
"mail_id": message.get("Message-ID", ""),
"timestamp": message.get("Date", ""),
"from": parseaddr(message.get("From", ""))[1],
"to": to_list,
"subject": message.get("Subject", ""),
"body": body,
"thread_id": thread_id,
"in_reply_to": in_reply_to,
"references": references,
# ...
}
发送邮件 = 追加到 .mbox 文件(tools/send_mail.py)——没有 SMTP,直接写文件:
def send_mail(input: SendMailInput) -> str:
# 构造 EmailMessage 对象
msg = EmailMessage()
msg["From"] = from_email
msg["To"] = ", ".join(to_list)
msg["Subject"] = subject
msg["Date"] = formatdate(timestamp.timestamp(), localtime=True)
msg["Message-ID"] = make_msgid(domain=from_email.split("@")[-1])
msg.set_content(body)
# 追加到 mbox 文件(不是发送到 SMTP 服务器)
mbox_path = get_mbox_path()
mbox = mailbox.mbox(mbox_path)
mbox.lock()
try:
mbox.add(msg) # 追加到文件末尾
mbox.flush() # 写入磁盘
finally:
mbox.unlock()
mbox.close()
关键确认:main.py 第 30 行的 instructions 字段明确写道:
“Email stored in mbox format in a sandboxed directory. No external SMTP/IMAP.”
4.2 Calendar Server:icalendar 库解析 .ics
数据加载路径(utils/config.py):
_apps_data_root = os.getenv("APP_APPS_DATA_ROOT", "/.apps_data")
CALENDAR_DATA_ROOT = os.getenv("APP_CALENDAR_DATA_ROOT") or os.path.join(
_apps_data_root, "calendar"
)
读取所有 .ics 文件(utils/ical.py):
def get_all_ical_files() -> list[Path]:
calendar_dir = Path(resolve_calendar_path(""))
return sorted(calendar_dir.rglob("*.ics")) # 递归扫描所有 .ics
def read_calendar_from_file(file_path: Path) -> Calendar:
with open(file_path, "rb") as f:
return Calendar.from_ical(f.read()) # 使用 icalendar 库解析
def get_all_events() -> list[CalendarOutputEvent]:
events = []
for ical_file in get_all_ical_files():
calendar = read_calendar_from_file(ical_file)
for component in calendar.walk():
if component.name == "VEVENT":
event = ical_event_to_calendar_event(component)
events.append(event)
return events
创建/更新事件 = 修改 .ics 文件:
def add_event_to_calendar(event, calendar_file=None):
file_path = calendar_dir / (calendar_file or "calendar.ics")
if file_path.exists():
calendar = read_calendar_from_file(file_path) # 读现有日历
else:
calendar = Calendar() # 或创建新的
calendar.add_component(calendar_event_to_ical(event)) # 加事件
write_calendar_to_file(calendar, file_path) # 写回文件
def update_event_in_calendar(event):
calendar, old_event, file_path = find_event_in_calendars(event.id)
calendar.subcomponents.remove(old_event) # 删旧
calendar.add_component(calendar_event_to_ical(event)) # 加新
write_calendar_to_file(calendar, file_path) # 写回
def delete_event_from_calendar(event_id):
calendar, event, file_path = find_event_in_calendars(event_id)
calendar.subcomponents.remove(event) # 删除
write_calendar_to_file(calendar, file_path) # 写回
关键确认:没有 Google Calendar API 调用,全部是本地 .ics 文件的读写操作。
4.3 Chat Server:直接加载 Google Chat JSON
数据加载路径(utils/config.py):
_apps_data_root = os.getenv("APP_APPS_DATA_ROOT", "/.apps_data")
CHAT_DATA_ROOT = os.getenv("APP_CHAT_DATA_ROOT") or os.path.join(
_apps_data_root, "chat"
)
# 当前用户身份
CURRENT_USER_ID = os.getenv("CHAT_CURRENT_USER_ID", "User 000000000000000000000")
CURRENT_USER_EMAIL = os.getenv("CHAT_CURRENT_USER_EMAIL", "user@example.com")
Chat Server 直接读取 Google Chat Takeout 导出的 JSON 目录结构:
/.apps_data/chat/Google Chat/
├── Groups/
│ ├── DM xxx/messages.json ← 私聊消息
│ └── Space xxx/messages.json ← 群组消息
└── Users/
└── User xxx/user_info.json ← 用户身份和群组成员
4.4 快照机制:评分前后的状态 diff
快照创建(environment/runner/data/snapshot/main.py):
async def handle_snapshot(pre_snapshot_hooks=None):
snapshot_id = f"snap_{uuid().hex}"
# 快照包含两个子系统
subsystems = [
settings.FILESYSTEM_SUBSYSTEM_NAME, # "filesystem"
settings.APPS_DATA_SUBSYSTEM_NAME # ".apps_data"
]
# 创建 tar.gz 归档并流式传输
return create_tar_gz_stream(subsystems, snapshot_id, iter_paths), filename
快照覆盖 filesystem/ 和 .apps_data/ 两个目录——也就是说,Agent 对文件系统的修改(新建文档、编辑 Excel)和对应用数据的修改(发送邮件、创建日程、发送消息)都会被完整捕获。
评分流程:
任务开始 → POST /data/snapshot → 初始快照(snap_xxx_before.tar.gz)
↓
Agent 执行(通过 MCP 工具修改 .mbox/.ics/.json/文件)
↓
任务结束 → POST /data/snapshot → 最终快照(snap_xxx_after.tar.gz)
↓
Grading 系统:diff(before, after) + Agent 输出 → 逐条 rubric 评估
4.5 FastMCP 框架:统一注册工具
所有 MCP Server 使用 FastMCP 框架(Python 的 MCP SDK),工具注册方式统一:
from fastmcp import FastMCP
mcp = FastMCP("mail-server", instructions="...")
mcp.add_middleware(ErrorHandlingMiddleware(include_traceback=True))
mcp.add_middleware(RetryMiddleware())
mcp.add_middleware(LoggingMiddleware())
# 注册工具(两种模式二选一)
if os.getenv("USE_INDIVIDUAL_TOOLS"):
mcp.tool(list_mails) # 7 个独立工具
mcp.tool(read_mail)
mcp.tool(send_mail)
# ...
else:
mcp.tool(mail) # 2 个合并工具
mcp.tool(mail_schema)
# 启动(stdio 或 http)
mcp.run(transport="stdio") # 与 MCP Gateway 通过 stdin/stdout 通信
4.6 总结:完整的数据流
Google Takeout 导出
│
▼
World 数据包(.mbox + .ics + messages.json + PDF/Excel/Word)
│
▼ 部署到 Docker Container
│
/.apps_data/mail/ ← Mail MCP Server 用 mailbox.mbox() 读写
/.apps_data/calendar/ ← Calendar MCP Server 用 icalendar 库读写
/.apps_data/chat/ ← Chat MCP Server 直接读写 JSON
/filesystem/ ← Filesystem/Spreadsheets/Documents/PDFs Server 操作
│
▼ MCP 协议(JSON-RPC 2.0 over stdio)
│
MCP Gateway(port 8080,热插拔路由)
│
▼ MCP 协议
│
Agent(被评测的 LLM)
没有一行代码调用了 Gmail API / Google Calendar API / Google Chat API。
所有操作都是对本地文件的 CRUD——Python 标准库 mailbox 读写 .mbox,icalendar 库读写 .ics,标准 json 模块读写 messages.json。MCP 协议只是把这些文件操作包装成了 Agent 可调用的工具接口。
4.1 核心 9 服务(所有 World 共有)
| MCP Server | 工具数 | 数据源 | 具体 Tools |
|---|---|---|---|
| 7 | .mbox | list_mails, read_mail, search_mail, send_mail, reply_mail, reply_all_mail, forward_mail | |
| Chat | 9 | messages.json | list_channels, get_channel_history, get_thread_replies, get_user_profile, get_users, post_message, reply_to_thread, add_reaction, delete_post |
| Calendar | 5 | .ics | list_events, read_event, create_event, update_event, delete_event |
| Filesystem | - | 目录树 | 文件读写、目录浏览 |
| Spreadsheets | - | .xlsx | Excel 读写 |
| Documents | - | .docx | Word 读写 |
| PDFs | - | PDF 解析提取 | |
| Presentations | - | .pptx | PPT 操作 |
| Code | - | Python 环境 | 代码执行 |
4.2 投行专用服务(2 个 World 额外配置)
| MCP Server | 用途 |
|---|---|
| Edgar SEC | SEC 文件检索(10-K、10-Q、8-K 等) |
| FMP | Financial Modeling Prep——金融市场数据 API |
4.3 App 覆盖率
9 个核心服务在所有 33 个 World 中100% 覆盖。Edgar SEC 和 FMP 仅出现在 2 个投行 World 中(6%)。
4.4 Mail Server 工具详解
Mail Server 是最复杂的 MCP 服务之一,提供两种操作模式:
默认模式(7 个独立工具):
| 工具 | 参数 | 说明 |
|---|---|---|
list_mails | limit(1-100), offset | 分页列出邮件 |
read_mail | mail_id | 按 Message-ID 读取完整邮件 |
search_mail | from_email, to_email, subject, body | 多字段部分匹配搜索 |
send_mail | from, to, subject, body, attachments | 发送新邮件 |
reply_mail | original_mail_id, body | 回复单人 |
reply_all_mail | original_mail_id, body | 回复所有人 |
forward_mail | original_mail_id, to_email | 转发 |
合并模式(Consolidated Mode)——所有操作通过一个 mail 工具入口:
mail(action="send", to_email="alice@c-mercor.com", subject="Q3 Review", body="...")
mail(action="search", subject="budget", after_date="2025-11-01")
mail(action="list", limit=20)
合并模式的设计意图:减少 Agent 需要记住的工具数量,用 action 参数做路由。这降低了工具选择的认知负担,但增加了参数组合的复杂度。
4.5 Chat Server 工具详解
Chat Server 模拟了类 Slack/Google Chat 的群组消息系统:
| 工具 | 说明 |
|---|---|
list_channels | 列出频道(DM + Space) |
get_channel_history | 获取频道消息历史(默认 30 条) |
get_thread_replies | 获取帖子回复 |
get_user_profile | 获取用户资料 |
get_users | 列出所有用户 |
post_message | 发送消息到频道 |
reply_to_thread | 回复帖子 |
add_reaction | 添加 emoji 反应 |
delete_post | 删除帖子 |
4.6 Calendar Server 工具详解
标准 CRUD:
| 工具 | 参数 |
|---|---|
list_events | limit |
read_event | event_id |
create_event | summary, start, end, attendees, location, reminders |
update_event | event_id + 任意字段 |
delete_event | event_id |
第六章 · 典型任务解剖
5.1 投行任务——交易倍数计算
Prompt: 计算 Kenvue 交易的 FY24 税前目标倍数,使用中位数潜在交易价值。
返回 Deal value/EBITDA、Deal value/EBIT、Deal value/OpFCF,保留一位小数。
需要的能力链:
1. filesystem → 找到相关 Excel 模型
2. spreadsheets → 读取财务数据(EBITDA、EBIT、OpFCF)
3. spreadsheets → 找到中位数交易价值
4. code → 计算三个比率
5. 输出文本结果
Gold: Deal value/EBIT: 24.9x, Deal value/EBITDA: 18.6x, Deal value/OpFCF: 34.5x
5.2 法务任务——合同条款分析
Prompt: 供应商原材料来自伊拉克。特朗普 7 月 20 日宣布额外关税。
供应商能否基于 Master Supply Agreement Template 1 援引不可抗力条款?
需要的能力链:
1. filesystem → 找到 Master Supply Agreement 文档
2. documents/pdfs → 读取合同全文
3. 定位 Section 14(不可抗力条款)
4. 理解 25% 关税阈值条件
5. 推理:35% > 25% → 可以援引
6. 输出分析结论
Gold: 可以。Section 14 规定关税增幅超过 25% 即可援引。
伊拉克商品关税 35% > 25%,供应商有权援引。
5.3 咨询任务——投资组合分析
Prompt: Rakling 的强卖建议会为 HP 带来多少欧元现金?
假设以 2025 年 10 月 30 日价格出售,无交易成本。
需要的能力链:
1. mail/chat → 理解 Rakling 是谁、强卖建议指什么
2. filesystem → 找到投资组合数据
3. spreadsheets → 读取持仓和价格数据
4. code → 计算总收益(EUR 77,977,663.40)
5. 输出精确数字
Gold: EUR 77,977,663.40
第七章 · Leaderboard 分析
6.1 当前排名(2026.04)
| 模型 | Pass@1 | Pass@8 | Mean Score | IB | 咨询 | 法务 |
|---|---|---|---|---|---|---|
| GPT-5.2 | 23.0% | 40.0% | 38.7% | 27.3% | 22.7% | 18.9% |
| Gemini 3 Flash | 24.0% | 36.7% | 39.5% | 26.7% | 19.3% | 25.9% |
| Claude Opus 4.5 | 18.4% | 34.0% | 34.8% | 21.6% | 13.2% | 20.2% |
| Gemini 3 Pro | 18.4% | 37.3% | 34.1% | 18.8% | 12.4% | 23.9% |
| GPT-5 | 18.3% | 31.0% | 32.9% | 27.3% | 12.3% | 15.3% |
| Grok 4 | 15.2% | 32.9% | 30.3% | 17.0% | 12.0% | 16.5% |
| Kimi K2 Thinking | 4.0% | 14.4% | 11.5% | 1.2% | 2.9% | 8.0% |
| GPT-OSS-120B | 4.7% | 11.5% | 14.5% | 2.7% | 3.5% | 7.8% |
6.2 关键观察
整体水平极低:最强模型 Pass@1 仅 23%,意味着 77% 的专业任务 Agent 无法一次做对。
领域差异显著:
- 投行任务(数值计算为主)相对最好——GPT-5 和 GPT-5.2 均达 27.3%
- 咨询任务(综合分析)最难——即使 GPT-5.2 也仅 22.7%
- 法务任务(合同推理)中间——Gemini 3 Flash 表现最好(25.9%),可能受益于长上下文能力
Pass@1 vs Pass@8 差距大:GPT-5.2 从 23% 到 40%,说明模型有能力但不稳定——同样的任务跑 8 次,至少有一次对的概率翻了近一倍。
开源模型差距悬殊:GPT-OSS-120B(推测为开源 120B 模型)仅 4.7%,与商业模型差距 5 倍以上。
6.3 为什么这么难
- 长程依赖:平均 1.82 小时的人类任务,Agent 需要数十步工具调用
- 跨应用推理:答案散落在邮件、聊天、Excel、PDF 中,需要交叉验证
- 隐性知识:投行的”中位数交易价值”、法务的”不可抗力条款触发条件”等专业概念
- 精确性要求:财务计算要求精确到分(EUR 77,977,663.40),不是”大约 7800 万”
第八章 · 对比分析
7.1 APEX-Agents vs 其他 Agent Benchmark
| 维度 | APEX-Agents | GAIA | SWE-bench | OSWorld | TheAgentCompany |
|---|---|---|---|---|---|
| 领域 | 金法医 | 通用 | 软件工程 | 操作系统 | 企业软件 |
| 数据来源 | 真实 Google Takeout | 人工构造 | GitHub Issue | 手动设计 | 手动设计 |
| 交互协议 | MCP | Function Calling | Code-as-Action | GUI/Code | 混合 |
| 工具数量 | 9-11 个 MCP Server | 按需 | Shell + 编辑器 | 整个 OS | 企业 SaaS |
| 平均任务时长 | 1.82 小时 | 分钟级 | 分钟级 | 分钟级 | 小时级 |
| 评分方式 | Rubric 多标准 | 精确匹配 | 测试通过率 | 状态检查 | Rubric |
| 最高 Pass@1 | 24% | ~70% | ~53% | ~22% | ~25% |
| 环境真实性 | 高(真实数据) | 低 | 高(真实代码库) | 最高(真实 VM) | 高 |
7.2 APEX-Agents 的独特价值
- 真实数据而非合成:Google Takeout 导出保留了真实的邮件上下文、团队动态、文件版本历史
- 专业深度:由投行分析师、律师、顾问亲自设计的任务,不是通用的”帮我搜索一下”
- MCP 标准化:工具接口使用 MCP 协议,可直接对接任何支持 MCP 的 Agent 框架
- 精确可验证:rubric 标准是二值的(对/错),不需要主观判断
7.3 局限性
- 仅三个领域:金融、法律、咨询——不覆盖医疗、科研、工程等
- 单次尝试评测:没有错误恢复/多次尝试的机制
- 无协作维度:Agent 独自工作,没有人机交互或多 Agent 协作
- Web 搜索禁用:为了可复现性,禁止了 Web 搜索——但真实工作中搜索是核心能力
- CC-BY 4.0 但禁止训练:明确禁止用于模型训练,仅限评测
第九章 · 对数据合成的启示
8.1 World 构建方法论可复用
APEX-Agents 的 World 构建方法本身就是一套数据合成方法论:
真实专家 + Google Workspace → Google Takeout 导出 → 匿名化 → World 数据包
这个流程可以复制到其他领域:
- 医疗:临床医生的邮件 + 病历系统 + 电子处方
- 科研:研究员的论文管理 + 实验记录 + 通讯协作
- 教育:教师的 LMS + 学生作业 + 家长沟通
8.2 MCP Server 是 Harness 反转的好载体
APEX-Agents 的 9 个 MCP Server 定义了清晰的工具接口规范。这些规范可以直接用来生成训练数据:
| 工具接口 | 可生成的训练数据 |
|---|---|
mail(action="search", subject="budget") | 邮件检索 + 条件过滤任务 |
spreadsheets.read_cell(sheet, range) | Excel 数据提取 + 计算任务 |
calendar.create_event(...) | 日程管理 + 冲突检测任务 |
| 多工具组合 | 跨应用推理链(邮件→Excel→回复) |
8.3 Rubric 设计模式值得借鉴
APEX-Agents 的 rubric 不检查”最终答案是否等于 gold”,而是检查推理链的关键节点:
不是: answer == "EUR 77,977,663.40"
而是:
✓ 正确识别了 Rakling 的强卖建议
✓ 找到了正确的持仓数据
✓ 使用了 10 月 30 日的价格
✓ 最终金额正确
这种设计让评测更加鲁棒——即使最终数字因四舍五入略有差异,只要推理链正确,仍能获得部分分数。
核心论文与参考索引
| 项目 | 详情 |
|---|---|
| APEX-Agents 论文 | arXiv:2601.14242 (Mercor, 2026.01) |
| Archipelago 框架 | github.com/Mercor-Intelligence/archipelago |
| 数据集 | huggingface.co/datasets/mercor/apex-agents |
| Leaderboard | mercor.com/apex/apex-agents-leaderboard |
| MCP 协议 | modelcontextprotocol.io |
第十章 · 关键设计决策分析:为什么这样而不那样
APEX-Agents 的架构中有一系列非显而易见的设计决策。这些决策不是随意的——每一个都在评测可信度、工程成本、安全性、可扩展性之间做了精心的 trade-off。本章逐一分析。
10.1 为什么用 Google Takeout 真实数据,而不是合成数据
决策:从真实专业人士的 Google Workspace 导出数据,匿名化后作为评测环境。
替代方案:用 LLM 合成虚拟邮件/聊天/文件(TheAgentCompany 就是这么做的)。
选择真实数据的原因:
-
信息密度不可合成。真实投行分析师的收件箱有几十封邮件构成的因果链——A 发给 B 一个 Excel 模型,B 回复说第三行有误,A 修改后抄送 C 确认。LLM 合成这种多人多轮、跨应用的信息网络,容易出现逻辑断裂。
-
隐性上下文真实。真实数据中,聊天里会出现”那个文件你看了吗”但不说是哪个文件——因为上下文对真实用户是显然的。这种省略正是 Agent 需要从邮件和文件系统中推理出来的。合成数据往往过于显式。
-
文件生态真实。真实年报是 200 页 PDF,真实财务模型是多 sheet 的 Excel,真实备忘录用了特定的 Word 模板。这些格式细节(扫描页、嵌套公式、DUMMYFUNCTION 伪公式)是 Agent 在真实工作中必然遇到的。
代价:数据收集成本高(需要招募专业人士、配置 Workspace、执行真实工作流、Google Takeout 导出、匿名化处理)。CC-BY 4.0 但禁止训练,也限制了数据的二次利用。
10.2 为什么用 MCP 模拟层,而不是跑真实服务
决策:用 Python 解析 .mbox/.ics/.json 文件,通过 MCP 协议暴露标准化 CRUD 工具。不跑真实的 Gmail/Calendar/Chat 服务。
替代方案:在 Docker 中部署 Roundcube(Web Mail)+ Radicale(CalDAV)+ Rocket.Chat 等真实服务,让 Agent 通过 API 或 GUI 操作。
选择模拟层的原因:
-
快照可行性。评分需要对比初始和最终状态的 diff。对文件系统做
tar.gz快照是毫秒级的;对运行中的 IMAP 服务器做状态快照几乎不可能——你需要 dump 整个邮件数据库。 -
确定性与可复现。同一个 World 的每次评测必须从完全相同的初始状态开始。文件快照天然满足这个要求;而真实服务有后台进程、缓存、时间戳等不可控因素。
-
部署成本。MCP Server 是单个 Python 进程,用
fastmcp几百行代码就能实现完整的 CRUD。而部署一套企业邮件+日历+聊天系统需要多个容器、数据库、认证服务——仅 Roundcube 一个就需要 Postfix + Dovecot + MySQL。 -
安全隔离。Agent 的”发送邮件”操作只是在
.mbox文件末尾追加一条记录,不会真的发出去。如果用真实 SMTP,需要额外的安全措施防止 Agent 向外部发送邮件。
代价:Agent 不需要处理真实的认证流程、网络延迟、API 限流等工程问题。推测这会高估 Agent 在真实部署时的表现——但在评测”理解任务并正确操作”这个核心能力上,模拟层已经足够。
10.3 为什么提供合并模式(Consolidated Mode)
决策:每个 Server 提供两种工具注册模式——7+ 个独立工具 vs 2 个 meta-tools(如 mail(action="send", ...))。
分析:
这不是一个可有可无的功能,而是一个经过实验验证的设计。源码中 main.py 的注释明确区分了两种场景:
USE_INDIVIDUAL_TOOLS=true → "7 individual tools for UI display"
USE_INDIVIDUAL_TOOLS=false → "2 meta-tools for LLM agents"
对 LLM Agent 的影响:
| 方面 | 独立模式(7+ 工具) | 合并模式(2 工具) |
|---|---|---|
| 工具选择准确率 | 低——Agent 需要从 7+ 工具中选对一个 | 高——只需选 mail,action 参数在描述中列出 |
| 参数填写复杂度 | 低——每个工具参数明确 | 高——需要根据 action 填写不同的参数组合 |
| Token 消耗 | 高——所有工具的 schema 都要塞进 system prompt | 低——只有 2 个工具的 schema |
| 调试可读性 | 高——直接看工具名就知道做了什么 | 低——需要看 action 参数 |
OpenAI 的研究表明,当工具数超过 20 个时,模型的工具选择准确率显著下降。APEX-Agents 全量 9 个 Server × 平均 8 个工具 = 72 个工具——这远超 20 个的临界点。合并模式把 72 个工具压缩到 18 个(9 × 2),大幅降低了 Agent 的认知负担。
10.4 为什么 Filesystem 是只读的
决策:filesystem_server 只提供 list/read/search/metadata/tree 操作,没有 write/create/delete。
看似矛盾:Agent 的某些任务需要创建新文件(12.1% 的任务要求文件输出)。
实际设计:文件写入由其他 Server 负责——documents_server 创建 Word,sheets_server 创建 Excel,slides_server 创建 PPT,pdf_server 创建 PDF,code_execution_server 可以执行任意 shell 命令(包括 touch/echo 等)。
这种分离的原因:
-
最小权限原则。文件浏览器只需要读权限,不应该能写。把读和写分到不同的 Server,可以对每个 Server 设置不同的安全策略。
-
结构化写入。直接给 Agent 一个
write_file(path, content)工具,Agent 可能直接写入原始文本而不遵循文档格式。通过documents_server的create_document工具,Agent 被迫使用结构化的参数(段落、标题、表格),输出的.docx更可能是格式正确的。 -
快照粒度。读操作不改变状态,写操作改变状态。分离后,评分系统可以只 diff 那些有写入权限的 Server 管理的目录。
10.5 为什么 Code Execution 用 LD_PRELOAD 沙箱
决策:通过 LD_PRELOAD=sandbox_fs.so 拦截 libc 文件系统调用来实现沙箱,屏蔽 /app 和 /.apps_data 目录。
替代方案:Docker 容器隔离(已有)、proot、gVisor、seccomp、chroot。
选择 LD_PRELOAD 的原因(源码注释明确说明):
“Unlike proot, this approach has no ptrace overhead, works on unprivileged container platforms (Fargate, Modal, Fly.io), and is purely userspace with no kernel privileges needed.”
翻译:
-
零 ptrace 开销。proot 通过 ptrace 拦截系统调用,每个文件操作都需要内核上下文切换,性能损失 30-50%。LD_PRELOAD 在用户空间拦截 libc 调用,几乎零开销。
-
兼容无特权容器。Fargate、Modal、Fly.io 等 serverless 平台不允许 ptrace 或修改内核。LD_PRELOAD 只需要一个
.so文件,任何 Linux 环境都能用。 -
Fail-closed 设计。源码中每次执行前都检查
sandbox_fs.so是否存在——如果不存在,拒绝执行而不是无沙箱运行。这比 LD_PRELOAD 的默认行为(找不到库就跳过,打个警告继续执行)安全得多。
10.6 为什么 Chat 的删除是软删除
决策:delete_post 设置 message_state="DELETED" + 清空内容,但保留在 messages.json 中。
原因:
-
评分可追溯。如果 Agent 删除了一条不该删的消息,评分系统需要能检测到”曾经存在过一条消息被错误删除了”。硬删除后这个信息就丢了。
-
模拟真实行为。Google Chat 和 Slack 的删除本身就是软删除——消息显示”此消息已被删除”,但 admin 仍可查看。
-
防止数据丢失。Agent 的操作不可逆(没有 undo 机制),软删除相当于内置了一层安全网。
10.7 为什么 Spreadsheets 需要 LibreOffice
决策:openpyxl 读写 Excel,但需要调用 soffice --headless 来重新计算公式。
根因:openpyxl 的 data_only=True 模式只读取上次保存时缓存的公式结果。如果 Agent 修改了某个单元格的值,依赖它的公式不会自动重算——openpyxl 不是计算引擎。
解决方案:修改后调用 LibreOffice 做一次 headless 转换,LibreOffice 的计算引擎会重新计算所有公式并缓存结果。
这对 Agent 评测的影响:Agent 修改了 Excel 后,read_tab 能读到正确的公式结果(而不是过期的缓存值)。这对财务分析类任务至关重要——一个错误的中间值会导致后续所有计算出错。
10.8 为什么 PDF 需要双引擎 + OCR
决策:read_pdf_pages 先用 PyMuPDF(fitz),空则回退 pypdf layout mode,再空则 OCR fallback。
原因:World 数据包含真实的公司年报——这些 PDF 的复杂度远超普通文档:
- 有些页是扫描件(纯图片,没有文本层)→ 需要 OCR
- 有些用了复杂的排版(多栏、表格、页眉页脚)→ fitz 比 pypdf 处理得好
- 有些是 Google Docs 导出的 PDF → pypdf 的 layout mode 可能更准
双引擎 + OCR 保证了 Agent 在任何格式的 PDF 上都能提取到文本。大 PDF(>5 页)还用了 ThreadPoolExecutor(4 workers)并行搜索——因为投行年报动辄 200+ 页,串行搜索太慢。
10.9 综合:设计哲学总结
| 原则 | 体现 |
|---|---|
| 真实数据 > 合成数据 | Google Takeout 导出而非 LLM 生成 |
| 文件操作 > 服务调用 | mbox/ics/json CRUD 而非 Gmail API |
| 状态可快照 | tar.gz 归档 filesystem + .apps_data |
| Fail-closed | 沙箱库缺失则拒绝执行 |
| 最小权限 | Filesystem 只读,写入由专用 Server 负责 |
| 可追溯 | Chat 软删除保留记录 |
| 工具数量控制 | 合并模式 72→18 工具 |
| 格式兼容性 | PDF 双引擎+OCR,Excel LibreOffice 重算 |
这套设计哲学的核心是:让评测环境尽可能接近真实工作场景,同时保持工程上的可控性和可复现性。每一个”不够真实”的决策(如用文件替代服务)都有明确的工程理由,而每一个增加复杂度的决策(如双引擎 PDF、LibreOffice 公式重算)都是为了避免评测失真。
第十一章 · 开源的边界:480 个任务中有多少真正可复现
在前面的分析中,我们多次提到 FMP、EDGAR 等非公开服务。本章将这些发现系统化,回答一个关键问题:拿到开源数据集和框架后,研究者到底能复现多少?
11.1 从源码中泄露的完整服务清单
Archipelago 仓库中每个 MCP Server 的 scripts/extract_tools.py 都包含了一段环境变量设置代码。这段代码的本意是在 CI 中提取工具 schema 时跳过真实 API 调用——但它无意中泄露了 Mercor 内部使用的全部服务列表:
# extract_tools.py — 每个 MCP Server 目录下都有这份相同的代码
# Set offline mode for all services to avoid API key requirements
os.environ["OFFLINE_MODE"] = "true"
os.environ["TERRAPIN_OFFLINE"] = "1" # 未知内部服务
os.environ["DATAGOV_OFFLINE_MODE"] = "true" # 政府公开数据 API
os.environ["XERO_OFFLINE_MODE"] = "true" # Xero 会计 SaaS
os.environ["CANVAS_OFFLINE"] = "true" # Canvas LMS(学习管理)
os.environ["EDGAR_OFFLINE_MODE"] = "true" # SEC EDGAR 上市公司文件
os.environ["FMP_OFFLINE_MODE"] = "true" # Financial Modeling Prep
os.environ["COURT_LISTENER_OFFLINE"] = "true" # CourtListener 法院判例
# Set dummy API keys to prevent startup failures
os.environ.setdefault("TERRAPIN_API_KEY", "dummy_key_for_extraction")
os.environ.setdefault("FMP_API_KEY", "dummy_key_for_extraction")
os.environ.setdefault("XERO_CLIENT_ID", "dummy_client_id")
os.environ.setdefault("XERO_CLIENT_SECRET", "dummy_secret")
os.environ.setdefault("CANVAS_API_KEY", "dummy_key")
os.environ.setdefault("DATAGOV_API_KEY", "dummy_key")
os.environ.setdefault("SEARCH_MCP_GOOGLE_API_KEY", "dummy_key_for_extraction")
os.environ.setdefault("SEARCH_MCP_GOOGLE_CSE_ID", "dummy_cse_id_for_extraction")
从这段代码可以确认:至少存在 7 个非公开的 MCP Server + 1 个 Google 搜索服务。
Dockerfile 中还额外暴露了两个可选的 Python 包组:
INSTALL_MEDICINE=false INSTALL_SCICOMP=false
对应推介材料中 Medicine 和 Data Science 两个尚未公开的领域。
11.2 开源 vs 非公开的完整对比
| 服务 | 类型 | 开源仓库 | 推介材料 | 数据集用到 |
|---|---|---|---|---|
| Calendar | 通用 | ✅ | ✅ | ✅ 33/33 worlds |
| Chat | 通用 | ✅ | ✅ | ✅ 33/33 worlds |
| 通用 | ✅ | ✅ | ✅ 33/33 worlds | |
| Filesystem | 通用 | ✅ | ✅ | ✅ 33/33 worlds |
| Spreadsheets | 通用 | ✅ | ✅ | ✅ 33/33 worlds |
| Documents | 通用 | ✅ | ✅ | ✅ 33/33 worlds |
| PDFs | 通用 | ✅ | ✅ | ✅ 33/33 worlds |
| Presentations | 通用 | ✅ | ✅ | ✅ 33/33 worlds |
| Code Execution | 通用 | ✅ | ✅ | ✅ 33/33 worlds |
| FMP | 金融 | ❌ | ✅ | ⚠️ 2/33 worlds |
| EDGAR SEC | 金融 | ❌ | ✅ | ⚠️ 2/33 worlds |
| Xero | 会计 | ❌ | ✅ | — |
| CourtListener | 法律 | ❌ | — | — |
| DataGov | 政府 | ❌ | — | — |
| Canvas | 教育 | ❌ | — | — |
| Terrapin | 内部 | ❌ | — | — |
| Google Search | 搜索 | ❌ | — | — |
11.3 受影响的 28 个任务
公开数据集中,2 个投行 World 声明了 FMP 和 EDGAR SEC 依赖:
world_5970ed13783a463181bdf38337f0cad1(16 个 task)world_43a921f91f0f4d2c85d8bd2774f9e681(12 个 task)
这 28 个 task 中,部分需要实时金融数据——例如特定日期的股票收盘价和美国国债利率:
“Update the base-case DCF model of KVUE with U.S. total equity risk premium of 4.33%, the risk free rate with the 5-Year Treasury rate and the KVUE Close share price on 2025-12-15.”
这类数据只能通过 FMP MCP Server 获取。在开源环境中:
- FMP Server 不存在 → 无法查询股价和利率
INTERNET_ENABLED=false(Dockerfile 第 63 行)→ Code Execution 也无法联网- Web 搜索被禁用 → “Web search is disabled to keep evaluations reproducible”
Agent 在执行这些 task 时会找不到任何数据源,必然失败。
11.4 OFFLINE_MODE:缓存注入机制
extract_tools.py 中每个非公开服务都有 xxx_OFFLINE_MODE 环境变量。这证实了这些服务支持离线模式——不调真实 API,从本地缓存读数据。
结合 Archipelago 的 POST /data/populate 机制(支持从 S3 下载数据到容器 + 执行 lifecycle hooks),完整的工作流推测如下:
一次性准备(Mercor 内部):
FMP API(付费)→ 查询所需股价/利率 → 缓存为文件 → 上传到 S3
每次评测运行:
Orchestrator → POST /data/populate → Docker 容器
├── source 1: World zip(filesystem + .apps_data)← 这部分已开源
├── source 2: FMP 缓存数据 → /.apps_data/fmp/ ← 这部分未开源
└── hook: 启动 FMP Server(OFFLINE_MODE=true)
验证:解压两个有 FMP/EDGAR 依赖的 World zip 后,.apps_data/ 目录下只有 calendar/chat/mail——没有 fmp 或 edgar 目录。这确认了 FMP 数据不在 HuggingFace 公开的 zip 包里,而是通过 S3 populate 另行注入的。
example.py 中还泄露了真实的 S3 Bucket 名:
"url": "s3://rl-studio-snapshots-prod/worlds/snap_xxx/filesystem/"
rl-studio-snapshots-prod 是 Mercor 的私有 S3 存储桶,外部无法访问。
11.5 文档中的模糊表述
HuggingFace 数据集页面只有一句含糊的说明:
“Some worlds include additional finance data applications.”
没有说明:这些 “additional finance data applications” 具体是什么?开源版本是否包含?缺少它们哪些 task 无法执行?
Archipelago README 提供了一个名为 mcp_config_all_oss_servers.json 的配置文件——文件名中 oss(Open Source Software)暗示这不是全部 Server,但文档没有任何进一步解释。
HuggingFace 示例说 ./run.sh 42 可以运行 0-479 任意索引的 task——没有警告某些 task 可能因缺少非公开 Server 而失败。
11.6 实际可复现比例
| 类别 | 任务数 | 占比 | 可完整复现 |
|---|---|---|---|
| 仅依赖 9 个开源 Server | 452 | 94.2% | ✅ |
| 依赖 FMP + EDGAR | 28 | 5.8% | ❌ |
| 合计 | 480 | 100% | 94.2% |
94.2% 的可复现率对 benchmark 来说其实还可以,但问题在于:
- 研究者不会事先知道哪 28 个 task 会失败——错误表现为 Agent 找不到数据源(或瞎编一个数字),而不是框架报错”缺少 FMP Server”
- 这 28 个 task 全部来自投行领域——如果只评测投行 Agent,受影响比例远高于 5.8%
- Leaderboard 上的成绩是用完整环境跑的——开源用户无法精确复现官方排行榜的数字
11.7 对研究者的建议
- 跳过 2 个 FMP/EDGAR World。在评测时过滤掉
world_5970ed13和world_43a921f9,用 452 个 task 评测 - 自建 FMP Server。FMP API 有免费额度(每天 250 次),可以自己写一个 MCP Server 实现离线缓存模式
- 比较时注意口径。论文报告的 Pass@1 是基于 480 个 task 的,自己跑的是 452 个——需要分别计算
- 关注 Mercor 后续开源动态。推介材料中列出的 QuickBooks、Workday、OpenEMR 等服务如果后续开源,可执行的 task 会进一步增加
附录 · 9 个 MCP Server 实现逻辑详解
以下基于 Archipelago 仓库源码(github.com/Mercor-Intelligence/archipelago/mcp_servers/)的逐文件分析。
通用架构模式
所有 9 个 Server 共享同一套架构:
- 框架:
FastMCP+Pydantic数据模型 + 中间件栈(ErrorHandling → Retry → Logging → ValidationErrorSanitizer) - 双模式注册:
USE_INDIVIDUAL_TOOLS=true暴露独立工具(UI 友好),否则暴露 2 个 meta-tools(LLM 友好) - 异步执行:所有工具函数通过
@make_async_background(基于asyncer.asyncify)在后台线程执行 - 传输:支持 HTTP(
host=0.0.0.0)和 stdio 双模式 - 沙箱安全:所有路径操作均有
resolve_under_root+realpathsymlink 解析 + 路径遍历检查
A.1 Calendar Server
| 属性 | 值 |
|---|---|
| 数据根 | /.apps_data/calendar |
| 数据格式 | iCalendar .ics |
| 核心库 | icalendar(iCal 解析/生成) |
| 工具数 | 5:create_event / read_event / update_event / delete_event / list_events |
实现要点:
get_all_ical_files():递归扫描*.ics,解析所有VEVENT组件create_event:生成YYYYMMDD_HHMMSS_microseconds_random6格式 ID → 构造CalendarOutputEvent→ 转为 iCal VEVENT → 追加到.ics文件update_event:遍历所有.ics查找 UID →calendar.subcomponents.remove(old)→ 加入更新后的 VEVENT → 回写delete_event:查找并remove→ 回写list_events:加载所有事件 → 按 start time 排序 → 分页- 支持:all-day / timed 事件、attendees、RFC 5545 RRULE 循环、VALARM 提醒
A.2 Chat Server
| 属性 | 值 |
|---|---|
| 数据根 | /.apps_data/chat(含 Google Chat 子目录 fallback) |
| 数据格式 | JSON(Google Chat Takeout 格式) |
| 当前用户 | CHAT_CURRENT_USER_EMAIL 环境变量 |
| 工具数 | 9 |
目录映射:Groups/{group_id}/messages.json + group_info.json,Users/{user_id}/user_info.json
实现要点:
list_channels:扫描Groups/目录 → 加载group_info.json和messages.json→ 统计成员和消息数get_channel_history:加载messages.json→ 过滤只取 root messages(topic_id == message_id末段)→ 逆序分页post_message:生成topic_id+message_id→ 构造ChatMessage→ 追加到messages.jsonreply_to_thread:找到父消息 → 复用其topic_id→ 新message_id(is_reply=True)→ 追加add_reaction:找到消息 → 添加EmojiReaction到 reactions 数组 → 去重检查delete_post:软删除——设置message_state="DELETED",清空 text/reactions/annotations,保留在文件中get_users:扫描Users/目录 → 读user_info.json→ 分页
A.3 Code Execution Server
| 属性 | 值 |
|---|---|
| 工作目录 | /filesystem |
| 沙箱 | LD_PRELOAD=sandbox_fs.so 拦截 libc 文件系统调用 |
| 屏蔽路径 | /app, /.apps_data |
| 工具数 | 1:code_exec |
实现要点:
- 接收的是 shell 命令(非 Python 代码)——有 Python 检测逻辑(
import/from/def/class/@),误传 Python 会提示用python -c包裹 run_sandboxed_command:subprocess.Popen(["sh", "-c", command])+start_new_session=True(新进程组便于超时杀死)- Fail-closed:每次执行前检查
sandbox_fs.so是否存在,不存在则拒绝执行 - 超时默认 300 秒,输出限制 100KB(HTML 限 2KB)
- 环境隔离:清除
PYTHONPATH,设置HOME=/tmp,过滤 mise/venv 路径
A.4 Documents Server
| 属性 | 值 |
|---|---|
| 数据根 | APP_FS_ROOT(/filesystem) |
| 数据格式 | Word .docx |
| 核心库 | python-docx |
| 工具数 | 15 |
工具列表:create_document / delete_document / get_document_overview / read_document_content / read_image / add_content_text / edit_content_text / delete_content_text / add_image / modify_image / apply_formatting / header_footer / page_margins / page_orientation / comments
实现要点:
- 稳定元素标识符:
helpers/identifier.py实现body.p.0格式的路径定位,使 Agent 可以精确引用文档内任意元素 - 数据模型 37.9KB(
utils/models.py),覆盖 Word 文档的所有结构化操作 - 文件锁机制避免并发写入
A.5 Filesystem Server
| 属性 | 值 |
|---|---|
| 数据根 | /filesystem |
| 权限 | 只读 |
| 工具数 | 6 |
工具列表:list_files / read_text_file / search_files / read_image_file / get_file_metadata / get_directory_tree
实现要点:
read_text_file:白名单扩展名检查(~50 种:py/json/csv/md/xml/yaml/html/…)→ symlink 安全验证 → 读取search_files:os.walk+fnmatchglob 匹配 →followlinks=False防逃逸 → 最多 100 条read_image_file:支持 png/jpg/gif/webp → magic bytes 检测实际格式 → 返回Image对象get_directory_tree:递归 ASCII 树(深度 1-10,最多 500 条目)- 安全:多层路径检查(
resolve_under_root+realpath+is_path_within_sandbox)
A.6 Mail Server
| 属性 | 值 |
|---|---|
| 数据根 | /.apps_data/mail |
| 数据格式 | mbox(Python mailbox 标准库) |
| 工具数 | 7 |
详见第四章源码分析。核心:mailbox.mbox() 读写,mbox.add(msg) + mbox.flush() 实现”发送”。
A.7 PDF Server
| 属性 | 值 |
|---|---|
| 数据根 | APP_FS_ROOT(/filesystem) |
| 核心库 | pypdf + PyMuPDF (fitz) + reportlab + Pillow |
| 工具数 | 5 |
工具列表:create_pdf / read_pdf_pages / search_pdf / read_image / read_page_as_image
实现要点:
read_pdf_pages:双引擎策略——先用 fitz 提取文本,空则回退 pypdf layout mode,再空则 OCR fallback。同时提取嵌入图片(pypdf XObject)和 StrikeOut 注释search_pdf:大 PDF(>5 页)使用ThreadPoolExecutor(4 workers)并行搜索。支持大小写敏感、全词匹配、上下文字符数配置create_pdf:基于reportlab从结构化 block 列表(paragraph/heading/bullet_list/numbered_list/table/page_break/spacer)生成 PDFread_page_as_image:fitz 渲染单页为 144 DPI PNG → PIL 转 JPEG- 图片缓存:
read_pdf_pages提取的图片存入IMAGE_CACHE,read_image通过 annotation key 取出
A.8 Presentations Server
| 属性 | 值 |
|---|---|
| 数据根 | APP_FS_ROOT(/filesystem) |
| 核心库 | python-pptx + MarkItDown |
| 工具数 | 13 |
工具列表:create_deck / delete_deck / add_slide / edit_slides / add_image / modify_image / insert_chart / insert_table / add_shape / read_slides / read_completedeck / read_individualslide / read_image
实现要点:
create_deck:Presentation()→ 清除模板幻灯片 → 按 layout map 添加(title/title_and_content/section_header/two_content/title_only/blank)→ 填充 title/subtitle/bullets/table/notesadd_slide:在指定 index 插入(通过 XML 操作_sldIdLst移动位置)edit_slides:578 行,支持文本格式(字体/颜色/大小/粗体/斜体)、hyperlink、table cell 格式、对齐等read_slides:用MarkItDown库将.pptx转 Markdown → 返回字符范围(最大 10,000 chars)- 数据模型巨大:
slide_edit.py18KB,tool_inputs.py17KB
A.9 Spreadsheets Server
| 属性 | 值 |
|---|---|
| 数据根 | APP_FS_ROOT(/filesystem) |
| 核心库 | openpyxl + LibreOffice(soffice --headless) |
| 工具数 | 12 |
工具列表:create_spreadsheet / delete_spreadsheet / read_tab / read_csv / list_tabs_in_spreadsheet / add_tab / delete_tab / edit_spreadsheet / add_content_text / delete_content_cell / create_chart / filter_tab
实现要点:
read_tab:openpyxl.load_workbook(data_only=True)→ 若数据为空尝试 LibreOffice 重新计算 →normalize_cell_value处理日期/DUMMYFUNCTION →trim_empty_trailing_dataedit_spreadsheet(30KB,833 行):支持 SetCell / AppendRows / FormatCells / MergeCells / FreezePanes / SetColumnWidth / SetRowHeight / SetNumberFormat / AddConditionalFormatting / AddDataValidation / AddImage / AddNamedRange / SetAutoFilter / RenameSheet 等recalculate_formulas:调用soffice --headless --calc --convert-to xlsx触发 LibreOffice 公式重算normalize_cell_value:处理 openpyxl datetime、Excel serial date(1-2,958,465 范围)、Google SheetsDUMMYFUNCTION伪公式- 数据模型最大:
sheet.py43.3KB,response.py18.6KB
第三方库汇总
| Server | 关键库 | 用途 |
|---|---|---|
| Calendar | icalendar | iCal 解析/生成 |
| Chat | 纯标准库(json/os) | JSON 读写 |
| Code | subprocess + LD_PRELOAD | 沙箱命令执行 |
| Documents | python-docx | Word 文档操作 |
| Filesystem | 纯标准库 | 只读文件浏览 |
mailbox(标准库) | mbox 读写 | |
pypdf + fitz + reportlab + Pillow | PDF 解析/生成/OCR | |
| Presentations | python-pptx + MarkItDown | PPT 操作 |
| Spreadsheets | openpyxl + soffice | Excel 操作 + 公式重算 |
所有 Server 共用:FastMCP, Pydantic, loguru, asyncer, mcp_schema