"""AI routing and provider governance API.""" from __future__ import annotations from typing import Any, Literal, Optional from fastapi import APIRouter, Depends from fastapi.responses import JSONResponse from pydantic import BaseModel, Field from sqlalchemy.ext.asyncio import AsyncSession from ..core.database import get_db from ..core.response import error_response, success_response from ..dependencies.auth import get_current_user from ..dependencies.tenant import get_tenant_id from ..models import User from ..services.ai_routing_governance_service import AIRoutingGovernanceService from ..services.auth import can_approve_ai_routing_change, can_operate_ai_routing, can_view_ai_policy from ..services.operation_log_service import log_operation router = APIRouter(prefix="/ai-routing", tags=["ai_routing"]) class TeamPolicySceneUpdate(BaseModel): key: str = Field(min_length=1, max_length=80) policy: str = Field(min_length=1, max_length=300) mode: Optional[str] = Field(default=None, max_length=80) human_confirmation: bool = True protected: bool = True class TeamPolicyUpdateRequest(BaseModel): strategy: Literal["quality_first", "balanced", "speed_first"] = "balanced" scenes: list[TeamPolicySceneUpdate] = Field(default_factory=list) automation_boundary: Optional[str] = Field(default=None, max_length=300) budget_guard: dict[str, Any] = Field(default_factory=dict) quality_targets: list[str] = Field(default_factory=list, max_length=8) def _forbidden(message: str) -> JSONResponse: return JSONResponse(status_code=403, content=error_response(403, message)) def _service(db: AsyncSession, tenant_id: int) -> AIRoutingGovernanceService: return AIRoutingGovernanceService(db, tenant_id=int(tenant_id)) @router.get("/overview") async def get_ai_routing_overview( db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), current_user: User = Depends(get_current_user), ): if not can_view_ai_policy(str(current_user.role or "")): return _forbidden("仅团队负责人可查看 AI 路由概览") data = await _service(db, tenant_id).get_overview() return success_response(data) @router.get("/team-policy") async def get_ai_team_policy( db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), current_user: User = Depends(get_current_user), ): if not can_view_ai_policy(str(current_user.role or "")): return _forbidden("仅团队负责人可查看团队 AI 策略") data = await _service(db, tenant_id).get_team_policy() return success_response(data) @router.put("/team-policy") async def update_ai_team_policy( body: TeamPolicyUpdateRequest, db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), current_user: User = Depends(get_current_user), ): if not can_approve_ai_routing_change(str(current_user.role or "")): return _forbidden("仅 OWNER 可保存团队 AI 策略") service = _service(db, tenant_id) before = await service.get_team_policy() after = await service.update_team_policy( payload=body.model_dump(mode="json"), updated_by=int(current_user.id), ) await log_operation( db, tenant_id=int(tenant_id), user_id=int(current_user.id), project_id=None, entity_type="ai_routing_policy", entity_id="team-policy", action="update", before_data=before, after_data=after, remark="更新团队 AI 策略", ) await db.commit() return success_response(after) @router.get("/admin-console") async def get_ai_routing_admin_console( db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), current_user: User = Depends(get_current_user), ): if not can_view_ai_policy(str(current_user.role or "")): return _forbidden("仅团队负责人可查看 AI 路由控制台") data = await _service(db, tenant_id).get_admin_console() return success_response(data) @router.post("/diagnostics/run") async def run_ai_routing_diagnostics( db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), current_user: User = Depends(get_current_user), ): if not can_operate_ai_routing(str(current_user.role or "")): return _forbidden("仅 OWNER / REVIEWER 可执行 AI 路由诊断") data = await _service(db, tenant_id).run_diagnostics() return success_response(data)