使用 llama.cpp、Claude Code 和 Codex 搭建本地 Agent 编码环境
太阳作者太阳
原创内容采用 CC-4.0 协议发布,转载请注明出处
llama.cppClaude CodeCodexAgent本地部署GGUF开发工具

使用 llama.cpp、Claude Code 和 Codex 搭建本地 Agent 编码环境

我手头有一张 24GB 显存的 RTX 4090。平时写代码主要用 Codex,一些简单任务则交给本地模型,能少占一点订阅额度。

本文按实际操作顺序记录环境准备、模型启动和客户端接入。Qwen3.6-27B 已经能通过 llama-server 稳定运行,普通对话和编码工具调用都测试通过。

CUDA 环境准备

先安装添加 NVIDIA 软件源需要的工具:

sudo apt install dirmngr ca-certificates software-properties-common apt-transport-https curl -y

添加 Debian 12 对应的 NVIDIA GPG 公钥:

# 下载 NVIDIA GPG 公钥
curl -fSsL https://developer.download.nvidia.com/compute/cuda/repos/debian12/x86_64/3bf863cc.pub \
  | sudo gpg --dearmor \
  | sudo tee /usr/share/keyrings/nvidia-drivers.gpg > /dev/null

这台机器的旧驱动有问题,需要清理后重装。下面的命令会卸载已有的 NVIDIA 和 CUDA 软件包,驱动正常的机器不要执行:

# 清理旧环境
sudo apt-get purge -y '^nvidia-.*'
sudo apt-get purge -y '^libnvidia-.*'
sudo apt-get purge -y '^cuda-.*'
sudo apt autoremove --purge -y

apt search nvidia-driver
sudo apt install --reinstall -y nvidia-driver nvidia-driver-cuda

# 重启后确认驱动
nvidia-smi

重启后运行 nvidia-smi,确认系统已经识别 RTX 4090:

Tue Jul 21 17:35:09 2026       
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 610.43.02              KMD Version: 610.43.02     CUDA UMD Version: 13.3     |
+-----------------------------------------+------------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA GeForce RTX 4090        On  |   00000000:01:00.0 Off |                  Off |
| 30%   34C    P8             12W /  450W |       1MiB /  24564MiB |      0%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+

+-----------------------------------------------------------------------------------------+
| Processes:                                                                              |
|  GPU   GI   CI              PID   Type   Process name                        GPU Memory |
|        ID   ID                                                               Usage      |
|=========================================================================================|
|  No running processes found                                                             |
+-----------------------------------------------------------------------------------------+

配置显卡容器支持

这台机器还需要通过 Docker 使用显卡,因此安装 NVIDIA Container Toolkit:

sudo apt install nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker

如果只准备直接运行 llama.cpp,不使用 Docker,这一步可以跳过。

安装 CUDA 开发工具包

apt search cuda-toolkit
sudo apt install cuda-toolkit-13-3

# 将以下内容添加到 ~/.bashrc
echo 'export CUDA_HOME=/usr/local/cuda' >> ~/.bashrc
echo 'export PATH=$CUDA_HOME/bin:$PATH' >> ~/.bashrc
echo 'export LD_LIBRARY_PATH=$CUDA_HOME/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc

source ~/.bashrc

这里安装的是实际使用的 cuda-toolkit-13-3。环境变量生效后,可以用 nvcc --version 再确认一次。

编译 llama.cpp

git clone https://github.com/ggml-org/llama.cpp.git
cd llama.cpp

# 编译前清理旧的构建目录
rm -fr build

# CUDA 构建
cmake -B build -DGGML_CUDA=ON -DGGML_RPC=ON

# 纯 CPU 构建,二选一
# cmake -B build -DGGML_CUDA=OFF -DGGML_RPC=OFF

cmake --build build --config Release -j 32
./build/bin/llama-server --version

这里同时启用了 CUDA 和 RPC。最后一条命令能打印 llama-server 版本,就说明编译成功了。

下载模型文件

