feat: 视频计费增强与影子计费系统

This commit is contained in:
fawney19
2026-02-03 18:48:39 +08:00
parent ed68aebfb0
commit 00442c41fa
84 changed files with 6507 additions and 1678 deletions

View File

@@ -28,6 +28,10 @@ class CacheTTL:
DASHBOARD_STATS = 120 # 2分钟管理员
DASHBOARD_DAILY = 600 # 10分钟每日统计
# Admin usage pages (heavy DB aggregations / list queries)
ADMIN_USAGE_AGGREGATION = 30 # 30秒
ADMIN_USAGE_RECORDS = 10 # 10秒列表页短缓存避免轮询/重复刷新打爆 DB
# 并发锁 TTL - 防止死锁
CONCURRENCY_LOCK = 600 # 10分钟

View File

@@ -148,21 +148,6 @@ class Config:
# HTTP_REQUEST_TIMEOUT: 非流式请求整体超时(秒),默认 300 秒
self.http_request_timeout = float(os.getenv("HTTP_REQUEST_TIMEOUT", "300.0"))
# 格式转换配置
# FORMAT_CONVERSION_ENABLED: 全局格式转换总开关,默认开启
# 注意:即使开启,也需要端点配置 format_acceptance_config.enabled=true 才能生效
self.format_conversion_enabled = (
os.getenv("FORMAT_CONVERSION_ENABLED", "true").lower() == "true"
)
# KEEP_PRIORITY_ON_CONVERSION: 格式转换时是否保持提供商原优先级,默认关闭
# - false默认: 需要格式转换的候选整体降级到不需要转换的候选之后
# - true: 所有提供商保持原优先级,不因格式转换降级
# 注意:即使全局关闭,单个提供商也可以通过 keep_priority_on_conversion 字段保持自己的优先级
self.keep_priority_on_conversion = (
os.getenv("KEEP_PRIORITY_ON_CONVERSION", "false").lower() == "true"
)
# HTTP 连接池配置
# HTTP_MAX_CONNECTIONS: 最大连接数,影响并发能力
# - 每个连接占用一个 socket过多会耗尽系统资源
@@ -192,15 +177,8 @@ class Config:
# Usage 队列配置Redis Streams
# 默认启用队列模式,通过 Redis Streams 异步写入 DB提升响应性能
self.usage_queue_enabled = os.getenv("USAGE_QUEUE_ENABLED", "true").lower() == "true"
# 默认传输 headers/bodies由系统request_log_level决定最终存储内容
self.usage_queue_include_headers = (
os.getenv("USAGE_QUEUE_INCLUDE_HEADERS", "true").lower() == "true"
)
self.usage_queue_include_bodies = (
os.getenv("USAGE_QUEUE_INCLUDE_BODIES", "true").lower() == "true"
)
# 0 表示不截断由系统设置max_request/response_body_size统一控制
self.usage_queue_body_max_bytes = int(os.getenv("USAGE_QUEUE_BODY_MAX_BYTES", "0"))
# 队列事件是否包含 headers/bodies 由系统request_record_level决定
# 最终写入 DB 前仍会按 SystemConfigService 做脱敏与截断。
self.usage_queue_stream_key = os.getenv("USAGE_QUEUE_STREAM_KEY", "usage:events")
self.usage_queue_stream_group = os.getenv("USAGE_QUEUE_STREAM_GROUP", "usage_consumers")
self.usage_queue_stream_maxlen = int(os.getenv("USAGE_QUEUE_STREAM_MAXLEN", "200000"))
@@ -217,6 +195,17 @@ class Config:
os.getenv("USAGE_QUEUE_METRICS_INTERVAL_SECONDS", "30")
)
# Admin analytics query defaults (protect DB from unbounded scans)
# ADMIN_USAGE_DEFAULT_DAYS:
# - 0: keep current behavior (no implicit time filter)
# - >0: when admin usage endpoints omit start_date/end_date, default to "last N days"
default_admin_usage_default_days = (
"0" if self.environment in {"development", "test", "testing"} else "30"
)
self.admin_usage_default_days = int(
os.getenv("ADMIN_USAGE_DEFAULT_DAYS", default_admin_usage_default_days)
)
# Thinking 整流器配置
# THINKING_RECTIFIER_ENABLED: 是否启用 Thinking 整流器
# 当遇到跨 Provider 的 thinking 签名错误时,自动整流请求体后重试
@@ -252,6 +241,37 @@ class Config:
self.billing_require_rule = os.getenv("BILLING_REQUIRE_RULE", "false").lower() == "true"
self.billing_strict_mode = os.getenv("BILLING_STRICT_MODE", "false").lower() == "true"
# 计费迁移运行时开关(用于灰度/影子计费/快速止血)
# BILLING_ENGINE:
# - legacy: 仅旧系统(当前默认)
# - shadow: 旧系统为真值 + 新系统影子计算(对账期)
# - new_with_fallback: 新系统为真值,差异过大时回退旧系统
# - new: 仅新系统
# Default to "new" per unified billing architecture.
self.billing_engine = os.getenv("BILLING_ENGINE", "new").strip().lower()
# 按 provider/model 粒度覆盖JSON 字符串)
# 示例: {"anthropic/*": "shadow", "openai/gpt-4*": "new"}
self.billing_engine_overrides = os.getenv("BILLING_ENGINE_OVERRIDES", "{}")
# 影子计费差异阈值(美元)
self.billing_diff_threshold_usd = float(os.getenv("BILLING_DIFF_THRESHOLD_USD", "0.0001"))
# 差异日志级别DEBUG/INFO/WARNING/ERROR
self.billing_shadow_log_level = os.getenv("BILLING_SHADOW_LOG_LEVEL", "INFO").strip()
# 是否启用差异告警(预留扩展)
self.billing_diff_alert_enabled = (
os.getenv("BILLING_DIFF_ALERT_ENABLED", "false").lower() == "true"
)
# Usage.request_metadata 体积控制(用于降低 DB/CPU/内存压力)
# USAGE_METADATA_MAX_BYTES:
# - 0: unlimited (backward compatible)
# - >0: best-effort prune large keys when metadata JSON exceeds this size
default_usage_metadata_max_bytes = (
"0" if self.environment in {"development", "test", "testing"} else "65536"
)
self.usage_metadata_max_bytes = int(
os.getenv("USAGE_METADATA_MAX_BYTES", default_usage_metadata_max_bytes)
)
# 视频任务轮询配置
# VIDEO_POLL_INTERVAL_SECONDS: 轮询间隔(秒),默认 10 秒
# VIDEO_MAX_POLL_COUNT: 最大轮询次数,默认 360 次(约 1 小时)