Spring Boot 3 + AI大模型整合全攻略

技术架构AI Agent 生產實務

2026年,Java開發者必須擁抱AI大模型

Python訓練模型,Java應用模型——這是2026年AI工程化的黃金分工。

一個事實:全球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的三大場景

┌──────────────────────────────────────────────────────────┐
│                  Java + AI 應用全景                        │
├──────────────┬──────────────┬────────────────────────────┤
│   AI增強應用  │   AI原生應用  │      AI Agent              │
│              │              │                            │
│ 智慧客服     │ ChatBot      │ 自主決策Agent              │
│ 文件問答     │ Code Copilot │ Planner→Executor→Evaluator │
│ 資料分析助手 │ RAG知識庫    │ 多工具編排Agent            │
│ 智慧推薦     │ 內容生成     │ 工作流自動化Agent          │
├──────────────┴──────────────┴────────────────────────────┤
│              Spring Boot 3 + Spring AI                   │
│         統一程式設計模型 · 宣告式設定 · 生產級可靠性          │
└──────────────────────────────────────────────────────────┘

Spring AI vs LangChain4j:框架選型

2026年Java AI生態兩大主流框架,各有側重。

核心定位對比

維度 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();
    }
}

// 呼叫:一行搞定
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快速入門

第一步: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>
        <!-- Spring AI OpenAI -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
        </dependency>

        <!-- Spring AI Vector Store (PGVector) -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId>
        </dependency>

        <!-- Redis for Chat Memory -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-chat-memory-redis</artifactId>
        </dependency>

        <!-- Spring Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Spring Actuator -->
        <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>

第二步: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

第三步: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) {}
}

第四步:啟動與驗證

# 設定API Key
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回應 ←── 模型推理              │
│                                                          │
│  Memory結構:                                             │
│  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("""
                你是工具庫網站的AI助手,專注於Java和Spring技術。
                回答要專業、準確、有程式碼範例。
                如果不確定,請誠實說明。
                """)
            .defaultAdvisors(
                MessageChatMemoryAdvisor.builder(chatMemory)
                    .conversationIdExpression("userId")
                    .maxMessages(20)
                    .build(),
                new SimpleLoggerAdvisor()
            )
            .build();
    }
}

ChatController with Memory

@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輪 簡單,token可控 遺失早期上下文 通用對話
摘要記憶 舊對話壓縮為摘要 保留全局語意 摘要可能遺失細節 長對話
混合記憶 摘要 + 最近N輪 兼顧全局與細節 實作複雜 企業客服
向量記憶 按語意檢索相關片段 無限上下文 檢索延遲 知識密集型

RAG實戰:文件ETL Pipeline + 向量檢索 + 上下文注入

RAG(檢索增強生成)是讓大模型擁有企業私有知識的核心技術。

RAG完整架構

