본문으로 건너뛰기
Previous
Next
Vercel AI SDK 완벽 가이드 | Streaming·Chat UI·RAG·Edge·실전 활용

Vercel AI SDK 완벽 가이드 | Streaming·Chat UI·RAG·Edge·실전 활용

Vercel AI SDK 완벽 가이드 | Streaming·Chat UI·RAG·Edge·실전 활용

이 글의 핵심

AI SDK는 모델 프로바이더를 통합한 스트리밍·도구 호출 계층을 제공하고, React 훅과 Route Handler를 연결합니다. 스트림 백프레셔·중단·토큰 비용·관측을 포함해 프로덕션에서 안전하게 운영하는 패턴을 정리합니다.

이 글의 핵심

Vercel AI SDK로 AI 앱을 구축하는 완벽 가이드입니다. Streaming, Chat UI, RAG, Edge Runtime, OpenAI/Anthropic 통합까지 실전 예제로 정리했습니다.

실무 경험 공유: 직접 구현한 Chat UI를 Vercel AI SDK로 전환하면서, 개발 시간이 80% 단축되고 Streaming 구현이 간편해진 경험을 공유합니다.

들어가며: “Chat UI 구현이 복잡해요”

실무 문제 시나리오

시나리오 1: Streaming이 어려워요

SSE 구현이 복잡합니다. Vercel AI SDK는 자동으로 처리합니다. 시나리오 2: Chat UI가 필요해요

처음부터 만들기 어렵습니다. Vercel AI SDK는 컴포넌트를 제공합니다. 시나리오 3: 다양한 LLM을 지원해야 해요

각각 연동이 복잡합니다. Vercel AI SDK는 통합 API를 제공합니다.

1. Vercel AI SDK란?

핵심 특징

Vercel AI SDK는 AI 앱 개발 도구입니다. 주요 기능:

  • Streaming: 실시간 응답
  • Chat UI: React 컴포넌트
  • 다중 LLM: OpenAI, Anthropic, Cohere
  • Edge Runtime: 빠른 응답
  • RAG: 문서 기반 응답

2. 설치 및 설정

설치

npm install ai

환경 변수

OPENAI_API_KEY=sk-...

3. Chat Completion

API Route

// app/api/chat/route.ts
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';
export const runtime = 'edge';
export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = await streamText({
    model: openai('gpt-4-turbo'),
    messages,
  });
  return result.toAIStreamResponse();
}

클라이언트

// app/page.tsx
'use client';
import { useChat } from 'ai/react';
export default function Chat() {
  const { messages, input, handleInputChange, handleSubmit } = useChat();
  return (
    <div>
      <div>
        {messages.map((m) => (
          <div key={m.id}>
            <strong>{m.role}:</strong> {m.content}
          </div>
        ))}
      </div>
      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={handleInputChange}
          placeholder="Say something..."
        />
        <button type="submit">Send</button>
      </form>
    </div>
  );
}

4. Streaming

Text Generation

// app/api/generate/route.ts
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';
export async function POST(req: Request) {
  const { prompt } = await req.json();
  const result = await streamText({
    model: openai('gpt-4-turbo'),
    prompt,
  });
  return result.toAIStreamResponse();
}

클라이언트

'use client';
import { useCompletion } from 'ai/react';
export default function Generate() {
  const { completion, input, handleInputChange, handleSubmit } = useCompletion();
  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input value={input} onChange={handleInputChange} />
        <button type="submit">Generate</button>
      </form>
      <div>{completion}</div>
    </div>
  );
}

5. Function Calling

import { openai } from '@ai-sdk/openai';
import { streamText, tool } from 'ai';
import { z } from 'zod';
export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = await streamText({
    model: openai('gpt-4-turbo'),
    messages,
    tools: {
      weather: tool({
        description: 'Get the weather for a location.. Vercel AI SDK 완벽 가이드에 대한 완전한 가이드입니다. 실전 예제와 함께 핵심 개념부터 고급 활용까지 다룹니다.',
        parameters: z.object({
          location: z.string().describe('The city name'),
        }),
        execute: async ({ location }) => {
          const weather = await getWeather(location);
          return weather;
        },
      }),
    },
  });
  return result.toAIStreamResponse();
}

