Changelog
All notable changes to this project are documented here. The format follows Keep a Changelog and the project uses Semantic Versioning.
[Unreleased]
[0.2.0] - 2026-09-18
Measured against the live API on a 2,000-row table (from Europe, ~190 ms RTT to the API): first run
8.5 s → 3.5 s, LIMIT 3 on a fresh condition 8.4 s → 0.6 s, 12 % fewer input tokens, and answers that
match single-row evaluation instead of drifting.
Changed
- Streaming read-ahead. A table is no longer read whole (up to
jev.max_prefetch_rows) before the first answer. It is streamed in physical order (TID range scans for tables,OFFSETpages for views), judged in batches with up to 2 ×jev.concurrencyrequests in flight, and each row is answered as soon as its batch returns. Memory is constant whatever the table size; rows beyond the old 5,000-row limit were previously judged one request at a time. ALIMITstops the read-ahead after the in-flight window. Rows that cheaper predicates filter out beforejev()runs are skipped instead of judged. Rows requested out of physical order (backward index scans, joins) are batched with their neighbours instead of judged one by one. - Persistent HTTPS connections. Requests reuse keep-alive connections across batches and statements (one TLS handshake per connection instead of one per request: 880 ms → 300 ms per request from Europe).
jev.batch_sizedefault 40 → 20. Ground-truth tests (job title, EU membership, a phrase in a free-text field) are 100 % correct up to 20 rows per request and fall to 92–98 % at 40 and to 77–94 % at 80: the model has to findrows[i]by position, and that gets unreliable in long arrays. The cost is +4 % input tokens.- Noul questions no longer carry the generic
criteria(“the record satisfies the condition”): they cost 16 % of all input tokens and changed no answers. jev.concurrencydefault 6 → 16. The API handles 16 parallel requests without queueing.jev.timeoutdefault 90 → 30 seconds. Waits are also interruptible now:statement_timeoutand cancel requests take effect within 250 ms instead of after the HTTP timeout.429/529/5xxretries honourRetry-After. Pooled connections are checked before reuse (closed by the server, or idle for more thanjev.keepalive= 600 s) and carry TCP keepalive probes, so a request is never sent into a dead socket; a connection that still fails is retried at once on a fresh one. Keeping connections matters: the first request on a fresh connection was measured at 0.9–1.9 s against 0.3 s afterwards.jev.max_prefetch_rowsnow bounds how far the read-ahead scans past a cache miss (and how many skipped rows it remembers), not the size of the table it can handle.- Rows are serialised with
to_json(column order preserved) instead ofto_jsonb(keys sorted by length). - A cache hit costs no SPI call at all; settings are read in one query per cache miss.
jev_stats()gainsretries,in_flightandconnections(idle, pooled).
Added
- With
jev.noticeson, oneNOTICEper finished API request while a table is being judged (jev: progress 12/50 requests, 480/2000 rows), so clients that stream notices can show a live progress bar. - Setting
jev.keepalive(default 600 s): how long an idle pooled API connection is kept before it is reconnected. Measured: connections stay usable for at least 10–15 minutes of idle time. - Upgrade script
jev--0.1.0--0.2.0.sql(ALTER EXTENSION jev UPDATE). - Regression tests for streaming,
LIMIT, filtered scans, backward index scans and views.
[0.1.0] - 2026-09-17
Added
jev(),jev_prob(),jev_score(),jev_score_norm(),jev_choice(),jev_confidence(),jev_eval().- Whole-table read-ahead with batched, concurrent requests and a per-session answer cache.
jev_stats()andjev_cache_clear().- Settings:
jev.api_key,jev.model,jev.threshold,jev.batch_size,jev.concurrency,jev.max_prefetch_rows,jev.notices,jev.api_url,jev.timeout, and the spend guardsjev.max_rows_per_statement,jev.max_chars_per_statement. - Regression suite against a deterministic mock API; CI for PostgreSQL 14–17.