LangGraphによるマルチエージェントRAGの実装

本記事は日本オラクルが運営する下記Meetupで発表予定の内容になります。発表までに今後、内容は予告なく変更される可能性があることをあらかじめご了承ください。

https://oracle-code-tokyo-dev.connpass.com/event/325752/

※本記事は下記記事の補足記事となります。

https://qiita.com/ksonoda/items/f3c703fb7b689fd65868

※ミートアップ実施時の動画は↓こちら。

<iframe width="560" height="315" src="https://www.youtube.com/embed/XQCKTrnxO88?si=o7FeZB6h_no65YbO" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

# はじめに
今年に入ってからLLMを利用するアプリにグラフ技術を応用する論文が多数発表されています。それを受けてかLangChain, Inc.からもLangGraphという名のライブラリが登場しました。このライブラリではこれまでのように一つではなく複数のエージェントを定義し、各エージェントに異なる役割を与え、それらを協業させることにより、プロンプトに対する応答テキストを生成します。
ここで重要になるのは、複数のエージェントをどのように協業させるかというロジックの実装です。その協業ロジック定義、いわば、複数エージェントの協業ワークフローの定義にグラフの技術を応用し、マルチエージェントシステムを実装しようという試みです。これを簡単に実装できるようLangGraphがリリースされました。

# そもそもエージェントとは
昨今はLLMと、その様他の々なサービスを連携させることにより、更に便利な生成AIアプリを開発するというトレンドが続いています。この連携ロジックをプランし実行するコンポーネントをエージェントと呼んでいます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109260/20c78310-dd79-2e21-59ae-7db7e1e21ac0.png)

このエージェントの典型的な実装としてRAGがあります。RAGはLLMが学習していない社内ドキュメントのようなデータをベクトルデータベースに配置しておき、そのドキュメントに関する質問や分析結果を自然言語で行うというソリューションです。

上図では社内就業規定の質問をするチャットアプリのケースですが、質問が入力されると、まず初めに「問い合わせ内容でベクトルDBを検索」してから次に、「検索結果とプロンプトをLLMに入力」というような順序の決定と実際の処理を行う必要がありこれをエージェントとして実装するということになります。

上述の例はLLMとベクトルデータベース連携させていますが、連携する対象は下図のように世の中に存在するあらゆる製品やサービス、更には自分で書いたプログラムなどとの連携が可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109260/9c29bf84-79e0-6751-6091-cdcc9550454a.png)

一般的にはこの単一のエージェント配下でいくつかのサービスや製品を連携させることをエージェントシステムと呼んでいますが、本記事ではこのエージェントを複数定義し、時にはエージェント同士を協業させたり、また時には競争させたりというようなことをさせ、シングルエージェントよりも複雑な処理が行えるマルチエージェントシステムについてご紹介します。

# マルチエージェント
マルチエージェントを理解するためには、シングルエージェント(従来のエージェント)の仕組みの本質を把握している必要があります。シングルエージェントのコードを抽象的な図で表すと下図のようになります。LangChain Agentを使う場合、テキスト生成に必要な主要パーツ(ここではLLM、プロンプト、Tools)をエージェントで纏め、このエージェントがどのパーツをどの順序で使うかというプランをたてて実行してゆく構造になります。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109260/c5661fb1-982b-3bf8-49b6-5436f33dba6d.png)

以前の記事[「自然言語によるSQLデータベースの分析」](https://qiita.com/ksonoda/items/9a40c1017613d3ccf1e0)、[「Function Calling(Tool Calling)でLLMアプリケーションをより多機能に)」](https://qiita.com/ksonoda/items/1ba3916c9ee9f4d9c10c)などの実装はすべてこのLangChain Agentを利用する構成でご紹介しました。複雑なパイプラインを自動化してくれる非常に便利な仕組みです。つまり、エージェントとはプロンプトの入力に応じて複雑なパイプラインを計画し実行してくれるこのLangChain Agentそのものということになります。

そして、この実装の本質的なコンセプトは「LLMによってエージェントに明確な役割が割り当てられている」ような状態を作っているというところです。例えば現在の天気情報を求められた場合、エージェントには「天気情報を検索し、ユーザーのプロンプトに応答する」という役割が割り当てられているということになります。

上図では３つのコンポーネント(LLM, Prompt, Tools)がエージェントにぶら下がっており、エージェントはこれらを使ってプロンプトに必要な処理をこなします。よりたくさんのコンポーネントをエージェントにぶら下げてより複雑で便利なアプリケーションにすることができますが、その際に大きな課題になることがあります。

それは各ツールを使うかどうか、また、どのような順序で使うかなどのプランは気まぐれなLLM任せになってしまうという点です。あまり複雑なパイプラインを実装してしまうと、エージェントが意図しないタイミングで各コンポーネントを使ってしまい、目的のパイプラインが実行できない状態になってしまうケースがあります。

そこで登場するのがマルチエージェントの仕組みです。マルチエージェントでは文字通り複数のエージェントを定義し、各エージェントにそれぞれ異なる役割を与え、協業することで最終的な応答を生成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109260/ecf87fb2-7ecd-7fc4-2a8a-f74467f473d9.png)

