はじめに
現代においてAI技術は様々な分野で活用されており、特に生成AIは、ChatGPTの公開を機に、ここ数年で大きく注目されるようになりました。
その中で、特定のタスクの遂行だけでなく、目標達成のために必要なタスクの選定から遂行までを自律的に行うAIシステムであるAIエージェントが注目されています。
本記事では、LangGraphを用いたAIエージェントの構築方法について解説します。
LangGraphは、LangChainの拡張として開発され、ステートフルなオーケストレーションワークフローを構築するために利用可能なPythonライブラリです。実際にシンプルなAIエージェントを構築するプロセスを通じて、その構築方法を紹介します。
作成したエージェント
検索したい飲食店の条件を入力すると、条件に合った飲食店をブラウザより検索し、ユーザーの入力文と検索結果に基づいた案内メールを作成&送信するAIエージェントを作成しました。

今回はLangGraphを用いたAIエージェントの構築手順の紹介のため、AIエージェントが実行可能なタスクを、以下の3つに限定しています。
- ユーザーが入力した条件に一致する飲食店のインターネット検索
- 案内メールの自動作成
- 案内メールの自動送信
実行した例を紹介します。
作成したエージェントを実行すると、検索したい飲食店の条件を問われるので入力します。

入力したプロンプトは次の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
以下の条件に合う飲食店を探してください。 ###目的 忘年会 ###開催日時 2025年12月15日18:00-20:00 ###参加人数 20名 ###予算 一人あたり4000円程度 ###場所 新宿駅付近 ###その他条件 ・飲み放題コースあり ・個室あり |
プロンプトを入力すると、希望条件に沿った飲食店を検索し、ユーザーの入力文と検索結果から案内メールの作成&送信が実行されます。
すべてのタスクが完了し、最終的にユーザーに出力される内容は次の通りです。
※実行結果に実際の店舗情報やURLが含まれている箇所はマスクしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
実行結果: ### 検索結果 おすすめの飲食店 〇〇〇〇 - 住所: JR新宿駅 西口 徒歩△分 - 参加人数: 最大100名まで収容可能 - 予算: 一人あたり約4,950円から - コース内容: 飲み放題コースあり - 個室: 完全個室あり - 特徴: 海老の生春巻きやパクチーを使用したグリーンサラダなど、旅行気分を味わえるメニューが揃っています。 詳細は[こちらのリンク](https://aaa.bbb.ccc.jp)からご確認ください。 このお店は、忘年会の目的にぴったりです。予約を検討されることをお勧めします。 --- ### 参加案内メール作成結果 以下は、忘年会の参加案内メールの内容です。 --- 宛先各位 お疲れ様です。 ○○部署の情報 太郎です。 忘年会の開催が近づいてまいりましたので、当日の詳細及び会費のご連絡をいたします。 ※本メールは当日ご参加される予定ではない方にもお送りしております。ご了承ください。 ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー ▼開催日時 2025年12月15日(月) 18:00~20:00 の2時間 ※17:55までにご着席ください。 ▼開催場所 〇〇〇〇 JR新宿駅 西口 徒歩△分 詳細は[こちらのリンク](https://aaa.bbb.ccc.jp)からご確認ください。 ▼会費 =============== 一律 4000円(現金) =============== 当日、会場入口またはオフィスにて集金いたしますので、ご準備の程よろしくお願いいたします。 ※コース外のメニューやドリンクについては自己負担になります。 ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー もし、忘年会当日のご都合がつかなくなってしまい、キャンセルされる場合は、12/1(日)までに私までご連絡ください。12/2(月)以降にキャンセルされる場合は、キャンセル料が全額発生されますので、ご注意ください。 当日は、何卒よろしくお願いいたします。 署名: 情報技術株式会社 情報 太郎 ×××-○○○○-△△△△ --- ### 送信結果 忘年会の参加案内メールを無事に送信しました。何か他にお手伝いできることがあれば教えてください。 |
実際に届いたメールの内容は次の通りです。

