---
title: LLM-Gateway
canonical_url: "https://www.modelscope.cn/studios/AMD_Dev/LLM-Gateway"
md_url: "https://www.modelscope.cn/studios/AMD_Dev/LLM-Gateway.md"
repository: AMD_Dev/LLM-Gateway
last_updated: 2026-10-15
sdk_type: docker
sdk_version: 6.17.3
downloads: 0
stars: 4
---

# LLM-Gateway

> LLM-Gateway - AMD_Dev 在 ModelScope 创建的在线 Demo。AMD GPU Model API Gateway 设计交接

AMD_Dev/LLM-Gateway 是 ModelScope 魔搭社区上的在线可交互 Demo（创空间），基于 docker 6.17.3 构建。

- **Repository**: AMD_Dev/LLM-Gateway
- **SDK**: docker
- **SDK version**: 6.17.3
- **Downloads**: 0
- **Stars**: 4
- **Last updated**: 2026-10-15

Source: https://www.modelscope.cn/studios/AMD_Dev/LLM-Gateway

---

# AMD GPU Model API Gateway 设计交接

本文档仅涵盖基于
[`theopenco/llmgateway`](https://github.com/theopenco/llmgateway)
建设 AMD GPU Model API 调度与权益平台的方案。

不包含现有模型服务代码、压测结果、Token、密钥或 DigitalOcean 后端实现。

> 当前正式运行入口是 Python/FastAPI 实现：`apps/control-api-python`。原
> `apps/control-api` TypeScript 工程仅保留为早期原型和设计参考，不再由
> Dockerfile、Compose 或 Kubernetes 清单启动。
>
> ADP 的公网浏览器回调由 `apps/oauth-bff-python` 承接；身份映射、
> ADP Token/UserInfo 调用和数据库写入仍在 Python Control API 内完成。

## 1. 项目目标

通过 LLMGateway 提供统一的 OpenAI-compatible 模型入口，并组合 AMD 自有服务完成：

- ModelScope 用户身份关联；
- 每位用户终身 10 次免费生成调用；
- 免费次数耗尽后的 ADP 注册流程；
- ADP 注册成功后的 API 权限开放；
- 异步触发 ModelScope 发放 100 小时 AMD GPU Notebook 权益；
- 多个 ModelScope Space 的模型路由、健康检查和故障隔离；
- 请求、Token、延迟、错误和后端尝试数据收集；
- 向 ModelScope 上层提供规范化用量数据。

首个模型后端：

```text
https://studio-amd-dev-hy4-preview-fp8.api-inference.modelscope.net/v1
```

后续会增加其他 ModelScope Space。DigitalOcean 后端暂不实施。

## 2. 已确认的业务规则

### 2.1 免费调用

- 免费次数按稳定的 ModelScope 用户主体计算，不按浏览器、IP、Token或API Key分别计算。
- 每个用户终身 10 次。
- 请求产生首个有效模型输出后才消耗一次。
- 鉴权失败、参数错误、429、首 Token 前失败不消耗。
- 已产生输出后，即使客户端主动断开，仍消耗一次。
- 并发请求必须先原子占位，确保 `CONSUMED + HELD <= 10`。
- 相同幂等键和相同请求摘要不能重复扣次数。

### 2.2 ADP

- 免费次数用完后，页面引导用户注册 AMD Developer Program。
- OpenAI API请求不进行HTTP 302跳转，而是返回结构化错误和注册链接。
- 只有ADP服务端确认状态为 `ACTIVE` 才算注册成功。
- ADP注册成功后立即开放计量API，不等待Notebook权益发放完成。
- 用户浏览器回跳只能用于展示，不能作为注册成功的安全依据。

### 2.3 Notebook权益

- ADP注册成功后异步触发ModelScope发放100小时AMD GPU Notebook权益。
- 时长单位沿用ModelScope已有配额单位，但ModelScope接口必须明确返回 `unit`。
- 同一活动、同一用户、同一权益只能发放一次。
- 发放失败不能要求用户重复注册ADP，应进入后台重试或人工处理。

### 2.4 魔力值

- 魔力值余额、价格、扣减和充值均属于ModelScope上层系统。
- AMD Gateway不维护魔力值账本。
- Gateway只提供模型、输入/输出/Reasoning/Cache Token、请求结果和唯一请求ID等规范化用量。
- 如果ModelScope需要调用前余额控制，应由ModelScope另行提供预授权接口；不纳入首期MVP。

## 3. 为什么不能只部署原版LLMGateway

LLMGateway适合：

- OpenAI-compatible统一API；
- Provider和模型管理；
- 基础API Key、IAM、限流和报表；
- Token、成本、延迟和错误统计；
- 多Provider路由及部分重试；
- Docker、Compose和Kubernetes自托管。

LLMGateway不直接提供：

- ModelScope稳定用户身份关联；
- 精确的一人10次免费额度；
- ADP OAuth状态机、身份绑定和浏览器回调；
- Notebook权益发放；
- 多个Custom Provider组成一个实时负载池；
- vLLM队列、KV Cache、GPU负载感知；
- ModelScope Space冷启动和实例生命周期管理；
- 防止用户绕过Gateway直连Space。

此外，LLMGateway Custom Provider通常对应一个Base URL，不能直接把多个Space当作同一模型的推理副本池。

因此采用“组合服务”，不深度Fork LLMGateway。

## 4. 推荐架构

```mermaid
flowchart LR
  WebUI["ModelScope AMD GPU Model API页面"] --> Access["AMD Access Gateway"]
  APIClient["用户API Client"] --> Access
  Access -.->|"一次性 enrollment URL"| OAuthBFF["Public OAuth BFF"]
  OAuthBFF --> ADPProvider["ADP OAuth"]
  OAuthBFF -->|"签名的内部请求"| Access

  Access --> Policy["AMD Policy Service"]
  Access --> LLMGateway["LLMGateway内部入口"]

  LLMGateway --> SpaceRouter["AMD Space Router"]
  SpaceRouter --> HY4["ModelScope HY4 Space"]
  SpaceRouter --> Space2["ModelScope Space 2"]

  Policy --> Identity["身份和API Key"]
  Policy --> Trial["免费10次"]
  Policy --> ADP["ADP关联状态"]
  Policy --> Usage["规范化用量与审计"]

  ADP --> Outbox["Notebook权益Outbox"]
  Outbox --> MSGrant["ModelScope权益发放API"]
  Usage --> MSUsage["ModelScope用量接收接口"]

  Identity --> Postgres[(PostgreSQL)]
  Trial --> Postgres
  ADP --> Postgres
  Usage --> Postgres

  LLMGateway --> Redis[(Redis)]
```

### 4.1 AMD Access Gateway

这是实际公网入口，必须位于LLMGateway之前。

职责：

- 验证ModelScope登录或AMD Gateway API Key；
- 将外部身份映射为内部 `principal_id`；
- 执行免费次数占位、确认和释放；
- 返回ADP注册提示；
- 代理SSE且禁止响应缓冲；
- 观察首Token和最终usage；
- 生成 `logical_request_id`；
- 调用内部LLMGateway。

LLMGateway没有通用的外部鉴权Hook。如果把Policy放在LLMGateway之后，Policy未必能获得可信的终端用户身份，因此无法可靠执行一人10次。

推荐让LLMGateway仅在内网可访问。Access Gateway使用服务凭证调用它，用户不直接持有LLMGateway内部Key。

### 4.2 LLMGateway

职责：

- 统一OpenAI协议；
- 模型目录和Provider配置；
- 基础网关监控；
- 将逻辑AMD Provider转发到Space Router；
- 提供辅助统计，但不作为免费次数或权益事实源。

建议配置一个Custom Provider：

```text
name: amd-router
base_url: https://<amd-space-router-host>
```

Base URL不要包含 `/v1`，LLMGateway会追加 `/v1/chat/completions`。

客户端模型名称示例：

```text
amd-router/hy4-preview
```

技术验证阶段可以暂时将Custom Provider直接指向HY4 Space，以确认stream、tools、reasoning和usage兼容性；生产必须经过Space Router。

### 4.3 AMD Policy Service

权威管理：

- 内部用户；
- ModelScope和ADP账号关联；
- 免费次数；
- ADP注册会话和状态；
- Notebook发放状态；
- 用户API Key；
- 请求和用量审计。

它是控制面，不直接执行LLM推理。

### 4.4 AMD Space Router

向LLMGateway暴露：

```text
POST /v1/chat/completions
GET  /v1/models
GET  /healthz
```

职责：

- 逻辑模型到Space的映射；
- ModelScope服务凭证管理；
- 健康探测；
- 静态权重、熔断、在途请求和延迟感知；
- `logical_request_id` 与 `attempt_id` 记录；
- 统一后端错误；
- 将usage原样或规范化返回。

首期只有HY4时直接路由。Space2接入后再增加权重和故障转移。

已经向用户发送任何Token后，禁止自动切换后端重试，否则可能产生重复内容和重复计量。

非流式请求发生连接错误时，也不能默认认为后端没有执行；必须依赖 `attempt_id` 和用量回执判断能否重试。

## 5. 用户状态机

```mermaid
stateDiagram-v2
  [*] --> Trial
  Trial --> Trial: "有效输出且累计少于10次"
  Trial --> EnrollmentRequired: "第10次已消费"
  EnrollmentRequired --> ADPPending: "创建ADP关联会话"
  ADPPending --> Metered: "ADP签名事件确认ACTIVE"
  ADPPending --> Review: "身份冲突或异常"
  Metered --> Suspended: "账号停用或资格策略触发"
  Suspended --> Metered: "恢复资格"
```

Notebook权益使用独立状态：

```text
NOT_ELIGIBLE
  -> GRANT_PENDING
  -> GRANTED
  -> RETRYING
  -> FAILED_FINAL
```

API权限与Notebook发放不能合并成一个状态。

## 6. 免费次数事务模型

每次生成请求创建 `trial_reservation`：

```text
HELD -> CONSUMED
HELD -> RELEASED
```

处理规则：

1. 请求进入时，在PostgreSQL事务中检查 `consumed + held < 10`。
2. 创建HELD记录并提交事务。
3. 转发请求。
4. 收到首个有效模型Token时，将HELD原子更新为CONSUMED。
5. 首Token前失败则更新为RELEASED。
6. HELD设置过期时间，后台回收异常中断遗留记录。
7. 相同 `Idempotency-Key + body_hash` 返回原有reservation，不重复占位。

API错误示例：

```json
{
  "error": {
    "type": "account_activation_required",
    "code": "adp_enrollment_required",
    "message": "初始免费调用次数已用完，请完成AMD开发者计划注册。",
    "trial_used": 10,
    "trial_limit": 10,
    "enrollment_url": "https://..."
  }
}
```

建议使用HTTP 403，而不是429。429仅表示速率或并发限制。

## 7. ModelScope接口需求

### 7.1 身份

Gateway 已实现 ModelScope
`GET https://www.modelscope.cn/openapi/v1/users/me` 适配器。该接口直接接收 SDK Token 时可用，
当前响应只提供 `username`、`email` 和 `avatar_url`。但线上探测确认 ModelScope API Edge
转发给容器的 `Authorization` 不是原始 SDK Token，无法再次用于调用 `users/me`。

因此当前开放访问阶段使用 `MODELSCOPE_ADAPTER=edge` 和
`TRUST_MODELSCOPE_EDGE=true`：身份认证由 ModelScope Edge 完成，Gateway 仅保存转发凭证的
不可逆 HMAC。该方式可以先恢复调用，但识别的是凭证而不是自然人；同一用户的多个 Token
暂时可能形成不同 principal。`users/me` 适配代码保留，待平台提供原始 Token 的安全传递方式
或稳定用户 Header 后再启用。

该办法可以完成当前端到端联调，但 username 不是已经确认的不可变标识。最终仍优先要求
ModelScope提供OIDC：

- Authorization Code + PKCE；
- `iss`、`aud`、`sub`、`exp`、`nbf`；
- JWKS密钥轮换；
- 不可变且永不复用的 `sub`；
- 账号停用、删除和合并事件。

username、email和display name仅用于展示，不能作为数据库主键。

API客户端可使用两种方式：

- 首次通过ModelScope OIDC登录后签发AMD Gateway API Key；
- 或使用ModelScope Token Introspection换取稳定 `sub`。

原始ModelScope Token不落库，也不转发到Space。

### 7.2 Notebook权益

建议接口：

```http
POST /v1/entitlement-grants
Idempotency-Key: <campaign|modelscope_sub|benefit>
```

请求示例：

```json
{
  "client_grant_id": "grant_xxx",
  "modelscope_sub": "stable-ms-subject",
  "campaign_id": "amd-adp-2026",
  "benefit_type": "AMD_GPU_NOTEBOOK_HOURS",
  "quantity": 100,
  "unit": "MODELSCOPE_NOTEBOOK_QUOTA_UNIT",
  "adp_event_id": "evt_xxx"
}
```

响应：

- `201 GRANTED`：立即成功；
- `202 PENDING`：返回operation ID；
- 重复幂等键返回原结果；
- 相同幂等键但正文不同返回409；
- 提供查询接口和状态变化Webhook。

### 7.3 用量接收

建议Gateway向ModelScope发送：

```json
{
  "schema_version": "1.0",
  "event_id": "usage_xxx",
  "logical_request_id": "req_xxx",
  "modelscope_sub": "stable-ms-subject",
  "model_sku": "amd/hy4-preview",
  "backend_id": "modelscope/hy4-space",
  "billing_mode": "trial_or_metered",
  "prompt_tokens": 1024,
  "completion_tokens": 200,
  "reasoning_tokens": 0,
  "cached_prompt_tokens": 0,
  "status": "SUCCEEDED",
  "occurred_at": "2026-09-18T06:00:00Z"
}
```

要求：

- `event_id`幂等；
- 支持批量上报；
- 返回逐条接受结果；
- 提供对账查询；
- Token缺失时明确标记UNKNOWN，不能当作0。

### 7.4 Space防绕过

ModelScope必须至少提供一种能力：

- Space私有网络入口；
- Gateway专用服务Token；
- mTLS；
- Gateway签发的短期JWT；
- 平台级来源/IP限制；
- 外部用户Token禁止直接调用Space。

如果用户仍可直接请求公开Space URL，则免费次数、ADP要求和用量统计都只能作为“推荐入口”，不能称为强制策略。

## 8. ADP OAuth接口

### 8.1 关联入口

免费次数耗尽后，Gateway 创建短期、一次性的关联 ticket，并在 403 中返回：

```json
{
  "error": {
    "code": "adp_enrollment_required",
    "enrollment_url": "https://.../v1/integrations/adp/connect?ticket=..."
  }
}
```

关联页展示说明和 ADP 登录按钮。ticket 是由 session UUID 和 HMAC-SHA256 签名组成的短期
capability，绑定内部 principal，不携带原始 ModelScope Token，也不以明文或摘要形式保存到
数据库；服务端只保存 OAuth `state` 的 SHA-256 摘要。ticket 有效期默认 10 分钟，进入
Authorization Code 流程时只能消费一次。

### 8.2 Authorization Code回调

```text
Authorize: https://developer.amd.com.cn/api/api/Oauth/Authorize
Token:     https://developer.amd.com.cn/api/api/Oauth/Token
UserInfo:  https://developer.amd.com.cn/api/api/Oauth/UserInfo
Scope:     amdid
```

浏览器 callback：

```http
GET /v1/integrations/adp/oauth/callback?code=<authorization-code>&state=<opaque-state>
```

Gateway 校验一次性 state，通过固定 Token 端点交换 Access Token，再使用 Bearer Token 调用
UserInfo。只保存稳定、区分大小写的 `sub`，不保存 Access Token。ADP 身份和 ModelScope
principal 成功绑定后，principal 从 `ADP_PENDING` 更新为 `METERED`。相同 ADP subject 绑定
不同 principal 时进入 `REVIEW`，不会自动合并账号。

当前 OAuth 合同沿用 Radeon-Cloud：成功取得 UserInfo `sub` 即视为已完成 ADP 注册和登录。
如果 ADP 后续提供资格撤销、账号删除或会员状态事件，再增加独立的签名 Webhook；浏览器
callback 本身不能充当未经服务端 Token/UserInfo 校验的状态通知。

## 9. 数据模型

建议PostgreSQL核心表：

```text
principals
external_identities
gateway_api_keys
trial_reservations
adp_sessions
adp_memberships
notebook_grants
outbox_events
model_skus
backend_routes
backend_instances
inference_requests
inference_attempts
usage_receipts
usage_delivery_attempts
admin_audit_events
```

关键约束：

- ModelScope `issuer + subject` 唯一；
- ADP `issuer + subject` 唯一；
- 一个ADP subject默认只能绑定一个principal；
- `Idempotency-Key + body_hash` 唯一；
- `(campaign_id, principal_id, benefit_type)` 唯一；
- `usage event_id` 唯一；
- 所有状态变化写审计事件。

Redis仅用于：

- 短期身份缓存；
- RPM/并发限制；
- Backend健康和熔断缓存；
- Session stickiness；
- 非权威分布式锁。

不要将Redis List `LPOP` 或普通内存队列作为免费次数、权益或用量交付的唯一事实源。

## 10. 部署建议

### POC

可以使用LLMGateway Unified Docker或单节点Compose：

```text
AMD Access Gateway
LLMGateway
AMD Policy Service
AMD Space Router
PostgreSQL
Redis
```

单Pod部署到ModelScope Space只适合技术验证。

### 生产

建议：

- Kubernetes；
- LLMGateway、Access Gateway、Policy和Router均无状态多副本；
- 托管PostgreSQL；
- 持久化Redis；
- Secret Manager/KMS；
- Gateway和Space Router独立HPA；
- 数据库备份、PITR和跨可用区；
- OpenTelemetry、Prometheus和集中日志。

LLMGateway官方Kubernetes文档：

<https://docs.llmgateway.io/self-host/kubernetes>

## 11. 安全和隐私

- 不保存Prompt和回复正文，除非用户明确授权。
- 不记录Authorization、Cookie、ModelScope Token或ADP凭证。
- 外部身份映射加密存储。
- 服务凭证通过Secret Manager管理。
- ADP回调和ModelScope权益回调验签并防重放。
- API Key只保存HMAC或强哈希，明文只展示一次。
- 权益发放、账号合并、人工调账和管理员查询必须审计。
- 明确数据保留、删除请求和账务审计保留策略。

## 12. 关键卡点

按优先级：

1. ModelScope是否能阻止用户绕过Gateway直连Space。
2. ModelScope是否能提供稳定不可变subject。
3. ADP是否能提供签名Webhook和状态查询。
4. ModelScope是否能提供幂等Notebook发放接口。
5. 流式中断后是否能获得准确最终usage。
6. Space健康和容量指标能否统一。
7. ModelScope公网约120秒连接时限如何调整。
8. LLMGateway Custom Provider和自定义模型能力的Enterprise边界。
9. AGPLv3网络服务义务和专有AMD服务的隔离方式。
10. 单Pod部署的数据库、Redis和Gateway单点问题。

其中第1项是强制配额能否成立的Go/No-Go条件。

## 13. 推荐实施阶段

### Phase 0：技术验证

- 自托管LLMGateway；
- Custom Provider直连HY4；
- 验证stream、non-stream、usage、reasoning、tools和错误格式；
- 验证LLMGateway是否保留必要的request ID和内部Header；
- 确认许可证边界。

### Phase 1：HY4单后端MVP

- AMD Access Gateway；
- Policy Service和PostgreSQL；
- ModelScope身份Mock适配器；
- 免费10次完整状态机；
- ADP和Notebook Mock适配器；
- Space Router单后端；
- 用户用量页面和管理审计。

### Phase 2：外部闭环

- ModelScope OIDC/Introspection；
- ADP签名回调；
- Notebook幂等发放；
- ModelScope用量交付和对账；
- Space防绕过。

### Phase 3：多Space

- Space2注册；
- 健康检查和熔断；
- 静态权重；
- 在途请求和延迟感知；
- 故障转移和attempt级用量。

### Phase 4：生产化

- Kubernetes HA；
- 安全审查；
- 并发和断流压测；
- Webhook重放测试；
- 权益、用量和审计对账；
- 灰度发布与回滚。

DigitalOcean后端在上述阶段完成后再评估。

## 14. MVP验收标准

- 同一用户100个并发试用请求最多成功占用10次。
- 首Token前失败不扣次数，首Token后断开扣1次。
- 重复请求幂等键不重复扣次数。
- username/email改变不创建新用户。
- 第10次后返回结构化ADP提示。
- 重复ADP回调不重复发放Notebook权益。
- ModelScope发放超时可使用相同幂等键安全重试。
- 用户不能绕过Gateway直连Space。
- SSE不被缓冲，最终usage能够记录。
- 多Space重试不会向用户输出重复内容。
- Redis重启不丢免费次数、ADP和Notebook状态。
- 任一Gateway Pod重启不影响权威状态。

## 15. 推荐代码仓库结构

```text
amd-llm-platform/
  services/
    access-gateway/
    policy-service/
    space-router/
    entitlement-worker/
  packages/
    contracts/
    database/
    observability/
    test-fixtures/
  deploy/
    docker-compose/
    helm/
  docs/
    architecture/
    modelscope-contracts/
    adp-contracts/
    runbooks/
  tests/
    integration/
    load/
    security/
```

LLMGateway建议作为外部依赖使用官方镜像/Helm Chart，不将完整上游源码复制进AMD业务仓库。

## 16. 参考资料

- GitHub：<https://github.com/theopenco/llmgateway>
- Custom Providers：<https://docs.llmgateway.io/features/custom-providers>
- Routing：<https://docs.llmgateway.io/features/routing>
- Rate Limits：<https://docs.llmgateway.io/resources/rate-limits>
- Error Handling：<https://docs.llmgateway.io/resources/error-handling>
- Self-host：<https://docs.llmgateway.io/self-host>
- Kubernetes：<https://docs.llmgateway.io/self-host/kubernetes>
- API Keys：<https://docs.llmgateway.io/features/api-keys>

## 17. 交接后的第一步

在另一台机器上不要立即开发全部业务。先完成以下三个Spike：

1. 部署原版LLMGateway，并将Custom Provider指向HY4。
2. 验证Access Gateway放在LLMGateway之前时的SSE、usage和Header透传。
3. 与ModelScope确认Space防绕过、稳定subject和Notebook发放接口是否可提供。

三个Spike未通过前，不进入完整MVP开发。

## 18. 当前 Python 实现

当前实现集中在 `apps/control-api-python`，使用 FastAPI、asyncpg、Redis 和 httpx。
详细模块说明及状态机见 [`apps/control-api-python/README.md`](apps/control-api-python/README.md)。

```text
apps/control-api-python/
  app/
    main.py              # HTTP API、用户接口和管理接口
    proxy.py             # 三种 OpenAI API 的流式/非流式代理
    lifecycle.py         # logical request、attempt、免费额度和 usage 事务
    auth.py              # ModelScope OIDC、Mock 身份和用户 API Key
    rate_limit.py        # Redis Lua RPM 与并发 lease
    protocol.py          # Chat/Completions/Responses 输出和 usage 归一化
    usage_delivery.py    # PostgreSQL outbox 到 ModelScope 的异步交付
    module_contracts.py  # 模块、Hook、Event 和模型池协议
    config_reconciler.py # generation 配置 Watch 与原子切换
    module_manager.py    # 独立模块服务 Hook/Event 调度
    module_events.py     # 模块事件持久 Outbox 与重试
    router_data.py       # 永久 Router URL 只读数据接口
    router_admin.py      # Router 动态 Worker API
    worker_reconciler.py # workers.d 热注册和摘除
    worker.py            # usage worker 进程入口
  migrations/
Dockerfile
docker-compose.yml
deploy/k8s/
```

上游 LLMGateway 使用固定版本
[`v1.11.0`](https://github.com/theopenco/llmgateway/releases/tag/v1.11.0)。终端用户凭证不会被转发；
Control API 只使用 `LLMGATEWAY_SERVICE_API_KEY` 调用内网 LLMGateway。

### 18.1 已实现能力

- `POST /v1/chat/completions`、`POST /v1/completions`、`POST /v1/responses`；
- `GET /v1/models`，并按 API Key 的模型白名单过滤；
- 服务端生成 `logical_request_id`，每次上游调用生成独立 `attempt_id`；
- 请求、attempt、错误、客户端断开和 Token usage 全部持久化；
- Chat Completions、Completions 和 Responses usage 字段归一化；
- 缺失 Token 保存为 `NULL` 且 `usage_status=UNKNOWN`，不会伪装成零；
- PostgreSQL outbox 始终记录 usage 投递任务，worker 使用 `event_id` 幂等上报；
- 用户 API Key 签发、列表和立即撤销，数据库仅保存 HMAC；
- ModelScope users/me 临时账号解析、OIDC/JWKS 验证以及开发环境 Mock 身份；
- ADP OAuth 关联页、Authorization Code callback、UserInfo `sub` 唯一绑定和状态查询；
- 每用户跨模型共享 10 次免费额度，使用 PostgreSQL 行锁保证并发正确性；
- GLOBAL、MODEL、PRINCIPAL、API_KEY 四级 RPM 和并发策略同时生效；
- 流式首输出前断开释放免费次数，首输出后断开继续读取 usage，最长 150 秒；
- 请求体上限、安全响应头、生产配置校验、非 root 及只读容器配置；
- Compose 与 Kubernetes 中独立运行 Control API、迁移任务和 usage worker。

第一版不会自动重试 LLMGateway。`attempt_id` 只表示 Control API 到 LLMGateway 的一次调用，
无法代表 LLMGateway 或下游 Router 内部不可见的重试。

### 18.2 启动前置条件

复制配置并替换所有占位密码和密钥：

```bash
cp .env.example .env
openssl rand -base64 48
```

还需要先在 LLMGateway 管理界面创建仅供 Control API 使用的内部 service key，并写入：

```text
LLMGATEWAY_SERVICE_API_KEY=<internal-llmgateway-key>
```

启用 ADP OAuth 时还需要由 OAuth 团队签发并注入：

```text
ADP_ADAPTER=oauth
ADP_OAUTH_CLIENT_ID=<ADP 签发的 client_id>
ADP_OAUTH_CLIENT_SECRET=<ADP 签发的 client_secret>
ADP_OAUTH_REDIRECT_URI=https://amd-model-api-oauth.anruicloud.com/v1/integrations/adp/oauth/callback
ADP_OAUTH_RETURN_URL=https://modelscope.cn/studios/AMD_Dev/LLM-Gateway
ADP_OAUTH_PKCE_ENABLED=false
ADP_OAUTH_SESSION_TTL_SECONDS=600
OAUTH_BFF_SHARED_SECRET=<Gateway 与 OAuth BFF 共享的独立随机密钥>
OAUTH_BFF_MAX_CLOCK_SKEW_SECONDS=60
```

ModelScope 单容器部署从平台 Secrets 读取 `ADP_OAUTH_CLIENT_ID` 和
`ADP_OAUTH_CLIENT_SECRET`。Client Secret 不得写入 `ms_deploy.json`、Git、镜像或日志。

当前开放验证阶段由 ModelScope supervisor 强制使用：

```text
RATE_LIMIT_ENABLED=false
TRIAL_ENFORCEMENT_ENABLED=false
ADP_ADAPTER=disabled
```

因此普通用户不会因 RPM、并发、10 次试用或 ADP 待关联状态被阻挡；请求、attempt、试用计数
和 usage 仍照常写入，`SUSPENDED` 与 `REVIEW` 仍然拒绝访问，Router/Worker 不受影响。身份验证
完成后可恢复这些开关并重新启用已有 ADP 凭证。

ModelScope API Edge 会在请求到达 Python 前检查 Bearer Token，而 ADP 的浏览器
callback 不会携带 ModelScope Token。生产环境因此使用独立的 OAuth BFF：

```text
https://amd-model-api-oauth.anruicloud.com
```

BFF 只公开 `connect`、`oauth/start`、`oauth/callback` 和结果页；它不接收
`principal_id`。Gateway 先在已认证的 ModelScope 请求中创建绑定 principal 的
一次性 ticket，BFF 再使用带时间戳和一次性 nonce 的 HMAC 签名调用 Gateway
内部 OAuth 接口。ADP Client Secret、Token 交换、UserInfo 和身份绑定仍只在
Gateway 中执行。详见 `docs/oauth-bff-deployment.md`。

开发环境允许该值暂时为空；`NODE_ENV=production` 时为空会拒绝启动。生产环境还会拒绝
Mock 身份、Mock ADP/Notebook adapter、短密钥及示例数据库密码。

Compose 启动命令：

```bash
docker compose up --build
```

`control-api` 启动命令会先执行数据库迁移，随后启动 Uvicorn；`usage-worker` 使用同一镜像和
数据库。如果 `USAGE_DELIVERY_URL` 为空，worker 保持空闲，已经写入 outbox 的事件不会丢失。

入口：

```text
http://localhost:3002  LLMGateway UI
http://localhost:4002  LLMGateway 管理 API
http://localhost:4100  AMD Control API
```

Kubernetes 清单：

- `deploy/k8s/local-test.yaml`：本地镜像、单副本测试环境；
- `deploy/k8s/production.yaml`：多副本、HPA、PDB、NetworkPolicy 和独立 worker；
- `deploy/k8s/production-secret.example.yaml`：生产 Secret 字段示例。

### 18.3 ModelScope 身份核对、Mock 用户与 API Key

线上账号可用以下只读接口确认当前身份解析结果，不返回或保存原始 Token：

```bash
curl https://studio-amd-dev-llm-gateway.api-inference.modelscope.net/v1/account/me \
  -H "Authorization: Bearer $MODELSCOPE_SDK_TOKEN"
```

当前 ModelScope 单容器部署临时使用 `MODELSCOPE_ADAPTER=edge` 和
`TRUST_MODELSCOPE_EDGE=true`。ModelScope API Edge 先验证调用方，Gateway 将转发凭证的 HMAC
映射为内部 principal，不会把原始凭证写入数据库。该模式优先保证现阶段用户请求可用；由于
它按凭证而不是按自然人识别，同一用户多个 Token 的合并需等待 ModelScope 提供稳定身份信息。

开发环境可以直接用稳定 Mock subject 登录。首次请求会创建内部 principal：

```bash
curl -X POST http://localhost:4100/v1/api-keys \
  -H 'Authorization: Bearer mock:mock-modelscope-subject' \
  -H 'Content-Type: application/json' \
  -d '{"name":"local-client","allowed_models":[]}'
```

响应中的 `api_key` 只展示一次，格式为 `amd_sk_<id>_<secret>`。也可以由管理员先创建 Mock
用户，再通过 `/v1/admin/api-keys` 为返回的 principal ID 签发 Key：

```bash
curl -X POST http://localhost:4100/v1/admin/principals/mock \
  -H "X-Admin-Key: $CONTROL_ADMIN_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"subject":"mock-modelscope-subject","username":"developer"}'
```

### 18.4 三种推理接口

以下示例中的 `$AMD_API_KEY` 是 Control API 签发的用户 Key，不是 LLMGateway 内部 service key。

```bash
curl -X POST http://localhost:4100/v1/chat/completions \
  -H "Authorization: Bearer $AMD_API_KEY" \
  -H 'Idempotency-Key: chat-example-0001' \
  -H 'Content-Type: application/json' \
  -d '{"model":"amd-router/hy4-preview","messages":[{"role":"user","content":"hello"}],"stream":true}'

curl -X POST http://localhost:4100/v1/completions \
  -H "Authorization: Bearer $AMD_API_KEY" \
  -H 'Idempotency-Key: completion-example-0001' \
  -H 'Content-Type: application/json' \
  -d '{"model":"amd-router/hy4-preview","prompt":"hello","stream":false}'

curl -X POST http://localhost:4100/v1/responses \
  -H "Authorization: Bearer $AMD_API_KEY" \
  -H 'Idempotency-Key: response-example-0001' \
  -H 'Content-Type: application/json' \
  -d '{"model":"amd-router/hy4-preview","input":"hello","stream":true}'
```

`/v1/responses` 保持原协议和路径透传，不转换成 Chat Completions；最终能否调用取决于当前
LLMGateway 版本及其目标 Provider 是否支持 Responses API。

成功响应包含 `X-Request-ID` 和 `X-Attempt-ID`。客户端提供的 `X-Request-ID` 仅作为
`client_request_id` 保存，不能覆盖服务端权威 ID。

### 18.5 Usage 与限流策略

用户可查看自己的明细和按模型汇总：

```bash
curl http://localhost:4100/v1/usage \
  -H "Authorization: Bearer $AMD_API_KEY"

curl http://localhost:4100/v1/usage/summary \
  -H "Authorization: Bearer $AMD_API_KEY"
```

管理员可创建全局、模型、用户或 API Key 规则。所有匹配规则同时生效；模型规则不会覆盖
全局规则。默认规则为每个 principal 60 RPM、2 并发；使用 API Key 时还同时应用该 Key 的
60 RPM、2 并发规则。

```bash
curl -X POST http://localhost:4100/v1/admin/limit-policies \
  -H "X-Admin-Key: $CONTROL_ADMIN_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"scope_type":"MODEL","scope_value":"amd-router/hy4-preview","metric":"RPM","limit_value":120,"window_seconds":60}'

curl -X POST http://localhost:4100/v1/admin/limit-policies \
  -H "X-Admin-Key: $CONTROL_ADMIN_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"scope_type":"GLOBAL","scope_value":"*","metric":"CONCURRENCY","limit_value":100}'
```

### 18.6 尚需外部确认或后续接入

- ModelScope `users/me` 当前只返回 username；仍需正式不可变 subject、issuer/audience/JWKS
  或等价的可信身份合同；
- LLMGateway v1.11.0 对 `/v1/responses` 及目标 Provider 的实际兼容性；
- ModelScope usage 接收接口的最终 URL、鉴权方式和对账合同；
- ADP OAuth 已在 Python Control API 实现；正式 Client 凭证、callback 登记和公网匿名回跳
  联调待完成；
- ADP 签名事件与 Notebook 权益发放业务流程仍待外部合同；
- Router 正式镜像、Worker 服务凭证和多 Space 调度算法；
- Space 私网、mTLS 或服务 Token 等防绕过措施。

## 19. 可热更新模块与 Worker 注册

Python Control API 是唯一生产实现。`apps/control-api` TypeScript 目录只保留为历史原型，
不得再新增生产功能。

### 19.1 运行边界

```text
client
  -> Gateway Core
       -> 同步 Module Hook（身份、ADP、限流、模型策略）
       -> LLMGateway
       -> 每模型独立 vLLM Router
            -> 同模型 Worker 池
       -> 生命周期 Event（监控、usage、Dashboard）
```

Router、Gateway Core 和每个模块使用独立 Deployment/进程。更新某一模块不会重启
Gateway 或 Router。模块不能通过 `importlib.reload` 注入 Gateway 进程。

### 19.2 平台配置热加载

配置 schema 示例：

```text
config/platform.example.json
```

主要环境变量：

```text
MODULE_CONFIG_PATH=/config/platform/platform.json
MODULE_CONFIG_POLL_SECONDS=2
MODULE_CONFIG_REQUIRED=true
```

配置必须携带递增的 `generation`。Config Reconciler 执行完整 schema 校验和模块健康检查后
才原子切换；非法配置、generation 回退或同 generation 内容变化均不会覆盖上一版本。

模块可以声明三类数据源：

- `gateway_hooks`：同步身份、ADP、限流和模型策略；
- `lifecycle_events`：请求开始、路由、首 Token、完成、失败和断开事件；
- `router_api`：按 `router_ref` 调用永久 Router 的 health、models、metrics、load 和
  workers 只读接口。

Router 凭证和模块凭证只引用环境变量名，不写入配置文件。

管理接口：

```text
GET  /v1/admin/modules
POST /v1/admin/modules/reload
GET  /v1/admin/routers
GET  /v1/admin/routers/{router_ref}/{scope}
GET  /v1/internal/routers/{router_ref}/{scope}
```

### 19.3 Worker 文件热注册

Worker 文件 schema 示例：

```text
config/workers.example.d/glm-worker3.json
```

生产挂载目录：

```text
WORKER_CONFIG_DIR=/config/workers.d
WORKER_CONFIG_POLL_SECONDS=2
WORKER_HEARTBEAT_SECONDS=30
WORKER_CONFIG_RECONCILE_ENABLED=true
```

新增 `worker3.json` 后，Reconciler 会：

1. 校验 pool、model、URL、凭证引用和 schema；
2. 请求 Worker `/health` 与 `/v1/models`；
3. 幂等 upsert 数据库记录；
4. 调用对应 Router 的 `POST /workers`；
5. 周期性确认 Worker 和 Router 注册状态。

删除或禁用文件时，Worker 先进入 `DRAINING`，从 Router
`DELETE /workers/{url}` 摘除，再进入 `REMOVED`。该过程不修改、不重启 LLMGateway。

管理接口：

```text
GET  /v1/admin/worker-reconciler
POST /v1/admin/worker-reconciler/reload
```

### 19.4 多模型池

每个模型家族拥有独立三层地址：

```text
PUBLIC_MODEL_BASE_URL -> ROUTER_BASE_URL -> WORKER_ADVERTISE_URL[]
```

例如 GLM Worker 只能注册到 GLM Router；未来 DeepSeek 使用独立 Router2 和公开 URL。
`deploy/k8s/routers.yaml` 提供 GLM Router 的独立 Deployment/Service 模板。

### 19.5 更新和回滚

模块服务应采用版本化镜像和独立滚动部署。配置更新顺序：

1. 部署新模块版本；
2. `/healthz` 通过；
3. 提升 platform config `generation` 并切换 endpoint/version；
4. 观察 shadow 模式；
5. 切换到 enforce；
6. drain 并删除旧模块。

如果新模块健康检查失败，Config Reconciler 保留上一 generation。
