메시지 큐

메시지 큐 (Message Queues)

채팅 UI를 쓰다 보면, 에이전트가 현재 메시지를 처리하는 동안에도 사용자가 다음 메시지를 계속 입력하고 싶어질 때가 많아요. 기본 채팅 인터페이스는 에이전트가 응답을 끝낼 때까지 기다려야 하는데, 메시지 큐(pattern)를 쓰면 여러 메시지를 연달아 보내고, 에이전트가 순서대로 처리하는 동안 그 대기 작업을 직접 관리할 수 있습니다. 이 페이지에서 그 방법을 설명해 드릴게요.

출처: LangChain 공식 문서 — frontend-message-queues

왜 메시지 큐가 필요할까요?

일반적인 채팅 인터페이스에서는 에이전트가 응답을 끝낼 때까지 기다렸다가 다음 메시지를 보내야 해요. 이런 제약은 여러 상황에서 답답함을 만들죠.

  • 일괄 질문(Batch questions) — 각 답을 기다리지 않고, 관련 질문 다섯 개를 한 번에 묻고 싶은 경우
  • 후속 질문 체인(Follow-up chains) — 에이전트가 작업 중일 때 설명을 보강하거나 추가 컨텍스트를 제출하는 경우
  • 자동 테스트 시퀀스(Automated testing) — 에이전트 동작을 검증하려고 일련의 프롬프트를 프로그래밍 방식으로 보내는 경우
  • 데이터 입력 워크플로(Data entry) — 구조화된 입력을 처리하기 위해 하나씩 연달아 넣어야 하는 경우

메시지 큐는 모든 제출을 즉시 받아들이고 순서대로 처리함으로써 이 문제를 해결해요. 이건 단순한 화면 장식용 채팅 기능이 아니라 에이전트 UX 원시 동작(primitive)입니다. SDK가 스트림 컨트롤러의 일부로 큐를 추적하기 때문에, 여러분의 UI는 대기 중인 작업을 보여주고, 오래된 요청을 취소하고, 현재 실행이 계속되는 동안에도 입력창(composer)을 활성 상태로 유지할 수 있어요.

어떻게 동작하나요?

현재 실행 중인 요청 뒤에 대기하도록 만들려면 multitaskStrategy: "enqueue"를 전달하면 됩니다. 에이전트가 처리하는 동안 큐에 들어온 제출은 활성 스레드의 큐에 추가되고, 현재 실행이 끝나면 다음 큐 메시지가 자동으로 배포(dispatch)돼요.

프레임워크별 동반 큐 헬퍼로 큐 상태를 읽을 수 있습니다.

프로퍼티 타입 설명
queue.entries SubmissionQueueEntry[] 대기 중인 모든 큐 항목의 배열
queue.size number 현재 큐에 있는 항목 수
queue.cancel(id) (id: string) => Promise<void> ID로 특정 큐 항목 취소
queue.clear() () => Promise<void> 큐의 모든 항목 취소

SubmissionQueueEntry 객체는 다음 필드를 포함해요.

필드 타입 설명
id string 이 큐 항목의 고유 식별자
values object 제출된 입력 값(메시지 포함)
options object 제출 시 전달한 추가 옵션
createdAt string 항목이 생성된 시점의 ISO 타임스탬프

useStream 설정하기

useStream을 에이전트에 연결하고, 프레임워크의 제출 큐 헬퍼와 짝지어 주세요. 실행 중일 때 메시지를 보내려면 stream.submit()을 호출하고, 활성 요청 뒤에 대기해야 하는 제출에는 multitaskStrategy: "enqueue"를 전달합니다. queue.entriesqueue.size를 읽어 대기 작업을 렌더링하고, 처리 시작 전에 항목을 제거하려면 queue.cancel()이나 queue.clear()를 씁니다.

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

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

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

  const handleSubmit = (text: string) => {
    stream.submit({
      messages: [{ type: "human", content: text }],
    });
  };

  const pendingCount = queue.size;
  const entries = queue.entries;

  return (
    <div>
      <MessageList messages={stream.messages} />
      {pendingCount > 0 && <QueueList entries={entries} queue={queue} />}
      <ChatInput onSubmit={handleSubmit} />
    </div>
  );
}

