API
verified · type: how-to · verified: 2026-07 · review: 30d · updated: 2026-07-25 · [api]

Vercel AI SDK 실전

Next.js/TS 앱에서 여러 LLM 프로바이더를 한 API 로 붙일 때 연다. 함수 시그니처, 툴 호출, 스트리밍 UI, 프로바이더 교체, 그리고 메이저 버전 간 breaking change 지뢰밭.

신선도 경고. 메이저가 빠르게 오르고 함수·필드 이름이 자주 바뀐다. 버전·API 이름은 전부 (verified 2026-07) 기준 — 다음 세션에서 npm view ai version 으로 재확인하라. Anthropic 모델 ID 는 SDK 문서 예시가 아니라 claude-family 를 정본으로 삼는다.


패키지 구조와 버전 핀

코어 ai 와 프로바이더/UI 패키지는 버전 번호가 따로 논다. 코어가 7.x 라고 @ai-sdk/react 도 7 이라 넘겨짚지 마라.

패키지 import 최신 (verified 2026-07) 역할
ai 'ai' 7.0.37 Core: generateText, tools, agents
@ai-sdk/anthropic '@ai-sdk/anthropic' 4.0.20 Anthropic 프로바이더
@ai-sdk/openai '@ai-sdk/openai' 4.0.20 OpenAI 프로바이더
@ai-sdk/google '@ai-sdk/google' 4.0.24 Google 프로바이더
@ai-sdk/react '@ai-sdk/react' 4.0.40 UI 훅 (useChat 등)

aiv5·v6·v7 세 메이저 라인 동시 유지보수 중이다 (dist-tags: latest 7.0.37, ai-v6 6.0.235, ai-v5 5.0.220 — verified 2026-07). pnpm add ai@latest @ai-sdk/react @ai-sdk/anthropic 로 v7 를 깔되 package.json 에 정확히 핀하라. 세 surface: Core('ai'), UI(@ai-sdk/react|vue|svelte|angular), Harnesses(v7 신규, Claude Code/Codex 하네스를 HarnessAgent 로 감쌈).

핵심 함수 4개

전부 import { ... } from 'ai'. 텍스트는 generateText/streamText, 임베딩은 embed/embedMany.

import { generateText, streamText, embed, embedMany } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';

// 1) 한 방에 텍스트
const { text, usage, finishReason } = await generateText({
  model: anthropic('claude-opus-5'),
  prompt: '3문장 요약해줘',
});

// 2) 스트리밍 — result 는 즉시 반환, textStream 을 순회
const result = streamText({ model: anthropic('claude-opus-5'), prompt: '...' });
for await (const delta of result.textStream) process.stdout.write(delta);
const total = await result.usage;  // Promise

// 3)/4) 임베딩 — 단일 / 배치
const { embedding } = await embed({ model: 'openai/text-embedding-3-small', value: 'sunny day' });
const { embeddings } = await embedMany({ model: 'openai/text-embedding-3-small', values: ['a', 'b'] });

result.stream 은 텍스트뿐 아니라 툴콜·reasoning 포함 전체 이벤트 스트림 (v7 에서 fullStreamstream 개명). 콜백은 onEnd/onError/onChunk.

툴 호출

tool() 로 정의하고 이름을 key 로 하는 객체로 넘긴다. 스키마 필드는 inputSchema 다 (parameters 아님).

import { tool, generateText, isStepCount } from 'ai';
import { z } from 'zod';

const weather = tool({
  description: 'Get the weather in a location',
  inputSchema: z.object({ location: z.string() }),
  execute: async ({ location }) => ({ location, temperature: 72 }),
});

const { text, steps } = await generateText({
  model: anthropic('claude-opus-5'),
  tools: { weather },
  stopWhen: isStepCount(5),   // 멀티스텝 정지조건 (v7: stepCountIs → isStepCount)
  prompt: 'SF 날씨 알려줘',
});

에이전트 루프가 필요하면 ToolLoopAgent 를 쓴다 (new ToolLoopAgent({ model, tools }).generate({ prompt }); v6 에서 Experimental_Agent 안정화, 기본 stopWhen step count 20). Anthropic SDK 자체의 tool runner 와는 별개다 → agent-frameworks.

구조화 출력

v7 권장은 generateText/streamTextoutput 옵션 + Output.* 헬퍼. generateObject/streamObject 는 v6 부터 deprecated (export 는 잔존 — 완전 제거 여부는 재확인 필요).

