Day 2: 从 100% 浪费到 95% 产出——效率优化的杠杆在哪里
十天写一个编码 Agent,第二天的主题是:找到浪费,消灭浪费。
回顾 Day 1 的问题
Day 1 我们搭了一个能跑通的框架,在 django__django-11179 上成功生成了正确的 patch。但仔细分析日志后发现了一个扎心的事实:
Agent 59% 的步骤是浪费的。
22 步中只有 5 步在真正解决问题,其余 13 步在做无用功。具体来说:
| 浪费类型 | 步骤数 | 原因 |
|---|---|---|
| 搜索不存在的测试 | 8 步 | 不知道 FAIL_TO_PASS 测试由评测框架注入 |
| pip install | 4 步 | 环境应由 harness 提前配置 |
| 修改测试文件 | 1 步 | SWE-bench 明确禁止修改测试 |
这就是 Day 2 的切入点:不是让模型更聪明,而是不让模型做蠢事。
优化一:告诉 Agent 什么不该做
SWE-bench 有一个反直觉的设计:FAIL_TO_PASS 字段列出了修复后应该通过的测试,但这些测试可能不存在于当前代码库中——它们会在评测阶段由 harness 自动注入。
Day 1 的 Agent 不知道这一点,花了 8 步去找一个 test_fast_delete_instance_set_pk_none 测试,最终还自作主张写了一个。这不仅浪费了步骤,还可能导致评测失败(因为 Agent 修改了测试文件,导致 harness 注入 patch 时冲突)。
修复很简单,在系统提示词中加两句话:
- The test(s) listed above may NOT exist in the repo yet.
They will be added by the evaluation harness.
Do NOT create or modify any test files.
- The environment is already set up. Do NOT run pip install.
效果立竿见影。修改后 Agent 在遇到测试不存在时,不再死磕,而是直接说"这个测试会由评测框架添加",然后转向跑已有的测试来验证修复。
经验:编码 Agent 的效率瓶颈往往不是推理能力,而是对任务规则的理解。
优化二:把基础设施的活从 Agent 身上拿走
Day 1 的 Agent 花了 4 步在执行 pip install -e .,包括处理权限错误和重试。这完全不应该是 Agent 的工作。
解决方案是在 harness 层自动处理:
REPO_INSTALL_COMMANDS = {
"django/django": ["pip", "install", "-e", ".", "-q", "--break-system-packages"],
"sympy/sympy": ["pip", "install", "-e", ".", "-q", "--break-system-packages"],
# ...
}
def setup_repo(instance, workdir):
# ... git clone, checkout ...
# 自动安装依赖
install_cmd = REPO_INSTALL_COMMANDS.get(repo)
if install_cmd:
subprocess.run(install_cmd, cwd=repo_dir, ...)
这样 Agent 启动时环境已经就绪,不需要浪费任何步骤在环境配置上。
经验:能确定性完成的事情,就不要交给概率性的 LLM 去做。
优化三:释放上下文空间
Day 1 留下了一个隐患:每个 slot 有 32K token 的上下文窗口,但 Agent 自限于 5.5K token。这意味着 83% 的上下文空间被浪费了。
后果是 Agent 很快就开始"失忆"——旧的工具调用结果被裁掉后,Agent 会重复搜索已经看过的文件。
Day 2 的修改:
- 上下文预算 5.5K → 28K,充分利用可用空间
- 工具输出截断上限 3K → 8K,让 Agent 能看到更完整的文件内容
- 智能压缩替代粗暴删除:旧的工具结果不再直接删掉,而是压缩为一行摘要
# 旧方式:直接删除
self.messages = self.messages[:2] + self.messages[-20:]
# 新方式:压缩旧消息
for msg in old_messages:
if msg["role"] == "tool" and len(msg["content"]) > 200:
first_line = msg["content"].split("
")[0][:150]
msg["content"] = f"[compressed] {first_line}"
这样 Agent 至少能记住"我之前读过 deletion.py 的 270-327 行",不会再去重复读取。
优化四:Patch 卫生
SWE-bench 评测只看源代码的修改。如果 Agent 顺手改了测试文件,不仅无用,还可能干扰评测。
def extract_patch(repo_path):
# 排除测试目录的修改
result = subprocess.run(
["git", "diff", "HEAD", "--", ".", ":!tests/", ":!test/"],
...
)
一行改动,确保产出干净的 patch。
优化五:网络韧性
批量跑 20 个实例时发现,本地 LLM 服务器偶尔会重启(加载模型、内存压力等原因)。第一轮有 6 个实例因为 503 错误直接失败。
加了指数退避重试:
for attempt in range(5):
try:
response = self.client.chat.completions.create(**kwargs)
break
except (APIConnectionError, APITimeoutError, InternalServerError) as e:
wait = min(2 ** attempt, 30)
time.sleep(wait)
批量评测结果
在 20 个 Django 实例(按 patch 长度排序,取最简单的 20 个)上运行:
| 指标 | 值 |
|---|---|
| 总实例数 | 20 |
| 生成 patch | 19 |
| 无 patch(模型能力限制) | 1 |
| Patch 生成率 | 95% |
唯一失败的 django__django-11964 是一个涉及 enum/choices 多文件交互的复杂 bug,Agent 跑满 40 步仍未收敛——在 fields/__init__.py、base.py、query.py 之间反复搜索,始终没有定位到修改点。这是模型推理能力的真实瓶颈。
需要强调:生成 patch ≠ 正确修复。95% 是 patch 生成率,不是通过率。后续需要用 SWE-bench 的正式评测框架来验证这些 patch 是否真的能通过测试。
Day 2 的认知收获
1. 提示词是最高杠杆的优化
两句话的提示词改动("测试可能不存在"、"不要 pip install")比任何架构改动效果都大。在编码 Agent 领域,提示词工程不是"锦上添花",而是"必须做对"。
2. 分层设计:确定性的事不要让 LLM 做
确定性层(Harness):环境安装、repo checkout、patch 提取
概率性层(Agent):理解问题、定位代码、生成修复
把确定性的工作从 Agent 中抽出来,是效率的根本保证。
3. 上下文就是记忆
对 Agent 来说,上下文窗口就是它的工作记忆。限制上下文 = 限制记忆 = 强迫重复劳动。在上下文管理上的投入,回报率极高。
4. 失败案例比成功案例更有价值
19 个成功的实例告诉你"能跑",但那 1 个失败的实例告诉你"瓶颈在哪"。django-11964 的失败模式(探索不收敛)指向了下一步的优化方向:结构化探索策略。
下一步
- 用 SWE-bench 正式评测验证 patch 正确性
- 分析 19 个 patch 的质量(是否真的修复了 bug)
- 针对"探索不收敛"问题设计结构化策略
- 扩展到 SymPy 等其他仓库
这是「十天开发编码 Agent」系列的第二篇。代码已打 tag day2。