用 Agent 修 Bug,最怕的是"修好了但不知道修好没"。本文给出一套可复用的 Bugfix 任务模板,把缺陷修复拆成日志采集、稳定复现、最小补丁、回归测试四个阶段,每个阶段都有对应的 Prompt 模板、上下文包模板和验收标准,让 Agent 修 Bug 不再是"碰运气"。

Bugfix 任务模板:日志、复现、补丁、回归测试四段式

用 Agent 修 Bug,最怕的是"修好了但不知道修好没"。本文给出一套可复用的 Bugfix 任务模板,把缺陷修复拆成日志采集、稳定复现、最小补丁、回归测试四个阶段,每个阶段都有对应的 Prompt 模板、上下文包模板和验收标准,让 Agent 修 Bug 不再是"碰运气"。

一、为什么 Bugfix 需要模板化

一个典型的 Agent Bugfix 流程是这样的:开发者把 Issue 链接扔给 Agent,Agent 读完 Issue 就开始改代码。问题在于——Issue 里的信息通常不够:错误日志只截了一半、复现步骤缺失、涉及文件没标注。Agent 只能靠猜,猜错了就反复改,最终提交一个"看起来能用"的补丁。

把"碰运气"换成"四段式":

阶段 没有模板 有四段式模板
日志采集 Agent 自己翻日志,可能漏掉关键堆栈 日志模板指定时间范围、服务名、错误码、完整堆栈
复现 "在我这里跑不起来" 复现脚本 + 最小数据集,Agent 和人都能跑
补丁 改了一大片,不知道改对了没 限定修改范围,只动根因相关代码
回归测试 手动点一遍 自动化回归用例,CI 直接验证

模板的价值不是限制 Agent,而是把人的经验固化成 Agent 可以执行的步骤。

二、四段式流程架构

text
┌──────────────┐    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│  第一段:日志  │───▶│  第二段:复现  │───▶│  第三段:补丁  │───▶│  第四段:回归  │
│  采集与分析    │    │  脚本与数据    │    │  最小化修改    │    │  测试与验收    │
└──────────────┘    └──────────────┘    └──────────────┘    └──────────────┘
       │                   │                   │                   │
  输出:                输出:                输出:                输出:
  · 错误堆栈           · repro.py           · diff.patch         · test_report.json
  · 影响范围           · fixture data        · 修改说明           · 覆盖率报告
  · 候选文件           · 预期 vs 实际        · 关联测试           · 验收结论

每个阶段都有输入处理输出。上一段的输出是下一段的输入。如果某一段无法完成(比如日志不足以定位问题),流程回到上一段补充信息,而不是硬着头皮往下走。

三、第一段:日志采集模板

3.1 Prompt 模板

yaml
# bugfix-phase1-log-collection.yaml
name: Bugfix-Phase1-日志采集
version: "1.0"
description: 从错误报告和日志中采集完整的缺陷信息

inputs:
  issue_url:       # Issue 链接或错误报告
  log_source:      # 日志来源:文件路径 / ELK / CloudWatch
  time_range:      # 日志时间范围
  service_name:    # 服务名(微服务场景)

prompt: |
  你是一个缺陷分析专家。请根据以下信息完成日志采集:

  ## 已知信息
  - Issue:{{issue_url}}
  - 日志来源:{{log_source}}
  - 时间范围:{{time_range}}
  - 服务:{{service_name}}

  ## 任务
  1. 提取完整的错误堆栈(不要截断)
  2. 识别错误类型(NullPointerException / TimeoutError / DataError / ...)
  3. 标注错误发生的代码路径(文件:行号)
  4. 列出可能相关的上下文日志(错误前后 30 秒)
  5. 给出候选文件列表(最可能包含 Bug 的文件,按可能性排序)

  ## 输出格式
  严格按以下 JSON 输出:
  ```json
  {
    "error_type": "",
    "error_message": "",
    "stack_trace": "",
    "code_path": ["file:line", ...],
    "context_logs": ["..."],
    "candidate_files": [{"path": "", "reason": "", "confidence": 0.0}],
    "impact_scope": {"affected_modules": [], "affected_users": "unknown"}
  }
text

### 3.2 日志采集示例输出

```json
{
  "error_type": "DataError",
  "error_message": "Cannot read property 'amount' of undefined at OrderService.calculateTotal",
  "stack_trace": "TypeError: Cannot read property 'amount' of undefined\n  at OrderService.calculateTotal (src/services/order.js:142)\n  at OrderController.checkout (src/controllers/order.js:58)",
  "code_path": ["src/services/order.js:142", "src/controllers/order.js:58"],
  "candidate_files": [
    {"path": "src/services/order.js", "reason": "直接报错位置,order.items 为 undefined", "confidence": 0.85},
    {"path": "src/controllers/order.js", "reason": "调用方,可能传入了不完整的 order 对象", "confidence": 0.45}
  ],
  "impact_scope": {"affected_modules": ["order", "checkout"], "affected_users": "unknown"}
}

