How Hiring-Agent Manages Prompts: A Jinja2 Template Architecture
Hiring-Agent stores all LLM prompts as Jinja2 templates in prompts/templates and uses a centralized TemplateManager class to load, cache, and render them with runtime variables, completely decoupling prompt content from application logic.
Hiring-Agent, the open-source resume evaluation tool from InterviewStreet, implements a declarative approach to prompt management. Rather than embedding hardcoded strings throughout the codebase, every prompt lives as a version-controlled Jinja template. A dedicated TemplateManager handles the lifecycle of these templates, ensuring that prompt engineers can iterate on wording without modifying Python code.
The TemplateManager Class
The TemplateManager is the single source of truth for prompt retrieval and rendering. It initializes a Jinja2 Environment with a FileSystemLoader pointed at the templates directory, then eagerly loads all defined templates into an internal cache.
Initialization and Template Loading
During instantiation, the manager maps logical section names to physical filenames and loads them into memory:
# prompts/template_manager.py
class TemplateManager:
def __init__(self, template_dir: str = "prompts/templates"):
self.template_dir = template_dir
self.env = Environment(
loader=FileSystemLoader(template_dir),
trim_blocks=True,
lstrip_blocks=True
)
self._templates: Dict[str, Template] = {}
self._load_templates()
def _load_templates(self):
template_files = {
"basics": "basics.jinja",
"work": "work.jinja",
"education": "education.jinja",
"skills": "skills.jinja",
"projects": "projects.jinja",
"awards": "awards.jinja",
"system_message": "system_message.jinja",
"github_project_selection": "github_project_selection.jinja",
"resume_evaluation_criteria": "resume_evaluation_criteria.jinja",
"resume_evaluation_system_message": "resume_evaluation_system_message.jinja",
}
for section_name, filename in template_files.items():
template_path = os.path.join(self.template_dir, filename)
if os.path.exists(template_path):
self._templates[section_name] = self.env.get_template(filename)
This eager loading means disk I/O happens once at startup, and subsequent render calls operate against cached Template objects.
Rendering Logic
The render_template() method accepts a logical section name and keyword arguments that populate the Jinja placeholders:
def render_template(self, section_name: str, **kwargs) -> str:
template = self._templates.get(section_name)
if not template:
raise ValueError(f"Template {section_name} not found")
return template.render(**kwargs)
Components call this method with runtime data—such as raw resume markdown—and receive a fully interpolated string ready for the LLM.
Template Organization
All template files reside in prompts/templates/. The naming convention follows resume sections and functional concerns:
- Section extraction:
basics.jinja,work.jinja,education.jinja,skills.jinja,projects.jinja,awards.jinja - System context:
system_message.jinja - GitHub integration:
github_project_selection.jinja - Evaluation:
resume_evaluation_criteria.jinja,resume_evaluation_system_message.jinja
A typical template, such as basics.jinja, uses the {{ text_content }} placeholder to inject the resume text at runtime:
Extract ONLY the basic information (name, email, phone, location, profiles) from this resume.
--- The input resume markdown starts here ---
{{ text_content }}
--- The input resume markdown ends here ---
How Components Consume the Prompt System
PDF Resume Extraction
In [pdf.py](https://github.com/interviewstreet/hiring-agent/blob/main/pdf.py), the extractor instantiates TemplateManager and renders section-specific prompts to guide the LLM in parsing unstructured PDF text:
# pdf.py
self.template_manager = TemplateManager()
work_prompt = self.template_manager.render_template(
"work",
text_content=resume_text
)
The rendered prompt is then passed to the LLM to extract structured work history.
GitHub Project Selection
The GitHub integration in [github.py](https://github.com/interviewstreet/hiring-agent/blob/main/github.py) uses the manager to generate selection prompts:
# github.py
selection_prompt = self.template_manager.render_template(
"github_project_selection",
projects=candidate_projects,
job_description=job_desc
)
Resume Evaluation
The [evaluator.py](https://github.com/interviewstreet/hiring-agent/blob/main/evaluator.py) module composes complex evaluation prompts by rendering multiple templates:
# evaluator.py
criteria = self.template_manager.render_template(
"resume_evaluation_criteria",
text_content=resume_text
)
system_msg = self.template_manager.render_template(
"resume_evaluation_system_message"
)
These strings are combined and sent to the LLM defined in [prompt.py](https://github.com/interviewstreet/hiring-agent/blob/main/prompt.py), which holds model configuration constants and provider mappings but does not define the prompt text itself.
Summary
- Prompts are Jinja2 templates stored in
prompts/templates/and mapped to logical names inTemplateManager._load_templates(). - TemplateManager handles the lifecycle, loading all templates on initialization and rendering them via
render_template(section_name, **kwargs). - Components remain decoupled from prompt text; [
pdf.py](https://github.com/interviewstreet/hiring-agent/blob/main/pdf.py), [github.py](https://github.com/interviewstreet/hiring-agent/blob/main/github.py), and [evaluator.py](https://github.com/interviewstreet/hiring-agent/blob/main/evaluator.py) simply request the section they need. - Runtime variables like
text_contentare injected at render time, allowing the same template to be reused across different candidate resumes.
Frequently Asked Questions
Where are prompts stored in the hiring-agent repository?
All prompt files are stored as .jinja templates in the prompts/templates/ directory. This includes files for resume sections (basics.jinja, work.jinja, etc.), system messages, and evaluation criteria.
What templating engine does hiring-agent use for prompt management?
The system uses Jinja2, configured via jinja2.Environment with FileSystemLoader, trim_blocks=True, and lstrip_blocks=True to handle whitespace control. This engine powers the TemplateManager central to the prompt architecture.
How can I add a new prompt template to hiring-agent?
Create a new .jinja file in prompts/templates/, then register it by adding a key-value pair to the template_files dictionary inside TemplateManager._load_templates() in [prompts/template_manager.py](https://github.com/interviewstreet/hiring-agent/blob/main/prompts/template_manager.py). The key becomes the logical name used by other components.
How does hiring-agent inject dynamic data into prompts?
Components call TemplateManager.render_template(section_name, **kwargs), passing variables such as text_content that correspond to Jinja placeholders (e.g., {{ text_content }}) in the template files. The method returns a fully interpolated string ready for the LLM API call.
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 →