텍스트 생성과 스트리밍

텍스트 생성과 스트리밍

LLM과 대화하는 가장 기본 단위는 프롬프트에 대한 텍스트 생성이에요. AI SDK Core는 이 작업을 위해 generateTextstreamText 두 함수를 제공하고, 도구 호출이나 구조화된 출력 같은 고급 기능도 전부 이 위에 얹여 있어요. 어느 쪽을 쓸지는 "한 번에 답이 필요한지, 흘러가는 답이 필요한지"로 가르면 돼요.

출처: 공식문서

본문

generateText

generateText는 프롬프트와 모델을 받아 텍스트를 한 번에 생성해요. 이메일 초안을 쓰거나 문서를 요약하는 등 상호작용이 필요 없는 작업과, 도구를 쓰는 에이전트에 알맞아요.

import { generateText } from 'ai';

const { text } = await generateText({
  model: "xai/grok-4.6",
  prompt: 'Write a vegetarian lasagna recipe for 4 people.',
});

instructions를 함께 넘기면 말투나 제약 같은 체계적인 지시를 프롬프트와 분리해서 줄 수 있어요. 결과 객체에는 생성된 텍스트뿐 아니라 아래 같은 메타데이터도 담겨요.

  • result.content — 모든 스텝에서 생성된 콘텐츠
  • result.text — 마지막 스텝에서 생성된 텍스트
  • result.toolCalls / result.toolResults — 모든 스텝에서 발생한 도구 호출과 그 결과
  • result.finishReason — 모델이 생성을 마친 이유
  • result.usage — (멀티 스텝 생성의 경우) 전체 사용량
  • result.warnings — 공급자가 내보낸 경고
  • result.steps / result.finalStep — 각 스텝 혹은 마지막 스텝의 상세

각 스텝에는 성능 정보도 함께 들어가요. stepTimeMs는 모델 응답 시간과 도구 실행 시간을 합친 전체 시간이고, responseTimeMs는 모델 응답을 기다린 시간, toolExecutionMs는 클라이언트 도구 실행에 쓴 시간이에요. 스트리밍 스텝에서는 timeToFirstOutputMs(첫 청크까지)나 outputTokensPerSecond 같은 지표도 확인할 수 있죠.

프로바이더가 돌려준 원본 응답 헤더나 본문이 필요할 때는 result.finalStep.response로 접근하면 돼요.

console.log(JSON.stringify(result.finalStep.response.headers, null, 2));
console.log(JSON.stringify(result.finalStep.response.body, null, 2));

병렬로 이뤄지는 실험용 라이프사이클 콜백도 있는데, 로깅이나 관찰성, 디버깅에 유용해요. onStart, onStepStart, onLanguageModelCallStart, onToolExecutionStart, onStepEnd 등이 있어서 각 단계의 시작과 끝을 잡을 수 있어요. 콜백 안에서 던진 오류는 조용히 삼켜지므로 생성 흐름을 깨지 않아요.

streamText

모델에 따라 응답 생성에 최대 1분까지 걸릴 수 있어요. 채팅처럼 즉각적인 반응을 기대하는 인터랙티브 환경에서는 이 지연이 치명적이죠. streamText는 그럴 때 쓰는 함수예요.

import { streamText } from 'ai';

const result = streamText({
  model: "xai/grok-4.6",
  prompt: 'Invent a new holiday and describe its traditions.',
});

// example: use textStream as an async iterable
for await (const textPart of result.textStream) {
  console.log(textPart);
}

result.textStreamReadableStream이면서 AsyncIterable이에요. streamText는 호출 즉시 스트리밍을 시작하고, 서버가 죽지 않도록 에러를 던지지 않고 스트림 안으로 흘려보내요. 따라서 에러 로그는 onError 콜백으로 남기는 걸 권장해요.

또한 백프레셔를 사용해서 요청된 만큼만 토큰을 생성해요. 그래서 스트림을 끝까지 소비해야 생성이 완료돼요. result.text, result.finishReason, result.usage 같은 프로미스는 스트림이 끝나면 값이 채워져요.

streamText 결과를 AI SDK UI에 연결할 때는 몇 가지 헬퍼를 쓰면 돼요. createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }) })는 Next.js App Router API 라우트에서 바로 쓸 수 있는 UI 메시지 스트림 HTTP 응답을 만들어 주고, createTextStreamResponse는 단순 텍스트 스트림 응답을 만들어 줘요.

전체 이벤트가 담긴 result.stream을 직접 읽을 수도 있어요. 각 파트의 typeswitch로 분기해서 text-delta, tool-call, tool-result, error 등을 처리하면 자기만의 UI를 만들 수 있어요.

스트림 변환

experimental_transform 옵션으로 스트림을 변환할 수 있어요. 텍스트를 필터링하거나 바꾸거나 부드럽게 다듬는 용도예요. 변환은 콜백이 호출되기 전에 적용되므로, 예를 들어 모든 텍스트를 대문자로 바꾸는 변환이라면 onEnd 콜백도 변환된 텍스트를 받아요.

AI SDK Core는 텍스트와 리저닝 스트리밍을 부드럽게 만들어 주는 smoothStream 함수를 제공하고, 직접 커스텀 변환을 만들 수도 있어요. stopStream으로 스트림을 중단할 수도 있는데, 그럴 땐 잘 만들어진 스트림을 반환하고 모든 콜백이 호출되도록 finish-stepfinish 이벤트를 직접 흉내 내는 게 중요해요. 변환은 여러 개를 배열로 넘겨 순서대로 적용할 수도 있어요.

출처(소스) 읽기

Perplexity나 Google 같은 일부 프로바이더는 응답에 출처를 포함해요. result.sources 프로퍼티로 접근할 수 있고, 각 소스는 id, url, title, providerMetadata를 가져요. 스트리밍에서는 result.stream을 순회하며 source 타입 파트에서 같은 정보를 읽으면 돼요.

더 알아보기