# LLMUtils A Python utility library for managing LLM prompts with Jinja2 templating and automatic schema loading. ## Installation ### Install using uv ```bash uv add git+https://git.project-insanity.de/gmarth/LLMUtils.git ``` ### Install using pip ```bash pip install git+https://git.project-insanity.de/gmarth/LLMUtils.git ``` ## Features - **Jinja2 Template Engine**: Full support for loops, conditionals, filters, and complex data structures - **Smart Prompt Management**: Load and manage prompt templates with automatic schema detection - **On-demand Loading**: Prompts are loaded lazily at runtime for better performance - **Caching Support**: Optional caching to avoid repeated disk reads - **Automatic Schema Loading**: JSON schemas are automatically loaded when found next to prompt files - **Flexible Undefined Handling**: Configure how Jinja2 handles missing variables (strict, debug, silent) ## Quick Start ### Basic Usage ```python from llmutils.prompt_manager import PromptManager from jinja2 import UndefinedError # Get a prompt template result = PromptManager.get_prompt('greeting') print(result.template) # View the template: "Hello {{name}}, you are {{age}} years old" # Fill the template - Jinja2 handles missing variables try: filled = result.fill(name='Alice', age=30) print(filled) # "Hello Alice, you are 30 years old" except UndefinedError as e: print(f"Missing variable: {e}") ``` ### Pre-filling Variables ```python # Fill variables during retrieval result = PromptManager.get_prompt('greeting', name='Alice', age=30) print(result.prompt) # Already filled: "Hello Alice, you are 30 years old" ``` ### Handling Optional Variables Use Jinja2's built-in features for optional variables: ```python # Template with optional variables (greeting_flexible.md): # Hello {{ name | default('Guest') }}! # {% if age is defined %}You are {{ age }} years old.{% endif %} result = PromptManager.get_prompt('greeting_flexible') filled = result.fill(name='Alice') # Works! Age is optional print(filled) # "Hello Alice!\n" ``` ### Advanced Jinja2 Features ```python # Using lists and loops result = PromptManager.get_prompt('task_list') filled = result.fill( tasks=['Write code', 'Review PR', 'Deploy'], priority='high' ) # Using conditionals for optional variables result = PromptManager.get_prompt('status_report') # Template can handle optional variables with {% if variable %} filled = result.fill( error='Connection timeout', # Will show error message items=[] # Will show "No items" ) # Using complex nested data result = PromptManager.get_prompt('user_profile') filled = result.fill( user={ 'name': 'Alice', 'roles': ['admin', 'developer'], 'projects': [ {'name': 'Project A', 'status': 'active'}, {'name': 'Project B', 'status': 'completed'} ] } ) ``` ### Configuring Undefined Behavior ```python from jinja2 import DebugUndefined, Undefined # Default: StrictUndefined (raises errors on missing variables) PromptManager.configure() # Debug mode: shows undefined variable names in output PromptManager.configure(undefined=DebugUndefined) result = PromptManager.get_prompt('greeting') filled = result.fill(name='Alice') # Output: "Hello Alice, you are {{ age }} years old" # Silent mode: undefined variables become empty strings PromptManager.configure(undefined=Undefined) result = PromptManager.get_prompt('greeting') filled = result.fill(name='Alice') # Output: "Hello Alice, you are years old" ``` ### Automatic Schema Loading Schemas are automatically loaded when a `.json` file exists next to the prompt: ```python # If you have both greeting.md and greeting.json files: result = PromptManager.get_prompt('greeting') if result.schema: print("Schema automatically loaded!") print(result.schema) # The JSON schema dictionary ``` ## Configuration ```python from pathlib import Path from jinja2 import DebugUndefined from llmutils.prompt_manager import PromptManager # Configure custom prompts directory (default: ./prompts) PromptManager.configure(path=Path('/custom/prompts/location')) # Configure undefined variable handling PromptManager.configure(undefined=DebugUndefined) # Disable caching for development PromptManager.configure(caching=False) # Clear cache to force reload PromptManager.reload_prompts() ``` ## Prompt Files Place your prompt templates in the `prompts/` directory: - `prompts/greeting.md` - Markdown file with template - `prompts/greeting.json` - Optional JSON schema for structured output ### Simple Template Example (`greeting.md`): ```markdown Hello {{name}}, You are {{age}} years old. ``` ### Jinja2 Template Examples #### Lists and Loops (`task_list.md`): ```markdown Priority: {{ priority }} Tasks to complete: {% for task in tasks %} - {{ task }} {% endfor %} Total: {{ tasks | length }} tasks ``` #### Conditionals (`status_report.md`): ```markdown {% if error %} ⚠️ ERROR: {{ error }} {% else %} ✅ All systems operational {% endif %} {% if items %} Items ({{ items | length }}): {% for item in items %} {{ loop.index }}. {{ item }} {% endfor %} {% else %} No items to process. {% endif %} ``` #### Complex Data (`user_profile.md`): ```markdown # User: {{ user.name }} ## Roles {{ user.roles | join(', ') }} ## Projects {% for project in user.projects %} - {{ project.name }} [{{ project.status | upper }}] {% endfor %} ``` ### JSON Schema Example (`greeting.json`): ```json { "type": "object", "properties": { "response": { "type": "string" } } } ``` ## API Reference ### ManagedPrompt Class The `ManagedPrompt` dataclass returned by `get_prompt()`: - `template: str` - The Jinja2 template string (or filled result if pre-filled) - `name: str` - The prompt name - `schema: Optional[Dict]` - Associated JSON schema (automatically loaded) - `prompt: str` - Property that returns the template (backward compatibility) - `fill(**kwargs) -> str` - Fill template with variables using Jinja2 ### PromptManager Methods - `get_prompt(prompt_name, **kwargs) -> ManagedPrompt` - Get a prompt template - `list_prompts() -> Dict[str, Dict[str, Any]]` - List all available prompts with schema info - `configure(path=None, caching=None, undefined=None)` - Configure settings - `reload_prompts()` - Clear the cache ### Configuration Options - `path: Path` - Custom prompts directory (default: `./prompts`) - `caching: bool` - Enable/disable prompt caching (default: `True`) - `undefined: type` - Jinja2 undefined behavior: - `StrictUndefined` (default): Raises error on undefined variables - `DebugUndefined`: Shows `{{ variable }}` for undefined variables - `Undefined`: Replaces undefined variables with empty string ### Jinja2 Template Features The library supports all standard Jinja2 features: #### Filters - `{{ items | length }}` - Get length of list - `{{ name | upper }}` - Convert to uppercase - `{{ name | lower }}` - Convert to lowercase - `{{ skills | join(', ') }}` - Join list items - `{{ data | tojson }}` - Convert to JSON - `{{ price | round(2) }}` - Round numbers #### Loops - `{% for item in items %}...{% endfor %}` - Iterate over lists - `{{ loop.index }}` - Current iteration (1-indexed) - `{{ loop.index0 }}` - Current iteration (0-indexed) - `{% for key, value in dict.items() %}...{% endfor %}` - Iterate over dictionaries #### Conditionals - `{% if condition %}...{% endif %}` - Simple conditional - `{% if condition %}...{% else %}...{% endif %}` - If/else - `{% if condition %}...{% elif other %}...{% else %}...{% endif %}` - Multiple conditions ## License MIT