本地模型使用 GGUF 格式。我从 ModelScope 下载了已经跑通过的 Qwen3.6-27B。

Qwen3.6-27B:已跑通

modelscope download --model unsloth/Qwen3.6-27B-GGUF --include "*Q4_K_M*" "*mmproj-F16*"

这条命令只下载 Q4_K_M 量化模型和 F16 视觉投影文件,避免把仓库里的其他量化版本全部拉下来。

启动 Qwen3.6-27B

先设置本次服务使用的 API key。实际值只放在当前终端或私有配置中,不写进文章:

# 先设置实际的 llama.cpp API key,不要把 key 写进脚本或文章
export LLAMA_API_KEY='在这里填入你的实际 key'

./build/bin/llama-server --metrics --alias Qwen3.6-27B \
  --model /vol1/1000/models/modelscope/models/unsloth/Qwen3.6-27B-GGUF/Qwen3.6-27B-Q4_K_M.gguf \
  --mmproj /vol1/1000/models/modelscope/models/unsloth/Qwen3.6-27B-GGUF/mmproj-F16.gguf --reasoning-preserve  \
  --host 0.0.0.0 --port 30104 --ctx-size 262144 -ngl 100 \
  --cache-type-k q4_0 --cache-type-v q4_0 --parallel 1 \
  --batch-size 4096 --ubatch-size 1024 --flash-attn on --mlock --no-mmap --threads $(nproc)

下面这些参数针对这台 RTX 4090 机器调过,不适合原样套到所有环境:

  • --ctx-size 262144 将上下文设为 262K。
  • -ngl 100 尽量把模型层放到 GPU。
  • --cache-type-k q4_0 --cache-type-v q4_0 压缩 KV Cache,降低长上下文的显存占用。
  • --parallel 1 只保留一个并行槽位。
  • --metrics 开启指标接口,便于观察运行状态。
  • --batch-size 4096 设置逻辑批次上限。对 llama-server 来说,它限制连续批处理时每轮最多送入多少个 token;调大后可能加快长提示词的处理,也会增加内存或显存压力。
  • --ubatch-size 1024 设置一次实际计算的物理批次上限。batch-size 可以再拆成多个 ubatch 执行,因此这里保持 4096 >= 1024
  • --flash-attn on 强制启用 Flash Attention,减少注意力计算的中间数据读写,通常能降低显存占用并提高速度。
  • --reasoning-preserve 在聊天模板中保留模型的推理链输出。Qwen3.6 是带推理能力的模型,GGUF 内置的聊天模板支持此功能;不加这个参数时,Claude Code 只能看到最终结果。保留推理内容后,排查编码任务时更容易看出模型卡在哪一步。
  • --mlock 尝试把模型占用的内存锁在 RAM 中,避免被系统换出到 swap。进程需要足够的锁页权限,物理内存也必须留有余量。
  • --no-mmap 不使用内存映射加载模型,而是直接读入内存。启动通常更慢、常驻内存更明确;这里与 --mlock 一起使用,目的是减少运行中发生换页的可能。
  • --threads $(nproc) 将生成阶段的 CPU 线程数设为当前系统可见的逻辑处理器数量。模型的大部分层已经交给 GPU,但剩余 CPU 计算、采样和调度仍会使用这些线程。

模型稳定启动后再配置 Claude Code。第一次报错时不要同时改上下文、KV Cache、批大小和线程数,否则很难判断究竟是哪项参数起了作用。

纯文本编码场景

如果只通过 Claude Code 处理文本和代码,不需要向模型发送图片,可以不加载视觉投影文件。下面的命令使用 --no-mmproj 明确关闭多模态支持,同时保留与前面相同的上下文和 KV Cache 配置:

