Ctrl K
ComunidadGuías…

docs/MODEL_BILLING.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# 模型服务与套餐计费

本模块为可选的微型模型计费网关,从 Core 0.9.0 提供。[English](MODEL_BILLING.en.md)。管理员添加模型连接、选择参考价格,创建套餐并开通给账户;本地智能体继续决定如何思考、搜索工具、生成图片及执行业务动作。中枢不接管客户端编排,也不按用户轮次或任务步数扣费。

## 一个套餐,一份额度

例如,为团队开通一个售价 300 USD、每月额度 100 USD 的套餐,统一倍率为 1.5,勾选两个对话模型及一个图片工具。一次模型请求的参考费用为 0.20 USD,则套餐扣除 0.30 USD,实际剩余额度占比为 99.70%。摘要同时提供精确数值与用于界面的显示字符串;百分比显示可向下取整。不同模型和工具共用这份额度。

- **周期套餐**:`priceUsd` 是售价,`periodAllowanceUsd` 必须明确填写且大于零,是每个重置周期可用的美元额度;周期可为日、周、月。客户显示剩余百分比,分母为周期额度。
- **额度包**:`priceUsd` 是一次开通的美元额度,不接受 `periodAllowanceUsd`。客户显示积分,固定 1 USD = 1000 积分,无独立积分账本。
- `duration` 决定开通有效期,与重置周期分开;最后不足一个完整周期仍提供完整周期额度,到期停止,未用额度不结转。日、周、月继续按原开通时间及 UTC 锚点重置。支付、汇率、退款及商品定价归接入方负责。
- `multiplier` 是套餐统一倍率,不能为同一个套餐的不同模型配置另一套倍率。
- 已开通账户按套餐 ID 读取当前模型集合与倍率;售价、周期额度、周期和有效期在开通时固定。修改套餐金额用于以后开通,不会重置正在使用的账户余额。
- 删除套餐内某个模型不会删除账户额度。允许套餐没有可用模型,此时客户目录为空;删除套餐本身须先解除当前使用关系。已有真实结算的历史记录保留审计证据。

输入、输出和总 Token 数仍是原始用量字段,可供后台和使用明细查看;它们不是额度上限或百分比分母。没有提供 Token 的生图服务不伪造 Token 值。

## 参考价格

当前参考来源为 OpenRouter 的公开模型及图片端点价格目录,绑定精确 `modelId + endpointId`。实际模型调用仍使用管理员配置的模型凭证、URL 与型号。参考价格不表示其他服务商的实际账单,管理员应核对模型对应关系。

价格缓存六小时后由访问触发后台刷新,管理员也可以主动同步。暂时拉取失败时可使用已存价格并显示过期状态;成功刷新明确发现该端点规则不支持时拒绝新的计价请求,不能当作免费。每个请求保存自己的价格与套餐倍率快照;后来调价不会重算旧请求。

费用 = 供应商返回的实际计量 × 参考单价 × 套餐倍率。Token 单价、图片数量和支持的图片像素计价由适配器映射;无法可靠对应的计量保持 `billing_state=pending`,不会猜测零成本。供应商响应里的任意 `cost` 字段不能覆盖已保存的价格快照。

内部采用十二位小数定点金额。余额为正时允许新请求,实际费用异步结算;并发请求可能带来小额超出,记录超出金额并阻止余额耗尽后的新请求。不会为了实时结算打断正在输出的内容,也不会因上一笔待核算而禁止全部后续任务。

## 模型与工具

模型服务用途分为 `chat` 与 `tool`:

- 对话模型进入模型选择器,模型响应和流式片段直接交回本地宿主。
- 模型工具进入独立工具目录,包含名称、用途、参数、输出及可调用状态,由宿主注入并执行。
- 首批执行适配为阿里云 OpenAI Images 兼容接口、DashScope 原生接口及 OpenRouter Images 接口的文生图。图片请求先保存派发记录,返回 `202/pending` 后异步完成。
- 视频、语音及自定义能力可以保存声明,尚无执行适配时明确 `callable=false`;声明不代表已支持生成。
- 图片结果为 URL 或 Base64。若要长期使用或写入业务系统,宿主按需复用已有附件空间。生成图片不会自动修改商品或其他业务数据。

