"""CRUD routes for LLM profiles plus the scenario-patching apply endpoint.""" from __future__ import annotations from fastapi import APIRouter, HTTPException from webapp.models import ( CreateProfileRequest, LLMProfile, ProfileApplyRequest, ProfileApplyResponse, ) from webapp.services.profile_manager import profile_manager from webapp.services.yaml_patcher import apply_profiles_to_scenario router = APIRouter(prefix="/api/llm-profiles", tags=["llm-profiles"]) @router.get("", response_model=dict) def list_profiles() -> dict: """Return all saved LLM profiles.""" return {"profiles": [p.model_dump() for p in profile_manager.list_all()]} @router.post("", status_code=201, response_model=LLMProfile) def create_profile(request: CreateProfileRequest) -> LLMProfile: """Create a new LLM profile.""" return profile_manager.create( name=request.name, model=request.model, base_url=request.base_url, api_key=request.api_key, timeout_seconds=request.timeout_seconds, ) @router.put("/{profile_id}", response_model=LLMProfile) def update_profile(profile_id: str, request: CreateProfileRequest) -> LLMProfile: """Update an existing LLM profile by id.""" updated = profile_manager.update( profile_id=profile_id, name=request.name, model=request.model, base_url=request.base_url, api_key=request.api_key, timeout_seconds=request.timeout_seconds, ) if updated is None: raise HTTPException(status_code=404, detail=f"Profile not found: {profile_id}") return updated @router.delete("/{profile_id}", response_model=dict) def delete_profile(profile_id: str) -> dict: """Delete an LLM profile by id.""" deleted = profile_manager.delete(profile_id) if not deleted: raise HTTPException(status_code=404, detail=f"Profile not found: {profile_id}") return {"deleted": True} @router.post("/apply", response_model=ProfileApplyResponse) def apply_profiles(request: ProfileApplyRequest) -> ProfileApplyResponse: """Patch selected LLM profiles into the target scenario YAML file.""" role_profiles: dict[str, LLMProfile | None] = { "judge": profile_manager.get(request.judge_profile_id) if request.judge_profile_id else None, "answer": profile_manager.get(request.answer_profile_id) if request.answer_profile_id else None, "dataset": profile_manager.get(request.dataset_profile_id) if request.dataset_profile_id else None, } missing = [ role for role, pid in [ ("judge", request.judge_profile_id), ("answer", request.answer_profile_id), ("dataset", request.dataset_profile_id), ] if pid and role_profiles[role] is None ] if missing: raise HTTPException( status_code=400, detail=f"Profile(s) not found for roles: {', '.join(missing)}", ) patched = apply_profiles_to_scenario( scenario_path=request.scenario_path, judge_profile=role_profiles["judge"], answer_profile=role_profiles["answer"], dataset_profile=role_profiles["dataset"], ) return ProfileApplyResponse( scenario_path=request.scenario_path, patched_fields=patched, )