Skip to content

图片多模态输入 ​

随着多模态模型的发展,越来越多的业务场景需要直接向大模型输入图片,并结合文本指令完成任务,例如:

  • 图片文字识别(OCR)
  • 图片内容理解与描述
  • 图表、试题、合同、截图等内容解析
  • 图片 + 文本混合推理(VLM / Vision-Language Model)

java-openai 在统一聊天抽象的基础上,提供了统一的图片输入模型,使业务侧可以用一致的方式对接支持视觉能力的模型,而无需关心各平台在图片参数、编码方式上的差异。


设计思路概述 ​

在 java-openai 中,图片输入并不是一个“独立 API”,而是作为 消息的一部分 存在:

  • 图片被抽象为 ChatImageFile
  • 图片与文本一起挂载到 UniChatMessage
  • 通过 UniChatRequest 统一发送
  • 由底层平台适配层转换为对应模型所需的协议格式

这使得以下场景成为可能:

  • 纯图片任务(只发图片)
  • 图片 + 指令(如“识别图片中的文字”)
  • 图片 + 多轮对话上下文

示例:图片文字识别接口 ​

下面示例实现了一个 HTTP 接口:

  • 客户端通过 multipart/form-data 上传图片
  • 服务端将图片转换为 Base64
  • 使用支持视觉能力的模型进行推理
  • 返回模型识别出的文本内容

示例代码 ​

java
package nexus.io.tio.web.hello.handler;

import java.util.ArrayList;
import java.util.List;

import nexus.io.chat.ChatImageFile;
import nexus.io.chat.PlatformInput;
import nexus.io.chat.UniChatClient;
import nexus.io.chat.UniChatMessage;
import nexus.io.chat.UniChatRequest;
import nexus.io.chat.UniChatResponse;
import nexus.io.consts.ModelPlatformName;
import nexus.io.openrouter.OpenRouterModels;
import nexus.io.tio.boot.http.TioRequestContext;
import nexus.io.tio.http.common.HttpRequest;
import nexus.io.tio.http.common.HttpResponse;
import nexus.io.model.upload.UploadFile;
import nexus.io.tio.http.server.handler.HttpRequestHandler;
import nexus.io.tio.http.server.util.CORSUtils;
import nexus.io.tio.utils.base64.Base64Utils;
import nexus.io.tio.utils.http.ContentTypeUtils;
import nexus.io.tio.utils.hutool.FilenameUtils;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class PredictFileHandler implements HttpRequestHandler {

  @Override
  public HttpResponse handle(HttpRequest httpRequest) throws Exception {
    HttpResponse response = TioRequestContext.getResponse();
    CORSUtils.enableCORS(response);

    UploadFile uploadFile = httpRequest.getUploadFile("file");
    byte[] data = uploadFile.getData();
    String name = uploadFile.getName();

    String systemPrompt = "识别图片中的文字";
    String result = predict(systemPrompt, data, name);

    return response.setBody(result);
  }

  private String predict(String systemPrompt, byte[] data, String filename) {
    // 1. 推断文件类型
    String suffix = FilenameUtils.getSuffix(filename);
    String mimeType = ContentTypeUtils.getContentType(suffix);

    // 2. 图片转 Base64
    String encodeImage = Base64Utils.encodeImage(data, mimeType);

    // 3. 构造图片文件对象
    List<ChatImageFile> files = new ArrayList<>();
    ChatImageFile file = ChatImageFile.base64(mimeType, encodeImage);
    files.add(file);

    // 4. 构造消息(图片作为 message 的一部分)
    UniChatMessage uniChatMessage = new UniChatMessage();
    uniChatMessage.setContent("image");
    uniChatMessage.setFiles(files);

    // 5. 选择支持视觉能力的模型
    PlatformInput platformInput = new PlatformInput(
        ModelPlatformName.OPENROUTER,
        OpenRouterModels.QWEN_QWEN2_5_VL_7B_INSTRUCT_FREE
    );

    // 6. 构造统一请求
    UniChatRequest uniChatRequest = new UniChatRequest(platformInput);
    uniChatRequest.setSystemPrompt(systemPrompt);

    List<UniChatMessage> messages = new ArrayList<>();
    messages.add(uniChatMessage);
    uniChatRequest.setMessages(messages);

    try {
      UniChatResponse response = UniChatClient.generate(uniChatRequest);
      return response.getMessage().getContent();
    } catch (Exception e) {
      log.error(e.getMessage(), e);
    }
    return null;
  }
}