┌──────────────────────────────────────────────────────────────┐
│                      RAG 完整 Pipeline                        │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌─────────┐    ┌──────────┐    ┌──────────┐               │
│  │  文件源  │───→│  ETL     │───→│  向量庫   │               │
│  │ PDF/MD  │    │ Pipeline │    │ PGVector │               │
│  │ DOCX    │    │          │    │          │               │
│  └─────────┘    └──────────┘    └──────────┘               │
│                      │              ▲                       │
│                      ▼              │                       │
│              ┌──────────────┐      │                       │
│              │  Embedding   │──────┘                       │
│              │  text-embed-3│                               │
│              └──────────────┘                               │
│                                                              │
│  ┌─────────┐    ┌──────────┐    ┌──────────┐               │
│  │ 使用者提問│───→│  檢索    │───→│  生成    │               │
│  │          │    │ 相似度TopK│    │ 上下文+Q │               │
│  └─────────┘    └──────────┘    └──────────┘               │
│                      │              │                       │
│                      ▼              ▼                       │
│              ┌──────────────┐  ┌──────────────┐           │
│              │  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 {

    private final TikaDocumentReader tikaReader;

    @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]
                %s
                """.formatted(
                    doc.getMetadata().get("source"),
                    doc.getContent()))
            .collect(Collectors.joining("\n---\n"));

        return chatClient.prompt()
            .system("""
                基於以下參考文件回答使用者問題。
                如果文件中沒有相關資訊,請說明"根據現有文件無法回答"。
                回答時請標注引用來源。

                參考文件:
                {context}
                """)
            .user(question)
            .call()
            .content();
    }
}

RAG檢索增強Advisor

@Component
public class RetrievalAugmentationAdvisor implements CallAdvisor {

    private final VectorStore vectorStore;
    private final QueryExpander queryExpander;

    @Override
    public AdvisedResponse adviseCall(AdvisedRequest request, Map<String, Object> context) {
        String query = request.userText();
        List<String> expandedQueries = queryExpander.expand(query);

        List<Document> allDocs = expandedQueries.stream()
            .flatMap(q -> vectorStore.similaritySearch(
                SearchRequest.builder()
                    .query(q)
                    .topK(3)
                    .build()
            ).stream())
            .toList();

        List<Document> ranked = rerank(allDocs, query, 5);

        String augmentedUserText = """
            參考文件:
            %s

            使用者問題:%s
            """.formatted(formatDocuments(ranked), query);

        return new AdvisedResponse(
            request.mutate().userText(augmentedUserText).build(),
            context
        );
    }
}

RAG效能最佳化清單

最佳化項 方法 效果
分塊大小 512-1024 tokens,overlap 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: "運輸中", location: "..."} │
│    │                                                     │
│    ▼                                                     │
│  ┌──────────┐                                            │
│  │ 大模型   │ → 結合函式結果生成自然語言回答               │
│  └──────────┘                                            │
│    │                                                     │
│    ▼                                                     │
│  "訂單ORD-20260601目前正在運輸中,預計明天到達..."        │
│                                                          │
└──────────────────────────────────────────────────────────┘

訂單查詢Function

@Configuration
public class OrderFunctions {

    @Bean
    @Description("根據訂單號查詢訂單詳情,包括商品、金額、狀態")
    public Function<OrderQuery, OrderInfo> queryOrder(OrderService orderService) {
        return query -> orderService.getOrderInfo(query.orderId());
    }

    @Bean
    @Description("根據訂單號查詢物流狀態,包括目前位置和預計到達時間")
    public Function<LogisticsQuery, LogisticsInfo> queryLogistics(
            LogisticsService logisticsService) {
        return query -> logisticsService.getLogisticsInfo(query.orderId());
    }

    @Bean
    @Description("為訂單發起退款申請,需要提供訂單號和退款原因")
    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("""
                你是一個電商智慧客服助手。你可以:
                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: "杭州轉運中心", estimatedArrival: "2026-06-07"}
4. 生成回答:

AI: "您的訂單ORD-20260601目前正在運輸中,已到達杭州轉運中心,預計2026年6月7日送達。"
使用者: "這個訂單我不想要了,幫我退款"

AI內部流程:
1. 意圖識別 → 先查訂單再退款
2. 呼叫 queryOrder({orderId: "ORD-20260601"})
3. 函式回傳: {productName: "MacBook Pro", amount: 14999, status: "已發貨"}
4. 呼叫 requestRefund({orderId: "ORD-20260601", reason: "使用者不想要"})
5. 函式回傳: {refundId: "REF-20260605", refundAmount: 14999, status: "處理中"}
6. 生成回答:

AI: "已為您提交退款申請。訂單ORD-20260601(MacBook Pro,¥14,999)的退款正在處理中,
     退款編號REF-20260605。由於商品已發貨,退款將在商品退回後3-5個工作日內到帳。"

