# 数据模块 ## 1. 定位 数据模块是 `cpa-ext` 的统一数据语言。它只回答三件事: 1. 系统需要从 CLIProxyAPI(CPA)及相关来源关注什么数据; 2. 这些数据在插件内部以什么稳定语义流转; 3. 计费、统计、Key 管理、配额、审计和展示模块可以得到什么输出。 数据模块不负责采集回调、计算费用、执行扣款、生成统计图或决定界面权限。具体模块负责产生和消费数据,数据模块只定义契约。 本模块以两个已经实际工作的项目为需求来源: - `cpa-plugin-key-billing` 已经拿到并使用的数据,全部视为我们需要关注的数据; - `cpa-usage-keeper` 已经拿到并使用的数据,全部视为我们需要关注的数据; - 两者已经对外提供的数据产品,全部纳入我们的输出能力全集; - 第一版可以只实现其中一部分,但数据抽象不能阻止以后补齐其余能力。 ## 2. 两个参考项目的数据总结 ### 2.1 `cpa-plugin-key-billing` #### 它拿到的数据 | 数据域 | 实际取得的内容 | 主要来源 | | --- | --- | --- | | 下游调用身份 | `caller_scope`、Key 脱敏预览、标签、Key 是否仍在 CPA 配置中 | 请求 metadata、管理端同步的 CPA Key 列表 | | 请求关联 | `request_id`、入口 endpoint、请求格式、上游格式、流式标记、是否真实生成 | request interceptor、request lifecycle | | 模型 | 用户/路由模型、实际上游模型、稳定 billing model | 请求阶段、响应阶段 | | 上游凭证 | `auth_index`、provider、auth type、OAuth 账户或脱敏 provider API Key | CPA usage callback | | 请求终态 | 正常、失败、取消、拒绝 | request lifecycle | | 原始用量 | OpenAI/Codex、Claude、Gemini、Interactions 等响应中的 usage 和 response ID | 翻译前响应、非流式响应、流式 chunk | | 标准 Token | 非缓存输入、缓存读取、缓存写入、普通输出、推理输出、总量、无法分类数量 | 自身规范化逻辑 | | 数据质量 | complete、inconsistent、unclassified、未观察到用量 | 自身规范化逻辑 | | 价格 | 模型匹配规则、每百万 Token 各分项价格、长上下文门槛与价格、价格来源 | 内置 models.dev 目录、管理员覆盖 | | 套餐与周期 | 套餐 ID/名称、美元额度、日/周/月/自定义周期、周期开始与结束 | 插件配置与持久化状态 | | 请求准入快照 | 请求进入时绑定的 Key、套餐和周期 | 请求 interceptor | 它没有把 CPA 的 `usage_plugin` 当作完整用量事实:该回调缺少 `RequestID`,所以项目主要从带有请求关联信息的响应 hook 中恢复权威 Token,再在终态回调结算;`usage_plugin` 主要用来补充上游凭证身份。 #### 它存储的数据 - 价格规则、套餐、Key 状态、上游凭证目录; - Key 的当前周期和生命周期累计; - 按模型累计; - 最近 30 天请求日志; - 每次请求的标准 Token、金额分项、价格来源、长上下文状态和数据质量; - 运行时未定价、无 Token、无法分类等诊断计数。 #### 它输出的数据 | 输出域 | 已提供的数据 | | --- | --- | | 请求控制 | 放行或拒绝,额度耗尽时返回明确的 HTTP 错误 | | Key 目录 | Key 标识/预览/标签、套餐、是否无限、是否阻断、周期额度、已用、使用率、周期结束时间 | | 套餐管理 | 套餐及金额、周期,绑定、解绑和重置 | | 请求账目 | 时间、Key、请求 ID、endpoint、上游凭证、模型、结果、Token、金额、价格来源和核算质量 | | 汇总 | Key 数量、阻断数量、生命周期请求/Token/金额、按模型汇总 | | 价格管理 | 当前模型价格、自定义覆盖、价格刷新结果 | | 运维诊断 | 持久化状态、配置状态、未定价/缺失/无法分类计数 | ### 2.2 `cpa-usage-keeper` #### 它拿到的数据 | 数据域 | 实际取得的内容 | 主要来源 | | --- | --- | --- | | 用量事件 | provider、endpoint、auth type/index、request ID、API Key、模型/别名、请求与响应 service tier、reasoning effort、executor、时间、失败、延迟、TTFT、完整 Token 分项 | CPA Redis usage queue / HTTP usage queue | | 请求环境 | client IP、X-Forwarded-For、User-Agent、source、API group key | CPA usage payload | | 下游 Key | 完整 CPA API Key、展示值、别名、删除/同步状态 | CPA Management API | | 上游 Auth File | auth index、名称、文件、email、provider、label、状态、priority、disabled、note、account/project、订阅起止与 plan type | CPA Management API | | Provider API Key 配置 | provider 类型、名称、prefix、base URL、lookup key 等归一化元数据 | CPA 各 provider management API | | 模型目录 | 模型 ID、owner、创建时间 | CPA `/v1/models` | | 上游配额 | Codex 主/次窗口、允许状态、使用率、重置时间、额外限制、reset credits;以及其他 provider 的 quota | CPA 代调用各 provider API | | 上游订阅 | Codex plan、tier、有效期;其他 provider 的订阅/账户资料 | auth metadata、provider API | | 请求日志 | 按 request ID 获取的请求日志、预览与下载数据 | CPA request-log management API | | 价格 | 模型价格快照、条件规则、service tier、reasoning、模型、Key、auth、endpoint、executor 等价格维度 | models.dev、管理员配置 | #### 它存储的数据 - 原始 Redis inbox 及处理/重试状态; - 标准化的逐次用量事件和归档; - CPA API Key 与上游身份目录; - 模型价格设置、同步快照和条件价格规则; - 小时/日 overview、activity、latency、identity 等预聚合及各自 checkpoint; - 配额快照和应用设置; - 排名等派生结果。 #### 它输出的数据 | 输出域 | 已提供的数据 | | --- | --- | | Overview | 请求数、Token、金额、RPM、TPM、日均值、缓存命中率和时间序列 | | 实时状态 | Token 速度、请求速度、TTFT/Latency P50/P95、响应分布、当前模型/Key/Auth Top、缓存水平 | | 事件明细 | 可分页筛选的逐请求记录,包含模型、身份、结果、Token、金额、延迟和请求日志入口 | | Analysis | Token 时序、模型使用、Key/模型/Auth 构成、Key×模型热力图、金额分项、模型效率 | | Activity | 日/周/月/年活动格、成功失败、成功率和各 Token 分项 | | 身份目录 | 下游 Key 与上游账号/provider 的元数据、别名、启停状态、订阅和累计使用情况 | | 配额 | 各上游账号的窗口、使用率、剩余、重置时间、订阅层级和 reset credits | | 价格 | 当前价格、价格来源、同步状态、条件规则及预览 | | 排名与导出 | 本地排名、事件导出、请求日志查看/下载 | ## 3. 我们的数据关注全集 两个项目的数据取并集后,数据模块定义以下九个数据域。这里的“需要”表示契约需要容纳,不表示所有字段都必须在 MVP 首日采集完成。 ### 3.1 请求与执行 必须同时表达两个层级: - `Request`:一次下游用户请求; - `Execution`:一次 CPA after-auth 可观察到的上游逻辑执行段。 一个 Request 可以因为重试、故障切换或路由产生多个 Execution。Execution 不保证等于一个物理 HTTP dispatch:当前 CPA 在 auth refresh 后的内部重发不会再次触发 after-auth,应以 `subattempt_count/observability=partial` 表达,而不是伪造额外 Execution。未来宿主提供 dispatch lifecycle 时再增加 `DispatchAttempt`。 关注字段: - request/event/trace/execution ID; - endpoint、source、source format、upstream format; - requested model、routed model、upstream model、model alias; - stream、generate、reasoning effort; - requested、reported、effective service tier; - requested、started、first-token、completed 时间; - outcome、HTTP status、标准错误类别; - settlement status(与请求 outcome 分开,允许 canceled/failed 请求在迟到 Usage 后结算); - latency、TTFT; - 重试序号及最终尝试标记。 ### 3.2 下游身份 关注字段: - account/member ID; - credential ID; - credential secret version/public ID/HMAC key ID/status; - 自管 Key 的 public key ID、HMAC digest、digest secret version; - CPA 根据 Principal 派生的稳定 caller scope(仅用于请求期关联,不作为业务主键); - 兼容模式下 CPA 原生 API Key 的稳定 scope/hash; - 脱敏预览、别名、标签; - 启用、删除、同步状态; - 套餐/计费账户绑定。 自管 Key 的完整值只在创建/轮换成功时展示一次,持久层只保存摘要;CPA 原生 Key 即使在兼容迁移模式由 CPA 配置持有,也不得复制进插件事件、日志或跨模块消息。 ### 3.3 上游身份 关注字段: - auth ID/index/type; - provider、executor type; - 名称、别名、email/account、文件标识; - provider prefix、base URL、lookup key; - priority、disabled、status、note; - account/project ID; - plan/tier 和订阅有效期。 秘密凭证不属于数据流转契约。 ### 3.4 用量 统一为互不重叠、跨 provider 可比较的字段: - uncached input; - cache read; - cache creation/write; - non-reasoning output; - reasoning output; - total; - unclassified; - generate 标记。 同时保留规范化质量:`complete`、`normalized`、`partial`、`inconsistent`、`unclassified`、`missing`,以及修正动作/异常代码。原始 provider 计数可以作为受限审计快照保存,但不能直接成为跨模块语义。 ### 3.5 金额与价格 关注字段: - settlement currency; - 金额的定点整数值; - 未缓存输入、缓存读取、缓存写入、输出的金额分项; - base amount、final charged amount; - 每百万 Token 的实际应用价格; - service tier / Fast multiplier; - 长上下文门槛及是否命中; - price version、source、effective time; - 价格是否可用及不可用原因; - billing record ID、request/execution ID、usage revision; - charge、late settlement、credit、refund、adjustment 类型及被修正记录; - pending/awaiting usage/settled/unmeasured/inconsistent 状态; - occurred at 与 booked at。 用户侧核心金额只使用最终结算金额;Token、价格与倍率是后台审计数据。 ### 3.6 下游套餐、周期与余额 关注字段: - plan、额度金额和周期规则; - BillingAccount 与 plan 的绑定、Credential → BillingAccount 绑定; - cycle start/end; - limit、spent、remaining; - blocked/allowed; - 管理员调整及其原因; - 请求准入时使用的周期快照。 ### 3.7 上游配额与订阅 它和我们分配给用户的金额额度是不同概念,必须分开表达。 关注字段: - provider/account; - plan/tier、订阅起止; - primary/secondary/additional windows; - used、limit、remaining、percentage; - allowed、limit reached; - window duration、reset at/after; - window usage token/cost; - reset credit 数量、状态和过期时间; - snapshot time 和数据新鲜度。 ### 3.8 模型、价格与配置目录 关注字段: - CPA 可用模型及 owner; - 模型别名和路由映射; - provider/model 对应关系; - 价格目录、管理员覆盖和条件规则; - Key、auth、provider 的当前配置事实; - 同步时间、来源、版本和删除状态。 ### 3.9 诊断、日志与数据质量 关注字段: - schema version、producer version; - source/provenance、observed at、persisted at; - idempotency key; - 缺失、修正、冲突、未定价状态; - inbox/checkpoint/retry/archive 状态; - 请求日志引用和受控下载信息; - 原始数据哈希或受限审计引用。 Prompt、模型完整响应、Authorization、Cookie、OAuth token 和 provider API Key 默认不进入通用数据契约。 ## 4. 核心抽象 数据模块不要求所有内容塞进一张表。它定义以下稳定对象,存储模块可以分别落库。 ```text DownstreamAccount └─ BillingAccount └─ DownstreamCredential (1..N) ├─ CredentialSecretVersion (1..N) └─ Request └─ Execution (1..N) ├─ Usage ├─ Outcome & Performance └─ UpstreamIdentity Usage + PricingSnapshot └─ BillingRecord └─ LedgerEntry Usage / BillingRecord / Identity / QuotaSnapshot └─ Aggregate & View ``` ### 4.1 `RequestRecord` 用户视角的一次请求。保存下游身份、入口、请求模型、最终结果、时间以及所有 Execution 的关联,不直接猜测每次上游尝试的细节。 ### 4.2 `ExecutionRecord` 一次 CPA after-auth 可观察到的逻辑执行段。保存上游凭证、provider、实际模型、tier、格式、终态、性能、重试关系和 observability;不能宣称等于每个物理 HTTP dispatch。 ### 4.3 `UsageRecord` 一次 Execution 对应的标准 Token 事实,包含质量和来源。Request 层用量由明确规则合并,不覆盖底层执行事实。当前 CPA 对部分 Usage 缺少 RequestID/AttemptID,无法可靠归属时必须保持 `unmeasured`/未关联事实,不能按时间或上游账户猜配。 ### 4.4 `BillingRecord` 计费模块对 Usage 应用价格后产生的不可变金额事实。它引用 Usage revision 和价格快照,迟到结算、退款或人工修正通过新增记录表达;金额一旦入账,统计模块不得重新计算并改写历史。 ### 4.5 `IdentityRecord` 统一承载下游 Key/账户和上游 auth/provider 的非秘密身份信息,但通过明确的 identity kind 区分,不能只靠一个字符串猜类型。 ### 4.6 `QuotaSnapshot` 某个上游身份在某一时刻的 provider 配额和订阅快照。它是时间点事实,不覆盖历史;也不与下游金额余额混用。 ### 4.7 `CatalogSnapshot` 模型、价格、路由和配置目录的带版本快照,使历史请求可以解释当时使用的模型和价格。 ### 4.8 `AggregateRecord` 从不可变事实派生的小时、日、活动、延迟、身份和排名数据。它可以删除并重建,不是账本真相。 ### 4.9 `ProjectionEvent` 跨 Request、Usage、Billing、Ledger 多表的统一追加变化流。它携带单调 `event_seq`、唯一 `event_id`、kind、fact ID/revision、occurred/booked time、可选 supersedes ID 和确定性统计差量。领域事实与对应 ProjectionEvent 必须在同一事务提交;统计 projector 只按这条流推进,不能比较各表互不相关的自增 ID。 ## 5. 统一数据元信息 所有可持久化的事实对象都应携带或可追溯到: | 字段 | 含义 | | --- | --- | | `schema_version` | 数据契约版本 | | `event_id` | 全局稳定事件 ID | | `idempotency_key` | 重复回调或重放时去重 | | `source` | CPA callback、response hook、Redis、Management API、provider API 等 | | `observed_at` | 来源数据被观察到的时间 | | `occurred_at` | 业务事实真实发生时间 | | `persisted_at` | 成功落库时间 | | `quality` | 完整、修正、部分、矛盾、缺失等 | | `producer_version` | 产生该规范化数据的插件版本 | 字段缺失必须保持“未知”,不能自动等价为零、空字符串或成功。 ## 6. 数据模块输出契约 数据模块定义四类输出,具体模块按需实现和消费。 ### 6.1 事实输出 - Request、Execution、Usage、Billing、Ledger、ProjectionEvent; - Downstream/Upstream Identity; - Quota、Subscription、Catalog 快照; - 数据质量和诊断事件。 ### 6.2 查询输出 - 逐请求和逐执行明细; - Key、账户、上游身份、模型和价格目录; - 套餐、周期、余额和账本; - 配额、订阅和重置时间; - 请求日志的受控引用。 ### 6.3 聚合输出 完整能力集合至少容纳: - 请求、Token、金额、RPM、TPM; - 时间序列和日均; - 成功率、错误分布; - TTFT/Latency 分位数和分布; - 缓存读取率; - 模型、Key、账户、Auth、Provider 构成及 Top; - Key×模型热力图; - 金额分项和模型效率; - 活动格、排名和历史对比。 ### 6.4 控制输出 - 请求允许/拒绝及稳定错误码; - Key、套餐、价格和绑定的变更结果; - 配额刷新/重置结果; - 同步、归档、重建和诊断结果。 控制输出由业务模块决定,数据模块只定义其可交换的数据形状。 ## 7. 模块边界 | 模块 | 对数据模块的关系 | | --- | --- | | CPA 适配层 | 把 CPA 回调、响应和 Management API 数据翻译成标准事实 | | 存储模块 | 按契约持久化、查询、去重、归档和迁移 | | Core/Key 模块 | 维护下游身份、金额账户和绑定关系 | | 上游账户模块 | 维护 CPA Auth 引用、priority、bindability 和 Quota/Subscription 快照 | | 价格模块 | 维护不可变 PriceVersion、模型 alias 和计价政策 | | 计费模块 | 消费 Usage、Pricing、Plan,输出 Billing 和 Ledger | | 统计模块 | 消费不可变事实,输出可重建的 Aggregate | | API/UI 模块 | 将同一数据按用户或管理员权限投影,不发明新的业务事实 | 数据模块自身: - 不依赖 UI; - 不依赖某一种数据库; - 不调用 CPA; - 不计算价格; - 不聚合统计; - 不保存或传播秘密; - 不把参考项目现有字段名直接当成永久领域语义。 ## 8. 如何参考两个项目 ### 8.1 从 `cpa-plugin-key-billing` 参考 重点查看: - `internal/plugin/usage_tracker.go`:请求与响应用量关联; - `internal/plugin/upstream_usage.go`:跨 provider Token 语义; - `internal/billing/pricing.go`:标准 Token、数据质量、价格和金额分项; - `internal/billing/account.go`:终态用量到费用记录; - `internal/billing/state.go`:价格、套餐、Key、周期、累计和日志; - `internal/billing/log.go`、`keys.go`:请求账目和管理输出; - `internal/billing/credentials.go`:上游凭证的安全展示身份。 吸收它对请求关联、Token 不重叠、质量显式化、请求准入周期快照和上游身份脱敏的定义;不要继承其 JSON 大状态、浮点余额、30 天日志限制和把重试只保留为最终 provider usage 的数据损失。 ### 8.2 从 `cpa-usage-keeper` 参考 重点查看: - `internal/entities/usage_event.go`:逐请求事件字段; - `internal/entities/usage_identity.go`、`cpa_api_key.go`:身份目录; - `internal/service/sync.go`:用量规范化、来源和 inbox 处理; - `internal/service/tokenprocessor/`:Token 修正与质量判断; - `internal/pricing/`、`internal/entities/model_price_*`:价格快照与条件; - `internal/quota/`:上游配额、订阅和 provider 抽象; - `internal/service/dto/usage.go`、`analysis.go`、`usage_activity.go`:查询和分析输出; - `internal/repository/usage*.go`:明细、聚合、checkpoint 和归档。 吸收它完整的事件、身份、配额、价格、实时和分析数据面;不要把其 Redis/HTTP 拉取方式、明文 CPA Key 存储方式、GORM 实体或动态历史费用重算直接变成我们的领域契约。 ## 9. 本模块的完成标准 - 两个参考项目实际使用的输入数据均能映射到本模块的数据域; - 两个参考项目实际提供的输出均能由本模块的事实或派生数据表达; - Request 与 Execution、下游额度与上游配额、Usage 与 Billing、事实与聚合均明确分离; - 未知、缺失、零和失败具有不同语义; - 所有金额可使用定点整数,所有历史收费可追溯到价格快照; - 所有秘密字段均被排除或明确限定在受保护配置边界; - 后续模块只能扩展契约版本,不能用自己的私有字段重新定义同一个业务事实。