mcpField
L
langgraph-fundamentals

langgraph-fundamentals

github.com/langchain-aiエージェントワークフロー11964 回インストール10 週間前に更新
リポジトリ

langgraph-fundamentals について

この学習スキルは、LangGraphのワークフローを状態を持つ有向グラフとして設計・実装する方法を解説します。状態リデューサー、ノード、ルーティング、並列処理、ストリーミング、エラー処理をPythonとTypeScriptの例で学べます。

主な機能

  1. 01StateGraphの構造とコンパイル
  2. 02状態スキーマとリデューサー
  3. 03条件ルーティングとCommandによる更新
  4. 04Sendによる並列ワーカー
  5. 05Invokeとストリーミングモード
  6. 06エラー処理と再試行

活用シーン

  1. 01分岐のあるエージェントワークフローの設計
  2. 02グラフノード間での状態の蓄積
  3. 03タスクの並列実行と結果集約
  4. 04グラフの進捗やLLM出力のストリーミング

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/langchain-ai/skills-benchmarks --skill langgraph-fundamentals

インストール後、Claude Code で/langgraph-fundamentalsを入力して読み込まれたか確認します。

詳細

LangGraph では、エージェントのワークフローを**有向グラフ**としてモデル化します。
  • StateGraph:状態を持つグラフを構築するメインクラスです
  • ノード:処理を行い、状態を更新する関数です
  • エッジ:実行順序(静的または条件付き)を定義します
  • START/END:開始点と終了点を示す特別なノードです
  • リデューサー付き状態:状態の更新をどのようにマージするかを制御します

グラフは実行前に compile() する必要があります。

LangGraph アプリケーションの設計

新しいグラフを構築する際は、次の5つの手順に従います。

  1. 個別のステップを洗い出す — ワークフローのフローチャートを描きます。各ステップがノードになります。
  2. 各ステップの処理内容を特定する — ノードをLLMステップ、データステップ、アクションステップ、ユーザー入力ステップに分類します。それぞれについて、静的コンテキスト(プロンプト)、動的コンテキスト(状態から取得)、再試行戦略、期待する結果を決定します。
  3. 状態を設計する — 状態はすべてのノードで共有されるメモリです。生データを保存し、ノード内で必要に応じてプロンプトをフォーマットします。
  4. ノードを構築する — 各ステップを、状態を受け取り、部分的な更新を返す関数として実装します。
  5. ノードを接続する — エッジでノードを接続し、条件付きルーティングを追加して、必要に応じてチェックポインターを指定してコンパイルします。
LangGraph を使用する場合代替手段を使用する場合
エージェントのオーケストレーションをきめ細かく制御する必要がある手早いプロトタイピング → LangChain agents
分岐やループを含む複雑なワークフローを構築するシンプルでステートレスなワークフロー → LangChain direct
Human-in-the-loop や永続化が必要である必要な機能がすべてそろったものを求める場合 → Deep Agents

状態管理

必要な処理解決策例
値を上書きするリデューサーなし(デフォルト)カウンターなどのシンプルなフィールド
リストに追加するリデューサー(operator.add / concat)メッセージ履歴、ログ
独自のロジックを適用するカスタムリデューサー関数複雑なマージ
リストの蓄積と整数の合計にリデューサーを使う状態スキーマを定義します。 ```python from typing_extensions import TypedDict, Annotated import operator

class State(TypedDict): name: str # Default: overwrites on update messages: Annotated[list, operator.add] # Appends to list total: Annotated[int, operator.add] # Sums integers

</python>
<typescript>
配列の蓄積に ReducedValue を使う StateSchema を使用します。
```typescript
import { StateSchema, ReducedValue, MessagesValue } from "@langchain/langgraph";
import { z } from "zod";

const State = new StateSchema({
  name: z.string(),  // Default: overwrites
  messages: MessagesValue,  // Built-in for messages
  items: new ReducedValue(
    z.array(z.string()).default(() => []),
    { reducer: (current, update) => current.concat(update) }
  ),
});
リデューサーがない場合、リストを返すと以前の値は上書きされます。 ```python # WRONG: List will be OVERWRITTEN class State(TypedDict): messages: list # No reducer!

Node 1 returns: {"messages": ["A"]}

Node 2 returns: {"messages": ["B"]}

Final: {"messages": ["B"]} # "A" is LOST!

CORRECT: Use Annotated with operator.add

from typing import Annotated import operator

class State(TypedDict): messages: Annotated[list, operator.add]

Final: {"messages": ["A", "B"]}

</python>
<typescript>
ReducedValue がない場合、配列は追加されずに上書きされます。
```typescript
// WRONG: Array will be overwritten
const State = new StateSchema({
  items: z.array(z.string()),  // No reducer!
});
// Node 1: { items: ["A"] }, Node 2: { items: ["B"] }
// Final: { items: ["B"] }  // A is lost!

