用于大语言模型的LSP
在那个在编辑器中输入代码的黑暗时代,我们借助破碎代码下的曲线、点击定义链接等辅助。它由语言服务器和类型检查器驱动。现在有几个线索将LSP暴露为工具,基于合理的前提:更好的代码智能造就了更好的代理。
不过模型主要用他们一直拥有的工具训练,通常是grep和远程阅读。取代那些增益效果很棘手。
模型对代码库的理解和人类略有不同。人类可以同时在视野中保留少量文件,工作记忆中稍多一些,随着时间推移,他们会构建出代码库的大致心理模型。
代理拥有巨大的上下文窗口,可以同时理解大量文件。他们可以通过搜索符号来找到符号,通常会触发部分读取,将部分文件拉入上下文窗口。
他们也只是......懂东西?它们的权重包含了大量公共规范的相当高保真版本。这有助于你理解代码,或者通过类比推理其他代码库。
为了看看LSP如何帮助这个过程,我做了许多实验,这些实验都被这个仓库.1实验涉及本地模型和API模型的混合,针对Python进行。我用Pyrefly做检查器,并且用静态AST解析验证了Pyrefly2进行大部分测试。
我有疑问的是,LSP是否会告诉代理一些它无法找到的信息,LSP的答案是否比默认读数更便宜,以及诊断的时间是否会影响结果。
第一个问题的答案大多是否定的。无论模型是否配备LSP工具,在各种设置中解析10个同名覆盖都可行。类型注释确实如此不过确实会有影响。
如果我把这些拆掉,文本检索会变得更差。一个有能力的模型在背带上基本上已经是个不错的类型检测员了。
关于效率,仅仅添加LSP并无济于事:模型不会在没有提示的情况下使用它。如果模型需要读取文件来解析类型,LSP defn 工具比文件读取工具更便宜。
3但是!模型通常会自己读取文件还有这完全抵消了 LSP 在代币效率上的优势。
即使我注入了包含相关修复的定义,模型在几乎每种情况下都会重新读取文件。告诉模型提供的跨度已完成只节省了少数几个案例,而且提供提示本身会消耗更多代币!
要让模型优先调用 defn 调用读取文件,需要微调。我在Qwen 3.6上用了DAgger风格的重标(生成轨迹,将文件读值替换为定义读值,微调结果),这样模型就能避免多余调用。
4
还值得一提的是,便宜不一定更好。这些是输入/预填充代币,是那些更便宜、优化起来没那么有趣的。该代码库-内存Paper发现,文件探索代理在真实仓库上比结构化图表工具更高效(92%对83%),其代币价格大约是其十倍。
所以,有时候这些额外的代币确实有用。
第三个问题是关于时机。如果你想保持类型注释正确(你确实想),可以用类型检查器。但什么时候发布反馈最好呢?
我让一位代理人处理了一组通过明显测试但未通过一项未通过的修改草案,并要求其审核并提交。如果不管,模型在12次中有11次接受了错误的修订。
当类型检查器对提交进行门槛时,它只接受了12次中的1次错误更改。5
继Thinky在交互模型我想看看在生成过程中实时传递诊断(基本上是agentic squiggles)是否有帮助。6总体来说,答案是否定的:直播是中立的,而非没有反馈。
促使模型进行修正似乎是有害的:让代理处理工具反馈似乎以一种负面的方式覆盖了它自身的判断。
在回合结束时或每次编辑后批量处理反馈确实有帮助,整体代币花费中,回合结束是明显的赢家。
注意事项 注意事项很多任务都是合成的,所有任务都相当简单,而且每个不错的模型几乎都能解决所有问题。时间结果只用7B模型测试过,所以用强模型可能会有更好的结果。
这些代码库可以完全包含在上下文窗口内,尽管实际使用中代理似乎从未主动直接导入整个代码!
话虽如此,我有一些收获,至少对我自己的工作来说是这样:
- 类型是好的。正确的注释帮助代理在代码库中导航,无论使用何种工具。
- 用回合结束钩子保持类型正确。理想情况下,把它设为门控,在类型错误时主动请求修复,否则保持沉默。不过,还是要量一下!记录它阻挡的频率、维修通过的频率、拒绝不该做的任务的频率。
- 在任务层面衡量代币节省。如果对单个操作进行基准测试,很容易得出结论是该操作更便宜。不过你需要看到完整的模型行为才能真正评估变化:有没有工具或提示时的正确性是否相同,以及在不同使用范围内是否能带来一致的代币节省。
- 多尝试在更大的代码库中使用LSP和提示词。我没有直接测试过,但从结果来看,对于大型、复杂、私有的代码库,导航工具的收益会更多。你仍然可能需要提示模型主动使用这些工具:默认情况下,模型会选择已知的工具。
未来有一些有趣的工作。我们用的工具是为能自行判断输出/忽略输出的人类设计的。这对模特来说更难,可能需要培训。
我也希望能够评估一个代码库,判断某个工具是否能帮忙,而不必只是对它运行一堆任务:有没有我们可以静态收集的指标,从而帮助做出这类决策?
最后,我认为我们需要更多大型代码库重构和迁移任务:比如ProgramBench规模的问题,但要处理一个庞大、现有且不在训练集中的代码库。
说到这里,SWE-Bench ProMax写这篇文章时我说出来了:好像相关,但我还没看过!
- 这要归功于Codex和Claude,报告中的写作是LLM+大量编辑,所以请调和你的期望,所有数字都可以从那里的文稿中复现,如果感兴趣的话。︎
- 在真实库符号上,两者大多数情况下意见一致:空隙是重新导出,Pyrefly 返回为空,而解析器则没有。︎
- ranged-read的代币成本大约是defn调用的1.3倍。︎
- 这种培训的一个风险是,它教模型的是表面的工具使用,而非判断:有时你确实需要真正阅读文件!有些令人惊讶的是,这似乎不是问题:当我给它不够的跨度时,它每次都读,而当跨度足够时,它只读了两次。训练数据中没有实际需要读取的任务实例,这表明条件化并未完全破坏模型判断。︎
- 其他情况下,错误通过类型检查暴露,但这次类型检查干净,没有信号。︎
- 我实际用来注入结果到直播流的方法,是异步事件注入方法,描述如下胡珀等人。.︎