Files
resume-agent/backend/app/resume_routes.py
T

288 lines
9.7 KiB
Python

"""FastAPI route registration for resume editing and optimization."""
from typing import Any, Callable
from fastapi import FastAPI, Header
from fastapi.responses import StreamingResponse
from .agent import ResumeAgent
from .fsm import FSMError
from .models import ActionResponse, OptimizeEntryRequest, OptimizeRequest, ResumePatchRequest
from .optimization_models import (
OptimizationRunView,
OptimizationStartRequest,
TargetPositionRequest,
)
from .resume_api_models import SkillRecommendationRequest, SkillRecommendationResponse
def register_resume_routes(
application: FastAPI,
agent: ResumeAgent,
prefix: str,
*,
authorize_session_request: Callable[[str, str | None], str | None] | None = None,
) -> None:
authorize_session_request = authorize_session_request or (
lambda _session_id, _authorization: None
)
def mutate_with_sync(
session_id: str,
external_token: str | None,
operation: Callable[[], Any],
*,
allow_pulled_resume: bool = False,
) -> Any:
if external_token is None:
return operation()
return agent.mutate_with_offerpai_sync(
session_id,
external_token,
operation,
allow_pulled_resume=allow_pulled_resume,
)
@application.patch(
f"{prefix}/sessions/{{session_id}}/resume",
response_model=ActionResponse,
tags=["resume-agent"],
)
def patch_resume(
session_id: str,
request: ResumePatchRequest,
authorization: str | None = Header(default=None),
) -> ActionResponse:
external_token = authorize_session_request(session_id, authorization)
return mutate_with_sync(
session_id,
external_token,
lambda: agent.patch_resume(session_id, request),
allow_pulled_resume=True,
)
@application.post(
f"{prefix}/sessions/{{session_id}}/resume/profile-summary/generate",
response_model=ActionResponse,
tags=["resume-agent"],
)
def generate_profile_summary(
session_id: str,
authorization: str | None = Header(default=None),
) -> ActionResponse:
external_token = authorize_session_request(session_id, authorization)
return mutate_with_sync(
session_id,
external_token,
lambda: agent.generate_profile_summary(session_id),
)
@application.post(
f"{prefix}/sessions/{{session_id}}/resume/profile-summary/confirm",
response_model=ActionResponse,
tags=["resume-agent"],
)
def confirm_profile_summary(
session_id: str,
authorization: str | None = Header(default=None),
) -> ActionResponse:
external_token = authorize_session_request(session_id, authorization)
return mutate_with_sync(
session_id,
external_token,
lambda: agent.confirm_profile_summary(session_id),
)
@application.post(
f"{prefix}/sessions/{{session_id}}/resume/profile-summary/reject",
response_model=ActionResponse,
tags=["resume-agent"],
)
def reject_profile_summary(
session_id: str,
authorization: str | None = Header(default=None),
) -> ActionResponse:
external_token = authorize_session_request(session_id, authorization)
return mutate_with_sync(
session_id,
external_token,
lambda: agent.reject_profile_summary(session_id),
)
@application.post(
f"{prefix}/sessions/{{session_id}}/resume/skills/recommend",
response_model=SkillRecommendationResponse,
tags=["resume-agent"],
)
def recommend_skills(
session_id: str,
request: SkillRecommendationRequest,
authorization: str | None = Header(default=None),
) -> SkillRecommendationResponse:
external_token = authorize_session_request(session_id, authorization)
if external_token is not None:
agent.reconcile_resume_with_offerpai(session_id, external_token)
return SkillRecommendationResponse(
candidates=agent.recommend_skills(session_id, request.question)
)
@application.post(
f"{prefix}/sessions/{{session_id}}/resume/optimize",
response_model=ActionResponse,
tags=["resume-agent"],
)
def optimize_entry(
session_id: str,
request: OptimizeRequest,
authorization: str | None = Header(default=None),
) -> ActionResponse:
external_token = authorize_session_request(session_id, authorization)
return mutate_with_sync(
session_id,
external_token,
lambda: agent.optimize_entry(session_id, request),
)
@application.post(
f"{prefix}/sessions/{{session_id}}/resume/optimize/confirm",
response_model=ActionResponse,
tags=["resume-agent"],
)
def confirm_optimize(
session_id: str,
request: OptimizeEntryRequest,
authorization: str | None = Header(default=None),
) -> ActionResponse:
external_token = authorize_session_request(session_id, authorization)
return mutate_with_sync(
session_id,
external_token,
lambda: agent.confirm_optimize(session_id, request),
)
@application.post(
f"{prefix}/sessions/{{session_id}}/resume/optimize/reject",
response_model=ActionResponse,
tags=["resume-agent"],
)
def reject_optimize(
session_id: str,
request: OptimizeEntryRequest,
authorization: str | None = Header(default=None),
) -> ActionResponse:
external_token = authorize_session_request(session_id, authorization)
return mutate_with_sync(
session_id,
external_token,
lambda: agent.reject_optimize(session_id, request),
)
@application.post(
f"{prefix}/sessions/{{session_id}}/resume/optimize/undo",
response_model=ActionResponse,
tags=["resume-agent"],
)
def undo_optimize(
session_id: str,
request: OptimizeEntryRequest,
authorization: str | None = Header(default=None),
) -> ActionResponse:
external_token = authorize_session_request(session_id, authorization)
return mutate_with_sync(
session_id,
external_token,
lambda: agent.undo_optimize(session_id, request),
)
@application.post(
f"{prefix}/sessions/{{session_id}}/target-position",
tags=["resume-agent"],
)
def set_session_target_position(
session_id: str,
request: TargetPositionRequest,
authorization: str | None = Header(default=None),
) -> dict[str, object]:
external_token = authorize_session_request(session_id, authorization)
return mutate_with_sync(
session_id,
external_token,
lambda: agent.set_target_position(session_id, request.target_position),
)
@application.post(
f"{prefix}/sessions/{{session_id}}/resume/optimize/light",
response_model=OptimizationRunView,
tags=["resume-agent"],
)
def optimize_light(
session_id: str,
request: OptimizationStartRequest,
authorization: str | None = Header(default=None),
) -> OptimizationRunView:
external_token = authorize_session_request(session_id, authorization)
def mutate() -> OptimizationRunView:
limiter = getattr(application.state, "light_opt_limiter", None)
if limiter is not None and not limiter.allow(session_id):
raise FSMError(
"rate_limited",
"操作过于频繁,请稍后再试(轻度优化每小时最多 20 次)。",
status_code=429,
)
return agent.optimize_light(session_id, request)
return mutate_with_sync(
session_id,
external_token,
mutate,
)
@application.get(
f"{prefix}/sessions/{{session_id}}/resume/optimize/runs/active",
response_model=list[OptimizationRunView],
tags=["resume-agent"],
)
def list_active_optimization_runs(
session_id: str,
authorization: str | None = Header(default=None),
) -> list[OptimizationRunView]:
external_token = authorize_session_request(session_id, authorization)
if external_token is not None:
agent.reconcile_resume_with_offerpai(session_id, external_token)
return agent.list_active_optimization_runs(session_id)
@application.post(
f"{prefix}/sessions/{{session_id}}/resume/optimize/runs/{{run_id}}/confirm",
response_model=OptimizationRunView,
tags=["resume-agent"],
)
def confirm_deep_optimization(
session_id: str,
run_id: str,
authorization: str | None = Header(default=None),
) -> OptimizationRunView:
external_token = authorize_session_request(session_id, authorization)
return mutate_with_sync(
session_id,
external_token,
lambda: agent.confirm_deep_optimization(session_id, run_id),
)
@application.post(
f"{prefix}/sessions/{{session_id}}/resume/optimize/runs/{{run_id}}/reject",
response_model=OptimizationRunView,
tags=["resume-agent"],
)
def reject_optimization(
session_id: str,
run_id: str,
authorization: str | None = Header(default=None),
) -> OptimizationRunView:
external_token = authorize_session_request(session_id, authorization)
return mutate_with_sync(
session_id,
external_token,
lambda: agent.reject_optimization(session_id, run_id),
)