Information extraction turns unstructured text into records that software can query, validate, and join with downstream systems. A naive LLM prompt can often produce a JSON object from a paragraph, but production extraction needs stronger guarantees: every extracted field should be tied back to the original text, reviewers should be able to inspect the evidence, and long documents should not lose recall because the model saw too much context at once.
Source-grounded LLM extraction addresses that gap by treating structured output and evidence alignment as one pipeline. The extractor defines a schema with examples, asks the model for entities and attributes, resolves each extraction back to exact spans in the input, and stores offsets alongside the structured data. The result is not just “the model says this medication dosage exists”; it is a record that points to the characters in the source document that support the claim.
Google’s LangExtract library illustrates the pattern. It combines few-shot extraction instructions, schema-like extraction classes, controlled-generation support where available, long-document chunking, parallel processing, multiple extraction passes, JSONL persistence, and self-contained HTML visualization. Those pieces make it a useful reference architecture for document pipelines that need auditable entity extraction rather than generic summaries.
A source-grounded extractor starts by naming the entity classes it expects to find. A class might be medication, dosage, contract_clause, risk_factor, or customer_request. Each extraction can carry attributes that add normalized metadata, such as dose units, sentiment, relationship type, priority, date, or confidence category. This differs from free-form summarization because the output is designed for later validation and storage.
Instead of fine-tuning a model for each domain, the pipeline provides one or more examples that show the target extraction behavior. The examples demonstrate which text spans should be copied exactly, which attributes should be attached, how overlapping entities should be avoided, and what should be ignored. In practice, the examples become an executable specification for the extraction task.
Grounding means each extraction includes start and end positions in the original document. These offsets allow a reviewer or downstream service to reconstruct the evidence span without trusting the generated text alone. Offsets also enable visual inspection: an HTML viewer can highlight each extracted entity in the source document and group related annotations.
The LLM may generate the entity text, class, and attributes, but the pipeline should still resolve the generated entity back to the source. Exact matching is ideal; fuzzy alignment may be needed when model output differs slightly from the source. Without a resolver, the system has structured output but not grounded structured output.
Million-token context windows do not automatically solve extraction. Multi-fact retrieval can still miss relevant details in long documents. Chunking, parallel workers, and multiple extraction passes keep each model call focused while improving coverage across the full document.
The prompt should describe the extraction target in operational terms: copy exact text, avoid overlapping spans, preserve order, and attach only attributes that are supported by the text or explicitly allowed as inferred metadata. Examples then show the expected record shape. LangExtract models this with ExampleData and Extraction objects.
import textwrap
import langextract as lx
prompt = textwrap.dedent("""\
Extract medications, dosages, and administration schedules in order of appearance.
Use exact source text for every extraction. Do not paraphrase.
Attach normalized attributes only when the source text supports them.""")
examples = [
lx.data.ExampleData(
text="Patient takes metformin 500 mg twice daily with meals.",
extractions=[
lx.data.Extraction(
extraction_class="medication",
extraction_text="metformin",
attributes={"status": "active"},
),
lx.data.Extraction(
extraction_class="dosage",
extraction_text="500 mg",
attributes={"unit": "mg"},
),
lx.data.Extraction(
extraction_class="schedule",
extraction_text="twice daily",
attributes={"frequency": "BID"},
),
],
)
]
The extraction call sends the prompt, examples, model selection, and input document to the backend. For long documents, a production wrapper usually chunks the input, runs chunks in parallel, merges non-overlapping extractions, and retries or re-runs passes when recall is more important than latency. Persisting to JSONL keeps extraction output separate from visualization and makes it easier to feed records into review tools or databases.
input_text = """
The patient reports taking metformin 500 mg twice daily.
Lisinopril 10 mg was started last month for blood pressure control.
"""
result = lx.extract(
text_or_documents=input_text,
prompt_description=prompt,
examples=examples,
model_id="gemini-2.5-pro",
)
lx.io.save_annotated_documents([result], output_name="extractions.jsonl")
html = lx.visualize("extractions.jsonl")
with open("review.html", "w", encoding="utf-8") as f:
f.write(html)
After extraction, each record should be checked against the source. A strict resolver accepts only exact substring matches. A more forgiving resolver can search local windows, normalize whitespace, or allow small edit distance, but it must report weaker alignment so reviewers do not mistake inferred or corrected text for verbatim evidence.
Clinical records, radiology reports, case narratives, and scientific abstracts often contain valuable facts embedded in prose. A source-grounded extractor can pull medication names, dosages, conditions, findings, evidence snippets, inclusion criteria, or adverse events while retaining offsets for human verification. The same pattern supports annotation workflows where LLMs propose labels and domain experts approve or correct them.
Contract systems need more than a summary that says a clause exists. They need the exact clause span, normalized clause type, related obligations, dates, exceptions, and confidence flags. Grounded extraction lets a reviewer jump directly from a database record back to the clause that produced it, reducing the risk of acting on hallucinated or paraphrased terms.
Support tickets, call transcripts, incident reports, and sales records can be transformed into structured signals: product areas, requested features, failure modes, sentiment, urgency, and owners. Offset grounding is useful here because teams can audit whether a generated priority or category is actually supported by the customer’s wording.
Store the original text as immutable input, extracted records as generated annotations, and review decisions as a separate layer. This lets you rerun extraction with a better model or prompt without losing the human approval history. It also prevents offset drift caused by silently editing the text after annotations were generated.
{
"document_id": "note-1042",
"source_sha256": "9b0f...",
"extractions": [
{
"class": "medication",
"text": "metformin",
"start": 28,
"end": 37,
"attributes": {"status": "active"},
"alignment": "exact",
"review": "pending"
}
]
}
Chunking must preserve a mapping from chunk-local positions back to document-global positions. If a chunk starts at character 20,000 and the resolver finds an entity at local offset 180, the stored global offset is 20,180. If preprocessing removes headers, normalizes whitespace, or applies OCR corrections, keep a parallel offset map or treat the normalized text as the new auditable source.
def resolve_global_offsets(chunks, extraction):
chunk = chunks[extraction.chunk_id]
return {
"start": chunk.source_start + extraction.local_start,
"end": chunk.source_start + extraction.local_end,
}
Multiple passes can improve recall, but they also multiply cost and make duplicate handling harder. A practical design runs a cheap first pass for obvious entities, a targeted second pass for missing classes, and a merge step that rejects overlapping spans unless a later pass produces a more specific class with stronger evidence.