给 Coding Agent 装上 LSP:别再让它靠 grep 猜代码

在这里插入图片描述

2026 年 6 月 10 日,GitHub 发了一篇很具体的文章:给 Copilot CLI 接上 Language Server,让它不要再靠 grep、解压 JAR、翻 .class 文件去猜代码。

很多终端里的 Coding Agent 默认工作方式是:

rg "getUser"
rg "UserService"
rg "OrderMapper"
rg "execute"

然后它把搜出来的文件一段段读进去,自己猜哪个是入口、哪个是实现、哪个是测试、哪个是旧代码、哪个是同名但无关的方法。

简单项目里,这种方式能凑合。

但一到 Java 后端、Spring 项目、多模块仓库、接口实现、泛型、重载、注解、依赖包,grep 很容易把 Agent 带歪。

所以这篇只讲一个具体问题:

Coding Agent 想改真实代码,不能只靠文本搜索。它需要 IDE 级别的语义导航能力,而 LSP 正好就是这层能力。

1. grep 能搜到字符串,但它不知道“这个符号是谁”

先看一个很常见的 Java 后端场景。

你给 Agent 一个任务:

给用户查询接口增加 tenantId 隔离,不允许跨租户读取用户信息。

项目里可能有这些代码:

// Controller
@GetMapping("/users/{id}")
public UserVO getUser(@PathVariable Long id) {
    return userService.getUser(id);
}

// Service interface
public interface UserService {
    UserVO getUser(Long id);
}

// Service implementation
public class UserServiceImpl implements UserService {
    @Override
    public UserVO getUser(Long id) {
        UserDO user = userRepository.findById(id)
            .orElseThrow(() -> new NotFoundException("user not found"));
        return userConverter.toVO(user);
    }
}

// Feign client
@FeignClient("profile-service")
public interface ProfileClient {
    UserProfileDTO getUser(Long id);
}

// Test fixture
private UserVO getUser() {
    return new UserVO(1L, "demo");
}

如果 Agent 只跑:

rg "getUser"

它会看到一堆结果。问题是,文本搜索并不知道:

  • Controller 里的 getUser 是 HTTP handler。
  • userService.getUser(id) 调的是接口方法。
  • UserServiceImpl#getUser 是真正业务实现。
  • ProfileClient#getUser 是另一个远程服务的接口。
  • 测试里的 getUser() 只是 fixture。

这些在人眼里不难,但对只拿到一堆 grep 结果的 Agent 来说,很容易出现两种错误。

第一种错误:改多了。

它看到所有 getUser,于是把 Controller、Service、Feign、测试工具方法都加上 tenantId,改出一堆本来不该动的接口。

第二种错误:改少了。

它只改了 Controller 和 ServiceImpl,忘了接口声明、调用方、测试、Mapper 查询条件。代码可能编译不过;更糟糕的是,编译能过,但业务隔离没做完整。

这不是模型弱,而是输入信息太粗。

grep 给 Agent 的只是“文本位置”,不是“符号关系”。
在这里插入图片描述

2. LSP 解决的不是搜索问题,而是语义定位问题

LSP,全称 Language Server Protocol。它不是专门为 AI 发明的,而是编辑器和语言服务器之间的标准协议。

你在 VS Code 或 IntelliJ 里用的这些能力,本质上都依赖类似的语言服务:

  • 跳转到定义。
  • 查找引用。
  • 查看类型和函数签名。
  • 查看诊断错误。
  • 重命名符号。
  • 找实现类。
  • 提供 code action。

LSP 官方说明里有一个很典型的交互:工具向 language server 发 textDocument/definition 请求,带上文件 URI 和光标位置,server 返回符号定义所在的文件和范围。

这和 grep 的差别很大。

grep 是:

给我所有包含 getUser 的文本行。

LSP 是:

在这个文件的这个位置,这个 getUser 调用实际绑定到哪个方法?

一个是字符串匹配,一个是语义解析。

