Tutorial 02: Template Engine
Learn how to use Jinja2 templates in your AI agents to create dynamic prompts.
What You'll Learn
- How to use Jinja2 template syntax in agent docstrings
- Template variables and expressions
- Advanced template features (loops, conditionals)
- Best practices for prompt engineering
Prerequisites
- Completed Tutorial 01: Your First Agent
- Basic understanding of Python f-strings
Template Basics
Kagura uses Jinja2 template syntax in agent docstrings. Templates are rendered before being sent to the LLM.
Simple Variables
from kagura import agent
@agent
async def greet(name: str) -> str:
"""Say hello to {{ name }}"""
pass
# Template renders to: "Say hello to Alice"
result = await greet("Alice")
Key Points:
- Use {{ variable }}
to insert values
- Variable names must match function parameters
- Values are automatically escaped
Multiple Variables
@agent
async def introduce(name: str, age: int, occupation: str) -> str:
"""
Introduce yourself as {{ name }}, a {{ age }}-year-old {{ occupation }}.
Be friendly and professional.
"""
pass
result = await introduce("Bob", 30, "engineer")
# Template renders to: "Introduce yourself as Bob, a 30-year-old engineer..."
Template Expressions
Jinja2 supports Python-like expressions:
@agent
async def analyze(score: int) -> str:
"""
The score is {{ score }}.
{% if score >= 80 %}
This is excellent performance!
{% elif score >= 60 %}
This is good performance.
{% else %}
This needs improvement.
{% endif %}
"""
pass
Expressions You Can Use:
- Arithmetic: {{ price * 1.1 }}
- Comparison: {% if age > 18 %}
- String methods: {{ name.upper() }}
- List access: {{ items[0] }}
Loops
Process lists and dictionaries in templates:
from typing import List
@agent
async def summarize_items(items: List[str]) -> str:
"""
Summarize the following items:
{% for item in items %}
- {{ item }}
{% endfor %}
Provide a brief overview.
"""
pass
result = await summarize_items(["apples", "oranges", "bananas"])
Loop Features:
- {% for item in list %}
: Iterate over lists
- {{ loop.index }}
: Current iteration (1-based)
- {{ loop.first }}
: True on first iteration
- {{ loop.last }}
: True on last iteration
Filters
Transform values with filters:
@agent
async def format_text(text: str) -> str:
"""
Original: {{ text }}
Uppercase: {{ text | upper }}
Capitalized: {{ text | capitalize }}
First 50 chars: {{ text[:50] }}
"""
pass
Common Filters:
- upper
, lower
, capitalize
: Text transformation
- length
: Get length of string/list
- default(value)
: Default value if undefined
- join(separator)
: Join list items
Complex Data Structures
Work with dictionaries and objects:
from pydantic import BaseModel
from typing import Dict
class User(BaseModel):
name: str
email: str
age: int
@agent
async def analyze_user(user: User) -> str:
"""
Analyze user profile:
- Name: {{ user.name }}
- Email: {{ user.email }}
- Age: {{ user.age }}
Provide insights about this user.
"""
pass
user = User(name="Alice", email="alice@example.com", age=25)
result = await analyze_user(user)
Accessing Data:
- Dictionary: {{ data['key'] }}
or {{ data.key }}
- Object attributes: {{ obj.attribute }}
- Nested: {{ user.address.city }}
Multiline Templates
For complex prompts, use multiline docstrings:
@agent
async def write_email(
recipient: str,
subject: str,
points: List[str],
tone: str = "professional"
) -> str:
"""
Write an email with the following specifications:
To: {{ recipient }}
Subject: {{ subject }}
Tone: {{ tone }}
Key points to cover:
{% for point in points %}
{{ loop.index }}. {{ point }}
{% endfor %}
Make it {{ tone }} and concise.
"""
pass
Best Practices
1. Clear Instructions
# ✅ Good: Clear instructions
@agent
async def translate(text: str, target_lang: str) -> str:
"""
Translate the following text to {{ target_lang }}:
{{ text }}
Return only the translated text, no explanations.
"""
pass
# ❌ Bad: Vague instructions
@agent
async def translate(text: str, target_lang: str) -> str:
"""{{ text }} {{ target_lang }}"""
pass
2. Structure Your Prompts
@agent
async def analyze(data: str) -> str:
"""
## Task
Analyze the following data.
## Data
{{ data }}
## Requirements
- Identify key trends
- Provide insights
- Be concise
"""
pass
3. Use Conditionals Wisely
@agent
async def respond(message: str, context: str = None) -> str:
"""
{% if context %}
Context: {{ context }}
{% endif %}
User message: {{ message }}
Respond appropriately{{ " based on the context" if context else "" }}.
"""
pass
4. Validate Input
@agent
async def process(items: List[str]) -> str:
"""
{% if items %}
Process these items:
{% for item in items %}
- {{ item }}
{% endfor %}
{% else %}
No items to process.
{% endif %}
"""
pass
Common Patterns
Chain of Thought
@agent
async def solve_math(problem: str) -> str:
"""
Solve this math problem: {{ problem }}
Think step by step:
1. First, identify the operation
2. Then, calculate the result
3. Finally, verify your answer
"""
pass
Few-Shot Learning
@agent
async def classify(text: str) -> str:
"""
Classify the sentiment of the text.
Examples:
Text: "I love this!" → Sentiment: positive
Text: "This is terrible" → Sentiment: negative
Text: "It's okay" → Sentiment: neutral
Text: {{ text }} → Sentiment: ?
"""
pass
Role-Based Prompts
@agent
async def code_review(code: str, language: str) -> str:
"""
You are an expert {{ language }} developer.
Review this code and provide suggestions:
```{{ language }}
{{ code }}
```
Focus on:
- Code quality
- Best practices
- Potential bugs
"""
pass
Troubleshooting
Template Syntax Errors
# ❌ Wrong: Missing closing tag
"""
{% for item in items %}
{{ item }}
"""
# ✅ Correct: Proper closing
"""
{% for item in items %}
{{ item }}
{% endfor %}
"""
Variable Not Found
# ❌ Wrong: Variable doesn't match parameter
@agent
async def greet(name: str) -> str:
"""Hello {{ username }}""" # username doesn't exist
pass
# ✅ Correct: Variable matches parameter
@agent
async def greet(name: str) -> str:
"""Hello {{ name }}"""
pass
Escaping Special Characters
# If you need literal {{ or }}
@agent
async def explain() -> str:
"""
In Jinja2, use {% raw %}{{ variable }}{% endraw %} for templates.
"""
pass
Practice Exercises
Exercise 1: User Profile Generator
Create an agent that generates user profiles:
from typing import List
@agent
async def create_profile(
name: str,
skills: List[str],
experience_years: int
) -> str:
"""
# TODO: Write template that:
# - Introduces the person
# - Lists their skills
# - Mentions experience level
"""
pass
Exercise 2: Conditional Email Writer
Create an agent with conditional formatting:
@agent
async def write_email(
recipient: str,
is_urgent: bool,
has_attachments: bool
) -> str:
"""
# TODO: Write template that:
# - Adds [URGENT] to subject if urgent
# - Mentions attachments if present
"""
pass
Exercise 3: Data Analyzer
Create an agent that analyzes data with loops:
from typing import Dict
@agent
async def analyze_metrics(metrics: Dict[str, float]) -> str:
"""
# TODO: Write template that:
# - Iterates over metrics
# - Highlights values > 80
# - Provides summary
"""
pass
Next Steps
- Tutorial 03: Type-Based Parsing - Learn how to parse structured responses
- API Reference: Templates - Complete template documentation