직렬화
직렬화 (Serialization)
AG-UI에서 이벤트 스트림을 저장하고, 분기(branch)하고, 압축(compact)하기 위해 어떻게 직렬화하는지 다루는 페이지예요. 직렬화된 스트림을 이용하면 대화와 UI 상태를 복원하고, 실행 중인 에이전트에 붙고, 과거 실행에서 분기하는 것까지 가능합니다.
출처: 문서
본문
AG-UI의 직렬화는 에이전트–UI 세션을 구동하는 이벤트 스트림을 영속화하고 복원하는 표준 방법을 제공해요. 직렬화된 스트림이 있으면 여러분은:
- 새로고침이나 재연결 뒤에 채팅 내역과 UI 상태를 복원할 수 있어요
- 실행 중인 에이전트에 붙어서 계속 이벤트를 받을 수 있어요
- 이전 실행에서 분기(타임 트래블)를 만들 수 있어요
- 의미를 잃지 않으면서 저장된 내역을 압축해 크기를 줄일 수 있어요
이 페이지는 그 모델, 갱신된 이벤트 필드, 그리고 예시와 함께 실용적인 사용 패턴을 설명합니다.
핵심 개념 (Core Concepts)
- 스트림 직렬화 – 전체 이벤트 내역을 휴대용 표현(예: JSON)으로/로부터 변환해 데이터베이스, 파일, 로그에 저장합니다.
- 이벤트 압축 – 장황한 스트림을 의미를 보존하면서 스냅샷으로 줄여요(예: 콘텐츠 청크 병합, delta를 snapshot으로 축소).
- 실행 계보 (Run lineage) –
parentRunId로 대화의 분기를 추적해, 타임 트래블과 대안 경로를 가능하게 하는 git 같은 append-only 로그를 형성합니다.
갱신된 이벤트 필드 (Updated Event Fields)
RunStarted 이벤트는 추가 선택 필드들을 포함합니다:
type RunStartedEvent = BaseEvent & {
type: EventType.RUN_STARTED
threadId: string
runId: string
/** Parent for branching/time travel within the same thread */
parentRunId?: string
/** Exact agent input for this run (may omit messages already in history) */
input?: AgentInput
}
이 필드들은 계보 추적을 가능하게 하고, 구현체가 이미 기록된 메시지와 무관하게 에이전트에게 전달된 것이 정확히 무엇인지 기록할 수 있게 해 줍니다.
이벤트 압축 (Event Compaction)
압축은 동일한 관찰 가능한 결과를 유지하면서 이벤트 스트림의 잡음을 줄여요. 전형적인 구현은 유틸리티를 제공합니다:
declare function compactEvents(events: BaseEvent[]): BaseEvent[]
흔한 압축 규칙은 다음과 같습니다:
- 메시지 스트림 –
TEXT_MESSAGE_*시퀀스를 단일 메시지 스냅샷으로 결합하고, 같은 메시지에 대한 인접한TEXT_MESSAGE_CONTENT를 이어붙입니다. - 도구 호출 – 도구 호출 start/content/end를 컴팩트한 레코드로 축소해요.
- 상태 – 연속된
STATE_DELTA이벤트를 단일 최종STATE_SNAPSHOT으로 병합하고, 대체된 업데이트를 버립니다. - 실행 입력 정규화 –
RunStarted.input.messages에서 스트림 앞부분에 이미 있는 메시지를 제거합니다.
분기와 타임 트래블 (Branching and Time Travel)
RunStarted 이벤트에 parentRunId를 설정하면 git 같은 계보를 만듭니다. 스트림은 불변의 append-only 로그가 되며, 각 실행은 이전 실행 중 어느 것에서든 분기할 수 있습니다.
gitGraph
commit id: "run1"
commit id: "run2"
branch alternative
checkout alternative
commit id: "run3 (parent run2)"
commit id: "run4"
checkout main
commit id: "run5 (parent run2)"
commit id: "run6"
장점:
- 같은 직렬화된 로그에 여러 분기
- 불변 내역(append-only)
- 어떤 지점으로든 결정적 타임 트래블
예시 (Examples)
기본 직렬화 (Basic Serialization)
// Serialize event stream
const events: BaseEvent[] = [...];
const serialized = JSON.stringify(events);
await storage.save(threadId, serialized);
// Restore and compact later
const restored = JSON.parse(await storage.load(threadId));
const compacted = compactEvents(restored);
이벤트 압축 (Event Compaction)
Before:
[
{ type: "TEXT_MESSAGE_START", messageId: "msg1", role: "user" },
{ type: "TEXT_MESSAGE_CONTENT", messageId: "msg1", delta: "Hello " },
{ type: "TEXT_MESSAGE_CONTENT", messageId: "msg1", delta: "world" },
{ type: "TEXT_MESSAGE_END", messageId: "msg1" },
{ type: "STATE_DELTA", patch: { op: "add", path: "/foo", value: 1 } },
{ type: "STATE_DELTA", patch: { op: "replace", path: "/foo", value: 2 } },
]
After:
[
{
type: "MESSAGES_SNAPSHOT",
messages: [{ id: "msg1", role: "user", content: "Hello world" }],
},
{
type: "STATE_SNAPSHOT",
state: { foo: 2 },
},
]
parentRunId로 분기 (Branching With parentRunId)
// Original run
{
type: "RUN_STARTED",
threadId: "thread1",
runId: "run1",
input: { messages: ["Tell me about Paris"] },
}
// Branch from run1
{
type: "RUN_STARTED",
threadId: "thread1",
runId: "run2",
parentRunId: "run1",
input: { messages: ["Actually, tell me about London instead"] },
}
정규화된 입력 (Normalized Input)
// First run includes full message
{
type: "RUN_STARTED",
runId: "run1",
input: { messages: [{ id: "msg1", role: "user", content: "Hello" }] },
}
// Second run omits already‑present message
{
type: "RUN_STARTED",
runId: "run2",
input: { messages: [{ id: "msg2", role: "user", content: "How are you?" }] },
// msg1 omitted; it already exists in history
}
메타데이터 (Metadata)
이벤트와 메시지는 선택적인 metadata 객체를 가지며, 그것은 직렬화 대상의 일부예요 — 다른 모든 것과 함께 영속시키세요. 아니면 재생된 스트림에 토큰 사용량, 트레이스 ID, 그리고 생산자가 붙인 나머지가 빠져 버립니다.
영속화할 때 알아야 할 두 가지:
- 압축이 그것을 접습니다. delta를 축소하면 그 메타데이터가 병합되며, 마지막 쓰기가 이깁니다. 이벤트 압축과 순서 주의점에 대한 압축 레퍼런스를 참고하세요.
- 바이너리는 JSON보다 좁아요. protobuf 위에서 메타데이터는
google.protobuf.Struct이므로 null, 배열, 중첩 객체는 살아남지만 — 숫자는 IEEE-754 double이라 2^53을 넘는 정수는 정밀도를 잃고, 일부 이벤트 타입은 protobuf 표현이 아예 없습니다. JSON은 모든 것을 정확히 라운드트립해요. 보관 저장에는 JSON을 선호하세요.
구현 노트 (Implementation Notes)
- 압축 및 (역)직렬화를 위한 SDK 헬퍼를 제공하세요.
- 스트림을 append-only로 저장하고, 가능하면 증분 쓰기를 선호하세요.
- 긴 내역을 영속화할 때는 압축을 고려하세요.
- 빠른 검색을 위해
threadId,runId, 타임스탬프에 인덱스를 추가하세요.
같이 보기 (See Also)
- 개념: 이벤트 (Events), 메타데이터 (Metadata), 상태 관리 (State Management)
- SDK: TypeScript 인코더와 핵심 이벤트 타입
더 알아보기 (Learn more)
- 메타데이터 (Metadata) — 이벤트·메시지에 붙이는 추가 정보
- 도구 (Tools) — AG-UI 도구의 구조와 수명주기
- 이벤트 (1.0 스펙) — 여덟 가지 이벤트 패밀리