Files
Aether/_deprecated_py_src/services/billing/rule_service.py
fawney19 1d9c77522a refactor: 移除 Python 后端源码,全面迁移至 Rust gateway 架构
- 删除全部 Python 源码 (src/) 及 Alembic 迁移脚本,归档至 _deprecated_py_src/
- 重构 Rust gateway ai_pipeline: 拆分 planner/finalize 模块,新增 contracts/adaptation 层
- 重组 handlers 模块为 admin/public/proxy/internal/shared 子模块结构
- 新增 executor 模块,引入 Rust 原生数据库迁移 (aether-data/migrations)
- 简化 CI/Docker 构建流程,移除 base image 二级构建,统一为单一 app image
- 移除 Python 相关基础设施文件 (entrypoint.sh, gunicorn_conf.py, Dockerfile.base)
2026-04-03 16:26:16 +08:00

134 lines
4.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
BillingRule 查找逻辑
查找顺序(与 .plans/humming-seeking-marble.md 一致):
1) 读取 GlobalModel/Model 价格配置 → 2) 使用代码内置计费模板生成规则config-file mode
注意:
- CLI 在计费域等同于 chatbilling_rules.task_type 不含 "cli"
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Literal, Protocol
from sqlalchemy.orm import Session
from src.config.settings import config
from src.models.database import GlobalModel, Model
from src.services.billing.cache import BillingCache
from src.services.billing.default_rules import DefaultBillingRuleGenerator, VirtualBillingRule
from src.services.billing.rule_templates import CodeBillingRuleTemplateService
TaskType = Literal["chat", "cli", "video", "image", "audio"]
BillingRuleScope = Literal["model", "global", "default"]
class BillingRuleLike(Protocol):
id: str
name: str
expression: str
variables: dict[str, Any]
dimension_mappings: dict[str, Any]
def effective_rule_task_type(task_type: str) -> str:
"""CLI 在计费规则域里恒等于 chat。"""
t = (task_type or "").lower()
return "chat" if t == "cli" else t
@dataclass(frozen=True)
class BillingRuleLookupResult:
rule: BillingRuleLike
scope: BillingRuleScope
effective_task_type: str
class BillingRuleService:
@staticmethod
def find_rule(
db: Session,
*,
provider_id: str | None,
model_name: str,
task_type: str,
) -> BillingRuleLookupResult | None:
effective_task = effective_rule_task_type(task_type)
# Normalize provider_id for cache key to avoid duplicate entries (None vs "").
pid = provider_id or ""
# Cache must include runtime knobs that affect fallback behavior.
cache_key = (
f"{pid}:{model_name}:{effective_task}:require={int(config.billing_require_rule)}"
)
cached = BillingCache.get_rule(cache_key)
if cached is not None:
return cached
global_model = (
db.query(GlobalModel)
.filter(
GlobalModel.name == model_name,
GlobalModel.is_active == True, # noqa: E712
)
.first()
)
if not global_model:
return None
model_obj: Model | None = None
# Provider Model用于覆盖价格配置
if provider_id:
model_obj = (
db.query(Model)
.filter(
Model.provider_id == provider_id,
Model.global_model_id == global_model.id,
Model.is_active == True, # noqa: E712
)
.first()
)
# Code templates (config-file mode)
code_rule = CodeBillingRuleTemplateService.resolve_rule(
global_model=global_model,
model=model_obj,
provider_id=provider_id,
model_name=model_name,
task_type=effective_task,
)
if code_rule is not None:
result = BillingRuleLookupResult(
rule=code_rule,
scope="default",
effective_task_type=effective_task,
)
BillingCache.set_rule(cache_key, result)
return result
# Runtime default rule (backward compatible)
#
# - Always applies to chat-domain billing (cli is normalized to chat).
# - For video/image/audio:
# - When BILLING_REQUIRE_RULE=true, caller expects an explicit BillingRule (missing -> no_rule/error).
# - When BILLING_REQUIRE_RULE=false, fallback to default rule to preserve legacy pricing semantics
# (avoid silent $0 billing due to missing rule).
if effective_task == "chat" or not config.billing_require_rule:
default_rule = DefaultBillingRuleGenerator.generate_for_model(
global_model=global_model,
model=model_obj,
task_type=effective_task,
)
result = BillingRuleLookupResult(
rule=default_rule,
scope="default",
effective_task_type=effective_task,
)
BillingCache.set_rule(cache_key, result)
return result
return None