# 第 6 章 召回率低：大型任务中 AI 为什么会"偷懒"

> 预计学习时间：80–100 分钟
> 一句话总结：把"AI 偷懒"还原为可观察的**输出衰减**、**步骤遗漏**和**流程控制失效**，然后从实验证据中找出真正需要解决的问题。

## 一场让人不安的审查

先讲一次真实经历。一个约 200 个文件、大约 16,000 diff 行的任务，交给 AI Code Review。开发者等了很久，最终拿到一批审查结果。翻看前面几个文件，问题定位准确，分析合理。翻到中间，描述开始变短。翻到最后几十个文件——AI 几乎没有报告任何问题。

不是这批代码写得特别好。人工抽查发现，后端有未关闭的资源、前端有遗漏的错误处理、工具函数缺少入参校验——都是审查规则明确要求检查的问题类型，但 AI 完全没有提到它们。

更让人不安的是，同样的模型、同样的 Prompt、同样的规则，在另一个只有 15 个文件、大约 2,000 行变更的 MR 上，这些类型的问题全部被正确识别。模型的能力没有变，规则没有变，代码类型也没有变。唯一不同的是任务规模。

这就是召回率问题的核心形态：模型不是不会做，而是在大任务里"不做了"。

## 先把"偷懒"翻译成工程语言

团队习惯用"AI 偷懒"描述这种现象。标签有助于传播，却不适合作为诊断依据。"偷懒"暗示主观动机——AI 累了、不想干了、敷衍了事——这些不是可观察、可验证的事实。

课程案例团队在复盘时，把"偷懒"拆成了几个具体可测量的行为模式。

输出衰减是指模型在任务前段给出详细分析，后段描述逐渐缩短，最终变成一两句话甚至空白。步骤遗漏是指 Prompt 明确要求审查后还要验证、去重、输出统计摘要，但模型直接跳到"下一批"或"结束"。上下文利用率下降是指早期审查引用了关联文件的具体行号，后期审查不再引用上下文，只用 diff 片段本身做判断。风险判断漂移是指同一类问题在任务前段被标为高风险，后段被标为低风险或完全不报告。

这些都是可观察的行为，不需要猜测模型"怎么想的"。你可以在审查记录中统计每个 Batch 的产出数量、描述长度、引用次数和评分分布，然后画出趋势线。如果后几批的趋势确实不同于前几批——而代码难度没有明显变化——你就在观察输出衰减。

```mermaid
flowchart LR
  B1["Batch 1: 8 问题, avg desc 180 字"] --> B2["Batch 3: 5 问题, avg desc 120 字"]
  B2 --> B3["Batch 6: 3 问题, avg desc 70 字"]
  B3 --> B4["Batch 9: 0 问题, avg desc -"]
  B4 --> FN["FN: 人工抽查发现遗漏的正确性问题"]
  style B1 fill:#c8e6c9
  style B4 fill:#ffcdd2
  style FN fill:#ffcdd2
```

图中每个 Batch 的问题数量下降可能有两种解释。一种是代码确实越来越好；另一种是审查在衰减。如果人工抽查在后期 Batch 发现了模型没有报告的问题，衰减就是真实存在的。

## 长上下文不等于长注意力

有一个公开研究经常被用来解释这种现象。Liu 等人在 2023 年发表了"Lost in the Middle"的观察：在需要从长文档中提取信息的任务中，模型对文档开头和结尾的信息利用较好，对中间部分的信息利用较差。这个效应不是模型"记不住"，而是注意力分布不均匀。

课程案例的上下文利用模式与这个观察部分一致，但有自己的特点。AICR 审查不是在一个长文档中找一条答案，而是在多个文件间反复判断、输出、再判断。模型在前几批审查中"活跃"地调用关联上下文，后面逐渐退化为只看 diff 本身。这不是中间信息被遗忘，而是模型不再主动检索。

更关键的是，审查任务有顺序。Batch 1 的审查结果会影响 Batch 2 的上下文窗口。如果前几批的审查消耗了大量输出 token，模型的后续输出质量可能下降——不是因为输入太长，而是因为输出太密集。

