開発者と読み解くAIの世界

LangGraphでAIエージェントアプリケーションを設計する際のポイント

実開発を通して見えてきた原理原則

 本コーナー「開発者と読み解くAIの世界」では、AIアプリ開発に携わるエンジニアより寄稿いただき、開発者目線でみる生成AIの面白さや活用法、開発現場のリアルをお伝えします。

 「ChatGPT」などのチャット型や「Dify」などのワークフロー型のアプリケーションは浸透してきていますが、一方それらでは解決できない問題領域の解決手段として、LLMを用いた「AIエージェント」が注目を集めています。さらに、LLMアプリケーションを効率よく構築するための開発フレームワークとして「LangChain」や「LangGraph」が広く採用されており、特にLangGraphはエージェントのオーケストレーションを柔軟に行えることから人気を集めています。

 しかし、LangGraphを用いたアプリケーション開発は、まだ一般的に成熟したノウハウが少ないのも事実です。そこで本記事では、筆者が実際にLangGraphを用いてLLMアプリケーションを開発した事例をもとに、設計上の注意点や考え方を解説します。LLMやエージェントという新しい概念を取り扱う場面でも本質的にはソフトウェア開発の原理原則が重要となることにフォーカスします。

 自分でエージェントアプリケーションを作ってみたいものの、設計段階でどのようにモジュール分割すればよいかなど、実例を参考にしたい方はぜひ一読してみてください。

 なお、本記事は以下を目的としています。

筆者の実務経験をもとにしたノウハウ共有

 「LangGraph」は、LLMアプリケーションに特化したワークフロー/エージェントのオーケストレーションを行う人気のフレームワークです。すでに数多くの紹介記事や書籍によって細かい勘所やノウハウがいくつか共有されていますが、実アプリケーションの設計機会がその数を上回っているように思います。

 そこで筆者が業務でLangGraphを使ってエージェントアプリケーションを開発した際、どのように設計していったかを具体的にご紹介します。アプリケーションの中核機能や、設計を考える上で気をつけたポイントなどを共有できればと思います。

LangGraphとは?

 「LangGraph」は、LangChainとの併用を想定したフレームワークであり、LLMを活用してステートフルなエージェントやワークフローアプリケーションを構築できます。最大の特徴は、有向巡回グラフ(DCG: Directed Cyclic Graph)を用いるため、LangChain標準のLCEL(LangChain Expression Language)ではやや実装が難しかったループ構造を伴う連鎖を表現しやすい点にあります。

参考記事

 詳細は上記の記事を参照ください。本記事では主にLangGraphを使ったアプリケーション設計のポイントに絞って解説します。

実際に開発したAIエージェントアプリの概要

 筆者がLangGraphを用いて開発したアプリケーションは、 自然言語によるデータウェアハウス(DWH)への問い合わせと可視化 を行うものです。以下に動作の流れを示します。

  1. ユーザーがWeb UIに自然言語で分析したい内容を入力
    例:「商品Xと商品Yの過去一年における月次の売上推移を折れ線グラフで示して」
  2. 自然言語からSQLへ変換し、DWHに問い合わせを実行
    SQL実行結果をUI上に表示
  3. ユーザーが表示されたSQL実行結果を確認し、次の行動を決定
    A:SQLの実行結果が想定と異なる → 2へ戻る
    B:実行結果が想定どおり → 次のフェーズへ移行
  4. SQL実行結果に合わせてグラフを生成
    上記の問い合わせ内容とSQL実行結果から、最適なグラフ(折れ線・棒グラフなど)を作成し、UIに表示
  5. ユーザーがグラフ表示結果を確認
    C:グラフの表示形式について修正を依頼 → 4へ戻る
    D:グラフのデータ内容自体を修正したい → 2へ戻る

 アプリケーションのデモ動画については、下記よりご確認いただけます。

テキストtoSQL

アプリケーションの「核」

 ステークホルダーから要件をヒアリングしたり、プロトタイプのフィードバックを受けて、以下のようなアプリケーションの核が見えてきました。

  • 自然言語をSQLに変換
  • クエリ内容とSQL実行結果をもとに、ユーザーが求めるグラフを自動生成
  • エラーハンドリングやリトライ(LLMからの出力が失敗した場合など)を柔軟性高く実施
  • ユーザーのフィードバック(Human in the Loop)で柔軟に軌道修正

 この「核」をどのような順番・方法で実現するかを考えながら設計・実装を進めました。

開発時に気をつけること

