初始化提交

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

+157
View File
@@ -0,0 +1,157 @@
"""设置服务:模型网关状态聚合与生成偏好。
模型网关状态从 config/models.yaml 派生,provider 密钥按 settings 是否配置脱敏为
configured 布尔(绝不暴露值);生成偏好为本地单用户单行,无则返回默认值。
"""
from app.ai_provider.config.loader import load_models_config
from app.ai_provider.config.models import ModelConfig
from app.core.config import settings
from app.core.time import utc_now
from app.db.session import AsyncSessionLocal
from app.models.governance import GenerationPreferences
from app.repositories import preferences as preferences_repo
from app.schemas.preferences import (
GenerationPreferencesResponse,
LogicalModel,
ModelEndpointStatus,
ModelGatewayStatus,
ProviderKeyStatus,
UpdateGenerationPreferencesRequest,
)
from app.services.ai_provider_admin import get_endpoint_overrides_cache
from app.services.serializers import generation_preferences_to_response
# provider → 持有其 API Key 的环境变量名;未登记的 provider 回退 <PROVIDER>_API_KEY。
_PROVIDER_KEY_ENV: dict[str, str] = {
"openai": "OPENAI_API_KEY",
"deepseek": "DEEPSEEK_API_KEY",
"anthropic": "ANTHROPIC_API_KEY",
"google": "GOOGLE_API_KEY",
"minimax": "MINIMAX_API_KEY",
"zhipu": "ZHIPU_API_KEY",
"tencentcloud": "TENCENTCLOUD_API_KEY",
"volcengine": "VOLCENGINE_ARK_API_KEY",
"bailian": "BAILIAN_API_KEY",
}
def provider_configured(provider: str | None) -> bool:
"""provider 的 API Key 是否已在 settings 配置(仅返回布尔,不暴露值)。"""
if not provider:
return False
env_name = _PROVIDER_KEY_ENV.get(provider, f"{provider.upper()}_API_KEY")
return bool(getattr(settings, env_name.lower(), None))
def _endpoint_status(
endpoint, model_name: str, provider: str, overrides_cache
) -> ModelEndpointStatus:
ov = overrides_cache.get((model_name, endpoint.name))
return ModelEndpointStatus(
name=endpoint.name,
provider=provider,
status=(ov.get("status") or endpoint.status) if ov else endpoint.status,
weight=endpoint.weight,
priority=endpoint.priority,
is_overridden=ov is not None,
has_url_override=bool(ov and ov.get("base_url")),
has_key_override=bool(ov and ov.get("api_key")),
base_url_env=endpoint.base_url_env,
api_key_env=endpoint.api_key_env,
)
def _logical_model(cfg: ModelConfig, overrides_cache) -> LogicalModel:
provider = cfg.provider or "unknown"
endpoints = [
_endpoint_status(ep, cfg.name, provider, overrides_cache) for ep in cfg.endpoints
] or None
return LogicalModel(
name=cfg.name,
display_name=cfg.display_name or cfg.name,
provider=provider,
capabilities=[k for k, v in cfg.resolved_capabilities().items() if v],
routing_strategy=cfg.routing_strategy,
supports_streaming=bool(cfg.supports_streaming),
supports_thinking=bool(cfg.supports_thinking),
supports_structured_output=bool(cfg.supports_structured_output),
context_window=cfg.context_window,
max_output_tokens=cfg.max_output_tokens,
endpoints=endpoints,
is_default=True if cfg.name == settings.default_model_hint else None,
)
def get_provider_gateway() -> ModelGatewayStatus:
"""从 config/models.yaml 派生模型网关总览(含 DB 覆盖标记,脱敏不回原值)。"""
models_cfg = load_models_config()
overrides_cache = get_endpoint_overrides_cache()
providers_seen: dict[str, None] = {}
logical_models: list[LogicalModel] = []
for cfg in models_cfg:
logical_models.append(_logical_model(cfg, overrides_cache))
provider = cfg.provider or "unknown"
providers_seen.setdefault(provider, None)
providers = [
ProviderKeyStatus(provider=name, configured=provider_configured(name))
for name in providers_seen
]
return ModelGatewayStatus(
models=logical_models,
providers=providers,
default_model_hint=settings.default_model_hint,
)
def _default_preferences() -> GenerationPreferences:
return GenerationPreferences(
owner_scope=settings.default_owner_scope,
default_model_hint=settings.default_model_hint,
temperature=0.7,
top_p=1.0,
max_output_tokens=4096,
streaming=True,
)
async def get_generation_preferences(
owner_scope: str = settings.default_owner_scope,
) -> GenerationPreferencesResponse:
async with AsyncSessionLocal() as session:
prefs = await preferences_repo.get_preferences(session, owner_scope)
if prefs is None:
prefs = _default_preferences()
return generation_preferences_to_response(prefs)
async def update_generation_preferences(
payload: UpdateGenerationPreferencesRequest,
owner_scope: str = settings.default_owner_scope,
) -> GenerationPreferencesResponse:
async with AsyncSessionLocal() as session:
prefs = await preferences_repo.get_preferences(session, owner_scope)
if prefs is None:
prefs = _default_preferences()
for key, value in payload.model_dump(exclude_unset=True).items():
setattr(prefs, key, value)
prefs.updated_at = utc_now()
session.add(prefs)
try:
await session.commit()
except Exception:
await session.rollback()
prefs = await preferences_repo.get_preferences(session, owner_scope)
if prefs is None:
raise
for key, value in payload.model_dump(exclude_unset=True).items():
setattr(prefs, key, value)
prefs.updated_at = utc_now()
await session.commit()
else:
for key, value in payload.model_dump(exclude_unset=True).items():
setattr(prefs, key, value)
prefs.updated_at = utc_now()
await session.commit()
await session.refresh(prefs)
return generation_preferences_to_response(prefs)