generated from kgod/ai-review-template
154 lines
4.9 KiB
Python
154 lines
4.9 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
from pathlib import Path
|
|
from typing import Any
|
|
from uuid import uuid4
|
|
|
|
from fastapi import FastAPI, Response, status
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import JSONResponse
|
|
|
|
from .agent import ResumeAgent
|
|
from .database import Database
|
|
from .fsm import FSMError
|
|
from .llm_services import build_services
|
|
from .models import (
|
|
ActionResponse,
|
|
ComponentEventRequest,
|
|
CreateResumeRequest,
|
|
CreateResumeResponse,
|
|
CreateSessionRequest,
|
|
ErrorDetail,
|
|
MessageRequest,
|
|
TimelineResponse,
|
|
)
|
|
from .services import (
|
|
ExperienceExtractor,
|
|
ResumeRewriter,
|
|
)
|
|
from .settings import Settings, load_settings
|
|
|
|
|
|
API_PREFIX = "/ai-api/resume-agent"
|
|
|
|
|
|
def create_app(
|
|
*,
|
|
database_path: str | Path | None = None,
|
|
extractor: ExperienceExtractor | None = None,
|
|
rewriter: ResumeRewriter | None = None,
|
|
cors_origins: list[str] | None = None,
|
|
settings: Settings | None = None,
|
|
openai_client: Any | None = None,
|
|
) -> FastAPI:
|
|
default_database = Path(__file__).resolve().parent.parent / "data" / "resume_agent.db"
|
|
database = Database(database_path or os.getenv("RESUME_AGENT_DATABASE", default_database))
|
|
database.initialize()
|
|
if extractor is None or rewriter is None:
|
|
default_extractor, default_rewriter = build_services(
|
|
settings or load_settings(), openai_client
|
|
)
|
|
extractor = extractor or default_extractor
|
|
rewriter = rewriter or default_rewriter
|
|
agent = ResumeAgent(database, extractor, rewriter)
|
|
application = FastAPI(
|
|
title="Resume Agent MVP",
|
|
version="0.1.0",
|
|
description="SQLite-backed resume workflow implemented as an explicit finite-state machine.",
|
|
)
|
|
origins = cors_origins or _cors_origins_from_environment()
|
|
application.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=origins,
|
|
allow_credentials="*" not in origins,
|
|
allow_methods=["GET", "POST", "DELETE", "OPTIONS"],
|
|
allow_headers=["*"],
|
|
)
|
|
application.state.database = database
|
|
application.state.resume_agent = agent
|
|
|
|
@application.exception_handler(FSMError)
|
|
async def handle_fsm_error(_request: Any, exc: FSMError) -> JSONResponse:
|
|
trace_id = f"trace_{uuid4().hex}"
|
|
detail = ErrorDetail(
|
|
code=exc.code,
|
|
message=exc.message,
|
|
missing_fields=exc.missing_fields,
|
|
trace_id=trace_id,
|
|
)
|
|
return JSONResponse(
|
|
status_code=exc.status_code,
|
|
content={"error": detail.model_dump(mode="json"), "trace_id": trace_id},
|
|
)
|
|
|
|
@application.get("/health", tags=["system"])
|
|
def health() -> dict[str, str]:
|
|
return {"status": "ok"}
|
|
|
|
@application.post(
|
|
f"{API_PREFIX}/sessions",
|
|
response_model=TimelineResponse,
|
|
status_code=status.HTTP_201_CREATED,
|
|
tags=["resume-agent"],
|
|
)
|
|
def create_session(request: CreateSessionRequest | None = None) -> TimelineResponse:
|
|
return agent.create_session(request or CreateSessionRequest())
|
|
|
|
@application.get(
|
|
f"{API_PREFIX}/sessions/{{session_id}}/timeline",
|
|
response_model=TimelineResponse,
|
|
tags=["resume-agent"],
|
|
)
|
|
def get_timeline(session_id: str) -> TimelineResponse:
|
|
return agent.timeline(session_id)
|
|
|
|
@application.post(
|
|
f"{API_PREFIX}/sessions/{{session_id}}/component-events",
|
|
response_model=ActionResponse,
|
|
tags=["resume-agent"],
|
|
)
|
|
def post_component_event(
|
|
session_id: str, request: ComponentEventRequest
|
|
) -> ActionResponse:
|
|
return agent.component_event(session_id, request)
|
|
|
|
@application.post(
|
|
f"{API_PREFIX}/sessions/{{session_id}}/messages",
|
|
response_model=ActionResponse,
|
|
tags=["resume-agent"],
|
|
)
|
|
def post_message(session_id: str, request: MessageRequest) -> ActionResponse:
|
|
return agent.add_message(session_id, request)
|
|
|
|
@application.post(
|
|
f"{API_PREFIX}/sessions/{{session_id}}/create",
|
|
response_model=CreateResumeResponse,
|
|
tags=["resume-agent"],
|
|
)
|
|
def create_resume(
|
|
session_id: str, request: CreateResumeRequest | None = None
|
|
) -> CreateResumeResponse:
|
|
return agent.create_resume(session_id, request or CreateResumeRequest())
|
|
|
|
@application.delete(
|
|
f"{API_PREFIX}/sessions/{{session_id}}",
|
|
status_code=status.HTTP_204_NO_CONTENT,
|
|
tags=["resume-agent"],
|
|
)
|
|
def delete_session(session_id: str) -> Response:
|
|
agent.delete_session(session_id)
|
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
|
|
|
return application
|
|
|
|
|
|
def _cors_origins_from_environment() -> list[str]:
|
|
configured = os.getenv("RESUME_AGENT_CORS_ORIGINS")
|
|
if configured:
|
|
return [origin.strip() for origin in configured.split(",") if origin.strip()]
|
|
return ["http://localhost:5173", "http://127.0.0.1:5173"]
|
|
|
|
|
|
app = create_app()
|