Claude Code、Codex 和 Cursor 究竟会选择哪些工具?一项实验把这个问题放进了 16893 次真实实现型会话中,覆盖 1163 个 prompt 变体、75 个代码仓库和 10 种语言。它的价值不只在于比较三个 coding agent,更在于展示:要研究 agent 的工程行为,不能只看一两个演示,也不能只让模型口头描述自己会怎么做。
不过,规模大并不等于结论可以直接照搬。实验最终筛出 5292 个有效会话,约占原始会话的 31.3%,覆盖范围也从 75 个仓库缩小到 51 个仓库。理解筛选规则、任务分布和统计口径,比记住一个“谁最常使用某工具”的排行榜更重要。
这项实验真正测量了什么
这些任务要求 agent 直接修改代码,而不是给出实现建议。这一点很关键:模型在聊天框里声称“我会先运行测试”,并不代表它进入仓库后真的会执行测试命令。
实现型会话可以观察到更具体的行为,例如:
- agent 是否先搜索代码,再打开文件;
- 它使用了 shell、文件编辑器、代码搜索或测试运行器中的哪些工具;
- 一次任务调用了多少次工具,调用顺序如何;
- 工具调用失败后,它是重试、换工具,还是直接继续修改;
- 不同语言、仓库和 prompt 表述是否改变工具选择。
但“选择了某个工具”不应自动解释为“更擅长使用这个工具”。调用次数多,可能代表探索充分,也可能代表重复搜索;调用次数少,可能是路径更短,也可能是遗漏了验证。工具使用数据需要和任务是否完成、测试是否通过、改动是否正确一起分析。
5292 个有效会话意味着什么
从 16893 次会话筛到 5292 次,有效样本保留率约为:
5292 / 16893 ≈ 31.3%
这不是一个可以略过的数据清洗细节。至少需要回答以下问题:
- “有效”是指 agent 成功启动、产生代码改动、通过测试,还是仅仅生成了可解析的日志?
- 三个 agent 的淘汰比例是否接近?如果某个 agent 的失败会话被大量排除,剩余样本可能显得异常稳定。
- 75 个仓库中为什么只有 51 个进入最终结果?被排除的仓库是否集中在某些语言或行业?
- 1163 个 prompt 变体是否均匀分配?同一个任务的轻微措辞变化不能被当作完全独立的工程任务。
- 工具名称是否经过统一映射?例如
ripgrep、IDE 全局搜索和 agent 内置搜索可能承担相同角色,却被记录成不同工具。
摘要还提到最终样本横跨 18 个行业,这有助于扩大场景覆盖,但并不能自动消除仓库规模、测试质量和任务难度带来的混杂因素。更稳妥的做法是同时报告整体结果和按仓库、语言、任务类型分层后的结果。
此外,研究方 Armature 自己明确承认分析带有偏见。这里不必把“有偏见”简单理解为结果无效,但读者应检查工具分类、有效会话筛选和指标选择是否会放大某种预设观点。摘要没有提供三个 agent 的具体工具占比,因此不能据此补出谁更偏爱 shell、搜索或编辑器的结论。
可以这样复算自己的会话日志
如果团队正在评估多个 coding agent,可以先建立一个极简的 JSONL 事件格式。下面假设每一行代表一次工具调用,字段包括会话、agent、仓库、语言、工具名称以及该会话是否有效。
示例数据:
{"session_id":"s1","agent":"codex","repo":"api","language":"python","tool_name":"search","valid":true}
{"session_id":"s1","agent":"codex","repo":"api","language":"python","tool_name":"shell","valid":true}
{"session_id":"s2","agent":"cursor","repo":"web","language":"typescript","tool_name":"edit","valid":true}
{"session_id":"s2","agent":"cursor","repo":"web","language":"typescript","tool_name":"test","valid":true}
{"session_id":"s3","agent":"claude-code","repo":"api","language":"python","tool_name":"search","valid":false}
将数据保存为 sessions.jsonl,再运行下面的标准库脚本。实际接入时,只需修改顶部的字段名映射:
#!/usr/bin/env python3
import json
from collections import Counter, defaultdict
from pathlib import Path
EVENTS_FILE = Path("sessions.jsonl")
sessions = defaultdict(lambda: {"tools": Counter()})
with EVENTS_FILE.open(encoding="utf-8") as source:
for line_number, line in enumerate(source, 1):
if not line.strip():
continue
event = json.loads(line)
if not event.get("valid", False):
continue
session = sessions[event["session_id"]]
session["agent"] = event["agent"]
session["repo"] = event["repo"]
session["language"] = event["language"]
session["tools"][event["tool_name"]] += 1
agent_sessions = Counter()
agent_calls = Counter()
agent_tool_sessions = Counter()
for session in sessions.values():
agent = session["agent"]
agent_sessions[agent] += 1
agent_calls[agent] += sum(session["tools"].values())
for tool in session["tools"]:
agent_tool_sessions[(agent, tool)] += 1
for agent in sorted(agent_sessions):
total = agent_sessions[agent]
mean_calls = agent_calls[agent] / total
print(f"\n{agent}: {total} sessions, {mean_calls:.2f} calls/session")
rows = [
(count / total, tool, count)
for (row_agent, tool), count in agent_tool_sessions.items()
if row_agent == agent
]
for rate, tool, count in sorted(rows, reverse=True):
print(f" {tool:12} {rate:6.1%} ({count}/{total} sessions)")
运行命令:
python3 analyze_tools.py
这里按“使用过该工具的会话比例”统计,而不是直接比较调用总数。这样可以降低某个 agent 在单次会话里反复调用同一工具造成的膨胀,但仍然不够:正式评估还应按仓库计算比例,再对仓库结果取平均,避免一个拥有大量 prompt 变体的仓库支配整体排名。
从工具频率走向工程质量
工具选择适合回答“agent 如何行动”,却不能单独回答“哪个 agent 更好”。一套可落地的评估至少应并列记录四类指标:
- 完成质量:测试通过率、需求满足率、人工审查结果;
- 过程行为:工具覆盖率、调用顺序、失败重试和验证行为;
- 资源成本:会话时长、token、工具调用次数和计算成本;
- 稳定性:同一任务在不同 prompt 变体、不同运行批次中的方差。
选型时不要直接采用跨 51 个仓库的总榜。更可靠的办法是挑选与自己代码库相似的语言、项目规模和任务类型,固定容器、测试命令、超时与权限,再让每个 agent 多次运行同一批任务。与此同时保留失败会话,并明确区分基础设施失败、agent 失败和评测器失败。
这类大规模研究最值得借鉴的,不是一个永久有效的冠军,而是实验单位的设计:让 agent 真正实现任务,保留完整轨迹,公开筛选口径,并把工具行为和最终代码质量放在同一张表里。做到这些,工具选择数据才会从有趣的统计变成可用于采购和工程决策的证据。