当前图片适配只接受已声明并受支持的文生图参数。附件引用不能冒充供应商可访问的文件地址。阿里云提供两个显式适配:`aliyun-image` 使用 OpenAI Images 兼容协议,`aliyun-image-native` 使用 DashScope 原生协议。同一凭据可配置为标准服务根地址、`/api/v1` 或 `/compatible-mode/v1`;原生适配在同一 origin 下使用固定的原生路径,将文生图参数转换为该协议。不会遇到错误就自动切换协议、地区或模型重试。

## 客户端接口

`GET /usage/v1/capabilities` 的 `model_gateway.supported=true` 为接入入口,schema 为 `bailing.model-gateway.v1`,`billing_unit=USD`、`orchestration=host`、`turn_required=false`。

对话模型可显式分开默认输出预算和服务上限,并声明实际供应商参数及是否包含思考;旧服务不会自动迁移。配置、目录字段、拒绝回执及原请求恢复边界见[模型输出预算契约](MODEL_OUTPUT_BUDGET.md)。

| 接口 | 用途 |
| --- | --- |
| `GET /usage/v1/model/models` | 当前账户套餐中的对话模型;显示管理员配置的 label |
| `GET /usage/v1/model/tools` | 当前账户套餐中的工具声明、可调用状态 |
| `GET /usage/v1/model/summary` | 剩余金额、周期及客户展示 presentation |
| `POST /usage/v1/model/requests` | 单次对话模型请求 |
| `POST /usage/v1/model/requests/stream` | 同一协议的流式请求 |
| `POST /usage/v1/model/tools/requests` | `{operation_id,service_id,arguments,conversation_id?,turn_id?}` |
| `GET /usage/v1/model/requests/{operation_id}` | 查原结果,不重新调用模型或生成工具 |
| `POST /usage/v1/model/requests/{operation_id}/cancel` | 取消继续使用结果;保留已发生用量 |

所有请求在本地持久保存原 `operation_id`。丢失回包、断网重开、结果不确定时只查询原请求,不能换 ID 重发生成。相同 ID、相同内容只派发一次;相同 ID 不同内容明确冲突。新动作和原调用恢复严格区分。取消的晚返回仍记录用量,不恢复已结束的本地轮次。

响应分别提供 `result_state`、`billing_state`、`response`、原始 `usage`(可为空)、`raw_usage`、`reference_cost_usd`、`billed_usd`、`overage_usd`。`billing_rate` 包含套餐修订、统一倍率和参考价格快照。`response` 有限期保存,过期返回 `response_expired`,不能因此重发原操作。

对话模型的 `result_state=complete` 只表示供应商响应交换已保存,不表示模型答案完整或宿主任务已经完成。供应商的原始 `finish_reason`、部分正文、思考内容及工具调用仍保留在 `bailing.provider-response.v1` 中,由宿主适配器解释。明确的输出预算截断与网络执行结果未知是两类情况;中枢不会因截断自动再次调用模型。参见[长任务续跑与原请求恢复](MODEL_CONTINUATION.md)。

可信业务后端使用原身份交换接口。`model_access: token_gateway` 是套餐范围凭证的身份标记,不是旧 Token 额度模式;`service` 仍严格限制单个模型。身份、原业务授权、固定会话范围、审批和业务调用归档独立存在,不因套餐扩大授权。

后台接口统一 `/admin/api/usage/billing/`;业务后端可使用 `/usage/v1/external/billing/plans` 及 `/accounts/{id}/summary|grant|control`,沿用来源所有权、权限和幂等开通规则。

## 安装边界