./build/bin/llama-server --metrics --alias Qwen3.6-27B \
  --model /vol1/1000/models/modelscope/models/unsloth/Qwen3.6-27B-GGUF/Qwen3.6-27B-Q4_K_M.gguf \
  --no-mmproj --reasoning-preserve  \
  --host 0.0.0.0 --port 30104 --ctx-size 262144 -ngl 100 --fit off \
  --cache-type-k q4_0 --cache-type-v q4_0 --parallel 1 \
  --batch-size 4096 --ubatch-size 1024 --flash-attn on --mlock --no-mmap --threads $(nproc)

这里保留显式的 -ngl 100,因此同时使用 --fit off 关闭显存参数自动适配,避免启动时出现自动适配无法修改 GPU 层数的警告。不加载 mmproj 后,视觉输入不可用,也不会再出现针对 Qwen-VL 图像 token 数量的提醒。

Claude Code 配置

Claude Code 的用户级配置文件是 ~/.claude/settings.jsonllama-server 使用的模型别名是 Qwen3.6-27B,这里也沿用同一个名称:

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "env": {
    "ANTHROPIC_AUTH_TOKEN": "与 LLAMA_API_KEY 相同",
    "ANTHROPIC_BASE_URL": "http://localhost:30104",
    "ANTHROPIC_MODEL": "Qwen3.6-27B",
    "ANTHROPIC_DEFAULT_HAIKU_MODEL": "Qwen3.6-27B",
    "ANTHROPIC_DEFAULT_SONNET_MODEL": "Qwen3.6-27B",
    "ANTHROPIC_DEFAULT_OPUS_MODEL": "Qwen3.6-27B",
    "ANTHROPIC_DEFAULT_FABLE_MODEL": "Qwen3.6-27B",
    "CLAUDE_CODE_SUBAGENT_MODEL": "Qwen3.6-27B",
    "API_TIMEOUT_MS": "3000000",
    "CLAUDE_CODE_AUTO_COMPACT_WINDOW": "200000",
    "CLAUDE_AUTOCOMPACT_PCT_OVERRIDE": "90"
  },
  "theme": "auto",
  "statusLine": {
    "type": "command",
    "command": "node -e \"let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{const j=JSON.parse(s),c=j.context_window||{},i=c.total_input_tokens||0,o=c.total_output_tokens||0,m=(j.model&&(j.model.display_name||j.model.id))||'model';console.log(m+' | Claude '+(i+o)+'/'+(c.context_window_size||200000)+' ('+(c.used_percentage||0)+'%) | Server 262144')})\"",
    "padding": 1
  }
}

ANTHROPIC_AUTH_TOKEN 填写 llama-server 使用的 API Key,不要加 Bearer 前缀。ANTHROPIC_BASE_URL 是服务地址,其他模型字段全部指向同一个本地模型。

statusLine 会读取 Claude Code 传入的会话 JSON。Claude Code 按 200000 token 管理这个本地模型,llama-server 的实际上下文则是 262144,所以状态栏同时显示两个数值。命令使用 Node.js,不依赖 jq

Qwen3.6-27B | Claude 16700/200000 (8.4%) | Server 262144

第一次收到模型响应后,状态栏才会出现 token 数据。CLAUDE_CODE_AUTO_COMPACT_WINDOW 把自动压缩窗口设为 200000 token,CLAUDE_AUTOCOMPACT_PCT_OVERRIDE 再把阈值设为 90。这样大约到 180000 token 时就会开始压缩,给服务端留出余量。Claude Code 会先清理旧工具输出,再摘要较早的对话。

可以随时输入 /context 查看上下文占用,或输入 /compact 手动压缩。状态栏中的 Server 262144 只是显示服务端容量,不会把 Claude Code 的自动压缩上限扩大到 262144

先测试普通对话:

claude --model Qwen3.6-27B -p --max-turns 1 --no-session-persistence "只回复 OK"

实际返回 OK。再测试 Bash 工具调用:

claude --model Qwen3.6-27B -p --max-turns 2 --no-session-persistence "使用 Bash 执行 git rev-parse --short HEAD,只回复命令输出"

实际返回 c193f661。至此,Qwen3.6-27B 的普通对话和 Bash 工具调用都已跑通。Qwen-AgentWorld-35B-A3B 的接入情况单独记在文末。

