Template Guideο
Learn how to create powerful Jinja2 templates for ostruct that combine static text with dynamic content, file processing, and advanced filtering capabilities. Templates are prompt templates - they define the text that gets sent to OpenAI models to generate structured JSON responses.
Note
This guide assumes no prior knowledge of Jinja2. Templates use a customized Jinja2 environment with ostruct-specific enhancements.
Tip
Schema Creation Tool: When creating templates, use the Schema Generator meta-tool to automatically create corresponding JSON schemas:
tools/schema-generator/run.sh -o my_schema.json my_template.j2
This ensures your schemas are OpenAI-compliant and match your template structure.
Understanding Templatesο
What Are ostruct Templates?ο
ostruct templates are prompt templates - Jinja2 files that define the text sent to OpenAI models. The model receives your rendered template as its prompt and generates structured JSON responses based on your schema.
Template Flow: 1. Your template is rendered with data (files, variables, etc.) 2. The rendered text becomes the prompt sent to the OpenAI model 3. The model generates a JSON response matching your schema 4. ostruct returns the structured output
Template Structureο
ostruct templates are Jinja2 files (typically with .j2 extension) that can include:
Static text - Regular content that appears as-is in the prompt
Variables - Dynamic content from files, CLI arguments, or system data
Control structures - Loops, conditionals, and logic for complex prompts
Filters - Functions to process and transform data
YAML frontmatter - Configuration and system prompts
Basic Template Exampleο
---
system_prompt: You are an expert data analyst.
---
Analyze this configuration file:
{{ config_yaml.content }}
Summary of findings:
{% for file in logs %}
- {{ file.name }}: {{ file.content | word_count }} words
{% endfor %}
Variables and Data Accessο
Variable Sourcesο
ostruct makes data available through several sources:
File Variables - From file routing options (
--file,--file ci:,--file fs:)Directory Variables - From directory routing options (
--dir,--dir ci:,--dir fs:)Literal Variables - From
-V key=valuecommand line argumentsJSON Variables - From
-J key=jsoncommand line arguments for complex data structuresEnvironment Variables - From
-Eor--envflagsComputed Variables - Generated by ostruct (timestamps, metadata, etc.)
Tool Variables - Automatic variables indicating which tools are enabled
Standard Input - From
stdinwhen piped to ostruct
Tool Variablesο
ostruct automatically provides variables indicating which tools are enabled based on file routing:
{% if code_interpreter_enabled %}
# Code Interpreter Analysis
Analyze the uploaded data files and generate visualizations.
{% endif %}
{% if file_search_enabled %}
# Document Search
Search the uploaded documents for relevant information.
{% endif %}
{% if web_search_enabled %}
# Web Research
Research current trends and developments.
{% endif %}
Standard Input Variable:
The stdin variable contains piped input when available:
echo "Process this text" | ostruct run template.j2 schema.json
{% if stdin %}
Input from stdin: {{ stdin }}
{% endif %}
Available Tool Variables:
code_interpreter_enabled- True when files are routed withci:targetfile_search_enabled- True when files are routed withfs:targetweb_search_enabled- True when--enable-tool web-searchis usedauto_download_enabled- True when Code Interpreter file downloads are enabled (--ci-downloadflag or legacy config)code_interpreter_config- Configuration object for Code Interpreter settingscurrent_model- The OpenAI model being used (e.g., βgpt-4oβ)
Important
Advanced
auto_download_enabled and code_interpreter_config
exist only for advanced or organisation-wide templates that need to adapt to
different Code-Interpreter policies. Everyday templates can ignore them safely.
They may evolve as new CI features appear, so avoid hard-coding against specific
keys unless you control the runtime environment.
Example Usage:
# This command will set both code_interpreter_enabled and file_search_enabled to True
ostruct run template.j2 schema.json \
--file ci:data analysis.csv \
--file fs:docs manual.pdf
# Template can conditionally provide instructions
{% if code_interpreter_enabled and file_search_enabled %}
Combine quantitative analysis with document research:
1. Analyze the data files using statistical methods
2. Search the documents for context and background
3. Integrate findings from both sources
{% elif code_interpreter_enabled %}
Focus on data analysis and visualization.
{% elif file_search_enabled %}
Focus on document research and information extraction.
{% endif %}
File Variablesο
Understanding File Routingο
Files are routed to different tools using the --file flag with target specifications:
Flag |
Purpose |
Template Access |
|---|---|---|
|
File available in template only |
Direct content access via |
|
Upload for code execution and analysis |
Analysis results and execution context |
|
Upload for semantic search and retrieval |
Search results and document context |
File Attachment Syntaxο
When attaching files, you provide a meaningful alias that becomes the template variable name:
ostruct run template.j2 schema.json --file ALIAS FILE_PATH
The alias you choose becomes the variable name in your template:
CLI Command |
Template Variable |
Use Case |
|---|---|---|
|
|
Configuration file |
|
|
Data for analysis |
|
|
Document for search |
|
|
Source code directory |
Template Usageο
Access file content using the alias you provided:
Configuration settings:
{{ config.content }}
Process the data:
{% for file in source %}
File: {{ file.name }}
Content: {{ file.content }}
{% endfor %}
Choosing Good Aliasesο
Use descriptive names that make your templates readable:
# Clear, descriptive aliases
ostruct run template.j2 schema.json \
--file app_config config.yaml \
--file ci:sales_data quarterly_sales.csv \
--file fs:user_manual documentation.pdf
# Templates are self-documenting
Application configuration:
{{ app_config.content }}
Sales analysis shows {{ sales_data.content | word_count }} data points.
Search the user manual: {{ user_manual.content }}
Important: File Content Access
All file variables in ostruct require the .content property to access file content:
β
Correct: {{ my_file.content }}
β Incorrect: {{ my_file }} # Shows guidance message, not content
If you accidentally use {{ my_file }} without .content, youβll see a helpful message like:
[File 'config.yaml' - Use {{ my_file.content }} to access file content]
File Variable Propertiesο
Each file variable provides these properties:
Content and Path Information:
{{ file.content }} <!-- File contents as string -->
{{ file.path }} <!-- Relative path from base directory -->
{{ file.abs_path }} <!-- Absolute filesystem path -->
{{ file.name }} <!-- File name with extension -->
File Properties:
{{ file.name }} <!-- Filename with extension -->
{{ file.extension }} <!-- Extension without dot (e.g., "txt") -->
{{ file.size }} <!-- File size in bytes -->
{{ file.mtime }} <!-- Modification time (Unix timestamp, may be None) -->
Path Properties:
{{ file.basename }} <!-- Filename without directory (same as .name) -->
{{ file.dirname }} <!-- Directory portion of path -->
{{ file.parent }} <!-- Parent directory -->
{{ file.stem }} <!-- Filename without extension -->
{{ file.suffix }} <!-- File extension with dot (e.g., ".txt") -->
Note
Extension vs Suffix: Use {{ file.extension }} for the extension without the dot (e.g., βtxtβ) and {{ file.suffix }} for the extension with the dot (e.g., β.txtβ).
Optional Metadata:
{{ file.encoding }} <!-- File encoding (may be None) -->
{{ file.hash }} <!-- File hash (may be None) -->
Boolean Properties:
{% if file.exists %} <!-- File exists -->
{% if file.is_file %} <!-- Is a regular file -->
{% if file.is_dir %} <!-- Is a directory -->
{% if file.is_url %} <!-- Is a URL (remote file) -->
File Sequence Protocol:
{{ file.first }} <!-- First file (itself for single files) -->
{{ file.is_collection }} <!-- False for single files -->
File Attachment Systemο
File Attachment Helpersο
ostruct provides two workflows for handling files in templates:
Text Workflow (XML Appendix)
For including file content as text in an XML appendix:
Review the configuration in {{ get_embed_ref("config") }}.
{{ embed_text("config") }}
Binary Workflow (Vision/Code Interpreter)
For direct model access to files (vision, code execution):
Analyze {{ get_file_ref("chart.png") }} for trends.
{{ attach_file("chart.png") }}
Template Helper Functions:
attach_file(path): Attach a file for binary model accessget_file_ref(path): Get the deterministic label for a fileembed_text(alias): Schedule file content for XML appendix inclusionget_embed_ref(alias): Get reference tag for embedded contentfile_ref(alias): Deprecated - Useget_embed_ref()+embed_text()instead
Legacy File References (Optional)ο
File references provide an optional mechanism to reference attached files in templates using {{ file_ref("alias") }} syntax. When used, files are automatically included in an XML appendix at the end of your prompt.
This is completely optional - you can always access files directly in templates using standard Jinja2 syntax if you prefer manual control over formatting and placement.
Quick Start with File Referencesο
Attach files via CLI:
ostruct run template.j2 schema.json \ --dir source-code src/ \ --file config config.yaml \ --collect data-files @filelist.txt
Reference in template:
Analyze the source code in {{ file_ref("source-code") }}. Check the configuration in {{ file_ref("config") }}. Review the data files in {{ file_ref("data-files") }}.
Output includes references and XML appendix:
Analyze the source code in <source-code>. Check the configuration in <config>. Review the data files in <data-files>. <files> <dir alias="source-code" path="src/"> <file path="main.py"> <content><![CDATA[...]]></content> </file> </dir> <file alias="config" path="config.yaml"> <content><![CDATA[...]]></content> </file> <collection alias="data-files" path="@filelist.txt"> <file path="data1.csv"> <content><![CDATA[...]]></content> </file> </collection> </files>
Automatic vs Manual File Formattingο
Automatic File References (Optional):
{# In your template - automatic XML appendix #}
Review the configuration in {{ file_ref("config") }}.
Analyze the source code in {{ file_ref("source") }}.
Process the data files in {{ file_ref("data") }}.
Manual File Formatting (Alternative):
You can access files directly and format them however you prefer:
{# Manual markdown formatting #}
## Configuration Analysis
```yaml
{{ config.content }}
```
## Source Code Files
{% for file in source %}
### {{ file.name }}
```{{ file.name.split('.')[-1] }}
{{ file.content }}
```
{% endfor %}
Mixed Approach:
You can combine both approaches in the same template:
{# Manual formatting for main analysis #}
## Quick Overview
The configuration contains {{ config.content | length }} characters.
{# Automatic XML appendix for detailed reference #}
For complete file contents, see {{ file_ref("config") }} and {{ file_ref("source") }}.
File Placement Strategyο
LLM performance is heavily influenced by the position of information in the prompt. Research confirms that models recall information best when it is placed at the very beginning (primacy) or the very end (recency) of the context window. Information placed in the middle is more likely to be overlooked (a phenomenon known as the βLost in the Middleβ problem).
Use this principle to guide your choice between manual and automatic file inclusion:
For Critical Files: Manually place your most important file(s) immediately after your primary instructions at the beginning of the prompt. This puts them in a high-attention zone.
For Reference Material: Use the automatic
file_ref()appendix for all supporting files. This correctly places them at the end of the prompt, another high-attention zone.
Best Practice Example:
{# Critical file is placed manually at the top #}
Please review this Python script for performance issues.
```python
{{ source['main.py'].content }}
```
My main concern is the efficiency of the data processing loop.
Analyze the script above and use the attached logs and configuration for context.
Supporting files for your analysis: {{ file_ref("logs") }} {{ file_ref("config") }}
Directory Variablesο
Working with Directory Collectionsο
Directory variables contain multiple files and always behave as collections:
{# Always iterate over directory variables #}
{% for file in source_code %}
## {{ file.name }}
{{ file.content }}
{% endfor %}
Important: The most important principle for file handling in ostruct templates is uniform iteration: always treat file variables as collections, even when they contain just one file. This makes your templates work reliably regardless of how users attach files.
Uniform Template Example:
{# Works with both single files and directories #}
{% for file in code %}
### {{ file.name }}
```{{ file.extension or 'text' }}
{{ file.content }}
```
{% endfor %}
This template works with either:
- ostruct run template.j2 schema.json --file code main.py (single file)
- ostruct run template.j2 schema.json --dir code ./src/ (multiple files)
Directory Attachmentsο
Attach entire directories using the same alias pattern:
ostruct run template.j2 schema.json --dir ALIAS DIRECTORY_PATH
Directory examples:
# Different routing targets
ostruct run template.j2 schema.json --dir config ./config_files
ostruct run template.j2 schema.json --dir ci:data ./datasets
ostruct run template.j2 schema.json --dir fs:docs ./documentation
{# Process all files in a directory #}
Configuration files:
{% for file in config %}
- {{ file.name }}: {{ file.content | word_count }} words
{% endfor %}
Template Reusability:
Choose aliases that work across different projects:
# Generic aliases work anywhere
ostruct run template.j2 schema.json --dir source_code ./src
ostruct run template.j2 schema.json --dir test_data ./test_files
{# Template uses stable variable names #}
Application configuration:
{% for file in app_config %}
- {{ file.name }}: {{ file.content | word_count }} words
{% endfor %}
Template Reusability: Use aliases (--dir alias, --dir ci:alias, --dir fs:alias) for templates that need to work across different projects or directory structures.
Literal and JSON Variablesο
Simple Variablesο
Pass simple values using -V:
ostruct run template.j2 schema.json -V env=production -V debug=false
Environment: {{ env }}
Debug mode: {{ debug }}
Complex JSON Variablesο
Pass structured data using -J:
ostruct run template.j2 schema.json -J config='{"database":{"host":"localhost","port":5432},"features":["auth","billing"]}'
Database host: {{ config.database.host }}
Features: {{ config.features | join(", ") }}
Template Filtersο
ostruct provides many built-in filters for data processing:
Text Processing:
- {{ text | word_count }} - Count words
- {{ text | char_count }} - Count characters
- {{ text | length }} - Count characters (built-in)
- {{ text | strip }} - Remove whitespace
- {{ text | extract_keywords }} - Extract keywords from text
- {{ text | normalize }} - Normalize whitespace
- {{ text | strip_markdown }} - Remove markdown formatting
Data Processing:
- {{ items | sort_by("name") }} - Sort by property
- {{ items | group_by("category") }} - Group items by property
- {{ items | filter_by("active", true) }} - Filter items by criteria
- {{ items | extract_field("email") }} - Extract field from items
- {{ items | unique }} - Get unique items
- {{ items | frequency }} - Calculate frequency counts
- {{ data | aggregate }} - Aggregate data (sum, avg, count)
Data Conversion:
- {{ json_text | from_json }} - Parse JSON (custom filter)
- {{ data | to_json }} - Convert to JSON (custom filter)
- {{ data | tojson }} - Convert to JSON (built-in filter)
Table Formatting:
- {{ data | table }} - Format data as table
- {{ data | align_table }} - Align table columns
- {{ dict | dict_to_table }} - Convert dictionary to table
- {{ list | list_to_table }} - Convert list to table
- {{ data | auto_table }} - Auto-format data as table
Code Processing:
- {{ code | format_code("python") }} - Format code with syntax highlighting
- {{ code | strip_comments("python") }} - Remove comments from code
- {{ text | escape_special }} - Escape special characters
File Operations:
- {{ files | single }} - Extract single file from collection
- {{ files | files }} - File sequence protocol support
- {{ file.name }} - Get filename (FileInfo property)
- {{ file.path }} - Get full file path (FileInfo property)
Safety and Validation:
- {{ value | default("fallback") }} - Provide default value
- {{ safe_get("config.database.host", "localhost") }} - Safe nested access
Template Functionsο
ostruct provides global functions for advanced template operations:
Utility Functions:
- {{ estimate_tokens(content) }} - Estimate token count for text
- {{ format_json(data) }} - Format JSON with indentation
- {{ now() }} - Get current timestamp
- {{ type_of(variable) }} - Get type name of variable
- {{ debug(variable) }} - Debug output for development
Data Analysis Functions:
- {{ summarize(data_list) }} - Summarize data collections
- {{ pivot_table(data, rows, cols) }} - Create pivot tables
File Attachment Helpers:
- {{ attach_file("chart.png") }} - Attach file for binary model access
- {{ get_file_ref("chart.png") }} - Get deterministic file label
- {{ embed_text("config") }} - Schedule file for XML appendix
- {{ get_embed_ref("config") }} - Get reference tag for embedded content
Safe Access Utilities:
- {{ safe_get("config.database.host", "localhost") }} - Safe nested property access
Control Structuresο
Conditionalsο
{% if config_yaml is defined %}
Configuration found: {{ config_yaml.name }}
{% else %}
No configuration provided.
{% endif %}
{% if files | length > 0 %}
Processing {{ files | length }} files...
{% endif %}
Loopsο
{% for file in source_code %}
## File: {{ file.name }}
{{ file.content }}
{% if not loop.last %}---{% endif %}
{% endfor %}
Error Handlingο
{# Defensive template coding #}
{% if source_files is defined and source_files | length > 0 %}
{% for file in source_files %}
- {{ file.name }}: {{ file.content | word_count }} words
{% endfor %}
{% else %}
No source files provided.
{% endif %}
YAML Frontmatterο
System Promptsο
Add configuration and system prompts to templates using YAML frontmatter:
---
system_prompt: |
You are an expert code reviewer. Focus on:
- Security vulnerabilities
- Performance issues
- Best practices
---
Please review this code:
{{ code.content }}
Template Debuggingο
Debug Variablesο
Use --template-debug vars to see all available variables:
ostruct run template.j2 schema.json --file config config.yaml --template-debug vars
Dry Run Testingο
Always use --dry-run to validate templates during development:
ostruct run template.j2 schema.json --file data report.xlsx --dry-run
Template Expansion Debuggingο
Debug the template rendering process:
# Debug template expansion
ostruct run template.j2 schema.json --template-debug post-expand --file config config.yaml
Best Practicesο
Template Design Principlesο
Uniform Iteration: Always treat file variables as collections
Defensive Coding: Check if variables exist before using them
Clear Instructions: Write clear, specific prompts for the model
Strategic File Placement: Use primacy/recency effects for important content
Consistent Naming: Use aliases for reusable templates
Real-World Examplesο
Code Review Templateο
This template works whether the user provides one file or an entire directory:
---
system_prompt: You are an expert code reviewer.
---
Please review the following code for security issues, performance problems, and best practices:
{% for file in code %}
## {{ file.name }}
```{{ file.extension or 'text' }}
{{ file.content }}
```
{% endfor %}
Focus on:
1. Security vulnerabilities
2. Performance bottlenecks
3. Code quality issues
4. Best practice violations
Multi-File Analysis Templateο
---
system_prompt: You are a senior software architect.
---
Analyze this codebase structure and provide architectural recommendations:
## Project Overview
Total files: {{ source_code | length }}
## File Analysis
{% for file in source_code %}
### {{ file.path }}
- Size: {{ file.content | char_count }} characters
- Type: {{ file.extension or 'unknown' }}
{% if file.content | char_count < 1000 %}
```{{ file.extension or 'text' }}
{{ file.content }}
```
{% endif %}
{% endfor %}
Please provide:
1. Architecture assessment
2. Code organization recommendations
3. Potential improvements
See Alsoο
Template Quick Reference - Quick syntax reference
Advanced Template Patterns - Advanced template techniques
CLI Reference - Command-line options
Multi-Tool Integration - Multi-tool integration patterns