跳转到内容
cd /blog

为什么你的 AI 编码智能体会产生文件路径幻觉

[AI Agents] [Hallucination] [Retrieval] [MCP]

> 当 AI 编码智能体没有仓库的真实地图时,就会编造文件路径。本文说明这为何发生,以及扎根依据如何带来更少的幻觉编辑。

破绽不是一个写错的函数体。而是一次自信满满的编辑,针对一个不存在的文件,或者一个存在但并非实现该行为的文件。

你要求修复会话过期问题。智能体打开了 src/auth/session.ts。这条路径看起来合理。但它也是错的。真正的处理函数在 apps/api/src/middleware/expiry.ts 里,两次重构之前被重命名过。智能体没有对你撒谎。它是用训练先验加上上下文窗口里已经有的内容,补全了一个路径形状的字符串。

这是真实仓库中常见的失败模式。模型会写代码。失误在于选错了写代码的地方。

为什么智能体会编造路径

编码智能体是一个带工具的下一个 token 预测模型。当你说“修一下会话过期”时,它必须先指定一个文件,才能读取或修补任何东西。如果它还没有一张有依据的地图,它就会从以下来源构造一条路径:

  1. 它在其他仓库里见过的命名约定。src/auth/session.ts 是一种常见形态。
  2. 已经出现在对话里的文件,那可能是你上周粘贴过的文件,而不是现在真正重要的文件。
  3. 它碰巧运行过的目录列表,在大型代码树上是不完整的。
  4. 指令文件。CLAUDE.md 和 AGENTS.md 告诉智能体该如何表现。它们不是符号、调用方或当前路径的活索引。

这些来源没有一个是今天这个仓库的地图。模型填补空白的方式,和它填补任何空白的方式一样:用一个高概率的延续。这个延续常常看起来像一条真实的路径。有时那个文件确实存在,而且就在正确文件旁边。有时它曾经存在于上个月已合并的一个分支上。有时它从来就不存在。

如果智能体从未搜索过正确的字符串,grep 在这里救不了你。智能体猜了一条路径,然后读了那条路径返回的内容,然后编辑了它。第一个错误的步骤是路径,不是补丁。

读文件也不等于找到正确的文件。Claude Code、Cursor、Codex 以及类似的工具,可以打开你指向的任何东西。问题在于,它们能否在编辑之前,可靠地找到正确的文件、重要的符号、依赖关系、测试,以及影响范围。没有地图的文件访问,依然是从猜测开始的。

错误文件的编辑是一个检索问题

把编造出来的路径当作一次检索失败来处理,而不是当作性格缺陷。

如果智能体在被允许编辑之前必须返回 path/file.ext:line,会发生两件事。第一,你可以打开这条引用,在十秒内拒绝这次工作。第二,智能体必须去查询某个真正了解这棵代码树的东西。

这就是代码库上下文层的工作。仅靠语义搜索是不够的。一个“听起来像会话过期”的片段,依然可能落在一个已废弃的包里。当你知道字符串时,你还需要精确文本;当你知道名字时,你需要符号查找;当你知道形状时,你需要结构化搜索;当你需要调用方和影响范围时,你需要依赖关系图。

Maguyva 就是为兼容 MCP 的编码智能体提供的这一层。它不是编辑器。Claude Code、Cursor、Codex CLI、VS Code、Windsurf 以及其他 MCP 客户端,都留在它们原本的位置。智能体在触碰任何一行代码之前,先查询一张排序过的地图。

这个主张刻意保持谦逊:更少的幻觉编辑。Maguyva 并不会剥夺模型猜测的能力。它改变的是默认的第一步,从“编造一条看似合理的路径”变成“请求排序过、带引用的位置”。

一份有依据的答案是什么样子

对“会话过期在哪里处理”这个问题,一个有用的答案不是一段话。而是一份简短的位置清单:

  • apps/api/src/middleware/expiry.ts:48 - 处理函数
  • apps/web/src/lib/auth-client.ts:112 - 调用方
  • apps/api/tests/expiry.test.ts:20 - 如果这次改动出错就会失败的测试

你可以打开这些行。如果错了,你就停下。如果对了,智能体就可以编辑。重点在于验证,而不是一句口号。

Maguyva 提供 11 个面向客户的 MCP 工具,涵盖搜索、符号、依赖分析、文件检索、仓库上下文和确定性实用工作流。四种检索模式(语义、结构、图/依赖和文本)融合成排序结果;符号查找是一个独立的 MCP 工具(find_symbol)。有依据的答案会带着文件路径和行号返回,方便你对照眼前的代码树来评判。

文档 描述了最短路径:先免费开始,索引一个你已经了解的仓库,连接客户端,然后问一个你能核实的问题。

一个改变第一次编辑的循环

你不需要一套新的工作流信条。你需要的是在第一次落笔之前多加一步。

  1. 问一个你已经能评判的问题。“哪段代码处理登录和会话过期”比“修一下认证”更好。
  2. 要求引用。文件路径和行号,而不是“认证模块”的一段摘要。
  3. 打开被引用的文件。跟着符号走。确认你即将触碰的依赖关系。
  4. 只有到那时,才让智能体去打补丁。

如果第二步没有返回任何有用的东西,智能体就没有资格临时编造一条路径。它要么再搜索一次,要么由你来指路。正是这一道关卡,产生了更少的幻觉编辑。模型还是同一个模型。变了的是地图。

指令文件依然重要。保留它们。用它们来记录约定、审查规则,以及不该触碰的东西。不要要求它们充当索引。一个列出三个重要目录的 CLAUDE.md,在一次包拆分之后的那一周就会过时。而一张可查询的地图,会随着仓库的更新而更新。

一个相关的失败是“几乎对”的文件:当行为其实在 auth-client.ts 里时,智能体却打开了 auth.ts;或者补丁打在了一个生成的文件上,而不是源文件;或者因为名字匹配,编辑了 packages/legacy 里的那份拷贝。引用不会让这些错误变得不可能发生。它们只是让这些错误在 diff 落地之前变得可见。

一张地图不会为你修复的东西

对自己得到的东西保持清醒。一张有依据的地图不会让每一次编辑都变得正确——你的智能体依然可能在完全正确的文件里写出一个糟糕的补丁。如果你的代码库依赖动态派发、反射或猴子补丁,一些符号查找返回的结果会比你希望的更单薄,因为运行时的间接性对任何静态索引都会造成这种效果。而如果你在期待一个百分比——“幻觉路径减少了 X%”——你在这里找不到,因为除了你自己,没有人能为你的仓库诚实地测出这个数字。

你可以期待的东西范围更窄,也更有用:智能体不再用一条猜测出来的路径来开始它的编辑。这第一步会扎根于一张真实的地图,而更少的幻觉编辑正是由此而来。引用之后的一切——阅读 diff、判断补丁——依然是你的工作,和以往一样。

这个测试几乎不花你什么成本:索引一个你已经了解的仓库,问一个你能核实的问题。从一条曾经让你吃过亏的路径开始。如果引用错了,你会立刻看出来。如果对了,你智能体的下一次编辑就有了一个更好的落脚点。

// it doesn't know the file. it knows how to find it.

相关阅读

更多来自 Maguyva 开发日志的内容