# openextract

> Extract structured Pydantic models from documents, images, audio, and video using LLMs.

Python 3.12+. Install `openextract` plus a provider extra (`openextract[openai]`, `openextract[anthropic]`, `openextract[xai]`, or `openextract[all]`). The library does not load `.env` files; the CLI and examples do. Public API is `openextract.__all__`. Do not import `openextract._*` modules.

Site: https://mellow-artificial-intelligence.github.io/openextract/
Repo: https://github.com/Mellow-Artificial-Intelligence/openextract

## Docs

- [Guide](https://mellow-artificial-intelligence.github.io/openextract/guide.html): install, inputs, styles, sessions, batch, errors, CLI
- [For agents](https://mellow-artificial-intelligence.github.io/openextract/agents.html): which API to generate, do-nots, TestModel
- [API reference](https://mellow-artificial-intelligence.github.io/openextract/api-reference.html): CI-checked public signatures
- [CLI contracts](https://mellow-artificial-intelligence.github.io/openextract/cli.html): stdout/stderr/exit codes
- [Providers](https://mellow-artificial-intelligence.github.io/openextract/providers.html): extras, credentials, media matrix
- [Troubleshooting](https://mellow-artificial-intelligence.github.io/openextract/troubleshooting.html): extras, URLs, retries, batch choice

## Contract

```python
from pydantic import BaseModel
from openextract import extract

class Info(BaseModel):
    summary: str

extract(schema=Info, model="openai:gpt-5", input_file="doc.pdf")
```

Inputs: `str` path/URL, `pathlib.Path`, `bytes` or file-like (**`media_type` required**), or `ExtractionInput`. Default cap 50 MiB → `InputTooLargeError`.

One-shot: `extract`, `extract_async`, `extract_with_usage`, `extract_with_usage_async`.
Sessions: `Extractor`, `AsyncExtractor`, `RetryPolicy` (context managers; not thread-safe / one event loop).
Batch (provisional): `extract_many` input order (not from a running loop); `iter_extract_many_async` completion-order `(index, result)`; `extract_many_with_results*` + `total_usage`.
Swarm (provisional): `extract_swarm*` runs N agents on **one** input (`agents` = model id / `Model` / `SwarmMember`, `size` ≤ 16, `reduce` = `merge` | `vote` | `first`); `extract_swarm_with_results*` returns `SwarmResult` (`output`, `agents`, `usage`, `reduce`, `citations` when `cite=True`). Not from a running loop.
Agents (provisional): `define_agent(description, *, model, style, instructions, output_schema, subagents)` and `define_remote_agent(url, description, *, auth, headers, path, output_schema)`; `load_agent` / `load_agents` / `load_agent_directory` read a directory (`agent.py` + `subagents/` + `instructions.md`), a file, or `module:attribute`. Agents are accepted by `extract*` in the `model` position, and `extract(agent, input_file)` uses the agent's `output_schema`; an agent with subagents or a remote endpoint fans out into a swarm. Auth helpers: `openextract.auth.bearer` / `basic` / `vercel_oidc`. Remote failures raise `RemoteAgentError`.
Styles: `direct` (default, any media); `table` (invoices/statements/line items; PDFs parse-then-window, no extra package; boxes still never invented — use `cite=True`); `form` (forms/receipts/labeled key-value; same parse-then-window path as `table`); `search`/`code` text-only + `pydantic-ai-harness`. Do not combine `table`/`form`/`search`/`code` with an injected `agent=`.
Language (provisional): optional `language="es"` (BCP-47-ish tag or plain name) on `extract*` / sessions / batch / swarm appends a short preserve-language instruction. Default `None` is unchanged. Empty values raise `ValueError`. CLI `--language TAG`.
Citations (provisional): `cite=True` asks for per-field `Citation` (`field`, `quote`, `page`) on `ExtractionResult.citations` and, for swarms, `SwarmResult.citations` (reduced to match `output`; per-agent cites stay on each agent). `extract()` / `extract_with_usage()` / `extract_swarm()` still return the schema instance. PDFs are parsed locally and chunked by page; `bbox` is parser-backed only (never invented, never from the model). Grounding stamps heuristic `confidence` (`[0, 1]`) and `match` (`exact` / `numeric` / `fuzzy` / `value` / `page` / `quote`) from quote/value match strength — not a model-provided probability. Optional `cite_min_confidence` (default `None` keeps every citation) drops cites with `confidence is None` or below the `[0, 1]` threshold after grounding (`ValueError` if out of range); extracted field values are unchanged. `Citation.as_dict()` is JSON-stable `{field, quote, page, bbox, confidence, match}`. `Citation.as_field_citation()` maps to ExtractBench `FieldCitation` (no confidence keys) and needs `page >= 1`. Default off. Extra `pdf` (`pypdfium2`) for parse-then-extract boxes. CLI `--cite` uses the same path and adds a `citations` array to JSON/jsonl (`confidence` / `match` when stamped); `--cite-min-confidence FLOAT` is the same filter.
Progress (provisional): `on_progress=callback` on `extract*` / sessions / batch / swarm. Callback receives `ExtractProgress` (`current`, `total`, `page`, `pages`) once per window immediately before that window is sent to the model. Default `None` is silent. CLI `--progress`: single input writes `progress: window N/M (page P)` to stderr; batch still writes per-item completion lines.
OpenAI: `openai:` → Responses API; `openai-chat:` for Chat Completions.

CLI: `--swarm N`, `--models a,b`, `--agent SPEC`, `--agents SPEC,SPEC`, `--reduce merge|vote|first` — single input only; `--schema` optional when an agent declares `output_schema`. Exit `8` is a remote agent failure.

Exceptions (all subclass `ExtractionError`): `UrlFetchError`, `InputTooLargeError`, `SchemaValidationError` (field path + expected type; `.errors`), `ModelError` (`.retryable`, `.status_code`, `.provider`, `.retry_after`), `ProviderNotInstalledError`, `RemoteAgentError` (`.url`, `.status_code`, `.retryable`). Invalid retry/concurrency options raise `ValueError`.

CLI: `openextract INPUT --schema mod:Class --model openai:gpt-5`. Exit 0 success; 1 usage (including `--out` missing parent / unwritable path); 2–6 mapped errors; 7 partial batch. Parse stdout, or `--out PATH` when set (parent directory must already exist).

Retries: `max_retries` default 0; only transient `ModelError`. Media is resolved once and reused.

Tests without keys: `pydantic_ai.models.test.TestModel`.
