mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 09:50:21 +08:00
feat: 添加视频生成功能增强和多维度计费系统适配
- 视频生成: 增强 video_handler,重构 task_poller,新增 telemetry - 计费系统: 适配新的 signature 格式,支持 video 任务类型回退 - 数据库迁移: 添加 api_family/endpoint_kind 字段和 video_formats
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
from src.services.billing import BillingCalculator, UsageMapper, StandardizedUsage
|
||||
|
||||
# 1. 将原始 usage 映射为标准格式
|
||||
usage = UsageMapper.map(raw_usage, api_format="OPENAI")
|
||||
usage = UsageMapper.map(raw_usage, api_format="openai:chat")
|
||||
|
||||
# 2. 使用计费计算器计算费用
|
||||
calculator = BillingCalculator(template="openai")
|
||||
|
||||
@@ -29,7 +29,11 @@ ValueType = Literal["float", "int", "string"]
|
||||
|
||||
|
||||
def _normalize_api_format(api_format: str | None) -> str:
|
||||
return (api_format or "").upper()
|
||||
if not api_format:
|
||||
return ""
|
||||
from src.core.api_format.signature import normalize_signature_key
|
||||
|
||||
return normalize_signature_key(api_format)
|
||||
|
||||
|
||||
def _normalize_task_type(task_type: str | None) -> str:
|
||||
@@ -312,6 +316,45 @@ class DimensionCollectorService:
|
||||
task = _normalize_task_type(task_type)
|
||||
api_variants = list({api, api.lower()})
|
||||
|
||||
if task == "video":
|
||||
# VIDEO → base 回退:优先使用 family:video 专用 collector;
|
||||
# 缺失的维度再回退到 family:chat。
|
||||
from src.core.api_format.signature import parse_signature_key
|
||||
|
||||
base_api = api
|
||||
try:
|
||||
sig = parse_signature_key(api)
|
||||
if sig.endpoint_kind.value == "video":
|
||||
base_api = f"{sig.api_family.value}:chat"
|
||||
except Exception:
|
||||
base_api = api
|
||||
base_variants = list({base_api, base_api.lower()})
|
||||
|
||||
video_collectors = (
|
||||
self.db.query(DimensionCollector)
|
||||
.filter(
|
||||
DimensionCollector.api_format.in_(api_variants),
|
||||
DimensionCollector.task_type == "video",
|
||||
DimensionCollector.is_enabled == True, # noqa: E712
|
||||
)
|
||||
.all()
|
||||
)
|
||||
base_collectors = (
|
||||
self.db.query(DimensionCollector)
|
||||
.filter(
|
||||
DimensionCollector.api_format.in_(base_variants),
|
||||
DimensionCollector.task_type == "video",
|
||||
DimensionCollector.is_enabled == True, # noqa: E712
|
||||
)
|
||||
.all()
|
||||
)
|
||||
video_dims: set[str] = {c.dimension_name for c in video_collectors}
|
||||
result: list[DimensionCollector] = list(video_collectors)
|
||||
for c in base_collectors:
|
||||
if c.dimension_name not in video_dims:
|
||||
result.append(c)
|
||||
return result
|
||||
|
||||
if task == "cli":
|
||||
# CLI → chat:按维度回退(维度存在 cli collector 则用 cli,否则用 chat)
|
||||
cli_collectors = (
|
||||
|
||||
@@ -4,9 +4,9 @@ Usage 字段映射器
|
||||
将不同 API 格式的原始 usage 数据映射为标准化格式。
|
||||
|
||||
支持的格式:
|
||||
- OPENAI / OPENAI_CLI: OpenAI Chat Completions API
|
||||
- CLAUDE / CLAUDE_CLI: Anthropic Messages API
|
||||
- GEMINI / GEMINI_CLI: Google Gemini API
|
||||
- openai:*: OpenAI compatible (Chat/CLI)
|
||||
- claude:*: Anthropic Messages (Chat/CLI)
|
||||
- gemini:*: Google Gemini (Chat/CLI)
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
@@ -73,16 +73,6 @@ class UsageMapper:
|
||||
"usageMetadata.cachedContentTokenCount": "cache_read_tokens",
|
||||
}
|
||||
|
||||
# 格式名称到映射的对应关系
|
||||
FORMAT_MAPPINGS: dict[str, dict[str, str]] = {
|
||||
"OPENAI": OPENAI_MAPPING,
|
||||
"OPENAI_CLI": OPENAI_MAPPING,
|
||||
"CLAUDE": CLAUDE_MAPPING,
|
||||
"CLAUDE_CLI": CLAUDE_MAPPING,
|
||||
"GEMINI": GEMINI_MAPPING,
|
||||
"GEMINI_CLI": GEMINI_MAPPING,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def map(
|
||||
cls,
|
||||
@@ -142,12 +132,13 @@ class UsageMapper:
|
||||
Returns:
|
||||
标准化的 usage 对象
|
||||
"""
|
||||
format_upper = api_format.upper() if api_format else ""
|
||||
format_norm = (api_format or "").strip().lower()
|
||||
api_family = format_norm.split(":", 1)[0] if ":" in format_norm else format_norm
|
||||
|
||||
# 提取 usage 部分
|
||||
usage_data: dict[str, Any] = {}
|
||||
|
||||
if format_upper.startswith("GEMINI"):
|
||||
if api_family == "gemini":
|
||||
# Gemini: usageMetadata
|
||||
usage_data = response.get("usageMetadata", {})
|
||||
if not usage_data:
|
||||
@@ -164,21 +155,14 @@ class UsageMapper:
|
||||
@classmethod
|
||||
def _get_mapping(cls, api_format: str) -> dict[str, str]:
|
||||
"""获取对应格式的字段映射"""
|
||||
if not api_format:
|
||||
return cls.CLAUDE_MAPPING
|
||||
format_norm = (api_format or "").strip().lower()
|
||||
api_family = format_norm.split(":", 1)[0] if ":" in format_norm else format_norm
|
||||
|
||||
format_upper = api_format.upper()
|
||||
|
||||
# 精确匹配
|
||||
if format_upper in cls.FORMAT_MAPPINGS:
|
||||
return cls.FORMAT_MAPPINGS[format_upper]
|
||||
|
||||
# 前缀匹配
|
||||
for key, mapping in cls.FORMAT_MAPPINGS.items():
|
||||
if format_upper.startswith(key.split("_")[0]):
|
||||
return mapping
|
||||
|
||||
# 默认使用 Claude 映射
|
||||
if api_family == "openai":
|
||||
return cls.OPENAI_MAPPING
|
||||
if api_family == "gemini":
|
||||
return cls.GEMINI_MAPPING
|
||||
# 默认 Claude(也覆盖未知/空值)
|
||||
return cls.CLAUDE_MAPPING
|
||||
|
||||
@classmethod
|
||||
|
||||
Reference in New Issue
Block a user