1-背景故事
前几篇我们从零开始,一步步创建并让AI助手有了各种能力。
但是只会文字的AI助手,终究还是差了那么点人味不是么?
好在现在已经有了多模态模型,可以理解并输出图片、文字、音频、视频等各种内容。
接下来让我们借助多模态模型,让AI助手能根据情景(图片)随兴作诗,并进一步通过流式输出让AI助手的输出更"流畅"。
2-动手实践
2.1-前置条件
作为本篇的准备工作,你需要先完成《1-Spring AI手把手教程-亲手创造我的AI助手》
其它篇的内容,则不是必须。
2.2-准备多模态模型
之前用到的模型,都属于大语言模型
或嵌入模型
,今天我们需要用到多模态大模型
,通过搜索引擎、问AI助手或你能想到的任何方式,很容易找到支持图片的大模型。
作为示例,我们依然选择白嫖openrouter.ai
,通过搜索支持图片的免费模型,我们可以找到一堆免费模型,找一个替换application.yaml
配置文件中的模型:
yaml
spring:
ai:
openai:
base-url: "https://openrouter.ai/api"
api-key: "" #都白嫖了,你自己去注册并申请一个api-key吧
chat:
options:
# 千问图片理解模型
model: "qwen/qwen2.5-vl-32b-instruct:free"
2.3-编写代码
在我们原来的控制器类中添加根据图片作诗的API入口和代码:
java
@RestController
public class MyChatController {
// Spring AI聊天客户端
private final ChatClient chatClient;
// 注入ChatClient生成器,Spring Boot已经根据上文的配置自动为我们创建了一个生成器实例
public MyChatController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
//...
// 根据图片作诗的API入口
@PostMapping(path = "multimodal/pic-to-poe")
public String picToPoe(MultipartFile file) {
return chatClient.prompt()
.user(u -> u
.text("根据图片作一首诗")
.media(MimeTypeUtils.IMAGE_PNG, file.getResource()))
.call()
.content();
}
}
2.4-测试效果
使用你喜欢的http客户端测试效果,本文还是以curl为例:
shell
# 使用新添加的图片接口上传一张图片
curl -F "file=@ai-assistant/src/main/resources/multimodal.test.png" http://localhost:8080/multimodal/pic-to-poe
# AI助手的回复:
金篮静置果香浓,
黄蕉红果映晨风。
日常小景藏诗意,
清韵悠然入梦中。
可以看出AI助手根据我上传的图片内容,作出了一首七言律诗。诗意优雅,就是突然迸出四句诗,有点突兀。
接下来我们用流式输出的方式,让AI助手作诗的过程更"流畅":
2.5-编写流式输出代码
在原代码的基础上、再增加一种返回方式:
java
@RestController
public class MyChatController {
//...
// (Spring)流式输出
@PostMapping(path = "multimodal/pic-to-poe/stream")
public Flux<String> picToPoeStream(MultipartFile file) {
return chatClient.prompt()
.user(u -> u
.text("根据图片作一首诗")
.media(MimeTypeUtils.IMAGE_PNG, file.getResource()))
//与非流式输出的关键区别,是使用stream()代替call()
.stream()
.content();
}
}
2.6-测试流式输出
再次用工具测试以上接口,会发现AI助手的输出是一个字接一个字,看起来流畅很多:
shell
# 使用新添加的图片接口上传一张图片
curl -F "file=@ai-assistant/src/main/resources/multimodal.test.png" http://localhost:8080/multimodal/pic-to-poe/stream
# AI助手的回复:
金篮轻悬映晨光,
黄蕉红果溢芬芳。
静谧时光添雅趣,
一隅清欢入梦长。
纯文字展示不出实际效果,还希望各位亲自动手实践。
2.7-编写SSE输出代码
细心的小伙伴会发现,上面的流式输出格式,并不是我们编写前端聊天代码、或客户端工具时常用到的流式输出格式。
这是因为聊天工具中所谓的流式输出,实际上是指"服务器发送事件SSE"。
使用SSE的代码也不难:
java
@RestController
public class MyChatController {
//...
// 使用SSE流式输出
@PostMapping(path = "multimodal/pic-to-poe/sse")
public SseEmitter picToPoeSse(MultipartFile file) {
// 创建 SSE 发射器,设置超时时间(例如 1 分钟)
SseEmitter emitter = new SseEmitter(60_000L);
// 调用大模型
chatClient.prompt()
.user(u -> u
.text("根据图片作一首诗")
.media(MimeTypeUtils.IMAGE_PNG, file.getResource()))
//与非流式输出的关键区别,是使用stream()代替call()
.stream()
.content()
//与普通流式输出的区别,是每收到一次大模型的输出,都转换为服务器事件"发射"给客户端
.doOnEach(s-> {
try {
emitter.send(SseEmitter.event()
.data(s)
.id(String.valueOf(System.currentTimeMillis()))
.build());
} catch (IOException e) {
throw new RuntimeException(e);
}
})
// 注意大模型流结束时,也要告诉发射器结束了,否则客户端会继续等着你的输出
.doOnTerminate(emitter::complete)
.subscribe();
return emitter;
}
}
2.8-测试SSE输出
SSE主要用于代码集成,输出就没那么直观了:
shell
# 使用新添加的图片接口上传一张图片
curl -F "file=@ai-assistant/src/main/resources/multimodal.test.png" http://localhost:8080/multimodal/pic-to-poe/sse
# AI助手的回复(篇幅有限,下面省略了很多输出):
data:金
id:1753341478512
...
data:时光
id:1753341479023
...
data:。
id:1753341479515
3-课外拓展
- 验证更多的模型,看看哪个效果好;
- 使用不同的
temperature
参数,让输出更随机/确定; - 探索除图片外的更多多模型模型的输入输出,如:音视频输入、输出。