例えば、「過去5年間の日本のGDPをチャートにしてください。」というプロンプトの場合、エージェント1には「ウェブ検索をして過去5年間の日本のGDPの情報を収集する」という役割を与え、エージェント2には「収集された情報からチャートを作成するコードを生成する」という役割を与えます。これらが協業することにより最終的な応答を生成するという実装がマルチエージェントRAGの実装です。

このように役割を複数のエージェントに分けることによって、各エージェントをより限られた役割に専念させることで意図しない挙動を抑制することができるようになります。それと同時により複雑なタスクを実行できるようなパイプラインも可能になります。

具体的に書き出すと、シングルエージェントに対するマルチエージェントのメリットには以下のようなものが挙げられます。


| **特性**                   | **シングルエージェントRAG**                                   | **マルチエージェントRAG**                                  |
|----------------------------|-------------------------------------------------------------|-------------------------------------------------------------|
| **専門性の向上**           | 幅広いタスクに対応するが専門性が低い可能性がある              | 各エージェントが特定のドメインに特化し、高い専門性を発揮   |
| **タスクの分割と効率化**    | すべてのタスクを1つのエージェントが処理するため、処理が遅くなる可能性がある | タスクを分割し並行処理することで、全体の処理速度が向上     |
| **柔軟性とスケーラビリティ**| 1つのエージェントに依存するため、スケーラビリティに限界がある  | エージェントを追加・削除することで、システムが柔軟に拡張できる|
| **エラー耐性と信頼性の向上**| エージェントの失敗がシステム全体に影響を与える可能性がある  | 異なるエージェントがタスクをカバーすることで、信頼性が向上 |
| **応答の多様性と品質の向上**| 応答が1つのエージェントに依存するため、多様性が制限される可能性がある | 複数のエージェントが異なる視点で応答を生成し、質と多様性が向上 |


このように、複数のエージェントに異なる役割を与え、時には「協業」また時には「競合」、というように複雑なパイプラインを定義することができる実装がマルチエージェントです。ここで、この「協業」や「競合」をどのように制御するのか？というところでグラフ技術の登場となります。


# グラフとは
グラフとは主にノードとエッジから構成されるデータモデルです。ノードは具体的なもの（例えば、人、場所、製品、イベントなど）や抽象的な概念（例えば、アイデア、カテゴリ、トピックなど）を定義し、エッジはノード間の関係性を表します。例えばノードにSNSのアカウントユーザーを定義した場合、エッジは人とユーザー同士の関係性、ということでフォローしている、されているという情報を定義することになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109260/e2e5ed5a-7a51-8424-d937-21e8e5e19137.png)

このグラフ技術は、RDBでは表現できない複雑なデータのスキーマ構造を定義しなければいけない技術領域で古くからグラフデータベースとして利用されてきました。MetaやLinkedInなどのソーシャルメディアプラットフォームでは、ユーザー間の友人関係やフォロワー関係のモデル化に利用されていますし、AmazonやNetflixのような企業では、ユーザーの購入履歴や視聴履歴に基づいて、関連商品やコンテンツを推薦するシステムで利用されているそうです。


そして、このグラフをマルチエージェントに適用する場合はちょうど下図のような実装になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109260/4bae1aa6-8a0e-52b6-8a1b-5b9c445be9ab.png)

グラフのノードには「エージェント」が定義され、エッジには「エージェント間の実行順序」の関係性が定義されます。この定義により、複数のエージェントの協業モデル(ワークフロー)を定義しているということになります。たとえば「Agent1の処理が実行されたあとは Agent3に遷移し、処理を実行する」というような感じです。

ただし、LangGraphの場合は、単純なエッジではなく、「条件付きエッジ」と呼ばれるタイプになります。その名の通り、条件によってエッジの向き、つまり次に実行されるエージェントが動的に変わるというものです。この「条件」はエージェントの実行結果から取り出される「ステイタス」により決まります。この条件付きエッジによってエージェントの実行順序のロジックを柔軟に定義することができるという非常に重要かつ、マルチエージェントの中核となる概念です。(サンプルコードのパートで詳解します。)


