LangGraph Tutorial (Python): parsing structured output for advanced developers
This tutorial shows how to build a LangGraph workflow that turns messy model output into validated Python objects. You need this when your agent must return strict JSON-like data for downstream systems, not free-form text.
What You'll Need
- •Python 3.10+
- •
langgraph - •
langchain-openai - •
langchain-core - •
pydantic - •An OpenAI API key set as
OPENAI_API_KEY - •Basic familiarity with LangGraph state, nodes, and edges
Install the packages:
pip install langgraph langchain-openai langchain-core pydantic
Step-by-Step
- •Start by defining the shape of the output you want. In production, structured output should be explicit, typed, and validated before anything else touches it.
from typing import TypedDict, Optional
from pydantic import BaseModel, Field
class TicketSummary(BaseModel):
category: str = Field(..., description="Support category")
priority: str = Field(..., description="low, medium, or high")
customer_id: Optional[str] = Field(None, description="Customer identifier")
summary: str = Field(..., description="Short issue summary")
class GraphState(TypedDict):
text: str
parsed: dict | None
error: str | None
- •Next, create a node that asks the model to emit structured data. The key detail here is using a parser after generation so your graph can fail fast on malformed output instead of passing garbage downstream.
import os
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import PydanticOutputParser
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
parser = PydanticOutputParser(pydantic_object=TicketSummary)
prompt = ChatPromptTemplate.from_messages([
("system", "Extract support ticket fields exactly."),
("human", "{text}\n\n{format_instructions}")
])
def parse_ticket(state: GraphState):
chain = prompt | llm | parser
result = chain.invoke({
"text": state["text"],
"format_instructions": parser.get_format_instructions(),
})
return {"parsed": result.model_dump(), "error": None}
- •Add a fallback node for parsing failures. In real systems, you want to capture the raw failure path so you can retry, alert, or route to human review.
def handle_parse_error(state: GraphState):
return {
"parsed": None,
"error": state["error"] or "Unknown parsing error",
}
- •Wire the graph with conditional routing. This pattern lets you branch based on whether parsing succeeded without mixing control flow into your business logic.
from langgraph.graph import StateGraph, START, END
def route_after_parse(state: GraphState):
return "ok" if state["parsed"] is not None else "fail"
builder = StateGraph(GraphState)
builder.add_node("parse_ticket", parse_ticket)
builder.add_node("handle_parse_error", handle_parse_error)
builder.add_edge(START, "parse_ticket")
builder.add_conditional_edges(
"parse_ticket",
route_after_parse,
{
"ok": END,
"fail": "handle_parse_error",
},
)
builder.add_edge("handle_parse_error", END)
graph = builder.compile()
- •Run it with real input and inspect the parsed object. If the model follows instructions, you get a validated Python dictionary; if it doesn't, your error path stays explicit.
if __name__ == "__main__":
sample_text = """
Customer 48291 says their payment failed twice.
They need this fixed today because billing is blocked.
Please mark as urgent and assign to payments.
"""
result = graph.invoke({"text": sample_text, "parsed": None, "error": None})
print(result)
Testing It
Run the script with a valid OpenAI key in your environment and confirm the output contains a populated parsed field. Then intentionally break the input or lower model quality and verify that malformed outputs do not silently propagate.
You should also test schema changes by adding a required field to TicketSummary. That forces you to see whether your extraction prompt and validation logic still hold under stricter contracts.
For deeper confidence, wrap this graph in unit tests and assert on both branches:
- •successful parse returns
parsedwith all required fields - •failure path returns
errorand no parsed payload
Next Steps
- •Add retries with a repair prompt when parsing fails
- •Replace
PydanticOutputParserwith tool calling for stricter model contracts - •Persist parsed objects into Postgres or a queue for downstream services
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
By Cyprian Aarons, AI Consultant at Topiax.
Want the complete 8-step roadmap?
Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.
Get the Starter Kit