業界・業務から探す
導入目的・課題から探す
データ・AIについて学ぶ
News
Hakkyについて
ウェビナーコラム
◆トップ【AI・機械学習】
AI

執筆者:Handbook編集部

LangGraphとは?サンプルコードをもとにわかりやすく解説!

はじめに

これまでの LangChain は一本の鎖のように A=>B=>C というように、処理が連続していて一方向にデータが流れていく構造でした。 LangGraph の登場によって簡単にサイクル(繰り返し循環する)を実装することができるようになりました。 LangChain についてはLangChain とは?各モジュールの機能と活用事例まとめLangChain のエージェントについてで解説しています。

LangGraph の全体像

それでは公式のサンプルに沿って実装しながら解説していきます。 公式ではTavilyという検索ツールを使って、学習データにない情報を取得して質問に答えるというサンプルが紹介されています。 完全なコードは公式のサンプルを参照してください。

今回はこのような循環する二つのノードのグラフを作成します。

まずは、上の図を実際にコードで定義してみます。コード内で登場する未実装の関数は後ほど解説します。

from langgraph.graph import StateGraph, END
# 新しいグラフを定義
workflow = StateGraph(AgentState)

#先ほどの図の二つのノードを定義
workflow.add_node("agent", call_model)
workflow.add_node("action", call_tool)

#エントリーポイントをagentに設定(一番初めに呼ばれる)
workflow.set_entry_point("agent")

#agent →action という条件付きエッジを作成
workflow.add_conditional_edges(
    #開始ノードを指定
    "agent",
    #条件が定義されどのノードを呼ぶか判断する関数を指定
    should_continue,
    {
        #続けるのならばactionノードを呼ぶ
        "continue": "action",
        #続けないのならばENDノードを呼ぶ(ライブラリによって実装済み)
        "end": END
    }
)

#action→agentというノーマルエッジを作成
workflow.add_edge('action', 'agent')
app = workflow.compile()

#条件付きエッジの実装
def should_continue(state):
    messages = state['messages']
    last_message = messages[-1]
    #messagesの一番後ろ(最も最近に追加されたメッセージ)にfunction_callが含まれていない場合はENDノードを呼ぶ
    #function_callにはarguments(queryなど)とtoolのnameが含まれている
    if "function_call" not in last_message.additional_kwargs:
        return "end"
    else:
        return "continue"

ノードの処理の解説

先ほどの例で追加した二つのノードがどのような実装をされているのか見ていきましょう。 まずは Action ノードの処理から解説します。

from langchain_community.tools.tavily_search import TavilySearchResults
from langgraph.prebuilt import ToolExecutor

#LLMが学習していない情報を取得するために検索ツールのTavilyを使用
#ツール作成は公式ページを参照
tools = [TavilySearchResults(max_results=1)]
tool_executor = ToolExecutor(tools)

#Actionノードの処理
def call_tool(state):
    messages = state['messages']
    last_message = messages[-1]
    # Agentから渡されたfunction_callの情報からToolInvocationを作成
    action = ToolInvocation(
        tool=last_message.additional_kwargs["function_call"]["name"],
        tool_input=json.loads(last_message.additional_kwargs["function_call"]["arguments"]),
    )
    # tool_executorを使用してツールを実行
    response = tool_executor.invoke(action)
    # 実行結果をFunctionMessageとして返す
    function_message = FunctionMessage(content=str(response), name=action.tool)
    return {"messages": [function_message]}

続いて Agent ノードの処理を解説します。

from langchain_openai import ChatOpenAI
from langchain.tools.render import format_tool_to_openai_function

#ストリーミングを有効にするとレスポンスが随時届く
model = ChatOpenAI(temperature=0, streaming=True)

#モデルに使用可能なツールがあることを知らせる。
#LangChainのtoolをOpenAIのfunction callingに変換
functions = [format_tool_to_openai_function(t) for t in tools]

#モデルに関数をバインド
model = model.bind_functions(functions)

#Agentノードの処理
def call_model(state):
    messages = state['messages']
    response = model.invoke(messages)
    return {"messages": [response]}

グラフの実行

それでは実際にどのようなにノードが呼び出されているのか確認してみましょう。はじめに学習データで答えることのできる内容を入力してみます。

from langchain_core.messages import HumanMessage

inputs = {"messages": [HumanMessage(content="Appleの創業者は?")]}

#Streamで出力
for output in app.stream(inputs):
    for key, value in output.items():
        print(f"Output from node '{key}':")
        print(value)

出力は以下のようになりました。action ノードが呼ばれずに agent ノードの LLM だけで完結し、content が返されていることがわかります。

#エントリーポイントのAgentノードに入力された質問が渡される、
Output from node 'agent':
{'messages': [AIMessage(content='Appleの創業者は、スティーブ・ジョブズ、スティーブ・ウォズニアック、およびロナルド・ウェインの3人です。')]}
#should_continue関数によってadditional_lwargsにfunction_callが含まれていないのでENDノードを呼ばれる
Output from node '__end__':
{'messages': [HumanMessage(content='Appleの創業者は?'), AIMessage(content='Appleの創業者は、スティーブ・ジョブズ、スティーブ・ウォズニアック、およびロナルド・ウェインの3人です。')]}

次に、学習データが網羅していないであろう最近の情報を聞いてみましょう。

from langchain_core.messages import HumanMessage

inputs = {"messages": [HumanMessage(content="現在のAppleのCEOは?")]}
for output in app.stream(inputs):
    for key, value in output.items():
        print(f"Output from node '{key}':")
        print(value)

Agent が学習データでは回答できないと判断し、content が空になっている代わりに additional_kwargs に function_call を追加し Action ノードを通して情報を取得しようとしていることがわかります。

Output from node 'agent':
{'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\n  "query": "Apple CEO"\n}', 'name': 'tavily_search_results_json'}})]}
#should_continue関数によってadditional_lwargsにfunction_callが含まれているのでactionノードが呼ばれる
Output from node 'action':
#actionノードがtoolを実行し、FunctionMessageとしてcontentとtoolnameを返す。max_resultを1に指定しているので、結果は一つのみ
{'messages': [FunctionMessage(content='[{\'url\': 参照したURL, \'content\': 参照した内容', name='tavily_search_results_json')]}
#Agentノードに戻り、messagesが読み込まれる
Output from node 'agent':
{'messages': [AIMessage(content='現在のAppleのCEOはTim Cookです。')]}
#function_callが含まれていないのでENDノードが呼ばれる
Output from node '__end__'
{'messages': [HumanMessage(content='現在のAppleのCEOは?'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\n  "query": "Apple CEO"\n}', 'name': 'tavily_search_results_json'}}), FunctionMessage(content='[{\'url\': 参照したURL, \'content\': 参照した内容, name='tavily_search_results_json'), AIMessage(content='現在のAppleのCEOはTim Cookです。')]}

参考文献

info
備考

LLM を業務で活用したり、自社のサービスに組み込みたくありませんか?Hakky では、AI を用いた企業独自のシステムを構築するご支援を行っています。 ソリューションサイト:https://www.about.st-hakky.com/chatgpt-solution

「どんなことが出来るのか」「いま考えているこのようなことは本当に実現可能か」など、ご検討段階でも構いませんので、ぜひお気軽にフォームよりお問い合わせくださいませ。

Hakkyへのお問い合わせ
2025年06月13日に最終更新
読み込み中...