客户端调用示例 ​

使用 curl 以 multipart/form-data 方式上传图片:

bash
curl -X POST http://127.0.0.1/predict/image \
  -F "file=@challenging-sat-math-geometry-problem-v0-6h5fen0qwkzc1.jpg"

服务端将返回模型对图片内容的识别结果(例如图片中的文字或题目内容)。


关键点说明 ​

1. 图片如何传递给模型 ​

在 java-openai 中,图片的传递流程是:

  1. 原始图片 → 二进制 byte[]
  2. 根据文件后缀推断 mimeType
  3. 转换为 Base64 编码
  4. 封装为 ChatImageFile
  5. 挂载到 UniChatMessage.files
  6. 随 UniChatRequest 一起发送

业务侧不需要关心底层平台是使用:

  • image_url
  • inline_data
  • content.parts
  • 或其他私有字段

这些差异都由平台适配层自动处理。


2. 为什么 content 设置为 "image" ​

在图片任务中:

  • UniChatMessage.content 用于表达“这是一个图片消息”
  • 实际的图片数据通过 files 传递
  • 文本指令通过 systemPrompt 或额外的 user message 提供

这种设计便于支持:

  • 多图片输入
  • 图片 + 文本混合消息
  • 与后续文本对话自然衔接

3. 模型选择的重要性 ​

并非所有模型都支持图片输入。 在使用图片能力时,必须选择:

  • 明确支持 Vision / VL / Multimodal 的模型
  • 例如:Qwen-VL、Gemini、Claude Vision 等

在示例中,通过:

java
OpenRouterModels.QWEN_QWEN2_5_VL_7B_INSTRUCT_FREE

明确选择了一个支持图片理解的模型。


4. 与纯文本调用的统一性 ​

尽管引入了图片能力,但会发现:

  • 入口仍然是 UniChatClient.generate(...)
  • 请求对象仍然是 UniChatRequest
  • 返回对象仍然是 UniChatResponse
  • 获取结果仍然是 response.getMessage().getContent()

这正是 java-openai 统一抽象的价值所在: 多模态能力是对消息的扩展,而不是对调用方式的破坏。


常见扩展场景 ​

基于当前结构,可以很自然地扩展出:

  • 图片 + 提示词:“请解释这张图的含义”
  • 图片 + 多轮上下文:先上传图片,再追问细节
  • 多图片对比分析
  • 图片 + 搜索 / 工具调用(结合前文 search 能力)

视频输入 ​

ChatImageFile 同样可以承载视频。MIME 类型以 video/ 开头时,OpenAI 兼容请求会自动生成 video_url 内容块,业务代码仍使用同一套 UniChatMessage 和 UniChatClient。

java
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;
import java.util.List;

import nexus.io.chat.ChatImageFile;
import nexus.io.chat.UniChatClient;
import nexus.io.chat.UniChatMessage;
import nexus.io.chat.UniChatRequest;
import nexus.io.chat.UniChatResponse;

public class VideoUnderstandingExample {
  public static void main(String[] args) throws Exception {
    byte[] bytes = Files.readAllBytes(Path.of("demo.mp4"));
    String data = "data:video/mp4;base64," + Base64.getEncoder().encodeToString(bytes);
    UniChatMessage message = new UniChatMessage("user", "请概括视频内容");
    message.setFiles(List.of(ChatImageFile.base64("video/mp4", data)));

    UniChatRequest request = new UniChatRequest();
    request.setModel("your-video-model");
    request.setApiPrefixUrl("https://api.example.com/v1");
    request.setApiKey(System.getenv("VIDEO_MODEL_API_KEY"));
    request.setMessages(List.of(message));

    UniChatResponse response = UniChatClient.generate(request);
    System.out.println(response.getMessage().getContent());
  }
}

小结 ​

  • java-openai 通过 ChatImageFile 抽象图片和视频输入
  • 图片作为 UniChatMessage 的一部分参与对话
  • 统一使用 UniChatRequest / UniChatResponse 调用与返回
  • 平台差异完全由内部适配层处理
  • 只要更换模型,即可在同一套代码下切换不同视觉模型能力