AI 编程 Agent 的账,往往不是写了多少行代码,而是读了多少遍代码。Stencil 的 Can Boluk 对 SWE-Bench 基准测试数据进行拆解后发现:在约 1.81 亿个 token、近 200 万次工具调用中,编辑和写入只占 9%,其余 91% 都消耗在读取文件、执行 grep、查看命令输出等活动上。
这改变了优化方向。提高生成速度只能影响成本的一小部分;真正值得优化的是 Agent 如何寻找上下文、如何保存已经确认的事实,以及多个 Agent 之间如何交接工作。相关实践据称可以把成本降低约四成,但具体收益仍取决于仓库规模、任务类型、模型和工具设计。
先把“读代码”拆开看
Agent 的阅读成本通常来自四类重复动作:
- 不知道入口在哪里,于是递归列目录、反复搜索关键词。
- 已经看过某个模块,但下一轮又重新读取完整文件。
- 一个 Agent 调查问题,另一个 Agent 接手时只能从头建立上下文。
- 命令输出过长,真正有用的几行被淹没,Agent 只好再次执行命令。
这些调用对人类开发者来说可能只是几秒钟,但对长上下文模型来说,每次读取都会增加输入 token,并可能挤占真正需要保留的代码、错误信息和约束条件。
因此,优化目标不应只是“让 Agent 少调用几个工具”,而应是让每次调用都带来新的信息。一个好的上下文交接记录,至少要包含:
- 已确认的入口文件和调用链。
- 与问题相关的文件及其职责。
- 已排除的假设,以及排除它们的证据。
- 当前复现命令和失败输出的关键行。
- 尚未验证的假设。
- 下一步建议,以及不要重复做的搜索。
把探索结果变成可复用的工作包
可以把一次调查看成一个小型“上下文工作包”,而不是一串不可复用的聊天记录。下面是一个适合放进仓库临时目录或 Agent 工作目录的格式:
# .agent/context/task-184.yaml
task: "修复订单取消后库存没有释放的问题"
status: "investigating"
entrypoints:
- path: "src/orders/cancel_order.py"
reason: "HTTP handler 调用的业务入口"
- path: "src/inventory/reservation.py"
reason: "负责创建和释放库存预留"
confirmed:
- "cancel_order() 会更新订单状态,但当前调用链没有 release_reservation()"
- "订单状态写入使用事务,库存释放由独立服务完成"
evidence:
- command: "rg -n \"release_reservation|cancel_order\" src tests"
result: "只在 reservation.py 和测试夹具中找到 release_reservation"
- command: "pytest tests/orders/test_cancel_order.py -q"
result: "现有测试通过,但没有断言库存预留被释放"
excluded:
- "不是数据库连接失败:取消接口日志中事务提交成功"
unknowns:
- "库存释放是否必须与订单状态更新保持同一事务"
next_steps:
- "阅读 reservation.py 的幂等性实现"
- "补充取消订单后的库存断言"
avoid_repeating:
- "不要重新扫描整个 src 目录"
- "不要重复运行没有过滤条件的 pytest 全量测试"
这个文件不需要记录所有读过的内容,只记录能改变决策的事实。接手任务的 Agent 可以先读工作包,再定向打开两个入口文件,避免从仓库根目录重新开始探索。
如果团队使用编排器,可以把工作包作为阶段之间的显式输入:调查阶段输出 context.yaml,实现阶段读取它,验证阶段只补充新的证据。这样做的价值不只是减少 token,也让错误假设更容易被审查。
用“先索引、后局部读取”替代全量扫描
在大型仓库中,Agent 常见的低效路径是:列出全部文件,读取多个大文件,再通过关键词搜索确认入口。更稳定的方式是维护一份轻量索引,先回答“应该读哪些文件”,再读取局部内容。
下面的 Python 示例演示了一个简单的上下文收集器。它只读取命中的文件,并对每个文件限制输出行数;实际项目中可以把关键词、路径白名单和测试命令替换成任务专属配置。
#!/usr/bin/env python3
from pathlib import Path
import re
import sys
ROOT = Path(sys.argv[1] if len(sys.argv) > 1 else ".")
TERMS = ["release_reservation", "cancel_order", "reservation"]
MAX_MATCHES_PER_FILE = 12
MAX_LINES_PER_FILE = 80
ignored = {".git", "node_modules", "dist", "build", ".venv", "__pycache__"}
pattern = re.compile("|".join(re.escape(term) for term in TERMS), re.IGNORECASE)
for path in ROOT.rglob("*"):
if not path.is_file() or any(part in ignored for part in path.parts):
continue
if path.suffix not in {".py", ".js", ".ts", ".java", ".go", ".rb", ".rs"}:
continue
try:
lines = path.read_text(encoding="utf-8").splitlines()
except (OSError, UnicodeDecodeError):
continue
matches = [i for i, line in enumerate(lines) if pattern.search(line)]
if not matches:
continue
print(f"\n## {path}")
for index in matches[:MAX_MATCHES_PER_FILE]:
start = max(0, index - 3)
end = min(len(lines), index + 4)
print(f"-- lines {start + 1}-{end} --")
for number in range(start, end):
print(f"{number + 1:4}: {lines[number]}")
运行方式:
python3 collect_context.py ./src > context-snippets.txt
这个脚本不是通用代码搜索器,也不能替代人工理解。它的作用是把第一次探索限制在与任务相关的局部窗口中。对于跨语言仓库,还可以加入 go.mod、package.json、pom.xml 等依赖入口,或者改用现有的语言服务器和代码索引工具。
输出也要有预算
读取代码不是唯一的 token 消耗点,工具输出同样可能制造浪费。测试命令、日志和编译器输出应当优先保留错误摘要、失败测试名和相关堆栈,而不是把完整日志无条件交给模型。
例如,CI 或 Agent 工具可以把测试输出压缩成如下结构:
status: failed
failed_tests:
- tests/orders/test_cancel_order.py::test_releases_reservation
error_summary: AssertionError: expected released=True, got released=False
relevant_trace:
src/orders/cancel_order.py:48
src/inventory/reservation.py:91
rerun: pytest tests/orders/test_cancel_order.py::test_releases_reservation -q
这类摘要必须保留可复现命令和定位信息,否则压缩会损失调试能力。一个实用的规则是:可以删除噪声,但不能删除证据;可以缩短堆栈,但不能删除文件、行号和失败断言。
怎么判断优化是否有效
不要只看模型回答是否变快。建议在 Agent 编排层记录至少四项指标:
- 每个任务的输入 token、输出 token 和缓存命中量。
read_file、搜索、目录遍历、测试等工具调用次数。- 重复读取的文件比例。
- 从任务开始到首次有效修改、最终测试通过的时间。
可以先选一批相似任务做基线,再加入上下文工作包、路径索引和输出摘要,比较相同成功标准下的成本。成功标准应包含测试通过或补丁被人工接受,不能只比较 token 数,因为过度压缩上下文可能导致 Agent 漏掉关键约束。
落地清单
- 为调查、实现、验证阶段定义统一的上下文交接文件。
- 记录入口、证据、已排除假设和下一步,不记录无关阅读历史。
- 对搜索和文件读取设置路径、文件类型、行数和输出大小限制。
- 把长日志转换为带文件、行号、错误摘要和复现命令的结构化结果。
- 统计重复读取率与成功率,确认节省 token 没有换来更多返工。
- 对高风险改动保留完整原始输出,摘要只作为模型输入优化,不作为唯一审计记录。
SWE-Bench 数据揭示的是一个容易被忽略的事实:Agent 的主要工作量是建立和重建上下文。只要把探索结果变成可交接的资产,把全量阅读改成有边界的定向读取,再对工具输出设置预算,成本优化就从“少写几行提示词”进入了工程系统设计的范围。