설정
예제는 AI SDK 7을 사용합니다. OpenAI 공급자, React 훅, Zod와 함께 설치하세요:npm install ai@7 @ai-sdk/openai@4 @ai-sdk/react@4 zod
공급자 구성
OpenAI 호환 어댑터로 Venice 공급자를 생성하세요:// lib/venice.ts
import { createOpenAI } from '@ai-sdk/openai';
const openai = createOpenAI({
apiKey: process.env.VENICE_API_KEY!,
baseURL: 'https://api.venice.ai/api/v1',
});
// Venice의 chat completions endpoint와 호환되도록 .chat() 사용
export const venice = (modelId: string) => openai.chat(modelId);
.chat()을 사용하면 요청이 Venice의 /chat/completions endpoint로 전달됩니다. 최신 AI SDK 버전에서는 기본 openai('model') 문법이 대신 Responses API를 사용하며, Venice는 이를 일부 제한 사항과 함께 /responses에서 제공합니다. 사용하기 전에 Responses API 가이드를 참고하세요.스트리밍 채팅 (Next.js App Router)
API 라우트
// app/api/chat/route.ts
import {
convertToModelMessages,
createUIMessageStreamResponse,
streamText,
toUIMessageStream,
type UIMessage,
} from 'ai';
import { venice } from '@/lib/venice';
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const result = streamText({
model: venice('venice-uncensored-1-2'),
instructions: 'You are a helpful, privacy-respecting AI assistant.',
messages: await convertToModelMessages(messages),
});
return createUIMessageStreamResponse({
stream: toUIMessageStream({ stream: result.stream }),
});
}
React 컴포넌트
// app/page.tsx
'use client';
import { useChat } from '@ai-sdk/react';
import { useState } from 'react';
export default function Chat() {
const [input, setInput] = useState('');
const { messages, sendMessage, status } = useChat();
const isLoading = status === 'submitted' || status === 'streaming';
return (
<div className="max-w-2xl mx-auto p-4">
<div className="space-y-4 mb-4">
{messages.map((m) => (
<div key={m.id} className={m.role === 'user' ? 'text-right' : 'text-left'}>
<span className="font-bold">{m.role === 'user' ? 'You' : 'Venice'}:</span>
{m.parts.map((part, i) =>
part.type === 'text' ? (
<p key={i} className="whitespace-pre-wrap">{part.text}</p>
) : null,
)}
</div>
))}
</div>
<form
onSubmit={(e) => {
e.preventDefault();
if (!input.trim()) return;
sendMessage({ text: input });
setInput('');
}}
className="flex gap-2"
>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Ask anything..."
className="flex-1 border rounded px-3 py-2"
disabled={isLoading}
/>
<button type="submit" disabled={isLoading} className="bg-red-600 text-white px-4 py-2 rounded">
Send
</button>
</form>
</div>
);
}
텍스트 생성(비스트리밍)
import { generateText } from 'ai';
import { venice } from '@/lib/venice';
const { text } = await generateText({
model: venice('z-ai-glm-5-3-flash'),
prompt: 'Explain zero-knowledge proofs in simple terms.',
});
console.log(text);
구조화된 출력
import { generateText, Output } from 'ai';
import { venice } from '@/lib/venice';
import { z } from 'zod';
const { output } = await generateText({
model: venice('venice-uncensored-1-2'),
output: Output.object({
schema: z.object({
recipe: z.object({
name: z.string(),
ingredients: z.array(z.string()),
steps: z.array(z.string()),
prepTimeMinutes: z.number(),
}),
}),
}),
prompt: 'Generate a recipe for chocolate chip cookies.',
});
console.log(output.recipe.name);
console.log(`Prep time: ${output.recipe.prepTimeMinutes} minutes`);
도구 호출(Tool Calling)
import { isStepCount, streamText, tool } from 'ai';
import { venice } from '@/lib/venice';
import { z } from 'zod';
const result = streamText({
model: venice('z-ai-glm-5-3-flash'),
messages: [{ role: 'user', content: 'What is the weather in Tokyo?' }],
tools: {
getWeather: tool({
description: 'Get current weather for a location',
inputSchema: z.object({
location: z.string().describe('City name'),
}),
execute: async ({ location }) => {
// 여기에 weather API 호출
return { temperature: 22, condition: 'Sunny', location };
},
}),
},
stopWhen: isStepCount(2),
});
for await (const part of result.stream) {
if (part.type === 'text-delta') {
process.stdout.write(part.text);
} else if (part.type === 'tool-result') {
console.log('Tool result:', part.output);
}
}
stopWhen: isStepCount(2)를 사용하면 도구가 실행된 뒤 모델이 두 번째 단계에서 답변할 수 있습니다. 이 옵션이 없으면 스트림은 도구 결과 후에 종료됩니다.
이미지 생성
Venice 이미지 생성은 AI SDK와 함께 직접 호출할 수 있습니다:// app/api/image/route.ts
export async function POST(req: Request) {
const { prompt } = await req.json();
const response = await fetch('https://api.venice.ai/api/v1/image/generate', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.VENICE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'qwen-image',
prompt,
aspect_ratio: '1:1',
}),
});
const data = await response.json();
return Response.json({ image: data.images[0] });
}
멀티 모델 채팅(모델 선택기)
사용자가 Venice 모델 중에서 선택할 수 있게 하세요:// app/api/chat/route.ts
import {
convertToModelMessages,
createUIMessageStreamResponse,
streamText,
toUIMessageStream,
type UIMessage,
} from 'ai';
import { venice } from '@/lib/venice';
const ALLOWED_MODELS = [
'venice-uncensored-1-2',
'z-ai-glm-5-3-flash',
'qwen3-vl-235b-a22b',
'nvidia-nemotron-3-nano-30b-a3b',
];
export async function POST(req: Request) {
const { messages, model: modelId }: { messages: UIMessage[]; model: string } =
await req.json();
if (!ALLOWED_MODELS.includes(modelId)) {
return new Response('Invalid model', { status: 400 });
}
const result = streamText({
model: venice(modelId),
messages: await convertToModelMessages(messages),
});
return createUIMessageStreamResponse({
stream: toUIMessageStream({ stream: result.stream }),
});
}
// 모델 선택기가 있는 클라이언트 컴포넌트
'use client';
import { useChat } from '@ai-sdk/react';
import { useState } from 'react';
const MODELS = [
{ id: 'venice-uncensored-1-2', name: 'Venice Uncensored', desc: 'Fast & uncensored' },
{ id: 'z-ai-glm-5-3-flash', name: 'GLM 5.3 Flash', desc: 'Fast private reasoning' },
{ id: 'qwen3-vl-235b-a22b', name: 'Qwen Vision', desc: 'Advanced vision + text' },
{ id: 'nvidia-nemotron-3-nano-30b-a3b', name: 'Nemotron 3 Nano 30B', desc: 'Fast & low cost' },
];
export default function Chat() {
const [model, setModel] = useState('venice-uncensored-1-2');
const [input, setInput] = useState('');
const { messages, sendMessage } = useChat();
return (
<div>
<select value={model} onChange={(e) => setModel(e.target.value)}>
{MODELS.map((m) => (
<option key={m.id} value={m.id}>{m.name} — {m.desc}</option>
))}
</select>
{messages.map((m) => (
<p key={m.id}>
{m.role}: {m.parts.map((part) => (part.type === 'text' ? part.text : '')).join('')}
</p>
))}
<form
onSubmit={(e) => {
e.preventDefault();
sendMessage({ text: input }, { body: { model } });
setInput('');
}}
>
<input value={input} onChange={(e) => setInput(e.target.value)} />
</form>
</div>
);
}
sendMessage는 각 메시지와 함께 선택된 model을 요청 본문에 담아 보내므로, 모델을 전환하면 다음 메시지부터 적용됩니다.
웹 검색 통합
모델 ID에 모델 기능 접미사를 붙여 Venice 웹 검색을 켜세요. 접미사는 모델 ID를 통해venice_parameters를 전달하므로 요청에 필드를 추가할 필요가 없습니다:
import { streamText } from 'ai';
import { venice } from '@/lib/venice';
const result = streamText({
model: venice('venice-uncensored-1-2:enable_web_search=auto'),
messages: [{ role: 'user', content: 'What happened in AI news today?' }],
});
임베딩(Embeddings)
임베딩에는 공급자에서 직접embeddingModel()을 사용하세요:
import { embed, embedMany } from 'ai';
import { createOpenAI } from '@ai-sdk/openai';
const openai = createOpenAI({
apiKey: process.env.VENICE_API_KEY!,
baseURL: 'https://api.venice.ai/api/v1',
});
// 단일 임베딩
const { embedding } = await embed({
model: openai.embeddingModel('text-embedding-bge-m3'),
value: 'Privacy-first AI infrastructure',
});
// 배치 임베딩
const { embeddings } = await embedMany({
model: openai.embeddingModel('text-embedding-bge-m3'),
values: [
'Venice AI provides private inference.',
'Zero data retention guaranteed.',
'OpenAI SDK compatible.',
],
});
환경 변수
# .env.local
VENICE_API_KEY=your-venice-api-key
권장 모델
| Use Case | Model | Why |
|---|---|---|
| 채팅 앱 | venice-uncensored-1-2 | 빠르고, 저렴하고, 필터링 없음 |
| 복잡한 작업 | z-ai-glm-5-3-flash | 빠른 프라이빗 추론 |
| 비전 앱 | qwen3-vl-235b-a22b | 고급 이미지 이해 |
| 대량 처리 | nvidia-nemotron-3-nano-30b-a3b | 100만 input 토큰당 $0.075, output $0.30로 저렴 |
| 도구 호출 | z-ai-glm-5-3-flash | 신뢰성 있는 함수 호출 |
Vercel AI SDK 문서
공식 Vercel AI SDK 문서
Venice 모델
모든 Venice 모델 둘러보기