初始化提交

This commit is contained in:
chenxin committed 2026-08-21 16:00:13 +08:00
commit 887c1f449e
963 files changed
+166869

No files matched your search

+108
View File
@@ -0,0 +1,108 @@
"""AI provider 运行时治理:endpoint 覆盖(DB 优先于 .env/yaml)+ 热重载。
override 写 DB → 读 DB 全量刷 spec 同步缓存 → ProviderManager 下次构建读新缓存。
api_key 明文存 DB(本地单用户边界),响应层脱敏不回值(见 schemas)。
"""
from __future__ import annotations
from typing import Any
from app.ai_provider.config.loader import load_models_config
from app.core.errors import AppError
from app.db.session import AsyncSessionLocal
from app.models.governance import ModelEndpointOverride
from app.repositories import model_endpoints as overrides_repo
from app.schemas.preferences import UpdateEndpointOverrideRequest
from app.services.audit import record_audit_event
# —— endpoint 运行时覆盖(DB 优先于 .env/yaml)——
# reload_provider_manager 时(异步 service)读 DB 全量刷此同步缓存,
# load_models_config 仍可独立使用。None 字段表示不覆盖、回退 yaml/env。
_endpoint_overrides_cache: dict[tuple[str, str], dict[str, Any]] = {}
def set_endpoint_overrides_cache(overrides: list[dict[str, Any]]) -> None:
"""用 DB 全量覆盖列表刷新模块缓存(同步)。"""
global _endpoint_overrides_cache
_endpoint_overrides_cache = {
(o["logical_model_name"], o["endpoint_name"]): o for o in overrides
}
def get_endpoint_overrides_cache() -> dict[tuple[str, str], dict[str, Any]]:
"""读取当前覆盖缓存快照。"""
return dict(_endpoint_overrides_cache)
async def list_endpoint_overrides() -> list[dict[str, Any]]:
"""读 DB 全量覆盖,序列化为 dict 列表(供刷缓存 + 响应)。"""
async with AsyncSessionLocal() as session:
rows = await overrides_repo.list_overrides(session)
return [_override_to_dict(r) for r in rows]
async def reload_provider_manager() -> None:
"""读 DB 全量 → 刷 endpoint 覆盖同步缓存。"""
overrides = await list_endpoint_overrides()
set_endpoint_overrides_cache(overrides)
async def update_endpoint_override(
logical_model_name: str,
endpoint_name: str,
payload: UpdateEndpointOverrideRequest,
) -> dict[str, Any]:
"""upsert 覆盖(payload 字段全量写入,None 清除字段)+ reload + 审计。
校验 (model, endpoint) 存在于 yaml(避免覆盖幽灵 endpoint)。
"""
if not _endpoint_exists(logical_model_name, endpoint_name):
raise AppError("not_found", "endpoint 不存在于模型配置中。")
async with AsyncSessionLocal() as session:
override = ModelEndpointOverride(
logical_model_name=logical_model_name,
endpoint_name=endpoint_name,
base_url=payload.base_url,
api_key=payload.api_key,
status=payload.status,
weight=payload.weight,
priority=payload.priority,
)
saved = await overrides_repo.upsert_override(session, override)
await record_audit_event(
session,
action="model_endpoint.override_updated",
actor="user",
input_summary=f"{logical_model_name}/{endpoint_name}",
resource_type="model_endpoint_override",
resource_id=f"{logical_model_name}/{endpoint_name}",
)
await session.commit()
await session.refresh(saved)
result = _override_to_dict(saved)
await reload_provider_manager()
return result
def _endpoint_exists(logical_model_name: str, endpoint_name: str) -> bool:
"""校验 (model, endpoint) 在 yaml 配置中存在(避免覆盖幽灵 endpoint)。"""
models = load_models_config()
for model in models:
if model.name != logical_model_name:
continue
return any(ep.name == endpoint_name for ep in model.endpoints)
return False
def _override_to_dict(row: ModelEndpointOverride) -> dict[str, Any]:
return {
"logical_model_name": row.logical_model_name,
"endpoint_name": row.endpoint_name,
"base_url": row.base_url,
"api_key": row.api_key,
"status": row.status,
"weight": row.weight,
"priority": row.priority,
"updated_at": row.updated_at,
}