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 historyextract_education_section: Extracts academic credentialsextract_skills_section: Identifies technical and soft skillsextract_projects_section: Captures portfolio projectsextract_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:
- Constructs the system message using the section-specific template
- Builds the user prompt with the extracted PDF text
- Invokes the LLM provider (initialized via
_initialize_llm_provider) - Extracts and validates JSON from the response
- Applies
transform_parsed_datato 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:
- Accepts a section name and markdown text
- Loads appropriate templates from
prompts/template_manager.py - Invokes the provider with error handling
- Parses JSON responses using utilities from [
llm_utils.py](https://github.com/interviewstreet/hiring-agent/blob/main/llm_utils.py)
_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
JSONResumeobject provides IDE autocompletion and runtime validation through Pydantic - Method returns
Noneon 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
PDFHandlerclass inpdf.pyserves as the primary orchestrator for converting PDF resumes into structuredJSONResumeobjects within the interviewstreet/hiring-agent repository. - It uses PyMuPDF via
extract_text_from_pdfto 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_pdfmethod provides a single public interface that returns fully-typed resume data orNoneon 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →