Using Includes in Python API¶
MARGARITA's include functionality allows you to compose templates from reusable snippets, making it easy to build modular, maintainable prompt libraries. This page covers how to use includes programmatically through the Python API.
Basic Include Usage¶
Setting Up the Renderer¶
The key to using includes is setting the base_path parameter when creating a Renderer. This tells MARGARITA where to resolve relative include paths:
from pathlib import Path
from margarita.parser import Parser
from margarita.renderer import Renderer
# Define base path for includes
template_dir = Path("./templates")
# Parse your main template
parser = Parser()
template_content = """
{% include "header.marg" %}
Main content here.
{% include "footer.marg" %}
"""
metadata, nodes = parser.parse(template_content)
# Create renderer with base_path
renderer = Renderer(
context={"app_name": "MyApp"},
base_path=template_dir
)
# Render - includes will be resolved relative to base_path
output = renderer.render(nodes)
Creating Reusable Snippets¶
Example: Prompt Building Blocks¶
Create a library of reusable prompt components:
templates/snippets/system_role.marg:
You are {{role}}, a helpful AI assistant.
templates/snippets/task_context.marg:
## Task Context
User: {{user_name}}
Session: {{session_id}}
Timestamp: {{timestamp}}
templates/snippets/output_format.marg:
## Output Requirements
- Provide responses in {{format}} format
- Keep responses {{length}}
- Use {{tone}} tone
Using the Snippets¶
from pathlib import Path
from margarita.parser import Parser
from margarita.renderer import Renderer
# Main template that composes snippets
main_template = """
{% include "snippets/system_role.marg" %}
{% include "snippets/task_context.marg" %}
## User Request
{{user_request}}
{% include "snippets/output_format.marg" %}
"""
# Parse and render
parser = Parser()
_, nodes = parser.parse(main_template)
renderer = Renderer(
context={
"role": "technical expert",
"user_name": "Alice",
"session_id": "sess_123",
"timestamp": "2024-01-19T10:30:00Z",
"user_request": "Explain quantum computing",
"format": "markdown",
"length": "concise",
"tone": "professional"
},
base_path=Path("./templates")
)
prompt = renderer.render(nodes)
print(prompt)
Dynamic Include Loading¶
MargaritaComposer¶
Include complex prompts dynamically using Composer:
from pathlib import Path
from margarita.composer import Composer
# Usage
manager = Composer(Path("./templates"))
# Compose a complex prompt from multiple snippets
prompt = manager.compose_prompt(
snippets=[
"snippets/system_role.marg",
"snippets/task_context.marg",
"snippets/chain_of_thought.marg",
"snippets/output_format.marg"
],
context={
"role": "data scientist",
"user_name": "Bob",
"task": "Analyze customer churn",
"format": "JSON",
"tone": "analytical"
}
)
Conditional Snippet Loading¶
Using Conditionals with Includes¶
from margarita.parser import Parser
from margarita.renderer import Renderer
# Template with conditional includes
template = """
{% include "snippets/system_role.marg" %}
{% if use_examples %}
{% include "snippets/few_shot_examples.marg" %}
{% endif %}
## Task
{{task}}
{% if detailed_output %}
{% include "snippets/detailed_format.marg" %}
{% else %}
{% include "snippets/brief_format.marg" %}
{% endif %}
"""
parser = Parser()
_, nodes = parser.parse(template)
# Render with detailed mode
renderer = Renderer(
context={
"role": "assistant",
"use_examples": True,
"task": "Summarize the article",
"detailed_output": True
},
base_path=Path("./templates")
)
prompt = renderer.render(nodes)
Nested Includes¶
Includes can reference other includes, creating a hierarchy of snippets. Important: All include paths are always resolved relative to the base_path set in the renderer, not relative to the file doing the including.
Understanding Base Path Resolution¶
Given this directory structure:
templates/
main.marg
snippets/
complete_prompt.marg
header_section.marg
system_role.marg
safety_guidelines.marg
body_section.marg
footer_section.marg
templates/snippets/complete_prompt.marg:
{% include "snippets/header_section.marg" %}
{% include "snippets/body_section.marg" %}
{% include "snippets/footer_section.marg" %}
templates/snippets/header_section.marg:
{% include "snippets/system_role.marg" %}
{% include "snippets/safety_guidelines.marg" %}
Notice that even though header_section.marg is in the snippets/ directory, it still uses "snippets/system_role.marg" in its include statement, not just "system_role.marg". This is because all paths are resolved from base_path.
Example: Nested Include Rendering¶
from pathlib import Path
from margarita.parser import Parser
from margarita.renderer import Renderer
# Parse the main template
parser = Parser()
_, nodes = parser.parse('{% include "snippets/complete_prompt.marg" %}')
# Set base_path to templates/
renderer = Renderer(
context={"role": "assistant"},
base_path=Path("./templates")
)
# All includes are resolved from ./templates/
# - snippets/complete_prompt.marg -> ./templates/snippets/complete_prompt.marg
# - snippets/header_section.marg -> ./templates/snippets/header_section.marg
# - snippets/system_role.marg -> ./templates/snippets/system_role.marg
output = renderer.render(nodes)
Deep Nesting Example¶
You can nest includes as deeply as needed:
templates/layouts/full_prompt.marg:
{% include "sections/preamble.marg" %}
{% include "sections/main_content.marg" %}
{% include "sections/conclusion.marg" %}
templates/sections/preamble.marg:
{% include "components/header.marg" %}
{% include "components/instructions.marg" %}
templates/components/header.marg:
{% include "atoms/logo.marg" %}
{% include "atoms/title.marg" %}
# All paths resolve from base_path, no matter how deep the nesting
parser = Parser()
_, nodes = parser.parse('{% include "layouts/full_prompt.marg" %}')
renderer = Renderer(
context={"title": "My Prompt"},
base_path=Path("./templates")
)
output = renderer.render(nodes)
Why Base Path Matters¶
This design makes your templates portable and predictable:
# ✅ CORRECT: All paths from base_path
# templates/snippets/section.marg contains:
{% include "snippets/subsection.marg" %}
# ❌ WRONG: Don't use relative paths from the current file
# templates/snippets/section.marg should NOT contain:
{% include "subsection.marg" %} # This won't work!
Practical Tip: Organizing Nested Structures¶
Use consistent path prefixes to make nested includes clear:
templates/
prompts/
agent/
researcher.marg -> includes "components/agent/..."
analyzer.marg -> includes "components/agent/..."
components/
agent/
role.marg -> includes "atoms/agent/..."
tools.marg -> includes "atoms/agent/..."
atoms/
agent/
identity.marg
capabilities.marg
This structure makes it obvious that all includes use the full path from templates/.
Error Handling¶
Always handle include errors gracefully:
from pathlib import Path
from margarita.parser import Parser
from margarita.renderer import Renderer
def safe_render(template_content: str, context: dict, base_path: Path) -> str:
"""Safely render a template with error handling."""
try:
parser = Parser()
_, nodes = parser.parse(template_content)
renderer = Renderer(context=context, base_path=base_path)
return renderer.render(nodes)
except FileNotFoundError as e:
# Handle missing include files
print(f"Warning: Include file not found - {e}")
return template_content # Return unrendered template
except Exception as e:
# Handle other rendering errors
print(f"Error rendering template: {e}")
return ""
# Usage
result = safe_render(
'{% include "optional_snippet.marg" %}\nMain content.',
context={},
base_path=Path("./templates")
)
Best Practices¶
1. Organize Snippets by Purpose¶
templates/
snippets/
system/
role_definitions.marg
safety_guidelines.marg
formatting/
json_output.marg
markdown_output.marg
examples/
few_shot_classification.marg
few_shot_extraction.marg
sections/
header.marg
footer.marg
2. Use Descriptive Naming¶
# Good: Clear, descriptive names
{% include "snippets/system/expert_role.marg" %}
{% include "snippets/formatting/structured_json_output.marg" %}
# Avoid: Vague names
{% include "snippets/s1.marg" %}
{% include "snippets/format.marg" %}
3. Keep Snippets Focused¶
Each snippet should have a single, clear purpose:
# Good: Focused snippet
# file: role_definition.marg
You are a {{role}} with expertise in {{domain}}.
# Avoid: Mixing multiple concerns
# file: everything.marg
You are a {{role}}.
Task: {{task}}
Output format: {{format}}
4. Document Snippet Context Requirements¶
Add metadata to snippets documenting required context variables:
---
name: role-definition
version: 1.0.0
required_context:
- role
- domain
- expertise_level
---
You are a {{role}} with {{expertise_level}} expertise in {{domain}}.
5. Cache Parsed Templates¶
Parse templates once, render many times:
class OptimizedRenderer:
def __init__(self, template_dir: Path):
self.template_dir = template_dir
self.parser = Parser()
self.parsed_cache = {}
def get_nodes(self, template_content: str):
cache_key = hash(template_content)
if cache_key not in self.parsed_cache:
_, nodes = self.parser.parse(template_content)
self.parsed_cache[cache_key] = nodes
return self.parsed_cache[cache_key]
def render(self, template_content: str, context: dict) -> str:
nodes = self.get_nodes(template_content)
renderer = Renderer(context=context, base_path=self.template_dir)
return renderer.render(nodes)
Real-World Example: Multi-Agent System¶
from pathlib import Path
from margarita.parser import Parser
from margarita.renderer import Renderer
class AgentPromptBuilder:
"""Build prompts for different agent types using snippets."""
def __init__(self, snippets_dir: Path):
self.snippets_dir = snippets_dir
self.parser = Parser()
def build_agent_prompt(
self,
agent_type: str,
task: str,
context: dict
) -> str:
"""Build a prompt for a specific agent type."""
# Map agent types to snippet combinations
snippet_map = {
"researcher": [
"roles/researcher.marg",
"capabilities/web_search.marg",
"output/structured_findings.marg"
],
"analyzer": [
"roles/analyzer.marg",
"capabilities/data_analysis.marg",
"output/insights_report.marg"
],
"writer": [
"roles/writer.marg",
"capabilities/content_creation.marg",
"output/polished_text.marg"
]
}
snippets = snippet_map.get(agent_type, [])
# Build the main template
template = "\n\n".join([
f'{{% include "{snippet}" %}}'
for snippet in snippets
])
template += f"\n\n## Current Task\n\n{task}"
# Render
_, nodes = self.parser.parse(template)
renderer = Renderer(
context=context,
base_path=self.snippets_dir
)
return renderer.render(nodes)
# Usage
builder = AgentPromptBuilder(Path("./agent_snippets"))
# Build a researcher agent prompt
researcher_prompt = builder.build_agent_prompt(
agent_type="researcher",
task="Find the latest developments in quantum computing",
context={
"expertise": "quantum physics",
"sources": ["arxiv", "google scholar"],
"depth": "comprehensive"
}
)
# Build an analyzer agent prompt
analyzer_prompt = builder.build_agent_prompt(
agent_type="analyzer",
task="Analyze customer feedback trends",
context={
"data_source": "customer_reviews.json",
"analysis_type": "sentiment",
"output_format": "executive_summary"
}
)