Vue에서는 useSubmissionQueue + computed로, Svelte에서는 useSubmissionQueue로, Angular에서는 injectSubmissionQueue로 같은 구조를 만들 수 있어요.

큐 표시하기

대기 중인 각 메시지에 취소 버튼을 달아 QueueList 컴포넌트를 만드세요. 사용자가 무엇이 기다리고 있는지 보고, 더 이상 필요 없는 항목을 제거할 수 있게 해 주는 거죠. 각 큐 메시지의 첫 몇 글자를 미리보기로 보여주면, 전체 메시지를 읽지 않고도 어떤 항목을 취소할지 빠르게 식별할 수 있어요.

큐 메시지 취소하기

취소는 두 가지 수준으로 할 수 있습니다.

단일 항목 취소

ID로 큐에서 특정 메시지를 제거합니다. 에이전트는 그 항목을 건너뛰고 다음 항목으로 넘어가요.

await queue.cancel(entryId);

전체 큐 비우기

대기 중인 모든 메시지를 한 번에 제거합니다. 사용자가 컨텍스트를 바꾸거나 처음부터 다시 시작하고 싶을 때 유용해요.

await queue.clear();

주의할 점이 있어요. 큐 항목 취소는 아직 처리되지 않은 메시지에만 영향을 줍니다. 에이전트가 이미 작업 중인 메시지를 큐에서 취소해도 효과가 없고, 그런 경우에는 stream.stop()으로 현재 실행을 중단해야 해요.

onCreated로 후속 제출 체인 만들기

onCreated 콜백은 새 실행(run)이 생성될 때 실행되어, 후속 메시지를 프로그래밍 방식으로 제출하는 훅 역할을 해요. 다음 질문이 이전 제출이 받아들여졌는지에 의존하는 다단계 워크플로를 만드는 데 유용합니다.

stream.submit(
  { messages: [{ type: "human", content: "What is quantum computing?" }] },
  {
    onCreated(run) {
      console.log("Run created:", run.runId);
      // Chain a follow-up
      stream.submit({
        messages: [{ type: "human", content: "Give me a simple analogy." }],
      });
    },
  }
);

이 패턴은 자연스럽게 큐를 채워요. 첫 번째 메시지는 즉시 처리를 시작하고, 후속 메시지는 그 뒤에 큐에 쌓이죠.

새 스레드 시작하기

새 대화를 시작하려면 스트림에 전달하는 반응형 threadId를 업데이트하면 됩니다. null을 전달하면 현재 스레드 바인딩이 지워지고, 다음 제출이 새 스레드를 만듭니다.

function NewThreadButton() {
  const [threadId, setThreadId] = useState<string | null>(null);
  const stream = useStream<typeof myAgent>({ threadId, onThreadId: setThreadId });

  return (
    <button onClick={() => setThreadId(null)}>
      New conversation
    </button>
  );
}

모범 사례

  • 큐 크기 제한 — 클라이언트 쪽에 하드 제한은 없지만, 큐가 매우 커지면 사용자 경험이 나빠질 수 있어요. 합리적인 임계값(예: 10개)을 넘으면 경고를 보여주는 걸 고려하세요.
  • 큐 위치 표시 — 각 항목에 번호를 매겨 사용자에게 처리 순서를 알려주세요.
  • 입력 포커스 유지 — 제출 후에도 입력 필드에 포커스를 유지해서 다음 메시지를 바로 입력할 수 있게 하세요.
  • 전환 애니메이션 — 처리 시작 시 큐 패널에서 메시지 목록으로 항목이 부드럽게 이동하게 하세요.
  • 오류 우아하게 처리 — 큐 메시지가 실패해도 이후 큐 항목을 막지 않도록 오류를 표면화하세요.
  • 빠른 제출 디바운스 — 자동 또는 프로그래밍 방식 제출에서는 메시지 사이에 작은 지연을 넣어 서버에 과부하가 걸리지 않게 하세요.

더 알아보기 (Learn more)