mirror of
https://github.com/RaisFast/raisfast.git
synced 2026-09-23 16:02:25 +00:00
26 KiB
26 KiB
LLM API 网关技术参考
基于 one-api / new-api 的完整技术流程分析,作为 raisfast 实现 LLM Gateway 的参考文档。
1. 产品定位
LLM API 网关(也称"API 中转"或"API 聚合")的核心价值:
- 统一入口:客户端只需对接一个 API 地址,网关路由到不同上游
- Key 管理:统一颁发 API Key,控制额度、过期、模型权限
- 负载均衡:多渠道分发,失败自动切换
- 格式转换:OpenAI / Claude / Gemini 等格式互转
- 计费统计:按 token 用量计费,支持缓存 token 折扣
2. 端到端请求流程
┌──────────┐ ┌──────────────────────────────────────────────────────┐ ┌──────────┐
│ Client │────▶│ raisfast Gateway │────▶│ Upstream │
│ │◀────│ │◀────│ (OpenAI/ │
│ │ │ RateLimit → Auth → Distribute → Relay → Response │ │ Claude/ │
└──────────┘ └──────────────────────────────────────────────────────┘ │ Gemini) │
└──────────┘
2.1 请求阶段
POST /v1/chat/completions
Authorization: Bearer sk-xxxxx
{
"model": "gpt-4",
"messages": [{"role":"user","content":"hello"}],
"stream": true
}
2.2 中间件链
请求进入
│
├─ Step 1: Rate Limit(IP 限流)
│ · 滑动窗口,按 IP 计数
│ · 默认 180 req / 3 min
│ · Redis 或内存存储
│
├─ Step 2: Token Auth(认证 + 预检)
│ · 提取 Bearer token → sk-xxx
│ · 查缓存 → DB 回退
│ · 检查:enabled? expired? quota > 0?
│ · IP 白名单验证
│ · 模型白名单验证(token 级别)
│ · 预扣额度(估算值,防止超额)
│
├─ Step 3: Distribute(渠道选择)
│ · 根据 user_group + model 查 abilities 表
│ · 优先级 + 随机算法
│ · 选中的渠道信息注入 context
│
├─ Step 4: Relay(请求转发)
│ · 格式转换(按上游类型)
│ · 构建 HTTP 请求
│ · 设置超时
│ · 发送到上游
│
├─ Step 5: Response(响应处理)
│ · 非流式:解析 JSON → 提取 usage → 返回
│ · 流式 SSE:逐行转发 → 累积 token → 结算
│
├─ Step 6: Billing(计费结算)
│ · 计算实际用量
│ · 结算预扣额度
│ · 记录日志
│
└─ 完成
失败重试(Step 4-5 之间):
· 429 / 5xx → 自动重试
· 400 / 客户端错误 → 不重试
· 每次重试换一个不同渠道
· 超过重试次数 → 返回错误 + 退还额度
3. 数据库 Schema
3.1 核心表
channels — 上游渠道
CREATE TABLE channels (
id BIGINT PRIMARY KEY,
document_id VARCHAR(36) NOT NULL UNIQUE,
tenant_id VARCHAR(36), -- 多租户
-- 渠道基本信息
name VARCHAR(255) NOT NULL,
provider_type SMALLINT NOT NULL DEFAULT 1, -- 1=OpenAI, 3=Azure, 14=Anthropic, 15=Gemini ...
status SMALLINT NOT NULL DEFAULT 1, -- 1=enabled, 2=manual_disabled, 3=auto_disabled
base_url VARCHAR(1024), -- 上游 API 地址
api_key TEXT NOT NULL, -- 上游 API Key(可存多个,逗号分隔)
-- 路由配置
models TEXT NOT NULL, -- 支持的模型列表(逗号分隔)
model_mapping TEXT, -- {"客户端模型":"上游模型"} JSON
priority BIGINT NOT NULL DEFAULT 0, -- 路由优先级,越高越优先
weight INT NOT NULL DEFAULT 0, -- 权重(预留)
channel_group VARCHAR(255) DEFAULT 'default', -- 渠道分组
-- 高级配置
config TEXT, -- 渠道特定配置 JSON
system_prompt TEXT, -- 强制注入的 system prompt
header_override TEXT, -- 请求头覆盖 JSON
param_override TEXT, -- 请求参数覆盖 JSON
status_code_mapping TEXT, -- 错误码映射 JSON
-- 统计
used_quota BIGINT NOT NULL DEFAULT 0,
response_time INT, -- 平均响应时间 ms
test_time TIMESTAMPTZ,
-- 审计
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
tokens — API Key 管理
CREATE TABLE tokens (
id BIGINT PRIMARY KEY,
document_id VARCHAR(36) NOT NULL UNIQUE,
tenant_id VARCHAR(36),
user_id BIGINT NOT NULL REFERENCES users(id),
-- Key 信息
name VARCHAR(255) NOT NULL,
key VARCHAR(64) NOT NULL UNIQUE, -- sk-xxxx
status SMALLINT NOT NULL DEFAULT 1, -- 1=enabled, 2=disabled, 3=expired, 4=exhausted
-- 额度控制
remain_quota BIGINT NOT NULL DEFAULT 0,
used_quota BIGINT NOT NULL DEFAULT 0,
unlimited_quota BOOLEAN NOT NULL DEFAULT FALSE,
-- 访问控制
expired_at TIMESTAMPTZ, -- 过期时间
allowed_models TEXT, -- 允许的模型列表(空=全部)
allowed_ips TEXT, -- IP 白名单 CIDR
token_group VARCHAR(255), -- Token 级分组覆盖
-- 审计
accessed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
abilities — 路由交叉表
-- 预计算的 (group × model × channel) 关系
-- 避免 runtime 时复杂查询
CREATE TABLE abilities (
channel_group VARCHAR(255) NOT NULL, -- 用户分组
model VARCHAR(255) NOT NULL, -- 模型名
channel_id BIGINT NOT NULL REFERENCES channels(id),
enabled BOOLEAN NOT NULL DEFAULT TRUE,
priority BIGINT NOT NULL DEFAULT 0,
PRIMARY KEY (channel_group, model, channel_id)
);
CREATE INDEX idx_abilities_group_model ON abilities(channel_group, model, enabled);
gateway_logs — 请求日志
CREATE TABLE gateway_logs (
id BIGINT PRIMARY KEY,
tenant_id VARCHAR(36),
-- 请求信息
request_id VARCHAR(64), -- X-Request-ID
user_id BIGINT NOT NULL,
token_id BIGINT,
channel_id BIGINT,
model_name VARCHAR(255),
is_stream BOOLEAN NOT NULL DEFAULT FALSE,
-- 用量
prompt_tokens INT NOT NULL DEFAULT 0,
completion_tokens INT NOT NULL DEFAULT 0,
cache_tokens INT NOT NULL DEFAULT 0, -- 缓存命中 token
-- 计费
quota BIGINT NOT NULL DEFAULT 0, -- 本次消耗额度
group_ratio REAL, -- 分组倍率
model_ratio REAL, -- 模型倍率
-- 性能
elapsed_time INT, -- 耗时 ms
status_code INT, -- 上游返回的 HTTP 状态码
-- 审计
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_gateway_logs_user ON gateway_logs(user_id, created_at);
CREATE INDEX idx_gateway_logs_token ON gateway_logs(token_id, created_at);
CREATE INDEX idx_gateway_logs_channel ON gateway_logs(channel_id, created_at);
CREATE INDEX idx_gateway_logs_model ON gateway_logs(model_name, created_at);
pricing — 模型定价
CREATE TABLE pricing (
id BIGINT PRIMARY KEY,
document_id VARCHAR(36) NOT NULL UNIQUE,
tenant_id VARCHAR(36),
model_name VARCHAR(255) NOT NULL, -- 模型名
-- 计费方式
billing_mode VARCHAR(20) NOT NULL DEFAULT 'ratio', -- ratio=倍率, price=固定价
model_ratio REAL NOT NULL DEFAULT 1.0, -- 模型倍率
completion_ratio REAL NOT NULL DEFAULT 1.0, -- 补全倍率
cache_ratio REAL, -- 缓存 token 倍率(默认 = model_ratio)
model_price REAL, -- 每次请求固定价格
billing_expr TEXT, -- 表达式计费(高级)
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
user_groups — 用户分组倍率
CREATE TABLE user_groups (
id BIGINT PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE, -- default, vip, enterprise
ratio REAL NOT NULL DEFAULT 1.0, -- 分组倍率
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
4. 渠道选择算法
4.1 基础算法(one-api)
输入: user_group, model
输出: channel
1. 查 abilities 表:
WHERE channel_group = user_group
AND model = model
AND enabled = true
2. 取最高 priority 的记录集合
3. 在同 priority 集合内随机选一个
4. 返回对应 channel
4.2 增强算法(new-api)
输入: user_group, model, session_id
输出: channel
1. 会话亲和检查:
· 如果 session_id 存在,查缓存看上次用的哪个 channel
· 同一会话优先路由到同一 channel(保持上下文一致)
2. auto group 模式:
· 如果 user_group = "auto"
· 按优先级尝试多个 group
· 第一个有可用 channel 的 group 生效
3. 多 Key 轮询:
· 如果 channel 配置了多个 api_key
· 按 round-robin 轮询使用
4. 降级:
· 首次尝试: 最高 priority
· 重试时: 忽略 priority,任何可用 channel
4.3 raisfast 建议的改进
1. 加权随机(weighted random)— 代替简单随机
· channel.weight 字段实际生效
· 响应时间越短 → 动态权重越高
2. 最少连接(least connections)
· 跟踪每个 channel 的活跃连接数
· 选连接数最少的
3. 健康检查
· 后台定时 ping 上游
· 异常自动降权或禁用
· 恢复后自动提权
4. 地理路由(预留)
· 根据 channel 的 region 和客户端位置
· 优先路由到最近的 region
5. 格式转换
5.1 Adaptor 接口设计
/// LLM 提供商适配器接口
#[async_trait]
pub trait ProviderAdaptor: Send + Sync {
/// 提供商名称
fn provider_name(&self) -> &str;
/// 将统一请求转换为上游格式
fn convert_request(
&self,
unified: &UnifiedChatRequest,
channel: &Channel,
) -> AppResult<ProviderRequest>;
/// 构建上游 HTTP 请求
fn build_http_request(
&self,
base_url: &str,
api_key: &str,
body: ProviderRequest,
) -> AppResult<reqwest::Request>;
/// 处理非流式响应
async fn handle_response(
&self,
resp: reqwest::Response,
) -> AppResult<UnifiedChatResponse>;
/// 处理流式响应(返回 SSE 流)
fn handle_stream_response(
&self,
resp: reqwest::Response,
) -> AppResult<Pin<Box<dyn Stream<Item = Result<SSEChunk>> + Send>>>;
/// 从响应中提取 token 用量
fn extract_usage(&self, response: &ProviderResponse) -> TokenUsage;
/// 该提供商支持的模型列表
fn supported_models(&self) -> &[&str];
}
5.2 统一请求结构
/// 统一的 Chat 请求(OpenAI 兼容格式作为内部标准)
pub struct UnifiedChatRequest {
pub model: String,
pub messages: Vec<ChatMessage>,
pub temperature: Option<f64>,
pub top_p: Option<f64>,
pub max_tokens: Option<i64>,
pub stream: bool,
pub stop: Option<Vec<String>>,
pub tools: Option<Vec<Tool>>,
pub response_format: Option<ResponseFormat>,
}
pub struct ChatMessage {
pub role: MessageRole, // system, user, assistant, tool
pub content: MessageContent, // text or array of content parts
pub tool_calls: Option<Vec<ToolCall>>,
pub tool_call_id: Option<String>,
}
pub struct TokenUsage {
pub prompt_tokens: i64,
pub completion_tokens: i64,
pub cache_tokens: i64, // 缓存命中的 token 数
pub total_tokens: i64,
}
5.3 OpenAI → Claude 转换要点
OpenAI 格式 Claude 格式
───────────── ──────────
messages: [ system: "system prompt", ← 提取到顶层
{role:"system", content:"..."}, messages: [
{role:"user", content:"hi"} {role:"user", content:"hi"}
] ]
max_tokens: 可选 max_tokens: 必填(默认 4096)
stop: ["\n"] stop_sequences: ["\n"] ← 字段名不同
tools: [{type:"function",...}] tools: [{name:"",...}] ← 格式不同
tool_choice: "auto" tool_choice: {type:"auto"}
stream: true stream: true
temperature: 0.7 temperature: 0.7
5.4 OpenAI → Gemini 转换要点
OpenAI 格式 Gemini 格式
───────────── ──────────
POST /v1/chat/completions POST /v1/models/{model}:generateContent
{ {
"model": "gpt-4", "contents": [
"messages": [ {"role":"user","parts":[{"text":"hi"}]}
{role:"user", content:"hi"} ],
], "systemInstruction": {"parts":[{"text":"..."}]},
"temperature": 0.7 "generationConfig": {
} "temperature": 0.7
}
}
5.5 透传优化(零拷贝)
当上游是 OpenAI 兼容 且 没有模型映射 且 没有 system prompt 注入时:
→ 直接转发原始请求体,不解析不序列化
→ 节省 CPU 和延迟
6. 流式转发(SSE)
6.1 架构
Upstream ──SSE──▶ raisfast ──SSE──▶ Client
┌──────────────┐
│ SSE Proxy │
│ │
│ line_scanner ◀─── 逐行读取上游
│ │ │
│ ▼ │
│ parse_chunk │─── 提取 usage
│ │ │
│ ▼ │
│ forward │─── 逐行转发客户端
│ │ │
│ ▼ │
│ accumulate │─── 累积 token 计数
└──────────────┘
6.2 SSE 协议
上游返回(chunked transfer encoding):
data: {"id":"chatcmpl-xxx","choices":[{"delta":{"content":"Hello"},"index":0}]}
data: {"id":"chatcmpl-xxx","choices":[{"delta":{"content":" world"},"index":0}]}
data: {"id":"chatcmpl-xxx","choices":[{"delta":{},"index":0}],"usage":{"prompt_tokens":10,"completion_tokens":5}}
data: [DONE]
6.3 Token 计数策略
优先级:
1. 上游返回 usage 字段(最准确)
2. 强制 stream_options: {"include_usage": true} 让上游返回 usage
3. tiktoken 本地估算(退路)
流式场景的计费流程:
1. 请求开始:预扣额度 = max_tokens × model_ratio × group_ratio
2. 流式传输中:累积 completion_tokens
3. 流结束:结算 = (prompt_tokens + completion_tokens × completion_ratio) × group_ratio
4. 差额退还
6.4 Rust 实现(建议)
use axum::response::sse::{Event, Sse};
use futures::stream::Stream;
use reqwest::Response;
pub async fn proxy_sse(
upstream_resp: Response,
) -> Sse<Pin<Box<dyn Stream<Item = Result<Event, axum::Error>> + Send>>> {
let stream = async_stream::stream! {
let mut lines = upstream_resp.lines();
while let Some(line) = lines.next_line().await.unwrap_or(None) {
if line.starts_with("data: ") {
let data = &line[6..];
if data == "[DONE]" {
yield Ok(Event::default().data("[DONE]"));
break;
}
// 提取 usage(如果有)
if let Ok(chunk) = serde_json::from_str::<SSEChunk>(data) {
if let Some(usage) = chunk.usage {
// 记录 token 用量
record_usage(usage).await;
}
}
yield Ok(Event::default().data(data.to_string()));
}
}
};
Sse::new(Box::pin(stream))
}
7. 计费系统
7.1 额度计算公式
quota = group_ratio × model_ratio × (
prompt_tokens
+ completion_tokens × completion_ratio
- cache_tokens × (1 - cache_ratio)
)
其中:
- group_ratio: 用户分组倍率(VIP 打折)
- model_ratio: 模型倍率(GPT-4 比 GPT-3.5 贵)
- completion_ratio: 补全倍率(GPT-4 补全 = 2x prompt)
- cache_ratio: 缓存折扣(缓存 token 更便宜)
7.2 预扣 + 结算
时间线:
─────────────────────────────────────────────────────▶
│ │ │
│ 预扣额度(estimate) │ 实际用量(usage) │ 结算(refund)
│ remain_quota -= 预估值 │ │ remain_quota += (预估 - 实际)
│ │ │
│ 如果余额不足预扣 → 拒绝 │ │ 如果中途失败 → 全额退还
7.3 各模型倍率参考
模型 model_ratio completion_ratio
───────────────────── ────────── ───────────────
gpt-3.5-turbo 0.75 1.0
gpt-4 15.0 2.0
gpt-4-turbo 5.0 2.0
gpt-4o 2.5 2.0
claude-3-haiku 0.5 1.25
claude-3-sonnet 1.5 1.25
claude-3-opus 7.5 1.25
claude-3.5-sonnet 1.5 1.25
gemini-pro 1.0 1.0
gemini-1.5-pro 1.75 1.0
gemini-1.5-flash 0.075 1.0
deepseek-chat 0.14 1.0
deepseek-reasoner 0.55 1.0
8. 失败重试与容错
8.1 重试策略
RetryTimes = 可配置(默认 0)
重试条件:
429 (Too Many Requests) → 重试(上游限流)
500 (Internal Server Error) → 重试
502 (Bad Gateway) → 重试
503 (Service Unavailable) → 重试
504 (Gateway Timeout) → 重试
400 (Bad Request) → 不重试(客户端问题)
401 (Unauthorized) → 不重试(Key 无效)
403 (Forbidden) → 不重试
404 (Not Found) → 不重试
每次重试:
1. 选一个不同的 channel(排除上次失败的)
2. 重新预扣额度
3. 重新发送请求
8.2 渠道健康监控
每个 channel 维护一个成功率队列(最近 N 次请求):
success_rate = successes / total_requests
如果 success_rate < threshold(默认 0.8):
→ 自动禁用该 channel(status = auto_disabled)
后台定时检查:
→ 定期 ping 被禁用的 channel
→ 恢复后自动启用(status = enabled)
8.3 raisfast 改进建议
1. 熔断器(Circuit Breaker):
· Closed → Open → Half-Open → Closed
· 比简单成功率监控更优雅
2. 指数退避重试:
· retry 1: 100ms 后
· retry 2: 200ms 后
· retry 3: 400ms 后
3. 优先级感知:
· 记录每个 channel 的平均延迟
· 同优先级内优先选延迟低的
4. 渠道预热:
· 新添加的 channel 先做健康检查
· 确认可用后才加入路由池
9. Provider 类型常量
const OPENAI: 1
const AZURE: 3
const CUSTOM: 4 // 自定义 URL
const ANTHROPIC: 14
const GEMINI: 15
const BAIDU: 16
const ZHIPU: 17
const ALI: 18
const XUNFEI: 19
const AWS: 20
const COHERE: 21
const DEEPSEEK: 22
const MOONSHOT: 23
const BAICHUAN: 24
const MINIMAX: 25
const GROQ: 26
const OLLAMA: 27
const TONGYI_QWEN: 28 // 通义千问
const YI: 29 // 零一万物
const STEP: 30 // 阶跃星辰
const DOUBAO: 31 // 字节豆包
const COZE: 32
const CLOUDFLARE: 33
const DEEPL: 34
const TOGETHER_AI: 35
const DIFY: 36
const XAI: 37 // Grok
const SILICONFLOW: 38 // 硅基流动
const VERTEX_AI: 39 // Google Vertex AI
10. raisfast 实现建议
10.1 模块划分
src/
├── gateway/
│ ├── mod.rs -- 模块入口
│ ├── router.rs -- /v1/* 路由注册
│ ├── middleware/
│ │ ├── rate_limit.rs -- IP 限流
│ │ ├── auth.rs -- Token 认证
│ │ └── distribute.rs -- 渠道选择
│ ├── relay/
│ │ ├── mod.rs -- 转发入口 + 重试逻辑
│ │ ├── sse.rs -- SSE 流式代理
│ │ ├── billing.rs -- 预扣/结算
│ │ └── usage.rs -- Token 计数
│ ├── adaptor/
│ │ ├── mod.rs -- trait ProviderAdaptor
│ │ ├── openai.rs -- OpenAI 透传
│ │ ├── anthropic.rs -- Claude 格式转换
│ │ ├── gemini.rs -- Gemini 格式转换
│ │ ├── azure.rs -- Azure OpenAI
│ │ └── custom.rs -- 自定义 OpenAI 兼容
│ ├── channel.rs -- 渠道管理 (CRUD)
│ ├── token.rs -- API Key 管理 (CRUD)
│ ├── ability.rs -- 路由交叉表维护
│ ├── pricing.rs -- 模型定价
│ └── health.rs -- 渠道健康检查
├── models/
│ ├── channel.rs -- Channel model
│ ├── gateway_token.rs -- GatewayToken model
│ ├── gateway_log.rs -- GatewayLog model
│ └── pricing.rs -- Pricing model
10.2 Feature Flag
[features]
gateway = ["reqwest/stream"]
10.3 依赖
[dependencies]
reqwest = { version = "0.12", features = ["stream", "json"] }
async-stream = "0.3"
tokio-stream = "0.1"
10.4 性能目标(vs Go)
Go (one-api) Rust (raisfast)
并发连接数 ~1,000 ~10,000+
内存占用 100-200MB 10-20MB
SSE 延迟抖动 GC 导致 ~50ms <1ms
冷启动 ~1s <50ms
二进制大小 ~50MB ~10MB
单核 QPS ~3,000 ~15,000+
10.5 与现有模块集成
gateway 模块复用:
· users 表 → 用户管理
· rbac → 权限控制
· audit_log → 审计日志
· api_tokens → Key 管理(扩展 gateway_token)
· plugin_system → 格式转换可作为插件实现
· worker/cron → 渠道健康检查定时任务
· webhook → 用量告警
· media → 图片生成接口
· tenant → 多租户隔离
11. API 路由设计
11.1 中转接口(兼容 OpenAI)
# Chat
POST /v1/chat/completions
POST /v1/completions
# Embeddings
POST /v1/embeddings
# Images
POST /v1/images/generations
POST /v1/images/edits
POST /v1/images/variations
# Audio
POST /v1/audio/transcriptions
POST /v1/audio/translations
POST /v1/audio/speech
# Models
GET /v1/models
# Rerank (new-api)
POST /v1/rerank
11.2 管理接口
# 渠道管理
GET /api/v1/gateway/channels
POST /api/v1/gateway/channels
PUT /api/v1/gateway/channels/:id
DELETE /api/v1/gateway/channels/:id
POST /api/v1/gateway/channels/:id/test
# Key 管理
GET /api/v1/gateway/tokens
POST /api/v1/gateway/tokens
PUT /api/v1/gateway/tokens/:id
DELETE /api/v1/gateway/tokens/:id
# 定价管理
GET /api/v1/gateway/pricing
POST /api/v1/gateway/pricing
PUT /api/v1/gateway/pricing/:id
# 统计
GET /api/v1/gateway/logs
GET /api/v1/gateway/stats/overview
GET /api/v1/gateway/stats/models
GET /api/v1/gateway/stats/channels
# 用户分组
GET /api/v1/gateway/groups
PUT /api/v1/gateway/groups/:id
12. 与 one-api/new-api 的差异化
one-api/new-api 的不足 raisfast 的改进
───────────────────── ────────────────
Go GC 导致 SSE 抖动 无 GC,延迟稳定
单机架构,扩展靠 Redis 多租户原生支持
格式转换硬编码 插件化 adaptor
定价硬编码或单表 灵活表达式定价
无桌面端 Tauri 桌面管理
无 Content Type 可组合内容建模
无工作流 工作流引擎
无搜索 Tantivy 全文搜索