import { generateText, Output } from 'ai';
const { output } = await generateText({
  model: anthropic('claude-opus-5'),
  output: Output.object({ schema: z.object({ name: z.string(), age: z.number().nullable() }) }),
  prompt: '인물 정보를 뽑아줘',
});

Output.object|array|choice|json|text 가 있고 스키마는 Zod, 스트리밍은 result.partialOutputStream. Anthropic 네이티브 방식은 structured-output 참고.

스트리밍 UI — Next.js 라우트 핸들러

클라이언트는 useChat(@ai-sdk/react), 서버는 라우트 핸들러에서 streamText. 메시지는 v6 부터 parts 배열 모델이다.

// app/api/chat/route.ts
import { streamText, convertToModelMessages, createUIMessageStreamResponse, toUIMessageStream, type UIMessage } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';

export const runtime = 'edge';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();
  const result = streamText({
    model: anthropic('claude-opus-5'),
    messages: await convertToModelMessages(messages),  // v6부터 async
  });
  return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }) });
}
// 클라이언트
const { messages, sendMessage, status } = useChat({
  transport: new DefaultChatTransport({ api: '/api/chat' }),
});
// status: 'submitted' | 'streaming' | 'ready' | 'error'
// 렌더: message.parts.map(p => p.type === 'text' ? p.text : null)

프로바이더 교체 (Anthropic ↔ 타사)

방식 코드 언제
문자열 ID + AI Gateway (v7 기본) model: 'anthropic/claude-...' 코드 안 고치고 프로바이더/모델 스왑, Gateway 라우팅·비용집계
프로바이더 인스턴스 anthropic('claude-...') 프로바이더 고정, providerOptions 로 벤더 기능 접근
createProviderRegistry / customProvider registry.languageModel('anthropic:opus') 별칭·폴백 프리컨피그 (v7: experimental_customProvidercustomProvider)

Anthropic 은 pnpm add @ai-sdk/anthropic, import { anthropic }, env ANTHROPIC_API_KEY. extended thinking 은 providerOptions.anthropic.thinking 로 켠다.

모델 ID 는 SDK 문서를 믿지 마라. AI SDK Anthropic 문서 예시(claude-opus-4-20250514 등)와 Gateway 예시(anthropic/claude-opus-4.1 등)는 오래됐다. 현행 ID(claude-opus-5 기본, 날짜 접미사 금지)는 claude-family 를 정본으로 써라.

함정 (gotcha)

  • 버전 이름 드리프트. v6→v7 만 해도: systeminstructions(messages 내 system 은 기본 거부), experimental_* 접두사 대거 제거(output/telemetry/activeTools/generateImage), result.fullStreamresult.stream, stepCountIsisStepCount, onFinishonEnd. 코드젠·구버전 스니펫이 조용히 틀린다. npx @ai-sdk/codemod v7 로 마이그레이션.
  • 토큰 사용량 집계 위치가 바뀐다. v7 에서 top-level usage/content모든 step 합산으로 바뀌고, 마지막 step 전용값은 finalStep 로 이동. 캐시 필드는 usage.cachedInputTokensusage.inputTokenDetails.cacheReadTokens. 비용 계산 코드가 여기서 깨진다.
  • 런타임 요건. v7 은 Node.js 22+ 필수, ESM only (CommonJS require() 불가). edge runtime 에서는 export const runtime = 'edge' 를 라우트에 명시하고, 노드 전용 API(fs 등)를 execute 안에서 부르지 마라.
  • OpenTelemetry 분리. v7 텔레메트리는 @ai-sdk/otel 별도 설치 + registerTelemetry(). 기존 experimental_telemetry 자동 켜짐을 기대하면 계측이 사라진다. 그리고 convertToModelMessages 는 async(v6부터) — await 빼먹으면 Promise 가 messages 로 들어간다.
  • 프로바이더 버전 불일치. 코어만 올리고 @ai-sdk/react/프로바이더를 안 올리면 wire format 불일치로 스트리밍이 깨진다. 세트로 올려라.

참고

출처: 버전은 npm registry 실측 [HIGH], API 형태는 ai-sdk.dev 공식 docs [HIGH]. 비Anthropic 프로바이더 모델명·GA 날짜는 미검증. 원본 → 2026-07-25-vercel-ai-sdk