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)

表现: 代码结构完整,但实际实现全是 passTODOraise 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 个代码块)
"""

关键设计原则

  1. 不信任 AI 的自我评估 — Developer 说"做完了"不算数,Reviewer 的命令说了算
  2. 用命令输出做硬判定 — 不是 AI 判断"代码好不好",是 pytest 判断"能不能跑"
  3. 角色之间没有 reward 共享 — Developer 想"快点做完",Reviewer 想"找到问题",激励方向对抗
  4. 限定回合数 — 最多 3 轮,防止无限循环浪费 token

写在最后

AI Agent 的失败不是随机的,它有可预测的模式——这意味着 可预测 = 可防御

很多人抱怨"AI 不好用",本质上是把 AI 当黑盒在用:输入 prompt,祈祷输出正确。而工程化的做法是:理解模型的激励结构(Reward),承认它和你的目标(Target)之间有 gap,然后用分层防御去系统性地缩小这个 gap。

一句话总结:

不要问"AI 为什么这么笨",要问"我的系统哪层防御没做到位"。


这个系列的下一篇

《AI 版 TDD:用 Eval-Driven Development 驯服非确定性》

预告:传统 TDD 是"先写测试再写代码",AI 版 EDD 是"先写评估标准再调 prompt"。但 AI 的输出是非确定性的——同一个输入跑 10 次得 10 个不同结果。怎么在这种情况下做回归测试?下一篇聊。


如果这篇文章帮到了你,收藏 + 点赞就是最好的支持。有问题欢迎评论区交流。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