xstructured
Validated Pydantic data and natural-language text from one LLM response.
xstructured asks the model to answer in its own words and to put a JSON
payload in an envelope. It extracts the payload, validates it with Pydantic v2,
and returns both, even when the payload is wrapped in Markdown, buried in
prose, or still streaming in. It wraps any LangChain v1 Runnable, chat model,
chain, or LangGraph graph, adds middleware to create_agent and Deep Agents,
and ends any chain as an output parser.
Why xstructured
A support assistant has to explain a decision to the customer while your code
acts on a validated Decision. The usual answers fall short:
- Native structured output returns only the data. The prose the user should read is gone, or needs a second call.
- Asking for JSON in the prompt is fragile. Models wrap it in fences, add
a preamble, or stop halfway, and
json.loadseither fails or accepts a truncated payload. - Streaming and parsing don't mix. A UI wants tokens as they arrive; a parser wants the whole response.
- Agents and chains aren't bare chat models. Rebuilding them around a provider feature is costly.
How xstructured addresses each one:
| Problem | What xstructured does |
|---|---|
| Only data, no prose | One response carries both: result.content for people and result.structured for code |
| Fragile prompt-and-parse | Strict JSON decoding and Pydantic validation in JSON mode; recovery only chooses which part of a response to parse and never rewrites JSON |
| Streaming versus parsing | Ordered stream events: text deltas as they arrive, and the validated value the moment its envelope closes |
| Not a bare chat model | Wraps any Runnable or graph, adds middleware to agents, or ends a chain as an output parser |
Highlights
- Async first.
ainvoke,abatch, andastreamthroughout; tracing, retries, fallbacks, and composition behave as in any LangChain Runnable. - Agents keep their tools.
XStructuredMiddlewaregivescreate_agentand Deep Agents a validated final answer while the agent keeps calling tools and writing prose. - Bounded by design. Input, envelope, payload, and nesting limits;
duplicate keys,
NaN, and overflowing numbers are rejected before validation. - Optional, bounded repair. An opt-in repair Runnable gets a fixed number of attempts, and its output is held to the same schema.
- Multiple and named payloads. Several values of one schema, or one value per named schema, from a single response.
- Extensible through ABCs. Every behavior is an abstract base class with
a default, and
xstructured.testingships a contract suite for each. - Observable. An
Observersees every call, andOpenTelemetryObserverturns calls into spans. - Small and typed. The core depends only on
langchain-coreandpydantic, ships type hints, and is tested on Python 3.12 to 3.15 with 100% statement and branch coverage.
Installation
pip install xstructured # core: chat models, chains, parsers
pip install "xstructured[agents]" # with create_agent middleware
Optional extras:
| Extra | Adds |
|---|---|
agents |
LangChain 1.x for XStructuredMiddleware and create_agent |
langgraph |
LangGraph 1.x, to wrap compiled graphs and nodes |
deepagents |
Deep Agents, for create_deep_agent |
otel |
OpenTelemetry API, for OpenTelemetryObserver |
all |
Every extra above |
Quickstart
Wrap a chat model, then read the prose and the validated value:
import asyncio
from langchain.chat_models import init_chat_model
from pydantic import BaseModel, Field
from xstructured import with_xstructured_output
class Ticket(BaseModel):
priority: str = Field(description="low, high, or critical")
team: str
summary: str
triage = with_xstructured_output(init_chat_model("openai:gpt-5-mini"), Ticket)
async def main() -> None:
result = await triage.ainvoke("Checkout is down for every EU customer.")
print(result.content) # The model's prose, without the envelope.
print(result.structured) # priority='critical' team='payments' ...
asyncio.run(main())
Stream the prose to a UI, then render the validated card:
from xstructured import StreamEventKind
async def stream(message: str) -> None:
async for event in triage.astream(message):
if event.kind is StreamEventKind.TEXT_DELTA:
print(event.text, end="")
elif event.kind is StreamEventKind.STRUCTURED_END:
print("\nTicket:", event.structured)
Give an agent a validated final answer while it keeps its tools:
from langchain.agents import create_agent
from xstructured.agents import XStructuredMiddleware
agent = create_agent(
"openai:gpt-5-mini",
tools=[lookup_owner],
middleware=[XStructuredMiddleware(Ticket)],
)
async def handle(message: str) -> Ticket:
state = await agent.ainvoke({"messages": [("human", message)]})
return state["structured_response"].structured
Or parse text from any source, without a model:
from xstructured import EnvelopeSpec, StructuredParser
parser = StructuredParser(Ticket, envelope=EnvelopeSpec())
reply = (
"Escalating now. <xstructured>"
'{"priority": "critical", "team": "payments", "summary": "EU outage"}'
"</xstructured>"
)
print(parser.parse(reply).value)
The quickstart covers chains, output parsers, LangGraph, and tests without a provider.
Customize everything
Every behavior is an abstract base class with a default. Pass your own with
the matching keyword, and check it with its contract in xstructured.testing:
| Base class | Keyword | Replace it to |
|---|---|---|
InstructionRenderer |
renderer= |
write the instructions in your own style or language |
InstructionInjector |
injector= |
add the instructions to inputs of your own shape |
OutputTextExtractor |
extractor= |
read the reply from outputs of your own shape |
RecoveryStrategy |
recovery= |
find the payload in a model's quirky output |
JsonDecoder |
decoder= |
accept a model's non-standard JSON |
RepairPromptBuilder |
repair_prompt= |
change what the repair model is asked |
Observer |
observer= |
send metrics and traces to any backend |
The options of the libraries underneath pass straight through, too.
SchemaOptions forwards Pydantic's validation (strict, extra, context,
by_alias, by_name, a ConfigDict) and JSON schema generation
(schema_generator, ref_template, union_format). The wrapper is a
LangChain Runnable, so with_retry, with_fallbacks, with_config, and
callbacks work unchanged, and OpenTelemetryObserver takes your own
TracerProvider. See
Extension points.
Benchmarks
An offline benchmark runs 11 model-style outputs through four parsers, 1,000 times each, and checks every result against an explicit expectation. A truncated or ambiguous payload that a parser accepts counts as a failure:
| Mechanism | Correct | Median latency |
|---|---|---|
json.loads + Pydantic |
45.45% | 2.00 µs |
LangChain JsonOutputParser + Pydantic |
45.45% | 18.20 µs |
LangChain PydanticOutputParser |
45.45% | 17.70 µs |
| xstructured | 100% | 12.90 µs |
Measured on one machine; see Benchmarks for the method, per-case results, and how to reproduce.
Examples
Twelve complete programs in
examples/.
Each runs offline with a scripted model, or live when EXPLABS_API_KEY is
set:
- Single-agent extraction
- Multi-agent pipeline
- Deep Agents research
- LangGraph workflow
- RAG with citations
- Incident analysis
- Streaming UI events
- Multiple payloads and repair
- Named envelopes
- Output parser chain
- Customization
- OpenTelemetry tracing
Documentation
Full documentation: xstructured.readthedocs.io
Two Agent Skills
teach coding agents such as Claude Code, Codex, Cursor and GitHub Copilot to
use xstructured correctly. xstructured adds it to your application, and
xstructured-extend writes decoders, recovery strategies, observers, and the
other extension points:
npx skills add smuniharish/xstructured
xstructured follows semantic versioning. Changes are listed in the changelog.
Contributing
Contributions are welcome. See CONTRIBUTING.md for the development setup and checks, and SECURITY.md to report a vulnerability.
License
Apache License 2.0. See LICENSE.
Metadata
Release files for xstructured 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| xstructured-1.0.0.tar.gz | 61.7 kB | Without provenance | Details for xstructured-1.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| xstructured-1.0.0-py3-none-any.whl | Python 3 | none | any | Details for xstructured-1.0.0-py3-none-any.whl |
Total release size: 132.0 kB
Release files / xstructured-1.0.0.tar.gz
| Download URL | xstructured-1.0.0.tar.gz |
|---|---|
| Size | 61.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ba4a76ffe00e329c6b36a7a6495b627510c7f717d2f70f234ccc4bdb53ea57cd
|
|
BLAKE2b-256 checksum How to use checksums |
8a18006954132f70895b9239a374eb6b6b8a3637fa14032cd0a584f8c6e3dd5a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / xstructured-1.0.0-py3-none-any.whl
| Download URL | xstructured-1.0.0-py3-none-any.whl |
|---|---|
| Size | 70.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f445cf5651fcf4b0f406f13a5eedfc7bc2b2cdf8ba694c4bf9a6b65b31dbcc3f
|
|
BLAKE2b-256 checksum How to use checksums |
2705e639bddf2967a0b1e3d5749f9e26313f6d8e071ef46b6fe3bed092d46b13
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for xstructured 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.