hemp-eval 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.
- hemp_eval-0.1.0/LICENSE +21 -0
- hemp_eval-0.1.0/PKG-INFO +233 -0
- hemp_eval-0.1.0/README.md +198 -0
- hemp_eval-0.1.0/pyproject.toml +93 -0
- hemp_eval-0.1.0/src/hemp/__init__.py +32 -0
- hemp_eval-0.1.0/src/hemp/_version.py +6 -0
- hemp_eval-0.1.0/src/hemp/calibrate/__init__.py +24 -0
- hemp_eval-0.1.0/src/hemp/calibrate/agreement.py +33 -0
- hemp_eval-0.1.0/src/hemp/calibrate/labels.py +143 -0
- hemp_eval-0.1.0/src/hemp/calibrate/optimize.py +213 -0
- hemp_eval-0.1.0/src/hemp/calibrate/render.py +87 -0
- hemp_eval-0.1.0/src/hemp/cli.py +649 -0
- hemp_eval-0.1.0/src/hemp/config.py +38 -0
- hemp_eval-0.1.0/src/hemp/dataset.py +92 -0
- hemp_eval-0.1.0/src/hemp/doctor.py +189 -0
- hemp_eval-0.1.0/src/hemp/hashing.py +14 -0
- hemp_eval-0.1.0/src/hemp/images.py +209 -0
- hemp_eval-0.1.0/src/hemp/metrics/__init__.py +31 -0
- hemp_eval-0.1.0/src/hemp/metrics/embeddings.py +72 -0
- hemp_eval-0.1.0/src/hemp/metrics/format.py +137 -0
- hemp_eval-0.1.0/src/hemp/metrics/judge.py +178 -0
- hemp_eval-0.1.0/src/hemp/metrics/plugins.py +73 -0
- hemp_eval-0.1.0/src/hemp/metrics/reference.py +153 -0
- hemp_eval-0.1.0/src/hemp/metrics/registry.py +205 -0
- hemp_eval-0.1.0/src/hemp/metrics/text.py +146 -0
- hemp_eval-0.1.0/src/hemp/plan/__init__.py +24 -0
- hemp_eval-0.1.0/src/hemp/plan/defaults.py +125 -0
- hemp_eval-0.1.0/src/hemp/plan/io.py +349 -0
- hemp_eval-0.1.0/src/hemp/plan/planner.py +312 -0
- hemp_eval-0.1.0/src/hemp/plan/render.py +48 -0
- hemp_eval-0.1.0/src/hemp/plan/schema.py +110 -0
- hemp_eval-0.1.0/src/hemp/plan/validate.py +39 -0
- hemp_eval-0.1.0/src/hemp/profile/__init__.py +25 -0
- hemp_eval-0.1.0/src/hemp/profile/infer.py +113 -0
- hemp_eval-0.1.0/src/hemp/profile/profiler.py +185 -0
- hemp_eval-0.1.0/src/hemp/profile/render.py +94 -0
- hemp_eval-0.1.0/src/hemp/profile/roles.py +115 -0
- hemp_eval-0.1.0/src/hemp/profile/schema.py +94 -0
- hemp_eval-0.1.0/src/hemp/prompt.py +21 -0
- hemp_eval-0.1.0/src/hemp/py.typed +0 -0
- hemp_eval-0.1.0/src/hemp/runner/__init__.py +30 -0
- hemp_eval-0.1.0/src/hemp/runner/baselines.py +62 -0
- hemp_eval-0.1.0/src/hemp/runner/evaluate.py +45 -0
- hemp_eval-0.1.0/src/hemp/runner/judging.py +117 -0
- hemp_eval-0.1.0/src/hemp/runner/parallel.py +37 -0
- hemp_eval-0.1.0/src/hemp/runner/render.py +135 -0
- hemp_eval-0.1.0/src/hemp/runner/results.py +119 -0
- hemp_eval-0.1.0/src/hemp/runner/run.py +261 -0
- hemp_eval-0.1.0/src/hemp/runner/target.py +231 -0
- hemp_eval-0.1.0/src/hemp/store/__init__.py +14 -0
- hemp_eval-0.1.0/src/hemp/store/db.py +392 -0
- hemp_eval-0.1.0/src/hemp/store/diff.py +172 -0
- hemp_eval-0.1.0/src/hemp/store/render.py +160 -0
hemp_eval-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nithilan
|
|
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
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do 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.
|
hemp_eval-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hemp-eval
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Help Evaluate My Prompt: work out how to test an LLM prompt from the prompt and your data, run it, and compare prompt versions.
|
|
5
|
+
Keywords: llm,evaluation,prompt,eval,dspy,llm-as-a-judge,prompt-engineering
|
|
6
|
+
Author: NithilanVishvanath
|
|
7
|
+
Author-email: NithilanVishvanath <nithilanvishvanath@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
16
|
+
Classifier: Topic :: Software Development :: Testing
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Requires-Dist: dspy>=3.4.1
|
|
23
|
+
Requires-Dist: litellm>=1.106.6
|
|
24
|
+
Requires-Dist: pydantic>=2.6
|
|
25
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
26
|
+
Requires-Dist: rich>=13.7
|
|
27
|
+
Requires-Dist: typer>=0.12
|
|
28
|
+
Requires-Dist: pillow>=10 ; extra == 'images'
|
|
29
|
+
Requires-Python: >=3.11
|
|
30
|
+
Project-URL: Homepage, https://github.com/NithilanVishvanath/hemp
|
|
31
|
+
Project-URL: Repository, https://github.com/NithilanVishvanath/hemp
|
|
32
|
+
Project-URL: Issues, https://github.com/NithilanVishvanath/hemp/issues
|
|
33
|
+
Provides-Extra: images
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# HEMP: Help Evaluate My Prompt
|
|
37
|
+
|
|
38
|
+
You have a prompt and some example data. HEMP works out **how that prompt should be tested**, runs the test, and tells you whether a new version of the prompt is better or worse, down to the examples that got fixed or broke.
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
hemp plan prompt.txt data.jsonl # decide how to test the prompt (you review the plan)
|
|
42
|
+
hemp run prompt.txt data.jsonl # run it, score it, compare with simple baselines
|
|
43
|
+
hemp diff # after editing the prompt: what got better, what broke
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- **It reads your data, not just your prompt.** An answer column with labels gets an exact-match check, JSON answers get field-by-field checks, free text gets meaning-based checks.
|
|
47
|
+
- **Yes/no questions for an LLM judge** cover what code can't check ("Is the answer polite?"). No 1–10 scores.
|
|
48
|
+
- **You can check the judge.** Label a few outputs yourself and HEMP reports how often the judge agrees with you, and tunes it to agree more.
|
|
49
|
+
- **Baselines give scores context.** "Always answer the most common label" and "no instructions at all" run alongside your prompt.
|
|
50
|
+
- **Local and private by default.** It runs on your machine with Ollama, or with any model LiteLLM supports (OpenAI, Gemini, Anthropic, ...). There's no account and no server.
|
|
51
|
+
- **Text and images.** A column of image paths is sent to the model as real images.
|
|
52
|
+
|
|
53
|
+
## Install
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
pip install hemp-eval
|
|
57
|
+
pip install "hemp-eval[images]" # also scale down large images before sending them
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The command is `hemp` and the Python package is `hemp`. Python 3.11+ is required.
|
|
61
|
+
|
|
62
|
+
You also need a model. Either:
|
|
63
|
+
|
|
64
|
+
- **Local (default):** install [Ollama](https://ollama.com), then `ollama pull qwen2.5:7b` and `ollama pull nomic-embed-text`. Keep Ollama running.
|
|
65
|
+
- **Hosted:** set `HEMP_MODEL` to a [LiteLLM model name](https://docs.litellm.ai/docs/providers) and its API key, e.g. `HEMP_MODEL=openai/gpt-4o-mini` with `OPENAI_API_KEY`, or `HEMP_MODEL=gemini/gemini-2.0-flash` with `GEMINI_API_KEY`.
|
|
66
|
+
|
|
67
|
+
Then check your setup with `hemp doctor`. It confirms each model answers, and says exactly what to fix if one doesn't. Use `--offline` to check without making any calls.
|
|
68
|
+
|
|
69
|
+
## Quick start
|
|
70
|
+
|
|
71
|
+
**1. Write your prompt** with `{column}` where each row's data goes, in `prompt.txt`:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
Classify the sentiment of this product review as exactly one of: positive, negative, neutral.
|
|
75
|
+
Reply with the label only.
|
|
76
|
+
|
|
77
|
+
Review: {review}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**2. Collect examples** in `data.jsonl` (CSV and JSON work too), ideally with the right answer. 20–100 rows is a good start.
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
{"review": "Great product, arrived early.", "label": "positive"}
|
|
84
|
+
{"review": "Broke after two days.", "label": "negative"}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**3. Plan.** Run `hemp plan prompt.txt data.jsonl`. HEMP profiles the data, asks the planner model how to test the prompt, and writes `prompt.plan.yaml`:
|
|
88
|
+
|
|
89
|
+
```yaml
|
|
90
|
+
columns:
|
|
91
|
+
input: [review]
|
|
92
|
+
answer: label
|
|
93
|
+
|
|
94
|
+
checks: # automatic, no LLM needed
|
|
95
|
+
- check: allowed_values
|
|
96
|
+
values: [negative, positive, neutral]
|
|
97
|
+
- check: exact_match
|
|
98
|
+
compare_to: label
|
|
99
|
+
|
|
100
|
+
questions: # an LLM judge answers each; "yes" = pass
|
|
101
|
+
- id: q1
|
|
102
|
+
ask: Does the label fit the review's overall tone?
|
|
103
|
+
|
|
104
|
+
baselines: # simple strategies your prompt should beat
|
|
105
|
+
- majority_class
|
|
106
|
+
- zero_shot
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Read it, and delete or add checks and questions as you like. `hemp validate prompt.plan.yaml` checks your edits, and `hemp metrics` lists every available check.
|
|
110
|
+
|
|
111
|
+
**4. Run.** `hemp run prompt.txt data.jsonl` runs your prompt on every row exactly as written and scores it:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
| Check | Your prompt | majority_class (baseline) | zero_shot (baseline) |
|
|
115
|
+
| allowed_values | 100% (8/8) | 100% (8/8) | 0% (0/8) |
|
|
116
|
+
| exact_match_label | 100% (8/8) | 38% (3/8) | 0% (0/8) |
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Then it lists the examples your prompt got wrong. Results are kept in `.hemp/hemp.db`; `hemp show` lists past runs.
|
|
120
|
+
|
|
121
|
+
**5. Improve and compare.** Edit the prompt, run again, then `hemp diff`:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
Changed: prompt Same: plan, dataset, target model, judge model
|
|
125
|
+
| Check | Run #1 | Run #2 | Change | Fixed | Broke |
|
|
126
|
+
| exact_match_label | 75% | 100% | +25 pts | 3 | 0 |
|
|
127
|
+
Fixed (3): "It's ok" before: positive -> now: neutral
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Baselines are reused between runs when nothing they depend on changed, so each round only pays for your prompt.
|
|
131
|
+
|
|
132
|
+
## Images
|
|
133
|
+
|
|
134
|
+
A column of image paths (`.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.bmp`), image URLs, or `data:image/...` URIs is an image column. Use it in the prompt like any other column:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
Classify this photo as cat, dog or bird. Reply with the label only.
|
|
138
|
+
{image}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
image,label
|
|
143
|
+
photos/cat_01.jpg,cat
|
|
144
|
+
photos/dog_01.jpg,dog
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
- **What the model gets:** the image itself, at the spot where `{image}` is, not the path.
|
|
148
|
+
- **Paths:** relative paths are looked up next to the data file.
|
|
149
|
+
- **Size:** with `hemp-eval[images]`, images bigger than 1536 px are scaled down first. Set the limit with `HEMP_IMAGE_MAX_SIDE`; `0` turns it off.
|
|
150
|
+
- **Baselines and judge:** the zero_shot baseline sends just the image, and the judge sees the images too.
|
|
151
|
+
- **Model:** use one that can see images, such as a Gemini or GPT-4o model, or a local `ollama_chat/gemma3:4b`.
|
|
152
|
+
|
|
153
|
+
## Check and tune the judge
|
|
154
|
+
|
|
155
|
+
Judge scores are only useful if the judge answers the way you would. After a run with questions:
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
hemp calibrate prompt.plan.yaml
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
HEMP shows outputs one at a time, with the judge's answer hidden. You answer each question yourself: `y`, `n`, `s` to skip, `q` to stop. It then reports how often the judge agrees with you, as accuracy and Cohen's kappa (which corrects for lucky agreement), per question.
|
|
162
|
+
|
|
163
|
+
With 8 or more labels, HEMP also tunes the judge with [DSPy](https://dspy.ai): it picks worked examples from half your labels and tests on the other half. If the tuned judge agrees with you more, it's saved next to the plan and `hemp run` uses it from then on.
|
|
164
|
+
|
|
165
|
+
## Commands
|
|
166
|
+
|
|
167
|
+
| Command | What it does |
|
|
168
|
+
|---|---|
|
|
169
|
+
| `hemp doctor` | Check that your models are reachable, with a fix for each problem. |
|
|
170
|
+
| `hemp profile data.jsonl` | Column types and roles (input, answer, id) and value stats. No model calls. |
|
|
171
|
+
| `hemp plan prompt.txt data.jsonl` | Write an editable evaluation plan. `--no-llm` uses rules only. |
|
|
172
|
+
| `hemp validate plan.yaml` | Check an edited plan. |
|
|
173
|
+
| `hemp metrics` | List the checks a plan can use and their settings. |
|
|
174
|
+
| `hemp run prompt.txt data.jsonl` | Run and score. Useful options: `--target-model`, `--judge-model`, `--no-judge`, `--no-baselines`. |
|
|
175
|
+
| `hemp show [run]` | List runs, or show one with its failed examples. |
|
|
176
|
+
| `hemp diff [A] [B]` | Compare two runs (default: the latest two). `--fail-on-regression` for CI. |
|
|
177
|
+
| `hemp calibrate plan.yaml` | Label outputs, measure and tune the judge. |
|
|
178
|
+
|
|
179
|
+
## Models
|
|
180
|
+
|
|
181
|
+
| Used for | Default | Change with |
|
|
182
|
+
|---|---|---|
|
|
183
|
+
| Planning, running your prompt, judging | `ollama_chat/qwen2.5:7b` | `HEMP_MODEL` for all three, or `HEMP_PLANNER_MODEL`, `HEMP_TARGET_MODEL`, `HEMP_JUDGE_MODEL` |
|
|
184
|
+
| Comparing free-text answers by meaning | `ollama/nomic-embed-text` | `HEMP_EMBED_MODEL` |
|
|
185
|
+
|
|
186
|
+
Any [LiteLLM model name](https://docs.litellm.ai/docs/providers) works. A judge at least as strong as the model being tested gives better scores.
|
|
187
|
+
|
|
188
|
+
## Your own checks
|
|
189
|
+
|
|
190
|
+
Write a check in a Python file:
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
# my_metrics.py
|
|
194
|
+
import hemp
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@hemp.metric("polite")
|
|
198
|
+
def polite(ctx: hemp.MetricContext, params: hemp.MetricParams) -> hemp.MetricResult:
|
|
199
|
+
"""The output says please or thanks."""
|
|
200
|
+
text = ctx.output.lower()
|
|
201
|
+
return hemp.MetricResult.check("please" in text or "thank" in text)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Pass it with `--plugin my_metrics.py` to `hemp plan` or `hemp metrics`. The planner can choose it, and the plan remembers the file.
|
|
205
|
+
|
|
206
|
+
## Examples
|
|
207
|
+
|
|
208
|
+
The [examples folder](https://github.com/NithilanVishvanath/hemp/tree/main/examples) has ready-to-run projects:
|
|
209
|
+
|
|
210
|
+
| Example | What it shows |
|
|
211
|
+
|---|---|
|
|
212
|
+
| `sentiment` | Labels: exact match and baselines |
|
|
213
|
+
| `extraction` | JSON output checked field by field |
|
|
214
|
+
| `qa` | Free-text answers, a judge question, and a custom check |
|
|
215
|
+
| `colors` | Images: name the color of each swatch (needs a vision model) |
|
|
216
|
+
|
|
217
|
+
For example: `hemp plan examples/sentiment/prompt.txt examples/sentiment/data.jsonl`.
|
|
218
|
+
|
|
219
|
+
## Development
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
git clone https://github.com/NithilanVishvanath/hemp
|
|
223
|
+
cd hemp
|
|
224
|
+
uv sync
|
|
225
|
+
uv run pytest # unit tests, no models needed
|
|
226
|
+
uv run pytest -m integration # tests that call a real model
|
|
227
|
+
uv run ruff check . && uv run ruff format .
|
|
228
|
+
uv run mypy
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## License
|
|
232
|
+
|
|
233
|
+
MIT
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# HEMP: Help Evaluate My Prompt
|
|
2
|
+
|
|
3
|
+
You have a prompt and some example data. HEMP works out **how that prompt should be tested**, runs the test, and tells you whether a new version of the prompt is better or worse, down to the examples that got fixed or broke.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
hemp plan prompt.txt data.jsonl # decide how to test the prompt (you review the plan)
|
|
7
|
+
hemp run prompt.txt data.jsonl # run it, score it, compare with simple baselines
|
|
8
|
+
hemp diff # after editing the prompt: what got better, what broke
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
- **It reads your data, not just your prompt.** An answer column with labels gets an exact-match check, JSON answers get field-by-field checks, free text gets meaning-based checks.
|
|
12
|
+
- **Yes/no questions for an LLM judge** cover what code can't check ("Is the answer polite?"). No 1–10 scores.
|
|
13
|
+
- **You can check the judge.** Label a few outputs yourself and HEMP reports how often the judge agrees with you, and tunes it to agree more.
|
|
14
|
+
- **Baselines give scores context.** "Always answer the most common label" and "no instructions at all" run alongside your prompt.
|
|
15
|
+
- **Local and private by default.** It runs on your machine with Ollama, or with any model LiteLLM supports (OpenAI, Gemini, Anthropic, ...). There's no account and no server.
|
|
16
|
+
- **Text and images.** A column of image paths is sent to the model as real images.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
pip install hemp-eval
|
|
22
|
+
pip install "hemp-eval[images]" # also scale down large images before sending them
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The command is `hemp` and the Python package is `hemp`. Python 3.11+ is required.
|
|
26
|
+
|
|
27
|
+
You also need a model. Either:
|
|
28
|
+
|
|
29
|
+
- **Local (default):** install [Ollama](https://ollama.com), then `ollama pull qwen2.5:7b` and `ollama pull nomic-embed-text`. Keep Ollama running.
|
|
30
|
+
- **Hosted:** set `HEMP_MODEL` to a [LiteLLM model name](https://docs.litellm.ai/docs/providers) and its API key, e.g. `HEMP_MODEL=openai/gpt-4o-mini` with `OPENAI_API_KEY`, or `HEMP_MODEL=gemini/gemini-2.0-flash` with `GEMINI_API_KEY`.
|
|
31
|
+
|
|
32
|
+
Then check your setup with `hemp doctor`. It confirms each model answers, and says exactly what to fix if one doesn't. Use `--offline` to check without making any calls.
|
|
33
|
+
|
|
34
|
+
## Quick start
|
|
35
|
+
|
|
36
|
+
**1. Write your prompt** with `{column}` where each row's data goes, in `prompt.txt`:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
Classify the sentiment of this product review as exactly one of: positive, negative, neutral.
|
|
40
|
+
Reply with the label only.
|
|
41
|
+
|
|
42
|
+
Review: {review}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**2. Collect examples** in `data.jsonl` (CSV and JSON work too), ideally with the right answer. 20–100 rows is a good start.
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
{"review": "Great product, arrived early.", "label": "positive"}
|
|
49
|
+
{"review": "Broke after two days.", "label": "negative"}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**3. Plan.** Run `hemp plan prompt.txt data.jsonl`. HEMP profiles the data, asks the planner model how to test the prompt, and writes `prompt.plan.yaml`:
|
|
53
|
+
|
|
54
|
+
```yaml
|
|
55
|
+
columns:
|
|
56
|
+
input: [review]
|
|
57
|
+
answer: label
|
|
58
|
+
|
|
59
|
+
checks: # automatic, no LLM needed
|
|
60
|
+
- check: allowed_values
|
|
61
|
+
values: [negative, positive, neutral]
|
|
62
|
+
- check: exact_match
|
|
63
|
+
compare_to: label
|
|
64
|
+
|
|
65
|
+
questions: # an LLM judge answers each; "yes" = pass
|
|
66
|
+
- id: q1
|
|
67
|
+
ask: Does the label fit the review's overall tone?
|
|
68
|
+
|
|
69
|
+
baselines: # simple strategies your prompt should beat
|
|
70
|
+
- majority_class
|
|
71
|
+
- zero_shot
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Read it, and delete or add checks and questions as you like. `hemp validate prompt.plan.yaml` checks your edits, and `hemp metrics` lists every available check.
|
|
75
|
+
|
|
76
|
+
**4. Run.** `hemp run prompt.txt data.jsonl` runs your prompt on every row exactly as written and scores it:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
| Check | Your prompt | majority_class (baseline) | zero_shot (baseline) |
|
|
80
|
+
| allowed_values | 100% (8/8) | 100% (8/8) | 0% (0/8) |
|
|
81
|
+
| exact_match_label | 100% (8/8) | 38% (3/8) | 0% (0/8) |
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Then it lists the examples your prompt got wrong. Results are kept in `.hemp/hemp.db`; `hemp show` lists past runs.
|
|
85
|
+
|
|
86
|
+
**5. Improve and compare.** Edit the prompt, run again, then `hemp diff`:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
Changed: prompt Same: plan, dataset, target model, judge model
|
|
90
|
+
| Check | Run #1 | Run #2 | Change | Fixed | Broke |
|
|
91
|
+
| exact_match_label | 75% | 100% | +25 pts | 3 | 0 |
|
|
92
|
+
Fixed (3): "It's ok" before: positive -> now: neutral
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Baselines are reused between runs when nothing they depend on changed, so each round only pays for your prompt.
|
|
96
|
+
|
|
97
|
+
## Images
|
|
98
|
+
|
|
99
|
+
A column of image paths (`.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.bmp`), image URLs, or `data:image/...` URIs is an image column. Use it in the prompt like any other column:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
Classify this photo as cat, dog or bird. Reply with the label only.
|
|
103
|
+
{image}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
image,label
|
|
108
|
+
photos/cat_01.jpg,cat
|
|
109
|
+
photos/dog_01.jpg,dog
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
- **What the model gets:** the image itself, at the spot where `{image}` is, not the path.
|
|
113
|
+
- **Paths:** relative paths are looked up next to the data file.
|
|
114
|
+
- **Size:** with `hemp-eval[images]`, images bigger than 1536 px are scaled down first. Set the limit with `HEMP_IMAGE_MAX_SIDE`; `0` turns it off.
|
|
115
|
+
- **Baselines and judge:** the zero_shot baseline sends just the image, and the judge sees the images too.
|
|
116
|
+
- **Model:** use one that can see images, such as a Gemini or GPT-4o model, or a local `ollama_chat/gemma3:4b`.
|
|
117
|
+
|
|
118
|
+
## Check and tune the judge
|
|
119
|
+
|
|
120
|
+
Judge scores are only useful if the judge answers the way you would. After a run with questions:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
hemp calibrate prompt.plan.yaml
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
HEMP shows outputs one at a time, with the judge's answer hidden. You answer each question yourself: `y`, `n`, `s` to skip, `q` to stop. It then reports how often the judge agrees with you, as accuracy and Cohen's kappa (which corrects for lucky agreement), per question.
|
|
127
|
+
|
|
128
|
+
With 8 or more labels, HEMP also tunes the judge with [DSPy](https://dspy.ai): it picks worked examples from half your labels and tests on the other half. If the tuned judge agrees with you more, it's saved next to the plan and `hemp run` uses it from then on.
|
|
129
|
+
|
|
130
|
+
## Commands
|
|
131
|
+
|
|
132
|
+
| Command | What it does |
|
|
133
|
+
|---|---|
|
|
134
|
+
| `hemp doctor` | Check that your models are reachable, with a fix for each problem. |
|
|
135
|
+
| `hemp profile data.jsonl` | Column types and roles (input, answer, id) and value stats. No model calls. |
|
|
136
|
+
| `hemp plan prompt.txt data.jsonl` | Write an editable evaluation plan. `--no-llm` uses rules only. |
|
|
137
|
+
| `hemp validate plan.yaml` | Check an edited plan. |
|
|
138
|
+
| `hemp metrics` | List the checks a plan can use and their settings. |
|
|
139
|
+
| `hemp run prompt.txt data.jsonl` | Run and score. Useful options: `--target-model`, `--judge-model`, `--no-judge`, `--no-baselines`. |
|
|
140
|
+
| `hemp show [run]` | List runs, or show one with its failed examples. |
|
|
141
|
+
| `hemp diff [A] [B]` | Compare two runs (default: the latest two). `--fail-on-regression` for CI. |
|
|
142
|
+
| `hemp calibrate plan.yaml` | Label outputs, measure and tune the judge. |
|
|
143
|
+
|
|
144
|
+
## Models
|
|
145
|
+
|
|
146
|
+
| Used for | Default | Change with |
|
|
147
|
+
|---|---|---|
|
|
148
|
+
| Planning, running your prompt, judging | `ollama_chat/qwen2.5:7b` | `HEMP_MODEL` for all three, or `HEMP_PLANNER_MODEL`, `HEMP_TARGET_MODEL`, `HEMP_JUDGE_MODEL` |
|
|
149
|
+
| Comparing free-text answers by meaning | `ollama/nomic-embed-text` | `HEMP_EMBED_MODEL` |
|
|
150
|
+
|
|
151
|
+
Any [LiteLLM model name](https://docs.litellm.ai/docs/providers) works. A judge at least as strong as the model being tested gives better scores.
|
|
152
|
+
|
|
153
|
+
## Your own checks
|
|
154
|
+
|
|
155
|
+
Write a check in a Python file:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
# my_metrics.py
|
|
159
|
+
import hemp
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@hemp.metric("polite")
|
|
163
|
+
def polite(ctx: hemp.MetricContext, params: hemp.MetricParams) -> hemp.MetricResult:
|
|
164
|
+
"""The output says please or thanks."""
|
|
165
|
+
text = ctx.output.lower()
|
|
166
|
+
return hemp.MetricResult.check("please" in text or "thank" in text)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Pass it with `--plugin my_metrics.py` to `hemp plan` or `hemp metrics`. The planner can choose it, and the plan remembers the file.
|
|
170
|
+
|
|
171
|
+
## Examples
|
|
172
|
+
|
|
173
|
+
The [examples folder](https://github.com/NithilanVishvanath/hemp/tree/main/examples) has ready-to-run projects:
|
|
174
|
+
|
|
175
|
+
| Example | What it shows |
|
|
176
|
+
|---|---|
|
|
177
|
+
| `sentiment` | Labels: exact match and baselines |
|
|
178
|
+
| `extraction` | JSON output checked field by field |
|
|
179
|
+
| `qa` | Free-text answers, a judge question, and a custom check |
|
|
180
|
+
| `colors` | Images: name the color of each swatch (needs a vision model) |
|
|
181
|
+
|
|
182
|
+
For example: `hemp plan examples/sentiment/prompt.txt examples/sentiment/data.jsonl`.
|
|
183
|
+
|
|
184
|
+
## Development
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
git clone https://github.com/NithilanVishvanath/hemp
|
|
188
|
+
cd hemp
|
|
189
|
+
uv sync
|
|
190
|
+
uv run pytest # unit tests, no models needed
|
|
191
|
+
uv run pytest -m integration # tests that call a real model
|
|
192
|
+
uv run ruff check . && uv run ruff format .
|
|
193
|
+
uv run mypy
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
MIT
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "hemp-eval"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Help Evaluate My Prompt: work out how to test an LLM prompt from the prompt and your data, run it, and compare prompt versions."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "NithilanVishvanath", email = "nithilanvishvanath@gmail.com" }
|
|
10
|
+
]
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
keywords = ["llm", "evaluation", "prompt", "eval", "dspy", "llm-as-a-judge", "prompt-engineering"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Environment :: Console",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Intended Audience :: Science/Research",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
20
|
+
"Topic :: Software Development :: Testing",
|
|
21
|
+
"Typing :: Typed",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"dspy>=3.4.1",
|
|
29
|
+
"litellm>=1.106.6",
|
|
30
|
+
"pydantic>=2.6",
|
|
31
|
+
"pyyaml>=6.0.3",
|
|
32
|
+
"rich>=13.7",
|
|
33
|
+
"typer>=0.12",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/NithilanVishvanath/hemp"
|
|
38
|
+
Repository = "https://github.com/NithilanVishvanath/hemp"
|
|
39
|
+
Issues = "https://github.com/NithilanVishvanath/hemp/issues"
|
|
40
|
+
|
|
41
|
+
[project.scripts]
|
|
42
|
+
hemp = "hemp.cli:app"
|
|
43
|
+
|
|
44
|
+
[project.optional-dependencies]
|
|
45
|
+
images = [
|
|
46
|
+
"pillow>=10",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[dependency-groups]
|
|
50
|
+
dev = [
|
|
51
|
+
"mypy>=1.10",
|
|
52
|
+
"pillow>=10",
|
|
53
|
+
"pytest>=8.0",
|
|
54
|
+
"ruff>=0.6",
|
|
55
|
+
"types-pyyaml>=6.0.12.20260906",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
[build-system]
|
|
59
|
+
requires = ["uv_build>=0.11.14,<0.12.0"]
|
|
60
|
+
build-backend = "uv_build"
|
|
61
|
+
|
|
62
|
+
[tool.uv.build-backend]
|
|
63
|
+
module-name = "hemp"
|
|
64
|
+
|
|
65
|
+
[tool.pytest.ini_options]
|
|
66
|
+
testpaths = ["tests"]
|
|
67
|
+
addopts = ["-ra", "--strict-markers", "-m", "not integration"]
|
|
68
|
+
markers = [
|
|
69
|
+
"integration: tests that call a real model (run with `uv run pytest -m integration`)",
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
[tool.ruff]
|
|
73
|
+
line-length = 100
|
|
74
|
+
target-version = "py311"
|
|
75
|
+
|
|
76
|
+
[tool.ruff.lint]
|
|
77
|
+
select = ["E", "F", "W", "I", "B", "UP", "SIM", "RUF"]
|
|
78
|
+
|
|
79
|
+
[tool.mypy]
|
|
80
|
+
python_version = "3.11"
|
|
81
|
+
strict = true
|
|
82
|
+
files = ["src"]
|
|
83
|
+
plugins = ["pydantic.mypy"]
|
|
84
|
+
|
|
85
|
+
[[tool.mypy.overrides]]
|
|
86
|
+
# dspy and litellm ship without type information.
|
|
87
|
+
module = ["dspy", "dspy.*", "litellm", "litellm.*"]
|
|
88
|
+
ignore_missing_imports = true
|
|
89
|
+
|
|
90
|
+
[[tool.mypy.overrides]]
|
|
91
|
+
# DSPy signatures and modules are declared by subclassing dspy classes.
|
|
92
|
+
module = ["hemp.plan.planner", "hemp.runner.target", "hemp.metrics.judge"]
|
|
93
|
+
disallow_subclassing_any = false
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""HEMP: Help Evaluate My Prompt."""
|
|
2
|
+
|
|
3
|
+
from hemp._version import __version__
|
|
4
|
+
from hemp.dataset import DatasetError, load_dataset
|
|
5
|
+
from hemp.metrics import MetricContext, MetricParams, MetricResult, metric
|
|
6
|
+
from hemp.plan import EvalPlan, PlanError, create_plan, read_plan, write_plan
|
|
7
|
+
from hemp.profile import DatasetProfile, profile_dataset, profile_rows
|
|
8
|
+
from hemp.runner import RunResult, run_eval
|
|
9
|
+
from hemp.store import RunDiff, Store, diff_runs
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"DatasetError",
|
|
13
|
+
"DatasetProfile",
|
|
14
|
+
"EvalPlan",
|
|
15
|
+
"MetricContext",
|
|
16
|
+
"MetricParams",
|
|
17
|
+
"MetricResult",
|
|
18
|
+
"PlanError",
|
|
19
|
+
"RunDiff",
|
|
20
|
+
"RunResult",
|
|
21
|
+
"Store",
|
|
22
|
+
"__version__",
|
|
23
|
+
"create_plan",
|
|
24
|
+
"diff_runs",
|
|
25
|
+
"load_dataset",
|
|
26
|
+
"metric",
|
|
27
|
+
"profile_dataset",
|
|
28
|
+
"profile_rows",
|
|
29
|
+
"read_plan",
|
|
30
|
+
"run_eval",
|
|
31
|
+
"write_plan",
|
|
32
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Human labeling, judge tuning, and judge-human agreement."""
|
|
2
|
+
|
|
3
|
+
from hemp.calibrate.agreement import Agreement, agreement
|
|
4
|
+
from hemp.calibrate.labels import Label, LabelItem, label_candidates
|
|
5
|
+
from hemp.calibrate.optimize import (
|
|
6
|
+
MIN_LABELS_TO_TUNE,
|
|
7
|
+
CalibrationFailed,
|
|
8
|
+
CalibrationReport,
|
|
9
|
+
QuestionAgreement,
|
|
10
|
+
calibrate,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"MIN_LABELS_TO_TUNE",
|
|
15
|
+
"Agreement",
|
|
16
|
+
"CalibrationFailed",
|
|
17
|
+
"CalibrationReport",
|
|
18
|
+
"Label",
|
|
19
|
+
"LabelItem",
|
|
20
|
+
"QuestionAgreement",
|
|
21
|
+
"agreement",
|
|
22
|
+
"calibrate",
|
|
23
|
+
"label_candidates",
|
|
24
|
+
]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""How well the judge agrees with human labels: accuracy and Cohen's kappa.
|
|
2
|
+
|
|
3
|
+
Accuracy alone flatters a judge on lopsided labels (if 90% of outputs are good, a
|
|
4
|
+
judge that always says yes agrees 90% of the time). Kappa corrects for agreement
|
|
5
|
+
expected by chance: 1 is perfect, 0 is no better than chance.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from collections.abc import Sequence
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, ConfigDict
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Agreement(BaseModel):
|
|
14
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
15
|
+
|
|
16
|
+
n: int
|
|
17
|
+
n_agree: int
|
|
18
|
+
accuracy: float | None
|
|
19
|
+
kappa: float | None # None when chance agreement is total (one answer only)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def agreement(pairs: Sequence[tuple[bool, bool]]) -> Agreement:
|
|
23
|
+
"""Agreement over (human, judge) verdict pairs."""
|
|
24
|
+
n = len(pairs)
|
|
25
|
+
if n == 0:
|
|
26
|
+
return Agreement(n=0, n_agree=0, accuracy=None, kappa=None)
|
|
27
|
+
n_agree = sum(1 for human, judge in pairs if human == judge)
|
|
28
|
+
observed = n_agree / n
|
|
29
|
+
human_yes = sum(1 for human, _ in pairs if human) / n
|
|
30
|
+
judge_yes = sum(1 for _, judge in pairs if judge) / n
|
|
31
|
+
chance = human_yes * judge_yes + (1 - human_yes) * (1 - judge_yes)
|
|
32
|
+
kappa = None if chance >= 1 else (observed - chance) / (1 - chance)
|
|
33
|
+
return Agreement(n=n, n_agree=n_agree, accuracy=observed, kappa=kappa)
|