The parser agent
Calling json.loads on an LLM response is a gamble. Half the time the model wraps the JSON in a markdown fence. The other half it drops a trailing comma. Pydantic models let you declare the shape you want and get a typed object back, with validation errors you can actually handle.
from pydantic import BaseModel, Field
class JobAnalysis(BaseModel):
"""Structured output for the JD analyzer agent."""
keywords: list[str] = Field(default_factory=list)
mandatory_requirements: list[str] = Field(default_factory=list)
nice_to_haves: list[str] = Field(default_factory=list)
role_level: str = Field(default="unknown")
async def analyze_jd(self, state: CVAnalysisState) -> CVAnalysisState:
jd = state.get("job_description", "") or ""
if len(jd.strip()) < 10:
state["job_analysis"] = JobAnalysis().model_dump()
return state
prompt = f"""Extract requirements from this job description as JSON.
Return: keywords, mandatory_requirements, nice_to_haves, role_level.
Job Description: {jd}"""
raw = await self.llm.generate_text(prompt)
cleaned = clean_json_response(raw)
parsed = JobAnalysis.model_validate_json(cleaned)
state["job_analysis"] = parsed.model_dump()
return statePydantic parses, validates, and returns a typed object. If the model fabricates a field, validation fails loudly and you know exactly which agent to fix.
Matching exercise: Match each structured output pattern to its role
Loading practice…
AI prompt: Try it: structured JD analyzer prompt
Loading practice…
Quiz: Quiz
Loading practice…