TypedDict for a conversation

Welcome! I'm Param. In this workshop we are going to model a multi-turn conversation as a LangGraph state machine. The demo is a booking assistant, but the pattern is the real thing you are here to learn. Every slot-filling chat (intake forms, travel booking, support triage) follows the same recipe once you see it.

A single-turn LLM call is easy. Multi-turn is where things fall apart. The conversation needs to remember what was said, know what slots are still missing, and decide where to go next. A state machine makes every one of those things explicit. No more prompt stuffing, no more guessing what the bot remembers.

terminal
bash
# Clone the workshop repository
git clone https://github.com/learnwithparam/healthcare-booking-langgraph.git
cd healthcare-booking-langgraph

# One command to set up everything
make dev

This clones the repo, creates a virtual environment with uv, installs LangGraph and FastAPI, and starts the server.

booking_graph.py
python
from typing import TypedDict, Optional, List, Dict, Any

class BookingState(TypedDict, total=False):
    """Shared state passed between every node in the graph."""
    # Conversation
    user_message: str
    thread_id: str
    patient_id: Optional[str]
    history: List[Dict[str, str]]

    # Derived fields, filled by nodes as the turn progresses
    intent: Optional[str]          # book | list | cancel | smalltalk | unknown
    symptom: Optional[str]
    specialty: Optional[str]
    doctor: Optional[Dict[str, Any]]
    slot_options: List[str]
    slot: Optional[str]
    status: str                    # gathering | ready_to_confirm | confirmed | ended

    # Output
    assistant_reply: str
    events: List[Dict[str, Any]]

TypedDict with total=False means every field is optional. Nodes fill in what they can, leave the rest alone. The state is the single source of truth for the turn.

State shape across one turn

How slots and status fields evolve as the turn moves from intent routing to confirmation.

LangGraph passes state between nodes as a plain dict. TypedDict gives you static type checking with zero runtime overhead and zero conversion. Pydantic would re-validate on every hop between nodes, which is wasted work here because the state fields change frequently. Dataclasses would force every node to construct a new instance. TypedDict fits the "mutable shared dict" semantics of a graph run exactly.

Quiz: Quiz

Loading practice…