Spring Boot 3 + AI大模型整合全攻略
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 │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘
核心要點回顧
- Java是AI應用的最佳生產載體 — 並行、安全、運維三位一體,Python訓練用,Java應用用
- Spring AI是2026年首選框架 — Spring官方出品,宣告式設定,零學習成本
- 多輪對話必須有持久化記憶 — Redis ChatMemory,視窗/摘要/混合策略按場景選
- RAG是企業AI的基石 — ETL Pipeline + 向量檢索 + 上下文注入,缺一不可
- Function Calling連接模型與世界 — 模型決策何時呼叫,你定義呼叫什麼
- Agent = Planner + Executor + Evaluator — 從被動回應到主動執行
- 多模型路由是生產標配 — GPT-4o + 通義千問 + DeepSeek,Fallback鏈保可用性
- 生產級部署不能省 — Docker + 熔斷 + 限流 + 監控,一個都不能少
Spring Boot 3 + AI大模型,不是「Java追趕AI」,而是「AI終於找到了最可靠的生產執行環境」。2026年,Java開發者的AI時代,已經到來。
本站提供瀏覽器本地工具,免註冊即可試用 →
#Spring Boot#Spring AI#LangChain4j#AI大模型#RAG#Function Calling#Java Agent