公开文献把长上下文的位置敏感性问题讲清楚了。但把"长上下文模型对信息位置敏感"直接翻译成"审查到第 N 批必然衰减"，跳过了太多具体机制。课程案例的观察是：衰减不是均匀发生的，它和单批任务量、批次间状态变化、模型需要维持的"任务记忆"有关。


## session 状态视角：衰减在哪个层面发生

审查衰减不只是"后面比前面差"。把每次审查的 Session 数据拆开，衰减发生在三个不同的层面。

第一个层面是 Batch 间的衰减。每个 Batch 独立审查一批文件，模型在前几个 Batch 中产出丰富，后面逐渐变少。这种衰减最容易观察——画一条 Batch 索引对问题数的曲线就能看到。

第二个层面是 Batch 内的衰减。同一个 Batch 中，模型审查前几个文件时给出详细分析，后几个文件逐渐简化。大文件中尤其明显：一个 500 行的文件，前 200 行的注释密度远高于后 300 行。

第三个层面是 Session 间的衰减。如果同一个开发者在短时间内发起多次审查——比如上午审了一个 50 文件的 MR，下午又审了一个 80 文件的 MR——第二次审查的质量可能低于第一次。这不是模型能力下降，而是模型的"任务疲劳"在跨 Session 传递。如果 Session 间共享了部分上下文窗口（如使用 resume 机制），前面的审查负担会影响后面的审查质量。

这三个层面需要不同的检测和应对策略。Batch 间衰减用质量门和批次大小控制。Batch 内衰减需要在文件排序上做文章——把复杂度高、风险大的文件放在 Batch 的前半部分，让它们在模型注意力最好的时候被审查。Session 间衰减需要监控 Session 创建频率和"审查负担"（一个窗口内的总 diff 行数），必要时提醒开发者间隔审查或拆分任务。

案例代码中的  有一个  检测——如果同一个 Session 被重复使用，系统返回已有的会话而不是创建新的。这个设计在节省 Token 的同时，也避免了不必要的跨 Session 累积。但它的副作用是：如果第一次审查就不够充分，重复使用时不会有机会重新审查，除非显式创建新 Session。

## 内容哈希与文件复用：哪些变化能真正帮到模型

衰减的一个反向思路是：不让模型审查不需要审查的代码。

 中的  机制实现了文件级别的变化检测。每个文件在首次审查时计算内容哈希，下次 Session 创建时比较哈希值。哈希相同的文件不会进入新的审查批次——它们直接归入"历史复用"批次，状态标记为 ，沿用上次审查的结果。

这个机制的价值不只在节省 Token。从召回率的角度看，它让模型的注意力集中在真正有变化的代码上。一个 200 文件的 MR，可能有一半是自动生成的类型定义、mock 数据或配置文件。如果这些文件没有实质变化却仍然进入审查，模型会在这批代码上消耗注意力，留给真实业务逻辑的注意力就少了。

文件复用需要配合合理的粒度。内容哈希的单位是文件，不是 diff 片段。如果一个文件的 500 行中只改了一行注释，整个文件的哈希都会变，整个文件都会被重新审查。这是必要的权衡——片段级别的复用需要更复杂的变化追踪，而文件级别在工程上更简单、更可靠。

从衰减诊断的角度，历史复用批次的数量也是一个信号。如果一个 Session 有大量文件被复用——比如 200 个文件中 150 个没有变化——那实际需要审查的只有 50 个文件。这个 Session 的"有效审查负荷"远比  小。用  做统计分析时，需要把复用文件的影响剥离，否则会低估单位文件的审查投入。

## 三次失败的尝试

在找到正确解法之前，案例团队做了三组实验。每组实验都有明确假设、控制条件和失败原因。把它们讲清楚，比直接跳到最终方案更有教学价值。

### 第一次尝试：加强 Prompt

假设是模型没有足够重视审查要求。团队在 Prompt 中增加了详细的审查清单、必须检查的项目、输出格式要求和"请认真完成每一步"的强调语。

结果：前几个 Batch 的问题数量确实上升了，但衰减模式没有改变。更糟的是，更长的 Prompt 压缩了可用上下文空间，模型在后期能看到的关联代码反而更少。

