# How Jinja Templates Are Used for Extraction and Scoring in the Hiring-Agent Repository

> Discover how the interviewstreet Hiring Agent uses Jinja templates to dynamically create LLM prompts for resume extraction and candidate scoring. Learn to generate prompts efficiently.

- Repository: [HackerRank/hiring-agent](https://github.com/interviewstreet/hiring-agent)
- Tags: how-to-guide
- Published: 2026-06-28

---

**The Hiring-Agent repository uses Jinja2 templates to dynamically generate LLM prompts for both resume section extraction and candidate scoring, storing templates in `prompts/templates/` and rendering them via `TemplateManager` to inject variables like `section_name_param` and `text_content`.**

The InterviewStreet Hiring-Agent pipeline leverages Jinja templates to bridge raw resume data and structured LLM outputs. By separating prompt logic from Python code, the system enables dynamic extraction of JSON Resume sections and rule-based scoring evaluation without hardcoding prompt strings.

## Template Management Infrastructure

### The TemplateManager Class

All Jinja templates reside under `prompts/templates/` and are orchestrated by **`TemplateManager`** in [`prompts/template_manager.py`](https://github.com/interviewstreet/hiring-agent/blob/main/prompts/template_manager.py). Upon initialization, the class creates a Jinja `Environment` with `FileSystemLoader` pointing to the template directory, configured with `trim_blocks=True` and `lstrip_blocks=True` to clean whitespace.

```python
class TemplateManager:
    def __init__(self, template_dir: str = "prompts/templates"):
        self.env = Environment(
            loader=FileSystemLoader(template_dir),
            trim_blocks=True,
            lstrip_blocks=True,
        )
        self._load_templates()

```

The `_load_templates()` method pre-compiles every template file into the `_templates` dictionary, including `system_message.jinja` for extraction and `resume_evaluation_criteria.jinja` for scoring.

### Rendering Dynamic Content

The manager exposes `render_template(section_name, **kwargs)` to substitute template variables. This method retrieves the compiled template from `_templates` and executes the render with provided keyword arguments.

```python
section_system_message = self.template_manager.render_template(
    "system_message", section_name_param="work"
)
prompt = self.template_manager.render_template(
    "work", text_content=resume_text
)

```

## Extraction Workflow with Jinja

### Section-Specific System Messages

The `PDFHandler` class in [`pdf.py`](https://github.com/interviewstreet/hiring-agent/blob/main/pdf.py) instantiates `TemplateManager` to process PDF resumes. For each section (e.g., *basics*, *work*, *skills*), it renders a system message from `system_message.jinja` by passing the target section name via `section_name_param`:

```jinja
You are an expert resume parser. Extract ONLY the {{ section_name_param }} section from resumes and format it according to the JSON Resume specification.

```

When rendered with `section_name_param="basics"`, the LLM receives precise instructions scoped to that specific resume section.

### Processing PDF Content

For the user prompt, `PDFHandler` renders section-specific templates (e.g., `basics.jinja`, `work.jinja`) with the full resume text supplied as `text_content`:

```python
section_system_message = self.template_manager.render_template(
    "system_message", section_name_param=section_name
)
prompt = self.template_manager.render_template(
    section_name, text_content=resume_text
)

```

These two rendered strings form the chat payload sent to `self.provider.chat` according to the `_call_llm_for_section` implementation.

### From Template to Pydantic Model

The LLM returns a JSON blob that `PDFHandler` parses and validates against Pydantic models (`Basics`, `Work`, `Skills`, etc.). The Jinja templates ensure the LLM receives consistent formatting instructions, while the Python code handles structural validation and type conversion.

## Scoring Workflow with Jinja

### Evaluation Criteria Templates

The `ResumeEvaluator` class in [`evaluator.py`](https://github.com/interviewstreet/hiring-agent/blob/main/evaluator.py) mirrors the extraction pattern for scoring. It loads evaluation criteria by rendering `resume_evaluation_criteria.jinja` with the full resume text as `text_content`, alongside a generic system message from `resume_evaluation_system_message.jinja`:

```python
criteria_template = self.template_manager.render_template(
    "resume_evaluation_criteria", text_content=resume_text
)
system_message = self.template_manager.render_template(
    "resume_evaluation_system_message"
)

```

The criteria template instructs the LLM to output a JSON object containing four score categories, bonus points, deductions, and supporting evidence.

### Structured Output Validation

The evaluation response is forced into the `EvaluationData` Pydantic schema using `EvaluationData.model_json_schema()` to guarantee structural integrity. This validation occurs within the `evaluate_resume` method, ensuring that Jinja-rendered prompts produce machine-readable outputs that conform to expected data structures.

## Score Calculation Pipeline

After the LLM supplies raw scores via the rendered template workflow, [`score.py`](https://github.com/interviewstreet/hiring-agent/blob/main/score.py) aggregates the results. The calculation logic caps each category at its maximum value, applies bonus points (capped at 20), and enforces an overall ceiling of 120 points:

```python
total_score = 0
max_score = 0
if hasattr(evaluation, "scores"):
    for cat, data in evaluation.scores.model_dump().items():
        cat_score = min(data["score"], data["max"])
        total_score += cat_score
        max_score += data["max"]
total_score += evaluation.bonus_points.total
total_score -= evaluation.deductions.total

```

The final numeric result is printed and can be serialized to CSV for downstream analytics.

## Summary

- **TemplateManager** in [`prompts/template_manager.py`](https://github.com/interviewstreet/hiring-agent/blob/main/prompts/template_manager.py) centralizes Jinja environment configuration and template rendering for both extraction and scoring workflows.
- **Extraction** uses `system_message.jinja` combined with section-specific templates (e.g., `basics.jinja`) to generate structured JSON Resume sections via `PDFHandler`.
- **Scoring** leverages `resume_evaluation_criteria.jinja` and `resume_evaluation_system_message.jinja` to produce standardized evaluation outputs through `ResumeEvaluator`.
- **Dynamic injection** of variables like `section_name_param` and `text_content` enables reusable templates without string concatenation.
- **Validation layers** using Pydantic models (`Basics`, `Work`, `EvaluationData`) ensure Jinja-rendered LLM outputs conform to expected schemas.

## Frequently Asked Questions

### How does TemplateManager handle template loading and caching?

`TemplateManager` initializes a Jinja `Environment` with `FileSystemLoader` pointing to `prompts/templates/` during instantiation. The `_load_templates()` method pre-compiles all template files into the `_templates` dictionary, storing them as Jinja `Template` objects. This compilation happens once at initialization, ensuring subsequent calls to `render_template()` execute quickly without re-reading files from disk.

### What variables can be passed to the extraction templates?

The extraction workflow accepts `section_name_param` for the system message template and `text_content` for section-specific templates. The `section_name_param` variable scopes the LLM's task to a specific resume section (e.g., "work", "skills"), while `text_content` contains the full raw text extracted from the PDF, allowing the LLM to locate and extract relevant information.

### How does the scoring template ensure consistent output formatting?

The `resume_evaluation_criteria.jinja` template contains explicit instructions for the LLM to return a JSON object with specific keys for four scoring categories, bonus points, and deductions. This structured prompt design, combined with Pydantic schema validation using `EvaluationData.model_json_schema()`, forces the LLM output into a predictable format that [`score.py`](https://github.com/interviewstreet/hiring-agent/blob/main/score.py) can reliably parse and calculate.

### Why use Jinja instead of Python f-strings for prompt generation?

Jinja templates provide **separation of concerns** by isolating prompt text from Python logic, enabling non-developers to modify prompts without touching code. The template system supports complex control structures and whitespace management through `trim_blocks` and `lstrip_blocks`, preventing formatting errors common with string concatenation. Additionally, the centralized `TemplateManager` ensures consistent rendering behavior across both extraction and evaluation pipelines.