mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-12 12:09:50 +08:00
feat: 添加格式转换追踪、模型过滤规则和 Provider 超时配置
1. Usage 格式转换追踪 - 新增 endpoint_api_format 和 has_format_conversion 字段 - 在用量记录中显示格式转换信息(请求格式 → 端点格式) - 兼容历史数据的回填逻辑 2. Provider API Key 模型过滤规则 - 新增 model_include_patterns 和 model_exclude_patterns 字段 - 支持 * 和 ? 通配符,不区分大小写 - 自动获取模型时应用过滤规则 3. Provider 超时配置 - 新增 stream_first_byte_timeout 和 request_timeout 字段 - 支持每个 Provider 单独配置超时时间 - 优先使用 Provider 配置,否则回退到全局配置 Close #122, Close #123 Co-Authored-By: AAEE86 <[email protected]>
This commit is contained in:
@@ -224,6 +224,10 @@ class AdminUpdateEndpointKeyAdapter(AdminApiAdapter):
|
||||
# 记录 allowed_models 变化前的值
|
||||
allowed_models_before = set(key.allowed_models or [])
|
||||
|
||||
# 记录过滤规则变化前的值(用于检测是否需要重新应用过滤)
|
||||
include_patterns_before = key.model_include_patterns
|
||||
exclude_patterns_before = key.model_exclude_patterns
|
||||
|
||||
update_data = self.key_data.model_dump(exclude_unset=True)
|
||||
if "api_key" in update_data:
|
||||
update_data["api_key"] = crypto_service.encrypt(update_data["api_key"])
|
||||
@@ -247,6 +251,17 @@ class AdminUpdateEndpointKeyAdapter(AdminApiAdapter):
|
||||
if isinstance(lm, list) and len(lm) == 0:
|
||||
update_data["locked_models"] = None
|
||||
|
||||
# 处理模型过滤规则:空字符串 -> None
|
||||
if "model_include_patterns" in update_data:
|
||||
patterns = update_data["model_include_patterns"]
|
||||
if isinstance(patterns, list) and len(patterns) == 0:
|
||||
update_data["model_include_patterns"] = None
|
||||
|
||||
if "model_exclude_patterns" in update_data:
|
||||
patterns = update_data["model_exclude_patterns"]
|
||||
if isinstance(patterns, list) and len(patterns) == 0:
|
||||
update_data["model_exclude_patterns"] = None
|
||||
|
||||
for field, value in update_data.items():
|
||||
setattr(key, field, value)
|
||||
key.updated_at = datetime.now(timezone.utc)
|
||||
@@ -285,6 +300,27 @@ class AdminUpdateEndpointKeyAdapter(AdminApiAdapter):
|
||||
)
|
||||
db.commit()
|
||||
db.refresh(key)
|
||||
elif auto_fetch_enabled_after:
|
||||
# auto_fetch_models 保持开启状态,检查过滤规则是否变更
|
||||
include_patterns_after = key.model_include_patterns
|
||||
exclude_patterns_after = key.model_exclude_patterns
|
||||
patterns_changed = (
|
||||
include_patterns_before != include_patterns_after
|
||||
or exclude_patterns_before != exclude_patterns_after
|
||||
)
|
||||
if patterns_changed:
|
||||
# 过滤规则变更,重新应用过滤(使用缓存的上游模型数据)
|
||||
logger.info(
|
||||
"[AUTO_FETCH] Key %s 过滤规则变更,重新应用过滤",
|
||||
self.key_id,
|
||||
)
|
||||
try:
|
||||
from src.services.model.fetch_scheduler import get_model_fetch_scheduler
|
||||
|
||||
scheduler = get_model_fetch_scheduler()
|
||||
await scheduler._fetch_models_for_key_by_id(self.key_id)
|
||||
except Exception as e:
|
||||
logger.error(f"重新应用过滤规则失败: {e}")
|
||||
|
||||
# 任何字段更新都清除缓存,确保缓存一致性
|
||||
# 包括 is_active、allowed_models、capabilities 等影响权限和行为的字段
|
||||
@@ -622,6 +658,12 @@ class AdminCreateProviderKeyAdapter(AdminApiAdapter):
|
||||
max_probe_interval_minutes=self.key_data.max_probe_interval_minutes,
|
||||
auto_fetch_models=self.key_data.auto_fetch_models,
|
||||
locked_models=self.key_data.locked_models if self.key_data.locked_models else None,
|
||||
model_include_patterns=(
|
||||
self.key_data.model_include_patterns if self.key_data.model_include_patterns else None
|
||||
),
|
||||
model_exclude_patterns=(
|
||||
self.key_data.model_exclude_patterns if self.key_data.model_exclude_patterns else None
|
||||
),
|
||||
request_count=0,
|
||||
success_count=0,
|
||||
error_count=0,
|
||||
|
||||
@@ -295,6 +295,9 @@ class AdminCreateProviderAdapter(AdminApiAdapter):
|
||||
concurrent_limit=validated_data.concurrent_limit,
|
||||
max_retries=validated_data.max_retries,
|
||||
proxy=validated_data.proxy.model_dump() if validated_data.proxy else None,
|
||||
# 超时配置
|
||||
stream_first_byte_timeout=validated_data.stream_first_byte_timeout,
|
||||
request_timeout=validated_data.request_timeout,
|
||||
config=validated_data.config,
|
||||
)
|
||||
|
||||
|
||||
@@ -310,6 +310,8 @@ def _build_provider_summary(db: Session, provider: Provider) -> ProviderWithEndp
|
||||
quota_expires_at=provider.quota_expires_at,
|
||||
max_retries=provider.max_retries,
|
||||
proxy=provider.proxy,
|
||||
stream_first_byte_timeout=provider.stream_first_byte_timeout,
|
||||
request_timeout=provider.request_timeout,
|
||||
total_endpoints=total_endpoints,
|
||||
active_endpoints=active_endpoints,
|
||||
total_keys=total_keys,
|
||||
|
||||
@@ -802,6 +802,22 @@ class AdminUsageRecordsAdapter(AdminApiAdapter):
|
||||
if usage.provider_id and str(usage.provider_id) in provider_map:
|
||||
provider_name = provider_map[str(usage.provider_id)]
|
||||
|
||||
# 格式转换追踪(兼容历史数据:尽量回填可展示信息)
|
||||
api_format = usage.api_format or (
|
||||
endpoint.api_format if endpoint and endpoint.api_format else None
|
||||
)
|
||||
endpoint_api_format = usage.endpoint_api_format or (
|
||||
endpoint.api_format if endpoint else None
|
||||
)
|
||||
|
||||
has_format_conversion = usage.has_format_conversion
|
||||
if has_format_conversion is None:
|
||||
client_fmt = str(api_format or "").upper()
|
||||
endpoint_fmt = str(endpoint_api_format or "").upper()
|
||||
has_format_conversion = bool(
|
||||
client_fmt and endpoint_fmt and client_fmt != endpoint_fmt
|
||||
)
|
||||
|
||||
data.append(
|
||||
{
|
||||
"id": usage.id,
|
||||
@@ -842,8 +858,9 @@ class AdminUsageRecordsAdapter(AdminApiAdapter):
|
||||
"has_fallback": fallback_map.get(usage.request_id, False),
|
||||
"has_retry": retry_map.get(usage.request_id, False),
|
||||
"has_rectified": rectified_map.get(usage.request_id, False),
|
||||
"api_format": usage.api_format
|
||||
or (endpoint.api_format if endpoint and endpoint.api_format else None),
|
||||
"api_format": api_format,
|
||||
"endpoint_api_format": endpoint_api_format,
|
||||
"has_format_conversion": bool(has_format_conversion),
|
||||
"api_key_name": provider_api_key.name if provider_api_key else None,
|
||||
"request_metadata": usage.request_metadata, # Provider 响应元数据
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user