A simple type-safe, validated prompt management system for LLMs
Project description
typed-prompt
A type-safe, validated prompt management system for LLMs that catches errors early, enforces type safety, and provides a structured way to manage prompts. Uses Pydantic models for variable validation and Jinja2 templates for prompt rendering.
Note: This library is in early development and subject to change.
Why typed-prompt?
I have always found it challenging to manage dynamic prompts for LLMs. The process is error-prone, with issues often discovered only at runtime. typed-prompt aims to solve this problem by providing a structured, type-safe way to manage prompts that catches errors early and enforces type safety.
Disclaimer: This is a personal project to solve gripes ive had in the past and not affiliated with any organization. It is a work in progress and subject to change.
I will be adding more features and examples in the future. If you have any suggestions or feedback, feel free to open an issue!
Quick Examples
1. Basic Usage with Validation
from typed_prompt import BasePrompt
from pydantic import BaseModel
from typing import Optional
# Define your variables
class UserVars(BaseModel):
name: str
expertise: str
# This works - all template variables are defined
class ValidPrompt(BasePrompt[UserVars]):
"""Helping {{name}} with {{expertise}} level knowledge."""
prompt_template: str = "Explain {{topic}} to me"
variables: UserVars
def render(self, *, topic: str, **extra_vars) -> RenderOutput:
extra_vars["topic"] = topic
return super().render(**extra_vars)
# This fails immediately - 'unknown_var' not defined
class InvalidPrompt(BasePrompt[UserVars]):
prompt_template: str = "What is {{unknown_var}}?" # ValueError!
variables: UserVars
# This fails - 'expertise' defined but never used
class UnusedVarPrompt(BasePrompt[UserVars]):
prompt_template: str = "Hello {{name}}" # ValueError!
variables: UserVars
2. Conditional Templates
from typing import Union
class TemplateVars(BaseModel):
user_type: Union["expert", "beginner"]
name: str
preferences: Optional[dict] = None
class ConditionalPrompt(BasePrompt[TemplateVars]):
"""{% if user_type == 'expert' %}
Technical advisor for {{name}}
{% else %}
Friendly helper for {{name}}
{% endif %}"""
prompt_template: str = """
{% if preferences %}
Considering your preferences: {% for k, v in preferences.items() %}
- {{k}}: {{v}}{% endfor %}
{% endif %}
How can I help with {{topic}}?
"""
variables: TemplateVars
def render(self, *, topic: str, **extra_vars) -> RenderOutput:
extra_vars["topic"] = topic
return super().render(**extra_vars)
3. LLM configuration defined with the template
from typed_prompt import RenderOutput
from pydantic import BaseModel, Field
class MyConfig(BaseModel):
temperature: float = Field(default=0.7, ge=0, le=2)
model: str = Field(default="gpt-4")
class MyPrompt(BasePrompt[UserVars]):
"""Assistant for {{name}}"""
prompt_template: str = "Help with {{topic}}"
variables: UserVars
config: MyConfig = Field(default_factory=MyConfig)
def render(self, *, topic: str, **extra_vars) -> RenderOutput:
extra_vars["topic"] = topic
return super().render(**extra_vars)
# Use custom config
prompt = MyPrompt(
variables=UserVars(name="Alice", expertise="intermediate"),
config=MyConfig(temperature=0.9, model="gpt-3.5-turbo")
)
Note: Using None as a value for optional variables will render as
Nonein the prompt. e.g "Test example{{var}}will render asTest example NoneifvarisNone. This is the default behaviour of jinja. Therefore you need to handle this in your jinja2 template. e.g{{if var}}or{{var | default('default value')}}or however you want to handle it.
Key Features
Early Validation
The library validates your prompt templates during class definition:
- Missing variables are caught immediately
- Unused variables are detected
- Template syntax is verified
- Type checking is enforced
Type Safety
All variables are validated through Pydantic:
- Required vs optional fields
- Type constraints
- Custom validators
- Nested models
Flexible Configuration
Attach custom configuration to prompts:
- Model parameters
- Custom settings
- Validation rules
- Default values
Why Early Validation Matters
Consider this example:
# Without typed-prompt
def create_prompt(user_data):
template = "Hello {{username}}, your level is {{level}}"
# Error only discovered when rendering with wrong data
return template.format(**user_data) # KeyError at runtime!
# With typed-prompt
class UserPrompt(BasePrompt[UserVars]):
prompt_template: str = "Hello {{unknown_var}}" # Error immediately!
variables: UserVars
The library catches template errors at definition time.
Installation
uv add tpyed-prompt
or
pip install typed-prompt
Examples
For more examples and detailed documentation, check the examples directory.
To run the examples:
uv run python examples/user.py
Core Concepts
The Prompt Structure
typed-prompt uses a two-part prompt structure that matches common LLM interaction patterns:
-
System Prompt: Provides context or instructions for the AI model. You can define this in two ways:
- As a class docstring (recommended for better code organization)
- As a
system_prompt_templateclass attribute
-
User Prompt: Contains the actual prompt template that will be sent to the model. This is always defined in the
prompt_templateclass attribute.
Variable Management
Variables in typed-prompt are handled through three complementary mechanisms:
-
Variables Model: A Pydantic model that defines the core variables your prompt needs:
class UserVariables(BaseModel): name: str age: int occupation: Optional[str] = None
-
Render Method Parameters: Additional variables can be defined as keyword-only arguments in a custom render method:
def render(self, *, learning_topic: str, **extra_vars) -> RenderOutput: extra_vars["learning_topic"] = learning_topic return super().render(**extra_vars)
-
Extra Variables: One-off variables can be passed directly to the render method.
Template Validation
The library performs comprehensive validation to catch common issues early:
- Missing Variables: Ensures all variables used in templates are defined either in the variables model or render method
- Unused Variables: Identifies variables that are defined but never used in templates
- Template Syntax: Validates Jinja2 template syntax at class definition time
- Type Checking: Leverages Pydantic's type validation for all variables
Working with External Templates
For complex prompts, you can load templates from external files:
class ComplexPrompt(BasePrompt[ComplexVariables]):
system_prompt_template = Path("templates/system_prompt.j2").read_text()
prompt_template: str = Path("templates/user_prompt.j2").read_text()
Note: With templating engines like Jinja2, you can normally hot reload templates, but this is not supported in typed-prompt as the templates are validated at class definition time.
API Reference
BasePrompt[T]
The foundational class for creating structured prompts.
Type Parameters
T: A Pydantic BaseModel subclass defining the structure of template variables
Class Attributes
system_prompt_template: Optional[str] - System prompt templateprompt_template: str - User prompt templatevariables: T - Instance of the variables model
Methods
render(**extra_vars) -> RenderOutput: Renders both prompts with provided variables
RenderOutput
A NamedTuple providing structured access to rendered prompts:
system_prompt: Optional[str] - The rendered system promptuser_prompt: str - The rendered user prompt
Best Practices
Template Organization
Structure your templates for maximum readability and maintainability:
-
Use Docstrings for System Prompts: When possible, define system prompts in class docstrings for better code organization:
class UserPrompt(BasePrompt[UserVariables]): """You are having a conversation with {{name}}, a {{age}}-year-old {{occupation}}.""" prompt_template: str = "What would you like to discuss?"
-
Separate Complex Templates: For longer templates, use external files:
system_prompt_template = Path("templates/system_prompt.j2").read_text()
Common Patterns
Conditional Content
Use Jinja2's conditional syntax for dynamic content:
class DynamicPrompt(BasePrompt[Variables]):
prompt_template: str = """
{% if expert_mode %}
Provide a detailed technical explanation of {{topic}}
{% else %}
Explain {{topic}} in simple terms
{% endif %}
"""
Contributing
Contributions are welcome!
License
This project is licensed under the MIT License - see the LICENSE file for details.
TODO
-
Optionals will still render as
Nonein the prompt. -
Make Jinja2 optional, (for very simple templating just use string formatting e.g
f"Hello {name}"). Maybe shoulda started simpler lol. -
Output OpenAI compatible Message objects.
-
The ability to define, not just a system prompt and a single prompt, but prompt chains. eg
system_prompt -> user_prompt -> assistant_response -> user_prompt -> assistant_response -> ...
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file typed_prompt-0.1.4.tar.gz.
File metadata
- Download URL: typed_prompt-0.1.4.tar.gz
- Upload date:
- Size: 50.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.5.24
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a0b5fe3aaaf72af381a97f03933eee52b86df20e11210259c3b491a962da3acb
|
|
| MD5 |
86a8a61962d43ad6d56493d358291aba
|
|
| BLAKE2b-256 |
60b5835128862dcc4f6cdc54f75dc0a051421ddfe274e7d295fa76634051dc10
|
File details
Details for the file typed_prompt-0.1.4-py3-none-any.whl.
File metadata
- Download URL: typed_prompt-0.1.4-py3-none-any.whl
- Upload date:
- Size: 11.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.5.24
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
96c3944ab3be111375e673367344f996d232df58ab80c8c59df39553d78e64da
|
|
| MD5 |
0c1a2ec7c06659072bf1642154672152
|
|
| BLAKE2b-256 |
7332bdb9c2327c00988a7719bf94949ca58603c0c2d1f4cf3f4b40176583dd68
|