생성
대화가 아니라 이미지, 음성, 전사 또는 동영상이 필요할 수 있습니다. 이 모든 것은 하나의 요청과 하나의 결과로 이루어진 생성입니다. 모두 같은 형태를 공유하므로 하나를 익히면 나머지도 사용할 수 있습니다.
가장 빠른 방법
결과를 스트리밍하는 서버 라우트입니다.
import { generateImage, toServerSentEventsResponse } from '@tanstack/ai'
import { openaiImage } from '@tanstack/ai-openai'
export async function POST(request: Request) {
const { prompt } = await request.json()
const stream = generateImage({
adapter: openaiImage('dall-e-3'),
prompt: typeof prompt === 'string' ? prompt : '',
stream: true,
})
return toServerSentEventsResponse(stream)
}
이를 호출하는 훅입니다.
import { fetchServerSentEvents, useGenerateImage } from '@tanstack/ai-react'
function ImageGenerator() {
const { generate, result, isLoading, error } = useGenerateImage({
connection: fetchServerSentEvents('/api/generate/image'),
})
return (
<div>
<button onClick={() => generate({ prompt: 'A sunset over mountains' })}>
{isLoading ? 'Generating…' : 'Generate'}
</button>
{error && <p role="alert">{error.message}</p>}
{result?.images.map((img, i) => (
<img key={i} src={img.url || `data:image/png;base64,${img.b64Json}`} />
))}
</div>
)
}
이것이 전체 루프입니다. 아래 표의 다른 쌍으로 generateImage와 useGenerateImage를 바꾸면 다른 것은 변경할 필요가 없습니다.
어떤 전송 방식인가요?
| 전송 방식 | 사용 시점 | 방법 |
|---|---|---|
| 스트리밍 라우트 | API 라우트가 있을 때 사용합니다. 기본 방식이며 위 스니펫에서 사용합니다. | stream: true와 toServerSentEventsResponse를 사용하고 훅에 connection:을 전달합니다. |
| 직접 호출 | 서버 함수를 호출하고 일반 JSON을 반환받을 때 사용합니다. 가장 간단하며 스트리밍하지 않습니다. | 함수에서 결과를 반환하고 훅에 fetcher:를 전달합니다. |
| 스트리밍하는 서버 함수 | TanStack Start 서버 함수를 사용하며 타입이 지정된 입력과 스트리밍이 필요할 때 사용합니다. | 함수에서 toServerSentEventsResponse(...)를 반환하고 fetcher:를 전달합니다. |
Start를 사용할 때는 세 번째 방식이 두 장점을 모두 제공합니다. 입력에 타입이 완전히 지정되고 스트리밍이 자동으로 처리됩니다. 세 가지 방식 모두 전송 방식 전체 보기에 설명되어 있습니다.
사용 가능한 생성
| 활동 | 서버 함수 | 클라이언트 훅(React) | 가이드 |
|---|---|---|---|
| 이미지 생성 | generateImage() | useGenerateImage() | 이미지 생성 |
| 오디오 생성 | generateAudio() | useGenerateAudio() | 오디오 생성 |
| 텍스트 음성 변환 | generateSpeech() | useGenerateSpeech() | 텍스트 음성 변환 |
| 전사 | generateTranscription() | useTranscription() | 전사 |
| 요약 | summarize() | useSummarize() | - |
| 동영상 생성 | generateVideo() | useGenerateVideo() | 동영상 생성 |
참고: 동영상 생성은 작업/폴링 아키텍처를 사용합니다.
useGenerateVideo훅은 폴링 수명 주기를 추적할 수 있도록jobId,videoStatus,onJobCreated,onStatusUpdate도 추가로 제공합니다. 자세한 내용은 동영상 생성 가이드를 참고합니다.
고급
전송 방식 전체 보기
스트리밍 모드(Connection Adapter)
서버는 생성 함수에 stream: true를 전달하고 결과를 SSE로 보냅니다. 클라이언트는 fetchServerSentEvents()를 사용해 스트림을 소비합니다.
서버:
import { generateImage, toServerSentEventsResponse } from '@tanstack/ai'
import { openaiImage } from '@tanstack/ai-openai'
// In your API route handler
const stream = generateImage({
adapter: openaiImage('dall-e-3'),
prompt: 'A sunset over mountains',
stream: true,
})
return toServerSentEventsResponse(stream)
클라이언트:
import { useGenerateImage, fetchServerSentEvents } from '@tanstack/ai-react'
const { generate, result, isLoading } = useGenerateImage({
connection: fetchServerSentEvents('/api/generate/image'),
})
직접 호출 모드(Fetcher)
클라이언트는 서버 함수를 직접 호출하고 결과를 JSON으로 받습니다. 스트리밍 프로토콜은 필요하지 않습니다.
서버:
import { createServerFn } from '@tanstack/react-start'
import { generateImage } from '@tanstack/ai'
import { openaiImage } from '@tanstack/ai-openai'
export const generateImageFn = createServerFn({ method: 'POST' })
.inputValidator((data: { prompt: string }) => data)
.handler(async ({ data }) => {
return generateImage({
adapter: openaiImage('dall-e-3'),
prompt: data.prompt,
})
})
클라이언트:
import { useGenerateImage } from '@tanstack/ai-react'
import { generateImageFn } from '../lib/server-functions'
const { generate, result, isLoading } = useGenerateImage({
fetcher: (input) => generateImageFn({ data: input }),
})
서버 함수 스트리밍(Fetcher + Response)
두 방식의 장점을 결합합니다. fetcher 패턴의 타입 안전 입력과 SSE Response를 반환하는 서버 함수의 스트리밍을 함께 사용합니다. fetcher가 일반 결과가 아닌 Response 객체를 반환하면 클라이언트가 이를 SSE 스트림으로 자동 파싱합니다.
서버:
import { createServerFn } from '@tanstack/react-start'
import { generateImage, toServerSentEventsResponse } from '@tanstack/ai'
import { openaiImage } from '@tanstack/ai-openai'
export const generateImageStreamFn = createServerFn({ method: 'POST' })
.inputValidator((data: { prompt: string }) => data)
.handler(({ data }) => {
return toServerSentEventsResponse(
generateImage({
adapter: openaiImage('dall-e-3'),
prompt: data.prompt,
stream: true,
}),
)
})
클라이언트:
import { useGenerateImage } from '@tanstack/ai-react'
import { generateImageStreamFn } from '../lib/server-functions'
const { generate, result, isLoading } = useGenerateImage({
fetcher: (input) => generateImageStreamFn({ data: input }),
})
TanStack Start 서버 함수를 사용할 때 권장하는 방식입니다. 입력에 타입이 완전히 지정되고(예: ImageGenerateInput) 스트리밍 프로토콜이 투명하게 처리됩니다.
스트리밍 작동 방식
생성 함수에 stream: true를 전달하면 일반 결과 대신 StreamChunk 이벤트의 비동기 이터러블을 반환합니다.
1. RUN_STARTED → Client sets status to 'generating'
2. CUSTOM → Client receives the result
name: 'generation:result'
value: <your result>
3. RUN_FINISHED → Client sets status to 'success'
함수에서 예외가 발생하면 대신 RUN_ERROR 이벤트가 방출됩니다.
1. RUN_STARTED → Client sets status to 'generating'
2. RUN_ERROR → Client sets error + status to 'error'
error: { message: '...' }
이는 채팅 스트리밍에서 사용하는 것과 동일한 이벤트 프로토콜이므로 두 경우 모두 같은 전송 계층(toServerSentEventsResponse, fetchServerSentEvents)이 작동합니다.
서버가 RUN_ERROR를 방출하면 클라이언트는 이를 error에 표시하고 status를 'error'로 설정합니다. 이에 대응하려면 onError 콜백을 사용하고 UI에 error?.message를 렌더링합니다.
import { useGenerateImage, fetchServerSentEvents } from '@tanstack/ai-react'
function ImageGenerator() {
const { generate, result, error, status } = useGenerateImage({
connection: fetchServerSentEvents('/api/generate/image'),
onError: (err) => console.error('Generation failed:', err.message),
})
return (
<div>
<button onClick={() => generate({ prompt: 'A sunset over mountains' })}>
Generate
</button>
{status === 'error' && <p role="alert">Error: {error?.message}</p>}
{result?.images.map((img, i) => (
<img key={i} src={img.url || `data:image/png;base64,${img.b64Json}`} />
))}
</div>
)
}
공통 훅 API
모든 생성 훅은 동일한 인터페이스를 공유합니다.
| 옵션 | 타입 | 설명 |
|---|---|---|
connection | ConnectionAdapter | 스트리밍 전송(SSE, HTTP 스트림, 사용자 지정) |
fetcher | (input) => Promise<Result | Response> | 직접 호출하는 비동기 함수 또는 SSE Response를 반환하는 서버 함수 |
threadId | string | 이 생성의 안정적인 범위입니다. persistence가 켜져 있을 때 필요하며, 일시적인 실행에서는 선택 사항입니다. |
body | Record<string, any> | 추가 본문 매개변수(연결 모드) |
onResult | (result) => T | null | void | 결과를 변환하거나 결과에 반응합니다. |
onError | (error) => void | 오류 콜백 |
onProgress | (progress, message?) => void | 진행률 업데이트(0-100) |
| 반환값 | 타입 | 설명 |
|---|---|---|
generate | (input) => Promise<void> | 생성 시작 |
result | T | null | 결과(선택적으로 변환됨) 또는 null |
isLoading | boolean | 생성 진행 여부 |
error | Error | undefined | 현재 오류(있는 경우) |
status | GenerationClientState | 'idle' | 'generating' | 'success' | 'error' |
stop | () => void | 현재 생성 중단 |
reset | () => void | 모든 상태를 지우고 idle 상태로 돌아갑니다. |
결과 변환
onResult 콜백은 저장된 결과를 선택적으로 변환할 수 있습니다.
- null이 아닌 값을 반환하면 저장된 결과를 변환된 값으로 바꿉니다.
- **
null**을 반환하면 이전 결과를 변경하지 않습니다(필터링에 유용합니다). - 아무것도 반환하지 않으면(
void) 원시 결과를 그대로 저장합니다.
TypeScript는 onResult 반환값에서 결과 타입을 자동으로 추론하므로 명시적인 제네릭 매개변수가 필요하지 않습니다.
import { useGenerateSpeech, fetchServerSentEvents } from '@tanstack/ai-react'
import type { TTSResult } from '@tanstack/ai'
function SpeechPlayer() {
const { result } = useGenerateSpeech({
connection: fetchServerSentEvents('/api/generate/speech'),
onResult: (raw: TTSResult) => ({
audioUrl: `data:${raw.contentType};base64,${raw.audio}`,
duration: raw.duration,
}),
})
// result is typed as { audioUrl: string; duration?: number } | null
}
아키텍처
flowchart TB
subgraph Server ["Server"]
direction TB
activities["generateImage({ ..., stream: true })
generateSpeech({ ..., stream: true })
generateTranscription({ ..., stream: true })
summarize({ ..., stream: true })
generateVideo({ ..., stream: true })"]
transport["toServerSentEventsResponse()"]
activities --> transport
end
transport -- "StreamChunks via SSE
RUN_STARTED → generation:result → RUN_FINISHED" --> adapter
subgraph Client ["Client"]
direction TB
adapter["fetchServerSentEvents('/api/...')"]
gc["GenerationClient
(state machine)"]
hooks["Framework Hooks
useGenerateImage() · useGenerateSpeech()
useGenerateVideo() · useSummarize()
useTranscription()"]
adapter --> gc
gc -- "result, isLoading, error, status" --> hooks
end
핵심은 서버의 모든 생성 활동이 결과를 반환하는 비동기 함수일 뿐이라는 점입니다. stream: true를 전달하면 함수는 일반 결과 대신 StreamChunk 이터러블을 반환하며, 클라이언트는 이를 이미 소비할 수 있습니다.