MQTT 与用量事件协议
MQTT 承载三类消息:
| 方向 | Topic | QoS | 功能 |
|---|---|---|---|
| ScootGate → broker | {prefix}/usage | 1 | 非权威用量事件 |
| broker → ScootGate | {prefix}/auth/notify | 1 | 授权快照更新提示 |
| broker → ScootGate | {prefix}/routing/notify | 1 | 路由快照更新提示 |
默认 prefix 为 scootgate。ScootGate 使用 clean session,每次连接后重新订阅
两个 notify topic;keep-alive 为 30 秒,连接错误后退避 1 秒重连。
Broker URL
支持:
mqtt://host[:port]、tcp://host[:port](默认 1883);mqtts://host[:port]、ssl://、tls://、tcps://(默认 8883)。
URL 不允许 path。生产使用 TLS scheme 和专用服务身份。
快照通知
两个 notify topic 使用同一 payload:
{"version": 42}
约束:
- JSON body 最大 4 KiB;
version为 u64;- 只有严格大于本地当前版本才触发拉取;
- malformed、重复、旧版本和乱序通知被忽略;
- 通知只触发受认证 HTTPS 全量拉取,不直接修改状态;
- 500ms debounce 会合并突发通知;
- 通知丢失不影响正确性,定时轮询仍会发现版本。
控制面应使用 QoS 1 发布,不需要 retain;即使发生重复投递也必须保持幂等。
用量事件投递
请求完成后,事件先写入本地磁盘 outbox,再向 {prefix}/usage 发布:
- MQTT QoS 1;
retain = false;- 收到匹配 PUBACK 后删除 outbox 文件;
- broker 断线或进程重启后重放;
event_id在重放时保持不变;- 消费方必须按
event_id去重。
outbox 满或不可写时,ScootGate 按 drop_newest 丢弃新事件并增加健康计数,
但 Provider API 数据面继续服务。
Unix 平台上,outbox 目录权限为 0700,事件文件权限为 0600;写入采用临时文件、
fsync 和原子 rename。运维备份或诊断不得把这些文件上传到第三方系统。
Usage event schema 2
示例:
{
"schema": 2,
"event_id": "gate-a-1784700000000000000-17",
"instance_id": "gate-a",
"tenant_id": "tenant-1",
"key_id": "key-1",
"provider": "openai_compatible",
"path": "/v1/chat/completions",
"status": 200,
"outcome": "relayed",
"public_model": "chat-fast",
"upstream_model": "gpt-4o-mini",
"backend_id": "openai-primary",
"selection_attempts": 1,
"attempted_backend_ids": ["openai-primary"],
"failure_stage": null,
"send_state": "response_received",
"charge_state": "provider_reported",
"routing_version": 41,
"prompt_tokens": 12,
"completion_tokens": 7,
"request_body_bytes": 138,
"response_body_bytes": 512,
"first_byte_ms": 220,
"duration_ms": 640,
"ts_unix_ms": 1784700000000
}
字段
| 字段 | 类型 | 说明 |
|---|---|---|
schema | u32 | 固定 2 |
event_id | string | 稳定幂等键 |
instance_id | string | ScootGate 节点 |
tenant_id / key_id | string | 授权快照中的身份 |
provider | string | openai_compatible 或 anthropic_messages |
path | string | 客户端 Provider API 路径 |
status | u16 | 返回客户端的状态码 |
outcome | string | 请求结果分类 |
public_model | string/null | 客户端公共模型 |
upstream_model | string/null | 改写后的 Provider 模型 |
backend_id | string/null | 最后选择/尝试的 backend |
selection_attempts | u32 | 总选择次数 |
attempted_backend_ids | array | 按顺序记录尝试 |
failure_stage | string/null | 失败阶段 |
send_state | string | Provider 发送状态 |
charge_state | string | token/计费不确定性 |
routing_version | u64 | snapshot 版本;static 模式为表 fingerprint |
prompt_tokens / completion_tokens | u64/null | Provider 原生 usage |
request_body_bytes / response_body_bytes | u64 | relay 字节计数 |
first_byte_ms | u64/null | Provider 首包延迟 |
duration_ms | u64 | 请求总时长 |
ts_unix_ms | u64 | 事件创建 Unix 毫秒时间 |
send_state
| 值 | 含义 |
|---|---|
not_sent | 选择前拒绝,未尝试 backend |
definitely_not_sent | 尝试失败且能证明 Provider 请求字节未发出 |
possibly_sent | 请求可能到达 Provider,不能安全重试 |
response_received | 已收到 Provider 响应头 |
charge_state
| 值 | 含义 |
|---|---|
not_sent | Provider 未收到请求 |
unknown | 可能发送/已响应,但 Provider 未报告 token usage |
provider_reported | 至少一个 token 字段来自 Provider 原生 usage |
缺失 token 必须是 JSON null,不能根据字节数估算。
常见 failure_stage
selectionreselect_exhaustedegress_connectprovider_tlsprovider_writeresponse_headprovider_authnull(正常 relay 或 Provider 429 等已收到响应场景)
常见 outcome
relayedthrottledbad_requestunknown_modelunsupported_model_capabilityno_backend_availablebad_gatewaygateway_timeoutupstream_auth_rejected
数据最小化
事件和 outbox 明确不包含:
- 客户端原始 API key;
- Provider credential;
- Authorization / x-api-key header;
- prompt、messages、tools 或响应正文;
- Provider 完整 URL query;
- TLS key/certificate 内容。
该协议固定为非权威观测通道。需要权威计费时必须使用单独设计,不能仅依赖 MQTT QoS 1、磁盘 outbox 或此 schema。