refactor: 提取 HandlerAdapterBase 基类,新增 api_family/endpoint_kind 结构化维度

- 从 ChatAdapterBase 和 CliAdapterBase 提取公共逻辑到 HandlerAdapterBase,
  消除头部处理、异常处理、计费策略等重复代码
- Usage 记录链路全程透传 api_family / endpoint_kind / provider_api_family /
  provider_endpoint_kind 四个结构化字段,替代从 api_format 字符串解析
- 删除冗余的 ClaudeCliNormalizer、GeminiCliNormalizer、ClaudeCliResponseParser、
  GeminiCliResponseParser,改用 data_format_id 回退机制自动复用 Chat 版本
- build_endpoint_url 签名统一扩展 request_data / model_name 参数
- 新增 Alembic 迁移,含历史数据回填
This commit is contained in:
fawney19
2026-02-21 13:17:11 +08:00
parent 54988916e3
commit 0fe5346f4d
36 changed files with 967 additions and 1310 deletions

View File

@@ -0,0 +1,86 @@
"""Add api_family and endpoint_kind columns to usage table
Revision ID: 8f9a0b1c2d3e
Revises: 7e8f9a0b1c2d
Create Date: 2026-02-21 15:00:00.000000
"""
from __future__ import annotations
from collections.abc import Sequence
import sqlalchemy as sa
from sqlalchemy import inspect, text
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "8f9a0b1c2d3e"
down_revision: str | None = "7e8f9a0b1c2d"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
# Usage 表新增列
NEW_COLUMNS = [
("api_family", sa.String(50)),
("endpoint_kind", sa.String(50)),
("provider_api_family", sa.String(50)),
("provider_endpoint_kind", sa.String(50)),
]
# 新增索引
NEW_INDEXES = [
("idx_usage_api_family", "usage", ["api_family"]),
("idx_usage_endpoint_kind", "usage", ["endpoint_kind"]),
("idx_usage_family_kind", "usage", ["api_family", "endpoint_kind"]),
]
def upgrade() -> None:
conn = op.get_bind()
inspector = inspect(conn)
existing_columns = {col["name"] for col in inspector.get_columns("usage")}
for col_name, col_type in NEW_COLUMNS:
if col_name not in existing_columns:
op.add_column("usage", sa.Column(col_name, col_type, nullable=True))
# 数据迁移:从 api_format 解析 api_family + endpoint_kind
conn.execute(text("""
UPDATE usage SET
api_family = lower(split_part(api_format, ':', 1)),
endpoint_kind = lower(split_part(api_format, ':', 2))
WHERE api_format IS NOT NULL
AND api_format LIKE '%%:%%'
AND api_family IS NULL
"""))
conn.execute(text("""
UPDATE usage SET
provider_api_family = lower(split_part(endpoint_api_format, ':', 1)),
provider_endpoint_kind = lower(split_part(endpoint_api_format, ':', 2))
WHERE endpoint_api_format IS NOT NULL
AND endpoint_api_format LIKE '%%:%%'
AND provider_api_family IS NULL
"""))
# 创建索引
existing_indexes = {idx["name"] for idx in inspector.get_indexes("usage")}
for idx_name, table, columns in NEW_INDEXES:
if idx_name not in existing_indexes:
op.create_index(idx_name, table, columns)
def downgrade() -> None:
conn = op.get_bind()
inspector = inspect(conn)
existing_indexes = {idx["name"] for idx in inspector.get_indexes("usage")}
for idx_name, _, _ in reversed(NEW_INDEXES):
if idx_name in existing_indexes:
op.drop_index(idx_name, table_name="usage")
existing_columns = {col["name"] for col in inspector.get_columns("usage")}
for col_name, _ in reversed(NEW_COLUMNS):
if col_name in existing_columns:
op.drop_column("usage", col_name)