
토큰 스트림에서 에이전트 스트림으로
요약
현대적인 AI 에이전트는 단순한 텍스트 토큰 출력을 넘어 도구 호출, 하위 에이전트 활동, 상태 변경 등 구조화된 이벤트 스트림을 필요로 합니다. LangChain과 LangGraph는 타입이 지정된 애플리케이션 이벤트를 통해 복잡한 에이전트 워크플로우를 효율적으로 스트리밍하고 렌더링할 수 있는 아키텍처를 제공합니다.
핵심 포인트
- 단순 토큰 스트리밍을 넘어 구조화된 이벤트 스트림 도입 필요
- 타입 지정된 이벤트로 프론트엔드 렌더링 복잡성 해결
- 텍스트, 도구, 미디어 등 다양한 모달리티 통합 지원
- 하위 에이전트 상태 및 인간의 승인 요청을 일급 이벤트로 처리

핵심 요약 (Key Takeaways)
스트리밍은 토큰을 넘어 진화해야 합니다
현대의 에이전트(Agents)는 메시지, 도구 호출 (Tool calls), 하위 에이전트 (Subagent) 활동, 상태 변경, 승인 및 미디어를 생성하므로, 평면적인 텍스트 출력 대신 구조화된 이벤트 스트림 (Structured event streams)이 필요합니다.
타입이 지정된 이벤트와 프로젝션 (Projections)은 프론트엔드 개발을 단순화합니다
프론트엔드는 에이전트 트리 (Agent tree)에서 렌더링할 부분만 스트리밍하므로, 효율적인 하위 에이전트 검사기 (Subagent inspectors), 대시보드 및 장기 실행되는 프로덕션 워크로드를 구현할 수 있습니다.
하나의 스트리밍 모델이 다양한 런타임과 모달리티 (Modalities)를 아우릅니다
동일한 아키텍처가 로컬 및 원격 실행, React/Vue/Svelte/Angular SDK를 지원하며 텍스트, 도구, 이미지, 오디오, 비디오 및 사용자 정의 애플리케이션 이벤트를 지원합니다.
사람들이 현재 구축하고 있는 에이전트들은 많은 일을 수행합니다. 단 한 번의 딥 에이전트 (Deep Agents) 실행으로 계획을 세우고, 하위 에이전트에게 위임하며, 도구를 호출하고, 인간의 승인을 위해 일시 중지하며, 그 과정에서 텍스트, 구조화된 데이터 또는 미디어를 생성할 수 있습니다. 이러한 모든 단계는 사용자가 발생하는 즉시 확인하고 싶어 할 수 있는 요소들입니다.
최신 Deep Agents, LangChain, 그리고 LangGraph의 스트리밍 작업은 가공되지 않은 청크 (Raw chunks) 대신 애플리케이션 이벤트 (Application events)를 중심으로 설계되었습니다. 각 이벤트는 타입이 지정되어 있으며 에이전트 트리 내 어디에서 왔는지 태그가 붙습니다. 애플리케이션은 메시지, 도구 호출 또는 하위 에이전트 상태와 같은 프로젝션 (Projections)을 반복 처리하며, 동일한 모델이 로컬 실행에서 원격 스레드, 그리고 React, Vue, Svelte, Angular SDK까지 이어집니다. 이번 출시와 함께, 실행 가능한 Python 및 TypeScript 예제가 포함된 스트리밍 쿡북 (Streaming cookbook)을 발행합니다.
요구 사항 (The Requirements)
세 명의 하위 에이전트에게 위임하고, 각 하위 에이전트가 도구를 호출하며, 상태를 업데이트하고, 중간 결과물을 스트리밍하는 연구 에이전트(Research agent)를 가정해 봅시다. 유용한 제품 UI라면 메인 답변을 토큰 단위로 렌더링하고, 각 하위 에이전트의 상태, 조립되는 과정의 도구 호출, 그리고 에이전트가 생성하는 모든 미디어를 보여주고 싶을 것입니다.
이 모든 것을 하나의 스트림으로 평탄화(Flattening)하는 것은 애플리케이션 개발자에게 너무 많은 부담을 줍니다. 만약 스트리밍 레이어가 이러한 복잡성을 흡수한다면, 던져야 할 질문은 "토큰을 보여줄 수 있는가?"를 넘어 다음과 같이 변화할 것입니다:
-
에이전트 작업의 라이브 트리 (live tree)를 렌더링할 수 있는가?
-
단순히 연결된 청크 (concatenated chunks)가 아니라, 명시적인 구조를 가진 추론 (reasoning), 도구 호출 (tool calls), 상태 (state), 미디어 (media)를 스트리밍할 수 있는가?
-
인간의 승인 요청 (human approval requests)을 일급 이벤트 (first-class events)로 노출할 수 있는가?
-
실행 중인 스레드 (thread)에 다시 연결하여 중단된 지점부터 이어서 수행할 수 있는가?
-
런타임 (runtime)을 포크 (forking)하지 않고도 사용자 정의 도메인 특화 스트림 (custom domain-specific streams)을 추가할 수 있는가?
-
동일한 개념을 로컬 (locally), 원격 (remotely), 그리고 프론트엔드 프레임워크 (frontend framework)에서 사용할 수 있는가?

