Answers with a fixed shape
A TypedDict describes your state but never checks it. If you would rather be told when something is wrong, use a Pydantic model instead.
Everything you have written so far used TypedDict, which is a description and nothing more. Put a number where a string belongs and it will sail through and break somewhere later. A Pydantic model is the same description with the checking turned on.
from pydantic import BaseModel
from langgraph.graph import StateGraph, START
class State(BaseModel):
ticket: str
urgent: bool = False
def triage(state: State):
return {"urgent": "charge" in state.ticket}
builder = StateGraph(State)
builder.add_node("triage", triage)
builder.add_edge(START, "triage")
print(builder.compile().invoke({"ticket": "charged twice"}))Two differences from a TypedDict. The node reads state.ticket with a dot instead of square brackets, and urgent has a default, so you did not have to pass it in.
What you get for it
from pydantic import BaseModel, ValidationError
from langgraph.graph import StateGraph, START
class State(BaseModel):
ticket: str
def triage(state: State):
return {"ticket": state.ticket.upper()}
builder = StateGraph(State)
builder.add_node("triage", triage)
builder.add_edge(START, "triage")
try:
builder.compile().invoke({"ticket": 12345})
except ValidationError as e:
print("rejected at the door:", e.errors()[0]["msg"])The run stopped before any node ran. With a TypedDict that same call would have reached triage and failed on .upper(), several steps away from the thing that was actually wrong.
Which to use
| Use | When |
|---|---|
TypedDict | Most of the time. It is lighter, and the state is coming from your own code. |
BaseModel | When the state comes from outside, from a web request or a form, and you want it checked at the edge. |
TypedDict for that reason.The other kind of fixed shape
There is a related idea worth knowing the name of. You can also ask the model to answer in a fixed shape rather than in prose, by handing with_structured_output a schema.
from pydantic import BaseModel
class Triage(BaseModel):
category: str
urgent: bool
sorted_ticket = model.with_structured_output(Triage).invoke(messages)
print(sorted_ticket.category, sorted_ticket.urgent)That one has no output on this page, because our stand-in model cannot do it. It works by binding the schema as a tool, so it needs a model that really understands tools. Come back to it in lesson 30, once you have a real model.
- Add a third field with a default and invoke without passing it.
- Return the wrong type from
triageand see where the error appears. - Change the state to a
TypedDictand confirm the bad ticket is no longer rejected.
This is what real progress feels like.