Ship and recap
The last step ties everything to an API. One POST endpoint, one thread_id, one graph behind it. From the outside it looks like a plain chat API. From the inside, every turn runs through the router, hits the right node, and checkpoints its state.
from fastapi import APIRouter, HTTPException
from models import ChatRequest, ChatResponse
from service import service
router = APIRouter(prefix="/flight-booking", tags=["flight-booking"])
@router.post("/chat", response_model=ChatResponse)
async def chat(request: ChatRequest):
"""
Run one conversational turn through the booking graph. Pass the same
thread_id across turns to continue an existing booking flow.
"""
try:
result = await service.chat(request.thread_id, request.message)
return ChatResponse(**result)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Chat failed: {e}")The API is one endpoint. Send a thread_id and a message, get back a reply, a stage, and a state snapshot. The client only needs to remember the thread_id.
# Start a booking
curl -s -X POST http://localhost:8000/flight-booking/chat \
-H "Content-Type: application/json" \
-d '{"thread_id":"demo-1","message":"I want to fly from LHR to JFK"}' | jq
# Follow up on the same thread
curl -s -X POST http://localhost:8000/flight-booking/chat \
-H "Content-Type: application/json" \
-d '{"thread_id":"demo-1","message":"next Friday, 2 passengers"}' | jq
# Pick an option
curl -s -X POST http://localhost:8000/flight-booking/chat \
-H "Content-Type: application/json" \
-d '{"thread_id":"demo-1","message":"2"}' | jqThree turns on one thread_id. The graph remembers the origin from turn one, the date and passengers from turn two, and generates a booking on turn three. Close the terminal, reuse the same thread_id an hour later, and the state is still there.
Checkpoint: Stateful agent patterns recap
Loading practice…