見出し画像

Google Colab で LangGraph を試す

「Google Colab」で「LangGraph」を試したので、まとめました。

・LangChain v0.1.4


1. LangGraph

LangGraph」は、LLMでステートフルな「マルチアクターアプリケーション」を構築するためのライブラリです。「LCEL」(LangChain Expression Language) を拡張して、複数チェーン (またはアクター) を複数ステップにわたって循環的に協調動作させることができます。

LangChain」の大きな価値の1つに、カスタムチェーンを簡単に作成できることがあります。このための機能として「LCEL」を提供してきましたが、サイクルを簡単に導入する方法がありませんでした。「LangGraph」によって、LLMアプリケーションにサイクルを簡単に導入できるようになりました。

2. Colabでの実行

Colabでの実行手順は、次のとおりです。

2-1. セットアップ

(1) パッケージのインストール。

# パッケージのインストール
!pip install -U langgraph
!pip install -U langchain langchain_openai tavily-python

(2) 環境変数の準備。
左端の鍵アイコンで「OPENAI_API_KEY」と「TAVILY_API_KEY」を設定してからセルを実行してください。

# 環境変数の準備 (左端の鍵アイコンでOPENAI_API_KEYTAVILY_API_KEYを設定)
import os
from google.colab import userdata
os.environ["OPENAI_API_KEY"] = userdata.get("OPENAI_API_KEY")
os.environ["TAVILY_API_KEY"] = userdata.get("TAVILY_API_KEY")

2-2. ツールとモデルの準備

(1) ツールの準備。
今回は、ツール「TavilySearchResults」を使用します。
AIエージェント専用の検索エンジン「Tavily」で、リアルタイムで正確かつ事実に基づく情報を取得するツールになります。

from langchain_community.tools.tavily_search import TavilySearchResults

# ツールの準備
tools = [TavilySearchResults(max_results=1)]

(2) ToolExecutorの準備。
ToolExecutor」は、ToolInvocationを受け取り、そのツールを呼び出して、出力を返す単純なクラスです。

from langgraph.prebuilt import ToolExecutor

# ToolExecutorの準備
tool_executor = ToolExecutor(tools)

(3) モデルの準備。
今回は、「ChatOpenAI」を使用します。

from langchain_openai import ChatOpenAI

# モデルの準備
model = ChatOpenAI(temperature=0, streaming=True)

(4) モデルにツールをバインド。
「ツール」を「OpenAI Function Calling」形式に変換し、「モデル」にバインドします。

from langchain.tools.render import format_tool_to_openai_function

# モデルにツールをバインド
functions = [format_tool_to_openai_function(t) for t in tools]
model = model.bind_functions(functions)

2-3. グラフの定義

「LangGraph」の主な要素は、次の3つです。

・StateGraph : ステートフルなグラフ
・Node
: ノード (頂点)
・Edge
: エッジ (辺)

「グラフ」とは、「ノード」 と 「エッジ」で構成される数学的な構造です。「ノード」はグラフ内の個別の点や位置を表し、「エッジ」はこれらの点や位置の間の関係や接続を表します。この概念は、様々な関係や接続をモデル化するのに非常に有用であり、数学のグラフ理論において中心的な役割を果たします。

(1) StateGraphの準備。
StateGraph」は、ステートフルなグラフを表すクラスです。初期化時に、「State」を渡します。「State」は時間の経過とともに更新される状態で、グラフ内の「Node」によって更新されます。「State」の属性の更新方法 (override or add) も指定します。

今回は、メッセージリスト (Sequence[BaseMessage]) を持つ「State」で、「StageGraph」を準備します。操作はメッセージ追加のみ (operator.add) とします。

from typing import TypedDict, Annotated, Sequence
import operator
from langchain_core.messages import BaseMessage
from langgraph.graph import StateGraph

# Stateの準備
class AgentState(TypedDict):
    messages: Annotated[Sequence[BaseMessage], operator.add]

# StateGraphの準備
workflow = StateGraph(AgentState)

(2) NodeとEdgeの準備。
「Node」の種類には、「Runnable」と「Function」があります。

・Runnable : 実行するアクションを決定するエージェント
・Function : エージェントが決定したアクションを実行する関数

「Edge」の種類には、「Conditional Edge」と「Normal Edge」があります。

・Conditional Edge : 条件付きエッジ
・Normal Edge : ノーマルエッジ

今回は、循環する2つのノードのグラフを作成します。

・agentノード : LLMを呼び出す
・actionノード: ツールを呼び出す

agentノードの処理を定義する関数、actionノードの処理を定義する関数、そして、agentノードの次に呼び出されるノードを決定する関数を準備します。

from langgraph.prebuilt import ToolInvocation
import json
from langchain_core.messages import FunctionMessage

# agentノードの次に呼び出されるノードを決定する関数の準備
def should_continue(state):
    messages = state['messages']
    last_message = messages[-1]

    # function_call がない場合は終了
    if "function_call" not in last_message.additional_kwargs:
        return "end"
    # そうでない場合は続行
    else:
        return "continue"

