# llm-proxy **Repository Path**: HSKS/llm-proxy ## Basic Information - **Project Name**: llm-proxy - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-07 - **Last Updated**: 2026-08-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # llm-proxy 极简的 LLM API 协议代理:单二进制 + 一个 JSON 配置文件,在 **OpenAI Chat Completions ↔ Anthropic Messages** 之间**双向转换**或**纯透传**。适合放在公司内网给员工共享上游 apiKey(员工客户端只配代理地址,不接触真实 key)。 无数据库、无前端、无 OAuth。 ## 工作原理 代理同时挂两个入站端点(`/v1/messages` 与 `/v1/chat/completions`)。配置里每个块描述「某协议入站 -> 转到哪个上游」: - **顶层 key(`openai` / `anthropic`)= 入站协议**(客户端访问的端点协议) - **块内 `provider` = 上游协议(target)** - **块内 `baseUrl` / `apiKey` = 上游真实端点与 key,必须与 `provider` 对应**(provider=openai 则 baseUrl 是 openai 端点) - **入站协议 == `provider` -> 纯透传**(body 与流式字节原样转发,只换 baseUrl + apiKey) - **入站协议 != `provider` -> 协议转换**(请求体/响应按对端协议互转) | 入站端点(含前缀) | 入站协议 | 用哪个块 | 上游协议(provider) | |---|---|---|---| | `{baseUrl}/v1/chat/completions` | openai | `openai` 块 | `openai.provider` | | `{baseUrl}/v1/messages` | anthropic | `anthropic` 块 | `anthropic.provider` | ## 配置(config.json) ```json { "baseUrl": "/llm-proxy", "apiKey": "sk-你自定义的客户端口令(客户端访问代理用;缺省则不启用鉴权)", "anthropic": { "baseUrl": "https://api.openai.com/v1", "apiKey": "sk-xxx", "provider": "openai" }, "openai": { "baseUrl": "https://api.anthropic.com", "apiKey": "sk-ant-xxx", "provider": "anthropic" }, "listen": "0.0.0.0:8080" } ``` 字段: - `baseUrl`:对外路径前缀(nginx 路由用),默认 `/`。 - `apiKey`:客户端访问本代理的鉴权口令(顶层;与各块内服务商真实 key 区分)。缺省或为空则不启用入站鉴权(任意 key 可访问)。客户端可用 `Authorization: Bearer ` 或 `x-api-key: ` 之一传入。 - `openai` / `anthropic`:两块,**顶层 key = 入站协议**;块内 `provider` = 上游协议;`baseUrl`/`apiKey` 必须与 `provider` 对应(是该上游的真实端点与 key)。 - `listen`:监听地址,默认 `0.0.0.0:8080`。 ### 三种典型用法 1. **双向转换**(上方示例):`anthropic.provider=openai`(baseUrl=openai 端点)、`openai.provider=anthropic`(baseUrl=anthropic 端点) - Claude Code 打 `/v1/messages`(anthropic 入站)-> `anthropic` 块 -> provider=openai -> 转 openai 发 openai 端点 - OpenAI 客户端打 `/v1/chat/completions`(openai 入站)-> `openai` 块 -> provider=anthropic -> 转 anthropic 发 anthropic 端点 2. **纯流量代理 / 共享 key**:`openai.provider=openai`(baseUrl=openai 端点)、`anthropic.provider=anthropic`(baseUrl=anthropic 端点),入站==provider 走透传,只做 url/key 中转,隐藏真实 key。 3. **单向**:只配其中一个方向有效 provider 即可。 ## 运行 ```bash cargo build --release ./target/release/llm-proxy -c config.json ``` `-c` 可省略,默认读当前目录 `config.json`。日志级别用 `RUST_LOG=info`(或 `debug`)控制。 ## 交叉编译(Windows -> Linux) `reqwest` 的 TLS 走 `rustls`:crypto provider 为 aws-lc-rs(含 C 代码,编译需 `cmake`),根证书走 `rustls-platform-verifier`(读目标机系统证书,常规 Linux 服务器默认可用;Alpine 等极简镜像需 `apk add ca-certificates`)。 **方案:在 WSL Ubuntu 中原生编译,产出 musl 静态二进制。** > 为何不用 zig/cross:Windows 用户名含空格时,`cargo-zigbuild` 的 `.bat` 包装器在路径空格处断裂、链接失败;`cross` 0.2.5 在 Windows 主机会被 rustup 拒绝安装 Linux 工具链。WSL 原生 Linux 无此问题,是最可靠的路径。 ### 一次性环境准备(在 WSL Ubuntu 中,只需做一次) ```bash # 1) 装 rustup(走 tuna 镜像加速下载) export RUSTUP_DIST_SERVER=https://mirrors.tuna.tsinghua.edu.cn/rustup export RUSTUP_UPDATE_ROOT=https://mirrors.tuna.tsinghua.edu.cn/rustup/rustup curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal . "$HOME/.cargo/env" # 2) 加 musl target + 静态链接/C 构建工具(musl-tools 提供 musl-gcc;cmake/pkg-config 供 aws-lc-rs 编译 C) rustup target add x86_64-unknown-linux-musl sudo apt-get update && sudo apt-get install -y musl-tools cmake pkg-config # 3) 配 crates.io 国内镜像 ~/.cargo/config.toml(index + crate 全在国内) cat > ~/.cargo/config.toml <<'EOF' [source.crates-io] replace-with = "rsproxy-sparse" [source.rsproxy-sparse] registry = "sparse+https://rsproxy.cn/index/" EOF ``` > 镜像选择:勿用 tuna 的 `crates.io-index`——它只镜像索引,`dl` 仍指向官方 `static.crates.io`,crate 文件走国外 CDN,`aws-lc-sys`(~50MB)会卡死。**rsproxy** 的索引与 crate 文件都在国内。 ### 编译(WSL 中,项目根目录下) ```bash ./build-linux.sh # musl 静态(任意 Linux 可跑,推荐) # ./build-linux.sh gnu # gnu 动态(需目标机 glibc ≥ 编译机 glibc) ``` 脚本会自动把源码从 `/mnt/...`(NTFS,I/O 慢)复制到 WSL 本地 ext4 编译、保留增量,编译完把产物同步回 `target/`。 产物:`target/x86_64-unknown-linux-musl/release/llm-proxy`(完全静态的 ELF 二进制),`scp` 到任意 Linux 服务器直接 `./llm-proxy` 运行。 ### musl 静态 vs gnu 动态 | 方案 | 命令 | 产物特性 | |---|---|---| | musl 静态(默认) | `./build-linux.sh` | 完全静态,任意 Linux 可跑,部署首选 | | gnu 动态 | `./build-linux.sh gnu` | 体积略小,依赖目标机 glibc ≥ 编译机 glibc | ### 验证产物 ```bash file target/x86_64-unknown-linux-musl/release/llm-proxy # 期望:ELF 64-bit LSB pie executable, x86-64, ... static-pie linked, stripped ldd target/x86_64-unknown-linux-musl/release/llm-proxy # 期望:statically linked ``` ## 客户端配置 > 若配置了顶层 `apiKey`,下列 key 必须填**该 `apiKey` 的值**,否则代理返回 401;未配置时填任意值即可。 - **Claude Code**(走 Anthropic 入站): ``` ANTHROPIC_BASE_URL=http://你的服务器/llm-proxy ANTHROPIC_AUTH_TOKEN=你的 apiKey 值 # 推荐:以 Authorization: Bearer 发送,专为代理/网关场景设计 # 或改用 ANTHROPIC_API_KEY=你的 apiKey 值 # 以 x-api-key 发送,二者等价 ``` - **OpenAI 兼容客户端**(走 OpenAI 入站): ``` base_url=http://你的服务器/llm-proxy/v1 api_key=你的 apiKey 值 # 以 Authorization: Bearer 发送 ``` > 注:Codex CLI 默认走 OpenAI **Responses** 协议,本代理不支持;需配成 OpenAI Chat 兼容模式。 ## nginx 反代示例(SSE 友好) ```nginx location /llm-proxy/ { proxy_pass http://127.0.0.1:8080; # 不带尾部 URI,原样保留 /llm-proxy 前缀 proxy_http_version 1.1; proxy_set_header Connection ''; proxy_buffering off; # 关键:流式响应不能缓冲 proxy_read_timeout 600s; } ``` ## 架构与扩展点 ``` src/ ├── main.rs # 启动装配 ├── app.rs # AppState:共享 config / reqwest 连接池 / audit logger ├── config.rs # 配置加载、校验、路由解析(顶层 key=入站, provider=上游) ├── error.rs # 错误类型 ├── audit/ # 审计扩展点:AuditRecord + trait AuditLogger + Noop/Stderr impl ├── middleware/ # axum 中间件层:access_log / 未来 auth、限流 ├── server.rs # 路由 + handler(编排:forwarder -> 审计 -> 响应) ├── forwarder.rs # 核心转发:路由 -> 透传/转换 -> 鉴权 -> reqwest -> 装配响应 └── transform/ # 协议转换(纯函数) ├── anthropic_to_openai.rs # 请求体 + 非流响应(搬运自 cc-switch) ├── openai_to_anthropic.rs # 请求体 + 非流响应(新写,对称) ├── streaming.rs # SSE 双向(一搬一新写) └── sse.rs # SSE 切分工具(搬运) ``` 三个可插拔扩展点: - **审计**(`audit/`):`AuditRecord` + `trait AuditLogger`,当前 `StderrLogger` 输出 JSON;加 File/DB sink 只需实现 trait。 - **HTTP 横切**(`middleware/`):`access_log` 已有;未来 auth/限流加这里。 - **共享状态**(`AppState`):metrics、限流器等挂这里,经 axum `State` 注入。 ## 范围外(不做) Responses 协议、Gemini、OAuth、多路由、数据库、前端 UI、模型名映射、usage 统计聚合、故障转移/熔断。