ai-prompter 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ai_prompter/__init__.py
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
"""
|
2
|
+
A prompt management module using Jinja to generate complex prompts with simple templates.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import os
|
6
|
+
from dataclasses import dataclass
|
7
|
+
from datetime import datetime
|
8
|
+
from typing import Any, Dict, Optional, Union
|
9
|
+
|
10
|
+
from jinja2 import Environment, FileSystemLoader, Template
|
11
|
+
from pydantic import BaseModel
|
12
|
+
|
13
|
+
prompt_path_default = os.path.join(
|
14
|
+
os.path.dirname(os.path.abspath(__file__)), "prompts"
|
15
|
+
)
|
16
|
+
prompt_path_custom = os.getenv("PROMPT_PATH")
|
17
|
+
|
18
|
+
env_default = Environment(loader=FileSystemLoader(prompt_path_default))
|
19
|
+
|
20
|
+
|
21
|
+
@dataclass
|
22
|
+
class Prompter:
|
23
|
+
"""
|
24
|
+
A class for managing and rendering prompt templates.
|
25
|
+
|
26
|
+
Attributes:
|
27
|
+
prompt_template (str, optional): The name of the prompt template file.
|
28
|
+
prompt_variation (str, optional): The variation of the prompt template.
|
29
|
+
prompt_text (str, optional): The raw prompt text.
|
30
|
+
template (Union[str, Template], optional): The Jinja2 template object.
|
31
|
+
"""
|
32
|
+
|
33
|
+
prompt_template: Optional[str] = None
|
34
|
+
prompt_variation: Optional[str] = "default"
|
35
|
+
prompt_text: Optional[str] = None
|
36
|
+
template: Optional[Union[str, Template]] = None
|
37
|
+
parser: Optional[Any] = None
|
38
|
+
|
39
|
+
def __init__(self, prompt_template=None, prompt_text=None, parser=None):
|
40
|
+
"""
|
41
|
+
Initialize the Prompter with either a template file or raw text.
|
42
|
+
|
43
|
+
Args:
|
44
|
+
prompt_template (str, optional): The name of the prompt template file.
|
45
|
+
prompt_text (str, optional): The raw prompt text.
|
46
|
+
"""
|
47
|
+
self.prompt_template = prompt_template
|
48
|
+
self.prompt_text = prompt_text
|
49
|
+
self.parser = parser
|
50
|
+
self.setup()
|
51
|
+
|
52
|
+
def setup(self):
|
53
|
+
"""
|
54
|
+
Set up the Jinja2 template based on the provided template file or text.
|
55
|
+
Raises:
|
56
|
+
ValueError: If neither prompt_template nor prompt_text is provided, or if template name is empty.
|
57
|
+
"""
|
58
|
+
if self.prompt_template is not None:
|
59
|
+
if not self.prompt_template:
|
60
|
+
raise ValueError("Template name cannot be empty")
|
61
|
+
# attempt to load from custom path at runtime
|
62
|
+
custom_path = os.getenv("PROMPT_PATH")
|
63
|
+
if custom_path and os.path.exists(custom_path):
|
64
|
+
try:
|
65
|
+
env = Environment(loader=FileSystemLoader(custom_path))
|
66
|
+
self.template = env.get_template(f"{self.prompt_template}.jinja")
|
67
|
+
return
|
68
|
+
except Exception:
|
69
|
+
pass
|
70
|
+
# fallback to default path
|
71
|
+
try:
|
72
|
+
env = Environment(loader=FileSystemLoader(prompt_path_default))
|
73
|
+
self.template = env.get_template(f"{self.prompt_template}.jinja")
|
74
|
+
except Exception as e:
|
75
|
+
raise ValueError(f"Template {self.prompt_template} not found in default folder: {e}")
|
76
|
+
elif self.prompt_text is not None:
|
77
|
+
self.template = Template(self.prompt_text)
|
78
|
+
else:
|
79
|
+
raise ValueError("Prompter must have a prompt_template or prompt_text")
|
80
|
+
|
81
|
+
# Removed assertion as it's redundant with the checks above
|
82
|
+
# assert self.prompt_template or self.prompt_text, "Prompt is required"
|
83
|
+
|
84
|
+
def to_langchain(self):
|
85
|
+
# only file-based templates supported for LangChain
|
86
|
+
if self.prompt_text is not None:
|
87
|
+
raise ImportError(
|
88
|
+
"langchain-core integration only supports file-based templates; install with `pip install .[langchain]`"
|
89
|
+
)
|
90
|
+
try:
|
91
|
+
from langchain_core.prompts import ChatPromptTemplate
|
92
|
+
except ImportError:
|
93
|
+
raise ImportError(
|
94
|
+
"langchain-core is required for to_langchain; install with `pip install .[langchain]`"
|
95
|
+
)
|
96
|
+
if isinstance(self.template, str):
|
97
|
+
template_text = self.template
|
98
|
+
elif isinstance(self.template, Template):
|
99
|
+
# raw Jinja2 template object
|
100
|
+
template_text = self.prompt_text
|
101
|
+
else:
|
102
|
+
# file-based template
|
103
|
+
prompt_dir = (
|
104
|
+
prompt_path_custom
|
105
|
+
if prompt_path_custom and os.path.exists(prompt_path_custom)
|
106
|
+
else prompt_path_default
|
107
|
+
)
|
108
|
+
template_file = os.path.join(prompt_dir, f"{self.prompt_template}.jinja")
|
109
|
+
with open(template_file, "r") as f:
|
110
|
+
template_text = f.read()
|
111
|
+
return ChatPromptTemplate.from_template(template_text, template_format="jinja2")
|
112
|
+
|
113
|
+
@classmethod
|
114
|
+
def from_text(cls, text: str):
|
115
|
+
"""
|
116
|
+
Create a Prompter instance from raw text, which can contain Jinja code.
|
117
|
+
|
118
|
+
Args:
|
119
|
+
text (str): The raw prompt text.
|
120
|
+
|
121
|
+
Returns:
|
122
|
+
Prompter: A new Prompter instance.
|
123
|
+
"""
|
124
|
+
|
125
|
+
return cls(prompt_text=text)
|
126
|
+
|
127
|
+
def render(self, data: Optional[Union[Dict, BaseModel]] = None) -> str:
|
128
|
+
"""
|
129
|
+
Render the prompt template with the given data.
|
130
|
+
|
131
|
+
Args:
|
132
|
+
data (Union[Dict, BaseModel]): The data to be used in rendering the template.
|
133
|
+
Can be either a dictionary or a Pydantic BaseModel.
|
134
|
+
|
135
|
+
Returns:
|
136
|
+
str: The rendered prompt text.
|
137
|
+
|
138
|
+
Raises:
|
139
|
+
AssertionError: If the template is not defined or not a Jinja2 Template.
|
140
|
+
"""
|
141
|
+
if isinstance(data, BaseModel):
|
142
|
+
data_dict = data.model_dump()
|
143
|
+
elif isinstance(data, dict):
|
144
|
+
data_dict = data
|
145
|
+
else:
|
146
|
+
data_dict = {}
|
147
|
+
render_data = dict(data_dict)
|
148
|
+
render_data["current_time"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
149
|
+
if self.parser:
|
150
|
+
render_data["format_instructions"] = self.parser.get_format_instructions()
|
151
|
+
assert self.template, "Prompter template is not defined"
|
152
|
+
assert isinstance(
|
153
|
+
self.template, Template
|
154
|
+
), "Prompter template is not a Jinja2 Template"
|
155
|
+
return self.template.render(render_data)
|
@@ -0,0 +1,206 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: ai-prompter
|
3
|
+
Version: 0.1.0
|
4
|
+
Summary: A prompt management library using Jinja2 templates to build complex prompts easily.
|
5
|
+
Author-email: LUIS NOVO <lfnovo@gmail.com>
|
6
|
+
License: MIT
|
7
|
+
License-File: LICENSE
|
8
|
+
Requires-Python: >=3.10.6
|
9
|
+
Requires-Dist: jinja2>=3.1.6
|
10
|
+
Requires-Dist: pip>=25.0.1
|
11
|
+
Requires-Dist: pydantic>=2.0
|
12
|
+
Provides-Extra: langchain
|
13
|
+
Requires-Dist: langchain-core>=0.3; extra == 'langchain'
|
14
|
+
Description-Content-Type: text/markdown
|
15
|
+
|
16
|
+
# AI Prompter
|
17
|
+
|
18
|
+
A prompt management library using Jinja2 templates to build complex prompts easily. Supports raw text or file-based templates and integrates with LangChain.
|
19
|
+
|
20
|
+
## Features
|
21
|
+
|
22
|
+
- Define prompts as Jinja templates.
|
23
|
+
- Load default templates from `src/ai_prompter/prompts`.
|
24
|
+
- Override templates via `PROMPT_PATH` environment variable.
|
25
|
+
- Render prompts with arbitrary data or Pydantic models.
|
26
|
+
- Export to LangChain `ChatPromptTemplate`.
|
27
|
+
|
28
|
+
## Installation
|
29
|
+
|
30
|
+
1. (Optional) Create and activate a virtual environment:
|
31
|
+
```bash
|
32
|
+
python3 -m venv .venv
|
33
|
+
source .venv/bin/activate
|
34
|
+
```
|
35
|
+
2. Install the package:
|
36
|
+
```bash
|
37
|
+
pip install .
|
38
|
+
```
|
39
|
+
### Extras
|
40
|
+
|
41
|
+
To enable LangChain integration:
|
42
|
+
|
43
|
+
```bash
|
44
|
+
pip install .[langchain]
|
45
|
+
# or
|
46
|
+
uv add langchain_core
|
47
|
+
```
|
48
|
+
|
49
|
+
## Configuration
|
50
|
+
|
51
|
+
Configure a custom template path by creating a `.env` file in the project root:
|
52
|
+
|
53
|
+
```dotenv
|
54
|
+
PROMPT_PATH=path/to/custom/templates
|
55
|
+
```
|
56
|
+
|
57
|
+
## Usage
|
58
|
+
|
59
|
+
### Raw text template
|
60
|
+
|
61
|
+
```python
|
62
|
+
from ai_prompter import Prompter
|
63
|
+
|
64
|
+
template = """Write an article about {{ topic }}."""
|
65
|
+
prompter = Prompter(prompt_text=template)
|
66
|
+
prompt = prompter.render({"topic": "AI"})
|
67
|
+
print(prompt) # Write an article about AI.
|
68
|
+
```
|
69
|
+
|
70
|
+
### Using File-based Templates
|
71
|
+
|
72
|
+
You can store your templates in files and reference them by name (without the `.jinja` extension). The library looks for templates in the `prompts` directory by default, or you can set a custom directory with the `PROMPT_PATH` environment variable.
|
73
|
+
|
74
|
+
```python
|
75
|
+
from ai_prompter import Prompter
|
76
|
+
|
77
|
+
prompter = Prompter(prompt_template="greet")
|
78
|
+
prompt = prompter.render({"who": "Tester"})
|
79
|
+
print(prompt) # GREET Tester
|
80
|
+
```
|
81
|
+
|
82
|
+
### Including Other Templates
|
83
|
+
|
84
|
+
You can include other template files within a template using Jinja2's `{% include %}` directive. This allows you to build modular templates.
|
85
|
+
|
86
|
+
```jinja
|
87
|
+
# outer.jinja
|
88
|
+
This is the outer file
|
89
|
+
|
90
|
+
{% include 'inner.jinja' %}
|
91
|
+
|
92
|
+
This is the end of the outer file
|
93
|
+
```
|
94
|
+
|
95
|
+
```jinja
|
96
|
+
# inner.jinja
|
97
|
+
This is the inner file
|
98
|
+
|
99
|
+
{% if type == 'a' %}
|
100
|
+
You selected A
|
101
|
+
{% else %}
|
102
|
+
You didn't select A
|
103
|
+
{% endif %}
|
104
|
+
```
|
105
|
+
|
106
|
+
```python
|
107
|
+
from ai_prompter import Prompter
|
108
|
+
|
109
|
+
prompter = Prompter(prompt_template="outer")
|
110
|
+
prompt = prompter.render(dict(type="a"))
|
111
|
+
print(prompt)
|
112
|
+
# This is the outer file
|
113
|
+
#
|
114
|
+
# This is the inner file
|
115
|
+
#
|
116
|
+
# You selected A
|
117
|
+
#
|
118
|
+
#
|
119
|
+
# This is the end of the outer file
|
120
|
+
```
|
121
|
+
|
122
|
+
### Using Variables
|
123
|
+
|
124
|
+
Templates can use variables that you pass in through the `render()` method. You can use Jinja2 filters and conditionals to control the output based on your data.
|
125
|
+
|
126
|
+
```python
|
127
|
+
from ai_prompter import Prompter
|
128
|
+
|
129
|
+
prompter = Prompter(prompt_text="Hello {{name|default('Guest')}}!")
|
130
|
+
prompt = prompter.render() # No data provided, uses default
|
131
|
+
print(prompt) # Hello Guest!
|
132
|
+
prompt = prompter.render({"name": "Alice"}) # Data provided
|
133
|
+
print(prompt) # Hello Alice!
|
134
|
+
```
|
135
|
+
|
136
|
+
The library also automatically provides a `current_time` variable with the current timestamp in format "YYYY-MM-DD HH:MM:SS".
|
137
|
+
|
138
|
+
```python
|
139
|
+
from ai_prompter import Prompter
|
140
|
+
|
141
|
+
prompter = Prompter(prompt_text="Current time: {{current_time}}")
|
142
|
+
prompt = prompter.render()
|
143
|
+
print(prompt) # Current time: 2025-04-19 23:28:00
|
144
|
+
```
|
145
|
+
|
146
|
+
### File-based template
|
147
|
+
|
148
|
+
Place a Jinja file (e.g., `article.jinja`) in the default prompts directory (`src/ai_prompter/prompts`) or your custom path:
|
149
|
+
|
150
|
+
```jinja
|
151
|
+
Write an article about {{ topic }}.
|
152
|
+
```
|
153
|
+
|
154
|
+
```python
|
155
|
+
from ai_prompter import Prompter
|
156
|
+
|
157
|
+
prompter = Prompter(prompt_template="article")
|
158
|
+
prompt = prompter.render({"topic": "AI"})
|
159
|
+
print(prompt)
|
160
|
+
```
|
161
|
+
|
162
|
+
### LangChain integration
|
163
|
+
|
164
|
+
```python
|
165
|
+
from ai_prompter import Prompter
|
166
|
+
|
167
|
+
prompter = Prompter(prompt_template="article")
|
168
|
+
lc_template = prompter.to_langchain()
|
169
|
+
# use lc_template in LangChain chains
|
170
|
+
```
|
171
|
+
|
172
|
+
### Jupyter Notebook
|
173
|
+
|
174
|
+
See `notebooks/prompter_usage.ipynb` for interactive examples.
|
175
|
+
|
176
|
+
## Project Structure
|
177
|
+
|
178
|
+
```
|
179
|
+
ai-prompter/
|
180
|
+
├── src/ai_prompter
|
181
|
+
│ ├── __init__.py
|
182
|
+
│ └── prompts/
|
183
|
+
│ └── *.jinja
|
184
|
+
├── notebooks/
|
185
|
+
│ ├── prompter_usage.ipynb
|
186
|
+
│ └── prompts/
|
187
|
+
├── pyproject.toml
|
188
|
+
├── README.md
|
189
|
+
└── .env (optional)
|
190
|
+
```
|
191
|
+
|
192
|
+
## Testing
|
193
|
+
|
194
|
+
Run tests with:
|
195
|
+
|
196
|
+
```bash
|
197
|
+
uv run pytest -v
|
198
|
+
```
|
199
|
+
|
200
|
+
## Contributing
|
201
|
+
|
202
|
+
Contributions welcome! Please open issues or PRs.
|
203
|
+
|
204
|
+
## License
|
205
|
+
|
206
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
@@ -0,0 +1,5 @@
|
|
1
|
+
ai_prompter/__init__.py,sha256=4Zzy7drJRTmQZGJCEcqnf1-RkvDvZiLIYXLDeyFXGJw,5913
|
2
|
+
ai_prompter-0.1.0.dist-info/METADATA,sha256=6siPIYPmficMeNOXkSzXk4-GjzTacIhaJjF2BRc7WUM,4757
|
3
|
+
ai_prompter-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
4
|
+
ai_prompter-0.1.0.dist-info/licenses/LICENSE,sha256=cS0_fa_8BoP0PvVG8D19pn_HDJrG96hd4PyEm9nkRo8,1066
|
5
|
+
ai_prompter-0.1.0.dist-info/RECORD,,
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 Luis Novo
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
9
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
10
|
+
so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|