생성형 사용자 인터페이스(Generative UI)

생성형 사용자 인터페이스(Generative UI)

생성형 사용자 인터페이스(generative UI)는 대규모 언어 모델(LLM)이 텍스트를 넘어 "UI를 생성"하도록 허용하는 과정이에요. 모델이 단순히 답변만 주는 게 아니라, 상황에 맞는 인터페이스를 직접 만들어 내기 때문에 사용자에게 더 몰입감 있고 AI 네이티브한 경험을 만들어 줍니다. 이 개념이 어떻게 동작하는지, 그리고 채팅 인터페이스에 어떻게 적용하는지 살펴볼게요.

출처: 공식문서

본문

generative UI는 대략 이렇게 흘러가요.

  1. 모델에 프롬프트나 대화 기록과 함께 도구(tool) 목록을 제공합니다.
  2. 모델은 문맥에 따라 도구를 호출하기로 결정할 수 있어요.
  3. 도구가 호출되면 실행되고 데이터를 돌려줍니다.
  4. 그 데이터를 React 컴포넌트에 넘겨 렌더링할 수 있어요.

예를 들어 "샌프란시스코 날씨가 어때?"라는 질문에 모델이 getWeather("San Francisco") 도구를 호출하고, 반환된 날씨 데이터를 받아 <WeatherCard> 같은 컴포넌트로 화면에 그려 주는 식이에요.

기본 채팅 구현

먼저 클라이언트에서 useChat 훅으로 채팅 입력과 메시지를 관리합니다.

'use client';

import { useChat } from '@ai-sdk/react';
import { useState } from 'react';

export default function Page() {
  const [input, setInput] = useState('');
  const { messages, sendMessage } = useChat();
  // ...
}

채팅 요청과 모델 응답을 처리할 API 라우트를 만듭니다. 이 라우트는 streamText로 채팅 메시지를 처리하고 모델 응답을 클라이언트로 스트리밍해요.

import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  isStepCount,
  streamText,
  toUIMessageStream,
  UIMessage,
} from 'ai';

export async function POST(request: Request) {
  const { messages }: { messages: UIMessage[] } = await request.json();

  const result = streamText({
    model: "xai/grok-4.6",
    instructions: 'You are a friendly assistant!',
    messages: await convertToModelMessages(messages),
    stopWhen: isStepCount(5),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}

도구 만들기

채팅을 동적 UI로 확장하기 전에, 모델이 특정 동작(예: 날씨 정보 가져오기)을 수행할 수 있게 해주는 도구와 그에 대응하는 React 컴포넌트를 만듭니다. ai/tools.ts 파일에 도구를 정의하고, API 라우트의 streamTexttools를 넘겨주면 돼요.

import { tools } from '@/ai/tools';
// ...
const result = streamText({
  model: "xai/grok-4.6",
  instructions: 'You are a friendly assistant!',
  messages: await convertToModelMessages(messages),
  stopWhen: isStepCount(5),
  tools,
});

날씨 컴포넌트 렌더링하기

모델이 도구를 호출했는지 확인하려면 UIMessage 객체의 parts 배열에서 도구 관련 파트를 찾아야 해요. AI SDK 5.0부터 도구 파트는 제네릭 타입 대신 tool-${toolName} 같은 타입 이름을 사용합니다. parts 배열의 각 파트 타입에 맞춰 컴포넌트를 조건부로 렌더링하면, 모델 응답에 따라 UI가 동적으로 바뀌는 대화형 채팅이 완성돼요.

  • 입력 상태는 내장 input/handleInputChange 대신 useState로 직접 관리.
  • 메시지 전송은 handleSubmit 대신 sendMessage 사용.
  • 각 메시지의 parts 배열을 확인해 콘텐츠 타입별로 렌더링.

더 알아보기

  • 도구 호출 시 모델이 stopWhen: isStepCount(5)로 다단계 호출을 제한하는 예시.
  • 서버에서 생성형 UI를 표시하고 싶다면 AI SDK RSC(@ai-sdk/rsc) 참고.
  • 스트림 프로토콜 페이지에서 UI 메시지 스트림이 어떻게 전달되는지 확인.