news 2026/4/28 23:22:56

大模型通义千问3-VL-Plus - 视觉推理(本地图片)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
大模型通义千问3-VL-Plus - 视觉推理(本地图片)

一、概论

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

简单来说,视觉推理是人工智能的一个分支,核心是让模型像人一样 “看懂” 图像并进行逻辑分析,而非仅识别表面元素。只要训练好智能体,你甚至可以拍摄一个人的样貌,然后智能体可以根据知识库来判断出一个人的心理活动(当然,这需要大量的训练)

二、视觉推理模型核心技术与典型应用清单

核心是通过 “图像理解 + 逻辑推理” 实现复杂视觉任务处理,以下是关键技术与落地场景梳理:

1、核心技术(支撑模型推理能力)

  • 多模态融合技术:将图像像素信息与文字、数学符号等跨模态数据关联,打破单一模态的信息局限。
  • 视觉元素解析技术:精准提取图像中的关键元素,如图表的坐标轴 / 数据点、数学题的图形条件、视频的场景物体。
  • 逻辑推理引擎:基于提取的信息搭建推理链条,处理因果、逻辑运算、数据关联等复杂关系。
  • 可解释性模块:专门生成推理过程,把 “如何从图像到结论” 的步骤清晰输出,提升结果可信度。

2、典型应用场景(落地价值体现)

  • 教育领域:解读数学几何题、物理示意图,拆解解题条件与推导步骤。
  • 数据处理领域:分析柱状图 / 折线图 / 饼图,不仅提取数字,还计算增长率、占比、趋势变化。
  • 视频分析领域:理解长视频中的事件逻辑,如监控视频中异常行为的因果推导、影视剧情的关键情节梳理。
  • 医疗领域:辅助解读医学影像,结合病灶形态与临床知识,推理可能病症及诊断依据。
  • 工业领域:质检时识别产品缺陷,同时推理缺陷产生的可能环节(如零件装配偏差、材料问题)。

三、代码实现

在前文 大模型通义千问3-VL-Plus - 视觉理解-CSDN博客 中我们已经简单的写了一个需要https图片链接的视觉理解功能,现在我们改造一下,要做到可以上传本地文件。

1、Base64

通义千问VL 提供两种本地文件上传方式:Base64 编码上传和文件路径直接上传。可根据文件大小、SDK类型选择上传方式.

两者的区别:

Base64编码:将文件转换为 Base64 编码字符串,再传入模型。适用于 OpenAI 和 DashScope SDK 及 HTTP 方式

本地文件路径:直接向模型传入本地文件路径,仅 DashScope Python 和 Java SDK 支持,不支持 DashScope HTTP 和OpenAI 兼容方式。

这里为了方便各位后续可以使用 OpenAI 的内容,所以我们直接用 Base64编码,当然我也会给出本地文件路径的核心修改代码,但是就不做演示啦。

注意:通义千问VL API对单张图像编码后的视觉 Token 数量设有限制,默认配置下,高分辨率图像会被压缩,可能丢失细节,影响理解准确性。启用vl_high_resolution_images或调整max_pixels可增加视觉 Token 数量,从而保留更多图像细节,提升理解效果。

1.1 调整请求参数实体MultimodalRequest

新增本地图片路径字段,用于接收本地文件路径(二选一:imageUrl 传网络图片,localImagePath 传本地图片)

import lombok.Data; @Data public class MultimodalRequest { /** 图片URL */ private String imageUrl; /** 提问文本 */ private String question; /** 本地图片路径(如:/usr/local/images/test.png 或 D:/images/test.jpg) */ private String localImagePath; }

1.2 修改核心服务类MultimodalServiceImpl

