Skip to content
MrJev

jevframe

A .jev accessor for pandas and Polars: the request is built from the columns you name and nothing else in the row, and a failed row raises naming the row instead of becoming a null.

View on GitHub →

Hands-on review

A .jev accessor for pandas and Polars. We put canaries in the columns we did not name, and none of them left the machine.

Good for

  • State built from the columns you list, and nothing else in the row
  • A failed row raises and names the row, never a silent NaN
  • Full probability distributions as ordinary DataFrame columns

Watch out for

  • One request per row: this is not the tool for a million rows
  • Async-only API, so scripts need `asyncio.run`
  • Thirteen stars, one author, version 0.1.0

Tested Sep 22, 2026 at 16bd3eae69b7 · python:3.12-slim in Docker, its 108 tests and full CI, then the accessor driven against a stand-in that logged every request

How we reviewed this: we ran its full CI, then drove the accessor against a stand-in that logged every request, with canary strings planted in the columns we did not pass. We made no Jev calls.

What it is

df["dissatisfied"] = await df.jev.noul(
    "Is this customer dissatisfied?",
    state=["title", "review"],
)

An accessor for pandas and Polars that asks a typed question about each row and gives you back ordinary Series and DataFrames. Built on the official typesafe-sdk.

What leaves the row

This is the design decision worth the review. state= is a list of column names, so the request is built from those columns and not from the row. We gave it a frame with four columns, named two, and put distinct strings in the other two:

state on the wire: {"title": "Charged twice", "review": "Please refund the duplicate charge today."}

  SSN_CANARY          on the wire: 0
  ID_CANARY           on the wire: 0
  Charged twice       on the wire: 1
  duplicate charge    on the wire: 1

Nothing from secret_notes or internal_id left the machine. That sounds obvious until you consider the alternative — a library that serialises the whole row because it is easier — which is what we have found elsewhere. Here the columns you send are the columns you typed.

The pandas index survived too: a frame indexed [10, 20, 30] came back as a Series indexed [10, 20, 30].

One request per row, several questions inside it

res = await df.jev.evaluate(
    state=["title", "review"],
    questions={
        "urgent": Noul(instructions="Does the customer need help today?"),
        "topic": Choice(instructions="What is the main issue?",
                        criteria={"billing": "Charges and refunds", "bug": "Broken behavior", "other": None}),
    },
)

Three rows, two questions each, produced three requests of two questions. Questions about a row share its request; rows do not share with each other.

That is the right shape for a dataframe accessor and the wrong shape for bulk work, which is worth saying plainly: a million rows is a million requests. If that is your job, jev-ultralightspeed exists for exactly it. jevframe is for the notebook, where you have a few thousand rows and want them back as columns.

And you do get them as columns — the whole distribution, not just the winner:

['urgent__probability', 'topic__label', 'topic__confidence',
 'topic__p__billing', 'topic__p__bug', 'topic__p__other']

When a row fails

No silent nulls, and it tells you which row:

what the provider did what happened
HTTP 500 EvaluationError: Row 2: inference failed (TypeSafeInternalServerError)
a 200 with an empty answers EvaluationError: Row 0: inference failed (ValueError)
no API key No API key was provided. Pass api_key or set the TYPESAFE_API_KEY environment variable.

For a library whose output is a column you are about to filter on, a NaN that means “the API was down” and a NaN that means “the model was unsure” would be indistinguishable and dangerous. This one raises, and names the row.

Engineering

MIT, Python 3.10+, pandas and Polars both optional extras — importing an integration registers its accessor. 108 tests pass, ruff check and ruff format --check are clean across 22 files, marimo check --strict passes on the example notebook, and uv build works. That is its whole CI and it ran green on the first try.

Our first attempt at the multi-question API failed because we passed the instruction positionally; the constructors are keyword-only, and the README’s examples are correct. Worth a note only because it is the kind of thing a reader will hit too.

Thirteen stars, one author, version 0.1.0, four days old when we tested it.

Verdict

A small, well-made thing that does one job and does not overreach. The column-selection design means the privacy question has a precise answer — the columns you name, and nothing else — and we checked it rather than assuming it.

Use it in a notebook on the scale a notebook implies. Do not reach for it to label a million rows; that is a different tool, and the author has not claimed otherwise.

For bulk work over the same kind of data, see jev-ultralightspeed; for checking that the question you are about to run over a column actually works, jev-calibrate.

See how it compares with other tools in Best Jev tools, tested hands-on.

Review updated Sep 22, 2026. Numbers quoted from the project are its author's own; we don't publish our own measurements of Jev.

More in Data & Observability

pg-jev

★ 1.2k▲ 24

realZachi/pg-jev

PostgreSQL extension to filter, rank, and classify rows with plain-language conditions.

Shell

DocJev

★ 546▲ 7

jerryjliu/docjev

Classifies and splits PDF, DOCX and PPTX with Jev and local OCR, asking one typed question per page and per boundary in a single request. Ships the manifest, per-call records and error analysis behind its benchmark.

PythonReviewed

tax-doc-classifier

★ 515▲ 1

kyotofin/tax-doc-classifier

Classifies tax-document pages into IRS forms and page kinds with one Jev request per page, driven by a JSON file of form descriptions.

TypeScriptReviewed

Get new Jev projects every week

New Jev releases, pricing changes, and the best new projects, once a week. No spam; unsubscribe anytime.

Powered by Buttondown. See our privacy policy.