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.
@@ -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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -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.