Key Findings
- Spring AI 支援四大多模態能力:GPT-4o 圖片分析(URL、本地檔案、多圖比較)、DALL-E 3 圖片生成、Whisper 語音轉文字、TTS 文字轉語音
- 圖片分析只需在
UserMessage上加.media()即可——支援 URL 與本地檔案,甚至可以同時送入多張圖片進行比較分析 - DALL-E 3 的圖片品質取決於 Prompt 品質——用 AI 先優化中文描述為英文 DALL-E Prompt,生成效果大幅提升
- Whisper + TTS 的組合可以打造完整的語音助手管線——語音輸入 → AI 處理 → 語音輸出,成本僅約 $0.02/次互動
什麼是多模態 AI?
前七堂課處理的都是文字。但真實世界的資訊不只有文字——有圖片(發票、產品照片、設計稿)、有聲音(客戶來電、會議錄音)、有影片。多模態 AI 就是讓 AI 同時理解和產生多種資料格式[1]:
【單一模態】 文字 → AI → 文字
【多模態】 文字 ─┐ ┌→ 文字
圖片 ─┤→ AI 模型 ──┤→ 圖片
音訊 ─┘ └→ 音訊
Spring AI 支援的多模態功能:
| 功能 | 模型 | Spring AI 類別 | 用途 |
|---|---|---|---|
| 圖片分析 | GPT-4o | ChatModel + Media | OCR、圖片描述、比較 |
| 圖片生成 | DALL-E 3 | OpenAiImageModel | 行銷素材、產品圖 |
| 語音轉文字 | Whisper | OpenAiAudioTranscriptionModel | 會議記錄、字幕 |
| 文字轉語音 | OpenAI TTS | OpenAiAudioSpeechModel | 語音助手、播報 |
Step 1:環境準備
開啟課程專案的 Lesson8/Lesson8_Multimodal.ipynb:
@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")
import org.springframework.ai.openai.OpenAiChatModel
import org.springframework.ai.openai.api.OpenAiApi
import org.springframework.ai.chat.client.ChatClient
val apiKey = System.getenv("OPENAI_API_KEY")
?: error("請先設定 OPENAI_API_KEY(參考 Lesson 0)")
val openAiApi = OpenAiApi.builder().apiKey(apiKey).build()
val chatModel = OpenAiChatModel.builder().openAiApi(openAiApi).build()
val chatClient = ChatClient.builder(chatModel).build()
println("✓ 環境準備完成")
Step 2:圖片分析——讓 AI 看得懂圖片
GPT-4o 是一個原生的多模態模型[2],可以同時理解文字和圖片。在 Spring AI 中,只需要在 UserMessage 上加 .media():
方式一:分析網路圖片(URL)
import org.springframework.ai.chat.messages.UserMessage
import org.springframework.ai.model.Media
import org.springframework.util.MimeTypeUtils
import org.springframework.ai.chat.prompt.Prompt
import org.springframework.ai.openai.OpenAiChatOptions
import java.net.URI
val imageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/PNG_transparency_demonstration_1.png/300px-PNG_transparency_demonstration_1.png"
val userMessage = UserMessage.builder()
.text("請用繁體中文描述這張圖片的內容,包含主要元素和顏色。")
.media(Media(MimeTypeUtils.IMAGE_PNG, URI.create(imageUrl)))
.build()
val response = chatModel.call(
Prompt(userMessage, OpenAiChatOptions.builder().model("gpt-4o").build())
)
println(response.result.output.text)
方式二:分析本地檔案
import org.springframework.core.io.FileSystemResource
import java.io.File
val localFile = File("sample-docs/test-image.jpg")
val localMessage = UserMessage.builder()
.text("請用繁體中文描述這張圖片的內容。")
.media(Media(MimeTypeUtils.IMAGE_JPEG, FileSystemResource(localFile)))
.build()
val localResponse = chatModel.call(
Prompt(localMessage, OpenAiChatOptions.builder().model("gpt-4o").build())
)
println(localResponse.result.output.text)
方式三:多圖比較
GPT-4o 可以同時分析多張圖片:
val img1 = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/300px-Cat03.jpg"
val img2 = "https://upload.wikimedia.org/wikipedia/commons/thumb/2/26/YellowLabradorLooking_new.jpg/300px-YellowLabradorLooking_new.jpg"
val compareMessage = UserMessage.builder()
.text("請比較這兩張圖片的異同,用繁體中文回答。")
.media(Media(MimeTypeUtils.IMAGE_JPEG, URI.create(img1)))
.media(Media(MimeTypeUtils.IMAGE_JPEG, URI.create(img2)))
.build()
val compareResponse = chatModel.call(
Prompt(compareMessage, OpenAiChatOptions.builder().model("gpt-4o").build())
)
println(compareResponse.result.output.text)
預期輸出:
這兩張圖片都是動物的特寫照片。第一張是一隻灰色虎斑貓,
眼睛碧綠,表情警覺;第二張是一隻金色拉布拉多犬,
表情溫和友善。兩者都是受歡迎的寵物,但貓較為獨立,
狗則更親人。拍攝風格都是自然光下的近距離特寫。
企業應用: 多圖比較可用於品質檢測(比較良品/瑕疵品)、進度追蹤(比較施工前後)、版本比對(比較設計稿修改前後)。
Step 3:圖片生成——DALL-E 3
使用 DALL-E 3[3] 從文字描述生成圖片:
import org.springframework.ai.openai.OpenAiImageModel
import org.springframework.ai.openai.api.OpenAiImageApi
import org.springframework.ai.image.ImagePrompt
import org.springframework.ai.openai.OpenAiImageOptions
val imageApi = OpenAiImageApi.builder().apiKey(apiKey).build()
val imageModel = OpenAiImageModel(imageApi)
val imagePrompt = ImagePrompt(
"A futuristic cityscape at sunset with flying cars and neon lights, cyberpunk style, ultra detailed",
OpenAiImageOptions.builder()
.model("dall-e-3")
.quality("standard")
.height(1024)
.width(1024)
.build()
)
val imageResponse = imageModel.call(imagePrompt)
val generatedUrl = imageResponse.result.output.url
println("生成的圖片: $generatedUrl")
DALL-E 3 的參數選項:
| 參數 | 選項 | 說明 |
|---|---|---|
| model | dall-e-3 / dall-e-2 | 3 品質更好,2 速度更快 |
| quality | standard / hd | hd 更精細,價格翻倍 |
| size | 1024x1024 / 1792x1024 / 1024x1792 | 正方形 / 橫幅 / 直幅 |
| n | 1 | DALL-E 3 每次只能生成 1 張 |
AI 優化 Prompt——中文描述自動轉英文 DALL-E Prompt
DALL-E 對英文 Prompt 的效果遠好於中文。用 AI 先優化 Prompt:
fun generateImageWithAI(description: String): String {
// Step 1: AI 將中文描述優化為英文 DALL-E Prompt
val optimizedPrompt = chatClient.prompt()
.system("""你是 DALL-E 圖像生成的 Prompt 專家。
將使用者的中文描述轉換為高品質的英文 DALL-E Prompt。
包含:主體、動作、風格、光線、背景、色調。
只回傳英文 Prompt,不要其他說明。""")
.user(description)
.call().content() ?: description
println("優化後 Prompt: $optimizedPrompt")
// Step 2: 用優化後的 Prompt 生成圖片
val response = imageModel.call(ImagePrompt(
optimizedPrompt,
OpenAiImageOptions.builder()
.model("dall-e-3").quality("standard")
.height(1024).width(1024).build()
))
return response.result.output.url
}
// 測試:用中文描述生成圖片
val url = generateImageWithAI("一隻穿著太空服的柴犬在月球上散步")
println("生成結果: $url")
Step 4:語音轉文字——Whisper
OpenAI 的 Whisper 模型[4]可以將音訊檔案轉錄為文字,支援多種語言:
import org.springframework.ai.openai.OpenAiAudioTranscriptionModel
import org.springframework.ai.openai.api.OpenAiAudioApi
import org.springframework.ai.openai.OpenAiAudioTranscriptionOptions
import org.springframework.ai.audio.transcription.AudioTranscriptionPrompt
val audioApi = OpenAiAudioApi.builder().apiKey(apiKey).build()
val transcriptionModel = OpenAiAudioTranscriptionModel(audioApi)
val transcriptionOptions = OpenAiAudioTranscriptionOptions.builder()
.language("zh") // 指定語言(中文)
.temperature(0.0f) // 0.0 = 最精確
.build()
val audioFile = File("sample-docs/test-audio.mp3")
val audioPrompt = AudioTranscriptionPrompt(
FileSystemResource(audioFile),
transcriptionOptions
)
val transcription = transcriptionModel.call(audioPrompt)
println("轉錄結果: ${transcription.result.output}")
Whisper 的參數選項:
| 參數 | 說明 | 建議值 |
|---|---|---|
| language | 音訊語言 | zh(中文)、en(英文)、ja(日文) |
| temperature | 轉錄的隨機性 | 0.0(最精確) |
| responseFormat | 輸出格式 | json / text / srt / vtt |
實用技巧: 設定responseFormat為srt可以直接生成帶時間軸的字幕檔案,適合影片字幕製作。
Step 5:文字轉語音——TTS
OpenAI TTS[4] 提供六種語音風格:
| 語音 | 風格 | 適合場景 |
|---|---|---|
| alloy | 中性、專業 | 企業通知、系統語音 |
| echo | 溫暖男聲 | 播客、故事敘述 |
| fable | 英式口音 | 品牌形象、有特色的內容 |
| onyx | 深沉男聲 | 新聞播報、權威感 |
| nova | 活潑女聲(推薦) | 客服助手、互動式內容 |
| shimmer | 柔和女聲 | 冥想、舒緩內容 |
import org.springframework.ai.openai.OpenAiAudioSpeechModel
import org.springframework.ai.openai.audio.speech.SpeechPrompt
import org.springframework.ai.openai.audio.speech.SpeechRequest
import org.springframework.ai.openai.OpenAiAudioSpeechOptions
val speechModel = OpenAiAudioSpeechModel(audioApi)
val text = "歡迎使用 Spring AI 智慧語音助手!今天我們來學習多模態處理。"
val speechOptions = OpenAiAudioSpeechOptions.builder()
.voice(SpeechRequest.Voice.NOVA)
.speed(1.0f)
.model("tts-1")
.build()
val speechResponse = speechModel.call(SpeechPrompt(text, speechOptions))
// 儲存為 MP3 檔案
val outputFile = File("sample-docs/output-speech.mp3")
outputFile.writeBytes(speechResponse.result.output)
println("✓ 語音已儲存: ${outputFile.name}(${outputFile.length() / 1024} KB)")
語音風格比較
val sampleText = "Spring AI 讓 Java 開發者也能輕鬆建構 AI 應用。"
val voices = listOf(
SpeechRequest.Voice.ALLOY to "voice-alloy.mp3",
SpeechRequest.Voice.NOVA to "voice-nova.mp3",
SpeechRequest.Voice.ONYX to "voice-onyx.mp3"
)
voices.forEach { (voice, filename) ->
val options = OpenAiAudioSpeechOptions.builder()
.voice(voice).speed(1.0f).model("tts-1").build()
val response = speechModel.call(SpeechPrompt(sampleText, options))
File("sample-docs/$filename").writeBytes(response.result.output)
println("✓ ${voice.name} → $filename")
}
Step 6:實戰——圖片描述 + 語音導覽
結合圖片分析與 TTS,打造一個自動語音導覽系統——上傳一張藝術品照片,AI 生成描述並轉為語音導覽:
fun imageToSpeech(imageUrl: String, outputFileName: String): String {
// Step 1: GPT-4o 分析圖片
val descMessage = UserMessage.builder()
.text("你是一位美術館導覽員。請用繁體中文,以生動的語氣描述這幅藝術作品," +
"包含作品風格、色彩運用和情感傳達。控制在 50 字以內。")
.media(Media(MimeTypeUtils.IMAGE_JPEG, URI.create(imageUrl)))
.build()
val description = chatModel.call(
Prompt(descMessage, OpenAiChatOptions.builder().model("gpt-4o").build())
).result.output.text
println("📝 圖片描述: $description")
// Step 2: TTS 將描述轉為語音
val options = OpenAiAudioSpeechOptions.builder()
.voice(SpeechRequest.Voice.NOVA)
.speed(0.9f) // 導覽語速稍慢
.model("tts-1")
.build()
val speech = speechModel.call(SpeechPrompt(description, options))
File("sample-docs/$outputFileName").writeBytes(speech.result.output)
println("🔊 語音導覽已儲存: $outputFileName")
return description
}
// 測試:分析一幅畫作並生成語音導覽
imageToSpeech(
"https://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg/300px-Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg",
"art-guide.mp3"
)
這個管線:圖片 → GPT-4o 分析 → 文字描述 → TTS 語音,可以應用在美術館導覽、房地產看屋語音介紹、電商商品語音描述等場景。
成本參考
| 功能 | 模型 | 單價 | 說明 |
|---|---|---|---|
| 圖片分析 | GPT-4o | ~$0.01~0.05 / 張 | 依圖片大小與 detail 設定 |
| 圖片生成 | DALL-E 3 standard | $0.04 / 張 | HD 品質 $0.08 / 張 |
| 語音轉文字 | Whisper | $0.006 / 分鐘 | 1 小時約 $0.36 |
| 文字轉語音 | TTS-1 | $0.015 / 1000 字元 | TTS-1-HD 品質 $0.030 |
成本估算: 一次完整的語音助手互動(Whisper 轉錄 10 秒 + GPT-4o 處理 + TTS 回覆 200 字)約 $0.02。1 萬次互動僅 $200。
本課重點回顧
| 功能 | 模型 | 關鍵程式碼 |
|---|---|---|
| 圖片分析 | GPT-4o | UserMessage.builder().text().media() |
| 多圖比較 | GPT-4o | 多次 .media() 呼叫 |
| 圖片生成 | DALL-E 3 | ImagePrompt + OpenAiImageOptions |
| 語音轉文字 | Whisper | AudioTranscriptionPrompt |
| 文字轉語音 | TTS | SpeechPrompt + SpeechRequest.Voice |
| 組合管線 | GPT-4o + TTS | 圖片分析 → 文字 → 語音 |
課程回顧:Lesson 0~8 完整技術棧
到此為止,你已經掌握了 Spring AI 在 Kotlin Notebook 環境中的所有核心功能:
- Lesson 0~1 — 環境設定 + ChatClient / ChatModel 基礎
- Lesson 2~3 — 串流輸出 + Prompt 工程 + 結構化輸出
- Lesson 4~5 — Function Calling + ChatMemory
- Lesson 6~7 — RAG 基礎 + 進階 RAG
- Lesson 8 — 多模態(圖片 + 語音)
下一步:Lesson 9 — 文件處理
接下來進入企業文件處理——RAG 系統的「食材準備」階段:
- PDF 解析 — 自動提取 PDF 文件的文字與表格
- 文件切割 — TokenTextSplitter 的進階配置
- 多格式支援 — Word、Excel、HTML 等格式的處理


