Hydrogen
LLM 代理
自托管 LLM 代理

HydrogenLLM 代理

所有模型共用一个端点。重试、回退、编排都由它完成,客户端代码一行不用改。

Hydrogen 替你保管各家供应商的 API 密钥,每个请求由它决定交给哪家供应商的哪个模型处理。客户端只报一个模型服务的名字,从不指定真实模型。OpenAI 和 Anthropic 两种协议格式都支持,并在两者之间转换。

release v1.8.0-rc.2 MIT OpenAI + Anthropic Node ≥ 20 SQLite TypeScript
2种 API 格式,双向转换OpenAI 和 Anthropic,双向都通
8种模型类型对话 · 图像 · 视频 · 语音 · 嵌入 · 重排序
3项服务能力参数覆写 · 微代理 · 自动故障转移

工作原理

客户端从不指定真实模型,只指定一个模型服务;这个名字实际意味着什么,由 Hydrogen 按请求在你自己维护的清单里决定。

01 / 05

一个请求会经过哪几层

  1. 客户端请求model = "sonnet-any"客户端只能看到模型服务
  2. 模型服务有序步骤:先试这个,再试那个
  3. 模型你的内部名称,如 sonnet4.6
  4. 供应商Base URL + 加密的 API 密钥
  5. 上游模型 ID供应商实际使用的名称,如 claude-sonnet-4-6

四个概念,各司其职

概念是什么示例
供应商一个上游端点及其 API 密钥。openai-official、anthropic-official
模型你为一个模型起的内部名称。sonnet4.6
映射由哪家供应商服务这个模型,用什么上游 ID。sonnet4.6 → anthropic-official,上游 ID claude-sonnet-4-6
模型服务客户端请求的名称,以及背后的规则。sonnet-any

模型服务的每个步骤都绑定一个明确的(模型, 供应商)对,各有自己的重试次数、重试间隔,以及哪些故障重试、哪些故障跳下一步。供应商回退和模型回退,都只是再加一步。所有步骤都走完仍未成功,真正的上游错误会转换成客户端使用的协议格式,返回给客户端。

三个服务,三种行为

模型服务行为
sonnet-any先试 sonnet4.6 @ anthropic → 失败则回退到 gpt5.4 @ openai
sonnet-persist试 sonnet4.6 @ anthropic,每隔 1 秒重试,最多 5 次
essay 微代理draft → critique → revise,评审通过时直接返回草稿

换来的是:换供应商、加回退、限制思考预算,或者插入一整条代理流水线,都只是仪表盘上改一改。客户端代码照旧请求 sonnet-any。

功能概览

全部装在一个容器里,内置 SQLite。供应商密钥落盘即加密,客户端密钥只存哈希,整个实例可以导出成一个用口令封存的文件。

02 / 05

两种协议格式,双向转换

OpenAI Chat Completions、OpenAI Responses 和 Anthropic Messages:流式与非流式、工具调用、图片、思考块,都完整转换过去,不会半路丢掉。

八种服务类型

chat、ocr、image、video、tts、stt、embedding、rerank。非对话类型走 OpenAI 风格的直通转发,同样会跑你的步骤链。

微代理

只往前走的阶段流水线,支持条件分支和路由。每个阶段都运行一个已保存的模型服务,自动继承它的重试与回退规则。可以嵌套,环路会被校验拒绝。

参数覆写到步骤级

Temperature、top-p/top-k、max tokens、停止序列、思考等级、system 覆写,外加任意额外的 body 参数,全部钉在步骤上,与客户端无关。

可靠的流式

上游流先缓冲,中途截断按可重试失败处理,然后重放完整响应,或者干脆返回一个干净的 502,总之不会给一半。

带缓存的图像 OCR

视觉预检先把图片转成文字,给后面的阶段用;转写按图片哈希缓存,在 LRU 字节预算内复用。

带配额的 API 密钥

把密钥限定在指定服务上,设请求数和 Token 上限,设过期时间;持有人可以在公开的密钥查询页随时查看自己的状态。

可观测性

每个请求都有日志:每次尝试、载荷、延迟、Token 用量,代理的各阶段嵌套在客户端请求之下;另有实时的活动请求面板和仪表盘统计。