// CORRECT: Use ReducedValue
const State = new StateSchema({
  items: new ReducedValue(
    z.array(z.string()).default(() => []),
    { reducer: (current, update) => current.concat(update) }
  ),
});
// Final: { items: ["A", "B"] }
ノードは状態を変更して全体を返すのではなく、部分的な更新を返す必要があります。 ```python # WRONG: Returning entire state object def my_node(state: State) -> State: state["field"] = "updated" return state # Don't mutate and return!

CORRECT: Return dict with only the updates

def my_node(state: State) -> dict: return {"field": "updated"}

</python>
<typescript>
状態オブジェクト全体ではなく、部分的な更新のみを返します。
```typescript
// WRONG: Returning entire state
const myNode = async (state: typeof State.State) => {
  state.field = "updated";
  return state;  // Don't do this!
};

// CORRECT: Return partial updates
const myNode = async (state: typeof State.State) => {
  return { field: "updated" };
};

ノード

ノード関数が受け取る引数は次のとおりです。

シグネチャ使用する場面
def node(state: State)状態のみを必要とするシンプルなノード
def node(state: State, config: RunnableConfig)thread_id、tags、または設定可能な値が必要な場合
def node(state: State, runtime: Runtime[Context])ランタイムコンテキスト、ストア、または stream_writer が必要な場合
from langchain_core.runnables import RunnableConfig
from langgraph.runtime import Runtime

def plain_node(state: State):
    return {"results": "done"}

def node_with_config(state: State, config: RunnableConfig):
    thread_id = config["configurable"]["thread_id"]
    return {"results": f"Thread: {thread_id}"}

def node_with_runtime(state: State, runtime: Runtime[Context]):
    user_id = runtime.context.user_id
    return {"results": f"User: {user_id}"}
シグネチャ使用する場面
(state) => {...}状態のみを必要とするシンプルなノード
(state, config) => {...}thread_id、tags、または設定可能な値が必要な場合
import { GraphNode, StateSchema } from "@langchain/langgraph";

const plainNode: GraphNode<typeof State> = (state) => {
  return { results: "done" };
};

const nodeWithConfig: GraphNode<typeof State> = (state, config) => {
  const threadId = config?.configurable?.thread_id;
  return { results: `Thread: ${threadId}` };
};

エッジ

必要な処理エッジの種類使用する状況
常に同じノードに進むadd_edge()固定された決定的なフロー
状態に基づいてルーティングするadd_conditional_edges()動的な分岐
状態を更新してルーティングするCommand1つのノードでロジックを組み合わせる
複数のノードにファンアウトするSend入力を動的に指定した並列処理
エッジを直線的につないだ、シンプルな2ノードのグラフです。 ```python from langgraph.graph import StateGraph, START, END from typing_extensions import TypedDict

class State(TypedDict): input: str output: str

def process_input(state: State) -> dict: return {"output": f"Processed: {state['input']}"}

def finalize(state: State) -> dict: return {"output": state["output"].upper()}

graph = ( StateGraph(State) .add_node("process", process_input) .add_node("finalize", finalize) .add_edge(START, "process") .add_edge("process", "finalize") .add_edge("finalize", END) .compile() )

result = graph.invoke({"input": "hello"}) print(result["output"]) # "PROCESSED: HELLO"

</python>
<typescript>
ノードを addEdge でつなぎ、呼び出す前にコンパイルします。
```typescript
import { StateGraph, StateSchema, START, END } from "@langchain/langgraph";
import { z } from "zod";

const State = new StateSchema({
  input: z.string(),
  output: z.string().default(""),
});

const processInput = async (state: typeof State.State) => {
  return { output: `Processed: ${state.input}` };
};

const finalize = async (state: typeof State.State) => {
  return { output: state.output.toUpperCase() };
};

const graph = new StateGraph(State)
  .addNode("process", processInput)
  .addNode("finalize", finalize)
  .addEdge(START, "process")
  .addEdge("process", "finalize")
  .addEdge("finalize", END)
  .compile();

