多模态 AI(图片/语音/视频)
AI 不再只是文字对话。用户可以上传图片让 AI 分析、用语音代替打字、让 AI 生成图片。本章讲清多模态 AI 在 Next.js 中的集成方式——Vision(图片理解)、Image Generation(图片生成)、TTS/STT(语音合成/识别)。
1. 图片理解(Vision)
1.1 原理
GPT-4o、Claude 3.5、Gemini 等模型原生支持图片输入——你可以在消息中同时传文本和图片,AI 能"看懂"图片内容。
图片是如何被模型理解的? 模型会把图片切成小块(patch),每个小块转换成一个“图像 Token”,然后和文本 Token 一起送入 Transformer 处理。这意味着:
- 图片会消耗 Token 配额,而且通常比文本贵得多——一张 1024x1024 的图片大约消耗 765 Token(GPT-4o)
- 图片分辨率越高,消耗的 Token 越多。建议在上传前压缩图片到合理尺寸
- 模型对图片的理解能力很强(OCR、场景描述、图表解读),但不能精确计数或识别很小的文字
1.2 发送图片消息
AI SDK 的消息格式支持多模态内容——content 字段可以是文本和图片的数组:
// app/api/chat/route.ts
import { streamText } from 'ai'
import { openai } from '@ai-sdk/openai'
export async function POST(request: Request) {
const { messages } = await request.json()
const result = streamText({
model: openai('gpt-4o'), // GPT-4o 支持 Vision
messages,
})
return result.toDataStreamResponse()
}客户端发送图片消息。这里用 experimental_attachments 把图片转成 base64 Data URL 附加到消息中。base64 会让数据体积增大约 33%,但胜在简单——不需要单独的文件上传服务。生产环境建议先上传到 CDN,然后传 URL(见 1.3 节):
'use client'
import { useChat } from 'ai/react'
export function MultimodalChat() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat()
async function handleImageUpload(e: React.ChangeEvent<HTMLInputElement>) {
const file = e.target.files?.[0]
if (!file) return
// 转为 base64
const reader = new FileReader()
reader.onload = () => {
const base64 = reader.result as string
// 发送包含图片的消息
handleSubmit(undefined, {
experimental_attachments: [
{
name: file.name,
contentType: file.type,
url: base64,
},
],
})
}
reader.readAsDataURL(file)
}
return (
<div>
{messages.map((msg) => (
<div key={msg.id}>
{/* 渲染附件(图片) */}
{msg.experimental_attachments?.map((attachment) => (
<img
key={attachment.name}
src={attachment.url}
alt={attachment.name}
className="max-w-sm rounded-lg"
/>
))}
{msg.content && <p>{msg.content}</p>}
</div>
))}
<form onSubmit={handleSubmit} className="flex gap-2">
<input type="file" accept="image/*" onChange={handleImageUpload} />
<input value={input} onChange={handleInputChange} placeholder="描述图片或提问..." />
<button type="submit">发送</button>
</form>
</div>
)
}1.3 图片 URL 方式
URL 方式适合图片已存储在 CDN 的场景。模型会从 URL 下载图片,不需要编码在请求体里,请求体积小得多:
const messages = [
{
role: 'user',
content: [
{ type: 'text', text: '这张图片里有什么?' },
{ type: 'image', image: new URL('https://cdn.example.com/photo.jpg') },
],
},
]
const result = streamText({ model: openai('gpt-4o'), messages })1.4 实用场景
| 场景 | Prompt 示例 |
|---|---|
| OCR 文字识别 | "提取这张图片中的所有文字" |
| 图片描述 | "详细描述这张图片的内容" |
| 代码截图解析 | "这段代码有什么 bug?" |
| 产品分析 | "这个 UI 设计有什么改进建议?" |
| 数据提取 | "读取这张表格/图表中的数据" |
2. 图片生成
2.1 DALL-E 集成
DALL-E 3 基于扩散模型(Diffusion Model)原理:从纯噪声图片开始,通过多步去噪(受文本 Prompt 引导)逐渐生成清晰图片。DALL-E 3 还会自动改写你的 Prompt(revised_prompt)——它先用 GPT-4 把你的简短描述扩展成详细的图片描述,再用这个详细描述生成图片。这就是为什么生成结果中会返回 revised_prompt。
// app/api/generate-image/route.ts
import OpenAI from 'openai'
const openai = new OpenAI()
export async function POST(request: Request) {
const { prompt, size = '1024x1024' } = await request.json()
const response = await openai.images.generate({
model: 'dall-e-3',
prompt,
n: 1,
size: size as '1024x1024' | '1792x1024' | '1024x1792',
quality: 'standard',
})
const imageUrl = response.data[0].url
const revisedPrompt = response.data[0].revised_prompt
return Response.json({ imageUrl, revisedPrompt })
}2.2 作为工具调用
把图片生成作为 AI 工具——AI 自己决定什么时候生成图片:
const generateImage = tool({
description: '生成图片。当用户要求创建、绘制、画一张图片时调用',
parameters: z.object({
prompt: z.string().describe('图片描述,用英文,尽量详细'),
size: z.enum(['1024x1024', '1792x1024', '1024x1792']).default('1024x1024'),
}),
execute: async ({ prompt, size }) => {
const response = await openai.images.generate({
model: 'dall-e-3',
prompt,
n: 1,
size,
})
return {
imageUrl: response.data[0].url,
revisedPrompt: response.data[0].revised_prompt,
}
},
})前端渲染图片结果:
function ImageResultCard({ data }: { data: { imageUrl: string; revisedPrompt: string } }) {
return (
<div className="space-y-2">
<img src={data.imageUrl} alt={data.revisedPrompt} className="rounded-xl max-w-md" />
<p className="text-xs text-gray-400">Prompt: {data.revisedPrompt}</p>
</div>
)
}3. 语音合成(TTS)
3.1 OpenAI TTS
TTS(Text-to-Speech)把文本转成自然语音。OpenAI 提供两个模型:tts-1(快,适合实时)和 tts-1-hd(慢,音质更好)。它们基于深度学习语音合成,能生成接近真人的语音,支持多种音色(alloy、echo、nova 等)。每 1M 字符约 $15。
将 AI 回复转成语音——实现语音播报:
// app/api/tts/route.ts
import OpenAI from 'openai'
const openai = new OpenAI()
export async function POST(request: Request) {
const { text, voice = 'alloy' } = await request.json()
const response = await openai.audio.speech.create({
model: 'tts-1',
voice: voice as 'alloy' | 'echo' | 'fable' | 'onyx' | 'nova' | 'shimmer',
input: text,
response_format: 'mp3',
})
// 返回音频流
const buffer = Buffer.from(await response.arrayBuffer())
return new Response(buffer, {
headers: {
'Content-Type': 'audio/mpeg',
'Content-Length': buffer.length.toString(),
},
})
}3.2 客户端播放
'use client'
export function SpeakButton({ text }: { text: string }) {
const [isPlaying, setIsPlaying] = useState(false)
async function speak() {
setIsPlaying(true)
const response = await fetch('/api/tts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text, voice: 'nova' }),
})
const audioBlob = await response.blob()
const audioUrl = URL.createObjectURL(audioBlob)
const audio = new Audio(audioUrl)
audio.onended = () => {
setIsPlaying(false)
URL.revokeObjectURL(audioUrl)
}
audio.play()
}
return (
<button onClick={speak} disabled={isPlaying} className="text-gray-400 hover:text-gray-600">
{isPlaying ? '🔊 播放中...' : '🔈 朗读'}
</button>
)
}4. 语音识别(STT)
4.1 OpenAI Whisper
STT(Speech-to-Text)把语音转成文字。Whisper 是 OpenAI 开源的语音识别模型,支持 99 种语言,对中文识别效果很好。它基于 Transformer 架构,把音频转换成频谱图(mel spectrogram)后进行序列到序列的转录。指定 language: 'zh' 可以提高中文识别准确率,因为模型不需要花精力判断语言。
将用户语音转成文字——实现语音输入:
// app/api/stt/route.ts
import OpenAI from 'openai'
const openai = new OpenAI()
export async function POST(request: Request) {
const formData = await request.formData()
const audioFile = formData.get('audio') as File
const transcription = await openai.audio.transcriptions.create({
model: 'whisper-1',
file: audioFile,
language: 'zh', // 指定中文
})
return Response.json({ text: transcription.text })
}4.2 浏览器录音
浏览器通过 MediaRecorder API 录音。注意几个关键点:
- 需要用户授权麦克风(
getUserMedia),首次会弹出权限请求 - 录音格式用
audio/webm(浏览器原生支持),Whisper API 能直接处理 - 录音结束后记得释放麦克风(
stream.getTracks().forEach(track => track.stop())),否则浏览器会一直显示录音指示器
'use client'
export function VoiceInput({ onTranscription }: { onTranscription: (text: string) => void }) {
const [isRecording, setIsRecording] = useState(false)
const mediaRecorder = useRef<MediaRecorder | null>(null)
const chunks = useRef<Blob[]>([])
async function startRecording() {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
const recorder = new MediaRecorder(stream, { mimeType: 'audio/webm' })
recorder.ondataavailable = (e) => chunks.current.push(e.data)
recorder.onstop = async () => {
const audioBlob = new Blob(chunks.current, { type: 'audio/webm' })
chunks.current = []
// 发送到服务端识别
const formData = new FormData()
formData.append('audio', audioBlob, 'recording.webm')
const response = await fetch('/api/stt', { method: 'POST', body: formData })
const { text } = await response.json()
onTranscription(text)
// 释放麦克风
stream.getTracks().forEach((track) => track.stop())
}
recorder.start()
mediaRecorder.current = recorder
setIsRecording(true)
}
function stopRecording() {
mediaRecorder.current?.stop()
setIsRecording(false)
}
return (
<button
onMouseDown={startRecording}
onMouseUp={stopRecording}
className={`p-3 rounded-full ${isRecording ? 'bg-red-500 animate-pulse' : 'bg-gray-200'}`}
>
🎤 {isRecording ? '松开结束' : '按住说话'}
</button>
)
}4.3 集成到聊天
export function ChatUI() {
const { append } = useChat()
function handleVoiceInput(text: string) {
// 语音识别结果直接发送到对话
append({ role: 'user', content: text })
}
return (
<div>
{/* ... 消息列表 ... */}
<div className="flex gap-2">
<input ... />
<VoiceInput onTranscription={handleVoiceInput} />
<button type="submit">发送</button>
</div>
</div>
)
}5. 文件处理
5.1 PDF 分析
用户上传 PDF → 提取文本 → 作为上下文发送给 AI:
// app/api/analyze-pdf/route.ts
import pdf from 'pdf-parse'
export async function POST(request: Request) {
const formData = await request.formData()
const file = formData.get('file') as File
const question = formData.get('question') as string
const buffer = Buffer.from(await file.arrayBuffer())
const pdfData = await pdf(buffer)
const result = await generateText({
model: openai('gpt-4o'),
messages: [
{
role: 'system',
content: `以下是一份 PDF 文档的内容,请根据文档内容回答用户问题。\n\n文档内容:\n${pdfData.text.slice(0, 50000)}`,
},
{ role: 'user', content: question },
],
})
return Response.json({ answer: result.text })
}5.2 多模态消息统一处理
生产环境中,用户可能同时发送文本、图片和文件。需要一个统一的处理层把不同类型的附件转换成 AI SDK 的多模态消息格式。核心思路:图片转 base64 作为 image 类型,PDF 提取文本作为 text 类型注入上下文:
export async function POST(request: Request) {
const formData = await request.formData()
const messagesJson = formData.get('messages') as string
const messages = JSON.parse(messagesJson)
const attachments = formData.getAll('attachments') as File[]
// 处理附件 → 转成多模态消息
const lastMessage = messages[messages.length - 1]
if (attachments.length > 0) {
const content: any[] = [{ type: 'text', text: lastMessage.content }]
for (const file of attachments) {
if (file.type.startsWith('image/')) {
const base64 = Buffer.from(await file.arrayBuffer()).toString('base64')
content.push({
type: 'image',
image: `data:${file.type};base64,${base64}`,
})
}
// PDF/文本文件 → 提取内容注入上下文
if (file.type === 'application/pdf') {
const pdfData = await pdf(Buffer.from(await file.arrayBuffer()))
content.push({
type: 'text',
text: `\n\n[文件: ${file.name}]\n${pdfData.text.slice(0, 20000)}`,
})
}
}
lastMessage.content = content
}
const result = streamText({ model: openai('gpt-4o'), messages })
return result.toDataStreamResponse()
}6. 模型能力对比
| 能力 | GPT-4o | Claude 3.5 | Gemini 2.0 |
|---|---|---|---|
| 文本 | ✅ | ✅ | ✅ |
| 图片输入 | ✅ | ✅ | ✅ |
| 图片生成 | ✅(DALL-E) | ❌ | ✅(Imagen) |
| 音频输入 | ✅ | ❌ | ✅ |
| 音频输出 | ✅(TTS) | ❌ | ✅ |
| 视频理解 | ❌ | ❌ | ✅ |
| PDF 直接输入 | ❌(需提取文字) | ✅(原生) | ✅(原生) |
选择模型时考虑多模态能力。几个实用建议:
- 如果只需要图片理解,GPT-4o / Claude 3.5 都很好
- 如果需要 PDF 原生支持(不用提取文本),Claude 和 Gemini 更方便
- 如果需要视频理解,目前只有 Gemini 支持
- 图片生成 + 理解都需要时,GPT-4o(DALL-E)是最全面的选择
- 多模态的 Token 成本比纯文本高得多,要注意控制图片分辨率和调用频率
本章小结
- Vision:GPT-4o/Claude/Gemini 原生支持图片输入,用
experimental_attachments发送 - 图片生成:DALL-E 3 + 工具调用,AI 自动决定何时生成图片
- TTS:OpenAI TTS API → 返回音频流 → 前端 Audio 播放
- STT:浏览器 MediaRecorder 录音 → Whisper API 转文字 → 作为消息发送
- 文件处理:PDF 提取文字注入上下文,图片转 base64 作为多模态消息
- 模型选择:根据多模态能力选模型,GPT-4o 最全面
下一章讲 AI 应用安全与成本控制。