추론 토큰

추론 토큰 (Reasoning Tokens)

OpenAI의 GPT-5나 확장 사고(extended thinking)를 지원하는 Anthropic의 Claude처럼, 일부 고급 모델은 스스로 "생각하는" 과정을 거친 뒤 답을 냅니다. 그 사고 과정을 그냥 버려두면 아까워요. 추론 토큰 패턴을 쓰면 그 내부 사고 과정을 UIs에서 그대로 보여줄 수 있습니다 — 모델이 어떤 경로로 응답에 도달했는지 말이죠. 이 페이지에서 그 방법을 설명해 드릴게요.

출처: LangChain 공식 문서 — frontend-reasoning-tokens

추론 토큰이 뭔가요?

추론(reasoning) 능력을 가진 모델이 프롬프트를 처리하면, 두 가지 유형의 콘텐츠를 생성해요.

  1. 추론 블록 (Reasoning blocks) — 모델의 내부 사고 사슬(chain-of-thought), 문제 분해, 단계별 분석
  2. 텍스트 블록 (Text blocks) — 사용자에게 보여주는 최종적으로 다듬어진 응답

이 둘은 AIMessage 안의 타입 있는 콘텐츠 블록으로 전달되며, contentBlocks 프로퍼티로 접근합니다.

// Reasoning block
{ type: "reasoning", reasoning: "Let me think about this step by step..." }

// Text block
{ type: "text", text: "The answer is 42." }

모든 모델이 추론 토큰을 만들어내는 건 아니에요. 이 패턴은 확장 사고나 chain-of-thought 출력을 지원하는 모델에 한정됩니다. 일반 채팅 모델은 텍스트 블록만 반환하죠.

사용 사례

  • 투명성 (Transparency) — 답에 대한 신뢰를 쌓기 위해 모델의 추론 과정을 사용자에게 보여줍니다.
  • 디버깅 (Debugging) — 모델의 사고 과정을 살펴 어디서 잘못됐는지 파악합니다.
  • 교육 도구 (Educational tools) — AI가 질문에 어떻게 접근하는지 드러내 학생들의 문제 해결을 가르칩니다.
  • 의사 결정 지원 (Decision support) — 도메인 전문가가 추천 뒤의 근거를 검증하게 합니다.
  • 품질 보증 (Quality assurance) — 규제 산업에서 추론 사슬을 컴플라이언스 관점에서 감사합니다.

추론 블록과 텍스트 블록 추출하기

AIMessagecontentBlocks 배열에는 생성된 순서대로 모든 블록이 들어 있어요. type으로 필터링하면 추론과 텍스트를 분리할 수 있습니다.

import { AIMessage } from "langchain";

function extractBlocks(msg: AIMessage) {
  const reasoningBlocks = msg.contentBlocks
    .filter((b) => b.type === "reasoning")
    .map((b) => b.reasoning);

  const textBlocks = msg.contentBlocks
    .filter((b) => b.type === "text")
    .map((b) => b.text);

  return {
    reasoning: reasoningBlocks.join(""),
    text: textBlocks.join(""),
  };
}

하나의 메시지가 여러 추론 블록을 가질 수도 있어요. 모델이 추론을 잠시 멈추고 부분 텍스트를 만들었다가 다시 추론하는 경우죠. 이들을 이어 붙이면 완전한 사고 과정을 얻을 수 있습니다.

useStream에서 메시지 접근하기

추론 가능한 에이전트에 useStream을 연결하고, 채팅 UI에서 stream.messages를 순회하세요. HumanMessage.isInstanceAIMessage.isInstance로 분기한 뒤, 각 어시스턴트 메시지를 contentBlocks를 읽어 추론과 텍스트를 분리하는 컴포넌트에 넘기면 됩니다. stream.isLoading이 true인 동안 마지막 메시지에 isStreaming을 설정해서, 토큰이 도착할 때마다 사고 블록이 업데이트되게 하세요.

아래 코드는 타입 안전한 스트림 상태를 위해 useStream<typeof myAgent>를 사용합니다. Python 또는 JavaScript 백엔드의 타입 추론에 대해서는 프론트엔드 개요의 해당 섹션을 참고하세요.

import { useStream } from "@langchain/react";
import { AIMessage, HumanMessage } from "langchain";

function Chat() {
  const stream = useStream<typeof myAgent>({
    apiUrl: "http://localhost:2024",
    assistantId: "reasoning",
  });

  return (
    <div className="messages">
      {stream.messages.map((msg) => {
        if (AIMessage.isInstance(msg)) {
          return (
            <AIResponse
              key={msg.id}
              message={msg}
              isStreaming={stream.isLoading}
            />
          );
        }
        if (HumanMessage.isInstance(msg)) {
          return <p key={msg.id}>{msg.text}</p>;
        }
      })}
    </div>
  );
}

ThinkingBubble 컴포넌트 만들기

ThinkingBubble는 추론 토큰을 시각적으로 구분되는 접을 수 있는(collapsible) 컨테이너에 담아 보여줍니다. 사용자가 펼치면 전체 사고 과정을 보고, 접으면 최종 답변에 집중할 수 있어요.

import { useState } from "react";

function ThinkingBubble({ reasoning, isStreaming }) {
  const [isExpanded, setIsExpanded] = useState(false);
  const charCount = reasoning.length;
  const previewLength = 120;
  const preview = reasoning.slice(0, previewLength);

  return (
    <div className="thinking-bubble">
      <button
        onClick={() => setIsExpanded((e) => !e)}
        aria-expanded={isExpanded}
        aria-controls="thinking-content"
      >
        {isStreaming ? "Thinking..." : `Thought process (${charCount} chars)`}
        <span className={`chevron ${isExpanded ? "expanded" : ""}`}>▶</span>
      </button>

      {isExpanded && (
        <div className="thinking-content">
          <pre>{reasoning}</pre>
        </div>
      )}
    </div>
  );
}

최종 AI 응답 컴포넌트에서는 추론 블록과 텍스트 블록을 각각 모아, 추론이 있으면 ThinkingBubble을, 텍스트가 있으면 답변 버블을 렌더링하면 돼요.

엣지 케이스 다루기

추론이 없는 메시지

모든 AI 메시지에 추론 블록이 있는 건 아니에요. contentBlocks에 텍스트 블록만 있다면 ThinkingBubble 없이 일반 메시지 버블을 렌더링하세요.

여러 추론-텍스트 순환

하나의 메시지가 추론과 텍스트 블록을 번갈아 포함할 수 있어요. 이 인터리빙을 보존해야 한다면 type으로 묶지 말고 contentBlocks를 순서대로 순회하세요.

모범 사례

  • 기본은 접기 — 추론은 기본적으로 숨기고 요청 시에만 보여주세요.
  • 문자 수 표시 — 응답에 얼마나 많은 사고가 들어갔는지 사용자가 감을 잡게 해 줍니다.
  • 시각적으로 구분 — 추론이 실제 답변과 혼동되지 않도록 별도 색상, 테두리, 배경을 사용하세요.
  • 전환 애니메이션 — 부드러운 펼침/접힘 애니메이션이 체감 품질을 높여요.
  • 접근성 고려 — 토글 버튼에 적절한 ARIA 속성(aria-expanded, aria-controls)을 사용하세요.
  • 미리보기로 잘라내기 — 접혔을 때 추론의 짧은 미리보기를 보여줘 사용자가 펼칠지 결정하게 하세요.

더 알아보기 (Learn more)