92 lines
2.7 KiB
Python
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 "",
|
|
}
|