構築手順
構築手順の前に、LangGraphについて紹介します。
LangGraphの特徴として、エージェントのワークフローをグラフとしてモデル化することができます。モデル化する際には、次の3つの主要なコンポーネントを用いて定義します。
ステート
ノード遷移時に一貫した情報を連携するためのしくみで、各ノードにおいて参照、更新されます。
ノード
エージェントが実行する処理を表し、ステートを入力として受け取り、処理実行後、ステートを更新します。
エッジ
ノード間の接続を表し、決まった遷移に限らず、現在のステートに基づいた条件分岐も可能です。
各コンポーネントの関係を図示すると、次の通りです。

各種コンポーネントの構築手順の詳細を、以下の順番で解説します。
- 手順1:ライブラリのインポート
- 手順2:ステートの定義
- 手順3:ノードが処理するタスクに合わせて参照可能なツールの定義
- 手順4:ノードの定義
- 手順5:作成したコンポーネントを用いたワークフローの構築
それでは構築手順の紹介に移ります。
実際に手元で動かしてみたい方は、Python3の環境(Google Colaboratoryがお手軽です)を用意して、下記パッケージをインストールしてください。
1 2 3 |
!pip install langgraph langgraph.prebuilt langchain_openai !pip install tavily-python langchain_community !pip install google_auth_oauthlib google-api-python-client |
また、Tavily、OpenAIのAPIキーを取得して、環境変数に設定し、GmailのクライアントIDをダウンロード(ファイル名:credential.json)し、同フォルダに配置してください。
※Google Colaboratoryの場合、シークレットに追加後、以下を実行してください。
1 2 3 4 5 |
from google.colab import userdata import os os.environ["OPENAI_API_KEY"] = userdata.get("OPENAI_API_KEY") os.environ["TAVILY_API_KEY"] = userdata.get("TAVILY_API_KEY") |
1. ライブラリのインポート
必要なライブラリをインポートします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
from typing_extensions import TypedDict, Annotated from langgraph.graph import StateGraph, END from langgraph.graph.message import add_messages from langgraph.prebuilt import create_react_agent from langchain_core.prompts import ChatPromptTemplate from langchain_core.tools import tool from langchain_core.output_parsers import StrOutputParser from langchain_community.tools.tavily_search import TavilySearchResults from langchain_openai import ChatOpenAI from google_auth_oauthlib.flow import InstalledAppFlow from google.oauth2.credentials import Credentials from googleapiclient.discovery import build from email.mime.text import MIMEText import base64 |
2. ステートの定義
プログラム内で利用するデータ構造をTypedDictを用いて定義します。
定義したデータ構造とwith_structured_outputを用いて、生成AIモデルの出力データを指定したデータ構造に制御することができます。今回は、案内メール作成時とタスク一覧作成時に出力されるデータ構造を制御するために、次の通りに定義しています。
1 2 3 4 5 6 7 8 |
# 作成されたメール内容を表すデータモデル class Email(TypedDict): title: str body: str # 作成されたタスク一覧を表すデータモデル class Tasks(TypedDict): tasks: list[str] |
またステートの型を次の通りに定義しています。
デフォルトでは、ノード遷移時にフィールドを指定して値を更新すると、各フィールドの値は置換されますが、task_resultsフィールドは、タスクを実行するたびに実行結果を追加していくため、Annotatedとaddを用いています。
1 2 3 4 5 6 7 |
# ステート class State(TypedDict): query: str tasks: list[str] current_task_index: int task_results: Annotated[list[Any], add] response: str |
3. ツールの定義
タスクを実行する際に利用可能なツールを定義しています。
カスタムツールを定義するには、作成した関数に@toolとツールの説明文を記載するのが一番シンプルな方法になります。
今回は、独自に作成したメール作成ツールとメール送信ツール、そしてLangChainの組み込みツールであるTavilySearchResultsの3種類のツールを利用します。
メールの送信元や宛先を固定し、限定的なツールのみを作成していますが、様々なツールを定義しておくことで、エージェントがより多くのタスクを自律的に処理することが可能となります。
(例)飲食店の空き状況を確認するツール、飲食店を指定された日時で予約するツールなど
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
@tool def create_email_body(user_input: str, restaurant_info: str): """メール本文を作成するツール""" prompt = ChatPromptTemplate.from_messages( [ ( "system", "あなたは案内メールを作成する専門家です。\n" "ただしユーザー入力と飲食店情報から取得できない情報はメールに記載しないでください。", ), ( "human", "ユーザーが入力した内容と希望条件をもとに検索した飲食店情報から、出欠確認用の参加案内メールを作成してください。\n" "ただし飲食店情報が複数ある場合は、ユーザー入力に最も適した飲食店を1店舗のみ選定し、その店舗のみをメールに記載してください。\n" "出欠回答期限は、開催日の2週間前として設定してください。" "ユーザー入力: 以下の条件に合う飲食店はありますか\n###目的\n新年会\n###開催日時\n1月26日(金) 18:30~20:30\n###参加予定人数\n50名\n###開催場所\n新宿駅付近\n###予算\n一人あたり5000円程度\n###店舗条件\n・飲み放題コースあり\n・個室あり'\n" "飲食店情報: ### 店舗情報\n店名: ○○ 新宿店\nURL: https://aaa.bbb.ccc.jp\\n- \n特徴:\n・個室あり\n・ 飲み放題コースあり\n・参加人数: 最大100名まで対応可能\n・会費: 一人あたり5000円程度\n・開催日時: 1月26日(金) 18:30~20:30\n\nこの店舗は全席個室で、和洋折衷の創作料理が楽しめるため、忘年会にぴったりです。\n" "署名:\n情報技術株式会社\n情報 太郎\n×××-○○○○-△△△△" ), ( "ai", """ 宛先各位 お疲れ様です。 ○○部署の情報 太郎です。 新年会の開催が迫ってまいりましたので、当日の詳細及び会費のご連絡をいたします。 ※本メールは当日ご参加される予定ではない方にもお送りしております。ご了承ください。 ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー ▼開催日時 1月26日(金) 18:30~20:30 の2時間 ※18:25までにご着席ください。 ▼開催場所 ○○ 新宿店 https://aaa.bbb.ccc.jp ▼会費 ================ 一律 5000円(現金) ================ 当日、会場入口またはオフィスにて集金いたしますので、ご準備の程よろしくお願いいたします。 ※コース外のメニューやドリンクについては自己負担になります。 ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー もし、忘年会当日のご都合がつかなくなってしまい、キャンセルされる場合は、1/17(水)までに私までご連絡ください。1/18(木)以降にキャンセルされる場合は、キャンセル料が全額発生されますので、ご注意ください。 当日は、何卒よろしくお願いいたします。 """ ), ( "human", "ユーザーが入力した内容と希望条件をもとに検索した飲食店情報から、出欠確認用の参加案内メールを作成してください。\n" "ただし飲食店情報が複数ある場合は、ユーザー入力に最も適した飲食店を1店舗のみ選定し、その店舗のみをメールに記載してください。\n" "出欠回答期限は、開催日の2週間前として設定してください。" "ユーザー入力: {user_input}\n" "飲食店情報: {restaurant_info}\n" "署名:\n情報技術株式会社\n情報 太郎\n×××-○○○○-△△△△" ) ] ) chain = prompt | llm return chain.invoke({"user_input": user_input, "restaurant_info": restaurant_info}) @tool def send_email(email_body: str): """メールを送信するツール""" try: SCOPES = ["https://mail.google.com/"] flow = InstalledAppFlow.from_client_secrets_file("credentials.json", SCOPES) creds = flow.run_local_server(port=0) with open("token.json", "w") as token: token.write(creds.to_json()) scopes = ["https://mail.google.com/"] creds = Credentials.from_authorized_user_file("token.json", scopes) service = build("gmail", "v1", credentials=creds) # メール本文より適切な件名を作成 prompt = ChatPromptTemplate.from_messages( [ ( "system", "あなたは次のメールから、適切な本文と件名を作成する専門家です。\n" ), ( "human", "次のメールから適切な本文と件名を作成してください。\n" "メール: {email_body}" ) ] ) chain = prompt | llm.with_structured_output(Email) email = chain.invoke({"email_body": email_body.replace("*", "")}) message = MIMEText(email["body"]) message["To"] = "宛先メールアドレスを入力してください" message["From"] = "送信元メールアドレスを入力してください" message["Subject"] = email["title"] raw = {"raw": base64.urlsafe_b64encode(message.as_bytes()).decode()} service.users().messages().send( userId="me", body=raw ).execute() return "メール送信に成功しました。" except Exception as e: return f"以下の理由により、メール送信に失敗しました。\n{str(e)}" # タスク実行時に利用するツールを定義 search_restaurant = TavilySearchResults(max_results=3) | StrOutputParser() tools = [TavilySearchResults(max_results=3), create_email_body, send_email] |
4. ノードの定義
各ノードで実行する処理を定義します。
ノードの入力はステートで、返り値のキーに指定したステートのフィールド値が更新されます。
今回、定義したノードは次の通りです。
list_task_node
ユーザーが入力した希望条件に沿った飲食店の検索において実施するタスク一覧を作成します。
今回は、タスク一覧を動的に生成するのではなく、以下の3個のタスクに限定しています。
- ユーザーが入力した希望条件に沿った飲食店を検索
- ユーザー入力と検索結果をもとに案内メールを作成
- 案内メールを送信
処理実行後、以下のフィールド値を更新します。
- query:ユーザー入力文
- tasks:作成したタスク一覧
- current_task_index: 0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
def list_task_node(state: State): prompt = ChatPromptTemplate.from_messages( [ ( "system", "あなたはタスクを整理する専門家です。\n" "現在のタスク一覧は、以下の通りです。\n" "タスク一覧\n" "1. ユーザーの入力内容を満たす飲食店の情報を、最適な1店舗のみブラウザで検索\n" "2. 参加案内メールを作成\n" "3. 作成した参加案内メールを送信\n" ), ( "human", "ユーザーが入力した内容に基づいて、現在のタスクを再作成してください。\n\n" "ユーザー入力\n{query}" ) ] ) chain = prompt | llm.with_structured_output(Tasks) return {"query": state["query"], "tasks": chain.invoke({"query": state["query"]})["tasks"], "current_task_index": 0} |
execute_task_node
list_task_nodeで作成された各タスクをツールを用いて実行し、ステートの以下フィールド値を更新します。
- task_results: タスクの実行結果
- current_task_index: 現在のcurrent_task_indexフィールドの値から1加算
create_react_agentは、入力内容をもとにツールの選定から実行までを行う事前構築済みのエージェントです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
def execute_task(state: State): agent = create_react_agent(llm, tools=tools) query = f""" 次のタスクを最適なツールを用いて、実行してください。 ユーザー入力: {state["query"]} タスク: {state["tasks"][state["current_task_index"]]} 過去タスクの実行結果: {state["task_results"]} """ try: result = agent.invoke({"messages": [("human", query)]}) return result["messages"][-1].content except Exception as e: return f"以下の理由により、タスクの実行に失敗しました。\n{str(e)}" def execute_task_node(state: State): current_task = state["tasks"][state["current_task_index"]] result = execute_task(state) return {"task_results": [result], "current_task_index": state["current_task_index"] + 1} |
create_response_node
作成されたタスク一覧と各タスクの実行結果をもとに、ユーザーにとって読みやすい形式に文章を再構成し、ステートの以下フィールド値を更新します。
- response: 再構成された文章
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
def create_response_node(state: State): prompt = ChatPromptTemplate.from_messages( [ ( "system", "あなたは、ユーザーの希望条件をもとに検索した結果と、参加案内メール作成、送信の実行結果をまとめる専門家です。" ), ( "human", "以下は、ユーザー入力をもとに条件に沿った飲食店の検索結果です。\n" "検索結果と、参加案内メール作成結果、送信結果をそれぞれまとめてください。\n" "ただし、飲食店情報は可能な限り詳細に記載してください。\n" "ユーザー入力: {query}\n" "タスク一覧: {tasks}\n" "タスク実行結果: {task_results}" ) ] ) chain = prompt | llm | StrOutputParser() response = chain.invoke({"query": state["query"], "tasks": state["tasks"], "task_results": state["task_results"]}) formatted_response = response.replace("*", "") print("実行結果:\n", formatted_response) return {"response": formatted_response} |
5. ワークフローの構築
ここまで作成したコンポーネントを用いて、全体のワークフローを構築します。
ワークフローの流れは、次の通りです。
- ワークフロー開始
- list_task_nodeに遷移し、タスク一覧を作成
- execute_task_nodeに遷移し、タスクを実行
- 全てのタスクが完了していない場合は、3.に移動
- create_response_nodeに遷移し、最終的な出力文を作成
- ワークフロー終了
上記ワークフローを構築するコードは、次の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) # State型のステートを持つグラフのインスタンスを作成 graph_builder = StateGraph(State) # ノードをグラフに追加 graph_builder.add_node("list_task_node", list_task_node) graph_builder.add_node("execute_task_node", execute_task_node) graph_builder.add_node("create_response_node", create_response_node) # 条件付きエッジをグラフに追加 graph_builder.add_conditional_edges( "execute_task_node", lambda state: state["current_task_index"] < len(state["tasks"]), {True: "execute_task_node", False: "create_response_node"} ) # ワークフロー開始時に遷移するノードを指定 graph_builder.set_entry_point("list_task_node") # エッジをグラフに追加 graph_builder.add_edge("list_task_node", "execute_task_node") graph_builder.add_edge("create_response_node", END) graph = graph_builder.compile() |
LangGraphでは、最終的なグラフ構造を視覚化して確認することが可能です。
まず必要なパッケージをインストールするために、次コマンドを実行してください。
1 2 |
!apt-get install graphviz libgraphviz-dev pkg-config !pip install pygraphviz |
インストールに成功したら、次コードを実行してください。
1 2 |
from IPython.display import Image Image(graph.get_graph().draw_png()) |
実行すると、グラフ構造が画像形式で出力されます。

6. 実行
これで、エージェントの構築は以上となります。
動作確認時には、次のコードを実行して、イベント内容をコンソールに入力してください。
1 2 3 |
query = input("検索したい飲食店の条件を入力してください: ") initial_state = State(query=query) graph.invoke(initial_state) |
さいごに
今回は、LangGraphを使用して、条件に沿った飲食店の検索から案内メールの作成&送信を行うAIエージェントを構築しました。LangGraphを使うことで、シンプルな構成でAIエージェントを構築できることがご理解いただけたかと思います。
今回の例では、利用可能なツールとしてブラウザ検索と案内メールの作成・送信のみを定義しましたが、用途に応じてツールを追加することで、より広範なタスクの自動化が実現できます。また、各ノードで定義するプロンプトを工夫することで、エージェントの振る舞いをさらに希望通りに制御することも可能です。この機会に、LangGraphを用いたAIエージェントの開発を検討してみてはいかがでしょうか。
執筆者プロフィール
- tdi AI&データマネジメント部
-
生成AIを用いたアプリケーション開発やBIツールの導入等を行っています。
最近はネイティブアプリ開発とアルゴリズムに興味があります。
この執筆者の最新記事
Pick UP!2025年4月1日LangGraphを用いてAIエージェントを構築してみた