메시지

메시지 (Messages)

AG-UI 프로토콜에서 메시지가 어떻게 구조화되고 주고받아지는지 설명해 드릴게요. 메시지는 사용자와 AI 에이전트 사이의 대화 히스토리를 나타내고, 어떤 AI 서비스를 쓰든 정보를 교환하는 표준화된 방식을 제공해요.

출처: 문서

본문

메시지는 AG-UI 프로토콜에서 통신의 중추를 이룬답니다. 사용자와 AI 에이전트 사이의 대화 히스토리를 나타내며, 사용 중인 AI 서비스와 무관하게 정보를 교환할 수 있는 표준화된 방식을 제공해요.

메시지 구조

AG-UI 메시지는 벤더 중립(vendor-neutral) 형식을 따릅니다. 그래서 서로 다른 AI 프로바이더 사이에서도 일관된 구조를 유지하면서 호환성을 보장해요. 이 덕분에 애플리케이션이 클라이언트 측 구현을 바꾸지 않고도 OpenAI, Anthropic, 커스텀 모델 같은 AI 서비스 사이를 전환할 수 있어요.

기본 메시지 구조는 이렇게 생겼어요.

interface BaseMessage {
  id: string // Unique identifier for the message
  role: string // The role of the sender (user, assistant, system, tool, reasoning)
  content?: string // Optional text content of the message
  name?: string // Optional name of the sender
  encryptedContent?: string // Optional encrypted content for privacy-preserving state continuity
  metadata?: Record<string, any> // Optional extra information attached to the message
}

role 판별자(discriminator)는 "user", "assistant", "system", "tool", "developer", "activity", "reasoning" 중 하나가 돼요. 구체적인 메시지 타입은 필요한 필드를 추가해 이 형태를 확장합니다.

모든 메시지 타입은 metadata를 담아요. 이것은 이벤트에서 메시지가 조립되면서 누적되는, 키로 여는(open-by-key) 선택적 객체예요. 도구 호출은 각자 자신의 메타데이터를 가집니다. 규칙은 Metadata를 참고하세요.

encryptedContent 필드는 프라이버시 보존 워크플로를 가능하게 해요. 민감한 콘텐츠(예: 추론 체인)를 원문 그대로 노출하지 않고 턴 사이에 전달할 수 있게 하죠. 특히 제로 데이터 보존(ZDR) 컴플라이언스와 store:false 시나리오에서 유용합니다.

메시지 타입

AG-UI는 대화의 서로 다른 참여자를 다루기 위해 여러 메시지 타입을 지원해요.

사용자 메시지 (User Messages)

최종 사용자에서 에이전트로 가는 메시지예요.

interface UserMessage {
  id: string
  role: "user"
  content: string | ContentPart[] // Text or multimodal input from the user
  name?: string // Optional user identifier
}

type ContentPart =
  | TextPart
  | ImagePart
  | AudioPart
  | VideoPart
  | DocumentPart

interface DataSource {
  type: "data"
  value: string
  mimeType: string
}

interface UrlSource {
  type: "url"
  value: string
  mimeType?: string
}

interface FileSource {
  type: "file"
  value: string // a handle the provider issued; opaque, never fetched
  provider?: string // who issued it, e.g. "openai"
  mimeType?: string
}

type PartSource = DataSource | UrlSource | FileSource

interface TextPart {
  type: "text"
  text: string
}

interface ImagePart {
  type: "image"
  source: PartSource
  metadata?: Record<string, unknown>
}

interface AudioPart {
  type: "audio"
  source: PartSource
  metadata?: Record<string, unknown>
}

interface VideoPart {
  type: "video"
  source: PartSource
  metadata?: Record<string, unknown>
}

interface DocumentPart {
  type: "document"
  source: PartSource
  metadata?: Record<string, unknown>
}

Python에서는 이전의 BinaryInputContent 모델은 deprecated이며, 호환성 경로로 임시로 남아 있습니다.

