LangChain4j Java AI 应用开发实战(三):多模态 AI 开发 - 图片理解与图像生成实战
系列篇章💥
| No. | 文章 |
|---|---|
| 1 | LangChain4j Java AI 应用开发实战(一):LangChain4j 快速入门指南 |
| 2 | LangChain4j Java AI 应用开发实战(二):大模型参数调优实战:Temperature、TopP、MaxTokens 深度解析 |
| 3 | LangChain4j Java AI 应用开发实战(三):多模态 AI 开发 - 图片理解与图像生成实战 |
目录
前言
大语言模型不仅能处理文本,还能"看懂"图片、"听懂"音频、"画出"图像。多模态能力让 AI 应用从单一的文本交互升级为全方位的感官体验。本文将带你掌握 LangChain4j 的多模态开发技术,包括 GPT-4V/GPT-5 的视觉理解、DALL·E 3 的图像生成、Whisper 的语音转文字。我们将通过真实代码示例,展示如何让 Java 应用识别图片内容、根据描述生成图像、转录音频文件,并分享多模态数据流设计、Base64 编码优化、国产模型多模态能力对比等实战技巧,开启 AI 应用的新维度。
一、什么是多模态 AI?
1.1 从文本到多感官
传统的大语言模型(LLM)只能处理文本输入和输出,就像一个只会读书写字的人。而多模态 AI(Multimodal AI)则具备多种"感官":
| 模态 | 能力 | 类比人类感官 |
|---|---|---|
| 文本 | 阅读、写作 | 语言能力 |
| 图像 | 看图、画图 | 视觉能力 |
| 音频 | 听音、说话 | 听觉/语言能力 |
| 视频 | 看动态画面 | 视觉+时间感知 |
1.2 多模态的应用场景
(1)视觉理解(Image Understanding)
- OCR 增强:识别发票、合同、手写笔记
- 商品识别:拍照识别商品、比价
- 医疗影像:X 光片、CT 扫描辅助诊断
- 工业质检:产品缺陷检测
- 安防监控:异常行为识别
(2)图像生成(Image Generation)
- 营销素材:自动生成广告图、海报
- 游戏开发:角色设计、场景概念图
- 教育内容:插图、示意图生成
- 个性化定制:头像、壁纸生成
(3)音频处理(Audio Processing)
- 会议记录:自动转录会议纪要
- 客服录音分析:情感分析、关键词提取
- 播客字幕:自动生成字幕
- 语音助手:语音指令识别
1.3 LangChain4j 的多模态支持
LangChain4j 提供了统一的多模态接口:
┌─────────────────────────────────┐
│ LangChain4j 多模态 API │
├──────────┬──────────┬───────────┤
│ 视觉理解 │ 图像生成 │ 音频处理 │
│ ChatModel│ImageModel│AudioModel │
│ + Image │generate()│transcribe()│
└──────────┴──────────┴───────────┘
↓ ↓ ↓
OpenAI DALL·E 3 Whisper
GPT-4V/5
二、视觉理解:让 AI "看懂"图片
2.1 实践代码解析
让我们从项目中的示例开始:
package com.langchain4j;
import dev.langchain4j.data.message.ImageContent;
import dev.langchain4j.data.message.TextContent;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.openai.OpenAiChatModel;
public class ChatModeWithImageExamples {
public static void main(String[] args) {
// 携带图片
ChatModel chatModel = OpenAiChatModel.builder()
.baseUrl("http://langchain4j.dev/demo/openai/v1")
.modelName("gpt-4o-mini")
.maxTokens(50)
.build();
UserMessage userMessage = UserMessage.from(
TextContent.from("图片中有什么?"),
ImageContent.from("https://cdn.pixabay.com/photo/2017/08/07/16/36/cat-2605502_1280.jpg")
);
ChatResponse chatResponse = chatModel.chat(userMessage);
System.out.println(chatResponse.aiMessage().text());
//输出:这是一只可爱的小猫,它正在休息或睡觉,身上有黑白相间的毛发,躺在绿色的表面上。
}
}
2.2 核心概念解析
(1)UserMessage 支持多模态内容
传统的 UserMessage 只包含文本:
// 纯文本消息
UserMessage message = UserMessage.from("你好");
多模态版本的 UserMessage 可以包含多个内容块:
UserMessage userMessage = UserMessage.from(
TextContent.from("图片中有什么?"), // 文本内容
ImageContent.from("图片URL或Base64") // 图片内容
);
关键点:
TextContent:文本描述、问题、指令ImageContent:图片数据(URL 或 Base64)- 可以混合多个内容块,如:文本 + 图片1 + 图片2
(2)ImageContent 的两种形式
方式一:URL(推荐)
ImageContent.from("https://example.com/image.jpg")
优点:
- 节省带宽(不传输图片二进制数据)
- 速度快
- 适合公开可访问的图片
缺点:
- 需要图片可通过 URL 访问
- 隐私图片不适合
方式二:Base64 编码
// 读取本地图片并转换为 Base64
byte[] imageBytes = Files.readAllBytes(Path.of("cat.jpg"));
String base64Image = Base64.getEncoder().encodeToString(imageBytes);
ImageContent.from("data:image/jpeg;base64," + base64Image)
优点:
- 支持本地图片
- 适合隐私图片
缺点:
- 数据传输量大(Base64 比原文件大约 33%)
- 可能超过 API 限制
(3)支持的模型
| 模型 | 多模态支持 | 说明 |
|---|---|---|
| GPT-4V | ✅ | OpenAI 首个多模态模型 |
| GPT-4o | ✅ | 原生多模态,速度更快 |
| GPT-4o-mini | ✅ | 性价比最高,推荐首选 |
| GPT-5 | ✅ | 最强多模态能力(2026 年最新) |
| 通义千问 VL | ✅ | 阿里多模态模型 |
| DeepSeek | ❌ | 暂不支持图片 |
2.3 实战案例 1:智能 OCR 系统
(1)需求
用户上传发票图片,AI 自动提取关键信息(金额、日期、商家)。
(2)完整代码
import dev.langchain4j.data.message.ImageContent;
import dev.langchain4j.data.message.TextContent;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import java.util.Base64;
import java.nio.file.Files;
import java.nio.file.Path;
public class SmartOCR {
public static void main(String[] args) throws Exception {
// 1. 配置模型
ChatModel chatModel = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o") // 使用高质量模型提升识别准确率
.build();
// 2. 读取发票图片
byte[] imageBytes = Files.readAllBytes(Path.of("invoice.jpg"));
String base64Image = "data:image/jpeg;base64," +
Base64.getEncoder().encodeToString(imageBytes);
// 3. 构建多模态消息
UserMessage userMessage = UserMessage.from(
TextContent.from("""
请识别这张发票图片中的以下信息,并以 JSON 格式返回:
- invoice_number: 发票号码
- date: 开票日期(YYYY-MM-DD 格式)
- merchant: 商家名称
- total_amount: 总金额(数字)
- items: 商品列表(数组,每项包含 name 和 price)
只返回 JSON,不要其他文字。
"""),
ImageContent.from(base64Image)
);
// 4. 调用模型
String response = chatModel.chat(userMessage);
// 5. 解析结果
System.out.println(response);
// 输出示例:
// {
// "invoice_number": "INV-2024-001234",
// "date": "2024-03-15",
// "merchant": "星巴克咖啡",
// "total_amount": 45.00,
// "items": [
// {"name": "拿铁咖啡", "price": 32.00},
// {"name": "蓝莓 muffin", "price": 13.00}
// ]
// }
}
}
(3)关键技术点
✅ 结构化输出:要求模型返回 JSON,便于程序解析
✅ 详细指令:明确指定需要提取的字段和格式
✅ 高质量模型:OCR 任务建议使用 GPT-4o 或 GPT-5 等模型
2.4 实战案例 2:商品识别与比价
(1)需求
用户拍摄商品照片,AI 识别商品名称并提供购买建议。
(2)完整代码
public class ProductRecognizer {
public static void main(String[] args) throws Exception {
ChatModel chatModel = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini") // 性价比模型
.build();
// 读取商品图片
byte[] imageBytes = Files.readAllBytes(Path.of("product.jpg"));
String base64Image = "data:image/jpeg;base64," +
Base64.getEncoder().encodeToString(imageBytes);
UserMessage userMessage = UserMessage.from(
TextContent.from("""
请识别这张图片中的商品,并提供以下信息:
1. 商品名称和品牌
2. 商品类别
3. 预估价格范围
4. 购买建议(是否值得购买,注意事项)
以简洁的列表形式回答。
"""),
ImageContent.from(base64Image)
);
String response = chatModel.chat(userMessage);
System.out.println(response);
// 输出示例:
// 1. 商品名称:Nike Air Max 270 运动鞋
// 2. 商品类别:运动鞋/休闲鞋
// 3. 预估价格:¥800-1200
// 4. 购买建议:
// - 适合日常穿着和轻度运动
// - 建议在官方旗舰店购买,避免假货
// - 当前市场价格稳定,可立即购买
}
}
2.5 性能优化技巧
(1)图片尺寸压缩
大图片会消耗更多 Token 和处理时间。建议在上传前压缩图片:
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
public class ImageCompressor {
public static byte[] compressImage(byte[] originalBytes, int maxWidth, int maxHeight)
throws Exception {
// 读取原始图片
BufferedImage originalImage = ImageIO.read(
new ByteArrayInputStream(originalBytes)
);
// 计算缩放比例
double scale = Math.min(
(double) maxWidth / originalImage.getWidth(),
(double) maxHeight / originalImage.getHeight()
);
if (scale >= 1.0) {
return originalBytes; // 无需压缩
}
// 缩放图片
int newWidth = (int) (originalImage.getWidth() * scale);
int newHeight = (int) (originalImage.getHeight() * scale);
BufferedImage scaledImage = new BufferedImage(
newWidth, newHeight, BufferedImage.TYPE_INT_RGB
);
scaledImage.createGraphics().drawImage(
originalImage, 0, 0, newWidth, newHeight, null
);
// 转换为 JPEG(质量 80%)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(scaledImage, "jpg", baos);
return baos.toByteArray();
}
}
// 使用
byte[] compressed = ImageCompressor.compressImage(imageBytes, 1024, 1024);
效果:
- 原始图片:5MB → 压缩后:200KB
- Token 消耗减少 80%
- 响应速度提升 50%
(2)缓存识别结果
对于重复的图片(如同一张发票多次上传),使用哈希缓存:
import java.security.MessageDigest;
import java.util.concurrent.ConcurrentHashMap;
public class OCRService {
private final ConcurrentHashMap<String, String> cache = new ConcurrentHashMap<>();
private final ChatModel chatModel;
public String recognizeInvoice(byte[] imageBytes) {
// 计算图片哈希
String hash = calculateHash(imageBytes);
// 检查缓存
if (cache.containsKey(hash)) {
return cache.get(hash); // 直接返回缓存结果
}
// 调用 AI 识别
String result = callAI(imageBytes);
// 存入缓存(设置过期时间)
cache.put(hash, result);
return result;
}
private String calculateHash(byte[] data) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hashBytes = md.digest(data);
return Base64.getEncoder().encodeToString(hashBytes);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
三、图像生成:让 AI "画出"图片
3.1 实践代码解析
package com.langchain4j;
import dev.langchain4j.data.image.Image;
import dev.langchain4j.model.image.ImageModel;
import dev.langchain4j.model.openai.OpenAiImageModel;
import dev.langchain4j.model.output.Response;
import static dev.langchain4j.model.openai.OpenAiImageModelName.DALL_E_3;
public class T10_OpenAiImageModelExamples {
public static void main(String[] args) {
// ==================== 1. 配置图像生成模型 ====================
ImageModel model = OpenAiImageModel.builder()
.apiKey("demo")
.modelName(DALL_E_3)
.build();
// ==================== 2. 生成图像 ====================
Response<Image> response = model.generate("Donald Duck in New York, cartoon style");
// ==================== 3. 获取生成结果 ====================
System.out.println(response.content().url());
// 输出:https://oaidalleapiprodscus.blob.core.windows.net/private/...
}
}
3.2 核心概念解析
(1)ImageModel 接口
与 ChatModel 不同,图像生成使用专门的 ImageModel 接口:
public interface ImageModel {
Response<Image> generate(String prompt);
Response<List<Image>> generate(String prompt, int n); // 生成多张
}
(2)DALL·E 3 vs DALL·E 2
| 特性 | DALL·E 3 | DALL·E 2 |
|---|---|---|
| 图像质量 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| Prompt 理解 | 极强,能理解复杂描述 | 一般 |
| 生成数量 | 每次 1 张 | 每次 1-10 张 |
| 成本 | $0.04-0.08/张 | $0.016-0.02/张 |
| 适用场景 | 高质量需求 | 批量生成 |
推荐:除非需要批量生成,否则优先使用 DALL·E 3。
(3)高级配置
ImageModel model = OpenAiImageModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName(DALL_E_3)
.size("1792x1024") // 图像尺寸
.quality("hd") // 高清质量
.style("vivid") // 鲜明风格
.responseFormat("url") // 返回格式
.build();
参数说明:
| 参数 | 可选值 | 说明 |
|---|---|---|
| size | 1024x1024(默认)1792x1024(宽屏)1024x1792(竖屏) |
图像分辨率 |
| quality | standard(默认)hd |
图像质量,hd 更细腻但成本高 2 倍 |
| style | vivid(默认)natural |
vivid 更鲜艳,natural 更写实 |
| responseFormat | url(默认)b64_json |
url 返回临时链接,b64_json 返回 Base64 |
3.3 Prompt 工程:如何写出好的图像描述
(1)基础结构
一个好的图像 Prompt 应包含:
[主体] + [动作/状态] + [环境/背景] + [风格] + [细节]
示例:
// ❌ 差的 Prompt
model.generate("一只猫");
// ✅ 好的 Prompt
model.generate(
"一只橘色的波斯猫,蜷缩在阳光照射的窗台上," +
"窗外是巴黎埃菲尔铁塔的远景," +
"印象派油画风格,温暖的色调,柔和的光线," +
"高细节,8k 分辨率"
);
(2)风格关键词库
| 风格类型 | 关键词 |
|---|---|
| 写实 | photorealistic, realistic, hyperrealistic, 8k |
| 卡通 | cartoon style, anime, manga, Disney style |
| 艺术 | oil painting, watercolor, impressionist, abstract |
| 科幻 | cyberpunk, futuristic, sci-fi, neon lights |
| 复古 | vintage, retro, 1950s style, film grain |
| 极简 | minimalist, clean, simple, flat design |
(3)实战案例:营销海报生成
public class MarketingPosterGenerator {
public static void main(String[] args) {
ImageModel model = OpenAiImageModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName(DALL_E_3)
.size("1024x1792") // 竖屏,适合手机海报
.quality("hd")
.style("vivid")
.build();
String prompt = """
A vibrant summer sale poster for a coffee shop,
featuring a large iced latte with condensation droplets,
surrounded by fresh coffee beans and green leaves,
bright yellow and orange color scheme,
bold text space at the top for "SUMMER SALE 50% OFF",
modern minimalist design, high contrast,
professional commercial photography style, 8k
""";
Response<Image> response = model.generate(prompt);
String imageUrl = response.content().url();
System.out.println("海报生成成功:" + imageUrl);
// ⚠️ 重要:立即下载保存,URL 1 小时后失效
downloadAndSave(imageUrl, "summer_sale_poster.png");
}
private static void downloadAndSave(String url, String filename) {
// 实现下载逻辑...
}
}
3.4 图像保存与管理
⚠️ 重要提醒:URL 临时性
DALL·E 返回的 URL 有效期仅 1 小时,必须及时下载保存!
(1)下载到本地
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
public class ImageDownloader {
public static void downloadImage(String imageUrl, String savePath)
throws Exception {
URL url = new URL(imageUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
try (var inputStream = conn.getInputStream()) {
Files.copy(inputStream, Path.of(savePath));
}
System.out.println("图片已保存到:" + savePath);
}
}
// 使用
downloadImage(response.content().url(), "/images/generated/poster.png");
(2)上传到云存储(推荐生产环境)
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
public class S3ImageUploader {
private final S3Client s3Client;
private final String bucketName;
public String uploadToS3(byte[] imageBytes, String filename) {
String key = "generated-images/" + UUID.randomUUID() + "-" + filename;
PutObjectRequest request = PutObjectRequest.builder()
.bucket(bucketName)
.key(key)
.contentType("image/png")
.build();
s3Client.putObject(request,
software.amazon.awssdk.core.sync.RequestBody.fromBytes(imageBytes)
);
// 返回永久 URL
return String.format("https://%s.s3.amazonaws.com/%s", bucketName, key);
}
}
四、音频处理:让 AI "听懂"声音
4.1 实践代码解析
package com.langchain4j;
import dev.langchain4j.data.audio.Audio;
import dev.langchain4j.model.audio.AudioTranscriptionModel;
import dev.langchain4j.model.audio.AudioTranscriptionRequest;
import dev.langchain4j.model.audio.AudioTranscriptionResponse;
import dev.langchain4j.model.openai.OpenAiAudioTranscriptionModel;
import static dev.langchain4j.model.openai.OpenAiAudioTranscriptionModelName.WHISPER_1;
public class T10_OpenAiAudioModelExamples {
public static void main(String[] args) {
// ==================== 1. 配置语音转录模型 ====================
AudioTranscriptionModel model = OpenAiAudioTranscriptionModel.builder()
.apiKey("demo")
.modelName(WHISPER_1)
.logRequests(true)
.logResponses(true)
.build();
// ==================== 2. 构建转录请求 ====================
AudioTranscriptionResponse response = model.transcribe(
AudioTranscriptionRequest.builder()
.audio(Audio.builder()
.mimeType("audio/wav") // 音频格式
.binaryData(toBytes("audio.wav")) // 音频数据
.build())
.build()
);
// ==================== 3. 获取转录结果 ====================
System.out.println(response.text());
}
private static byte[] toBytes(String fileName) {
try {
URL fileUrl = T10_OpenAiAudioModelExamples.class.getResource(fileName);
return Files.readAllBytes(Path.of(fileUrl.toURI()));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
4.2 核心概念解析
(1)Whisper 模型
Whisper 是 OpenAI 开发的开源语音识别模型,支持:
- ✅ 99 种语言:包括中文、英文、日文、韩文等
- ✅ 多种音频格式:mp3, wav, m4a, flac, ogg 等
- ✅ 说话人分离:识别不同说话者(需额外处理)
- ✅ 时间戳:精确到单词级别的时序信息
(2)AudioTranscriptionModel 接口
public interface AudioTranscriptionModel {
AudioTranscriptionResponse transcribe(AudioTranscriptionRequest request);
}
(3)Audio 对象
Audio audio = Audio.builder()
.mimeType("audio/wav") // MIME 类型(必填)
.binaryData(audioBytes) // 音频二进制数据(必填)
.build();
支持的 MIME 类型:
audio/wavaudio/mpeg(mp3)audio/mp4(m4a)audio/flacaudio/oggaudio/webm
文件大小限制:25MB,超过需切割。
4.3 高级配置
AudioTranscriptionResponse response = model.transcribe(
AudioTranscriptionRequest.builder()
.audio(Audio.builder()
.mimeType("audio/mp3")
.binaryData(audioBytes)
.build())
.language("zh") // 指定语言为中文
.prompt("这是技术会议讨论内容") // 提示词,引导术语风格
.responseFormat("verbose_json") // 详细 JSON 格式
.temperature(0.2) // 采样温度
.build()
);
参数说明:
| 参数 | 说明 | 示例 |
|---|---|---|
| language | 指定音频语言,提升准确率 | "zh", "en", "ja" |
| prompt | 提示词,引导转录风格 | "这是医学讲座" |
| responseFormat | 输出格式 | text(默认), json, srt, vtt, verbose_json |
| temperature | 采样温度(0-1) | 0(确定性), 0.5(平衡) |
4.4 实战案例 1:会议记录自动生成
(1)需求
录制会议音频,自动转录为文字,并提取关键决策。
(2)完整代码
public class MeetingTranscriber {
public static void main(String[] args) throws Exception {
// 1. 配置 Whisper 模型
AudioTranscriptionModel whisper = OpenAiAudioTranscriptionModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName(WHISPER_1)
.build();
// 2. 读取会议录音
byte[] audioBytes = Files.readAllBytes(Path.of("meeting.mp3"));
// 3. 转录音频
AudioTranscriptionResponse response = whisper.transcribe(
AudioTranscriptionRequest.builder()
.audio(Audio.builder()
.mimeType("audio/mp3")
.binaryData(audioBytes)
.build())
.language("zh") // 中文会议
.build()
);
String transcript = response.text();
System.out.println("转录结果:\n" + transcript);
// 4. 使用 ChatModel 提取关键决策
ChatModel chatModel = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.build();
String summaryPrompt = """
以下是会议转录文本,请提取:
1. 主要讨论议题
2. 达成的决策
3. 待办事项(负责人 + 截止时间)
以 Markdown 格式输出。
会议内容:
%s
""".formatted(transcript);
String summary = chatModel.chat(summaryPrompt);
System.out.println("\n会议纪要:\n" + summary);
// 输出示例:
// # 主要讨论议题
// 1. Q2 产品发布计划
// 2. 市场推广预算分配
//
// # 达成的决策
// - 确定 6 月 15 日发布新产品
// - 市场预算增加 20%
//
// # 待办事项
// - 张三:完成产品测试报告(6 月 10 日前)
// - 李四:制定市场推广方案(6 月 12 日前)
}
}
4.5 实战案例 2:长音频分段处理
(1)问题
Whisper API 限制单次最多 25MB 音频(约 25 分钟)。如何处理 1 小时的录音?
(2)解决方案:音频切割 + 合并
import javax.sound.sampled.*;
import java.io.ByteArrayInputStream;
public class LongAudioTranscriber {
private static final int MAX_SEGMENT_DURATION = 20 * 60; // 20 分钟/段
public String transcribeLongAudio(byte[] audioBytes) throws Exception {
// 1. 切割音频
List<byte[]> segments = splitAudio(audioBytes, MAX_SEGMENT_DURATION);
// 2. 逐段转录
AudioTranscriptionModel whisper = OpenAiAudioTranscriptionModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName(WHISPER_1)
.build();
StringBuilder fullTranscript = new StringBuilder();
for (int i = 0; i < segments.size(); i++) {
System.out.println("处理第 " + (i + 1) + "/" + segments.size() + " 段...");
AudioTranscriptionResponse response = whisper.transcribe(
AudioTranscriptionRequest.builder()
.audio(Audio.builder()
.mimeType("audio/wav")
.binaryData(segments.get(i))
.build())
.language("zh")
.build()
);
fullTranscript.append(response.text()).append("\n\n");
}
return fullTranscript.toString();
}
private List<byte[]> splitAudio(byte[] audioBytes, int maxSeconds)
throws Exception {
// 读取音频流
AudioInputStream audioStream = AudioSystem.getAudioInputStream(
new ByteArrayInputStream(audioBytes)
);
AudioFormat format = audioStream.getFormat();
long totalFrames = audioStream.getFrameLength();
float frameRate = format.getFrameRate();
// 计算每段的帧数
long framesPerSegment = (long) (frameRate * maxSeconds);
List<byte[]> segments = new ArrayList<>();
long currentFrame = 0;
while (currentFrame < totalFrames) {
long remainingFrames = totalFrames - currentFrame;
long segmentFrames = Math.min(framesPerSegment, remainingFrames);
// 读取当前段
byte[] segmentBytes = readSegment(audioStream, segmentFrames, format);
segments.add(segmentBytes);
currentFrame += segmentFrames;
}
return segments;
}
private byte[] readSegment(AudioInputStream stream, long frames,
AudioFormat format) throws Exception {
// 实现音频段读取逻辑...
// 简化版:实际需要使用 Java Sound API 精确切割
return new byte[0];
}
}
提示:音频切割较复杂,生产环境建议使用专业库如 FFmpeg(通过 ProcessBuilder 调用)。
五、多模态数据流设计
5.1 架构概览
┌──────────┐ ┌──────────────┐ ┌─────────────┐
│ 用户上传 │────▶│ 预处理服务 │────▶│ AI 处理 │
│ 图片/音频 │ │ - 压缩 │ │ - 视觉理解 │
└──────────┘ │ - 格式转换 │ │ - 图像生成 │
│ - 鉴权 │ │ - 语音转录 │
└──────────────┘ └──────┬──────┘
│
▼
┌─────────────┐
│ 结果存储 │
│ - 图片保存 │
│ - 文本索引 │
│ - 元数据记录 │
└─────────────┘
5.2 Base64 编码优化
(1)问题
Base64 编码会使数据量增加 33%,影响传输速度。
(2)解决方案
方案 1:使用 URL(优先)
// ✅ 推荐:直接传 URL
ImageContent.from("https://example.com/image.jpg")
方案 2:压缩后再编码
// 压缩图片
byte[] compressed = compressImage(originalBytes, 1024, 1024);
// Base64 编码
String base64 = Base64.getEncoder().encodeToString(compressed);
方案 3:异步上传 + URL 回调
// 1. 用户上传到 OSS
String ossUrl = ossService.upload(userFile);
// 2. 传 URL 给 AI
ImageContent.from(ossUrl);
5.3 错误处理与重试
public class MultimodalService {
private static final int MAX_RETRIES = 3;
public String processImageWithRetry(byte[] imageBytes) {
for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
return processImage(imageBytes);
} catch (Exception e) {
logger.warn("第 {} 次尝试失败: {}", attempt, e.getMessage());
if (attempt == MAX_RETRIES) {
throw new ServiceException("图像处理失败,请稍后重试", e);
}
// 指数退避重试
try {
Thread.sleep((long) Math.pow(2, attempt) * 1000);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
}
throw new IllegalStateException("不应到达此处");
}
private String processImage(byte[] imageBytes) {
// 实际处理逻辑...
return chatModel.chat(userMessage);
}
}
六、常见问题与避坑指南
6.1 图片无法识别
❌ 现象:AI 回复"我无法查看图片"或返回无关内容
原因:
- 图片 URL 不可访问(403/404)
- Base64 格式错误
- 模型不支持多模态
✅ 解决方案:
// 1. 验证 URL 可访问性
try {
HttpURLConnection conn = (HttpURLConnection) new URL(imageUrl).openConnection();
conn.setRequestMethod("HEAD");
if (conn.getResponseCode() != 200) {
throw new IllegalArgumentException("图片 URL 不可访问");
}
} catch (Exception e) {
// 降级:下载到本地再转 Base64
}
// 2. 确认使用多模态模型
OpenAiChatModel.builder()
.modelName("gpt-4o") // ✅ 支持多模态
// .modelName("gpt-3.5-turbo") // ❌ 不支持
.build();
6.2 图像生成超时
❌ 现象:SocketTimeoutException
原因:DALL·E 生成耗时 5-15 秒,默认超时太短
✅ 解决方案:
OpenAiImageModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.timeout(Duration.ofSeconds(30)) // 增加超时时间
.build();
6.3 音频转录不准确
❌ 现象:中文识别成英文,或专有名词错误
原因:未指定语言或缺少上下文
✅ 解决方案:
AudioTranscriptionRequest.builder()
.audio(audio)
.language("zh") // 明确指定中文
.prompt("这是人工智能技术研讨会,涉及 Transformer、BERT 等专业术语")
.build();
结语
通过本文的学习,你已经掌握了 LangChain4j 的多模态开发核心技术。从让 AI "看懂"图片的视觉理解,到"画出"图像的 DALL·E 3 生成,再到"听懂"声音的 Whisper 转录,多模态能力为你的 Java 应用打开了全新的交互维度。记住,多模态不仅是技术的堆砌,更是用户体验的升级——让用户能用最自然的方式(拍照、说话)与 AI 交互。下一篇我们将探索提示词工程进阶,学习如何通过模板化和结构化的 Prompt 设计,让 AI 的输出更加精准可控,为构建企业级 AI 应用打下坚实基础!

🎯🔖更多专栏系列文章:AI大模型提示工程完全指南、AI大模型探索之路(零基础入门)、AI大模型预训练微调进阶、AI大模型开源精选实践、AI大模型Spring AI开发实战🔥🔥🔥 其他专栏可以查看博客主页
🔔 关于作者:资深程序老猿,10年+架构经验,现专注 AIGC 探索与实践。
👍 若文章对你有所触动,恳请点赞 ⭐ 关注 ⭐ 收藏!AI 浪潮已至,愿与你同行。
更多推荐



所有评论(0)