【初心者向け】LangGraphの紹介と基本的な使い方

# 目次
[1.はじめに](#1-はじめに)
[2.今回の実施内容](#2-今回の実施内容)
[3.開発環境の確認と事前準備](#3-開発環境の確認と事前準備)
[4.実装](#4-実装)
[5.最後に](#5-最後に)

# 1 はじめに
本記事では、大規模言語モデル(Large Language Model、以下、LLM)を活用した複雑なワークフローシステムを開発するためのライブラリであるLangGraphについて基本的な使い方を紹介します。

## LangGraphとは
LangGraphは、ワークフローをグラフ構造としてモデル化し、複雑なタスクの自動化、システムの状態管理を支援します。LangGraphはLangChainの一部として提供されています。LangChainのChainsは一方向の処理フローを定義しますが、LangGraphは循環的な処理フローを定義します。LangGraphを活用することでより複雑なシステムに対する処理フローを実現することが可能です。その他のLangGraphの特徴は以下の通りです。
* 複数のエージェントが協力して問題を解決していくマルチエージェント開発に適している
* LangChainを基に開発されているため、LangChainの機能が活用できる
* グラフ構造を用いることで、「繰り返し処理」や「分岐処理」を実現できる

LangGraphの主要な構成要素は、State、Node、Edge、Graphの4つがあります。
|構成要素 |説明  |
|---|---|
|State |グラフに共通する状態を管理する要素。ワークフローで実行される各Nodeによって更新された値を保存する。 |
|Node |処理を行う要素。Stateから必要な情報を取り出し、処理結果をもとにStateを更新する。|
|Edge |Node間を接続する要素。次に実行するノードを指定する。無条件に遷移するEdgeや条件に基づいて遷移先のNodeを決定する条件付きEdgeが設定できる。  |
|Graph  |NodeとEdgeで構成されるシステム全体を表す要素。  |

グラフ構造のイメージは以下の通りです。
![グラフ構造イメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3582428/7fed1608-215f-4466-8947-b6b18f58db4b.png)


# 2 今回の実施内容
LangGraphの基本的な使い方を紹介するために、今回は以下2つのシステムを構築します。
* **チャットボットの構築**
* **ツールの使用が可能なチャットボットの構築**

チャットボットの構築では、LangGraphの基本的な実装方法を学びます。
ツールの使用が可能なチャットボットの構築では、LangGraphを使用してツールの呼び出しを学びます。LangGraphでツールを使用することで、より複雑なシステムを構築することが可能になります。
それぞれのグラフ構造のイメージは以下の通りです。
![グラフ構造.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3582428/57cfd7cb-fd7c-4b77-9927-87b363939303.png)



# 3 開発環境の確認と事前準備
## 開発環境の確認
今回は以下のバージョンで動作確認を行いました。
* Windows 11
* Python 3.13.2
* 主なLangChainのライブラリ
  * langchain==0.3.20
  * langchain-openai==0.3.7 
  * langgraph==0.3.3

## 事前準備
### Pythonのインストール
以下の公式サイトよりPythonをダウンロードしてインストールを行います。

https://www.python.org/downloads/

### OpenAI API keyの取得
本記事の動作確認では、OpenAIの「gpt-4o-mini」を利用します。OpenAIのモデルを利用するためには、OpenAI API keyを取得する必要があります。事前に以下のサイトにアクセスしOpenAI API keyを取得します。初回はアカウントの登録が必要です。
※OpenAI API keyは従量課金制であるため、使用した分の料金が発生することをご留意ください。

https://platform.openai.com/api-keys

### 仮想環境の構築
以下の手順で仮想環境を構築します。

#### 作業フォルダ作成
今回は「training」フォルダを作成します。コマンドプロンプトを開いて以下のコマンドを実行します。カレントディレクトリは`C:\Users\Username`とします。
※Usernameは各端末によって異なりますのでご留意ください。
```
mkdir training
cd training
```

#### 仮想環境の作成
仮想環境を作成するため、以下のコマンドを実行します。
```
python -m venv .venv
```

#### 仮想環境の有効化
仮想環境を有効化するため、以下のコマンドを実行します。
```
.venv\Scripts\activate.bat
```

#### 仮想環境の無効化
仮想環境を無効化する場合、以下のコマンドを実行します。
```
deactivate
```

### 必要なパッケージのインストール
今回の動作確認に必要なパッケージをインストールします。
```
pip install langchain langchain-openai langgraph dotenv grandalf
```

### OpenAI API keyの設定
OpenAI API keyを使用するため、以下の設定を行います。
* trainingフォルダ内に.envファイルを作成します
* .envファイルを開き、以下の内容を入力後保存します
  ```
  OPENAI_API_KEY="取得したOpenAI API key"
  ```

# 4 実装
LangGraphを使用してシステムを構築します。本項を実施するためには、前項の[事前準備](#事前準備)が完了していることが必要です。

## チャットボットの構築
### 実装コード
trainingフォルダ内に新規ファイル`chatbot.py`を作成して、以下のコードを入力後保存します。
```Python:chatbot.py
from dotenv import load_dotenv
from typing import TypedDict
from typing import Annotated
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages

# .envファイルから環境変数を読み込む
load_dotenv()

# モデル設定
llm = ChatOpenAI(model="gpt-4o-mini")

# Stateの定義
class State(TypedDict):
    messages: Annotated[list, add_messages]

# Nodeの定義
def chatbot(state: State):
    # LLMが生成した回答でStateを更新
    return {"messages": [llm.invoke(state["messages"])]}

# Graphの初期化
graph_builder = StateGraph(State)

# Nodeの追加
graph_builder.add_node("chatbot", chatbot)

# Node間を接続するEdgeの追加
graph_builder.add_edge(START, "chatbot")
graph_builder.add_edge("chatbot", END)

# Graphのコンパイル
graph = graph_builder.compile()

# Graphの実行
result = graph.invoke({"messages": ["こんにちは"]})

# 結果表示
print(result)
```

### コード実行
コマンドプロンプトから実装したコードを実行します。
※本手順を実行するためには事前に[仮想環境の有効化](#仮想環境の有効化)の手順が必要です。
```
(.venv) C:\Users\Username\training>python chatbot.py
```

### コード説明
#### モデルの設定
```Python
from langchain_openai import ChatOpenAI

# モデル設定
llm = ChatOpenAI(model="gpt-4o-mini")
```
`ChatOpenAI`クラスで使用する言語モデル`gpt-4o-mini`を設定しインスタンス化します。

#### Stateの定義
```Python
from typing import TypedDict
from typing import Annotated
from langgraph.graph.message import add_messages

class State(TypedDict):
    messages: Annotated[list, add_messages]
```
`TypeDict`クラスを継承したStateクラスを定義します。TypeDictクラスは辞書型の型ヒントを定義するクラスです。`messages`フィールドに、型ヒントをつける関数`Annotated`とメッセージの追加を行う関数`add_messages`を定義しています。messagesフィールドの第1引数にリスト、第2引数にadd_messagesを設定しているため、State更新時はリストの要素が追加されます。本例では、チャットボットとの会話履歴がリストの要素として追加されます。

#### Nodeの定義
```Python
def chatbot(state: State):
    # LLMが生成した回答でStateを更新
    return {"messages": [llm.invoke(state["messages"])]}
```
LangGraphではNodeの処理を`関数`として定義します。Nodeの処理を関数として定義する場合、Stateオブジェクトを引数に指定し戻り値をStateクラスの形式に合わせて`辞書型`で返します。chatbot関数の場合、stateオブジェクトを引数に受け取り、LLMのinvoke関数にstate["messages"]を渡して呼び出します。戻り値はStateクラスの辞書型に合わせるため、keyとしてmessages、valueとしてinvoke関数の出力結果をリスト型で返しています。
※Nodeの処理は、Runnableオブジェクトでも定義可能ですが、本記事では説明を割愛します。

#### Graphの初期化
```Python
# Graphの初期化
graph_builder = StateGraph(State)
```
`StateGraph`クラスに`State`を渡して、Graphのインスタンスを生成します。今回はGraphのインスタンスとしてgraph_builderを生成しました。以降の手順でgraph_builderに対して、NodeやEdgeの設定を追加していきます。

#### Nodeの追加
```Python
graph_builder.add_node("chatbot", chatbot)
```
Nodeの追加は`add_node`関数を使用します。add_node関数の第1引数にはNode名、第2引数には関数名を設定します。第1引数に設定されたNode名はEdgeの設定の際に使用します。

#### Edgeの追加
```Python
graph_builder.add_edge(START, "chatbot")
graph_builder.add_edge("chatbot", END)
```
Edgeの追加は`add_edge`関数を使用します。add_edge関数の第1引数には遷移元のNode名を設定し、第2引数には遷移先のNode名を設定します。`START`は始点Nodeを`END`は終点Nodeを表す特殊なNodeです。

#### Graphのコンパイル
```Python
graph = graph_builder.compile()
```
Graphのコンパイルは`compile`関数を使用します。compile関数は`StateGraph`をコンパイルして`CompiledStateGraph`のインスタンスを生成します。CompiledStateGraphのインスタンスは`Runnable`として使えるため、invoke関数、ainvoke関数、stream関数等の関数を実行することができます。

#### Graphの実行
```Python
# Graphの実行
result = graph.invoke({"messages": ["こんにちは"]})

# 結果表示
print(result)
```
本例では`invoke`関数を使用してGraphを実行しています。invoke関数に初期状態を渡して実行すると、Graph内のすべての処理が実行された後の最終結果が返されます。

出力例は以下の通りです。※出力例は整形表示しています。
```
{
    'messages':[
    HumanMessage(content='こんにちは',
      additional_kwargs={
      
    },
      response_metadata={
      
    },
      id='36dda751-466f-4cde-83bd-19cf644e824a'),
      AIMessage(content='こんにちは！どうかされましたか？何かお手伝いできることがあれば教えてください。',
      additional_kwargs={
        'refusal':None
    },
      response_metadata={
        'token_usage':{
          'completion_tokens':25,
          'prompt_tokens':8,
          'total_tokens':33,
          'completion_tokens_details':{
            'accepted_prediction_tokens':0,
            'audio_tokens':0,
            'reasoning_tokens':0,
            'rejected_prediction_tokens':0
        },
          'prompt_tokens_details':{
            'audio_tokens':0,
            'cached_tokens':0
        }
      },
        'model_name':'gpt-4o-mini-2024-07-18',
        'system_fingerprint':'fp_06737a9306',
        'finish_reason':'stop',
        'logprobs':None
    },
      id='run-4b603376-6809-4fb9-8865-4bcd172ff02f-0',
      usage_metadata={
        'input_tokens':8,
        'output_tokens':25,
        'total_tokens':33,
        'input_token_details':{
          'audio':0,
          'cache_read':0
      },
        'output_token_details':{
          'audio':0,
          'reasoning':0
      }
    }
)
  ]
}

```
チャットボットの回答のみ出力したい場合、print文を以下の通り修正します。
```Python
print(result["messages"][-1].content)
```

出力例は以下の通りです。
```Python
こんにちは！どうかされましたか？何かお手伝いできることがあれば教えてください。
```
### グラフ構造の可視化
LangGraphにはグラフ構造を可視化する機能が備わっています。この機能を活用することで、構築したチャットボットのグラフ構造を表示できます。グラフ構造の可視化のコードを実行する前に、以下2点を完了する必要があります。
* `grandalf`のインストール
* [Graphのコンパイル](#graphのコンパイル)

```Python:グラフ構造の可視化のコード
print(graph.get_graph().print_ascii())
```
出力例は以下の通りです。
![グラフ可視化(chatbot).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3582428/87dfeb9e-32d3-40f8-88c8-3d629ba2353c.png)


## ツールの使用が可能なチャットボットの構築
### 実装コード
trainingフォルダ内に新規ファイル`chatbot_tools.py`を作成して、以下のコードを入力後保存します。
```Python:chatbot_tools.py
from dotenv import load_dotenv
from typing import TypedDict
from typing import Annotated
from langchain_openai import ChatOpenAI
from langchain_core.messages import ToolMessage
from langchain_core.tools import tool
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages

# .envファイルから環境変数を読み込む
load_dotenv()

# toolの定義
@tool
def multiply_function(x: int, y: int) -> int:
    """
    2つのint型の値を引数で受け取り、掛け算の結果をint型で返す

    Args:
        x (int): 1つ目のint型の引数
        y (int): 2つ目のint型の引数

    Returns:
        int: x * y
    """
    return x * y

# toolの定義
@tool
def add_function(x: int, y: int) -> int:
   """
    2つのint型の値を引数で受け取り、足し算の結果をint型で返す

    Args:
        x (int): 1つ目のint型の引数
        y (int): 2つ目のint型の引数

    Returns:
        int: x + y
    """
   return x + y

# ツールの辞書作成
tools_dict = {
    "multiply_function": multiply_function,
    "add_function": add_function
}

# モデル設定
llm = ChatOpenAI(model="gpt-4o-mini")
# toolsの作成
tools = [multiply_function, add_function]
# toolsの紐づけ
llm_with_tools = llm.bind_tools(tools)

# Stateの定義
class State(TypedDict):
    messages: Annotated[list, add_messages]

# chatbotのNodeを定義
def chatbot(state: State):
    # LLMが有効なtoolを選択して生成した回答でStateを更新
    result = llm_with_tools.invoke(state["messages"])
    return {"messages": [result]}

# toolのNodeを定義
def tool(state: State):
    # 最後のメッセージを取得
    last_message = state["messages"][-1]
    # 実行結果を保存するためのリスト
    messages = []
    for tool_call in last_message.tool_calls:
        # toolの実行
        tool_output = tools_dict[tool_call["name"]].invoke(tool_call["args"])
        # toolの結果を使用してToolMessageの作成
        tool_messages = ToolMessage(
            content=tool_output,
            name=tool_call["name"],
            tool_call_id=tool_call["id"],
        )
        # ToolMessageをmessagesリストに追加
        messages.append(tool_messages)
    # Stateを更新可能な辞書型を返す
    return {"messages": messages}

# 条件分岐を判断する関数を定義
def router(state: State):
    # 最後のメッセージを取得
    last_message = state["messages"][-1]
    # 最後のメッセージにtool_callsが存在するか判定
    if last_message.tool_calls:
        # tool_callsが存在する場合、"tool"を返す
        return "tool"
    else:
        # それ以外の場合、"end"を返す
        return "end"

# Graphの初期化
graph_builder = StateGraph(State)

# chatbot Nodeの追加
graph_builder.add_node("chatbot", chatbot)
# tool Nodeの追加
graph_builder.add_node("tool", tool)

# 条件付きEdgeの追加
graph_builder.add_conditional_edges(
    # 遷移元のNode名を設定
    "chatbot",
    # 条件分岐を判断する関数を設定
    router,
    # routerの戻り値によって遷移先のNodeを決める
    {
        # "tool"の場合、tool Nodeに遷移
        "tool": "tool",
        #"end"の場合、END Nodeに遷移
        "end": END
    }
)

# tool Nodeからchatbot NodeへのEdgeを追加
graph_builder.add_edge("tool", "chatbot")
# STARTからchatbot NodeへのEdgeを追加
graph_builder.add_edge(START, "chatbot")

# Graphのコンパイル
graph = graph_builder.compile()

# Graphの実行
result = graph.invoke({"messages": ["100掛ける200の計算と1足す2の計算をそれぞれしてください"]})

# 結果表示
print(result)
```

### コード実行
コマンドプロンプトから実装したコードを実行します。
※本手順を実行するためには事前に[仮想環境の有効化](#仮想環境の有効化)の手順が必要です。
```
(.venv) C:\Users\Username\training>python chatbot_tools.py
```

### コード説明
#### toolの定義
```Python
# toolの定義
@tool
def multiply_function(x: int, y: int) -> int:
    """
    2つのint型の値を引数で受け取り、掛け算の結果をint型で返す

    Args:
        x (int): 1つ目のint型の引数
        y (int): 2つ目のint型の引数

    Returns:
        int: x * y
    """
    return x * y

# toolの定義
@tool
def add_function(x: int, y: int) -> int:
   """
    2つのint型の値を引数で受け取り、足し算の結果をint型で返す

    Args:
        x (int): 1つ目のint型の引数
        y (int): 2つ目のint型の引数

    Returns:
        int: x + y
    """
   return x + y

# ツールの辞書作成
tools_dict = {
    "multiply_function": multiply_function,
    "add_function": add_function
}
```
LLMから呼び出される関数を定義します。`@toolデコレータ`を使用して関数を定義することでLLMから呼び出される関数を作成することができます。ここでは、2つの引数を取って掛け算の結果を返す`multiply_function`と足し算の結果を返す`add_function`を作成します。関数の`docstring`は、LLMがプロンプトの内容から適切な関数(tool)を判断して呼び出す際に利用されます。そのため、docstringに適切な説明を記載することは重要です。`tools_dict`はLLMがツールを呼び出す際に使用するツールの辞書です。

#### モデルの設定
```Python
from langchain_openai import ChatOpenAI

# モデル設定
llm = ChatOpenAI(model="gpt-4o-mini")
# toolsの作成
tools = [multiply_function, add_function]
# toolsの紐づけ
llm_with_tools = llm.bind_tools(tools)
```
LLMが作成したツールを呼び出すための設定を行います。設定手順は、モデルを設定した後、定義した各ツールを要素に持つリストを`bind_tools`関数に渡してモデルに紐づけを行います。本例では、multiply_functionとadd_functionを要素に持つリストtoolsを作成後、bind_tools関数にtoolsを渡してLLMに紐づけを行っています。

#### Stateの定義
```Python
from typing import TypedDict
from typing import Annotated
from langgraph.graph.message import add_messages

class State(TypedDict):
    messages: Annotated[list, add_messages]
```
「チャットボットの構築」の[Stateの定義](#stateの定義)と同じ内容のため説明を割愛します。

#### 各Nodeの定義
##### chatbot Nodeの定義
```Python
# chatbotのNodeを定義
def chatbot(state: State):
    # LLMが有効なtoolを選択して生成した回答でStateを更新
    result = llm_with_tools.invoke(state["messages"])
    return {"messages": [result]}
```
chatbot関数は、stateオブジェクトを受け取り、llm_with_tools(ツールが紐づけられたLLM)のinvoke関数にstate["messages"]を渡して呼び出します。戻り値はStateクラスの辞書型に合わせるため、keyとしてmessages、valueとしてinvoke関数の出力結果をリスト型で返しています。

##### tool Nodeの定義
```Python
# toolのNodeを定義
def tool(state: State):
    # 最後のメッセージを取得
    last_message = state["messages"][-1]
    # 実行結果を保存するためのリスト
    messages = []
    for tool_call in last_message.tool_calls:
        # toolの実行
        tool_output = tools_dict[tool_call["name"]].invoke(tool_call["args"])
        # toolの結果を使用してToolMessageの作成
        tool_messages = ToolMessage(
            content=tool_output,
            name=tool_call["name"],
            tool_call_id=tool_call["id"],
        )
        # ToolMessageをmessagesリストに追加
        messages.append(tool_messages)
    # Stateを更新可能な辞書型を返す
    return {"messages": messages}
```
tool関数は、stateオブジェクトを受け取り、`state["messages"][-1]`によってstateオブジェクトに保存されている最後のメッセージを取得します。for文では最後のメッセージに含まれる`too_calls`からツール情報を取得し、too_callsの`name`をもとに[toolの定義](#toolの定義)で定義したツールの辞書(tools_dict)から実際のツールを呼び出します。また、ツールの変数としてtoo_callsの`args`を渡してツールを実行します。ツールの実行結果を使用して`ToolMessage`オブジェクトを作成します。ToolMessageのパラメータとして、content(ツールの実行結果)、name(ツールの名前)、tool_call_id(ツールのID)を設定します。作成したToolMessageオブジェクトは、`messages`に追加します。戻り値はStateクラスの辞書型に合わせるため、keyとしてmessages、valueとしてmessagesリストを設定して返しています。

##### 条件分岐を判断する関数の定義
```Python
# 条件分岐を判断する関数を定義
def router(state: State):
    # 最後のメッセージを取得
    last_message = state["messages"][-1]
    # 最新メッセージにtool_callsが存在するか判定
    if last_message.tool_calls:
        # tool_callsが存在する場合、"tool"を返す
        return "tool"
    else:
        # それ以外の場合、"end"を返す
        return "end"
```
条件分岐を判断するためrouter関数を定義します。router関数は、stateオブジェクトを受け取り、`state["messages"][-1]`によってstateオブジェクトに保存されている最後のメッセージを取得します。if文では最後のメッセージにツールの呼び出しに対応する`tool_call`が存在するかを判定します。具体的には、`last_message.tool_calls`を判定し、tool_callが存在する場合は"tool"を返し、それ以外の場合は"end"を返します。

#### Graphの初期化
「チャットボットの構築」の[Graphの初期化](#graphの初期化)と同じ内容のため説明を割愛します。

#### 各Nodeの追加
```Python
# chatbot Nodeの追加
graph_builder.add_node("chatbot", chatbot)
# tool Nodeの追加
graph_builder.add_node("tool", tool)
```
`add_node`関数を使用して、chatbot Nodeとtool Nodeを追加します。

#### 各Edgeの追加
##### 条件付きEdgeの追加
```Python
# 条件付きEdgeの追加
graph_builder.add_conditional_edges(
    # 遷移元のNode名を設定
    "chatbot",
    # 条件分岐を判断する関数を設定
    router,
    # routerの戻り値によって遷移先のNodeを決める
    {
        # "tool"の場合、tool Nodeに遷移
        "tool": "tool",
        #"end"の場合、END Nodeに遷移
        "end": END
    }
)
```
条件付きEdgeは、条件に基づいて遷移先のNodeを決定するEdgeです。`add_conditional_edges`関数を使用します。第1引数に遷移元のNode名を設定します。第2引数には条件分岐を判断する関数を設定します。本例では、[条件分岐を判断する関数の定義](#条件分岐を判断する関数の定義)で定義したrouter関数を設定します。第3引数には第2引数の関数で返される値とその値に対応する遷移先のNodeを辞書型で設定します。router関数の戻り値が"tool"の場合はtool Nodeへ遷移し、"end"の場合はENDへ遷移します。

##### Edgeの追加
```Python
# tool Nodeからchatbot NodeへのEdgeを追加
graph_builder.add_edge("tool", "chatbot")
# STARTからchatbot NodeへのEdgeを追加
graph_builder.add_edge(START, "chatbot")
```
`add_edge`関数で遷移元のNodeと遷移先のNodeを定義します。graph_builder.add_edge("tool", "chatbot")は、tool Nodeからchatbot Nodeへの遷移を設定します。graph_builder.add_edge(START, "chatbot")は、STARTからchatbot Nodeへの遷移を設定します。

#### Graphのコンパイル
```Python
# Graphのコンパイル
graph = graph_builder.compile()
```
「チャットボットの構築」の[Graphのコンパイル](#graphのコンパイル)と同じ内容のため説明を割愛します。

#### Graphの実行
本例では、以下4パターンのGraph実行結果を確認します。
* チャットボットが`multiply_function`、`add_function`を呼び出すパターン
* チャットボットが`multiply_function`のみ呼び出すパターン
* チャットボットが`add_function`のみ呼び出すパターン
* チャットボットがツールを呼び出さないパターン

:::note info
* 簡単な掛け算や足し算は、ツールを使用せずにgpt-4o-miniを使用して直接計算可能ですが、本例ではチャットボットがツールを呼び出す処理を確認するためツールを準備しています
* ツールを2つ準備した理由は、チャットボットが複数のツールの中から有効と判断したツールを選択して呼び出せることを確認するためです
:::

```Python: チャットボットがmultiply_functionとadd_functionを呼び出すパターン
# Graphの実行
result = graph.invoke({"messages": ["100掛ける200の計算と1足す2の計算をそれぞれしてください"]})

# 結果表示
print(result)
```

出力例は以下の通りです。※出力例は整形表示しています。
```Python:
{
    'messages':[
    HumanMessage(content='100掛ける200の計算と1足す2の計算をそれぞれしてください',
      additional_kwargs={
      
    },
      response_metadata={
      
    },
      id='610ba8cd-e2c2-4765-a7e7-9380cb06cf3b'),
      AIMessage(content='',
      additional_kwargs={
        'tool_calls':[
        {
            'id':'call_lT9Qfzb9LsKlcJ0nFgXHU4so',
            'function':{
              'arguments':'{"x": 100, "y": 200}',
              'name':'multiply_function'
          },
            'type':'function'
        },
          {
            'id':'call_uhocLNTmBaEamMt1G7NNQAo5',
            'function':{
              'arguments':'{"x": 1, "y": 2}',
              'name':'add_function'
          },
            'type':'function'
        }
      ],
        'refusal':None
    },
      response_metadata={
        'token_usage':{
          'completion_tokens':53,
          'prompt_tokens':222,
          'total_tokens':275,
          'completion_tokens_details':{
            'accepted_prediction_tokens':0,
            'audio_tokens':0,
            'reasoning_tokens':0,
            'rejected_prediction_tokens':0
        },
          'prompt_tokens_details':{
            'audio_tokens':0,
            'cached_tokens':0
        }
      },
        'model_name':'gpt-4o-mini-2024-07-18',
        'system_fingerprint':'fp_b8bc95a0ac',
        'finish_reason':'tool_calls',
        'logprobs':None
    },
      id='run-0c31e74c-a663-41e4-bc1c-3a769b8018a1-0',
      tool_calls=[
      {
          'name':'multiply_function',
          'args':{
            'x':100,
            'y':200
        },
          'id':'call_lT9Qfzb9LsKlcJ0nFgXHU4so',
          'type':'tool_call'
      },
        {
          'name':'add_function',
          'args':{
            'x':1,
            'y':2
        },
          'id':'call_uhocLNTmBaEamMt1G7NNQAo5',
          'type':'tool_call'
      }
    ],
      usage_metadata={
        'input_tokens':222,
        'output_tokens':53,
        'total_tokens':275,
        'input_token_details':{
          'audio':0,
          'cache_read':0
      },
        'output_token_details':{
          'audio':0,
          'reasoning':0
      }
    }
),
      ToolMessage(content='20000',
      name='multiply_function',
      id='6802bb59-097c-4e78-a30a-98db8f29a0a8',
      tool_call_id='call_lT9Qfzb9LsKlcJ0nFgXHU4so'),
      ToolMessage(content='3',
      name='add_function',
      id='88d0c420-2cd1-40c7-8afa-04d7cb955bdc',
      tool_call_id='call_uhocLNTmBaEamMt1G7NNQAo5'),
      AIMessage(content='100掛ける200の結果は20000で、1足す2の結果は3です。',
      additional_kwargs={
        'refusal':None
    },
      response_metadata={
        'token_usage':{
          'completion_tokens':24,
          'prompt_tokens':291,
          'total_tokens':315,
          'completion_tokens_details':{
            'accepted_prediction_tokens':0,
            'audio_tokens':0,
            'reasoning_tokens':0,
            'rejected_prediction_tokens':0
        },
          'prompt_tokens_details':{
            'audio_tokens':0,
            'cached_tokens':0
        }
      },
        'model_name':'gpt-4o-mini-2024-07-18',
        'system_fingerprint':'fp_b8bc95a0ac',
        'finish_reason':'stop',
        'logprobs':None
    },
      id='run-cd2c8b19-340c-4d6e-8328-997f245d3b6e-0',
      usage_metadata={
        'input_tokens':291,
        'output_tokens':24,
        'total_tokens':315,
        'input_token_details':{
          'audio':0,
          'cache_read':0
      },
        'output_token_details':{
          'audio':0,
          'reasoning':0
      }
    }
)
  ]
}
```
AIMessageのtool_callsにmultiply_functionとadd_functionのツール情報が保存されています。

```Python: チャットボットがmultiply_functionのみ呼び出すパターン
# Graphの実行
result = graph.invoke({"messages": ["100掛ける200の計算をしてください"]})

# 結果表示
print(result)
```

出力例は以下の通りです。※出力例は整形表示しています。
```Python:
{
    'messages':[
    HumanMessage(content='100掛ける200の計算をしてください',
      additional_kwargs={
      
    },
      response_metadata={
      
    },
      id='5d547b8e-ea1c-485d-8868-d098b3cbd7a6'),
      AIMessage(content='',
      additional_kwargs={
        'tool_calls':[
        {
            'id':'call_6nNQUiItocW0i67BZStdMZlF',
            'function':{
              'arguments':'{"x":100,"y":200}',
              'name':'multiply_function'
          },
            'type':'function'
        }
      ],
        'refusal':None
    },
      response_metadata={
        'token_usage':{
          'completion_tokens':19,
          'prompt_tokens':211,
          'total_tokens':230,
          'completion_tokens_details':{
            'accepted_prediction_tokens':0,
            'audio_tokens':0,
            'reasoning_tokens':0,
            'rejected_prediction_tokens':0
        },
          'prompt_tokens_details':{
            'audio_tokens':0,
            'cached_tokens':0
        }
      },
        'model_name':'gpt-4o-mini-2024-07-18',
        'system_fingerprint':'fp_b8bc95a0ac',
        'finish_reason':'tool_calls',
        'logprobs':None
    },
      id='run-6ff2ffb0-3a42-4387-8361-666c76f3f3c1-0',
      tool_calls=[
      {
          'name':'multiply_function',
          'args':{
            'x':100,
            'y':200
        },
          'id':'call_6nNQUiItocW0i67BZStdMZlF',
          'type':'tool_call'
      }
    ],
      usage_metadata={
        'input_tokens':211,
        'output_tokens':19,
        'total_tokens':230,
        'input_token_details':{
          'audio':0,
          'cache_read':0
      },
        'output_token_details':{
          'audio':0,
          'reasoning':0
      }
    }
),
      ToolMessage(content='20000',
      name='multiply_function',
      id='52a90328-d42c-4391-9454-9ab33b0e4c2a',
      tool_call_id='call_6nNQUiItocW0i67BZStdMZlF'),
      AIMessage(content='100掛ける200の計算結果は20000です。',
      additional_kwargs={
        'refusal':None
    },
      response_metadata={
        'token_usage':{
          'completion_tokens':16,
          'prompt_tokens':239,
          'total_tokens':255,
          'completion_tokens_details':{
            'accepted_prediction_tokens':0,
            'audio_tokens':0,
            'reasoning_tokens':0,
            'rejected_prediction_tokens':0
        },
          'prompt_tokens_details':{
            'audio_tokens':0,
            'cached_tokens':0
        }
      },
        'model_name':'gpt-4o-mini-2024-07-18',
        'system_fingerprint':'fp_b8bc95a0ac',
        'finish_reason':'stop',
        'logprobs':None
    },
      id='run-6586f0b6-e2f2-40aa-9330-d7905b888bab-0',
      usage_metadata={
        'input_tokens':239,
        'output_tokens':16,
        'total_tokens':255,
        'input_token_details':{
          'audio':0,
          'cache_read':0
      },
        'output_token_details':{
          'audio':0,
          'reasoning':0
      }
    }
)
  ]
}
```
AIMessageのtool_callsにmultiply_functionのツール情報のみが保存されています。

```Python: チャットボットがadd_functionのみ呼び出すパターン
# Graphの実行
result = graph.invoke({"messages": ["1足す2の計算をしてください"]})

# 結果表示
print(result)
```

出力例は以下の通りです。※出力例は整形表示しています。
```Python:
{
    'messages':[
    HumanMessage(content='1足す2の計算をしてください',
      additional_kwargs={
      
    },
      response_metadata={
      
    },
      id='766e74d9-4444-415d-9517-d9f99912a453'),
      AIMessage(content='',
      additional_kwargs={
        'tool_calls':[
        {
            'id':'call_DPt0KPDmLPWOgXonPzAGl5FE',
            'function':{
              'arguments':'{"x":1,"y":2}',
              'name':'add_function'
          },
            'type':'function'
        }
      ],
        'refusal':None
    },
      response_metadata={
        'token_usage':{
          'completion_tokens':19,
          'prompt_tokens':210,
          'total_tokens':229,
          'completion_tokens_details':{
            'accepted_prediction_tokens':0,
            'audio_tokens':0,
            'reasoning_tokens':0,
            'rejected_prediction_tokens':0
        },
          'prompt_tokens_details':{
            'audio_tokens':0,
            'cached_tokens':0
        }
      },
        'model_name':'gpt-4o-mini-2024-07-18',
        'system_fingerprint':'fp_b8bc95a0ac',
        'finish_reason':'tool_calls',
        'logprobs':None
    },
      id='run-4c149635-f960-4f29-a72b-8947ecdb8b99-0',
      tool_calls=[
      {
          'name':'add_function',
          'args':{
            'x':1,
            'y':2
        },
          'id':'call_DPt0KPDmLPWOgXonPzAGl5FE',
          'type':'tool_call'
      }
    ],
      usage_metadata={
        'input_tokens':210,
        'output_tokens':19,
        'total_tokens':229,
        'input_token_details':{
          'audio':0,
          'cache_read':0
      },
        'output_token_details':{
          'audio':0,
          'reasoning':0
      }
    }
),
      ToolMessage(content='3',
      name='add_function',
      id='c37c23a9-fd9b-4dc2-812a-036f6c6069b8',
      tool_call_id='call_DPt0KPDmLPWOgXonPzAGl5FE'),
      AIMessage(content='1足す2は3です。',
      additional_kwargs={
        'refusal':None
    },
      response_metadata={
        'token_usage':{
          'completion_tokens':10,
          'prompt_tokens':237,
          'total_tokens':247,
          'completion_tokens_details':{
            'accepted_prediction_tokens':0,
            'audio_tokens':0,
            'reasoning_tokens':0,
            'rejected_prediction_tokens':0
        },
          'prompt_tokens_details':{
            'audio_tokens':0,
            'cached_tokens':0
        }
      },
        'model_name':'gpt-4o-mini-2024-07-18',
        'system_fingerprint':'fp_b8bc95a0ac',
        'finish_reason':'stop',
        'logprobs':None
    },
      id='run-ec71e5bc-438f-48e0-9a33-ea25e3300065-0',
      usage_metadata={
        'input_tokens':237,
        'output_tokens':10,
        'total_tokens':247,
        'input_token_details':{
          'audio':0,
          'cache_read':0
      },
        'output_token_details':{
          'audio':0,
          'reasoning':0
      }
    }
)
  ]
}
```
AIMessageのtool_callsにadd_functionのツール情報のみが保存されています。

```Python: チャットボットがツールを呼び出さないパターン
# Graphの実行
result = graph.invoke({"messages": ["こんにちは"]})

# 結果表示
print(result)
```

出力例は以下の通りです。※出力例は整形表示しています。
```Python:
{
    'messages':[
    HumanMessage(content='こんにちは',
      additional_kwargs={
      
    },
      response_metadata={
      
    },
      id='d7108f0b-9ba5-487b-ad7a-14346062b8bb'),
      AIMessage(content='こんにちは！何かお手伝いできることがありますか？',
      additional_kwargs={
        'refusal':None
    },
      response_metadata={
        'token_usage':{
          'completion_tokens':16,
          'prompt_tokens':202,
          'total_tokens':218,
          'completion_tokens_details':{
            'accepted_prediction_tokens':0,
            'audio_tokens':0,
            'reasoning_tokens':0,
            'rejected_prediction_tokens':0
        },
          'prompt_tokens_details':{
            'audio_tokens':0,
            'cached_tokens':0
        }
      },
        'model_name':'gpt-4o-mini-2024-07-18',
        'system_fingerprint':'fp_b8bc95a0ac',
        'finish_reason':'stop',
        'logprobs':None
    },
      id='run-3261cace-04df-4633-94df-66859f1bbcbc-0',
      usage_metadata={
        'input_tokens':202,
        'output_tokens':16,
        'total_tokens':218,
        'input_token_details':{
          'audio':0,
          'cache_read':0
      },
        'output_token_details':{
          'audio':0,
          'reasoning':0
      }
    }
)
  ]
}
```
AIMessageにtool_callsが存在しないことを確認できます。

### グラフ構造の可視化
今回構築したグラフ構造を可視化します。詳細については「チャットボットの構築」の[グラフ構造の可視化](#グラフ構造の可視化)と同じ内容のため説明を割愛します。

```Python:グラフ構造の可視化のコード
print(graph.get_graph().print_ascii())
```
出力例は以下の通りです。
![グラフ可視化(chatbot_tools).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3582428/0b5242c3-3543-4e17-9887-5e9db84bc3a9.png)


# 5 最後に
今回は基礎的なシステム構築を通して、LangGraphの基本的な使い方を紹介しました。次回はLangGraphを使用したマルチエージェントシステムについて触れたいと思います。

# 参考文献
* [LangGraph Quickstart](https://langchain-ai.github.io/langgraph/tutorials/introduction/)
* [How to call tools using ToolNode](https://langchain-ai.github.io/langgraph/how-tos/tool-calling/)
* [LangChainとLangGraphによるRAG・AIエージェント［実践］入門 (エンジニア選書)](https://www.amazon.co.jp/LangChain%E3%81%A8LangGraph%E3%81%AB%E3%82%88%E3%82%8BRAG%E3%83%BBAI%E3%82%A8%E3%83%BC%E3%82%B8%E3%82%A7%E3%83%B3%E3%83%88%EF%BC%BB%E5%AE%9F%E8%B7%B5%EF%BC%BD%E5%85%A5%E9%96%80-%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E9%81%B8%E6%9B%B8-%E8%A5%BF%E8%A6%8B-%E5%85%AC%E5%AE%8F/dp/4297145308)
* [やさしく学ぶLLMエージェント: 基本からマルチエージェント構築まで](https://www.amazon.co.jp/%E3%82%84%E3%81%95%E3%81%97%E3%81%8F%E5%AD%A6%E3%81%B6LLM%E3%82%A8%E3%83%BC%E3%82%B8%E3%82%A7%E3%83%B3%E3%83%88-%E5%9F%BA%E6%9C%AC%E3%81%8B%E3%82%89%E3%83%9E%E3%83%AB%E3%83%81%E3%82%A8%E3%83%BC%E3%82%B8%E3%82%A7%E3%83%B3%E3%83%88%E6%A7%8B%E7%AF%89%E3%81%BE%E3%81%A7-%E4%BA%95%E4%B8%8A-%E9%A1%A7%E5%9F%BA/dp/4274233162)
* [仕組みからわかる大規模言語モデル 生成AI時代のソフトウェア開発入門](https://www.amazon.co.jp/%E4%BB%95%E7%B5%84%E3%81%BF%E3%81%8B%E3%82%89%E3%82%8F%E3%81%8B%E3%82%8B%E5%A4%A7%E8%A6%8F%E6%A8%A1%E8%A8%80%E8%AA%9E%E3%83%A2%E3%83%87%E3%83%AB-%E7%94%9F%E6%88%90AI%E6%99%82%E4%BB%A3%E3%81%AE%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E9%96%8B%E7%99%BA%E5%85%A5%E9%96%80-%E5%A5%A5%E7%94%B0-%E5%8B%9D%E5%B7%B1/dp/4798185264)