Codex 配置

Codex 桌面应用、CLI 和 IDE 扩展共用 ~/.codex/config.toml。可以在保留 ChatGPT 订阅登录的同时注册自定义模型 provider,但本地模型不能直接加入桌面端的订阅模型列表。选择自定义 provider 后,请求会发往 llama-server,不会消耗 Codex 订阅额度;切回 OpenAI provider 仍可继续使用订阅模型。

llama-server 已提供 /v1/responses,Codex 可以直接走 Responses API。最好单独创建一个 profile,免得覆盖日常使用的订阅配置:

  • Linux:~/.codex/local-qwen.config.toml
  • Windows:%USERPROFILE%\.codex\local-qwen.config.toml
model = "Qwen3.6-27B"
model_provider = "llama_cpp_qwen"
model_context_window = 262144
model_auto_compact_token_limit = 200000
model_reasoning_summary = "none"
approval_policy = "never"
sandbox_mode = "workspace-write"
web_search = "disabled"

[features]
apps = false
plugins = false
tool_search = false
standalone_web_search = false

[model_providers.llama_cpp_qwen]
name = "llama.cpp Qwen3.6-27B"
base_url = "http://localhost:30104/v1"
experimental_bearer_token = "与 LLAMA_API_KEY 相同的 key"
wire_api = "responses"
request_max_retries = 2
stream_max_retries = 5
stream_idle_timeout_ms = 3000000

[sandbox_workspace_write]
network_access = true

[tui]
status_line = ["model-with-reasoning", "context-remaining", "current-dir", "git-branch"]

experimental_bearer_token 直接填写 API Key,不要加 Bearer 前缀,Codex 会自行生成 Authorization 请求头。这个 key 会以明文保存在配置文件中,别把文件提交到仓库或同步到公共位置。

status_line 位于交互式命令行底部,显示当前模型与推理级别、剩余上下文、当前目录和 Git 分支。输入 /status 可以查看 token 使用、审批策略和可写目录,/statusline 则用于调整底部字段。

workspace-write 允许 Codex 读取项目、修改当前工作区并执行命令,network_access = true 则允许这些命令访问网络。配合 approval_policy = "never" 后,沙箱内的操作直接执行,越界操作直接失败,不会反复弹出确认框。这仍然不是完整的系统权限。

如果几乎每条命令都要求确认,通常是 profile 仍在使用 approval_policy = "on-request",或者当前会话通过 /permissions 改过权限。这里假设项目可信,因此选择 never + workspace-write。如果希望越界时由人来决定,再改回 on-request

另一种做法是保留交互式审批,把符合策略的请求交给审核代理:

approval_policy = "on-request"
approvals_reviewer = "auto_review"
sandbox_mode = "workspace-write"

自动审核不会扩大沙箱范围,高风险操作仍可能要求人工确认。只连接本地模型、又不想频繁被打断时,approval_policy = "never" 更省事。

web_search = "disabled" 用来关闭 Codex 自带的 Web Search。当前 llama.cpp 不能处理 Responses API 的 web_search 工具类型,开着只会在服务端留下跳过警告。[features] 里的四个开关还会关闭 Apps、插件、工具延迟加载和独立 Web Search,以减少发给 llama.cpp 的命名空间工具。如果这些功能有用,就不要照搬这部分配置。

base_url 只写到 /v1,Codex 会自行追加 /responses。如果 Codex 与模型服务不在同一台机器,将 localhost 换成模型服务器的可访问地址。跨公网不要直接用 HTTP,Bearer Token 会明文传输;用 HTTPS 反向代理、VPN 或 SSH 隧道。

profile 只对 CLI 生效。直接运行 codex 会使用订阅配置,运行 codex --profile local-qwen 才会连接本地模型。桌面应用没有 profile 切换入口;要在桌面端使用本地模型,需要把配置写进 config.toml,切回订阅时再移除 modelmodel_provider

启动交互式对话:

codex --profile local-qwen

也可以只对本次启动覆盖审批和沙箱设置,不修改配置文件:

codex --profile local-qwen --ask-for-approval never --sandbox workspace-write

同一条命令可以简写为 -a never -s workspace-write。配置修改后要新开会话,再输入 /status 核对审批策略、沙箱模式和可写目录。

单次执行任务用 codex exec

测试普通回复:

codex exec --profile local-qwen "只回复 OK"

在 Git 仓库中测试工具调用:

codex exec --profile local-qwen "运行 git rev-parse --short HEAD,只回复命令输出"

两项都通过,才算真正接通。

我的实际体验是,即使用自有模型,Codex 仍会读取 AGENTS.md 和原有项目上下文。执行简单命令时,和订阅模型的操作方式没有明显区别。

排查 Codex 模型目录错误

使用 Codex CLI 0.144.6 连接 llama-server 时,我遇到了三条相关日志:

failed to load models cache: missing field `supports_reasoning_summaries`
failed to decode models response: missing field `slug`
Model metadata for `Qwen3.6-27B` not found. Defaulting to fallback metadata

supports_reasoning_summaries 来自共享的 %USERPROFILE%\.codex\models_cache.json。Codex 桌面应用与独立安装的 npm CLI 共用该缓存,两者内部模型目录格式不同时,CLI 会无法解析另一端写入的缓存。client_version 不等于 npm 版本号,不需要去找不存在的版本。

slug 则是另一处格式差异。llama-server/v1/models 返回 OpenAI 兼容的 data[].id,Codex 内部模型目录却要求 models[].slug,还需要上下文、推理和工具元数据。解析失败后,Codex 会退回默认元数据。我的命令最终仍返回了 OK,说明 Responses API 推理正常,出问题的只是模型元数据加载。

models_cache.json 是 Codex 自己管理的缓存,不必手工补字段。需要清理时,完全退出桌面应用后删除文件,下次启动会自动重建:

Remove-Item -LiteralPath "$env:USERPROFILE\.codex\models_cache.json"

桌面应用运行时会再次写入缓存,所以日志也可能回来。只要命令能返回结果,就不用围着这条日志手写 JSON。

如果启动后一直显示 Reconnecting,最终也没有结果,就该检查实际的推理请求了。experimental_bearer_token 填错时,/v1/responses 会返回:

{
  "error": {
    "message": "Invalid API Key",
    "type": "authentication_error",
    "code": 401
  }
}

这个 401 错误说明 experimental_bearer_token 没有替换成实际的 LLAMA_API_KEY

排查 llama-server 工具类型警告

Codex 开始执行真实编码任务后,llama-server 可能反复输出:

W srv server_chat_: unsupported Responses tool type 'namespace' skipped
W srv server_chat_: unsupported Responses tool type 'web_search' skipped

这是工具兼容性警告,不是推理错误。日志后面若还有 processing taskprint_timingrelease,请求就已经跑完了。不过,标记为 skipped 的工具并没有传给模型。能正常聊天,不代表 Agent 工具也能调用。

问题出在两端对工具格式的理解不同。Codex CLI 0.144.6 默认认为自定义 provider 支持 namespaceweb_search,会把它们写入 Responses 请求。llama.cpp 目前只转换 type = "function",其余类型记录警告后直接跳过。这和 API Key、量化版本、上下文长度或 CUDA 参数都无关,调整 llama-server 启动参数也解决不了。

前面配置中的 web_search = "disabled" 可以去掉 web_search 警告。它必须写在 [model_providers.llama_cpp_qwen] 之前,保持为顶层配置;写在 provider 表后面会变成该表的字段。

Codex CLI 0.144.6 没有开放关闭 namespace provider 能力的配置项,namespace_tools = false 之类的写法无效。前面的完整 profile 已经关闭 Apps、插件和延迟加载工具,对应配置如下:

[features]
apps = false
plugins = false
tool_search = false
standalone_web_search = false