이 구조는 전통적인 평문 입력이 계속 동작하게 하면서도, 같은 메시지 안에서 이미지, 오디오 클립, 업로드된 파일 같은 더 풍부한 페이로드를 지원해요.

어시스턴트 메시지 (Assistant Messages)

AI 어시스턴트에서 사용자에게 가는 메시지예요.

interface AssistantMessage {
  id: string
  role: "assistant"
  content?: string // Text response from the assistant (optional if using tool calls)
  name?: string // Optional assistant identifier
  toolCalls?: ToolCall[] // Optional tool calls made by the assistant
  encryptedContent?: string // Optional encrypted content for state continuity
}

시스템 메시지 (System Messages)

에이전트에 제공되는 지시나 컨텍스트예요.

interface SystemMessage {
  id: string
  role: "system"
  content: string // Instructions or context for the agent
  name?: string // Optional identifier
}

도구 메시지 (Tool Messages)

도구 실행의 결과를 나타내요.

interface ToolMessage {
  id: string
  role: "tool"
  content: string // Result from the tool execution
  toolCallId: string // ID of the tool call this message responds to
  error?: string // Optional error message if the tool execution failed
  encryptedValue?: string // Optional encrypted reasoning for state continuity
}

핵심 포인트:

  • toolCallId는 결과를 원래 도구 호출에 연결해 줍니다.
  • error로 도구 실행 실패를 나타낼 수 있어요.
  • encryptedValue로 에이전트가 도구 결과를 어떻게 해석·처리했는지와 관련한 암호화된 체인-오브-쏘트를 붙일 수 있어요.

액티비티 메시지 (Activity Messages)

프런트엔드에만 존재하는 구조화된 UI 메시지예요. 진행, 상태, 모델에 보내면 안 되는 커스텀 시각 요소 같은 데 쓰입니다.

interface ActivityMessage {
  id: string
  role: "activity"
  activityType: string // e.g. "PLAN", "SEARCH", "SCRAPE"
  content: Record<string, any> // Structured payload rendered by the frontend
}

핵심 포인트

  • ACTIVITY_SNAPSHOT과 ACTIVITY_DELTA를 통해 내보내져 실시간으로 갱신 가능한 UI(체크리스트, 단계, 검색 진행 중 등)를 지원합니다.
  • 프런트엔드 전용: 에이전트에 전달되지 않으므로 필터링도 LLM 혼란도 없어요.
  • 커스터마이징 가능: 자신만의 activityType과 content를 정의하고 그에 맞는 UI 컴포넌트를 렌더링할 수 있어요.
  • 스트리밍 가능: 오래 걸리는 작업 중에도 시간에 따라 갱신할 수 있어요.
  • 커스텀 이벤트를 지속 가능한 메시지 객체로 바꿔 저장/복원을 돕습니다.

개발자 메시지 (Developer Messages)

개발이나 디버깅에 사용되는 내부 메시지예요.

interface DeveloperMessage {
  id: string
  role: "developer"
  content: string
  name?: string
}

추론 메시지 (Reasoning Messages)

에이전트의 내부 추론이나 체인-오브-쏘트 과정을 나타내는 메시지예요.

interface ReasoningMessage {
  id: string
  role: "reasoning"
  content: string // Reasoning content (visible to client)
  encryptedValue?: string // Optional encrypted reasoning for state continuity
}

액티비티 메시지와 달리 추론 메시지는 에이전트의 내부 사고 과정을 나타내며, 프라이버시를 위해 암호화될 수 있어요. 또한 이후 턴에서 추가 처리를 위해 에이전트로 다시 보내지는 것을 전제로 해요.