这组实验说明了一个重要原则：Prompt 可以告诉模型"做什么"，但不能告诉模型"怎样持续做到"。模型知道应该检查资源释放、空值处理、异常捕获——Prompt 写得足够清楚。问题不在"不知道"，而在"执行到后面时不再执行"。

### 第二次尝试：让 AI 自己管理流程

假设是任务拆分和状态管理应该由模型自己完成。团队给模型一段详细的流程指令，要求它自己阅读所有 diff，自己决定如何分步审查，自己检查是否遗漏，最后提交汇总。

结果：模型确实生成了计划，也执行了审查，但在大型任务中出现了三种新问题。第一种是幻觉计划——模型声称已经审查了某个文件，但日志显示它从未打开那个文件。第二种是计划退化——模型开始把越来越多的工作塞进同一个步骤，相当于绕过了自己的分步指令。第三种是收尾加速——最后几步明显变快，问题密度远低于前几步。

Anthropic 在讨论 Agent 设计时区分了两种模式：Workflow 由预定义代码路径编排模型与工具，Agent 由模型动态决定过程与工具使用。课程案例的这组实验恰好验证了这个区分：把流程控制交给模型本身，在简单任务中可行；任务复杂度一旦上升，模型对自身执行过程的感知就不够精确。

### 第三次尝试：人工对话拆分

假设是通过多次对话手动拆分任务，每次只审查少量文件。开发者把 200 个文件分给 10 次对话，每次 20 个文件。

结果：单次对话的衰减问题被缓解了。但新问题出现。首先，不同对话之间的审查标准不统一——同一类问题在第一次对话中被发现，在第三次对话中可能被忽略。其次，对话间缺少全局去重——同一个问题可能在不同对话中重复报告。第三，开发者负担大幅增加——拆分、发起、收集、汇总全部依赖人工。

这组实验说明：拆分任务方向正确，但人工拆分不可持续。真正需要的是一个外部系统，自动完成拆分、调度、状态追踪和质量验证。

```mermaid
flowchart TD
  E1["尝试 1: 加强 Prompt"] --> R1["衰减未改变, 上下文被压缩"]
  E2["尝试 2: AI 自管流程"] --> R2["幻觉/退化/收尾加速"]
  E3["尝试 3: 人工对话拆分"] --> R3["标准不统一, 缺全局去重, 人工负担大"]
  R1 --> INSIGHT["关键洞察: 流程控制必须由外部系统接管"]
  R2 --> INSIGHT
  R3 --> INSIGHT
```

每组实验的失败方向不同，但指向同一个结论：模型在大型任务中的衰减，不能靠模型自身的能力来纠正。需要外部系统负责三件事——把大任务切成小块、驱动每一步的执行与验证、在失败时恢复而不是跳过。

## 从代码中读取设计意图

案例代码的质量门配置提供了一个观察窗口。打开 `utils/config.ts`，你会看到四个关键阈值。

`minTimeCoefficient` 的默认值是 0。这意味着时间验证默认关闭——实际审查时间即使为 0，也会通过时间检查。`minDensityCoefficient` 的默认值也是 0，密度验证默认关闭。`highRiskScoreThreshold` 默认值是 999，意味着没有任何问题会被当作高风险要求提供代码建议。`minContentLength` 默认值是 0，意味着空描述也能通过质量检查。

这些值不是设计错误。它们是兜底默认值，确保数据库加载失败时服务仍能正常运行。但它们的含义很明确：质量门需要显式配置才能生效，不能假设"服务启动了就等于质量门在运行"。

代码中的 `verifier.ts` 实现了完整的三层验证逻辑。质量验证检查必填字段——file_path、line、score、category、content 缺一不可，同时检查高分数问题的内容长度是否达标。时间验证比较实际审查时间与期望时间乘以系数。密度验证比较实际发现问题数与期望问题数乘以系数。时间与密度需要同时通过——如果都失败，审查会被打回重做。

但 `verifier.ts` 的代码本身不决定阈值。阈值由 `configService` 从数据库读取，数据库没有配置时回退到 `config.ts` 的兜底值。这意味着同一套代码在不同环境可能表现出完全不同的验证行为。一个环境的 `minTimeCoefficient` 设为 0.3，另一个环境的同一参数仍为 0。前者会拦截过快审查，后者不会。

