エージェントと Agentic AI
このセクションでは、langchain4j-agentic モジュールを使用してエージェント型 AI アプリケーションを構築する方法を説明します。モジュール全体は実験的機能と見なされており、将来のリリースで変更される可能性があることに注意してください。
エージェント型システム
AI エージェントについて普遍的に合意された定義はありませんが、複数の AI サービスの能力を連携・組み合わせることで、より複雑なタスクを達成 できる AI を活用したアプリケーションを作成する方法として、いくつかのパターンが現れています。これらのパターンは、しばしば「エージェント型システム」または「エージェント型 AI」と呼ばれます。通常は、大規模言語モデル(LLM)を用いて、タスクの実行をオーケストレーションし、ツールの利用を管理し、対話をまたいでコンテキストを維持します。
Anthropic の研究者による最近の記事によれば、これらのエージェント型システムのアーキテクチャは、ワークフローと純粋なエージェントという 2 つの主なカテゴリに分類できます。

このチュートリアルで扱う langchain4j-agentic モジュールは、ワークフローおよび純粋なエージェント型 AI アプリケーションの構築を支援する抽象化とユーティリティ一式を提供します。ワークフローの定義、ツール利用の管理、異なる LLM との対話をまたいだコンテキストの維持を可能にします。
LangChain4j のエージェント
LangChain4j のエージェントは、LLM を使用して特定のタスクまたは一連のタスクを実行します。通常の AI サービスと同様に、単一メソッドを持つインターフェースとして定義でき、そこに @Agent アノテーションを追加するだけです。
public interface CreativeWriter {
@UserMessage("""
You are a creative writer.
Generate a draft of a story no more than
3 sentences long around the given topic.
Return only the story and nothing else.
The topic is {{topic}}.
""")
@Agent("Generates a story based on the given topic")
String generateStory(@V("topic") String topic);
}
このアノテーションには、エージェントの目的を示す短い説明も付与することを推奨します。特に、他のエージェントがこのエージェントをいつどのように利用するかを適切に判断する必要がある純粋なエージェント型パターンでは重要です。この説明は、エージェントビルダーの description メソッドを使用して、プログラムから指定することもできます。
エージェントには、エージェント型システム内で一意に識別する名前も必要です。この名前は @Agent アノテーションで指定することも、エージェントビルダーの name メソッドを使用してプログラムから指定することもできます。指定しない場合は、@Agent が付与されたメソッド名が使用されます。
これで、インターフェースと使用するチャットモデルを指定して、AgenticServices.agentBuilder() メソッドによりこのエージェントのインスタンスを構築できます。
CreativeWriter creativeWriter = AgenticServices
.agentBuilder(CreativeWriter.class)
.chatModel(myChatModel)
.outputKey("story")
.build();
本質的に、エージェントは通常の AI サービスであり、同じ機能を提供します。ただし、他のエージェントと組み合わせて、より複雑なワークフローやエージェント型システムを作成できます。
AI サービスとのもう 1 つの主な違いは、エージェント呼び出しの結果を保存する共有変数の名前を指定する outputKey パラメータがあることです。これにより、同じエージェント型システム内の他のエージェントが結果を利用できます。代わりに、この例のようにプログラムから指定するのではなく、出力名を @Agent アノテーションで直接宣言することもできます。
@Agent(outputKey = "story", description = "Generates a story based on the given topic")
AgenticServices クラスは、langchain4j-agentic フレームワークで利用可能なあらゆる種類のエージェントを作成・定義するための静的ファクトリメソッド群を提供します。
AgenticScope の紹介
langchain4j-agentic モジュールは、エージェント型システムに参加するエージェント間で共有されるデータの集合である AgenticScope の概念を導入します。AgenticScope は共有変数を保存するために使われます。エージェントは生成した結果を伝えるために変数へ書き込み、別のエージェントは自身のタスクに必要な情報を組み立てるために変数を読み取れます。これにより、必要に応じて情報と結果を共有し、エージェントが効率的に協調できます。
AgenticScope は、すべてのエージェントの呼び出し順序と応答など、他の関連情報も自動的に登録します。エージェント型システムのメインエージェントが呼び出されると自動的に作成され、必要に応じてコールバックを通じてプログラムから提供されます。AgenticScope のさまざまな利用方法は、langchain4j-agentic が実装するエージェント型パターンを説明する際に、実践的な例で明らかにします。
ワークフローパターン
langchain4j-agentic モジュールは、複数のエージェントをプログラムからオーケストレーションし、エージェント型ワークフローパターンを作成するための抽象化一式を提供します。これらのパターンは組み合わせて、より複雑なワークフローを作成できます。
順次ワークフロー
順次ワークフローは最も単純なパターンで、複数のエージェントを順番に呼び出し、各エージェントの出力を次のエージェントへの入力として渡します。特定の順序で実行する必要がある一連のタスクに適しています。
たとえば、先に定義した CreativeWriter エージェントに、生成さ れた物語を特定の読者層により適合するよう編集できる AudienceEditor エージェントを追加するとよいでしょう。
public interface AudienceEditor {
@UserMessage("""
You are a professional editor.
Analyze and rewrite the following story to better align
with the target audience of {{audience}}.
Return only the story and nothing else.
The story is "{{story}}".
""")
@Agent("Edits a story to better fit a given audience")
String editStory(@V("story") String story, @V("audience") String audience);
}
さらに、特定のスタイルに対して同じ仕事を行う、非常によく似た StyleEditor も追加します。
public interface StyleEditor {
@UserMessage("""
You are a professional editor.
Analyze and rewrite the following story to better fit and be more coherent with the {{style}} style.
Return only the story and nothing else.
The story is "{{story}}".
""")
@Agent("Edits a story to better fit a given style")
String editStory(@V("story") String story, @V("style") String style);
}
このエージェントの入力引数には変数名のアノテーションが付与されていることに注意してください。実際には、エージェントに渡す引数の値を直接 提供するのではなく、同じ名前を持つ AgenticScope の共有変数から取得します。これにより、エージェントはワークフロー内の前のエージェントの出力にアクセスできます。エージェントクラスを -parameters オプションを有効にしてコンパイルし、メソッドパラメータ名を実行時に保持している場合は、@V アノテーションを省略でき、変数名はパラメータ名から自動的に推論されます。
この時点で、これら 3 つのエージェントを組み合わせた順次ワークフローを作成できます。CreativeWriter の出力は AudienceEditor と StyleEditor の両方への入力として渡され、最終出力は編集済みの物語です。
CreativeWriter creativeWriter = AgenticServices
.agentBuilder(CreativeWriter.class)
.chatModel(BASE_MODEL)
.outputKey("story")
.build();
AudienceEditor audienceEditor = AgenticServices
.agentBuilder(AudienceEditor.class)
.chatModel(BASE_MODEL)
.outputKey("story")
.build();
StyleEditor styleEditor = AgenticServices
.agentBuilder(StyleEditor.class)
.chatModel(BASE_MODEL)
.outputKey("story")
.build();
UntypedAgent novelCreator = AgenticServices
.sequenceBuilder()
.subAgents(creativeWriter, audienceEditor, styleEditor)
.outputKey("story")
.build();
Map<String, Object> input = Map.of(
"topic", "dragons and wizards",
"style", "fantasy",
"audience", "young adults"
);
String story = (String) novelCreator.invoke(input);
ここでの novelCreator エージェントは、3 つのサブエージェントを順番に呼び出して組み合わせる、順次ワークフローを実装したエージェント型システムです。このエージェントの定義には型付きインターフェースが提供されていないため、シーケンスエージェントビルダーは、入力マップで呼び出せる汎用エージェントである UntypedAgent インスタンスを返します。
public interface UntypedAgent {
@Agent
Object invoke(Map<String, Object> input);
}
その入力マップ内の値は AgenticScope の共有変数へコピーされるため、サブエージェントからアクセスできます。novelCreator エージェントの出力も、物語の作成・編集ワークフローの実行中に他のすべてのエージェントによって書き換えられた、"story" という名前の AgenticScope 共有変数から取得されます。
な お、型付きインターフェースを提供せず、単一のエージェントも UntypedAgent インスタンスとして定義できます。たとえば、CreativeWriter エージェントは次のように定義することも可能でした。
UntypedAgent creativeWriter = AgenticServices.agentBuilder()
.chatModel(BASE_MODEL)
.description("Generate a story based on the given topic")
.userMessage("""
You are a creative writer.
Generate a draft of a story no more than
3 sentences long around the given topic.
Return only the story and nothing else.
The topic is {{topic}}.
""")
.inputKey(String.class, "topic")
.returnType(String.class) // String is the default return type for untyped agents
.outputKey("story")
.build();
一方、ワークフローエージェントにも、型の強い入力と出力で呼び出せるよう、任意で型付きインターフェースを提供できます。この場合、UntypedAgent インターフェースを次のようなより具体的なものに置き換えられます。
public interface NovelCreator {
@Agent
String createNovel(@V("topic") String topic, @V("audience") String audience, @V("style") String style);
}
これにより、novelCreator エージェントは次のように作成・使用できます。
NovelCreator novelCreator = AgenticServices
.sequenceBuilder(NovelCreator.class)
.subAgents(creativeWriter, audienceEditor, styleEditor)
.outputKey("story")
.build();
String story = novelCreator.createNovel("dragons and wizards", "young adults", "fantasy");
ループワークフロー
LLM の能力をより活用する一般的な方法は、物語などのテキストを編集・改善できるエージェントを繰り返し呼び出して、反復的に洗練することです。これは、特定の条件を満たすまでエージェントを複数回呼び出すループワークフローパターンで実現できます。
必要なスタイルとの整合性をスコアに基づいて評価する StyleScorer エージェントを使用できます。
public interface StyleScorer {
@UserMessage("""
You are a critical reviewer.
Give a review score between 0.0 and 1.0 for the following
story based on how well it aligns with the style '{{style}}'.
Return only the score and nothing else.
The story is: "{{story}}"
""")
@Agent("Scores a story based on how well it aligns with a given style")
double scoreStyle(@V("story") String story, @V("style") String style);
}
次に、このエージェントを StyleEditor とループで使用し、スコアが 0.8 などのしきい値に達するか、最大反復回数に達するまで物語を反復的に改善できます。
StyleEditor styleEditor = AgenticServices
.agentBuilder(StyleEditor.class)
.chatModel(BASE_MODEL)
.outputKey("story")
.build();
StyleScorer styleScorer = AgenticServices
.agentBuilder(StyleScorer.class)
.chatModel(BASE_MODEL)
.outputKey("score")
.build();
UntypedAgent styleReviewLoop = AgenticServices
.loopBuilder()
.subAgents(styleScorer, styleEditor)
.maxIterations(5)
.exitCondition( agenticScope -> agenticScope.readState("score", 0.0) >= 0.8)
.build();
ここで styleScorer エージェントは出力を "score" という名前の AgenticScope 共有変数に書き込み、ループの終了条件で同じ変数にアクセスして評価します。
exitCondition メソッドは引数として Predicate<AgenticScope> を受け取り、デフォルトでは各エージェント呼び出しの後に評価されます。これにより、呼び出し回数を可能な限り減らすため、条件が満たされるとすぐにループが終了します。ただし、ループの最後でのみ終了条件を確認し、条件をテストする前にすべてのエージェントを必ず呼び出すこともできます。その場合は、ループビルダーを testExitAtLoopEnd(true) メソッドで設定します。あるいは、exitCondition メソッドは第 2 引数として現在のループ反復回数を受け取る BiPredicate<AgenticScope, Integer> も受け取れます。たとえば、次のループ定義では、
UntypedAgent styleReviewLoop = AgenticServices
.loopBuilder()
.subAgents(styleScorer, styleEditor)
.maxIterations(5)
.testExitAtLoopEnd(true)
.exitCondition( (agenticScope, loopCounter) -> {
double score = agenticScope.readState("score", 0.0);
return loopCounter <= 3 ? score >= 0.8 : score >= 0.6;
})
.build();
最初の 3 回の反復でスコアが少なくとも 0.8 ならループを終了し、それ以外では品質への期待を下げ、スコアが少なくとも 0.6 になった時点で終了します。また、終了条件が満たされた後でも、最後にもう一度 styleEditor エージェントを強制的に呼び出します。
この styleReviewLoop を設定した後は、単一のエージェントと見なして CreativeWriter エージェントとシーケンスに組み込み、StyledWriter エージェントを作成できます。
public interface StyledWriter {
@Agent
String writeStoryWithStyle(@V("topic") String topic, @V("style") String style);
}
これにより、物語生成とスタイルレビューのプロセスを組み合わせた、より複雑なワークフローを実装します。
CreativeWriter creativeWriter = AgenticServices
.agentBuilder(CreativeWriter.class)
.chatModel(BASE_MODEL)
.outputKey("story")
.build();
StyledWriter styledWriter = AgenticServices
.sequenceBuilder(StyledWriter.class)
.subAgents(creativeWriter, styleReviewLoop)
.outputKey("story")
.build();
String story = styledWriter.writeStoryWithStyle("dragons and wizards", "comedy");
並列ワークフロー
特に同じ入力に対して独立して作業できる場合、複数のエージェントを並列に呼び出すことが有用なことがあります。これは、複数のエージェントを同時に呼び出して出力を単一の結果に結合する、並列ワークフローパターンで実現できます。
たとえば、映画と食事を組み合わせて、指定されたムードに合う素敵な夜のプランをいくつか生成するために、映画と料理の専門家を使ってみましょう。
public interface FoodExpert {
@UserMessage("""
You are a great evening planner.
Propose a list of 3 meals matching the given mood.
The mood is {{mood}}.
For each meal, just give the name of the meal.
Provide a list with the 3 items and nothing else.
""")
@Agent
List<String> findMeal(@V("mood") String mood);
}
public interface MovieExpert {
@UserMessage("""
You are a great evening planner.
Propose a list of 3 movies matching the given mood.
The mood is {mood}.
Provide a list with the 3 items and nothing else.
""")
@Agent
List<String> findMovie(@V("mood") String mood);
}
2 人の専門家の作業は独立しているため、次のように AgenticServices.parallelBuilder() メソッドを使用して並列に呼び出すことができます。
FoodExpert foodExpert = AgenticServices
.agentBuilder(FoodExpert.class)
.chatModel(BASE_MODEL)
.outputKey("meals")
.build();
MovieExpert movieExpert = AgenticServices
.agentBuilder(MovieExpert.class)
.chatModel(BASE_MODEL)
.outputKey("movies")
.build();
EveningPlannerAgent eveningPlannerAgent = AgenticServices
.parallelBuilder(EveningPlannerAgent.class)
.subAgents(foodExpert, movieExpert)
.executor(Executors.newFixedThreadPool(2))
.outputKey("plans")
.output(agenticScope -> {
List<String> movies = agenticScope.readState("movies", List.of());
List<String> meals = agenticScope.readState("meals", List.of());
List<EveningPlan> moviesAndMeals = new ArrayList<>();
for (int i = 0; i < movies.size(); i++) {
if (i >= meals.size()) {
break;
}
moviesAndMeals.add(new EveningPlan(movies.get(i), meals.get(i)));
}
return moviesAndMeals;
})
.build();
List<EveningPlan> plans = eveningPlannerAgent.plan("romantic");
ここで EveningPlannerAgent に定義した AgenticScope の output 関数により、2 つのサブエージェントの出力を組み立て、指定されたムードに合う映画と食事を組み合わせた EveningPlan オブジェクトのリストを作成できます。output メソッドは並列ワークフローで特に重要ですが、単に AgenticScope の値を返す代わりに、サブエージェントの出力を単一の結果へ結合する方法を定義するため、実際には任意のワークフローパターンで使用できます。executor メソッドでは、サブエージェントの並列実行に使用する Executor を任意で提供できます。指定しない場合、デフォルトで内部キャッシュスレッドプールが使用されます。