大模型通义千问3-VL-Plus - QVQ 视觉推理模型

目录

一、概论

二、代码实现

[第一步:新增请求实体类 QvqReasoningRequest](#第一步:新增请求实体类 QvqReasoningRequest)

[第二步:新增 QVQ 推理服务接口 QvqReasoningService](#第二步:新增 QVQ 推理服务接口 QvqReasoningService)

[第三步:新增 QVQ 推理服务实现类 QvqReasoningServiceImpl](#第三步:新增 QVQ 推理服务实现类 QvqReasoningServiceImpl)

[第四步 VideoController](#第四步 VideoController)

三、演示

五、视觉推理模型核心总结

[QVQ 视觉推理模型服务实现类(QvqReasoningServiceImpl)实现过程详细总结](#QVQ 视觉推理模型服务实现类(QvqReasoningServiceImpl)实现过程详细总结)

一、初始化准备:基础配置与线程模型搭建

[二、前置参数构建:贴合 SDK 规范封装输入](#二、前置参数构建:贴合 SDK 规范封装输入)

[三、流式 API 调用:对接 SDK 的流式能力](#三、流式 API 调用:对接 SDK 的流式能力)

[四、流式结果解析与 SSE 推送:拆分 "思考过程 + 最终回复"](#四、流式结果解析与 SSE 推送:拆分 “思考过程 + 最终回复”)

五、流式收尾与异常处理:保证服务健壮性

核心设计细节与适配点


一、概论

视觉推理模型能够先输出思考过程,再输出回答内容,适用于处理复杂的视觉分析任务,如解读数学题、分析图表数据或复杂视频理解等任务。

简单来说,视觉推理模型的核心特点是 **"先拆解逻辑,再给出结论"**------ 它不仅能完成复杂视觉分析任务,还能像人一样暴露背后的思考逻辑,彻底区别于普通视觉模型 "直接输出结果" 的模式,尤其适配需要深度逻辑推导的场景:

比如处理数学几何题时,它不会直接给出答案,而是先输出思考过程:"首先观察图片中的图形结构,识别出三角形的类型(等腰直角三角形)→ 提取已知条件(直角边长度为 5cm)→ 回忆勾股定理公式(a²+b²=c²)→ 代入数值计算斜边长度→ 验证计算结果是否符合图形比例",之后再给出明确的解题答案和最终结果;

分析图表数据(如柱状图、折线图)时,思考过程会是:"先确定图表类型为年度销售额折线图→ 解读横轴(年份 2020-2024)和纵轴(销售额单位万元)→ 提取各年份关键数据(2020 年 800 万、2021 年 1200 万...)→ 计算年度增长率(2021 年同比增长 50%)→ 分析增长趋势(2022-2023 年增速放缓)",再输出整合后的数据分析结论;

理解复杂视频(如事件类短视频、监控画面)时,思考过程会围绕 "事件顺序、因果关系" 展开:"先梳理视频帧中的关键场景(第 1 帧:车辆正常行驶,第 3 帧:行人横穿马路,第 5 帧:车辆刹车避让)→ 还原事件时间线(行人未走斑马线→ 司机发现后紧急刹车→ 未发生碰撞)→ 提炼核心事件(车辆避让违规横穿马路的行人)",最终给出完整的视频内容总结。

这种 "思考过程 + 最终答案" 的输出模式,让模型的决策逻辑可追溯、可解释,不仅能应对复杂视觉任务的深度分析需求,还能帮助用户理解结论的由来,尤其适合对逻辑严谨性要求高的场景(如教育解题、专业数据分析、事件溯源等)。

二、代码实现

以下是基于官方qvq-max模型(支持思考过程输出)的前后端分离后端接口实现,包含请求实体、服务接口、实现类、控制器,适配流式返回「思考过程 + 最终回复」

第一步:新增请求实体类 QvqReasoningRequest

java 复制代码
package gzj.spring.ai.Request;

import lombok.Data;
import javax.validation.constraints.NotBlank;

/**
 * QVQ模型推理请求参数(支持思考过程+最终回复流式输出)
 * @author DELL
 */
@Data
public class QvqReasoningRequest {
    /** 图片URL(支持HTTPS) */
    @NotBlank(message = "图片URL不能为空")
    private String imageUrl;

    /** 提问文本(如解题、分析图片等) */
    @NotBlank(message = "提问文本不能为空")
    private String question;

    /** 模型名称(默认qvq-max,需SDK≥2.19.0) */
    private String modelName = "qvq-max";
}

第二步:新增 QVQ 推理服务接口 QvqReasoningService

java 复制代码
package gzj.spring.ai.Service;

import com.alibaba.dashscope.exception.ApiException;
import com.alibaba.dashscope.exception.InputRequiredException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.alibaba.dashscope.exception.UploadFileException;
import gzj.spring.ai.Request.QvqReasoningRequest;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

/**
 * QVQ模型推理服务接口(支持思考过程流式输出)
 * @author DELL
 */
public interface QvqReasoningService {

    /**
     * QVQ模型流式推理(返回思考过程+最终回复)
     * @param request 推理请求参数
     * @return SseEmitter 用于前端接收流式结果(区分思考过程/最终回复)
     */
    SseEmitter streamReasoningCall(QvqReasoningRequest request);
}

第三步:新增 QVQ 推理服务实现类 QvqReasoningServiceImpl

java 复制代码
package gzj.spring.ai.Service.ServiceImpl;

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.InputRequiredException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.alibaba.dashscope.exception.UploadFileException;
import gzj.spring.ai.Request.QvqReasoningRequest;
import gzj.spring.ai.Service.QvqReasoningService;
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.*;

/**
 * QVQ模型推理服务实现(支持思考过程+最终回复流式输出)
 * 要求:dashscope SDK版本 ≥ 2.19.0
 * @author DELL
 */
@Slf4j
@Service
public class QvqReasoningServiceImpl implements QvqReasoningService {

    @Value("${dashscope.api-key}")
    private String apiKey;

    /**
     * 构建多模态消息(图片+文本)
     */
    private MultiModalMessage buildUserMessage(QvqReasoningRequest request) {
        return MultiModalMessage.builder()
                .role(Role.USER.getValue())
                .content(Arrays.asList(
                        Collections.singletonMap("image", request.getImageUrl()),
                        Collections.singletonMap("text", request.getQuestion())
                )).build();
    }

    /**
     * 构建请求参数(开启增量输出)
     */
    private MultiModalConversationParam buildConversationParam(QvqReasoningRequest request, MultiModalMessage userMsg) {
        return MultiModalConversationParam.builder()
                .apiKey(apiKey)
                .model(request.getModelName())
                .messages(Arrays.asList(userMsg))
                .incrementalOutput(true) // 增量输出(流式核心)
                .build();
    }

    /**
     * 处理流式结果,拆分思考过程和最终回复,推送到前端
     */
    private void handleStreamResult(MultiModalConversationResult result, SseEmitter emitter) {
        try {
            // 1. 处理思考过程(reasoningContent)
            String reasoning = Optional.ofNullable(result.getOutput().getChoices().get(0).getMessage().getReasoningContent()).orElse("");
            if (!reasoning.isEmpty()) {
                // 推送思考过程(事件名:reasoning)
                emitter.send(SseEmitter.event().name("reasoning").data(reasoning));
                log.debug("推送思考过程片段:{}", reasoning);
            }

            // 2. 处理最终回复(text)
            List<Map<String, Object>> content = result.getOutput().getChoices().get(0).getMessage().getContent();
            if (content != null && !content.isEmpty()) {
                String text = Optional.ofNullable(content.get(0).get("text")).orElse("").toString();
                if (!text.isEmpty()) {
                    // 推送最终回复(事件名:answer)
                    emitter.send(SseEmitter.event().name("answer").data(text));
                    log.debug("推送回复片段:{}", text);
                }
            }
        } catch (Exception e) {
            log.error("流式结果处理失败", e);
            handleEmitterError(emitter, "结果推送失败:" + e.getMessage());
        }
    }

    /**
     * 统一处理SSE发射器异常
     */
    private void handleEmitterError(SseEmitter emitter, String errorMsg) {
        try {
            emitter.send(SseEmitter.event().name("error").data(errorMsg));
            emitter.completeWithError(new RuntimeException(errorMsg));
        } catch (Exception e) {
            log.error("发射器异常处理失败", e);
        }
    }

    /**
     * QVQ模型流式推理(核心方法)
     */
    @Override
    public SseEmitter streamReasoningCall(QvqReasoningRequest request) {
        // 设置超时时间60秒(推理类任务耗时更长)
        SseEmitter emitter = new SseEmitter(60000L);

        new Thread(() -> {
            MultiModalConversation conv = new MultiModalConversation();
            try {
                // 1. 构建用户消息和请求参数
                MultiModalMessage userMsg = buildUserMessage(request);
                MultiModalConversationParam param = buildConversationParam(request, userMsg);

                // 2. 流式调用API
                Flowable<MultiModalConversationResult> resultFlow = conv.streamCall(param);
                resultFlow.blockingForEach(result -> handleStreamResult(result, emitter));

                // 3. 流式结束标记
                emitter.send(SseEmitter.event().name("complete").data("推理完成"));
                emitter.complete();

            } catch (ApiException | NoApiKeyException | UploadFileException | InputRequiredException e) {
                log.error("QVQ模型API调用失败", e);
                handleEmitterError(emitter, "API调用失败:" + e.getMessage());
            } catch (Exception e) {
                log.error("QVQ模型流式推理未知异常", e);
                handleEmitterError(emitter, "系统异常:" + e.getMessage());
            }
        }).start();

        return emitter;
    }
}

第四步 VideoController

java 复制代码
package gzj.spring.ai.Controller;

import com.alibaba.dashscope.exception.ApiException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.alibaba.dashscope.exception.UploadFileException;
import gzj.spring.ai.Request.QvqReasoningRequest;
import gzj.spring.ai.Request.VideoFrameListRequest;
import gzj.spring.ai.Request.VideoRequest;
import gzj.spring.ai.Service.QvqReasoningService;
import gzj.spring.ai.Service.VideoFrameListService;
import gzj.spring.ai.Service.VideoService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

/**
 * @author DELL
 */
@RestController
@RequestMapping("/api/multimodal/video")
@RequiredArgsConstructor
@CrossOrigin // 跨域支持(生产环境建议限定域名)
public class VideoController {

    private final VideoService videoService;
    private final VideoFrameListService videoFrameListService;
    private final QvqReasoningService qvqReasoningService;


    @RequestMapping("/simple")
    public String simpleVideoCall(@RequestBody VideoRequest request) throws ApiException, NoApiKeyException, UploadFileException {
        return videoService.simpleVideoCall(request);
    }



    /**
     * 视频帧列表理解-普通调用(非流式)
     */
    @PostMapping("/simpleFrame")
    public String simpleFrameListCall(@Validated @RequestBody VideoFrameListRequest request)
            throws ApiException, NoApiKeyException, UploadFileException {
        return videoFrameListService.simpleFrameListCall(request);
    }

    /**
     * 视频帧列表理解-流式调用(SSE推送)
     */
    @PostMapping("/streamFrame")
    public SseEmitter streamFrameListCall(@Validated @RequestBody VideoFrameListRequest request) {
        return videoFrameListService.streamFrameListCall(request);
    }


    /**
     * QVQ模型流式推理接口
     * @param request 推理请求参数(图片URL+提问文本)
     * @return SseEmitter 流式返回结果
     */
    @PostMapping("/stream-reasoning")
    public SseEmitter streamReasoning(@Validated @RequestBody QvqReasoningRequest request) {
        return qvqReasoningService.streamReasoningCall(request);
    }

}

三、演示

参postman参数:

java 复制代码
{
  "imageUrl": "https://img.alicdn.com/imgextra/i1/O1CN01gDEY8M1W114Hi3XcN_!!6000000002727-0-tps-1024-406.jpg",
  "question": "请解答这道题",
  "modelName": "qvq-max"
}

结果:

一大串因为是流式的,后续我们更新前端的页面就可以看到了,总之测试结果没有问题。

五、视觉推理模型核心总结

QVQ 视觉推理模型服务实现类(QvqReasoningServiceImpl)实现过程详细总结

该实现类是视觉推理模型(QVQ 系列)工程化落地的核心层,基于 DashScope SDK(≥2.19.0)将官方控制台示例转化为适配前后端分离的 SSE 流式服务,完整实现 "思考过程 + 最终回复" 的拆分推送,其实现过程可拆解为初始化准备、参数构建、流式调用、结果解析推送、收尾与异常处理五大核心阶段,具体如下:

一、初始化准备:基础配置与线程模型搭建
  1. 依赖注入与基础配置 :通过@Value注入 DashScope 的 API Key,作为调用模型的身份凭证;定义 SSE 发射器(SseEmitter)超时时间为 60 秒(适配视觉推理(如数学题解读)的长耗时特性,避免过早断开连接)。
  2. 线程隔离设计:创建独立线程处理流式调用逻辑,避免 SSE 推送阻塞 Web 容器的请求线程,保证接口响应性(符合 Spring Web 的线程模型规范)。
二、前置参数构建:贴合 SDK 规范封装输入

核心是将前端请求参数转化为 DashScope SDK 要求的格式,分为两步:

  1. 构建用户多模态消息 :通过buildUserMessage方法,将请求中的imageUrl(图片 HTTPS 链接)和question(提问文本)封装为MultiModalMessage,指定角色为USER,内容按 "图片 + 文本" 的列表格式组织,完全对齐 SDK 的消息结构要求。
  2. 构建请求参数 :通过buildConversationParam方法,基于MultiModalConversationParam构建器配置核心参数 ------API Key、模型名(默认qvq-max,支持请求自定义)、消息列表,并开启incrementalOutput(true)(增量输出),确保模型返回流式结果而非一次性返回。
三、流式 API 调用:对接 SDK 的流式能力
  1. 实例化MultiModalConversation客户端,调用streamCall方法传入构建好的参数,获取Flowable<MultiModalConversationResult>类型的流式结果(RxJava 流,适配 SDK 的响应式设计);
  2. 通过blockingForEach遍历流式结果,逐段处理模型返回的每一批数据(增量输出的核心载体)。
四、流式结果解析与 SSE 推送:拆分 "思考过程 + 最终回复"

这是视觉推理模型 "先思考、后回答" 核心特性的工程化落地,通过handleStreamResult方法实现精细化解析:

  1. 思考过程解析推送 :提取结果中的ReasoningContent字段(QVQ 模型专属,需 SDK≥2.19.0),通过Optional.ofNullable做空值保护,非空时以 SSE 事件名reasoning推送(前端可单独监听思考过程);
  2. 最终回复解析推送 :解析结果中content列表的text字段,同样做空值保护,非空时以 SSE 事件名answer推送(与思考过程拆分,便于前端差异化渲染);
  3. 日志埋点:为每段推送的思考过程 / 回复片段添加调试日志,便于问题排查。
五、流式收尾与异常处理:保证服务健壮性
  1. 正常收尾 :所有流式结果处理完成后,推送complete事件(内容为 "推理完成"),调用emitter.complete()关闭 SSE 连接,释放资源;
  2. 异常分层处理
    • 捕获 SDK 专属异常(ApiException/NoApiKeyException/UploadFileException等):定位模型调用层面的问题(如 API Key 错误、图片无法读取);
    • 捕获通用异常(Exception):兜底处理未知错误;
    • 异常标准化推送:通过抽离的handleEmitterError工具方法,统一推送error事件(携带错误信息),调用emitter.completeWithError()标记异常结束,保证前端能感知错误类型;
  3. 工具方法复用:将 SSE 异常处理逻辑抽离为独立方法,减少代码冗余,保证异常处理逻辑的一致性。
核心设计细节与适配点
  1. 事件拆分设计 :按reasoning/answer/error/complete四类 SSE 事件拆分输出,精准贴合视觉推理模型 "先输出思考过程、再输出回答" 的核心特性,前端可按需监听不同事件实现差异化渲染;
  2. 健壮性保障 :全链路空值保护(Optional)、分层异常捕获,避免空指针或单一异常导致服务崩溃;
  3. 扩展性设计:模型名通过请求参数自定义,无需修改代码即可切换 QVQ 系列其他模型;
  4. 工程化适配:完全贴合 Spring Boot 服务层规范,可直接集成到现有多模态服务体系中,与此前的图片 / 视频理解服务保持一致的代码风格和异常处理逻辑。

综上,该实现类的核心价值是将 QVQ 视觉推理模型的 "思考 + 回答" 能力从官方示例的控制台输出,转化为工程化、可复用、适配前后端分离的 SSE 流式服务,既保留了模型的核心特性,又符合企业级后端开发的规范与健壮性要求。

相关推荐
nnsix2 小时前
Unity ReferenceFinder插件 窗口中选择资源时 同步选择Assets下的资源
java·unity·游戏引擎
中华网商业2 小时前
从制造到智造!格力金湾领航级智能工厂的升级路径与经验启示
人工智能·制造
天天摸鱼的java工程师2 小时前
🚪单点登录实战:同端同账号互踢下线的最佳实践(Java 实现)
java·后端
数据的世界012 小时前
重构智慧书-第12条:自然与人工的辩证之美
人工智能
爱写代码的小朋友2 小时前
AI赋能的混合式教育模式中师生角色重构与互动机制研究
人工智能
Kiri霧2 小时前
Go 结构体
java·开发语言·golang
AI即插即用2 小时前
即插即用系列 | MICCAI EM-Net:融合 Mamba 与频域学习的高效 3D 医学图像分割网络
网络·人工智能·深度学习·神经网络·学习·计算机视觉·视觉检测
狂奔小菜鸡2 小时前
Day29 | Java集合框架之Map接口详解
java·后端·java ee
阿杰学AI2 小时前
AI核心知识53——大语言模型之Structured CoT 超级模版(简洁且通俗易懂版)
人工智能·ai·语言模型·prompt·提示词·pe·structured cot