这个设计有工程合理性——配置热更新避免重启，兜底值避免崩溃——但它制造了一个认知陷阱：只看代码逻辑的人以为质量门在运行；只读过设计文档的人以为阈值已校准；实际效果由数据库中的配置决定，而数据库配置可能没有在文档中反映。

课程案例团队在实际运行中通过数据库热配置开启了质量门，并逐步调整了阈值。这个过程的经验是：先让门运行但不阻断（记录告警），收集一段时间数据后确定基线，再逐步收紧。直接从关闭切到严格阻断，可能导致大量正常审查被误杀。

## 批次大小如何影响衰减

案例的批次分配算法把 800–1200 diff 行作为理想范围，但这不是固定的魔法数字。它来自实验观察：在这个范围内，单批审查的完成度相对稳定；超过 1200 行，问题密度的下降开始加速。

这个数字的另一个来源是模型上下文窗口的工程约束。每批审查需要把 diff、关联上下文、规则、Prompt 指令和输出空间全部放进上下文窗口。如果 diff 太大，留给关联上下文和输出的空间就变小。模型可能被迫在"读取足够上下文"和"输出足够分析"之间做隐式取舍。

代码中的 Batch Allocator 使用三层算法。第一层按目录和文件类型做语义分组，保证相关文件在同一批。例如 `/pages/` 下的文件与 `/components/` 下的文件分到不同组，Hooks 与类型定义也分开。这背后的逻辑是：同组文件共享更多上下文关联，分开审查会丢失这些关联。

第二层合并相似度高的组，避免碎片化。同目录下不同文件类型的组，如果相似度超过 0.8，会被合并。跨目录的组如果相似度超过 0.6，也会考虑合并。目标是在"保持语义内聚"和"避免碎片化"之间平衡。

第三层验证完整性，确保每个文件都出现在某个批次中。如果验证失败，算法会抛出错误而不是静默丢失文件。

这个设计不只关心"把任务拆开"，更关心"怎么拆才让模型最容易处理"。语义分组的价值在于减少模型的认知跳跃。当一个批次里既有前端组件又有后端 SQL，模型需要在两种完全不同的审查模式之间切换。把同目录、同类型文件分在一起，模型的审查节奏更稳定，衰减也更可控。

```mermaid
flowchart TD
  F["变更文件列表"] --> P1["阶段 1: 语义分组<br/>目录 + 文件类型"]
  P1 --> P2["阶段 2: 相似度合并<br/>高相似度 > 0.8 合并<br/>中相似度 > 0.6 合并"]
  P2 --> P3["阶段 3: 工作量拆分<br/>超过 1200 行拆分<br/>不足 800 行合并"]
  P3 --> P4["完整性验证<br/>无遗漏文件, 无重复分配"]
  P4 --> B["输出批次列表"]
  P3 -. "拆分后仍过大" .-> FALLBACK["兜底拆分: 按 100 行切分"]
  FALLBACK --> P4
```

## 状态机如何防止"跳过"

审查流程的每一步由 `allowed_next_step` 字段控制。这不是一个建议值，而是硬约束。Agent 请求"下一个任务"时，服务端只返回当前步骤允许的任务。如果当前步骤是 2（等待提交审查结果），Agent 不会收到"开始扩展检查"的任务；必须先完成审查结果的提交并通过验证。

这个设计的核心思想是，把"模型应该做什么"变成"系统允许模型做什么"。模型不会被告知"请检查你是否完成了所有步骤"，因为经验已经证明这个提示在大任务中会被忽略。系统直接通过状态字段限制下一步。

完整的状态序列是 `0 → 1 → 2 → 2.x → 3`。Step 0 是环境检查，确认 CLI 版本和 MCP 连接。Step 1 是审查任务下发，返回当前批次的 diff、关联上下文和审查规则。Step 2 是提交审查结果，然后触发质量验证。验证通过后进入 Step 2.x（扩展检查），不通过则打回 Step 1。Step 3 是所有批次完成后汇总输出。

