ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

终端党必备:本地大模型网关+CLI工具链实战指南

终端党必备:本地大模型网关+CLI工具链实战指南 不知道从什么时候开始我发现自己和网页版大模型对话框的对话越来越少。日常在终端里处理日志、写脚本、排查报错时切到浏览器开一个对话框把上下文粘过去等回答再把结果粘回来这套流程在终端党眼里实在太割裂了。于是我把本地大模型网关和配套的 CLI 工具链完整搭了起来终端里敲一条命令请求先到本地网关由网关做鉴权、限流、路由再分发到本地模型或远端的模型服务返回结果可以直接进管道、进文件、进脚本整个过程不需要离开终端。这篇文章把这套方案从选型、安装、配置到核心命令、常见报错、生产细节全部整理出来基于我在 Ubuntu 服务器和 macOS 上实际跑通的路径适合想用命令行统一管理本地大模型请求、又不想被某个特定厂商 SDK 绑死的开发者。内容不掺概念包装照着敲基本能通。1. 先搞清楚“本地大模型网关”和“CLI”各自解决了什么1.1 一个终端用户的真实痛点先说我当时为什么要折腾这套东西。我的工作流里有不少场景是需要“顺手”问一下大模型的比如拿到一个看不懂的报错比如想快速把一段日志归纳成摘要再比如写脚本时临时想确认某个 API 的用法。以前的做法是打开浏览器、复制、粘贴、等输出一来一回快的时候也要半分钟慢的时候上下文一长网页版自己先卡住。更麻烦的是我手头的模型源不止一个。本地 Ollama 跑着一个开源模型公司内部有一个统一的模型服务另外还要接几个商用模型的接口。每个来源都有各自的 SDK 和请求格式脚本里调这个、调那个很快变成一坨只能跑不能维护的代码。命令行工具也同样各搞各的有的工具只认自己的环境变量有的要单独登录有的连 base URL 都不让改。这种状态下“本地大模型网关 统一 CLI”就成了必然的解法。1.2 网关在整条链路里的位置本地大模型网关本质上是把“模型能力”和“调用方”拆开的中间层。调用方不直接面向具体的模型实例而是面向网关暴露的一个 OpenAI 兼容接口。这样做的价值很直接模型源怎么换、密钥怎么配、上下文怎么处理都集中在网关这一层管理调用方只需要关心“我要调用哪个模型、传什么请求”。我实际跑通的架构是这样的CLIllm / codex 等 ↓ 统一 OpenAI 兼容接口 本地大模型网关LiteLLM监听 127.0.0.1:8080 ↓ 路由分发 Ollama 本地模型 / 其他模型服务CLI 只认识一个地址、一个密钥网关负责把请求转成上游模型需要的格式。这样一来我可以在终端里随意切换模型而不需要切换工具、不需要重新配置密钥、不需要改脚本里的请求逻辑。维度直连模型源经过本地大模型网关密钥管理每个工具各配各的网关统一持有调用方只用一个 key模型切换改代码、改环境变量改路由配置CLI 无感请求日志几乎没有网关统一记录方便审计限流控制依赖上游网关层可做缓存、限流、超时格式兼容各种 SDK 不统一全部收敛为 OpenAI 兼容格式这也是为什么这篇文章的主角是“网关”而不仅仅是某个 CLI。CLI 解决的是终端操作的体验问题网关解决的是多模型源、多工具、多密钥的治理问题两边配合才是完整闭环。2. 环境搭建从宿主机到网关进程2.1 网关选型LiteLLM 还是 Ollama动手之前先选型。我发现很多人对“网关”有误解以为装一个 Ollama 就算有网关。严格来说Ollama 是一个模型运行器它自己也暴露了一个 OpenAI 兼容的/v1接口但它没有统一路由、统一鉴权、请求转发这些网关能力。如果你只跑本地模型只用一台机器、一个用户那直接把 Ollama 当服务用也够但如果你想同时接本地模型和远端模型或者想在一个入口里统一管理多个下游就需要一个真正的网关层。我当时对比了 LiteLLM、one-api 和新出的几个代理工具。实际用下来 LiteLLM 最合适原因有三个它是纯 Python 服务装起来简单pip一条命令就完事。它的上游模型列表是 YAML 配置加模型源不需要改代码。它对 OpenAI 兼容接口的支持最完整CLI 工具基本不需要特殊适配。如果你不需要多模型源只想让 CLI 连本地 Ollama也可以跳过网关层直接把 base URL 指向http://127.0.0.1:11434/v1。但那样的话密钥管理、日志、限流这些能力就都没有了后面要补很麻烦。2.2 安装与首次启动我的服务器是 Ubuntu 22.04先装 Ollama 跑本地模型。如果你用的是 macOS官方安装包或者 Homebrew 也都可以。# 安装 Ollama curl -fsSL https://ollama.com/install.sh | sh # 拉取模型并验证 ollama pull qwen2.5:7b ollama run qwen2.5:7b 你好Ollama 默认监听11434端口这个接口本身就是一个 OpenAI 兼容端点不过在这里它只负责本地模型这一路。接着装 LiteLLM 作为网关层。pip install litellm[proxy]这里建议用虚拟环境避免污染系统 Python。装完后创建一个网关配置文件config.yaml把上游模型源写进去model_list: - model_name: local-qwen litellm_params: model: ollama/qwen2.5:7b api_base: http://127.0.0.1:11434 - model_name: remote-mini litellm_params: model: openai/gpt-4o-mini api_key: ${REMOTE_API_KEY}注意model_name是暴露给 CLI 的模型别名litellm_params.model才是上游真实模型标识。对外部模型源我建议把密钥放到环境变量里不要直接写进 YAML后面讲密钥管理时会细说。启动网关litellm --config ./config.yaml --port 8080看到类似Uvicorn running on http://0.0.0.0:8080的日志就说明起来了。这里要注意LiteLLM 默认绑定的可能是0.0.0.0如果只在本机用建议用--host 127.0.0.1限制监听地址避免局域网内其他人直接访问你的网关。2.3 验证网关可用网关起来了先不要急着上 CLI先用curl手动验证一遍这样后续排查时能分清是网关的问题还是 CLI 的问题。# 查看模型列表 curl http://127.0.0.1:8080/v1/models \ -H Authorization: Bearer ${LOCAL_GW_KEY} # 发送一次聊天请求 curl http://127.0.0.1:8080/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer ${LOCAL_GW_KEY} \ -d { model: local-qwen, messages: [{role: user, content: 用一句话介绍你自己}], stream: false }如果你给网关配置了自定义密钥LiteLLM 支持--master_key参数或环境变量LITELLM_MASTER_KEY客户端请求时带上这个 key 才能真正通过鉴权。很多新手在这里踩坑CLI 那边报 401其实不是密钥写错了而是网关没有开启任何认证导致 CLI 发送的 Authorization 头被忽略或者反过来被拒绝。验证时如果返回了choices[0].message.content就说明网关到模型源的链路是通的可以放心进入下一步。3. CLI 安装与初始化把终端变成客户端3.1 安装 CLI 工具的几种方式网关就绪之后终端这头需要一个对得上话的 CLI。我首选的是llm这是一个用 Python 写的命令行工具作者是 Simon Willison支持通过插件和 OpenAI 兼容接口对接任意模型服务。选它是因为它足够轻、配置直观而且对管道处理非常友好非常适合脚本化使用。pipx install llm llm --version如果你之前没用过pipx它本质上是把 Python 命令行工具安装到独立隔离环境里避免和系统依赖冲突。macOS 上也可以用 Homebrewbrew install llm除了llm市面上还有几个能对接本地网关的 CLI像 Claude Code CLI、Codex CLI、以及一些云厂商命令行工具里的模型插件。它们各有侧重但只要是提供了“自定义 base URL”或“自定义 model provider”的能力就能接到本地网关上来。文章后面会专门讲 Codex CLI 和 Claude Code CLI 的对接差异。3.2 初始化配置llm默认把模型来源锁定在几家主流云服务上要让它把请求发到本地网关需要指定 OpenAI 兼容的 base URL。# 设置默认模型源为本地网关 llm keys set openai # 输入 http://127.0.0.1:8080/v1 对应的网关密钥这里有个容易混淆的点llm keys set openai这个命令在工具里负责配置“OpenAI 兼容源”的密钥但不代表它真的要连 OpenAI 官方服务。只要在环境变量里指定 base URL它就会把所有本该发往 OpenAI 的请求转发到本地网关。# 在 shell 配置里加入 export LLM_OPENAI_API_BASEhttp://127.0.0.1:8080/v1 export OPENAI_API_KEY你的网关密钥注意变量名。llm工具的文档里LLM_OPENAI_API_BASE和OPENAI_API_BASE在某些版本下的优先级不同实测用LLM_OPENAI_API_BASE更可靠。如果不确定当前版本支持哪个可以用llm logs show查看实际请求地址或者直接看工具帮助信息里的环境变量说明。3.3 配置多模型源在网关的 YAML 配置里我把模型别名定义为local-qwen、remote-mini等等。在llm里通过-m参数指定模型别名即可llm -m local-qwen 你好 llm -m remote-mini 写一封请假邮件如果你不想每次都敲-m可以把某个别名设为默认llm models default local-qwen这个命令的效果是以后直接执行llm 问题时默认请求会路由到local-qwen也就是本地 Ollama 上的qwen2.5:7b。需要切换模型时只改-m参数不用改任何环境变量。多模型源的实际意义在对比场景里特别明显。我经常拿同一个问题问两个模型一个本地模型、一个远端模型看看回答风格差异。以前需要打开两个网页或者写两套脚本现在就是两条命令的区别llm -m local-qwen 解释一下什么是 CAP 定理 llm -m remote-mini 解释一下什么是 CAP 定理这是因为 CLI 只面向网关而网关把路由接管的复杂度消化掉了。4. 核心命令实操对话、管道、脚本化4.1 单次对话最基本的使用方式就是直接给llm传一段文本作为 promptllm 用一句话解释什么是进程和线程的区别执行后终端会阻塞等待模型流式返回然后完整输出结果。这里的默认行为是“一次性对话”也就是说每条命令都是一个独立会话模型不会记得你之前问过什么。加-m指定模型llm -m local-qwen 写一段 Python 代码使用 asyncio 实现并发 HTTP 请求如果你只是想在终端里快速问一个问题这个命令已经够了。真正让它发挥价值的是和管道、脚本结合起来的用法。4.2 流式输出与管道管道是我用这个 CLI 最频繁的场景。把上一条命令的输出或文件内容直接喂给模型省去复制粘贴这一步。cat error.log | llm -m local-qwen 分析这段日志中的异常原因并给出排查建议这里的关键点是llm会从标准输入读取内容拼接在你的 prompt 后面发送给网关。你可以把它理解为“给模型发了一份附带上文的问题”。如果希望模型在生成时边生成边输出可以加--stream参数大多数模型服务都支持流式响应效果更接近网页版的打字机输出tail -f app.log | llm --stream -m local-qwen 实时分析最新的日志内容注意流式模式在管道场景下有个细节如果上游命令一直输出不结束CLI 会一直等待所以tail -f这种实时流要配合超时机制使用或者只处理固定行数后再进入模型请求。4.3 会话管理与上下文llm的单次对话模式没有上下文但llm chat会进入一个交互式多轮会话llm chat -m local-qwen进入后可以连续提问模型能记住前面几轮的内容适合做一些需要多步推理的任务比如“先帮我把思路列出来再基于这个思路写代码”。中间可以用/quit退出用/multiline切换多行输入。如果你想在脚本里保留多轮上下文llm也支持继续上次会话llm -c 接着上一个问题补充具体的代码实现-c参数会带上最近一次会话的历史记录。实测在本地模型上上下文长度越长响应越慢所以不要无脑把整段历史都带上可以结合模型本身的上下文窗口控制对话轮数。4.4 脚本批处理CLI 的最大优势是能被脚本直接调用。比如我想给某个目录下的每个 Markdown 文件生成一句话摘要for f in docs/*.md; do echo $f cat $f | llm -m local-qwen 用一句话概括这篇文章 done或者配合xargs做并行批处理注意控制并发量避免把本地模型的显存打满ls logs/*.log | head -5 | xargs -I {} sh -c echo --- {} ---; llm -m local-qwen 总结这个日志的告警类型 {}这种批处理方式非常灵活但有两个实际风险。第一是请求失败时脚本不会自动重试最好在命令里加|| sleep 3 ...之类的重试逻辑第二是要注意速率限制如果同时发太多请求网关和模型源都可能超时。5. 对接 Codex CLI 以及其他 AI CLI 工具5.1 Codex CLI 与本地网关的对接逻辑Codex CLI 是面向代码场景的命令行工具很多人拿它来直接改代码、跑测试、提交 PR。它的默认配置是连 OpenAI 的模型服务但它支持通过model_providers自定义模型提供方我们可以利用这个能力把请求重定向到本地大模型网关。配置位置在~/.codex/config.toml核心内容如下model local-coder model_providers [ { name local-coder, base_url http://127.0.0.1:8080/v1, wire_api chat, env_key LOCAL_GW_KEY } ]env_key指定的是存放密钥的环境变量名CLI 会在当前环境中读取它并作为Authorization头发送。设置好环境变量后执行codex时它的所有模型请求都会先到本地网关。这里要注意两个常见误区wire_api要写chat如果写成completions会走老的补全接口本地网关未必支持。model字段对应的是网关里定义的模型别名不是网关里的上游模型名弄反了会报 404。5.2 安装 Codex CLI 时最常见的报错排查很多人在安装 Codex CLI 后会遇到这个报错unable to locate the codex cli binary or required runtime components我第一次看到这个报错时也被绕了一下因为它没有直接告诉你少了哪个组件只说“找不到二进制或运行时组件”。完整的排查链路我建议按顺序走第一步确认二进制是否真的存在which codex ls -l $(which codex)如果which没有输出说明安装根本没成功优先检查 npm 安装是否报错。Codex CLI 通常通过 npm 安装npm install -g openai/codex第二步检查 PATH。npm 全局包安装目录可能不在你的PATH中npm config get prefix # 例如 /usr/local那么二进制应该在 /usr/local/bin echo $PATH如果/usr/local/bin不在PATH里就把它加上。这一步能解决大多数“显示已安装但 run 不起来”的问题。第三步检查执行权限chmod x /usr/local/bin/codex第四步确认 Node.js 版本和运行时组件。Codex CLI 依赖 Node.js 运行时某些情况下 Node 版本太旧会导致二进制能启动但缺少内部组件node -v npm -v如果版本太老先升级 Node.js 再重新安装。第五步如果上述都正常但问题依旧可能是缓存了旧的二进制路径。尝试卸载重装并清理 npm 缓存npm uninstall -g openai/codex npm cache clean --force npm install -g openai/codex这个报错反复出现时最笨但最有效的方式是在卸载后重启终端某些 shell 会话会缓存旧的命令路径重开终端能强制刷新。5.3 其他 CLI 类工具的配置差异Claude Code CLI 的配置方式和 Codex CLI 类似但它更偏向读取环境变量export ANTHROPIC_BASE_URLhttp://127.0.0.1:8080/v1 export ANTHROPIC_AUTH_TOKEN你的网关密钥设置好之后启动claude命令请求也会被转发到本地网关。如果你在用云厂商的命令行工具比如 AWS CLI 里的模型调用相关功能一般也是通过自定义 endpoint 配置实现把endpoint_url指向本地网关地址即可。总的原则只有一个判断一个 CLI 能不能接入本地网关就看它是否允许自定义 base URL 或 model provider。CLI 工具配置方式是否支持本地网关llm环境变量指定 base URL支持Codex CLIconfig.toml 的 model_providers支持Claude Code CLIANTHROPIC_BASE_URL支持云厂商 CLI自定义 endpoint取决于具体命令6. 权限、超时与密钥管理生产环境的细节6.1 密钥集中管理本地网关最大的优势之一就是把“散落各处”的密钥集中起来。但集中之后网关本身的密钥管理就成了新问题。我的做法是网关持有所有上游模型源的密钥通过环境变量或密钥管理工具注入CLI 统一使用一个“网关密钥”不直接接触任何上游密钥网关密钥级别低于上游密钥即使泄露也只会暴露网关入口不会直接泄露上游厂商的完整凭据。在服务器上把这些密钥写入.env文件并设置权限chmod 600 .env set -a source .env set a litellm --config ./config.yaml --port 8080不建议把密钥写进 YAML 文件再提交到 Git否则日志、历史记录、同事的clone都会成为泄密渠道。6.2 超时与重试CLI 接入本地网关后你可能会忽略一个实际问题上游模型源响应慢怎么办尤其是本地模型如果显卡负载高一次请求可能要几十秒。在网关层配置合理的超时很重要。参考 LiteLLM 的配置示例litellm_settings: request_timeout: 300 num_retries: 2这里的request_timeout单位是秒num_retries是上游请求失败时的重试次数。我建议把超时设置到 300 秒因为本地模型在长上下文场景下确实可能超过默认的 60 秒但重试次数不要太多否则模型实际上已经生成了一半再并发重试会加剧显存压力。CLI 侧也有相应设置比如llm可以用--timeout或者环境变量控制内部 HTTP 超时。如果网关已经配置了 300 秒CLI 的超时时间必须大于这个值否则客户端会先断开网关还在继续等待模型响应白白浪费资源。6.3 日志与监控网关和 CLI 搭配使用时日志的完整性很容易被忽略。CLI 主要在终端输出如果没有落盘时间一长什么都查不到。建议在网关层开启完整日志记录每一次请求的来源、模型、耗时、token 消耗和错误码。LiteLLM 可以配合--log参数或 json 格式日志输出指向固定目录litellm --config ./config.yaml --port 8080 --log /var/log/litellm.log这样排查问题时可以看到两类信息一是 CLI 是否真正把请求发到了网关二是网关是否成功转发到了上游模型源。我遇到过很多次用户抱怨“CLI 不工作”结果查网关日志发现根本没有请求进来问题出在 CLI 的 base URL 配置上。7. 常见踩坑与调试方法7.1 网关已启动但 CLI 无法连接这是问得最多的问题。网关明明起来了浏览器访问http://127.0.0.1:8080/v1/models也有响应但 CLI 就是报连接失败或者 404。我的排查顺序是# 1. 确认网关监听地址 ss -tlnp | grep 8080 # 2. 确认 CLI 请求的实际地址 llm logs show --json | tail -20如果监听地址是127.0.0.1而 CLI 配置的 base URL 是局域网 IP 或0.0.0.0那连接失败是必然的因为端口只对本机开放。反过来如果 CLI 请求的地址还停留在官方 API 域名说明环境变量没生效检查 shell profile 里是否正确 export 了LLM_OPENAI_API_BASE。另一个常见问题是 URI 拼接差异。有的 CLI 配置里写了http://127.0.0.1:8080/v1有的工具会在配置中的 base URL 后面自动补/v1如果你两处都补了最终请求地址会变成/v1/v1/chat/completions直接在网关日志里就能看到。7.2 返回内容乱码或截断本地模型在中文场景下偶尔会出现乱码或输出截断这通常不完全是 CLI 的问题。乱码多半是终端编码问题。SSH 到服务器时客户端和服务器字符集不一致会导致中文输出显示异常。可以先执行export PYTHONIOENCODINGutf-8 export LANGC.UTF-8再跑llm看是否恢复。截断问题则大概率是上下文窗口或max_tokens限制。本地模型一般默认最大输出 token 数有限长文生成时会在固定长度处停住。最简单的方式是在网关层或 CLI 层显式调大max_tokensllm -m local-qwen --max-tokens 4096 写一篇 2000 字的技术博客如果还是截断检查本地模型的上下文窗口总长度把系统提示词和对话历史压缩一下给生成内容留出足够空间。7.3 端口占用与多网关冲突本地模型和网关可能同时占用端口。最常见的是 Ollama 的11434和 LiteLLM 的8080被其他进程占用。查看端口占用lsof -i :8080如果有其他服务占用了最省事的办法是换一个端口启动网关litellm --config ./config.yaml --port 18080多个网关同时跑时建议每个网关对应独立的配置文件、独立的密钥前缀、独立的日志文件避免路由串线。我自己就干过这事两个网关都用了local-qwen这个别名结果 CLI 请求打到 A 网关A 网关配置又指到了 B 网关的上游模型排查了半天才定位到是别名冲突。7.4 本地模型推理速度慢到影响体验最后说一个很实际的问题本地模型吞吐量有限尤其是 CPU 推理或小显存 GPU。网关虽然能统一处理请求但不能解决模型本身的计算瓶颈。如果你的任务对速度要求高可以在网关层做一个简单的“模型分组”快模型别名指向小参数模型慢模型别名指向大参数模型CLI 侧按场景选择。我目前就是日常日志分析走local-qwen代码生成走参数更大的模型需要速度时用-m切到小模型无需改动脚本逻辑。写在最后的一点使用体会整套方案跑通之后我最深的感受是“工具链分层”带来的舒适模型路由交给网关终端交互交给 CLI密钥管理独立出来日志也集中收敛每一层只做一件事出了问题可以一层一层排查。如果只是图新鲜装一个 CLI 直连某个模型服务你永远体会不到这个状态。最后分享一个我长期在用的技巧把 base URL、网关密钥、默认模型别名这些参数写进项目级的.env文件里用 direnv 之类的工具在进入项目目录时自动加载。团队里任何人拿到仓库后只需要复制一份.env.example、填上自己的密钥所有 CLI 工具就能一致地连接到同一个本地大模型网关不用再为了“怎么连”“连哪个模型”到处问人。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进