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

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 and 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. 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.

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.


# 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

The 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.


# 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

The evaluator.py module utilizes templates for generating system messages and evaluation criteria. It injects candidate-specific metadata into base templates to create contextualized prompts.


# 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

For GitHub profile analysis, 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.


# 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 handles loading, caching, and rendering via render_template().
  • Consumer Pattern: Modules like pdf.py, evaluator.py, and 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.

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 →