如果 Agent 在 Step 2 提交了不完整的结果，验证失败，`allowed_next_step` 会被重置为 1。Agent 必须重新审查同一批代码。重试次数有上限——当前代码中 `maxRetryCount` 默认为 3——超过后强制通过，避免死循环。

这里有一个值得讨论的设计选择：超过重试上限后强制通过。如果质量门持续失败，可能意味着阈值设置不合理，或者这批代码确实非常干净。强制通过保留了流程推进能力，但牺牲了质量保证。课程案例团队在实践中会监控重试次数异常升高的 Session，手动复查阈值和代码。

降级路径同样重要。如果 Agent 在 Step 2 提交时没有附带 `batch_results`，系统不会报错退出，而是回退到 Step 1 重新下发审查任务。如果扩展检查结果缺失，系统也会清空扩展状态并回退。降级保证流程不会因为一次通信失败而永久挂起。

```mermaid
stateDiagram-v2
  [*] --> Step0: Session 创建
  Step0 --> Step1: 环境检查通过
  Step1 --> Step2: Agent 领取审查任务
  Step2 --> Step2: 质量验证
  Step2 --> Step1: 验证失败, 重试 < maxRetryCount
  Step2 --> Step2x: 验证通过, 进入扩展检查
  Step2x --> Step2x: 逐个执行扩展检查器
  Step2x --> Step1: 还有未审查批次
  Step2x --> Step3: 所有批次完成
  Step3 --> [*]: 汇总输出
  Step2 --> Step3: 超过 maxRetryCount 强制通过
```

## 把衰减量化：建立审查完成度信号

光靠感觉说"后面审查质量不好"是不够的。需要几个可以持续计算的信号，它们不需要人工标注，可以直接从审查记录中提取。

每个 Batch 的问题数量是最直观的信号，但它受代码复杂度影响太大。更好的做法是比较问题数量与预期基准，或者用同仓库相似规模任务的历史分布作为参照。如果当前 Batch 有 500 行 diff，同仓库历史数据中相似 Batch 的中位问题数是 4，而当前只发现 1 个，这就是异常信号。

描述长度是另一个有效信号。它不是越长越好，但急剧缩短往往意味着分析深度下降。如果前几个 Batch 的平均描述是 180 字，最后一个 Batch 变成 30 字，即使代码确实更简单，这种幅度也值得抽查。

上下文引用次数可以直接从日志和 Issue 数据中提取。如果某条 Issue 的 `existing_code` 或 `improve_code` 字段为空，或者内容中没有提到任何非 diff 文件的符号，很可能模型只看了 diff 本身。连续多个 Batch 的引用率下降，是衰减的有力证据。

验证位掩码提供了结构化的信号。代码中用位掩码记录每条 Issue 的 category、intent、score 是否在预定义枚举内。十进制值 7（二进制 111）表示全部正常。如果某个 Batch 出现较多非 7 的位掩码值，可能意味着模型输出开始偏离规范——这是更早期的衰减信号。

这些信号可以组合。一个 Batch 同时出现低问题数、短描述、零引用和非全 7 位掩码，比单一指标的异常更有说服力。可以在看板中为每个 Batch 计算一个综合完成度分数，标记出需要人工抽查的低分 Batch。

## 从漏报到真值：召回率的分母建设

第 3 章讲过，召回率的分母需要从独立来源构造。现在把这个原则放到大型任务衰减的语境中。

假设一次 200 文件的审查产生了 10 个 Batch。AI 总共发现 30 个问题，全部被判定为有效。如果只看 AI 自己的输出，你会认为"系统发现了 30 个问题，没有漏掉任何东西"。但这是循环论证——你用来检查漏报的唯一数据，正是 AI 的输出。

真值必须来自外部。人工 CR 可能在这批代码中发现了 12 个 AI 没有提到的问题。后续测试可能发现了 5 个 Bug，其中 3 个属于代码审查范围。把这些外部发现按规则分类，才能算出真正的遗漏率。

课程案例的 `bugSync.ts` 实现了 Bug 归因的三层判定。第一层按 Bug 状态过滤——只有 Done 状态的 Bug 才考虑纳入。第二层按 Bug 原因分类——Code Logic、Security、Performance、Compatibility 直接进入候选召回列表；PRD Issue、Environment、Data Configuration、Advice/improvement 直接排除。第三层按 Bug 标签做白名单匹配——`slow_sql` 和 `joint-doc-api-inconsistent` 即使原因不在直接召回列表，也标记为应召回。

