平时写代码的时候,大家可能都有过这种体验:让大模型帮你生成一段调用代码,它给出的方法名看起来头头是道,一查根本不存在;让它补全某个模块里的函数,它完全不知道你当前项目里有哪些符号;让它重构代码,它甚至会把已经过期的 API 继续用下去。
这不是大模型不够聪明,而是它缺少一种“当前项目语义信息”的获取通道。模型只知道通用语法,不熟悉你本地工程里真实存在的类、函数、变量和类型关系。于是这两年出现了一个很有意思的方向——把经典的 LSP(Language Server Protocol,语言服务器协议)接入 LLM,让语言服务器充当大模型的眼睛,帮它看清楚代码库的真实结构。
这篇文章会从 LSP 的原理讲起,说明为什么 LSP 对大模型应用如此重要,然后给出一个可直接运行的“LLM + LSP”最小实战案例,最后总结接入过程中的高频问题和工程建议。无论你是做大模型应用开发、AI 编程助手,还是想给团队内部的代码智能体补充语义能力,这篇文章都能提供一条可以落地的思路。
1. 背景与核心概念
1.1 LSP 到底是什么
LSP 的全称是 Language Server Protocol,也就是语言服务器协议。它最初由微软提出,目的是解决一个非常头疼的问题:以前每种语言都要为每个编辑器单独开发一套插件,VSCode 一套、Vim 一套、Emacs 一套、Sublime 一套,重复造轮子。
协议出现之后,语言的语法分析、补全、跳转、诊断这些重量级逻辑被独立成“语言服务器”,编辑器只需要通过统一的 JSON-RPC 消息和语言服务器通信。协议标准化了,编辑器只需要实现一次客户端,就能对接所有语言服务器。VSCode 能补全 Python,靠的是 Pyright;能补全 Go,靠的是 gopls;能补全 C/C++,靠的是 clangd。这些语言服务器都是 LSP 的具体实现。
我们平时在 IDE 里每天都会用到 LSP,只是多数情况下没有感知。每当你输入代码,编辑器把textDocument/didChange通知给语言服务器,服务器分析完后返回诊断信息;每当你输入一个点,编辑器把textDocument/completion请求发过去,语言服务器就返回当前上下文里可用的成员列表。这些交互都是通过 JSON-RPC 完成的。
1.2 LLM 为什么需要 LSP
大语言模型本质上是一个“文本预测器”,它靠海量训练数据学会了代码的统计规律,但它没有实时看代码库的能力。当模型被问到“当前仓库里UserService有哪些方法”时,它的唯一信息来源是训练数据里的记忆,而不是你项目里真实的代码。这就造成了两个典型问题:
- 幻觉 API:模型返回了看起来合理但实际不存在的函数名。
- 上下文缺失:模型不知道当前模块的导入路径、类型别名、重载函数。
LSP 恰好能弥补这一点。语言服务器由本地编译器或静态分析器驱动,它读取真实代码,构建符号表,能准确回答“某个文件里有哪些类”“某个类有哪些方法”“某个位置是什么类型”“某个变量在哪里定义”。如果把这些信息作为工具返回给 LLM,模型就相当于拿到了真实项目的地图,回答准确率会明显提升。
1.3 LSPs for LLMs 的常见形态
现在“LSPs for LLMs”并没有一个严格统一的定义,更多是一种架构思想。常见的落地形态有三种:
第一种是AI 编程助手。IDE 插件在收到用户自然语言指令后,先通过 LSP 拿到光标位置的符号、类型和代码范围,再把这些结构化信息拼进 Prompt,最后让 LLM 生成补全或修改建议,例如 GitHub Copilot Chat 在 VSCode 里做代码解释、修复、重命名时,背后就有 LSP 提供代码上下文。
第二种是Code Agent / 自动化任务。智能体收到一个任务,比如“修复所有文件里的某个 Bug”,Agent 需要定位文件、分析符号引用、修改代码、检查诊断。这些步骤如果全部靠 LLM 猜测会非常不可靠,最稳妥的做法是调 LSP 的textDocument/definition、textDocument/references、textDocument/documentSymbol、textDocument/diagnostic来获得准确定位。
第三种是对话式代码问答。用户问“这个handle函数为什么报错”,系统先调用 LSP 的 hover 和诊断接口拿到错误信息,再交给 LLM 解释根因、给修复建议。这个模式实现起来最快,也是很多企业知识库问答和代码审计系统采用的路线。
在这三种形态里,LSP 扮演的角色都不是“替 LLM 思考”,而是“替 LLM 感知代码库”。这是两者最核心的分工关系。
2. 环境准备与版本说明
为了让你能够完整跑通后面的实战示例,这里先明确环境需求。
本文的代码示例是基于 Python 写的,理论上支持 Windows、macOS、Linux 三个平台,但更推荐在 macOS 或 Linux 上运行,因为子进程管理、stdio 管道处理在这些系统上更顺畅。
运行环境建议如下:
- Python 3.9 及以上版本。
- 需要安装
pygls库,用于实现一个极简语言服务器。pygls 是 Python 生态中最流行的 LSP 框架之一,具体版本请以你安装时 pypi 上的最新稳定版为准。本文的 API 用法适配 pygls 较新的主流版本。 - 需要安装
requests库,用于编写客户端向语言服务器发送 JSON-RPC 消息,以及向本地 LLM 服务发送 HTTP 请求。 - 如果演示 LLM 调用部分,建议本地准备一个 OpenAI 兼容的推理服务,例如 Ollama、vLLM、llama.cpp server 等。你也可以直接改用 OpenAI API,代码里只需要修改
base_url和api_key。
需要说明的是,LSP 协议本身的版本目前主流是 3.17,这也是各大语言服务器普遍支持的版本。本文示例只用到其中几个稳定方法,不依赖任何新版本特性,所以即使你用的语言服务器版本较旧,也能正常对齐。
项目结构上,演示时会包含三个文件:
llm-lsp-demo/ ├── demo_lsp_server.py # 基于 pygls 的极简语言服务器 ├── lsp_client.py # 封装 JSON-RPC 客户端 └── llm_agent.py # 将 LSP 结果交给 LLM 的演示入口如果版本在后续迭代中变化,请优先参考官方文档和项目内 README,本文重点演示的是链路和思路,不是某一版 API 的精确复刻。
3. LSP 协议工作原理解读
3.1 客户端-服务器模型
LSP 的通信模型很简单,只有两个角色:客户端(通常是编辑器或 Agent)和服务器(语言服务器)。
- 客户端负责收集用户的编辑行为,例如打开文件、修改内容、移动光标,然后把这些事件转成协议消息发给服务器。
- 服务器负责分析和响应,比如返回补全列表、跳转位置、诊断结果,然后通过 JSON-RPC 消息回给客户端。
在传输层上,LSP 最常见的方式是stdio,也就是客户端启动一个语言服务器子进程,然后往子进程的 stdin 写请求,从 stdout 读响应。这种方式最简单,也是 VSCode 默认采用的通信方式。除了 stdio,LSP 也支持 TCP 或命名管道,但核心交互模型完全一致。
3.2 JSON-RPC 消息格式
LSP 的消息格式基于 JSON-RPC 2.0。每个消息是 JSON 对象,并且传输时外面包了一层Content-Length头,用于告诉接收方这次消息体有多长。一个典型的消息长这样:
Content-Length: 139\r\n \r\n {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"processId":1234,"rootUri":"file:///path/to/project","capabilities":{}}}在写客户端时要特别注意这个头部,字节数必须准确,否则服务器会解析失败。实战里最常见的坑就是这个长度算错,导致消息错位。
3.3 常用方法说明
LSP 涉及的方法很多,但面向 LLM 集成的场景,只需要重点关注下面几个:
| 方法名 | 用途 | 对 LLM 的价值 |
|---|---|---|
initialize | 建立会话,确定能力 | 启动一切的前提 |
textDocument/hover | 获取鼠标悬停位置的类型、文档注释 | 解释变量/函数是什么 |
textDocument/completion | 获取当前上下文补全项 | 让 LLM 知道你正在编辑的位置能填什么 |
textDocument/definition | 找到符号定义位置 | 让 LLM 定位实现 |
textDocument/references | 找到所有引用位置 | 分析影响面、批量修改 |
textDocument/documentSymbol | 获取文件内符号树 | 让 LLM 快速理解文件结构 |
textDocument/diagnostic | 获取诊断、报错信息 | 修复 Bug 时获取真实错误原因 |
3.4 关键点:LSP 解决的核心问题
LSP 返回的不是自然语言,而是结构化数据。比如completion返回的是一个CompletionItem列表,每一项包含label、kind、detail、documentation等字段;definition返回的是Location,包含文件和行号。
这意味着我们在接 LLM 的时候,不能直接把原始 JSON 一股脑塞进 Prompt。更好的做法是把结构化结果转换成一段自然语言摘要,比如:
当前文件 src/service/user_service.py 第 42 行,可用补全项: - create_user(params: CreateUserRequest) -> User - delete_user(user_id: int) -> bool - find_user_by_email(email: str) -> User | None这样 LLM 得到的是一段语义清晰、token 友好的上下文,而不是它不擅长解析的嵌套 JSON。
4. 完整实战案例:用 LSP 给 LLM 提供代码上下文
现在进入核心环节。我们来实现一个最小闭环:启动一个极简语言服务器,客户端发送补全和悬停请求,拿到结果后交给 LLM,让 LLM 基于真实代码符号回答用户问题。这个过程虽然短,但已经跑通了“LLM 通过 LSP 感知代码库”的完整链路。
4.1 创建项目结构
先在任意目录下创建项目文件夹:
mkdir llm-lsp-demo cd llm-lsp-demo安装依赖:
pip install pygls requests4.2 编写极简语言服务器
我们用 pygls 写一个非常小巧的语言服务器。它不需要真正解析代码,而是模拟一个“用户注册相关符号”的代码库,返回一些示例补全项和悬停信息。这样能让你快速理解 LSP 交互流程,而不用陷入编译器的复杂逻辑。
文件路径:demo_lsp_server.py
import logging from pygls.lsp import LanguageServer from pygls.lsp.methods import COMPLETION, HOVER, TEXT_DOCUMENT_DID_OPEN from pygls.lsp.types import ( CompletionItem, CompletionItemKind, CompletionList, CompletionParams, Hover, HoverParams, MarkupContent, MarkupKind, Position, Range, TextDocumentItem, DidOpenTextDocumentParams ) logging.basicConfig(level=logging.INFO, format="%(levelname)s - %(message)s") server = LanguageServer("demo-lsp-server", "v0.1") # 模拟符号表 SYMBOLS = { "create_user": { "label": "create_user(user: UserCreateRequest) -> User", "detail": "创建一个新用户并写入数据库", "kind": CompletionItemKind.Function, }, "delete_user": { "label": "delete_user(user_id: int) -> bool", "detail": "根据用户 ID 删除用户", "kind": CompletionItemKind.Function, }, "get_user_by_email": { "label": "get_user_by_email(email: str) -> User | None", "detail": "通过邮箱查询用户,不存在时返回 None", "kind": CompletionItemKind.Function, }, } @server.feature(COMPLETION) def complete(ls: LanguageServer, params: CompletionParams): items = [] prefix = "" # 简单演示:从当前行文本中提取光标前最近的一个标识符 line = params.context.trigger_kind if params.context else None for name, info in SYMBOLS.items(): items.append( CompletionItem( label=info["label"], kind=info["kind"], detail=info["detail"], insert_text=name, ) ) return CompletionList(is_incomplete=False, items=items) @server.feature(HOVER) def hover(ls: LanguageServer, params: HoverParams): doc = ls.workspace.get_document(params.text_document.uri) line = doc.lines[params.position.line] if params.position.line < len(doc.lines) else "" for name, info in SYMBOLS.items(): if name in line: content = f"**{name}** \n{info['detail']}" return Hover( contents=MarkupContent(kind=MarkupKind.Markdown, value=content), range=Range( start=Position(line=params.position.line, character=0), end=Position(line=params.position.line, character=len(name)), ), ) return Hover( contents=MarkupContent(kind=MarkupKind.Markdown, value="未识别符号"), ) if __name__ == "__main__": server.start_io()这段代码做了三件事:
- 定义了一个
SYMBOLS字典,模拟项目里真实的函数签名。 - 在收到
completion请求时,返回所有模拟函数名和说明。 - 在收到
hover请求时,检查当前行是否包含模拟符号,并返回说明文档。
实际的 pygls API 可能因版本不同略有变化,但整体思路是一致的。你完全可以用 gopls、clangd、Pyright 等成熟服务器替换这个示例服务器,客户端代码不需要大改。
4.3 编写 JSON-RPC 客户端
接下来写客户端。客户端的职责是启动语言服务器子进程,按 LSP 的消息格式发请求、读响应,并封装成好用的接口。
文件路径:lsp_client.py
import json import struct import subprocess from typing import Any, Dict, Optional class LSPClient: def __init__(self, server_command: list, root_uri: str): self.server_command = server_command self.root_uri = root_uri self.process: Optional[subprocess.Popen] = None self.request_id = 0 def start(self): self.process = subprocess.Popen( self.server_command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) init_params = { "processId": None, "rootUri": self.root_uri, "capabilities": {}, } response = self.send_request("initialize", init_params) self.send_notification("initialized", {}) return response def _send_data(self, data: bytes): if not self.process or not self.process.stdin: raise RuntimeError("server not started") header = f"Content-Length: {len(data)}\r\n\r\n".encode("utf-8") self.process.stdin.write(header + data) self.process.stdin.flush() def _read_message(self) -> Optional[Dict[str, Any]]: if not self.process or not self.process.stdout: raise RuntimeError("server not started") content_length = None while True: line = self.process.stdout.readline() if not line: return None if line in (b"\r\n", b"\n", b""): break if line.lower().startswith(b"content-length:"): content_length = int(line.split(b":")[1].strip()) if content_length is None: return None payload = self.process.stdout.read(content_length) return json.loads(payload.decode("utf-8")) def send_request(self, method: str, params: Dict[str, Any]) -> Dict[str, Any]: self.request_id += 1 message = { "jsonrpc": "2.0", "id": self.request_id, "method": method, "params": params, } data = json.dumps(message, ensure_ascii=False).encode("utf-8") self._send_data(data) while True: response = self._read_message() if response is None: raise RuntimeError("connection closed early") if response.get("id") == self.request_id: return response def send_notification(self, method: str, params: Dict[str, Any]): message = {"jsonrpc": "2.0", "method": method, "params": params} data = json.dumps(message, ensure_ascii=False).encode("utf-8") self._send_data(data) def request_completion(self, uri: str, line: int, character: int) -> Dict[str, Any]: params = { "textDocument": {"uri": uri}, "position": {"line": line, "character": character}, "context": {"triggerKind": 1}, } return self.send_request("textDocument/completion", params) def request_hover(self, uri: str, line: int, character: int) -> Dict[str, Any]: params = { "textDocument": {"uri": uri}, "position": {"line": line, "character": character}, } return self.send_request("textDocument/hover", params) def stop(self): if self.process: self.process.terminate() self.process.wait()这个客户端实现了 LSP 通信的完整基础流程:启动进程、握手、发送请求、读取响应。关键点在于_read_message方法,它必须严格按Content-Length头去读取消息体。很多初学 LSP 的人会在这里踩坑,因为如果直接按行读取 JSON,遇到多行 JSON 就会解析失败。
4.4 编写 LLM Agent 入口
现在写主程序。它启动语言服务器,请求补全和悬停信息,然后组织成 Prompt 发给本地 LLM。这里使用 OpenAI 兼容接口作为示例。
文件路径:llm_agent.py
import json import requests from lsp_client import LSPClient # 配置 LLM 服务,这里以本地 Ollama 为例 LLM_BASE_URL = "http://localhost:11434/v1" LLM_MODEL = "qwen2.5-coder:7b" API_KEY = "ollama" # Ollama 本地服务通常不校验 key FILE_URI = "file:///tmp/user_service.py" def call_llm(system_prompt: str, user_prompt: str) -> str: response = requests.post( f"{LLM_BASE_URL}/chat/completions", headers={"Authorization": f"Bearer {API_KEY}"}, json={ "model": LLM_MODEL, "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}, ], "temperature": 0.2, }, timeout=30, ) data = response.json() return data["choices"][0]["message"]["content"] def format_lsp_result(completion_result: dict, hover_result: dict) -> str: lines = [] # 处理补全结果 items = [] if "result" in completion_result and completion_result["result"]: raw = completion_result["result"] if isinstance(raw, dict): items = raw.get("items", []) elif isinstance(raw, list): items = raw if items: lines.append("当前上下文中可用的函数/补全项:") for item in items: lines.append(f"- {item.get('label', '')}: {item.get('detail', '')}") else: lines.append("未获取到补全项。") hover_content = "" if "result" in hover_result and hover_result["result"]: raw = hover_result["result"] if isinstance(raw, dict): contents = raw.get("contents", "") if isinstance(contents, dict): hover_content = contents.get("value", "") else: hover_content = str(contents) lines.append("光标位置悬停信息:") lines.append(hover_content) return "\n".join(lines) def main(): client = LSPClient( server_command=["python", "demo_lsp_server.py"], root_uri="file:///tmp", ) try: client.start() print("LSP 初始化完成") completion_result = client.request_completion(FILE_URI, line=3, character=10) hover_result = client.request_hover(FILE_URI, line=5, character=5) context_text = format_lsp_result(completion_result, hover_result) print("----- LSP 返回的上下文 -----") print(context_text) system_prompt = ( "你是一个代码助手。下面给出了从语言服务器获取的真实代码上下文。" "请基于这些信息回答用户问题,不要编造当前代码库中不存在的函数。" ) user_question = "当前代码库里有哪些可用函数?帮我生成一段调用 create_user 的示例。" answer = call_llm(system_prompt, f"代码上下文:\n{context_text}\n\n用户问题:{user_question}") print("----- LLM 回答 -----") print(answer) finally: client.stop() if __name__ == "__main__": main()这里的call_llm函数使用 OpenAI 兼容接口,方便接入各种本地推理服务。如果你使用的是其他厂商的 API,只需要替换base_url、api_key和model参数即可。
4.5 运行与验证
首先在终端启动本地大模型服务,这里以 Ollama 为例:
ollama serve ollama pull qwen2.5-coder:7b然后运行主程序:
python llm_agent.py预期会看到类似这样的输出:
LSP 初始化完成 ----- LSP 返回的上下文 ----- 当前上下文中可用的函数/补全项: - create_user(user: UserCreateRequest) -> User: 创建一个新用户并写入数据库 - delete_user(user_id: int) -> bool: 根据用户 ID 删除用户 - get_user_by_email(email: str) -> User | None: 通过邮箱查询用户,不存在时返回 None 光标位置悬停信息: 未识别符号 ----- LLM 回答 ----- 当前代码库里可用的函数有 create_user、delete_user、get_user_by_email。 下面是调用 create_user 的示例: user = create_user(UserCreateRequest(name="张三", email="zhangsan@example.com"))请注意,这里的关键不在于补全列表本身,而在于:LLM 没有根据自己记忆中的 API 凭空回答,而是严格使用了语言服务器提供的符号。这就从根源上避免了幻觉 API 的问题。
4.6 结果说明
这个 Demo 最核心的链路就是:
- LLM Agent 收到用户问题。
- Agent 先向语言服务器发起 LSP 请求。
- 语言服务器基于真实代码库返回结构化符号信息。
- Agent 将符号信息转成自然语言摘要。
- LLM 基于摘要生成回答或补全建议。
如果你的语言服务器替换成 Pyright 或 gopls,那么返回的就是你项目里真实符号,整个过程就具备生产落地价值了。
5. 常见问题与排查思路
下面把实践中最常遇到的问题整理成表,每个问题都给出现象、原因和解决思路。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 语言服务器启动后客户端一直收不到响应 | 子进程启动失败,或 JSON-RPC 的 Content-Length 头解析错误 | 先单独运行python demo_lsp_server.py检查是否有报错;检查客户端_read_message的读取逻辑,确认长度读取准确 |
| init 请求返回 error code -32600 | initialize 参数格式不完整,缺少capabilities | 检查 JSON-RPC 2.0 格式,确认jsonrpc、id、method、params字段齐全 |
| 补全结果为空列表 | 语言服务器没有加载对应文件,或未声明补全能力 | 先通过textDocument/didOpen打开文件;确认服务器 capabilities 里声明了completionProvider |
| LLM 调用超时 | 本地模型推理慢,或网络连接不通 | 为请求设置超时时间,例如 30 秒;改用更小的模型;确认 LLM 服务地址可访问 |
| LSP 返回数据太大,Prompt 超长 | 较大项目里 references 或 documentSymbol 返回大量节点 | 在转发给 LLM 前做过滤和摘要,只保留符号名、文件路径、行号等核心字段 |
| LLM 仍然编造 API | 提示词没有强调“只能使用给定符号” | 在 system prompt 中增加约束,例如“如果上下文中没有该函数,直接说明不存在,不要猜测” |
| 频繁启动语言服务器导致性能差 | 每个请求都新起一个子进程 | 对语言服务器做常驻管理,做成单例服务,只建立一次会话,后续复用连接 |
排查时建议按这个顺序:先确认服务器能独立启动,再确认 JSON-RPC 消息发送正确,再确认响应能读回,最后才是调 LLM 的逻辑。大多数问题都出在前两步,而不是 LLM 本身。
6. 最佳实践与工程建议
6.1 优先使用成熟语言服务器
不要从零实现一个语言服务器。pyright、clangd、gopls、rust-analyzer、typescript-language-server这些项目经过了大量实际项目验证,解析准确率高,性能也更好。你的核心工作量应该放在“如何把 LSP 返回的结果组织给 LLM”,而不是重复造语言服务器。
6.2 对 LSP 结果做摘要和缓存
LSP 返回的原始数据往往有很多字段,直接全量塞进 Prompt 既浪费 token 又降低响应质量。建议做两层处理:
- 摘要层:从 CompletionItem、SymbolInformation 等结构中只提取 label、kind、detail、location,去掉无用元数据。
- 缓存层:同一个文件、同一个位置、同一段代码内容下的 LSP 响应可以缓存,设置 TTL(比如 30 秒)。代码内容没有变化时,不必重复请求。
6.3 给 LLM 明确指令约束
很多“模型乱编”的问题,不是模型能力不行,而是提示词没有设置边界。在系统提示词里要有类似这样的话:
当前项目符号仅以上下文提供的为准。如果待调用的函数不在列表中,请明确回复“当前上下文中没有该函数”,不要猜测或补全可能不存在的 API。这条约束能显著减少幻觉。
6.4 注意异步与超时
LSP 是请求-响应模型,LLM 请求也可能很慢。在工程落地时,不要让 Agent 主流程同步等待所有请求完成。更合理的做法是批量并行请求多个 LSP 方法,统一等待结果后合并上下文。同时给所有网络请求设置超时,避免一个慢服务拖垮整个 Agent。
6.5 权限与安全边界
语言服务器本质上是一个可以执行代码、读写文件的高权限进程。如果只在本地 IDE 场景使用,问题不大;但如果要远程部署成 Agent 服务,就必须注意:
- 不要随便启动不可信的语言服务器。
- 远程场景建议通过 SSH 隧道或带鉴权的 TCP 端口访问,不要裸暴露 0.0.0.0。
- Agent 的代码修改动作应该限定在指定工作目录,避免越权写入。
- 对 LLM 生成的代码建议继续做静态检查,确认没有明显安全风险后再落地。
6.6 引入诊断信息提升修复能力
如果做的是 Bug 修复类 Agent,建议把textDocument/diagnostic也纳入上下文。修复前先拿到真实报错,修复后再请求一次诊断,能自动验证是否修复成功。这个循环比单纯让 LLM 阅读代码更可靠。
7. 总结与下一步学习方向
这篇文章从 LSP 的诞生讲起,分析了 LLM 代码应用为什么需要 LSP,并通过一个可运行的最小 Demo 展示了完整的“LLM + LSP”链路。核心收获有四点:
- LSP 是 LLM 理解真实代码库的可靠通道,能有效减少 API 幻觉。
- LLM 负责语义理解和生成,LSP 负责符号定位和结构分析,二者的分工要清晰。
- 接入 LSP 的关键不是实现协议,而是把结构化结果转换成对 LLM 友好的上下文。
- 生产落地时要特别关注摘要、缓存、超时、权限这些工程细节。
接下来如果想继续深入,建议按这个顺序学习:
- 读 LSP 官方规范,重点看
textDocument/*系列方法请求和响应结构。 - 尝试把 Demo 里的服务器替换成 Pyright 或 gopls,用真实项目跑一遍。
- 研究 pygls 的高级用法,理解
workspace、diagnostics、settings的机制。 - 在开源 Code Agent 框架里接入 LSP,例如给 Agent 增加一个
get_symbol_context工具函数。 - 如果对性能有追求,可以研究 LSP 的增量同步、多根工作区、语义令牌等进阶能力。
大模型代码应用正在从“背题模式”走向“查阅模式”,LSP 在其中扮演的角色会越来越重要。如果你正准备给自己的 AI 编程工具补充真实代码上下文能力,不妨从今天这个最小闭环开始改造。你自己动手跑通一次,会比看十篇文章更有效果。