채팅 완성 (chat completions) 및 단일 모델 호출을 위한 스트리밍은 이미 해결된 문제입니다. 다음 단계는 백엔드 (backends)와 프론트엔드 (frontends) 전반에 걸쳐 실행되는 그래프 형태 (graph-shaped), 도구 사용 (tool-using), 상태 유지 (stateful), 중단 가능 (interruptible), 멀티모달 (multimodal) 에이전트를 위한 스트리밍입니다.
해결책 (The Solution)
새로운 스트리밍 프리미티브 (streaming primitives)는 네 가지 아이디어를 중심으로 구축됩니다:
가공되지 않은 청크 (raw chunks) 대신 타입이 지정된 이벤트 (Typed events). 각 이벤트는 그것이 설명하는 작업의 종류(메시지, 도구 호출, 상태 변경, 서브에이전트 상태)와 에이전트 트리 (agent tree) 내의 어느 위치에서 왔는지에 대한 라벨과 함께 도착합니다.
파싱 (parsing) 대신 프로젝션 (Projections). 애플리케이션은 렌더링하고자 하는 뷰(view)인 메시지, 도구 호출, 서브에이전트 상태, 사용자 정의 채널을 반복적으로 처리합니다. 런타임은 조립 (assembly), 재정렬 (reordering), 그리고 재연결 (reconnection)을 처리합니다.
범위가 지정된 구독 (Scoped subscriptions). 클라이언트는 자신이 렌더링하는 채널과 에이전트 트리의 부분만을 요청하므로, 서브에이전트 검사기 (subagent inspector)가 모든 서브에이전트의 토큰을 가져오지 않습니다.
런타임 전반에 걸친 동일한 모델 (The same model across runtimes). 로컬 그래프 실행 (local graph runs), 원격 스레드 (remote threads), 그리고 React/Vue/Svelte/Angular 컴포넌트는 모두 동일한 프로토콜을 사용하며, 하단에는 프로젝션 (projections)이, 상단에는 훅 (hooks)이 위치합니다.
타입이 지정된 이벤트 프로토콜 (A Typed Event Protocol)
새로운 스트리밍 기반은 공통 이벤트 엔벨로프 (event envelope)로 시작합니다. 불투명한 스트림 튜플 (opaque stream tuples) 대신, 어떤 종류의 작업을 설명하는지, 그리고 에이전트 트리의 어디에서 왔는지가 라벨링된 구조화된 이벤트를 받게 됩니다.
채널 (Channels)은 스트리밍되는 관심사 (concern)를 설명합니다:
messages: 트랜스크립트 (transcript) 및 콘텐츠 블록 (content-block) 델타 (deltas)용
values: 그래프 상태 (graph state) 및 updates용
tools: 도구 실행 (tool execution) 라이프사이클 (lifecycle)용
lifecycle: 실행 (runs), 서브그래프 (subgraphs), 및 서브에이전트 (subagents)용
checkpoints: 브랜칭 (branching) 및 타임 트래블 (time travel)용
custom:*: 애플리케이션 정의 프로젝션 (projections)용
네임스페이스 (Namespaces)는 에이전트 트리 (agent tree) 내에서 이벤트가 발생한 위치를 설명합니다. 루트 그래프 (root graph), 중첩된 서브그래프 (nested subgraph), 그리고 Deep Agents 서브에이전트 (subagent)는 모두 자신의 정체성을 잃지 않으면서 동일한 채널 (channel) 유형을 방출할 수 있습니다.
그러한 분리가 핵심적인 설계 선택입니다. 채널 (channels)은 재사용 가능한 관심사 (concerns)인 반면, 네임스페이스 (namespaces)는 실행 (run) 중 해당 이벤트를 생성하는 부분을 식별합니다.
프로젝션 (Projections): 개발자가 실제로 원하는 API
대부분의 애플리케이션 코드는 가공되지 않은 프로토콜 이벤트 (raw protocol events)를 반복 (iterate)해서는 안 됩니다. 대신 렌더링하고자 하는 대상을 요청해야 합니다.
실행 (Runs)은 정확히 그렇게 동작하며, 이벤트 스트림 (event stream) 위에 타입이 지정된 프로젝션 (typed projections)을 노출합니다:
run = await graph.astream_events(
{"messages": [{"role": "user", "content": "Research LangChain streaming"}]},
version="v3",
...
각 메시지는 애플리케이션이 다시 하나로 이어 붙여야 하는 문자열 스트림 (stream of strings) 대신, 텍스트 (text), 추론 (reasoning), 도구 호출 인자 (tool-call arguments), 그리고 사용량 데이터 (usage data)와 같이 타입이 지정된 콘텐츠 블록 (content blocks)으로 도착합니다.
이는 현대적인 모델 출력 (model output)에 있어 매우 중요합니다. 추론 (reasoning)은 최종 답변 텍스트 (final answer text)와 다르게 렌더링되어야 합니다. 도구 호출 인자 (tool-call arguments)는 구조화된 데이터 (structured data)로 조립되어야 합니다. 사용량 (usage) 및 출력 메타데이터 (output metadata)는 스트리밍 경로 (streaming path)를 통해 유지되어야 합니다. 멀티모달 데이터 (multimodal data)가 텍스트 전용 인터페이스 (text-only interface)를 통해 강제로 처리되어서는 안 됩니다.
서브에이전트 (Subagents) 및 서브그래프 (Subgraphs)
동일한 프로젝션 패턴이 메시지 이외의 영역에도 적용됩니다. LangGraph는 개발자가 중첩된 서브그래프 (nested subgraphs)를 포함하여 에이전트를 노드 (nodes)의 그래프로 구조화할 수 있게 해주는 런타임 레이어 (runtime layer)입니다. Deep Agents는 그 위에 위치하며, 에이전트가 서브에이전트 (subagent)에게 작업을 넘길 수 있는 상위 수준의 위임 모델 (delegation model)을 추가합니다. 스트리밍은 이 둘을 하나의 평면적인 트랜스크립트 (flat transcript)로 붕괴시키지 않고 모두 가시화할 수 있어야 합니다.
새로운 프리미티브 (primitives)는 서브그래프 (subgraphs)와 서브에이전트 (subagents)를 구분합니다:
Subgraphs는 중첩된 그래프 (nested graph) 실행을 위한 표면 (surface) 역할을 합니다. Subagents는 에이전트가 Deep Agents의 task 호출을 통해 권한을 위임할 때 나타납니다.
async for subagent in run.subagents:
print(f"{subagent.name}: {subagent.status}")
async for message in subagent.messages:
...
두 가지 모두 정체성 (identity), 위치 (position), 상태 (status)를 읽을 수 있는 경량 핸들 (lightweight handles)로 전달됩니다. 상세 메시지 (detailed messages), 도구 호출 (tool calls), 상태 변경 (state changes)은 UI에서 이를 요청할 때만 스트리밍됩니다.
이를 통해 에이전트의 복잡성에 따라 확장 가능한 UI를 구현할 수 있습니다. Deep Agents를 기반으로 구축된 대시보드는 실행 중인 서브에이전트 (subagents) 목록을 추가 비용 없이 보여준 다음, 선택된 에이전트에 대해서만 메시지 및 도구 스트림을 열 수 있습니다. 연구용 제품은 모든 작업자 (worker)가 생성하는 모든 토큰에 대한 네트워크 비용 (wire cost)을 지불하지 않고도, 작업 트리 전체의 상위 수준 진행 상황을 보여줄 수 있습니다.
개발자에게 이것은 실질적인 변화입니다: 스트리밍은 더 이상 각 애플리케이션이 직접 파싱해야 하는 저수준 전송 세부 사항 (low-level transport detail)이 아닙니다. 그것은 애플리케이션 API (application API)입니다.
커스텀 채널 (Custom Channels)
모든 유용한 스트림이 내장된 채널인 것은 아닙니다. 애플리케이션은 종종 도메인 특화된 투영 (domain-specific projections)을 필요로 합니다: 인용 (citations), 진행 이벤트 (progress events), 구조화된 계획 (structured plans), UI 설명 (UI descriptions), 미디어 핸들 (media handles), 워크플로 메트릭 (workflow metrics) 또는 제품이 실시간으로 렌더링하고자 하는 그 어떤 것이든 해당됩니다.
스트리밍 트랜스포머 (Streaming transformers)는 프로토콜 이벤트 (protocol events)를 필터링하고 파생된 항목을 이름이 지정된 채널로 밀어넣는 작은 클래스입니다. 여기서 ToolActivityTransformer는 도구 호출 시작을 감지하기 위해 messages 채널을 모니터링하고, 그 결과를 toolActivity 확장 (extension)으로 노출합니다. 전체 패턴은 'Build your own projection'을 참조하세요.
run = await graph.astream_events(
input,
version="v3",
...
프론트엔드에서는 동일한 개념이 확장 선택자 (extension selectors)로 나타납니다:
const stream = useStream({
assistantId: "agent",
apiUrl: "<http://localhost:2024>",
...
하나의 이벤트 로그, 다양한 뷰 (One Event Log, Many Views)
Agent UI는 종종 동일한 실행(run)에 대해 여러 개의 라이브 뷰(live views)를 필요로 합니다. 채팅 패널은 주요 답변을 렌더링하고, 사이드 패널은 서브에이전트(subagent)의 활동을 렌더링합니다. 디버거는 가공되지 않은 이벤트(raw events)를 렌더링하며, 진행률 표시줄(progress bar)은 상태를 모니터링합니다. 분석 레이어(analytics layer)는 도구 사용(tool usage)을 기록합니다.
이러한 뷰들이 동일한 스트림을 소모하기 위해 서로 경쟁해서는 안 됩니다.
새로운 런타임 모델은 동일한 기반 이벤트 로그(event log)에 대해 다중 프로젝션(multiple projections)을 지원합니다. 메시지, 값(values), 도구 호출(tool calls), 서브그래프(subgraphs), 그리고 커스텀 확장 기능(custom extensions)을 독립적으로 소비할 수 있습니다. 진행률 뷰를 추가한다고 해서 채팅 스트림을 다시 작성할 필요는 없습니다. 서브에이전트 검사기(subagent inspector)를 추가한다고 해서 모든 서브에이전트 토큰을 모든 컴포넌트에 전송해야 한다는 의미도 아닙니다.
const thread = client.threads.stream({
assistantId: "research-agent",
});
...
이벤트는 정렬 메타데이터(ordering metadata)를 포함하고 있어, 클라이언트가 마지막으로 확인한 지점부터 다시 연결하고 재생(replay)할 수 있습니다. 에이전트가 실행 중인 동안 브라우저가 새로고침되더라도, UI는 스레드(thread)에 다시 연결되어 버퍼링된 이벤트를 따라잡고, 실행을 재시작하거나 콘텐츠를 중복 생성하는 대신 라이브 업데이트를 계속할 수 있습니다.
이는 특히 프로덕션 에이전트 애플리케이션에서 매우 중요합니다. 장시간 실행되는 에이전트는 더 이상 예외적인 사례가 아니며, 개발자들이 목표로 삼고 구축하는 워크로드(workloads)입니다.
멀티모달 스트림 (Multimodal Streams)
이 프로토콜은 단순 문자열이 아닌 콘텐츠 블록(content blocks)을 중심으로 설계되었으며, 이는 멀티모달 스트리밍(multimodal streaming)을 동일한 모델의 자연스러운 확장으로 만들어 줍니다.

쿡북(cookbook)의 멀티모달 스토리북 데모에서는 그래프가 잠자리 동화, 페이지 이미지, 오디오 내레이션, 그리고 비디오를 생성합니다. UI는 미디어 선택기(media selectors)의 범위를 각 페이지를 담당하는 그래프 노드(graph nodes)로 제한하여, 각 페이지의 에셋(assets)이 도착하는 즉시 렌더링할 수 있도록 합니다.
const visualizer = useNodeRun(`visualizer_${index}`);
const images = useImages(stream, visualizer?.namespace);
const imageURL = useMediaURL(images[0]);
중요한 부분은 데모 그 자체가 아닙니다. 텍스트, 추론 (reasoning), 도구 활동 (tool activity), 이미지, 오디오, 비디오, 그리고 커스텀 데이터가 모두 동일한 스트리밍 아키텍처(streaming architecture), 즉 타입화된 블록 (typed blocks), 명명된 채널 (named channels), 네임스페이스 (namespaces), 그리고 프로젝션 (projections) 안에 들어맞는다는 점입니다.
실제 애플리케이션을 위한 프레임워크 SDK
이번 릴리스에는 스트리밍되는 에이전트 애플리케이션을 구축하기 위한 v1 프레임워크 패키지도 포함되어 있습니다:
각 패키지는 프레임워크의 관용구 (idioms)에 맞춰 동일한 스트리밍 개념을 노출합니다. React는 훅 (hooks)을 사용하고, Vue는 컴포저블 (composables)을 사용하며, Svelte는 반응형 헬퍼 (reactive helpers)를 사용하고, Angular는 인젝터 (injectors)와 시그널 (signals)을 사용합니다.
핵심적인 멘탈 모델 (mental model)은 공유됩니다:
- 하나의 루트 훅 (root hook):
useStream
또는 Angular의 경우
injectStream
- 최상위 프로젝션 (Top-level projections):
messages,values, 도구 호출 (tool calls), 인터럽트 (interrupts)는 별도의 설정 없이 사용할 수 있습니다. - 컴포넌트 수준의 셀렉터 (Component-level selectors):
useMessages,useToolCalls,useExtension및 관련 함수들은 무언가가 마운트 (mount)될 때만 범위가 지정된 구독 (scoped subscriptions)을 수행합니다. - 서브에이전트 (Subagents)와 서브그래프 (subgraphs)는 즉시 나타납니다: 이들의 상세 스트림은 사용자가 직접 접근할 때만 열립니다.
- 동일한 스레드에서 다시 마운트 (remounting)하면 재생 (replay)이나 중복 없이 진행 중인 실행 (in-flight run)에 다시 연결됩니다.
React에서 기본적인 스트리밍 채팅은 다음과 같이 간결하게 유지될 수 있습니다:
const stream = useStream({
assistantId: "agent",
apiUrl: "<http://localhost:2024>",
...
function SubagentCard({ stream, subagent }) {
const messages = useMessages(stream, subagent);
const toolCalls = useToolCalls(stream, subagent);
...
이것이 콜백 중심의 스트리밍 (callback-heavy streaming)과 렌더링 중심의 스트리밍 (render-driven streaming)의 차이입니다. 컴포넌트는 필요한 프로젝션을 마운트하며, SDK가 구독 수명 주기 (subscription lifetimes), 재연결 (reconnection), 그리고 조립 (assembly)을 처리합니다.
다음 단계
이번 릴리스는 타입화된 이벤트 (typed events), 콘텐츠 블록 (content blocks), 프로젝션 (projections), 범위 지정된 구독 (scoped subscriptions), 재연결 의미론 (reconnect semantics), 프레임워크 SDK, 그리고 커스텀 채널 (custom channels)이라는 토대를 마련했습니다.
우리는 이미 프로토콜을 더욱 확장하고 있습니다. 단기적인 방향 중 하나는 더욱 풍부한 파일 스트리밍 (file streaming)입니다. 즉, 샌드박스 (sandbox)나 기타 관리형 실행 환경 (managed execution environment)과 같은 백엔드에 연결된 임의의 파일을 에이전트가 스트리밍할 수 있도록 하는 모듈입니다. 에이전트가 더 많은 아티팩트 (artifacts)를 생성하고 수정함에 따라, 해당 아티팩트들은 메시지 (messages), 도구 (tools), 상태 (state), 그리고 미디어 (media)와 동일한 스트리밍 모델을 통해 흘러가야 합니다.
스트리밍 에이전트 (Streaming agents)를 구축하는 것은 로그를 파싱 (parsing)하는 것이 아니라, 애플리케이션을 구축하는 것처럼 느껴져야 합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 LangChain Blog의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기