经过这三层，剩下的标记为 `should_recall` 或 `pending`。`pending` 是需要人工判断的灰色地带——它没有被自动排除，也没有明确证据要求召回。`pending` 的比例本身就是一个重要指标：如果大量 Bug 落在 `pending`，说明归因规则需要细化。

然后还要做角色归属。当前代码按 FE、BE、PM、unknown 分类。只有 FE 和 BE 的问题进入召回分母。PM 问题和 unknown 归属的问题被排除。这个选择有实际理由——PM 问题可能涉及需求理解而非代码审查能力——但它同时引入了归属偏差。一个真实的后端缺陷如果 Bug 描述中没有明确的技术角色标识词，可能被归为 unknown 而排除。

```mermaid
flowchart TD
  B["全部 Bug 记录"] --> S1{"status = Done?"}
  S1 -- "否" --> X1["排除"]
  S1 -- "是" --> S2{"bug_reason?"}
  S2 -- "Code Logic / Security / Performance / Compatibility" --> RECALL["should_recall"]
  S2 -- "PRD / Environment / Data Config / Advice" --> X2["not_recall"]
  S2 -- "其他" --> S3{"labels 白名单?"}
  S3 -- "slow_sql / joint-doc-api-inconsistent" --> RECALL
  S3 -- "否" --> PENDING["pending: 待人工判定"]
  RECALL --> S4{"owner_role?"}
  S4 -- "FE / BE" --> DENOM["进入召回分母"]
  S4 -- "PM / unknown" --> X3["角色排除"]
```

## 一个衰减诊断的完整案例

下面构造一个教学案例来展示完整的诊断过程。所有数据是教学构造，不代表任何真实仓库的运行数据。

一次任务有 12 个 Batch，每个 Batch 约 900–1100 diff 行。模型配置、规则版本、Prompt 模板在审查期间没有变化。

Batch 1–4 共发现 22 个问题，人工确认全部有效，平均描述 165 字。Batch 5–8 发现 11 个问题，人工确认有效，平均描述 102 字。Batch 9–12 发现 3 个问题，平均描述 45 字。

人工 CR 在 Batch 9–12 的代码中另外发现了 7 个问题，其中 4 个属于审查规则明确要求检查的类型（未释放资源、空值风险、异常处理缺失），另 3 个属于规则未覆盖的建议。

测试阶段发现了 2 个 Bug，其中 1 个（未释放数据库连接）被判定为 `should_recall` 且归属 BE，另 1 个被判定为环境配置问题，不纳入召回统计。

```mermaid
flowchart TD
  S["12 Batch 审查任务"] --> F["Batch 1-4: 22 问题, avg 165 字"]
  S --> M["Batch 5-8: 11 问题, avg 102 字"]
  S --> L["Batch 9-12: 3 问题, avg 45 字"]
  L --> FN["人工 CR 发现 4 个应召回问题<br/>测试发现 1 个应召回 Bug"]
  FN --> DIAG["诊断结论: 后段存在显著衰减<br/>5 个 FN 中 4 个属于规则覆盖范围"]
```

诊断结论：后段存在显著衰减。5 个 FN 中有 4 个属于当前审查规则明确覆盖的类型——系统有能力发现它们，但在后段没有发现。衰减不是模型能力不足，而是执行持续性不足。

从这个诊断出发的工程方向：检查 Batch 9–12 的质量门是否通过；检查后段 Batch 的实际审查时间是否异常短；检查是否需要更小的批次或更严格的验证阈值；评估是否需要为后段 Batch 启用更强的上下文注入。

如果只看总体平均，12 个 Batch 共发现 36 个问题，人工发现了 7 个遗漏，综合 Recall 是 `36 / (36 + 7) ≈ 83.7%`。这个数字看起来还不错，但它掩盖了后段 4 个 Batch 几乎全部遗漏的事实。分层诊断的价值正在于此。

## 为什么不是所有漏报都需要"修复"

