그래프 실행 시각화

그래프 실행 시각화 (Graph execution · LangGraph 프론트엔드 패턴)

LangGraph 에이전트는 블랙박스가 아니에요. 모든 그래프는 classify, research, analyze, synthesize처럼 순차 또는 병렬로 실행되는 '이름 있는 노드(named nodes)'로 구성돼요. 그래프 실행 카드 패턴은 각 노드마다 카드를 렌더링해 상태를 보여주고, 콘텐츠를 실시간으로 스트리밍하며, 전체 워크플로의 완료를 추적해요. 사용자는 에이전트가 무엇을 하고 있는지, 어떤 단계인지, 각 단계가 무엇을 만들었는지 정확히 볼 수 있어요.

출처: 공식문서

그래프 노드와 UI 카드 매핑

LangGraph 그래프는 각각 특정 작업을 담당하는 노드 시리즈를 정의해요. 예를 들어 리서치 파이프라인은 Classify(쿼리 분류) → Research(정보 수집) → Analyze(결론 도출) → Synthesize(최종 응답)로 구성할 수 있어요.

프론트엔드에서 이 매핑을 하드코딩할 필요는 없어요. useStream이 실행 시점에 stream.subgraphs로 각 노드를 자동 발견하고, 관찰된 단계마다 SubgraphDiscoverySnapshot을 노출해요.

// 노드는 자동 발견됨 — 하드코딩된 목록 불필요
const graphNodes = [...stream.subgraphs.values()];
graphNodes.forEach((node) => {
  console.log(node.nodeName, node.status); // "classify", "running"
});

node.nodeName을 진행바와 카드 헤더의 라벨로 쓰고, 각 스냅샷을 useMessages(stream, node)에 전달해 노드 스코프의 스트리밍 콘텐츠를 그래프 상태 키 이름에 결합하지 않고 렌더링해요. 이 매핑이 그래프와 UI 사이의 계약(contract)이 돼요. 백엔드 개발자는 의도적으로 노드를 추가·이름 변경·재정렬할 수 있고, 프론트엔드 개발자는 각 상태 키를 상태 배지, 마크다운 패널, 표, 차트, 트레이스 뷰, 승인 카드 등으로 시각화할지 정해요.

useStream 설정

useStream의 핵심 속성은 messages(대화)와 subgraphs(현재 실행에서 발견된 그래프 노드)예요. React/Vue/Svelte/Angular 각각의 훅이 있어요. useStream<typeof myAgent>로 타입 안전한 스트림 상태를 만드는 걸 권장해요.

스트리밍 토큰을 노드로 라우팅

그래프가 스트리밍되면 각 발견된 서브그래프 스냅샷이 속한 노드를 식별해요. 그 스냅샷을 셀렉터 훅/컴포저블에 전달해 해당 노드 스코프의 메시지를 읽어요.

import { AIMessage } from "langchain";
import { useMessages, type AnyStream, type SubgraphDiscoverySnapshot } from "@langchain/react";

function NodeCard({ node, stream }: { node: SubgraphDiscoverySnapshot; stream: AnyStream }) {
  const messages = useMessages(stream, node);
  const lastAIMessage = messages.find(AIMessage.isInstance);
  const streamingContent = lastAIMessage?.text ?? "";
  return <NodeCardBody node={node} content={streamingContent} />;
}

첫 번째 마운트된 셀렉터가 해당 노드 네임스페이스의 스코프 구독을 열고, 카드가 언마운트되면 구독이 자동 해제돼요.

노드 상태 판단

각 발견된 노드는 현재 상태를 갖고 있어요. node.status를 직접 쓰며, 디스커버리 스냅샷은 "pending", "running", "complete", "error"를 보고해요.

type NodeStatus = SubgraphDiscoverySnapshot["status"];
const status: NodeStatus = node.status;

파이프라인 진행바 만들기

상단의 가로 진행바가 파이프라인 전체의 조감도를 줘요. 각 단계는 라벨이 붙은 세그먼트로, 노드가 완료되면 채워져요. 색상은 pending(회색), running(파란색 animate-pulse), complete(초록), error(빨강)로 구분해요.

접을 수 있는 NodeCard 컴포넌트

각 노드는 상태 배지, 콘텐츠(스트리밍 또는 최종), 긴 출력을 위한 접을 수 있는 본문을 가진 카드를 가져요. running 상태면 자동으로 펼치고, complete되면 접어요.

스트리밍 vs 완료 콘텐츠

노드 카드는 스트리밍·최종 콘텐츠 모두 스코프 메시지를 읽어요. 그래프 노드 이름이 쓰는 상태 키와 일치한다고 가정하지 않아요. 패턴은 카드에 가장 최근 스코프 AI 메시지를 보여주고, stream.values는 그래프 상태 필드가 의도적으로 필요할 때만 써요. 스코프 메시지는 노드에 연결되므로 메시지 순서를 추측하지 않고 병렬 그래프 경로를 지원해요. 주의: 스트리밍 콘텐츠는 완성되지 않은 토큰이나 마크다운을 포함할 수 있어요. 마크다운 렌더러가 닫히지 않은 볼드(**) 같은 불완전한 문법을 우아하게 처리하게 하세요.

유스 케이스

  • 리서치 파이프라인: classify → gather sources → analyze → synthesize a report
  • 콘텐츠 생성: outline → draft → fact-check → edit → publish
  • 데이터 처리: ingest → validate → transform → aggregate → export
  • 코드 생성: understand requirements → plan architecture → write code → review → test
  • 의사결정 워크플로: gather context → evaluate options → score alternatives → recommend

동적 파이프라인 처리

모든 그래프가 고정 노드 집합을 갖는 건 아니에요. 입력에 따라 노드를 추가하거나 건너뛰는 파이프라인이 있어요. 디스커버리 맵에는 현재 스레드에서 관찰된 노드만 들어가요. [...stream.subgraphs.values()]로 UI에 현재 실행과 관련된 카드만 보여주면 빈 플레이스홀더를 피할 수 있어요. 조건부 분기가 있으면(예: 단순 팩트 쿼리에 "Research" 건너뛰기) 건너뛴 노드는 stream.subgraphs에 나타나지 않아요.

모범 사례

  • 노드를 스트림에서 발견: stream.subgraphs에서 카드를 렌더링하고, 하드코딩하지 마세요.
  • 상태 키를 UI 계약으로 취급: 프론트엔드가 렌더링할 안정적인 그래프 출력을 정하고, 그래프 정의 옆에 문서화하세요.
  • 노드 카드는 스코프 메시지 사용: 스트리밍 중·완료 후 모두 동작하며, 상태 키 이름에 결합되지 않아요.
  • 완료 노드 자동 접기: 긴 파이프라인에서 완료 카드를 자동 접어 현재 활성 단계에 집중하게 하세요.
  • 추정 시간 표시: 각 노드가 걸리는 시간 데이터가 있으면 예상 시간을 보여주세요.
  • 전체 진행 지표 추가: 파이프라인 상단에 "Step 2 of 4" 같은 전체 진행바를 넣으세요.
  • 노드별 오류 처리: 노드가 실패하면 카드 안에 오류를 보여주고 전체 파이프라인을 접지 마세요.

더 알아보기 (Learn more)