# How Hiring-Agent Uses Prompt Templates with Python: A Complete Guide to Jinja2 Integration

> Discover how Hiring-Agent leverages Jinja2 prompt templates with Python for efficient LLM prompt generation. Explore the TemplateManager for modular and maintainable solutions.

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

---

**The Hiring-Agent project uses a centralized `TemplateManager` class to load Jinja2 templates from the `prompts/templates/` directory and render them with runtime variables, enabling modular, maintainable LLM prompt generation across modules like [`pdf.py`](https://github.com/interviewstreet/hiring-agent/blob/main/pdf.py) and [`evaluator.py`](https://github.com/interviewstreet/hiring-agent/blob/main/evaluator.py).**

The `interviewstreet/hiring-agent` repository demonstrates a clean architectural pattern for managing Large Language Model (LLM) prompts in Python applications. Instead of hardcoding prompt strings throughout the codebase, the project treats prompts as **Jinja2 template assets**, loading them dynamically and rendering them with context-specific data at runtime.

## Template Architecture and File Organization

### Template Storage in `prompts/templates/`

All prompt definitions reside as individual `.jinja` files within the `prompts/templates/` directory. Each logical prompt section maintains its own template file, such as `basics.jinja`, `work.jinja`, `education.jinja`, and `system_message.jinja`. This separation allows developers to modify prompt wording without touching Python logic.

### The TemplateManager Class

The core abstraction lives in [`prompts/template_manager.py`](https://github.com/interviewstreet/hiring-agent/blob/main/prompts/template_manager.py). The `TemplateManager` class encapsulates Jinja2 environment configuration and provides a simple interface for template retrieval and rendering.

Key responsibilities include:
- Initializing a Jinja2 `Environment` with a `FileSystemLoader` pointing to `prompts/templates/`
- Pre-loading all defined templates into memory during instantiation
- Providing a type-safe `render_template()` method for consumers

## How TemplateManager Loads and Renders Prompts

### Initialization and Template Loading

When instantiated, `TemplateManager.__init__()` creates the Jinja2 environment and calls the private `_load_templates()` method. This method iterates over a hard-coded mapping of section names to file names, loading each template file into a dictionary stored in `self._templates`.

```python
from prompts.template_manager import TemplateManager

# Initialize once at application startup

template_manager = TemplateManager()

# Templates are now loaded and cached in memory

```

### Rendering Prompts with Runtime Data

The `render_template(section_name, **variables)` method handles runtime prompt generation. It retrieves the pre-loaded `jinja2.Template` object from `self._templates`, renders it with the supplied keyword arguments, and returns the final string. If a template is missing or rendering fails, the method prints a warning and returns `None`.

```python

# Render the work experience section with extracted resume text

prompt = template_manager.render_template(
    "work",
    text_content="Software Engineer at TechCorp, 2020-2024..."
)

if prompt:
    # Send to LLM client

    print(prompt)

```

## Practical Implementation Examples

### Resume Processing in [`pdf.py`](https://github.com/interviewstreet/hiring-agent/blob/main/pdf.py)

The [`pdf.py`](https://github.com/interviewstreet/hiring-agent/blob/main/pdf.py) module demonstrates practical template usage for resume extraction. It instantiates `TemplateManager` during initialization and uses section-specific templates to structure extracted text for LLM processing.

```python

# pdf.py implementation pattern

from prompts.template_manager import TemplateManager

class PDFProcessor:
    def __init__(self):
        self.template_manager = TemplateManager()
    
    def extract_work_history(self, raw_text: str):
        # Render the work section template

        prompt = self.template_manager.render_template(
            "work",
            text_content=raw_text
        )
        if prompt:
            # Pass rendered prompt to LLM utility

            return llm_utils.invoke(prompt)
        return None

```

### Evaluation Workflows in [`evaluator.py`](https://github.com/interviewstreet/hiring-agent/blob/main/evaluator.py)

The [`evaluator.py`](https://github.com/interviewstreet/hiring-agent/blob/main/evaluator.py) module utilizes templates for generating system messages and evaluation criteria. It injects candidate-specific metadata into base templates to create contextualized prompts.

```python

# evaluator.py excerpt

tm = TemplateManager()

# Generate system message with candidate context

system_prompt = tm.render_template(
    "system_message",
    candidate_name="Alice Johnson",
    job_title="Senior Data Scientist"
)

# Combine with evaluation criteria

criteria_prompt = tm.render_template(
    "evaluation_criteria",
    required_skills=["Python", "Machine Learning", "SQL"]
)

```

### GitHub Integration in [`github.py`](https://github.com/interviewstreet/hiring-agent/blob/main/github.py)

For GitHub profile analysis, [`github.py`](https://github.com/interviewstreet/hiring-agent/blob/main/github.py) leverages templates to structure repository selection prompts. The pattern remains consistent: instantiate the manager, render with specific variables, and pass the result to the LLM client.

```python

# github.py pattern

tm = TemplateManager()
project_prompt = tm.render_template(
    "github_projects",
    repositories=["repo1", "repo2"],
    candidate_name="Bob Smith"
)

```

## Summary

- **Template Storage**: All Jinja2 templates reside in `prompts/templates/` as `.jinja` files, separating prompt content from application logic.
- **Centralized Management**: The `TemplateManager` class in [`prompts/template_manager.py`](https://github.com/interviewstreet/hiring-agent/blob/main/prompts/template_manager.py) handles loading, caching, and rendering via `render_template()`.
- **Consumer Pattern**: Modules like [`pdf.py`](https://github.com/interviewstreet/hiring-agent/blob/main/pdf.py), [`evaluator.py`](https://github.com/interviewstreet/hiring-agent/blob/main/evaluator.py), and [`github.py`](https://github.com/interviewstreet/hiring-agent/blob/main/github.py) instantiate `TemplateManager` and call `render_template(section_name, **variables)` to generate LLM-ready prompts.
- **Error Handling**: The rendering method returns `None` and prints warnings when templates are missing or rendering fails, preventing runtime crashes.
- **Runtime Flexibility**: The `**variables` parameter allows dynamic injection of resume text, candidate metadata, and job requirements into static template definitions.

## Frequently Asked Questions

### How does TemplateManager handle missing template files?

If a template file referenced in the internal mapping does not exist in `prompts/templates/`, the `_load_templates()` method skips it during initialization. When `render_template()` is called with a section name that wasn't loaded, it prints a warning message and returns `None`, allowing the calling code to handle the gracefully handle the missing template scenario.

### Can I pass any Python object to the template renderer?

Yes. The `render_template()` method accepts arbitrary keyword arguments via `**variables` and passes them directly to Jinja2's `template.render()` method. This means you can inject strings, lists, dictionaries, or custom objects, as long as your Jinja2 template references them correctly (e.g., `{{ candidate_name }}` or `{{ repositories[0] }}`).

### Is the TemplateManager thread-safe for concurrent usage?

While the underlying Jinja2 `Environment` and loaded `Template` objects are generally thread-safe for rendering, the `hiring-agent` implementation instantiates separate `TemplateManager` instances within each consumer class (like `PDFProcessor`). This pattern avoids shared state and ensures that template loading happens independently in different modules, eliminating potential race conditions during initialization.

### Where are the actual prompt instructions stored in the repository?

The prompt instructions live in the `prompts/templates/` directory as individual `.jinja` files. Each file contains the raw text and Jinja2 logic for a specific prompt type (e.g., `basics.jinja` for basic candidate information, `work.jinja` for work history analysis). The `TemplateManager` maps logical section names to these physical file paths during the loading phase.