Structured output#

In the recipe on output parsing, we extracted a timeline from a paragraph by hand. We wrote a prompt that asked the model for JSON, then ran the response through a parser. That worked, but it was fragile: re-running the prompt could produce a different format, wrap the JSON in Markdown, or add a stray sentence of preamble. Each drift meant tweaking the prompt and trying again.

LangChain can run that loop for us. In this recipe, we hand a model a schema describing the shape we want, and create_agent takes responsibility for getting the model to fill it in, retrying when the output does not fit. We will use the same Baker-Berry passage and the same extraction task as the output parsing recipe, so the only thing that changes is how we get the structured data back.

Input data#

This is the same passage on the history of the Baker-Berry Library from Wikipedia that we used in the output parsing recipe:

unstructured_text = """The original, historic library building is the Fisher Ames Baker Memorial Library; it opened in 1928 with a collection of 240,000 volumes. The building was designed by Jens Fredrick Larson, modeled after Independence Hall in Philadelphia, and funded by a gift to Dartmouth College by George Fisher Baker in memory of his uncle, Fisher Ames Baker, Dartmouth class of 1859. The facility was expanded in 1941 and 1957–1958 and received its one millionth volume in 1970.

In 1992, John Berry and the Baker family donated US $30 million for the construction of a new facility, the Berry Library designed by architect Robert Venturi, adjoining the Baker Library. The new complex, the Baker-Berry Library, opened in 2000 and was completed in 2002.[6] The Dartmouth College libraries presently hold over 2 million volumes in their collections."""

print(unstructured_text)
The original, historic library building is the Fisher Ames Baker Memorial Library; it opened in 1928 with a collection of 240,000 volumes. The building was designed by Jens Fredrick Larson, modeled after Independence Hall in Philadelphia, and funded by a gift to Dartmouth College by George Fisher Baker in memory of his uncle, Fisher Ames Baker, Dartmouth class of 1859. The facility was expanded in 1941 and 1957–1958 and received its one millionth volume in 1970.

In 1992, John Berry and the Baker family donated US $30 million for the construction of a new facility, the Berry Library designed by architect Robert Venturi, adjoining the Baker Library. The new complex, the Baker-Berry Library, opened in 2000 and was completed in 2002.[6] The Dartmouth College libraries presently hold over 2 million volumes in their collections.

Setup#

We start by loading our API key and importing the chat model, as in earlier recipes:

from langchain_dartmouth.llms import ChatDartmouth
from dotenv import find_dotenv, load_dotenv

load_dotenv(find_dotenv())
True

Defining the output shape#

Instead of describing the format in a prompt, we describe it as a schema: a small set of Python classes that spell out the fields we want and their types. We want a timeline, which is a list of events, and each event has a year and a description. We can express that with two dataclasses:

from dataclasses import dataclass


@dataclass
class Event:
    """An event in the timeline"""

    year: int  # The year of the event
    description: str  # The description of the event


@dataclass
class Timeline:
    """A sequence of events"""

    events: list[Event]  # The events of the timeline

The docstrings and inline comments are not just for us. LangChain passes the field names, types, and descriptions to the model, so clear names and comments help it fill in the right values.

Hint

A dataclass is one of several ways to define a schema. We could also use a Pydantic model, a TypedDict, or a raw JSON schema. See the structured output documentation for the trade-offs between them.

Creating the agent#

We met create_agent in the recipe on agents, where it automated the tool calling loop. Here we use a different argument, response_format, to hand the agent our schema. The agent now treats producing a valid Timeline as its job. We reuse the exact same prompt string as the output parsing recipe:

from langchain.agents import create_agent

llm = ChatDartmouth(model_name="openai.gpt-oss-120b")

# Pass the schema via response_format; the agent will work to return a Timeline
agent = create_agent(model=llm, response_format=Timeline)


prompt = (
    "Extract a succinct timeline of events directly to the Library from the following text: \n\n"
    + unstructured_text
)

Invoking the agent#

We invoke the agent the same way we did in the agents recipe by passing a message. The parsed result waits for us under the structured_response key:

response = agent.invoke({"messages": ("human", prompt)})

timeline = response["structured_response"]

Since we defined the shape of the data via the response_format argument, we don’t need to do any prompt engineering for JSON outputs. The parser and re-running loop when the output format isn’t correct is handled internally in the agent loop. To see what the agent did on our behalf, we can inspect the messages it exchanged with the model, using the same pretty_print() pattern from the agents recipe:

for msg in response["messages"]:
    msg.pretty_print()
================================ Human Message =================================

Extract a succinct timeline of events directly to the Library from the following text: 

