Project

llm-lsp

0.0
No release in over 3 years
Ruby LSP server that provides code completion using LLM FIM (Fill-In-the-Middle). Supports Ollama and other OpenAI API compatible backends.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

~> 2.0
~> 0.11
 Project Readme

llm-lsp.rb

Ruby 实现的 LLM 代码补全 LSP Server —— 让任何编辑器都能用上 AI 智能补全。

利用大模型的 FIM (Fill-In-the-Middle) 能力,通过标准 LSP 协议为编辑器提供实时代码补全。后端支持 Ollama、vLLM 等任何兼容 OpenAI Completions API 的服务。

灵感来源

本项目参考了 huggingface/llm-ls(Rust 实现的 LLM LSP 服务端)的设计思路,并在此基础上做了改进:

llm-ls (HuggingFace) llm-lsp.rb
语言 Rust Ruby
LSP 补全协议 自定义 llm-ls/getCompletions + textDocument/inlineCompletion 仅标准 textDocument/inlineCompletion
动态配置 不支持;inlineCompletion 只读全局配置,灵活配置依赖自定义协议每次请求传参 标准 didChangeConfiguration 动态切换 provider/model
请求取消 不支持 支持 $/cancelRequest,中断 HTTP 连接立即释放 GPU
流式补全 不支持(硬编码 stream: false 支持,逐 chunk 流式返回
请求防抖 客户端插件实现 服务端内置,连续请求只处理最新
补全采纳遥测 自定义协议 acceptCompletion/rejectCompletion 标准 executeCommand + 超时自动拒绝
异步架构 Tokio Async (Fiber)

llm-ls 虽然也注册了标准的 textDocument/inlineCompletion,但该实现只是 getCompletions 的简化包装——所有配置(model、backend、FIM 参数等)只能读取全局固定值,无法动态调整。要获得灵活的每次请求配置,必须走自定义协议 llm-ls/getCompletions,现有的编辑器插件(llm-vscode、llm.nvim 等)也均基于此通信。

llm-lsp.rb 则完全基于标准 LSP 协议,通过 didChangeConfiguration 实现运行时动态切换 provider 和 model,既保持了协议标准性,又提供了不亚于自定义协议的灵活度。

特点

  • 标准 LSP 协议 —— 实现 textDocument/inlineCompletion,无需自定义协议适配
  • 流式补全 —— 逐 token 返回结果,打字般的补全体验
  • 智能取消 —— $/cancelRequest 立即中断 HTTP 连接释放 GPU,不浪费算力
  • 请求防抖 —— 连续快速输入时只处理最新请求,减少无效推理
  • 异步非阻塞 —— 基于 Async Fiber 调度,补全过程不影响编辑器交互
  • 多 Provider —— 同时配置多个后端,运行时随意切换
  • Token 精确计数 —— 集成 HuggingFace Tokenizers,精确控制上下文窗口
  • 补全采纳遥测 —— 每条补全附带 inlineCompletion/accept command,编辑器采纳时回传;超时未采纳自动记录拒绝,可用于统计采纳率和模型效果评估

环境要求

  • Ruby >= 3.1(开发使用 3.4)
  • Bundler
  • Ollama 或其他兼容 OpenAI Completions API 的后端

安装

gem install llm-lsp

或在 Gemfile 中添加后 bundle install

gem "llm-lsp"

快速开始

# 启动 Ollama(如果还没有的话)
ollama serve

# 拉取一个适合补全的小模型
ollama pull qwen2.5-coder:1.5b

然后在编辑器中配置 LSP 客户端指向 llm-lsp 即可。

使用

llm-lsp [options]
# 或通过 bundle
bundle exec llm-lsp [options]

命令行参数

参数 说明
-c, --config FILE 配置文件路径(默认 ~/.config/llm-lsp/llm-lsp.yml
-m, --provider NAME 选择 provider(覆盖配置文件设定)
--verbose LEVEL 日志级别 (1=ERROR, 2=WARN, 3=INFO, 4=DEBUG)
--log FILE 日志文件路径(默认 STDERR)
-v, --version 显示版本号
-h, --help 显示帮助信息

配置文件

支持 YAML 配置文件,默认路径 ~/.config/llm-lsp/llm-lsp.yml,可通过 -c 指定其他路径。

provider: ollama

providers:
  ollama:
    model: qwen2.5-coder:1.5b
    api_base: http://localhost:11434/v1
    context_window: 2048
    tokens_to_clear:
      - "<|endoftext|>"
  openai:
    model: gpt-4
    api_base: https://api.openai.com/v1
    access_token: sk-xxxx
    context_window: 8192

优先级(从低到高):

  1. 配置文件(~/.config/llm-lsp/llm-lsp.yml
  2. -m 命令行参数(仅覆盖 provider 选择)
  3. LSP 客户端 initializationOptions(最高优先级,同名 provider 完全覆盖)

LSP 协议配置

initializationOptions

通过 LSP 客户端的 initialize 请求传入。完整结构:

{
  "provider": "ollama",              // 可选,当前使用的 provider 名称
  "providers": {                     // 可选,provider 定义(支持多个)
    "ollama": {
      "model": "qwen2.5-coder:1.5b", // 必填,模型名称
      "api_base": "http://localhost:11434/v1", // 必填,API 端点
      "access_token": "",             // 可选,API 密钥(默认空)
      "context_window": 2048,         // 可选,上下文窗口大小(默认 2048)
      "fim": {                        // 可选,FIM 特殊标记(默认 null,使用 prompt+suffix 模式)
        "prefix": "<fim_prefix>",
        "suffix": "<fim_suffix>",
        "middle": "<fim_middle>"
      },
      "tokenizer_config": {           // 可选,tokenizer 配置(默认 null,回退字符计数)
        // 三选一:
        "path": "/path/to/tokenizer.json",         // 本地文件
        "repository": "Qwen/Qwen2.5-Coder-1.5B",   // HuggingFace Hub
        "url": "https://example.com/tokenizer.json" // URL 下载
      },
      "tokens_to_clear": ["<|endoftext|>"] // 可选,从补全结果中清除的标记(默认 [])
    }
  }
}

initializationOptions 未提供 providers/provider 时,会使用配置文件中的默认值。

workspace/didChangeConfiguration

运行时动态修改配置,参数结构位于 params.settings

{
  "provider": "ollama",     // 可选,切换当前 provider
  "providers": {            // 可选,更新 provider 配置(结构同 initializationOptions.providers)
    "ollama": {
      "model": "qwen2.5-coder:7b",
      "api_base": "http://localhost:11434/v1"
      // ... 同上所有字段
    }
  }
}

编辑器集成

coc.nvim

coc-settings.json 中添加:

{
  "languageserver": {
    "llm-lsp": {
      "command": "llm-lsp",
      "args": ["--log", "/tmp/llm-lsp.log", "--verbose", "4"],
      "filetypes": ["*"],
      "initializationOptions": {
        "provider": "ollama",
        "providers": {
          "ollama": {
            "model": "qwen2.5-coder:1.5b",
            "api_base": "http://localhost:11434/v1"
          }
        }
      }
    }
  }
}

如果已在配置文件中定义了 providers,initializationOptions 可以省略 providers 部分:

{
  "languageserver": {
    "llm-lsp": {
      "command": "llm-lsp",
      "args": ["-c", "/path/to/config.yml", "--log", "/tmp/llm-lsp.log"],
      "filetypes": ["*"]
    }
  }
}

测试

项目使用 Minitest 进行集成测试,通过启动 LSP 服务器子进程并进行 JSON-RPC 通信验证。

# 运行全部测试
bundle exec rake test
bundle exec ruby test/test_lsp.rb

# 运行单个测试
bundle exec ruby test/test_lsp.rb --name test_inline_completion

注意:

  • 部分测试依赖 Ollama 在 localhost:11434 运行,不可用时自动 skip
  • Ollama 首次加载模型可能需要 30 秒以上
  • 测试日志输出到 /tmp/llm-lsp-test.log

技术栈

组件 用途
Async 异步事件驱动(Fiber 调度)
IO::Stream 配合 Async 的缓冲 IO
ruby-openai OpenAI 兼容 API 客户端
tokenizers HuggingFace Tokenizers 绑定,精确 token 计数
Minitest 集成测试

致谢

  • huggingface/llm-ls —— 本项目的主要参考,Rust 实现的 LLM LSP 服务端
  • Ollama —— 本地运行大模型的最简方案

License

MIT