文档
CodeRabbit
Cloudflare
AG Grid
Netlify
Neon
WorkOS
Clerk
Convex
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
CodeRabbit
Cloudflare
AG Grid
Netlify
Neon
WorkOS
Clerk
Convex
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
类引用
函数引用
接口引用
类型别名引用
变量引用
指南

多模态内容

TanStack AI 支持在消息中使用多模态内容,允许您将图像、音频、视频和文档与文本一起发送给支持这些模态的 AI 模型。

向 AI 模型发送消息时,您可以包含不同类型的内容

  • 文本 - 纯文本消息
  • 图像 - JPEG、PNG、GIF、WebP 图像
  • 音频 - 音频文件(模型相关支持)
  • 视频 - 视频文件(模型相关支持)
  • 文档 - PDF 和其他文档类型

内容部分

多模态消息使用 ContentPart 类型来表示不同的内容类型

typescript
import type { ContentPart, ImagePart, TextPart } from '@tanstack/ai'

// Text content
const textPart: TextPart = {
  type: 'text',
  content: 'What do you see in this image?'
}

// Image from base64 data (mimeType is required for data sources)
const imagePart: ImagePart = {
  type: 'image',
  source: {
    type: 'data',
    value: 'base64EncodedImageData...',
    mimeType: 'image/jpeg' // Required for data sources
  },
  metadata: {
    // Provider-specific metadata
    detail: 'high' // OpenAI detail level
  }
}

// Image from URL (mimeType is optional for URL sources)
const imageUrlPart: ImagePart = {
  type: 'image',
  source: {
    type: 'url',
    value: 'https://example.com/image.jpg',
    mimeType: 'image/jpeg' // Optional hint for URL sources
  }
}

在消息中使用多模态内容

消息的 content 可以是字符串或 ContentPart 数组

typescript
import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'

const response = await chat({
  adapter: openaiText('gpt-5.2'),
  messages: [
    {
      role: 'user',
      content: [
        { type: 'text', content: 'What is in this image?' },
        {
          type: 'image',
          source: {
            type: 'url',
            value: 'https://example.com/photo.jpg'
          }
        }
      ]
    }
  ]
})

提供商支持

OpenAI

OpenAI 在其视觉和音频模型中支持图像和音频

typescript
import { openaiText } from '@tanstack/ai-openai'

const adapter = openaiText()

// Image with detail level metadata
const message = {
  role: 'user' ,
  content: [
    { type: 'text' , content: 'Describe this image' },
    {
      type: 'image' ,
      source: { type: 'data' , value: imageBase64, mimeType: 'image/jpeg' },
      metadata: { detail: 'high' } // 'auto' | 'low' | 'high'
    }
  ]
}

模型支持的模态

  • gpt-5.2, gpt-5-mini: 文本, 图像
  • gpt-5.2-audio-preview: 文本, 图像, 音频

Anthropic

Anthropic 的 Claude 模型支持图像和 PDF 文档

typescript
import { anthropicText } from '@tanstack/ai-anthropic'

const adapter = anthropicText()

// Image with mimeType in source
const imageMessage = {
  role: 'user' ,
  content: [
    { type: 'text' , content: 'What do you see?' },
    {
      type: 'image' ,
      source: { type: 'data' , value: imageBase64, mimeType: 'image/jpeg' }
    }
  ]
}

// PDF document
const docMessage = {
  role: 'user',
  content: [
    { type: 'text', content: 'Summarize this document' },
    {
      type: 'document',
      source: { type: 'data', value: pdfBase64, mimeType: 'application/pdf' }
    }
  ]
}

支持的模态

  • Claude 3 模型: 文本, 图像
  • Claude 3.5 模型: 文本, 图像, 文档 (PDF)

Gemini

Google 的 Gemini 模型支持广泛的模态

typescript
import { geminiText } from '@tanstack/ai-gemini'

const adapter = geminiText()

// Image with mimeType in source
const message = {
  role: 'user',
  content: [
    { type: 'text', content: 'Analyze this image' },
    {
      type: 'image',
      source: { type: 'data', value: imageBase64, mimeType: 'image/png' }
    }
  ]
}