::: note info
上述したグラフの図は非常に単純なものですが実際には下記のように様々なグラフの種類が存在します。




| グラフの種類               | 説明                                                         | 使用例                                   |
|----------------------------|--------------------------------------------------------------|----------------------------------------|
| エンティティ-リレーショングラフ (Entity-Relationship Graph) | エンティティとその関係を示すグラフ。データベース設計に使用。   | データベース設計、情報構造化            |
| セマンティックネットワーク (Semantic Network)         | 概念と意味的な関係を示すグラフ。知識の表現や自然言語処理に使用。 | 知識表現、自然言語処理                  |
| オントロジーグラフ (Ontology Graph)                    | ドメインの概念とその関係を定義するグラフ。セマンティックWebに利用。 | セマンティックWeb、知識管理            |
| トリプルストア (Triple Store)                           | RDFトリプルでデータを表現するグラフ。Linked Dataの基盤として使用。 | Linked Data、セマンティックWeb          |
| コンセプトマップ (Concept Map)                          | 概念とその関係を視覚化するグラフ。知識の整理や教育に利用。       | 教育、知識整理                          |
:::



# ユースケース
LLMを利用する全てのアプリケーションで利用可能です。特にシングルエージェント構成での下記のようなデメリットが発生してしまう場合に適用を検討するとよいかと思います。

- シングルエージェント構成ではテキスト生成の精度が上がらない場合
- シングルエージェント構成で多数のfunction(tool)を定義していることが理由でエージェントが理想的なtoolの使い方をプランできない状況に陥る場合(意図的に特定のtoolを実行させるオプションがありますがそれを定義するとパイプラインの柔軟性が失われます。)
- シングルエージェント構成では実装しきれないような複雑なワークフローが必要になるRAG


# サンプルコードの概説

シナリオとしては、上述したマルチエージェントの説明の図そのまま実装しています。「過去5年間の日本のGDPをチャートにしてください。」というものです。この命題に対し、下図のように2つエージェントを定義し、一つは「ウェブ検索を実行して過去5年間の日本のGDPを調査する」という役割を与えたエージェントを、そしてもう一つは「収集された情報からチャートを作成する」という役割を与えたエージェントを定義します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109260/ecf87fb2-7ecd-7fc4-2a8a-f74467f473d9.png)

これを上述したようにグラフ構造と条件分岐の説明を入れた図が下記になります。オリジナルはLangChainのチュートリアルノートブックにありますが、少しわかりにくいので手を加えてみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109260/6e73f5b6-cda2-f44f-500a-8147bf66da30.png)

コード概説を全て読み終えた時点で再度確認いただけると、コード外観が模式図化されていることがわかるのではないかと思います。今の時点では

- どのようなエージェントが構成されているのか？
Resercher(インターネットを検索し過去5年の日本のGDPの情報を収集する役割)と、Chart Generator(収集されたデータからチャートを作成するPythonコードを生成する役割)の2つのエージェントが構成されています。

- どのようなノードが構成されているのか？
上述のエージェントに対応した2つのノード以外にcall_toolノードがあります。つまり、上述したように、この３つのノードが遷移先になるということです。例えば、Researcherエージェントノードがインターネット検索をするためにfunction callingを実行する場合、call_toolノードに遷移するというイメージです。

- 遷移先の次ノードはどのように決まるのか？
ノードでの処理結果から条件付きエッジの「条件」となる情報を抽出し、その内容によって次のノードが決まります。この「条件」を抽出する関数がrouterです。その条件により3つのノードを経由しながら、最終的に条件が「end」となった場合に、応答テキストがユーザーに返され、処理完了。

ということだけ理解いただければいいかと思います。

::: note info
LangGraphを使ったマルチエージェントの実装はサンプル実装として3パターンが公開されています。