这些开关只能减少可选扩展产生的 namespace,未必能彻底消除警告。Apps、插件和工具搜索也会随之不可用。要保留这些工具,需要换用兼容 Codex Responses 工具格式的后端,或者在 Codex 和 llama.cpp 之间加一层双向转换代理。升级 llama.cpp 前,先看它的 Responses 适配器是否已经支持非 function 工具。

“只回复 OK”只能测试文本生成。要确认 Agent 能用,得让它实际调用一次终端:

codex exec --profile local-qwen "必须调用终端执行 git rev-parse --show-toplevel,然后原样输出命令结果"

看到真实仓库路径和工具调用记录,才能确认终端工具可用。如果模型只是生成了一个看似正确的路径,应当按工具接入失败处理。

相关实现可以直接查看 llama.cpp 的 Responses 工具转换代码Codex CLI 0.144.6 的 provider 默认能力以及 Codex 的命名空间工具筛选逻辑

检查 llama-server 接口

llama-serverLLAMA_API_KEY 环境变量读取 API Key,查询时带上同一个值:

curl -s http://localhost:30104/health
curl -s -H "Authorization: Bearer $LLAMA_API_KEY" http://localhost:30104/v1/models
curl -N -H "Authorization: Bearer $LLAMA_API_KEY" -H "Content-Type: application/json" \
  -d '{"model":"Qwen3.6-27B","input":"只回复 OK","stream":true}' \
  http://localhost:30104/v1/responses
curl -s -H "Authorization: Bearer $LLAMA_API_KEY" http://localhost:30104/metrics
curl -s -H "Authorization: Bearer $LLAMA_API_KEY" http://localhost:30104/props
curl -s -H "Authorization: Bearer $LLAMA_API_KEY" http://localhost:30104/slots

/health 用来确认服务是否在线,/v1/models 返回已加载模型。性能和运行状态分别看 /metrics/props/slots

本次检查时,/health 返回:

{"status":"ok"}

/v1/models 实际返回:

{"models":[{"name":"Qwen3.6-27B","model":"Qwen3.6-27B","modified_at":"","size":"","digest":"","type":"model","description":"","tags":[""],"capabilities":["completion","multimodal"],"parameters":"","details":{"parent_model":"","format":"gguf","family":"","families":[""],"parameter_size":"","quantization_level":""}}],"object":"list","data":[{"id":"Qwen3.6-27B","aliases":["Qwen3.6-27B"],"tags":[],"object":"model","created":1784619582,"owned_by":"llamacpp","meta":{"vocab_type":2,"n_vocab":248320,"n_ctx":262144,"n_ctx_train":262144,"n_embd":5120,"n_params":26895998464,"size":16806250496,"ftype":"Q4_K - Medium"}}]}

启动时设置了 --alias Qwen3.6-27B,所以返回的模型名称和 ID 都是 Qwen3.6-27B

/metrics 返回的主要统计如下:

llamacpp:prompt_tokens_total 70285
llamacpp:prompt_seconds_total 26.391
llamacpp:prompt_tokens_seconds 2663.22
llamacpp:tokens_predicted_total 4021
llamacpp:tokens_predicted_seconds_total 83.936
llamacpp:predicted_tokens_seconds 47.9055
llamacpp:n_decode_total 4051
llamacpp:n_tokens_max 25184
llamacpp:requests_processing 0
llamacpp:requests_deferred 0
llamacpp:n_busy_slots_per_decode 1

这次记录到的提示词处理速度约为 2663 token/s,生成速度约为 47.9 token/s。/props 显示上下文为 262144,/slots 中唯一的 slot 处于空闲。指标会随请求变化,这里只记录当时的运行状态,不当作基准测试结果。

TMUX 工具

sudo apt install tmux
tmux
tmux attach -t 0

进入 tmux 后运行 llama-server。离开时按 Ctrl+B,再按 D 分离会话,模型会继续运行。不要在会话里执行 exit,否则进程也会结束。

重新登录后用 tmux attach -t 0 回到原来的终端。