对于 Coding Agent,这个区别非常关键。

因为 Agent 真正需要的不是“搜到很多可能相关的文件”,而是:

  • 这个调用跳到哪里?
  • 这个接口有哪些实现?
  • 这个方法有哪些真实调用方?
  • 这个重命名会影响哪些引用?
  • 当前修改后有没有类型错误?
  • 这个泛型最终推导出来是什么类型?

这些问题,靠 rg 很难稳定回答。

LSP 的价值,就是把 Agent 从“读文本猜代码”拉到“用语言服务问代码”。

3. 为什么 Java/Spring 项目尤其需要 LSP

前端小项目里,靠 rg 找文件有时还算够用。

但 Java/Spring 项目对 Coding Agent 很不友好。

3.1 接口和实现经常分离

Java 后端里,调用方经常依赖接口:

private final UserService userService;

public UserVO detail(Long id) {
    return userService.getUser(id);
}

真正实现可能在:

public class UserServiceImpl implements UserService {
    @Override
    public UserVO getUser(Long id) {
        ...
    }
}

只靠文本搜索,Agent 需要自己判断 userService 的真实类型、接口实现、Spring 注入关系。

LSP 至少可以帮它解决一部分基础语义问题:这个 symbol 的声明在哪里、接口方法有哪些引用、实现类在哪里。

它不能完全理解 Spring 容器,但比纯文本搜索强很多。

3.2 重载和泛型会让文本搜索变得很脆

比如项目里有几个同名方法:

UserVO getUser(Long id);

UserVO getUser(String username);

UserVO getUser(Long id, boolean includeProfile);

List<UserVO> getUser(List<Long> ids);

你让 Agent 改 getUser(Long id),它用 rg "getUser" 找出来一堆结果。如果没有类型和签名信息,它很容易把重载方法也一起改了。

泛型也一样。

ApiResponse<UserVO> response = userClient.getUser(id);
UserVO user = response.getData();

只看文本,Agent 可能不知道 getData() 最终是什么类型。LSP 的 hover、definition、type information 能给它更稳定的答案。

3.3 依赖包里的 API 不是源码文本

GitHub 那篇文章举了一个非常典型的场景:没有 LSP 时,Agent 可能会去 Maven 本地仓库里找 JAR,解压,再 grep .class 文件,试图拼出 API 签名。

这听起来很离谱,但终端 Agent 真的会这么干。它很努力,但方式很原始。

对于 Java 项目,很多关键 API 都在依赖包里:

  • Spring Framework。
  • Spring Data。
  • MyBatis。
  • Netty。
  • Jackson。
  • 各种公司内部 SDK。

如果 Agent 不知道依赖 API 的真实签名,它就只能猜。

LSP 接上之后,至少可以让 Agent 像 IDE 一样问语言服务器:这个类有哪些方法、这个方法签名是什么、这个调用是不是类型正确。

这就是“代码智能”跟“全文搜索”的差别。

在这里插入图片描述

4. 一个 Coding Agent 正确改代码前,应该先做语义预检

现在很多人用 Agent 改代码,流程是这样的:

给需求 -> Agent rg 搜文件 -> 读几个文件 -> 直接改 -> 跑测试

这套流程的问题是,Agent 在定位阶段就可能已经错了。后面再努力,也是在错误上下文里努力。

如果接了 LSP,我更希望 Agent 先做一轮语义预检:

1. 先定位入口方法的 definition。
2. 查这个方法的 references。
3. 查接口对应的 implementations。
4. 查看目标文件当前 diagnostics。
5. 输出影响范围,再开始改代码。

拿刚才的 tenantId 例子,Agent 动手前应该先给出类似结论:

语义预检结果:

入口:
- UserController#getUser(Long)

真实业务实现:
- UserService#getUser(Long)
- UserServiceImpl#getUser(Long)

直接调用方:
- UserController#getUser(Long)
- UserFacade#queryUser(Long)
- UserServiceTest#shouldGetUser()