支持的模态

  • gemini-1.5-pro, gemini-1.5-flash: 文本, 图像, 音频, 视频, 文档
  • gemini-2.0-flash: 文本, 图像, 音频, 视频, 文档

Ollama

Ollama 支持兼容模型中的图像

typescript
import { ollamaText } from '@tanstack/ai-ollama'

const adapter = ollamaText('https://:11434')

// Image as base64
const message = {
  role: 'user',
  content: [
    { type: 'text', content: 'What is in this image?' },
    {
      type: 'image',
      source: { type: 'data', value: imageBase64, mimeType: 'image/jpeg' }
    }
  ]
}

注意:Ollama 的支持因模型而异。请查阅特定模型的文档以获取多模态功能。

源类型

内容可以作为内联数据或 URL 提供

数据 (Base64)

对于内联 base64 编码的内容,请使用 type: 'data'需要 mimeType 字段,以确保提供商接收到正确的内容类型信息

typescript
const imagePart = {
  type: 'image',
  source: {
    type: 'data',
    value: 'iVBORw0KGgoAAAANSUhEUgAAAAUA...', // Base64 string
    mimeType: 'image/png' // Required for data sources
  }
}

const audioPart = {
  type: 'audio',
  source: {
    type: 'data',
    value: 'base64AudioData...',
    mimeType: 'audio/mp3' // Required for data sources
  }
}

URL

对于托管在 URL 上的内容,请使用 type: 'url'mimeType 字段是 可选的,因为提供商通常可以从 URL 或响应标头推断它

typescript
const imagePart = {
  type: 'image' ,
  source: {
    type: 'url' ,
    value: 'https://example.com/image.jpg',
    mimeType: 'image/jpeg' // Optional hint
  }
}

注意:并非所有提供商都支持基于 URL 的内容的所有模态。请查阅提供商文档以获取具体信息。

向后兼容性

字符串内容继续像以前一样工作

typescript
// This still works
const message = {
  role: 'user',
  content: 'Hello, world!'
}

// And this works for multimodal
const multimodalMessage = {
  role: 'user',
  content: [
    { type: 'text', content: 'Hello, world!' },
    { type: 'image', source: { type: 'url', value: '...' } }
  ]
}

类型安全

多模态类型已完全类型化。提供特定元数据类型可用

typescript
import type { 
  ContentPart,
  ImagePart,
  DocumentPart,
  AudioPart,
  VideoPart,
  TextPart 
} from '@tanstack/ai'

// Provider-specific metadata types
import type { OpenAIImageMetadata } from '@tanstack/ai-openai'
import type { AnthropicImageMetadata } from '@tanstack/ai-anthropic'
import type { GeminiMediaMetadata } from '@tanstack/ai-gemini'

处理动态消息

从外部来源(如 request.json())接收消息时,数据被类型化为 any,这可能会绕过 TypeScript 的类型检查。使用 assertMessages 来恢复类型安全

typescript
import { chat, assertMessages } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'

// In an API route handler
const { messages: incomingMessages } = await request.json()

const adapter = openaiText('gpt-5.2')

// Assert incoming messages are compatible with gpt-5.2 (text + image only)
const typedMessages = assertMessages({ adapter }, incomingMessages)

// Now TypeScript will properly check any additional messages you add
const stream = chat({
  adapter,
  messages: [
    ...typedMessages,
    // This will error if you try to add unsupported content types
    {
      role: 'user',
      content: [
        { type: 'text', content: 'What do you see?' },
        { type: 'image', source: { type: 'url', value: '...' } }
      ]
    }
  ]
})

注意:assertMessages 仅是一个类型级别的断言。它不执行运行时验证。对于消息内容的运行时验证,请使用像 Zod 这样的模式验证库。

