做 Code Review Agent 时,最容易发生的事不是 Prompt 写错,而是 Prompt 越改越对。
某次模型漏掉了一个空指针风险,于是在 Prompt 里补一句:
不要遗漏潜在风险。
下一次,它把一个理论上可能发生、实际路径根本走不到的并发问题也报了出来。于是再补一句:
不要过度推测,只报告有充分依据的问题。
误报少了,但模型也变得保守。一个需要跨文件追踪的资源泄漏没有被指出,只好继续补:
对复杂调用链要适当深入分析。
几轮之后,Prompt 里出现了那句很熟悉的话:
多分析,但不要猜;谨慎一点,但不要漏;深入一点,但不要过度。
每句话单独看都合理,放在一起却没有告诉模型该怎么选。下一次遇到证据不完整的风险,它究竟应该继续推断,还是停下来避免误报?
这时继续改措辞,通常已经解决不了问题了。
每个补丁都修好了一个 Case
补规则为什么这么自然?因为它确实有效。
模型漏报,就加强覆盖;误报增多,就强调证据;分析太浅,就要求追踪调用链。每次修改都能在眼前的失败样例上看到改善。
问题出在修改的作用范围。我们想修的是一个 Case,改动的却是模型处理所有代码时的判断方式。
markdown
漏报一个空指针
↓
全局提高风险敏感度
↓
更多不确定问题被报告
↓
全局提高证据门槛
↓
需要深入推理的问题又被压掉
这些规则不是在增加彼此独立的能力,而是在反复移动同一条判断边界。覆盖率高一点,噪音往往也会增加;证据门槛抬高,误报少了,漏报可能回来。
更麻烦的是,我们通常只知道"这次改完看起来更好",不知道它在其他代码上破坏了什么。Prompt 已经改到第八版,团队仍然回答不了三个问题:哪条规则真的生效了,哪两条规则正在冲突,新版本总体上是否优于旧版本。
到了这里,Prompt 不只是长,而是失去了可维护的边界。
有些检查本来就不该让模型决定
先看一次实际 Review。输入是一段 TypeScript 变更,模型需要判断:
markdown
1. 代码能否通过类型检查
2. 新增接口是否缺少权限校验
3. useEffect 的依赖是否完整
4. 这个问题是否严重到需要阻塞合并
这四件事看起来都叫"检查代码",性质却不同。
代码能否通过类型检查,运行 tsc 就有确定结果。格式、lint、单测也一样。让模型仅凭代码猜测这些结果,既慢又不可靠;再在 Prompt 里强调"务必准确检查",也不会让猜测变成事实。
所以第一步不是写出更强的指令,而是把确定性检查拿出去:
代码变更
├─ 类型检查、lint、测试
└─ 模型分析语义风险
模型仍然负责权限遗漏、状态错乱、调用链副作用这类难以穷举的问题,但它拿到的不再只有 diff,还包括类型检查和测试的真实结果。
这项拆分没有让模型更聪明。它只是停止让模型承担程序已经能稳定完成的工作。换来的好处也很具体:类型错误出现在报告里时,我们知道它来自编译器,而不是模型"觉得可能有问题"。
规则写得再细,也补不出缺失的上下文
接着运行同一个 Review。模型看到某个接口没有显式鉴权,于是报出高危问题。开发者却说,这个仓库的鉴权统一在网关完成,业务代码里本来就不写。
这不是"不要过度推测"没有写好,而是模型缺了一条项目事实。
最直接的做法,是把所有语言规范、框架知识和仓库约定全部塞进 Prompt。小项目里可以这么做,规则一多,新的问题又出现了:React 规则会干扰 Go 服务,旧版约定会和当前架构冲突,大量无关内容还会挤占真正需要看的代码。
更合适的边界是:Prompt 说明 Review 要完成什么,系统根据本次变更选择相关资料。
支付目录变更 → 加载支付权限与幂等约定
React 文件变更 → 加载当前前端规范
数据库迁移 → 加载迁移与回滚要求
现在再遇到那个接口,模型能同时看到 diff 和"鉴权由网关统一完成"的仓库约定。它可以不报问题,也可以在证据冲突时明确指出依据来自哪里。
这里新增了一层检索和选择逻辑,代价是资料要维护版本、适用范围和来源。但它买来了一条重要边界:改项目知识时,不必重新改整套行为指令。
"要不要报"是系统策略,不是写作语气
模型已经有代码、有工具结果,也有项目约定,仍然会碰到证据不足但后果严重的问题。
例如,一个支付回调看起来可能重复入账,但只看当前 diff 无法确认上游是否已经去重。对普通页面,这类不确定判断也许只会制造噪音;对资金链路,哪怕证据不完整,也值得要求人工确认。
同一句"只报告有充分依据的问题"无法同时适用于这两个场景。真正变化的不是模型分析能力,而是团队愿意承担的风险。
因此,系统还要在模型之外保存策略:
普通目录:高置信问题进入 Review
核心链路:高影响、证据不完整的问题标记为待确认
明确违反硬规则:直接阻塞
模型负责提出候选问题和证据,系统根据目录、影响范围和仓库策略决定它以什么级别出现。这样调整风险偏好时,改的是可见的策略,不是把"更谨慎一点"塞回 Prompt。
没有固定样例,任何优化都只是手感
系统边界拆开后,还剩一个最容易被忽略的问题:怎么知道改动真的更好?
如果每次都拿刚刚失败的 Case 验证,新版本几乎一定会赢。它本来就是为这个 Case 改的。
需要保留一组固定样例,里面既有应该发现的问题,也有容易误报但实际安全的代码。每次修改 Prompt、知识或策略,都在同一组样例上比较:
该发现的问题发现了多少
安全代码被误报了多少
高风险问题是否给出了可核查证据
运行记录还要能回答:本次用了哪版 Prompt,加载了哪些项目规则,工具返回了什么,最终是哪条策略决定了严重级别。
这样,漏报上升时可以回到具体环节,而不是继续往 Prompt 末尾补一句话。评估和 Trace 不会自动修好系统,它们只是让团队第一次有证据判断"哪里变好了,哪里变坏了"。
Prompt 最后只留下它擅长的部分
走到这里,一次 Code Review 已经变成:
markdown
代码变更
↓
运行确定性检查,选择相关项目知识
↓
模型提出候选问题与证据
↓
系统按仓库策略分级
↓
用固定样例持续比较版本
Prompt 仍然重要。它负责告诉模型当前任务、可用证据、分析边界和输出结构。只是它不再同时充当编译器、知识库、风险制度和质量证明。
这套拆分也不是免费的。它增加了检查流程、知识维护、策略配置和评估数据。一次性脚本、低风险任务,或者最终始终由人逐条判断时,一份短 Prompt 可能已经够用。
当同一个 Agent 要反复运行,结果开始影响合并、发布或资金安全,团队又需要解释"为什么这次和上次不同",这些复杂度才值得买。它买来的不是零错误,而是错误出现后,知道该改 Prompt、补资料、调策略,还是修检查程序。
所以 Prompt 堆叠真正的尽头,不是再找到一句更精确的话。是承认那些彼此冲突的要求,本来就属于不同的系统职责。
本文首发于微信公众号「LZ AI Note」。