No EZGraph key required. Free production use. No runtime fee. Bring your own model provider. Run in your own infrastructure. Optional support is available.

The benchmark

One chatbot, built twice, measured

Everything on this page is reproducible from github.com/picoflowio/ezgraph-demo. Both implementations live side by side, share the same domain backend, and pass the same live evaluation. If you think a number here is wrong, the control group is right there to check it against. Start with the QuoteGraph walkthrough if you have not opened the tree yet.

EZ

quote-graph — the EZGraph implementation

Five conversational stages as five node classes, plus a graph definition and a state annotation. 617 normalized lines.

LG

quote-langgraph — the control group

The identical product on raw LangGraph, importing no EZGraph code: one 980-line graph class, a 177-line session store, a state annotation, and a types module. 1,283 normalized lines.

What "identical" means here

Same product: Sequoia Auto Insurance quoting. Five ordered stages — driver, vehicle, insurance history, coverage, quote — with 8 tools, 3 isolated history channels, real slot validation, gates that refuse to advance on invalid input, a backward transition from quote to coverage, a deterministic rating engine, 30-minute idle expiry, and a terminate_session path. Both share the same rating engine, prompts, vehicle catalog and clock helpers — 287 normalized lines each, which is how we know the delta is framework cost rather than a difference in scope.

4.1

The headline metric

Normalized executable lines: blank lines, comment-only lines, and import statements removed, so neither side is rewarded for terse imports or punished for documenting itself.

Scope EZGraph quote-graph LangGraph quote-langgraph Reduction
Framework-facing graph code 617 1,283 51.9% less
Shared domain backend2872870% — identical
NestJS controller477436.5% less
Graph code + controller6641,35751.1% less
Raw lines, nothing removed9241,58841.8% less

File by file

EZGraphLines LangGraphLines
quote-graph.ts49quote-langgraph.ts980
quote-graph.state.ts66quote-langgraph.state.ts83
nodes/driver.node.ts81quote-session-store.ts177
nodes/vehicle.node.ts110quote-types.ts43
nodes/history.node.ts79
nodes/coverage.node.ts71
nodes/quote.node.ts161
Total617Total1,283
The shape of the two trees is itself the finding. EZGraph's code is five node files of comparable size, each one a stage — you open the stage you care about. LangGraph's is a single 980-line class that owns every stage, the request boundary, the agent loop and the routing, plus a separate 177-line persistence module. The line count is a symptom; the structure is the disease.
4.2

Where the 666 lines went

The first four rows are the current, additive source artifacts behind the headline. The remaining rows are independently recounted repetition counts: they explain what makes the source artifacts different, but are not added again. The comparison does not count framework source on either side. The main reduction is recurring application plumbing, not insurance-domain logic.

ConcernLangGraphEZGraphDelta
Source artifacts — additive normalized lines
Main graph and all stage implementations980551−429
State definition and reducers8366−17
Session store — 3 backends + serialize/hydrate1770−177
Standalone domain type module430 types live with node state−43
Framework-facing graph code1,283617−666
Repetition counts — not additive line buckets
Manual Schema.parse(call.args) blocks80−8
Tool-name dispatch comparisons140−14
Explicit route: state writes160−16
Termination handling sites in graph code70 built-in node−7
Reducer helpers / channels2 × 170framework-owned
Domain tool declarations88same product scope
Explicit response sitesroute/branch updates5 go, 8 stay, 1 direct, 1 finishvisible contract

The expensive work is concentrated

The direct graph's 980-line file owns stage prompts, tool declarations and dispatch, model calls, message handling, validation, persistence boundary, and routing. The recounted repetition rows show the maintenance cost hidden inside that one artifact. EZGraph splits its corresponding 551 lines between a small graph definition and five stage files, while the framework owns the repeated turn mechanics.

The domain logic itself is unchanged between the two implementations, and that is the claim in one sentence: this is not a DSL that compresses business rules, it is a framework that deletes plumbing.

What this count does not claim

It does not claim that a line count measures quality, performance, or every LangGraph application. The quote application's provider, prompts, domain rules, and rating backend remain intentionally equivalent.

The source counts are useful because they are reproducible and scoped: they include application code, exclude the framework itself, and keep matching domain backend code out of the headline.

4.3

Modularity, measured as edit sites

Line counts are a proxy. The honest test of modularity is how many places you touch to add a sixth stage — say a DiscountsNode between coverage and quote. This is the number that predicts your team's velocity in month six.

LangGraph — 14 edit sites

#EditLocation
1Add "discounts" to the QuoteStage unionquote-langgraph.ts:52
2Add a stageMessageKey entry:92–98
3Declare the Zod schema const:100–160
4Wrap it in a tool() const:162–212
5Add a stageTools entry:214–225
6Bind a model in the constructor:267–273
7Write the discountsAgent method:473–526
8Write the discountsTools method:553–932
9addNode("discountsAgent", …):427–471
10addNode("discountsTools", …):427–471
11addConditionalEdges for the agent, plus one for the tools node:427–471
12Add an agentRoutes entry:957–964
13Add to QuoteLanggraphPhasestate.ts:12–18
14Add to QuoteLanggraphRoute, plus state channels for the new data:20–26, :39–100

