给 Coding Agent 换上更精准的 LSP 工具,效果不一定更好。任务要找什么、代码库里有多少同名文本、工具返回了哪些代码,都会影响结果。
我们最近做了一组小规模实验:给 Coding Agent 接上一套基于 LSP 的语义导航工具,和它平时用的 grep 对比。
我们本来以为,用 LSP 的效果会更好。它理解符号、定义和引用,能排掉注释和普通字符串里的同名文本,返回结果更准,Agent 要处理的无关内容也更少。
实际测下来,LSP 的效果取决于具体任务。代码定位任务里,Agent 几乎只用 grep;强制它先用语义工具,成功率有时还会下降。只有在要找全调用方,或者代码库里同名文本很多的时候,LSP 的好处才明显。
检索准不准只是一部分。工具还得把下一步要用的上下文一起返回,让模型拿到结果就能用。模型在训练里见过多少类似的工具调用,可能也有影响。这次实验能看出上下文和返回格式的影响;训练数据没有改过,只能提出猜测,没法验证。
LSP 还有诊断、重命名、代码操作这些能力,这次都没测。下面的结论只适用于查找引用、跳转定义和列出文档符号这三种语义导航,代表不了 LSP 的全部价值。
grep 按字符串查找文本,不理解代码结构,但直接返回文件路径、行号和匹配内容。LSP 语义导航基于语言服务器协议(Language Server Protocol),我们只接了查找引用、跳转定义、列出文档符号三种能力,它们理解代码里的符号关系,分得清真正的函数调用和注释里的同名文字。
我们在多个 Python 和 TypeScript 代码库里,用三个 Claude 模型做了三类任务:定位代码、找全引用、多文件重命名。只有两种方法都成功完成任务,才比较 token 消耗——失败的尝试往往半路就停了,看起来消耗少,这种“节省”没有意义。每种实验设置只跑了两到三次,这些结果只能提供一些线索,还不能当作普遍结论。
简单的代码定位任务里,两种工具都可用时,三个模型主动选语义工具的比例只有 0%~6%。强制先用语义工具,这组任务的成功率从 100% 掉到 89%。
换成“找出所有调用方”这类引用完整性任务,三个模型用语义导航的比例升到 45%~57%。这组任务里,LSP 把精确率(precision)从 grep 的 0.76 提到 1.00:返回的结果全是真正的调用点,排除了同名文本造成的误报。但两种方法的召回率(recall)都只有 0.66 左右——结果更干净了,找到的真实调用点并没有变多。Agent 没有把结果查完,换检索后端也没解决这个问题;在更强的模型上,换成 LSP 后查得更准了,token 却花得更多。
Agent 不是一味偏爱 grep,而是按任务挑工具
两种工具都可用、Agent 自由选择时,语义(LSP)工具调用所占的比例。
图例:Opus 4.8(蓝)、Sonnet 4.6(品红)、Haiku 4.5(绿)。
grep;要找全引用时,不用提示它也有一半左右的调用走 LSP。| 任务 | Opus 4.8 | Sonnet 4.6 | Haiku 4.5 |
|---|---|---|---|
| 代码定位 | 0% | 4% | 6% |
| 引用完整性 | 45% | 50% | 57% |
| 多文件重命名 | 3% | — | — |
代码库本身也很重要。在同名文本干扰较少的 TypeScript 代码库 remeda 里,grep 本身就能准确找出引用,换成 LSP,F1 没有提升,token 反而多了 16%。另一个 TypeScript 代码库 hono 里,同名文本干扰多,grep 的精确率只有 0.51,换成 LSP 后 F1 提高了 0.246,token 还省了 12%。Python 代码库 requests 介于两者之间:F1 多了 0.072,token 多了 19%。
同名文本越多,LSP 越有用
引用完整性任务中的 F1 增益(ΔF1 = LSP − grep)。柱子的颜色表示 grep 在该代码库里的误报程度,prec 是 grep 在这里的精确率。
图例:蓝色表示 grep 在这个代码库里查得准,品红表示误报多。
grep 已经查得很准时,换成语义导航只会多出调用和解析的开销;同名文本多时,LSP 减少误报的好处才显出来。| 代码库 | 语言 | grep 精确率 | ΔF1(LSP − grep) | Token 变化 |
|---|---|---|---|---|
| remeda | TypeScript | 1.00 | +0.000 | +16% |
| hono | TypeScript | 0.51 | +0.246 | −12% |
| requests | Python | 0.76 | +0.072 | +19% |
两个 TypeScript 代码库结果相反,光看是不是强类型语言,判断不了 LSP 有没有用,还得看 grep 在这个代码库里有多少误报。Agent 也不是简单地“一直用 grep”:换个任务,工具选择会变;换个代码库,语义导航值不值得也会变。
我们最初提供的 LSP 工具只返回文件路径、行号和列号。Agent 知道引用在哪里,却看不到代码,得逐个打开文件才能判断下一步。grep 的一行返回是这样的:src/auth.ts:42: return validateToken(token),位置和代码内容都在,模型不用再读一次文件,就能判断这条匹配相不相关。
于是我们改了一版 LSP 工具:除了位置,再附上引用点前后两行源码。语义后端没动,找到的引用还是那些,只是模型现在能直接看到旁边的代码了。做多文件重命名时,一次尝试成功率(pass@1)从 0.67 提高到 0.83,后续读取文件的次数从 15.2 次降到 3.2 次,比用 grep 时的 4.3 次还少。
返回源码上下文,语义导航才好用
多文件重命名任务,模型为 Opus 4.8,使用预热索引的 pyright。两个 LSP 组用同一个语义后端,只有返回格式不同。
图例:grep(蓝)、LSP 只返回位置(品红)、LSP 附带源码上下文(绿)。
| 方案 | pass@1 | 引用点召回率 | Token 数 | 后续读取次数 |
|---|---|---|---|---|
| grep | 1.00 | 1.000 | 2,451 | 4.3 |
| LSP,只返回位置 | 0.67 | 0.930 | 4,131 | 15.2 |
| LSP,附带源码上下文 | 0.83 | 0.958 | 3,336 | 3.2 |
Anthropic 在 Writing effective tools for agents 里也讲到这一点:工具返回的上下文本身就是接口的一部分。语义上正确只是第一步,返回内容还得够模型做下一步决策。
模型会不会因为训练里见多了类似 grep 的工具调用,才更习惯这种返回格式?这次没有改训练数据,回答不了这个问题。附上源码,可能更接近模型熟悉的返回格式,也可能只是省了几步操作。两种解释都说得通,这次实验还分不清。
LSP 和 grep 覆盖的范围不一样。语义引用只包括代码里真正的符号关系;但多文件重命名要改的,还有注释、docstring、配置文件和普通字符串。这些不算语义引用,却同样得更新。find_references 按设计不会返回它们,grep 会把所有同名文本都列出来。
语义引用 ⊂ 文本出现位置
要把这些文本一并改掉,grep 本来就能查得更全,模型再熟悉 LSP 也改变不了两种工具的查找范围。
grep 为什么表现更好,这里有两种解释,需要分清:
第一条实验已经验证了,直接来自两种工具查找范围的差别。第二条只是假设,与工具选择和返回格式的结果一致,但这次没有改训练数据,验证不了。
grep 什么时候更合适,使用习惯则解释了模型为什么更愿意走熟悉的路径。Coding Agent 还包括模型周围的运行环境,通常叫 harness(运行框架)。模型收到哪些指令、能调哪些工具、参数怎么写、结果和错误怎么返回,以及下一轮能看到什么,都由它安排。
这次实验里,LSP 后端没动,只是多返回几行源码,成功率和文件读取次数就都变了。模型权重没有变,改的只是工具接口。
Anthropic 在 Effective harnesses for long-running agents 里也谈到了运行环境:长时间工作的 Agent 要跨会话继续做事,需要初始化环境、记录进度、验证结果。即使只看一次工具调用,返回格式、错误信息和备用路径,也都会影响模型接下来做什么。
后训练里如果包含 Agent 轨迹,这些轨迹里的提示、工具调用、返回结果和出错后的补救路径,同样由 harness 定义。一个反复用 read、grep、edit、bash 训练出来的模型,学到的做法可能就依赖这几个接口;换一套工具,实际能力也可能跟着变。
Agent 的实际能力 = 模型 × harness
所以同一个模型换到另一套 runtime,不一定还能跑出原来那套 harness 的 benchmark 成绩。支持同一个模型,不等于复现同一个 Agent:工具怎么选、参数怎么定义、结果和错误怎么返回,都会影响模型的做法。
LSP 在同名文本多的代码库里确实查得更好,多返回几行源码也确实减少了后续读取,不能据此说 LSP、MCP 或新的 Agent Skill 没用。给 Agent 加工具,要看它做完任务的整个过程。
我们的建议是先用原生工具,再按下面六条检查新加的能力:
path:line:content 往往比只有位置的对象好用。grep。Anthropic 在 Building effective agents 里建议优先用简单、可组合的模式。工具也是一样,数量多了,任务不一定做得更好;工具之间要区分清楚、容易理解,还得在模型的工作流程里真的用得上。
LSP 能减少误报,但代码库里得确实有同名文本的干扰,才能看到这个好处。工具只返回位置,查得再准,模型也得额外打开文件;附上几行源码,拿到结果就能继续判断。评估新工具时,得一起看 Agent 会不会选它、任务做没做完、花了多少成本,以及返回的内容够不够用。
完整的实验设计、任务定义和结果见 Does a Language Server Save Tokens for Coding Agents?。
AgentConnect 也是这个思路:用开放协议连接不同 Agent,同时保留各自的原生 runtime 和工具循环。
textDocument/rename、diagnostics 或 code actions。如果直接让 LSP 做语义重命名,在这次 grep 表现较好的重构任务里,结果可能会不同。编辑任务都在本地完成,这些不是标准 SWE-bench 分数。结果可以作为设计工具时的参考,但不能代表所有模型、工具和代码库。