Spring Boot 3 + AI大モデル統合完全ガイド

技术架构本番環境の AI Agent

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│     │
│  └────────────────────────────────────────────────────────┘     │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

要点まとめ

  1. JavaはAIアプリケーションの最適な本番実行環境 — 並行性、セキュリティ、運用の三位一体、Pythonは訓練用、Javaは適用用
  2. Spring AIは2026年の推奨フレームワーク — Spring公式、宣言型設定、学習コストゼロ
  3. マルチターン対話には永続化メモリが必須 — Redis ChatMemory、シナリオに応じてウィンドウ/サマリー/ハイブリッド戦略を選択
  4. RAGはエンタープライズAIの礎 — ETL Pipeline + ベクトル検索 + コンテキスト注入、全て不可欠
  5. Function Callingがモデルと世界を繋ぐ — モデルがいつ呼ぶかを決定、あなたが何を呼ぶかを定義
  6. Agent = Planner + Executor + Evaluator — 受動的応答から能動的実行へ
  7. マルチモデルルーティングは本番標準 — GPT-4o + Qwen + DeepSeek、フォールバックチェーンで可用性確保
  8. 本番級デプロイは省略不可 — Docker + サーキットブレーカー + レート制限 + モニタリング、全て必須

Spring Boot 3 + AI大モデルは「JavaがAIを追いかける」のではなく、「AIがついに最も信頼できる本番実行環境を見つけた」こと。2026年、Java開発者のAI時代が到来した。

ブラウザローカルツールを無料で試す →

#Spring Boot#Spring AI#LangChain4j#AI大模型#RAG#Function Calling#Java Agent