(20) 実践!初回LangGraphプロジェクト:ジョークボットで学ぶエージェント型システムの基礎
こんにちは!前回のLangGraphの理論編に続いて、今回はいよいよ実践編です。ReadyTensorのエージェント型AI開発者認定プログラムの第5週レッスン2bをもとに、最初のLangGraphプロジェクトとして「ジョークボット」を構築していきます。
この記事では、LLMを使わずにLangGraphの基礎メカニクスを学ぶことで、エージェント型システムの動作原理をしっかりと理解できるようになります。
なぜジョークボットから始めるのか?
LangGraphの基礎に集中するアプローチ
ツール、エージェント、LLMに入る前に、LangGraphの基礎 — グラフベースワークフローの構築と実行方法をマスターしましょう。
シンプル(で楽しく)保つために、異なるカテゴリからジョークを話してくれるジョークボットを構築します。ここにはLLMはありません — そしてそれは意図的です。
学習目標の明確化
この例は有用なチャットボットの構築についてではありません。以下を助けることについてです:
- LangGraphがどう動作するかを理解する
- 状態あり、ノード駆動フローの構造化を練習する
- 条件付きエッジ、ルーティング、状態更新で自信を得る
LangGraphは通常エージェントと言語モデルで使用されます。しかしここでは、中核メカニクスに焦点を当てるためにそれらすべてをスキップします — 後で複雑さを追加する時、ボンネットの下で正確に何が起こっているかを知るために。
習得するスキル
以下を学びます:
- 状態とリデューサー関数の定義
- 再利用可能で、ロジック駆動ノードの作成
- 条件付きルーティングでメニューループの構築
- ステップ間で進化する情報の追跡
最後には、動作するLangGraphアプリ — そしてLangGraphシステムがどう動作するかの明確な精神モデルを持つことになります。
構築するもの:インタラクティブなジョーク話ボット
基本機能
あなたのプロジェクトは、LangGraphで構築されたシンプルで遊び心のあるジョーク話ボットです。pyjokes
ライブラリを使用してジョークを取得します。
動作方法は以下の通りです:
- シンプルなメニューとやりとりして、新しいジョークを聞く、ジョークカテゴリを切り替える、またはボットを終了する
- ジョークは組み込みライブラリから来て、好きなだけ続けることができます
- 裏では、LangGraphがロジック、状態遷移、ルーティングを管理します — すべて構造化された、検査可能なグラフ内で
追跡する情報
ボットは2つの重要なことを追跡します:
- 選択したジョークのカテゴリ(例:neutral、chuck、またはall)
- すでに聞いたジョーク(実行中のリストを見ることができるように)
グラフノードとして再利用可能な関数を構築し、選択に基づいて動的にルーティングし、LangGraphがステップ間で状態をどう進化させるかを見ます。
そして、あなたも笑うかもしれません 😄
ワークフロー設計:1つのグラフ、4つのノード
グラフ構造の理解
コードを書く前に、構築するグラフの構造を見てみましょう。
このフローはシンプルに見えるかもしれません — そしてそれがポイントです。興味深いほど十分なロジックでLangGraphメカニクスを教えることを意図しています。
4つの主要ノード
🟪 show_menu:メイン制御ノード
これはメイン制御ノードです。実行するたびに、ユーザーに3つのオプションを表示します:
- 別のジョークを聞く
- ジョークカテゴリを変更
- アプリを終了
ユーザーの入力に基づいて、グラフは次のノードの1つにルーティングします。
🟩 fetch_joke:ジョーク取得ノード
このノードは選択されたカテゴリからジョークを取得して表示します。ジョークを表示した後、ユーザーが別の選択をできるようにshow_menu
に戻ります。
🟦 update_category:カテゴリ更新ノード
これによりユーザーは新しいジョークカテゴリ(「chuck」や「neutral」など)を選択できます。更新後、メニューに戻ります。
⛔ exit_bot:終了ノード
ユーザーが終了を選択すると、このノードはお別れを言い、特別な__end__
ノードにルーティングしてグラフを終了します。
システムの流れ
システム全体は、ユーザーが明示的に終了を選択するまでループします。この設定により、以下を練習する素晴らしい小さなサンドボックスが得られます:
- ループ動作
- 条件付きルーティング
- 動的状態更新
LangGraphによる実装:5つのステップ
統一された構築プロセス
前のレッスンで、任意のLangGraphシステムを構築する5つのステップを学びました:
- 状態を定義
- ノード関数を書く
- グラフビルダーを作成
- ノードとエッジを追加
- グラフをコンパイルして実行
ここでも同じ構造に従います — しかし今回は実践的にやります。
ステップ1:状態を定義
ボットが記憶すべきことを宣言することから始めます。
class Joke(BaseModel):
text: str
category: str
class JokeState(BaseModel):
jokes: Annotated[List[Joke], add] = []
jokes_choice: Literal["n", "c", "q"] = "n" # 次のジョーク、カテゴリ変更、または終了
category: str = "neutral"
language: str = "en"
quit: bool = False
重要なポイント
Annotated
をadd
と一緒に使用して、新しいジョークをリストに自動的に追加jokes
のリデューサーは新しいジョークを既存のリストに追加し、これまでに話されたことを追跡- 他のフィールドでは、現在の値を単純に上書きするデフォルトリデューサーを使用
ステップ2:ノード関数を書く
各ノードは状態を受け取り、更新を返す単なる関数です。
メニュー表示ノード
def show_menu(state: JokeState) -> dict:
user_input = input("[n] Next [c] Category [q] Quit\n> ").strip().lower()
return {"jokes_choice": user_input}
ジョーク取得ノード
def fetch_joke(state: JokeState) -> dict:
joke_text = get_joke(language=state.language, category=state.category)
new_joke = Joke(text=joke_text, category=state.category)
return {"jokes": [new_joke]}
カテゴリ更新ノード
def update_category(state: JokeState) -> dict:
categories = ["neutral", "chuck", "all"]
selection = int(input("Select category [0=neutral, 1=chuck, 2=all]: ").strip())
return {"category": categories[selection]}
終了ノード
def exit_bot(state: JokeState) -> dict:
return {"quit": True}
ルーター関数
次に行くノードを決定するルーター関数も定義します:
def route_choice(state: JokeState) -> str:
if state.jokes_choice == "n":
return "fetch_joke"
elif state.jokes_choice == "c":
return "update_category"
elif state.jokes_choice == "q":
return "exit_bot"
return "exit_bot"
重要な設計原則:状態管理の考え方
宣言的アプローチ
ノード関数のどれも状態を直接変更していないことに気づいたかもしれません。代わりに、各ノードはフィールド更新の辞書を返すだけです。
これは設計によるものです:
- 状態をその場で変更しません
{field_name: new_value}
を返すだけです- LangGraphがグラフ内で次の状態の作成を処理します
利点
この宣言的アプローチにより、ロジックがきれいで、予測可能で、デバッグしやすくなります — 特にグラフが複雑になるにつれて。
だから:変更したものだけを返し、残りはLangGraphに処理させてください。
ステップ3&4:グラフを作成してノード+エッジを追加
ビルダー関数を使用してグラフを作成します。
def build_joke_graph() -> CompiledStateGraph:
workflow = StateGraph(JokeState)
# ノード追加
workflow.add_node("show_menu", show_menu)
workflow.add_node("fetch_joke", fetch_joke)
workflow.add_node("update_category", update_category)
workflow.add_node("exit_bot", exit_bot)
# エントリーポイント設定
workflow.set_entry_point("show_menu")
# 条件付きエッジ追加
workflow.add_conditional_edges(
"show_menu",
route_choice,
{
"fetch_joke": "fetch_joke",
"update_category": "update_category",
"exit_bot": "exit_bot",
}
)
# 固定エッジ追加
workflow.add_edge("fetch_joke", "show_menu")
workflow.add_edge("update_category", "show_menu")
workflow.add_edge("exit_bot", END)
return workflow.compile()
構造の説明
この関数は以下を行います:
StateGraph
をインスタンス化し、状態タイプを指定- ノードを登録し、各機能を名前に関連付け
- エントリーポイントを設定し、グラフの開始点を定義
- ルーティングロジックを定義し、条件付きおよび固定エッジを追加
- グラフをコンパイルし、実行のために準備
ステップ5:グラフを実行
すべてが配線されたら、グラフを実行するメイン関数を持つことができます:
def main():
graph = build_joke_graph()
final_state = graph.invoke(JokeState(), config={"recursion_limit": 100})
それだけです — 動作する最初の完全なLangGraphです。
実際の動作:ジョークボットの体験
ユーザーインターフェース
スクリプトを実行すると、シンプルですが機能的なインタラクティブメニューが得られます。以下ができます:
- 好きなだけジョークをループ
- セッション中にカテゴリを切り替え
- 完了時にきれいに終了
⚠️ はい、UIは極めて… ミニマリストです。LangGraphを学ぶためにここにいるのであって、デザイン賞を獲得するためではありません 😅
サンプルセッション
典型的なセッションは以下のような感じです:
🎉==========================================================🎉
LangGraphジョークボットへようこそ!
この例はLLMなしでエージェント型状態フローを実演します
============================================================
🚀==========================================================🚀
ジョークボットセッションを開始中...
============================================================
🎭 メニュー | カテゴリ:NEUTRAL | ジョーク:0
--------------------------------------------------
オプションを選んでください:
[n] 🎭 次のジョーク [c] 📂 カテゴリ変更 [q] 🚪 終了
ユーザー入力:n
😂 3人のデータベース管理者がNoSQLバーに入った。しばらくして彼らは出て行った。テーブルが見つからなかったからだ。
============================================================
🎭 メニュー | カテゴリ:NEUTRAL | ジョーク:1
--------------------------------------------------
オプションを選んでください:
[n] 🎭 次のジョーク [c] 📂 カテゴリ変更 [q] 🚪 終了
ユーザー入力:q
🚪==========================================================🚪
さようなら!
============================================================
注目すべきポイント
動作中の完全なループを見ることができます:
- 入力 → 状態更新 → 再ルーティング → 繰り返し
- グラフが遷移を処理する
- 状態(カテゴリやジョーク数など)が自然に進化する
- ユーザーは常に一貫したメニュー体験に戻る
シンプルで、予測可能で、拡張しやすいです — これはエージェント型ワークフロー構築の初期段階で正確に求めるものです。
拡張演習:ボットをカスタマイズしよう
スキルアップのための課題
最初のLangGraphシステムを構築したので、その新しい筋肉を伸ばす時です。ジョークボットにいくつかの追加機能で拡張してみてください。
🗣️ 演習1:多言語対応
pyjokes
は複数の言語(en
、de
、es
など)をサポートします。ボットを多言語にしましょう!
実装内容:
JokeState
にlanguage
という新しいフィールドを追加- ユーザーが言語を選択できる新しいノード(例:
update_language
)を追加 - 新しいメニューオプション(
[l] 🌐 言語変更
など)を追加してルーティングロジックを更新
🔄 演習2:リセット機能
ユーザーにジョーク履歴をリセットする方法を与えましょう。
実装内容:
- 新しいメニューオプション
[r] 🔁 ジョーク履歴リセット
を追加 - 過去のジョークのリストをクリアする新しいノード(例:
reset_jokes
)を作成 - 新しいオプションをサポートするようにルーティングロジックを更新
✨ ボーナス演習:LLM駆動ジョーク
次のレッスンでは、pyjokes
を実際のLLM呼び出しに置き換えますが、今それを自分で試したい場合:
実装内容:
fetch_joke
ノードロジックをLLM呼び出し(OpenAI、Anthropic、Mistralなど)と交換- 選択されたカテゴリの単一ジョークを返すプロンプトを設計
category
を使用してテーマを制御:"programmer"
→ 一般的な開発ジョーク"chuck"
→ チャック・ノリスジョーク"dad"
→ 古典的なお父さんジョーク
学習成果と重要なポイント
達成したこと
最初のLangGraphプロジェクトを構築して実行しました — LLMなし、エージェントなし、きれいなグラフロジックだけ。
その過程で以下をしました:
- Pydanticで進化する状態を定義
- シンプルで再利用可能なノード関数を作成
- ユーザーの選択を処理するために条件付きエッジを使用
- リデューサーが状態更新を自動的に管理できるようにした
- すべてをきれいで検査可能なグラフに構造化
LangGraphが解決してくれた問題
LangGraphのおかげで、以下を心配する必要がありませんでした:
- 手動状態遷移
- ループロジックや再ルーティング
- 競合する更新の処理
設計の哲学
最も美しいUIではありません — しかしそれがポイントではありませんでした。LangGraphワークフローが実際にどう実行されるか — そしてそれらを一から構築する方法を今知っています。
エージェント型システム開発の洞察
基礎の重要性
この単純なジョークボットから学べる重要な洞察:
1. 状態中心の設計
エージェント型システムでは、状態が中核です。すべてのノードが状態を受け取り、状態を更新し、状態に基づいて決定を行います。
2. 宣言的なアプローチ
LangGraphの宣言的アプローチ(状態を直接変更するのではなく、更新辞書を返す)により、システムが予測可能で検査可能になります。
3. グラフベースの思考
線形なフローではなく、グラフとして考えることで、より柔軟で適応的なシステムを設計できます。
実世界への応用
この基礎的なパターンは、以下のような実世界のシステムに拡張できます:
- カスタマーサービスボット:複数の問い合わせタイプを処理
- ワークフロー管理システム:承認プロセスや段階的作業
- マルチエージェントシステム:複数のAIエージェントが協調作業
次のステップ:LangGraphの更なる探求
今後の学習方向
この基礎を築いた上で、以下のより高度な機能を探求できます:
1. LLM統合
実際の言語モデルをノードに統合し、動的な応答生成を実現
2. ツール統合
外部API、データベース、ファイルシステムとの連携
3. マルチエージェント協調
複数のエージェントが協力して複雑なタスクを解決
4. 可観測性とデバッグ
LangSmithなどのツールを使用したシステムの監視と改善
実用的なスキルの構築
このジョークボットプロジェクトで学んだスキルは、以下の実用的な開発に直接応用できます:
- 状態管理: 複雑なビジネスロジックの管理
- フロー制御: 条件分岐とループの設計
- モジュラー設計: 再利用可能なコンポーネントの作成
- エラーハンドリング: 堅牢なシステムの構築
まとめ:LangGraphマスターへの第一歩
この記事では、LangGraphの実践的な使用方法を、シンプルながら完全なジョークボットプロジェクトを通じて学びました。
重要な学習ポイント
- LangGraphの5つのステップ:状態定義、ノード作成、グラフ構築、エッジ追加、実行
- 状態中心の設計:すべてがグローバル状態を中心に回る
- 宣言的プログラミング:直接変更ではなく、更新を宣言
- グラフベースの思考:線形フローを超えた柔軟な設計
今後の可能性
このシンプルなプロジェクトは、以下のような高度なシステムへの出発点です:
- 企業向けワークフロー自動化
- インテリジェントなカスタマーサービス
- 複雑な意思決定サポートシステム
- マルチモーダルAIアプリケーション
LangGraphの基礎をマスターしたことで、エージェント型AI開発の世界での次のレベルに進む準備が整いました。次は、これらの基礎の上により複雑で実用的なシステムを構築していきましょう!
コメント