const result = await graph.invoke({ input: "hello" });
console.log(result.output);  // "PROCESSED: HELLO"
条件付きエッジを使って、状態に基づき異なるノードにルーティングします。 ```python from typing import Literal from langgraph.graph import StateGraph, START, END

class State(TypedDict): query: str route: str result: str

def classify(state: State) -> dict: if "weather" in state["query"].lower(): return {"route": "weather"} return {"route": "general"}

def route_query(state: State) -> Literal["weather", "general"]: return state["route"]

graph = ( StateGraph(State) .add_node("classify", classify) .add_node("weather", lambda s: {"result": "Sunny, 72F"}) .add_node("general", lambda s: {"result": "General response"}) .add_edge(START, "classify") .add_conditional_edges("classify", route_query, ["weather", "general"]) .add_edge("weather", END) .add_edge("general", END) .compile() )

</python>
<typescript>
addConditionalEdges は、関数の戻り値に基づいてルーティングします。
```typescript
import { StateGraph, StateSchema, START, END } from "@langchain/langgraph";
import { z } from "zod";

const State = new StateSchema({
  query: z.string(),
  route: z.string().default(""),
  result: z.string().default(""),
});

const classify = async (state: typeof State.State) => {
  if (state.query.toLowerCase().includes("weather")) {
    return { route: "weather" };
  }
  return { route: "general" };
};

const routeQuery = (state: typeof State.State) => state.route;

const graph = new StateGraph(State)
  .addNode("classify", classify)
  .addNode("weather", async () => ({ result: "Sunny, 72F" }))
  .addNode("general", async () => ({ result: "General response" }))
  .addEdge(START, "classify")
  .addConditionalEdges("classify", routeQuery, ["weather", "general"])
  .addEdge("weather", END)
  .addEdge("general", END)
  .compile();

Command

Command は、状態の更新とルーティングを1つの戻り値にまとめます。フィールドは次のとおりです。

  • update:適用する状態の更新(ノードから dict を返す場合と同様です)
  • goto:次に進むノード名
  • resume:interrupt() の後に再開するための値 — human-in-the-loop スキルを参照してください
Command を使うと、1回の return で状態を更新し、次のノードも選択できます。 ```python from langgraph.types import Command from typing import Literal

class State(TypedDict): count: int result: str

def node_a(state: State) -> Command[Literal["node_b", "node_c"]]: """Update state AND decide next node in one return.""" new_count = state["count"] + 1 if new_count > 5: return Command(update={"count": new_count}, goto="node_c") return Command(update={"count": new_count}, goto="node_b")

graph = ( StateGraph(State) .add_node("node_a", node_a) .add_node("node_b", lambda s: {"result": "B"}) .add_node("node_c", lambda s: {"result": "C"}) .add_edge(START, "node_a") .add_edge("node_b", END) .add_edge("node_c", END) .compile() )

</python>
<typescript>
update と goto を指定した Command を返し、状態の変更とルーティングをまとめます。
```typescript
import { StateGraph, StateSchema, START, END, Command } from "@langchain/langgraph";
import { z } from "zod";

const State = new StateSchema({
  count: z.number().default(0),
  result: z.string().default(""),
});

const nodeA = async (state: typeof State.State) => {
  const newCount = state.count + 1;
  if (newCount > 5) {
    return new Command({ update: { count: newCount }, goto: "node_c" });
  }
  return new Command({ update: { count: newCount }, goto: "node_b" });
};

const graph = new StateGraph(State)
  .addNode("node_a", nodeA, { ends: ["node_b", "node_c"] })
  .addNode("node_b", async () => ({ result: "B" }))
  .addNode("node_c", async () => ({ result: "C" }))
  .addEdge(START, "node_a")
  .addEdge("node_b", END)
  .addEdge("node_c", END)
  .compile();

Python:有効な goto の遷移先を宣言するには、戻り値の型注釈として Command[Literal["node_a", "node_b"]] を使用します。

TypeScript:有効な goto の遷移先を宣言するには、addNode の第3引数に { ends: ["node_a", "node_b"] } を渡します。

警告:Command が追加するのは動的エッジのみです。add_edge / addEdge で定義された静的エッジも引き続き実行されます。node_a が Command(goto="node_c") を返し、さらに graph.add_edge("node_a", "node_b") もある場合、node_b と node_c の両方が実行されます。


Send API

Send でファンアウトするには、条件付きエッジから [Send("worker", {...})] を返し、並列ワーカーを起動します。結果フィールドにはリデューサーが必要です。