# agentノードの処理を定義する関数の準備
def call_model(state):
    messages = state['messages']

    # モデルの呼び出し
    response = model.invoke(messages)

    # メッセージリストに追加するメッセージを返す
    return {"messages": [response]}

# actionノードの処理を定義する関数の準備
def call_tool(state):
    messages = state['messages']
    last_message = messages[-1]

    # ツールの呼び出し
    action = ToolInvocation(
        tool=last_message.additional_kwargs["function_call"]["name"],
        tool_input=json.loads(last_message.additional_kwargs["function_call"]["arguments"]),
    )
    response = tool_executor.invoke(action)
    function_message = FunctionMessage(content=str(response), name=action.tool)

    # メッセージリストに追加するメッセージを返す
    return {"messages": [function_message]}

(3) グラフの定義。
グラフを定義して、最後にコンパイルすることで、Runnableとして使用できるようになります。

from langgraph.graph import END

# グラフに2つのノードを追加
workflow.add_node("agent", call_model)
workflow.add_node("action", call_tool)

# agentをエントリポイントとして指定
workflow.set_entry_point("agent")

# agentノードに条件付きエッジを追加
workflow.add_conditional_edges(
    "agent", # 開始ノード
    should_continue, # 呼び出されるノードを決定する関数
    {
        "continue": "action",  # actionノードに遷移
        "end": END  # 終了
    }
)

# actionノードからagentノードへのノーマルエッジを追加
workflow.add_edge('action', 'agent')

# コンパイル
app = workflow.compile()

(4) 2023年4月以前の情報をLLMの知識のみで答えるのを確認。

from langchain_core.messages import HumanMessage

# 質問応答
inputs = {"messages": [HumanMessage(content="ぼっち・ざ・ろっくのぼっちちゃんの得意な楽器は?")]}
app.invoke(inputs)
{
    'messages': [
        HumanMessage(content='ぼっち・ざ・ろっくのぼっちちゃんの得意な楽器は?'),
        AIMessage(content='ぼっちちゃんの得意な楽器はギターです。')
    ]
}

(5) 最新の情報をツールで調べて答えるのを確認。

from langchain_core.messages import HumanMessage

# 質問応答
inputs = {"messages": [HumanMessage(content="バーンブレイバーンの主人公の名前は?")]}
app.invoke(inputs)
{
    'messages': [
        HumanMessage(content='バーンブレイバーンの主人公の名前は?'),
        AIMessage(
            content='', 
            additional_kwargs={
                'function_call': {
                    'arguments': '{\n  "query": "バーンブレイバーン 主人公 名前"\n}', 
                    'name': 'tavily_search_results_json'
                 }
            }
        ),
        FunctionMessage(
            content="[{
                'url': 'https://game.watch.impress.co.jp/docs/kikaku/1562690.html', 
                'content': '【冬アニメ2024】大張正己氏によるロボットアニメ! 「勇気爆発バーンブレイバーン」が2024年1月11日放送開始...'
            }]", 
            name='tavily_search_results_json'
        ),
        AIMessage(content='バーンブレイバーンの主人公の名前はイサミです。')
    ]
}

3. ストリーミング

「LangGraph」は、各ノードの出力を簡単にストリーミングすることができます。

# ストリーミング
inputs = {"messages": [HumanMessage(content="バーンブレイバーンの主人公の名前は?")]}
for output in app.stream(inputs):
    for key, value in output.items():
        print(f"Output from node '{key}':")
        print("---")
        print(value)
    print("\n---\n")
Output from node 'agent':
---
{'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\n  "query": "バーンブレイバーン 主人公 名前"\n}', 'name': 'tavily_search_results_json'}})]}

---

Output from node 'action':
---
{'messages': [FunctionMessage(content="[{'url': 'https://game.watch.impress.co.jp/docs/kikaku/1562690.html', 'content': '【冬アニメ2024】大張正己氏によるロボットアニメ! 「勇気爆発バーンブレイバーン」が2024年1月11日放送開始...'}]", name='tavily_search_results_json')]}

---

Output from node 'agent':
---
{'messages': [AIMessage(content='バーンブレイバーンの主人公の名前はイサミです。')]}

---

Output from node '__end__':
---
{'messages': [HumanMessage(content='バーンブレイバーンの主人公の名前は?'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\n  "query": "バーンブレイバーン 主人公 名前"\n}', 'name': 'tavily_search_results_json'}}), FunctionMessage(content="[{'url': 'https://game.watch.impress.co.jp/docs/kikaku/1562690.html', 'content': '【冬アニメ2024】大張正己氏によるロボットアニメ! 「勇気爆発バーンブレイバーン」が2024年1月11日放送開始...'}]", name='tavily_search_results_json'), AIMessage(content='バーンブレイバーンの主人公の名前はイサミです。')]}

---

関連



この記事が気に入ったらサポートをしてみませんか?