OPEN HYPER STEP
← 목록으로 (master-project)
MASTER-PROJECT · 35 / 50
master-project
CHAPTER 35 / 50
읽기 약 2
FUNCTION

AI API 연동: OpenAI/Claude + 스트리밍


핵심 개념

Vercel AI SDK·streamText·useChat·다중 Provider·에러 처리 — 모던 AI 통합 패턴.

본문

Vercel AI SDK 셋업

BASH📋 코드 (1줄)
pnpm add ai @ai-sdk/openai @ai-sdk/anthropic
ENV📋 코드 (3줄)
# .env.local
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...

Server Action으로 AI 호출

TS📋 코드 (15줄)
// src/lib/ai.ts
import { generateText, streamText } from 'ai'
import { openai } from '@ai-sdk/openai'
import { anthropic } from '@ai-sdk/anthropic'

export async function generateCopy(prompt: string) {
  const { text, usage } = await generateText({
    model: anthropic('claude-sonnet-4-6'),
    system: '한국 마케터를 위한 카피라이터. 짧고 임팩트 있게.',
    prompt,
    maxTokens: 500,
    temperature: 0.7,
  })
  return { text, tokens: usage.totalTokens }
}

스트리밍 (Route Handler)

TS📋 코드 (29줄)
// src/app/api/chat/route.ts
import { streamText } from 'ai'
import { anthropic } from '@ai-sdk/anthropic'
import { createClient } from '@/lib/supabase/server'

export async function POST(request: Request) {
  const supabase = await createClient()
  const { data: { user } } = await supabase.auth.getUser()
  if (!user) return new Response('unauthorized', { status: 401 })

  const { messages } = await request.json()

  const result = streamText({
    model: anthropic('claude-sonnet-4-6'),
    system: '한국 마케터를 위한 카피라이터.',
    messages,
    onFinish: async ({ usage, text }) => {
      // 사용량·결과 DB 저장
      await supabase.from('generations').insert({
        user_id: user.id,
        prompt: messages[messages.length - 1].content,
        result: text,
        tokens_used: usage.totalTokens,
      })
    },
  })

  return result.toDataStreamResponse()
}

클라이언트 useChat 훅

TSX📋 코드 (37줄)
// src/app/(app)/generate/page.tsx
'use client'
import { useChat } from 'ai/react'

export default function GeneratePage() {
  const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
    api: '/api/chat',
  })

  return (
    <div className="max-w-2xl mx-auto p-6">
      <div className="space-y-4 mb-6 min-h-[400px]">
        {messages.map(m => (
          <div key={m.id} className={m.role === 'user' ? 'text-right' : ''}>
            <div className={`inline-block p-3 rounded-lg ${
              m.role === 'user' ? 'bg-primary text-primary-foreground' : 'bg-muted'
            }`}>
              {m.content}
            </div>
          </div>
        ))}
      </div>

      <form onSubmit={handleSubmit} className="flex gap-2">
        <input
          value={input}
          onChange={handleInputChange}
          placeholder="원하는 카피를 설명하세요"
          className="flex-1 px-3 py-2 border rounded-md"
        />
        <button disabled={isLoading} className="bg-primary text-primary-foreground px-4 py-2 rounded">
          {isLoading ? '생성 중...' : '생성'}
        </button>
      </form>
    </div>
  )
}

generateObject (구조화된 출력)

TS📋 코드 (17줄)
import { z } from 'zod'
import { generateObject } from 'ai'

const schema = z.object({
  variants: z.array(z.object({
    text: z.string(),
    tone: z.enum(['friendly', 'professional', 'playful']),
    length: z.number(),
  })).length(5),
})

const { object } = await generateObject({
  model: anthropic('claude-sonnet-4-6'),
  schema,
  prompt: '여름 세일 30% 카피 5가지 톤별',
})
// object.variants: [{ text, tone, length }, ...]

다중 Provider Fallback

TS📋 코드 (14줄)
async function generateWithFallback(prompt: string) {
  try {
    return await generateText({
      model: anthropic('claude-sonnet-4-6'),
      prompt,
    })
  } catch (err) {
    console.warn('Anthropic 실패, OpenAI fallback')
    return await generateText({
      model: openai('gpt-5.5'),
      prompt,
    })
  }
}

에러 처리

TS📋 코드 (15줄)
import { APICallError } from 'ai'

try {
  const result = await generateText({ ... })
} catch (err) {
  if (APICallError.isInstance(err)) {
    if (err.statusCode === 429) {
      return { error: 'AI 서버 과부하. 잠시 후 재시도.' }
    }
    if (err.statusCode === 401) {
      return { error: '인증 실패. 관리자 문의.' }
    }
  }
  throw err
}

캐시 (같은 prompt 재사용)

TS📋 코드 (7줄)
import { unstable_cache } from 'next/cache'

const cachedGenerate = unstable_cache(
  generateCopy,
  ['ai-generate'],
  { revalidate: 3600 }  // 1시간
)

다음 챕터

CH.36 "RAG 구현: 문서 임베딩 + 벡터 검색".


AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude

무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6

내 마스터 프로젝트의 AI API 연동 부분을 분석해서
실전 적용 + 개선 우선순위 3가지를 알려줘.
ChatGPT

무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro

AI API 연동 관련 모범 사례·안티패턴 5개를
비교 분석해서 실전 적용를 위한 추천 방안을 알려줘.
Gemini

무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro

내 프로젝트 전체에서 AI API 연동
최적화 가능 위치와 리스크를 보고해줘.
Grok

무료: Grok 4.1 / SuperGrok $30/mo

2026년 한국 1인 개발자 시장의
AI API 연동 트렌드와 차별화 포인트를 정리해줘.

⭐ 이것만 기억하세요
AI API 연동: OpenAI/Claude + 스트리밍 이 3가지만 확실히 잡으세요
1.Vercel AI SDK = streamText + useChat 표준
2.다중 Provider + 에러 처리 = 안정성
3.다음 챕터에서 RAG 구현


공유하기
진행도 35 / 50