AI SDK 4.1 → 4.2 마이그레이션

AI SDK 4.1 → 4.2 마이그레이션

AI SDK 4.1에서 4.2로 업그레이드할 때 챙겨야 할 것들을 정리한 가이드입니다. 이번 버전에서는 여러 API가 안정(stable)으로 승격됐고, 무엇보다 useChat이 메시지 파츠(parts) 방식으로 동작을 재설계했어요. 다중 모달·멀티스텝 응답을 UI에 그릴 때 훨씬 깔끔해졌으니, 그 변화를 꼭 확인하시길 바랍니다.

출처: 문서

본문

자세한 내용은 AI SDK 4.2 릴리스 블로그 글을 확인해보세요.

이 가이드는 AI SDK 4.2로 업그레이드하는 데 도움을 드립니다.

안정화된 API (Stable APIs)

다음 API들이 안정(stable) 상태로 이동하여 더 이상 experimental_ 접두사가 붙지 않습니다:

  • customProvider
  • providerOptions (프로바이더별 입력을 위해 providerMetadata에서 이름 변경)
  • providerMetadata (프로바이더별 출력용)
  • streamText의 toolCallStreaming 옵션

의존성 버전 (Dependency Versions)

AI SDK는 비옵션(non-optional) zod 의존성이 필요하며 버전은 ^3.23.8입니다.

UI 메시지 파츠 (UI Message Parts)

AI SDK 4.2에서는 useChat이 메시지 파츠와 여러 단계(steps)로 모델 출력을 처리하는 방식을 재설계했습니다. 이것은 복잡한 다중 모달 AI 응답을 UI에서 렌더링하는 것을 크게 단순화해주는 중요한 개선입니다.

무엇이 바뀌었나 (What's Changed)

도구 호출이 있는 assistant 메시지는 이제 각 단계마다 별도의 메시지를 만드는 대신, 여러 파츠를 가진 하나의 메시지로 합쳐집니다. 이 변경은 AI 애플리케이션의 두 가지 핵심 발전을 다룹니다:

  1. 다양한 출력 유형: 모델은 이제 텍스트만이 아니라 추론 단계(reasoning), 소스(source), 도구 호출도 만들어냅니다.
  2. 인터리브된 출력 (Interleaved Outputs): 멀티스텝 에이전트 사용 사례에서 이런 서로 다른 출력 유형은 자주 섞여서 나타납니다.

새 방식의 이점 (Benefits of the New Approach)

이전에는 useChat이 서로 다른 출력 유형을 각각 따로 저장했기 때문에, 응답 안에서 이런 요소들이 섞여 있을 때 UI에서 올바른 순서를 유지하기 어려웠고, 도구 호출이 있을 때 assistant 메시지가 여러 번 연속으로 나타나기도 했습니다. 예를 들면:

message.content = "Final answer: 42";
message.reasoning = "First I'll calculate X, then Y...";
message.toolInvocations = [{toolName: "calculator", args: {...}}];

이 구조는 제약이 많았습니다. 새 메시지 파츠 방식은 별도의 속성들을 정확한 순서를 보존하는 순서형 배열로 대체합니다:

message.parts = [
  { type: "text", text: "Final answer: 42" },
  { type: "reasoning", reasoning: "First I'll calculate X, then Y..." },
  { type: "tool-invocation", toolInvocation: { toolName: "calculator", args: {...} } },
];

마이그레이션 (Migration)

기존 메시지 형식을 사용하던 애플리케이션은 새 parts 배열을 처리하도록 UI 컴포넌트를 업데이트해야 합니다. 이전 형식의 필드는 하위 호환성을 위해 여전히 제공되지만, 다중 모달·멀티스텝 상호작용을 더 잘 지원하려면 새 형식으로 마이그레이션하는 것을 권장합니다.

새 메시지 파츠와 함께 useChat 훅을 다음과 같이 사용할 수 있습니다:

function Chat() {
  const { messages } = useChat();
  return (
    <div>
      {messages.map(message =>
        message.parts.map((part, i) => {
          switch (part.type) {
            case 'text':
              return <p key={i}>{part.text}</p>;
            case 'source':
              return <p key={i}>{part.source.url}</p>;
            case 'reasoning':
              return <div key={i}>{part.reasoning}</div>;
            case 'tool-invocation':
              return <div key={i}>{part.toolInvocation.toolName}</div>;
            case 'file':
              return (
                <img
                  key={i}
                  src={`data:${part.mediaType};base64,${part.data}`}
                />
              );
          }
        }),
      )}
    </div>
  );
}

더 알아보기 (Learn more)

전체 사이트맵