1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
|
# AIGW
AIGW 是一个轻量、无状态的 AI API 中转后端。当前阶段聚焦上游接入与 API 分发:提供 OpenAI Chat Completions 和 Anthropic Messages 兼容入口,支持公开模型名映射、多上游路由、加权分流、故障转移、SSE 直通、客户密钥鉴权以及异步用量事件。
它借鉴了 ZenMux 的双协议、`provider/model` 模型命名、统一错误、请求 ID、路由和可观测性边界,但没有复制其业务实现。
## 当前能力
- OpenAI:`POST /v1/chat/completions`、`GET /v1/models`
- Anthropic:`POST /anthropic/v1/messages`、`GET /anthropic/v1/models`
- ZenMux 风格别名:所有入口同时提供 `/api/...` 路径
- 一个公开模型可配置多个同协议上游,低 `priority` 优先,同级按 `weight` 分流
- 在 429、502、503、504 或连接失败时,于响应开始前自动尝试下一条路由
- SSE 增量直通、主动断连传播、共享 HTTP/2 连接池
- `Authorization: Bearer` 和 `x-api-key` 客户鉴权
- 统一 JSON 错误、`X-AIGW-Request-ID`、Prometheus 文本指标
- 非阻塞用量事件,包含租户、项目、模型、上游、尝试次数、耗时和 token 用量
- PostgreSQL 预付余额、请求额度冻结、实际 token 结算和不可变账本
- Stripe 托管 Checkout 充值、签名 Webhook 与事件/订单双重幂等
- PostgreSQL Usage Ledger、月度项目汇总和单请求成本追溯
- 平台/租户控制台令牌、六种 RBAC 角色和管理 API 审计日志
- 项目级 RPM、估算 TPM、并发限制和月度消费配额
## 快速运行
需要 Go 1.26。先准备配置和环境变量:
```bash
cp config.example.json config.json
export AIGW_API_KEYS='[{"key":"sk-local-change-me","key_id":"local-key","tenant_id":"tenant-demo","project_id":"project-default","scopes":["inference"]}]'
export OPENAI_API_KEY='your-upstream-key'
export ANTHROPIC_API_KEY='your-upstream-key'
go run ./cmd/aigw -config config.json
```
如果当前只有一种上游,从 `config.json` 删除未使用的 provider 和对应 model,避免启动时要求该密钥。
不使用真实密钥的本地体验方式:
```bash
# terminal 1
go run ./cmd/mockupstream
# terminal 2
export MOCK_UPSTREAM_KEY='local-only'
export AIGW_API_KEYS='[{"key":"sk-local-change-me","key_id":"local-key","tenant_id":"tenant-demo","project_id":"project-default","scopes":["inference"]}]'
go run ./cmd/aigw -config config.local.json
```
本地网关会监听 `http://127.0.0.1:18081`,公开模型为 `demo/openai` 和 `demo/anthropic`。`cmd/mockupstream` 只用于开发验证,不应部署到生产环境。
OpenAI 调用示例:
```bash
curl http://127.0.0.1:8080/v1/chat/completions \
-H 'Authorization: Bearer sk-local-change-me' \
-H 'Content-Type: application/json' \
-d '{"model":"openai/gpt-4.1-mini","messages":[{"role":"user","content":"hello"}],"stream":true}'
```
Anthropic 调用示例:
```bash
curl http://127.0.0.1:8080/anthropic/v1/messages \
-H 'x-api-key: sk-local-change-me' \
-H 'anthropic-version: 2023-06-01' \
-H 'Content-Type: application/json' \
-d '{"model":"anthropic/claude-sonnet","max_tokens":256,"messages":[{"role":"user","content":"hello"}]}'
```
## 配置路由
每条 route 把一个对外模型映射到一个上游模型:
```json
{
"id": "vendor/model-public",
"owned_by": "vendor",
"routes": [
{"provider": "provider-a", "upstream_model": "model-v3", "priority": 0, "weight": 80},
{"provider": "provider-b", "upstream_model": "model-v3", "priority": 0, "weight": 20},
{"provider": "provider-c", "upstream_model": "model-v2", "priority": 10, "weight": 100}
]
}
```
这里 A/B 承担约 80/20 的首选流量,C 只作为更低优先级的后备。所有上游密钥仅通过 `api_key_env` 指向的环境变量读取。客户密钥从 `AIGW_API_KEYS` JSON 数组读取,进程内只保存 SHA-256 摘要。
## 生产边界
当前版本可以作为带预付计费的数据面,但上线前仍需完成业务侧对账:
- 控制面计费模式会把 UsageEvent、冻结记录和扣费流水同步、幂等写入 PostgreSQL;异步结构化日志只是可观测副本,丢弃不会影响账本。
- 客户密钥和模型目录在控制面模式下从 PostgreSQL 载入到原子内存快照;Redis 只是可选的变更广播加速层,故障时通过 PostgreSQL generation 轮询收敛。
- 当前只把 OpenAI 入口发给 OpenAI 兼容上游、Anthropic 入口发给 Anthropic 兼容上游,不做跨协议转换。
- 自动故障转移可能在极少数网络错误下造成上游重复执行。正式计费时需要上游幂等能力、请求去重策略和重复成本对账。
- 上游必须返回 usage 才能按 token 结算;未返回 usage 的成功响应当前记为零费用,应在上生产前为每个供应商做账单对账或补充 token 计算器。
- `/metrics` 应仅在内网暴露;公网 TLS、WAF 和连接层限速应放在负载均衡器或边缘代理。
## PostgreSQL + Redis 控制面
控制面模式使用 [config.control.example.json](config.control.example.json)。PostgreSQL 保存事实数据:租户、项目、API Key 摘要、加密后的供应商密钥、模型和路由。Redis 保存当前 generation 并广播变更事件;它不是客户账本,PG 才是控制面事实来源。Redis 短暂不可用或未配置时,网关仍从 PostgreSQL 启动并通过周期轮询收敛,恢复后会在后台自动重连订阅。管理写入先提交 PG 并替换本机快照,Redis 广播失败只记录告警,不会把已提交的写入报告为失败。
凭证密钥必须是 base64 编码的 32 字节随机值。供应商 API Key 使用 AES-256-GCM 加密后写入 PostgreSQL,运行时解密到当前内存快照,管理接口永远不会返回它。
开发环境可以直接启动:
```bash
./scripts/start-debug.sh
```
脚本会首次生成仅当前用户可读的 `.env.debug`,构建并等待 PostgreSQL、Redis 和网关健康,然后打印后台地址和管理员 token。关闭调试服务:
```bash
./scripts/stop-debug.sh
```
关闭脚本保留 PostgreSQL/Redis 数据卷,下一次启动仍可继续使用已有控制面数据。需要测试 Stripe Checkout 时,先把 `.env.debug` 中两个 Stripe 占位值替换成测试环境 restricted key 和 Webhook signing secret。
源码未变化时可跳过镜像构建以快速重启:`AIGW_DEBUG_SKIP_BUILD=1 ./scripts/start-debug.sh`。默认构建使用 Docker host network;特殊环境可以通过 `AIGW_DOCKER_BUILD_NETWORK=default` 覆盖。
然后打开 `http://127.0.0.1:8080/admin/`,输入脚本打印的 `AIGW_ADMIN_TOKEN`。这个 token 是平台管理员的 bootstrap/break-glass 凭证;日常操作应在 Team 页面签发数据库控制台令牌。第一套资源的创建顺序是:Tenant → Project → API key → Provider → Model route。客户 API Key 和控制台令牌的明文都只在创建成功时返回一次。
控制台角色分为:`platform_admin`、`platform_viewer`、`tenant_admin`、`tenant_billing`、`tenant_developer`、`tenant_viewer`。租户角色的查询条件在服务端下推到 PostgreSQL,不能读取其他租户的项目、密钥、余额、Usage 或审计事件;供应商凭证和路由管理只对平台角色开放。
## Usage、配额与限流
每个完成上游尝试的请求都会按 `request_id` 幂等写入 `usage_events`,并更新 `usage_monthly_rollups`。Usage 持久化独立于预付费冻结记录,因此关闭计费也不会关闭用量账本。Admin WebUI 提供本月汇总、单请求状态、模型、token、成本、未收金额和延迟查询。
Limits 页面按项目配置:
- `requests_per_minute`:固定分钟窗口请求数,`0` 表示不限。
- `tokens_per_minute`:请求体约算输入 token 加显式/默认最大输出 token,`0` 表示不限。
- `concurrent_requests`:从进入上游前到响应复制完成的并发 lease,`0` 表示不限。
- `monthly_spend_micros`:PG 事务内检查当月已计成本和未结冻结,`0` 表示不限。
Redis 可用时,RPM/TPM/并发通过 Lua 原子执行并在多实例间共享;Redis 故障时自动退回本机计数,服务继续可用,但降级期间限制是“每实例”而不是“全局”。Redis 恢复后新请求会自动重新使用分布式计数。启用预付计费时,月度消费配额和余额由 PostgreSQL 保证,不依赖 Redis。
## 余额与 Stripe 充值
模型价格在后台按“币种单位 / 100 万 token”配置,数据库使用 `amount_micros` 固定精度整数保存金额。推理请求会先按请求体字节数和 `max_tokens`/`max_completion_tokens` 保守冻结额度;成功响应按上游返回的输入、输出和缓存 token 结算,失败请求释放冻结。`request_id` 是用量与扣费幂等键。
Stripe 使用托管 Checkout,服务端不会接触卡号,也没有硬编码支付方式;支付方式由 Stripe Dashboard 动态配置。充值只在签名校验通过的 Webhook 确认 `payment_status=paid` 后入账,成功跳转页不会直接修改余额。
配置 Stripe 测试环境:
```bash
cp .env.control.example .env
# 将 AIGW_STRIPE_API_KEY 设为最小权限的 rk_test_ restricted key
# 本地转发会显示 whsec_...,填入 AIGW_STRIPE_WEBHOOK_SECRET
stripe listen --forward-to http://127.0.0.1:8080/billing/stripe/webhook
docker compose up --build
```
Webhook 至少订阅:
- `checkout.session.completed`
- `checkout.session.async_payment_succeeded`
- `checkout.session.async_payment_failed`
- `checkout.session.expired`
当前没有默认启用 Stripe Tax,因为是否有有效税务注册不能由代码推断。确认注册和税务处理方案后再显式加入 `automatic_tax`。生产环境应把 Stripe restricted key 和 Webhook signing secret 放入云平台的密钥管理服务,并限制密钥权限和来源 IP,不要放进镜像或仓库。
生产环境建议把 `auto_migrate` 改为 `false`,先执行:
```bash
AIGW_DATABASE_URL="postgres://..." go run ./cmd/migrate
```
管理 API 支持 bootstrap token 和数据库控制台令牌。即使已经启用 RBAC,管理监听端口仍应放在内网、VPN 或身份感知反向代理后;bootstrap token 应只用于首次建号和故障恢复。
完整的扩展边界见 [架构说明](docs/architecture.md)。
## 验证
```bash
go test ./...
go vet ./...
```
|