Teams want to extract structured data from text constantly: tag a million product reviews, match duplicate customer records, filter rows by a fuzzy condition, split a batch of scanned pages into separate documents. The data lives in free text; the analysis needs columns. This page explains where typed decisions fit in an extraction pipeline, where they do not, and how to design one with Jev.
The problem
Unstructured text is the largest data source most companies have and the least queryable. Analysts cannot GROUP BY the complaint in a review or JOIN two vendor names spelled differently. Extraction converts text into values that SQL, dashboards and rules understand. Most practical extraction asks one of a few question shapes:
- Which category? Product area, document type, reason for contact.
- How much? Severity, sentiment strength, relevance.
- Yes or no? Mentions a competitor, is the same entity, satisfies the filter, starts a new document.
Those are decisions, not text generation, and treating them as decisions makes the output directly usable.
Common approaches and trade-offs
Regex and rules. Excellent for formats — dates, IDs, amounts, emails. Weak for meaning.
Named-entity recognition and span extractors. Pull names, places and organisations out of text. Good at "what is mentioned," not at "what does it mean for my schema."
Prompted chat models returning JSON. Flexible and capable, but per-row cost adds up on large tables, outputs sometimes break the schema or invent values, and scores vary run to run.
Trained classifiers per column. Fast and cheap once built, but you need labels for every column, and analysts change their minds about columns weekly.
How Jev's typed decisions fit
Jev, from TypeSafe AI, reads a state (one row, one record pair, one page boundary) and answers typed questions:
- Choice for categorical columns — up to 255 options — with a probability per option.
- Score for ordinal columns on an ordered scale of 2 to 10 levels described in words.
- Noul for boolean columns as a 0–1 probability, which you can threshold or keep as a float.
The output never leaves your schema, so it loads straight into typed columns. Jev 1.13 is $0.042 per million input tokens on Flatkey with free output, and TypeSafe reports 70–500 ms per request, so row-by-row enrichment of large tables is practical with modest concurrency.
What Jev does not do is copy or write strings. If you need the customer's name or the invoice number as text, extract it with a parser or a span model and use Jev for the judgments around it.
Pipeline design walkthrough
- Design columns as questions. For each output column, decide its type and write option or level descriptions. Include
unknownornot_mentionedoptions so missing information is not forced into a real value. - Keep rows small. Send only the text that informs the columns. Long rows cost more input tokens and dilute attention.
- Batch columns, parallelise rows. All columns for one row go in one request; rows run concurrently within rate limits.
- Store probabilities alongside values. A
sentimentcolumn plussentiment_plets analysts filter to confident rows. - Review a sample, then backfill. Validate on a labelled sample before running the full table.
The patterns are shown in text to columns, entity resolution, semantic SQL filters and document split.
Mapping column types to question types
| Output column | SQL type | Jev question | Notes |
|---|---|---|---|
| Product area | enum / text | Choice | Add not_mentioned |
| Sentiment | smallint | Score | Levels such as very_negative → very_positive |
| Mentions competitor | boolean or float | Noul | Keep the probability as a float |
| Same entity as record B | boolean | Noul | Ask only for blocked candidate pairs |
| Document boundary | boolean | Noul | One request per page transition |
| Matches filter | boolean | Noul | Used as a semantic WHERE clause |
A useful habit is to keep both the value and its probability. A product_area column with a product_area_p column lets analysts run the same query at different confidence levels and see how sensitive a conclusion is to uncertain rows. For semantic SQL filters, evaluate cheap exact predicates first (date range, region) and run the Noul only on the rows that survive, which keeps input tokens — the main cost driver — down.
For document splitting, frame the question per boundary ("does page N+1 start a new document?") rather than asking for all split points at once. Each boundary becomes an independent, checkable decision.
Pitfalls and evaluation tips
- Forced choices. Without an
unknownoption every row gets a value, and the column looks cleaner than the data is. - Pairwise blow-up. Entity resolution over n records has n² pairs. Use blocking (same postcode, similar name) in code first, then ask Jev only about candidate pairs.
- Inconsistent definitions. If two analysts would label a row differently, the model will too. Tighten descriptions until humans agree.
- Per-column metrics. Measure each column separately; one weak column can hide in an overall average.
- Drift. New products and new slang change the data. Re-sample periodically.
When not to use Jev
For fixed formats (dates, amounts, IDs) use parsers. For copying spans of text, use NER or a generative model. For a column that is a pure function of other columns, compute it in SQL.
Glossary
- Enrichment: adding derived columns to existing records.
- Entity resolution: deciding whether two records refer to the same real-world thing.
- Blocking: cheap pre-filtering that limits which pairs are compared.
- Ordinal column: a column whose values have an order, like low < medium < high.
- Semantic filter: a WHERE condition expressed in meaning rather than exact values.
Pick a use case below for a live demo and copyable code.
Structured Data Extraction use cases
Data extractionTurn text into typed columns
Ask many bounded questions about the same text in one Jev call and store the answers as typed columns that SQL, dashboards and rules can use.
See the workflow →
Entity / Record matchingMatch or reject candidate record pairs with typed same/different/review decisions
Decide whether two records or mentions refer to the same real-world entity—same_entity, different_entity, or review—with calibrated confidence, so dedupe and linking pipelines stop guessing from fuzzy string scores alone.
See the workflow →
Data / Semantic SQLTurn free-text rows into typed SQL columns: a boolean filter, an ENUM routing value and a sortable score
WHERE clauses can't read meaning. Ask Jev plain-English questions per row—"is this customer angry?", "which team owns this ticket?", "how urgent is it?"—and get typed Noul, Choice and Score answers you can store as a boolean predicate, an ENUM column and a sortable value in DuckDB or any SQL engine.
See the workflow →
DocumentsClassify inbox documents and decide multi-doc PDF split boundaries
After LiteParse (or similar) extracts text from an inbox PDF, let Jev classify the document and decide keep_whole / split_at_page / needs_review at candidate boundaries—plus a needs_human flag—so routing and multi-doc splits stay typed and gated in code.
See the workflow →
Data extraction · also fitsScreen crash narratives against a schema
Screen free-text crash and incident narratives against a fixed question schema, write the answers as columns and flag narratives that contradict the coded fields.
See the workflow →
Research · also fitsTriage research papers by topic and relevance
Sort a flood of research papers by topic and relevance to your brief with typed questions, so people only read what matters.
See the workflow →
Recruiting · also fitsScreen resumes against a role
Screen resumes against a role with typed Choice, Score and Noul questions, shortlist only confident matches and keep a human on every rejection.
See the workflow →
Product discovery / Needs ranking · also fitsClassify every product need from complaint data so a weighted ranking shows which problems are worth customer interviews
Turn a pile of user complaints into a ranked list of product needs. Jev labels each need's type, severity and whether recent launches already solve it; your code applies user-tunable weights and sorts.
See the workflow →
Ad intelligence · also fitsClassify every live ad in a competitor's ad library (creative angle, funnel stage, whether it carries an offer, how strong it is) cheaply and fast enough to scan thousands of ads across many brands, then let code count angles per brand and pull the standouts into a swipe file
Scan a competitor's whole ad library instead of scrolling it. Jev tags every live ad with its creative angle, funnel stage, offer and strength in one parallel call, so code can map what each brand is testing.
See the workflow →
Routing · also fitsExtract typed intent fields at the edge
Turn a user request into four bounded intent fields in one Jev call, then validate, admit and schedule it in ordinary code instead of waiting on an LLM parser.
See the workflow →
CI / Semantic lint · also fitsDecide pass / warn / fail / needs review for each changed line against meaning-level lint rules like "never log PII"
ESLint catches syntax, not meaning. Write lint rules as English yes/no questions—"does this change log PII?"—and let Jev return calibrated probabilities per file, line and rule: pass / warn / fail / needs_review, plus a PII severity Score, so CI blocks real leaks and stays quiet on safe logging.
See the workflow →Copy the code
The request behind Use Jev to turn text into columns, calling Jev 1.13 on Flatkey. Set FLATKEY_API_KEY and run it as is, then swap in your own state and questions.
curl -sS https://router.flatkey.ai/api/alpha/decisions \
-H "Authorization: Bearer $FLATKEY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "typesafe/jev-1.13",
"state": "I'\''ve been charged for the Team plan twice this month and support hasn'\''t answered in 5 days. Honestly we'\''re evaluating other tools now. Fix the charge or I'\''m filing a chargeback.",
"questions": {
"topic": {
"type": "choice",
"instructions": "What is this message mainly about?",
"criteria": {
"billing_dispute": "Disputed charges, refunds or double billing",
"bug_report": "Something in the product is broken",
"feature_request": "Asking for something the product doesn'\''t do",
"cancellation": "Wants to cancel or downgrade",
"other": "None of these"
}
},
"urgency": {
"type": "score",
"instructions": "How time-sensitive is this message?",
"criteria": [
"low",
"normal",
"high",
"critical"
]
},
"churn_risk": {
"type": "score",
"instructions": "How likely is this customer to leave, even if they don'\''t say so directly?",
"criteria": [
"none",
"low",
"medium",
"high"
]
},
"threat": {
"type": "noul",
"instructions": "The message threatens legal action, a chargeback or public escalation, beyond ordinary frustration."
},
"needs_human": {
"type": "noul",
"instructions": "A person should read this message before any automated action."
}
}
}'// Node.js 18+ (built-in fetch). Run: FLATKEY_API_KEY=... node jev.mjs
const body = {
"model": "typesafe/jev-1.13",
"state": "I've been charged for the Team plan twice this month and support hasn't answered in 5 days. Honestly we're evaluating other tools now. Fix the charge or I'm filing a chargeback.",
"questions": {
"topic": {
"type": "choice",
"instructions": "What is this message mainly about?",
"criteria": {
"billing_dispute": "Disputed charges, refunds or double billing",
"bug_report": "Something in the product is broken",
"feature_request": "Asking for something the product doesn't do",
"cancellation": "Wants to cancel or downgrade",
"other": "None of these"
}
},
"urgency": {
"type": "score",
"instructions": "How time-sensitive is this message?",
"criteria": [
"low",
"normal",
"high",
"critical"
]
},
"churn_risk": {
"type": "score",
"instructions": "How likely is this customer to leave, even if they don't say so directly?",
"criteria": [
"none",
"low",
"medium",
"high"
]
},
"threat": {
"type": "noul",
"instructions": "The message threatens legal action, a chargeback or public escalation, beyond ordinary frustration."
},
"needs_human": {
"type": "noul",
"instructions": "A person should read this message before any automated action."
}
}
};
const res = await fetch("https://router.flatkey.ai/api/alpha/decisions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.FLATKEY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!res.ok) throw new Error(`Jev request failed: ${res.status} ${await res.text()}`);
const { answers, usage } = await res.json();
console.log(JSON.stringify(answers, null, 2), usage);# Python 3.8+ (pip install requests). Run: FLATKEY_API_KEY=... python jev.py
import json, os, requests
body = json.loads(r"""{
"model": "typesafe/jev-1.13",
"state": "I've been charged for the Team plan twice this month and support hasn't answered in 5 days. Honestly we're evaluating other tools now. Fix the charge or I'm filing a chargeback.",
"questions": {
"topic": {
"type": "choice",
"instructions": "What is this message mainly about?",
"criteria": {
"billing_dispute": "Disputed charges, refunds or double billing",
"bug_report": "Something in the product is broken",
"feature_request": "Asking for something the product doesn't do",
"cancellation": "Wants to cancel or downgrade",
"other": "None of these"
}
},
"urgency": {
"type": "score",
"instructions": "How time-sensitive is this message?",
"criteria": [
"low",
"normal",
"high",
"critical"
]
},
"churn_risk": {
"type": "score",
"instructions": "How likely is this customer to leave, even if they don't say so directly?",
"criteria": [
"none",
"low",
"medium",
"high"
]
},
"threat": {
"type": "noul",
"instructions": "The message threatens legal action, a chargeback or public escalation, beyond ordinary frustration."
},
"needs_human": {
"type": "noul",
"instructions": "A person should read this message before any automated action."
}
}
}""")
res = requests.post(
"https://router.flatkey.ai/api/alpha/decisions",
headers={"Authorization": f"Bearer {os.environ['FLATKEY_API_KEY']}"},
json=body,
timeout=60,
)
res.raise_for_status()
data = res.json()
print(json.dumps(data["answers"], indent=2), data.get("usage"))FAQ
Can Jev extract arbitrary strings like names or addresses?
No. Jev returns typed decisions — an option, a level on a scale or a probability — not free text. It is a fit for extraction tasks that map text to a known set of values. For copying spans of text, use a parser, a NER model or a generative model.
What kinds of extraction does Jev handle well?
Turning text into categorical or ordinal columns (sentiment, product area, severity), yes/no attributes, matching two records as the same entity or not, choosing where a document splits, and evaluating a semantic filter row by row.
How do I turn a text column into several columns at once?
Define one question per output column — Choice for categorical, Score for ordinal, Noul for boolean probability — and send them in one request per row. Each answer becomes a typed column.
Is it cheaper than using a chat model for extraction?
Jev 1.13 on Flatkey costs $0.042 per million input tokens with free output. For high-volume row-by-row enrichment, the cost is dominated by the input text you send.
How do I validate the extracted columns?
Label a random sample of rows by hand, compare per column, and keep the probability so you can filter low-confidence rows or send them for review.
Can I use the output directly in SQL?
Yes. Because every answer is one of your options, a level or a number, it maps to an enum, an integer or a float column with no parsing.
Further reading
Other categories
23 use casesAI Agent GuardrailsGate every agent action, memory write and retry with a cheap, typed allow / review / block decision.
9 use casesLLM RoutingRoute each request to the right model, tool or intent with one fast typed decision.
11 use casesLLM-as-a-Judge & EvalsScore agent runs, RAG answers and CI test evidence with low-variance typed verdicts.
22 use casesClassification & TriageRoute tickets, issues, reports and applications by category, urgency and review need.
7 use casesSecurity & ComplianceGate invoice payments, validate pentest findings and lint for personal data before it ships.
5 use casesGames & Adaptive UIFast typed decisions for game agents, RTS unit control and interfaces that adapt as users type.Run structured data extraction on real Jev
Every use case above runs live with your own key. Get a Flatkey key for Jev 1.13, or bring an OpenRouter key under Dashboard → BYOK.