architecture // parse → locate → pick

Two Jev calls, one optional trim. Here is exactly what happens.

jextract treats extraction as retrieval plus decision, never generation. LiteParse turns the PDF into positioned text in memory. Jev, a model that answers questions with probabilities instead of writing text, is asked once where each field lives and once which candidate span is its value; free-text spans get one more question to trim them to the exact words. Everything below is from one real run over the sample invoice: 12/12 fields, 813ms, $0.00055.

the flow
1 · parse3 ms
liteparse · local · code
in
  • PDF bytes
  • taxonomy: fields with name, description, type
does
  • text items with boxes (no OCR unless asked)
  • lines by baseline
  • segments at wide x-gaps: columns, label | value
  • chunks by vertical gap, ≤ 900 chars
out
  • 6 chunks with bbox
  • page sizes
2 · locate475 ms
jev · 1 request · choice per field
in
  • state = { document_chunks: { c0: …, c5: … } }
  • 12 questions, one per field
does
  • each question: which chunk holds the value of <field>? options = chunk ids + none
  • answers in parallel over the same state
  • > 22k tokens → windows in parallel, merged
out
  • per field: distribution over chunks
  • p(none) = field absent
3 · pick161 ms
code cuts candidates · jev · 1 request
in
  • top chunk (+ runner-up or neighbour) per field
  • typed candidates: regex, label splits, segments, proper nouns, each marked «…» in its line
does
  • state = only the located chunks
  • choice per field over candidates + none
  • enum → its options · boolean → yes/no
out
  • text, normalised value
  • confidence = p(chosen) × (1 − p(none))
  • page, bbox, top candidates
4 · refine173 ms
free text only · jev · 1 request
in
  • string/id spans that came from a line, segment or label split
  • every prefix/suffix token window of the span (2..n tokens)
does
  • noul per field: is the picked span already exact?
  • choice per field: if not, which window is the complete value?
  • trim applies only when p(exact) < 0.5
out
  • exact span, sub-box
  • confidence × (½ + ½·p(window))
  • `refined.from` keeps the original
requests per document32 + 1 trim for free text · any field count
tokens · this run13,150input only · output is free
cost · this run$0.00055$0.042 per million input tokens
generated tokens0nothing to parse, validate or retry
frame by frame // pick a field, watch its path

One field, end to end.

The page on the left is the real invoice drawn from LiteParse boxes. The right pane shows the actual question Jev received for the chosen field and the probabilities it returned, taken from the captured requests below.

frame 1/6 · parse · positioned text, locally
invoice.pdf · page 1 · 612×792 pt
c0c1c2c3c4c5Total due
liteparse · 1,035 chars · 3 ms
what liteparse returns

Every text item with its font, size and box. No layout model, no OCR unless asked. jextract rebuilds lines by baseline and splits them into segments wherever the horizontal gap is wide, so labels, values and columns stay separate and each keeps its own box.

text items~120
lines29
parse3 ms

Why chunks as state

Jev reads the state once and answers every question against it. Putting the whole document in as chunks and asking one question per field costs one request, and the answer is a distribution over chunk ids, which is exactly a retrieval result with calibrated scores.

Why candidates are cut in code

A date, an amount or an IBAN has a shape. Regex finds every span of that shape for free; Jev only has to decide which one is the field. The value returned is a real span with a real box, so it can be verified against the page and never hallucinated.

Why this is fast and cheap

Parsing is milliseconds. Two Jev requests of a few thousand input tokens each run in about 150 ms and cost a few hundredths of a cent. There are no output tokens, so the field count barely moves the cost and never adds a serial decode step.

the exact calls // captured from the run above

Request by request.

Open any row to see the question text, the options offered and the probability Jev put on each. The app shows this same view for your own documents under the Jev calls tab.

jev calls // exactly what was sent and what came back

3 requests, 30 questions, 13.2k tokens, $0.00055.

Every request carries a state (the chunks Jev reads) and a set of questions answered in parallel over that state. Nothing is generated: each answer is a probability distribution over the options offered, which is why the whole taxonomy fits in two round trips, three when a free-text span needs trimming.

