一个网关,三种接法,一个能落地的 Agent:llm-api-gateway-cli 项目介绍

我为我的大模型网关做了一个CLI工具 类似deepseek harness。

开源大模型网关地址: github.com/boonya-hrgk...

注:llm-api-gateway-cli目前闭源,本篇文章就是讲述的大模型网关llm-api-gateway配套的llm-api-gateway-cli项目。

本文面向第一次接触本项目的人:它是什么、能解决什么问题、怎么在 5 分钟内跑起来、以及哪些地方还在路上。 文中所有数字与行为都来自当前仓库代码与实机运行结果,不是营销话术。 相关文档:20260910-优化计划.md(总览 + 已完成记录)、20260911-后续迭代计划.md(迭代明细与验收矩阵)。


来吧看看系统效果:

任务模式:

聊天模式:

一、它到底解决什么问题

你有一个本地跑的 LLM API Gateway(默认 http://127.0.0.1:9000),它把上游模型统一成 OpenAI / Anthropic 两种标准方言。

但你凭什么确信它真的能用? 光有管理后台的「测试连接」不够 ------ 真实开发里,模型是被这些东西调用的:

  • 一段 Python / Node 代码,用 openai SDK 指向你的网关;
  • 一个 Anthropic 生态的客户端,用 @anthropic-ai/sdk 或 Claude Code;
  • 一个要在终端里读你代码、改你文件的 Agent。

只要其中任意一条链路没验证过,问题迟早会在你最不想它出现的时候冒出来。

这个项目的定位就是把这三条链路全部走一遍,并且让第三条链路直接变成顺手的日常工具

  • cli-openai.js → 走 /v1/chat/completions,证明 OpenAI 兼容;
  • cli-anthropic.js / cli-claude-code.js → 走 /v1/messages,证明 Anthropic 兼容(后者直接让 Claude Code 把这台网关当后端);
  • cli-agent.js网关自带的原生 Agent,终端里直接干活,不需要装 Claude Code;
  • server.js / task-server.js → 一个只监听本机的 Web 服务,聊天页和任务页共用一个端口。

一句话:验证网关的四种 CLI + 一个本地 Web UI,而且它们共用同一套 Agent 内核。


二、先看结果:终端里的原生 Agent

这是项目里最实用的部分。装一次全局命令,之后在任意项目目录里直接干活:

bash 复制代码
cd llm-api-gateway-cli
npm install
npm link

cd D:\your-project
llm-api-gateway-cli -p "看一下 src 目录结构,总结这个模块在干什么"

它不需要 Claude Code,也不依赖任何第三方 Agent ------ 工具循环、SSE 解析、流式渲染、审批闸门全是仓库自己的代码,网络请求走 Node 原生 fetch

真实跑一遍(附证据)

我用一个本地假网关(进程内返回固定的工具调用)把工具循环完整跑了一次,命令与输出如下:

perl 复制代码
node cli-agent.js --base-url http://127.0.0.1:9099 --key sk-demo \
                  --model demo-model --no-session --no-color \
                  -p "读一下 package.json"
csharp 复制代码
[gateway] 原生 Agent · base=http://127.0.0.1:9099 model=demo-model
          工作目录 E:\AI\python\llm-api-gateway-cli
          工具 list_dir / read_file / search_files / glob / grep / apply_patch / write_file
          轮次上限 40 轮
⚙ 读取文件 package.json
  ✓ 读取 package.json:35 行 / 1.1 KB
工具已返回 1068 个字符,循环跑通。
[usage] 输入 256 / 输出 48 / 合计 304 tokens

同一时刻,假网关侧收到的两次请求是:

json 复制代码
[
  {"model":"demo-model","stream":true,
   "tools":["list_dir","read_file","search_files","glob","grep","apply_patch","write_file"],
   "messages":2,"lastUser":"读一下 package.json"},
  {"model":"demo-model","stream":true,
   "tools":["list_dir","read_file","search_files","glob","grep","apply_patch","write_file"],
   "messages":4,"lastUser":"读一下 package.json"}
]

这两条记录说明了很多事,而且每条都是可以直接核对的事实:

  1. 工具真的下发给了模型 ------ tools 数组里有 7 个工具,不是空壳;
  2. bash 不在里面 ------ 它默认关闭,必须显式 --allow-bash 才出现,这是刻意的安全默认值;
  3. 循环真的转了两圈 ------ 第一次 messages: 2(system + user),模型要求调用工具;工具执行后回填结果,第二次 messages: 4(多了 assistant 的工具调用与 tool 结果),模型才给出正文;
  4. 流式是真的 ------ 两次请求都带 stream: true,终端输出是逐块渲染出来的。

常用姿势

bash 复制代码
llm-api-gateway-cli -i                          # 交互模式(REPL)
llm-api-gateway-cli -i --continue               # 接着最近一次会话继续
llm-api-gateway-cli -C ./myproject -p "批量补文件头注释"
echo "列出目录并总结" | llm-api-gateway-cli       # 从标准输入读提示词
llm-api-gateway-cli -p "改名" --yes              # 自动批准所有写入(危险)
llm-api-gateway-cli -p "跑测试" --output-format stream-json   # 逐行 JSON,便于管道 / CI

交互模式里有一组斜杠命令:/help/model(切换模型且保留上下文)、/cost(累计 token 与费用粗估)、/resume/compact/reset/exit。行尾写 `` 可以续行,多行提示词不用引号包一坨。

小细节/cost 认不出的模型不会瞎猜价格 ------ 只报 token 数并说明「无内置单价」,而不是编一个美元数字出来。这个克制的判断体现在 lib/pricing.js 的注释和实现里。


三、Agent 的底子:8 个工具 + 硬沙箱

模型能用的工具一共 8 个,分只读与写入两类:

工具 作用 计划模式下可用
list_dir 列目录(带大小)
read_file 读文本全文(上限 200KB,拒绝二进制)
search_files 关键字搜索,返回 文件:行号: 内容
glob 文件名模式匹配(* / ** / ? / {a,b}
grep 正则搜索内容,可忽略大小写
apply_patch 增量编辑:查找 → 替换,一处不匹配就整体不落盘
write_file 整体覆盖写入(上限 512KB,自动建父目录)
bash 工作目录内执行 shell(默认关闭 ,需 --allow-bash

安全边界不是「提示模型别乱来」,而是机制上做不到

  • 所有路径相对工作目录 解析,../、绝对路径、Windows 盘符路径一律以「路径越界」拒绝;
  • 除了字符串层面的校验,还会对最近存在的父目录取 realpath 再校验一次 ------ 所以目录联接 / 符号链接也逃不出去(tests/tools.test.mjs 里有专门的对抗用例);
  • 越界拒绝不是「崩溃」,而是把拒绝原因作为工具结果回填给模型,模型自己就能纠正方向;
  • Web 侧的目录浏览与任务存储接口只在服务绑定本机 时开放;绑 0.0.0.0 必须显式加 --allow-remote-fs

三种审批模式,切换跟任务走

模式 行为 适合
手动(默认) 每次写入都要你点「批准」才落盘 改别人的代码、不熟的仓库
自动 写入直接执行,但仍逐条显示改了什么 自己的仓库、批量重构
计划 只给只读工具,先出方案不动文件 先看清打算怎么改,再决定放不放行

计划模式值得单独说一句:它不是「要求模型别写文件」,而是写入工具根本没提供给模型 ------ 模型手上就没有这支笔;万一它幻觉出一个写入调用,服务端还会兜一道拒绝并回填原因。

CLI 侧的审批体验也做了细节:覆盖已有文件时,预览框里会打印行级 - / + diff(lib/commands.js:diffLines,纯字符串比较,没引 diff 库)。

挂起不会白等

手动模式下等你批准的那个状态(挂起态)保留 60 分钟,并且落盘。刷新页面、关掉标签页、甚至重启服务或终端进程,回来卡片还在,点一下就能接着跑完 ------ 不用从头重跑整个任务。


四、Web 侧:聊天和任务是同一个服务

server.jstask-server.js 起的是同一个服务、同一个端口(默认 3100),只是两个入口别名,保留两个名字只为不打断已有习惯:

页面 地址 是什么
聊天 http://127.0.0.1:3100/ 纯对话:多轮、流式打字机、Markdown、模型切换、token 统计
任务 http://127.0.0.1:3100/task 选一个工作目录,让模型真的读写文件

请求链路是「浏览器 → 本机 3100 → 网关 9000」,sk- 密钥只留在服务端进程里,不下发到页面

几个体验上花了心思的点:

  • 主题三挡 :跟随系统 / 白天 / 黑夜,选过就记住。颜色全部走 CSS 变量,<head> 里内联一段定主题脚本,刷新时不会先闪一下白底;有测试专门盯着「两套主题不可能漏色」。
  • 思考模型的思维链 :网关把 reasoning_content 和正文分开,页面渲染成「思考过程 · N 字」可折叠块,思考时自动展开、正文一开始就自动收起。max_tokens 给小了、只出思维链没出正文时,页面会明确提示原因,而不是留一个空气泡。
  • 切任务不打断也不刷新 :点侧边栏另一条任务不会停掉正在跑的那条,它在后台跑完并落盘到自己的历史;切回去是立刻恢复(内存里每条任务各留一份状态)。停止按钮只停你正在看的那条。这一点有源码契约测试守着。
  • 多标签页看到同一份数据 :任务数据存在磁盘上,浏览器只留「当前打开哪条任务」这类纯 UI 状态。

任务记录存在磁盘上

一次任务可能产生十几条工具输出、每条都带文件正文,浏览器那 ~5MB 的 localStorage 根本装不下。所以任务数据落在磁盘:

平台 默认目录
Windows %LOCALAPPDATA%\llm-api-gateway-cli\tasks
macOS ~/Library/Application Support/llm-api-gateway-cli/tasks
Linux $XDG_DATA_HOME/llm-api-gateway-cli/tasks

索引(index.json,只有元信息、列表用)与数据(tasks/<id>.json)分开存:列表页不必读全部正文,索引坏了也能扫 tasks/ 重建。写入统一用「先写 .tmp 再 rename」,进程被杀不会留下半截 JSON。

自动管理规则是滑动 15 天 / 最多 20 条,超过就连数据文件一起删;会话(CLI 侧)同理,但配额是 15 天 / 50 条 / 单文件 1MB。


五、工程取向:零新增依赖 + 契约测试

这两条不是口号,是可以从 package.jsontests/ 直接核对的:

依赖面dependencies 只有 openai@anthropic-ai/sdk,而这两个只被模式一 / 模式二两个验证脚本使用 。主力的 cli-agent.js、Web 服务、Agent 内核全部走 Node 原生能力(httpfetchreadlinespawn),零新增依赖 。所以安装成本就是「克隆 + npm install + npm link」。

测试面 :15 个测试文件,分成两组,由 scripts/run-tests.mjs 聚合执行(加测试只需往数组里加一行):

bash 复制代码
npm test          # 离线:不联网、不耗 token
npm run test:live # 在线:需要网关 / 服务 / 真模型可达
npm run test:all  # 先离线再联网

离线组的 12 个文件覆盖得相当细,值得列一下它们「盯」的是什么:

测试 覆盖内容
tools.test.mjs 路径越界(../、绝对路径、盘符、多级上跳)、符号链接逃逸、写入闸门、二进制与大文件拒绝、新工具的语义
agent.test.mjs 用模拟网关跑完整工具循环:SSE 分片重组、多工具并行、参数字符串拼装、批准/拒绝/坏参数回填、轮次上限与收尾轮
runner.test.mjs 「一次 runTurn 内连续多次写入」的重复挂起与续跑、拒绝后不落盘
modes.test.mjs 计划模式拿不到写入工具、硬写被挡且不落盘、自动模式仍守沙箱、换模块实例模拟重启后仍能取回待批准
dom.test.mjs JS 引用的 DOM id 是否都存在、服务端每种事件前端是否都处理、CSS 变量是否有定义、切任务不打断的源码契约
page-runtime.test.mjs task.js 放进最小 DOM 垫片 + 假服务端真跑一遍,用「卡住的流」验证切走不中断、切回走缓存
theme.test.mjs 两套 CSS 里没有裸颜色、浅色覆盖了深色的每一个 变量、内联防闪白脚本的 key 与 theme.js 一致

这种「契约测试」的价值在改动时最明显:重构 Web 侧代码时,dom.test.mjs 会直接告诉你「你删掉了一个 HTML 里不存在的 id 引用」;改任务切换逻辑时,源码契约会拦住你偷偷加回 abort

本机实测结论(诚实版)

在当前环境(Windows + Node v22.22.0)实机运行:

  • 12 个离线文件里,11 个单独执行全部 exit=0
  • npm test 停在 tests/tools.test.mjs(聚合器遇错即停,所以它后面的 10 个文件由我单独补齐跑过);
  • tools.test.mjs 在 bash 一节的前两条断言 ------ 「未开启时即使批准也拒绝」与「开启后出现在可用清单里」------ 都打出了 ✓,挂在真正执行命令那一步,报 spawn EPERM

这不是代码缺陷:本环境限制了子进程的管道式 stdio,bash 工具拉不起子进程。换个没有这层限制的终端应当全绿;如果你也在受限沙箱里跑,可以拿这一条当参照。


六、上手:5 分钟

bash 复制代码
git clone <repo> && cd llm-api-gateway-cli
npm install

# 密钥三选一(优先级从高到低):
#   ① 命令行 --key sk-xxx
#   ② 环境变量 SK / GATEWAY_KEY
#   ③ 复制 .env.example 为 .env 并填 GATEWAY_KEY=sk-xxx

npm link        # 装成全局命令,之后在任意目录直接干活

装好后:

bash 复制代码
llm-api-gateway-cli                        # 原生 Agent 交互(当前目录即工作目录)
llm-api-gateway-cli -p "任务描述"            # 一句话任务(写入前会确认)
gateway-web                                # 本地聊天 Web UI(http://127.0.0.1:3100)
gateway-openai "你好"                       # 验证 OpenAI 兼容
gateway-anthropic "你好"                    # 验证 Anthropic 兼容
gateway-claude-code                        # 验证 Claude Code 走网关

为什么用 npm link 而不是 npm install -g . :后者把文件拷贝 进全局目录,而 .env.gitignore 忽略、不会跟着拷走,结果就是读不到密钥。npm link 是软链到源目录,改 .env 立即生效。 卸载:在项目目录执行 npm unlink

Shell 补全 也备好了,在 completions/ 下有 bash / zsh / fish 三份。


七、什么时候用哪个

你想干的事 用哪个
写代码时验证「SDK 能连上网关」 gateway-openai / gateway-anthropic
想让 Claude Code 走自己的网关 gateway-claude-code(或直接 --config 拿 JSON 贴进 CC Switch)
终端里让模型读改自己的项目 llm-api-gateway-cli(推荐)
想在浏览器里聊、看思维链、管理多会话 gateway-web
想让模型真的动文件、但每一步都要我点头 gateway-task手动模式
不熟的仓库,先要方案 gateway-task计划模式
自己的仓库批量重构 gateway-task / CLI 的自动 模式,或 --yes(慎用)

八、目前的边界(不藏着)

  1. bash 默认关闭,且即便开启也仍走审批闸门。它把安全面从「路径级」升到「命令级」,这是刻意的取舍。
  2. private: true 还没去掉,许可证仍是 UNLICENSED ------ 也就是尚未正式发布到 npm。这一条在迭代计划里标着「待拍板」,因为它属于法律与分发决策,不适合由实现方单方面决定。
  3. 交互体验刻意停在「readline + ANSI」:没有 TUI 框架、没有 diff 库。目标是把零依赖这条线守住,代价是没有花哨的界面。
  4. MCP 客户端与多模态图片输入属于观望项,未排期。
  5. 思考模型吃 token--max-tokens 给小了可能只返回思维链、没有正文。调大再试。

九、结语

这个项目从一个「网关多方言验证脚本集」长成了现在这个样子,中间的关键一步是把 Agent 内核抽出来给 Web 和 CLI 共用lib/agent.js 负责模型 ↔ 工具的循环,lib/tools.js 负责工具与沙箱,lib/runner.js 负责「建会话 → 跑一轮 → 遇写入挂起 → 等批准 → 断点续跑」这条与传输无关的流程,Web 用 SSE + HTTP 断点续跑,CLI 用 readline 问 y/N ------ 同一套 runAgent,两种传输。

抽出来之后,验证三条接入链路的成本和「顺手用起来」的成本就被摊平了:验证完的同一个程序,就是你日常在终端里干活的那个程序。

bash 复制代码
npm link
cd D:\your-project
llm-api-gateway-cli -p "把这个模块的 README 补上使用示例"

本文基于 2026-09-11 的仓库实际代码与实机运行结果撰写。文中所有命令输出、测试结论、接口行为均可按文内描述复现。

相关推荐
wordbaby1 小时前
混合检索:两全其美的艺术
人工智能·算法
Amy187021118231 小时前
数据中心电气接点测温:从“被动抢修”到“主动预警”的安全革命
人工智能·安全
jimmyleeee2 小时前
GEN AI安全:威胁全景---从训练到运行的攻防实战
人工智能·安全
阿里云大数据AI技术2 小时前
DataWorks Data Agent 实战课堂(八):数据质量巡检服务
人工智能·agent
昇腾知识体系2 小时前
昇腾 A5 ISA 指令集:文档入口与 mem_bar 等关键指令
人工智能·华为·架构·知识图谱
ltqvibe2 小时前
让AI操作业务系统,误删数据谁来拦
人工智能·数字员工管理·高危操作拦截·ai审计追责
oscar9992 小时前
Ollie:Opik 内置的 AI 助手,让 Agent 调试从“看”变成“修”
人工智能·opik·ollie
wshzd2 小时前
LLM漫谈(十一)| 5 个 开源Agent 源码剖析
人工智能