无关同名方法:
- ProfileClient#getUser(Long),远程 profile 服务,不属于本次改动
- UserTestFixtures#getUser(),测试数据构造方法,不属于业务调用链

本次建议改动:
- Controller 增加 tenantId 获取
- Service 接口增加 tenantId 参数
- ServiceImpl 查询条件增加 tenantId
- Repository 增加 findByIdAndTenantId
- 单测覆盖跨租户查不到

这比直接贴一堆 grep 结果有用得多。

真正靠谱的 Coding Agent,不应该只是“会改代码”,还应该先证明自己找对了代码。

5. LSP 能给 Agent 哪些具体能力

结合 GitHub 文档和 LSP 规范,最值得给 Coding Agent 用的能力主要有这些。

能力 对 Agent 的价值 grep 的问题
Go to Definition 找到真实定义位置 同名文本太多
Find References 找到真实调用方 注释、字符串、无关方法会混进来
Hover / Signature 获取类型、参数、返回值 泛型、重载、依赖 API 容易猜错
Diagnostics 看到类型错误和编译诊断 只能等测试或构建失败
Rename 跨文件安全重命名 手工替换容易漏改或误改
Implementation 找接口实现 Java/Spring 项目里接口和实现分离
Code Action 获取语言服务建议修复 Agent 自己修可能绕远

这张表里,最关键的是前三个:definition、references、signature。

因为大多数 Agent 改歪,不是输在写代码,而是输在“找错地方”和“看错类型”。

在这里插入图片描述

6. 怎么配置:不要把 LSP 当魔法,先把语言服务器跑起来

GitHub Docs 里给 Copilot CLI 配 LSP 的核心配置是 lspServers

它大概长这样:

{
  "lspServers": {
    "typescript": {
      "command": "typescript-language-server",
      "args": ["--stdio"],
      "fileExtensions": {
        ".ts": "typescript",
        ".tsx": "typescriptreact",
        ".js": "javascript",
        ".jsx": "javascriptreact"
      }
    }
  }
}

几个字段要注意:

  • command:启动 language server 的命令,必须能在 PATH 里找到,或者写绝对路径。
  • args:传给 server 的参数,很多 server 需要 --stdio
  • fileExtensions:把文件扩展名映射到语言 ID。
  • rootUri:monorepo 很重要,用来指定语言服务器的项目根。
  • requestTimeoutMs:大仓库里可以适当调大超时时间。

GitHub Docs 还提供了 /lsp 命令:

/lsp show
/lsp test SERVER-NAME
/lsp reload

这说明 LSP 配置不是写完就算。你至少要确认三件事:

语言服务器能启动。
项目根目录识别正确。
目标文件的诊断和跳转可用。

尤其 Java 项目,不要低估配置成本。JDT LS、Maven/Gradle、多模块、JDK 版本、生成代码目录,都会影响语言服务器的效果。

如果 LSP 自己都没正确索引项目,Agent 拿到的语义信息也会不可靠。

7. LSP 不是万能的,它解决的是“代码事实”,不是业务判断

这点必须说清楚。

给 Agent 装上 LSP,不等于它突然懂业务。

LSP 能告诉它:

  • 这个方法定义在哪里。
  • 这个 symbol 有哪些引用。
  • 这个文件有没有类型错误。
  • 这个调用的签名是什么。
  • 这个重命名会影响哪些位置。

但 LSP 不能告诉它:

  • 这个需求真正想解决什么业务问题。
  • 这个接口是否应该保持兼容。
  • 这个改动是否符合团队架构边界。
  • 这个查询是否会影响性能。
  • 这个行为是否需要灰度。
  • 这个测试是否覆盖了真实业务风险。

所以 LSP 不是替代 review,也不是替代测试。

它更像是给 Agent 装了一副 IDE 级别的眼睛,让它不要在代码库里闭眼摸索。

一句话:

LSP 负责让 Agent 看清代码事实,开发者仍然要负责业务判断。

