Codex 写出的代码能跑却算错钱:我用 3 个测试拆穿一次 AI 编程幻觉
我让 Codex 写了一个折扣函数。代码有类型标注、有异常处理,命名也很专业,运行时完全不报错。
但输入原价 100、折扣率 0.20,它返回了 20。业务要的是折后价 80,AI 算出的却是优惠金额。
这次问题让我意识到:AI 编程最危险的不是语法错误,而是代码看起来非常正确,业务含义却错了。下面用这个真实的小案例,完整走一遍业务契约、差异审查、3 个边界测试和人工批准,看看怎样让 AI 的错误过不了交付门。

来源与证据
- 知识依据:RuyiBookCourse 中《Codex 智能体工程》"AI 代码审查与责任边界"和"用 Codex 建立 Python 交付闭环",用于确定审查、测试和人工责任边界。
- 实践依据:本文的折扣函数与边界测试已在本地执行,
pytest结果为3 passed,不是仅凭代码外观推断正确性。 - 官方资料:OpenAI Codex 开发者文档。
什么才算 AI 编程幻觉
在编码场景里,幻觉不只是假造不存在的 API。以下情况都值得警惕:
- 编造仓库中并不存在的函数、配置或文件路径;
- 误解金额、时间、权限等业务语义;
- 为了让测试变绿而删除关键断言;
- 修改了需求之外的模块;
- 给出"已验证"结论,却没有运行对应命令;
- 忽略版本差异,套用过期参数;
- 把静态检查通过等同于业务正确。
这些问题有一个共同点:输出形式像答案,但证据不足。
先写业务契约,再让 AI 写代码
假设需求是"计算折扣后的价格",至少要先确定:
price不能为负数;discount_rate使用0到1的比例;- 20% 折扣表示支付原价的 80%;
- 金额统一保留两位小数;
- 非法输入必须明确失败。
如果只说"写一个折扣函数",AI 可能把 discount_rate=20 理解成 20%,也可能把折扣金额直接累加到总价。需求没有表达清楚时,错误不全是模型问题,还是任务契约把关键判断权让了出去。
一个看起来合理的错误实现
python
from decimal import Decimal
def final_price(price: Decimal, discount_rate: Decimal) -> Decimal:
return price * discount_rate
函数能运行,类型也没有报错。但输入 100 和 0.20 时得到 20,这到底是折扣金额,还是最终应付金额?如果业务要的是最终价格,正确结果应该是 80。
这种错误靠语法检查发现不了,因为代码在语法层面完全成立。
修复后的实现
python
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"))
这里没有追求复杂,而是把业务边界直接写进代码:输入范围明确,公式明确,金额精度明确。
测试不能只覆盖快乐路径
第一条测试验证核心业务含义:
python
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")
)
再补充非法边界:
python
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)
本地真实运行:
text
$ python -m pytest -q test_discount.py
... [100%]
3 passed
三条测试并不多,却分别保护了核心结果与上下边界。测试的价值不在数量,而在是否对应真实风险。

测试通过仍然不代表可以交付
AI 可能通过改变测试来"解决"失败。例如把期望值从 80.00 改为 20.00,流水线会变绿,但业务错误被合法化了。
因此,测试结果必须和代码差异一起审查:
bash
git diff -- discount.py test_discount.py
python -m pytest -q test_discount.py
审查时至少回答:
- 代码变化是否只在授权范围内?
- 测试断言是否仍然表达原始业务要求?
- 是否新增了没有解释的依赖或网络访问?
- 是否处理了边界值和异常路径?
- 命令输出能否证明文章声称的结果?
用四层测试覆盖不同风险
真实项目可以把验证分成四层:
- 单元测试:验证纯业务规则和边界;
- 契约测试:验证模块、API 或制品字段的兼容性;
- 集成测试:验证数据库、文件系统和外部适配器;
- 端到端测试:验证少量关键业务闭环。
不要为了显得"工程化"而把所有情况都塞进端到端测试。测试越接近底层业务规则,通常越快、越稳定,也越容易定位问题。端到端测试只保留最关键的成功与失败路径。

建立可审计的验证记录
让 Codex 完成任务时,不要只要一句"已修复"。要求它保留:
- 修改了哪些文件;
- 差异为什么符合需求;
- 新增或更新了哪些测试;
- 实际运行的命令和退出码;
- 关键输出;
- 尚存风险;
- 哪些判断需要人工批准。
一个可用的完成报告可以是:
text
修改:discount.py、test_discount.py
验证:python -m pytest -q test_discount.py
结果:3 passed,退出码 0
未验证:未连接真实订单数据库
人工检查:确认 discount_rate 的业务定义为比例
这比"测试都通过了"多不了几行,却能让下一位审查者知道证据覆盖到哪里、没有覆盖到哪里。
权限边界也是幻觉控制的一部分
如果 AI 可以任意修改文件、访问网络、读取凭据并直接发布,那么一次错误判断的影响会被放大。
更稳妥的做法是按任务开放最小权限:
- 代码审查默认只读;
- 本地开发只写当前工作区;
- 网络访问按域名或任务开放;
- 凭据不进入提示词、日志和测试夹具;
- 发布、付款、删除数据等高影响动作保留人工批准;
- 测试失败时停止,而不是自动放宽边界。
权限不是用来阻止 AI 工作,而是把错误限制在可恢复范围内。
三类常见误区
误区一:让同一个 AI 生成、审查并批准
生成者天然容易沿用自己的假设。至少要把"生成"和"最终裁决"分开,关键变更由人确认。
误区二:没有发现就等于没有问题
一次 AI 审查没有输出发现,只能说明它在当前上下文中没有识别到问题,不能证明系统安全或业务完全正确。
误区三:用 Linter 重复替代业务审查
格式、未使用变量和简单类型问题交给确定性工具;AI 审查应关注跨文件语义、错误路径、权限和需求偏差。
可以直接复用的交付门
每次 AI 编程完成后,按顺序检查:
- 固定需求和不可改变的边界;
- 查看最终差异,而不是只看 AI 摘要;
- 运行聚焦测试;
- 运行静态检查和必要的完整测试;
- 检查日志中是否包含秘密;
- 记录未验证项;
- 由责任人决定是否发布。
其中任何一步缺少证据,就应该把状态写成"未验证"或"需要确认",而不是"完成"。
总结
Codex 生成代码不是交付终点,而是验证流程的输入。识别 AI 编程幻觉的核心,不是猜模型什么时候会错,而是用业务契约、差异审查、分层测试、最小权限和人工批准建立证据链。只要每个结论都能追溯到代码、命令输出和责任人,即使模型偶尔犯错,错误也很难悄悄穿过发布门。
参考资料:
- RuyiBookCourse《AI 代码审查与责任边界》"修复与验证"
- RuyiBookCourse《用 Codex 建立 Python 交付闭环》"测试金字塔要对应风险层级"
- OpenAI Codex 文档:developers.openai.com/codex/