你让 Cursor 或 Claude Code 接入一个库,生成的代码看上去合情合理,甚至能通过 lint,然后运行时直接炸了:TypeError: client.queryRecords is not a function,或 TypeScript 报 Argument of type 'string' is not assignable to parameter of type 'QueryOptions'。这个方法压根不存在;选项名拼错;参数顺序颠倒。AI 把三个相似库的 API 揉在一起,凭空造出了第四个看起来”应该有”的库。这并不是模型能力的问题,而是上下文锚定缺失——一种几乎所有 AI 编码场景都会遇到的失败模式。
常见原因
按真实编码会话中的频次排序。
1. 库版本与训练数据漂移
AI 学的是 v2 API,你装的是 v4。方法被改名,选项被搬进 config 对象,回调改成了 Promise。AI 输出的签名两年前没错,现在彻底过时。
如何识别:在 package.json 里看版本号,然后翻库的 CHANGELOG 或 releases 页,对照训练截止时间与你版本之间的 breaking change。
2. 跨相似库的方法名串味
fetch、axios、got、undici、ky 都做 HTTP,AI 把它们的 API 混在一起。于是出现 axios.fetch(url, { json: true })——看着像那么回事,其实纯属胡编。
如何识别:那个方法名在你导入的库的官方文档里完全搜不到,但在它竞品的文档里能搜到。
3. 选项 key 幻觉
方法和参数都对,但选项 key 写成了 timeoutMs,而库实际接受的是 timeout。TypeScript 能拦下一部分,弱类型 JS 则默默吞掉,选项被忽略,默认值继续生效。
如何识别:运行时 Object.keys(options) 显示你的 key 确实传进去了,但实际行为表明默认值并未被覆盖。
4. 返回类型臆造
const { data, error } = await client.query(...)——但库实际返回的是 Promise<QueryResult>,字段叫 rows 而不是 data,也根本没有 error 字段(错误是 throw 出来的)。AI 套用了 Supabase 风格的返回形状。
如何识别:console.log(JSON.stringify(result)) 打印出来的 key 跟你解构的完全对不上。
5. 参数顺序颠倒
fs.writeFile(content, path) 写成了 fs.writeFile(path, content) 的反面。或 localStorage.setItem(value, key)。AI 记住了函数名,却没记清参数顺序。
如何识别:文件被创建出来,文件名是一段乱码的内容字符串;或 storage 里 key 和 value 完全互换。
6. 自作主张的”便利方法”
array.removeAt(index)、string.contains(sub)、date.addDays(7)——这些方法在其他语言里确实存在,但 JS 标准库里没有。AI 替你”善意地”补全了一个不存在的 API。
如何识别:在 MDN 或官方文档里搜该方法名,零结果。
开始之前
- 锁死实际版本:
npm ls <package>或cat package.json | grep <package>。 - 打开对应版本的官方文档,不要点 “latest”。
- TypeScript 项目务必开启
strict: true,让类型检查器提前拦截签名不匹配。 - 在修改前先把 AI 的原始建议保存下来——后续做 prompt 修补时有用。
需要收集的信息
- AI 写出的那行方法调用,原样复制。
- 已安装包的版本(
package.json与node_modules/<pkg>/package.json都看一下)。 - 错误信息或运行时的错误输出。
- 库实际的签名:
cat node_modules/<pkg>/dist/index.d.ts | grep -A 3 <method>。 - AI 是否引用了来源。如果它引用的是某篇博客,那篇博客往往就是幻觉形状的源头。
分步修复
按从最便宜的检查到长期防御排序。
第 1 步:先确认这个方法到底存不存在
grep -r "methodName" node_modules/<package>/dist/
或直接在 Node REPL 里看:
node -e "console.log(Object.keys(require('<package>')))"
如果包的真实 exports 里根本没有这个方法,那就是彻头彻尾的幻觉——再怎么调参数都救不回来,得换方法或换思路。
第 2 步:从类型定义里读出真实签名
对于带类型的库:
cat node_modules/<package>/dist/index.d.ts | grep -B 1 -A 10 "<method>"
这就是 ground truth。和 AI 写的对比,diff 就是 bug。
第 3 步:把真实签名喂回 AI
重新提示时把实际的 .d.ts 片段粘进去:
真实签名是:
query(sql: string, params?: unknown[]): Promise<{ rows: Row[]; rowCount: number }>
请按这个签名重写之前的代码。
不要添加签名里没有的选项 key。
这样 AI 下一次生成会锚定在真实源码上,而不是凭训练记忆。
第 4 步:把 AI 锁定在已安装版本
在 system prompt 或规则文件里写:
本项目使用 <package>@<exact-version>。任何其他版本的 API 一律视为错误。
在生成调用 <package> 的代码前,先从
node_modules/<package>/dist/index.d.ts 引用方法签名,再写调用点。
Cursor 写到 .cursorrules,Claude Code 写到仓库根目录的 CLAUDE.md。
第 5 步:在调用点加运行时断言
哪怕已经修好了,也最好防御性地挡一道:
if (typeof client.query !== "function") {
throw new Error(
"client.query missing — likely an AI-hallucinated method. " +
"Check the @yourorg/db version and re-read the .d.ts."
);
}
把未来可能重新引入的回归变成一次明确的、带名字的失败。
第 6 步:为常见幻觉模式加 lint 规则
如果同一个不存在的方法名团队反复中招,加一条自定义 ESLint no-restricted-syntax:
{
"rules": {
"no-restricted-syntax": ["error", {
"selector": "CallExpression[callee.property.name='queryRecords']",
"message": "queryRecords does not exist on this client. Use query()."
}]
}
}
AI 一旦把这种调用塞进代码,lint 立刻报错。
验证
- 替换后的调用在
strict: true下编译通过,且没有// @ts-ignore这类补丁。 - 单元测试覆盖该路径,响应形状与代码期望一致(不会有意外的
undefined)。 grep -r "<hallucinated-method>" src/整个仓库零命中。- 同样的提示再次让 AI 生成相关功能时,第一次就给出正确的签名。
长期预防
- 在
CLAUDE.md或.cursorrules里固化已安装包的版本,以及”写调用点前先查 .d.ts”这条硬规定。 - TS 项目优先选带类型的库;大多数签名幻觉会被类型检查器在运行前拦下。
- 对于无类型或弱类型的库(裸 HTTP SDK、老旧 npm 包),用一层内部 wrapper 包成严格类型;AI 之后对接的是你的 wrapper,而不是野生 API。
- 接入新库时把它的 README 或
index.d.ts直接粘进 prompt,别让 AI 凭训练记忆硬猜。 - 长期项目升级依赖后,重新生成类型 stub,让 AI 看到的是当前形状。
- code review checklist 加上一条:“该方法在实际安装的库版本里存在吗?”作为通过 AI 生成的集成代码前的硬性步骤。
常见误区
- 轻信 AI 那句”这是 v2 的写法”——一定要去 v2 文档/CHANGELOG 里亲自核对 v2 是否真有这个方法。
- 用
as any把 TypeScript 报错压住,而不去修签名——运行时错误只是被推迟到了生产环境。 - 让 AI”修一下”,但不给真实签名——它只会幻觉出另一种不同的错误形状。
- 为了”也许 v6 里有”而升级库版本——通常哪个版本都没有。
- 假设 AI 只幻觉了一个方法——只要有一个签名错了,周围 30 行里大概率还藏着更多伪造调用。
相关失败模式见 AI 建议了过时的依赖、AI 生成的 TypeScript 错误、Cursor 漏看了项目上下文。
FAQ
Q:这个方法明明不存在,AI 却信誓旦旦,为什么?
模型训练时把许多相似库平均化了。它输出的是”听起来合理”的形状,而不是从一个被验证过的索引里检索出来的事实。把所有 AI 生成的 API 调用都当作假设,直到你和真实 .d.ts 或文档对照确认为止。
Q:我已经把文档粘进 prompt 了,它还是幻觉,为什么?
要么是文档在上下文里被截断,要么是 AI 还是把训练记忆权重压过了你贴的内容。重新提示时加一句”只能使用上面块里的签名。如果我要的方法不在那里,请明确说出来。”
Q:要不要换一个 AI 更熟的库?
除非那个库本身就更适合你的需求,否则不必。问题的本质是锚定不足,不是要投降。把真实签名钉进规则文件后,AI 对原本那个库一样能写对。
Q:怎么判断整个 AI 生成的文件里还有没有藏着别的幻觉?
把 TypeScript 严格模式 + 完整测试覆盖一起跑一遍。两者都过、且运行时行为符合规格,基本就干净了。类型检查 + 测试能抓住约 90% 的签名幻觉。
标签: #排查 #AI 编程 #幻觉 #api #type-errors