AI Agentとは?詳細な解説とJavaの例で理解する
1. 概要
「AI Agent」はどこにでも現れるキーワードですが、定義は不明確なことが多いです。すべてのチャットボットをエージェントと呼ぶ人もいますし、複雑なマルチステップシステムだけを真のエージェントと考える人もいます。この記事では、AI Agentが何であるか、必須コンポーネントは何か、そしてその背後にあるループメカニズムについて明確に説明します。読み終わった後、JavaでMinimalなエージェントを自分で構築して、その内部動作を理解し、その後、Spring AIを使ってリアルなLLMバージョンにアップグレードできるようになります。
2. AI Agentとは?
簡潔な定義:AI Agentは、単なる質問に答えるのではなく、目標を達成するために自ら行動を決定する、LLMを脳として使用するシステムです。
通常のチャットボットとの違い:
| 特徴 | 通常のチャットボット | AI Agent |
|---|---|---|
| 処理の回数 | 1回:質問→回答 | 複数ステップ:観察→推論→行動→繰り返し |
| Tool/APIの呼び出し | 通常はしない | はい、どのツールを呼び出すかは自分で決定 |
| 長期的な目標の有無 | いいえ | はい、エージェント自身がタスクを細分化して目標を達成 |
| 例 | 「ハノイの天気は?」 → 説明文を返答 | 「明日のハノイ~ホーチミン間の最安チケットを予約する」 → 自動でチケットを検索、比較、予約、確認 |
言い換えれば、LLMは単なる「言語脳」で、テキストを生成することを知っています。Agentはその脳を、実世界で行動する(API呼び出し、ファイル読み込み、データベースクエリ)ことができる実体に変え、タスク完了まで推論プロセスを自動で繰り返すことができます。
3. AI Agentのコアコンポーネント
最小限のエージェントには4つのコンポーネントが必要です。これらのいずれかが欠落すると、システムはエージェントの「衣装を被った」チャットボットに過ぎません。
3.1. LLM – 推論脳
LLMはプロンプト(目標、履歴、利用可能なツールのリストを含む)を受け取り、次の決定を生成します:直接回答するか、どのツールを呼び出すか、どのパラメータを使うか。
3.2. Tool(function calling) – エージェントの手足
Toolは、エージェントが呼び出すことができる具体的な関数です。注文の追跡、天気APIの呼び出し、データベースクエリなど。これは、エージェントを「話す」から「実行する」に変える部分です。
public interface Tool {
String name();
boolean canHandle(String input);
String execute(String input);
}
canHandleは、このツールが現在の入力を処理できるかどうかをチェックします。executeはアクションを実行し、結果(observation)を返します。
3.3. Memory – メモリ
エージェントは、不要な繰り返しを避けるために、前のステップで何をしたかを覚えておく必要があります(会話履歴、呼び出したツールの結果など)。メモリは単純なList<String>でステップのログを保存するか、またはより複雑なベクトルストアで関連情報を検索できます。
3.4. Control loop – 制御ループ
これがプロセス全体を駆動する「エンジン」です。Thought → Action → Observationステップを繰り返します。目標に達するか、許可された最大ステップに達するまで継続します。次のセクションでこのメカニズムについて詳しく説明します。
4. ReAct ループ:エージェントが「考えてから行動する」方法
ReAct(Reasoning + Acting)は、エージェントの最も一般的なループモデルです:
- Thought:LLMが「次に何をすべきか」を推論します。
- Action:ツールを選択し、適切なパラメータで呼び出します。
- Observation:ツールから結果を受け取り、コンテキストに戻します。
- ステップ1から繰り返します。LLMが最終回答に十分な情報があると判断するか、
maxStepsに達するまで。
このメカニズムを明確に理解するために、実際のLLMを呼び出す必要がなく(APIキーの費用がかかる)、純粋なJavaでミニエージェントを構築し、実際のLLMの代わりにif-elseを使用します。
public class SimpleAgent {
private final List<Tool> tools;
private final int maxSteps;
public SimpleAgent(List<Tool> tools, int maxSteps) {
this.tools = tools;
this.maxSteps = maxSteps;
}
public String run(String goal) {
String observation = goal;
for (int step = 1; step <= maxSteps; step++) {
Tool chosen = tools.stream()
.filter(t -> t.canHandle(observation))
.findFirst()
.orElse(null);
if (chosen == null) {
return "処理できるツールがありません:" + observation;
}
System.out.printf("ステップ%d - Thought:ツール'%s'を使う必要があります%n", step, chosen.name());
String result = chosen.execute(observation);
System.out.printf("ステップ%d - Observation:%s%n", step, result);
return result; // シンプルな例:1ステップ後に完了
}
return "許可されたステップ数(" + maxSteps + ")を超えても完了できませんでした。";
}
}
maxStepsはループステップの「予算」です — エージェントが目標に決して到達しない場合、無限に実行されないように必須です。forループはThought(ツール選択)、Action(execute)、Observation(コンソール出力結果)をシミュレートします。
3つの説明用ツール:
public class WeatherTool implements Tool {
@Override
public String name() {
return "WeatherTool";
}
@Override
public boolean canHandle(String input) {
return input.toLowerCase().contains("天気");
}
@Override
public String execute(String input) {
return "ハノイは今日28°C、くもりです。";
}
}
public class OrderStatusTool implements Tool {
@Override
public String name() {
return "OrderStatusTool";
}
@Override
public boolean canHandle(String input) {
return input.toLowerCase().contains("注文");
}
@Override
public String execute(String input) {
return "注文#1023は配送中で、2日以内に配達予定です。";
}
}
public class CalculatorTool implements Tool {
@Override
public String name() {
return "CalculatorTool";
}
@Override
public boolean canHandle(String input) {
return input.matches(".*\\d+\\s*\\+\\s*\\d+.*");
}
@Override
public String execute(String input) {
String digits = input.replaceAll("[^0-9+]", "");
String[] parts = digits.split("\\+");
int sum = Integer.parseInt(parts[0]) + Integer.parseInt(parts[1]);
return "結果:" + sum;
}
}
実行してみます:
SimpleAgent agent = new SimpleAgent(
List.of(new CalculatorTool(), new WeatherTool(), new OrderStatusTool()),
3
);
String result = agent.run("ハノイの今日の天気を教えてください");
System.out.println(result);
結果:
ステップ1 - Thought:ツール'WeatherTool'を使う必要があります
ステップ1 - Observation:ハノイは今日28°C、くもりです。
ハノイは今日28°C、くもりです。
正しいツール選択の動作と、適切なツールがない場合をテストします:
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class SimpleAgentTest {
@Test
void shouldPickWeatherToolWhenGoalMentionsWeather() {
SimpleAgent agent = new SimpleAgent(
List.of(new CalculatorTool(), new WeatherTool(), new OrderStatusTool()),
3
);
String result = agent.run("ハノイの今日の天気を教えてください");
assertThat(result).contains("ハノイ");
}
@Test
void shouldReturnErrorWhenNoToolMatches() {
SimpleAgent agent = new SimpleAgent(List.of(new CalculatorTool()), 3);
String result = agent.run("私のために歌を歌ってください");
assertThat(result).startsWith("処理できるツールがありません");
}
}
この例ではif-elseをLLMの代わりに使用するため、「推論」は非常に厳密です。本当のエージェントが異なる点:ハードコードされたcanHandleの代わりに、LLM自身がツール説明を読んで、どのツールを呼び出すかを決定し、パラメータはLLM自身が自然言語質問から抽出します。
5. Spring AIとtool callingで実際のエージェントを構築
LLMが実際に決定を下すのを見るために、Spring AI — Spring BootにAIモデルを統合するフレームワークを使用します。これはtool calling機能を備えています。
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
<version>1.0.0</version>
</dependency>
APIキーとモデルを設定:
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
chat:
options:
model: gpt-4o-mini
@Toolアノテーションを使用してツールを宣言します。前の例のOrderStatusToolの役割と同じですが、今度は実際のLLMが呼び出します:
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Component;
@Component
public class OrderTools {
@Tool(description = "注文IDで注文ステータスを照会")
public String getOrderStatus(String orderId) {
// 実際にはここでOrderService/データベースを呼び出します
return "注文" + orderId + "は配送中で、2日以内に配達予定です。";
}
}
descriptionは非常に重要です:LLMはこの説明に基づいてこのツールを呼び出すべきかどうかとどのパラメータを渡すかを決定します。
ChatClientを使用してツールをエージェントに接続するサービス:
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;
@Service
public class OrderAssistantService {
private final ChatClient chatClient;
public OrderAssistantService(ChatClient.Builder builder, OrderTools orderTools) {
this.chatClient = builder
.defaultTools(orderTools)
.build();
}
public String ask(String question) {
return chatClient.prompt(question).call().content();
}
}
ask("注文ORD-1023はどこにありますか?")を呼び出すと、Spring AIは内部的に以下を実行します:
- クエリとツール
getOrderStatusの説明をモデルに送信します。 - モデルは「
getOrderStatusをorderId="ORD-1023"で呼び出す」という決定を返すか、すぐに答えるのではなく。 - Spring AIは対応するJavaメソッドを自動的に呼び出し、結果を取得します。
- 結果をモデルに送り返して、モデルが最終的な自然な回答を作成します。
注文ORD-1023は現在配送中で、あと2日以内に配達される予定です。
上記の4つのステップ全体がReAct ループの完全な実装です。違いは、Spring AIがforループを隠しているため、セクション4の純粋なJavaの例のようにcontrol loopを自分で書く必要がないことです。
注意: バージョン
spring-ai-starter-model-openaiとアーティファクト名は時間とともに変わる可能性があります。依存関係を追加する前に、使用しているSpring Bootバージョンに対応するBOMを常に確認してください。
6. 一般的なエージェントの種類
- Single-agent ReAct:単一のエージェントがThought-Action-Observationをループします。上記の2つの例のような中程度のタスクに適しています。
- Plan-and-Execute:エージェントは最初に詳細な計画(ステップのリスト)を立てて、ReActのように継続的に推論し直さずに各ステップを実行します。構造化された複数ステップのタスク(例:「5つのセクションからなるレポートを作成」)に適しています。
- Multi-agent:複数の専門的なエージェントが協力します。通常、「Supervisor」エージェントが複数の「Worker」エージェント(例:検索エージェント、テキスト作成エージェント、データ検証エージェント)を調整し、結果が統合されます。
どのタイプを選択するかはタスクの複雑さに依存します:シンプルなタスクはsingle-agentを使用、複数のドメイン(検索+作成+検証)を含むタスクはmulti-agentに分割して、各部分を制御しやすく、デバッグしやすくします。
7. よくあるエラー&エージェントを使用すべきではない場合
- 無限ループ、トークンの消費:
maxStepsまたはトークン予算を設定しない場合、LLMが「まだ完了していない」と考えるため、エージェントは永遠にループする可能性があります。セクション4のSimpleAgentのように、常にステップ数を明確に制限します。 - ツール説明が曖昧:ツールの
descriptionが明確でない場合、LLMが間違ったツールを呼び出したり、パラメータを間違った型で渡したりする可能性があります。具体的な説明を書き、期待されるパラメータ形式を明確に示します。 - 危険なアクションの保護策の欠落:エージェントがデータを削除したり、お金を転送したりするツールを呼び出す権限がある場合、確認ステップがないことは大きなリスクです。取り消せないアクションを実行する前に、常に確認レイヤー(人間の参入またはルールベースのチェック)を追加します。
- 単純なタスクに対するオーバーエンジニアリング:質問がLLMの1回の呼び出しのみで回答を得られる場合(例:文の翻訳)、tool callingでエージェントループ全体を構築しないでください。余分なレイテンシーが発生し、不要にモデルを複数回呼び出すコストが増えます。
注意: システムが外部アクション(API、データベース)の呼び出しが不要で、テキストを1回生成するだけで済む場合、それは通常のLLMのタスクです — エージェントではありません。不要な複雑性を追加しないでください。
8. 結論
AI Agentは通常のチャットボットとは異なり、自ら行動を決定し、目標を達成するまで推論プロセスを繰り返すことができます。4つのコンポーネント(LLM、tool、memory、control loop)に基づいています。ReAct ループ(Thought-Action-Observation)は、現在のほとんどのエージェントの基盤となるメカニズムであり、本番環境でSpring AIなどのフレームワークに切り替える前に、純粋なJavaでシミュレートしてその性質を理解することができます。最も重要なのは:常にステップ数を制限し、ツール説明を明確に書き、取り消せないアクションに対して保護策を追加することです — エージェントは強力ですが、制御がなければ「やりすぎ」になりやすいのです。