The original, historic library building is the Fisher Ames Baker Memorial Library; it opened in 1928 with a collection of 240,000 volumes. The building was designed by Jens Fredrick Larson, modeled after Independence Hall in Philadelphia, and funded by a gift to Dartmouth College by George Fisher Baker in memory of his uncle, Fisher Ames Baker, Dartmouth class of 1859. The facility was expanded in 1941 and 1957–1958 and received its one millionth volume in 1970.

In 1992, John Berry and the Baker family donated US $30 million for the construction of a new facility, the Berry Library designed by architect Robert Venturi, adjoining the Baker Library. The new complex, the Baker-Berry Library, opened in 2000 and was completed in 2002.[6] The Dartmouth College libraries presently hold over 2 million volumes in their collections.
================================== Ai Message ==================================
Tool Calls:
  Timeline (chatcmpl-tool-86367012d33733c4)
 Call ID: chatcmpl-tool-86367012d33733c4
  Args:
    events: [{'year': 1928, 'description': 'Fisher Ames Baker Memorial Library opened with 240,000 volumes; building designed by Jens Fredrick Larson, modeled after Independence Hall, funded by George Fisher Baker in memory of his uncle.'}, {'year': 1941, 'description': 'First expansion of the library building.'}, {'year': 1957, 'description': 'Second expansion of the library building (completed in 1958).'}, {'year': 1970, 'description': 'Library received its one millionth volume.'}, {'year': 1992, 'description': 'John Berry and the Baker family donated $30\u202fmillion for construction of a new facility, the Berry Library.'}, {'year': 2000, 'description': 'Baker-Berry Library complex opened, featuring the new Berry Library designed by Robert Venturi.'}, {'year': 2002, 'description': 'Completion of the Baker-Berry Library complex.'}]
================================= Tool Message =================================
Name: Timeline

Returning structured response: Timeline(events=[Event(year=1928, description='Fisher Ames Baker Memorial Library opened with 240,000 volumes; building designed by Jens Fredrick Larson, modeled after Independence Hall, funded by George Fisher Baker in memory of his uncle.'), Event(year=1941, description='First expansion of the library building.'), Event(year=1957, description='Second expansion of the library building (completed in 1958).'), Event(year=1970, description='Library received its one millionth volume.'), Event(year=1992, description='John Berry and the Baker family donated $30\u202fmillion for construction of a new facility, the Berry Library.'), Event(year=2000, description='Baker-Berry Library complex opened, featuring the new Berry Library designed by Robert Venturi.'), Event(year=2002, description='Completion of the Baker-Berry Library complex.')])

The trace shows our prompt as a human message, then one or more AI messages that call a Timeline tool with the extracted events, then a tool message confirming the structured response. If anything is wrong with the output format, there may be multiple messages until everything complies with the requested output schema. This is the loop the output parsing recipe ran by hand, now automatic: the agent asks the model to produce the schema, checks the result, and if it does not fit, sends the error back and asks the model to try again.

Note

When we pass a schema type directly, LangChain picks a strategy based on the model. Models with native structured output use a provider strategy; others, including openai.gpt-oss-120b here, use a tool calling strategy, which is why the trace shows a Timeline tool call. The validate-and-retry behavior lives in the tool calling strategy and is on by default.

Displaying the timeline#

Because we get back a Timeline object, we can reach into its fields directly, with no parsing step in between:

for event in timeline.events:
    print(f"{event.year}:\t{event.description}")
1928:	Fisher Ames Baker Memorial Library opened with 240,000 volumes; building designed by Jens Fredrick Larson, modeled after Independence Hall, funded by George Fisher Baker in memory of his uncle.
1941:	First expansion of the library building.
1957:	Second expansion of the library building (completed in 1958).
1970:	Library received its one millionth volume.
1992:	John Berry and the Baker family donated $30 million for construction of a new facility, the Berry Library.
2000:	Baker-Berry Library complex opened, featuring the new Berry Library designed by Robert Venturi.
2002:	Completion of the Baker-Berry Library complex.

Each year is paired with its event, ready to use as ordinary Python objects.

Summary#

In this recipe, we have learned how to get structured data from a model without writing a format prompt and a parser by hand:

  • We declare the desired shape as a schema (here, two dataclasses) instead of describing a format in prose.

  • create_agent(response_format=...) returns the parsed data under response['structured_response'].

  • Under the hood, the agent automates the validate-and-retry loop we ran manually in the output parsing recipe: it asks the model to fill in the schema and re-prompts when the output does not fit.

  • A schema can be defined in several ways, including dataclasses, Pydantic models, TypedDict, or a JSON schema.