jev — ask your Postgres tables questions in plain language
Write the condition the way you would say it. Postgres does the rest.
jev lets you filter, rank and classify rows with plain-language conditions. Every row is judged by
TypeSafe's Jev, a System One model that returns calibrated probabilities
instead of generated text. No index, no embeddings, no vector column.
Website: pgjev.com
CREATE EXTENSION jev CASCADE;
SELECT * FROM people WHERE jev(people, 'the name is European');
SELECT subject, jev_prob(tickets, 'the customer is angry') AS p
FROM tickets ORDER BY p DESC LIMIT 20;
SELECT jev_choice(tickets, 'which team should handle this?',
ARRAY['billing', 'technical', 'security', 'sales']) AS team, count(*)
FROM tickets GROUP BY 1;
SELECT name, jev_score(products, 'how luxurious is this product?',
ARRAY['budget', 'mid-range', 'premium', 'luxury']) AS luxury
FROM products ORDER BY luxury DESC;
jev() is an ordinary boolean function, so it composes with everything else in SQL: AND age > 40,
joins, GROUP BY, LIMIT, ORDER BY jev_prob(...).
How it works
jev(table, 'condition')receives the row as a composite value. The first call for a table + condition starts a read-ahead that streams the table in physical order (TID range scans;OFFSETpages for views).jev.max_prefetch_rowslimits how far it searches for a requested row and how many skipped rows it retains; it does not limit the answer cache or total session memory.- Rows are packed
jev.batch_size(20) per request into one shared state ({"condition": ..., "rows": [...]}) with one yes/no Noul question per row. Jev evaluates all questions over one state in parallel, which amortises the ~270-token request overhead (about 435 tokens for one row alone vs 175 per row in batches of 20). - Up to 2 ×
jev.concurrencyrequests are in flight over persistent HTTPS connections, and every row is answered as soon as its batch returns, so aLIMITstops the read-ahead after the in-flight window, and rows that cheaper predicates filter out beforejev()runs (WHERE age > 60 AND jev(...)) are skipped rather than judged. - Answers are cached per row content and question for the backend session, so re-running, changing the threshold or
sorting by probability is free while those answers remain cached. The cache can grow with unique row/question
pairs until
jev_cache_clear()clears it or the backend session ends. Rows from a subquery or CTE (anonymousrecordtype) can't be read ahead and are judged one request at a time; putjev()on base tables or views when you can.
Measured on a 2,000-row table from Europe (~190 ms to the API): first run ≈ 3.5 s in 100 requests, ≈ 296k input
tokens, ≈ $0.012; second run ≈ 50 ms; LIMIT 3 on a new condition ≈ 0.6 s. A new condition in a session that
still holds its pooled connections (idle for less than jev.keepalive) takes ≈ 2.3 s: the fir