备份与恢复

整个实例导出成一个用口令封存的文件,能恢复到任何另一台 Hydrogen 上。恢复是单个事务:要么全部成功,要么什么都没动。

默认安全

供应商密钥用 AES-256-GCM 加密,密码用 argon2id 散列,客户端密钥用 SHA-256,供应商 Base URL 有 SSRF 防护,启动时还会自检主密钥。

两种角色,两种语言

admin 和 manager:manager 什么都能管,唯独签发 API 密钥和系统设置不行。仪表盘有中文和英文。

一个容器,一个端口

仪表盘和 API 共用一个端口,SQLite 内置在镜像里。数据库和主密钥都在 /data 里;把这一目录持久化,其余随时可以扔掉重来。

微代理

可组合的流水线:路由、评估、图片 OCR、嵌套代理,都搭在你的模型服务上。

03 / 05

模型服务把一个请求路由到一次上游调用;微代理跑的是多次模型调用(多个阶段),对客户端却仍然只是一个模型名。在外界看来,微代理就是一个模型服务,客户端不需要任何改造:把现有应用从 sonnet-any 改指 essay,它拿到的就是一条 起草 → 评审 → 修订 的流水线。

阶段从上往下依次运行。每个阶段跑完,按顺序检查它的转移条件,第一条命中的生效;都没有命中,就落到下一个阶段。跑出最后一个阶段,代理结束,返回停下的那个阶段的输出。

  • 转移只许往前。可以跳过,不能回头。没有环路,定长的流水线必然终止;向回跳的写法会被校验直接拒绝。
  • 每个阶段运行一个已保存的模型服务(或者另一个微代理),于是自动继承该服务的重试与回退规则,一次都不用额外配置。
  • 输入列表就是提示词工程。留空,阶段看到的就是原始对话;加了内容块,消息就由你自己按顺序组装。
名称(对外暴露的模型名)essay
单次尝试超时(毫秒)120000
agent: draft → critique → revise (branching)
把图片转成文字(OCR 预处理)

链条运行之前,请求里的每张图片都会先交给一个多模态/OCR 模型,替换成它的转写文本,这样纯文本的阶段模型也能处理请求。转写按图片哈希缓存。

阶段(从上往下运行;转移只能跳到后面的阶段)
阶段 1draft
运行sonnet-any
输入留空:原始消息原样通过,和客户端发来的一模一样
转移无:落到阶段 2
阶段 2critique
运行fast-cheap
输入
仅最后一条用户文本 阶段 draft 的输出 作为 assistant user 「对照请求评审上面的回答。如果已经正确完整,只回复 APPROVED,不要多说一个字。否则逐行列出具体缺陷。」
高级
system 「你是一位严格的技术编辑。简洁、具体。」 tools 只列出,不可调用 temperature 0
转移
当输出包含 APPROVED 跳到结束,返回 draft
阶段 3revise
运行sonnet-any
输入
仅最后一条用户文本 阶段 draft 的输出 作为 assistant user 「评审者提出了以下几点:」 阶段 critique 的输出 作为 user user 「重写你的回答,逐条解决。只回复重写后的回答。」
转移无:最后一个阶段,它的输出就是客户端拿到的响应
原始 JSON 工作流有效

评审说出 APPROVED,代理就停在阶段 2,返回的是草稿,也就是阶段 1 的文本,APPROVED 这个词不会出现;否则落到 revise,它是最后一个阶段,输出就是响应。两种走法,客户端看到的都是一次普通的回答;日志里每个阶段各记一次调用,嵌套在那一条客户端请求之下。

编排 01

回答之前,先互相审一遍

几个模型一起把问题想透。

一个模型起草,另一个挑它的毛病,草稿因此变得更扎实。每个答案都不止一个头脑过手,客户端看到的永远只是最终稿。

起草 · 强模型 评审 · 另一个强模型 评审说「APPROVED」→ 返回草稿 → 结束 否则往下走 ↓ 修订 · 强模型 → 结束

多花一次调用。第二个模型可以看到你的工具,但调不动它们。

编排 02

按难度付费

便宜的模型决定何时用贵的。