Send API を使ってタスクを並列ワーカーにファンアウトし、結果を集約します。 ```python from langgraph.types import Send from typing import Annotated import operator

class OrchestratorState(TypedDict): tasks: list[str] results: Annotated[list, operator.add] summary: str

def orchestrator(state: OrchestratorState): """Fan out tasks to workers.""" return [Send("worker", {"task": task}) for task in state["tasks"]]

def worker(state: dict) -> dict: return {"results": [f"Completed: {state['task']}"]}

def synthesize(state: OrchestratorState) -> dict: return {"summary": f"Processed {len(state['results'])} tasks"}

graph = ( StateGraph(OrchestratorState) .add_node("worker", worker) .add_node("synthesize", synthesize) .add_conditional_edges(START, orchestrator, ["worker"]) .add_edge("worker", "synthesize") .add_edge("synthesize", END) .compile() )

result = graph.invoke({"tasks": ["Task A", "Task B", "Task C"]})

</python>
<typescript>
Send API を使ってタスクを並列ワーカーにファンアウトし、結果を集約します。
```typescript
import { Send, StateGraph, StateSchema, ReducedValue, START, END } from "@langchain/langgraph";
import { z } from "zod";

const State = new StateSchema({
  tasks: z.array(z.string()),
  results: new ReducedValue(
    z.array(z.string()).default(() => []),
    { reducer: (curr, upd) => curr.concat(upd) }
  ),
  summary: z.string().default(""),
});

const orchestrator = (state: typeof State.State) => {
  return state.tasks.map((task) => new Send("worker", { task }));
};

const worker = async (state: { task: string }) => {
  return { results: [`Completed: ${state.task}`] };
};

const synthesize = async (state: typeof State.State) => {
  return { summary: `Processed ${state.results.length} tasks` };
};

const graph = new StateGraph(State)
  .addNode("worker", worker)
  .addNode("synthesize", synthesize)
  .addConditionalEdges(START, orchestrator, ["worker"])
  .addEdge("worker", "synthesize")
  .addEdge("synthesize", END)
  .compile();
リデューサーを使って並列ワーカーの結果を蓄積します(そうしないと、最後のワーカーが結果を上書きします)。 ```python # WRONG: No reducer - last worker overwrites class State(TypedDict): results: list

CORRECT

class State(TypedDict): results: Annotated[list, operator.add] # Accumulates

</python>
<typescript>
ReducedValue を使って並列ワーカーの結果を蓄積します。
```typescript
// WRONG: No reducer
const State = new StateSchema({ results: z.array(z.string()) });

// CORRECT
const State = new StateSchema({
  results: new ReducedValue(z.array(z.string()).default(() => []), { reducer: (curr, upd) => curr.concat(upd) }),
});

グラフの実行:Invoke と Stream

graph.invoke(input, config) を呼び出すと、グラフを最後まで実行し、最終状態を返します。

```python result = graph.invoke({"input": "hello"}) # With config (for persistence, tags, etc.) result = graph.invoke({"input": "hello"}, {"configurable": {"thread_id": "1"}}) ``` ```typescript const result = await graph.invoke({ input: "hello" }); // With config const result = await graph.invoke({ input: "hello" }, { configurable: { thread_id: "1" } }); ```
モードストリーミングする内容使用例
values各ステップ後の状態全体状態全体を監視する
updates状態の差分段階的な更新を追跡する
messagesLLM トークンとメタデータチャット UI
customユーザー定義データ進捗インジケーター
チャット UI にリアルタイム表示するため、LLM トークンをストリーミングします。 ```python for chunk in graph.stream( {"messages": [HumanMessage("Hello")]}, stream_mode="messages" ): token, metadata = chunk if hasattr(token, "content"): print(token.content, end="", flush=True) ``` チャット UI にリアルタイム表示するため、LLM トークンをストリーミングします。 ```typescript for await (const chunk of graph.stream( { messages: [new HumanMessage("Hello")] }, { streamMode: "messages" } )) { const [token, metadata] = chunk; if (token.content) { process.stdout.write(token.content); } } ``` ストリームライターを使って、ノード内からカスタムの進捗状況を出力します。 ```python from langgraph.config import get_stream_writer

def my_node(state): writer = get_stream_writer() writer("Processing step 1...") # Do work writer("Complete!") return {"result": "done"}

for chunk in graph.stream({"data": "test"}, stream_mode="custom"): print(chunk)

</python>
<typescript>
ストリームライターを使って、ノード内からカスタムの進捗状況を出力します。
```typescript
import { getWriter } from "@langchain/langgraph";

