Install any skill in seconds. Free to start, no credit card required.
Get Started Free →使用 StateGraph、节点、边、START/END 节点和 Command API 构建图,结合控制流与状态更新
.claude/skills/majiayu000-langgraph-graph-api/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 42% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 41% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 124% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 203% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 101% | 0% |
name: langgraph-graph-api description: 使用 StateGraph、节点、边、START/END 节点和 Command API 构建图,结合控制流与状态更新
LangGraph Graph API 允许您将 Agent 工作流定义为由节点(函数)和边(控制流)组成的有向图。这提供了对 Agent 编排的细粒度控制。
核心组件:
| 需求 | 边类型 | 何时使用 | |------|-----------|-------------| | 始终转到同一节点 | addEdge() | 固定、确定性流 | | 基于状态路由 | addConditionalEdges() | 动态分支逻辑 | | 扩散到多个节点 | Send API | Map-reduce、并行执行 | | 更新状态并路由 | Command | 在单个节点中组合逻辑 |
LangGraph 使用受 Google Pregel 启发的消息传递模型:
节点是异步函数,它们:
typescriptconst myNode = async (state: State): Promise<Partial<State>> => { // 节点只是异步函数! return { key: "updated_value" }; };
| 边类型 | 描述 | 示例 | |-----------|-------------|---------| | 静态 | 始终路由到同一节点 | addEdge("A", "B") | | 条件 | 基于状态/逻辑路由 | addConditionalEdges("A", router) | | 动态 (Send) | 扩散到多个节点 | new Send("worker", {...}) | | Command | 状态更新 + 路由 | new Command({ goto: "B" }) |
typescriptimport { StateGraph, StateSchema, START, END } from "@langchain/langgraph"; import { z } from "zod"; // 1. 定义状态 const State = new StateSchema({ input: z.string(), output: z.string(), }); // 2. 定义节点 const processInput = async (state: typeof State.State) => { return { output: `Processed: ${state.input}` }; }; const finalize = async (state: typeof State.State) => { return { output: state.output.toUpperCase() }; }; // 3. 构建图 const graph = new StateGraph(State) .addNode("process", processInput) .addNode("finalize", finalize) .addEdge(START, "process") // 入口点 .addEdge("process", "finalize") // 静态边 .addEdge("finalize", END) // 出口点 .compile(); // 4. 执行 const result = await graph.invoke({ input: "hello" }); console.log(result.output); // "PROCESSED: HELLO"
typescriptimport { StateGraph, StateSchema, ConditionalEdgeRouter, START, END } from "@langchain/langgraph"; import { z } from "zod"; const State = new StateSchema({ query: z.string(), route: z.string(), result: z.string().optional(), }); const classify = async (state: typeof State.State) => { if (state.query.toLowerCase().includes("weather")) { return { route: "weather" }; } return { route: "general" }; }; const weatherNode = async (state: typeof State.State) => { return { result: "Sunny, 72°F" }; }; const generalNode = async (state: typeof State.State) => { return { result: "General response" }; }; // 路由器函数 const routeQuery: ConditionalEdgeRouter<typeof State, "weather" | "general"> = (state) => { return state.route as "weather" | "general"; }; const graph = new StateGraph(State) .addNode("classify", classify) .addNode("weather", weatherNode) .addNode("general", generalNode) .addEdge(START, "classify") // 基于状态的条件边 .addConditionalEdges( "classify", routeQuery, ["weather", "general"] // 可能的目标 ) .addEdge("weather", END) .addEdge("general", END) .compile(); const result = await graph.invoke({ query: "What's the weather?" });
typescriptimport { StateGraph, StateSchema, Command, START, END } from "@langchain/langgraph"; import { z } from "zod"; const State = new StateSchema({ count: z.number(), result: z.string(), }); const nodeA = async (state: typeof State.State) => { const newCount = state.count + 1; if (newCount > 5) { // 转到 nodeC return new Command({ update: { count: newCount, result: "Going to C" }, goto: "nodeC" }); } else { // 转到 nodeB return new Command({ update: { count: newCount, result: "Going to B" }, goto: "nodeB" }); } }; const nodeB = async (state: typeof State.State) => { return { result: `B executed, count=${state.count}` }; }; const nodeC = async (state: typeof State.State) => { return { result: `C executed, count=${state.count}` }; }; const graph = new StateGraph(State) .addNode("nodeA", nodeA, { ends: ["nodeB", "nodeC"] }) // 指定可能的路由 .addNode("nodeB", nodeB) .addNode("nodeC", nodeC) .addEdge(START, "nodeA") .addEdge("nodeB", END) .addEdge("nodeC", END) .compile(); const result1 = await graph.invoke({ count: 0 }); console.log(result1.result); // "B executed, count=1" const result2 = await graph.invoke({ count: 5 }); console.log(result2.result); // "C executed, count=6"
typescriptimport { StateGraph, StateSchema, Send, ReducedValue, START, END } from "@langchain/langgraph"; import { z } from "zod"; const State = new StateSchema({ items: z.array(z.string()), results: new ReducedValue( z.array(z.string()).default(() => []), { reducer: (current, update) => current.concat(update) } ), final: z.string().optional(), }); const fanOut = (state: typeof State.State) => { // 将每个项目发送到工作节点 return state.items.map(item => new Send("worker", { item }) ); }; const worker = async (state: { item: string }) => { // 处理单个项目 return { results: [`Processed: ${state.item}`] }; }; const aggregate = async (state: typeof State.State) => { // 合并结果 return { final: state.results.join(", ") }; }; const graph = new StateGraph(State) .addNode("worker", worker) .addNode("aggregate", aggregate) .addConditionalEdges(START, fanOut, ["worker"]) .addEdge("worker", "aggregate") .addEdge("aggregate", END) .compile(); const result = await graph.invoke({ items: ["A", "B", "C"] }); console.log(result.final); // "Processed: A, Processed: B, Processed: C"
typescriptimport { StateGraph, StateSchema, ConditionalEdgeRouter, START, END } from "@langchain/langgraph"; import { z } from "zod"; const State = new StateSchema({ count: z.number(), maxIterations: z.number(), }); const increment = async (state: typeof State.State) => { return { count: state.count + 1 }; }; const shouldContinue: ConditionalEdgeRouter<typeof State, "increment"> = (state) => { if (state.count >= state.maxIterations) { return END; } return "increment"; }; const graph = new StateGraph(State) .addNode("increment", increment) .addEdge(START, "increment") .addConditionalEdges("increment", shouldContinue, ["increment", END]) .compile(); const result = await graph.invoke({ count: 0, maxIterations: 5 }); console.log(result.count); // 5
typescriptimport { MemorySaver } from "@langchain/langgraph"; const checkpointer = new MemorySaver(); const graph = new StateGraph(State) .addNode("nodeA", nodeA) .addEdge(START, "nodeA") .addEdge("nodeA", END) .compile({ checkpointer, // 启用持久化 interruptBefore: ["nodeA"], // 在节点之前设置断点 interruptAfter: ["nodeA"], // 在节点之后设置断点 });
✅ 定义自定义节点(任何异步函数) ✅ 在节点之间添加静态边 ✅ 添加带自定义逻辑的条件边 ✅ 使用 Command 进行组合的状态/路由 ✅ 使用条件终止创建循环 ✅ 使用 Send API 扩散(map-reduce) ✅ 设置断点(interruptBefore/After) ✅ 自定义状态模式 ✅ 指定检查点器用于持久化
❌ 修改 START/END 节点行为 ❌ 更改超级步执行模型 ❌ 修改消息传递协议 ❌ 覆盖图编译逻辑 ❌ 绕过状态更新机制
typescript// ❌ 错误 const builder = new StateGraph(State).addNode("node", func); await builder.invoke({ input: "test" }); // 错误! // ✅ 正确 const graph = builder.compile(); await graph.invoke({ input: "test" });
typescript// ❌ 错误 - "missingNode" 未添加到图中 const router = (state) => "missingNode"; builder.addConditionalEdges("nodeA", router, ["missingNode"]); // ✅ 正确 - 添加所有可能的目标 builder.addNode("missingNode", func); builder.addConditionalEdges("nodeA", router, ["missingNode"]);
ends 参数typescript// ❌ 错误 - 未指定 ends const nodeA = async (state) => { return new Command({ goto: "nodeB" }); }; builder.addNode("nodeA", nodeA); // 使用 Command 时出错! // ✅ 正确 - 指定可能的目标 builder.addNode("nodeA", nodeA, { ends: ["nodeB", "nodeC"] });
typescript// ❌ 错误 - 无限循环 builder .addEdge("nodeA", "nodeB") .addEdge("nodeB", "nodeA"); // 无路可出! // ✅ 正确 - 到 END 的条件边 const shouldContinue = (state) => { if (state.count > 10) return END; return "nodeB"; }; builder.addConditionalEdges("nodeA", shouldContinue, ["nodeB", END]);
typescript// ❌ 错误 - 结果将被覆盖 const State = new StateSchema({ results: z.array(z.string()), // 没有 reducer! }); // ✅ 正确 - 使用 ReducedValue import { ReducedValue } from "@langchain/langgraph"; const State = new StateSchema({ results: new ReducedValue( z.array(z.string()).default(() => []), { reducer: (current, update) => current.concat(update) } ), });
typescript// ❌ 错误 - 无法路由回 START builder.addEdge("nodeA", START); // 错误! // ✅ 正确 - 使用命名的入口节点代替 builder.addNode("entry", entryFunc); builder.addEdge(START, "entry"); builder.addEdge("nodeA", "entry"); // 可以
typescript// ❌ 错误 - 忘记 await const result = graph.invoke({ input: "test" }); console.log(result.output); // undefined (Promise!) // ✅ 正确 const result = await graph.invoke({ input: "test" }); console.log(result.output); // 可以工作!
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 26,657 | 18,531 | -30% | 1 | 1 | 0% | 4,540 | 6,453 | +42% | 0 | 0 | — |
case-02 | fail→pass | 26,187 | 14,581 | -44% | 1 | 1 | 0% | 4,084 | 5,756 | +41% | 0 | 0 | — |
case-03 | fail→pass | 14,316 | 8,207 | -43% | 1 | 1 | 0% | 2,280 | 5,105 | +124% | 0 | 0 | — |
case-04 | pass→pass | 11,180 | 8,610 | -23% | 1 | 1 | 0% | 2,025 | 4,998 | +147% | 0 | 0 | — |
case-05 | fail→pass | 12,743 | 14,473 | +14% | 1 | 1 | 0% | 1,745 | 5,291 | +203% | 0 | 0 | — |
case-06 | fail→pass | 14,942 | 6,847 | -54% | 1 | 1 | 0% | 2,181 | 4,388 | +101% | 0 | 0 | — |
case-07 | fail→pass | 9,871 | 15,708 | +59% | 1 | 1 | 0% | 1,803 | 5,046 | +180% | 0 | 0 | — |
case-08 | pass→pass | 10,410 | 10,814 | +4% | 1 | 1 | 0% | 2,046 | 5,215 | +155% | 0 | 0 | — |
case-09 | pass→pass | 10,913 | 12,708 | +16% | 1 | 1 | 0% | 2,069 | 5,491 | +165% | 0 | 0 | — |
case-10 | pass→pass | 12,076 | 6,585 | -45% | 1 | 1 | 0% | 1,769 | 4,428 | +150% | 0 | 0 | — |
case-11 | pass→pass | 5,802 | 4,406 | -24% | 1 | 1 | 0% | 1,042 | 4,123 | +296% | 0 | 0 | — |
case-12 | pass→pass | 12,792 | 10,284 | -20% | 1 | 1 | 0% | 2,201 | 4,976 | +126% | 0 | 0 | — |
case-13 | pass→pass | 12,138 | 10,923 | -10% | 1 | 1 | 0% | 2,172 | 5,516 | +154% | 0 | 0 | — |
case-14 | pass→pass | 14,400 | 7,555 | -48% | 1 | 1 | 0% | 2,489 | 4,885 | +96% | 0 | 0 | — |
case-15 | pass→pass | 7,521 | 5,213 | -31% | 1 | 1 | 0% | 1,469 | 4,490 | +206% | 0 | 0 | — |
case-16 | pass→pass | 14,522 | 10,399 | -28% | 1 | 1 | 0% | 2,718 | 5,373 | +98% | 0 | 0 | — |
case-17 | pass→pass | 12,086 | 9,335 | -23% | 1 | 1 | 0% | 2,105 | 5,141 | +144% | 0 | 0 | — |
case-18 | fail→pass | 16,118 | 8,979 | -44% | 1 | 1 | 0% | 2,877 | 5,376 | +87% | 0 | 0 | — |
case-19 | pass→pass | 13,628 | 12,663 | -7% | 1 | 1 | 0% | 2,275 | 5,591 | +146% | 0 | 0 | — |
case-20 | pass→pass | 15,383 | 7,949 | -48% | 1 | 1 | 0% | 2,584 | 4,901 | +90% | 0 | 0 | — |
case-21 | pass→pass | 9,972 | 6,860 | -31% | 1 | 1 | 0% | 1,528 | 4,893 | +220% | 0 | 0 | — |
case-22 | pass→pass | 8,752 | 7,139 | -18% | 1 | 1 | 0% | 1,669 | 4,555 | +173% | 0 | 0 | — |
case-23 | pass→pass | 11,123 | 6,498 | -42% | 1 | 1 | 0% | 2,174 | 4,704 | +116% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +30 percentage points is the difference between those two pass rates over the 23 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.