AI 编程代理写下一行代码之前,往往要先回答一个问题:这行代码究竟应该改在哪里?如果它只能依靠 grep、find 和反复读取文件来拼出代码库的地图,token 消耗就可能主要发生在「找代码」阶段。
Avi Chawla 在 X 上转述的一篇 Sonar 研究,把这个问题讲得很具体:在 6 个真实开源提交、4 种语言的受控对照任务中,语义代码导航的单次使用成本最高下降 36%。这个数字有明确边界——研究选择的是「定位关系比编辑本身更难」的重构任务,不能直接套到所有 Agent 工作流上。
这篇文章要保留的核心结论只有一句:语义导航减少的是 Agent 建立代码地图时的无效读取和推理回合;编译、测试以及真正复杂的设计决策,仍然需要独立验证。
高 token 账单可能来自哪里
Sonar 研究给出的一个近似模型很有帮助:Agent 的使用成本,大致由交互回合数乘以每一轮携带的上下文量决定。
当 Agent 执行一次文本搜索,打开若干匹配文件,再根据结果继续搜索时,会同时付出两种成本:搜索和推理增加了回合,已经读过的文件又会留在后续上下文里。一次噪声很高的搜索,影响的就不只是当前操作。
因此,排查 Agent 成本时,不能只看它最终生成了多少代码。还要看它为了确定修改范围,经历了多少次「搜索 → 打开文件 → 排除误命中 → 继续搜索」。这类连续读取可以称为 read storm(读取风暴)。
文本搜索的三个盲区
grep 有清晰的适用范围:它启动快、不需要索引,任何语言的代码库都能立即使用。当一个名字的命中位置几乎等于真正需要修改的位置时,文本搜索就是高效方案。
问题出现在搜索词无法干净地列出修改集合时。研究把这种情况归纳成三类:
| 盲区 | 文本搜索看到的东西 | 实际代价或风险 |
|---|---|---|
| 噪声洪水 | 同一个名字出现在真正目标和大量无关位置 | Agent 必须逐个打开文件,增加上下文和回合 |
| 错误命中 | 同名重载、局部变量遮蔽字段、不同作用域中的同名符号 | 命中看起来相同,身份却不同,可能误改或继续排查 |
| 隐形位置 | 需要修改的地方与搜索词没有共享文本,只存在实现、继承或调用关系 | 搜索直接漏掉位置,间接行为变更还可能留下静默缺陷 |
第一个盲区有一个很直观的实验例子:BloomFilter 的 Java 自类型重构只有 16 个实际修改位置,但搜索相关类型名返回了约 461 个命中,噪声比例约为 29:1。真正耗费 token 的环节,是从 461 个结果中筛出 16 个位置。
第二个盲区说明了「字符相同」和「符号相同」的区别。两个方法可能同名但参数不同;局部变量也可能遮蔽字段。Agent 需要打开上下文,才能判断它们是否指向同一个符号。
第三个盲区更值得警惕。一个类实现接口的关系、一个类型的继承链,或通过回调发生的调用,未必会在待修改行附近出现接口名或方法名。文本搜索找不到关系本身,只能让 Agent 继续读代码来推断。
语义导航到底增加了什么
语义导航把代码库表示成一张图:类、接口、方法、字段和参数是节点;调用、实现、继承和引用是边;每个节点和边都带有文件路径与行号。
这样,Agent 可以提出结构问题,而不必把结构问题伪装成关键词搜索:
| 结构查询 | 要回答的问题 |
|---|---|
get-type-hierarchy | 一个类或接口有哪些父类型、子类型和实现者? |
get-references | 哪些位置引用了这个确定的符号? |
trace-callers | 哪些调用者直接或间接到达这个方法? |
trace-callees | 这个方法向下调用了哪些方法? |
search-signatures | 符号声明在哪里,如何按签名区分它们? |
search-bodies | 符号实际在哪些方法体中被使用? |
高效的关键还有一层:查询结果需要直接返回 {file_path, line} 这样的编辑位置。如果只告诉 Agent「有这些符号」,却没有告诉它具体行号,Agent 仍然要再跑一轮文本搜索,语义查询就叠加了成本,替代效果会减弱。
这也是语义导航与普通检索的区别:普通检索返回相似文本或命中行,语义导航尝试回答「这个符号与哪些代码实体存在什么关系」。它解决的是定位和枚举问题,不能单独证明修改后的行为正确。
为什么可以放进 Agent 的内循环
Sonar 研究描述的实现有三个特点:
- 图谱可以直接读取源文件构建,不依赖编译器、语言服务器或网络请求,因此代码处在半成品状态时也能工作;
- 约 1,000 个源文件的完整建图在启动时需要几秒,修改后的增量刷新约为 1 毫秒,计算在本地进行,不计入模型回合;
- 符号识别很精确,但调用边的类型解析没有编译器或语言服务器完整,复杂调用关系仍可能存在近似。
这里有一个必须保留的边界:语义导航回答「应该去哪里看、哪些位置可能要改」,构建和测试回答「改完之后是否仍然正确」。如果一个导航工具让 Agent 少读了文件,却让团队减少了验证,整体风险反而会上升。
6 个任务的数字应该怎样读
Sonar 的研究页面发表于 2026 年 6 月 30 日。实验从流行开源项目中选取已经合并的真实提交,把提交前的代码作为起点,让同一个 Agent 在两种条件下重新实现变更:一组只使用普通工具,另一组额外使用图导航。
两组使用相同模型、工作量和环境,任务描述不提供文件名、符号名或行号;每侧运行 10 次,只有构建成功且目标测试通过的结果才计入比较。研究还关闭了网络和外部搜索,并对每次运行做了作弊审计。
表中的负数代表导航组成本更低。平均值展示总体变化,中位数更接近一次典型运行:
| 任务 | 语言 | 平均成本变化 | 中位数成本变化 |
|---|---|---|---|
| BloomFilter 自类型重构 | Java | -36% | -34% |
| BloomFilter 包重命名 | Java | -20% | -25% |
| SQLAlchemy 编译器关键字参数 | Python | -20% | -29% |
| TanStack Query mutation context | TypeScript | -5% | -6% |
| AssertJ 参数顺序调整 | Java | -4% | -15% |
| QuartzNET 返回类型调整 | C# | +5% | -20% |
最后一行尤其值得注意:QuartzNET 的平均值被少数编辑和构建耗时很高的运行拉高,中位数仍下降 20%。AssertJ 也表现出类似差异,平均值接近持平,中位数下降 15%。这说明 Agent 运行有明显噪声,单次对照很难支撑结论。
输入 token 在 6 个任务中都下降,范围为 11%–31%;输出 token 下降范围为 4%–35%。这与研究的解释一致:导航组需要保留和重复读取的文件更少,省下的是实际工作量,不是某种计费折扣。
还有一个很好的反例:SQLAlchemy 任务在只有两次运行时曾读出成本增加 92%,扩展到每侧 10 次后,平均值变成下降 20%,中位数下降 29%。「单次跑得更快」与「这类任务在重复实验中更省」是两个不同命题。
什么时候值得考虑语义导航
可以先检查下面四个条件:
- 存在广泛实现的抽象。 例如接口、基类或协议被许多类实现。
- 文本搜索无法干净列出实现位置。 目标位置可能隐藏在继承、实现或间接调用关系中。
- 各处修改高度一致。 变更通常是同一种行为或签名调整,能批量应用到相关位置。
- 发现位置是主要瓶颈。 编辑本身较机械,真正拖慢 Agent 的是确认范围、读取上下文和排除误命中。
四项同时满足时,语义导航更有可能带来可见收益。只满足「要改很多处」还不够:如果这些位置可以用一个精确的文本搜索直接枚举,导航层未必有必要。
下面这些任务通常优先保留普通搜索:新建一个局部功能、搜索词命中很少、代码库很小,或主要成本来自编译、测试和大量机械编辑。对于这类任务,导航工具没有太多发现工作可以替代。
一个稳妥的 Agent 工作流
可以把导航当成按需升级的第二条路径:
描述目标
│
├─ 精确名字、低噪声命中 ──► 文本搜索 ──► 读取少量文件 ──► 编辑
│
└─ 同名、间接关系、类型层级 ─► 结构查询 ──► 精确文件与行 ──► 编辑
│
构建 + 测试
实际使用时,可以按这个顺序检查:
- 先用文本搜索确认问题规模,观察命中数量与真实修改位置是否接近。
- 一旦问题变成「谁实现了这个接口」「谁引用了这个符号」或「谁间接调用了它」,切换到结构查询。
- 要求查询返回文件和行号,再打开少量上下文确认语义,避免把图谱结果当成无需阅读的结论。
- 修改后继续执行原有构建和测试,让导航负责发现,让工程验证负责把关。
- 如果要评估成本,使用同一模型、相同提示和相同环境,对一组相似任务重复测量,并优先比较中位数。
这套流程也能防止工具反客为主:当普通搜索已经能直接回答问题时,不必为了使用新工具增加一层调用;当搜索开始产生读取风暴时,结构查询才有清晰的进入点。
Sonar Vortex 是一个产品案例
原文把 Sonar Vortex 描述为语义导航和实时验证进入 Agent 内循环的一种产品实现。Sonar 的研究页面称,Vortex 将 Sonar Context Augmentation 与 SonarQube Agentic Analysis 的能力合并,相关能力通过 SonarQube CLI 和 SonarQube MCP Server 接入。
Sonar 当前的产品页面把 Context Augmentation 描述为:在 Agent 生成代码前提供与仓库相关的结构上下文、架构约束和质量规则;MCP Server 页面则提供了把 SonarQube 分析接入 AI 工具的方式,包括云端托管端点和自托管部署选项。它们体现的是「代码地图 + 质量验证」的产品组合,语义导航这个概念本身并不等于某一个厂商的产品。
这次来源还需要标注商业背景:X 原帖在结尾写明 Sonar 是当天的合作方,作者感谢 Sonar 的合作。因此,36% 应理解为 Sonar 在特定任务和受控条件下发布的研究结果。它值得用来理解机制和设计自己的评估,却不足以替代独立基准或团队实际测量。
结论
语义代码导航的价值,集中在一个很具体的场景:Agent 面对广泛实现的抽象,需要做一致性修改,但文本搜索无法完整、准确地列出所有位置。此时,一次结构查询可能替代多轮搜索、读文件和关系推理,token 成本与漏改风险都有机会下降。
如果你的任务主要是写新代码、改动位置容易枚举,或瓶颈在构建与测试,先把 grep 用好通常更合适。真正准备评估语义导航时,挑选同类重构任务,固定模型和环境,多跑几次,看中位数变化,并始终保留构建与测试这道独立检查。