阶段 1 用你最便宜的模型跑,只做一件事:给请求定难度。它的判断决定走哪条路:难的活儿交给强模型,其余的直接放行给便宜的。

分诊 · 便宜模型 输出包含「HARD」→ 强模型 → 结束 否则往下走 ↓ 作答 · 便宜模型 → 结束

打分只花一次便宜调用。改用正则分流,一个 router 阶段一分钱都不花。

调用

任何 OpenAI SDK 指向 /v1,任何 Anthropic SDK 指向根路径,把 model 设为模型服务的名字。客户端的格式和供应商的格式互不相关。

04 / 05

端点

OpenAI Base URL
http://localhost:8080/v1
Chat Completions 和 Responses 都挂在这个 base 下。model 填模型服务名。
Anthropic Base URL
http://localhost:8080
Messages 路径由 SDK 自动拼接。model 填模型服务名。
OpenAI 协议格式
curl http://localhost:8080/v1/chat/completions \
  -H "Authorization: Bearer sk-hproxy-..." \
  -H "content-type: application/json" \
  -d '{"model":"sonnet-any","messages":[{"role":"user","content":"hello"}]}'
Anthropic 协议格式:同一个服务,同样的上游
curl http://localhost:8080/v1/messages \
  -H "x-api-key: sk-hproxy-..." \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{"model":"sonnet-any","max_tokens":256,"messages":[{"role":"user","content":"hello"}]}'

面向客户端的路由

方法路径类别说明
POST/v1/chat/completionschatOpenAI Chat Completions,流式 + 非流式
POST/v1/responseschatOpenAI Responses API
POST/v1/messageschatAnthropic Messages
GET/v1/models—你的模型服务(发送 anthropic-version 时返回 Anthropic 形态)
POST/v1/embeddingsembedding兼容 OpenAI 的供应商
POST/v1/rerankrerank
POST/v1/images/generationsimage
POST/v1/videosvideo返回自带路由后缀的任务 ID
GET/v1/videos/:id · /v1/videos/:id/contentvideo轮询与下载,无状态路由
POST/v1/audio/speechtts二进制音频直接流过
POST/v1/audio/transcriptionssttmultipart,改写 model 后转发

/v1/models 返回的是模型服务,所以任何带模型选择器的工具都会显示你的服务名。这正是设计意图:在客户端眼里,sonnet-any 就是模型。认证用 Authorization: Bearer … 或 x-api-key: …。

部署

三条路径,按投入从小到大。无论走哪条,先记住一条规则。

05 / 05

/data 必须持久化。里面是 SQLite 数据库,还有 hydrogen-secrets.json:解密供应商 API 密钥的主密钥就在这个文件里。丢了它,Hydrogen 宁可拒绝启动,也不会带着读不出的密钥硬跑。

1

雨云应用商店

一键部署,不用自己跑服务器。托管,带持久卷和 HTTPS。

Hydrogen 已上架为雨云云应用。在商店里搜索 Hydrogen,保留模板自带的 /data 卷,主密钥和会话密钥留空,让 Hydrogen 自己生成并持久化。

2

容器镜像

任何 VPS 或家用服务器。从 GHCR 拉取;正式使用请固定一个标签。
docker run -d --name hydrogen \
  -p 8080:8080 \
  -v hydrogen-data:/data \
  ghcr.io/arrosam/hydrogen-llm-proxy:v1.7.3

接着 docker logs hydrogen,初始管理员凭据就打印在启动横幅里;然后放开 8080 端口。deploy/vps/ 里有现成的 compose 栈,附带 Caddy TLS 配置。

3

从源码构建

本地开发,或者自己打补丁的构建。Node 20+。
git clone https://github.com/Arrosam/Hydrogen-LLM-proxy.git
cd Hydrogen-LLM-proxy
cp .env.example .env
docker compose up -d --build

仓库根目录的 compose 从工作树构建,不走拉取。不用 Docker 的话:npm install、npm run build,设好 DATA_DIR 再启动服务器。

默认安全 AES-256-GCM 加密供应商密钥 argon2id 散列密码 SHA-256 客户端密钥,只显示一次 Base URL 的 SSRF 防护 启动时自检主密钥
已复制到剪贴板