发现了衰减，不等于所有衰减都需要立即处理。有些漏报的工程成本可能高于修复它带来的收益。

一个极端例子：某个低分维护建议在后段被遗漏了。它确实属于"应召回"范围，但采纳它只改了一行注释。为了捕获这类遗漏而去缩小批次、增加验证轮次、提高 Token 成本，整体收益为负。

课程案例团队在实践中建立了漏报的分层优先级。第一优先级是安全问题和可能导致线上故障的正确性问题——这类漏报即使只有一个也值得投入资源。第二优先级是健壮性问题——空值、异常、资源释放——这些在高频路径上的遗漏会产生累积风险。第三优先级是规范和可维护性建议——这些更适合通过静态工具和 lint 规则覆盖，而不是扩大 AI 审查的范围。

分层不只影响修复决策，也影响质量门的设计。可以为不同严重度设置不同的验证强度：高风险问题的密度阈值更严，低风险问题的阈值更宽。`verifier.ts` 当前对所有问题一视同仁——它只检查总问题数是否达到期望密度。未来可能的演进方向是按类别分别设置密度基线，但这需要更细粒度的标注数据。

```mermaid
flowchart LR
  P1["P1: 安全/正确性, 零容忍, 单个即触发复查"] --> A1["严格阈值, 小批次优先"]
  P2["P2: 健壮性, 高频路径累积风险"] --> A2["中等阈值, 监控趋势"]
  P3["P3: 规范/可维护性, 静态工具可覆盖"] --> A3["宽松阈值, 不单独扩大审查范围"]
```


## 文件过滤对衰减的影响

审查前的文件过滤看似只是减少工作量，实际上也改变了衰减的起点。

 中有一个  方法，用两套正则表达式过滤变更文件。白名单按扩展名匹配——默认包含 ts、js、vue、tsx、jsx、go、kt、java 等编程语言文件。黑名单排除测试文件、mock 数据、配置文件、文档和自动生成代码。

被排除的文件不会进入审查批次。这意味着一次 200 文件的变更，经过过滤后可能只剩 120 个文件需要审查。过滤不是因为那些文件不重要——测试文件当然重要，但让 AI 审查测试代码的 ROI 通常低于审查业务代码。测试代码的问题更多体现在"是否覆盖了关键路径"而不是"代码写得好不好"，前者更适合用覆盖率工具检查。

过滤也有代价。如果黑名单过宽，可能漏掉真正的风险——一个 CI/CD 配置文件的错误可能比一个前端组件的语法问题严重得多。如果白名单过窄，可能遗漏新语言或新框架的文件——例如项目引入了 Rust 或 Python 模块，但白名单中没有对应的扩展名。

课程案例团队的实践是先用保守的默认过滤运行，然后根据反馈调整。如果某种被排除的文件类型反复出现人工 CR 发现的问题，就把那种类型加入白名单。如果某种已包含的类型从未产生有价值的审查结果（例如自动生成的 protobuf 代码），就加入黑名单。过滤规则本身也需要迭代。

## 成本衰减：Token 消耗如何随批次变化

讨论衰减时，另一个重要的信号是 Token 消耗模式。单批审查的成本不是固定的——前几批通常消耗更多 Token，因为模型在建立对代码库的理解。后几批的 Token 消耗可能下降，既因为模型已经理解了项目结构，也因为模型开始在分析上"节省"。

如果后几批的 Token 消耗远低于前几批，而同批 diff 行数相近，这可能是衰减的信号。Token 消耗下降太快——比如从 25,000 降到 5,000——说明模型输出的分析文本在急剧缩短。如果同时伴随着问题数量下降，衰减的可能性很大。

课程案例团队在实践中同时监控输出 Token 趋势和问题密度趋势。一条典型的警戒线是：连续两个 Batch 的输出 Token 下降超过 50%，且问题密度同时低于基线 50%。这种情况下很可能需要触发质量门的重新审查。

Token 成本也制约了对抗衰减的手段。为每个后段 Batch 增加额外的"请仔细审查，不要遗漏"提示会增加 Token 消耗，而且效果有限（第 6 章第一次尝试已经验证）。为后段 Batch 注入更多上下文——比如整个项目的架构概览——也会增加 Token 消耗。召回率提升和成本控制之间存在竞争，需要在第 7 章的质量门设计中一并考虑。


