본문으로 건너뛰기

멀티모달 콘텐츠

TanStack AI는 메시지에서 멀티모달 콘텐츠를 지원하므로, 이러한 모달리티를 지원하는 AI 모델에 텍스트와 함께 이미지, 오디오, 비디오, 문서를 전송할 수 있습니다.

AI 모델에 메시지를 보낼 때 다음과 같은 다양한 콘텐츠 유형을 포함할 수 있습니다.

  • 텍스트 - 일반 텍스트 메시지
  • 이미지 - JPEG, PNG, GIF, WebP 이미지
  • 오디오 - 오디오 파일(모델에 따라 지원)
  • 비디오 - 비디오 파일(모델에 따라 지원)
  • 문서 - PDF 및 기타 문서 유형

콘텐츠 파트

멀티모달 메시지는 다양한 콘텐츠 유형을 표현하기 위해 ContentPart 타입을 사용합니다.

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 배열일 수 있습니다.

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

const response = await chat({
adapter: openaiText('gpt-5.5'),
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의 비전, 오디오 및 문서 지원 모델은 이미지, 오디오, PDF 문서를 지원합니다.

import { openaiText } from '@tanstack/ai-openai'
import { imageBase64, pdfBase64 } from './data'

const adapter = openaiText('gpt-5.5')

// 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'
}
]
}
import { pdfBase64 } from './data'

// PDF document via base64 data (the API requires a filename alongside
// inline data; defaults to "document.pdf" when omitted)
const documentMessage = {
role: 'user',
content: [
{ type: 'text', content: 'Summarize this document' },
{
type: 'document',
source: { type: 'data', value: pdfBase64, mimeType: 'application/pdf' },
metadata: { filename: 'report.pdf' }
}
]
}

모델별 지원 모달리티:

  • gpt-5.5, gpt-5.2, gpt-5-mini (그 외): 텍스트, 이미지, PDF 문서
  • gpt-4o-audio: 텍스트, 오디오

모델별 권위 있는 목록은 @tanstack/ai-openaimodel-meta.ts에서 각 모델의 supports.input을 확인하세요.

Anthropic

Anthropic의 Claude 모델은 이미지와 PDF 문서를 지원합니다.

import { anthropicText } from '@tanstack/ai-anthropic'
import { imageBase64, pdfBase64 } from './data'

const adapter = anthropicText('claude-sonnet-4-6')

// 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 모델(예: claude-haiku-4-5, claude-sonnet-5, claude-opus-4-8, claude-fable-5): 텍스트, 이미지 및 문서(PDF)

모델별 권위 있는 목록은 @tanstack/ai-anthropicmodel-meta.ts에서 각 모델의 supports.input을 확인하세요.

Gemini

Google의 Gemini 모델은 다양한 모달리티를 폭넓게 지원합니다.

import { geminiText } from '@tanstack/ai-gemini'
import { imageBase64 } from './data'

const adapter = geminiText('gemini-3-flash-preview')

// 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-2.5-flash: 텍스트, 이미지, 오디오, 비디오

Ollama

Ollama는 호환되는 모델에서 이미지를 지원합니다.

import { ollamaText } from '@tanstack/ai-ollama'
import { imageBase64 } from './data'

// `ollamaText(model)` takes a model name. The host is read from the
// `OLLAMA_HOST` environment variable (defaults to http://localhost:11434).
const adapter = ollamaText('llama3.2-vision')

// 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 필드는 필수입니다.

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'을 사용합니다. 제공자는 URL 또는 응답 헤더에서 콘텐츠 유형을 추론할 수 있는 경우가 많으므로 mimeType 필드는 선택 사항입니다.

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

참고: 모든 제공자가 모든 모달리티에서 URL 기반 콘텐츠를 지원하는 것은 아닙니다. 자세한 내용은 제공자 문서를 확인하세요.

이전 버전과의 호환성

