Helix结合llm-ls实现AI辅助编程

llm-ls

Hugging Face 使用 Rust 开发了一个 huggingface/llm-ls ,提供基于LLM来实现的LSP服务器,负责将编辑器的补全请求发送给 ollama 等后端。

llm-ls提供的功能:

  • Prompt(提示): 使用当前文件作为上下文生成提示,根据需求可以选择是否使用"fill in the middle" (中间填充)

  • Telemetry(遥测): 收集有关请求和补全的信息,以便进行重新训练(llm-ls除了查询模型API时设置用户代理外,所有数据都存储在日志文件 ~/.cache/llm_ls/llm-ls.log 中,所以不会有数据泄露隐患。注意,日志级别设置为info)

  • Completion(补全): llm-ls解析代码的抽象语法树(AST),以确定补全应该死多行、单行还是空(不补全)

  • 多种后端:

    • Hugging Face's Inference API

    • Hugging Face's text-generation-inference

    • ollama

    • OpenAI compatible APIs (如 Python Bindings for llama.cpp 提供模拟OpenAI API的web服务器`)

服务端

运行qwen3-coder
docker exec -it ollama ollama run qwen3-coder-30b-a3b-instruct-q4_k_m

安装llm-ls

llm-ls官方GitHub只提供了 Windows / LinuxmacOS 的二进制程序,由于我目前使用 FreeBSD 作为桌面,所以我需要自己编译安装 llm-ls

备注

当在Helix中同时为一个语言(如Python)指定了 pyrightllm-ls 时,这两种LSP是同时运行的: Helix作为客户端,会同时向 pyrightllm-ls 发送请求,并实时合并它们的结果:

  • pyright 会根据源代码、类型定义、标准库文档来提供 语法纠错、变量转跳、函数名提示 ,所给出的建议是 ""绝对正确的** (例如确定有这个方法)

  • llm-ls 是生成式AI,所以根据上下文"猜"下一步写什么,所以不仅能补全变量名,还能补全整行代码甚至一段逻辑,但是需要注意llm-ls给出的建议仅是 看起来概率最高的 (不一定准确,甚至有幻觉)

FreeBSD安装llm-ls

  • 在FreeBSD上安装 Rust :

在FreeBSD上安装rust
sudo pkg install rust
  • 下载源代码进行编译:

编译llm-ls
# 1. 克隆仓库
git clone https://github.com/huggingface/llm-ls.git
cd llm-ls

# 2. 编译(使用 Release 模式以获得最高性能)
# 如果你想针对你的 CPU 优化,可以添加 RUSTFLAGS
RUSTFLAGS="-C target-cpu=native" cargo build --release

编译生成的二进制文件是 target/release/llm-ls ,将这个文件复制到 /usr/local/bin/ 目录下

配置helix

排查

我在编辑 go 程序,发现没有出现 llm-ls 提示内容

由于 /usr/local/bin/llm-ls --help 显示只支持有限的参数:

llm-ls支持的参数`
Usage: llm-ls [OPTIONS]

Options:
      --port <SOCKET>  Wether to use a tcp socket for data transfer
  -s, --stdio          Wether to use stdio transport for data transfer, ignored because it is the default behaviour
  -h, --help           Print help
  -V, --version        Print version

所以尝试便携一个 /tmp/debug_llm.sh 脚本:

调试脚本 /tmp/debug_llm.sh
#!/bin/sh
# 显式导出路径
export PATH=$PATH:/usr/local/bin:/home/admin/go/bin
# 将 llm-ls 的 stderr 捕获到文件
/usr/local/bin/llm-ls "$@" 2> /tmp/llm-ls.err

设置脚本可执行 chmod +x /tmp/debug_llm.sh

然后修订 ~/.config/helix/languages.toml 如下:

debug方式的 languages.toml
[language-server.llm-ls]
command = "/tmp/debug_llm.sh"

其他配置内容不变

此时helix报错也可以通过 cat /tmp/llm-ls.err 获得 llm-ls 崩溃前的最后输出

再次编辑 go 程序,我发现 ~/.cache/helix/helix.log 显示 llm-ls 确实初始化失败:

helix.log 显示 llm-ls 服务器初始化失败
2026-03-24T16:18:19.960 helix_lsp::transport [ERROR] llm-ls err: <- StreamClosed
2026-03-24T16:18:19.960 helix_lsp [ERROR] failed to initialize language server: server closed the stream

/tmp/llm-ls.err 居然提示是参数错误:

显示llm-ls因为参数错误退出
error: unexpected argument '--log-file' found

Usage: llm-ls [OPTIONS]

For more information, try '--help'.

不过上述报错实际上是我配置文件问题,改正之后还是没有解决helix访问llm-ls问题

gemini提供了一个debug建议,采用FreeBSD的 truss :

使用 truss 跟踪 llm-ls 系统调用
truss /usr/local/bin/llm-ls --stdio

此时输入一些字符回车,程序会退出并打印出最后的系统调用(来查看问题所在)

使用 truss 跟踪 llm-ls 系统调用
...
_umtx_op(0x22d9151e62c0,UMTX_OP_RW_UNLOCK,0x0,0x0,0x0) = 0 (0x0)
_umtx_op(0x22d9151e62c0,UMTX_OP_RW_WRLOCK,0x0,0x0,0x0) = 0 (0x0)
_umtx_op(0x22d9151e62c0,UMTX_OP_RW_UNLOCK,0x0,0x0,0x0) = 0 (0x0)
_umtx_op(0x22d9151e62c0,UMTX_OP_RW_WRLOCK,0x0,0x0,0x0) = 0 (0x0)
_umtx_op(0x22d9151e62c0,UMTX_OP_RW_UNLOCK,0x0,0x0,0x0) = 0 (0x0)
_umtx_op(0x22d9151e62c0,UMTX_OP_RW_WRLOCK,0x0,0x0,0x0) = 0 (0x0)
_umtx_op(0x22d9151e6240,UMTX_OP_RW_WRLOCK,0x0,0x0,0x0) = 0 (0x0)
madvise(0x4e83d5a00000,12288,MADV_FREE)		 = 0 (0x0)
_umtx_op(0x22d9151e62c0,UMTX_OP_RW_WRLOCK,0x0,0x0,0x0) = 0 (0x0)
madvise(0x4e83d6000000,12288,MADV_FREE)		 = 0 (0x0)
_umtx_op(0x22d9151e6240,UMTX_OP_RW_UNLOCK,0x0,0x0,0x0) = 0 (0x0)
_umtx_op(0x22d9151e6240,UMTX_OP_RW_WRLOCK,0x0,0x0,0x0) = 0 (0x0)
<thread 164006 exited>
<thread 164007 exited>
_umtx_op(0x22d9151e62c0,UMTX_OP_RW_UNLOCK,0x0,0x0,0x0) = 0 (0x0)
madvise(0x4e83d6600000,12288,MADV_FREE)		 = 0 (0x0)
<thread 164009 exited>
madvise(0x4e83d5800000,8192,MADV_FREE)		 = 0 (0x0)
madvise(0x4e83d5804000,16384,MADV_FREE)		 = 0 (0x0)
<thread 164005 exited>
_umtx_op(0x4e83d4866810,UMTX_OP_WAIT,0x280a5,0x0,0x0) = 0 (0x0)
madvise(0x4e83d6400000,12288,MADV_FREE)		 = 0 (0x0)
<thread 164008 exited>
_umtx_op(0x4e83d486b010,UMTX_OP_WAIT,0x280a8,0x0,0x0) = 0 (0x0)
sigaltstack(0x22d91256b6b8,0x0)			 = 0 (0x0)
munmap(0x4e83d4887000,38912)			 = 0 (0x0)
_exit(0x0)					
process exit, rval = 0

日志中有密集的 _umtx_op(..., UMTX_OP_RW_WRLOCK, ...)UNLOCK ,显示 llm-ls 的一部运行时(Tokio)在FreeBSD 15的Jail环境下,尝试获取读写锁时候陷入某种争用或异常。

gemini建议我为Jail设置 allow.sysvipc ,所以我尝试修改 /etc/jail.conf 添加:

设置 allow.sysvipc
...
# PERMISSIONS
allow.sysvipc;
allow.raw_sockets;
exec.clean;
mount.devfs;
...

我尝试设置后重启jail(实际上我是重启了操作系统),但是依然没有解决llm-ls向 ollama 发送请求的问题(实际上根本没有发送请求)

此外,gemini还有一个建议是尝试限制 Rust 程序的多线程,尝试用一个现成来避免死锁。也就是修订 ~/.config/helix/languages.toml :

限制rust程序线程数量
[language-server.llm-ls]
command = "env"
# 强制 Rust 运行时只使用 1 个线程,避开复杂的线程锁争用
args = ["RAYON_NUM_THREADS=1", "TOKIO_WORKER_THREADS=1", "/usr/local/bin/llm-ls", "--stdio"]

[language-server.llm-ls.config]
# 之前的配置保持不变...
url = "http://192.168.6.200:11434/api/generate"
model = "qwen3-coder-30b-a3b-instruct-q4_k_m:latest"

但是我实践发现并没有解决问题

备注

遗留问题

目前我没有解决 FreeBSD 通过 llm-ls 调用Ollsms来实现AI辅助编程,问题出在FreeBSD的客户端 llm-ls 没有能够发起调用。

gemini推测可能是rust线程在FreeBSD上兼容的问题,我暂时没有时间继续排查。我准备先搞通Linux平台的AI辅助编程,至少 llm-ls 官方是支持Linux平台的,能够排除客户端不兼容的可能。

待续...`

参考