8. 什么时候必须要求 Agent 先用 LSP

不是所有任务都需要 LSP。

改一段 README、调一个 CSS、补一个简单日志,rg 就够了。

但下面这些任务,我会强制要求 Agent 先用 LSP 做语义预检。

8.1 改方法签名

比如:

getUser(Long id)

要改成:

getUser(Long tenantId, Long id)

这种任务必须查 references。否则很容易漏调用方,或者改到无关重载。

8.2 重命名 public API

如果要把:

queryUser()

改成:

getUserDetail()

不要让 Agent 手工搜索替换。让它用 rename 能力,至少比 rg + patch 稳。

8.3 找接口实现

Java 后端大量使用接口、抽象类、代理、注解。

你让 Agent 改一个行为,它必须先确认真正实现类在哪里。

8.4 查依赖 API

比如 Jackson、Spring Data、Netty、内部 SDK。

如果 Agent 开始翻 JAR、grep .class、猜方法签名,就应该停下来,先把 LSP 配好。

8.5 处理泛型和重载

泛型、重载、链式调用都是文本搜索的弱点。

这类场景不查类型,Agent 很容易写出“看起来合理、实际类型不对”的代码。

在这里插入图片描述

9. 可以直接给 Agent 的提示词

如果你的工具支持 LSP,可以给 Agent 这样的前置要求:

先不要修改代码。

请先用 LSP 做语义预检:
1. 找到目标方法的 definition。
2. 找到该方法的 references。
3. 如果它是接口方法,列出 implementations。
4. 查看相关文件的 diagnostics。
5. 输出你判断的影响范围,标注哪些同名符号不属于本次任务。

只有在影响范围确认后,再开始修改代码。
修改后再次检查 diagnostics,并运行相关测试。

这个提示词的关键不是“礼貌”,而是强制 Agent 先回答:

你到底准备改哪里?
你为什么认为这些文件相关?
哪些同名代码你明确排除了?
修改前有没有已知诊断?
修改后诊断有没有新增?

这比“帮我改一下用户查询接口”可靠得多。

10. 这篇文章真正想说的

现在 AI Coding 的竞争,不只是模型能力竞争。

模型当然重要,但真实工程里,Agent 还需要三类东西:

代码事实:definition、references、types、diagnostics
执行反馈:tests、lint、build、runtime logs
工程约束:instructions、skills、review rules、CI gates

LSP 补的是第一块:代码事实。

没有这块,Agent 很容易靠文本搜索拼上下文,越大的项目越容易猜错。

有了这块,Agent 才更像一个能使用 IDE 的开发者,而不是一个在终端里乱翻文件的脚本。

所以我觉得 GitHub 这次把 Copilot CLI 和 LSP 放到一起讲,信号很明确:

下一阶段的 Coding Agent,不会只拼谁生成代码更快,而是拼谁能更准确地理解真实代码库。

对我们实际使用者来说,结论也很简单:

如果你让 Agent 改的是小脚本,grep 够用。

如果你让 Agent 改的是 Java/Spring 项目里的业务链路、接口签名、跨模块调用、依赖 API,那就别再让它靠 grep 猜代码。

先给它装上 LSP。

参考资料

  • GitHub Blog: Give GitHub Copilot CLI real code intelligence with language servers:https://github.blog/ai-and-ml/github-copilot/give-github-copilot-cli-real-code-intelligence-with-language-servers/
  • GitHub Docs: Using LSP servers with GitHub Copilot CLI:https://docs.github.com/en/copilot/concepts/agents/copilot-cli/lsp-servers
  • GitHub Docs: Adding LSP servers for GitHub Copilot CLI:https://docs.github.com/en/copilot/how-tos/copilot-cli/set-up-copilot-cli/add-lsp-servers
  • Microsoft Language Server Protocol Overview:https://microsoft.github.io/language-server-protocol/overviews/lsp/overview/
  • Language Server Protocol Specification 3.17:https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/
    a
Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