- Spring AI 提供統一的抽象層,讓你用同一套程式碼切換 OpenAI、Anthropic、Google Gemini、Ollama 等不同 AI 供應商——就像 Spring Data 對資料庫做的事一樣
- ChatClient(高階 Fluent API)是日常開發的首選,而 ChatModel(底層介面)適合需要精細控制的進階場景——90% 的情況用 ChatClient 就夠了
- 透過三種訊息類型(SystemMessage、UserMessage、AssistantMessage)的組合,你可以精準控制 AI 的角色、行為與回應風格
- 理解 Token 計量機制是控制 AI 使用成本的關鍵——輸出 Token 的價格通常是輸入的 2~4 倍
為什麼選擇 Spring AI?
在 AI 應用開發快速演進的當下,Java/Kotlin 開發者面臨一個核心挑戰:每個 AI 供應商都有自己的 SDK、自己的 API 格式、自己的錯誤處理方式。今天用 OpenAI,明天想換 Anthropic Claude,後天可能要整合本地的 Ollama——每次切換都意味著大量程式碼重寫。
Spring AI[1] 正是為了解決這個問題而生。它在你的應用程式和 AI 供應商之間建立了一層統一抽象,讓切換模型就像切換資料庫一樣簡單:
| 痛點 | 沒有 Spring AI | 有 Spring AI |
|---|---|---|
| 切換供應商 | 重寫 HTTP 呼叫、序列化、錯誤處理 | 改一行設定 |
| 企業整合 | 自己處理 Spring Security、Transaction | 原生整合 Spring 生態系 |
| 功能擴展 | 自己實作 RAG、Memory、Tool Calling | 內建 Advisor API 框架 |
| 測試 | Mock 複雜的 HTTP 層 | 標準 Spring Test 模式 |
Spring AI 1.0 GA:從實驗到生產
2025 年發佈的 Spring AI 1.0 GA 標誌著這個框架正式進入生產級。以下是對開發者最重要的核心功能[3]:
- ChatClient — 統一的高階 Fluent API,用鏈式呼叫完成所有 AI 互動
- Advisor API — 類似 Spring AOP 的擴展機制,在不修改核心程式碼的情況下添加日誌、快取、過濾等功能
- Tool Calling — 讓 AI 模型呼叫你的 Java/Kotlin 方法(本系列 Lesson 4 將深入介紹)
- RAG 支援 — 內建向量資料庫整合,讓 AI 存取企業專屬知識
- 多模態支援 — 處理文字、圖片、語音等多種輸入輸出
- 結構化輸出 — AI 回應直接轉換為 Java/Kotlin 物件,不用自己解析 JSON
架構總覽:三層 API 設計
在開始寫程式碼之前,先理解 Spring AI 的三層架構,這會幫助你在後續課程中做出正確的設計決策:
┌─────────────────────────────────────────────────┐
│ 你的應用程式 (Application) │
├─────────────────────────────────────────────────┤
│ ChatClient(高階 Fluent API)← 日常開發推薦 │
│ ├─ .prompt().system().user().call() │
│ └─ 支援 Advisor 擴展 │
├─────────────────────────────────────────────────┤
│ ChatModel(底層模型介面)← 需要精細控制時使用 │
│ ├─ .call(Prompt) │
│ └─ 手動組裝 Message │
├─────────────────────────────────────────────────┤
│ HTTP Client(自動處理序列化、重試、錯誤處理) │
│ └─ OpenAI / Anthropic / Ollama / Azure ... │
└─────────────────────────────────────────────────┘
設計原則: 優先使用最上層的 ChatClient。只有當你需要自訂 Prompt 組裝邏輯、建構框架底層、或進行效能微調時,才需要降到 ChatModel 層。
Step 1:引入 Spring AI 依賴
在 Kotlin Notebook 中,我們使用 @file:DependsOn 來引入 Maven 依賴。開啟課程專案的 Lesson1/Lesson1_Hello_Spring_AI.ipynb,第一個 Cell 負責引入所有必要的函式庫:
@file:DependsOn("org.springframework.ai:spring-ai-openai:1.0.0")
@file:DependsOn("org.springframework.ai:spring-ai-client-chat:1.0.0")
@file:DependsOn("org.slf4j:slf4j-simple:2.0.16")
println("✓ Spring AI 依賴引入成功")
這三個依賴分別負責:
- spring-ai-openai — OpenAI 模型的連接器(包含 API 呼叫、序列化等實作)
- spring-ai-client-chat — ChatClient 與 ChatModel 的核心抽象層
- slf4j-simple — 日誌輸出(Spring AI 內部使用 SLF4J 記錄除錯資訊)
Step 2:載入 API Key 並初始化模型
使用 Lesson 0 設定好的環境變數來初始化 OpenAI 連線:
import org.springframework.ai.openai.OpenAiChatModel
import org.springframework.ai.openai.api.OpenAiApi
import org.springframework.ai.chat.client.ChatClient
// 從環境變數讀取 API Key
val apiKey = System.getenv("OPENAI_API_KEY")
?: error("請先設定 OPENAI_API_KEY 環境變數(參考 Lesson 0)")
// 建立 OpenAI API 連線
val openAiApi = OpenAiApi.builder()
.apiKey(apiKey)
.build()
// 建立 ChatModel(底層介面)
val chatModel = OpenAiChatModel.builder()
.openAiApi(openAiApi)
.build()
// 建立 ChatClient(高階 Fluent API)
val chatClient = ChatClient.builder(chatModel).build()
println("✓ Spring AI 初始化完成,已連線 OpenAI")
注意初始化的層次關係:API Key → OpenAiApi → ChatModel → ChatClient。每一層都封裝了下一層的複雜度,讓你在最上層用最簡潔的方式操作。
Step 3:用 ChatModel 發出第一次 AI 呼叫
先從底層的 ChatModel 開始,理解最基礎的呼叫方式[2]:
// 最簡單的呼叫方式:直接傳入字串
val response = chatModel.call("用一句話介紹什麼是 Spring AI")
println(response)
預期輸出(每次呼叫結果會略有不同):
Spring AI 是一個結合 Spring 框架的強大功能與人工智慧技術的開發工具集,
旨在簡化和加速 AI 應用的開發過程。
恭喜!你已經成功用 Spring AI 完成了第一次 AI 模型呼叫。但這只是最基礎的用法——接下來我們要學習如何透過訊息類型來精準控制 AI 的行為。
Step 4:理解三種訊息類型
AI 對話是由不同角色的訊息組成的。Spring AI 提供三種訊息類型,對應 OpenAI Chat API 的三種 role[2]:
| 訊息類型 | 角色 | 用途 | 範例 |
|---|---|---|---|
| SystemMessage | 系統 | 設定 AI 的角色與行為規則 | 「你是一位 Spring 框架專家」 |
| UserMessage | 使用者 | 使用者的提問或指令 | 「請問如何使用 Spring AI?」 |
| AssistantMessage | AI 助手 | AI 的回應(多輪對話時使用) | (AI 的回覆文字) |
使用 ChatModel 的 Prompt 物件來組合這些訊息:
import org.springframework.ai.chat.prompt.Prompt
import org.springframework.ai.chat.messages.SystemMessage
import org.springframework.ai.chat.messages.UserMessage
// 組裝多訊息 Prompt
val prompt = Prompt(listOf(
SystemMessage("你是一位資深 Spring 框架專家,擅長用簡潔的方式解釋複雜概念。回答限制在 100 字以內。"),
UserMessage("ChatClient 和 ChatModel 有什麼差別?")
))
val result = chatModel.call(prompt)
println(result.result.output.text)
預期輸出:
ChatModel 是底層介面,需要手動組裝 Prompt 和解析回應;
ChatClient 是高階 Fluent API,提供鏈式呼叫,自動處理 Prompt 組裝,
並支援 Advisor 擴展。日常開發建議使用 ChatClient,
只有需要精細控制時才用 ChatModel。
觀察: SystemMessage 的威力在於它能「設定舞台」——告訴 AI 扮演什麼角色、遵守什麼規則。這就是 Prompt Engineering 的核心概念,Lesson 3 會深入探討。
Step 5:用 ChatClient 體驗 Fluent API
同樣的需求,改用 ChatClient 的 Fluent API 來實作——注意程式碼的簡潔度:
val answer = chatClient
.prompt()
.system("你是一位親切的程式設計老師,用簡單易懂的比喻解釋技術概念。")
.user("什麼是 RAG?")
.call()
.content() ?: "(無回應)"
println(answer)
預期輸出:
想像你有一個非常聰明的朋友,但他的知識停留在幾年前。
RAG(Retrieval-Augmented Generation,檢索增強生成)就像是在他回答問題之前,
先幫他翻閱最新的參考資料。這樣他的回答就不只靠記憶,
還能引用最新、最準確的資訊。在技術上,就是先從資料庫搜尋相關文件,
再把這些文件一起交給 AI 模型,讓它生成更精確的回答。
對比一下兩種 API 的使用方式:
| 特性 | ChatModel(底層) | ChatClient(高階) |
|---|---|---|
| 呼叫方式 | 手動建立 Prompt 物件 | Fluent 鏈式呼叫 |
| 訊息組裝 | 手動 listOf(SystemMessage, UserMessage) | .system().user() |
| 回應取得 | result.result.output.text | .content() |
| Advisor 支援 | 不支援 | 支援(日誌、快取、RAG 等) |
| 推薦場景 | 框架開發、精細控制 | 日常開發(90% 的場景) |
Step 6:實作練習——切換 AI 角色
SystemMessage 的真正威力在於你可以讓同一個 AI 模型扮演完全不同的角色。讓我們用一個有趣的實驗來體驗:
// 角色 1:程式設計師
val programmer = chatClient
.prompt()
.system("你是一位嚴謹的資深程式設計師,所有回答都要用程式設計的概念來比喻。限 50 字。")
.user("描述一下你的午餐")
.call()
.content() ?: "(無回應)"
println("🧑💻 程式設計師:")
println(programmer)
println()
// 角色 2:美食評論家
val foodCritic = chatClient
.prompt()
.system("你是一位米其林等級的美食評論家,用華麗的詞藻描述一切。限 50 字。")
.user("描述一下你的午餐")
.call()
.content() ?: "(無回應)"
println("🍽️ 美食評論家:")
println(foodCritic)
可能的輸出:
🧑💻 程式設計師:
午餐就像一次函式呼叫——輸入食材,經過加熱處理的 pipeline,
輸出一份編譯完成的位元比特披薩。沒有 bug,味道穩定。
🍽️ 美食評論家:
金黃酥脆的外衣下,包裹著絲綢般的馬鈴薯泥,
佐以松露油的芬芳——這不僅是午餐,更是一場味蕾的交響樂。
同樣的問題,不同的 SystemMessage 就能得到風格截然不同的回答。這就是為什麼 Prompt Engineering 如此重要——你不只在寫程式碼,更在設計 AI 的「人格」。
Step 7:查看完整回應資訊與 Token 用量
在正式專案中,你需要追蹤 Token 用量來控制成本[4]。ChatClient 的 .chatResponse() 方法可以取得完整的回應資訊:
val chatResponse = chatClient
.prompt()
.system("你是一位 Spring AI 專家。")
.user("Spring AI 支援哪些模型供應商?簡要列出。")
.call()
.chatResponse()
// 取得回應文字
println("回應內容:")
println(chatResponse?.result?.output?.text)
println()
// 取得 Token 用量
val usage = chatResponse?.metadata?.usage
println("═══ Token 用量統計 ═══")
println("輸入 Token(Prompt): ${usage?.promptTokens}")
println("輸出 Token(Completion): ${usage?.completionTokens}")
println("總計 Token: ${usage?.totalTokens}")
預期輸出:
回應內容:
Spring AI 支援以下模型供應商:OpenAI(GPT 系列)、Anthropic(Claude 系列)、
Google(Gemini)、Azure OpenAI、Ollama(本地模型)、Amazon Bedrock、
Mistral AI、HuggingFace 等。
═══ Token 用量統計 ═══
輸入 Token(Prompt): 35
輸出 Token(Completion): 68
總計 Token: 103
理解 Token 的幾個關鍵要點:
- Token ≠ 字元 — 中文通常 1 個字 = 1~2 個 Token,英文 1 個詞 ≈ 1 Token
- 成本差異 — 輸出 Token 的價格通常是輸入的 2~4 倍,因此限制輸出長度是控制成本的有效手段
- 上下文限制 — 每個模型有 Token 上限(如 GPT-4o 為 128K),超過會被截斷
ChatClient vs ChatModel:什麼時候該用哪一個?
學完兩種 API 後,給你一個簡單的決策框架:
| 場景 | 推薦 API | 原因 |
|---|---|---|
| 日常 AI 功能開發 | ChatClient | 簡潔、支援 Advisor、覆蓋 90% 場景 |
| 聊天機器人 / RAG 應用 | ChatClient | Advisor API 輕鬆添加 Memory、RAG |
| 自訂 Prompt 模板引擎 | ChatModel | 需要完全控制 Prompt 組裝邏輯 |
| 建構 AI 框架底層 | ChatModel | 需要存取原始 Request/Response |
| 效能基準測試 | ChatModel | 減少抽象層開銷,取得精確數據 |
經驗法則: 如果你猶豫不決,就用 ChatClient。等到你明確知道 ChatClient 無法滿足需求時,再考慮 ChatModel。
本課重點回顧
在這堂課中,我們完成了以下學習目標:
- 理解 Spring AI 的定位 — 它是 AI 供應商的統一抽象層,類似 Spring Data 對資料庫的角色
- 掌握三層 API 架構 — ChatClient(高階)→ ChatModel(底層)→ HTTP Client(傳輸層)
- 完成第一次 AI 呼叫 — 分別使用 ChatModel 和 ChatClient 呼叫 OpenAI GPT 模型
- 學會控制 AI 行為 — 透過 SystemMessage、UserMessage 的組合精準控制回應風格
- 理解 Token 計量機制 — 掌握成本追蹤的基礎,為後續大量 API 呼叫做準備
下一步:Lesson 2 — 串流輸出與前端整合
目前我們的 AI 呼叫是「等待完成才顯示」——使用者要等幾秒鐘才能看到回應,體驗不佳。在下一堂課中,我們將學習:
- Streaming API — 讓 AI 回應像打字一樣逐字顯示
- Server-Sent Events(SSE) — 在 Web 應用中實現即時串流
- 打字機效果 — 打造像 ChatGPT 一樣流暢的使用者體驗


