Skip to main content

xstructured logo

xstructured

CI Documentation PyPI Python 3.12 | 3.13 | 3.14 | 3.15 Coverage 100% Typed License: Apache 2.0

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.

How a request flows through xstructured

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.loads either 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, and astream throughout; tracing, retries, fallbacks, and composition behave as in any LangChain Runnable.
  • Agents keep their tools. XStructuredMiddleware gives create_agent and 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.testing ships a contract suite for each.
  • Observable. An Observer sees every call, and OpenTelemetryObserver turns calls into spans.
  • Small and typed. The core depends only on langchain-core and pydantic, 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:

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)

Source distribution for xstructured 1.0.0
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)

Table of built distributions (wheels) for xstructured 1.0.0
File Interpreter ABI Platform Actions
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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page