PMFlow/backend/app/api/ai_steward_api/routes_mounted_ai.py

270 lines
11 KiB
Python

from __future__ import annotations
from fastapi import Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from ...core.response import ApiResponse, success_response
from ...dependencies.permissions import require_manager_user
from ...dependencies.tenant import get_tenant_id
from ...models import User
from ...schemas.mounted_ai_runtime import (
MountedAICutoverStatusPayload,
MountedAIEvalSuiteDetailPayload,
MountedAIEvalSuiteListPayload,
MountedAIHardCaseListPayload,
MountedAIReplayRunPayload,
MountedAIReplayRunRequest,
MountedAIRunDetailPayload,
MountedAIRunListPayload,
MountedAISourceActivationListPayload,
MountedAISourceActivationUpsertPayload,
MountedAISourceActivationUpsertRequest,
MountedAISourceListPayload,
MountedAIToolPolicyListPayload,
MountedAIToolPolicyUpsertPayload,
MountedAIToolPolicyUpsertRequest,
MountedAITrainingOpsPayload,
MountedAITopicListPayload,
)
from ...services.mounted_ai_runtime_service import MountedAIRuntimeService
from .shared import *
@router.get("/mounted-ai/sources", response_model=ApiResponse[MountedAISourceListPayload])
async def list_mounted_ai_sources(
current_user: User = Depends(require_manager_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id)
data = await MountedAIRuntimeService(db).list_sources(tenant_id=effective_tenant_id)
return success_response(data)
@router.get("/mounted-ai/source-activations", response_model=ApiResponse[MountedAISourceActivationListPayload])
async def list_mounted_ai_source_activations(
current_user: User = Depends(require_manager_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id)
data = await MountedAIRuntimeService(db).list_source_activations(tenant_id=effective_tenant_id)
return success_response(data)
@router.get("/mounted-ai/tool-policies", response_model=ApiResponse[MountedAIToolPolicyListPayload])
async def list_mounted_ai_tool_policies(
current_user: User = Depends(require_manager_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id)
data = await MountedAIRuntimeService(db).list_tool_policies(tenant_id=effective_tenant_id)
return success_response(data)
@router.get("/mounted-ai/topics", response_model=ApiResponse[MountedAITopicListPayload])
async def list_mounted_ai_topics(
mode: str | None = Query(default=None),
limit: int = Query(default=20, ge=1, le=100),
offset: int = Query(default=0, ge=0),
current_user: User = Depends(require_manager_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id)
data = await MountedAIRuntimeService(db).list_topics(
tenant_id=effective_tenant_id,
limit=limit,
offset=offset,
mode=mode,
)
return success_response(data)
@router.get("/mounted-ai/runs", response_model=ApiResponse[MountedAIRunListPayload])
async def list_mounted_ai_runs(
status: str | None = Query(default=None),
limit: int = Query(default=20, ge=1, le=100),
offset: int = Query(default=0, ge=0),
current_user: User = Depends(require_manager_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id)
data = await MountedAIRuntimeService(db).list_runs(
tenant_id=effective_tenant_id,
limit=limit,
offset=offset,
status=status,
)
return success_response(data)
@router.get("/mounted-ai/runs/{run_id}", response_model=ApiResponse[MountedAIRunDetailPayload])
async def get_mounted_ai_run_detail(
run_id: int,
current_user: User = Depends(require_manager_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id)
try:
data = await MountedAIRuntimeService(db).get_run_detail(
tenant_id=effective_tenant_id,
run_id=run_id,
)
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
return success_response(data)
@router.get("/mounted-ai/cutover-status", response_model=ApiResponse[MountedAICutoverStatusPayload])
async def get_mounted_ai_cutover_status(
current_user: User = Depends(require_manager_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id)
data = await MountedAIRuntimeService(db).get_cutover_status(tenant_id=effective_tenant_id)
return success_response(data)
@router.get("/mounted-ai/eval-suites", response_model=ApiResponse[MountedAIEvalSuiteListPayload])
async def list_mounted_ai_eval_suites(
current_user: User = Depends(require_manager_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id)
data = await MountedAIRuntimeService(db).list_eval_suites(tenant_id=effective_tenant_id)
return success_response(data)
@router.get("/mounted-ai/eval-suites/{dataset_id}", response_model=ApiResponse[MountedAIEvalSuiteDetailPayload])
async def get_mounted_ai_eval_suite_detail(
dataset_id: int,
current_user: User = Depends(require_manager_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id)
try:
data = await MountedAIRuntimeService(db).get_eval_suite_detail(
tenant_id=effective_tenant_id,
dataset_id=dataset_id,
)
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
return success_response(data)
@router.get("/mounted-ai/training-ops", response_model=ApiResponse[MountedAITrainingOpsPayload])
async def get_mounted_ai_training_ops(
current_user: User = Depends(require_manager_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id)
data = await MountedAIRuntimeService(db).list_training_ops(tenant_id=effective_tenant_id)
return success_response(data)
@router.get("/mounted-ai/hard-cases", response_model=ApiResponse[MountedAIHardCaseListPayload])
async def list_mounted_ai_hard_cases(
limit: int = Query(default=20, ge=1, le=100),
offset: int = Query(default=0, ge=0),
current_user: User = Depends(require_manager_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id)
data = await MountedAIRuntimeService(db).list_hard_cases(
tenant_id=effective_tenant_id,
limit=limit,
offset=offset,
)
return success_response(data)
@router.post("/mounted-ai/admin/source-activation", response_model=ApiResponse[MountedAISourceActivationUpsertPayload])
async def upsert_mounted_ai_source_activation(
payload: MountedAISourceActivationUpsertRequest,
current_user: User = Depends(require_manager_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id)
try:
data = await MountedAIRuntimeService(db).upsert_source_activation(
tenant_id=effective_tenant_id,
source_system=payload.source_system,
scope_type=payload.scope_type,
scope_ref=payload.scope_ref,
status=payload.status,
cutover_mode=payload.cutover_mode,
auto_reply_enabled=payload.auto_reply_enabled,
notes=payload.notes,
metadata_json=payload.metadata_json,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return success_response(data)
@router.post("/mounted-ai/admin/tool-policy", response_model=ApiResponse[MountedAIToolPolicyUpsertPayload])
async def upsert_mounted_ai_tool_policy(
payload: MountedAIToolPolicyUpsertRequest,
current_user: User = Depends(require_manager_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id)
try:
data = await MountedAIRuntimeService(db).upsert_tool_policy(
tenant_id=effective_tenant_id,
tool_name=payload.tool_name,
risk_level=payload.risk_level,
confirm_required=payload.confirm_required,
auto_execute_allowed=payload.auto_execute_allowed,
is_active=payload.is_active,
source_scope=payload.source_scope,
tenant_scope=payload.tenant_scope,
policy_metadata=payload.policy_metadata,
notes=payload.notes,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return success_response(data)
@router.post("/mounted-ai/admin/replay-run", response_model=ApiResponse[MountedAIReplayRunPayload])
async def replay_mounted_ai_run(
payload: MountedAIReplayRunRequest,
current_user: User = Depends(require_manager_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id)
try:
data = await MountedAIRuntimeService(db).replay_run(
tenant_id=effective_tenant_id,
actor_id=int(getattr(current_user, "id", 0) or 0),
run_id=payload.run_id,
asset_type=payload.asset_type,
bucket_key=payload.bucket_key,
question_text=payload.question_text,
answer_text=payload.answer_text,
corrected_answer=payload.corrected_answer,
notes=payload.notes,
persist_quality_asset=payload.persist_quality_asset,
overall_score=payload.overall_score,
rule_score=payload.rule_score,
llm_score=payload.llm_score,
findings=payload.findings,
)
except ValueError as exc:
message = str(exc)
raise HTTPException(status_code=404 if "not found" in message else 400, detail=message) from exc
return success_response(data)