1 · locatePOST /v1/system-one · model jev-1.13.0
12 questions · 6 chunks in state · 5.2k tok · $0.00022 · 475ms
c0
INVOICE   Northwind Traders Ltd.
14 Harbour Street, Suite 300
Seattle, WA 98101
Invoice number: NW-2026-0912   billing@northwind.example
Invoice date: September…
c1
Bill to   Ship to
Contoso Pharmaceuticals GmbH   Contoso Distribution Center
Attn: Accounts Payable   Industriepark 4
Friedrichstraße 120   60314 Frankfurt am M…
c2
Description   Qty   Unit price   Amount
Jev inference - 1.2B input tokens (September)   1   $50.40   $50.40
Enterprise support plan - Gold tier   1   $1,200.00 …
c3
Subtotal   $3,670.40
Discount (5%)   -$183.52
VAT 19%   $662.51
Total due   $4,149.39
c4
Payment instructions
Bank: First Cascade Bank
IBAN: GB29 NWBK 6016 1331 9268 19
SWIFT/BIC: NWBKGB2L
Reference: NW-2026-0912
c5
Thank you for your business. Late payments accrue 1.5% interest per month.
request.questions → response.answers · one row per field
2 · pickPOST /v1/system-one · model jev-1.13.0
12 questions · 6 chunks in state · 6.3k tok · $0.00027 · 161ms
c0
INVOICE   Northwind Traders Ltd.
14 Harbour Street, Suite 300
Seattle, WA 98101
Invoice number: NW-2026-0912   billing@northwind.example
Invoice date: September…
c1
Bill to   Ship to
Contoso Pharmaceuticals GmbH   Contoso Distribution Center
Attn: Accounts Payable   Industriepark 4
Friedrichstraße 120   60314 Frankfurt am M…
c2
Description   Qty   Unit price   Amount
Jev inference - 1.2B input tokens (September)   1   $50.40   $50.40
Enterprise support plan - Gold tier   1   $1,200.00 …
c3
Subtotal   $3,670.40
Discount (5%)   -$183.52
VAT 19%   $662.51
Total due   $4,149.39
c4
Payment instructions
Bank: First Cascade Bank
IBAN: GB29 NWBK 6016 1331 9268 19
SWIFT/BIC: NWBKGB2L
Reference: NW-2026-0912
c5
Thank you for your business. Late payments accrue 1.5% interest per month.
request.questions → response.answers · one row per field
3 · refinePOST /v1/system-one · model jev-1.13.0
6 questions · 3 chunks in state · 1.6k tok · $0.00007 · 173ms
supplier_name
picked: Northwind Traders Ltd.
line: INVOICE   Northwind Traders Ltd.
customer_name
picked: Contoso Pharmaceuticals GmbH
line: Contoso Pharmaceuticals GmbH   Contoso Distribution Center
iban
picked: GB29 NWBK 6016 1331 9268 19
line: IBAN: GB29 NWBK 6016 1331 9268 19
request.questions → response.answers · one row per field
questions people ask
Why two Jev calls and not one?
The second call's options depend on the first call's answer: candidates are cut from the chunk Jev located. Both calls fan out over every field in parallel, so the count is two per document whether the taxonomy has 5 fields or 40.
Why not ask Jev to read the whole document at once for the value?
Jev chooses among options; it does not write text. Offering every span in a long document as an option would be enormous. Locating the chunk first keeps the pick question to a dozen typed candidates, which is also what makes the probabilities sharp.
What happens on long documents?
Chunks are grouped into windows of about 22k tokens. Each window gets its own locate request, run in parallel; per-window probabilities are merged, weighting each window by how much probability it did not assign to 'none'. The pick call still happens once.
How is confidence computed?
For a picked span, confidence is the probability Jev assigned to that candidate multiplied by the probability that the field is present at all (one minus the locate call's p(none)). Enums and booleans are judged from context, so their confidence is the choice probability alone.
What does the deterministic part do?
Everything that is not a judgment: parsing, line and chunk construction, regex candidates for dates, amounts, ids, emails and phones, label/value splits, proper-noun phrases, date normalisation and number parsing. Code is free and exact; Jev decides only where code cannot.
Where does it fail?
Long clause-like values (a liability cap written as a sentence), ambiguous descriptions (current title vs headline title), multi-column prose where lines merge across columns, tables that should come back as arrays, and scanned pages without OCR. The candidates list on each field shows what Jev was offered, which makes these cases easy to spot.
What did the refine round borrow from jev-extract?
Three ideas from the open-source jev-extract benchmark: mark each option inside its context («…») so duplicate values in different places stay distinguishable; choose spans as contiguous token windows inside an already-located unit instead of enumerating the whole document; and gate boundary changes, because a bare “prefer shorter” prior over-trims names. On the four sample documents this took field exact-match from 90% to 94% for about 150 ms and $0.0001 more per document.