Eleven of these are one-line edits to shared module-level structures, and nothing checks that they agree. A missed agentRoutes entry is a runtime KeyError on the first customer who reaches that stage — in production, not in CI.

EZGraph — 5 edit sites

#EditLocation
1Write nodes/discounts.node.ts — the whole stage: prompt, tools, handlers, validation, state saves, and responsesnew file
2Add a DiscountsNode?: NodeStateValue<…> keyquote-graph.state.ts
3Add [DiscountsNode, "quote-intake"] to historySpacesquote-graph.ts:37–44
4Add DiscountsNode to registerTurnNodes(...)quote-graph.ts:64–71
5Change the upstream go(QuoteNode) to go(DiscountsNode)coverage.node.ts

One new file plus four one-line edits — and edits 3, 4 and 5 take the class, so forgetting one is a compile() error or a type error rather than a production KeyError. Conversational routing lives in the explicit go(TargetNode) response returned by the upstream node.

This is what "modular" means concretely: a stage is a file. Its prompt, tool schemas, handlers, validation, persisted slice and transition all live in one place, and adding one does not require editing five shared tables that nothing cross-checks.
4.4

Boilerplate patterns, counted

Exact ripgrep counts over the two trees. These are the repetitions that grow every time you add a tool or a stage.

PatternLangGraphEZGraph
Manual Schema.parse(call.args) in try/catch80
Tool-name dispatch checks — call.name === / !==140
terminate_session handling sites70 framework node
Explicit route: writes in application code160
Hand-written reducers2 helpers × 17 channels0
Hand-written session store implementations30 4 ship with it
Tool handler declarations8 in if/else chains8 @Tool decorators
Quit checks in application code5 inside dispatch chains5 quitRequested guards
The terminate_session row is the contract argument in one line. In LangGraph it is a schema, a tool() wrapper, an entry in all five stageTools lists, and a handler branch in all five tool nodes — seven sites, and a sixth stage makes it nine. In EZGraph every ConversationNode inherits the handler, TerminateSessionNode supplies the definition once, quit() routes through it, and compile() fails loudly if you forget to register it. Seven sites become zero, and the failure mode moves from "one stage silently can't be quit" to a build error.
A bug the benchmark found in its own control group

Writing the LangGraph version honestly surfaced a real defect: the hand-rolled agent loop services only one tool call per pass. Give it a genuine multi-tool batch and it appends one ToolMessage and drops the rest — a conversation shape providers reject. Nobody wrote that bug on purpose. It is what happens when the loop is application code that has to be reimplemented per stage, and it is the strongest argument on this page for moving the loop into a framework where it gets written once and tested once.

Method

How to reproduce every number

A benchmark you cannot re-run is an advertisement. Here is the exact normalizer.

normalize.js drops blanks, comments, and imports
function normalize(lines) {
  let total = 0, inBlockComment = false, inImport = false;
  for (const raw of lines) {
    const line = raw.trim();
    if (inBlockComment) { if (line.includes("*/")) inBlockComment = false; continue; }
    if (inImport) { if (/from\s+["'].*["']/.test(line) || /;\s*$/.test(line)) inImport = false; continue; }
    if (line === "") continue;
    if (line.startsWith("//")) continue;
    if (line.startsWith("/*")) { if (!line.includes("*/")) inBlockComment = true; continue; }
    if (line.startsWith("*")) continue;
    if (/^import\b/.test(line)) {
      if (!/from\s+["'].*["']\s*;?\s*$/.test(line)) inImport = true;
      continue;
    }
    total += 1;
  }
  return total;
}

Pattern counts are plain ripgrep over the two trees — for example, the manual argument-parse count:

$ rg -c 'Schema\.parse\(call\.args\)' src/graphs/quote-langgraph/

Honesty notes

  • Same authors, both sides. A LangGraph specialist would write the control group somewhat tighter. What they could not do is delete the session store (177), the request boundary (187), the tool dispatch (113), the agent loop (79) or the reducers (64) — 620 lines of pure plumbing that a tighter style trims by a few percent at best.
  • The 177-line session store is a fair charge, not a strawman. A LangGraph app could use a checkpointer instead — but then it inherits the schema-migration and retention problems in pain point 01, which is precisely why the control group writes an application-level store.
  • EZGraph's 617 lines exclude the framework itself. That is the point of a framework — but it does mean you are trading code you own for code you don't. Read the limits before deciding that trade is worth it.
  • One data point. One five-stage guided conversation. Generalize from the per-concern table, not from 51.9%: if your app needs persistence, a request boundary, tool dispatch, an agent loop and state reducers, expect a similar result. If it needs none of them, don't adopt this.
References

Primary sources

The pain points on this site are not invented. Each is documented by LangGraph's own documentation, its v1 feedback thread, or a public production write-up.

Credit where it is due. LangGraph is a genuinely good low-level orchestration runtime, and EZGraph runs on it — every graph here compiles to a real StateGraph and LangGraph executes it. Nothing on this page is a claim that LangGraph is doing something wrong. It is a claim that a guided enterprise chatbot needs an application layer above it, and that you should not have to write that layer five times per company.