What Does the PDFHandler Class Do in hiring-agent? PDF-to-JSON Resume Parsing Explained

The PDFHandler class in pdf.py orchestrates the end-to-end conversion of PDF resumes into fully-typed JSONResume objects by extracting markdown-friendly text with PyMuPDF and processing each section through a Large Language Model (LLM) with Pydantic validation.

The PDFHandler class is the central component of the interviewstreet/hiring-agent repository, designed to transform unstructured PDF documents into structured data. Located at line 38 in [pdf.py](https://github.com/interviewstreet/hiring-agent/blob/main/pdf.py#L38), this class handles the complete pipeline from raw document ingestion to validated JSON output, making it the primary interface for resume parsing operations.

Core Architecture of the PDFHandler Class

The PDFHandler implements a three-layer architecture that isolates document processing, language model interaction, and data validation. This separation ensures that PDF handling, LLM prompting, and data transformation remain distinct, testable units.

PDF Text Extraction Layer

The extract_text_from_pdf method handles document ingestion using PyMuPDF (via pymupdf). It opens the PDF file, iterates through each page, and converts the content to markdown format using the to_markdown helper from [pymupdf_rag.py](https://github.com/interviewstreet/hiring-agent/blob/main/pymupdf_rag.py). This preserves document structure—such as headers and lists—before LLM processing, ensuring that formatting cues remain available for section identification.

LLM Orchestration Layer

The class delegates section-specific extraction to dedicated methods that build targeted prompts:

  • extract_basics_section: Handles personal information (name, email, phone)
  • extract_work_section: Parses employment history
  • extract_education_section: Extracts academic credentials
  • extract_skills_section: Identifies technical and soft skills
  • extract_projects_section: Captures portfolio projects
  • extract_awards_section: Extracts achievements and certifications

Each method utilizes the TemplateManager to render Jinja templates for system messages and user prompts, then delegates to _call_llm_for_section. This internal method:

  1. Constructs the system message using the section-specific template
  2. Builds the user prompt with the extracted PDF text
  3. Invokes the LLM provider (initialized via _initialize_llm_provider)
  4. Extracts and validates JSON from the response
  5. Applies transform_parsed_data to normalize the output

Data Transformation Layer

The _extract_all_sections_separately method serves as the aggregation engine. It iterates through all required resume sections, dispatches extraction via _extract_section_data, merges the results into a single dictionary, and instantiates the Pydantic models defined in [models.py](https://github.com/interviewstreet/hiring-agent/blob/main/models.py). This includes creating Basics, Work, Education, and other typed sub-objects before assembling the final JSONResume instance.

Key Methods and Implementation Details

Understanding the PDFHandler requires familiarity with its primary interface methods and internal workflow:

__init__ (lines 38-42): Initializes the handler by creating a TemplateManager instance and preparing the LLM provider based on the DEFAULT_MODEL setting from [prompt.py](https://github.com/interviewstreet/hiring-agent/blob/main/prompt.py).

extract_json_from_pdf (lines 99-118): The public API entry point that accepts a file path, orchestrates text extraction, and manages the section-by-section parsing pipeline. Returns a validated JSONResume object or None on failure.

_call_llm_for_section (lines 66-134): The core LLM interaction logic that:

_extract_section_data (lines 219-227): A dispatcher that maps section names (e.g., "basics", "work") to their corresponding extract_*_section methods, enabling the loop-based aggregation in _extract_all_sections_separately.

Practical Usage Example

Implementing the PDFHandler requires minimal boilerplate. The class automatically handles LLM provider selection and template loading:

from pdf import PDFHandler

# Initialise the handler (templates & LLM provider are loaded automatically)

handler = PDFHandler()

# Path to a candidate's resume PDF

pdf_path = "candidates/jane_doe_resume.pdf"

# Convert the PDF → structured JSONResume (Pydantic model)

resume = handler.extract_json_from_pdf(pdf_path)

if resume:
    # `resume` is a JSONResume with typed sub‑objects (Basics, Work, etc.)

    print("Name:", resume.basics.name)
    print("Work experience count:", len(resume.work))
else:
    print("Failed to parse the PDF.")

Key implementation details:

  • The handler automatically selects the default LLM model specified in prompt.py
  • The returned JSONResume object provides IDE autocompletion and runtime validation through Pydantic
  • Method returns None on parsing failures, allowing graceful error handling by the caller

Integration with the Resume Processing Pipeline

The PDFHandler class does not operate in isolation. It relies on several supporting modules that constitute the hiring-agent's extraction pipeline:

File Role in PDFHandler Workflow
models.py Provides Pydantic schemas (JSONResume, Basics, Work, Education, etc.) that validate and type the extracted data
prompt.py Stores DEFAULT_MODEL configuration and API keys required by _initialize_llm_provider
prompts/template_manager.py Loads Jinja2 templates for system messages and section-specific user prompts used in _call_llm_for_section
pymupdf_rag.py Contains the to_markdown function that renders PDF pages to markdown before LLM processing
llm_utils.py Supplies provider initialization logic and JSON extraction utilities for cleaning LLM responses

Summary

  • The PDFHandler class in pdf.py serves as the primary orchestrator for converting PDF resumes into structured JSONResume objects within the interviewstreet/hiring-agent repository.
  • It uses PyMuPDF via extract_text_from_pdf to convert PDF pages to markdown-friendly text while preserving structural elements.
  • Section extraction occurs through _call_llm_for_section, which constructs targeted prompts, invokes the LLM, and validates JSON responses against Pydantic models.
  • The extract_json_from_pdf method provides a single public interface that returns fully-typed resume data or None on failure.
  • The class architecture separates concerns between document parsing (PyMuPDF), language model interaction (LLM providers), and data validation (Pydantic models).

Frequently Asked Questions

How does PDFHandler handle different PDF formats and layouts?

The PDFHandler utilizes PyMuPDF's to_markdown function from pymupdf_rag.py to normalize diverse PDF layouts into markdown text before LLM processing. This conversion preserves hierarchical structure (headers, lists, paragraphs) as markdown syntax, allowing the LLM to interpret document organization regardless of original formatting. The method handles both text-based and scanned PDFs that PyMuPDF can parse, though extraction quality depends on the underlying PDF structure.

What happens if the LLM returns malformed JSON or invalid data?

The _call_llm_for_section method (lines 66-134) includes validation logic that extracts JSON payloads from LLM responses and validates them against expected schemas. If parsing fails or data is invalid, the method returns None for that specific section. The calling code in _extract_all_sections_separately handles these failures gracefully, omitting invalid sections from the final JSONResume assembly rather than crashing the entire pipeline.

Can I use PDFHandler with different LLM providers?

Yes, the PDFHandler supports provider swapping through the _initialize_llm_provider method (lines 44-46), which selects the appropriate implementation based on the DEFAULT_MODEL configuration in prompt.py. The class uses abstraction layers from llm_utils.py to manage provider-specific initialization, allowing you to switch between OpenAI, Anthropic, or other supported models without modifying the core extraction logic in pdf.py.

What is the performance characteristic of the extract_json_from_pdf method?

The method calls _extract_all_sections_separately (lines 266-324), which processes each resume section sequentially through the LLM. Performance depends on the number of sections parsed (basics, work, education, skills, projects, awards) and the LLM's response time. The method includes runtime measurement logging and returns fully-materialized Pydantic objects, meaning all LLM calls complete before the function returns—there is no streaming or async processing in the current implementation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →