prompteval-delta 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.
- prompteval_delta-0.1.0/PKG-INFO +247 -0
- prompteval_delta-0.1.0/README.md +219 -0
- prompteval_delta-0.1.0/pyproject.toml +104 -0
- prompteval_delta-0.1.0/pyproject.toml.orig +106 -0
- prompteval_delta-0.1.0/src/prompteval/__init__.py +87 -0
- prompteval_delta-0.1.0/src/prompteval/cli.py +378 -0
- prompteval_delta-0.1.0/src/prompteval/compare/__init__.py +38 -0
- prompteval_delta-0.1.0/src/prompteval/compare/core.py +635 -0
- prompteval_delta-0.1.0/src/prompteval/compare/gates.py +153 -0
- prompteval_delta-0.1.0/src/prompteval/cost/__init__.py +30 -0
- prompteval_delta-0.1.0/src/prompteval/cost/compute.py +138 -0
- prompteval_delta-0.1.0/src/prompteval/cost/models.py +168 -0
- prompteval_delta-0.1.0/src/prompteval/cost/pricing.yaml +69 -0
- prompteval_delta-0.1.0/src/prompteval/eval/__init__.py +58 -0
- prompteval_delta-0.1.0/src/prompteval/eval/judge.py +107 -0
- prompteval_delta-0.1.0/src/prompteval/eval/persistence.py +99 -0
- prompteval_delta-0.1.0/src/prompteval/eval/runner.py +407 -0
- prompteval_delta-0.1.0/src/prompteval/eval/scorer.py +92 -0
- prompteval_delta-0.1.0/src/prompteval/eval/stock.py +183 -0
- prompteval_delta-0.1.0/src/prompteval/init/__init__.py +11 -0
- prompteval_delta-0.1.0/src/prompteval/init/bootstrap.py +80 -0
- prompteval_delta-0.1.0/src/prompteval/init/templates/__init__.py +2 -0
- prompteval_delta-0.1.0/src/prompteval/init/templates/dataset.jsonl +5 -0
- prompteval_delta-0.1.0/src/prompteval/init/templates/env.example +4 -0
- prompteval_delta-0.1.0/src/prompteval/init/templates/eval.py +63 -0
- prompteval_delta-0.1.0/src/prompteval/init/templates/prompts/v1.txt +11 -0
- prompteval_delta-0.1.0/src/prompteval/init/templates/prompts/v2.txt +9 -0
- prompteval_delta-0.1.0/src/prompteval/py.typed +0 -0
- prompteval_delta-0.1.0/src/prompteval/version.py +8 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: prompteval-delta
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: LLM eval framework with first-class token-cost tracking. Did your prompt change save money without losing quality?
|
|
5
|
+
Keywords: llm,evaluation,evals,prompt-engineering,openai,gpt-4,cost-tracking,token-cost,ai-quality,regression-detection
|
|
6
|
+
Author: Anuj Mahajan
|
|
7
|
+
Author-email: Anuj Mahajan <mahajananuj07@gmail.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
15
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
16
|
+
Classifier: Topic :: Software Development :: Testing
|
|
17
|
+
Requires-Dist: openai>=1.50.0
|
|
18
|
+
Requires-Dist: click>=8.1.7
|
|
19
|
+
Requires-Dist: pydantic>=2.9.0
|
|
20
|
+
Requires-Dist: pyyaml>=6.0
|
|
21
|
+
Requires-Dist: numpy>=1.26
|
|
22
|
+
Requires-Dist: scipy>=1.13
|
|
23
|
+
Requires-Python: >=3.12
|
|
24
|
+
Project-URL: Homepage, https://github.com/mahAnuj/prompteval
|
|
25
|
+
Project-URL: Repository, https://github.com/mahAnuj/prompteval
|
|
26
|
+
Project-URL: Issues, https://github.com/mahAnuj/prompteval/issues
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# prompteval
|
|
30
|
+
|
|
31
|
+
**Paired cost-and-quality delta reports for LLM prompts — so you can ship the cheaper version without guessing.**
|
|
32
|
+
|
|
33
|
+
> _Did your prompt change save money without losing quality?_
|
|
34
|
+
|
|
35
|
+
prompteval is **pytest for LLM prompts**. You define a small golden test set and one or more scorers. You run it against your v1 prompt, then your v2 prompt, then ask it to compare. You get back a single report:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
v1 v2 Δ 95% CI p
|
|
39
|
+
─────────────────────────────────────────────────────────────────────────────
|
|
40
|
+
mentions_required_terms 0.84 0.79 −0.05 [−0.13, +0.03] 0.21
|
|
41
|
+
professional_tone 0.81 0.83 +0.02 [−0.05, +0.09] 0.58
|
|
42
|
+
─────────────────────────────────────────────────────────────────────────────
|
|
43
|
+
total cost $0.158 $0.099 −37% [−42%, −32%] <0.001
|
|
44
|
+
avg latency 1.4s 1.1s −21% [−28%, −14%] <0.001
|
|
45
|
+
|
|
46
|
+
Quality verdict: no significant regression
|
|
47
|
+
Cost verdict: significant 37% reduction
|
|
48
|
+
Recommendation: ship v2 — cost savings real, quality holds
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Existing eval tools (Braintrust, Langfuse, promptfoo, Phoenix) track cost as a metric you graph; prompteval treats it as a **comparison axis** alongside quality. See [docs/competitor-scan.md](docs/competitor-scan.md) for verified evidence.
|
|
52
|
+
|
|
53
|
+
> ⚠️ **Alpha — v0.1.0.** The PyPI package is **`prompteval-delta`** (first
|
|
54
|
+
> release pending; until then, install from source). **Do not
|
|
55
|
+
> `pip install prompteval`** — that name on PyPI belongs to an unrelated
|
|
56
|
+
> project. The import and the command are still `prompteval`. The public API
|
|
57
|
+
> may still change before 1.0. Stars and issues welcome.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## What prompteval is, and is not
|
|
62
|
+
|
|
63
|
+
| ✅ It is | ❌ It is not |
|
|
64
|
+
|---|---|
|
|
65
|
+
| An **offline** eval framework (think pytest for prompts) | A runtime proxy or LLM gateway |
|
|
66
|
+
| Run from your CLI or your CI | Sitting in your prod request path |
|
|
67
|
+
| Compares two prompt versions on quality **and** cost | Caching prod LLM calls |
|
|
68
|
+
| Statistical significance built in | Replacing Helicone / Portkey / LiteLLM |
|
|
69
|
+
| Honest about scorer methodology | A black-box "AI judges your AI" |
|
|
70
|
+
|
|
71
|
+
**Closest neighbors:** pytest, [Braintrust Experiments](https://braintrust.dev/), [promptfoo](https://promptfoo.dev/), [Inspect AI](https://inspect.aisi.org.uk/).
|
|
72
|
+
|
|
73
|
+
**Not what we do:** [Helicone](https://helicone.ai/), [Portkey](https://portkey.ai/), [LiteLLM](https://github.com/BerriAI/litellm), [LangChain](https://langchain.com/).
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## User journey (v1)
|
|
78
|
+
|
|
79
|
+
Eight steps. ~30 minutes the first time. ~5 minutes every time after.
|
|
80
|
+
|
|
81
|
+
### 1. Install
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
uv add prompteval-delta # or: pip install prompteval-delta
|
|
85
|
+
prompteval --version
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 2. Bootstrap an evals folder
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
cd ~/my-app
|
|
92
|
+
prompteval init
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Creates:
|
|
96
|
+
```
|
|
97
|
+
evals/
|
|
98
|
+
├── prompts/
|
|
99
|
+
│ ├── v1.txt # paste your current prompt
|
|
100
|
+
│ └── v2.txt # paste the variant you want to test
|
|
101
|
+
├── dataset.jsonl # one example pre-filled
|
|
102
|
+
├── eval.py # pre-filled with 3 scorer examples
|
|
103
|
+
└── .env.example # OPENAI_API_KEY=...
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 3. Bring your prompts + a few examples
|
|
107
|
+
|
|
108
|
+
Paste prompts into `prompts/v1.txt` and `prompts/v2.txt`. Add 10–30 examples to `dataset.jsonl`:
|
|
109
|
+
|
|
110
|
+
```jsonl
|
|
111
|
+
{"id": "refund-1", "input": "My item arrived broken, refund please", "expected": {"must_mention": ["30-day refund"], "tone": "empathetic"}}
|
|
112
|
+
{"id": "refund-2", "input": "Refund request 45 days after purchase", "expected": {"must_mention": ["30-day", "outside policy"], "tone": "polite"}}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The `expected` field is whatever your scorers want — there's no fixed schema.
|
|
116
|
+
|
|
117
|
+
### 4. Define what "good" means for your use case (scorers)
|
|
118
|
+
|
|
119
|
+
`eval.py` ships with three example scorers. Edit them, write new ones, or copy from templates:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from prompteval import Eval, scorer, llm_judge
|
|
123
|
+
|
|
124
|
+
@scorer
|
|
125
|
+
def mentions_required_terms(output: str, expected: dict) -> float:
|
|
126
|
+
"""Hard scorer: did the reply contain required phrases?"""
|
|
127
|
+
must = expected.get("must_mention", [])
|
|
128
|
+
if not must:
|
|
129
|
+
return 1.0
|
|
130
|
+
return sum(1 for t in must if t.lower() in output.lower()) / len(must)
|
|
131
|
+
|
|
132
|
+
@scorer
|
|
133
|
+
def professional_tone(output: str, expected: dict) -> float:
|
|
134
|
+
"""LLM-as-judge: rate professionalism 0-1."""
|
|
135
|
+
return llm_judge(
|
|
136
|
+
rubric="Rate the tone of this support reply 0-1. 1=empathetic+professional, 0=rude.",
|
|
137
|
+
text=output,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
eval = Eval(
|
|
141
|
+
name="support-assistant",
|
|
142
|
+
dataset="dataset.jsonl",
|
|
143
|
+
scorers=[mentions_required_terms, professional_tone],
|
|
144
|
+
)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Don't know where to start? `prompteval scorer list` shows 6–8 templates (regex match, JSON-schema valid, contains-substring, exact-match, LLM-judge factuality, LLM-judge tone). `prompteval scorer copy <name>` drops one into your `eval.py`.
|
|
148
|
+
|
|
149
|
+
> **Honest caveat that matters:** prompteval doesn't define quality for your business — you do, by writing scorers. Get this part right and the tool earns its keep. Get it wrong and the tool will tell you wrong things confidently. **Every report shows which scorers ran on how many examples — methodology is always visible.** See [docs/writing-scorers.md](docs/writing-scorers.md) (coming v0.1) for guidance.
|
|
150
|
+
|
|
151
|
+
### 5. Run baseline (v1)
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
prompteval run --prompt prompts/v1.txt --tag baseline
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
Running 20 examples × 1 prompt against gpt-4o...
|
|
159
|
+
refund-1 ✓ mentions_required_terms=1.00 professional_tone=0.85
|
|
160
|
+
refund-2 ✓ mentions_required_terms=0.50 professional_tone=0.90
|
|
161
|
+
...
|
|
162
|
+
=== baseline ===
|
|
163
|
+
mentions_required_terms: 0.84 (n=20)
|
|
164
|
+
professional_tone: 0.81 (n=20)
|
|
165
|
+
total cost: $0.158 (avg $0.0079/call)
|
|
166
|
+
avg latency: 1.4s
|
|
167
|
+
saved to: .prompteval/runs/baseline-2026-06-26T1240.json
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### 6. Run the variant (v2)
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
prompteval run --prompt prompts/v2.txt --tag short-prompt
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### 7. Compare — the moment of truth
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
prompteval compare baseline short-prompt
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
You get the report shown at the top of this README — paired quality and cost deltas with 95% CIs, p-values, and a plain-English recommendation.
|
|
183
|
+
|
|
184
|
+
### 8. (Optional but the moat) Lock it into CI
|
|
185
|
+
|
|
186
|
+
A copy-and-paste GitHub Actions workflow lives at [`examples/github-actions/prompteval.yml`](examples/github-actions/prompteval.yml). The core of it:
|
|
187
|
+
|
|
188
|
+
```yaml
|
|
189
|
+
- run: prompteval run --prompt /tmp/baseline.txt --tag baseline
|
|
190
|
+
- run: prompteval run --prompt evals/prompts/v1.txt --tag candidate
|
|
191
|
+
- run: prompteval compare baseline candidate --html report.html --fail-on cost+10%,quality-5%
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Any future prompt change gets gated. PR fails if cost regresses >10% or any
|
|
195
|
+
scorer drops >5 percentage points (only statistically significant breaches
|
|
196
|
+
trip the gate). The `--html` report is uploaded as a build artifact and
|
|
197
|
+
linked from the PR's Checks tab.
|
|
198
|
+
|
|
199
|
+
See [`examples/github-actions/README.md`](examples/github-actions/README.md)
|
|
200
|
+
for setup steps, required secrets, and how to tune the gate.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Status, scope, and roadmap
|
|
205
|
+
|
|
206
|
+
Living source of truth lives in **[IMPLEMENTATION_PLAN.md](IMPLEMENTATION_PLAN.md)** —
|
|
207
|
+
week-by-week status table, decisions log, explicit non-goals, and post-v1 roadmap.
|
|
208
|
+
That file is updated the same commit any code ships; this README only summarises.
|
|
209
|
+
|
|
210
|
+
**As of 31 Aug 2026 — the tool works end to end.** Everything needed to run a
|
|
211
|
+
comparison has shipped and is tested: `init`, `run`, `compare`, `models` and
|
|
212
|
+
`scorer` CLIs; the cost model; 9 stock scorers plus `llm_judge`; paired deltas
|
|
213
|
+
with bootstrap CIs and significance; plain-English verdicts; the HTML report;
|
|
214
|
+
and the `--fail-on` CI gate with a GitHub Actions template. **210/210 tests
|
|
215
|
+
green.**
|
|
216
|
+
|
|
217
|
+
**What is left is packaging, not capability:** a scorer-writing guide, an API
|
|
218
|
+
stability sweep, and the first PyPI release (as `prompteval-delta`). Until that
|
|
219
|
+
release is live, install from source — and treat the public API as still
|
|
220
|
+
movable.
|
|
221
|
+
|
|
222
|
+
Original plan was 10 weeks with a 2026-08-16 review point. That date has passed
|
|
223
|
+
with the build ahead of where it needed to be, so the project continues; the
|
|
224
|
+
week-by-week table in the plan is the live record.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Development
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
# Install (creates a venv via uv, pulls Python 3.12)
|
|
232
|
+
uv sync
|
|
233
|
+
|
|
234
|
+
# Full quality gate
|
|
235
|
+
uv run ruff check . # lint
|
|
236
|
+
uv run ruff format --check . # format
|
|
237
|
+
uv run mypy src tests # types (strict)
|
|
238
|
+
uv run pytest # tests
|
|
239
|
+
uv run prompteval --version # CLI smoke
|
|
240
|
+
|
|
241
|
+
# One-liner
|
|
242
|
+
uv run pytest && uv run mypy src tests && uv run ruff check .
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## License
|
|
246
|
+
|
|
247
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# prompteval
|
|
2
|
+
|
|
3
|
+
**Paired cost-and-quality delta reports for LLM prompts — so you can ship the cheaper version without guessing.**
|
|
4
|
+
|
|
5
|
+
> _Did your prompt change save money without losing quality?_
|
|
6
|
+
|
|
7
|
+
prompteval is **pytest for LLM prompts**. You define a small golden test set and one or more scorers. You run it against your v1 prompt, then your v2 prompt, then ask it to compare. You get back a single report:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
v1 v2 Δ 95% CI p
|
|
11
|
+
─────────────────────────────────────────────────────────────────────────────
|
|
12
|
+
mentions_required_terms 0.84 0.79 −0.05 [−0.13, +0.03] 0.21
|
|
13
|
+
professional_tone 0.81 0.83 +0.02 [−0.05, +0.09] 0.58
|
|
14
|
+
─────────────────────────────────────────────────────────────────────────────
|
|
15
|
+
total cost $0.158 $0.099 −37% [−42%, −32%] <0.001
|
|
16
|
+
avg latency 1.4s 1.1s −21% [−28%, −14%] <0.001
|
|
17
|
+
|
|
18
|
+
Quality verdict: no significant regression
|
|
19
|
+
Cost verdict: significant 37% reduction
|
|
20
|
+
Recommendation: ship v2 — cost savings real, quality holds
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Existing eval tools (Braintrust, Langfuse, promptfoo, Phoenix) track cost as a metric you graph; prompteval treats it as a **comparison axis** alongside quality. See [docs/competitor-scan.md](docs/competitor-scan.md) for verified evidence.
|
|
24
|
+
|
|
25
|
+
> ⚠️ **Alpha — v0.1.0.** The PyPI package is **`prompteval-delta`** (first
|
|
26
|
+
> release pending; until then, install from source). **Do not
|
|
27
|
+
> `pip install prompteval`** — that name on PyPI belongs to an unrelated
|
|
28
|
+
> project. The import and the command are still `prompteval`. The public API
|
|
29
|
+
> may still change before 1.0. Stars and issues welcome.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## What prompteval is, and is not
|
|
34
|
+
|
|
35
|
+
| ✅ It is | ❌ It is not |
|
|
36
|
+
|---|---|
|
|
37
|
+
| An **offline** eval framework (think pytest for prompts) | A runtime proxy or LLM gateway |
|
|
38
|
+
| Run from your CLI or your CI | Sitting in your prod request path |
|
|
39
|
+
| Compares two prompt versions on quality **and** cost | Caching prod LLM calls |
|
|
40
|
+
| Statistical significance built in | Replacing Helicone / Portkey / LiteLLM |
|
|
41
|
+
| Honest about scorer methodology | A black-box "AI judges your AI" |
|
|
42
|
+
|
|
43
|
+
**Closest neighbors:** pytest, [Braintrust Experiments](https://braintrust.dev/), [promptfoo](https://promptfoo.dev/), [Inspect AI](https://inspect.aisi.org.uk/).
|
|
44
|
+
|
|
45
|
+
**Not what we do:** [Helicone](https://helicone.ai/), [Portkey](https://portkey.ai/), [LiteLLM](https://github.com/BerriAI/litellm), [LangChain](https://langchain.com/).
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## User journey (v1)
|
|
50
|
+
|
|
51
|
+
Eight steps. ~30 minutes the first time. ~5 minutes every time after.
|
|
52
|
+
|
|
53
|
+
### 1. Install
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
uv add prompteval-delta # or: pip install prompteval-delta
|
|
57
|
+
prompteval --version
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 2. Bootstrap an evals folder
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
cd ~/my-app
|
|
64
|
+
prompteval init
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Creates:
|
|
68
|
+
```
|
|
69
|
+
evals/
|
|
70
|
+
├── prompts/
|
|
71
|
+
│ ├── v1.txt # paste your current prompt
|
|
72
|
+
│ └── v2.txt # paste the variant you want to test
|
|
73
|
+
├── dataset.jsonl # one example pre-filled
|
|
74
|
+
├── eval.py # pre-filled with 3 scorer examples
|
|
75
|
+
└── .env.example # OPENAI_API_KEY=...
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 3. Bring your prompts + a few examples
|
|
79
|
+
|
|
80
|
+
Paste prompts into `prompts/v1.txt` and `prompts/v2.txt`. Add 10–30 examples to `dataset.jsonl`:
|
|
81
|
+
|
|
82
|
+
```jsonl
|
|
83
|
+
{"id": "refund-1", "input": "My item arrived broken, refund please", "expected": {"must_mention": ["30-day refund"], "tone": "empathetic"}}
|
|
84
|
+
{"id": "refund-2", "input": "Refund request 45 days after purchase", "expected": {"must_mention": ["30-day", "outside policy"], "tone": "polite"}}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The `expected` field is whatever your scorers want — there's no fixed schema.
|
|
88
|
+
|
|
89
|
+
### 4. Define what "good" means for your use case (scorers)
|
|
90
|
+
|
|
91
|
+
`eval.py` ships with three example scorers. Edit them, write new ones, or copy from templates:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from prompteval import Eval, scorer, llm_judge
|
|
95
|
+
|
|
96
|
+
@scorer
|
|
97
|
+
def mentions_required_terms(output: str, expected: dict) -> float:
|
|
98
|
+
"""Hard scorer: did the reply contain required phrases?"""
|
|
99
|
+
must = expected.get("must_mention", [])
|
|
100
|
+
if not must:
|
|
101
|
+
return 1.0
|
|
102
|
+
return sum(1 for t in must if t.lower() in output.lower()) / len(must)
|
|
103
|
+
|
|
104
|
+
@scorer
|
|
105
|
+
def professional_tone(output: str, expected: dict) -> float:
|
|
106
|
+
"""LLM-as-judge: rate professionalism 0-1."""
|
|
107
|
+
return llm_judge(
|
|
108
|
+
rubric="Rate the tone of this support reply 0-1. 1=empathetic+professional, 0=rude.",
|
|
109
|
+
text=output,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
eval = Eval(
|
|
113
|
+
name="support-assistant",
|
|
114
|
+
dataset="dataset.jsonl",
|
|
115
|
+
scorers=[mentions_required_terms, professional_tone],
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Don't know where to start? `prompteval scorer list` shows 6–8 templates (regex match, JSON-schema valid, contains-substring, exact-match, LLM-judge factuality, LLM-judge tone). `prompteval scorer copy <name>` drops one into your `eval.py`.
|
|
120
|
+
|
|
121
|
+
> **Honest caveat that matters:** prompteval doesn't define quality for your business — you do, by writing scorers. Get this part right and the tool earns its keep. Get it wrong and the tool will tell you wrong things confidently. **Every report shows which scorers ran on how many examples — methodology is always visible.** See [docs/writing-scorers.md](docs/writing-scorers.md) (coming v0.1) for guidance.
|
|
122
|
+
|
|
123
|
+
### 5. Run baseline (v1)
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
prompteval run --prompt prompts/v1.txt --tag baseline
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
Running 20 examples × 1 prompt against gpt-4o...
|
|
131
|
+
refund-1 ✓ mentions_required_terms=1.00 professional_tone=0.85
|
|
132
|
+
refund-2 ✓ mentions_required_terms=0.50 professional_tone=0.90
|
|
133
|
+
...
|
|
134
|
+
=== baseline ===
|
|
135
|
+
mentions_required_terms: 0.84 (n=20)
|
|
136
|
+
professional_tone: 0.81 (n=20)
|
|
137
|
+
total cost: $0.158 (avg $0.0079/call)
|
|
138
|
+
avg latency: 1.4s
|
|
139
|
+
saved to: .prompteval/runs/baseline-2026-06-26T1240.json
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 6. Run the variant (v2)
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
prompteval run --prompt prompts/v2.txt --tag short-prompt
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 7. Compare — the moment of truth
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
prompteval compare baseline short-prompt
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
You get the report shown at the top of this README — paired quality and cost deltas with 95% CIs, p-values, and a plain-English recommendation.
|
|
155
|
+
|
|
156
|
+
### 8. (Optional but the moat) Lock it into CI
|
|
157
|
+
|
|
158
|
+
A copy-and-paste GitHub Actions workflow lives at [`examples/github-actions/prompteval.yml`](examples/github-actions/prompteval.yml). The core of it:
|
|
159
|
+
|
|
160
|
+
```yaml
|
|
161
|
+
- run: prompteval run --prompt /tmp/baseline.txt --tag baseline
|
|
162
|
+
- run: prompteval run --prompt evals/prompts/v1.txt --tag candidate
|
|
163
|
+
- run: prompteval compare baseline candidate --html report.html --fail-on cost+10%,quality-5%
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Any future prompt change gets gated. PR fails if cost regresses >10% or any
|
|
167
|
+
scorer drops >5 percentage points (only statistically significant breaches
|
|
168
|
+
trip the gate). The `--html` report is uploaded as a build artifact and
|
|
169
|
+
linked from the PR's Checks tab.
|
|
170
|
+
|
|
171
|
+
See [`examples/github-actions/README.md`](examples/github-actions/README.md)
|
|
172
|
+
for setup steps, required secrets, and how to tune the gate.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Status, scope, and roadmap
|
|
177
|
+
|
|
178
|
+
Living source of truth lives in **[IMPLEMENTATION_PLAN.md](IMPLEMENTATION_PLAN.md)** —
|
|
179
|
+
week-by-week status table, decisions log, explicit non-goals, and post-v1 roadmap.
|
|
180
|
+
That file is updated the same commit any code ships; this README only summarises.
|
|
181
|
+
|
|
182
|
+
**As of 31 Aug 2026 — the tool works end to end.** Everything needed to run a
|
|
183
|
+
comparison has shipped and is tested: `init`, `run`, `compare`, `models` and
|
|
184
|
+
`scorer` CLIs; the cost model; 9 stock scorers plus `llm_judge`; paired deltas
|
|
185
|
+
with bootstrap CIs and significance; plain-English verdicts; the HTML report;
|
|
186
|
+
and the `--fail-on` CI gate with a GitHub Actions template. **210/210 tests
|
|
187
|
+
green.**
|
|
188
|
+
|
|
189
|
+
**What is left is packaging, not capability:** a scorer-writing guide, an API
|
|
190
|
+
stability sweep, and the first PyPI release (as `prompteval-delta`). Until that
|
|
191
|
+
release is live, install from source — and treat the public API as still
|
|
192
|
+
movable.
|
|
193
|
+
|
|
194
|
+
Original plan was 10 weeks with a 2026-08-16 review point. That date has passed
|
|
195
|
+
with the build ahead of where it needed to be, so the project continues; the
|
|
196
|
+
week-by-week table in the plan is the live record.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Development
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
# Install (creates a venv via uv, pulls Python 3.12)
|
|
204
|
+
uv sync
|
|
205
|
+
|
|
206
|
+
# Full quality gate
|
|
207
|
+
uv run ruff check . # lint
|
|
208
|
+
uv run ruff format --check . # format
|
|
209
|
+
uv run mypy src tests # types (strict)
|
|
210
|
+
uv run pytest # tests
|
|
211
|
+
uv run prompteval --version # CLI smoke
|
|
212
|
+
|
|
213
|
+
# One-liner
|
|
214
|
+
uv run pytest && uv run mypy src tests && uv run ruff check .
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## License
|
|
218
|
+
|
|
219
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "prompteval-delta"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "LLM eval framework with first-class token-cost tracking. Did your prompt change save money without losing quality?"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
keywords = [
|
|
8
|
+
"llm",
|
|
9
|
+
"evaluation",
|
|
10
|
+
"evals",
|
|
11
|
+
"prompt-engineering",
|
|
12
|
+
"openai",
|
|
13
|
+
"gpt-4",
|
|
14
|
+
"cost-tracking",
|
|
15
|
+
"token-cost",
|
|
16
|
+
"ai-quality",
|
|
17
|
+
"regression-detection",
|
|
18
|
+
]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Development Status :: 3 - Alpha",
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
"License :: OSI Approved :: MIT License",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
26
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
27
|
+
"Topic :: Software Development :: Testing",
|
|
28
|
+
]
|
|
29
|
+
dependencies = [
|
|
30
|
+
"openai>=1.50.0",
|
|
31
|
+
"click>=8.1.7",
|
|
32
|
+
"pydantic>=2.9.0",
|
|
33
|
+
"pyyaml>=6.0",
|
|
34
|
+
"numpy>=1.26",
|
|
35
|
+
"scipy>=1.13",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[project.license]
|
|
39
|
+
text = "MIT"
|
|
40
|
+
|
|
41
|
+
[[project.authors]]
|
|
42
|
+
name = "Anuj Mahajan"
|
|
43
|
+
email = "mahajananuj07@gmail.com"
|
|
44
|
+
|
|
45
|
+
[project.urls]
|
|
46
|
+
Homepage = "https://github.com/mahAnuj/prompteval"
|
|
47
|
+
Repository = "https://github.com/mahAnuj/prompteval"
|
|
48
|
+
Issues = "https://github.com/mahAnuj/prompteval/issues"
|
|
49
|
+
|
|
50
|
+
[project.scripts]
|
|
51
|
+
prompteval = "prompteval.cli:main"
|
|
52
|
+
|
|
53
|
+
[dependency-groups]
|
|
54
|
+
dev = [
|
|
55
|
+
"mypy>=1.11.0",
|
|
56
|
+
"pytest>=8.3.0",
|
|
57
|
+
"pytest-asyncio>=0.24.0",
|
|
58
|
+
"ruff>=0.6.9",
|
|
59
|
+
"types-pyyaml>=6.0",
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
[build-system]
|
|
63
|
+
requires = ["uv_build>=0.11.7,<0.12.0"]
|
|
64
|
+
build-backend = "uv_build"
|
|
65
|
+
|
|
66
|
+
[tool.uv.build-backend]
|
|
67
|
+
module-name = "prompteval"
|
|
68
|
+
|
|
69
|
+
[tool.ruff]
|
|
70
|
+
line-length = 100
|
|
71
|
+
target-version = "py312"
|
|
72
|
+
extend-exclude = ["src/prompteval/init/templates"]
|
|
73
|
+
|
|
74
|
+
[tool.ruff.lint]
|
|
75
|
+
select = [
|
|
76
|
+
"E",
|
|
77
|
+
"F",
|
|
78
|
+
"I",
|
|
79
|
+
"B",
|
|
80
|
+
"UP",
|
|
81
|
+
"SIM",
|
|
82
|
+
"RUF",
|
|
83
|
+
]
|
|
84
|
+
ignore = []
|
|
85
|
+
|
|
86
|
+
[tool.ruff.lint.per-file-ignores]
|
|
87
|
+
"tests/**/*" = ["S101"]
|
|
88
|
+
|
|
89
|
+
[tool.mypy]
|
|
90
|
+
python_version = "3.12"
|
|
91
|
+
strict = true
|
|
92
|
+
warn_unreachable = true
|
|
93
|
+
pretty = true
|
|
94
|
+
show_error_codes = true
|
|
95
|
+
exclude = ["src/prompteval/init/templates/.*"]
|
|
96
|
+
|
|
97
|
+
[tool.pytest.ini_options]
|
|
98
|
+
testpaths = ["tests"]
|
|
99
|
+
addopts = [
|
|
100
|
+
"-ra",
|
|
101
|
+
"--strict-markers",
|
|
102
|
+
"--strict-config",
|
|
103
|
+
]
|
|
104
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
# Distribution name. "prompteval" on PyPI belongs to an unrelated project, so
|
|
3
|
+
# this one installs as prompteval-delta; the import and the CLI stay
|
|
4
|
+
# `prompteval` (module-name below).
|
|
5
|
+
name = "prompteval-delta"
|
|
6
|
+
version = "0.1.0"
|
|
7
|
+
description = "LLM eval framework with first-class token-cost tracking. Did your prompt change save money without losing quality?"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
license = { text = "MIT" }
|
|
10
|
+
authors = [{ name = "Anuj Mahajan", email = "mahajananuj07@gmail.com" }]
|
|
11
|
+
requires-python = ">=3.12"
|
|
12
|
+
keywords = [
|
|
13
|
+
"llm",
|
|
14
|
+
"evaluation",
|
|
15
|
+
"evals",
|
|
16
|
+
"prompt-engineering",
|
|
17
|
+
"openai",
|
|
18
|
+
"gpt-4",
|
|
19
|
+
"cost-tracking",
|
|
20
|
+
"token-cost",
|
|
21
|
+
"ai-quality",
|
|
22
|
+
"regression-detection",
|
|
23
|
+
]
|
|
24
|
+
classifiers = [
|
|
25
|
+
"Development Status :: 3 - Alpha",
|
|
26
|
+
"Intended Audience :: Developers",
|
|
27
|
+
"License :: OSI Approved :: MIT License",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
30
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
31
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
32
|
+
"Topic :: Software Development :: Testing",
|
|
33
|
+
]
|
|
34
|
+
dependencies = [
|
|
35
|
+
"openai>=1.50.0",
|
|
36
|
+
"click>=8.1.7",
|
|
37
|
+
"pydantic>=2.9.0",
|
|
38
|
+
"pyyaml>=6.0",
|
|
39
|
+
"numpy>=1.26",
|
|
40
|
+
"scipy>=1.13",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.urls]
|
|
44
|
+
Homepage = "https://github.com/mahAnuj/prompteval"
|
|
45
|
+
Repository = "https://github.com/mahAnuj/prompteval"
|
|
46
|
+
Issues = "https://github.com/mahAnuj/prompteval/issues"
|
|
47
|
+
|
|
48
|
+
[project.scripts]
|
|
49
|
+
prompteval = "prompteval.cli:main"
|
|
50
|
+
|
|
51
|
+
[dependency-groups]
|
|
52
|
+
dev = [
|
|
53
|
+
"mypy>=1.11.0",
|
|
54
|
+
"pytest>=8.3.0",
|
|
55
|
+
"pytest-asyncio>=0.24.0",
|
|
56
|
+
"ruff>=0.6.9",
|
|
57
|
+
"types-pyyaml>=6.0",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
[build-system]
|
|
61
|
+
requires = ["uv_build>=0.11.7,<0.12.0"]
|
|
62
|
+
build-backend = "uv_build"
|
|
63
|
+
|
|
64
|
+
[tool.uv.build-backend]
|
|
65
|
+
# uv_build would otherwise look for a `prompteval_delta` module.
|
|
66
|
+
module-name = "prompteval"
|
|
67
|
+
|
|
68
|
+
# ---------- ruff ----------
|
|
69
|
+
[tool.ruff]
|
|
70
|
+
line-length = 100
|
|
71
|
+
target-version = "py312"
|
|
72
|
+
# Templates ship as resource files copied verbatim by `prompteval init`.
|
|
73
|
+
# They reference v0.1 public API that doesn't exist yet (Week 3-4 work),
|
|
74
|
+
# so linting them creates false positives. Treated as data, not code.
|
|
75
|
+
extend-exclude = ["src/prompteval/init/templates"]
|
|
76
|
+
|
|
77
|
+
[tool.ruff.lint]
|
|
78
|
+
# Start strict; prune later only if a rule is more noise than value.
|
|
79
|
+
select = [
|
|
80
|
+
"E", # pycodestyle errors
|
|
81
|
+
"F", # pyflakes
|
|
82
|
+
"I", # isort (import order)
|
|
83
|
+
"B", # bugbear (likely bugs)
|
|
84
|
+
"UP", # pyupgrade (modern syntax)
|
|
85
|
+
"SIM", # simplify
|
|
86
|
+
"RUF", # ruff-specific
|
|
87
|
+
]
|
|
88
|
+
ignore = []
|
|
89
|
+
|
|
90
|
+
[tool.ruff.lint.per-file-ignores]
|
|
91
|
+
"tests/**/*" = ["S101"] # `assert` is fine in tests
|
|
92
|
+
|
|
93
|
+
# ---------- mypy ----------
|
|
94
|
+
[tool.mypy]
|
|
95
|
+
python_version = "3.12"
|
|
96
|
+
strict = true
|
|
97
|
+
warn_unreachable = true
|
|
98
|
+
pretty = true
|
|
99
|
+
show_error_codes = true
|
|
100
|
+
exclude = ["src/prompteval/init/templates/.*"]
|
|
101
|
+
|
|
102
|
+
# ---------- pytest ----------
|
|
103
|
+
[tool.pytest.ini_options]
|
|
104
|
+
testpaths = ["tests"]
|
|
105
|
+
addopts = ["-ra", "--strict-markers", "--strict-config"]
|
|
106
|
+
asyncio_mode = "auto"
|