생산을 위한 효율적인 컨텍스트 인식 멀티에이전트 프레임워크 구축
Source: Google Developers Blog
개요
AI 에이전트 개발 환경이 빠르게 변하고 있습니다. 우리는 이제 단일 회차 챗봇을 프로토타이핑하는 단계에서 벗어났습니다. 오늘날 조직들은 장기 작업을 수행할 수 있는 정교하고 자율적인 에이전트를 배포하고 있습니다: 워크플로 자동화, 심층 연구 수행, 복잡한 코드베이스 유지 관리 등.
그러한 야망은 즉시 컨텍스트라는 병목에 부딪힙니다.
에이전트가 오래 실행될수록 추적해야 할 정보—채팅 기록, 도구 출력, 외부 문서, 중간 추론—의 양이 폭발적으로 증가합니다. 현재 주류인 “해결책”은 기반 모델의 컨텍스트 윈도우를 점점 더 크게 하는 것이었습니다. 하지만 단순히 에이전트에게 텍스트를 붙여넣을 공간을 더 주는 것만으로는 확장 전략이 될 수 없습니다.
신뢰할 수 있고, 효율적이며, 디버깅이 가능한 프로덕션 급 에이전트를 구축하기 위해 업계는 새로운 분야를 탐구하고 있습니다:
컨텍스트 엔지니어링
컨텍스트를 자체 아키텍처, 라이프사이클, 제약 조건을 가진 1급 시스템으로 다루는 접근 방식.
복잡한 단일‑ 또는 다중‑에이전트 시스템을 확장한 경험을 바탕으로, 우리는 Google Agent Development Kit (ADK) 에서 컨텍스트 스택을 설계·진화시켜 이 분야를 지원하고 있습니다. ADK는 실제 시스템에서 능동적인 컨텍스트 엔지니어링을 구현할 수 있도록 만든 오픈‑소스, 다중‑에이전트‑네이티브 프레임워크입니다.
확장성 병목 현상
큰 컨텍스트 윈도우는 컨텍스트와 관련된 문제들을 어느 정도 해결해 주지만 모든 문제를 해결해 주지는 못합니다. 실제로는 모든 것을 하나의 거대한 프롬프트에 추가하는 순진한 패턴이 다음과 같은 세 가지 압력에 의해 무너집니다:
- 비용 및 지연 시간 급증: 모델 비용과 첫 토큰까지의 시간이 컨텍스트 크기에 따라 급격히 증가합니다. 원시 히스토리와 상세한 툴 페이로드를 윈도우에 “쌓아 넣는” 방식은 에이전트를 지나치게 느리고 비싸게 만듭니다.
- 신호 약화(“중간에 묻힘”): 관련 없는 로그, 오래된 툴 출력, 혹은 폐기된 상태가 넘쳐나는 컨텍스트 윈도우는 모델을 산만하게 만들어 과거 패턴에 집착하게 하고, 즉각적인 지시를 놓치게 합니다. 견고한 의사결정을 위해서는 관련 정보의 밀도를 최대화해야 합니다.
- 물리적 한계: 전체 RAG 결과, 중간 산출물, 긴 대화 기록 등을 포함하는 실제 워크로드는 결국 가장 큰 고정 윈도우조차도 초과하게 됩니다.
더 많은 토큰을 투입하면 일시적으로 시간을 벌 수는 있지만, 곡선의 형태 자체를 바꾸지는 못합니다. 규모를 확장하려면 컨텍스트를 표현하고 관리하는 방식을 바꿔야 하며, 단순히 한 번의 호출에 넣을 수 있는 양을 늘리는 것만으로는 부족합니다.
설계 논문: 컴파일된 뷰로서의 컨텍스트
이전 세대 에이전트 프레임워크에서는 컨텍스트를 가변 문자열 버퍼처럼 취급했습니다. ADK는 다른 논문을 중심으로 구축됩니다:
컨텍스트는 더 풍부한 상태 시스템에 대한 컴파일된 뷰이다.
그 뷰에서:
- 세션, 메모리, 그리고 아티팩트(파일) 은 소스 — 상호작용과 그 데이터의 전체 구조화된 상태입니다.
- 플로우와 프로세서 는 컴파일러 파이프라인 — 그 상태를 변환하는 일련의 패스입니다.
- 작업 컨텍스트 는 컴파일된 뷰 로, 단일 호출을 위해 LLM에 전달됩니다.
이 사고 모델을 채택하면, 컨텍스트 엔지니어링은 프롬프트 체조가 아니라 시스템 엔지니어링처럼 보이게 됩니다. 표준 시스템 질문을 해야만 합니다: 중간 표현은 무엇인가? 어디에서 압축을 적용하는가? 변환을 어떻게 관찰 가능하게 할 것인가?
ADK의 아키텍처는 세 가지 설계 원칙을 통해 이러한 질문에 답합니다:
- 스토리지와 프레젠테이션을 분리 – 영구적인 상태(세션)와 호출당 뷰(작업 컨텍스트)를 구분합니다. 이를 통해 스토리지 스키마와 프롬프트 형식을 독립적으로 진화시킬 수 있습니다.
- 명시적 변환 – 컨텍스트는 임시 문자열 연결이 아니라 명명되고 순서가 지정된 프로세서를 통해 구축됩니다. 이는 “컴파일” 단계가 관찰 가능하고 테스트 가능하도록 합니다.
- 기본적으로 범위 제한 – 모든 모델 호출 및 서브 에이전트는 최소한의 필요한 컨텍스트만을 봅니다. 에이전트는 도구를 통해 명시적으로 더 많은 정보를 요청해야 하며, 기본적으로 넘쳐나는 정보를 받지 않습니다.
ADK의 계층 구조, 관련성 메커니즘, 그리고 다중 에이전트 핸드오프 의미론은 본질적으로 이 “컴파일러” 논문과 세 가지 원칙을 적용한 것입니다:
- 구조 – 정보가 어떻게 저장되는지와 모델이 무엇을 보는지를 분리하는 계층형 모델.
- 관련성 – 현재 중요한 것을 결정하는 에이전시 및 인간 제어.
- 다중 에이전트 컨텍스트 – 에이전트 간에 올바른 컨텍스트 조각을 전달하기 위한 명시적 의미론.
다음 섹션에서는 각각의 기둥을 차례로 살펴봅니다.
구조: 계층형 모델
대부분의 초기 에이전트 시스템은 단일 컨텍스트 창을 암묵적으로 가정합니다. ADK는 반대 방향으로 나아갑니다. 스토리지를 프레젠테이션과 분리하고, 컨텍스트를 특정 작업을 가진 별도 레이어로 조직합니다:
| 레이어 | 설명 |
|---|---|
| 작업 컨텍스트 | 이 모델 호출을 위한 즉각적인 프롬프트: 시스템 지시, 에이전트 정체성, 선택된 히스토리, 도구 출력, 선택적 메모리 결과, 그리고 아티팩트에 대한 참조. |
| 세션 | 상호작용의 영구 로그: 모든 사용자 메시지, 에이전트 응답, 도구 호출, 도구 결과, 제어 신호, 오류를 구조화된 Event 객체로 캡처. |
| 메모리 | 단일 세션을 초월하는 검색 가능한 지식: 사용자 선호, 과거 대화 등. |
| 아티팩트 | 세션 또는 사용자와 연관된 대용량 바이너리 또는 텍스트 데이터(파일, 로그, 이미지)로, 프롬프트에 붙여넣는 대신 이름과 버전으로 주소 지정. |
1.1 작업 컨텍스트를 재계산된 뷰로
각 호출마다 ADK는 기본 상태에서 작업 컨텍스트를 재구성합니다. 지시와 정체성으로 시작해 선택된 세션 이벤트를 끌어오고, 필요에 따라 메모리 결과를 첨부합니다. 이 뷰는:
- 일시적 – 호출이 끝나면 폐기됩니다.
- 구성 가능 – 스토리지를 마이그레이션하지 않고도 형식을 변경할 수 있습니다.
- 모델 비종속 – 어떤 LLM 백엔드와도 작동합니다.
이 유연성은 컴파일러 뷰의 첫 번째 장점입니다: “프롬프트”를 하드코딩하는 대신 파생된 표현으로 다루게 됩니다.
1.2 플로우와 프로세서: 파이프라인으로서의 컨텍스트 처리
스토리지와 프레젠테이션을 분리하면, 하나를 다른 것으로 컴파일할 메커니즘이 필요합니다. ADK에서는 모든 LLM 기반 에이전트가 LLM 플로우에 의해 지원되며, 여기에는 순서가 지정된 프로세서 목록이 유지됩니다.
간소화된 SingleFlow 예시는 다음과 같습니다:
# Example of a simplified SingleFlow definition
flow = Sing
Source: …
leFlow(
processors=[
"static_instruction", # immutable system prompt
"contents", # builds the working context from the Session
"retrieval", # optional external knowledge fetch
"tool_calls", # invoke tools if needed
"response", # generate the final LLM reply
]
)
이러한 흐름은 ADK가 컨텍스트를 컴파일하는 메커니즘입니다. 순서가 중요합니다: 각 프로세서는 이전 단계의 출력 위에 구축됩니다. 이를 통해 사용자 정의 필터링, 압축 전략, 캐싱 및 다중 에이전트 라우팅을 위한 자연스러운 삽입 지점을 제공받습니다. 이제 거대한 프롬프트 템플릿을 다시 작성할 필요가 없으며, 프로세서를 추가하거나 순서를 바꾸기만 하면 됩니다.
1.3 세션 및 이벤트: 구조화된, 언어에 구애받지 않는 히스토리
ADK 세션은 대화 또는 워크플로 인스턴스의 최종 상태를 나타냅니다. 구체적으로, 다음을 담는 컨테이너 역할을 합니다:
- 세션 메타데이터 (ID, 앱 이름)
- 구조화된 변수들을 위한 상태 스크래치패드
- 이벤트 – 강력히 타입이 지정된 레코드들의 연대순 목록
원시 프롬프트 문자열을 저장하는 대신, ADK는 사용자 메시지, 에이전트 응답, 도구 호출, 결과, 제어 신호 및 오류 등 모든 상호작용을 이벤트 레코드로 캡처합니다. 이 구조적 선택은 세 가지 뚜렷한 장점을 제공합니다:
| 장점 | 왜 중요한가 |
|---|---|
| 모델 독립성 | 히스토리를 다시 작성하지 않고도 기반 모델을 교체할 수 있으며, 저장 형식이 프롬프트 형식과 분리됩니다. |
| 풍부한 연산 | 하위 컴포넌트(압축, 시간 여행 디버깅, 메모리 인제션)들은 불투명한 텍스트를 파싱하는 대신 풍부한 이벤트 스트림을 활용할 수 있습니다. |
| 관측 가능성 | 분석을 위한 자연스러운 인터페이스를 제공하여 정확한 상태 전이와 행동을 검사할 수 있게 합니다. |
이 세션과 작업 컨텍스트를 연결하는 다리는 contents 프로세서입니다. 세션을 작업 컨텍스트의 히스토리 부분으로 변환하는 무거운 작업을 수행하며, 세 가지 핵심 단계를 실행합니다:
- 선택 – 모델에 전달되지 않아야 할 관련 없는 이벤트, 부분 이벤트 및 프레임워크 잡음을 필터링합니다.
- 변환 – 남은 이벤트들을 올바른 역할(
user/`
Source: …
rules before it ever reaches the model.
1.5 Context caching
Modern models support context caching (prefix caching), which allows the inference engine to reuse attention computation across calls. ADK’s separation of Session (storage) and Working Context (view) provides a natural substrate for this optimization.
The architecture effectively divides the context window into two zones:
| Zone | Typical contents |
|---|---|
| Stable prefixes | System instructions, agent identity, long‑lived summaries |
| Variable suffixes | Latest user turn, new tool outputs, small incremental updates |
Because ADK flows and processors are explicit, you can treat cache‑friendliness as a hard design constraint. By ordering your pipeline to keep frequently reused segments stable at the front of the context window—and pushing highly dynamic content toward the end—you maximize cache hit rates.
To enforce this rigor, ADK introduces static_instruction, a primitive that guarantees immutability for system prompts, ensuring that the cache prefix remains valid across invocations.
Relevance: Agentic management of what matters now
구조가 확립되면 핵심 과제는 관련성으로 이동합니다: 계층화된 컨텍스트 아키텍처가 주어졌을 때, 현재 모델의 활성 윈도우에 어떤 구체적인 정보가 들어가야 할까?
ADK는 인간 도메인 지식과 에이전시 의사결정의 협업을 통해 이를 해결합니다.
- 하드코딩된 규칙은 비용 효율적이지만 경직됩니다.
- 순수 에이전트 기반 탐색은 유연하지만 비용이 과도하고 불안정합니다.
최적의 Working Context는 두 요소 사이의 협상입니다. 인간 엔지니어가 아키텍처—데이터가 어디에 저장되는지, 어떻게 요약되는지, 어떤 필터가 적용되는지—를 정의합니다. 그 다음 에이전트가 지능을 제공하여 동적으로 언제 …를 결정합니다.
2.1 Artifacts: externalizing large state
초기 에이전트 구현은 종종 컨텍스트 덤핑 함정에 빠집니다: 5 MB CSV, 방대한 JSON API 응답, 혹은 전체 PDF 전사본과 같은 큰 페이로드를 채팅 기록에 직접 넣는 경우입니다. 이는 세션에 영구적인 세금을 부과하고, 이후 모든 턴이 그 페이로드를 끌고 다니게 하여 중요한 지시를 가리고 비용을 부풀립니다.
ADK는 큰 데이터를 Artifacts로 취급함으로써 이를 해결합니다: ArtifactService가 관리하는 이름이 지정되고 버전이 있는 바이너리 또는 텍스트 객체입니다.
개념적으로 ADK는 큰 데이터에 핸들 패턴을 적용합니다. 큰 데이터는 프롬프트가 아니라 아티팩트 스토어에 존재합니다. 기본적으로 에이전트는 요청 프로세서를 통해 가벼운 참조(이름과 요약)만을 봅니다. 에이전트가 질문에 답하기 위해 원시 데이터가 필요할 때만 LoadArtifactsTool을 사용합니다. 이 동작은 콘텐츠를 Working Context에 일시적으로 로드합니다.
핵심은 ADK가 일시적 확장을 지원한다는 점입니다. 모델 호출이나 작업이 완료되면 기본적으로 아티팩트는 작업 컨텍스트에서 오프로드됩니다. 이렇게 하면 “매 프롬프트마다 5 MB의 잡음”이 정확한 필요 시점에만 제공되는 자원으로 전환됩니다. 데이터는 방대할 수 있지만 컨텍스트 윈도우는 여전히 가볍게 유지됩니다.
2.2 Memory: long‑term knowledge, retrieved on demand
Artifacts가 개별적인 대용량 객체를 다루는 반면, ADK의 Memory 레이어는 단일 세션을 넘어서는 장기적인 의미론적 지식을 관리합니다—사용자 선호도, 과거 결정, 도메인 사실 등.
MemoryService는 두 가지 원칙을 중심으로 구축됩니다:
- 검색 가능성 – 메모리는 검색 가능해야 하며(영구적으로 고정되지 않아야 함).
- 에이전트 주도 검색 – 에이전트가 언제 가져올지를 결정합니다.
서비스는 데이터(주로 종료된 Sessions에서)를 벡터 또는 키워드 코퍼스로 ingest합니다. 에이전트는 두 가지 패턴을 통해 이 지식에 접근합니다:
- Reactive recall – 에이전트가 지식 격차를 인식하고(예: “사용자의 식이 제한은 무엇인가?”) 명시적으로
load_memory_tool을 호출해 코퍼스를 검색합니다. - Proactive recall – 사전 처리기가 최신 사용자 입력을 기반으로 유사도 검색을 수행하고, 모델이 호출되기 전
preload_memory_tool을 통해 관련 스니펫을 주입합니다.
이는 “컨텍스트 채우기” 안티패턴을 “메모리 기반” 워크플로로 대체합니다. 에이전트는 현재 단계에 필요한 스니펫만 정확히 기억해 가져오며, 과거에 나눈 모든 대화의 무게를 짊어지지 않습니다.
Multi‑agent context: who sees what, when
단일 에이전트 시스템은 컨텍스트 과부하에 어려움을 겪으며, 멀티‑에이전트 시스템은 이를 더욱 증폭시킵니다. 루트 에이전트가 전체 히스토리를 서브‑에이전트에 전달하고, 그 서브‑에이전트가 다시 같은 작업을 하면 컨텍스트 폭발이 발생합니다. 토큰 수가 급증하고, 서브‑에이전트는 관련 없는 대화 히스토리 때문에 혼란스러워집니다.
에이전트가 다른 에이전트를 호출할 때마다 ADK는 호출받는 쪽이 볼 수 있는 범위를 명시적으로 지정할 수 있게 합니다—예를 들어 최신 사용자 질의와 하나의 아티팩트만 보여주고, 대부분의 조상 히스토리는 억제합니다.
3.1 두 가지 멀티‑에이전트 상호작용 패턴
- 도구로서의 에이전트 – 루트 에이전트는 특화된 에이전트를 순수 함수처럼 취급합니다: 집중된 프롬프트로 호출하고, 결과를 받아서 다음 단계로 진행합니다. 호출받는 쪽은 특정 지시와 필요한 아티팩트만 보며, 히스토리는 전혀 보지 않습니다.
- 에이전트 전환 (계층 구조) – 제어권을 완전히 서브‑에이전트에게 넘겨 대화를 이어갑니다. 서브‑에이전트는 Session에 대한 뷰를 상속받아 워크플로를 주도하고, 자체 도구를 호출하거나 제어권을 다시 아래로 전환할 수 있습니다.
3.2 에이전트 전환을 위한 범위 지정 핸드오프
핸드오프 동작은 include_contents와 같은 호출받는 쪽의 옵션으로 제어되며, 루트 에이전트에서 서브‑에이전트로 얼마나 많은 컨텍스트가 흐를지를 결정합니다.
- 기본 모드 – ADK는 호출자의 작업 컨텍스트 전체를 전달합니다(서브‑에이전트가 전체 히스토리에서 실제로 이득을 볼 때 유용).
- None 모드 – 서브‑에이전트는 이전 히스토리를 전혀 보지 못하고, 새로 구성한 프롬프트만 받습니다(예: 최신 사용자 턴과 몇 개의 도구 호출 및 응답).
특화된 에이전트는 기본적으로 거대한 전사본을 상속받는 대신, 최소한의 필요한 컨텍스트만 받게 됩니다.
서브‑에이전트의 컨텍스트도 프로세서를 통해 구축되므로, 이러한 핸드오프 규칙은 다른 컨텍스트 구축 단계와 동일한 흐름 파이프라인에 끼워 넣어 작동합니다.