"""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 = "" return suggested, summary.strip(), reasoning.strip()