一、概论
Qwen3系列视觉理解模型,实现思考模式和非思考模式的有效融合,视觉智能体能力在OS World等公开测试集上达到世界顶尖水平。此版本在视觉coding、空间感知、多模态思考等方向全面升级;视觉感知与识别能力大幅提升,支持超长视频理解。
简单来说,阿里 Qwen3 系列视觉理解模型核心亮点如下:
- 模式灵活:能同时用 "思考模式"(解复杂问题,比如数学推理)和 "非思考模式"(快速响应,比如简单识图),不用单独切换模型;
- 会 "动手":能操作电脑 / 手机界面(比如点按钮、填表单),在 OS World 这类权威测试里拿了世界顶尖成绩;
- 能力升级:视觉 coding(图片转网页代码)、空间感知(判断物体位置 / 3D 关系)、多模态思考(结合图文推理)都比之前强;
- 看得更准:能认的物体更多,从日常东西到专业领域(比如动植物、产品型号)都能识别;
- 能 "追长视频":原生支持看几小时长视频,还能精准找到里面的细节(比如某句话在几分几秒)。
二、准备
如果之前已经有申请过 API-key 的同学,我们只需要复制一下视觉模型的model就可以了

如果没有的同学,可以看我这篇文章:Vue3+Springboot3+千问plus流式(前后端分离)_博客 springboot+vue-CSDN博客
完成第一步的 API 获取,然后将 key 设置到环境变量或者配置文件中就可以啦。
三、代码实现
1. 依赖
后续设计到电脑操作的依赖我们再另外添加,下面这些是一些基本的依赖,其中最重要的是SDK的引入,方便我们使用。
java
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.9-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>gzj.spring</groupId>
<artifactId>ai</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ai</name>
<description>ai</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
<java.json>2.0.32</java.json>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring AI Alibaba Agent Framework -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-agent-framework</artifactId>
<version>1.1.0.0-M5</version>
</dependency>
<!-- DashScope ChatModel 支持(如果使用其他模型,请参考文档选择对应的 starter) -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>1.1.0.0-M5</version>
</dependency>
<!-- 新增Redis依赖(Spring Boot整合版) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 可选:Redis连接池(提升性能) -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dashscope-sdk-java</artifactId>
<version>2.22.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${java.json}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-model</artifactId>
<version>1.1.0-M4</version>
</dependency>
<!-- RxJava(流式调用) -->
<dependency>
<groupId>io.reactivex.rxjava3</groupId>
<artifactId>rxjava</artifactId>
<version>3.1.8</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</pluginRepository>
</pluginRepositories>
</project>
2. 配置文件(application.yml)
java
server:
port: 8080
spring:
servlet:
multipart:
enabled: true
max-file-size: 100MB
max-request-size: 100MB
application:
name: ai
ai:
dashscope:
model: qwen-plus
modelV1: qwen3-vl-plus
api-key: ${DASHSCOPE_API_KEY}
agent:
options:
app-id: 0bc97a579fdb4c2880acc
其中 ,max-file-size 设置的是当个文件上传的最大值,max-request-size 设置的是整个HTTP请求的最大值(包括文件以及各类请求体)
3. 请求参数 DTO(MultimodalRequest.java)
java
import lombok.Data;
@Data
public class MultimodalRequest {
/** 图片URL */
private String imageUrl;
/** 提问文本 */
private String question;
}
由于后续我们要添加更多的新功能,所以目前我们就先搞个 req 类型的数据了(res 是出去的数据,req 是进来的数据)
4. 核心服务类()
4.1 MultimodalService
java
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.alibaba.dashscope.exception.UploadFileException;
import gzj.spring.ai.Request.MultimodalRequest;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
/**
* @author DELL
*/
public interface MultimodalService {
public SseEmitter streamCall(MultimodalRequest request);
public String simpleCall(MultimodalRequest request) throws NoApiKeyException, UploadFileException;
}
4.2 MultimodalServiceImpl
核心逻辑:
SseEmitter:Spring 提供的 SSE(Server-Sent Events)工具,用于服务端向前端单向推送数据(无需前端轮询);- 新线程处理:流式调用是阻塞的,开启独立线程避免占用 Spring MVC 的请求线程池;
incrementalOutput(true):百炼 SDK 的流式开关,开启后模型会分段返回回答(比如一句话拆成多次返回);resultFlow.blockingForEach:遍历流式结果,每收到一段就通过emitter.send推送给前端;- 异常 / 结束处理:推送错误事件、完成事件,确保连接正常关闭。
java
import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation;
import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam;
import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult;
import com.alibaba.dashscope.common.MultiModalMessage;
import com.alibaba.dashscope.common.Role;
import com.alibaba.dashscope.exception.ApiException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.alibaba.dashscope.exception.UploadFileException;
import com.alibaba.dashscope.utils.Constants;
import gzj.spring.ai.Request.MultimodalRequest;
import gzj.spring.ai.Service.MultimodalService;
import io.reactivex.Flowable;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* 多模态服务封装
* @author DELL
*/
@Slf4j
@Service
public class MultimodalServiceImpl implements MultimodalService {
@Value("${dashscope.api-key}")
private String apiKey;
/**
* 普通调用(非流式)
*/
@Override
public String simpleCall(MultimodalRequest request) throws ApiException, IllegalAccessError, NoApiKeyException, UploadFileException {
MultiModalConversation conv = new MultiModalConversation();
// 构建用户消息(图片+文本)
MultiModalMessage userMessage = MultiModalMessage.builder()
.role(Role.USER.getValue())
.content(Arrays.asList(
Collections.singletonMap("image", request.getImageUrl()),
Collections.singletonMap("text", request.getQuestion())
)).build();
// 构建请求参数
MultiModalConversationParam param = MultiModalConversationParam.builder()
.apiKey(apiKey)
.model("qwen3-vl-plus")
.messages(Arrays.asList(userMessage))
.build();
// 同步调用
MultiModalConversationResult result = conv.call(param);
// 解析返回结果
List<Map<String, Object>> content = result.getOutput().getChoices().get(0).getMessage().getContent();
if (content != null && !content.isEmpty()) {
return content.get(0).get("text").toString();
}
return "未获取到有效结果";
}
/**
* 流式调用(SSE推送)
*/
public SseEmitter streamCall(MultimodalRequest request) {
// 设置超时时间30秒
SseEmitter emitter = new SseEmitter(30000L);
new Thread(() -> {
MultiModalConversation conv = new MultiModalConversation();
// 构建用户消息
MultiModalMessage userMessage = MultiModalMessage.builder()
.role(Role.USER.getValue())
.content(Arrays.asList(
Collections.singletonMap("image", request.getImageUrl()),
Collections.singletonMap("text", request.getQuestion())
)).build();
// 构建请求参数
MultiModalConversationParam param = MultiModalConversationParam.builder()
.apiKey(apiKey)
.model("qwen3-vl-plus")
.messages(Arrays.asList(userMessage))
.incrementalOutput(true) // 增量输出(流式)
.build();
try {
// 流式调用
Flowable<MultiModalConversationResult> resultFlow = conv.streamCall(param);
resultFlow.blockingForEach(item -> {
try {
List<Map<String, Object>> content = item.getOutput().getChoices().get(0).getMessage().getContent();
if (content != null && !content.isEmpty()) {
String text = content.get(0).get("text").toString();
// 推送流式数据到前端
emitter.send(SseEmitter.event().data(text));
}
} catch (Exception e) {
log.error("流式推送失败", e);
try {
emitter.send(SseEmitter.event().name("error").data(e.getMessage()));
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
// 流式结束标记
emitter.send(SseEmitter.event().name("complete").data("流结束"));
emitter.complete();
} catch (ApiException | NoApiKeyException | UploadFileException e) {
log.error("流式调用失败", e);
try {
emitter.send(SseEmitter.event().name("error").data(e.getMessage()));
emitter.completeWithError(e);
} catch (Exception ex) {
ex.printStackTrace();
}
} catch (Exception e) {
log.error("未知异常", e);
try {
emitter.completeWithError(e);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}).start();
return emitter;
}
}
5. 接口层(MultimodalController)
java
import gzj.spring.ai.Request.MultimodalRequest;
import gzj.spring.ai.Service.MultimodalService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
/**
* 多模态接口控制器
*/
@RestController
@RequestMapping("/api/multimodal")
@RequiredArgsConstructor
@CrossOrigin // 跨域支持(生产环境建议限定域名)
public class MultimodalController {
private final MultimodalService multimodalService;
/**
* 普通调用接口(非流式)
*/
@PostMapping("/simple")
public ResponseEntity<String> simpleCall(@RequestBody MultimodalRequest request) {
try {
String result = multimodalService.simpleCall(request);
return ResponseEntity.ok(result);
} catch (Exception e) {
return ResponseEntity.badRequest().body("调用失败:" + e.getMessage());
}
}
/**
* 流式调用接口(SSE)
*/
@PostMapping("/stream")
public SseEmitter streamCall(@RequestBody MultimodalRequest request) {
return multimodalService.streamCall(request);
}
}
四、关键事项
- API Key 安全:生产环境不要硬编码 API Key,建议通过环境变量、配置中心管理。
- 跨域配置 :生产环境需限定允许跨域的域名,不要直接用
@CrossOrigin(可配置全局 CORS)。 - 地域适配 :若使用新加坡地域模型,需在
application.yml中配置dashscope.base-url为https://dashscope-intl.aliyuncs.com/api/v1。 - 流式调用 SSE:前端 EventSource 默认是 GET 请求,若需 POST,可改用 axios 的流式响应(或使用 fetch API)。
- 异常处理:后端增加全局异常处理器,前端完善错误提示。
- 模型替换 :可将
qwen3-vl-plus替换为其他多模态模型(如qwen-vl-max),需确认模型名称正确性。
五、总结
1.整个过程提供两种调用方式
- 「普通同步调用」:一次性返回完整的模型回答;
- 「流式 SSE 调用」:分段向前端推送模型回答(适合大文本、实时性要求高的场景);整体用于处理「图片 + 文本」的多模态问答请求(比如上传图片 URL,提问 "图里有什么?",返回模型的回答)。
2.核心逻辑:
- 组装「图片 URL + 文本问题」的用户消息,符合百炼多模态接口的参数格式;
- 同步调用
conv.call(param):阻塞当前线程,直到模型返回完整回答; - 解析结果:百炼返回的结果是嵌套结构,逐层提取
content中的文本内容,最终返回给调用方(如 Controller)
3.注意点:
- 方法抛出的
IllegalAccessError是「Error 类型」(系统级错误,如 JVM 类加载异常),不是业务异常,后续我会优化; - 仅处理单轮对话(消息列表只有用户消息),多轮对话需补充历史消息(ASSISTANT 角色的回复),这个我们后续和电脑操控的功能结合起来。
六、演示


注意,这里的 URL 我们暂时调用的是网上的,如果要调用本地的图片的话需要另外的写法,我会在下一篇使用 本地图片 的方式。
如果你觉得我写的还ok,可以关注我看后续的文章。