Codex 写出的代码能跑却算错钱:我用 3 个测试拆穿一次 AI 编程幻觉

0 阅读8分钟

Codex 写出的代码能跑却算错钱:我用 3 个测试拆穿一次 AI 编程幻觉

我让 Codex 写了一个折扣函数。代码有类型标注、有异常处理,命名也很专业,运行时完全不报错。

但输入原价 100、折扣率 0.20,它返回了 20。业务要的是折后价 80,AI 算出的却是优惠金额。

这次问题让我意识到:AI 编程最危险的不是语法错误,而是代码看起来非常正确,业务含义却错了。下面用这个真实的小案例,完整走一遍业务契约、差异审查、3 个边界测试和人工批准,看看怎样让 AI 的错误过不了交付门。

识别 AI 编程幻觉

来源与证据

  • 知识依据:RuyiBookCourse 中《Codex 智能体工程》“AI 代码审查与责任边界”和“用 Codex 建立 Python 交付闭环”,用于确定审查、测试和人工责任边界。
  • 实践依据:本文的折扣函数与边界测试已在本地执行,pytest 结果为 3 passed,不是仅凭代码外观推断正确性。
  • 官方资料:OpenAI Codex 开发者文档

什么才算 AI 编程幻觉

在编码场景里,幻觉不只是假造不存在的 API。以下情况都值得警惕:

  • 编造仓库中并不存在的函数、配置或文件路径;
  • 误解金额、时间、权限等业务语义;
  • 为了让测试变绿而删除关键断言;
  • 修改了需求之外的模块;
  • 给出“已验证”结论,却没有运行对应命令;
  • 忽略版本差异,套用过期参数;
  • 把静态检查通过等同于业务正确。

这些问题有一个共同点:输出形式像答案,但证据不足。

先写业务契约,再让 AI 写代码

假设需求是“计算折扣后的价格”,至少要先确定:

  • price 不能为负数;
  • discount_rate 使用 01 的比例;
  • 20% 折扣表示支付原价的 80%;
  • 金额统一保留两位小数;
  • 非法输入必须明确失败。

如果只说“写一个折扣函数”,AI 可能把 discount_rate=20 理解成 20%,也可能把折扣金额直接累加到总价。需求没有表达清楚时,错误不全是模型问题,还是任务契约把关键判断权让了出去。

一个看起来合理的错误实现

from decimal import Decimal


def final_price(price: Decimal, discount_rate: Decimal) -> Decimal:
    return price * discount_rate

函数能运行,类型也没有报错。但输入 1000.20 时得到 20,这到底是折扣金额,还是最终应付金额?如果业务要的是最终价格,正确结果应该是 80

这种错误靠语法检查发现不了,因为代码在语法层面完全成立。

修复后的实现

from decimal import Decimal


def final_price(price: Decimal, discount_rate: Decimal) -> Decimal:
    if price < 0:
        raise ValueError("price must be non-negative")
    if not Decimal("0") <= discount_rate <= Decimal("1"):
        raise ValueError("discount_rate must be between 0 and 1")

    return (
        price * (Decimal("1") - discount_rate)
    ).quantize(Decimal("0.01"))

这里没有追求复杂,而是把业务边界直接写进代码:输入范围明确,公式明确,金额精度明确。

测试不能只覆盖快乐路径

第一条测试验证核心业务含义:

from decimal import Decimal

from discount import final_price


def test_twenty_percent_discount() -> None:
    assert (
        final_price(Decimal("100"), Decimal("0.20"))
        == Decimal("80.00")
    )

再补充非法边界:

import pytest


@pytest.mark.parametrize(
    "rate",
    [Decimal("-0.01"), Decimal("1.01")],
)
def test_rejects_invalid_discount_rate(rate: Decimal) -> None:
    with pytest.raises(ValueError):
        final_price(Decimal("100"), rate)

本地真实运行:

$ python -m pytest -q test_discount.py
...                                                                      [100%]
3 passed

三条测试并不多,却分别保护了核心结果与上下边界。测试的价值不在数量,而在是否对应真实风险。

AI 编程幻觉验证闭环