从公开 Core 0.8.0 升级时,通过正式迁移器应用新增的 `063_usage_model_billing.sql` 及 `064_period_plan_allowance.sql`;首次安装应用全部未执行文件。正常升级保留真实数据,无需清退旧结构。已经部署过 063 计费预览的实例按[周期额度升级说明](PERIOD_ALLOWANCE_UPGRADE.md)转换各自原快照。模块不提供旧 Token 额度、按轮收费或逐模型倍率接口。废弃开发结构的维护不属于客户升级要求,不将测试数据清理混入公共 SQL。参见 [v0.9.0 升级指南](UPGRADE_v0.9.0.md)。

参考文档:[OpenRouter 图片接口](https://openrouter.ai/docs/guides/overview/multimodal/image-generation)、[阿里云图片接口](https://help.aliyun.com/zh/model-studio/qwen-image-generation-and-editing-api-reference)。

## 配置就绪与能力发现

服务状态为 `active` 不代表已经可调用:可执行服务还必须明确绑定参考模型与端点,并具有可用缓存报价。控制台服务列表的 `availability` 返回 `state`、`code` 和 `message`;保存启用的可执行服务前会验证报价。套餐不能新增选择未就绪服务,已有选择可以保留或移除,无需重建账户或改变额度。外部开通流程可以暂存声明,暂存服务不会因此变为可调用。

`/usage/v1/model/models` 与 `/usage/v1/model/tools` 的 `items` 只返回就绪项目。`unavailable_items` 提供当前套餐/凭证范围内未就绪服务的 ID、名称、修订和原因,不包含供应商凭据或工具 schema。默认对话模型必须是就绪项,否则为 `null`。选中模型不可用时,宿主不能自动切换模型或重放失败请求;管理员修复配置后刷新目录即可,无需重新授权或重置额度。旧 SDK 会传递这些新增字段;仅使用 `items` 也能构建安全的选择器与可调用工具目录。

- `USAGE_PRICE_NOT_CONFIGURED`:参考绑定缺失或不合法。
- `USAGE_PRICE_UNAVAILABLE`:无同步报价,或固定端点已消失。
- `USAGE_PRICE_UNSUPPORTED`:端点计量规则尚不支持。
- `MODEL_TOOL_ADAPTER_NOT_READY`:只有声明,没有执行适配。

这些配置错误发生在准入或供应商派发前,使用 `next_action=contact_operator`,不能自动重试。目录检查与准入使用同一缓存报价,不生成内容,也不等待远程价格查询。价格目录暂时不可用时,合法的过期报价仍可使用,并触发后台刷新。就绪不保证供应商可用性或完整计量回执;缺少实际用量仍待结算,不编造费用。

## 生成结果与计费状态分开

- `result_state=pending`:供应商请求仍在进行,宿主按原 ID 查询,不发起第二次生成。
- `complete`:结果可以立即交给宿主适配器,即使 `billing_state=pending`;结算不阻塞结果。对话适配器仍需判断答案、工具调用是否完整,图片使用声明的工具结果。
- `failed`:已获得明确拒绝(例如不存在的接口、无效参数、权限拒绝),`dispatch=rejected`,停止等待,显示安全错误;不自动重发。当前已确认拒绝无生成的请求以零扣费关闭。
- `unknown`:派发后发生真实传输异常或无法持久确认原执行结果。停止“正在生成”的展示,保留原 ID 供核对;不能自动换 ID 或切接口重发。不阻塞其他独立模型请求。完整收到的对话响应原文即使包含 `finish_reason=length`、供应商错误或语义缺尾,也不因宿主解析结论被重写为中枢的传输未知状态。
- `cancelled`:维持取消;晚到的结果和费用不重新启用宿主工具或恢复已结束轮次。

失败回执的 `error` 仅包含 `code`、固定安全文案 `message`、可选的 `http_status / provider_code / provider_request_id`、`retryable=false` 及 `next_action`。不保存或返回供应商原始错误正文、提示词、密钥或地址。HTTP 408、409 与 5xx 不等于已证明没有执行。

中枢异步受理与供应商异步任务协议是不同层次:当前原生/兼容图片适配都是单次供应商请求在后台完成;原 ID 查询只读取已持久化结果。没有供应商任务 ID 时不能宣称已具备供应商任务恢复。历史 unknown 不因本次修复被猜测改成失败。原记录和原价格快照保留。

## 套餐移除与历史记录

删除套餐采用保留原标识的软删除。只检查启用账户当前指向的、启用且未到期的开通记录(包括待生效记录);旧开通、已到期、已停用账户或已暂停开通不阻止移除。停用套餐模板仅停止新开通,不会终止现有账户的套餐。

历史开通、原额度池、账本与请求快照继续保留。已派发原请求仍按原快照恢复和结算,不转移到新套餐或再次派发。恢复账户身份不会恢复已删除的套餐;重新使用需开通有效套餐。

## 账户移除与恢复

个人账户和组织共享账户均可在停用后移除(`archived`)。默认列表隐藏,原账户 ID、可信身份、成员状态、套餐开通、额度、请求及账本保持原关联。已有历史不阻止移除,完整性异常须先修复。来源配置中的账户范围保持不变,但不能使用已移除账户。

移除撤销该账户现有使用凭证,阻止新请求、套餐开通、成员及账户状态修改。已派发请求仍按原快照保存结果并幂等结算。同一身份再次登录不会创建替代账户或补发额度。

管理员在“账户与成员 → 已移除”查看并恢复原账户。恢复只改为停用状态,不恢复被撤销的成员或旧凭证,不重置额度。需要使用时再启用并重新登录;若原套餐已删除或到期,需开通有效套餐。原 `deleted` 删除标记不通过恢复接口复活。

- 管理列表:`GET /admin/api/usage/accounts?view=current|archived`,默认 `current`,支持原分页参数。客户端账户列表不包含已移除账户。
- 移除:沿用 `DELETE /admin/api/usage/accounts/:id` 及预览接口,要求账户停用、当前修订与 `request_key`。
- 恢复:`POST /admin/api/usage/accounts/:id/restore`,请求含 `expected_revision`、`request_key`,要求管理员 `usage:write`。
- 两项变更均按原请求幂等;旧回执重试不覆盖后续管理决定。

不新增表或 SQL 迁移;SDK/DSH 无需调整。

## 单账户手动重置额度

控制台“账户用量 → 用量详情 → 重置额度”允许有 `usage:write` 与 `usage:adjust` 权限的管理员,为启用账户重置中枢维护的当前有效套餐。周期套餐恢复至原开通记录的周期额度(100%),从服务端确认重置的时刻重新起算周期;额度包恢复至原始积分,无自动重置时间。剩余额度不叠加,套餐到期时间不延长。最后一个周期若先到期,仍以到期时间结束,不显示到期后的下次重置时间。

- `POST /admin/api/usage/billing/accounts/:id/reset`,请求为 `request_key` 与当前开通记录的 `expected_revision`。
- 原套餐未生效、已到期、已暂停,或账户停用/移除时不能重置。业务后端维护的额度由原来源调整。
- 复用已有开通快照与额度池,创建新的当前开通记录;复制原额度条款,不因套餐模板后来改价而改变本次满额值。原模型目录和当前套餐倍率继续遵循既有规则。
- 原开通、请求、计量和账本保留;已派发请求晚到的结果仍归原额度池结算,不扣新额度。尚未派发的旧请求继续遵循原开通修订校验,不自动重新派发。
- 原请求幂等重试只返回原回执;并发变更通过修订号检查,避免重复重置。变更回执记录 `reset.previousGrantId`、`reset.actor`、`reset.at`,可关联原开通与操作人。

沿用现有摘要接口返回新的剩余额度及下次重置时间,不新增 SQL 迁移,不需要客户端 SDK/DSH 或业务后端改造。

## Core 0.9.2 的自动结算维护

Core 0.9.2 增加首轮及每分钟的后台结算维护、公平分页与数据库故障退避,结算不依赖新 HTTP 请求。仅使用原记录的计量、价格与统一倍率,不读取生成正文或重派供应商;缺少权威计量仍待核对。配套 065 索引及 managed tick/standalone 关闭边界见[Core 可靠性修复](CORE_RELIABILITY.md)。这些修改从 Core 0.9.2 提供,不扩展历史 v0.9.1 Tag 的保证。