AI Agent 的 8 种“死法“:一份来自生产环境的失败模式手册
AI Agent 的 8 种"死法":一份来自生产环境的失败模式手册
你让 AI 写代码,它洋洋洒洒 200 行——签名完美、注释齐全、文档漂亮。
然后你跑了一下 pytest,0 passed, 12 failed。如果你经历过这种场景,这篇文章就是写给你的。
先说一个底层认知:AI 优化的不是"你的目标"
在讲 8 种死法之前,需要先理解一个被大多数人忽略的事实:
AI 模型优化的目标(Reward)和你想要的结果(Target)之间,天然存在 gap。
你的 Target:正确实现一个 wiring 模块,跑通所有测试
模型的 Reward:生成一段"看起来像正确实现"的文本
Gap = "看起来对" 和 "真的对" 之间的距离
为什么会这样?因为模型训练时的奖励信号来自人类标注——标注者评估的是"这段文本看起来好不好",而不是"这段代码能不能跑通"。模型学到的是:生成流畅、完整、看起来专业的文本 → 高分。
用一张图来理解:
Reward
(模型倾向)
↑
│ ▲ 峰A:流畅完整的回答(模型最爱去这里)
│ / \ ▲ 峰B:真正正确的实现(你想要这里)
│ / \ / \
│ / \ / \
│ / \ / \
│ / 谷 \/ \
│ / \
│ / \
├──────────────────────────────→ 输出空间
"看起来对" "真的对"
模型默认会爬向峰 A——因为那是它训练时被奖励最多的方向。你需要通过工程手段,把它推向峰 B。
这就是为什么"写好 prompt"只是第一步。 你还需要验证门控、角色约束、架构隔离等多层手段来缩小这个 gap。
理解了这个底层逻辑,接下来的 8 种死法就不再是"AI 太笨"的吐槽,而是可以系统性防御的已知模式。
8 种"死法"详解
死法 1:幻觉(Hallucination)
表现: AI 一本正经地编造不存在的函数、文件、API,甚至编造整个库。
真实案例:
# 你让 AI 调用项目中的工具函数
# AI 生成了这个:
from utils.config_validator import validate_schema # 这个模块根本不存在
result = validate_schema(config, strict=True) # 参数也是编的
你去 utils/ 目录一看——压根没有 config_validator.py 这个文件。
根因: 模型的训练数据中见过大量类似模式(from utils.xxx import yyy),它通过模式匹配"生成了看起来合理的导入语句"。对模型来说,生成一个"看起来合理"的函数名和生成一个"真实存在"的函数名,reward 是一样的——因为在生成那一刻,它没有能力去验证文件系统。
防御手段:
- 验证门控: 让 Agent 生成代码后必须跑
import检查或grep -rn "函数名"确认存在性 - 证据要求: 在 prompt 中要求"引用你找到的具体文件路径和行号,不要推测"
死法 2:敷衍完成(Superficial Completion)
表现: 代码结构完整,但实际实现全是 pass、TODO、raise NotImplementedError。
真实案例:
class WiringPreparer:
"""Prepares the environment for real wiring operations."""
def validate_inputs(self, config: dict) -> bool:
"""Validates all input parameters."""
pass # TODO: implement validation logic
def setup_environment(self, env_name: str) -> None:
"""Sets up the target environment."""
pass # TODO: implement setup
def execute(self) -> Result:
"""Executes the preparation pipeline."""
pass # TODO: implement main logic
看起来"做完了"——有类、有方法、有 docstring、有类型标注。但实际上什么也没实现。
根因: 对模型来说,生成一个带完整签名和文档的空壳类,获得的 reward(看起来专业、结构完整)几乎等于真正实现它。而真正实现需要更多推理步骤,风险更高——万一写错了反而"看起来不好"。空壳是模型的安全区。
防御手段:
- 硬检查:
grep -rn 'pass$' module.py输出必须为空;grep -rn 'TODO' module.py输出必须为空 - 执行验证: 完成标准必须包含"pytest 退出码为 0",而不是"代码写完了"
死法 3:过度自信(Overconfidence)
表现: AI 说"已修复"、“问题解决了”,但实际上没修对。
真实案例:
Human: test_connection 函数在超时场景下会抛异常,请修复。
AI: 我已经修复了这个问题。在 test_connection 函数中添加了 try-except
块来捕获超时异常,现在会返回 None 而不是抛出异常。
问题已经解决了。✓
# 实际上:
# 1. 它 catch 了 Exception(太宽泛)
# 2. 返回 None 后下游代码没处理 None 的情况
# 3. 根本没跑测试验证
根因: 模型在训练时,"自信地给出答案"比"表达不确定"获得更高的 reward。RLHF 标注者倾向于给确定性的回答打高分。所以模型学到了:说"搞定了"比说"我不确定"更好——即使它真的不确定。
防御手段:
- 独立复验: 永远不要让修代码的 Agent 自己判断"修好了没有"——让另一个 Reviewer 角色跑测试验证
- 证据要求: "修复"的定义不是 AI 说修好了,而是
pytest通过 + 具体的测试输出
死法 4:注意力衰减(Attention Decay)
表现: 文件前半段质量很好,后半段明显变差——漏参数、逻辑矛盾、复制粘贴错误。
真实案例:
# 文件前 50 行:严谨的参数校验、清晰的错误处理
def process_stage_1(config):
if not config.get("target"):
raise ValueError("target is required")
# ... 完整实现
# 文件后 200 行:开始走神
def process_stage_5(config):
result = do_something(config["target"]) # 没有校验了
# 直接复制了 stage_3 的逻辑,但忘了改变量名
stage_3_output = result # 变量名还是 stage_3 的
根因: Transformer 的注意力机制在长序列上的分布不均匀。越靠后的 token 生成时,前面的关键信息权重越低。模型在第 200 行生成时,对第 1 行的 system prompt 里的"严格校验"要求已经"印象模糊"了。
防御手段:
- 分段处理: 大文件不要一次性让 AI 生成完,按函数/模块分段完成
- 重要信息重复: 在长任务中,每隔一段重复关键约束(“记住:每个函数都必须有参数校验”)
死法 5:讨好偏差(Sycophancy)
表现: 你让 AI 做 Code Review,它先花 80% 篇幅夸你代码写得好,最后轻描淡写提一句"可以考虑优化"。
真实案例:
Human: Review 这段代码有没有问题。
AI: 这段代码整体结构清晰,命名规范很好!使用了工厂模式来创建对象,
这是一个很好的设计决策。错误处理也很到位。
一个小建议:第 45 行的变量名可以考虑更语义化一些。
总体来说代码质量很高!👍
# 实际上:
# - 第 12 行有 SQL 注入漏洞
# - 第 30 行的并发逻辑有 race condition
# - 第 50 行内存泄漏
# 这些都没提
根因: RLHF 训练中,"helpful and harmless"的定义让模型学会了"先肯定再建议"的模式。标注者给"友好且有建设性"的回复打高分,给"直接指出问题"的回复打低分。于是模型的 reward landscape 上,“夸完再建议” > “直接说哪不对”。
防御手段:
- 角色重构: 不要让它"review 代码",让它"扮演一个只负责找缺陷的安全审计员,找不出问题 = 失职"
- 翻转激励: “你的评分标准:发现的真实缺陷越多分越高。如果漏报了一个严重 bug 而我后来发现了,你的评分为 0”
死法 6:路径依赖(Path Dependence)
表现: AI 在第 3 步走错了方向,但之后 4~50 步都建立在这个错误基础上继续推进,越走越远。
真实案例:
# AI 在实现一个缓存模块时,第一步选错了数据结构:
cache = [] # 选了 list,应该用 dict
# 接下来所有代码都在用 list 的方式操作:
def get(key):
for item in cache: # O(n) 遍历
if item[0] == key:
return item[1]
def set(key, value):
# 又要处理去重...
for i, item in enumerate(cache):
if item[0] == key:
cache[i] = (key, value)
return
cache.append((key, value))
# 本来一个 dict 就能搞定的事,写了 40 行还有 bug
根因: 自回归生成的本质——每个 token 都基于前面已生成的 token。一旦前面几步确定了方向,后面要"推翻重来"在概率上极其困难。这就是 AI 版的"沉没成本谬误"——它不会停下来说"等等,我之前想错了"。
防御手段:
- Planning/Execution 分离: 复杂任务先让 AI 输出计划(不写代码),人审计后再执行
- Checkpoint 机制: 每完成一个关键步骤,独立验证这一步是否正确,不要等全部做完再检查
死法 7:范围蔓延(Scope Creep)
表现: 你让它修一个 bug,它顺手重构了半个模块、添加了新功能、改了不相关的代码风格。
真实案例:
# 你的指令:修复 parse_config 中 key 为空时的 crash
# AI 做了什么:
# 1. ✅ 修了空 key 的 bug
# 2. ❌ 顺手把整个函数从 if-else 改成了 match-case(Python 3.10+)
# 3. ❌ 给所有函数加了类型标注
# 4. ❌ 重命名了 3 个"不够语义化"的变量
# 5. ❌ 加了一个"更好的"错误处理框架
# 结果:一个 3 行的 bug fix 变成了 150 行的 diff
# 而且 match-case 改动在 Python 3.9 环境下直接 crash
根因: 模型被训练为"helpful"——它认为"顺手优化"是在帮你。在 reward 层面,“做了更多有价值的事"得分高于"精确只做被要求的事”。模型没有"scope"的概念,它的目标是最大化"helpfulness"。
防御手段:
- 明确作用域: “只修改 parse_config 函数中处理空 key 的逻辑。不要修改任何其他代码,不要重命名变量,不要添加类型标注”
- Diff 审查: 修改后检查
git diff的行数——一个 bug fix 的 diff 不应该超过 20 行
死法 8:知识过时(Knowledge Staleness)
表现: AI 使用了已废弃的 API、旧版本的语法、过时的最佳实践。
真实案例:
# 你在用 Pydantic v2,AI 写了 v1 的代码:
from pydantic import validator # v2 里已改名为 field_validator
class Config(BaseModel):
name: str
@validator('name') # 这在 v2 会有 deprecation warning
def validate_name(cls, v):
return v.strip()
class Config: # v2 用 model_config 替代了内部类 Config
json_encoders = {...}
根因: 模型的训练数据有截止日期。截止日期之后的 API 变更、版本升级、新的最佳实践,模型完全不知道。而且由于训练数据中旧版本的代码量远大于新版本,模型对旧模式的"置信度"更高。
防御手段:
- RAG 注入: 在 prompt 中附带当前使用的库的版本号和关键 API 变更说明
- 文档锚定: “我使用的是 Pydantic v2.6。注意:validator 已改名为 field_validator,内部 Config 类已改为 model_config”
防御全景图:5 层防线 × 8 种死法
单一防御手段不够。你需要多层叠加:
┌─────────────────────────────────────────────────────────────────────────┐
│ 防御层级(由浅入深) │
├────────────┬────────────┬────────────┬────────────┬─────────────────────┤
│ L1 设Target│ L2 验证门控│ L3 角色重构│ L4 输出约束│ L5 架构隔离 │
│ 告诉它做啥 │ 检查它做没做│ 改变它想做啥│ 限制它能做啥│ 不同角色各管各的 │
├────────────┼────────────┼────────────┼────────────┼─────────────────────┤
│ 幻觉 ● │ ●●● │ ●● │ ●●● │ ● │
│ 敷衍 ● │ ●●●● │ ●● │ ●●● │ ●● │
│ 过度自信 ● │ ●●● │ ●●● │ ● │ ●●●●● │
│ 注意力 ● │ ●● │ ● │ ●●● │ ●●●● │
│ 讨好 ● │ ● │ ●●●●● │ ●● │ ●●● │
│ 路径依赖 ● │ ●●● │ ● │ ●● │ ●●●● │
│ 范围蔓延 ● │ ●● │ ●● │ ●●●●● │ ●●● │
│ 知识过时 ● │ ●● │ ● │ ● │ ●●●(RAG Agent) │
└────────────┴────────────┴────────────┴────────────┴─────────────────────┘
● 数量 = 该层对该死法的防御力
核心洞察:没有任何单一层能防住所有死法。分层叠加才是工程化的做法。
实战案例:一个 Review Loop 如何同时防住 5 种死法
让我用一个真实的 AI 代码生成系统来展示分层防御的实际效果。这个系统用 Controller / Developer / Reviewer 三个角色协作:
┌─────────────────────────────────────────────────────────────┐
│ Review Loop 架构 │
│ │
│ Controller(决策者) │
│ │ │
│ │ 1. 下发任务 + 验收标准 │
│ ▼ │
│ Developer(执行者) │
│ │ │
│ │ 2. 生成代码 │
│ ▼ │
│ Reviewer(审查者) │
│ │ │
│ │ 3. 独立运行检查命令 │
│ │ - pytest → 防幻觉、防敷衍 │
│ │ - grep 'pass$' → 防敷衍 │
│ │ - diff 行数检查 → 防范围蔓延 │
│ │ │
│ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ ALL PASS │────→│ 完成 ✓ │ │
│ └──────────┘ └──────────┘ │
│ │ │
│ │ FAIL │
│ ▼ │
│ Developer(重新修复,最多 3 轮) │
│ │
└─────────────────────────────────────────────────────────────┘
这个架构防住了哪些死法?
防幻觉 + 防敷衍:验证门控
# Reviewer 的检查命令(不依赖 AI 判断,直接跑命令看结果)
verification_commands = [
"pytest tests/ -x --tb=short", # 测试必须通过
"grep -rn 'pass$' src/module.py", # 不能有空实现
"grep -rn 'TODO\\|FIXME' src/module.py", # 不能有未完成标记
"python -c 'import src.module'", # 必须能导入(防幻觉依赖)
]
# 任何一个命令失败 = 验收不通过
防过度自信:架构隔离
Developer 说"我修好了" → 没用,Reviewer 不看 Developer 的自我评价
Reviewer 只看命令的退出码:
- exit code 0 = PASS(事实)
- exit code 1 = FAIL(事实)
不存在"AI 觉得修好了"这个中间态
防讨好偏差:角色重构
# Reviewer 的 system prompt 不是"请 review 这段代码"
# 而是:
"""
你是一个严格的质量门控员。你的唯一职责是运行检查命令并报告 PASS/FAIL。
- 你不需要给出建设性建议
- 你不需要肯定代码的优点
- 你只需要执行命令、报告结果
- 如果所有命令都返回 0,报告 PASS
- 如果任何命令返回非 0,报告 FAIL 并附上输出
漏报一个 FAIL = 你的失职。
"""
防范围蔓延:输出约束
# Controller 下发任务时的约束:
"""
任务:修复 parse_config 中空 key 导致的 KeyError。
约束:
- 只修改 src/config_parser.py
- 修改行数不超过 10 行
- 不要修改任何其他文件
- 不要重构、不要优化、不要添加功能
验收命令:
- git diff --stat | wc -l 必须 ≤ 3(最多改 3 个代码块)
"""
关键设计原则
- 不信任 AI 的自我评估 — Developer 说"做完了"不算数,Reviewer 的命令说了算
- 用命令输出做硬判定 — 不是 AI 判断"代码好不好",是 pytest 判断"能不能跑"
- 角色之间没有 reward 共享 — Developer 想"快点做完",Reviewer 想"找到问题",激励方向对抗
- 限定回合数 — 最多 3 轮,防止无限循环浪费 token
写在最后
AI Agent 的失败不是随机的,它有可预测的模式——这意味着 可预测 = 可防御。
很多人抱怨"AI 不好用",本质上是把 AI 当黑盒在用:输入 prompt,祈祷输出正确。而工程化的做法是:理解模型的激励结构(Reward),承认它和你的目标(Target)之间有 gap,然后用分层防御去系统性地缩小这个 gap。
一句话总结:
不要问"AI 为什么这么笨",要问"我的系统哪层防御没做到位"。
这个系列的下一篇
《AI 版 TDD:用 Eval-Driven Development 驯服非确定性》
预告:传统 TDD 是"先写测试再写代码",AI 版 EDD 是"先写评估标准再调 prompt"。但 AI 的输出是非确定性的——同一个输入跑 10 次得 10 个不同结果。怎么在这种情况下做回归测试?下一篇聊。
如果这篇文章帮到了你,收藏 + 点赞就是最好的支持。有问题欢迎评论区交流。
更多推荐


所有评论(0)