""" 计费模块数据模型 定义计费相关的核心数据结构: - BillingUnit: 计费单位枚举 - BillingDimension: 计费维度定义 - StandardizedUsage: 标准化的 usage 数据 - CostBreakdown: 计费明细结果 """ from dataclasses import dataclass, field from enum import Enum from typing import Any, Dict, Optional class BillingUnit(str, Enum): """计费单位""" PER_1M_TOKENS = "per_1m_tokens" # 每百万 token PER_1M_TOKENS_HOUR = "per_1m_tokens_hour" # 每百万 token 每小时(豆包缓存存储) PER_REQUEST = "per_request" # 每次请求 FIXED = "fixed" # 固定费用 @dataclass class BillingDimension: """ 计费维度定义 每个维度描述一种计费方式,例如: - 输入 token 计费 - 输出 token 计费 - 缓存读取计费 - 按次计费 """ name: str # 维度名称,如 "input", "output", "cache_read" usage_field: str # 从 usage 中取值的字段名 price_field: str # 价格配置中的字段名 unit: BillingUnit = BillingUnit.PER_1M_TOKENS # 计费单位 default_price: float = 0.0 # 默认价格(当价格配置中没有时使用) def calculate(self, usage_value: float, price: float) -> float: """ 计算该维度的费用 Args: usage_value: 使用量数值 price: 单价 Returns: 计算后的费用 """ if usage_value <= 0 or price <= 0: return 0.0 if self.unit == BillingUnit.PER_1M_TOKENS: return (usage_value / 1_000_000) * price elif self.unit == BillingUnit.PER_1M_TOKENS_HOUR: # 缓存存储按 token 数 * 小时数计费 return (usage_value / 1_000_000) * price elif self.unit == BillingUnit.PER_REQUEST: return usage_value * price elif self.unit == BillingUnit.FIXED: return price return 0.0 def to_dict(self) -> Dict[str, Any]: """转换为字典(用于序列化)""" return { "name": self.name, "usage_field": self.usage_field, "price_field": self.price_field, "unit": self.unit.value, "default_price": self.default_price, } @classmethod def from_dict(cls, data: Dict[str, Any]) -> "BillingDimension": """从字典创建实例""" return cls( name=data["name"], usage_field=data["usage_field"], price_field=data["price_field"], unit=BillingUnit(data.get("unit", "per_1m_tokens")), default_price=data.get("default_price", 0.0), ) @dataclass class StandardizedUsage: """ 标准化的 Usage 数据 将不同 API 格式的 usage 统一为标准格式,便于计费计算。 """ # 基础 token 计数 input_tokens: int = 0 output_tokens: int = 0 # 缓存相关 cache_creation_tokens: int = 0 # Claude: 缓存创建 cache_read_tokens: int = 0 # Claude/OpenAI/豆包: 缓存读取/命中 # 特殊 token 类型 reasoning_tokens: int = 0 # o1/豆包: 推理 token(通常包含在 output 中,单独记录用于分析) # 时间相关(用于按时计费) cache_storage_token_hours: float = 0.0 # 豆包: 缓存存储 token*小时 # 请求计数(用于按次计费) request_count: int = 1 # 扩展字段(未来可能需要的额外维度) extra: Dict[str, Any] = field(default_factory=dict) def get(self, field_name: str, default: Any = 0) -> Any: """ 通用字段获取 支持获取标准字段和扩展字段。 Args: field_name: 字段名 default: 默认值 Returns: 字段值 """ if hasattr(self, field_name): value = getattr(self, field_name) # 对于 extra 字段,不直接返回 if field_name != "extra": return value return self.extra.get(field_name, default) def set(self, field_name: str, value: Any) -> None: """ 通用字段设置 Args: field_name: 字段名 value: 字段值 """ if hasattr(self, field_name) and field_name != "extra": setattr(self, field_name, value) else: self.extra[field_name] = value def to_dict(self) -> Dict[str, Any]: """转换为字典""" result: Dict[str, Any] = { "input_tokens": self.input_tokens, "output_tokens": self.output_tokens, "cache_creation_tokens": self.cache_creation_tokens, "cache_read_tokens": self.cache_read_tokens, "reasoning_tokens": self.reasoning_tokens, "cache_storage_token_hours": self.cache_storage_token_hours, "request_count": self.request_count, } if self.extra: result["extra"] = self.extra return result @classmethod def from_dict(cls, data: Dict[str, Any]) -> "StandardizedUsage": """从字典创建实例""" extra = data.pop("extra", {}) if "extra" in data else {} # 只取已知字段 known_fields = { "input_tokens", "output_tokens", "cache_creation_tokens", "cache_read_tokens", "reasoning_tokens", "cache_storage_token_hours", "request_count", } filtered = {k: v for k, v in data.items() if k in known_fields} return cls(**filtered, extra=extra) @dataclass class CostBreakdown: """ 计费明细结果 包含各维度的费用和总费用。 """ # 各维度费用 {"input": 0.01, "output": 0.02, "cache_read": 0.001, ...} costs: Dict[str, float] = field(default_factory=dict) # 总费用 total_cost: float = 0.0 # 命中的阶梯索引(如果使用阶梯计费) tier_index: Optional[int] = None # 货币单位 currency: str = "USD" # 使用的价格(用于记录和审计) effective_prices: Dict[str, float] = field(default_factory=dict) # ========================================================================= # 兼容旧接口的属性(便于渐进式迁移) # ========================================================================= @property def input_cost(self) -> float: """输入费用""" return self.costs.get("input", 0.0) @property def output_cost(self) -> float: """输出费用""" return self.costs.get("output", 0.0) @property def cache_creation_cost(self) -> float: """缓存创建费用""" return self.costs.get("cache_creation", 0.0) @property def cache_read_cost(self) -> float: """缓存读取费用""" return self.costs.get("cache_read", 0.0) @property def cache_cost(self) -> float: """总缓存费用(创建 + 读取)""" return self.cache_creation_cost + self.cache_read_cost @property def request_cost(self) -> float: """按次计费费用""" return self.costs.get("request", 0.0) @property def cache_storage_cost(self) -> float: """缓存存储费用(豆包等)""" return self.costs.get("cache_storage", 0.0) def to_dict(self) -> Dict[str, Any]: """转换为字典""" return { "costs": self.costs, "total_cost": self.total_cost, "tier_index": self.tier_index, "currency": self.currency, "effective_prices": self.effective_prices, # 兼容字段 "input_cost": self.input_cost, "output_cost": self.output_cost, "cache_creation_cost": self.cache_creation_cost, "cache_read_cost": self.cache_read_cost, "cache_cost": self.cache_cost, "request_cost": self.request_cost, } def to_legacy_tuple(self) -> tuple: """ 转换为旧接口的元组格式 Returns: (input_cost, output_cost, cache_creation_cost, cache_read_cost, cache_cost, request_cost, total_cost, tier_index) """ return ( self.input_cost, self.output_cost, self.cache_creation_cost, self.cache_read_cost, self.cache_cost, self.request_cost, self.total_cost, self.tier_index, )