Cookbook: Pre-parsed value extraction
TL;DR Make the regex matches the
Choiceoptions. Because Jev only ever chooses among the spans you hand it, the value you get back is one of those spans copied unchanged — it cannot invent a value or transpose a digit. Add anoneescape hatch, then normalize in code.
Goal
Extract a verbatim value (an email address, a phone number, an invoice amount) from a document where several candidates exist and only the surrounding words say which one plays the requested role. Three steps:
- A regex finds the candidate values in the text. Tune it to over-find.
- Jev picks which candidate the question is asking for, and reads off any attribute the code needs downstream (currency, country, whether an amount is a credit or a charge).
- The code copies the picked value and normalizes it.
You end up with a reusable find / pick pair plus three worked cases.
Inputs / state shape
The state is the raw document string. Three documents, verbatim:
EMAIL_DOC = """From: Dana Whit <dana.whit@acme-corp.com>
To: billing@acme-corp.com
Cc: orders@acme-corp.com
Reply-To: dana.personal@gmail.com
Hi team - please don't use the billing alias for this one. Send my receipt to my
personal address instead. Thanks, Dana."""
PHONE_DOC = """Reach our San Francisco office at these numbers: main desk (415) 555-0199,
billing fax (415) 555-0142, and my direct cell (415) 555-0177. Call the cell if it's urgent."""
MONEY_DOC = """Invoice INV-2087.
Subtotal: $1,200.00
Sales tax: $115.50
Total due: $1,315.50
A $50.00 courtesy credit from last month has already been applied."""
The candidate finders:
EMAIL_RE = re.compile(r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}")
PHONE_RE = re.compile(r"\(?\+?\d[\d\s()\-.]{6,}\d")
MONEY_RE = re.compile(r"[$€£¥]\s?\d[\d,]*(?:\.\d{2})?")
Constants: TYPESAFE_MODEL = "jev-1.12", NONE = "none" — the escape hatch on every selection, "none of the candidates fits".
Questions asked
Three question shapes, all built dynamically from the candidates.
pick — a Choice whose options are the regex spans.
type:Choiceinstructions: the caller's question, passed through verbatimcriteria:{candidate: None for candidate in candidates} | {"none": "None of these is the requested value."}
The option descriptions are None because the span itself is the description; the none key is the only one carrying text. The exact questions used:
| case | instructions |
|---|---|
| email, receipt | Which email address does the sender want their receipt sent to? |
| email, sender | Which email address did this message come from (the From line)? |
| phone | Which of these is the direct mobile / cell number? |
| money, total | Which amount is the total the customer must pay? |
| money, credit | Which amount is the courtesy credit that was applied? |
classify — a small Choice over a fixed label set.
criteria:{option: None for option in options}
| case | instructions |
options |
|---|---|---|
| phone country | In what country is this office located? |
["US", "GB", "DE", "FR", "CA", "AU"] |
| currency | What currency are these amounts in? |
["USD", "EUR", "GBP", "JPY", "CAD"] |
is_true — a Noul, asked once per picked amount, with the picked span interpolated into the instructions:
f"Is the amount {chosen['choice']} a credit or refund to the customer, not a charge?"
Combining logic in code
import os
import re
from decimal import Decimal
import phonenumbers
from typesafe_sdk import Choice, Noul, TypeSafeClient
TYPESAFE_MODEL = "jev-1.12"
NONE = "none"
ts = TypeSafeClient(
api_key=os.environ.get("TYPESAFE_API_KEY", "cache-only"),
base_url=os.environ.get("TYPESAFE_BASE_URL"),
timeout=30.0,
)
def find(pattern: re.Pattern, text: str) -> list[str]:
"""Code-side candidate finder: recall-tuned regex, deduped, in document order."""
seen: set[str] = set()
out: list[str] = []
for match in pattern.findall(text):
span = match.strip()
if span and span not in seen:
seen.add(span)
out.append(span)
return out
def pick(document: str, candidates: list[str], question: str) -> dict:
"""TypeSafe selects which found span plays the role. Returns {choice, confidence}."""
criteria = {c: None for c in candidates} | {
NONE: "None of these is the requested value."
}
answer = ts.system_one(
state=document,
questions={"pick": Choice(instructions=question, criteria=criteria)},
model=TYPESAFE_MODEL,
).answers["pick"]
return {"choice": answer.choice, "confidence": answer.confidence}
def classify(document: str, question: str, options: list[str]) -> dict:
"""A small Choice over a fixed label set (currency, country, ...)."""
answer = ts.system_one(
state=document,
questions={"q": Choice(instructions=question, criteria={o: None for o in options})},
model=TYPESAFE_MODEL,
).answers["q"]
return {"choice": answer.choice, "confidence": answer.confidence}
def is_true(document: str, question: str) -> float:
"""A yes/no Noul. Returns P(yes)."""
return (
ts.system_one(
state=document,
questions={"q": Noul(instructions=question)},
model=TYPESAFE_MODEL,
)
.answers["q"]
.noul
)
Normalization stays entirely in code. Phone:
phones = find(PHONE_RE, PHONE_DOC)
mobile = pick(PHONE_DOC, phones, "Which of these is the direct mobile / cell number?")
region = classify(PHONE_DOC, "In what country is this office located?",
["US", "GB", "DE", "FR", "CA", "AU"])
parsed = phonenumbers.parse(mobile["choice"], region["choice"])
e164 = phonenumbers.format_number(parsed, phonenumbers.PhoneNumberFormat.E164)
Money:
def to_decimal(value: str) -> Decimal:
"""Copy the picked value and parse the number in code (US grouping/decimal here)."""
return Decimal(re.sub(r"[^\d.]", "", value))
for label, chosen in [("total due", total), ("credit", credit)]:
is_credit = is_true(
MONEY_DOC,
f"Is the amount {chosen['choice']} a credit or refund to the customer, not a charge?",
)
kind = "credit" if is_credit > 0.5 else "charge"
Results / what the cookbook reports
Email. Candidates ['dana.whit@acme-corp.com', 'billing@acme-corp.com', 'orders@acme-corp.com', 'dana.personal@gmail.com'].
receipt -> : dana.personal@gmail.com (conf 0.98)
sender -> : dana.whit@acme-corp.com (conf 1.00)
receipt is the personal Gmail address on the Reply-To: line, which is what the body asks for, not the To: billing alias.
Phone. Candidates ['(415) 555-0199', '(415) 555-0142', '(415) 555-0177'].
mobile -> : (415) 555-0177 (conf 1.00)
country -> : US (conf 0.90)
E.164 -> : +14155550177
Nothing in the digits says which number is the mobile or what country it is in; the words around them do.
Money. Candidates ['$1,200.00', '$115.50', '$1,315.50', '$50.00'].
total due : $1,315.50 -> 1315.50 USD (charge, P(credit)=0.01)
credit : $50.00 -> 50.00 USD (credit, P(credit)=0.99)
The credit-or-charge Noul answers 0.01 on the total and 0.99 on the credit, so the code knows the sign of each Decimal it parses.
Adapting it to a new domain
Point find/pick at your own documents: write a recall-tuned regex per value type, then one pick question per role you need filled, plus classify questions for any attribute the normalizer needs (currency, locale, country) and is_true questions for any boolean the downstream code branches on. Thresholds are yours — pick returns confidence, and none in choice means no candidate fit.
Gotchas
The cookbook closes with two explicit limits plus one inline warning:
- A
Choicequestion allows at most 255 options. With more candidates than that, narrow in two stages: pick the section first, then the span inside it. - Finding the candidates is the part that takes work. Emails, phone numbers and amounts have regexes that cover them; a name does not, so its candidates have to come from a roster you already have, from a named-entity recognizer, or from an LLM that proposes them. Jev then picks the one the question asks for.
- Number formatting is a locale assumption in your code, not Jev's.
to_decimalassumes the comma groups thousands and the dot is the decimal point. That holds for$1,315.50; in€1.315,50it is the other way round. The cookbook's fix: ask aNoulwhich convention the document uses, and branch on it in code. - Always include the
nonehatch. Without it,Choiceprobabilities still sum to 1 and some span wins even when the requested value is absent. - Install line.
pip install ipython phonenumbers "typesafe-sdk>=0.5.7" cooksafe --extra-index-url https://pypi.typesafe.ai/.cooksafeis a helper package from TypeSafe's private index andpypi.typesafe.aireturned 404 publicly as of 2026-09-17 — usepip install typesafe-sdk phonenumbersand reimplementJsonCache(Path("json_cache.json"))(a decorator that memoizes each call's result into a JSON file keyed on its arguments, so cached re-renders make no API calls) andmake_playground_link(state, questions, models=[...])(builds ahttps://console.typesafe.ai/playground#share/...URL). - The client here defaults
api_keyto the literal"cache-only"so cached re-renders need no key; that is a cookbook convenience, not an API feature.
Related
- Choice questions — options, criteria, and the
confidenceon the pick - Noul (yes/no) questions — the credit-vs-charge check
- Cookbook: Date extraction — the same "ask for the parts, resolve in code" shape for dates
- Cookbook: Line-by-line search — a
Choiceover line ids, with the 255-option limit again - Writing instructions and criteria that Jev reads correctly — phrasing role-specific questions
- Cookbooks overview — the full cookbook catalog
Sources
- raw/docs/cookbooks__pre_parsed_value_extraction_cookbook.md (https://docs.typesafe.ai/cookbooks/pre_parsed_value_extraction_cookbook.md)