77 lines
2.4 KiB
Python
77 lines
2.4 KiB
Python
"""Agent response parsers and input normalizers.
|
|
|
|
Pure module: no FastAPI imports, no HTTPException, and no module-level state.
|
|
Mirrors job/candidate/decorators.py — helpers that clean/shape data before or
|
|
after the graph nodes run.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import uuid
|
|
|
|
|
|
def normalize_job_posts(job_posts) -> list[dict]:
|
|
"""Keep only dict items with an id field; stringify ids for the LLM."""
|
|
if not job_posts:
|
|
return []
|
|
normalized: list[dict] = []
|
|
for item in job_posts:
|
|
if not isinstance(item, dict):
|
|
continue
|
|
job_id = item.get("id")
|
|
if job_id is None:
|
|
continue
|
|
normalized.append(
|
|
{
|
|
"id": str(job_id),
|
|
"title": item.get("title") or "",
|
|
"description": item.get("description") or "",
|
|
"post_text": item.get("post_text") or "",
|
|
"requirements": item.get("requirements") or [],
|
|
"optional_skills": item.get("optional_skills") or [],
|
|
"location": item.get("location") or "",
|
|
"employment_type": item.get("employment_type") or "",
|
|
}
|
|
)
|
|
return normalized
|
|
|
|
|
|
def parse_match_response(data, allowed_ids) -> tuple[list[str], str, str]:
|
|
"""Filter model JSON ids to the allowed job-post set."""
|
|
if not isinstance(data, dict):
|
|
raise RuntimeError(f"model did not return a JSON object: {data!r}")
|
|
|
|
allowed = set(allowed_ids or [])
|
|
raw_ids = data.get("suggested_job_post_ids") or []
|
|
if not isinstance(raw_ids, list):
|
|
raw_ids = []
|
|
|
|
suggested: list[str] = []
|
|
seen: set[str] = set()
|
|
for raw_id in raw_ids:
|
|
job_id = str(raw_id).strip()
|
|
if not job_id or job_id not in allowed or job_id in seen:
|
|
continue
|
|
try:
|
|
uuid.UUID(job_id)
|
|
except ValueError:
|
|
continue
|
|
seen.add(job_id)
|
|
suggested.append(job_id)
|
|
|
|
summary = data.get("summary")
|
|
if not isinstance(summary, str):
|
|
summary = ""
|
|
|
|
reasoning = data.get("reasoning")
|
|
if isinstance(reasoning, list):
|
|
reasoning = "\n".join(str(item) for item in reasoning)
|
|
if not isinstance(reasoning, str):
|
|
reasoning = ""
|
|
|
|
experience = data.get("experience")
|
|
if not isinstance(experience, str):
|
|
experience = ""
|
|
|
|
return suggested, summary.strip(), reasoning.strip(), experience.strip()
|