## 衰减诊断速查表

把本章讨论的诊断信号汇总为一张速查表，用于实际审查质量监控。

| 信号 | 正常模式 | 衰减模式 | 数据来源 |
| --- | --- | --- | --- |
| Batch 问题密度 | 与代码复杂度相关，波动合理 | 后段密度持续低于前段 50% 以上，且代码复杂度未下降 | batch_results |
| 平均描述长度 | 各 Batch 相对稳定 | 后两个 Batch 平均长度下降超过 50% | Issue.content |
| 上下文引用率 | 多数 Issue 包含关联代码引用 | 连续两个 Batch 无关联文件引用 | existing_code / improve_code |
| 验证位掩码 | > 90% 为全 7 | 某 Batch 非全 7 比例超过 30% | validation_flags |
| 输出 Token 趋势 | 随 Batch 索引平缓下降 | 连续两个 Batch 下降超过 50% | 模型 API 日志 |
| 质量门重试次数 | 0-1 次 | 同一 Batch 重试达到 maxRetryCount | batch.retry_count |

单一信号触发时，先检查代码特征是否变化——后段文件确实更简单时，低密度是合理的。两个以上信号同时触发，衰减的可能性大幅增加，建议人工抽查对应 Batch 的代码和 Issue。

## 本章收束

"AI 在大型任务中偷懒"是一个有效的现象标签，但它不解释机制。真正发生的是三件事：输出衰减使后段描述和分析变浅，步骤遗漏使验证、去重等必要操作被跳过，流程控制失效使模型对自身执行状态的感知失准。

加强 Prompt、让 AI 自管流程和人工对话拆分分别解决了问题的一部分，但都在大规模任务中暴露出新的失效模式。核心矛盾是：模型的能力足够发现这些问题，但模型自身无法持续、可靠地驱动完整流程。

解决方案的方向已经清晰。需要一个外部系统负责把大任务切分成可管理的小块、用状态机限制每一步的行为、用质量门验证每一步的产出、并在失败时进入可控恢复而不是静默跳过。这就是从 Prompt-only 到 Harness 的核心过渡——也是第 7 章要展开的主题：如何用分批、状态机、质量门和数据回流把召回率从 40% 推向 50%。

## 本章源码观察点

以下代码路径与本章讨论直接对应：

- `utils/config.ts`：`batchAllocation.idealMinLines`（800）和 `idealMaxLines`（1200）定义了批次大小边界；`verification` 中的 `minTimeCoefficient=0`、`minDensityCoefficient=0`、`highRiskScoreThreshold=999`、`minContentLength=0` 展示默认关闭的质量门。
- `verifier.ts`：`verifyWorkload` 实现质量验证 → 时间或密度验证的双层检查；`verifyTime`、`verifyDensity` 和 `verifyQuality` 分别对应三种验证策略；`maxRetryCount=3` 控制重试上限。
- `batchAllocator.ts`：三阶段算法——语义分组、相似度合并、工作量拆分；兜底拆分按 100 行切分为最后的保底路径。
- `processor.ts`：`allowed_next_step` 状态机驱动 0→1→2→2.x→3；`fallbackToStep1` 实现降级恢复。
- `bugSync.ts`：`judgeRecallStatus` 的三层过滤（status → bugReason → labels）和 `judgeOwnerRoleByContent` 的角色归属判定。
- `verifier.ts` 中的 `VALIDATION_FLAGS` 位掩码（category、intent、score 各占一位）可作为结构化审查完成度信号。

## 参考文献

1. Nelson F. Liu 等. [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172). TACL, 2023.
2. Anthropic. [Building effective agents](https://www.anthropic.com/engineering/building-effective-agents). 2024-12-19.

本章以课程案例的实际实验数据、当前代码实现和脱敏审查记录为主要证据来源。"AI 偷懒"标签仅用于引入现象，机制解释以可观察的衰减行为为准。实验数据来自案例团队内部记录，经教学化脱敏处理；不构成对任意模型或任务规模的通用断言。


---

