Walk a real graph, not a toy
QuoteGraph collects driver, vehicle, insurance history, and coverage details, rates them deterministically, then presents and accepts a quote. It is the same application used in the demo repository and it follows the current EZGraph response contract end to end.
Read and run the implementation in ezgraph-demo. The site explains the design; the repository is the executable reference.
-
Run the deterministic suite first
$git clone https://github.com/picoflowio/ezgraph-demo.git$cd ezgraph-demo && yarn$yarn test:quote-graphThe normal test target uses scripted models and exercises validation, state replacement, transitions, rating, and acceptance without calling a provider. Run the live scenario only when you opt in:
$USE_ENV=1 yarn test2:quote-graph -
Start with state ownership
Each durable fact belongs to the node that validates it. The registry is converted into the annotated LangGraph state once, in the graph-state module.
quote-graph.state.tsexport type QuoteGraphNodes = { DriverNode?: NodeStateValue<{ driver?: DriverProfile }>; VehicleNode?: NodeStateValue<{ resolvedVehicleId?: string; vehicle?: VehicleUse }>; HistoryNode?: NodeStateValue<{ history?: InsuranceHistory }>; CoverageNode?: NodeStateValue<{ coverage?: CoverageSelection }>; QuoteNode?: NodeStateValue<{ tiers?: QuoteTier[]; acceptedTier?: QuoteTierName }>; }; export const QuoteGraphState = createGraphStateAnnotation( DriverNode.name, () => ({} as QuoteGraphNodes), ); -
A collection node validates, saves, and moves on
The driver node validates model input in code. A rejection remains in the same node; an accepted driver is immediately persisted and transitions to vehicle collection.
nodes/driver.node.tsexport class DriverNode extends ConversationNode<QuoteGraphStateType> { @Tool("capture_driver") async captureDriver(input: DriverInput): Promise<ToolResponse> { const driver = validateDriver(input); // deterministic domain validation if (!driver.ok) return stay(JSON.stringify(driver.error)); this.saveState({ driver: driver.value }); return go(VehicleNode); } }This is intentionally direct: there is no separately maintained turn context, no synthetic handoff payload, and no automatic router to configure.
-
Keep policy and presentation code-owned
When coverage becomes valid, the graph can save the rate into a target channel and steer the model into quote presentation. Once a quote needs exact amounts and explicit approval, the response is built in code.
nodes/coverage.node.tsthis.saveState({ coverage }); const tiers = rateQuote(driver, vehicle, history, coverage); return go(QuoteNode).withState({ tiers });nodes/quote.node.tsthis.saveState({ acceptedTier: tier, referenceNumber }); return direct(renderQuoteConfirmation(tier, referenceNumber)); -
Topology is declared in one place
quote-graph.tsgraph.registerTurnNodes( DriverNode, VehicleNode, HistoryNode, CoverageNode, QuoteNode, TerminateSessionNode, ); graph.addEdge(TerminateSessionNode, END);Every conversational node, including the built-in termination node, is registered. Node-returned
go()responses decide ordinary transitions.
The response contract scales to corrections and side effects
Hotel, support, and invoice examples use the same primitives for corrections, approval gates, attachments, cleanup, and terminal actions.