PMFlow/backend/app/api/prompt_assets_api.py

373 lines
14 KiB
Python

"""Platform Prompt asset inventory API."""
from __future__ import annotations
from fastapi import APIRouter, Depends, Query
from fastapi.responses import JSONResponse
from sqlalchemy.ext.asyncio import AsyncSession
from ..core.database import get_db
from ..core.response import error_response, success_response
from ..dependencies.permissions import require_platform_ai_ops
from ..dependencies.tenant import get_tenant_id
from ..models import User
from ..schemas.prompt_assets import (
PromptAssetReleaseActionRequest,
PromptAssetReleaseCheckRunRequest,
PromptAssetReleaseCreateRequest,
PromptGoldenCaseCreateRequest,
PromptGoldenCaseFromCallRequest,
PromptReplayRunCreateRequest,
)
from ..services.prompt_assets_service import PromptAssetService
router = APIRouter(prefix="/prompt-assets", tags=["prompt_assets"])
@router.get("")
async def list_prompt_assets(
family: str | None = Query(default=None),
runtime_source: str | None = Query(default=None),
editable_policy: str | None = Query(default=None),
keyword: str | None = Query(default=None, max_length=200),
current_user: User = Depends(require_platform_ai_ops),
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 PromptAssetService(db).list_assets(
tenant_id=effective_tenant_id,
family=family,
runtime_source=runtime_source,
editable_policy=editable_policy,
keyword=keyword,
)
return success_response(data)
@router.get("/{prompt_key}")
async def get_prompt_asset(
prompt_key: str,
current_user: User = Depends(require_platform_ai_ops),
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 PromptAssetService(db).get_asset(tenant_id=effective_tenant_id, prompt_key=prompt_key)
except ValueError as exc:
return JSONResponse(status_code=404, content=error_response(404, str(exc)))
return success_response(data)
@router.get("/{prompt_key}/quality-summary")
async def get_prompt_asset_quality_summary(
prompt_key: str,
window: str = Query(default="7d", pattern="^(24h|7d|30d)$"),
status: str | None = Query(default=None, max_length=80),
release_id: int | None = Query(default=None),
current_user: User = Depends(require_platform_ai_ops),
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 PromptAssetService(db).get_quality_summary(
tenant_id=effective_tenant_id,
prompt_key=prompt_key,
window=window,
status=status,
release_id=release_id,
include_all_pmflow_feedback=True,
)
except ValueError as exc:
return JSONResponse(status_code=404, content=error_response(404, str(exc)))
return success_response(data)
@router.get("/{prompt_key}/golden-cases")
async def list_prompt_asset_golden_cases(
prompt_key: str,
status: str | None = Query(default=None, max_length=40),
current_user: User = Depends(require_platform_ai_ops),
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 PromptAssetService(db).list_golden_cases(
tenant_id=effective_tenant_id,
prompt_key=prompt_key,
status=status,
)
except ValueError as exc:
return JSONResponse(status_code=404, content=error_response(404, str(exc)))
return success_response(data)
@router.post("/{prompt_key}/golden-cases")
async def create_prompt_asset_golden_case(
prompt_key: str,
request: PromptGoldenCaseCreateRequest,
current_user: User = Depends(require_platform_ai_ops),
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 PromptAssetService(db).create_golden_case(
tenant_id=effective_tenant_id,
prompt_key=prompt_key,
case_name=request.case_name,
input_payload=request.input_payload,
expected_contract=request.expected_contract,
expected_contains=request.expected_contains,
case_type=request.case_type,
priority=request.priority,
scenario_key=request.scenario_key,
tags=request.tags,
created_by=int(current_user.id) if getattr(current_user, "id", None) is not None else None,
notes=request.notes,
)
except ValueError as exc:
return JSONResponse(status_code=400, content=error_response(400, str(exc)))
return success_response(data)
@router.post("/{prompt_key}/golden-cases/from-call/{ai_call_log_id}")
async def create_prompt_asset_golden_case_from_call(
prompt_key: str,
ai_call_log_id: int,
request: PromptGoldenCaseFromCallRequest | None = None,
current_user: User = Depends(require_platform_ai_ops),
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 PromptAssetService(db).create_golden_case_from_call(
tenant_id=effective_tenant_id,
prompt_key=prompt_key,
ai_call_log_id=ai_call_log_id,
created_by=int(current_user.id) if getattr(current_user, "id", None) is not None else None,
case_name=request.case_name if request else None,
notes=request.notes if request else None,
)
except ValueError as exc:
return JSONResponse(status_code=400, content=error_response(400, str(exc)))
return success_response(data)
@router.get("/{prompt_key}/releases")
async def list_prompt_asset_releases(
prompt_key: str,
current_user: User = Depends(require_platform_ai_ops),
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 PromptAssetService(db).list_releases(tenant_id=effective_tenant_id, prompt_key=prompt_key)
except ValueError as exc:
return JSONResponse(status_code=404, content=error_response(404, str(exc)))
return success_response(data)
@router.post("/{prompt_key}/releases")
async def create_prompt_asset_release(
prompt_key: str,
request: PromptAssetReleaseCreateRequest,
current_user: User = Depends(require_platform_ai_ops),
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 PromptAssetService(db).create_release(
tenant_id=effective_tenant_id,
prompt_key=prompt_key,
body_snapshot=request.body_snapshot,
created_by=int(current_user.id) if getattr(current_user, "id", None) is not None else None,
notes=request.notes,
)
except ValueError as exc:
return JSONResponse(status_code=400, content=error_response(400, str(exc)))
return success_response(data)
@router.post("/{prompt_key}/releases/{release_id}/activate")
async def activate_prompt_asset_release(
prompt_key: str,
release_id: int,
request: PromptAssetReleaseActionRequest | None = None,
current_user: User = Depends(require_platform_ai_ops),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
_ = request
effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id)
try:
data = await PromptAssetService(db).activate_release(
tenant_id=effective_tenant_id,
prompt_key=prompt_key,
release_id=release_id,
actor_id=int(current_user.id) if getattr(current_user, "id", None) is not None else None,
)
except ValueError as exc:
return JSONResponse(status_code=400, content=error_response(400, str(exc)))
return success_response(data)
@router.get("/{prompt_key}/releases/{release_id}/checks")
async def list_prompt_asset_release_checks(
prompt_key: str,
release_id: int,
current_user: User = Depends(require_platform_ai_ops),
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 PromptAssetService(db).list_release_checks(
tenant_id=effective_tenant_id,
prompt_key=prompt_key,
release_id=release_id,
)
except ValueError as exc:
return JSONResponse(status_code=404, content=error_response(404, str(exc)))
return success_response(data)
@router.post("/{prompt_key}/releases/{release_id}/checks/run")
async def run_prompt_asset_release_checks(
prompt_key: str,
release_id: int,
request: PromptAssetReleaseCheckRunRequest | None = None,
current_user: User = Depends(require_platform_ai_ops),
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 PromptAssetService(db).run_release_checks(
tenant_id=effective_tenant_id,
prompt_key=prompt_key,
release_id=release_id,
actor_id=int(current_user.id) if getattr(current_user, "id", None) is not None else None,
reason=request.reason if request else None,
)
except ValueError as exc:
return JSONResponse(status_code=400, content=error_response(400, str(exc)))
return success_response(data)
@router.post("/{prompt_key}/releases/{release_id}/replay-runs")
async def create_prompt_asset_replay_run(
prompt_key: str,
release_id: int,
request: PromptReplayRunCreateRequest,
current_user: User = Depends(require_platform_ai_ops),
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 PromptAssetService(db).create_replay_run(
tenant_id=effective_tenant_id,
prompt_key=prompt_key,
release_id=release_id,
created_by=int(current_user.id) if getattr(current_user, "id", None) is not None else None,
mode=request.mode,
case_status=request.case_status,
max_cases=request.max_cases,
include_llm=request.include_llm,
judge_enabled=request.judge_enabled,
reason=request.reason,
)
except ValueError as exc:
return JSONResponse(status_code=400, content=error_response(400, str(exc)))
return success_response(data)
@router.get("/{prompt_key}/releases/{release_id}/watch-summary")
async def get_prompt_asset_release_watch_summary(
prompt_key: str,
release_id: int,
current_user: User = Depends(require_platform_ai_ops),
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 PromptAssetService(db).get_release_watch_summary(
tenant_id=effective_tenant_id,
prompt_key=prompt_key,
release_id=release_id,
)
except ValueError as exc:
return JSONResponse(status_code=404, content=error_response(404, str(exc)))
return success_response(data)
@router.get("/{prompt_key}/releases/{release_id}/replay-runs")
async def list_prompt_asset_replay_runs(
prompt_key: str,
release_id: int,
current_user: User = Depends(require_platform_ai_ops),
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 PromptAssetService(db).list_replay_runs(
tenant_id=effective_tenant_id,
prompt_key=prompt_key,
release_id=release_id,
)
except ValueError as exc:
return JSONResponse(status_code=404, content=error_response(404, str(exc)))
return success_response(data)
@router.get("/{prompt_key}/replay-runs/{run_id}")
async def get_prompt_asset_replay_run(
prompt_key: str,
run_id: int,
current_user: User = Depends(require_platform_ai_ops),
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 PromptAssetService(db).get_replay_run(
tenant_id=effective_tenant_id,
prompt_key=prompt_key,
run_id=run_id,
)
except ValueError as exc:
return JSONResponse(status_code=404, content=error_response(404, str(exc)))
return success_response(data)
@router.post("/{prompt_key}/releases/{release_id}/rollback")
async def rollback_prompt_asset_release(
prompt_key: str,
release_id: int,
request: PromptAssetReleaseActionRequest | None = None,
current_user: User = Depends(require_platform_ai_ops),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
_ = request
effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id)
try:
data = await PromptAssetService(db).rollback_release(
tenant_id=effective_tenant_id,
prompt_key=prompt_key,
release_id=release_id,
actor_id=int(current_user.id) if getattr(current_user, "id", None) is not None else None,
)
except ValueError as exc:
return JSONResponse(status_code=400, content=error_response(400, str(exc)))
return success_response(data)