刚开工的时候,AI 表现得像个完美同事。
你定的规矩,它条条照办:Python 3.11、RESTful 命名、统一响应体、错误码规范——你说一遍,它第一版代码就全对上了。
你心里暗喜:这磨合得可以啊。
然后聊到第 6 轮、第 8 轮、第 10 轮——
它开始一点一点"失忆",而且失得很隐蔽。
不是突然翻脸,是温水煮青蛙式的漂移:
except ValueError, e:——Python 2 的语法,3.11 直接跑不起来return data,你前面定的统一响应体 {code, message, data} 不见了最要命的是:你不盯,根本发现不了。 每一段代码单看都能跑,但拼在一起,整个项目的风格是裂的。
一个 20 个接口的中等项目,后端用 Python。
对话第一轮,我把技术约束交代得明明白白:
- Python 3.11,禁止使用任何 Python 2 语法
- RESTful 风格:资源名用名词复数,操作靠 HTTP 方法区分
- 所有接口统一返回
{code: 0, message: "success", data: {...}}- 异常统一在中间件层捕获,业务代码里不允许 try-except 吞错误
- token 放 localStorage,前端从 Authorization 头带上来
前 4 轮,舒服得不像话。接口风格整齐划一,响应体一个模子刻出来的。
第 6 轮,我让它补一个数据导出接口。代码生成得很快,我扫一眼逻辑没问题,让它继续。
直到跑 CI 的时候挂了——语法错误。
点进去一看:
except ValueError, e:
logger.error("parse failed: %s" % e)
except ValueError, e 是 Python 2.6 之前的写法,3.x 里早就改成 except ValueError as e 了。%s 那种老式格式化也一起回来了。
我当时的第一反应是:"我第一轮不是说了 Python 3.11 吗?"
这还没完。我往后翻它第 8、9 轮写的代码,又发现:
return result_list,没有响应体包装——前端那边已经按统一格式写好了解析逻辑,对接直接崩/createOrder,而前面 17 个接口全是 POST /orders 这种风格try: ... except: pass——把异常全吞了,正好违反第 4 条第 1 轮定的 5 条规矩,到第 10 轮,它违反了 4 条。
而且它态度特别好,每次指出来都秒回"抱歉,我疏忽了,马上修正",改得也很快。
但问题是:我不指出来呢?
1. 上下文窗口不是"记住",是"暂时看得到"
大模型每轮生成时,能参考的是当前上下文窗口里的内容。对话一长,最早的那些消息——包括你第一轮定的规矩——在窗口里的位置越来越靠前,模型对它们的注意力会明显衰减。
这有个专门的说法,叫"lost in the middle":模型对开头和结尾的内容最敏感,中间的最容易被忽略。 你的技术约束在开头,最新写的代码在结尾——谁权重高一目了然。
2. 它自己刚写的代码,会变成"新事实"
第 8 轮它写了一个裸返回的接口,这段代码进了上下文。到第 10 轮,模型看到的"最近的事实"是:这个项目的接口好像直接 return 数据就行。
一次漂移会污染后面所有轮次。 它不是故意违反你,是它自己的偏离输出,把自己给带跑了。
3. AI 不会"回头翻文档"
人类同事做着做着会去翻一下项目规范,确认自己没写偏。AI 不会——它没有"等一下,我回去核对一下需求"这个动作。
它每轮都在"基于最近的上下文接着往下写",至于早期约束,它默认自己"应该还记得"。
但"记得"这件事,在长对话里根本不存在。
项目根目录维护一个 SPEC.md(或 AGENTS.md),把铁律写死:
# 项目技术约束
- Python 3.11,禁止Python 2语法(except X, e / print语句 / %格式化)
- 接口RESTful:资源用名词复数,POST /orders 而非 /createOrder
- 统一响应体:{code, message, data},禁止裸返回
- 禁止 try-except-pass,异常在中间件统一处理
- token存localStorage,走Authorization头
每个新对话开头第一句话:"先读 SPEC.md,本项目所有代码必须严格遵守其中约束。"
聊天记录会被挤出窗口,文件不会——只要它每轮都读,约束就永远在"最近"的位置。
20 个接口不要在一个对话里从头写到尾。按模块拆成多个对话:用户模块一个、订单模块一个、支付模块一个。
每个新对话:
对话越短,早期约束离"现在"越近,漂移概率越低。
定期让它自己复盘,别等它写错了再抓:
"继续之前,先列出本项目你必须遵守的技术约束,逐条说明你最近 3 个接口是怎么遵守的。"
它复述约束这一步,等于把开头的规矩重新拉回到注意力焦点上。它要是哪条说不上来,说明那条已经开始漂了——立刻补,不要等到代码里出现。
发现它写了 Python 2 语法的当下就指出来,不要想着"先让它把这个接口写完再说"。
漂移的代码多停留一轮,就多污染一轮上下文。 攒到第 10 轮再统一清算,你面对的是一个已经被带偏了 4 轮的模型,纠正成本完全不同。
这条是测试老兵的执念:凡是能让工具检查的,就不要相信"它记住了"。
requires-python = ">=3.11",CI 跑 ruff/mypy——except ValueError, e 这种代码直接过不了 lint最好的约束不是"请你记住",是"你不这么写,代码就跑不起来"。
AI 的上下文不是硬盘,是金鱼的记忆——对话越长,你第一轮说的话死得越早。别指望它"记住",要让它每一轮都"重新看到":约束进文件、长聊拆短聊、漂移靠工具抓。