Spring Boot 3 + AI大モデル統合完全ガイド
2026年、Java開発者はAI大モデルを取り込まなければならない
Pythonがモデルを訓練し、Javaがモデルを適用する——これが2026年のAIエンジニアリングの黄金分業。
一つの事実:Fortune 500企業の72%のバックエンドシステムがJVM上で動作しているが、AIアプリケーションのプロトタイプ開発の90%はPython。問題は——モデルの訓練が終わったら、誰が本番トラフィックを捌くのか? 答えはJava。
なぜJavaがAIアプリケーションの最適な実行環境なのか
| 次元 | Python | Java (Spring Boot) |
|---|---|---|
| 並行処理 | GIL制限、シングルスレッド | マルチスレッド + 仮想スレッド(Loom)、万級並行 |
| エンタープライズ統合 | 大量の接着コードが必要 | Springエコシステム、ワンストップ統合 |
| セキュリティ・コンプライアンス | 動的型付け、実行時エラー | 強い型付け + コンパイル時チェック |
| 運用成熟度 | Gunicorn/uWSGI | Spring Boot Actuator + K8s |
| チームスキル | AIリサーチャー | エンタープライズバックエンドエンジニア |
| デプロイ一貫性 | 依存関係地獄 | Fat JAR、一度ビルドすればどこでも実行 |
Java + AIの3つのシナリオ
┌──────────────────────────────────────────────────────────┐
│ Java + AI アプリケーション全景 │
├──────────────┬──────────────┬────────────────────────────┤
│ AI拡張 │ AIネイティブ │ AI Agent │
│ アプリケーション│ アプリケーション│ │
│ │ │ │
│ スマートCS │ ChatBot │ 自律型Agent │
│ ドキュメントQA│ Code Copilot │ Planner→Executor→Evaluator │
│ データ分析 │ RAGナレッジ │ マルチツールオーケストレーション│
│ スマート推薦 │ コンテンツ生成│ ワークフロー自動化 │
├──────────────┴──────────────┴────────────────────────────┤
│ Spring Boot 3 + Spring AI │
│ 統一プログラミングモデル · 宣言型設定 · 本番級信頼性 │
└──────────────────────────────────────────────────────────┘
Spring AI vs LangChain4j:フレームワーク選定
2026年のJava AIエコシステムにおける2大主流フレームワーク、それぞれ異なる重点。
コアポジショニング比較
| 次元 | Spring AI | LangChain4j |
|---|---|---|
| 設計哲学 | Springスタイル、宣言型 | LangChain移植、チェーンベース |
| コアチーム | Spring公式(VMware) | 独立オープンソースコミュニティ |
| 設定方法 | application.yml + Bean | Builderパターン + コード設定 |
| モデルサポート | OpenAI/Azure/Ollama/通義 | OpenAI/Azure/Ollama/通義/智譜 |
| ベクターストア | PGVector/Chroma/Milvus | PGVector/Chroma/Milvus/Weaviate |
| RAGサポート | 内蔵ETL Pipeline | 内蔵RAGモジュール |
| Function Calling | Spring Bean自動登録 | 手動@Toolメソッド登録 |
| Spring統合 | ネイティブ、ゼロ設定 | spring-boot-starterが必要 |
| ストリーミング応答 | Flux | TokenStream |
| コミュニティ活発度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 学習曲線 | Spring開発者にゼロハードル | LangChain概念の学習が必要 |
| バージョン(2026) | 1.0.0 GA | 1.0.0 GA |
コードスタイル比較
// Spring AI — 宣言型、Springスタイル
@Configuration
public class AiConfig {
@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultSystem("あなたはプロのJava技術コンサルタントです")
.defaultAdvisors(new SimpleLoggerAdvisor())
.build();
}
}
// 呼び出し:1行で完了
String response = chatClient.prompt()
.user("Spring Boot 3の仮想スレッドについて説明して")
.call()
.content();
// LangChain4j — チェーンベース、Builderスタイル
ChatLanguageModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o")
.temperature(0.7)
.build();
// 呼び出し:明示的構築
Response<AiMessage> response = model.generate(
SystemMessage.from("あなたはプロのJava技術コンサルタントです"),
UserMessage.from("Spring Boot 3の仮想スレッドについて説明して")
);
選定推奨
| シナリオ | 推奨 | 理由 |
|---|---|---|
| 既存SpringプロジェクトにAI追加 | Spring AI | 学習コストゼロ、ネイティブ統合 |
| 新規AIネイティブプロジェクト | どちらでも | チームの技術スタック好みによる |
| 複雑なAgentオーケストレーション | LangChain4j | Chain/Agent抽象がより成熟 |
| エンタープライズコンプライアンス要件 | Spring AI | Actuator + Security統合 |
| 高速プロトタイピング | LangChain4j | Builderパターンがより直感的 |
本記事はSpring AIを主軸とする。Spring Boot 3との統合が最も自然であり、2026年のエンタープライズJava AIアプリケーションの主流選択だからだ。
Spring Boot 3 + Spring AIクイックスタート
ステップ1:Maven依存関係
<?xml version="1.0" encoding="UTF-8"?>
<project>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.0</version>
</parent>
<properties>
<java.version>21</java.version>
<spring-ai.version>1.0.0</spring-ai.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-chat-memory-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
ステップ2:YAML設定
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
base-url: https://api.openai.com
chat:
options:
model: gpt-4o
temperature: 0.7
max-tokens: 4096
top-p: 0.9
embedding:
options:
model: text-embedding-3-large
vectorstore:
pgvector:
index-type: HNSW
dimensions: 3072
distance-type: COSINE
chat:
memory:
redis:
host: localhost
port: 6379
ttl: 3600
server:
port: 8080
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
ステップ3:ChatController
@RestController
@RequestMapping("/api/chat")
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@PostMapping
public String chat(@RequestBody ChatRequest request) {
return chatClient.prompt()
.user(request.message())
.call()
.content();
}
@PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chatStream(@RequestBody ChatRequest request) {
return chatClient.prompt()
.user(request.message())
.stream()
.content();
}
@PostMapping("/system")
public String chatWithSystem(
@RequestBody ChatRequest request,
@RequestParam String role) {
return chatClient.prompt()
.system(role)
.user(request.message())
.call()
.content();
}
public record ChatRequest(String message) {}
}
ステップ4:起動と検証
export OPENAI_API_KEY=sk-xxxxx
mvn spring-boot:run
curl -X POST http://localhost:8080/api/chat \
-H "Content-Type: application/json" \
-d '{"message": "Javaでクイックソートを書いて"}'
curl -X POST http://localhost:8080/api/chat/stream \
-H "Content-Type: application/json" \
-d '{"message": "JVMメモリモデルについて説明して"}'
┌──────────────────────────────────────────────────────┐
│ Spring Boot 3 + Spring AI 起動フロー │
├──────────────────────────────────────────────────────┤
│ │
│ main() → @SpringBootApplication │
│ ├── 自動設定 spring-ai-auto-configuration │
│ ├── OpenAiChatModel Bean登録 │
│ ├── OpenAiEmbeddingModel Bean登録 │
│ ├── PgVectorStore Bean登録 │
│ ├── ChatClient Bean登録 │
│ └── ChatMemory Bean登録 (Redis) │
│ │
│ ChatClient.prompt() │
│ ├── .user() → UserMessage構築 │
│ ├── .system() → SystemMessage構築 │
│ ├── .advisors() → Advisorチェーン注入 │
│ ├── .call() → 同期呼び出し → String │
│ └── .stream() → ストリーミング → Flux<String> │
│ │
└──────────────────────────────────────────────────────┘
エンタープライズChat:マルチターン対話とRedis永続化メモリ
メモリのないAIは金魚のよう——毎回の対話が新しく、コンテキストを理解できない。エンタープライズChatアプリには永続化メモリが不可欠。
対話メモリアーキテクチャ
┌──────────────────────────────────────────────────────────┐
│ マルチターン対話メモリアーキテクチャ │
├──────────────────────────────────────────────────────────┤
│ │
│ ユーザーメッセージ ──→ ChatMemoryAdvisor ──→ ChatClient │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ Redis │ │ OpenAI │ │
│ │ ChatStore│ │ API │ │
│ └──────────┘ └──────────┘ │
│ ▲ │ │
│ │ ▼ │
│ 書き戻し ←── AI応答 ←── モデル推論 │
│ │
│ メモリ構造: │
│ conversation:{userId} → [ │
│ {role: "system", content: "..."}, │
│ {role: "user", content: "..."}, │
│ {role: "assistant", content: "..."}, │
│ ... │
│ ] │
└──────────────────────────────────────────────────────────┘
ChatMemory設定
@Configuration
public class ChatMemoryConfig {
@Bean
public ChatClient chatClientWithMemory(
ChatClient.Builder builder,
ChatMemoryRepository memoryRepository) {
ChatMemory chatMemory = new RedisChatMemory(memoryRepository);
return builder
.defaultSystem("""
あなたはToolKitウェブサイトのAIアシスタントで、
JavaとSpring技術に特化しています。
プロフェッショナルで正確な回答とコード例を提供してください。
不明な場合は正直に伝えてください。
""")
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory)
.conversationIdExpression("userId")
.maxMessages(20)
.build(),
new SimpleLoggerAdvisor()
)
.build();
}
}
メモリ付きChatController
@RestController
@RequestMapping("/api/chat")
public class EnterpriseChatController {
private final ChatClient chatClient;
public EnterpriseChatController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@PostMapping("/conversation")
public ChatResponse conversation(
@RequestBody ConversationRequest request,
@RequestHeader("X-User-Id") String userId) {
String content = chatClient.prompt()
.user(request.message())
.advisors(a -> a.param("userId", userId))
.call()
.content();
return new ChatResponse(content, userId, Instant.now());
}
@PostMapping("/conversation/stream")
public Flux<ServerSentEvent<String>> conversationStream(
@RequestBody ConversationRequest request,
@RequestHeader("X-User-Id") String userId) {
return chatClient.prompt()
.user(request.message())
.advisors(a -> a.param("userId", userId))
.stream()
.content()
.map(chunk -> ServerSentEvent.<String>builder()
.data(chunk).build());
}
@DeleteMapping("/conversation/{userId}")
public ResponseEntity<Void> clearMemory(@PathVariable String userId) {
return ResponseEntity.noContent().build();
}
public record ConversationRequest(String message) {}
public record ChatResponse(String content, String userId, Instant timestamp) {}
}
Redis メモリストア実装
@Component
public class RedisChatMemoryRepository implements ChatMemoryRepository {
private final StringRedisTemplate redisTemplate;
private final ObjectMapper objectMapper;
private static final String KEY_PREFIX = "chat:memory:";
private static final Duration TTL = Duration.ofHours(2);
public RedisChatMemoryRepository(
StringRedisTemplate redisTemplate,
ObjectMapper objectMapper) {
this.redisTemplate = redisTemplate;
this.objectMapper = objectMapper;
}
@Override
public List<Message> findByConversationId(String conversationId) {
String json = redisTemplate.opsForValue().get(KEY_PREFIX + conversationId);
if (json == null) return new ArrayList<>();
try {
return objectMapper.readValue(json,
new TypeReference<List<Message>>() {});
} catch (JsonProcessingException e) {
throw new ChatMemoryException("Failed to read memory", e);
}
}
@Override
public void saveAll(String conversationId, List<Message> messages) {
try {
String json = objectMapper.writeValueAsString(messages);
redisTemplate.opsForValue()
.set(KEY_PREFIX + conversationId, json, TTL);
} catch (JsonProcessingException e) {
throw new ChatMemoryException("Failed to save memory", e);
}
}
}
メモリ戦略比較
| 戦略 | 実装 | メリット | デメリット | ユースケース |
|---|---|---|---|---|
| ウィンドウメモリ | 直近Nターンを保持 | シンプル、トークン制御可能 | 早期コンテキスト喪失 | 一般チャット |
| サマリーメモリ | 古い対話を要約に圧縮 | グローバルな意味を保持 | 詳細が失われる可能性 | 長対話 |
| ハイブリッドメモリ | 要約 + 直近Nターン | グローバルと詳細の両立 | 実装が複雑 | エンタープライズCS |
| ベクターメモリ | 意味的に関連するチャンクを検索 | 無限コンテキスト | 検索レイテンシ | ナレッジ集約型 |
RAG実践:ドキュメントETL Pipeline + ベクトル検索 + コンテキスト注入
RAG(検索拡張生成)は、大モデルにエンタープライズプライベートナレッジを与えるコア技術。
RAG完全アーキテクチャ
┌──────────────────────────────────────────────────────────────┐
│ RAG 完全 Pipeline │
├──────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌──────────┐ ┌──────────┐ │
│ │ ドキュメント│───→│ ETL │───→│ ベクター │ │
│ │ ソース │ │ Pipeline │ │ ストア │ │
│ │ PDF/MD │ │ │ │ PGVector │ │
│ └─────────┘ └──────────┘ └──────────┘ │
│ │ ▲ │
│ ▼ │ │
│ ┌──────────────┐ │ │
│ │ Embedding │──────┘ │
│ │ text-embed-3│ │
│ └──────────────┘ │
│ │
│ ┌─────────┐ ┌──────────┐ ┌──────────┐ │
│ │ ユーザー │───→│ 検索 │───→│ 生成 │ │
│ │ 質問 │ │ 類似度TopK│ │ コンテキスト│ │
│ └─────────┘ └──────────┘ └──────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ RRF再ランク │ │ 回答+引用 │ │
│ └──────────────┘ └──────────────┘ │
└──────────────────────────────────────────────────────────────┘
ドキュメントETL Pipeline
@Service
public class DocumentEtlService {
private final VectorStore vectorStore;
private final DocumentReader documentReader;
private final DocumentTransformer documentTransformer;
public DocumentEtlService(VectorStore vectorStore,
DocumentReader documentReader,
DocumentTransformer documentTransformer) {
this.vectorStore = vectorStore;
this.documentReader = documentReader;
this.documentTransformer = documentTransformer;
}
public void ingestDocuments(String directoryPath) {
List<Document> documents = documentReader.read(directoryPath);
List<Document> chunks = documentTransformer.apply(documents);
vectorStore.add(chunks);
log.info("Ingested {} documents, {} chunks into vector store",
documents.size(), chunks.size());
}
}
ドキュメントリーダー
@Component
public class SmartDocumentReader implements DocumentReader {
@Override
public List<Document> read(String path) {
return switch (getFileExtension(path)) {
case "pdf" -> readPdf(path);
case "md" -> readMarkdown(path);
case "docx" -> readDocx(path);
default -> throw new UnsupportedDocumentException(path);
};
}
private List<Document> readPdf(String path) {
var reader = new PagePdfDocumentReader(path,
PdfDocumentReaderConfig.builder().pagesPerDocument(1).build());
return reader.get();
}
private List<Document> readMarkdown(String path) {
var reader = new MarkdownDocumentReader(path,
MarkdownDocumentReaderConfig.builder().includeCodeBlocks(true).build());
return reader.get();
}
}
ドキュメントチャンキングとメタデータ強化
@Component
public class SmartDocumentTransformer implements DocumentTransformer {
private final TokenTextSplitter splitter;
@Override
public List<Document> apply(List<Document> documents) {
return documents.stream()
.flatMap(doc -> splitter.split(doc).stream())
.peek(this::enrichMetadata)
.toList();
}
private void enrichMetadata(Document chunk) {
Map<String, Object> metadata = chunk.getMetadata();
metadata.put("chunkId", UUID.randomUUID().toString());
metadata.put("createdAt", Instant.now().toString());
metadata.put("tokenCount", estimateTokenCount(chunk.getContent()));
metadata.put("version", "2026-Q2");
}
private int estimateTokenCount(String text) {
return text.length() / 4;
}
}
RAG検索サービス
@Service
public class RagService {
private final ChatClient chatClient;
private final VectorStore vectorStore;
public RagService(ChatClient chatClient, VectorStore vectorStore) {
this.chatClient = chatClient;
this.vectorStore = vectorStore;
}
public String query(String question) {
List<Document> relevantDocs = vectorStore.similaritySearch(
SearchRequest.builder()
.query(question).topK(5).similarityThreshold(0.7).build());
String context = relevantDocs.stream()
.map(doc -> "[出典: %s]\n%s".formatted(
doc.getMetadata().get("source"), doc.getContent()))
.collect(Collectors.joining("\n---\n"));
return chatClient.prompt()
.system("""
以下の参考ドキュメントに基づいてユーザーの質問に回答してください。
関連情報がない場合は「既存ドキュメントでは回答できません」と述べてください。
回答には引用元を明記してください。
参考ドキュメント:{context}
""")
.user(question).call().content();
}
}
RAGパフォーマンス最適化チェックリスト
| 最適化項目 | 方法 | 効果 |
|---|---|---|
| チャンクサイズ | 512-1024トークン、オーバーラップ64-128 | 意味完全性と検索精度のバランス |
| クエリ拡張 | HyDE + 同義語拡張 | リコール15-30%向上 |
| ハイブリッド検索 | ベクトル + BM25キーワード、RRF融合 | 精密一致 + 意味一致の補完 |
| 再ランク付け | Cross-Encoder / Cohere Rerank | Top5精度20%向上 |
| メタデータフィルタリング | 部門/バージョン/日付でフィルタ | 無関係ドキュメントのノイズ削減 |
| キャッシュ | 類似クエリの結果キャッシュ | 重複クエリレイテンシ90%削減 |
Function Calling:大モデルにJavaメソッドを呼び出させる
Function Callingは大モデルと外部世界を繋ぐ橋——モデルが「いつ呼ぶか」を決定し、あなたが「何を呼ぶか」を定義する。
Function Callingワークフロー
┌──────────────────────────────────────────────────────────┐
│ Function Calling ワークフロー │
├──────────────────────────────────────────────────────────┤
│ │
│ ユーザー: "注文ORD-20260601の配送状況を確認して" │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │ 大モデル │ → 意図分析 → 関数選択: queryLogistics │
│ └──────────┘ 引数: {orderId: "ORD-20260601"} │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │ Java │ → queryLogistics("ORD-20260601")呼び出し │
│ │ メソッド │ 戻り値: {status: "配送中", ...} │
│ └──────────┘ │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │ 大モデル │ → 関数結果から自然言語を生成 │
│ └──────────┘ │
│ │
│ "注文ORD-20260601は現在配送中で、明日到着予定です..." │
│ │
└──────────────────────────────────────────────────────────┘
注文照会Function
@Configuration
public class OrderFunctions {
@Bean
@Description("注文IDで注文詳細を照会(商品、金額、ステータス含む)")
public Function<OrderQuery, OrderInfo> queryOrder(OrderService orderService) {
return query -> orderService.getOrderInfo(query.orderId());
}
@Bean
@Description("注文IDで配送ステータスを照会(現在地と到着予定含む)")
public Function<LogisticsQuery, LogisticsInfo> queryLogistics(
LogisticsService logisticsService) {
return query -> logisticsService.getLogisticsInfo(query.orderId());
}
@Bean
@Description("注文の返金申請を提出(注文IDと理由が必要)")
public Function<RefundRequest, RefundResult> requestRefund(
RefundService refundService) {
return request -> refundService.processRefund(
request.orderId(), request.reason());
}
public record OrderQuery(String orderId) {}
public record LogisticsQuery(String orderId) {}
public record RefundRequest(String orderId, String reason) {}
public record OrderInfo(String orderId, String productName,
BigDecimal amount, String status, LocalDateTime orderTime) {}
public record LogisticsInfo(String orderId, String status,
String currentLocation, LocalDateTime estimatedArrival) {}
public record RefundResult(String refundId, String orderId,
BigDecimal refundAmount, String status) {}
}
Function Calling Controller
@RestController
@RequestMapping("/api/assistant")
public class AiAssistantController {
private final ChatClient chatClient;
public AiAssistantController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@PostMapping
public String assist(@RequestBody AssistRequest request) {
return chatClient.prompt()
.system("""
あなたはECスマートカスタマーサポートアシスタントです。
1. 注文詳細の照会
2. 配送ステータスの照会
3. 返金申請の処理
ユーザーの質問に応じて適切な操作を選択してください。
""")
.user(request.message())
.functions("queryOrder", "queryLogistics", "requestRefund")
.call()
.content();
}
public record AssistRequest(String message) {}
}
対話例
ユーザー: "注文ORD-20260601はどこ?"
AI内部フロー:
1. 意図認識 → 配送照会
2. queryLogistics({orderId: "ORD-20260601"})呼び出し
3. 戻り値: {status: "配送中", currentLocation: "杭州ハブ", ...}
4. 回答生成:
AI: "注文ORD-20260601は現在配送中で、杭州ハブに到着しています。2026年6月7日お届け予定です。"
ChatからAgentへ:Java版AI Agentの構築
Chatは「聞かれて答える」、Agentは「目標を設定されれば自律的に達成する」。
Agentコアループ
┌──────────────────────────────────────────────────────────┐
│ AI Agent コアループ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Planner │────→│ Executor │────→│Evaluator │ │
│ │ プランナー│ │ 実行器 │ │ 評価器 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ ▲ │ │
│ │ 不満足 │ │
│ └───────────────────────────────────┘ │
│ │
│ Planner: 目標を実行可能なステップリストに分解 │
│ Executor: ステップごとに実行、各ステップでツール呼び出し可能│
│ Evaluator: 結果が目標を満たすか確認、リトライを決定 │
│ │
│ 終了条件: 評価通過 / 最大リトライ回数 / ユーザー確認 │
└──────────────────────────────────────────────────────────┘
Agentコア実装
@Service
public class AiAgentService {
private final ChatClient chatClient;
private final List<AgentTool> tools;
public AiAgentService(ChatClient chatClient, List<AgentTool> tools) {
this.chatClient = chatClient;
this.tools = tools;
}
public AgentResult execute(String goal) {
int maxIterations = 5;
Plan plan = plan(goal);
List<StepResult> results = new ArrayList<>();
for (Step step : plan.steps()) {
results.add(executeStep(step, results));
}
Evaluation evaluation = evaluate(goal, results);
int iteration = 1;
while (!evaluation.satisfied() && iteration < maxIterations) {
plan = replan(goal, results, evaluation.feedback());
results.clear();
for (Step step : plan.steps()) {
results.add(executeStep(step, results));
}
evaluation = evaluate(goal, results);
iteration++;
}
return new AgentResult(results, evaluation, iteration);
}
private Plan plan(String goal) {
String planJson = chatClient.prompt()
.system("目標を実行可能なステップに分解してください。" +
"利用可能ツール: %s. JSON配列で出力。".formatted(getToolDescriptions()))
.user("目標: " + goal)
.call().content();
return parsePlan(planJson);
}
private StepResult executeStep(Step step, List<StepResult> previousResults) {
AgentTool tool = findTool(step.tool());
Object result = tool.execute(step.params());
return new StepResult(step, result, Instant.now());
}
private Evaluation evaluate(String goal, List<StepResult> results) {
String evalJson = chatClient.prompt()
.system("実行結果が目標を満たすか評価してください。" +
"出力: {\"satisfied\":boolean,\"score\":0-100,\"feedback\":\"...\"}")
.user("目標: %s\n結果: %s".formatted(goal, formatResults(results)))
.call().content();
return parseEvaluation(evalJson);
}
public record Plan(List<Step> steps) {}
public record Step(String action, String tool, Map<String, Object> params) {}
public record StepResult(Step step, Object result, Instant timestamp) {}
public record Evaluation(boolean satisfied, int score, String feedback) {}
public record AgentResult(List<StepResult> results, Evaluation evaluation, int iterations) {}
}
Agentツール登録
public interface AgentTool {
String name();
String description();
Object execute(Map<String, Object> params);
}
@Component
public class DatabaseQueryTool implements AgentTool {
private final JdbcTemplate jdbcTemplate;
@Override
public String name() { return "database_query"; }
@Override
public String description() { return "SQLクエリを実行、SELECTのみ"; }
@Override
public Object execute(Map<String, Object> params) {
String sql = (String) params.get("sql");
if (!sql.trim().toUpperCase().startsWith("SELECT"))
throw new SecurityException("Only SELECT queries are allowed");
return jdbcTemplate.queryForList(sql);
}
}
@Component
public class HttpApiTool implements AgentTool {
private final RestClient restClient;
@Override
public String name() { return "http_api"; }
@Override
public String description() { return "外部HTTP APIを呼び出し"; }
@Override
public Object execute(Map<String, Object> params) {
String url = (String) params.get("url");
return restClient.get().uri(url).retrieve().body(String.class);
}
}
マルチモデルルーティングとフォールバック戦略
本番環境では全ての卵を一つのカゴに入れない——GPT-4oがダウンしたら、Qwenが代わりに対応。
マルチモデルルーティングアーキテクチャ
┌──────────────────────────────────────────────────────────────┐
│ マルチモデルルーティングアーキテクチャ │
├──────────────────────────────────────────────────────────────┤
│ │
│ リクエスト ──→ ModelRouter ──→ ┌─────────┐ │
│ │ ルーティング│ │
│ │ 戦略 │ │
│ └────┬────┘ │
│ ┌──────────────────┼──────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ GPT-4o │ │ Qwen │ │ DeepSeek │ │
│ │ (高品質) │ │ (中国産) │ │ (高コスパ)│ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ フォールバックチェーン │ │
│ │ GPT-4o → Qwen → DeepSeek → ローカルOllama│ │
│ └─────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
ルーティング戦略実装
@Service
public class ModelRouterService {
private final Map<String, ChatModel> models;
private final CircuitBreakerRegistry breakerRegistry;
public ModelRouterService(
@Qualifier("openai") ChatModel openai,
@Qualifier("tongyi") ChatModel tongyi,
@Qualifier("deepseek") ChatModel deepseek,
@Qualifier("ollama") ChatModel ollama,
CircuitBreakerRegistry breakerRegistry) {
this.models = Map.of(
"gpt-4o", openai, "qwen-max", tongyi,
"deepseek-v3", deepseek, "llama3", ollama);
this.breakerRegistry = breakerRegistry;
}
public String chat(String prompt, RoutingStrategy strategy) {
List<String> modelChain = strategy.resolveChain(prompt);
for (String modelName : modelChain) {
try {
CircuitBreaker breaker = breakerRegistry.circuitBreaker(modelName);
ChatModel model = models.get(modelName);
String result = breaker.executeSupplier(() ->
model.call(new Prompt(prompt))
.getResult().getOutput().getText());
log.info("Model {} succeeded", modelName);
return result;
} catch (CallNotPermittedException e) {
log.warn("Model {} circuit breaker open, trying next", modelName);
} catch (Exception e) {
log.error("Model {} failed: {}", modelName, e.getMessage());
}
}
throw new AllModelsFailedException("All models in chain failed");
}
}
スマートルーティング戦略
@Component
public class SmartRoutingStrategy implements RoutingStrategy {
@Override
public List<String> resolveChain(String prompt) {
Complexity complexity = analyzeComplexity(prompt);
return switch (complexity) {
case HIGH -> List.of("gpt-4o", "qwen-max", "deepseek-v3", "llama3");
case MEDIUM -> List.of("qwen-max", "deepseek-v3", "gpt-4o");
case LOW -> List.of("deepseek-v3", "qwen-max", "llama3");
};
}
private Complexity analyzeComplexity(String prompt) {
if (prompt.length() > 500 || containsCodeRequest(prompt)) return Complexity.HIGH;
if (prompt.length() > 100 || containsReasoning(prompt)) return Complexity.MEDIUM;
return Complexity.LOW;
}
enum Complexity { HIGH, MEDIUM, LOW }
}
マルチモデルコスト比較
| モデル | 入力価格(/1M tokens) | 出力価格(/1M tokens) | 品質 | レイテンシ | ユースケース |
|---|---|---|---|---|---|
| GPT-4o | $2.50 | $10.00 | ⭐⭐⭐⭐⭐ | 1-3s | 複雑な推論、コード生成 |
| Qwen-Max | ¥8.00 | ¥32.00 | ⭐⭐⭐⭐ | 0.5-2s | 中国語シナリオ、コンプライアンス |
| DeepSeek-V3 | ¥1.00 | ¥2.00 | ⭐⭐⭐⭐ | 0.3-1s | 日常チャット、高並行 |
| Llama3(ローカル) | 無料 | 無料 | ⭐⭐⭐ | 2-5s | プライバシー重視、オフライン |
本番級デプロイとパフォーマンスチューニング
DemoからProductionまでの差は、これらのディテールにある。
Docker デプロイ
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY target/app.jar app.jar
ENV JAVA_OPTS="-XX:+UseZGC \
-XX:+ZGenerational \
-XX:MaxRAMPercentage=75.0 \
-XX:+EnableVirtualThreads \
-Dspring.profiles.active=prod"
EXPOSE 8080
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
services:
app:
build: .
ports: ["8080:8080"]
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY}
depends_on:
redis: { condition: service_healthy }
postgres: { condition: service_healthy }
deploy:
resources:
limits: { memory: 1G, cpus: "2.0" }
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/actuator/health"]
interval: 10s
redis:
image: redis:7-alpine
ports: ["6379:6379"]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
postgres:
image: pgvector/pgvector:pg16
ports: ["5432:5432"]
environment:
POSTGRES_DB: ai_vectors
POSTGRES_PASSWORD: ${PG_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
レート制限
@Configuration
public class RateLimitConfig {
@Bean
public RateLimiter aiApiRateLimiter() {
return RateLimiter.builder()
.name("ai-api")
.limitForPeriod(50)
.limitRefreshPeriod(Duration.ofSeconds(1))
.timeoutDuration(Duration.ofSeconds(5))
.build();
}
}
モニタリングメトリクス
@Component
public class AiMetrics {
private final MeterRegistry registry;
public AiMetrics(MeterRegistry registry) { this.registry = registry; }
public void recordApiCall(String model, boolean success, long latencyMs) {
registry.counter("ai.api.calls",
"model", model, "status", success ? "success" : "failure").increment();
registry.timer("ai.api.latency", "model", model)
.record(latencyMs, TimeUnit.MILLISECONDS);
}
public void recordTokenUsage(String model, int promptTokens, int completionTokens) {
registry.counter("ai.tokens.prompt", "model", model).increment(promptTokens);
registry.counter("ai.tokens.completion", "model", model).increment(completionTokens);
}
}
本番級パフォーマンスチェックリスト
| カテゴリ | チェック項目 | 目標 | ツール |
|---|---|---|---|
| レイテンシ | P95 API応答時間 | < 3s | Actuator + Grafana |
| レイテンシ | ストリーミング初トークン時間 | < 500ms | カスタムMetrics |
| スループット | 並行リクエスト処理能力 | > 100 QPS | JMeter/k6 |
| 信頼性 | サーキットブレーカー発動率 | < 5% | Resilience4j |
| 信頼性 | フォールバック成功率 | > 99% | カスタムMetrics |
| コスト | 日次トークン消費量 | < 予算 | トークンカウンター |
| コスト | モデルルーティング命中率 | 高複雑度→GPT-4o >80% | ルーティングMetrics |
| セキュリティ | API Keyローテーション | 90日ごと | Vault/KMS |
| セキュリティ | 機密情報フィルタリング | 100%傍受 | 入出力Guard |
| 運用 | ゼロダウンタイムローリングデプロイ | 0エラー | K8s ReadinessProbe |
| 運用 | 設定ホットリロード | 再起動不要 | Spring Cloud Config |
まとめとアーキテクチャ全景図
全景アーキテクチャ
┌──────────────────────────────────────────────────────────────────┐
│ Spring Boot 3 + AI 全景アーキテクチャ │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ API Gateway / ロードバランサー │ │
│ └──────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────▼─────────────────────────────────┐ │
│ │ Spring Boot 3 アプリケーション │ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Chat │ │ RAG │ │ Agent │ │ │
│ │ │ Controller│ │ Service │ │ Service │ │ │
│ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │
│ │ │ │ │ │ │
│ │ ┌────▼─────────────▼─────────────▼─────┐ │ │
│ │ │ ChatClient (Spring AI) │ │ │
│ │ │ ┌─────────────────────────────┐ │ │ │
│ │ │ │ Advisor Chain │ │ │ │
│ │ │ │ Memory → RAG → Function │ │ │ │
│ │ │ │ → Guard → Logging │ │ │ │
│ │ │ └─────────────────────────────┘ │ │ │
│ │ └──────────────┬──────────────────────┘ │ │
│ │ │ │ │
│ │ ┌──────────────▼──────────────────────┐ │ │
│ │ │ ModelRouter │ │ │
│ │ │ GPT-4o │ Qwen │ DeepSeek │ Ollama │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────▼─────────────────────────────────┐ │
│ │ インフラストラクチャ層 │ │
│ │ Redis(メモリ) │ PGVector(ベクトル) │ MySQL(業務) │ K8s│ │
│ └────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘
要点まとめ
- JavaはAIアプリケーションの最適な本番実行環境 — 並行性、セキュリティ、運用の三位一体、Pythonは訓練用、Javaは適用用
- Spring AIは2026年の推奨フレームワーク — Spring公式、宣言型設定、学習コストゼロ
- マルチターン対話には永続化メモリが必須 — Redis ChatMemory、シナリオに応じてウィンドウ/サマリー/ハイブリッド戦略を選択
- RAGはエンタープライズAIの礎 — ETL Pipeline + ベクトル検索 + コンテキスト注入、全て不可欠
- Function Callingがモデルと世界を繋ぐ — モデルがいつ呼ぶかを決定、あなたが何を呼ぶかを定義
- Agent = Planner + Executor + Evaluator — 受動的応答から能動的実行へ
- マルチモデルルーティングは本番標準 — GPT-4o + Qwen + DeepSeek、フォールバックチェーンで可用性確保
- 本番級デプロイは省略不可 — Docker + サーキットブレーカー + レート制限 + モニタリング、全て必須
Spring Boot 3 + AI大モデルは「JavaがAIを追いかける」のではなく、「AIがついに最も信頼できる本番実行環境を見つけた」こと。2026年、Java開発者のAI時代が到来した。
ブラウザローカルツールを無料で試す →
#Spring Boot#Spring AI#LangChain4j#AI大模型#RAG#Function Calling#Java Agent