N-field structured extraction from documents with LLMs.
Project description
nfield
Pull hundreds of structured fields out of a document, reliably.
Ask an LLM to fill one big JSON schema in a single call and the answers get worse as the schema grows. The model spends its output budget on brackets, commas, and quotes instead of the values, and a wide schema can overflow the context window before it finishes. Most structured-output tools are built for a handful of fields and quietly fall apart past that.
nfield is built for the wide case. It splits the schema into groups that fit the model,
finds the part of the document each group needs, extracts plain key = value lines instead
of nested JSON, validates every field against the text, retries the ones that fail, and
reassembles the clean nested JSON you asked for. Schemas with hundreds of fields come back
intact, on models that would choke on a single-call request.
Install
pip install nfield
pip install "nfield[groq]" # Groq provider
pip install "nfield[openai]" # OpenAI / any OpenAI-compatible endpoint
pip install "nfield[cli]" # command-line interface
Quickstart
from nfield import nfield
document = """
INVOICE #4471
Vendor: Acme Corporation
Total Due: 1284.50 USD
Date: 2026-01-15
"""
schema = {
"type": "object",
"properties": {
"vendor": {"type": "string"},
"total": {"type": "number"},
"date": {"type": "string", "format": "date"},
},
"required": ["vendor", "total", "date"],
}
result = nfield(document, schema, "groq/llama-3.1-8b-instant")
print(result.data)
# {'vendor': 'Acme Corporation', 'total': 1284.5, 'date': '2026-01-15'}
Set your provider key first (export GROQ_API_KEY=...). The model is a
"provider/model-name" string. A Pydantic model or a dataclass works as the schema too.
For large schemas on large models, pass the model's real limits so nfield can plan across the full window instead of a safe default:
result = nfield(
document, schema, "groq/llama-3.3-70b-versatile",
context_window=131_072, # the model's real context window
max_output_tokens=32_768, # the model's real output limit
)
Working with files
Real schemas and documents live on disk, not in string literals. The nfield.io helpers
load them and persist the results, which is where nfield is meant to be used: point it at a
wide schema file and a long document and it plans the calls for you.
from nfield import nfield, load_document, load_schema, save_results
schema = load_schema("schemas/clinical_trial.json") # a JSON Schema with hundreds of fields
document = load_document("records/trial_4471.txt")
result = nfield(
document, schema, "groq/llama-3.3-70b-versatile",
context_window=131_072, max_output_tokens=32_768,
)
save_results([result], "out/trials.jsonl") # JSON Lines, one record per line
Benchmarks
The repository ships a field-count scaling benchmark (benchmark/) that measures how
extraction holds up as the schema grows. It runs on real documents with schemas from a
couple hundred fields up to over a thousand (clinical-trial records, country factbooks) and
on synthetic fixtures into the thousands of fields. Every number is for a single named model
on a given date: accuracy depends on the model you choose, so the benchmark reports the
method's behaviour rather than a leaderboard. See benchmark/README.md.
Why nfield
- Built for wide schemas. Hundreds of fields, not a handful. nfield plans how to split the schema across calls so the model never sees more than it can handle at once.
- Grounded, not guessed. Every value is validated against the document; fields that fail are retried surgically rather than left wrong.
- Any model. Groq, OpenAI, or any OpenAI-compatible endpoint (Together, Fireworks,
DeepSeek, vLLM, Ollama, LM Studio) through one
base_url. - Typed in, typed out. Give it a JSON Schema, a Pydantic model, or a dataclass; get back nested JSON in the same shape.
- Fully typed, no required dependencies.
import nfieldpulls in nothing until you pick a provider; ships withpy.typedand passesmypy --strict.
How it works
A seven-stage pipeline turns one wide request into many small, grounded ones:
calibrate -> analyze schema -> group fields -> retrieve text (BM25)
-> pack to capacity -> extract (key = value) -> validate + retry -> assemble JSON
Only the extraction and retry stages call the model; everything else is local. See docs/concepts/pipeline.md for the full walkthrough.
Reusing an engine
For many documents, build the engine once so the schema is parsed and the model calibrated a single time:
from nfield import NField, AsyncNField
engine = NField("groq/llama-3.1-8b-instant", schema)
for doc in documents:
print(engine(doc).data)
# or run them concurrently
async with AsyncNField("groq/llama-3.1-8b-instant", schema) as engine:
results = await engine.extract_batch(documents)
Command line
nfield inspect schema.json
nfield extract doc.txt --schema schema.json --model groq/llama-3.1-8b-instant
Reasoning models
For a reasoning model (Qwen3, DeepSeek-R1, QwQ), pass
config=ExtractionConfig(reasoning_model=True) so its thinking is turned off per call and
does not eat the answer's output budget.
Documentation
Contributing
Issues and pull requests are welcome. Adding a provider is a single registry entry, and the
development workflow is uv + ruff + mypy --strict + pytest. See
CONTRIBUTING.md.
License
Apache-2.0. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nfield-0.1.0.tar.gz.
File metadata
- Download URL: nfield-0.1.0.tar.gz
- Upload date:
- Size: 3.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bae765f23aae4831689ece9ff4f53aa242a762324c2bdd24a5a6064c0c0c82a4
|
|
| MD5 |
971915bcd94ead330ca2e9ccc344c557
|
|
| BLAKE2b-256 |
e5a027c624fd456f2f4fd7f19f9ce7513b43cbe8ff4ec14c19014f117412fb88
|
Provenance
The following attestation bundles were made for nfield-0.1.0.tar.gz:
Publisher:
release.yml on nfield-labs/nfield
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nfield-0.1.0.tar.gz -
Subject digest:
bae765f23aae4831689ece9ff4f53aa242a762324c2bdd24a5a6064c0c0c82a4 - Sigstore transparency entry: 1982829279
- Sigstore integration time:
-
Permalink:
nfield-labs/nfield@12570c5783bae8a38e7e08fbfe8a07aacf2c2090 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/nfield-labs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@12570c5783bae8a38e7e08fbfe8a07aacf2c2090 -
Trigger Event:
push
-
Statement type:
File details
Details for the file nfield-0.1.0-py3-none-any.whl.
File metadata
- Download URL: nfield-0.1.0-py3-none-any.whl
- Upload date:
- Size: 192.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ebd484e43f571f472a291d25ad5d9b207bda5a0448e39e63e60e02b0f3e8386e
|
|
| MD5 |
c28a8ee5e964302221482bc2025392bc
|
|
| BLAKE2b-256 |
a4d91d71254ea8b9501db65365e0baceee3c5952132c0dce6d4c60f7aede4b21
|
Provenance
The following attestation bundles were made for nfield-0.1.0-py3-none-any.whl:
Publisher:
release.yml on nfield-labs/nfield
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nfield-0.1.0-py3-none-any.whl -
Subject digest:
ebd484e43f571f472a291d25ad5d9b207bda5a0448e39e63e60e02b0f3e8386e - Sigstore transparency entry: 1982829386
- Sigstore integration time:
-
Permalink:
nfield-labs/nfield@12570c5783bae8a38e7e08fbfe8a07aacf2c2090 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/nfield-labs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@12570c5783bae8a38e7e08fbfe8a07aacf2c2090 -
Trigger Event:
push
-
Statement type: