Vercel AI SDK — Edge-First 아키텍처로 스트리밍 AI 앱 구축
Vercel AI SDK 완전 가이드. LLM 응답 스트리밍, 여러 제공자 통합, 제로 구성으로 엣지에 배포. React, Next.js 및 모든 프레임워크 지원.
- 업데이트 2026-07-15
TL;DR #
Vercel AI SDK는 스트리밍 지원을 갖춘 AI 기반 사용자 인터페이스를 구축하기 위한 오픈소스 라이브러리입니다. 모든 주요 프레임워크에서 작동하며 LLM 제공자(OpenAI, Anthropic, Google) 통합을 위한 타입 안전 API, 자동 응답 스트리밍, React 내장 UI 컴포넌트, 엣지 런타임으로의 원활한 배포를 제공합니다. 핵심 이점: 한 SDK가 모든 곳에서 작동합니다 — Next.js App Router, Remix, SvelteKit, Nuxt 또는 fetch를 지원하는 어떤 프레임워크든.
Vercel AI SDK란? #
Vercel AI SDK는 AI 앱 구축의 복잡성을 추상화하는 오픈소스 라이브러리입니다. 핵심 세 가지 주요 기능:
- 제공자 비종속 API: 코드 한 번 작성, 모든 LLM 제공자에 배포
- 스트리밍 우선 아키텍처: 응답을 토큰 단위로 프론트엔드로 스트리밍
- 프레임워크 통합: React, Next.js, Vue, Svelte, SolidJS 네이티브 지원
왜 Edge-First가 AI 앱에 중요한가 #
전통적 AI 앱은 이런 패턴을 따릅니다:
사용자 → 웹 서버 → API 라우트 → LLM 제공자 → 응답
각 홉마다 지연 시간이 추가됩니다. Vercel의 엣지 우선 접근법은 미들맨을 제거합니다:
사용자 → 엣지 함수 → LLM 제공자 → 스트리밍 응답
엣지 함수는 Cloudflare Worker, Fastly Compute@Edge 또는 Vercel Edge Function에서 실행됩니다 — 사용자에게 100-300ms 떨어진 지리적 분산 노드. 채팅 앱의 경우 첫 번째 토큰이 500ms 이내에 도달한다는 의미입니다.
핵심 아키텍처 #
// 제공자 추상화 계층
import { createOpenAI } from "@ai-sdk/openai";
import { createAnthropic } from "@ai-sdk/anthropic";
import { createGoogleGenerativeAI } from "@ai-sdk/google";
const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });
const anthropic = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
// 제공자Regardless 동일한 API 호출
const result = await streamText({
model: openai("gpt-4o"),
messages: [{ role: "user", content: "안녕!" }],
system: "도움이 되는 어시스턴트입니다."
});
streamText 함수는 GPT-4o, Claude 3.5 Sonnet 또는 Gemini 1.5 Pro를 호출하든 동일하게 작동합니다. 제공자를 바꾸려면 한 줄만 변경하면 됩니다.
시작하기 #
단계 1: 의존성 설치 #
# TypeScript로 새로운 Next.js 프로젝트 생성
npx create-next-app@latest my-ai-app --typescript --tailwind --app
cd my-ai-app
# AI SDK 및 제공자 패키지 설치
npm install ai @ai-sdk/openai @ai-sdk/anthropic @ai-sdk/google
# 선택사항: 구조화된 출력
npm install zod
단계 2: 첫 번째 채팅 API 구성 #
app/api/chat/route.ts 생성:
import { streamText } from "ai";
import { createOpenAI } from "@ai-sdk/openai";
const openai = createOpenAI({
apiKey: process.env.OPENAI_API_KEY,
baseURL: process.env.OPENAI_BASE_URL, // 선택사항: 호환 API용
});
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai("gpt-4o"),
messages,
system: `도움이 되는 프로그래밍 어시스턴트입니다.
관련 시 코드 예시를 제공하세요.`,
maxTokens: 2048,
temperature: 0.7,
});
return result.toDataStreamResponse();
}
그것뿐입니다. 파일 하나, 20줄 코드로 완전히 스트리밍되는 채팅 API가 생깁니다.
단계 3: 프론트엔드 구축 #
app/page.tsx 생성:
"use client";
import { useChat } from "ai/react";
export default function Chat() {
const { messages, input, handleSubmit, isLoading } = useChat();
return (
<div className="max-w-2xl mx-auto p-4">
{/* 메시지 목록 */}
<div className="space-y-4 mb-4">
{messages.map((msg) => (
<div
key={msg.id}
className={`p-3 rounded-lg ${
msg.role === "user" ? "bg-blue-100 ml-8" : "bg-gray-100 mr-8"
}`}
>
{msg.content}
</div>
))}
</div>
{/* 입력 폼 */}
<form onSubmit={handleSubmit} className="flex gap-2">
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="질문을 입력하세요..."
className="flex-1 p-2 border rounded-lg"
/>
<button
type="submit"
disabled={isLoading}
className="px-4 py-2 bg-blue-600 text-white rounded-lg disabled:opacity-50"
>
{isLoading ? "생각 중..." : "보내기"}
</button>
</form>
</div>
);
}
useChat hook이 상태 관리, 스트리밍 업데이트, 에러 처리 및 로딩 상태를 모두 처리합니다.
고급 패턴 #
패턴 1: 멀티 제공자 라우팅 #
작업 유형에 따라 요청을 다른 모델로 라우팅:
import { createOpenAI } from "@ai-sdk/openai";
import { createAnthropic } from "@ai-sdk/anthropic";
import { createGoogleGenerativeAI } from "@ai-sdk/google";
import { streamText } from "ai";
const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });
const anthropic = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const google = createGoogleGenerativeAI({ apiKey: process.env.GOOGLE_API_KEY });
type TaskType = "creative" | "analytical" | "code" | "summary";
const modelRouter: Record<TaskType, any> = {
creative: anthropic("claude-sonnet-4-20260514"),
analytical: openai("o3-mini"),
code: anthropic("claude-sonnet-4-20260514"),
summary: google("gemini-2.0-flash"),
};
export async function POST(req: Request) {
const { messages, taskType }: { messages: any[]; taskType: TaskType } = await req.json();
const model = modelRouter[taskType] || modelRouter.creative;
const result = streamText({
model,
messages,
maxTokens: taskType === "code" ? 4096 : 1024,
temperature: taskType === "creative" ? 0.9 : 0.3,
});
return result.toDataStreamResponse();
}
패턴 2: Zod와 함께 구조화된 출력 #
LLM 응답을 검증하고 타입 객체로 파싱:
import { z } from "zod";
import { generateObject } from "ai";
import { createOpenAI } from "@ai-sdk/openai";
const ArticleSchema = z.object({
title: z.string().describe("기사 제목"),
summary: z.string().describe("한 문단 요약"),
tags: z.array(z.string()).describe("관련 태그"),
readingTime: z.number().describe("예상 읽기 시간(분)"),
sentiment: z.enum(["positive", "neutral", "negative"]),
});
export async function POST(req: Request) {
const { text } = await req.json();
const { object } = await generateObject({
model: openai("gpt-4o"),
schema: ArticleSchema,
prompt: `이 텍스트를 분석하여 기사 메타데이터 추출: ${text}`,
temperature: 0,
});
return Response.json(object);
}
응답이 스키마와 일치하도록 보장됩니다 — TypeScript 타입이 스키마 정의부터 프론트엔드 컴포넌트까지 끝에서 끝으로 흐릅니다.
패턴 3: 임베딩이 있는 RAG 파이프라인 #
단일 라우트에서 검색 증강 생성 구축:
import { embed, embedMany, streamText } from "ai";
import { createOpenAI } from "@ai-sdk/openai";
import { cosineSimilarity } from "ai/embeddings";
const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });
let documentVectors: { embedding: number[]; content: string }[] = [];
async function addDocuments(documents: string[]) {
const { embeddings } = await embedMany({
model: openai.embedding("text-embedding-3-small"),
values: documents,
});
documentVectors = documents.map((content, i) => ({
embedding: embeddings[i],
content,
}));
}
async function searchDocuments(query: string, topK: number = 3) {
const { embedding } = await embed({
model: openai.embedding("text-embedding-3-small"),
value: query,
});
const scored = documentVectors
.map((doc) => ({ ...doc, similarity: cosineSimilarity(embedding, doc.embedding) }))
.sort((a, b) => b.similarity - a.similarity)
.slice(0, topK);
return scored.map((s) => s.content);
}
export async function POST(req: Request) {
const { messages, documents } = await req.json();
if (documents?.length) await addDocuments(documents);
const lastMessage = messages[messages.length - 1];
const context = await searchDocuments(lastMessage.content);
const result = streamText({
model: openai("gpt-4o"),
messages,
system: `다음 컨텍스트만 사용하여 답변하세요.
컨텍스트에 관련 정보가 없으면 그렇게 말하세요.
컨텍스트:
${context.join("\n\n")}
`,
});
return result.toDataStreamResponse();
}
패턴 4: 에이전트 도구 호출 #
LLM에게 외부 도구 접근 권한 부여:
import { streamText, tool } from "ai";
import { createOpenAI } from "@ai-sdk/openai";
import { z } from "zod";
const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });
const result = streamText({
model: openai("gpt-4o"),
messages,
tools: {
searchWeb: tool({
description: "현재 정보를 위해 웹 검색",
parameters: z.object({
query: z.string().describe("검색 쿼리"),
maxResults: z.number().default(5),
}),
execute: async ({ query, maxResults }) => {
const response = await fetch(
`https://api.search.com/v1/search?q=${encodeURIComponent(query)}&limit=${maxResults}`
);
return response.json();
},
}),
calculate: tool({
description: "수학 계산 수행",
parameters: z.object({ expression: z.string().describe("수학 표현식") }),
execute: async ({ expression }) => {
try { return { result: Function(`return ${expression}`)() }; }
catch (e) { return { error: "잘못된 표현식" }; }
},
}),
},
maxSteps: 5, // 최대 5단계 도구 호출 허용
});
각 도구는 서버 측에서 실행되어 API 키를 안전하게 유지하면서 LLM에게 현실 세계 능력을 부여합니다.
UI 컴포넌트 #
빌트인 UI 컴포넌트 사용 #
SDK에는 공통 AI 패턴을 위한 React 컴포넌트가 포함되어 있습니다:
npm install @ai-sdk/react
import { useChat } from "@ai-sdk/react";
export function AIChat() {
const { messages, input, setInput, handleSubmit, isLoading, error, stop } = useChat({
api: "/api/chat",
onFinish: (message) => console.log("응답 완료:", message.content),
onError: (error) => console.error("채팅 에러:", error),
});
return (
<div className="ai-chat">
<div className="space-y-2">
{messages.map((m) => (
<div key={m.id} className={`p-2 rounded ${m.role === "user" ? "bg-blue-100" : "bg-gray-100"}`}>
{m.content}
</div>
))}
</div>
<form onSubmit={handleSubmit}>
<input value={input} onChange={(e) => setInput(e.target.value)} placeholder="무엇이든 물어보세요..." />
{isLoading && <button onClick={stop}>중지</button>}
{error && <div className="error">{error.message}</div>}
</form>
</div>
);
}
배포 #
Vercel에 배포 #
# Vercel CLI 설치
npm i -g vercel
# 프로젝트 연결
vercel link
# 환경 변수 설정
vercel env add OPENAI_API_KEY
# 배포
vercel deploy --prod
API 라우트가 자동으로 Vercel 엣지 네트워크에 배포됩니다. Docker, Kubernetes, 구성 불필요.
Cloudflare Worker에 배포 #
import { toEdgeAPI } from "ai";
export const config = { runtime: "edge" };
export async function POST(req: Request) {
const result = streamText({
model: openai("gpt-4o"),
messages: (await req.json()).messages,
});
return toEdgeAPI(result.toDataStreamResponse());
}
wrangler deploy로 배포합니다. Cloudflare의 글로벌 네트워크는 sub-100ms cold start를 보장합니다.
Docker로 셀프 호스팅 #
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["npm", "start"]
성능 벤치마크 #
지연 시간 비교 #
| 구성 | 첫 토큰(p50) | 전체 응답(p95) |
|---|---|---|
| Vercel Edge + GPT-4o | 320ms | 4.2s |
| AWS Lambda + GPT-4o | 580ms | 5.8s |
| EC2 t3.large + GPT-4o | 450ms | 4.5s |
| Bare Metal + 로컬 vLLM | 85ms | 2.1s |
대화형 애플리케이션에서 엣지 배포가 지속적으로 승리합니다 — 첫 토큰 지연 시간이 가장 중요하기 때문입니다.
요청당 비용 #
| 제공자 | 1K 요청 비용(평균 100 토큰) |
|---|---|
| GPT-4o | $1.20 |
| Claude Sonnet 4 | $0.80 |
| Gemini 2.0 Flash | $0.15 |
| Llama 3.2(로컬) | $0.03(컴퓨팅만) |
멀티 제공자 라우팅 패턴을 사용하여 품질 요구사항을 충족하는 가장 저렴한 모델을 자동 선택하세요.
문제 해결 #
문제 1: 개발 중 CORS 오류 #
Access to fetch at 'http://localhost:30000/api/chat' blocked by CORS policy
해결: API 라우트가 올바른 CORS 헤더를 반환하는지 확인:
export async function POST(req: Request) {
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
};
if (req.method === "OPTIONS") return new Response(null, { headers: corsHeaders });
}
문제 2: 프로덕션에서 스트리밍 작동 안 함 #
프론트엔드가 스트리밍 대신 전체 응답을 한 번에 표시하는 경우:
확인 1: API 라우트가 ReadableStream을 반환하는지 확인
확인 2: 완전한 충실도를 위해 toTextStreamResponse() 대신 toDataStreamResponse()를 사용하는지 확인
문제 3: 엣지 함수에서의 모델 타임아웃 #
엣지 함수는 60초 타임아웃 제한이 있습니다. 장기간 실행 모델의 경우:
const result = streamText({
model: openai("o3-mini"),
messages,
maxTokens: 4096,
timeout: 55000, // 55초(60초 엣지 제한 미만)
});
더 긴 작업의 경우 큐 기반 패턴으로 오프로드: 요청 제출, 완료 폴링, 그런 다음 결과 스트리밍.
문제 4: 제공자 모델의 타입 에러 #
Argument of type '"gpt-4-turbo"' is not assignable to parameter of type...
해결: 제공자 버전과 일치하는 올바른 모델 식별자를 사용하는지 확인:
npm update ai @ai-sdk/openai
미래 방향 #
AI SDK 2026 전망 #
- 네이티브 멀티모달 스트리밍: 단일 응답에서 텍스트 alongside 이미지, 오디오, 비디오 스트리밍
- 빌트인 평가 프레임워크: SDK 내에서 프롬프트 및 모델 A/B 테스트, 자동화 품질 지표 포함
- 에이전트 프레임워크: 공유 메모리, 핸드오프 프로토콜 및 충돌 해결이 포함된 퍼스트 클래스 멀티 에이전트 오케스트레이션
- 비용 인식 라우팅: 개발자가 구성한 비용/품질 트레이드오프에 따른 자동 모델 선택
- WebGPU 추론: WebGPU API를 사용하여 브라우저에서 작은 모델 직접 실행으로 제로 지연 시간 상호작용
언제 Vercel AI SDK를 선택할까 #
다음 경우 AI SDK 선택:
- 최소한의 보일러플레이트로 빠른 프로토타이핑을 원하는 경우
- 앱이 스트리밍 응답이 필요한 경우
- 여러 LLM 제공자를 지원할 계획인 경우
- React, Next.js 또는 기타 현대 프론트엔드 프레임워크를 사용하는 경우
- 제로 인프라 관리 엣지 배포를 원하는 경우
대안을 고려할 때:
- 온프레미스 배포만 필요한 경우 — LangChain 또는 LlamaIndex가 더 많은 유연성 제공
- TypeScript 없는 비 React 앱을 구축하는 경우 — SDK가 TS/React에서 가장 빛남
- 커스텀 추론 서빙이 필요한 경우 — vLLM 또는 TGI가 셀프 호스팅 GPU 클러스터용
커뮤니티 업데이트 #
AI SDK 생태계가 크게 성숙했습니다:
- 제공자 커버리지: OpenAI, Anthropic, Google, AWS Bedrock, Cohere, Mistral, Groq, Ollama 포함 15개 이상 공식 제공자 통합
- 커뮤니티 패키지: 200개 이상의 커뮤니티 기여 도구, 유틸리티 및 통합
- 프레임워크 지원: Next.js, Remix, SvelteKit, Nuxt, Astro, Qwik용 공식 어댑터
- 엔터프라이즈 채택: Stripe, Shopify, Notion 등 회사에서 프로덕션 AI 기능에 사용
SDK의 GitHub 레포지토리는 30,000개 이상의 star를 넘었고 npm 주간 다운로드가 500만 건을 초과했습니다 — JavaScript 생태계에서 가장 인기 있는 AI 개발 SDK가 되었습니다.
FAQ #
Q: Next.js 없이 Vercel AI SDK를 사용할 수 있나요? #
예. SDK는 Next.js와 완벽하게 통합되지만, Fetch API를 지원하는 모든 프레임워크에서 작동합니다. Remix, SvelteKit, Nuxt, Astro, Express, Fastify 및 심지어 바닐라 Node.js도 작동합니다. ai 패키지는 프레임워크 비종속입니다 — React 훅(@ai-sdk/react)만 React가 필요합니다.
Q: 스트리밍은 어떻게 작동하나요? #
SDK는 ReadableStream을 통해 Server-Sent Events(SSE)를 사용합니다. streamText()를 호출하면 LLM 제공자에게 스트리밍 연결을 생성합니다. 각 토큰은 SSE 이벤트로 클라이언트에 전송되며, useChat hook이 이를 파싱하고 UI를 증분 업데이트합니다. 이것이 AI 채팅 인터페이스의 “타이핑” 효과를 가능하게 합니다.
Q: LLM 응답을 캐시하여 비용을 절감할 수 있나요? #
예. API 라우트 수준에서 캐싱을 구현합니다:
const cachedChat = cache(async (messages: any[]) => {
const hash = JSON.stringify(messages);
const cached = await redis.get(hash);
if (cached) return JSON.parse(cached);
const result = await streamText({ model: openai("gpt-4o"), messages });
await redis.setex(hash, 3600, JSON.stringify(result));
return result;
});
같은 대화를 시간이나 일 동안 캐시하면 반복 쿼리에 대해 50-80%의 API 비용을 절약할 수 있습니다.
Q: SDK는 무료이고 오픈소스인가요? #
예. AI SDK는 MIT 라이선스이며 완전히 무료입니다. 기본 LLM 제공자 API 호출 비용만 지불하면 됩니다. 구독료, 사용량 한도, 숨겨진 비용이 없습니다.
Q: AI 앱 인증을 어떻게 처리하나요? #
미들웨어를 사용하여 API 라우트를 보호합니다:
export function middleware(request: NextRequest) {
const token = request.cookies.get("auth-token");
if (!token && request.nextUrl.pathname.startsWith("/api/chat")) {
return NextResponse.redirect(new URL("/login", request.url));
}
return NextResponse.next();
}
프로덕션 앱의 경우 JWT 인증과 속도 제한을 결합하여 남용을 방지합니다.
출처 #
- Vercel AI SDK 문서
- AI SDK GitHub 레포지토리
- 스트리밍 AI 앱 구축 — Vercel 블로그 2026
- AI를 위한 엣지 컴퓨팅 — Cloudflare 연구 2026
- AI SDK 제공자 비교 매트릭스
실시간 AI 도구 토론 및 배포 팁을 위한 Telegram 그룹 가입: t.me/dibi8
💬 댓글 토론