핵심 포인트:

  • REASONING_MESSAGE_START, REASONING_MESSAGE_CONTENT, REASONING_MESSAGE_END 이벤트를 통해 내보내집니다.
  • 가시성 제어: 콘텐츠를 사용자에게 보여줄 수 있고(요약으로), 완전히 암호화할 수도 있어요.
  • 암호화된 값: REASONING_ENCRYPTED_VALUE 이벤트로 콘텐츠를 노출하지 않고도 메시지나 도구 호출에 암호화된 체인-오브-쏘트를 붙일 수 있어요.
  • 상태 연속성: 암호화된 추론 항목을 원문 체인-오브-쏘트를 노출하지 않고 대화 턴 사이에 전달할 수 있어요.
  • 프라이버시 우선: 추론 능력을 보존하면서 store:false와 제로 데이터 보존(ZDR) 정책을 지원합니다.
  • 어시스턴트 메시지와 분리: 추론을 최종 응답과 분리해 대화 히스토리를 오염시키지 않습니다.

스트리밍 이벤트 라이프사이클은 Reasoning Events를 참고하세요.

벤더 중립성

AG-UI 메시지는 벤더 중립적으로 설계됐어요. 즉 다양한 AI 프로바이더가 쓰는 독점 형식으로 쉽게 변환하거나 그 형식에서 쉽게 되돌릴 수 있습니다.

// Example: Converting AG-UI messages to OpenAI format
const openaiMessages = agUiMessages
  .filter((msg) => ["user", "system", "assistant"].includes(msg.role))
  .map((msg) => ({
    role: msg.role as "user" | "system" | "assistant",
    content: msg.content || "",
    // Map tool calls if present
    ...(msg.role === "assistant" && msg.toolCalls
      ? {
          tool_calls: msg.toolCalls.map((tc) => ({
            id: tc.id,
            type: tc.type,
            function: {
              name: tc.function.name,
              arguments: tc.function.arguments,
            },
          })),
        }
      : {}),
  }))

이 추상화 덕분에 AG-UI는 사용 중인 AI 서비스와 무관하게 공통 인터페이스 역할을 할 수 있어요.

메시지 동기화

메시지는 두 가지 주요 메커니즘으로 클라이언트와 서버 사이에 동기화될 수 있어요.

전체 스냅샷

MESSAGES_SNAPSHOT 이벤트는 대화의 모든 메시지에 대한 전체 뷰를 제공해요.

interface MessagesSnapshotEvent {
  type: EventType.MESSAGES_SNAPSHOT
  messages: Message[] // Complete array of all messages
}

주로 이렇게 쓰입니다.

  • 대화를 초기화할 때
  • 연결이 끊긴 후
  • 큰 상태 변화가 있을 때
  • 클라이언트-서버 동기화를 보장하려 할 때

스트리밍 메시지

실시간 상호작용을 위해 새 메시지를 생성되는 대로 스트리밍할 수 있어요.

  1. 메시지 시작: 새 메시지를 만들고 있음을 알립니다.

    interface TextMessageStartEvent {
      type: EventType.TEXT_MESSAGE_START
      messageId: string
      role: string
    }
    
  2. 콘텐츠 스트리밍: 콘텐츠 덩어리를 준비되는 대로 보냅니다.

    interface TextMessageContentEvent {
      type: EventType.TEXT_MESSAGE_CONTENT
      messageId: string
      delta: string // Text chunk to append
    }
    
  3. 메시지 종료: 메시지가 완료됐음을 알립니다.

    interface TextMessageEndEvent {
      type: EventType.TEXT_MESSAGE_END
      messageId: string
    }
    

이 스트리밍 방식은 즉각적인 피드백으로 반응성 좋은 사용자 경험을 제공해요.

메시지에서의 도구 통합

AG-UI 메시지는 도구 사용을 우아하게 통합해요. 에이전트가 작업을 수행하고 그 결과를 처리할 수 있게 하죠.

도구 호출 (Tool Calls)

도구 호출은 어시스턴트 메시지 안에 포함됩니다.

