Failover Proxy 中文 | English
Failover Proxy 是一个 OpenAI 兼容的大模型故障转移代理,内置可视化管理界面。它可以暴露自定义 API Key 和 OpenAI 风格接口,并把一个或多个公开模型名按策略路由到多个上游模型,适合低延迟代理、故障转移和容器化部署场景。
- OpenAI 兼容接口:
GET /v1/models和GET /modelsPOST /v1/chat/completions和POST /chat/completionsPOST /v1/responses、POST /responses、POST /v1/response和POST /response- Responses 资源:
GET/DELETE /v1/responses/{id}、POST /v1/responses/{id}/cancel、GET /v1/responses/{id}/input_items、POST /v1/responses/compact和POST /v1/responses/input_tokens POST /v1/completions和POST /completions
- 自定义代理 API Key,客户端通过
Authorization: Bearer ...调用。 - Admin Token 登录与 Session 管理,支持
/api/login、/api/logout、/api/session。 - 多模型故障转移链,每个公开模型可配置有序 targets。
- Chat Completions 与 Responses 双向适配:自动按目标端点探测并转换请求、普通响应和 SSE 流;覆盖工具调用历史、文本格式、推理片段、结束原因、用量和常见图像/音频/文件/视频内容。已知的原生 Responses ID 保持原样并固定回创建它的上游;仅 Chat 转换响应的已知历史会展开后续轮次。
- Responses 资源路由自动跟随创建响应的上游。上游不支持检索或未保存响应时,
store未设为false的响应会从本地快照回退;store:false不提供本地检索。该短期索引驻留内存,12 小时过期,最多 256 项/32 MiB,服务重启后清空。普通请求仍按既有负载均衡策略选路;只有携带已知previous_response_id的续轮优先回到原目标。 - Chat Completions 与 Responses 自动双向路由:普通请求和 Chat 函数工具请求优先走对应原生端点,端点不受支持时自动尝试另一协议;Responses 函数工具请求自动优先尝试 Chat Completions,以兼容只在 Chat 接口实现工具调用的上游。Responses 有状态字段及 Chat 多候选等无法安全转换的请求保留原生路由,无需单独配置。
- 支持上游字段:
name、baseUrl、apiKey、modelName、enabled、priority、weight、maxRetries、timeoutMs等。 - 故障转移策略:
priority、round-robin、weighted、latency-based。 - 模型源模式:从自定义
/v1/modelsURL 拉取模型,支持 include/exclude 过滤、publicPrefix/publicSuffix 和{model}模板。 - 流式 SSE/chunked 原样转发或逐事件跨协议转换;早期分块会探测上游错误。一旦开始向客户端输出字节,中途错误只记录,不再切换上游。
- 熔断器:连续失败阈值、冷却时间、立即冷却状态码,例如
429。 - 被动故障检测:真实代理请求触发转移和熔断,管理界面健康检查只更新在线状态与延迟。
- 代理请求不再限制每个模型的并发线程数;请求或流结束后实时线程记录立即释放。
- 统计与日志:总请求、成功、失败、转移次数,按模型和 target 的细粒度统计,请求日志,实时线程,进程内存占用。
- 配置持久化:
data/config.json原子写入;请求日志、模型统计和运行统计分别保存到 CSV。旧data/stats.json仅用于兼容迁移,不会被修改。 - 管理界面以内嵌多文件前端形式随服务二进制发布,无需额外前端运行时。
- CORS、请求超时、优雅关闭和结构化日志。
Windows 一键启动:
.\start.ps1也可以双击:
start.bat
Linux/macOS:
chmod +x ./start.sh
./start.sh直接使用 Cargo:
cargo run默认配置:
- 管理后台:
http://127.0.0.1:8787 - 监听地址:
0.0.0.0:8787 - Admin Token:
admin - 代理 Key:
sk-local-test - 配置文件:
data/config.json - 请求日志:
data/request-logs.csv - 模型统计:
data/model-stats.csv - 运行统计:
data/runtime-stats.csv data/stats.json仅在首次迁移时读取,之后不再使用或写入
首次运行时,如果 data/config.json 不存在,程序会自动创建默认配置。
常用环境变量:
HOST=0.0.0.0
PORT=8787
DATA_DIR=./data
CONFIG_PATH=./data/config.json
STATS_PATH=./data/stats.json # 仅用于兼容迁移,不会写入
REQUEST_LOGS_PATH=./data/request-logs.csv
MODEL_STATS_PATH=./data/model-stats.csv
RUNTIME_STATS_PATH=./data/runtime-stats.csv
STREAM_FAILURE_PROBE_KB=64
RUST_LOG=failover_proxy=info,tower_http=info仓库中的 ui/ 是前端源码,assets/ 是 Rust 服务启动时嵌入的静态资源产物。修改前端后执行:
npm ci
npm --prefix ui ci
npm run build:uinpm run build:ui 会构建 React UI,并把产物复制到 assets/index.html、assets/app.css、assets/app.js 和 assets/app-core.js。
cargo build --release启用可选分配器:
cargo build --release --features mimallocRelease profile 已启用 opt-level=3、LTO、单 codegen unit、符号裁剪和 panic=abort。构建完成后可直接分发:
target/release/failover-proxy
target/release/failover-proxy.exe
GitHub Actions 的 Build Executables 工作流会生成以下二进制产物:
failover-proxy-windows-amd64.exefailover-proxy-windows-arm64.exefailover-proxy-linux-amd64failover-proxy-linux-arm64failover-proxy-macos-amd64failover-proxy-macos-arm64
发布 Release 时,Docker Image 工作流还会构建并推送多架构 Docker 镜像到 GHCR。
本地构建镜像:
docker build -t failover-proxy .运行:
docker run --rm -p 8787:8787 -v failover-proxy-data:/app/data failover-proxy使用 Compose:
cd deploy/compose
cp .env.example .env
# 发布 Release 后把 FAILOVER_PROXY_VERSION 改成 v0.1.0 等版本号
# 如需自定义宿主机端口,把 FAILOVER_PROXY_PORT 改成 18080 等端口
docker compose up -d可通过环境变量覆盖:
FAILOVER_PROXY_IMAGE=ghcr.io/clockclock1/failover-proxy
FAILOVER_PROXY_PORT=8787
FAILOVER_PROXY_DATA_DIR=./data
FAILOVER_PROXY_VERSION=latest
FAILOVER_PROXY_NETWORK=failover-proxy-network
RUST_LOG=failover_proxy=info,tower_http=infoKubernetes 配置位于 deploy/kubernetes/:
kubectl apply -f deploy/kubernetes/namespace.yaml
kubectl apply -f deploy/kubernetes/pvc.yaml
kubectl apply -f deploy/kubernetes/deployment.yaml
kubectl apply -f deploy/kubernetes/service.yaml默认镜像:
ghcr.io/clockclock1/failover-proxy:latest
默认端口:
8787
配置文件示例位于 data/config.example.json。最小模型配置如下:
{
"adminToken": "admin",
"proxyKeys": [
{
"name": "test-key",
"key": "sk-local-test",
"enabled": true
}
],
"models": [
{
"publicName": "gpt-failover",
"enabled": true,
"targets": [
{
"name": "primary-openai",
"baseUrl": "https://api.openai.com/v1",
"apiKey": "sk-replace-me",
"modelName": "gpt-4.1-mini",
"enabled": true
}
]
}
]
}请求示例:
curl http://127.0.0.1:8787/v1/chat/completions \
-H "Authorization: Bearer sk-local-test" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-failover",
"messages": [{"role": "user", "content": "Hello"}],
"stream": false
}'流式请求:
curl -N http://127.0.0.1:8787/v1/chat/completions \
-H "Authorization: Bearer sk-local-test" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-failover",
"messages": [{"role": "user", "content": "Say hello slowly"}],
"stream": true
}'建议先使用本地 OpenAI 兼容 mock 上游,再用相同配置进行压测:
oha -z 60s -c 128 -m POST http://127.0.0.1:8787/v1/chat/completions \
-H "Authorization: Bearer sk-local-test" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-failover","messages":[{"role":"user","content":"ping"}],"stream":false}'建议观察:
- QPS 和 p50/p95/p99 延迟:
oha、wrk、bombardier。 - 内存:Windows 任务管理器、
Get-Process failover-proxy、Linuxpidstat -r。 - 连接复用:上游访问日志,短时间调试可打开
RUST_LOG=reqwest=debug。 - 故障转移:让主上游返回 429、500 或超时,检查
/api/stats和x-proxy-target。 - 流式转发:使用
curl -N或流式压测工具,同时观察 CPU 和实时状态。
Failover Proxy is an OpenAI-compatible LLM failover proxy with a visual management UI. It exposes custom API keys and OpenAI-style endpoints while routing one or many public model names through configurable upstream failover targets, making it suitable for low-latency proxying, failover, and containerized deployments.
- OpenAI-compatible endpoints:
GET /v1/modelsandGET /modelsPOST /v1/chat/completionsandPOST /chat/completionsPOST /v1/responses,POST /responses,POST /v1/response, andPOST /response- Responses resources:
GET/DELETE /v1/responses/{id},POST /v1/responses/{id}/cancel,GET /v1/responses/{id}/input_items,POST /v1/responses/compact, andPOST /v1/responses/input_tokens POST /v1/completionsandPOST /completions
- Bidirectional Chat Completions / Responses adaptation: endpoint detection converts requests, non-streaming responses, and SSE streams, including tool history, text formats, reasoning, finish reasons, usage, and common image/audio/file/video content. Known native Responses IDs are preserved and pinned to their creating upstream; known Chat-converted histories are expanded for later turns.
- Responses resource routes follow the upstream that created each response. If that upstream cannot retrieve or did not store a response, a local snapshot is used unless the request set
store:false. The in-memory index expires after 12 hours and is capped at 256 entries/32 MiB; it is cleared on restart. Normal requests keep the existing load-balancing policy; only continuations with a knownprevious_response_idprefer their original target. - Automatic bidirectional Chat Completions / Responses routing: ordinary requests and Chat function-tool requests use their native endpoint first and try the other protocol when unsupported. Responses function-tool requests automatically prefer Chat Completions to support upstreams that implement tool calling only on the Chat endpoint. Stateful Responses fields and Chat multi-choice requests that cannot be safely converted stay on their native route; no separate setting is required.
- Custom proxy API keys via
Authorization: Bearer .... - Admin token login and session management with
/api/login,/api/logout, and/api/session. - Multiple model failover chains. Each public model can define ordered upstream targets.
- Each proxy model exposes its configurable
contextWindowTokens(default 1,000,000) through/v1/models, and inbound requests are checked against that model's window. Target fields includename,baseUrl,apiKey,modelName,enabled,priority,weight,maxRetries, andtimeoutMs. Failover Proxy sends the original request first. Only when an upstream returns a context-length error with HTTP 422 does it lossily compact the request context and retry that same upstream until it succeeds or the payload cannot be reduced further (with a 32-round loop-safety guard); unrelated 422 responses keep their normal error behavior. - Failover strategies:
priority,round-robin,weighted,latency-based. - Model-source mode: fetch models from a custom
/v1/modelsURL, apply include/exclude filters, add public prefixes/suffixes, expand{model}templates, and set a sharedcontextWindowTokensfor the generated proxy models. - Streaming SSE/chunked pass-through or per-event protocol translation with early upstream error probing. After bytes are written to the client, mid-stream errors are recorded but not failed over.
- Circuit breaker with failure threshold, cooldown duration, and immediate cooldown status codes such as
429. - Passive failure detection: real proxy requests trigger failover and circuit breaking; UI health checks only update observed provider status and latency.
- Proxy requests are no longer capped by per-model thread slots. Active thread records are released immediately when the request or stream finishes.
- Stats and logs: total requests, successes, failures, failovers, per-model and per-target stats, request logs, live threads, and process memory.
- Config persistence:
data/config.jsonis saved atomically; request logs, model stats, and runtime stats are stored as CSV files. Legacydata/stats.jsonis read only for compatibility migration and is never modified. - Embedded multi-file admin UI served from the service binary. No extra frontend runtime is required.
- CORS, request timeout, graceful shutdown, and structured logging.
Windows one-click start:
.\start.ps1Or double-click:
start.bat
Linux/macOS:
chmod +x ./start.sh
./start.shDirect Cargo start:
cargo runDefaults:
- Admin UI:
http://127.0.0.1:8787 - Bind address:
0.0.0.0:8787 - Admin Token:
admin - Proxy Key:
sk-local-test - Config:
data/config.json - Request logs:
data/request-logs.csv - Model stats:
data/model-stats.csv - Runtime stats:
data/runtime-stats.csv data/stats.jsonis read only for first-run migration and is never written or removed
On first run, the server creates data/config.json automatically if it does not exist.
Common environment variables:
HOST=0.0.0.0
PORT=8787
DATA_DIR=./data
CONFIG_PATH=./data/config.json
STATS_PATH=./data/stats.json # legacy migration input only; never written
REQUEST_LOGS_PATH=./data/request-logs.csv
MODEL_STATS_PATH=./data/model-stats.csv
RUNTIME_STATS_PATH=./data/runtime-stats.csv
STREAM_FAILURE_PROBE_KB=64
RUST_LOG=failover_proxy=info,tower_http=infoThe ui/ directory contains the frontend source. The assets/ directory contains static assets embedded by the Rust service. After changing the frontend, run:
npm ci
npm --prefix ui ci
npm run build:uinpm run build:ui builds the React UI and copies the output to assets/index.html, assets/app.css, assets/app.js, and assets/app-core.js.
cargo build --releaseOptional allocator:
cargo build --release --features mimallocThe release profile enables opt-level=3, LTO, single codegen unit, symbol stripping, and panic=abort. After building, distribute the binary directly:
target/release/failover-proxy
target/release/failover-proxy.exe
The GitHub Actions Build Executables workflow builds these artifacts:
failover-proxy-windows-amd64.exefailover-proxy-windows-arm64.exefailover-proxy-linux-amd64failover-proxy-linux-arm64failover-proxy-macos-amd64failover-proxy-macos-arm64
Publishing a Release also runs the Docker Image workflow, which builds and pushes a multi-arch Docker image to GHCR.
Build locally:
docker build -t failover-proxy .Run:
docker run --rm -p 8787:8787 -v failover-proxy-data:/app/data failover-proxyUse Compose:
cd deploy/compose
cp .env.example .env
# after publishing a Release, set FAILOVER_PROXY_VERSION to v0.1.0 or another tag
# to customize the host port, set FAILOVER_PROXY_PORT to 18080 or another port
docker compose up -dOverride with environment variables:
FAILOVER_PROXY_IMAGE=ghcr.io/clockclock1/failover-proxy
FAILOVER_PROXY_PORT=8787
FAILOVER_PROXY_DATA_DIR=./data
FAILOVER_PROXY_VERSION=latest
FAILOVER_PROXY_NETWORK=failover-proxy-network
RUST_LOG=failover_proxy=info,tower_http=infoKubernetes manifests live under deploy/kubernetes/:
kubectl apply -f deploy/kubernetes/namespace.yaml
kubectl apply -f deploy/kubernetes/pvc.yaml
kubectl apply -f deploy/kubernetes/deployment.yaml
kubectl apply -f deploy/kubernetes/service.yamlDefault image:
ghcr.io/clockclock1/failover-proxy:latest
Default port:
8787
The example config lives at data/config.example.json. Minimal model configuration:
{
"adminToken": "admin",
"proxyKeys": [
{
"name": "test-key",
"key": "sk-local-test",
"enabled": true
}
],
"models": [
{
"publicName": "gpt-failover",
"enabled": true,
"targets": [
{
"name": "primary-openai",
"baseUrl": "https://api.openai.com/v1",
"apiKey": "sk-replace-me",
"modelName": "gpt-4.1-mini",
"enabled": true
}
]
}
]
}Example request:
curl http://127.0.0.1:8787/v1/chat/completions \
-H "Authorization: Bearer sk-local-test" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-failover",
"messages": [{"role": "user", "content": "Hello"}],
"stream": false
}'Streaming request:
curl -N http://127.0.0.1:8787/v1/chat/completions \
-H "Authorization: Bearer sk-local-test" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-failover",
"messages": [{"role": "user", "content": "Say hello slowly"}],
"stream": true
}'Use a local OpenAI-compatible mock upstream first, then run benchmarks with the same config:
oha -z 60s -c 128 -m POST http://127.0.0.1:8787/v1/chat/completions \
-H "Authorization: Bearer sk-local-test" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-failover","messages":[{"role":"user","content":"ping"}],"stream":false}'Track:
- QPS and p50/p95/p99 latency:
oha,wrk,bombardier. - Memory: Windows Task Manager,
Get-Process failover-proxy, Linuxpidstat -r. - Connection reuse: upstream access logs and
RUST_LOG=reqwest=debugfor short runs. - Failover behavior: inject 429/500/timeouts from the primary and verify
/api/statsplusx-proxy-target. - Streaming forwarding: use
curl -Nor a streaming benchmark while watching CPU and live status.