最佳实践

  1. 使用适当的源类型:对于小内容或需要内联包含内容时,使用 data。对于大文件或内容已托管时,使用 url

  2. 包含元数据:提供相关元数据(如 mimeTypedetail)以帮助模型正确处理内容。

  3. 检查模型支持:并非所有模型都支持所有模态。验证您使用的模型是否支持您想要发送的内容类型。

  4. 优雅地处理错误:当模型不支持特定模态时,它可能会引发错误。在您的应用程序中处理这些情况。

客户端多模态消息

在使用 ChatClient@tanstack/ai-client 时,您可以使用 sendMessage 方法直接从您的 UI 发送多模态消息。

基本用法

sendMessage 方法接受一个简单的字符串或一个 MultimodalContent 对象

typescript
import { ChatClient, fetchServerSentEvents } from '@tanstack/ai-client'

const client = new ChatClient({
  connection: fetchServerSentEvents('/api/chat'),
})

// Simple text message
await client.sendMessage('Hello!')

// Multimodal message with image
await client.sendMessage({
  content: [
    { type: 'text', content: 'What is in this image?' },
    {
      type: 'image',
      source: { type: 'url', value: 'https://example.com/photo.jpg' }
    }
  ]
})

自定义消息 ID

您可以为消息提供自定义 ID

typescript
await client.sendMessage({
  content: 'Hello!',
  id: 'custom-message-id-123'
})

每条消息的主体参数

第二个参数允许您传递该特定请求的附加主体参数。这些参数将与客户端的基本主体配置浅合并,每条消息的参数优先

typescript
const client = new ChatClient({
  connection: fetchServerSentEvents('/api/chat'),
  body: { model: 'gpt-5' }, // Base body params
})

// Override model for this specific message
await client.sendMessage('Analyze this complex problem', {
  model: 'gpt-5',
  temperature: 0.2,
})

 

React 示例

以下是如何在 React 组件中使用多模态消息

tsx
import { useChat } from '@tanstack/ai-react'
import { fetchServerSentEvents } from '@tanstack/ai-client'
import { useState } from 'react'

function ChatWithImages() {
  const [imageUrl, setImageUrl] = useState('')
  const { sendMessage, messages } = useChat({
    connection: fetchServerSentEvents('/api/chat'),
  })

  const handleSendWithImage = () => {
    if (imageUrl) {
      sendMessage({
        content: [
          { type: 'text', content: 'What do you see in this image?' },
          { type: 'image', source: { type: 'url', value: imageUrl } }
        ]
      })
    }
  }

  return (
    <div>
      <input
        type="url"
        placeholder="Image URL"
        value={imageUrl}
        onChange={(e) => setImageUrl(e.target.value)}
      />
      <button onClick={handleSendWithImage}>Send with Image</button>
    </div>
  )
}

文件上传示例

以下是如何处理文件上传并将它们作为多模态内容发送

tsx
import { useChat } from '@tanstack/ai-react'
import { fetchServerSentEvents } from '@tanstack/ai-client'

function ChatWithFileUpload() {
  const { sendMessage } = useChat({
    connection: fetchServerSentEvents('/api/chat'),
  })

  const handleFileUpload = async (file: File) => {
    // Convert file to base64
    const base64 = await new Promise<string>((resolve) => {
      const reader = new FileReader()
      reader.onload = () => {
        const result = reader.result as string
        // Remove data URL prefix (e.g., "data:image/png;base64,")
        resolve(result.split(',')[1])
      }
      reader.readAsDataURL(file)
    })

    // Determine content type based on file type
    const type = file.type.startsWith('image/')
      ? 'image'
      : file.type.startsWith('audio/')
        ? 'audio'
        : file.type.startsWith('video/')
          ? 'video'
          : 'document'

    await sendMessage({
      content: [
        { type: 'text', content: `Please analyze this ${type}` },
        {
          type,
          source: { type: 'data', value: base64 },
          metadata: { mimeType: file.type }
        }
      ]
    })
  }

  return (
    <input
      type="file"
      accept="image/*,audio/*,video/*,.pdf"
      onChange={(e) => {
        const file = e.target.files?.[0]
        if (file) handleFileUpload(file)
      }}
    />
  )
}