useChat의 Stale body 값

useChat의 Stale body 값

useChat 훅 레벨의 body 파라미터로 동적 정보를 전달할 때 데이터가 초기 컴포넌트 렌더링 시점의 값에 머물러 있는 문제가 있어요. 원인과 해결 방법을 정리해 드릴게요.

출처: 문서

본문

문제 (Issue)

useChat을 사용하고 훅 레벨에서 body 파라미터로 동적 정보를 전달할 때, 데이터가 stale 상태로 남아 초기 컴포넌트 렌더링 시점의 값만 반영합니다. 이는 body 구성이 훅 초기화 시 한 번만 캡처되고 이후 컴포넌트가 다시 렌더링될 때 갱신되지 않기 때문입니다.

// Problematic code - body data will be stale
export default function Chat() {
  const [temperature, setTemperature] = useState(0.7);
  const [userId, setUserId] = useState('user123');

  // This body configuration is captured once and won't update
  const { messages, sendMessage } = useChat({
    transport: new DefaultChatTransport({
      api: '/api/chat',
      body: {
        temperature, // Always the initial value (0.7)
        userId, // Always the initial value ('user123')
      },
    }),
  });

  // Even if temperature or userId change, the body in requests will still use initial values
  return (
    <div>
      <input
        type="range"
        value={temperature}
        onChange={e => setTemperature(parseFloat(e.target.value))}
      />
      {/* Chat UI */}
    </div>
  );
}

배경 (Background)

훅 레벨의 body 구성은 초기 렌더링 중에 한 번만 평가되며 컴포넌트 상태가 바뀌어도 다시 평가되지 않습니다.

해결책 (Solution)

동적 변수는 훅 레벨 대신 sendMessage 함수의 두 번째 인자로 전달하세요. 요청 레벨 옵션은 호출할 때마다 평가되며 훅 레벨 옵션보다 우선합니다.

export default function Chat() {
  const [temperature, setTemperature] = useState(0.7);
  const [userId, setUserId] = useState('user123');
  const [input, setInput] = useState('');

  const { messages, sendMessage } = useChat({
    // Static configuration only
    transport: new DefaultChatTransport({
      api: '/api/chat',
    }),
  });

  return (
    <div>
      <input
        type="range"
        value={temperature}
        onChange={e => setTemperature(parseFloat(e.target.value))}
      />

      <form
        onSubmit={event => {
          event.preventDefault();
          if (input.trim()) {
            // Pass dynamic values as request-level options
            sendMessage(
              { text: input },
              {
                body: {
                  temperature, // Current value at request time
                  userId, // Current value at request time
                },
              },
            );
            setInput('');
          }
        }}
      >
        <input value={input} onChange={e => setInput(e.target.value)} />
      </form>
    </div>
  );
}

대안: 동적 훅 레벨 구성

변화에 대응하는 훅 레벨 구성이 필요하다면 설정 값을 반환하는 함수를 사용할 수 있습니다. 다만 컴포넌트 상태의 경우 useRef를 사용해 현재 값에 접근해야 합니다.

export default function Chat() {
  const temperatureRef = useRef(0.7);

  const { messages, sendMessage } = useChat({
    transport: new DefaultChatTransport({
      api: '/api/chat',
      body: () => ({
        temperature: temperatureRef.current, // Access via ref.current
        sessionId: getCurrentSessionId(), // Function calls work directly
      }),
    }),
  });

  // ...
}

권장사항: 요청 레벨 구성이 컴포넌트 상태에 더 간단하고 신뢰할 수 있습니다. 컴포넌트 수명 동안 변하는 동적 값을 전달해야 한다면 이것을 사용하세요.

서버 측 처리

서버 측에서는 요청 본문을 구조 분해(destructure)해서 커스텀 필드를 가져옵니다.

// app/api/chat/route.ts
export async function POST(req: Request) {
  const { messages, temperature, userId } = await req.json();

  const result = streamText({
    model: 'openai/gpt-4.1-mini', // Supports dynamic temperature settings
    messages: await convertToModelMessages(messages),
    temperature, // Use the dynamic temperature from the request
    // ... other configuration
  });

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

자세한 내용은 chatbot 요청 구성 문서를 참고하세요.

더 알아보기 (Learn more)