核心修改说明

  1. 新增工具方法

    • encodeLocalImageToBase64:读取本地图片文件并转换为带data:image/xxx;base64,前缀的 Base64 字符串(兼容 png/jpg/jpeg 格式)
    • buildImageContent:统一处理图片来源(优先本地文件,其次网络 URL),避免重复逻辑
    • handleEmitterError:统一处理 SSE 发射器的异常推送,简化代码
  2. 兼容两种图片来源

    • 通过判断localImagePath/imageUrl是否为空,自动选择图片处理方式
    • 本地图片:转 Base64 后传入 API;网络图片:直接传入 URL
  3. 完善异常处理

    • 新增IOException捕获(本地文件读取失败)
    • 新增参数校验(必须传入图片来源)
    • 流式调用中统一异常处理,确保前端能收到错误信息
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 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.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; import java.util.Base64; /** * 多模态服务封装(支持网络图片URL + 本地图片文件) * @author DELL */ @Slf4j @Service public class MultimodalServiceImpl implements MultimodalService { @Value("${dashscope.api-key}") private String apiKey; /** * 工具方法:将本地图片编码为Base64字符串(带data:image前缀) * @param localPath 本地图片绝对路径 * @return 带前缀的Base64字符串 * @throws IOException 文件读取异常 */ private String encodeLocalImageToBase64(String localPath) throws IOException { Path imagePath = Paths.get(localPath); // 校验文件是否存在 if (!Files.exists(imagePath)) { throw new IOException("本地图片文件不存在:" + localPath); } // 读取文件并编码 byte[] imageBytes = Files.readAllBytes(imagePath); String base64Str = Base64.getEncoder().encodeToString(imageBytes); // 自动识别图片格式(支持png/jpg/jpeg) String suffix = localPath.substring(localPath.lastIndexOf(".") + 1).toLowerCase(); if (!Arrays.asList("png", "jpg", "jpeg").contains(suffix)) { suffix = "png"; // 默认png格式 } return String.format("data:image/%s;base64,%s", suffix, base64Str); } /** * 工具方法:构建图片内容(兼容URL/本地文件) * @param request 请求参数 * @return 图片内容字符串(URL或Base64) * @throws IOException 本地文件读取异常 */ private String buildImageContent(MultimodalRequest request) throws IOException { // 优先级:本地文件 > 网络URL(可根据业务调整) if (request.getLocalImagePath() != null && !request.getLocalImagePath().isEmpty()) { log.info("使用本地图片:{}", request.getLocalImagePath()); return encodeLocalImageToBase64(request.getLocalImagePath()); } else if (request.getImageUrl() != null && !request.getImageUrl().isEmpty()) { log.info("使用网络图片URL:{}", request.getImageUrl()); return request.getImageUrl(); } else { throw new IllegalArgumentException("必须传入imageUrl(网络图片)或localImagePath(本地图片)"); } } /** * 普通调用(非流式)- 支持网络/本地图片 */ @Override public String simpleCall(MultimodalRequest request) throws ApiException, NoApiKeyException, UploadFileException, IOException { MultiModalConversation conv = new MultiModalConversation(); // 构建图片内容(兼容URL/本地文件) String imageContent = buildImageContent(request); // 构建用户消息(图片+文本) MultiModalMessage userMessage = MultiModalMessage.builder() .role(Role.USER.getValue()) .content(Arrays.asList( Collections.singletonMap("image", imageContent), 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推送)- 支持网络/本地图片 */ @Override public SseEmitter streamCall(MultimodalRequest request) { // 设置超时时间30秒 SseEmitter emitter = new SseEmitter(30000L); new Thread(() -> { MultiModalConversation conv = new MultiModalConversation(); try { // 构建图片内容(兼容URL/本地文件) String imageContent = buildImageContent(request); // 构建用户消息 MultiModalMessage userMessage = MultiModalMessage.builder() .role(Role.USER.getValue()) .content(Arrays.asList( Collections.singletonMap("image", imageContent), Collections.singletonMap("text", request.getQuestion()) )).build(); // 构建请求参数 MultiModalConversationParam param = MultiModalConversationParam.builder() .apiKey(apiKey) .model("qwen3-vl-plus") .messages(Arrays.asList(userMessage)) .incrementalOutput(true) // 增量输出(流式) .build(); // 流式调用 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) { log.error("推送错误信息失败", ex); } } }); // 流式结束标记 emitter.send(SseEmitter.event().name("complete").data("流结束")); emitter.complete(); } catch (IOException e) { log.error("读取本地图片失败", e); handleEmitterError(emitter, "读取本地图片失败:" + e.getMessage()); } catch (ApiException | NoApiKeyException | UploadFileException e) { log.error("多模态API调用失败", e); handleEmitterError(emitter, "API调用失败:" + e.getMessage()); } catch (IllegalArgumentException e) { log.error("请求参数异常", e); handleEmitterError(emitter, "参数错误:" + e.getMessage()); } catch (Exception e) { log.error("流式调用未知异常", e); handleEmitterError(emitter, "系统异常:" + e.getMessage()); } }).start(); return emitter; } /** * 工具方法:统一处理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); } } }

2、 本地路径

如果只做传本地路径,只需要修改以下内容

四、效果示例

五、总结

本次代码修改围绕让多模态服务同时兼容 HTTPS 网络图片和本地图片文件展开,核心总结如下:

一、核心目标

在保留原有 “普通调用(非流式)” 和 “流式调用(SSE 推送)” 能力的基础上,新增本地图片处理能力,实现「网络图片 URL」和「本地图片文件」两种输入方式的无缝兼容。

二、关键改动

  1. 请求参数扩展MultimodalRequest新增localImagePath字段,与原有imageUrl字段配合,分别接收本地图片绝对路径、HTTPS 图片 URL(二选一传入)。

  2. 新增核心工具方法

    • encodeLocalImageToBase64:读取本地图片文件,校验文件存在性,自动识别 png/jpg/jpeg 格式并转换为带data:image/xxx;base64,前缀的 Base64 字符串(符合通义千问 VL 模型要求);
    • buildImageContent:统一处理图片来源(优先本地文件,其次网络 URL,可灵活调整优先级),参数校验确保必传图片来源;
    • handleEmitterError:流式调用中统一处理异常,向前端推送标准化错误信息,简化异常处理逻辑。
  3. 核心业务方法改造

    • simpleCall/streamCall均接入buildImageContent方法,无需改动原有模型调用、参数构建、结果解析逻辑,仅扩展图片内容构建环节;
    • 新增IOException(文件读取)、IllegalArgumentException(参数缺失)等异常捕获,覆盖本地图片处理的异常场景。

三、核心能力

  1. 双源兼容:调用时只需传入imageUrl(网络图)或localImagePath(本地图),无需修改调用逻辑;
  2. 格式自适应:自动识别本地图片格式,默认兜底 png,避免格式不兼容问题;
  3. 异常友好:文件不存在、参数缺失、API 调用失败等场景均有明确错误提示,流式调用错误可实时推送到前端。

四、使用与注意事项

  1. 使用方式
    • 网络图片:传imageUrl字段,保持原有调用逻辑;
    • 本地图片:传localImagePath字段(需绝对路径),自动转 Base64 后调用 API。
  2. 关键注意
    • 本地图片需保证应用进程有文件读取权限;
    • 图片大小需符合通义千问 VL 模型要求(单张≤10MB);
    • Linux/Windows 路径分隔符需适配(Linux 用/,Windows 用\\/);
    • 图片源优先级可通过修改buildImageContent的判断逻辑调整。

希望这个兼容网络图 + 本地图的多模态服务小改造,能帮到正在折腾 AI 开发的你呀~✨ 代码跑通的瞬间是不是超有成就感!

如果觉得这份修改实用、总结清晰,别忘了动动小手点个赞👍,再关注一下呀~ 后续还会分享更多 AI 接口封装、代码优化的干货技巧,一起解锁更多好用的功能,少踩坑多提效!🥰 你的支持就是我更新的最大动力,咱们下次分享再见呀~🌟

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 6:04:26

Profiling 专项

Profiling 工具 https://github.com/iovisor/bcc

作者头像 李华
网站建设 2026/4/28 5:27:34

如何完成一个方便简单的Arduino共阳极数码管实验(从0~9依次循环亮起)

文章目录 实验演示共阴极数码管和共阳极数码管的区别所需器材连接草图程序代码代码说明代码功能概述核心数据结构关键函数逻辑 小结 实验演示 共阴极数码管和共阳极数码管的区别 在开始实验之前&#xff0c;请让我简单解释一下共阴极数码管和共阳极数码管的区别&#xff0c;这…

作者头像 李华
网站建设 2026/4/23 13:39:39

Sniffnet容器化部署终极指南:3步搞定网络流量监控

还在为复杂的网络分析工具配置头疼吗&#xff1f;Sniffnet容器化部署让你在5分钟内拥有专业级网络流量分析能力&#xff01;告别环境依赖冲突&#xff0c;开启零基础网络分析新时代 &#x1f680; 【免费下载链接】sniffnet Sniffnet 是一个能让你轻松监测网络流量的应用。你可…

作者头像 李华
网站建设 2026/4/24 4:16:48

基于Python+Django的毕业设计选题系统(源码+lw+部署文档+讲解等)

课题介绍本课题聚焦高校毕业设计选题环节的管理痛点&#xff0c;设计实现一套基于 PythonDjango 框架的毕业设计选题系统。传统毕业设计选题多依赖线下提交、人工统计&#xff0c;易出现选题冲突、信息不对称、流程效率低等问题&#xff0c;难以适配高校规模化教学管理需求。系…

作者头像 李华
网站建设 2026/4/17 7:43:45

IO相关函数多种类型的拷贝

一&#xff1a;将1.txt一半拷贝给2.txt&#xff0c;一半拷贝给3.txt 使用多个.c 使用makefile完成。main.c#include "fun.h"int main(int argc, const char *argv[]){cope1();return 0;}fan.c#include "fun.h"void cope1(){FILE *fp1fopen("1.txt&quo…

作者头像 李华