從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()) {
            StepResult result = executeStep(step, results);
            results.add(result);
        }

        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("""
                你是一個任務規劃器。將使用者目標分解為可執行的步驟。
                每個步驟必須包含: action(動作), tool(使用的工具), params(參數)。

                可用工具: %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

                執行結果:
                %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");
        String method = (String) params.getOrDefault("method", "GET");
        return restClient.get().uri(url).retrieve().body(String.class);
    }
}

@Component
public class FileWriteTool implements AgentTool {

    @Override
    public String name() { return "file_write"; }

    @Override
    public String description() { return "將內容寫入指定檔案"; }

    @Override
    public Object execute(Map<String, Object> params) {
        String path = (String) params.get("path");
        String content = (String) params.get("content");
        try {
            Files.writeString(Path.of(path), content);
            return "File written successfully: " + path;
        } catch (IOException e) {
            return "Failed to write file: " + e.getMessage();
        }
    }
}

Agent執行範例

使用者目標: "分析Q1銷售資料,生成報告並發送給管理層"

Agent執行過程:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[Planner] 分解目標為3個步驟:
  Step 1: 查詢Q1銷售資料 (tool: database_query)
  Step 2: 分析資料生成報告 (tool: llm_analyze)
  Step 3: 發送郵件 (tool: email_send)

[Executor] Step 1: 執行SQL...
  SELECT product, SUM(amount) as total, COUNT(*) as orders
  FROM sales WHERE quarter='Q1' GROUP BY product
  → 回傳12條產品銷售資料

[Executor] Step 2: 分析資料...
  "Q1總銷售額¥2,340萬,同比增長23%。Top3: MacBook(¥580萬)..."

[Executor] Step 3: 發送郵件...
  → 發送至 management@company.com,附件: Q1-Sales-Report.pdf

[Evaluator] 評估: satisfied=true, score=95
  "目標完成,資料準確,報告已發送"
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

多模型路由與Fallback策略

生產環境不能把所有雞蛋放在一個籃子裡——GPT-4o掛了,通義千問頂上。

多模型路由架構

┌──────────────────────────────────────────────────────────────┐
│                   多模型路由架構                               │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  請求 ──→ ModelRouter ──→ ┌─────────┐                       │
│                           │ 路由策略 │                       │
│                           └────┬────┘                       │
│                 ┌──────────────┼──────────────┐             │
│                 ▼              ▼              ▼             │
│          ┌──────────┐  ┌──────────┐  ┌──────────┐         │
│          │  GPT-4o  │  │ 通義千問  │  │ DeepSeek │         │
│          │ (高品質) │  │ (國產)   │  │ (高性價比)│         │
│          └──────────┘  └──────────┘  └──────────┘         │
│                 │              │              │             │
│                 ▼              ▼              ▼             │
│          ┌─────────────────────────────────────────┐       │
│          │          Fallback Chain                  │       │
│          │  GPT-4o → 通義千問 → 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 for prompt: {}",
                    modelName, truncate(prompt, 50));
                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;
        } else if (prompt.length() > 100 || containsReasoning(prompt)) {
            return Complexity.MEDIUM;
        }
        return Complexity.LOW;
    }

    private boolean containsCodeRequest(String prompt) {
        return prompt.toLowerCase().contains("程式碼") ||
               prompt.toLowerCase().contains("code") ||
               prompt.toLowerCase().contains("實作");
    }

    private boolean containsReasoning(String prompt) {
        return prompt.toLowerCase().contains("分析") ||
               prompt.toLowerCase().contains("比較") ||
               prompt.toLowerCase().contains("為什麼");
    }

    enum Complexity { HIGH, MEDIUM, LOW }
}

熔斷器設定

resilience4j:
  circuitbreaker:
    instances:
      gpt-4o:
        failure-rate-threshold: 50
        slow-call-rate-threshold: 80
        slow-call-duration-threshold: 10s
        wait-duration-in-open-state: 30s
        sliding-window-size: 10
      qwen-max:
        failure-rate-threshold: 50
        slow-call-rate-threshold: 80
        slow-call-duration-threshold: 8s
        wait-duration-in-open-state: 20s
        sliding-window-size: 10
      deepseek-v3:
        failure-rate-threshold: 60
        slow-call-duration-threshold: 5s
        wait-duration-in-open-state: 15s
        sliding-window-size: 10

多模型成本對比

模型 輸入價格(/1M tokens) 輸出價格(/1M tokens) 品質 延遲 適用場景
GPT-4o $2.50 $10.00 ⭐⭐⭐⭐⭐ 1-3s 複雜推理、程式碼生成
通義千問-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"]
# docker-compose.yml
services:
  app:
    build: .
    ports: ["8080:8080"]
    environment:
      OPENAI_API_KEY: ${OPENAI_API_KEY}
      SPRING_AI_OPENAI_BASE-URL: https://api.openai.com
    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
      timeout: 5s
      retries: 3

  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();
    }
}

@RestController
@RequestMapping("/api/chat")
public class RateLimitedChatController {

    private final ChatClient chatClient;
    private final RateLimiter rateLimiter;

    @PostMapping
    @RateLimiter(name = "ai-api", fallbackMethod = "rateLimitFallback")
    public String chat(@RequestBody ChatRequest request) {
        return chatClient.prompt()
            .user(request.message())
            .call()
            .content();
    }

    public String rateLimitFallback(ChatRequest request, Exception e) {
        return "目前請求過多,請稍後再試。";
    }
}

請求超時與重試

@Configuration
public class AiRetryConfig {

    @Bean
    public Retry aiRetry() {
        return Retry.builder()
            .name("ai-retry")
            .maxAttempts(3)
            .waitDuration(Duration.ofMillis(500))
            .retryOnException(e ->
                e instanceof ApiCallException ||
                e instanceof SocketTimeoutException)
            .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);
    }

    public void recordFunctionCall(String functionName, boolean success) {
        registry.counter("ai.function.calls",
            "function", functionName,
            "status", success ? "success" : "failure"
        ).increment();
    }
}

生產級效能清單

類別 檢查項 目標 工具
延遲 P95 API回應時間 < 3s Actuator + Grafana
延遲 串流首Token時間 < 500ms 自訂Metrics
吞吐 並行請求處理能力 > 100 QPS JMeter/k6
可靠性 熔斷器觸發率 < 5% Resilience4j
可靠性 Fallback成功率 > 99% 自訂Metrics
成本 日均Token消耗 < 預算 Token計數器
成本 模型路由命中率 高複雜→GPT-4o >80% 路由Metrics
安全 API Key輪換 90天一次 Vault/KMS
安全 敏感資訊過濾 100%攔截 輸入/輸出Guard
運維 滾動部署零中斷 0 error K8s ReadinessProbe
運維 設定熱更新 無需重啟 Spring Cloud Config

總結與架構全景圖

全景架構

┌──────────────────────────────────────────────────────────────────┐
│                  Spring Boot 3 + AI 全景架構                     │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌────────────────────────────────────────────────────────┐     │
│  │                    API Gateway / 負載均衡               │     │
│  └──────────────────────┬─────────────────────────────────┘     │
│                         │                                       │
│  ┌──────────────────────▼─────────────────────────────────┐     │
│  │              Spring Boot 3 Application                  │     │
│  │                                                        │     │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐            │     │
│  │  │ Chat     │  │ RAG      │  │ Agent    │            │     │
│  │  │ Controller│  │ Service  │  │ Service  │            │     │
│  │  └────┬─────┘  └────┬─────┘  └────┬─────┘            │     │
│  │       │             │             │                    │     │
│  │  ┌────▼─────────────▼─────────────▼─────┐            │     │
│  │  │         ChatClient (Spring AI)       │            │     │
│  │  │  ┌─────────────────────────────┐    │            │     │
│  │  │  │      Advisor Chain          │    │            │     │
│  │  │  │  Memory → RAG → Function   │    │            │     │
│  │  │  │  → Guard → Logging         │    │            │     │
│  │  │  └─────────────────────────────┘    │            │     │
│  │  └──────────────┬──────────────────────┘            │     │
│  │                  │                                    │     │
│  │  ┌──────────────▼──────────────────────┐            │     │
│  │  │         ModelRouter                 │            │     │
│  │  │  GPT-4o │ 通義千問 │ 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 + 通義千問 + DeepSeek,Fallback鏈保可用性
  8. 生產級部署不能省 — Docker + 熔斷 + 限流 + 監控,一個都不能少

Spring Boot 3 + AI大模型,不是「Java追趕AI」,而是「AI終於找到了最可靠的生產執行環境」。2026年,Java開發者的AI時代,已經到來。

本站提供瀏覽器本地工具,免註冊即可試用 →

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