最初の実装はシンプルに、アプリケーションの見通しを立てやすく

 LLMエージェントを全面的に導入すると、非常に柔軟な処理が可能になりますが、処理内容のブラックボックス化に伴い、設計やテストの複雑度が一気に上がります。そこで、筆者はまず次のような シーケンシャルな処理 での初版アプリを作成しました。

  1. 自然言語からSQLへ変換(LLM)
  2. DWHにSQLで問い合わせ
  3. グラフ生成

 この段階ではアプリケーションとして最低限成立する機能のみを単純なフローとして実装しました。

 最初は最低限の機能を実装を行うことで、フィージビリティの確認やステークホルダーとイメージのすり合わせを低コストで実施できます。またアプリケーション全体感の見通しも立てやすくなります。ループ機構(フィードバック付きリトライやHuman in the Loopなど)については、後になって「エラーが頻繁に発生する」「途中でユーザーが何度も修正を挟みたい」など、必要性が顕在化したタイミングでLangGraphのDCGを活用し、 ループ構造やエージェント的な自律行動 を追加しました。

Why LangGraph?

LangChainの標準機能(LCEL)だけではループ構造の実装が複雑になりがちでした。一方でLangGraphは有向非巡回グラフを前提とするため、ループをうまく扱える点が大きなメリットでした。

責務ごとにコンポーネントを分割して複雑性をコントロールする

 1つのLLM処理単位に対して多くの種類のタスクをもたせたり、タスクを達成するために多くの手順が必要になる場合、1つのプロンプトが肥大化・複雑化し、タスクの成功率が下がる傾向があります。また、分岐や繰り返しが多くなるとロジックも複雑になってしまいます。

ソフトウェア設計の基本原則を適用する

  • 責務の分離
    大きな仕事を複数のコンポーネントに分割し、それぞれが単一の責務を担うようにする
  • コンポーネントの達成すべきゴールを明確化
    例として「自然言語 → SQL変換からDataFrame生成まで」をひとまとめにした“Queryコンポーネント”など

具体例

  1. LLMで自然言語をSQLクエリに変換
  2. SQLクエリをバリデーション
  3. DWHへの問い合わせ(SQLクエリ実行)
  4. 結果をDataFrameに格納
  5. Python(matplotlibなど)でグラフ生成用コードを作成
  6. Pythonインタープリターでコードを実行
  7. グラフをPNG画像として保存

 ここでは、

  • Queryコンポーネント
    (1)~(4)をまとめ、「自然言語の問い合わせが入力されたらDWHにクエリを投げてDataFrameを返す」までの一連の処理を責務とする
  • Graphコンポーネント
    (5)~(7)をまとめ、「自然言語の問い合わせとDataFrameをもとにグラフを生成し、画像として出力」することを責務とする

 この段階では「エージェント化するか」「ワークフローとして実装するか」は決めず、 責務だけをはっきり分割 しておきました。Human in the Loopもまだ考慮せず、コンポーネント内で1つのトランザクションとして処理が完結するイメージです。

ワークフロー or エージェントとしてコンポーネントを設計・実装する

 次のステップでは、分割したコンポーネントを「ワークフローで実装するか」「エージェントとして実装するか」を判断していきます。

ワークフロー実装に向いているケース

 実行順序が厳密に決まっている場合、例えば「Queryコンポーネント」は以下のフローで動作します。

  1. LLMでSQL生成
  2. SQLバリデーション
  3. DWH問い合わせ
  4. DataFrame格納

 エラーがあれば(1)へ戻り、リトライするという構造が決まっています(下図参照)。このようにフローが定形であれば、ワークフローとして実装する方が制御しやすく、LLMの“不安定なルーティング先判断”を挟む必要がありません。実際にQueryコンポーネントではLLMのタスクは自然言語をSQLに変換する処理にとどまっています。

 ただし、Queryコンポーネントは「SQLバリデーションツール」と「SQL実行ツール」を持たせたReActパターンエージェントとしても実装可能ですが、LLMの出力は制御しきれない面があるため、今回のように 順序が明確な 処理にはワークフローの方が適していました。

参考リンク

エージェント実装に向いているケース

 一方、「Graphコンポーネント」はPythonのコード生成やDataFrame操作など、実施すべきタスクが多岐にわたります。SQL生成-検証-実行の場合ほどステップが明確に決まっていない上、実行のオーバーヘッドも小さい(ローカルPythonで完結する)ため、 エージェントの自律的判断が有用 だと考えました。

  • コード生成 → 実行 → エラーなら修正 → 再実行 → グラフ完成
  • 状況によっては「DataFrameの列名を変更して実行結果を確認する」「DataFrameのサイズを確認してグラフ表現方法を検討する」などのアクションが挟まるかもしれない

 LangChainの「Pandas Dataframe Agent」を活用すれば、エージェントがPythonインタープリター(ツール)を自由に呼び出し、データフレーム操作やグラフ作成を柔軟に行えます。これにより、あらかじめ複雑な条件分岐を決め打ちしなくても、 エージェント自身がコードの実行内容やエラー内容を判断して次のステップを決める ことが期待できます。