- [Basic Multi-agent Collaboration](https://github.com/langchain-ai/langgraph/blob/main/examples/multi_agent/multi-agent-collaboration.ipynb)

- [Agent Supervisor](https://github.com/langchain-ai/langgraph/blob/main/examples/multi_agent/agent_supervisor.ipynb)

- [Hierarchical Agent Teams](https://github.com/langchain-ai/langgraph/blob/main/examples/multi_agent/hierarchical_agent_teams.ipynb)

本記事ではBasic Multi-agent Collaborationのパターンを取り上げています。
::: 


ここから実装の概説なのですが、このコードを先頭から順に概説すると説明がとても分かりにくくなるためグラフ構造に関する部分から説明をしてゆこうと思います。実際のコードは[こちら](https://github.com/langchain-ai/langgraph/blob/main/examples/multi_agent/multi-agent-collaboration.ipynb)になり、下記のコード概説と順序はほぼ真逆になりますが、わかりやすさを優先してこのような説明にしたいと思います。

#### グラフの構造
はじめに、このコードで定義しているグラフの構造について概説します。(実際はコードのほぼ最終局面です。)

始めにグラフの定義です。

```python
workflow= StateGraph(AgentState)
```

このグラフにノードを追加します。このシナリオでは下記3つのノードを定義しています。

```python
workflow.add_node("Researcher", research_node)
workflow.add_node("Chart Generator", chart_node)
workflow.add_node("call_tool", tool_node)
```

各ノードの役割は以下の通りです。

- Researcher : tavilysearchを使ってインターネットを検索するtoolを処理するノード
- Chart Generator : Researcherノードが検索した結果の情報を使ってmatplotlibのpythonコードでチャートを作成するノード
- call_tool : エージェントがツールを呼び出す際にそのツールを実行し、その実行結果をメッセージとして返すことです。つまり、Researcher と Chart Generator のノードの両方からこのノードは接続される可能性があります。

このグラフの処理の一番最初の処理は下記コードで"Researcher"ノードと定義しています。

```python
workflow.set_entry_point("Researcher")
```

次にエッジの定義を追加します。

通常のエッジはノードAからノードBに直接的に遷移するリンクを表します。特定の条件がなく、ノードAの処理が完了したら自動的にノードBに移動します。一度設定されると、特定の条件評価なしで決まった次のノードに進みます。通常のエッジはシンプルなシーケンシャルなワークフローに適しています。

ですが、条件付きエッジは、ノード間の遷移が条件に基づいて決定されるエッジです。つまり、次に進むノードは、特定の条件や関数の評価結果によって動的に決まります。エッジに設定された関数が状態 (state) を評価し、その結果に基づいてどのノードに進むかが決まります。 条件付きエッジは、複数の可能性がある場合や、動的にワークフローを制御する必要がある場合に適しています。

このコードでは3つの条件付きエッジが設定されています。3つのノードがありますので、各ノードを起点に、どのような条件のときに、どのノードへ遷移するかという条件が記載されています。(条件がどのように決まるかは後述します。)

下記はResearcherノードを起点として、3パターンの遷移条件が定義されています。


```python
workflow.add_conditional_edges(
    "Researcher",　# <- 起点のエージェント
    router,　# <- ルーターの戻り値を条件とするという定義
    {"continue": "Chart Generator", "call_tool": "call_tool", "end": END},　# <- 3パターンの定義
)
```

- パターン1
router関数の戻り値が"continue"の場合は"Chart Generator"へ遷移し、Chart Generatorがその役割を果たします。つまり、チャートを作れるデータが揃った状態だと判断され、実際にチャートを作るべくChart Generatorノードへ遷移するというパターンです。

- パターン2
router関数の戻り値が"call_tool"の場合はcall_toolノードへ遷移し、call_toolがその役割を果たします。つまり、起点のResearcherノードが担当している役割のtoolであるtavily_toolを実行しインターネットから情報を収集するべくcall_toolノードへ遷移するというパターンです。

- パターン3
router関数の戻り値が"end"の場合は全てのノードが必要な処理を完了している状況なので、遷移は終了というパターンです。


次はChart Generatorノードを起点とした条件付きエッジの定義です。Researcherノードと同様の考え方で、各定義の説明を置き換える理解の仕方です。


```python
workflow.add_conditional_edges(
    "Chart Generator",
    router,
    {"continue": "Researcher", "call_tool": "call_tool", "end": END},
)
```

勘違いしやすいのですが、ResearcherノードおよびChart Generatorノードでtoolを実行する必要がある場合は必ず、tool_callノードに遷移し、tavily_toolかpython_replが実行されます。tool_callノードがtoolを実行すると、その実行結果を必ずその送信先(sender)のノードに返し、Graphのフローの一貫性を保ちます。つまり、Researcherノードがsenderだった場合はtool_callノードがtavily_toolを実行し、その実行結果をResearcherノードに返します。同様に、Chart Generatorノードがsenderだった場合はtool_callノードがpython_replを実行し、その実行結果をChart Generatorノードに返します。この条件を下記コードで定義しています。これまでと同じように起点であるcall_toolノードが定義されており、それ以降はsenderのノードと、tool実行結果をどのノードに戻すかという定義になります。


そのcall_toolノードを起点とした条件付きエッジの定義が以下になります。

```python
workflow.add_conditional_edges(
    "call_tool",
		lambda x: x["sender"],
    {
        "Researcher": "Researcher",
        "Chart Generator": "Chart Generator",
    },
)
```

最後にここまでの定義をコンパイルしてグラフモデルが出来上がります。

```python
graph= workflow.compile()
```

ここから、条件付きエッジの「条件」がどのように決まるのかをみてゆきたいと思います。LangGraphでは、各ノードの持つステイタス(≒条件)によって、遷移する次のノードが決まると上述しました。その条件を定義し、条件付きエッジに渡す処理をAgentState()クラスとrouter()という二つの関数で実行されます。

まず、ノードのステイタスを表すAgentState()クラスです。

```python
class AgentState(TypedDict):
    messages: Annotated[Sequence[BaseMessage], operator.add]
    sender: str
```

AgentState()クラスには"messages"と"sender"の2つ変数が定義されています。つまり、この二つの変数によりノードのステイタスが表現されています。

- messages
プロンプトの内容や、各ノード(が担当する役割、つまりtool)の実行結果などが入っています。(詳細はプロンプト実行結果で表示されるログに記載されています。)
- sender
どのノードの役割として実行されたのかという情報が入っています。

ステイタスの各情報はエージェントやツールが何らかのアクションを行ったとき、またはノード間で遷移が行われたときにアップデートされ、ノード間で共有されます。

そしてrouter関数はAgentStateクラスからノードのステイタスを表すmessagesとsenderから条件に関係する情報を取りだし、
条件付きエッジに渡すための関数です。

```python
def router(state):
    # This is the router
    messages = state["messages"]
    last_message = messages[-1]
    if "function_call" in last_message.additional_kwargs:
        # The previus agent is invoking a tool
        return "call_tool"
    if "FINAL ANSWER" in last_message.content:
        # Any agent decided the work is done
        return "end"
    return "continue"
```

- AgentStateからmessagesを取り出し、その中に"function_call"が含まれていれば前のエージェントがツールを呼び出しています。この場合、"call_tool"を返します。

- "FINAL ANSWER"が含まれている場合、どのエージェントも作業が完了したと判断していることを意味します。この場合、"end"を返します。

- 上記のいずれの条件にも該当しない場合、処理を続行する必要があると判断されます。この場合、"continue"を返します。

各ステイタスが条件付きエッジの中でどのように扱われるかは「条件付きエッジ」の箇所で説明した通りです。

ここまでグラフ構造について説明してきました。

#### ツールの定義とcall_toolノードの定義

この実装で定義しているツールは2つだけです。
一つはウェブ検索をするためのツールです。こちらはウェブ検索に[TavilySearch](https://tavily.com/)を利用します。google検索でもいいのですが、TavilySearchのほうがRAGに最適化されているということもあり、LLMアプリでウェブ検索の処理を入れる場合はよく使われます。LangChainにはTavilySearchと連携するAPIがありますので下記のようなコードで簡単に定義できます。

```python
from langchain_core.tools import tool
from langchain_community.tools.tavily_search import TavilySearchResults
tavily_tool = TavilySearchResults(max_results=5)
```

一つ目のツールの定義が終了です。

続いて二つ目のツールです。ウェブ検索から得られたデータを使ってチャートを作るためのpythonコードを生成するツールになります。最終的にはmatplotlibを使ってチャート作成するため、matplotlibのpythonコードをLLMに生成してもらい、そのコードを実行するツールを定義します。このツールにはLangChainのPythonREPLを使います。PythonREPLはLLMから受け取ったテキストをPythonのコードとして実行するためのクラスです。

```python
from langchain_experimental.utilities import PythonREPL
from typing import Annotated

repl = PythonREPL()

@tool
def python_repl(code: Annotated[str, "チャートを生成するために実行する Python コード"]):
    """
    これを使用して Python コードを実行します。
    値の出力を確認したい場合は、`print(...)` で出力する必要があります。
    これはユーザーに表示されます。
    """
    try:
        result = repl.run(code)
    except BaseException as e:
        return f"Failed to execute. Error: {repr(e)}"
    return f"Succesfully executed:\\\\n`python\\\\\\\\n{code}\\\\\\\\n`\\\\nStdout: {result}"

```

以上でツールの完成です。2つともかなりシンプルなツールとなっています。

定義した2つのツールをリストに纏めます。

```python
tools = [tavily_tool, python_repl]
```

纏めたtoolsを実行するcall_toolノードを作るため関数tool_nodeを定義します。この関数は上述したグラフ構造にcall_toolノードを追加するときに使われています。

```python
tool_executor = ToolExecutor(tools)

def tool_node(state):
    """
    これにより、グラフ内のツールが実行されます。
    エージェントのアクションを受け取り、そのツールを呼び出して結果を返します。
    """
    messages = state["messages"]
    # Based on the continue condition
    # we know the last message involves a function call
    last_message = messages[-1]
    # We construct an ToolInvocation from the function_call
    tool_input = json.loads(
        last_message.additional_kwargs["function_call"]["arguments"]
    )
    # We can pass single-arg inputs by value
    if len(tool_input) == 1 and "__arg1" in tool_input:
        tool_input = next(iter(tool_input.values()))
    tool_name = last_message.additional_kwargs["function_call"]["name"]
    action = ToolInvocation(
        tool=tool_name,
        tool_input=tool_input,
    )
    # We call the tool_executor and get back a response
    response = tool_executor.invoke(action)
    # We use the response to create a FunctionMessage
    function_message = FunctionMessage(
        content=f"{tool_name} response: {str(response)}", name=action.tool
    )
    # We return a list, because this will get added to the existing list
    return {"messages": [function_message]}
```


#### Resercher、Chart Generator用のエージェントの定義

エージェントを作るための関数create_agentを定義します。この関数はResercherエージェントだけでなくChart Generatorエージェントを作るときにも使われます。

```python
import json

from langchain_core.messages import (
    AIMessage,
    BaseMessage,
    ChatMessage,
    FunctionMessage,
    HumanMessage,
)
from langchain_core.utils.function_calling import convert_to_openai_function
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langgraph.graph import END, StateGraph
from langgraph.prebuilt.tool_executor import ToolExecutor, ToolInvocation

def create_agent(llm, tools, system_message: str):
    """エージェントを作成します。"""
    functions = [convert_to_openai_function(t) for t in tools]
    prompt = ChatPromptTemplate.from_messages(
        [
            (
                "system",
                " あなたは他のアシスタントと協力して、役に立つ AI アシスタントです。 "
                " 提供されたツールを使用して、質問の回答に進みます。 "
                " 完全に答えることができなくても大丈夫です。 "
                " 別のツールを備えた別のアシスタントが中断したところからサポートします。進歩するためにできることを実行してください。"
                " あなたまたは他のアシスタントが最終的な回答または成果物を持っている場合は、チームが停止することがわかるように、回答の前に「FINAL ANSWER」を付けます。"
                " 次のツールにアクセスできます: {tool_names}.\\\\n{system_message}",
            ),
            MessagesPlaceholder(variable_name="messages"),
        ]
    )
    prompt = prompt.partial(system_message=system_message)
    prompt = prompt.partial(tool_names=", ".join([tool.name for tool in tools]))
    return prompt | llm.bind_functions(functions)
```

上述のcreate_agent関数を使って、下記でresearch_agentとchart_agentを作っています。

```python
research_agent= create_agent(
    llm,
    [tavily_tool],
    system_message="Chart Generatorが使用する正確なデータを提供する必要があります。",
)

chart_agent= create_agent(
    llm,
    [python_repl],
    system_message="表示したグラフはすべてユーザーに表示されます。",
)
```
#### Resercher、Chart Generator用のノードを定義

Resercher、Chart Generator用のノードを作る関数を定義します。

```python
def agent_node(state, agent, name):
    result = agent.invoke(state)
    if isinstance(result, FunctionMessage):
        pass
    else:
        result = HumanMessage(**result.dict(exclude={"type", "name"}), name=name)
    return {
        "messages": [result],
        # Since we have a strict workflow, we can
        # track the sender so we know who to pass to next.
        "sender": name,
    }
```

上述したagent_node関数と、その前に作ったresearch_agent、chart_agentを使って、research_nodeとchart_nodeを作ります。

```python
import functools
research_node= functools.partial(agent_node, agent=research_agent, name="Researcher")
chart_node= functools.partial(agent_node, agent=chart_agent, name="Chart Generator")
```

この2つのノード定義(research_nodeとchart_node)ももちろん、上述したグラフ構造にノードとして追加するときに使われています。(LangSmithはオプションです。)

概説の最後になりましたが、OpenAI、TavilySearchのAPIキーの定義とOpenAIのLLMの定義をしておく必要があります。

```python
import getpass
import os

def _set_if_undefined(var: str):
    if not os.environ.get(var):
        os.environ[var] = getpass.getpass(f"Please provide your {var}")

_set_if_undefined("OPENAI_API_KEY")
_set_if_undefined("LANGCHAIN_API_KEY")
_set_if_undefined("TAVILY_API_KEY")

# Optional, add tracing in LangSmith
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_PROJECT"] = "Multi Agent"

from langchain_openai import ChatOpenAI
llm = ChatOpenAI()
```

以上、でグラフ構造の説明、グラフに追加されるノードの定義についての概説でした。

実際に以下のようなプロンプトを実行してみます。

```python
for s in graph.stream(
    {
        "messages": [
            HumanMessage(
                content="過去5年の日本のGDPを調査してください。"
                " 次に折れ線グラフを描きます。"
                " コード化したら完了です。"
            )
        ],
    },
    # Maximum number of steps to take in the graph
    {"recursion_limit": 100},
):
    print(s)
    print("----")
```

出力は下記の通り。

```python
{'Researcher': {'messages': [HumanMessage(content='', additional_kwargs={'function_call': {'arguments': '{"query":"Japan GDP by year for the past 5 years"}', 'name': 'tavily_search_results_json'}, 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 27, 'prompt_tokens': 381, 'total_tokens': 408}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'function_call', 'logprobs': None}, name='Researcher', id='run-9917ec8a-8b05-47e6-87c6-313aa4235072-0', usage_metadata={'input_tokens': 381, 'output_tokens': 27, 'total_tokens': 408}, tool_calls=[], invalid_tool_calls=[])], 'sender': 'Researcher'}}
----
{'call_tool': {'messages': [FunctionMessage(content='tavily_search_results_json response: [{\'url\': \'https://tradingeconomics.com/japan/gdp-growth-annualized\', \'content\': \'GDP Growth Annualized in Japan averaged 1.86 percent from 1980 until 2023, reaching an all time high of 24.00 percent in the third quarter of 2020 and a record low of -27.90 percent in the second quarter of 2020. source: Cabinet Office, Japan\\nGDP Growth Annualized in Japan decreased to -2.90 percent in the third quarter of 2023 from 3.60 percent in the second quarter of 2023.\\n This was the first yearly contraction in economic activity in a year and the sharpest pace since the height of the pandemic in Q2 of 2020, reflecting the fragility of recovery due to uncertainties stemming from elevated inflation and a cloudy global economic outlook. Markets\\nGDP\\nLabour\\nPrices\\nHealth\\nMoney\\nTrade\\nGovernment\\nBusiness\\nConsumer\\nHousing\\nTaxes\\nClimate GDP Growth Annualized in Japan decreased to -2.90 percent in the third quarter of 2023 from 3.60 percent in the second quarter of 2023.\'}, {\'url\': \'https://ycharts.com/indicators/japan_gdp_usd\', \'content\': \'Japan GDP is at a current level of 4.256T, down from 5.035T one year ago. This is a change of -15.46% from one year ago. In depth view into Japan GDP including historical data from 1960 to 2021, charts and stats.\'}, {\'url\': \'https://tradingeconomics.com/japan/gdp-growth-annual\', \'content\': \'The Gross Domestic Product (GDP) in Japan contracted 0.20 percent in the first quarter of 2024 over the same quarter of the previous year. GDP Annual Growth Rate in Japan averaged 1.74 percent from 1981 until 2024, reaching an all time high of 9.40 percent in the first quarter of 1988 and a record low of -9.70 percent in the second quarter of ...\'}, {\'url\': \'https://www.macrotrends.net/global-metrics/countries/JPN/japan/gdp-gross-domestic-product\', \'content\': "Japan gdp for 2021 was $5,034.62B, a 0.41% decline from 2020. Japan gdp for 2020 was $5,055.59B, a 1.22% decline from 2019. Japan gdp for 2019 was $5,117.99B, a 1.53% increase from 2018. GDP at purchaser\'s prices is the sum of gross value added by all resident producers in the economy plus any product taxes and minus any subsidies not included ..."}, {\'url\': \'https://www.macrotrends.net/global-metrics/countries/JPN/japan/economic-growth-rate\', \'content\': "Japan economic growth for 2018 was $5,543.98B, a 1.01% increase from 2018. Japan economic growth for 2018 was $5,488.45B, a 0.13% decline from 2018. Japan economic growth for 2018 was $5,495.85B, a 1.45% increase from 2018. Japan economic growth for 2018 was $5,417.32B, a 0.59% increase from 2017. GDP at purchaser\'s prices is the sum of gross ..."}, {\'url\': \'https://tradingeconomics.com/japan/gdp\', \'content\': \'The Gross Domestic Product (GDP) in Japan was worth 4212.95 billion US dollars in 2023, according to official data from the World Bank. The GDP value of Japan represents 4 percent of the world economy. This page provides - Japan GDP - actual values, historical data, forecast, chart, statistics, economic calendar and news.\'}, {\'url\': \'https://www.worldometers.info/gdp/japan-gdp/\', \'content\': \'Real GDP (constant, inflation adjusted) of Japan reached $4,553,470,000,000 in 2022. GDP Growth Rate in 2022 was 1.03%, representing a change of 45,900,000,000 US$ over 2021, when Real GDP was $4,462,650,000,000. GDP per Capita in Japan (with a population of 124,997,578 people) was $36,069 in 2022, an increase of $561 from $35,508 in 2021; this ...\'}, {\'url\': \'https://www.macrotrends.net/global-metrics/countries/JPN/japan/gdp-growth-rate\', \'content\': \'It is calculated without making deductions for depreciation of fabricated assets or for depletion and degradation of natural resources. Japan gdp growth rate for 2022 was 0.95%, a 1.6% decline from 2021. Japan gdp growth rate for 2021 was 2.56%, a 6.71% increase from 2020. Japan gdp growth rate for 2020 was -4.15%, a 3.74% decline from 2019.\'}, {\'url\': \'https://data.worldbank.org/indicator/NY.GDP.MKTP.KD.ZG?locations=JP\', \'content\': \'GDP growth (annual %) - Japan. World Bank national accounts data, and OECD National Accounts data files. License : CC BY-4.0. LineBarMap. Also Show Share Details. Label. 1961 - 2022.\'}, {\'url\': \'https://ycharts.com/indicators/japan_gdp\', \'content\': \'Basic Info. Japan GDP is at a current level of 149.52T, up from 149.12T last quarter and up from 146.40T one year ago. This is a change of 0.27% from last quarter and 2.13% from one year ago. Report. Quarterly Estimates of GDP.\'}]', name='tavily_search_results_json')]}}
----
{'Researcher': {'messages': [HumanMessage(content='過去5年間の日本のGDPデータを取得しました。以下にそれらのデータを示します:\n\n- 2023年のGDP: 4,212.95 billion US dollars\n- 2022年のGDP: $4,553,470,000,000\n- 2021年のGDP: $5,034.62 billion\n- 2020年のGDP: $5,055.59 billion\n- 2019年のGDP: $5,117.99 billion\n\nこれらのデータを使用して、折れ線グラフを作成することができます。グラフを作成するためのコードを生成しますか？', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 171, 'prompt_tokens': 1693, 'total_tokens': 1864}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-dd84c334-6efd-48e5-969f-d739874b11f9-0', usage_metadata={'input_tokens': 1693, 'output_tokens': 171, 'total_tokens': 1864}, tool_calls=[], invalid_tool_calls=[])], 'sender': 'Researcher'}}
----
{'Chart Generator': {'messages': [HumanMessage(content='', additional_kwargs={'function_call': {'arguments': '{"code":"import matplotlib.pyplot as plt\\n\\ndates = [2019, 2020, 2021, 2022, 2023]\\nGDP_values = [5117.99, 5055.59, 5034.62, 4553.47, 4212.95]\\n\\nplt.figure(figsize=(10,6))\\nplt.plot(dates, GDP_values, marker=\'o\')\\nplt.title(\'Japan GDP Over the Past 5 Years\')\\nplt.xlabel(\'Year\')\\nplt.ylabel(\'GDP (billion US dollars)\')\\nplt.grid(True)\\nplt.show()"}', 'name': 'python_repl'}, 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 148, 'prompt_tokens': 1905, 'total_tokens': 2053}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'function_call', 'logprobs': None}, name='Chart Generator', id='run-5ec11ccd-0e74-404d-b35b-bae66a17d438-0', usage_metadata={'input_tokens': 1905, 'output_tokens': 148, 'total_tokens': 2053}, tool_calls=[], invalid_tool_calls=[])], 'sender': 'Chart Generator'}}
----
{'call_tool': {'messages': [FunctionMessage(content="python_repl response: Succesfully executed:\\\\n`python\\\\\\\\nimport matplotlib.pyplot as plt\n\ndates = [2019, 2020, 2021, 2022, 2023]\nGDP_values = [5117.99, 5055.59, 5034.62, 4553.47, 4212.95]\n\nplt.figure(figsize=(10,6))\nplt.plot(dates, GDP_values, marker='o')\nplt.title('Japan GDP Over the Past 5 Years')\nplt.xlabel('Year')\nplt.ylabel('GDP (billion US dollars)')\nplt.grid(True)\nplt.show()\\\\\\\\n`\\\\nStdout: ", name='python_repl')]}}
----
```

![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109260/c85dca5d-f196-2f32-4280-415c49005fcb.png)


以上、LangGraphによるマルチエージェントRAGの実装でした。