125 lines
4.4 KiB
Python
125 lines
4.4 KiB
Python
"""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)
|