HR-ATS-Portal/backend/agent/views.py

92 lines
2.7 KiB
Python

"""Agent graph node logic.
Pure module: no FastAPI imports and no HTTPException.
LLM client/config lives in llm_setup — nodes call llm_call only.
"""
from __future__ import annotations
import logging
from typing import Literal
from langgraph.graph import END
from agent.decorators import normalize_job_posts, parse_match_response
from agent.models import AgentState
from agent.prompt import prompt, user_prompt
from llm_setup import llm_call
logger = logging.getLogger("agent")
async def prepare_context(state: AgentState) -> dict:
"""Validate inputs and decide whether matching should run."""
subject = (state.get("subject") or "").strip()
resume_text = (state.get("resume_text") or "").strip()
job_posts = normalize_job_posts(state.get("job_posts"))
if not resume_text:
return {
"status": "skipped",
"error": "resume_text is empty",
"suggested_job_post_ids": [],
"summary": "",
"reasoning": "",
}
if not job_posts:
return {
"status": "skipped",
"error": "no active job posts to match against",
"suggested_job_post_ids": [],
"summary": "",
"reasoning": "",
}
return {
"subject": subject,
"resume_text": resume_text,
"job_posts": job_posts,
"status": "ready",
"error": "",
}
def route_after_prepare(state: AgentState) -> Literal["match_jobs", "__end__"]:
if state.get("status") == "ready":
return "match_jobs"
return END
async def match_jobs(state: AgentState) -> dict:
"""Ask the LLM (via llm_setup.llm_call) to map the candidate to job posts."""
try:
data = await llm_call(prompt(), user_prompt(state), json_mode=True)
allowed_ids = {item["id"] for item in state.get("job_posts") or []}
suggested, summary, reasoning = parse_match_response(data, allowed_ids)
return {
"status": "matched",
"suggested_job_post_ids": suggested,
"summary": summary,
"reasoning": reasoning,
}
except Exception as exc:
logger.exception("agent match_jobs failed")
return {
"status": "failed",
"error": str(exc),
"suggested_job_post_ids": [],
"summary": "",
"reasoning": "",
}
async def finalize(state: AgentState) -> dict:
"""Normalize terminal state for callers."""
return {
"suggested_job_post_ids": state.get("suggested_job_post_ids") or [],
"summary": state.get("summary") or "",
"reasoning": state.get("reasoning") or "",
"status": state.get("status") or "failed",
"error": state.get("error") or "",
}