jevfilter
Judge content against plain-English definitions with TypeSafe's Jev. Describe what you care about in a few words; get back typed answers with calibrated probabilities: which topics match, which category, which company, how urgent.
jevfilter is stateless: no storage, no cache and no network except Jev. You get plain results back and store them however you like.
Pre-alpha. The API may change before 1.0.
Install
pip install "jevfilter[yaml]"
export TYPESAFE_API_KEY=...
On PyPI: https://pypi.org/project/jevfilter/
Python 3.10+. You can also set the key in code with
jf.configure(api_key=..., model="jev-1.13.0"). jevfilter never reads
.env files, so load one yourself if you use it.
Quick start
import jevfilter as jf
jf.choose("I was charged twice", ["billing", "bug", "feature request"])
# Choice(value='billing', confidence=0.96, ...)
jf.check("Can you send the form by Friday?", {"needs_reply": "The sender wants a reply"})
# {'needs_reply': 0.98}
jf.rate("The site is down for everyone", "severity", ["cosmetic", "degraded", "blocking"])
# Score(value=2.0, level='blocking', ...)
Topics
A topic only needs a name and a description. Categories, fields, scores and flags are optional.
# topics/jobs.yaml
name: Jobs
description: Applications I submitted, and recruiters contacting me about a role.
exclude: Job alerts, digests, newsletters.
categories:
applied: Confirms I submitted an application.
interview: Invites me to an interview.
rejection: Tells me I'm not moving forward.
fields:
company: {about: The hiring company, required: true}
flags:
needs_reply: The sender is asking me to reply.
f = jf.Filter(jf.Topic.load("topics/"))
email = {"from": "...", "subject": "...", "body": "..."}
r = f.judge(jf.Content(email, candidates={"Jobs": {"company": ["Acme", "Initech"]}}))
for t in r.matches:
print(t.topic, t.p, t.category.value, t.fields["company"].value, t.flags)
for t in r.review: # uncertain: let a person decide
print(t.topic, t.reasons)
r.cost_usd # every result reports its cost
db.save(r.to_dict()) # plain JSON; restore with jf.Result.from_dict
Each topic comes back as match, review or no. All questions for one
piece of content go to Jev in a single request. Field values are picked
from the candidates you pass in, never generated. f.explain(email) shows
the exact request and its estimated cost without sending it.
Tracked items and batches
# Which of your tracked items is this email about? (you store the items)
m = f.match_item(email, "Jobs", my_jobs, result=r["Jobs"])
m.item_id # an id from my_jobs, or None for a new one
jf.track.next_status(topics["Jobs"], "applied", "interview") # "interviewing"
# Many emails at once, with a spend cap that refuses instead of overspending
af = jf.AsyncFilter(topics, budget=jf.Budget(usd=0.50, per_minute=120))
results = await af.judge_many(emails, concurrency=8)
See docs/topic-format.md for every topic option
(scores, composites, when, thresholds, meta) and docs/api.md
for the rest of the API.
Testing without an API key
from jevfilter.judges import FakeJudge
fake = FakeJudge({"Jobs/membership": 0.95, "Jobs/categories": "applied"})
r = jf.Filter(topics, judge=fake).judge("...")
Development
uv sync
uv run pytest # unit tests, no network
JEVFILTER_LIVE=1 uv run pytest tests/live -s # real Jev, prints spend
uv run ruff check . && uv run ruff format .
License
MIT
Release files for jevfilter 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| jevfilter-0.2.0.tar.gz | 94.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| jevfilter-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 134.4 kB
Release files / jevfilter-0.2.0.tar.gz
| Download URL | jevfilter-0.2.0.tar.gz |
|---|---|
| Size | 94.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8f10d5a6124e75f73725aeb3705ec9e14439e0d78124bb123014037fb18b30d3
|
|
BLAKE2b-256 checksum How to use checksums |
0202ca234aa8a36378833ed198e5a7210525052b0c35d1efb009b7f972697830
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / jevfilter-0.2.0-py3-none-any.whl
| Download URL | jevfilter-0.2.0-py3-none-any.whl |
|---|---|
| Size | 39.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c77b3109ea114ae4884774b952bf984a9724f48377625a151bbd49dcefde154c
|
|
BLAKE2b-256 checksum How to use checksums |
337c7c5b07e50c63afeaf874d8e2120f083e0ca81fb1d8d8e663d33a9ff127de
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|