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.
Files changed (53) hide show
  1. hemp_eval-0.1.0/LICENSE +21 -0
  2. hemp_eval-0.1.0/PKG-INFO +233 -0
  3. hemp_eval-0.1.0/README.md +198 -0
  4. hemp_eval-0.1.0/pyproject.toml +93 -0
  5. hemp_eval-0.1.0/src/hemp/__init__.py +32 -0
  6. hemp_eval-0.1.0/src/hemp/_version.py +6 -0
  7. hemp_eval-0.1.0/src/hemp/calibrate/__init__.py +24 -0
  8. hemp_eval-0.1.0/src/hemp/calibrate/agreement.py +33 -0
  9. hemp_eval-0.1.0/src/hemp/calibrate/labels.py +143 -0
  10. hemp_eval-0.1.0/src/hemp/calibrate/optimize.py +213 -0
  11. hemp_eval-0.1.0/src/hemp/calibrate/render.py +87 -0
  12. hemp_eval-0.1.0/src/hemp/cli.py +649 -0
  13. hemp_eval-0.1.0/src/hemp/config.py +38 -0
  14. hemp_eval-0.1.0/src/hemp/dataset.py +92 -0
  15. hemp_eval-0.1.0/src/hemp/doctor.py +189 -0
  16. hemp_eval-0.1.0/src/hemp/hashing.py +14 -0
  17. hemp_eval-0.1.0/src/hemp/images.py +209 -0
  18. hemp_eval-0.1.0/src/hemp/metrics/__init__.py +31 -0
  19. hemp_eval-0.1.0/src/hemp/metrics/embeddings.py +72 -0
  20. hemp_eval-0.1.0/src/hemp/metrics/format.py +137 -0
  21. hemp_eval-0.1.0/src/hemp/metrics/judge.py +178 -0
  22. hemp_eval-0.1.0/src/hemp/metrics/plugins.py +73 -0
  23. hemp_eval-0.1.0/src/hemp/metrics/reference.py +153 -0
  24. hemp_eval-0.1.0/src/hemp/metrics/registry.py +205 -0
  25. hemp_eval-0.1.0/src/hemp/metrics/text.py +146 -0
  26. hemp_eval-0.1.0/src/hemp/plan/__init__.py +24 -0
  27. hemp_eval-0.1.0/src/hemp/plan/defaults.py +125 -0
  28. hemp_eval-0.1.0/src/hemp/plan/io.py +349 -0
  29. hemp_eval-0.1.0/src/hemp/plan/planner.py +312 -0
  30. hemp_eval-0.1.0/src/hemp/plan/render.py +48 -0
  31. hemp_eval-0.1.0/src/hemp/plan/schema.py +110 -0
  32. hemp_eval-0.1.0/src/hemp/plan/validate.py +39 -0
  33. hemp_eval-0.1.0/src/hemp/profile/__init__.py +25 -0
  34. hemp_eval-0.1.0/src/hemp/profile/infer.py +113 -0
  35. hemp_eval-0.1.0/src/hemp/profile/profiler.py +185 -0
  36. hemp_eval-0.1.0/src/hemp/profile/render.py +94 -0
  37. hemp_eval-0.1.0/src/hemp/profile/roles.py +115 -0
  38. hemp_eval-0.1.0/src/hemp/profile/schema.py +94 -0
  39. hemp_eval-0.1.0/src/hemp/prompt.py +21 -0
  40. hemp_eval-0.1.0/src/hemp/py.typed +0 -0
  41. hemp_eval-0.1.0/src/hemp/runner/__init__.py +30 -0
  42. hemp_eval-0.1.0/src/hemp/runner/baselines.py +62 -0
  43. hemp_eval-0.1.0/src/hemp/runner/evaluate.py +45 -0
  44. hemp_eval-0.1.0/src/hemp/runner/judging.py +117 -0
  45. hemp_eval-0.1.0/src/hemp/runner/parallel.py +37 -0
  46. hemp_eval-0.1.0/src/hemp/runner/render.py +135 -0
  47. hemp_eval-0.1.0/src/hemp/runner/results.py +119 -0
  48. hemp_eval-0.1.0/src/hemp/runner/run.py +261 -0
  49. hemp_eval-0.1.0/src/hemp/runner/target.py +231 -0
  50. hemp_eval-0.1.0/src/hemp/store/__init__.py +14 -0
  51. hemp_eval-0.1.0/src/hemp/store/db.py +392 -0
  52. hemp_eval-0.1.0/src/hemp/store/diff.py +172 -0
  53. hemp_eval-0.1.0/src/hemp/store/render.py +160 -0
@@ -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.
@@ -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,6 @@
1
+ from importlib.metadata import PackageNotFoundError, version
2
+
3
+ try:
4
+ __version__ = version("hemp-eval")
5
+ except PackageNotFoundError: # pragma: no cover - running from a source tree without install
6
+ __version__ = "0.0.0"
@@ -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)