LangGraph Tutorial (Python): parsing structured output for intermediate developers

By Cyprian AaronsUpdated 2026-04-22
langgraphparsing-structured-output-for-intermediate-developerspython

This tutorial shows you how to build a LangGraph workflow in Python that takes messy model output and turns it into validated structured data. You need this when your agent has to return predictable fields for downstream systems like case management, policy lookup, claims triage, or database writes.

What You'll Need

  • Python 3.10+
  • langgraph
  • langchain-core
  • langchain-openai
  • An OpenAI API key in OPENAI_API_KEY
  • Basic familiarity with LangGraph state, nodes, and edges

Install the packages:

pip install langgraph langchain-core langchain-openai

Step-by-Step

  1. Define the structured schema you want the model to produce.

Use Pydantic so your output contract is explicit. In insurance or banking workflows, this is what keeps the agent from returning half-baked JSON strings.

from typing import Literal
from pydantic import BaseModel, Field

class TicketSummary(BaseModel):
    customer_name: str = Field(description="Name of the customer")
    issue_type: Literal["billing", "claims", "login", "fraud"]
    urgency: Literal["low", "medium", "high"]
    summary: str = Field(description="Short summary of the issue")
  1. Build a model node that asks for raw text, then parses it into the schema.

with_structured_output() is the simplest path when you want LangChain to handle parsing for you. The key point is that your graph node should return a Python object, not a string blob.

import os
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    model="gpt-4o-mini",
    temperature=0,
    api_key=os.environ["OPENAI_API_KEY"],
)

structured_llm = llm.with_structured_output(TicketSummary)

def parse_ticket(state: dict) -> dict:
    text = state["raw_text"]
    parsed = structured_llm.invoke(
        f"Extract a ticket summary from this message:\n\n{text}"
    )
    return {"ticket": parsed}
  1. Define the LangGraph state and wire up a minimal graph.

For a single-node parser, TypedDict keeps things simple and readable. The graph input is plain text; the output is validated structured data stored under ticket.

from typing import TypedDict, Optional
from langgraph.graph import StateGraph, START, END

class GraphState(TypedDict):
    raw_text: str
    ticket: Optional[TicketSummary]

builder = StateGraph(GraphState)
builder.add_node("parse_ticket", parse_ticket)
builder.add_edge(START, "parse_ticket")
builder.add_edge("parse_ticket", END)

graph = builder.compile()
  1. Invoke the graph with real text and inspect the parsed object.

The result is a typed Pydantic object, which means you can safely access fields without manually decoding JSON. This is where structured output starts paying off in production.

result = graph.invoke(
    {
        "raw_text": (
            "Hi support, I'm Maria Chen. My card was charged twice "
            "for my last premium payment and I need this fixed today."
        )
    }
)

ticket = result["ticket"]
print(ticket)
print(ticket.customer_name)
print(ticket.issue_type)
print(ticket.urgency)
print(ticket.summary)
  1. Add basic validation handling for bad outputs or missing fields.

Even with structured output, you should expect occasional failures from malformed input or model issues. Catch them at the edge of your graph so downstream systems only see clean data or an explicit error state.

from typing import Any

class SafeState(TypedDict):
    raw_text: str
    ticket: Optional[TicketSummary]
    error: Optional[str]

def safe_parse_ticket(state: dict) -> dict[str, Any]:
    try:
        parsed = structured_llm.invoke(
            f"Extract a ticket summary from this message:\n\n{state['raw_text']}"
        )
        return {"ticket": parsed, "error": None}
    except Exception as e:
        return {"ticket": None, "error": str(e)}
  1. Route based on whether parsing succeeded.

This pattern is what you use when one branch sends valid tickets to CRM creation and another branch sends failures to human review. It keeps your graph deterministic instead of burying control flow inside prompt text.

from langgraph.graph import StateGraph, START, END

def route_after_parse(state: SafeState) -> str:
    return "success" if state["ticket"] else "failure"

builder = StateGraph(SafeState)
builder.add_node("parse_ticket", safe_parse_ticket)
builder.add_node("success_handler", lambda state: state)
builder.add_node("failure_handler", lambda state: state)

builder.add_edge(START, "parse_ticket")
builder.add_conditional_edges(
    "parse_ticket",
    route_after_parse,
    {
        "success": "success_handler",
        "failure": "failure_handler",
    },
)
builder.add_edge("success_handler", END)
builder.add_edge("failure_handler", END)

safe_graph = builder.compile()

Testing It

Run the graph with a few different inputs and check that each one maps to the correct enum values in your schema. Try one clean billing message, one claims message, and one ambiguous message that should still parse into a valid summary.

If you want stronger guarantees, add assertions around issue_type and urgency so invalid labels fail fast during testing. For example, verify that every returned object is an instance of TicketSummary before it reaches your next node.

Also test failure paths by passing garbage input or forcing a malformed response through your validation layer. In production, those are the cases that matter because they tell you whether your fallback routing actually works.

Next Steps

  • Add a second node that enriches the parsed ticket with customer metadata from your database.
  • Replace the simple invoke() call with tool-calling plus structured extraction for more complex workflows.
  • Learn LangGraph conditional edges in more depth so you can branch on confidence, validation errors, or business rules.

Keep learning

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

Related Guides