interface ToolCall {
  id: string // Unique ID for this tool call
  type: "function" // Type of tool call
  function: {
    name: string // Name of the function to call
    arguments: string // JSON-encoded string of arguments
  }
}

도구 호출이 포함된 어시스턴트 메시지 예시예요.

{
  id: "msg_123",
  role: "assistant",
  content: "I'll help you with that calculation.",
  toolCalls: [
    {
      id: "call_456",
      type: "function",
      function: {
        name: "calculate",
        arguments: '{"expression": "24 * 7"}'
      }
    }
  ]
}

도구 결과 (Tool Results)

도구 실행의 결과는 도구 메시지로 표현됩니다.

{
  id: "result_789",
  role: "tool",
  content: "168",
  toolCallId: "call_456" // References the original tool call
}

이것은 도구 사용의 명확한 체인을 만들어요.

  1. 어시스턴트가 도구 호출을 요청합니다.
  2. 도구가 실행되고 결과를 반환합니다.
  3. 어시스턴트가 결과를 참조하고 응답할 수 있습니다.

도구 호출 스트리밍

텍스트 메시지와 비슷하게, 도구 호출도 스트리밍해서 에이전트의 행동을 실시간으로 보여줄 수 있어요.

  1. 도구 호출 시작:

    interface ToolCallStartEvent {
      type: EventType.TOOL_CALL_START
      toolCallId: string
      toolCallName: string
      parentMessageId?: string // Optional link to parent message
    }
    
  2. 인자 스트리밍:

    interface ToolCallArgsEvent {
      type: EventType.TOOL_CALL_ARGS
      toolCallId: string
      delta: string // JSON fragment to append to arguments
    }
    
  3. 도구 호출 종료:

    interface ToolCallEndEvent {
      type: EventType.TOOL_CALL_END
      toolCallId: string
    }
    

이 덕분에 프런트엔드는 에이전트가 추론을 구성해 가면서 도구가 점차 호출되는 모습을 보여줄 수 있어요.

실전 예제

여기 도구 사용이 포함된 대화의 완전한 예시가 있어요.

// Conversation history
;[
  // User query
  {
    id: "msg_1",
    role: "user",
    content: "What's the weather in New York?",
  },

  // Assistant response with tool call
  {
    id: "msg_2",
    role: "assistant",
    content: "Let me check the weather for you.",
    toolCalls: [
      {
        id: "call_1",
        type: "function",
        function: {
          name: "get_weather",
          arguments: '{"location": "New York", "unit": "celsius"}',
        },
      },
    ],
  },

  // Tool result
  {
    id: "result_1",
    role: "tool",
    content:
      '{"temperature": 22, "condition": "Partly Cloudy", "humidity": 65}',
    toolCallId: "call_1",
  },

  // Assistant's final response using tool results
  {
    id: "msg_3",
    role: "assistant",
    content:
      "The weather in New York is partly cloudy with a temperature of 22°C and 65% humidity.",
  },
]

결론

AG-UI의 메시지 구조는 벤더 중립성을 유지하면서 정교한 대화형 AI 경험을 가능하게 해요. 메시지가 표현·동기화·스트리밍되는 방식을 표준화함으로써, AG-UI는 사용 중인 AI 서비스와 무관하게 인간-에이전트 상호작용을 구현하는 일관된 방법을 제공합니다.

이 시스템은 단순한 텍스트 교환부터 복잡한 도구 기반 워크플로까지 모두 지원하며, 실시간 반응성과 효율적인 데이터 전송을 모두 최적화해요.

더 알아보기 (Learn more)

  • 이벤트 (Events) — 메시지가 이벤트로 어떻게 흘러가는지, 그리고 추론 이벤트의 라이프사이클을 확인해 보세요.
  • 에이전트 (Agents) — 메시지를 만들고 처리하는 에이전트를 알아보세요.
  • 메타데이터 (Metadata) — 메시지 메타데이터가 쌓이는 규칙을 살펴보세요.