이미지 생성
TanStack AI는 전용 이미지 어댑터를 통해 이미지 생성을 지원합니다. 이 가이드에서는 OpenAI 및 Gemini 공급자와 함께 이미지 생성 기능을 사용하는 방법을 설명합니다.
개요
이미지 생성은 TanStack AI의 다른 어댑터와 동일한 트리 셰이킹 가능한 아키텍처를 따르는 이미지 어댑터가 처리합니다. 이미지 어댑터는 다음을 지원합니다.
- OpenAI: DALL-E 2, DALL-E 3, GPT-Image-1, GPT-Image-1-Mini 및 GPT-Image-2 모델
- Gemini: Gemini 네이티브 이미지 모델(NanoBanana) 및 Imagen 3/4 모델
- BytePlus: Seedream 5.0, 4.5 및 4.0 모델
- fal.ai: Nano Banana Pro, FLUX 등을 포함한 600개 이상의 모델
기본 사용법
OpenAI 이미지 생성
import { generateImage } from "@tanstack/ai";
import { openaiImage } from "@tanstack/ai-openai";
// Generate an image (the adapter uses OPENAI_API_KEY from environment)
const result = await generateImage({
adapter: openaiImage("dall-e-3"),
prompt: "A beautiful sunset over mountains",
});
console.log(result.images[0]?.url); // URL to the generated image
Gemini 이미지 생성
Gemini는 두 가지 유형의 이미지 생성을 지원합니다. Gemini 네이티브 모델(NanoBanana)과 Imagen 모델입니다. 어댑터는 모델 이름을 기준으로 올바른 API에 자동으로 라우팅합니다.
import { generateImage } from "@tanstack/ai";
import { geminiImage } from "@tanstack/ai-gemini";
// Gemini native model (NanoBanana) — uses generateContent API
const result = await generateImage({
adapter: geminiImage("gemini-3.1-flash-image"),
prompt: "A futuristic cityscape at night",
size: "16:9_4K",
});
// Imagen model — uses generateImages API
const result2 = await generateImage({
adapter: geminiImage("imagen-4.0-generate-001"),
prompt: "A futuristic cityscape at night",
});
console.log(result.images[0]?.b64Json); // Base64 encoded image
BytePlus 이미지 생성
Seedream 크기는 토큰(1K, 2K, 4K) 또는 명시적 픽셀(2048x2048) 중 하나를 사용하며, 두 형식을 섞을 수 없습니다.
import { generateImage } from "@tanstack/ai";
import { byteplusImage } from "@tanstack/ai-byteplus";
const result = await generateImage({
adapter: byteplusImage("dola-seedream-5-0-pro-260628"),
prompt: "A futuristic cityscape at night",
size: "2K",
modelOptions: { watermark: false },
});
console.log(result.images[0]?.url);
Seedream에는 다른 공급자와 다른 동작이 두 가지 있습니다.
watermark의 기본값은true입니다. 끄지 않으면 BytePlus가 모서리에 "AI generated"를 표시합니다.numberOfImages는 개수가 아니라 상한입니다. Seedream에는n매개변수가 없으므로 이미지가 두 개 이상이면 그룹 이미지 모드로 매핑되고, 모델이 프롬프트에 적합한 이미지 수를 결정합니다. 네 개를 요청해도 두 개가 반환될 수 있습니다.
이미지 URL은 24시간 후 만료됩니다. 인라인 바이트를 사용하려면 modelOptions에 response_format: 'b64_json'을 전달합니다. 자세한 내용은 BytePlus 어댑터를 참조합니다.
옵션
공통 옵션
모든 이미지 어댑터는 다음 공통 옵션을 지원합니다.
| 옵션 | 유형 | 설명 |
|---|---|---|
adapter | ImageAdapter | 모델이 포함된 이미지 어댑터 인스턴스(필수) |
prompt | string | MediaPromptPart[] | 생성할 이미지 설명(필수)입니다. 일반 문자열 또는 이미지 조건부 생성을 지원하는 모델에서 텍스트와 이미지 입력을 교차 배치한 콘텐츠 파트의 순서 배열입니다. 아래의 이미지 조건부 생성을 참조합니다. |
numberOfImages | number | 생성할 이미지 수 |
size | string | WIDTHxHEIGHT 형식의 생성 이미지 크기 |
modelOptions? | object | 모델별 옵션(providerOptions에서 이름이 변경됨) |
크기 옵션
OpenAI 모델
| 모델 | 지원 크기 |
|---|---|
gpt-image-2 | 1024x1024, 1536x1024, 1024x1536, auto |
gpt-image-1 | 1024x1024, 1536x1024, 1024x1536, auto |
gpt-image-1-mini | 1024x1024, 1536x1024, 1024x1536, auto |
dall-e-3 | 1024x1024, 1792x1024, 1024x1792 |
dall-e-2 | 256x256, 512x512, 1024x1024 |
Gemini 네이티브 모델(NanoBanana)
Gemini 네이티브 이미지 모델은 템플릿 리터럴 크기 형식인 "aspectRatio_resolution"을 사용합니다. 각 모델은 컴파일 시 범위가 좁혀진 고유한 집합을 허용합니다.
| 모델 | 가로세로 비율 | 해상도 |
|---|---|---|
gemini-3.1-flash-image | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9, 1:4, 4:1, 1:8, 8:1 | 512, 1K, 2K, 4K |
gemini-3.1-flash-lite-image | 위와 동일한 14개(참고) | 1K |
gemini-3-pro-image | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 | 1K, 2K, 4K |
gemini-2.5-flash-image | 위와 동일한 10개 | 없음 — 비율만 사용 |
// Examples
size: "16:9_4K"; // Widescreen at 4K resolution
size: "1:1_2K"; // Square at 2K resolution
size: "1:8_512"; // Tall banner at the 512 (0.5K) tier — Flash Image only
size: "16:9"; // gemini-2.5-flash-image: bare ratio, no resolution suffix
K는 대소문자를 구분하며(1k는 API에서 거부됨), 가장 작은 등급의 토큰은 512입니다(512px 또는 0.5K가 아님). 또한 9:21은 Vertex/Cloud 전용이므로 허용되지 않습니다. Google은 gemini-2.5-flash-image에 대해 image_size를 문서화하지 않았으므로 이 모델은 가로세로 비율만 사용하고 어댑터는 imageSize를 전송하지 않습니다.
gemini-3.1-flash-lite-image참고. 네 가지 극단적 배너 비율(1:4,4:1,1:8,8:1)은 이 모델에 대해 부분적으로 추론한 값입니다. 다른 세 모델과 달리 Flash Lite에는 Google의 Gemini API 가이드에 모델별 비율 표가 없습니다. 14개 값의 집합은 Cloud 모델 페이지의 명시적 열거와 가이드의 단순한 "14개의 개별 가로세로 비율 집합"이라는 설명에서 가져왔습니다. 이에 대한 유일한 Gemini API 열거는 "새 가로세로 비율"이라는 제목의 10개 항목 목록이며, 이를 전체 목록이 아니라 새로운 항목 목록으로 해석했습니다. 실제로 API가 이 네 가지를 거부하면 이 모델에서는 표준 10개 비율을 우선 사용합니다.
Gemini Imagen 모델
Imagen 모델은 WIDTHxHEIGHT 형식을 허용하며, 내부적으로 가로세로 비율에 매핑합니다.
| 크기 | 가로세로 비율 |
|---|---|
1024x1024 | 1:1 |
1920x1080 | 16:9 |
1080x1920 | 9:16 |
또는 모델 옵션에서 가로세로 비율을 직접 지정할 수 있습니다.
import { generateImage } from "@tanstack/ai";
import { geminiImage } from "@tanstack/ai-gemini";
const result = await generateImage({
adapter: geminiImage("imagen-4.0-generate-001"),
prompt: "A landscape photo",
modelOptions: {
aspectRatio: "16:9",
},
});
이미지 조건부 생성
이미지 간 변환, 참조 기반, 다중 참조 및 편집/인페인트 흐름에서는 prompt를 콘텐츠 파트의 순서가 지정된 배열로 전달합니다. 이는 멀티모달 콘텐츠의 다른 부분에서 사용하는 것과 동일한 TextPart / ImagePart 형태입니다.
import { generateImage } from "@tanstack/ai";
import { openaiImage } from "@tanstack/ai-openai";
await generateImage({
adapter: openaiImage("gpt-image-2"),
prompt: [
{ type: "text", content: "Turn this into a cinematic product photo" },
{
type: "image",
source: { type: "url", value: "https://example.com/product.png" },
},
],
});
파트 순서는 중요합니다. 네이티브 멀티모달 프롬프트를 사용하는 공급자(Gemini 이미지 모델, OpenRouter)는 작성된 그대로 파트를 받으므로 텍스트에서 인접한 이미지를 참조할 수 있습니다.
import { generateImage } from "@tanstack/ai";
import { geminiImage } from "@tanstack/ai-gemini";
import { badExampleUrl, goodExampleUrl } from "./urls";
await generateImage({
adapter: geminiImage("gemini-3.1-flash-image"),
prompt: [
{ type: "text", content: "Not like this" },
{ type: "image", source: { type: "url", value: badExampleUrl } },
{ type: "text", content: "more like this" },
{ type: "image", source: { type: "url", value: goodExampleUrl } },
],
});
이름이 지정된 요청 필드를 사용하는 공급자(OpenAI, fal, xAI)는 이미지 파트를 추출하고 텍스트를 평탄화합니다(텍스트 파트는 원문 그대로 문단 단위로 결합됩니다).
허용되는 파트 유형은 컴파일 시 모델별로 좁혀집니다. 텍스트 전용 모델(예: dall-e-3, Imagen)에 이미지 파트를 전달하면 단순한 런타임 예외가 아니라 타입 오류가 발생합니다.
프롬프트에서 이미지 참조하기
프롬프트 텍스트는 항상 있는 그대로 전송되며 SDK가 참조 마커를 삽입하거나 다시 작성하지 않습니다. 텍스트에서 특정 입력 이미지를 참조하려면 공급자 고유의 규칙을 직접 작성합니다.
| 공급자 | 규칙 | 예시 |
|---|---|---|
| OpenAI (gpt-image) | OpenAI 의 프롬프팅 가이드에 따라 인덱싱된 본문 | "apply the style of image 2 to image 1" |
| FLUX.2 on fal / BFL | 인덱스형 문장(BFL 문서는 image N을 파싱함) | "subject from image 1, style from image 2" |
| Gemini (native image models) | 내용/역할로 참조를 설명합니다 | "using the attached fabric sample as the texture" |
| fal Kling / Seedance 엔드포인트 | @-태그, 입력 순서로 1 인덱싱 | "Put @Image1 in the style of @Image2" |
| xAI grok-imagine | 프롬프트 내 문법 없음 — 요청 순서대로 이미지 처리 | "render the product in the style of the second image" |
"image 2" 또는 @Image2가 가리키는 파트를 추적하려면 정보 제공용
metadata.tag 필드로 파트에 레이블을 지정할 수 있습니다. SDK는 이 필드를 무시하지만 코드 자체에 설명이 포함되도록 해 줍니다.
prompt: [
{ type: "text", content: "Put @Image1 in the style of @Image2" },
{
type: "image",
source: { type: "url", value: productUrl },
metadata: { tag: "product" },
},
{
type: "image",
source: { type: "url", value: styleUrl },
metadata: { tag: "style" },
},
];
소스 형식
ImagePart.source는 URL과 인라인 base64 데이터를 모두 지원하는 판별 유니온이므로, 가진 형식에 맞는 값을 전달합니다.
// URL source
{ type: 'image', source: { type: 'url', value: 'https://example.com/img.png' } }
// Inline base64 data (mimeType required)
{ type: 'image', source: { type: 'data', value: base64String, mimeType: 'image/png' } }
Gemini 네이티브 이미지 생성은 URL 소스를 로컬에서 가져오지 않습니다. URL은 fileData.fileUri로 그대로 전달되고 Gemini가 서버 측에서 가져오므로, 공개 HTTPS URL, Files API URI 및 gs:// 참조를 런타임 메모리에 이미지를 버퍼링하지 않고 모두 사용할 수 있습니다.
다음 두 경로는 URL을 그대로 전달할 수 없으며 실제 바이트를 업로드해야 합니다.
- OpenAI의
/images/edits및 Sora의input_reference입니다. - Gemini Veo: predict API가 인라인 바이트 또는
gs://참조만 허용합니다.
이 경우 HTTP(S) URL 입력을 다운로드해 메모리에 버퍼링해야 하므로 메모리가 제한된 런타임(예: Cloudflare Workers)에서 OOM이 발생할 수 있습니다. 따라서 기본적으로는 HTTP(S) URL 이미지 입력을 가져오는 대신 예외를 발생시킵니다. data: URI(또는 Veo의 경우 gs:// 참조)를 전달하거나 allowUrlFetch로 가져오기를 명시적으로 활성화합니다.
import { createOpenaiImage } from "@tanstack/ai-openai/adapters";
// Opt into downloading + buffering HTTP(S) URL image inputs (server runtimes
// with headroom). data: URIs always work without this flag.
const adapter = createOpenaiImage("gpt-image-2", apiKey, {
allowUrlFetch: true,
});
동일한 allowUrlFetch 옵션이 createOpenaiVideo와 createGeminiVideo에도 있습니다.
metadata.role을 통한 역할 힌트
생성 작업에 서로 다른 역할(마스크, 참조, 시작/끝 프레임)을 가진 입력이 여러 개 있으면 각 파트에 metadata.role을 설정합니다. 어댑터는 역할에 따라 공급자별 필드로 라우팅하며, 역할이 없는 파트는 위치 기반 매핑으로 대체됩니다.
| 역할 | 매핑 대상 |
|---|---|
'reference' | fal reference_image_urls; Gemini 멀티모달 파트; 위치 기반 대체 |
'character' | 'reference'와 동일; Veo referenceImages 슬롯(예정 — 아직 Veo 어댑터 없음) |
'mask' | OpenAI mask (gpt-image-2, gpt-image-1, dall-e-2); fal mask_url |
'control' | fal control_image_url (ControlNet / 깊이 / 포즈 조건 지정) |
'start_frame' | fal start_image_url; Veo image(예정) (generateVideo에서 사용) |
'end_frame' | fal end_image_url; Veo lastFrame(예정) (generateVideo에서 사용) |
마스크를 사용한 인페인트/편집
import { generateImage } from "@tanstack/ai";
import { openaiImage } from "@tanstack/ai-openai";
import { photoUrl, maskUrl } from "./urls";
await generateImage({
adapter: openaiImage("gpt-image-2"),
prompt: [
{ type: "text", content: "Replace the masked region with a tree" },
{
type: "image",
source: { type: "url", value: photoUrl },
},
{
type: "image",
source: { type: "url", value: maskUrl },
metadata: { role: "mask" },
},
],
});
다중 참조 구성
import { generateImage } from "@tanstack/ai";
import { geminiImage } from "@tanstack/ai-gemini";
await generateImage({
adapter: geminiImage("gemini-3.1-flash-image"),
prompt: [
{
type: "text",
content:
"Generate a new image of the product using the style of the second reference",
},
{
type: "image",
source: { type: "url", value: "https://example.com/product.png" },
},
{
type: "image",
source: { type: "url", value: "https://example.com/style.png" },
},
],
});
공급자 지원
| 공급자 | 동작 |
|---|---|
| OpenAI | gpt-image-2 / gpt-image-1 / gpt-image-1-mini → images.edit()으로 라우팅되며 소스 이미지 최대 16개와 선택적 마스크를 지원합니다.dall-e-2 → 소스 이미지 1개만 사용해 images.edit()를 호출합니다.dall-e-3 → 예외가 발생합니다(편집 미지원). |
| Gemini | 네이티브 모델(gemini-*-flash-image, "nano-banana" 등) → 프롬프트 파트를 멀티모달 contents에 1:1로 매핑하며 교차 배치 순서를 유지합니다. 입력 이미지는 최대 약 14개입니다(공급자 제한이며 SDK가 강제하지 않음).Imagen 모델 → 예외가 발생합니다(텍스트-이미지만 지원). |
| fal.ai | 필드 이름은 fal SDK 엔드포인트 타입에서 생성된 맵을 통해 엔드포인트별로 결정됩니다(예: nano-banana 편집은 image_urls, Fooocus 마스크는 mask_image_url). 알 수 없는 엔드포인트의 기본값은 1개 입력 → image_url, 여러 입력 → image_urls, role: 'mask' → mask_url, role: 'control' → control_image_url, role: 'reference' / 'character' → reference_image_urls입니다. 엔드포인트별 필드는 modelOptions로 재정의합니다. |
| Grok | grok-imagine 모델 → xAI의 /v1/images/edits(소스 이미지 최대 3개, xAI가 요청 순서로 지정하며 프롬프트는 있는 그대로 전송됨)입니다. role: 'mask' / 'control'은 예외를 발생시킵니다(Imagine API에 해당 기능 없음). grok-2-image-1212도 예외를 발생시킵니다(텍스트-이미지만 지원). |
| OpenRouter | 프롬프트 파트는 멀티모달 image_url / text 콘텐츠 파트에 1:1로 매핑되고 교차 배치 순서를 유지한 채 기반 이미지 모델로 전달됩니다. modelOptions.strength(0.0~1.0)는 이를 문서화한 모델(예: Recraft)에서 이미지 간 변환의 영향력을 제어합니다. 요청당 이미지 하나만 가능하며 numberOfImages > 1이면 예외가 발생합니다(게이트웨이가 개수 키를 무시함). |
| BytePlus | Seedream 모델 → 모든 입력 이미지는 image 필드의 일반 참조로 전달됩니다(최대 14개 또는 dola-seedream-5-0-pro-260628에서는 10개). 마스크, 제어 또는 프레임 채널이 없으므로 'reference' / 'character' 이외의 역할은 조용히 평탄화하지 않고 예외를 발생시킵니다. |
| Anthropic | 해당 없음 — 이미지 생성 API가 없습니다. |
이미지 조건부 생성을 지원하지 않는 어댑터는 입력을 조용히 삭제하는 대신 명확한 런타임 오류를 발생시켜 호출이 즉시 실패하도록 합니다.
풀스택 사용법
TanStack AI는 React 훅과 서버 측 스트리밍 헬퍼를 제공하므로 상용구 코드를 최소화해 풀스택 이미지 생성을 구축할 수 있습니다.
참고: 다시 로드한 후에도 배치를 유지하거나 공급자의 URL이 만료된 후에도 이미지를 유지하려면 생성 영속성을 추가합니다.
스트리밍 모드(서버 라우트 + 클라이언트 훅)
서버 — generateImage를 스트리밍 응답으로 래핑하는 API 라우트를 생성합니다.
// routes/api/generate/image.ts
import { generateImage, toServerSentEventsResponse } from "@tanstack/ai";
import { openaiImage } from "@tanstack/ai-openai";
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/api/generate/image")({
server: {
handlers: {
POST: async ({ request }) => {
const body = await request.json();
const { prompt, size, model, numberOfImages } = body.data;
const stream = generateImage({
adapter: openaiImage(model ?? "dall-e-3"),
prompt,
size,
numberOfImages,
stream: true,
});
return toServerSentEventsResponse(stream);
},
},
},
});
클라이언트 — 연결 어댑터와 함께 useGenerateImage 훅을 사용합니다.
import { useGenerateImage, fetchServerSentEvents } from "@tanstack/ai-react";
function ImageGenerator() {
const { generate, result, isLoading, error, reset } = useGenerateImage({
connection: fetchServerSentEvents("/api/generate/image"),
});
return (
<div>
<button
onClick={() => generate({ prompt: "A sunset over mountains" })}
disabled={isLoading}
>
{isLoading ? "Generating..." : "Generate"}
</button>
{error && <p>Error: {error.message}</p>}
{result?.images.map((img, i) => (
<img
key={i}
src={img.url || `data:image/png;base64,${img.b64Json}`}
alt={img.revisedPrompt || "Generated image"}
/>
))}
{result && <button onClick={reset}>Clear</button>}
</div>
);
}
다른 두 전송 방식(JSON을 반환하는 서버 함수 또는 SSE Response를 반환하는 방식)도 여기서 동일하게 작동합니다. 고급: 기타 전송 방식에 있으며, 생성에서 한 번 설명합니다.
훅 API
useGenerateImage 훅은 다음을 받습니다.
| 옵션 | 유형 | 설명 |
|---|---|---|
connection | ConnectionAdapter | 스트리밍 전송(SSE, HTTP 스트림, 사용자 지정) |
fetcher | (input) => Promise<ImageGenerationResult | Response> | 직접 비동기 함수 또는 SSE Response를 반환하는 서버 함수 |
threadId | string | 이 생성 작업의 안정적인 범위입니다. persistence가 켜져 있으면 필수이고, 임시 실행에서는 선택 사항입니다. |
body | Record<string, any> | 추가 본문 매개변수(연결 모드) |
onResult | (result) => TOutput | null | void | 이미지 생성 시 호출되는 콜백입니다. 변환된 값을 반환해 result로 저장할 수 있습니다. |
onError | (error) => void | 오류 발생 시 호출되는 콜백 |
onProgress | (progress, message?) => void | 진행률 업데이트(0~100) |
다음 값을 반환합니다.
| 속성 | 유형 | 설명 |
|---|---|---|
generate | (input: ImageGenerateInput) => Promise<void> | 생성을 시작합니다. |
result | ImageGenerationResult | null | 결과 또는 null |
isLoading | boolean | 생성 진행 여부 |
error | Error | undefined | 현재 오류(있는 경우) |
status | GenerationClientState | 'idle' | 'generating' | 'success' | 'error' |
stop | () => void | 현재 생성을 중단합니다. |
reset | () => void | 결과와 오류를 지우고 유휴 상태로 돌아갑니다. |
팁: React, Vue 또는 Svelte 앱에서 로딩 상태와 오류 처리를 사용해 이미지 생성을 시작하려면 생성 훅을 참조합니다.
고급
작동에 필요하지 않은 참조 세부 정보입니다.
기타 전송 방식
직접 모드(서버 함수 + Fetcher)
TanStack Start 서버 함수에서 스트리밍하지 않고 사용하려면 다음과 같이 합니다.
// lib/server-functions.ts
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; model?: string }) => data)
.handler(async ({ data }) => {
return generateImage({
adapter: openaiImage(data.model ?? "dall-e-3"),
prompt: data.prompt,
});
});
// components/ImageGenerator.tsx
import { useGenerateImage } from "@tanstack/ai-react";
import { generateImageFn } from "../lib/server-functions";
function ImageGenerator() {
const { generate, result, isLoading } = useGenerateImage({
fetcher: (data) => generateImageFn({ data }),
});
return (
<div>
<button
onClick={() => generate({ prompt: "A sunset over mountains" })}
disabled={isLoading}
>
Generate
</button>
{result?.images.map((img, i) => (
<img key={i} src={img.url || `data:image/png;base64,${img.b64Json}`} />
))}
</div>
);
}
서버 함수 스트리밍(Fetcher + Response)
결과를 스트리밍하는 TanStack Start 서버 함수의 경우입니다. fetcher는 타입 안전한 입력을 받고 SSE Response를 반환하며, 클라이언트가 이를 자동으로 파싱합니다.
// lib/server-functions.ts
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; model?: string }) => data)
.handler(({ data }) => {
return toServerSentEventsResponse(
generateImage({
adapter: openaiImage(data.model ?? "dall-e-3"),
prompt: data.prompt,
stream: true,
}),
);
});
import { useGenerateImage } from "@tanstack/ai-react";
import { generateImageStreamFn } from "../lib/server-functions";
function ImageGenerator() {
const { generate, result, isLoading } = useGenerateImage({
fetcher: (input) => generateImageStreamFn({ data: input }),
});
return (
<div>
<button
onClick={() => generate({ prompt: "A sunset over mountains" })}
disabled={isLoading}
>
Generate
</button>
{result?.images.map((img, i) => (
<img key={i} src={img.url || `data:image/png;base64,${img.b64Json}`} />
))}
</div>
);
}
모델 옵션
OpenAI 모델 옵션
OpenAI 모델은 모델별 Model Options를 지원합니다.
GPT-Image-2 / GPT-Image-1 / GPT-Image-1-Mini
import { generateImage } from "@tanstack/ai";
import { openaiImage } from "@tanstack/ai-openai";
const result = await generateImage({
adapter: openaiImage("gpt-image-2"),
prompt: "A cat wearing a hat",
modelOptions: {
quality: "high", // 'high' | 'medium' | 'low' | 'auto'
background: "transparent", // 'transparent' | 'opaque' | 'auto'
output_format: "png", // 'png' | 'jpeg' | 'webp'
moderation: "low", // 'low' | 'auto'
},
});
DALL-E 3
import { generateImage } from "@tanstack/ai";
import { openaiImage } from "@tanstack/ai-openai";
const result = await generateImage({
adapter: openaiImage("dall-e-3"),
prompt: "A futuristic car",
modelOptions: {
quality: "hd", // 'hd' | 'standard'
style: "vivid", // 'vivid' | 'natural'
},
});
Gemini Imagen 모델 옵션
import { generateImage } from "@tanstack/ai";
import { geminiImage } from "@tanstack/ai-gemini";
const result = await generateImage({
adapter: geminiImage("imagen-4.0-generate-001"),
prompt: "A beautiful garden",
modelOptions: {
aspectRatio: "16:9",
// personGeneration accepts PersonGeneration enum values: 'DONT_ALLOW' | 'ALLOW_ADULT' | 'ALLOW_ALL'
personGeneration: "ALLOW_ADULT",
negativePrompt: "blurry, low quality",
addWatermark: true,
outputMimeType: "image/png", // 'image/png' | 'image/jpeg' | 'image/webp'
},
});
Gemini 네이티브 모델 옵션(NanoBanana)
Gemini 네이티브 이미지 모델은 generateContent가 제공하므로 modelOptions는 위의 Imagen 옵션과 형태가 다른 GenerateContentConfig 필드입니다.
import { generateImage } from "@tanstack/ai";
import { geminiImage } from "@tanstack/ai-gemini";
const result = await generateImage({
adapter: geminiImage("gemini-3.1-flash-image"),
prompt: "A beautiful garden",
size: "16:9_4K",
modelOptions: {
seed: 42,
thinkingConfig: { thinkingBudget: 512 },
systemInstruction: "Always render in watercolor.",
// Merged over the imageConfig derived from `size`, per field. This keeps
// the 16:9 aspect ratio and overrides only the resolution tier.
// imageConfig accepts only aspectRatio and imageSize on the Gemini
// Developer API.
imageConfig: { imageSize: "2K" },
},
});
safetySettings는 SDK의 HarmCategory / HarmBlockThreshold 열거형을 사용하므로 일반 문자열은 타입 검사를 통과하지 못합니다. 두 타입 모두 @tanstack/ai-gemini에서 다시 내보내므로 자체 의존성에 @google/genai를 추가할 필요가 없습니다.
import { generateImage } from "@tanstack/ai";
import {
HarmBlockThreshold,
HarmCategory,
geminiImage,
} from "@tanstack/ai-gemini";
const result = await generateImage({
adapter: geminiImage("gemini-3.1-flash-image-preview"),
prompt: "A beautiful garden",
modelOptions: {
safetySettings: [
{
category: HarmCategory.HARM_CATEGORY_HATE_SPEECH,
threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH,
},
],
},
});
responseModalities는 허용되지 않습니다. 어댑터는 항상 ['TEXT', 'IMAGE']를 요청하므로 어떤 설정도 이미지 출력을 조용히 비활성화할 수 없습니다.
응답 형식
이미지 생성 결과에는 다음이 포함됩니다.
import type { TokenUsage } from "@tanstack/ai";
interface ImageGenerationResult {
id: string; // Unique identifier for this generation
model: string; // The model used
images: GeneratedImage[]; // Array of generated images
// Canonical TokenUsage (same shape as chat). Token-billed models also surface
// a per-modality breakdown on `promptTokensDetails` (e.g. text vs image input
// tokens for gpt-image-1). Usage-billed providers (fal) instead surface
// `usage.billed` ({ quantity, unit }) — see the note below.
usage?: TokenUsage;
}
interface GeneratedImage {
b64Json?: string; // Base64 encoded image data
url?: string; // URL to the image (OpenAI only)
revisedPrompt?: string; // Revised prompt (OpenAI only)
}
비용 추적(fal): fal은 토큰이 아니라 사용량 기반 단위로 청구합니다. fal 이미지 어댑터는 fal의
x-fal-billable-units결과 헤더에서 읽은 실제 청구량을usage.billed—{ quantity, unit: 'units' }로 제공합니다. 정확한 비용을 계산하려면 수량에GET https://api.fal.ai/v1/models/pricing?endpoint_id=…에서 가져온 엔드포인트 단가를 곱하면 되며,fetch인터셉터는 필요하지 않습니다.
import { generateImage } from "@tanstack/ai";
import { falImage } from "@tanstack/ai-fal";
import { unitPrice } from "./pricing";
const result = await generateImage({
adapter: falImage("fal-ai/flux/dev"),
prompt: "a serene mountain lake",
});
if (result.usage?.billed) {
const { quantity, unit } = result.usage.billed;
const cost = quantity * unitPrice; // unitPrice from fal pricing API
console.log(`Billed ${quantity} ${unit} (~$${cost})`);
}
모델 사용 가능 여부
OpenAI 모델
| 모델 | 요청당 이미지 수 |
|---|---|
gpt-image-2 | 1-10 |
gpt-image-1 | 1-10 |
gpt-image-1-mini | 1-10 |
dall-e-3 | 1 |
dall-e-2 | 1-10 |
Gemini 네이티브 모델(NanoBanana)
| 모델 | 설명 |
|---|---|
gemini-3.1-flash-image | Nano Banana 2 — 최신이자 가장 빠른 Gemini 네이티브 이미지 생성 |
gemini-3.1-flash-lite-image | Nano Banana 2 Lite — 매우 짧은 지연 시간과 저렴한 비용의 이미지 생성 |
gemini-3-pro-image | Nano Banana Pro — 더 높은 품질의 Gemini 네이티브 이미지 생성 |
gemini-2.5-flash-image | Nano Banana — 레거시 모델이며 2026-10-02에 종료됨 |
gemini-3.1-flash-image-preview 및 gemini-3-pro-image-preview ID는 2026-06-25에 종료되었습니다. 기존 코드가 컴파일되도록 지원 중단 예정 별칭으로 타입 유니온에 남아 있지만 해당 ID로 호출하면 실패하므로 위의 GA ID를 사용합니다.
Gemini Imagen 모델
| 모델 | 요청당 이미지 수 |
|---|---|
imagen-4.0-ultra-generate-001 | 1-4 |
imagen-4.0-generate-001 | 1-4 |
imagen-4.0-fast-generate-001 | 1-4 |
오류 처리
이미지 생성은 여러 이유로 실패할 수 있습니다. 어댑터는 API를 호출하기 전에 입력을 검증합니다.
import { generateImage } from "@tanstack/ai";
import { openaiImage } from "@tanstack/ai-openai";
try {
const result = await generateImage({
adapter: openaiImage("dall-e-3"),
prompt: "A cat",
size: "512x512", // Invalid size for DALL-E 3 — throws at runtime
});
} catch (error) {
if (error instanceof Error) {
console.error(error.message);
// "Size "512x512" is not supported by model "dall-e-3".
// Supported sizes: 1024x1024, 1792x1024, 1024x1792"
}
}
환경 변수
이미지 어댑터는 텍스트 어댑터와 동일한 환경 변수를 사용합니다.
- OpenAI:
OPENAI_API_KEY - Gemini (including NanoBanana):
GOOGLE_API_KEY또는GEMINI_API_KEY - BytePlus (Seedream):
ARK_API_KEY또는BYTEPLUS_API_KEY
명시적 API 키
프로덕션에서 사용하거나 명시적으로 제어해야 하는 경우에는 다음과 같이 합니다.
import { createOpenaiImage } from "@tanstack/ai-openai";
import { createGeminiImage } from "@tanstack/ai-gemini";
// OpenAI
const openaiAdapter = createOpenaiImage("dall-e-3", "your-openai-api-key");
// Gemini
const geminiAdapter = createGeminiImage(
"imagen-4.0-generate-001",
"your-google-api-key",
);