测试通过仍然不代表可以交付

AI 可能通过改变测试来“解决”失败。例如把期望值从 80.00 改为 20.00,流水线会变绿,但业务错误被合法化了。

因此,测试结果必须和代码差异一起审查:

git diff -- discount.py test_discount.py
python -m pytest -q test_discount.py

审查时至少回答:

  1. 代码变化是否只在授权范围内?
  2. 测试断言是否仍然表达原始业务要求?
  3. 是否新增了没有解释的依赖或网络访问?
  4. 是否处理了边界值和异常路径?
  5. 命令输出能否证明文章声称的结果?

用四层测试覆盖不同风险

真实项目可以把验证分成四层:

  • 单元测试:验证纯业务规则和边界;
  • 契约测试:验证模块、API 或制品字段的兼容性;
  • 集成测试:验证数据库、文件系统和外部适配器;
  • 端到端测试:验证少量关键业务闭环。

不要为了显得“工程化”而把所有情况都塞进端到端测试。测试越接近底层业务规则,通常越快、越稳定,也越容易定位问题。端到端测试只保留最关键的成功与失败路径。

AI 代码交付的分层测试

建立可审计的验证记录

让 Codex 完成任务时,不要只要一句“已修复”。要求它保留:

  • 修改了哪些文件;
  • 差异为什么符合需求;
  • 新增或更新了哪些测试;
  • 实际运行的命令和退出码;
  • 关键输出;
  • 尚存风险;
  • 哪些判断需要人工批准。

一个可用的完成报告可以是:

修改:discount.py、test_discount.py
验证:python -m pytest -q test_discount.py
结果:3 passed,退出码 0
未验证:未连接真实订单数据库
人工检查:确认 discount_rate 的业务定义为比例

这比“测试都通过了”多不了几行,却能让下一位审查者知道证据覆盖到哪里、没有覆盖到哪里。

权限边界也是幻觉控制的一部分

如果 AI 可以任意修改文件、访问网络、读取凭据并直接发布,那么一次错误判断的影响会被放大。

更稳妥的做法是按任务开放最小权限:

  • 代码审查默认只读;
  • 本地开发只写当前工作区;
  • 网络访问按域名或任务开放;
  • 凭据不进入提示词、日志和测试夹具;
  • 发布、付款、删除数据等高影响动作保留人工批准;
  • 测试失败时停止,而不是自动放宽边界。

权限不是用来阻止 AI 工作,而是把错误限制在可恢复范围内。

三类常见误区

误区一:让同一个 AI 生成、审查并批准

生成者天然容易沿用自己的假设。至少要把“生成”和“最终裁决”分开,关键变更由人确认。

误区二:没有发现就等于没有问题

一次 AI 审查没有输出发现,只能说明它在当前上下文中没有识别到问题,不能证明系统安全或业务完全正确。

误区三:用 Linter 重复替代业务审查

格式、未使用变量和简单类型问题交给确定性工具;AI 审查应关注跨文件语义、错误路径、权限和需求偏差。

可以直接复用的交付门

每次 AI 编程完成后,按顺序检查:

  1. 固定需求和不可改变的边界;
  2. 查看最终差异,而不是只看 AI 摘要;
  3. 运行聚焦测试;
  4. 运行静态检查和必要的完整测试;
  5. 检查日志中是否包含秘密;
  6. 记录未验证项;
  7. 由责任人决定是否发布。

其中任何一步缺少证据,就应该把状态写成“未验证”或“需要确认”,而不是“完成”。

总结

Codex 生成代码不是交付终点,而是验证流程的输入。识别 AI 编程幻觉的核心,不是猜模型什么时候会错,而是用业务契约、差异审查、分层测试、最小权限和人工批准建立证据链。只要每个结论都能追溯到代码、命令输出和责任人,即使模型偶尔犯错,错误也很难悄悄穿过发布门。

参考资料:

  • RuyiBookCourse《AI 代码审查与责任边界》“修复与验证”
  • RuyiBookCourse《用 Codex 建立 Python 交付闭环》“测试金字塔要对应风险层级”
  • OpenAI Codex 文档:developers.openai.com/codex/