6. RAG 구현

import { openai } from '@ai-sdk/openai';
import { streamText, embed } from 'ai';
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_KEY!
);
export async function POST(req: Request) {
  const { messages } = await req.json();
  const lastMessage = messages[messages.length - 1].content;
  // 1. 질문 임베딩
  const { embedding } = await embed({
    model: openai.embedding('text-embedding-3-small'),
    value: lastMessage,
  });
  // 2. 유사 문서 검색
  const { data: documents } = await supabase.rpc('match_documents', {
    query_embedding: embedding,
    match_threshold: 0.7,
    match_count: 3,
  });
  // 3. 컨텍스트 구성
  const context = documents.map((doc) => doc.content).join('\n\n');
  // 4. LLM 호출
  const result = await streamText({
    model: openai('gpt-4-turbo'),
    messages: [
      {
        role: 'system',
        content: `Answer based on the following context:\n\n${context}`,
      },
      ...messages,
    ],
  });
  return result.toAIStreamResponse();
}

7. 다중 LLM

Anthropic

import { anthropic } from '@ai-sdk/anthropic';
const result = await streamText({
  model: anthropic('claude-3-opus-20240229'),
  messages,
});

Ollama

import { createOllama } from 'ollama-ai-provider';
const ollama = createOllama();
const result = await streamText({
  model: ollama('llama3'),
  messages,
});

8. 실전 예제: 문서 챗봇

'use client';
import { useChat } from 'ai/react';
export default function DocumentChat() {
  const { messages, input, handleInputChange, handleSubmit, isLoading } =
    useChat({
      api: '/api/chat',
    });
  return (
    <div className="flex flex-col h-screen">
      <div className="flex-1 overflow-y-auto p-4">
        {messages.map((m) => (
          <div key={m.id} className={m.role === 'user' ? 'text-right' : 'text-left'}>
            <div className="inline-block p-2 rounded bg-gray-100">
              {m.content}
            </div>
          </div>
        ))}
        {isLoading && <div>Thinking...</div>}
      </div>
      <form onSubmit={handleSubmit} className="p-4 border-t">
        <input
          value={input}
          onChange={handleInputChange}
          placeholder="Ask a question..."
          className="w-full p-2 border rounded"
        />
      </form>
    </div>
  );
}

심화: 스트리밍·도구 호출·프로덕션 운영

스트리밍과 백프레셔

streamText·toAIStreamResponse 경로는 토큰 단위 청크를 클라이언트로 밀어 넣습니다. 네트워크가 느리면 TCP 윈도우·소켓 버퍼에 데이터가 쌓이고, 일부 플랫폼에서는 함수 실행 시간응답 바이트에 상한이 있습니다. 긴 생성은 문장 단위가 아니라 작업 단위로 나누고, 사용자에게 진행률 이벤트를 별도 채널로 보내는 편이 UX에 유리합니다.

중단(Abort)과 비용

AbortSignalstreamText 등에 넘기면 사용자가 페이지를 떠날 때 upstream 요청을 중단해 토큰 비용을 줄일 수 있습니다. 클라이언트 useChatstop과 연결해 두면 반복 호출 누수를 막기 쉽습니다.

도구 호출의 신뢰 경계

tool({ execute }) 안에서 하는 일은 서버의 권한으로 실행됩니다. 사용자 입력을 그대로 쉘에 넣지 말고, 도구 인자에 Zod 스키마를 걸고, 허용된 작업만 노출합니다. 외부 HTTP 호출에는 타임아웃·재시도·서킷 브레이커를 적용합니다.

관측·로깅

모델별 지연·토큰 수·에러 코드를 구조화 로그로 남기고, PII는 마스킹합니다. RAG 경로에서는 검색된 문서 ID만 로깅해 디버깅 가능성과 개인정보 보호를 균형 있게 맞춥니다.

