Unstructured / LLM Extraction
OLC isn't limited to tabular sources. The extraction block (ExtractionConfig, 21 fields) turns unstructured input — documents, transcripts, images, free text — into governed columns using an LLM, with the same schema, quality, and PII guarantees as any other contract.
Powered by
ExtractionConfig is a Pydantic model. Extraction calls the configured LLM through its provider SDK — or, with provider: document, reads files offline with no LLM at all — (anthropic, openai, …); structured output is constrained by a JSON Schema (output_schema) so the model returns valid rows, not prose. Preprocessing uses the right tool per modality — OCR (tesseract / pytesseract), speech-to-text (whisper), and NLP chunking (spaCy).
Shape
extraction:
provider: anthropic
model: claude-sonnet-5
text_column: document_text # the column holding raw text
context_columns: [source_type, region] # extra columns given to the model
system_prompt: "You extract structured order data from support emails."
prompt_template: "Extract fields from:\n{{ document_text }}"
response_format: json
output_schema: # the model must return rows matching this
fields:
- { name: order_id, type: string }
- { name: sentiment, type: string, accepted_values: [pos, neg, neutral] }
temperature: 0
max_tokens: 1024
Offline document reading (provider: document)
When the input is a file — a PDF (typed or scanned), an image (.png, .jpg, .tif) or a Word document — and its fields sit next to printed labels, no LLM is needed. provider: document reads each file's text offline and fills each field with a regex that has one capture group. Nothing leaves your environment, and the same file always gives the same row.
source:
type: landing
path: "{landing_root}/incident_reports/**/*.docx"
format: docx # pdf | image | docx
extraction:
provider: document
model:
fields:
- name: file_path # the document each row came from
type: string
- name: incident_id
type: string
extraction_task: metadata
extraction_examples:
- 'Incident\s*ID\s*[:\t ]*\s*([A-Z]{3}-\d{8}-\d{3})'
- name: estimated_cost
type: string # keep as printed; cast in silver
extraction_task: metadata
extraction_examples:
- 'Estimated\s*cost\s*[:\t ]*\s*(\d+\.\d{2})'
| Rule | Why |
|---|---|
One row per file; each field's pattern is its first extraction_examples entry, or extraction.patterns[field] (which wins). |
The two places a contract already writes a pattern. |
The pattern is label, then one capture group: Label\s*[:\t ]*\s*(value). |
Scans split and misread labels; the reader retries the label tolerating spacing and OCR letter/digit swaps (c0st, Tota1), never the value. |
| A capture stops at the end of its line. | A greedy ([^\n]+) cannot swallow the next field on a scan. |
Keep amounts and dates as string in bronze; cast in silver. |
What was printed is kept, and a bad scan fails a silver rule instead of a parse. |
A field with no match is null, never a guess. |
Absent is not zero. |
Running it needs LakeLogic Core 1.82.0+ with the documents extra: pip install "lakelogic[documents]" (pdfplumber, rapidocr, python-docx — pip only, no Tesseract or PyTorch). Use an LLM provider instead when fields are not labelled (free-text letters, emails) or must be inferred.
Model & prompt
| Field | Purpose |
|---|---|
provider / model |
Which LLM to call. |
fallback_provider / fallback_model |
Failover model if the primary errors/limits. |
system_prompt |
The instruction/system message. |
prompt_template |
Per-row prompt; {{ column }} interpolates row values. |
text_column |
The primary unstructured input column. |
context_columns |
Extra columns passed as context. |
response_format |
Force structured output (e.g. json). |
output_schema |
The schema the response must conform to — turns free text into typed columns. |
temperature / max_tokens |
Standard decoding controls. |
Throughput & cost guards
Extraction over many rows is bounded so a run can't blow up latency or spend:
| Field | Purpose |
|---|---|
batch_size |
Rows per model call/batch. |
concurrency |
Parallel in-flight requests. |
retry |
RetryConfig for transient LLM failures (max_attempts, backoff, initial_delay). |
max_rows_per_run |
Hard cap on rows processed per run. |
max_cost_per_run |
Hard spend ceiling — the run stops before exceeding it. |
No silent caps
When max_rows_per_run / max_cost_per_run truncates a run, that's surfaced in the run result — a bounded run reports what it skipped rather than looking complete.
PII before the LLM
Because prompts leave your environment, strip identifiers first:
redact_pii_before_llm removes/masks the listed pii_fields before the prompt is built, so raw PII never reaches the provider. The field-level pii: true flags are the source of truth for what's sensitive.
Confidence
confidence (ConfidenceConfig) attaches a confidence signal to each extracted row so low-confidence extractions can be quarantined or reviewed rather than trusted blindly — extraction output flows through the same quality rules as everything else.
Preprocessing non-text modalities
Before extraction, preprocessing (PreprocessingConfig) converts documents/media into text:
extraction:
preprocessing:
content_type: pdf # pdf | image | audio | video | text
ocr: { lang: eng } # scanned docs/images → text (tesseract)
transcription: { model: whisper-1 } # audio/video → text
frame_extraction: { fps: 1 } # video → frames for vision models
chunking: { max_tokens: 1000, overlap: 100 } # split long docs (spaCy)
file_column: file_path
text_output_column: document_text
| Field | Purpose |
|---|---|
content_type |
The input modality. |
ocr |
Image/scanned-PDF text extraction. |
transcription |
Speech-to-text for audio/video. |
frame_extraction |
Sample video frames for vision models. |
chunking |
Split long text into overlapping windows. |
file_column / text_output_column |
Where the source file path is, and where extracted text lands. |
Field-level extraction hints
Individual fields can carry extraction guidance in model.fields[], so the "what to pull" lives next to the column:
model:
fields:
- name: sentiment
type: string
extraction_task: "Classify the customer's sentiment."
extraction_examples: ["'love it' → pos", "'terrible' → neg"]
Governed all the way down
Extracted columns are ordinary columns: they get schema validation, quality rules, PII/masking, lineage, and materialization like any other field. Unstructured input becomes a governed data product — not a side pipeline.