# How to Create Custom Jinja Templates for Prompt Engineering in the Hiring Agent

> Learn to create custom Jinja templates for prompt engineering in the Hiring Agent. Register your Jinja file and render prompts with dynamic data efficiently.

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

---

**Create a Jinja file in `prompts/templates/`, register it in `TemplateManager._load_templates`, and call `render_template()` to inject dynamic data into LLM prompts.**

The interviewstreet/hiring-agent repository uses Jinja2 to manage LLM prompts for resume parsing tasks. By creating custom Jinja templates for prompt engineering, you can define new extraction sections without modifying the core Python logic. The system loads templates from disk, caches them in a Jinja Environment, and renders them with variables like `{{ text_content }}` at runtime.

## Architecture of the Template System

The Hiring Agent separates prompt content from execution logic through a centralized template manager. Understanding this architecture is essential before extending it with custom templates.

### Core Components

- **TemplateManager** ([`prompts/template_manager.py`](https://github.com/interviewstreet/hiring-agent/blob/main/prompts/template_manager.py)): Scans the `prompts/templates` directory, initializes a Jinja `Environment` with `FileSystemLoader`, and caches templates in memory. It exposes `render_template(section_name, **kwargs)` to generate final prompt strings.

- **Template Files**: Plain Jinja files (e.g., `basics.jinja`, `work.jinja`) stored in `prompts/templates/`. These files define system messages or user prompts using placeholders like `{{ text_content }}`.

- **PDFHandler** ([`pdf.py`](https://github.com/interviewstreet/hiring-agent/blob/main/pdf.py)): Consumes rendered templates by calling `TemplateManager.render_template`, then passes the result to the LLM provider via `_call_llm_for_section`.

The rendering flow follows four stages:

1. **Load**: `TemplateManager.__init__` creates the Jinja Environment pointing to `prompts/templates`.
2. **Cache**: `_load_templates` iterates over the `template_files` dictionary and stores each `Template` object in `self._templates`.
3. **Render**: `render_template(section, **kwargs)` retrieves the cached template, injects variables, and returns the prompt string.
4. **Consume**: Callers like `PDFHandler` request specific sections and forward rendered prompts to the LLM.

## Step-by-Step Guide to Creating Custom Jinja Templates

### Step 1 – Create the Template File

Add a new Jinja file to `prompts/templates/`. The filename must match the entry you will register in the next step.

```jinja
You are a senior hiring engineer. Extract the following fields from the provided resume markdown:
- leadership experience
- patents (if any)

--- Resume starts ---
{{ text_content }}
--- Resume ends ---

Return ONLY a JSON object:
{
  "custom": {
    "leadership": {{ ""|tojson }},
    "patents": {{ ""|tojson }}
  }
}

```

The `{{ text_content }}` placeholder receives the raw resume markdown. You can define additional variables such as `{{ candidate_name }}` or `{{ job_role }}` and pass them during rendering.

### Step 2 – Register the Template

Edit `TemplateManager._load_templates` in [`prompts/template_manager.py`](https://github.com/interviewstreet/hiring-agent/blob/main/prompts/template_manager.py) to include your new file in the `template_files` dictionary:

```python
template_files = {
    # … existing entries …

    "custom_section": "custom_section.jinja",
}

```

The dictionary key (`"custom_section"`) becomes the identifier used when calling `render_template`.

### Step 3 – Render from Your Code

Import `TemplateManager` and invoke `render_template` with your section key and required variables:

```python
from prompts.template_manager import TemplateManager

tm = TemplateManager()
prompt = tm.render_template(
    "custom_section",
    text_content=resume_markdown,
    candidate_name="Alice"  # optional custom variable

)

```

The method returns a fully rendered string ready for the LLM.

### Step 4 – Send to LLM

Follow the pattern in `PDFHandler._call_llm_for_section` to send the rendered prompt:

```python
chat_params = {
    "model": DEFAULT_MODEL,
    "messages": [
        {"role": "system", "content": prompt},
    ],
    "options": {"temperature": 0.1, "top_p": 0.9},
}
response = provider.chat(**chat_params)

```

Parse the JSON response using the existing `extract_json_from_response` utility and map it to a Pydantic model (e.g., `CustomSectionModel`).

## Complete Working Example

Here is a full implementation of a custom template for extracting leadership experience:

**File:** `prompts/templates/leadership.jinja`

```jinja
You are an AI recruiter specializing in engineering leadership. Analyze the resume below and extract:
1. Team sizes managed
2. Duration in leadership roles
3. Notable achievements

Resume Content:
{{ text_content }}

Return ONLY valid JSON matching this structure:
{
  "leadership": {
    "team_sizes": [],
    "durations": [],
    "achievements": []
  }
}

```

**Registration:** Update [`prompts/template_manager.py`](https://github.com/interviewstreet/hiring-agent/blob/main/prompts/template_manager.py)

```python
class TemplateManager:
    def _load_templates(self):
        template_files = {
            "basics": "basics.jinja",
            "work": "work.jinja",
            "leadership": "leadership.jinja",  # new entry

        }
        # ... existing loading logic

```

**Usage:** Extend `PDFHandler` or create a standalone caller

```python
def extract_leadership(self, resume_text: str) -> Optional[Dict]:
    prompt = self.template_manager.render_template(
        "leadership", text_content=resume_text
    )
    if not prompt:
        logger.error("Failed to render leadership template")
        return None
    return self._call_llm_for_section(
        "leadership", resume_text, prompt, LeadershipModel
    )

```

## Summary

- The **TemplateManager** in [`prompts/template_manager.py`](https://github.com/interviewstreet/hiring-agent/blob/main/prompts/template_manager.py) orchestrates Jinja template loading and rendering for all LLM interactions.
- Create new templates as `.jinja` files in `prompts/templates/` using standard Jinja2 syntax and placeholders like `{{ text_content }}`.
- Register each template in the `template_files` dictionary within `_load_templates` to enable caching and retrieval.
- Call `render_template(section_name, **kwargs)` to inject dynamic data and generate prompts for the LLM provider.
- Reference `PDFHandler._call_llm_for_section` in [`pdf.py`](https://github.com/interviewstreet/hiring-agent/blob/main/pdf.py) for the standard pattern of sending rendered prompts and parsing JSON responses.

## Frequently Asked Questions

### How does the TemplateManager cache templates for performance?

During initialization, `TemplateManager.__init__` invokes `_load_templates`, which iterates over the `template_files` dictionary and pre-compiles each Jinja file into a `Template` object stored in `self._templates`. This eliminates file I/O on subsequent calls to `render_template`, reducing latency when processing multiple resumes.

### Can I pass custom variables beyond text_content to a template?

Yes. The `render_template` method accepts arbitrary keyword arguments via `**kwargs`. Define placeholders in your Jinja file (e.g., `{{ candidate_name }}` or `{{ job_id }}`), then pass them when rendering: `tm.render_template("custom_section", text_content=resume, candidate_name="Bob")`. All kwargs are injected into the template context automatically.

### Where should I place new template files in the repository?

Place all custom Jinja templates in the `prompts/templates/` directory at the repository root. The `TemplateManager` initializes its Jinja `Environment` with `FileSystemLoader("prompts/templates")`, so files outside this path will not be discovered or loaded during the caching phase.

### How do I validate the JSON output from custom templates?

Follow the existing pattern in [`pdf.py`](https://github.com/interviewstreet/hiring-agent/blob/main/pdf.py) where `extract_json_from_response` parses the LLM's string output into a Python dictionary. Define a Pydantic model (e.g., `CustomSectionModel`) that matches your template's JSON schema, then validate the parsed data against this model before using it in your application logic.