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.

cv_agentic_analyzer.py
python
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 state

Pydantic 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…