k
korAI
중급 전체
중급2026-04-167분

스트리밍으로 첫 토큰 지연 3배 줄이기

사용자는 완성까지 기다리지 않는다. `stream: true`와 서버-센트 이벤트로 체감 속도 개선.

apistreaming

왜 스트리밍인가

800토큰짜리 응답은 Sonnet 기준 4~8초 걸린다. 그동안 사용자가 로딩 스피너만 보면 이탈한다. 스트리밍은 첫 토큰을 300~500ms에 받아 체감 지연을 10배 줄인다.

기본 예제

import Anthropic from "@anthropic-ai/sdk"
const client = new Anthropic()

const stream = client.messages.stream({
  model: "claude-sonnet-4-6",
  max_tokens: 1024,
  messages: [{ role: "user", content: "블로그 글 800자 써줘" }],
})

for await (const event of stream) {
  if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
    process.stdout.write(event.delta.text)
  }
}

const final = await stream.finalMessage()
console.log("\n사용 토큰:", final.usage)

Next.js App Router에서 브라우저로 흘려보내기

// app/api/chat/route.ts
export async function POST(req: Request) {
  const { prompt } = await req.json()
  const stream = client.messages.stream({
    model: "claude-sonnet-4-6",
    max_tokens: 2048,
    messages: [{ role: "user", content: prompt }],
  })

  const encoder = new TextEncoder()
  const body = new ReadableStream({
    async start(controller) {
      for await (const ev of stream) {
        if (ev.type === "content_block_delta" && ev.delta.type === "text_delta") {
          controller.enqueue(encoder.encode(ev.delta.text))
        }
      }
      controller.close()
    },
  })

  return new Response(body, {
    headers: { "content-type": "text/plain; charset=utf-8" },
  })
}

주의할 함정

  1. tool_use 이벤트는 JSON 조각이다. 문자열로 합친 뒤 파싱해야 함
  2. max_tokens 한계에 닿으면 stop_reason: "max_tokens". UI에 "...계속" 버튼 달아라
  3. 중간에 끊기면 usage 집계 안 됨. finalMessage()로 확정
  4. 모바일 브라우저는 버퍼링으로 한꺼번에 오는 경우가 있음. X-Accel-Buffering: no 헤더 시도

체크리스트

  • [ ] 첫 토큰 지연 측정해봤나 (300~500ms 목표)
  • [ ] max_tokens 도달 시 UX 처리
  • [ ] 취소 버튼으로 AbortController 연결
  • [ ] 사용량 집계는 finalMessage에서