Java クイックスタート
JamJet で最初の AI エージェントとワークフローを Java で構築 — ツール、戦略、耐久実行、IR コンパイル。
Java クイックスタート
このガイドでは、JavaでAIエージェントと永続的なワークフローを構築する手順を説明します。最後には、JamJetのツール、エージェント戦略、IRコンパイル、型付きワークフロー状態がどのように連携するか、そして本番環境のエージェントシステムにおいて各設計選択がなぜ重要なのかを理解できるようになります。
前提条件
開始する前に、以下を準備してください:
- Java 21以降 — JamJetは、コールバック地獄なしでノンブロッキングI/Oを実現するために仮想スレッド(
Thread.ofVirtual)を使用します。仮想スレッドはJava 21で正式機能となりました(JEP 444)。 - Maven 3.9以降またはGradle 8以降 — Maven Centralから依存関係を取得できるビルドツール。
- 稼働中のJamJetランタイム — 本番環境での実行用。開発中はすべてをインプロセスで実行できます(サーバー不要)。ローカルランタイムを起動するには:
jamjet dev - LLM APIキー — 環境変数に
OPENAI_API_KEYまたはANTHROPIC_API_KEYを設定してください。ローカル環境のみで開発する場合、Ollamaはキー不要で動作します。
ヒント: このガイドは、ランタイムを起動せずに全て進めることができます。インプロセスエグゼキュータを使えば、ワークフローをローカルでコンパイル、検証、実行できます。クラッシュリカバリと永続的な状態が必要になったときにランタイムを追加してください。
依存関係の追加
Java SDKは、Maven Centralにdev.jamjet:jamjet-sdkとして公開されています。
Maven
<dependency>
<groupId>dev.jamjet</groupId>
<artifactId>jamjet-sdk</artifactId>
<version>0.4.0</version>
</dependency>Gradle (Kotlin DSL)
implementation("dev.jamjet:jamjet-sdk:0.4.0")Gradle (Groovy DSL)
implementation 'dev.jamjet:jamjet-sdk:0.4.0'プロジェクトがJava 21以降をターゲットにしていることを確認してください。Mavenの場合:
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>ツールの定義
ツールは、エージェントと外部世界(Web検索、データベースクエリ、API呼び出し、ファイルI/O)を繋ぐブリッジです。JamJetでは、ツールは@Toolアノテーションが付いたToolCall<T>を実装するJavaのrecordです。
import dev.jamjet.tool.Tool;
import dev.jamjet.tool.ToolCall;
@Tool(description = "トピックに関する情報をWebで検索する")
record WebSearch(String query) implements ToolCall<String> {
public String execute() {
// 本番環境では、ここで検索APIを呼び出します
return "'" + query + "'の検索結果: JamJetはパフォーマンス重視の"
+ "エージェントネイティブなランタイムおよびフレームワークです。";
}
}なぜレコードを使うのか?
この設計は意図的なものです。Javaレコードは、エージェントツーリングにとって重要な3つの特性を提供します:
-
不変性 — ツール呼び出しのパラメータは構築後に決して変更されません。これにより、ツール呼び出しのシリアライズ、再実行、監査が安全に行えます。JamJetが失敗したワークフローを再実行する際、まったく同じパラメータでまったく同じツール呼び出しを再実行します。
-
自動JSON Schema導出 — SDKがレコードコンポーネント(上記の
String queryなど)を検査し、LLMがツールを呼び出すために必要なJSON Schemaを生成します。手動でのスキーマ記述、アノテーションの山、コードとスキーマ間の乖離は不要です。 -
構造的等価性 — 2つの
WebSearch("jamjet")インスタンスは等しいと判定されます。これにより、リトライ時のツール呼び出しの重複排除とキャッシングが可能になります。
@Toolアノテーションは、LLMがどのツールを使用するか決定する際に参照するdescriptionを提供します。同僚にツールを説明するように書いてください — 明確に、具体的に、アクション指向で。
注意: ツール設計パターン、エージェント戦略、各ケースでの使い分けについての詳細は、Agentic AI Patternsを参照してください。
エージェントを構築する
エージェントは、モデル、ツール、指示、そして推論戦略を組み合わせたものです。戦略は、エージェントが何をするかではなく、どのように考えるかを決定します。
import dev.jamjet.agent.Agent;
var agent = Agent.builder("researcher")
.model("claude-haiku-4-5-20251001")
.tools(WebSearch.class)
.instructions("あなたは有能なリサーチアシスタントです。"
+ "常にまず検索を行い、その後で詳細なサマリーを提供してください。")
.strategy("react")
.maxIterations(5)
.build();各部分を詳しく見ていきましょう。
react戦略
.strategy("react")を設定すると、JamJetにReAct(Reasoning + Acting)ループを使用するよう指示することになります:
- 思考 — モデルが次に何をすべきか推論する
- 行動 — モデルがツールを呼び出す
- 観察 — ツールの結果がモデルにフィードバックされる
- モデルが最終的な回答を生成するか、反復上限に達するまで繰り返す
これは最も一般的なエージェント戦略です。モデルがどのツールをどの順序で呼び出すかを動的に決定できるため、柔軟性があります。正確なステップの順序を予測できないオープンエンドなタスクに適しています。
JamJetは3つの組み込み戦略をサポートしています:
| 戦略 | 使用するタイミング | 動作方法 |
|---|---|---|
react | オープンエンドなタスク、探索的な調査 | 思考-行動-観察ループ |
plan-and-execute | 事前計画が有効な構造化されたタスク | 計画を生成し、各ステップを順次実行 |
critic | 品質管理が必要なタスク | 草案-批評-修正ループ |
ヒント: どの戦略を選ぶべきか迷っていますか?まず
reactから始めましょう。エージェントが迷走しているようならplan-and-executeに、スピードよりも出力品質が重要ならcriticにアップグレードしてください。ベンチマークについてはjamjet.dev/researchの戦略比較を参照してください。
ガードレール:コスト、時間、イテレーション
本番環境のエージェントには厳格な制限が必要です。これらがないと、混乱したモデルがループ内でAPIの予算を使い果たす可能性があります:
var agent = Agent.builder("investment-researcher")
.model("gpt-4o")
.tools(WebSearch.class, FetchUrl.class, StoreNote.class)
.instructions("""
You are a professional investment research analyst.
Search for recent news and financials, then produce
a structured investment memo.
""")
.strategy("plan-and-execute")
.maxIterations(6)
.maxCostUsd(0.50)
.timeoutSeconds(120)
.build();maxIterations(6)— エージェントは6回の推論ステップ後に停止します。完了していなくても停止します。これにより無限ループを防ぎます。maxCostUsd(0.50)— ランタイムはトークンコストをリアルタイムで追跡し、支出が50セントを超えるとエージェントを停止します。timeoutSeconds(120)— 実時間でのタイムアウト。エージェントが2分以内に完了しない場合、実行は中止されます。
これらは提案ではありません — ランタイムによって強制される厳格な制限です。JamJetランタイムは終了時ではなく、すべてのステップの間にこれらをチェックします。
実行する
エージェントを構築したら、実行して結果を確認できます:
public static void main(String[] args) {
var agent = Agent.builder("researcher")
.model("claude-haiku-4-5-20251001")
.tools(WebSearch.class)
.instructions("You are a helpful research assistant. "
+ "Always search first, then provide a thorough summary.")
.strategy("react")
.maxIterations(5)
.build();
// Run the agent
var result = agent.run("What is JamJet?");
System.out.println(result.output());
System.out.printf("Duration: %.2f ms%n", result.durationUs() / 1000.0);
System.out.printf("Tool calls: %d%n", result.toolCalls().size());
}export OPENAI_API_KEY=sk-...
mvn compile exec:java -Dexec.mainClass=com.example.MyAgentIRコンパイル:内部で起こること
エージェントが実行される前に、JamJetはそれを中間表現(IR)にコンパイルします — Java SDK、Python SDK、YAMLワークフロー間で共有される正規のグラフ形式です。IRを直接確認できます:
var ir = agent.compile();
System.out.println("workflow_id: " + ir.id());
System.out.println("start_node: " + ir.startNode());
System.out.println("nodes: " + ir.nodes().size());
System.out.println("edges: " + ir.edges().size());これは次のような出力をします:
workflow_id: researcher
start_node: react_start
nodes: 3
edges: 4なぜこれが重要なのか?なぜならIRこそがJamJetランタイムが実際に実行するものだからです。エージェントをJava、Python、YAMLのどれで記述しても、同じグラフ形式にコンパイルされます。これは以下を意味します:
- ポータビリティ — Javaで記述されたエージェントは、任意のJamJetランタイム上にデプロイ可能
- 検査 — 実行前に実行グラフを検証・可視化できる
- 耐久性 — ランタイムはノード境界でチェックポイントを作成するため、クラッシュ後に再開可能
送信前にIRを検証することもできます:
import dev.jamjet.ir.IrValidator;
IrValidator.validateOrThrow(ir);これにより構造上の問題(切断されたノード、欠落したエッジ、無効な状態スキーマ)を実行時ではなくコンパイル時に検出できます。
ワークフローを構築する
エージェントは、モデルが何をすべきかを決定するオープンエンドなタスクに最適です。しかし、多くの実世界のシステムでは決定論的なマルチステップパイプラインが必要です — データエンリッチメント、RAG、承認チェーン、ETLなど。これらにはワークフローを使用します。
重要な違い:エージェントではLLMが実行パスを決定します。ワークフローではあなたが実行パスを決定し、LLMはパイプラインの単なる1ステップにすぎません。
以下は2ステップのRAG(検索拡張生成)ワークフローです:
import dev.jamjet.workflow.Workflow;
import java.util.List;
// 型付き状態 — Javaレコード
record RagState(
String query,
List<String> retrievedDocs,
String answer) {}
var workflow = Workflow.<RagState>builder("rag-assistant")
.version("1.0.0")
.state(RagState.class)
// ステップ1:関連ドキュメントを取得
.step("retrieve", state -> {
var docs = searchKnowledgeBase(state.query());
return new RagState(state.query(), docs, null);
})
// ステップ2:コンテキストから回答を合成
.step("synthesize", state -> {
var context = String.join("\n\n", state.retrievedDocs());
var answer = callLlm(state.query(), context);
return new RagState(state.query(), state.retrievedDocs(), answer);
})
.build();ワークフロー内での状態の流れ
各ステップは現在のRagStateを受け取り、新しいRagStateを返します。状態は常にイミュータブルです — 既存のレコードを変更するのではなく、新しいものを構築します。これがワークフローを永続的にする理由です:「retrieve」と「synthesize」の間でランタイムがクラッシュした場合、永続化された正確な状態で最後に完了したチェックポイントから再実行されます。
このワークフローの実行方法は以下の通りです:
graph LR
A["Start"] --> B["retrieve"]
B --> C["synthesize"]
C --> D["End"]「retrieve」ステップがretrievedDocsを入力します。「synthesize」ステップがそれらを読み取り、最終的なanswerを生成します。各ステップはチェックポイント化されます — 「retrieve」完了後にプロセスがクラッシュした場合、ランタイムは取得処理を再実行することなく「synthesize」から再開します。
ワークフローの実行
import dev.jamjet.workflow.ExecutionResult;
import java.util.ArrayList;
var initialState = new RagState(
"How does JamJet handle concurrent tool calls?",
new ArrayList<>(),
null);
ExecutionResult<RagState> result = workflow.run(initialState);
System.out.println(result.state().answer());
System.out.printf("Ran %d steps in %.2f ms%n",
result.stepsExecuted(), result.totalDurationUs() / 1000.0);エージェント vs ワークフロー:使い分けの基準
| エージェント | ワークフロー | |
|---|---|---|
| 制御フロー | LLMが決定 | 開発者が決定 |
| 最適な用途 | オープンエンドなタスク、調査、チャット | パイプライン、RAG、承認フロー、ETL |
| 決定性 | 非決定的(モデル駆動) | 決定的(コード駆動) |
| 耐久性 | ストラテジー境界でチェックポイント | 各ステップでチェックポイント |
| ツール | モデルが呼び出すツールを選択 | ステップが明示的にツールを呼び出し |
両方を組み合わせることも可能です:ワークフローを外側のオーケストレーターとして使い、個別のステップ内にエージェントを組み込むことで、インテリジェントなサブステップを持つ決定論的パイプラインを構築できます。
次のステップ
これでエージェントとワークフローが動作するようになりました。さらに深く学ぶには以下をご覧ください:
- Java SDK リファレンス — API全体:条件分岐、評価、状態管理、ランタイムクライアント、アノテーションベースエージェント
- Spring Boot Starter ガイド — JamJetをSpring AI、Spring Security、Micrometerオブザーバビリティと統合
- LangChain4j統合 — JamJetをLangChain4jエージェントの永続的実行レイヤーとして使用
- コアコンセプト — エージェント、ノード、状態、耐久性の詳細
- GitHubのサンプル — 基本的なツールフロー、計画実行型エージェント、RAGアシスタントなど実行可能なサンプル
- Agentic AIパターン — ストラテジー選択、ツール設計、エージェントシステムの本番パターン
tip: すでにSpring Bootをお使いですか?Spring Boot Starter ガイドに進んでください — このクイックスタートの内容すべてを、ヘルスチェック、メトリクス、監査ログを備えた自動設定でラップします。