コンポーネント同士の関係を整理してオーケストレーションする

 最後に、 複数のコンポーネント をどのようにつなぎ合わせるかを考えます。アプリケーション全体の流れとしては、

  1. Queryコンポーネント(ワークフロー)
    入力:ユーザーの自然言語
    出力:DataFrame
  2. 2. Graphコンポーネント(エージェント)
    入力:ユーザーの自然言語 + DataFrame
    出力:グラフ画像

 2つのコンポーネントの間にユーザーが介入し、「クエリ実行結果を修正」「次のフェーズに進む」「グラフを再生成する」など多様な操作を行います。また、ユーザーのフィードバックによっては「グラフ出力後に、出力データを変えるためにもう一度SQL生成からやり直したい」など、 動的なルーティング が必要になる場合があります。

Supervisorエージェントの採用

 LangGraphには「Multi-agent supervisor」と呼ばれるアーキテクチャーがあります。これは複数のエージェント/ノードを統括する“司令塔”のような役割で、 ユーザーからのフィードバック(非定型な入力) やアプリケーションの状態を解釈して「次にどのコンポーネントを実行するのか」を動的に判断してくれます(下図参照)。

  • Queryコンポーネントはワークフローとしてのノード
  • Graphコンポーネントはエージェントとしてのノード
  • Supervisorエージェントがユーザーフィードバックも考慮しながら、これら2つのコンポーネントへのルーティングを行う
Point

動的なルーティングが必要ない場面や、ユーザーフィードバックが定型フォーム(True/False程度)なら、そもそもエージェントにしなくてもIf-Else分岐だけで済むケースも多々あります。LLMによる判断は柔軟ですが、制御が難しい面もあるため、 不要な箇所では使わない のも大事な選択肢です。

参考リンク

DCGで親グラフを構築

 LangGraphでは、ワークフローやエージェントなど異なる実装形態のコンポーネントを1つのノードとしてまとめられます。

  • Queryコンポーネント:サブグラフ(ワークフロー)
  • Graphコンポーネント:サブグラフ(エージェント)
  • Supervisor:親グラフとして各サブグラフを統括

 このように階層的にグラフを組み合わせることで、 「共通のインターフェースで一貫したコードが書ける」 のが、LangGraphの大きなメリットといえます。

まとめ:ソフトウェア設計の原則が非常に重要に

 LLMを活用したエージェントシステムの構築は、従来のソフトウェア開発や機械学習開発とは異なる部分もありますが、 ソフトウェア設計の原則が非常に重要になってきます 。下記のように、一般的なソフトウェア設計手法に対してLLM固有の課題をいかに柔軟に組み合わせるかが重要です。

  1. まずはシンプルなシーケンスで核となる要件を実装
    LLMエージェントは便利な反面、テスト難易度や不確実性が大きくなります
  2. 責務ごとのコンポーネント分割はしっかり行う
    モノリス化やプロンプト肥大化を防ぎ、部分的な改修や差し替えを容易にします
  3. ワークフローとエージェントを使い分ける
    フローが定型化しているタスクはワークフローで制御し、事前に実行順序の定義が難しいタスクにはエージェントを使います
  4. LLMの制御が必要ないところはあえて使わない
    不要な箇所でLLMを活用すると、挙動の不安定さに振り回されるリスクがあります
  5. 各種ループ機構を有効に活用する
    ルーティング・リトライ機構・Human in the Loopといった制御方式を用いてコンポーネントが連携できるようにします

 LangGraphは「LLMアプリケーション=なんでもエージェント任せ」になりがちな設計を、 より明確な責務分割とワークフロー+エージェントのハイブリッド でまとめられる優れたフレームワークです。本記事のポイントを参考に、みなさんのエージェント型アプリケーション開発がスムーズに進めば幸いです。

著者プロフィール:山中 統吾

株式会社Algomatic AXカンパニー エンジニアリング本部長

受託分析/開発会社で大手通信企業向けのデータ分析コンサルティング・機械学習モデル開発に従事。株式会社タイミーにて全社向けに生成AI導入をルール策定から実導入、社内向け生成AIサービス開発を行った。2024年9月から株式会社Algomaticに入社。現在はAXカンパニーでエンジニアリング本部長としてアプリケーション開発や組織開発に従事。

・著者X(Twitter):https://x.com/gura105
・株式会社Algomatic:https://algomatic.jp/

開発者と読み解くAIの世界 記事一覧