Spring AI 多模态实战(下):文生图 + TTS 语音合成(含 2.0 流式音频分片)

Spring AI 多模态实战(下):文生图 + TTS 语音合成(含 2.0 流式音频分片)

系列:Spring AI 深入浅出(第 17 篇)

版本:Spring AI 2.0.1 / Spring Boot 4.1.x / JDK 21

标签:#SpringAI #文生图 #TTS #语音合成

前置阅读:第 16 篇《图像理解 Vision》

预计字数:6000 字|可复制源码 + 生成式多模态图 + 踩坑 FAQ

本文将解决:除了"看懂",多模态还能"造出来"------文生图 + 语音合成(TTS),一篇讲完生成式多模态。

你将学到:

  • 文生图:ImageModel / ImagePrompt
  • TTS:SpeechModel / SpeechPrompt
  • 2.0 流式音频分片
  • 生成物落盘与成本控制

一、版本标注

本文基于 Spring AI 2.0.1 / Boot 4.1 / JDK 21。文生图走 ImageModel/ImagePrompt,TTS 走 SpeechModel/SpeechPrompt(不同厂商 starter 提供对应实现);2.0 流式音频分片的具体 API 以 2.0.1 官方文档为准。


二、文生图:ImageModel / ImagePrompt

文生图是"输入文字 → 输出图片",走独立的 ImageModel 抽象(和聊天的 ChatModel 分开):

java 复制代码
import org.springframework.ai.image.ImageModel;
import org.springframework.ai.image.ImagePrompt;
import org.springframework.ai.image.ImageResponse;

@Service
public class ImageService {
    private final ImageModel imageModel;

    public ImageService(ImageModel imageModel) {
        this.imageModel = imageModel;
    }

    public String generateImage(String prompt) {
        ImageResponse response = imageModel.call(new ImagePrompt(prompt));
        // 取第一张图(返回 URL 或 base64,取决于实现)
        return response.getResult().getOutput().getUrl();
    }
}

要点:

  • ImageModel.call(ImagePrompt) 返回 ImageResponse
  • 图片输出一般是 URL (云端)或 base64(本地模型)
  • URL 会过期,生产上要及时落盘到对象存储

三、TTS:SpeechModel / SpeechPrompt

TTS 是"输入文字 → 输出音频":

java 复制代码
import org.springframework.ai.speech.SpeechModel;
import org.springframework.ai.speech.SpeechPrompt;
import org.springframework.ai.speech.SpeechResponse;

@Service
public class SpeechService {
    private final SpeechModel speechModel;

    public SpeechService(SpeechModel speechModel) {
        this.speechModel = speechModel;
    }

    public byte[] textToSpeech(String text) {
        SpeechResponse response = speechModel.call(new SpeechPrompt(text));
        return response.getResult().getOutput().getAudio(); // 音频字节
    }
}

得到的音频字节可写文件、存对象存储,或直接返回前端播放。


四、2.0 流式音频分片

长文本的 TTS 如果等全部生成完才返回,用户要等很久。2.0 支持流式音频分片------边合成边返回音频块:

java 复制代码
// 思路:流式 TTS,边生成边拿到音频分片
// 精确 API 以 2.0.1 官方文档为准(Flux<byte[]> 或类似)

适用:长文本朗读、实时语音播报等需要低延迟的场景。


五、生成物落盘与成本控制

生成式多模态(尤其文生图、长文本 TTS)成本不低,要控制:

手段 说明
缓存 相同 prompt 不重复生成,直接返回缓存
落盘 URL 会过期,及时存对象存储/本地
限流 文生图/TTS 设配额,防止刷爆成本
选型 本地模型(如 Ollama 的 TTS/图模型)可省云端费用

六、原理图解:生成式多模态请求-响应

