How Jinja Templates Are Used for Extraction and Scoring in the Hiring-Agent Repository
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. 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.
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.
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 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:
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:
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 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:
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 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:
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.pycentralizes Jinja environment configuration and template rendering for both extraction and scoring workflows. - Extraction uses
system_message.jinjacombined with section-specific templates (e.g.,basics.jinja) to generate structured JSON Resume sections viaPDFHandler. - Scoring leverages
resume_evaluation_criteria.jinjaandresume_evaluation_system_message.jinjato produce standardized evaluation outputs throughResumeEvaluator. - Dynamic injection of variables like
section_name_paramandtext_contentenables 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 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.
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 →