Structured Outputs and Validation
Understand how to design AI workflows that reliably produce structured outputs using extraction, validation, and routing nodes. Explore methods to handle model output drift by implementing prompt constraints, defensive parsing, and error signaling to maintain workflow stability and user experience. Gain practical skills to build feedback classifiers with layered validation and graceful fallbacks in LangGraph.
We'll cover the following...
In a chat application, a sloppy model response is a UX problem. The user sees something vague or oddly formatted and moves on. The app continues working.
In a graph workflow, a sloppy model response is a system failure. If a model node returns free-form text when a downstream node expects a JSON object with a category key, that downstream node raises a KeyError. If a routing function expects the category to be one of four known values and the model returns "Feature Request" instead of "feature_request", the routing function falls through to an unhandled case. If a tool node receives malformed input it cannot parse, the tool call silently fails or throws an exception that stops execution entirely.
The failures cascade. One unpredictable model output can break every node that runs after it.
This is why structure matters more in workflows than in chat. In a workflow, model outputs are not final answers; they are inputs to other nodes. Every downstream node is a consumer of the model’s output, and every consumer expects a specific shape.
Why models drift
Language models are probabilistic. The same prompt with the same input does not always produce the same output. On most runs, a well-written prompt reliably produces the expected format. On some runs, the model adds an explanation before the JSON object. On others, it uses slightly different field names. On rare runs, it produces something entirely unexpected.
The longer the workflow and the more nodes depend on a particular output shape, the more consequential any single drift becomes. We cannot eliminate drift entirely, but we can build a validation layer that catches it before it propagates.
What we are building
We will build a feedback classifier: a workflow that takes raw user feedback text and extracts four structured fields: a category, a sentiment, a priority level, and a one-sentence summary. Those fields then drive routing to a category-specific handler.
The workflow has two layers of protection. First, the extraction node wraps the model call and JSON parsing in a try/except block, writing a parse_error if anything goes wrong. Second, the validation node checks that the extracted fields are present and contain one of the allowed values. If either layer detects a problem, execution routes to a graceful fallback instead of crashing.
State design
The state schema separates the raw input from the extracted fields and uses parse_error as the signal that travels between the extraction, validation, and routing layers.
from typing import TypedDictclass FeedbackState(TypedDict):# Input — provided at invocation, never modifiedraw_feedback: str# Control — written by extraction and validation nodescategory: str # "bug", "feature_request", "general", or "billing"sentiment: str # "positive", "negative", or "neutral"priority: str # "high", "medium", or "low"parse_error: str # non-empty string if extraction or validation failed# Output — written by extraction and handler nodessummary: strresponse: str
Line 6: The only input field. All extracted fields start as empty strings at invocation.
Lines 9–12: Four control fields extracted from the raw text by the model. Each has a fixed set of allowed values.
Line 13: The error signal. An empty string means all is well. A non-empty string means something went wrong and the fallback handler should run.
Lines 16–17:
summaryis written by the extraction node.responseis written by whichever ...