四、第二段:复现模板

4.1 Prompt 模板

yaml
# bugfix-phase2-reproduction.yaml
name: Bugfix-Phase2-稳定复现
version: "1.0"

inputs:
  phase1_output:    # 第一段输出
  repo_path:        # 仓库路径
  test_env:         # 测试环境配置

prompt: |
  你是一个测试工程师。根据第一段的日志分析结果,编写稳定的复现脚本。

  ## 错误信息
  {{phase1_output.error_message}}

  ## 候选文件
  {{phase1_output.candidate_files | map('path') | join(', ')}}

  ## 任务
  1. 编写最小复现脚本(Python 或 Shell),不依赖生产数据库
  2. 准备 fixture 数据(JSON 格式),模拟触发 Bug 的输入
  3. 断言"错误情况"(当前行为)和"期望情况"(修复后行为)
  4. 脚本必须能在本地 30 秒内运行完成

  ## 输出
  - tests/reproduce/test_bug_<issue_id>.py
  - tests/reproduce/fixtures/<issue_id>.json
  - 复现命令:pytest tests/reproduce/test_bug_<issue_id>.py -v

4.2 复现脚本示例

python
# tests/reproduce/test_bug_1234.py
"""
Issue: #1234 订单结算时 amount 为 undefined 导致 500
复现条件:订单包含已下架商品时,items 字段被过滤为空数组
"""
import pytest
from src.services.order import OrderService

def test_calculate_total_with_empty_items():
    """复现:当 order.items 为空时,calculateTotal 应返回 0 而非抛异常"""
    service = OrderService()

    # 模拟已下架商品被过滤后的订单
    order = {
        "id": "ORD-1234",
        "items": [],  # 根因:items 被过滤后为空,但代码假设 items[0] 存在
        "coupon": None,
    }

    # 当前行为:抛出 TypeError
    # 期望行为:返回 total = 0
    result = service.calculate_total(order)
    assert result == {"total": 0, "item_count": 0}

def test_calculate_total_with_none_items():
    """边界:items 字段缺失时不应崩溃"""
    service = OrderService()
    order = {"id": "ORD-1235", "coupon": None}

    result = service.calculate_total(order)
    assert result == {"total": 0, "item_count": 0}

五、第三段:补丁模板

5.1 Prompt 模板

yaml
# bugfix-phase3-patch.yaml
name: Bugfix-Phase3-最小补丁
version: "1.0"

inputs:
  phase1_output:    # 日志分析结果
  phase2_output:    # 复现脚本路径
  repo_path:        # 仓库路径

prompt: |
  你是一个高级开发者。根据日志分析和复现脚本,编写最小化补丁。

  ## 约束
  1. 只修改根因相关代码,不做"顺便重构"
  2. 修改行数不超过 20 行(如果超过,说明你没有找到根因)
  3. 必须在修改前阅读相关代码的完整上下文(前后 50 行)
  4. 每次修改必须附带注释说明"为什么这样改"

  ## 修改流程
  1. 阅读候选文件,确认根因位置
  2.  grep 搜索相同模式是否在其他地方也存在
  3. 编写修复代码
  4. 运行复现测试,确认通过
  5. 运行相关模块的完整测试套件,确认无回归

  ## 输出
  - 修改的文件列表和 diff
  - 修改说明(每个文件改了什么、为什么)
  - 测试运行结果

5.2 补丁结果报告模板

json
{
  "patch_id": "PATCH-1234-001",
  "issue_id": "#1234",
  "root_cause": "OrderService.calculateTotal() 假设 order.items 非空,直接访问 items[0].amount,当 items 为空数组时抛出 TypeError",
  "files_changed": [
    {
      "path": "src/services/order.js",
      "lines_added": 3,
      "lines_removed": 1,
      "change_summary": "在访问 items[0] 前增加空数组检查,items 为空时直接返回 total=0"
    }
  ],
  "total_lines_changed": 4,
  "tests_added": ["tests/reproduce/test_bug_1234.py"],
  "tests_passed": 12,
  "tests_failed": 0,
  "side_effects_checked": [
    {"file": "src/services/refund.js", "reason": "相同模式,已检查,无此问题"},
    {"file": "src/services/shipping.js", "reason": "相同模式,已修复"}
  ],
  "verification_command": "pytest tests/reproduce/test_bug_1234.py tests/services/test_order.py -v"
}

六、第四段:回归测试模板

6.1 Prompt 模板

yaml
# bugfix-phase4-regression.yaml
name: Bugfix-Phase4-回归验收
version: "1.0"

inputs:
  phase3_output:    # 补丁结果
  repo_path:        # 仓库路径

