はじめに
その辺の学生です。
LLM を触り始めると、最初は「とりあえず API を叩いて返事をもらう」だけで十分に楽しいですが、
次のステップとして
- 外部 API を叩きたい
- メモリを持たせたい
- 複数ステップの処理を任せたい
となってくると、いわゆる 「AI エージェント」 という言葉が出てきます。
ただ、そのタイミングで
- とりあえず ReAct みたいな一見AIがよしなにやってくれそうな構造にツールを全部渡す
- 行動空間だけ広げて、設計は全部LLMに丸投げ
という設計をすると、割と早い段階で
- ツールのスキーマで過剰設計による想定外の挙動
- ステップ数・レイテンシ・コストが読めない
- ログを見ても何が起きてるか分からない
という状態になりがちですよね?
本記事は、そんなAIエージェントは作りたいけど、どんな場面でどのエージェント構造を選ぶべきかみたいなことをかなり初心者向けに書いていきます。
1. エージェント構造を決める前に見るべき軸
-
処理フローはどこまで決まっているか
- 完全に決まっている?
- 大枠だけ決まっていて細部は変わる?
- そもそも決まらない?
-
エージェントに何ステップくらい動いてほしいか
- 1回のLLMコールで終わればいい?
- 2〜3回のやりとりがあれば十分?
- 何度か行き来しながら探索してもいい?
-
レイテンシとコストの制約はどれくらい厳しいか
- 限界までレイテンシを下げたいレベルなのか
- 数秒〜十数秒は許容できるのか
-
ツールの数と複雑さ
- 使うツールは 1〜2 個なのか
- 10 個以上あって、組み合わせも変動するのか
このあたりを踏まえて、よく出てくる構造を順番に見ていきます。
2. 単純なLLM呼び出し:レイテンシ優先のときの選択肢
どういうときに使うか
そもそもとして、
推論時間がボトルネックで、限界までレイテンシを下げたいときは、
「ただの LLM 呼び出し」が普通に最適解になる時が多いです。
- 知りたいことがシンプル
- 要約 / タイトル生成 / 軽い書き換え
- ユースケースとしても軽い
- チャットの相槌
- ボタンに出す一言メッセージ
- 外部 API や Tool 呼び出しも不要
この場合、わざわざエージェントっぽい構造にせず、
prompt → completion- もしくは
prompt → streaming
に切り出した専用の軽量エンドポイントを用意した方が、速度・安定性・コストの面でUXが良くなりやすいです。
メリット
- 実装が単純で、バグる箇所がほぼない
- レイテンシとコストが読みやすい
- テストも単純(入力と出力だけ見ればよい)
デメリット
- inputのtokenが大きかったり投げるタスクの難易度が高すぎると、レイテンシの増加や実用に耐えない精度に苦しむ
3. Stateful Workflow:フローが完全に決まっているとき
どういうときに向いているか
「完全にどんな処理をしてほしいかのフローが決まっている」 場合は、ReAct ではなく Stateful Workflow を選ぶのが自然です。
例えば:
- 入力の形式をチェックして
- DB に保存して
- 通知を飛ばして
- ログを残す
といった、順番を間違えると困る処理や、
- 支払い系
- 申請承認フロー
- 既存バッチ処理の一部を LLM に置き換えるだけ
のように、「やるべきことの並び」が完全に固定の処理。
体感ですが、作りたいAIエージェントのアイデアは9割このアプローチで実現できます。まずはどのような処理をAIにさせるべきなのか、どの程度のタスクごとに切り出してflowに落とし込むのかを考えてみると良いと思います。
どういう構造になるか
処理の骨組みはWorkflowが全てを握ります。
「Step1 → Step2 → Step3…」という実行順や、「Step2 は Step1 の結果がないと動かない」といった依存関係は、すべてコード側で決め打ちします。
LLM / Agentは各ステップの中身だけを手伝うことになります。
例えば、ユーザー入力を読みやすい文章に整えるテキストをラベルに分類するといった、局所的な判断や生成だけをLLMに任せます。
メリット
- 実行の順番と条件が明示される
- 例外・リトライ・ロールバックをコードで書ける
- 「どのステップで何が起きたか」が追いやすい
注意点
- フローのバリエーションが増えると、Workflow自体が増えていく
- 条件分岐を全部 Workflowの中に押し込むと可読性が一気に落ちる
- 決まりきった部分だけWorkflowにするという線引きが重要
4. Plan & Execute:回数は少ないがタスク分解したいとき
どういうときに向いているか
エージェントの行動範囲が、そもそも 2〜3 ループ程度もあれば十分なときは、ReAct ではなく Plan & Execute で組むのが扱いやすいです。
具体的には、
- 1 回のタスクの中で:
- まず「やることリスト」を出して
- それを順番にこなしていけば良いと分かっている
- ループ自体は数回で終わってほしい
- 例:ブログ記事作成
- アウトラインを作る
- 各セクションの下書きを書く
- タイトル案を出す
- 例:ブログ記事作成
のように、サブタスクのリストが自然に思いつくタイプのタスクで最適となりやすいです。私はStateful Workflowの中の一種という認識で、よく使っています。
構造イメージ
- Planner 役の Agent:
- ユーザーの依頼を受けて
- 「タスクの配列(構造化された JSON)」を出す
- Executor:
- そのタスク配列を見ながら、1つずつ処理していく
- ここは Workflow や普通のコードで良い
メリット
- 何をやるか(Plan)と、どう実行するか(Execute)が分離される
- 途中でログを見るときも、「どのタスクの段階で止まっているか」が把握しやすい
- ReActほどその場の状況やpromptに振り回されない
注意点
- 最初のプランがズレていると、そのままズレたまま実行される
- 実行途中でプランを修正したくなった場合:
- 「再プランニングのタイミング」を設計する必要がある
- 分解パターンがそこそこ安定している、固定化できているタスク向きで、探索的すぎるタスクには向きにくい
- イメージとして、Stateful Workflowのうち最もReAct的な挙動をするものみたいなものとして扱うのが一番やりやすいです
5. ReAct:自律的なAI Agent設計
5-1. ReAct がやっていること
ReAct 型のエージェントは、内部で
- Thought:今の状況から、次に何をすべきか考える
- Action:使うツールとその引数を選ぶ
- Observation:ツールの結果を読む
を何度かループしながら、最終的な回答にたどり着きます。
あらかじめ
- 「この順番でこの API を叩く」
と決めておくのではなく、
- 「どのツールを、どの順番で、何回使うか」をその場で決めていく
という構造です。
5-2. ReAct がハマる場面(理想的な使い方)
ReAct が素直に機能するのは、だいたいこんなケースです:
-
事前にフローを細かく決めきれないタスク
- 社内ドキュメントと Web と自前 API を行き来しながら調査する
- ログとメトリクスを見比べながら原因を探す
-
途中で方針転換がありうるタスク
- ある検索結果が乏しければ、別の情報源に切り替える
- ユーザーからの追質問によって、調査方向を変える
-
「どの情報源をどう組み合わせたか」に価値があるタスク
- どのデータソースを見たかを、あとから説明したい
こういうタスクは、Plan & Execute で初手から綺麗なタスクリストに落とすのが難しく、ReAct のその場で動きながら決める強みが生きます。
5-3. ダメな使い方:こうすると ReAct は壊れやすい
(1) Tool のスキーマを盛りすぎる
1個のツールで、
- 検索対象
- フィルタ
- ソート順
- ページング
- その他オプション
を全部持たせて「万能検索ツール」にしたくなりますが、
LLM が毎回その引数の組み合わせを適切に選べるとは限りません。
よく起きるのは:
- そのツール自体がほとんど使われない
- 毎回同じパターンの引数だけが使われる
という状態です。
おおよそ、スキーマで8種類くらい要求し始めると明確にtoolの呼び出しが減ります。また、ここで最終的な応答の出力を厳密にするためにstructured output等を使い始めると、そもそもtoolが一切呼ばれずに応答が返るといった挙動が見られるので、なるだけスキーマは減らしましょう。
(2) 順番が壊れると困る処理まで ReAct に任せる
- 支払い処理
- DB 書き込み
- 申請の承認フロー
など、「順番と回数が大事な処理」まで ReAct に丸投げすると、
- 本来1回だけ叩くべき API を複数回叩いてしまう
- 例外処理の分岐まで LLM に委ねることになり、テストが難しくなる
など、本番運用では厳しい状態になりがちです。
(3) ステップ数・コスト・ログの設計をしない
ReAct は Thought / Action / Observation をループする構造なので、
- 1 リクエストあたりのステップ数
- 各ステップでどのツールがどれだけ呼ばれているか
を決めておかないと、
- たまたま動いたケースだけ見て「いい感じ」に見える
- 時々コスト爆発するが、どこでそうなっているのか分からない
という状態になります。
また、単純に成功した挙動の場合でもコストとしてはStateful Workflowなどで書いたエージェントに比べると圧倒的に高くなります。それだけのコストに合う複雑の処理をしているのかどうかは確認するようにしましょう。
5-4. ReAct を使うなら、どう枠を切るか
ダメな使い方を避けつつ ReAct を活かすには、役割と範囲をはっきり決めておくのが現実的です。
ポイント 1:ReAct に任せるのは「探索部分」だけ
- 「どの情報源を、どの順番で調べるか」
- 「次にどのツールを試すか」
といった探索の部分だけ ReAct に任せる。
一方で、
- DB 書き込み
- 決済
- ワークフローの承認
などは Workflow や通常のコード側で固定しておき、ReAct Agent は情報収集後にWorkflowが定まったエージェントを呼び出す。みたいな。
ポイント 2:ツールは小さく・用途ごとに分ける
- searchAllみたいな万能ツールではなく、
searchWebsearchDocsgetUserProfile
のように、用途ごとに分けてしまう。
それぞれのツールについて、
- 必要最小限の引数だけを持たせる
- 「どんな状況で使うべきか」を説明文に明示する
といった形にすると、ReAct から選ばれやすくなります。また、この時にtoolの説明を長々と書きすぎないようにしましょう。その場合にもtool自体が呼ばれにくくなる原因になります。
例外として、コマンドラインを打つためのtoolです、inputはコマンドのtextのみです。のようなtoolは、これを使用しながら行動を決定していくといった特殊な使い方も存在するので、必ずしもスキーマを減らしてtool数を増やすことが正義ではないです。
ポイント 3:ステップ数・ログをあらかじめ決めておく
-
max_steps(ツール呼び出しを含めたループ回数の上限) - 各ステップでの Thought / Action / Observation をログに残す
といった制約と可視化は、最初から入れておいた方が安全です。
5-5. ReAct の代わりに検討できるパターン
ReAct 以外にも選択肢があるので、「本当に ReAct なのか?」を一度立ち止まって考えた方がいい場面は多いです。
ケース A:ゴールとフローがほぼ決まっている
→ Stateful Workflow に寄せる
- 例:
- 入力検証 → DB 保存 → 通知 → ログ
- Workflow で順番と条件を固定し、LLM は各ステップの中だけに使う
ケース B:2〜3 ステップに分解すれば足りる
→ Plan & Execute
- Planner にタスクリストを出してもらい、Executor(Workflow / コード)で順に処理する
ケース C:単に「どのエージェントを使うか」切り替えたいだけ
→ Dynamic Routing / RuntimeContext
テナント / プラン / ユーザー属性に応じて使う Agent、許可するツールセットを切り替えるだけなら、ReAct ではなく「ルーティングの問題」として扱う。
ケース D:レスポンスをとにかく早く返したい
→ 単純な LLM 呼び出し
- 調査や探索を伴わない処理であれば、Tool や ReAct を噛ませるより、シンプルな LLM 呼び出しに特化したエンドポイントを作った方がよい
6. まとめ:エージェント構造の選び方メモ
-
完全にフローが決まっている処理
→ Stateful Workflow にして、順番と条件をコード側で固定する -
2〜3 回のループで十分なタスク分解が必要なとき
→ Plan & Execute で「タスクリスト」と「実行」を分ける -
推論時間がボトルネックで、限界までレイテンシを下げたいとき
→ 単純な LLM 単体呼び出し専用のエンドポイントを用意する -
どのツールをどう組み合わせるかがケースごとに変わる探索タスク
→ ReAct を検討するが、- ツールは小さく分割
- スキーマは最小限
- ステップ数とログをあらかじめ決めてから使う
-
ユーザーやテナントごとにやることが変わるだけのとき
→ Dynamic Routing / RuntimeContext でエージェントやツールセットを切り替える
そのタスクにとって、本当にAIは自律的に動く必要があるか?を毎回問い直すくらいがちょうどいいバランスだと思います。