#mermaid-svg-9Ne5Sip1blvozKU5{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9Ne5Sip1blvozKU5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9Ne5Sip1blvozKU5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9Ne5Sip1blvozKU5 .error-icon{fill:#552222;}#mermaid-svg-9Ne5Sip1blvozKU5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9Ne5Sip1blvozKU5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9Ne5Sip1blvozKU5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9Ne5Sip1blvozKU5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9Ne5Sip1blvozKU5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9Ne5Sip1blvozKU5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9Ne5Sip1blvozKU5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9Ne5Sip1blvozKU5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9Ne5Sip1blvozKU5 .marker.cross{stroke:#333333;}#mermaid-svg-9Ne5Sip1blvozKU5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9Ne5Sip1blvozKU5 p{margin:0;}#mermaid-svg-9Ne5Sip1blvozKU5 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-9Ne5Sip1blvozKU5 .cluster-label text{fill:#333;}#mermaid-svg-9Ne5Sip1blvozKU5 .cluster-label span{color:#333;}#mermaid-svg-9Ne5Sip1blvozKU5 .cluster-label span p{background-color:transparent;}#mermaid-svg-9Ne5Sip1blvozKU5 .label text,#mermaid-svg-9Ne5Sip1blvozKU5 span{fill:#333;color:#333;}#mermaid-svg-9Ne5Sip1blvozKU5 .node rect,#mermaid-svg-9Ne5Sip1blvozKU5 .node circle,#mermaid-svg-9Ne5Sip1blvozKU5 .node ellipse,#mermaid-svg-9Ne5Sip1blvozKU5 .node polygon,#mermaid-svg-9Ne5Sip1blvozKU5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9Ne5Sip1blvozKU5 .rough-node .label text,#mermaid-svg-9Ne5Sip1blvozKU5 .node .label text,#mermaid-svg-9Ne5Sip1blvozKU5 .image-shape .label,#mermaid-svg-9Ne5Sip1blvozKU5 .icon-shape .label{text-anchor:middle;}#mermaid-svg-9Ne5Sip1blvozKU5 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9Ne5Sip1blvozKU5 .rough-node .label,#mermaid-svg-9Ne5Sip1blvozKU5 .node .label,#mermaid-svg-9Ne5Sip1blvozKU5 .image-shape .label,#mermaid-svg-9Ne5Sip1blvozKU5 .icon-shape .label{text-align:center;}#mermaid-svg-9Ne5Sip1blvozKU5 .node.clickable{cursor:pointer;}#mermaid-svg-9Ne5Sip1blvozKU5 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9Ne5Sip1blvozKU5 .arrowheadPath{fill:#333333;}#mermaid-svg-9Ne5Sip1blvozKU5 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9Ne5Sip1blvozKU5 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9Ne5Sip1blvozKU5 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9Ne5Sip1blvozKU5 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9Ne5Sip1blvozKU5 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9Ne5Sip1blvozKU5 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9Ne5Sip1blvozKU5 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9Ne5Sip1blvozKU5 .cluster text{fill:#333;}#mermaid-svg-9Ne5Sip1blvozKU5 .cluster span{color:#333;}#mermaid-svg-9Ne5Sip1blvozKU5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9Ne5Sip1blvozKU5 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9Ne5Sip1blvozKU5 rect.text{fill:none;stroke-width:0;}#mermaid-svg-9Ne5Sip1blvozKU5 .icon-shape,#mermaid-svg-9Ne5Sip1blvozKU5 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9Ne5Sip1blvozKU5 .icon-shape p,#mermaid-svg-9Ne5Sip1blvozKU5 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9Ne5Sip1blvozKU5 .icon-shape rect,#mermaid-svg-9Ne5Sip1blvozKU5 .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9Ne5Sip1blvozKU5 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9Ne5Sip1blvozKU5 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9Ne5Sip1blvozKU5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 文生图
TTS
文字 prompt
模型类型
ImageModel
SpeechModel
图片 URL/base64
音频字节/流式分片


七、踩坑 FAQ

坑 1:图片 URL 过期

现象 :返回的图片 URL 过段时间就打不开了。

原因 :云端图片 URL 有有效期。

解决:拿到 URL 后及时下载落盘到对象存储。

坑 2:TTS 音频格式/编码不匹配

现象 :前端播放音频失败或音质异常。

原因 :音频格式(mp3/wav/opus)或编码与前端不匹配。

解决:核对 TTS 输出格式,统一转成前端支持的格式(如 mp3)。

坑 3:流式分片拼接顺序错

现象 :流式音频分片拼起来后播放乱序。

原因 :分片顺序没保证,或拼接逻辑错。

解决:按分片序号拼接,注意结束标记。

坑 4:生成成本高

现象 :文生图/TTS 调用多,成本飙升。

原因 :生成式多模态单价高,无缓存无配额。

解决:加缓存(相同 prompt 复用)+ 配额限流 + 必要时换本地模型。

坑 5:图片/音频没有落盘,丢了

现象 :重启或过期后生成物丢失。

原因 :只返回了 URL/临时数据,没持久化。

解决:生成后立即落盘对象存储/数据库,记录元数据。


八、小结与合集导航

本篇补全生成式多模态:

  • 文生图 ImageModel/ImagePrompt
  • TTS SpeechModel/SpeechPrompt
  • 2.0 流式音频分片
  • 落盘 + 缓存 + 限流的成本控制

下一篇:《流式输出对接前端》------类 ChatGPT 打字机效果,Flux + SSE 打通前后端。


九、互动钩子

投票:文生图和 TTS,你更需要哪个?

  • 文生图
  • TTS 语音合成
  • 两个都要
  • 都用不上

评论区聊聊你的生成式多模态场景;源码见置顶评论。

相关推荐
长不大的杨瑞杰1 小时前
OpenCode v2 Custom Tools 实战:开发数据库、工单与发布工具
人工智能·typescript·开发工具·opencode·custom tools
羊仔AI探索1 小时前
离世仅仅半个月,张雪峰被蒸馏了
人工智能·aigc·蒸馏·张雪峰·skills
步行cgn1 小时前
Spring MVC 执行流程详解:一次请求的完整旅程
java·spring·mvc
zhippt_10141 小时前
只剩截图没有源文件?图转PPT把图片还原成可编辑幻灯片
前端·人工智能·powerpoint
增量星球2 小时前
Spring AI 2.0 Tool Calling 到底怎么跑起来的:从 @Tool 到两次模型调用
java·agent·springai·ai coding
小oo呆2 小时前
【自然语言处理与大模型】自然语言处理的漫漫长路之深度学习时代
人工智能·深度学习·自然语言处理
步行cgn2 小时前
Spring MVC 九大核心角色详解:请求处理链路中的每一个关键组件
java·spring·mvc
JavaPub-rodert2 小时前
AI Agent 的记忆系统怎么设计?从短期记忆、长期记忆到 RAG,讲透 Memory 工程化
人工智能
智恒百亿2 小时前
一台 5090 八卡服务器怎么分给一个团队用?多模型多任务的显存分配实战
java·大数据·人工智能