prompt: |
  你是一个 QA 工程师。验证补丁的正确性和完整性。

  ## 验收清单
  1. 复现测试是否通过(证明 Bug 已修复)
  2. 相关模块测试是否全部通过(证明没有引入回归)
  3. 边界条件是否覆盖(空值、null、极端输入)
  4. 相同模式的代码是否都已修复(grep 搜索结果)
  5. 代码变更是否在 20 行以内(超过需重新评估方案)

  ## 输出格式
  严格按以下 JSON 输出验收报告:
  ```json
  {
    "verdict": "PASS | FAIL | CONDITIONAL_PASS",
    "bug_fixed": true,
    "regression_found": false,
    "coverage_delta": "+0.3%",
    "checks": [...],
    "recommendation": "可以合并 | 需要补充 | 需要重做"
  }
text

### 6.2 验收报告示例

```json
{
  "verdict": "PASS",
  "bug_fixed": true,
  "regression_found": false,
  "coverage_delta": "+0.3%",
  "checks": [
    {"name": "复现测试通过", "status": "PASS", "detail": "test_bug_1234.py 2/2 通过"},
    {"name": "模块测试通过", "status": "PASS", "detail": "test_order.py 12/12 通过"},
    {"name": "边界条件覆盖", "status": "PASS", "detail": "空数组、null、缺失字段均已覆盖"},
    {"name": "同模式扫描", "status": "PASS", "detail": "refund.js 和 shipping.js 已修复"},
    {"name": "变更行数", "status": "PASS", "detail": "共修改 4 行,低于 20 行阈值"}
  ],
  "recommendation": "可以合并"
}

七、真实经验与踩坑

7.1 复现脚本不能依赖生产数据

场景:Agent 写的复现脚本直接查生产数据库来获取"出错的那条记录"。 问题:CI 环境没有生产数据库访问权限,脚本在 CI 里跑不通;而且生产数据随时在变,今天能复现明天就不行了。 解决方案:所有复现脚本必须使用 fixture 数据(JSON 文件或 factory_boy)。fixture 文件跟测试代码一起提交到 Git,任何环境都能跑。如果 Bug 涉及特定的数据组合,把那个组合提取成最小 fixture,而不是 dump 整条生产记录。

7.2 补丁太大说明没找到根因

场景:Agent 提交了一个改了 8 个文件、120 行的补丁来修一个"undefined"错误。 问题:改太多文件意味着 Agent 在"到处试探",而不是精准修复。这种补丁 Review 成本极高,而且容易引入新问题。 解决方案:在补丁模板中硬性约束"修改不超过 20 行"。如果 Agent 说需要改更多,让它先停下来,重新分析根因。通常是漏看了某个关键逻辑,或者在错误的层级做修复。真正的根因修复往往只需要 1-5 行。

7.3 回归测试不只是"跑一遍现有测试"

场景:Agent 修完 Bug 后跑了 pytest,全部通过,然后报告"无回归"。 问题:现有测试可能根本没覆盖到 Bug 所在的代码路径。测试通过不代表 Bug 修好了,只代表没破坏已有功能。 解决方案:回归测试必须包含三部分——① 针对这个 Bug 的新测试(证明修好了);② 修改文件的已有测试(证明没破坏);③ 用变更分析工具选出的关联测试(证明影响范围可控)。文章中的 SmartTestSelector(参见上一篇质量门禁)可以自动完成第三步。

八、参数说明表

参数 类型 默认值 说明
issue_url string 必填 Issue 或错误报告链接
log_source string 必填 日志来源,支持文件路径 / ELK / CloudWatch
time_range string "1h" 日志采集时间范围,支持 30m / 1h / 24h
max_patch_lines int 20 补丁最大修改行数,超过需重新评估方案
candidate_file_limit int 5 候选文件数量上限
confidence_threshold float 0.3 候选文件置信度阈值,低于此值不纳入
fixture_format string "json" 复现数据格式,支持 json / yaml / csv
repro_timeout_sec int 30 复现脚本最大运行时间
regression_test_scope string "smart" 回归测试范围:smart(智能选择)/ full(全量)/ changed(仅修改文件)
max_retry_on_fail int 2 单阶段失败后最大重试次数
output_format string "json" 每阶段输出格式
approval_required bool false 补丁提交前是否需要人工审批

九、落地检查清单

  • 日志采集输出包含完整堆栈,没有被截断
  • 候选文件列表附带置信度和原因说明
  • 复现脚本使用 fixture 数据,不依赖生产环境
  • 复现脚本在 CI 环境中能 30 秒内跑完
  • 补丁修改行数不超过 20 行
  • 补丁附带修改原因注释
  • 相同模式的代码已用 grep 扫描并一并修复
  • 针对此 Bug 的回归测试已编写并通过
  • 修改文件的已有测试全部通过
  • 验收报告按模板 JSON 格式输出
  • 整个四段式流程的输入输出都记录在 Issue 评论中

十、系列导航

上一篇:Agent 质量门禁:测试、Lint、安全扫描与代码审查闭环 下一篇:Lab 005:上下文包越大越好吗?Token 成本与修复成功率实验