Optimized, versioned classifier prompts from labeled examples — GEPA prompt evolution with a classification-specific layer.
Project description
melvil
Labeled examples + a label taxonomy in → an optimized, versioned classifier prompt out.
melvil wraps the GEPA reflective prompt-evolution engine with a classification-specific layer that generic prompt optimizers lack: confusion-driven reflection, a per-label prompt codebook, and hard-example mining. It is a pure Python library — the API is the product.
Named for Melvil Dewey, who gave libraries a system for putting things in the right category.
import melvil as mv
examples = mv.load_csv("tickets.csv") # text,label columns
train, dev = mv.train_dev_split(examples, dev_size=100, seed=0)
spec = mv.TaskSpec.from_examples("ticket-intents", examples)
cfg = mv.Config(task_model="openai/gpt-4.1-mini",
reflection_model="openai/gpt-4.1", budget="light")
artifact = mv.optimize(spec, train, dev, cfg)
print(artifact.render()) # deployable prompt string
artifact.save("ticket_intents.v1.json")
Install
pip install pymelvil # the PyPI distribution is `pymelvil`; you `import melvil`
pip install 'pymelvil[hf]' # + HuggingFace dataset loaders
From a checkout:
pip install -e '.[hf,dev]' # + test/lint tooling
Model names are LiteLLM ids (openai/...,
anthropic/..., openrouter/...); set the matching API key env var
(OPENAI_API_KEY, OPENROUTER_API_KEY, ...).
What the benchmarks say (read this before choosing features)
We benchmark honestly, including against ourselves — full protocol and numbers in benchmarks/RESULTS.md. The pre-registered confirmation pass (8 public datasets, fresh seeds, light budget, gpt-4.1-mini) found:
- Vanilla GEPA over melvil's rendered prompt is the strongest configuration
(mean test accuracy 0.781 vs 0.755 for the full classification layer and 0.762 for
MIPROv2; seed prompt 0.703). At light budgets, prefer
features=mv.Features.none(). - The classification layer's per-component updates trade whole-prompt coverage for structure; at ~6–10 accepted proposals per run that trade loses, especially on hard small-taxonomy tasks. Whether it wins at medium/heavy budgets is an open question.
- Hard-example mining, behind its (strict, quarantined) accept gate, kept exemplars in 0/24 confirmation runs — treat it as a safety-gated no-op at light budgets.
What the classification layer does
Each feature is independently toggleable via mv.Features (all off by default — the default is the benchmark-strongest vanilla-GEPA configuration; enable the layer with mv.Features.all() or per-flag;
Features.none() is an explicit alias for the default):
- Per-label codebook — the prompt is not a free-text blob but named components: a task instruction, one definition per label, and boundary rules. GEPA evolves them per-component.
- Confusion-driven reflection — every full dev evaluation updates a confusion matrix; reflection rounds are pointed at the label components on both sides of the currently worst confused boundary, and the reflection LM is shown the top confused pairs with concrete misclassified examples.
- Hard-example mining — dev examples that stay misclassified across the run become candidate few-shot exemplars, selected to cover the top confused boundaries, and kept only if they don't hurt dev accuracy. (Exemplars come from dev, so dev scores of exemplar-augmented artifacts are mildly optimistic — judge them on test.)
The rendered prompt always ends with a fixed, non-evolvable output-format contract, so the optimizer can never break parseability.
Everything else you get
PromptArtifact— versioned JSON: components, models, budget and cost actually spent, dev scores + confusion matrix, the dev-score-vs-budget curve, config hash, lineage (parent_id).artifact.diff(other)gives a per-component diff with score deltas.evaluate(artifact, data, model=...)— accuracy, macro-F1, per-label P/R/F1, confusion, cost; pass a differentmodelfor a transfer evaluation.report(...)renders it as markdown.- Cost estimation before spending —
estimate_optimize_cost(spec, train, dev, cfg)is a dry-run upper bound; measured spend comes from the LM call history and lands in the artifact. - Run directories & resume — every run writes
runs/<task>/<hash>-s<seed>/(config, engine state, reflection traces, artifact);optimize(..., resume=True)continues an interrupted run. - Progress — pass
on_round=lambda info: ...for live dev score / spend after every full dev evaluation, or just read the default logging. - Offline testing —
melvil.testingships fake LMs with a real optimization gradient; the whole test suite runs with no API keys.
Worked example & docs
- examples/quickstart.py — the snippet above, runnable end-to-end on AG News.
- examples/agnews_demo.ipynb — notebook walkthrough with a live progress callback.
- docs/api.md — public API reference.
- benchmarks/ — the honest-benchmark harness (datasets × arms × seeds).
Development
pip install -e '.[dev]'
pytest # green with no API keys — fake-LM offline suite
ruff check .
License: MIT.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pymelvil-0.3.0.tar.gz.
File metadata
- Download URL: pymelvil-0.3.0.tar.gz
- Upload date:
- Size: 628.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7daa6a6afb4ae807129e91613c0b05882e1e681e9ea801a0ec5df75038061cf8
|
|
| MD5 |
101404a38a70b02e0f8148a14c55eaab
|
|
| BLAKE2b-256 |
3ccb6b28e13454fe67e078af251a3fdc116fdadf8ae832119aec917b8c1aaa6d
|
Provenance
The following attestation bundles were made for pymelvil-0.3.0.tar.gz:
Publisher:
release.yml on harishsiravuri/melvil
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pymelvil-0.3.0.tar.gz -
Subject digest:
7daa6a6afb4ae807129e91613c0b05882e1e681e9ea801a0ec5df75038061cf8 - Sigstore transparency entry: 2203716101
- Sigstore integration time:
-
Permalink:
harishsiravuri/melvil@e5efdd7aad763e041080d0ca9c03b6d7ff04daee -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/harishsiravuri
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e5efdd7aad763e041080d0ca9c03b6d7ff04daee -
Trigger Event:
release
-
Statement type:
File details
Details for the file pymelvil-0.3.0-py3-none-any.whl.
File metadata
- Download URL: pymelvil-0.3.0-py3-none-any.whl
- Upload date:
- Size: 41.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4fea10ba69e89242b0c47944cfe35976542350f67d84d7e554686e6db20db395
|
|
| MD5 |
5b7172abfb39625eccbe1d0658bdad72
|
|
| BLAKE2b-256 |
7f27d2b33ce9aed84ab70c0bc9f7ead1046d5db2098b9d2fcc99ebc010b83690
|
Provenance
The following attestation bundles were made for pymelvil-0.3.0-py3-none-any.whl:
Publisher:
release.yml on harishsiravuri/melvil
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pymelvil-0.3.0-py3-none-any.whl -
Subject digest:
4fea10ba69e89242b0c47944cfe35976542350f67d84d7e554686e6db20db395 - Sigstore transparency entry: 2203716108
- Sigstore integration time:
-
Permalink:
harishsiravuri/melvil@e5efdd7aad763e041080d0ca9c03b6d7ff04daee -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/harishsiravuri
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e5efdd7aad763e041080d0ca9c03b6d7ff04daee -
Trigger Event:
release
-
Statement type: