generated from kgod/ai-review-template
feat: add resume agent MVP
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user