문자열 콘텐츠는 이전과 동일하게 계속 작동합니다.

// 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: '...' } }
]
}

타입 안전성

멀티모달 타입은 완전히 타입이 지정되어 있습니다. 제공자별 메타데이터 타입을 사용할 수 있습니다.

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 { GeminiImageMetadata } from '@tanstack/ai-gemini'

동적 메시지 검증

외부 소스(예: request.json())에서 메시지를 받을 때 데이터의 타입은 any입니다. TanStack AI는 런타임 메시지 검증기를 제공하지 않으므로, 선호하는 Standard-Schema 라이브러리(Zod, Valibot, ArkType, …)로 스키마를 정의하고 본문을 파싱한 후 chat()에 전달하세요.

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

const ContentPartSchema = z.discriminatedUnion('type', [
z.object({ type: z.literal('text'), content: z.string() }),
z.object({
type: z.literal('image'),
source: z.object({ type: z.enum(['url', 'data']), value: z.string() }),
}),
])

const MessageSchema = z.object({
// `ModelMessage.role` is 'user' | 'assistant' | 'tool' — there is no
// 'system' role. System instructions are passed separately via the
// `systemPrompts` option on `chat()`, not as messages.
role: z.enum(['user', 'assistant', 'tool']),
content: z.union([z.string(), z.array(ContentPartSchema)]),
})

const BodySchema = z.object({ messages: z.array(MessageSchema) })

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

const stream = chat({
adapter: openaiText('gpt-5.5'),
messages,
})

chat()의 TypeScript 타입은 호출 위치에서 추가하는 모든 항목을 선택한 모델이 지원하는 모달리티로 제한합니다.

모범 사례

  1. 적절한 소스 유형 사용: 작은 콘텐츠이거나 콘텐츠를 인라인으로 포함해야 할 때는 data를 사용합니다. 큰 파일 또는 콘텐츠가 이미 호스팅된 경우에는 url을 사용합니다.

  2. 메타데이터 포함: 모델이 콘텐츠를 올바르게 처리하도록 관련 메타데이터(예: mimeType 또는 detail)를 제공합니다.

  3. 모델 지원 확인: 모든 모델이 모든 모달리티를 지원하는 것은 아닙니다. 사용 중인 모델이 전송하려는 콘텐츠 유형을 지원하는지 확인합니다.

  4. 오류를 적절히 처리: 모델이 특정 모달리티를 지원하지 않으면 오류가 발생할 수 있습니다. 애플리케이션에서 이러한 경우를 처리합니다.

클라이언트 측 멀티모달 메시지

@tanstack/ai-clientChatClient를 사용할 때 sendMessage 메서드로 UI에서 직접 멀티모달 메시지를 보낼 수 있습니다.

기본 사용법

sendMessage 메서드는 단순한 문자열 또는 MultimodalContent 객체를 받습니다.

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를 제공할 수 있습니다.

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

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

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

메시지별 전달된 props

두 번째 매개변수를 사용하면 해당 요청에 추가 forwardedProps를 전달할 수 있습니다. 이는 클라이언트의 기본 forwardedProps 구성과 얕게 병합되며 메시지별 값이 우선합니다.

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

const client = new ChatClient({
connection: fetchServerSentEvents('/api/chat'),
forwardedProps: { model: 'gpt-5' }, // Base forwarded props
})

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

참고: 레거시 body 생성자 옵션은 계속 지원되지만 더 이상 사용되지 않습니다. 새 코드에서는 forwardedProps를 사용해야 합니다. 두 옵션 모두 동일한 wire 필드를 채웁니다.

React 예제

React 컴포넌트에서 멀티모달 메시지를 사용하는 방법은 다음과 같습니다.

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>
)
}

파일 업로드 예제

파일 업로드를 처리하고 멀티모달 콘텐츠로 보내는 방법은 다음과 같습니다.

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, mimeType: file.type }
}
]
})
}

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