Lab 005:上下文包越大越好吗?Token 成本与修复成功率实验
"给 Agent 越多上下文,修 Bug 就越准"——这是直觉,不是事实。上下文越大,Token 成本越高、噪声越多、Agent 越容易迷失。本文用 30 个真实 Bug 做对照实验,比较小、中、大三种上下文包在修复成功率、Token 成本、耗时和代码质量上的差异,找到"够用但不浪费"的平衡点。
一、实验目标
我们要回答的问题:
- 上下文包大小与修复成功率之间是什么关系?
- Token 成本随上下文增长的边际曲线是什么形状?
- 是否存在"甜点区间"——成本可控且成功率最高的上下文大小?
- 不同类型的 Bug(逻辑错误 / 类型错误 / 并发问题 / 配置问题)对上下文大小的敏感度是否不同?
实验假设:
H1:上下文越大,修复成功率越高(可能不对)
H2:Token 成本与上下文大小线性相关(可能不对,有缓存)
H3:存在"甜点区间",超过后成功率不再显著提升(待验证)
H4:不同类型 Bug 的最佳上下文大小不同(待验证)二、实验设计
2.1 上下文包分级
| 级别 | 包含内容 | 预估 Token | 说明 |
|---|---|---|---|
| S(小) | 报错文件 + 堆栈指向的 3 个文件 | 2K-5K | 最小可运行上下文 |
| M(中) | S + 同模块相关文件 + CLAUDE.md + 测试文件 | 8K-20K | 包含项目规范 |
| L(大) | M + 依赖模块源码 + Git 历史 + 关联 Issue | 30K-80K | 全量上下文 |
2.2 任务集
从真实项目中选取 30 个已修复的 Bug,按类型分布:
| Bug 类型 | 数量 | 示例 |
|---|---|---|
| 逻辑错误 | 8 | 条件判断错误、边界值遗漏 |
| 类型错误 | 7 | null/undefined、类型转换 |
| 并发问题 | 5 | 竞态条件、死锁 |
| 配置问题 | 5 | 环境变量缺失、配置项拼写错误 |
| 集成问题 | 5 | API 调用参数错误、协议不匹配 |
每个 Bug 都有:① 已知的正确修复方案(作为评分基准);② 对应的回归测试(验证修复是否有效)。
2.3 评分维度
| 维度 | 权重 | 评分标准 |
|---|---|---|
| 修复正确性 | 40% | 回归测试是否全部通过 |
| 方案一致性 | 25% | 与人工修复方案的 diff 相似度 |
| 代码质量 | 20% | 是否引入新 warning、是否符合项目风格 |
| 成本控制 | 15% | Token 消耗是否在预算内 |
综合得分 = 正确性×0.4 + 一致性×0.25 + 质量×0.2 + 成本×0.15
三、实验执行
3.1 上下文包配置
# context-pack-configs.yaml
configs:
small:
name: "S-最小上下文"
max_tokens: 5000
include:
- error_file # 报错文件全文
- stack_trace_files: # 堆栈指向的文件
limit: 3
mode: "snippet" # 只取报错函数前后 30 行
- bug_description # Issue 描述
exclude:
- related_modules
- git_history
- test_files
- project_rules
medium:
name: "M-标准上下文"
max_tokens: 20000
include:
- error_file
- stack_trace_files:
limit: 3
mode: "full" # 完整文件
- bug_description
- related_modules: # 同模块文件
limit: 5
strategy: "import_graph"
- test_files: # 相关测试
limit: 3
- project_rules: # CLAUDE.md
files: ["CLAUDE.md", ".cursorrules"]
exclude:
- git_history
large:
name: "L-全量上下文"
max_tokens: 80000
include:
- error_file
- stack_trace_files:
limit: 10
mode: "full"
- bug_description
- related_modules:
limit: 20
strategy: "import_graph"
- test_files:
limit: 10
- project_rules:
files: ["CLAUDE.md", ".cursorrules", "docs/**/*.md"]
- git_history: # 最近 20 条 commit
limit: 20
scope: "affected_files"
- linked_issues: # 关联 Issue
limit: 53.2 实验 Runner
# context_size_experiment.py
import json
import time
import subprocess
from dataclasses import dataclass, asdict
@dataclass
class ExperimentResult:
bug_id: str
bug_type: str
config: str # "small" / "medium" / "large"
tokens_used: int
cost_usd: float
time_seconds: float
tests_passed: int
tests_total: int
fix_correct: bool
diff_similarity: float # 与标准修复的相似度 0-1
code_quality_score: float # 0-1
composite_score: float
def run_single(bug_id: str, config_name: str, agent_runner) -> ExperimentResult:
"""对单个 Bug 用指定上下文配置跑一次"""
# 1. 加载上下文包
context = load_context_pack(bug_id, config_name)
# 2. 记录开始时间和 Token
start = time.time()
token_counter = TokenCounter()
# 3. 调用 Agent 修复
patch = agent_runner.fix(
context=context,
bug_description=load_bug_desc(bug_id),
token_callback=token_counter.on_token,
)
elapsed = time.time() - start
tokens = token_counter.total
# 4. 应用补丁并运行测试
apply_patch(patch)
test_result = run_tests(get_test_suite(bug_id))
# 5. 计算评分
similarity = compute_diff_similarity(patch, load_golden_patch(bug_id))
quality = run_lint_check(patch)
correct = test_result.passed == test_result.total
# 6. 计算综合得分
cost_score = max(0, 1 - tokens / 80000) # Token 越少分越高
composite = (correct * 0.4 + similarity * 0.25 +
quality * 0.2 + cost_score * 0.15)
# 7. 回滚补丁(为下一轮准备)
revert_patch()
return ExperimentResult(
bug_id=bug_id,
bug_type=classify_bug(bug_id),
config=config_name,
tokens_used=tokens,
cost_usd=tokens * PRICE_PER_TOKEN,
time_seconds=elapsed,
tests_passed=test_result.passed,
tests_total=test_result.total,
fix_correct=correct,
diff_similarity=similarity,
code_quality_score=quality,
composite_score=composite,
)
def run_experiment(bug_ids: list[str], agent_runner):
"""运行完整实验:每个 Bug × 三种配置"""
configs = ["small", "medium", "large"]
results = []
for bug_id in bug_ids:
for config in configs:
result = run_single(bug_id, config, agent_runner)
results.append(asdict(result))
print(f"[{bug_id}] {config}: score={result.composite_score:.2f} "
f"tokens={result.tokens_used} correct={result.fix_correct}")
# 保存结果
with open("experiment-results.json", "w") as f:
json.dump(results, f, indent=2, ensure_ascii=False)
return results四、实验结果
4.1 总体数据(30 个 Bug × 3 种配置 = 90 次运行)
| 指标 | S(小) | M(中) | L(大) |
|---|---|---|---|
| 修复成功率 | 53% | 77% | 80% |
| 平均 Token | 3,200 | 12,800 | 48,500 |
| 平均成本 | $0.05 | $0.19 | $0.73 |
| 平均耗时 | 18s | 42s | 95s |
| 平均综合得分 | 0.42 | 0.68 | 0.58 |
| diff 相似度 | 0.35 | 0.62 | 0.55 |
关键发现:
- S → M:成功率从 53% 跳到 77%(+24%),成本只增加 3.8 倍。性价比最高的跃迁。
- M → L:成功率只提升 3%(77% → 80%),但成本增加 3.8 倍。边际收益急剧下降。
- 综合得分 M > L:因为 L 的成本分被拉低,而且大上下文有时反而让 Agent "想太多",改出更复杂的方案。
4.2 按 Bug 类型分析
| Bug 类型 | S 成功率 | M 成功率 | L 成功率 | 最佳配置 |
|---|---|---|---|---|
| 逻辑错误 | 50% | 88% | 88% | M |
| 类型错误 | 75% | 88% | 88% | S 已够用 |
| 并发问题 | 20% | 40% | 60% | L(需要更多上下文) |
| 配置问题 | 80% | 80% | 80% | S 已够用 |
| 集成问题 | 40% | 80% | 100% | L(需要看跨模块交互) |
成功率曲线(按上下文大小):
100% ┤ ●━━━━ L
│ ╱─────
80% ┤ ●────● M
│ ╱────╯
60% ┤ ╱────╯
│ ╱────╯
40% ┤ ╱────╯ ●━━━━━━━ S
│ ╱────╯
20% ┤────╯
│
0% ┼──────┬──────┬──────┬──────┬───
S(3K) M(12K) L(48K)
上下文 Token 数4.3 成本效率分析
| 指标 | S | M | L |
|---|---|---|---|
| 每成功修复的平均成本 | $0.09 | $0.25 | $0.91 |
| 成本效率排名 | ★★★★★ | ★★★☆☆ | ★☆☆☆☆ |
S 虽然成功率低,但每次成功的成本极低。M 综合最优。L 成本最高但成功率没有质的飞跃。
五、结论与建议
5.1 默认策略:M(标准上下文)
对于日常 Bugfix,M 配置是最佳默认选择。它提供了:
- 报错文件的完整上下文
- 同模块的关联文件
- 项目规范和测试文件
- 足够 Agent 理解问题并给出正确修复
5.2 动态升级策略
# dynamic-context-routing.yaml
strategy: "start_medium_then_scale"
rules:
- condition: "bug_type in ['配置问题', '类型错误']"
action: "use_small"
reason: "这类 Bug 通常只看报错文件就能修"
- condition: "bug_type in ['并发问题', '集成问题']"
action: "use_large"
reason: "需要跨模块上下文才能理解竞态和协议问题"
- condition: "medium_failed_and_retry_budget > 0"
action: "upgrade_to_large"
reason: "M 修不好,升级 L 再试一次"
- condition: "default"
action: "use_medium"
reason: "性价比最高的默认选择"5.3 成本守卫
# cost_guard.py
CONTEXT_BUDGET = {
"daily": 5.0, # 每日 Token 预算 $5
"per_task": 0.50, # 单任务预算 $0.50
"alert_threshold": 0.80, # 用到 80% 预算时告警
}
def check_budget(tokens_used: int, config: str) -> bool:
"""检查是否超出预算,超出不执行"""
cost = tokens_used * PRICE_PER_TOKEN
if cost > CONTEXT_BUDGET["per_task"]:
log_alert(f"Task would exceed per-task budget: ${cost:.2f}")
return False
return True六、真实经验与踩坑
6.1 大上下文让 Agent "想太多"
场景:一个简单的气泡排序写错了条件(> 写成 <),用 L 配置修复。
问题:Agent 看到大量历史 commit 和文档后,认为"这个排序算法应该换成快速排序",提交了一个完全不同的实现。虽然功能正确,但不是期望的最小修复。
解决方案:对 L 配置的 Prompt 增加强约束:"只修复报错位置,不做算法替换或重构"。或者在补丁模板中加"修改行数 ≤ 20 行"的硬限制。
6.2 Git 历史是双刃剑
场景:L 配置包含了 Git 历史,期望 Agent 能从历史中学到"这类问题以前怎么修的"。 问题:Git 历史中有些 commit 本身就是错误的修复(后来又被 revert 了),Agent 学到了错误模式。 解决方案:如果要把 Git 历史放进上下文,只包含"最终合并到 main 的 commit",过滤掉 revert 和 fixup。或者在上下文中明确标注"以下 commit 中有些已被回滚,请注意甄别"。
6.3 缓存能大幅降低大上下文的成本
场景:连续用 L 配置跑多个 Bug,每个都发送 50K Token 的上下文。 问题:成本直线上升,一天就超出了预算。 解决方案:Anthropic 和 OpenAI 都有 Prompt Cache 机制——相同的前缀 Token 第二次请求时价格降低 90%。把不变的上下文(CLAUDE.md、项目规范、通用工具代码)放在 Prompt 最前面,变化的部分(具体 Bug 描述、报错文件)放在后面。实测 L 配置在有缓存的情况下成本降低 60%。
七、参数说明表
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
config_level |
string | "medium" |
上下文级别:small / medium / large |
max_tokens |
int | 20000 |
上下文包 Token 上限 |
task_count |
int | 30 |
实验任务数量 |
bug_types |
list | 5 类 | 参与实验的 Bug 类型 |
scoring_weights |
object | 见 2.3 | 各评分维度权重 |
model |
string | "claude-sonnet" |
使用的模型 |
enable_cache |
bool | true |
是否启用 Prompt Cache |
retry_on_fail |
int | 1 |
失败后是否升级上下文重试 |
budget_per_task |
float | 0.50 |
单任务预算上限(美元) |
budget_daily |
float | 5.00 |
每日预算上限(美元) |
八、落地检查清单
- 任务集中的每个 Bug 都有已知的正确修复方案和回归测试
- 三种上下文配置的定义清晰、可复现
- 实验使用固定模型版本,避免模型更新干扰结果
- Token 成本和耗时由程序自动记录,不靠人工估算
- 评分采用盲评(不知道是哪种配置生成的结果)
- 按 Bug 类型分组统计,不只报总体数据
- 结果包含成本效率分析(每成功修复的平均成本)
- 实验脚本可一键重跑(
python context_size_experiment.py)
九、系列导航
上一篇:Bugfix 任务模板:日志、复现、补丁、回归测试四段式 下一篇:案例 001:用 Agent 改造电商订单系统的缺陷修复流程