llmpromptvault 0.1.0__tar.gz
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.
- llmpromptvault-0.1.0/LICENSE +23 -0
- llmpromptvault-0.1.0/PKG-INFO +331 -0
- llmpromptvault-0.1.0/README.md +301 -0
- llmpromptvault-0.1.0/llmpromptvault/__init__.py +37 -0
- llmpromptvault-0.1.0/llmpromptvault/compare.py +209 -0
- llmpromptvault-0.1.0/llmpromptvault/prompt.py +243 -0
- llmpromptvault-0.1.0/llmpromptvault/registry.py +158 -0
- llmpromptvault-0.1.0/llmpromptvault/storage.py +190 -0
- llmpromptvault-0.1.0/llmpromptvault.egg-info/PKG-INFO +331 -0
- llmpromptvault-0.1.0/llmpromptvault.egg-info/SOURCES.txt +14 -0
- llmpromptvault-0.1.0/llmpromptvault.egg-info/dependency_links.txt +1 -0
- llmpromptvault-0.1.0/llmpromptvault.egg-info/requires.txt +8 -0
- llmpromptvault-0.1.0/llmpromptvault.egg-info/top_level.txt +1 -0
- llmpromptvault-0.1.0/pyproject.toml +58 -0
- llmpromptvault-0.1.0/setup.cfg +4 -0
- llmpromptvault-0.1.0/tests/test_promptdiff.py +0 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
```
|
|
2
|
+
MIT License
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2025 Your Name
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
23
|
+
```
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: llmpromptvault
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Version, compare, and manage your LLM prompts. No API keys required. Bring your own model.
|
|
5
|
+
Author-email: Ankur Srivastav <ankursrivastava98@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: llm,prompts,prompt-engineering,prompt-management,versioning,mlops,ai,comparison,testing
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Classifier: Topic :: Software Development :: Version Control
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: pyyaml>=6.0
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
26
|
+
Requires-Dist: black>=23.0; extra == "dev"
|
|
27
|
+
Requires-Dist: build; extra == "dev"
|
|
28
|
+
Requires-Dist: twine; extra == "dev"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# PromptVault 🔐
|
|
32
|
+
|
|
33
|
+
> **Version and compare your LLM prompts. No API key required. Bring your own model.**
|
|
34
|
+
|
|
35
|
+
[](https://pypi.org/project/promptvault/)
|
|
36
|
+
[](https://www.python.org/)
|
|
37
|
+
[](LICENSE)
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## What PromptVault Does
|
|
42
|
+
|
|
43
|
+
PromptVault is a **prompt management library** — not an LLM client. It does three things:
|
|
44
|
+
|
|
45
|
+
1. **Versions your prompts** — tracks every change like Git
|
|
46
|
+
2. **Logs responses** — stores what each prompt returned, with latency and token counts
|
|
47
|
+
3. **Compares prompts** — shows you side-by-side how two prompt versions perform
|
|
48
|
+
|
|
49
|
+
**PromptVault never calls any LLM.** You call your own model however you like — OpenAI, Claude, Gemini, Ollama, a local model, anything. You pass the response to PromptVault and it handles the rest.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install promptvault
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Only one dependency: `pyyaml`. Everything else uses Python's standard library.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Quick Start
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from promptvault import Prompt, Compare
|
|
67
|
+
|
|
68
|
+
# 1. Define two versions of a prompt
|
|
69
|
+
v1 = Prompt("summarize", template="Summarize this: {text}", version="v1")
|
|
70
|
+
v2 = Prompt("summarize", template="Summarize in 3 bullet points: {text}", version="v2")
|
|
71
|
+
|
|
72
|
+
# 2. YOU call your LLM (any model, any way you like)
|
|
73
|
+
r1 = your_llm(v1.render(text="Some article content..."))
|
|
74
|
+
r2 = your_llm(v2.render(text="Some article content..."))
|
|
75
|
+
|
|
76
|
+
# 3. PromptVault compares and logs them
|
|
77
|
+
cmp = Compare(v1, v2)
|
|
78
|
+
cmp.log(r1, r2)
|
|
79
|
+
cmp.show()
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Output:
|
|
83
|
+
```
|
|
84
|
+
────────────────────────────────────────────────────────────
|
|
85
|
+
PROMPTVAULT COMPARISON
|
|
86
|
+
────────────────────────────────────────────────────────────
|
|
87
|
+
Prompt A summarize (v1)
|
|
88
|
+
Prompt B summarize (v2)
|
|
89
|
+
────────────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
── Response A ──
|
|
92
|
+
Here is a summary of the article...
|
|
93
|
+
|
|
94
|
+
── Response B ──
|
|
95
|
+
• Key point one
|
|
96
|
+
• Key point two
|
|
97
|
+
• Key point three
|
|
98
|
+
|
|
99
|
+
────────────────────────────────────────────────────────────
|
|
100
|
+
Metric Prompt A Prompt B
|
|
101
|
+
──────────────────────────── ──────────── ────────────
|
|
102
|
+
Word count 12 18
|
|
103
|
+
Char count 68 112
|
|
104
|
+
Latency (ms) 820.0 950.0
|
|
105
|
+
Tokens 45 62
|
|
106
|
+
────────────────────────────────────────────────────────────
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Core API
|
|
112
|
+
|
|
113
|
+
### `Prompt` — Define and version your prompts
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from promptvault import Prompt
|
|
117
|
+
|
|
118
|
+
# Create a prompt
|
|
119
|
+
p = Prompt(
|
|
120
|
+
name="classify",
|
|
121
|
+
template="Classify this text as positive or negative: {text}",
|
|
122
|
+
version="v1",
|
|
123
|
+
description="Sentiment classifier",
|
|
124
|
+
tags=["classify", "sentiment"],
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# See required variables
|
|
128
|
+
p.variables() # ['text']
|
|
129
|
+
|
|
130
|
+
# Render it (just string formatting — no LLM call)
|
|
131
|
+
rendered = p.render(text="I love this product!")
|
|
132
|
+
|
|
133
|
+
# YOU call your LLM
|
|
134
|
+
response = your_llm(rendered)
|
|
135
|
+
|
|
136
|
+
# Log the response
|
|
137
|
+
p.log(
|
|
138
|
+
rendered_prompt=rendered,
|
|
139
|
+
response=response,
|
|
140
|
+
model="gpt-4o-mini", # optional
|
|
141
|
+
latency_ms=820, # optional
|
|
142
|
+
tokens=45, # optional
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# View stats across all logged runs
|
|
146
|
+
p.stats()
|
|
147
|
+
# {
|
|
148
|
+
# 'run_count': 10,
|
|
149
|
+
# 'avg_latency_ms': 750.0,
|
|
150
|
+
# 'avg_tokens': 42.0,
|
|
151
|
+
# 'models_used': ['gpt-4o-mini'],
|
|
152
|
+
# ...
|
|
153
|
+
# }
|
|
154
|
+
|
|
155
|
+
# View raw run history
|
|
156
|
+
p.runs(last_n=10)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Versioning
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
# Create v2 — v1 is automatically preserved in history
|
|
163
|
+
v2 = p.update(
|
|
164
|
+
new_template="You are a sentiment expert. Classify as positive/negative/neutral: {text}"
|
|
165
|
+
)
|
|
166
|
+
# Auto-increments: v1 → v2. Or pass version="my-version"
|
|
167
|
+
|
|
168
|
+
# See all versions
|
|
169
|
+
p.history() # [{'version': 'v1', 'template': ..., ...}, {'version': 'v2', ...}]
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Save & Load YAML
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
# Export to a human-readable file
|
|
176
|
+
p.save("prompts/classify.yaml")
|
|
177
|
+
|
|
178
|
+
# Load it back anywhere
|
|
179
|
+
p = Prompt.load("prompts/classify.yaml")
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
```yaml
|
|
183
|
+
# prompts/classify.yaml
|
|
184
|
+
name: classify
|
|
185
|
+
version: v1
|
|
186
|
+
description: Sentiment classifier
|
|
187
|
+
template: 'Classify this text as positive or negative: {text}'
|
|
188
|
+
tags:
|
|
189
|
+
- classify
|
|
190
|
+
- sentiment
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
### `Compare` — Side-by-side prompt comparison
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
from promptvault import Compare
|
|
199
|
+
|
|
200
|
+
cmp = Compare(v1, v2)
|
|
201
|
+
|
|
202
|
+
# Log one comparison pair
|
|
203
|
+
cmp.log(
|
|
204
|
+
response_a=response_v1,
|
|
205
|
+
response_b=response_v2,
|
|
206
|
+
model="llama3", # optional
|
|
207
|
+
latency_ms_a=820, # optional
|
|
208
|
+
latency_ms_b=950, # optional
|
|
209
|
+
tokens_a=45, # optional
|
|
210
|
+
tokens_b=62, # optional
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# Print side-by-side to terminal
|
|
214
|
+
cmp.show()
|
|
215
|
+
|
|
216
|
+
# Get structured diff dict
|
|
217
|
+
cmp.diff()
|
|
218
|
+
# {
|
|
219
|
+
# 'response_a': '...', 'response_b': '...',
|
|
220
|
+
# 'words_a': 12, 'words_b': 18,
|
|
221
|
+
# 'latency_ms_a': 820, 'latency_ms_b': 950,
|
|
222
|
+
# ...
|
|
223
|
+
# }
|
|
224
|
+
|
|
225
|
+
# Aggregate summary across multiple comparison runs
|
|
226
|
+
cmp.summary()
|
|
227
|
+
# {
|
|
228
|
+
# 'total_comparisons': 5,
|
|
229
|
+
# 'avg_words_a': 12.0, 'avg_words_b': 18.2,
|
|
230
|
+
# 'avg_latency_ms_a': 810.0, 'avg_latency_ms_b': 940.0,
|
|
231
|
+
# ...
|
|
232
|
+
# }
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
### `Registry` — Share prompts with your team
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
from promptvault import Registry
|
|
241
|
+
|
|
242
|
+
reg = Registry("./shared_prompts") # any folder path
|
|
243
|
+
|
|
244
|
+
# Push prompts in
|
|
245
|
+
reg.push(v1)
|
|
246
|
+
reg.push(v2)
|
|
247
|
+
|
|
248
|
+
# Pull them out anywhere
|
|
249
|
+
p = reg.pull("classify") # latest version
|
|
250
|
+
p = reg.pull("classify", "v1") # specific version
|
|
251
|
+
|
|
252
|
+
reg.list() # ['classify', 'summarize', ...]
|
|
253
|
+
reg.versions("classify") # ['v1', 'v2']
|
|
254
|
+
reg.delete("classify", "v1")
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## Works with Any LLM
|
|
260
|
+
|
|
261
|
+
Because PromptVault never makes LLM calls itself, it works with literally any model:
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
# OpenAI
|
|
265
|
+
import openai
|
|
266
|
+
client = openai.OpenAI(api_key="...")
|
|
267
|
+
response = client.chat.completions.create(model="gpt-4o", messages=[{"role": "user", "content": rendered}])
|
|
268
|
+
p.log(rendered, response.choices[0].message.content, model="gpt-4o")
|
|
269
|
+
|
|
270
|
+
# Anthropic
|
|
271
|
+
import anthropic
|
|
272
|
+
client = anthropic.Anthropic(api_key="...")
|
|
273
|
+
response = client.messages.create(model="claude-haiku-4-5-20251001", max_tokens=1024, messages=[{"role": "user", "content": rendered}])
|
|
274
|
+
p.log(rendered, response.content[0].text, model="claude-haiku-4-5-20251001")
|
|
275
|
+
|
|
276
|
+
# Ollama (local, free, no key)
|
|
277
|
+
import requests
|
|
278
|
+
response = requests.post("http://localhost:11434/api/generate", json={"model": "llama3", "prompt": rendered, "stream": False})
|
|
279
|
+
p.log(rendered, response.json()["response"], model="llama3")
|
|
280
|
+
|
|
281
|
+
# Any other LLM, API, or local model
|
|
282
|
+
response = any_llm_you_want(rendered)
|
|
283
|
+
p.log(rendered, response)
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## File Structure
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
your_project/
|
|
292
|
+
├── prompts/
|
|
293
|
+
│ └── classify.yaml ← exported prompt templates
|
|
294
|
+
├── .promptvault/
|
|
295
|
+
│ ├── history.json ← version history
|
|
296
|
+
│ └── runs.db ← run logs (SQLite)
|
|
297
|
+
└── main.py
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Add `.promptvault/` to `.gitignore` to keep run logs local, or commit it to share analytics with your team.
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Run the Demo
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
git clone https://github.com/your-username/promptvault
|
|
308
|
+
cd promptvault
|
|
309
|
+
pip install pyyaml
|
|
310
|
+
python examples/demo.py
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
No API key needed — the demo uses simulated responses.
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Publish to PyPI
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
pip install build twine
|
|
321
|
+
python -m build
|
|
322
|
+
twine upload dist/*
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
See `PUBLISHING_GUIDE.md` for the full step-by-step.
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## License
|
|
330
|
+
|
|
331
|
+
MIT © PromptVault Contributors
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# PromptVault 🔐
|
|
2
|
+
|
|
3
|
+
> **Version and compare your LLM prompts. No API key required. Bring your own model.**
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/promptvault/)
|
|
6
|
+
[](https://www.python.org/)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## What PromptVault Does
|
|
12
|
+
|
|
13
|
+
PromptVault is a **prompt management library** — not an LLM client. It does three things:
|
|
14
|
+
|
|
15
|
+
1. **Versions your prompts** — tracks every change like Git
|
|
16
|
+
2. **Logs responses** — stores what each prompt returned, with latency and token counts
|
|
17
|
+
3. **Compares prompts** — shows you side-by-side how two prompt versions perform
|
|
18
|
+
|
|
19
|
+
**PromptVault never calls any LLM.** You call your own model however you like — OpenAI, Claude, Gemini, Ollama, a local model, anything. You pass the response to PromptVault and it handles the rest.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install promptvault
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Only one dependency: `pyyaml`. Everything else uses Python's standard library.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from promptvault import Prompt, Compare
|
|
37
|
+
|
|
38
|
+
# 1. Define two versions of a prompt
|
|
39
|
+
v1 = Prompt("summarize", template="Summarize this: {text}", version="v1")
|
|
40
|
+
v2 = Prompt("summarize", template="Summarize in 3 bullet points: {text}", version="v2")
|
|
41
|
+
|
|
42
|
+
# 2. YOU call your LLM (any model, any way you like)
|
|
43
|
+
r1 = your_llm(v1.render(text="Some article content..."))
|
|
44
|
+
r2 = your_llm(v2.render(text="Some article content..."))
|
|
45
|
+
|
|
46
|
+
# 3. PromptVault compares and logs them
|
|
47
|
+
cmp = Compare(v1, v2)
|
|
48
|
+
cmp.log(r1, r2)
|
|
49
|
+
cmp.show()
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Output:
|
|
53
|
+
```
|
|
54
|
+
────────────────────────────────────────────────────────────
|
|
55
|
+
PROMPTVAULT COMPARISON
|
|
56
|
+
────────────────────────────────────────────────────────────
|
|
57
|
+
Prompt A summarize (v1)
|
|
58
|
+
Prompt B summarize (v2)
|
|
59
|
+
────────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
── Response A ──
|
|
62
|
+
Here is a summary of the article...
|
|
63
|
+
|
|
64
|
+
── Response B ──
|
|
65
|
+
• Key point one
|
|
66
|
+
• Key point two
|
|
67
|
+
• Key point three
|
|
68
|
+
|
|
69
|
+
────────────────────────────────────────────────────────────
|
|
70
|
+
Metric Prompt A Prompt B
|
|
71
|
+
──────────────────────────── ──────────── ────────────
|
|
72
|
+
Word count 12 18
|
|
73
|
+
Char count 68 112
|
|
74
|
+
Latency (ms) 820.0 950.0
|
|
75
|
+
Tokens 45 62
|
|
76
|
+
────────────────────────────────────────────────────────────
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Core API
|
|
82
|
+
|
|
83
|
+
### `Prompt` — Define and version your prompts
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from promptvault import Prompt
|
|
87
|
+
|
|
88
|
+
# Create a prompt
|
|
89
|
+
p = Prompt(
|
|
90
|
+
name="classify",
|
|
91
|
+
template="Classify this text as positive or negative: {text}",
|
|
92
|
+
version="v1",
|
|
93
|
+
description="Sentiment classifier",
|
|
94
|
+
tags=["classify", "sentiment"],
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# See required variables
|
|
98
|
+
p.variables() # ['text']
|
|
99
|
+
|
|
100
|
+
# Render it (just string formatting — no LLM call)
|
|
101
|
+
rendered = p.render(text="I love this product!")
|
|
102
|
+
|
|
103
|
+
# YOU call your LLM
|
|
104
|
+
response = your_llm(rendered)
|
|
105
|
+
|
|
106
|
+
# Log the response
|
|
107
|
+
p.log(
|
|
108
|
+
rendered_prompt=rendered,
|
|
109
|
+
response=response,
|
|
110
|
+
model="gpt-4o-mini", # optional
|
|
111
|
+
latency_ms=820, # optional
|
|
112
|
+
tokens=45, # optional
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# View stats across all logged runs
|
|
116
|
+
p.stats()
|
|
117
|
+
# {
|
|
118
|
+
# 'run_count': 10,
|
|
119
|
+
# 'avg_latency_ms': 750.0,
|
|
120
|
+
# 'avg_tokens': 42.0,
|
|
121
|
+
# 'models_used': ['gpt-4o-mini'],
|
|
122
|
+
# ...
|
|
123
|
+
# }
|
|
124
|
+
|
|
125
|
+
# View raw run history
|
|
126
|
+
p.runs(last_n=10)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Versioning
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
# Create v2 — v1 is automatically preserved in history
|
|
133
|
+
v2 = p.update(
|
|
134
|
+
new_template="You are a sentiment expert. Classify as positive/negative/neutral: {text}"
|
|
135
|
+
)
|
|
136
|
+
# Auto-increments: v1 → v2. Or pass version="my-version"
|
|
137
|
+
|
|
138
|
+
# See all versions
|
|
139
|
+
p.history() # [{'version': 'v1', 'template': ..., ...}, {'version': 'v2', ...}]
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Save & Load YAML
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
# Export to a human-readable file
|
|
146
|
+
p.save("prompts/classify.yaml")
|
|
147
|
+
|
|
148
|
+
# Load it back anywhere
|
|
149
|
+
p = Prompt.load("prompts/classify.yaml")
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
```yaml
|
|
153
|
+
# prompts/classify.yaml
|
|
154
|
+
name: classify
|
|
155
|
+
version: v1
|
|
156
|
+
description: Sentiment classifier
|
|
157
|
+
template: 'Classify this text as positive or negative: {text}'
|
|
158
|
+
tags:
|
|
159
|
+
- classify
|
|
160
|
+
- sentiment
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
### `Compare` — Side-by-side prompt comparison
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from promptvault import Compare
|
|
169
|
+
|
|
170
|
+
cmp = Compare(v1, v2)
|
|
171
|
+
|
|
172
|
+
# Log one comparison pair
|
|
173
|
+
cmp.log(
|
|
174
|
+
response_a=response_v1,
|
|
175
|
+
response_b=response_v2,
|
|
176
|
+
model="llama3", # optional
|
|
177
|
+
latency_ms_a=820, # optional
|
|
178
|
+
latency_ms_b=950, # optional
|
|
179
|
+
tokens_a=45, # optional
|
|
180
|
+
tokens_b=62, # optional
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# Print side-by-side to terminal
|
|
184
|
+
cmp.show()
|
|
185
|
+
|
|
186
|
+
# Get structured diff dict
|
|
187
|
+
cmp.diff()
|
|
188
|
+
# {
|
|
189
|
+
# 'response_a': '...', 'response_b': '...',
|
|
190
|
+
# 'words_a': 12, 'words_b': 18,
|
|
191
|
+
# 'latency_ms_a': 820, 'latency_ms_b': 950,
|
|
192
|
+
# ...
|
|
193
|
+
# }
|
|
194
|
+
|
|
195
|
+
# Aggregate summary across multiple comparison runs
|
|
196
|
+
cmp.summary()
|
|
197
|
+
# {
|
|
198
|
+
# 'total_comparisons': 5,
|
|
199
|
+
# 'avg_words_a': 12.0, 'avg_words_b': 18.2,
|
|
200
|
+
# 'avg_latency_ms_a': 810.0, 'avg_latency_ms_b': 940.0,
|
|
201
|
+
# ...
|
|
202
|
+
# }
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
### `Registry` — Share prompts with your team
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from promptvault import Registry
|
|
211
|
+
|
|
212
|
+
reg = Registry("./shared_prompts") # any folder path
|
|
213
|
+
|
|
214
|
+
# Push prompts in
|
|
215
|
+
reg.push(v1)
|
|
216
|
+
reg.push(v2)
|
|
217
|
+
|
|
218
|
+
# Pull them out anywhere
|
|
219
|
+
p = reg.pull("classify") # latest version
|
|
220
|
+
p = reg.pull("classify", "v1") # specific version
|
|
221
|
+
|
|
222
|
+
reg.list() # ['classify', 'summarize', ...]
|
|
223
|
+
reg.versions("classify") # ['v1', 'v2']
|
|
224
|
+
reg.delete("classify", "v1")
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Works with Any LLM
|
|
230
|
+
|
|
231
|
+
Because PromptVault never makes LLM calls itself, it works with literally any model:
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
# OpenAI
|
|
235
|
+
import openai
|
|
236
|
+
client = openai.OpenAI(api_key="...")
|
|
237
|
+
response = client.chat.completions.create(model="gpt-4o", messages=[{"role": "user", "content": rendered}])
|
|
238
|
+
p.log(rendered, response.choices[0].message.content, model="gpt-4o")
|
|
239
|
+
|
|
240
|
+
# Anthropic
|
|
241
|
+
import anthropic
|
|
242
|
+
client = anthropic.Anthropic(api_key="...")
|
|
243
|
+
response = client.messages.create(model="claude-haiku-4-5-20251001", max_tokens=1024, messages=[{"role": "user", "content": rendered}])
|
|
244
|
+
p.log(rendered, response.content[0].text, model="claude-haiku-4-5-20251001")
|
|
245
|
+
|
|
246
|
+
# Ollama (local, free, no key)
|
|
247
|
+
import requests
|
|
248
|
+
response = requests.post("http://localhost:11434/api/generate", json={"model": "llama3", "prompt": rendered, "stream": False})
|
|
249
|
+
p.log(rendered, response.json()["response"], model="llama3")
|
|
250
|
+
|
|
251
|
+
# Any other LLM, API, or local model
|
|
252
|
+
response = any_llm_you_want(rendered)
|
|
253
|
+
p.log(rendered, response)
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## File Structure
|
|
259
|
+
|
|
260
|
+
```
|
|
261
|
+
your_project/
|
|
262
|
+
├── prompts/
|
|
263
|
+
│ └── classify.yaml ← exported prompt templates
|
|
264
|
+
├── .promptvault/
|
|
265
|
+
│ ├── history.json ← version history
|
|
266
|
+
│ └── runs.db ← run logs (SQLite)
|
|
267
|
+
└── main.py
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Add `.promptvault/` to `.gitignore` to keep run logs local, or commit it to share analytics with your team.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Run the Demo
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
git clone https://github.com/your-username/promptvault
|
|
278
|
+
cd promptvault
|
|
279
|
+
pip install pyyaml
|
|
280
|
+
python examples/demo.py
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
No API key needed — the demo uses simulated responses.
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Publish to PyPI
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
pip install build twine
|
|
291
|
+
python -m build
|
|
292
|
+
twine upload dist/*
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
See `PUBLISHING_GUIDE.md` for the full step-by-step.
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## License
|
|
300
|
+
|
|
301
|
+
MIT © PromptVault Contributors
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PromptVault — Version, compare, and manage your LLM prompts.
|
|
3
|
+
|
|
4
|
+
No API keys. No LLM calls. Bring your own model.
|
|
5
|
+
|
|
6
|
+
Quick Start:
|
|
7
|
+
from promptvault import Prompt, Compare
|
|
8
|
+
|
|
9
|
+
# 1. Define two prompt versions
|
|
10
|
+
v1 = Prompt("summarize", template="Summarize this: {text}")
|
|
11
|
+
v2 = Prompt("summarize", template="Summarize in 3 bullets: {text}", version="v2")
|
|
12
|
+
|
|
13
|
+
# 2. YOU call your LLM (any LLM, any way you like)
|
|
14
|
+
r1 = your_llm(v1.render(text="Some article..."))
|
|
15
|
+
r2 = your_llm(v2.render(text="Some article..."))
|
|
16
|
+
|
|
17
|
+
# 3. PromptVault compares and tracks
|
|
18
|
+
cmp = Compare(v1, v2)
|
|
19
|
+
cmp.log(r1, r2)
|
|
20
|
+
cmp.show()
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from .prompt import Prompt
|
|
24
|
+
from .compare import Compare
|
|
25
|
+
from .registry import Registry
|
|
26
|
+
from .storage import PromptStorage
|
|
27
|
+
|
|
28
|
+
__version__ = "0.1.0"
|
|
29
|
+
__author__ = "PromptVault Contributors"
|
|
30
|
+
__license__ = "MIT"
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"Prompt",
|
|
34
|
+
"Compare",
|
|
35
|
+
"Registry",
|
|
36
|
+
"PromptStorage",
|
|
37
|
+
]
|