트러블슈팅

증상점검
빈 스트림만 옴모델명·API 키·프로바이더 장애
ReadableStream 관련 런타임 오류Edge vs Node API 차이, 런타임 플래그
도구만 반복 호출프롬프트·maxSteps·도구 설명 과다
CORS/SSE 문제Route Handler 경로·프록시·쿠키 도메인

정리 및 체크리스트

핵심 요약

  • Vercel AI SDK: AI 앱 개발 도구
  • Streaming: 실시간 응답
  • Chat UI: React 컴포넌트
  • 다중 LLM: OpenAI, Anthropic, Ollama
  • Edge Runtime: 빠른 응답
  • RAG: 문서 기반 응답

구현 체크리스트

  • Vercel AI SDK 설치
  • API Route 구현
  • Chat UI 구현
  • Streaming 구현
  • Function Calling 구현
  • RAG 구현
  • 배포

같이 보면 좋은 글


이 글에서 다루는 키워드

Vercel AI SDK, AI, Streaming, Chat, OpenAI, Next.js, React

자주 묻는 질문 (FAQ)

Q. LangChain과 비교하면 어떤가요?

A. Vercel AI SDK가 더 간단하고 React 통합이 완벽합니다. LangChain은 더 많은 기능을 제공합니다.

Q. 무료로 사용할 수 있나요?

A. 네, SDK는 무료이고 LLM API 비용만 발생합니다.

Q. Ollama를 사용할 수 있나요?

A. 네, ollama-ai-provider를 사용하면 가능합니다.

Q. 프로덕션에서 사용해도 되나요?

A. 네, Vercel에서 만든 안정적인 SDK입니다.

심화 부록: 구현·운영 관점

이 부록은 앞선 본문에서 다룬 주제(「Vercel AI SDK 완벽 가이드 | Streaming·Chat UI·RAG·Edge·실전 활용」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.

내부 동작과 핵심 메커니즘

flowchart TD
  A[입력·요청·이벤트] --> B[파싱·검증·디코딩]
  B --> C[핵심 연산·상태 전이]
  C --> D[부작용: I/O·네트워크·동시성]
  D --> E[결과·관측·저장]
sequenceDiagram
  participant C as 클라이언트/호출자
  participant B as 경계(런타임·게이트웨이·프로세스)
  participant D as 의존성(API·DB·큐·파일)
  C->>B: 요청/이벤트
  B->>D: 조회·쓰기·RPC
  D-->>B: 지연·부분 실패·재시도 가능
  B-->>C: 응답 또는 오류(코드·상관 ID)
  • 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
  • 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
  • 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
  • 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.

프로덕션 운영 패턴

영역운영 관점 질문
관측성요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가
안전성입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가
신뢰성재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가
성능캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가
배포롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가
용량피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가

스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.

확장 예시: 엔드투엔드 미니 시나리오

앞선 본문 주제(「Vercel AI SDK 완벽 가이드 | Streaming·Chat UI·RAG·Edge·실전 활용」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.

  1. 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
  2. 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
  3. 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
  4. 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
  5. 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
  ctx = newCorrelationId()
  validated = validateSchema(request)
  authorize(validated, ctx)
  result = domainCore(validated)
  persistOrEmit(result, idempotentKey)
  recordMetrics(ctx, latency, outcome)
  return result

문제 해결(Troubleshooting)

증상가능 원인조치
간헐적 실패레이스, 타임아웃, 외부 의존성, DNS최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검
성능 저하N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거
메모리 증가캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납상한·TTL·힙/FD 스냅샷 비교
빌드·배포만 실패환경 변수, 권한, 플랫폼 차이, lockfileCI 로그와 로컬 diff, 런타임·이미지 버전 핀
설정 불일치프로필·시크릿·기본값, 리전스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화
데이터 불일치비멱등 재시도, 부분 쓰기, 캐시 무효화 누락멱등 키·아웃박스·트랜잭션 경계 재검토

권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.

배포 전에는 git addgit commitgit pushnpm run deploy 순서를 권장합니다.