mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 09:20:22 +08:00
- 删除全部 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)
672 lines
28 KiB
Python
672 lines
28 KiB
Python
"""
|
||
Model 映射缓存服务 - 减少模型查询
|
||
|
||
架构说明
|
||
========
|
||
本服务采用混合 async/sync 模式:
|
||
- 缓存操作(CacheService):真正的 async,使用 aioredis
|
||
- 数据库查询(db.query):同步的 SQLAlchemy Session
|
||
|
||
设计决策
|
||
--------
|
||
1. 保持 async 方法签名:因为缓存命中时完全异步,性能最优
|
||
2. 缓存未命中时的同步查询:FastAPI 会在线程池中执行,不会阻塞事件循环
|
||
3. 调用方必须在 async 上下文中使用 await
|
||
|
||
使用示例
|
||
--------
|
||
global_model = await ModelCacheService.resolve_global_model_by_name_or_mapping(db, "gpt-4")
|
||
"""
|
||
|
||
import time
|
||
|
||
from sqlalchemy.orm import Session
|
||
|
||
from src.config.constants import CacheTTL
|
||
from src.core.cache_service import CacheService
|
||
from src.core.logger import logger
|
||
from src.core.metrics import (
|
||
model_mapping_conflict_total,
|
||
model_mapping_resolution_duration_seconds,
|
||
model_mapping_resolution_total,
|
||
)
|
||
from src.models.database import GlobalModel, Model
|
||
|
||
|
||
class ModelCacheService:
|
||
"""Model 映射缓存服务
|
||
|
||
提供 GlobalModel 和 Model 的缓存查询功能,减少数据库访问。
|
||
所有公开方法均为 async,需要在 async 上下文中调用。
|
||
"""
|
||
|
||
# 缓存 TTL(秒)- 使用统一常量
|
||
CACHE_TTL = CacheTTL.MODEL
|
||
PROVIDER_MAPPING_INDEX_CACHE_KEY = "global_model:resolve_index:provider_model_mappings"
|
||
MODEL_MAPPING_RULES_CACHE_KEY = "global_model:resolve_index:model_mappings"
|
||
|
||
@staticmethod
|
||
async def get_model_by_id(db: Session, model_id: str) -> Model | None:
|
||
"""
|
||
获取 Model(带缓存)
|
||
|
||
Args:
|
||
db: 数据库会话
|
||
model_id: Model ID
|
||
|
||
Returns:
|
||
Model 对象或 None
|
||
"""
|
||
cache_key = f"model:id:{model_id}"
|
||
|
||
# 1. 尝试从缓存获取
|
||
cached_data = await CacheService.get(cache_key)
|
||
if cached_data:
|
||
logger.debug(f"Model 缓存命中: {model_id}")
|
||
return ModelCacheService._dict_to_model(cached_data)
|
||
|
||
# 2. 缓存未命中,查询数据库
|
||
model = db.query(Model).filter(Model.id == model_id).first()
|
||
|
||
# 3. 写入缓存
|
||
if model:
|
||
model_dict = ModelCacheService._model_to_dict(model)
|
||
await CacheService.set(cache_key, model_dict, ttl_seconds=ModelCacheService.CACHE_TTL)
|
||
logger.debug(f"Model 已缓存: {model_id}")
|
||
|
||
return model
|
||
|
||
@staticmethod
|
||
async def get_global_model_by_id(db: Session, global_model_id: str) -> GlobalModel | None:
|
||
"""
|
||
获取 GlobalModel(带缓存)
|
||
|
||
Args:
|
||
db: 数据库会话
|
||
global_model_id: GlobalModel ID
|
||
|
||
Returns:
|
||
GlobalModel 对象或 None
|
||
"""
|
||
cache_key = f"global_model:id:{global_model_id}"
|
||
|
||
# 1. 尝试从缓存获取
|
||
cached_data = await CacheService.get(cache_key)
|
||
if cached_data:
|
||
logger.debug(f"GlobalModel 缓存命中: {global_model_id}")
|
||
return ModelCacheService._dict_to_global_model(cached_data)
|
||
|
||
# 2. 缓存未命中,查询数据库
|
||
global_model = db.query(GlobalModel).filter(GlobalModel.id == global_model_id).first()
|
||
|
||
# 3. 写入缓存
|
||
if global_model:
|
||
global_model_dict = ModelCacheService._global_model_to_dict(global_model)
|
||
await CacheService.set(
|
||
cache_key, global_model_dict, ttl_seconds=ModelCacheService.CACHE_TTL
|
||
)
|
||
logger.debug(f"GlobalModel 已缓存: {global_model_id}")
|
||
|
||
return global_model
|
||
|
||
@staticmethod
|
||
async def get_model_by_provider_and_global_model(
|
||
db: Session, provider_id: str, global_model_id: str
|
||
) -> Model | None:
|
||
"""
|
||
通过 Provider ID 和 GlobalModel ID 获取 Model(带缓存)
|
||
|
||
Args:
|
||
db: 数据库会话
|
||
provider_id: Provider ID
|
||
global_model_id: GlobalModel ID
|
||
|
||
Returns:
|
||
Model 对象或 None
|
||
"""
|
||
cache_key = f"model:provider_global:{provider_id}:{global_model_id}"
|
||
hit_count_key = f"model:provider_global:hits:{provider_id}:{global_model_id}"
|
||
|
||
# 1. 尝试从缓存获取
|
||
cached_data = await CacheService.get(cache_key)
|
||
if cached_data:
|
||
logger.debug(
|
||
f"Model 缓存命中(provider+global): {provider_id[:8]}...+{global_model_id[:8]}..."
|
||
)
|
||
# 递增命中计数,同时刷新 TTL
|
||
await CacheService.incr(hit_count_key, ttl_seconds=ModelCacheService.CACHE_TTL)
|
||
return ModelCacheService._dict_to_model(cached_data)
|
||
|
||
# 2. 缓存未命中,查询数据库
|
||
model = (
|
||
db.query(Model)
|
||
.filter(
|
||
Model.provider_id == provider_id,
|
||
Model.global_model_id == global_model_id,
|
||
Model.is_active == True,
|
||
)
|
||
.first()
|
||
)
|
||
|
||
# 3. 写入缓存
|
||
if model:
|
||
model_dict = ModelCacheService._model_to_dict(model)
|
||
await CacheService.set(cache_key, model_dict, ttl_seconds=ModelCacheService.CACHE_TTL)
|
||
# 重置命中计数(新缓存从1开始)
|
||
await CacheService.set(hit_count_key, 1, ttl_seconds=ModelCacheService.CACHE_TTL)
|
||
logger.debug(
|
||
f"Model 已缓存(provider+global): {provider_id[:8]}...+{global_model_id[:8]}..."
|
||
)
|
||
|
||
return model
|
||
|
||
@staticmethod
|
||
async def get_global_model_by_name(db: Session, name: str) -> GlobalModel | None:
|
||
"""
|
||
通过名称获取 GlobalModel(带缓存)
|
||
|
||
Args:
|
||
db: 数据库会话
|
||
name: GlobalModel 名称
|
||
|
||
Returns:
|
||
GlobalModel 对象或 None
|
||
"""
|
||
cache_key = f"global_model:name:{name}"
|
||
|
||
# 1. 尝试从缓存获取
|
||
cached_data = await CacheService.get(cache_key)
|
||
if cached_data:
|
||
logger.debug(f"GlobalModel 缓存命中(名称): {name}")
|
||
return ModelCacheService._dict_to_global_model(cached_data)
|
||
|
||
# 2. 缓存未命中,查询数据库
|
||
global_model = db.query(GlobalModel).filter(GlobalModel.name == name).first()
|
||
|
||
# 3. 写入缓存
|
||
if global_model:
|
||
global_model_dict = ModelCacheService._global_model_to_dict(global_model)
|
||
await CacheService.set(
|
||
cache_key, global_model_dict, ttl_seconds=ModelCacheService.CACHE_TTL
|
||
)
|
||
logger.debug(f"GlobalModel 已缓存(名称): {name}")
|
||
|
||
return global_model
|
||
|
||
@staticmethod
|
||
async def invalidate_model_cache(
|
||
model_id: str,
|
||
provider_id: str | None = None,
|
||
global_model_id: str | None = None,
|
||
provider_model_name: str | None = None,
|
||
provider_model_mappings: list | None = None,
|
||
) -> None:
|
||
"""清除 Model 缓存
|
||
|
||
Args:
|
||
model_id: Model ID
|
||
provider_id: Provider ID(用于清除 provider_global 缓存)
|
||
global_model_id: GlobalModel ID(用于清除 provider_global 缓存)
|
||
provider_model_name: provider_model_name(用于清除 resolve 缓存)
|
||
provider_model_mappings: 映射名称列表(用于清除 resolve 缓存)
|
||
"""
|
||
# 清除 model:id 缓存
|
||
await CacheService.delete(f"model:id:{model_id}")
|
||
|
||
# 清除 provider_global 缓存及其命中计数(如果提供了必要参数)
|
||
if provider_id and global_model_id:
|
||
await CacheService.delete(f"model:provider_global:{provider_id}:{global_model_id}")
|
||
await CacheService.delete(f"model:provider_global:hits:{provider_id}:{global_model_id}")
|
||
logger.debug(
|
||
f"Model 缓存已清除: {model_id}, provider_global:{provider_id[:8]}...:{global_model_id[:8]}..."
|
||
)
|
||
else:
|
||
logger.debug(f"Model 缓存已清除: {model_id}")
|
||
|
||
# 清除 resolve 缓存(provider_model_name 和 mappings 可能都被用作解析 key)
|
||
resolve_keys_to_clear = []
|
||
if provider_model_name:
|
||
resolve_keys_to_clear.append(provider_model_name)
|
||
if provider_model_mappings:
|
||
for mapping_entry in provider_model_mappings:
|
||
if isinstance(mapping_entry, dict):
|
||
mapping_name = mapping_entry.get("name", "").strip()
|
||
if mapping_name:
|
||
resolve_keys_to_clear.append(mapping_name)
|
||
|
||
for key in resolve_keys_to_clear:
|
||
await CacheService.delete(f"global_model:resolve:{key}")
|
||
|
||
if resolve_keys_to_clear:
|
||
logger.debug(f"Model resolve 缓存已清除: {resolve_keys_to_clear}")
|
||
|
||
# provider_model_mappings 更新后,需要重建映射索引缓存。
|
||
await CacheService.delete(ModelCacheService.PROVIDER_MAPPING_INDEX_CACHE_KEY)
|
||
|
||
@staticmethod
|
||
async def invalidate_global_model_cache(global_model_id: str, name: str | None = None) -> None:
|
||
"""清除 GlobalModel 缓存"""
|
||
await CacheService.delete(f"global_model:id:{global_model_id}")
|
||
if name:
|
||
await CacheService.delete(f"global_model:name:{name}")
|
||
# 同时清除 resolve 缓存,因为 GlobalModel.name 也是一个 resolve key
|
||
await CacheService.delete(f"global_model:resolve:{name}")
|
||
# 全量清除 resolve 缓存,确保映射规则变更后不命中旧缓存
|
||
try:
|
||
await CacheService.delete_pattern("global_model:resolve:*")
|
||
await CacheService.delete(ModelCacheService.PROVIDER_MAPPING_INDEX_CACHE_KEY)
|
||
await CacheService.delete(ModelCacheService.MODEL_MAPPING_RULES_CACHE_KEY)
|
||
except Exception as e:
|
||
logger.error(f"GlobalModel resolve 缓存清除失败,可能导致映射不一致: {e}")
|
||
logger.debug(f"GlobalModel 缓存已清除: {global_model_id}")
|
||
|
||
@staticmethod
|
||
async def invalidate_all_resolve_cache() -> None:
|
||
"""
|
||
清除所有 GlobalModel 解析缓存
|
||
|
||
在 Provider 启用/禁用时调用,因为 Provider 状态变更会影响模型解析结果。
|
||
"""
|
||
try:
|
||
deleted = await CacheService.delete_pattern("global_model:resolve:*")
|
||
await CacheService.delete(ModelCacheService.PROVIDER_MAPPING_INDEX_CACHE_KEY)
|
||
await CacheService.delete(ModelCacheService.MODEL_MAPPING_RULES_CACHE_KEY)
|
||
logger.debug(f"已清除 {deleted} 个 GlobalModel resolve 缓存")
|
||
except Exception as e:
|
||
logger.error(f"GlobalModel resolve 缓存清除失败: {e}")
|
||
|
||
@staticmethod
|
||
async def _get_provider_mapping_index(
|
||
db: Session,
|
||
) -> dict[str, list[dict[str, object]]]:
|
||
cached_data = await CacheService.get(ModelCacheService.PROVIDER_MAPPING_INDEX_CACHE_KEY)
|
||
if isinstance(cached_data, dict):
|
||
return {
|
||
str(name): value
|
||
for name, value in cached_data.items()
|
||
if isinstance(name, str) and isinstance(value, list)
|
||
}
|
||
|
||
from src.models.database import Provider
|
||
|
||
rows = (
|
||
db.query(Model, GlobalModel)
|
||
.join(Provider, Model.provider_id == Provider.id)
|
||
.join(GlobalModel, Model.global_model_id == GlobalModel.id)
|
||
.filter(
|
||
Provider.is_active == True,
|
||
Model.is_active == True,
|
||
GlobalModel.is_active == True,
|
||
Model.provider_model_mappings.isnot(None),
|
||
)
|
||
.all()
|
||
)
|
||
|
||
index: dict[str, list[dict[str, object]]] = {}
|
||
seen_pairs: set[tuple[str, str]] = set()
|
||
for model, global_model in rows:
|
||
raw_mappings = getattr(model, "provider_model_mappings", None)
|
||
if not isinstance(raw_mappings, list):
|
||
continue
|
||
|
||
global_model_dict = ModelCacheService._global_model_to_dict(global_model)
|
||
for raw in raw_mappings:
|
||
if not isinstance(raw, dict):
|
||
continue
|
||
name = raw.get("name")
|
||
if not isinstance(name, str):
|
||
continue
|
||
normalized_name = name.strip()
|
||
if not normalized_name:
|
||
continue
|
||
pair_key = (normalized_name, str(global_model.id))
|
||
if pair_key in seen_pairs:
|
||
continue
|
||
seen_pairs.add(pair_key)
|
||
index.setdefault(normalized_name, []).append(global_model_dict)
|
||
|
||
await CacheService.set(
|
||
ModelCacheService.PROVIDER_MAPPING_INDEX_CACHE_KEY,
|
||
index,
|
||
ttl_seconds=ModelCacheService.CACHE_TTL,
|
||
)
|
||
return index
|
||
|
||
@staticmethod
|
||
async def _get_model_mapping_rules(
|
||
db: Session,
|
||
) -> list[dict[str, object]]:
|
||
cached_data = await CacheService.get(ModelCacheService.MODEL_MAPPING_RULES_CACHE_KEY)
|
||
if isinstance(cached_data, list):
|
||
return [entry for entry in cached_data if isinstance(entry, dict)]
|
||
|
||
rows = (
|
||
db.query(GlobalModel)
|
||
.filter(GlobalModel.is_active == True, GlobalModel.config.isnot(None))
|
||
.all()
|
||
)
|
||
|
||
rules: list[dict[str, object]] = []
|
||
for global_model in rows:
|
||
config = getattr(global_model, "config", None) or {}
|
||
mappings = config.get("model_mappings")
|
||
if not isinstance(mappings, list) or not mappings:
|
||
continue
|
||
patterns = [
|
||
pattern for pattern in mappings if isinstance(pattern, str) and pattern.strip()
|
||
]
|
||
if not patterns:
|
||
continue
|
||
rules.append(
|
||
{
|
||
"global_model": ModelCacheService._global_model_to_dict(global_model),
|
||
"patterns": patterns,
|
||
}
|
||
)
|
||
|
||
await CacheService.set(
|
||
ModelCacheService.MODEL_MAPPING_RULES_CACHE_KEY,
|
||
rules,
|
||
ttl_seconds=ModelCacheService.CACHE_TTL,
|
||
)
|
||
return rules
|
||
|
||
@staticmethod
|
||
async def resolve_global_model_by_name_or_mapping(
|
||
db: Session, model_name: str
|
||
) -> GlobalModel | None:
|
||
"""
|
||
通过名称解析 GlobalModel(带缓存)
|
||
|
||
查找顺序:
|
||
1. 检查缓存
|
||
2. 直接匹配 GlobalModel.name
|
||
3. 通过 provider_model_name 匹配(查询 Model 表)
|
||
4. 通过 provider_model_mappings 匹配(查询 Model 表)
|
||
5. 通过 GlobalModel.config.model_mappings 匹配(支持正则)
|
||
|
||
注意:provider_model_mappings 是 Provider 级别的映射配置,可能存在跨 Provider 冲突;
|
||
如匹配到多个 GlobalModel,将记录告警并选择第一个匹配结果。
|
||
|
||
Args:
|
||
db: 数据库会话
|
||
model_name: 模型名称(可以是 GlobalModel.name 或 provider_model_name)
|
||
|
||
Returns:
|
||
GlobalModel 对象或 None
|
||
"""
|
||
start_time = time.time()
|
||
resolution_method = "not_found"
|
||
cache_hit = False
|
||
|
||
normalized_name = model_name.strip()
|
||
if not normalized_name:
|
||
return None
|
||
|
||
cache_key = f"global_model:resolve:{normalized_name}"
|
||
|
||
try:
|
||
# 1. 尝试从缓存获取
|
||
cached_data = await CacheService.get(cache_key)
|
||
if cached_data:
|
||
if cached_data == "NOT_FOUND":
|
||
# 缓存的负结果
|
||
cache_hit = True
|
||
resolution_method = "not_found"
|
||
logger.debug(f"GlobalModel 缓存命中(映射解析-未找到): {normalized_name}")
|
||
return None
|
||
if isinstance(cached_data, dict) and "supported_capabilities" not in cached_data:
|
||
# 兼容旧缓存:字段不全时视为未命中,走 DB 刷新
|
||
logger.debug(f"GlobalModel 缓存命中但 schema 过旧,刷新: {normalized_name}")
|
||
else:
|
||
cache_hit = True
|
||
resolution_method = "direct_match" # 缓存命中时无法区分原始解析方式
|
||
logger.debug(f"GlobalModel 缓存命中(映射解析): {normalized_name}")
|
||
return ModelCacheService._dict_to_global_model(cached_data)
|
||
|
||
# 2. 直接通过 GlobalModel.name 匹配(优先级最高)
|
||
# 说明:如果存在同名 GlobalModel,应优先解析为 GlobalModel 本身,
|
||
# 避免被某个 Provider 的 provider_model_name 误导导致解析到错误的 GlobalModel。
|
||
global_model = (
|
||
db.query(GlobalModel)
|
||
.filter(GlobalModel.name == normalized_name, GlobalModel.is_active == True)
|
||
.first()
|
||
)
|
||
|
||
if global_model:
|
||
resolution_method = "direct_match"
|
||
global_model_dict = ModelCacheService._global_model_to_dict(global_model)
|
||
await CacheService.set(
|
||
cache_key, global_model_dict, ttl_seconds=ModelCacheService.CACHE_TTL
|
||
)
|
||
logger.debug(f"GlobalModel 已缓存(映射解析-直接匹配): {normalized_name}")
|
||
return global_model
|
||
|
||
# 3. 通过 provider_model_name 匹配
|
||
from src.models.database import Provider
|
||
|
||
models_with_global = (
|
||
db.query(Model, GlobalModel)
|
||
.join(Provider, Model.provider_id == Provider.id)
|
||
.join(GlobalModel, Model.global_model_id == GlobalModel.id)
|
||
.filter(
|
||
Provider.is_active == True,
|
||
Model.is_active == True,
|
||
GlobalModel.is_active == True,
|
||
Model.provider_model_name == normalized_name,
|
||
)
|
||
.all()
|
||
)
|
||
|
||
# 收集匹配的 GlobalModel(只通过 provider_model_name 匹配)
|
||
matched_global_models: list[GlobalModel] = []
|
||
seen_global_model_ids: set[str] = set()
|
||
for model, gm in models_with_global:
|
||
if gm.id not in seen_global_model_ids:
|
||
seen_global_model_ids.add(gm.id)
|
||
matched_global_models.append(gm)
|
||
logger.debug(
|
||
f"模型名称 '{normalized_name}' 通过 provider_model_name 匹配到 "
|
||
f"GlobalModel: {gm.name} (Model: {model.id[:8]}...)"
|
||
)
|
||
|
||
# 如果通过 provider_model_name 找到了,返回
|
||
if matched_global_models:
|
||
resolution_method = "provider_model_name"
|
||
|
||
if len(matched_global_models) > 1:
|
||
# 检测到冲突(多个不同的 GlobalModel 有相同的 provider_model_name)
|
||
model_names = [gm.name for gm in matched_global_models if gm.name]
|
||
logger.warning(
|
||
f"模型映射冲突: 名称 '{normalized_name}' 匹配到多个不同的 GlobalModel: "
|
||
f"{', '.join(model_names)},使用第一个匹配结果"
|
||
)
|
||
# 记录冲突指标
|
||
model_mapping_conflict_total.inc()
|
||
|
||
# 返回第一个匹配的 GlobalModel
|
||
result_global_model = matched_global_models[0]
|
||
global_model_dict = ModelCacheService._global_model_to_dict(result_global_model)
|
||
await CacheService.set(
|
||
cache_key, global_model_dict, ttl_seconds=ModelCacheService.CACHE_TTL
|
||
)
|
||
logger.debug(
|
||
f"GlobalModel 已缓存(映射解析-{resolution_method}): {normalized_name} -> {result_global_model.name}"
|
||
)
|
||
return result_global_model
|
||
|
||
# 4. 通过 provider_model_mappings 匹配
|
||
provider_mapping_index = await ModelCacheService._get_provider_mapping_index(db)
|
||
mapping_matched_global_models = [
|
||
ModelCacheService._dict_to_global_model(global_model_dict)
|
||
for global_model_dict in provider_mapping_index.get(normalized_name, [])
|
||
if isinstance(global_model_dict, dict)
|
||
]
|
||
|
||
for gm in mapping_matched_global_models:
|
||
logger.debug(
|
||
f"模型名称 '{normalized_name}' 通过 provider_model_mappings 匹配到 "
|
||
f"GlobalModel: {gm.name}"
|
||
)
|
||
|
||
if mapping_matched_global_models:
|
||
resolution_method = "provider_model_mappings"
|
||
|
||
if len(mapping_matched_global_models) > 1:
|
||
model_names = [gm.name for gm in mapping_matched_global_models if gm.name]
|
||
logger.warning(
|
||
f"模型映射冲突: 名称 '{normalized_name}' 匹配到多个不同的 GlobalModel: "
|
||
f"{', '.join(model_names)},使用第一个匹配结果"
|
||
)
|
||
model_mapping_conflict_total.inc()
|
||
|
||
# 按名称排序确保确定性
|
||
result_global_model = sorted(
|
||
mapping_matched_global_models, key=lambda gm: gm.name or ""
|
||
)[0]
|
||
global_model_dict = ModelCacheService._global_model_to_dict(result_global_model)
|
||
await CacheService.set(
|
||
cache_key, global_model_dict, ttl_seconds=ModelCacheService.CACHE_TTL
|
||
)
|
||
logger.debug(
|
||
f"GlobalModel 已缓存(映射解析-{resolution_method}): "
|
||
f"{normalized_name} -> {result_global_model.name}"
|
||
)
|
||
return result_global_model
|
||
|
||
# 5. 通过 GlobalModel.config.model_mappings 匹配(支持正则)
|
||
from src.core.model_permissions import match_model_with_pattern
|
||
|
||
mapping_matches: list[GlobalModel] = []
|
||
for entry in await ModelCacheService._get_model_mapping_rules(db):
|
||
global_model_dict = entry.get("global_model")
|
||
patterns = entry.get("patterns")
|
||
if not isinstance(global_model_dict, dict) or not isinstance(patterns, list):
|
||
continue
|
||
for pattern in patterns:
|
||
if isinstance(pattern, str) and match_model_with_pattern(
|
||
pattern, normalized_name
|
||
):
|
||
mapping_matches.append(
|
||
ModelCacheService._dict_to_global_model(global_model_dict)
|
||
)
|
||
break
|
||
|
||
if mapping_matches:
|
||
resolution_method = "model_mappings"
|
||
|
||
if len(mapping_matches) > 1:
|
||
model_names = [gm.name for gm in mapping_matches if gm.name]
|
||
logger.warning(
|
||
f"模型映射冲突: 名称 '{normalized_name}' 匹配到多个不同的 GlobalModel: "
|
||
f"{', '.join(model_names)},使用第一个匹配结果"
|
||
)
|
||
model_mapping_conflict_total.inc()
|
||
|
||
# 按名称排序确保确定性
|
||
result_global_model = sorted(mapping_matches, key=lambda gm: gm.name or "")[0]
|
||
global_model_dict = ModelCacheService._global_model_to_dict(result_global_model)
|
||
await CacheService.set(
|
||
cache_key, global_model_dict, ttl_seconds=ModelCacheService.CACHE_TTL
|
||
)
|
||
logger.debug(
|
||
f"GlobalModel 已缓存(映射解析-{resolution_method}): "
|
||
f"{normalized_name} -> {result_global_model.name}"
|
||
)
|
||
return result_global_model
|
||
|
||
# 6. 完全未找到
|
||
resolution_method = "not_found"
|
||
# 未找到匹配,缓存负结果
|
||
await CacheService.set(cache_key, "NOT_FOUND", ttl_seconds=ModelCacheService.CACHE_TTL)
|
||
logger.debug(f"GlobalModel 未找到(映射解析): {normalized_name}")
|
||
return None
|
||
|
||
finally:
|
||
# 记录监控指标
|
||
duration = time.time() - start_time
|
||
model_mapping_resolution_total.labels(
|
||
method=resolution_method, cache_hit=str(cache_hit).lower()
|
||
).inc()
|
||
model_mapping_resolution_duration_seconds.labels(method=resolution_method).observe(
|
||
duration
|
||
)
|
||
|
||
@staticmethod
|
||
def _model_to_dict(model: Model) -> dict:
|
||
"""将 Model 对象转换为字典"""
|
||
return {
|
||
"id": model.id,
|
||
"provider_id": model.provider_id,
|
||
"global_model_id": model.global_model_id,
|
||
"provider_model_name": model.provider_model_name,
|
||
"provider_model_mappings": getattr(model, "provider_model_mappings", None),
|
||
"is_active": model.is_active,
|
||
"is_available": model.is_available if hasattr(model, "is_available") else True,
|
||
"price_per_request": (
|
||
float(model.price_per_request) if model.price_per_request is not None else None
|
||
),
|
||
"tiered_pricing": model.tiered_pricing,
|
||
"supports_vision": model.supports_vision,
|
||
"supports_function_calling": model.supports_function_calling,
|
||
"supports_streaming": model.supports_streaming,
|
||
"supports_extended_thinking": model.supports_extended_thinking,
|
||
"supports_image_generation": getattr(model, "supports_image_generation", None),
|
||
"config": model.config,
|
||
}
|
||
|
||
@staticmethod
|
||
def _dict_to_model(model_dict: dict) -> Model:
|
||
"""从字典重建 Model 对象"""
|
||
model = Model(
|
||
id=model_dict["id"],
|
||
provider_id=model_dict["provider_id"],
|
||
global_model_id=model_dict["global_model_id"],
|
||
provider_model_name=model_dict["provider_model_name"],
|
||
provider_model_mappings=model_dict.get("provider_model_mappings"),
|
||
is_active=model_dict["is_active"],
|
||
is_available=model_dict.get("is_available", True),
|
||
price_per_request=model_dict.get("price_per_request"),
|
||
tiered_pricing=model_dict.get("tiered_pricing"),
|
||
supports_vision=model_dict.get("supports_vision"),
|
||
supports_function_calling=model_dict.get("supports_function_calling"),
|
||
supports_streaming=model_dict.get("supports_streaming"),
|
||
supports_extended_thinking=model_dict.get("supports_extended_thinking"),
|
||
supports_image_generation=model_dict.get("supports_image_generation"),
|
||
config=model_dict.get("config"),
|
||
)
|
||
return model
|
||
|
||
@staticmethod
|
||
def _global_model_to_dict(global_model: GlobalModel) -> dict:
|
||
"""将 GlobalModel 对象转换为字典"""
|
||
return {
|
||
"id": global_model.id,
|
||
"name": global_model.name,
|
||
"display_name": global_model.display_name,
|
||
"supported_capabilities": global_model.supported_capabilities,
|
||
"config": global_model.config,
|
||
"default_tiered_pricing": global_model.default_tiered_pricing,
|
||
"default_price_per_request": (
|
||
float(global_model.default_price_per_request)
|
||
if global_model.default_price_per_request is not None
|
||
else None
|
||
),
|
||
"is_active": global_model.is_active,
|
||
}
|
||
|
||
@staticmethod
|
||
def _dict_to_global_model(global_model_dict: dict) -> GlobalModel:
|
||
"""从字典重建 GlobalModel 对象"""
|
||
global_model = GlobalModel(
|
||
id=global_model_dict["id"],
|
||
name=global_model_dict["name"],
|
||
display_name=global_model_dict.get("display_name"),
|
||
supported_capabilities=global_model_dict.get("supported_capabilities") or [],
|
||
config=global_model_dict.get("config"),
|
||
default_tiered_pricing=global_model_dict.get("default_tiered_pricing"),
|
||
default_price_per_request=global_model_dict.get("default_price_per_request"),
|
||
is_active=global_model_dict.get("is_active", True),
|
||
)
|
||
return global_model
|