const myNode = async (state: typeof State.State) => {
  const writer = getWriter();
  writer("Processing step 1...");
  // Do work
  writer("Complete!");
  return { result: "done" };
};

for await (const chunk of graph.stream({ data: "test" }, { streamMode: "custom" })) {
  console.log(chunk);
}

エラー処理

エラーの種類に適したハンドラーを選びます。

エラーの種類修正する人戦略例
一時的(ネットワーク、レート制限)システムRetryPolicy(max_attempts=3)add_node(..., retry_policy=...)
LLM で復旧可能(ツールの失敗)LLMToolNode(tools, handle_tool_errors=True)ToolMessage として返されるエラー
ユーザーが修正可能(情報不足)人間interrupt({"message": ...})不足しているデータを収集する(HITL スキルを参照)
予期しないエラー開発者そのまま上位に伝播させるraise
一時的なエラー(ネットワークの問題、レート制限)には RetryPolicy を使用します。 ```python from langgraph.types import RetryPolicy

workflow.add_node( "search_documentation", search_documentation, retry_policy=RetryPolicy(max_attempts=3, initial_interval=1.0) )

</python>
<typescript>
一時的なエラーには retryPolicy を使用します。
```typescript
workflow.addNode(
  "searchDocumentation",
  searchDocumentation,
  {
    retryPolicy: { maxAttempts: 3, initialInterval: 1.0 },
  },
);
langgraph.prebuilt の ToolNode を使用して、ツールの実行とエラーを処理します。handle_tool_errors=True の場合、エラーは ToolMessage として返されるため、LLM は復旧できます。 ```python from langgraph.prebuilt import ToolNode

tool_node = ToolNode(tools, handle_tool_errors=True)

workflow.add_node("tools", tool_node)

</python>
<typescript>
@langchain/langgraph/prebuilt の ToolNode を使用して、ツールの実行とエラーを処理します。handleToolErrors が true の場合、エラーは ToolMessage として返されるため、LLM は復旧できます。
```typescript
import { ToolNode } from "@langchain/langgraph/prebuilt";

const toolNode = new ToolNode(tools, { handleToolErrors: true });

workflow.addNode("tools", toolNode);

よくある修正方法

実行可能なグラフを取得するには compile() が必要です。 ```python # WRONG builder.invoke({"input": "test"}) # AttributeError!

CORRECT

graph = builder.compile() graph.invoke({"input": "test"})

</python>
<typescript>
実行可能なグラフを取得するには compile() が必要です。
```typescript
// WRONG
await builder.invoke({ input: "test" });

// CORRECT
const graph = builder.compile();
await graph.invoke({ input: "test" });
無限ループを避けるため、END への条件付きパスを指定します。 ```python # WRONG: Loops forever builder.add_edge("node_a", "node_b") builder.add_edge("node_b", "node_a")

CORRECT

def should_continue(state): return END if state["count"] > 10 else "node_b" builder.add_conditional_edges("node_a", should_continue)

</python>
<typescript>
ループを終了するには、END を返す条件付きエッジを使用します。
```typescript
// WRONG: Loops forever
builder.addEdge("node_a", "node_b").addEdge("node_b", "node_a");

// CORRECT
builder.addConditionalEdges("node_a", (state) => state.count > 10 ? END : "node_b");
その他のよくある間違い: ```python # Router must return names of nodes that exist in the graph builder.add_node("my_node", func) # Add node BEFORE referencing in edges builder.add_conditional_edges("node_a", router, ["my_node"])

Command return type needs Literal for routing destinations (Python)

def node_a(state) -> Command[Literal["node_b", "node_c"]]: return Command(goto="node_b")

START is entry-only - cannot route back to it

builder.add_edge("node_a", START) # WRONG! builder.add_edge("node_a", "entry") # Use a named entry node instead

Reducer expects matching types

return {"items": ["item"]} # List for list reducer, not a string

```typescript
// Always await graph.invoke() - it returns a Promise
const result = await graph.invoke({ input: "test" });

// TS Command nodes need { ends } to declare routing destinations
builder.addNode("router", routerFn, { ends: ["node_b", "node_c"] });
### してはいけないこと
  • 状態を直接変更しないでください。ノードからは常に部分更新の辞書を返してください
  • START に戻るようルーティングしないでください。START はエントリ専用です。代わりに名前付きノードを使用してください
  • リストフィールドにリデューサーを設定し忘れないでください。設定しない場合、最後の書き込みが優先されます
  • 両方が実行されることを理解せずに、静的エッジと Command goto を併用しないでください

コメント