컨텍스트 압축
컨텍스트 압축 (Context Compaction)
컨텍스트 압축(context compaction)은 Agent의 대화를 줄여, 긴 실행이 모델의 컨텍스트 윈도우를 소진하지 않도록 해요. 이전 기록을 더 작은 표현으로 다시 쓰면서, Agent가 계속 작업하는 데 필요한 컨텍스트는 보존합니다.
출처: 문서
본문
압축은 손실이 있는(lossy) 작업입니다. 압축이 실행된 후 Agent는 대화의 더 짧은 기록으로 작업하게 됩니다. 무엇이 남고 무엇이 버려지는지는 압축 전략에 따라 달라져요.
How context compaction works
컨텍스트 압축은 세 가지 책임을 분리합니다:
| Responsibility | Abstraction | Purpose |
|---|---|---|
| 언제 압축할지 결정 | CompactionHook |
LLM 호출 전에 Agent의 컨텍스트를 모니터링하고, 설정한 임계값에 도달하면 compactor를 호출해요. |
| 어떻게 압축할지 결정 | Compactor 프로토콜 |
대화가 어떻게 다시 쓰이는지 정의해요. Haystack에는 SlidingWindowCompactor, SummarizationCompactor, ToolResultPruningCompactor가 포함됩니다. |
| 대화 측정 | TokenCounter 프로토콜 |
모델로 보내기 전에 메시지와 도구 스키마의 크기를 추정해요. |
이 분리를 통해 표준 트리거에 서로 다른 압축 전략과 토큰 카운터를 결합할 수 있어요. 예를 들어, 로컬 슬라이딩 윈도우는 추가 모델 호출 없이 오래된 기록을 제거할 수 있고, 요약 compactor는 같은 기록을 LLM으로 요약할 수 있습니다.
Basic setup
다음 예시는 Agent의 before_llm 후크 지점에 CompactionHook을 등록해요. 모델 컨텍스트 윈도우의 70%에서 압축을 시작하고, compactor가 컨텍스트를 약 40%로 줄이도록 합니다.
from typing import Annotated
from haystack.components.agents import Agent
from haystack.components.generators.chat import OpenAIResponsesChatGenerator
from haystack.dataclasses import ChatMessage
from haystack.hooks.compaction import CompactionHook, SlidingWindowCompactor
from haystack.tools import tool
@tool
def fetch_page(url: Annotated[str, "The URL to fetch"]) -> str:
"""Fetch a web page and return its text."""
return "Fusion startups reported net-energy-gain milestones this year. " * 500
compaction_hook = CompactionHook(
compactor=SlidingWindowCompactor(),
context_window=400_000, # gpt-5.4-nano's context window
compact_at=0.7,
compact_to=0.4,
)
agent = Agent(
chat_generator=OpenAIResponsesChatGenerator(model="gpt-5.4-nano"),
tools=[fetch_page],
system_prompt="You are a research assistant. Fetch pages as needed and cite what you used.",
hooks={"before_llm": [compaction_hook]},
)
result = agent.run(
messages=[ChatMessage.from_user("Summarize recent fusion energy milestones.")],
)
print(result["last_message"].text)
임계값 구성, 컨텍스트 측정, 수명주기, 직렬화에 대해서는 CompactionHook 문서를 참고하세요.
Compaction strategies
Compactor는 현재 메시지, 목표 토큰 수, 그리고 컨텍스트 측정에 쓰인 것과 같은 토큰 카운터를 받아요. 더 짧은 교체 대화를 반환하거나, 바꿀 유용한 것이 없으면 None을 반환합니다.
| Compactor | Strategy | Trade-off |
|---|---|---|
SlidingWindowCompactor |
Agent의 지시와 최신 사용자 작업을 보존하고, 맞는 동안 완전한 이력 턴을 유지하며, 그것으로 충분하지 않을 때만 현재 작업의 Agent 단계를 자름 | 빠르고 로컬에서 작동하지만, 버려진 정보는 요약되지 않음 |
SummarizationCompactor |
이력 턴과 현재 작업 단계를 LLM 생성 요약으로 점진적으로 교체하고, 필요할 때만 더 오래된 요약을 결합 | 이전 작업의 압축된 기록을 보존하지만 모델 비용·지연이 추가되고 여전히 손실이 있음 |
ToolResultPruningCompactor |
오래된 대형 도구 결과를 짧은 자리표시자로 교체하면서 도구 호출/결과 구조는 보존 | 실행의 형태와 최근 결과는 유지하지만, 정리된 결과의 내용은 제거됨 |
Combining compaction strategies
before_llm에 여러 CompactionHook 인스턴스를 등록하면 점점 더 공격적인 전략을 적용할 수 있어요. 후크는 리스트 순서대로 같은 Agent 상태에 대해 실행되므로, 각 후크는 이전 후크가 남긴 메시지를 측정합니다.
예를 들어, 먼저 큰 도구 결과를 정리하고 슬라이딩 윈도우를 백업으로 사용하세요:
from haystack.hooks.compaction import (
CompactionHook,
SlidingWindowCompactor,
ToolResultPruningCompactor,
)
prune_tool_results = CompactionHook(
compactor=ToolResultPruningCompactor(),
context_window=400_000,
compact_at=0.7,
compact_to=0.4,
)
drop_old_steps = CompactionHook(
compactor=SlidingWindowCompactor(),
context_window=400_000,
compact_at=0.7,
compact_to=0.4,
)
agent = Agent(
chat_generator=OpenAIResponsesChatGenerator(model="gpt-5.4-nano"),
tools=[fetch_page],
hooks={"before_llm": [prune_tool_results, drop_old_steps]},
)
정리(pruning)가 업데이트된 컨텍스트를 compact_at 아래로 내리면 슬라이딩 윈도우 후크는 아무것도 하지 않아요. 정리가 적격 결과가 남지 않아 None을 반환하거나, 컨텍스트를 트리거 아래로 내리지 않고 줄인다면, 슬라이딩 윈도우는 이력 턴을 제거한 뒤 필요하면 현재 작업의 가장 오래된 Agent 단계를 제거합니다. 정리 compactor가 이미 자리표시자로 교체한 결과는 그것이 속한 이력 턴과 함께 남아, 그 도구 호출은 계속 답을 갖게 됩니다. 두 후크를 같은 모델 컨텍스트 윈도우와 호환되는 토큰 카운터로 구성해 비슷한 추정치에서 결정을 내리게 하세요.
Creating a custom compactor
애플리케이션 특정 메시지를 보존하거나 커스텀 메타데이터로 내용을 선택적으로 줄이는 등 다른 전략이 필요하면 Compactor 프로토콜을 구현하세요.
from typing import Any
from haystack.core.serialization import default_to_dict
from haystack.dataclasses import ChatMessage
from haystack.hooks.compaction import Compactor
from haystack.token_counters import TokenCounter
class CustomCompactor(Compactor):
def compact(
self,
messages: list[ChatMessage],
target_tokens: int,
token_counter: TokenCounter,
) -> list[ChatMessage] | None:
# Return a shorter, valid conversation or None when nothing should change.
...
def to_dict(self) -> dict[str, Any]:
return default_to_dict(self)
Compactor는 다음 규칙을 따라야 합니다:
- 대화가 실제로 작아지지 않으면
None을 반환하세요. - 입력
messages리스트를 수정하지 않고 새 리스트를 반환하세요. - 도구 호출을 모든 결과 메시지와 함께 유지하세요. Chat-completion API는 불완전한 도구 호출 교환을 거부합니다.
target_tokens 값은 보장이 아니라 목표예요. 목표가 Agent가 반드시 유지해야 하는 컨텍스트와 충돌하면, 필요한 컨텍스트를 보존하고 목표에 최대한 가깝게 가세요.
compact_async()는 기본적으로 compact()를 호출합니다. LLM 호출처럼 압축이 I/O를 수행할 때는 이를 오버라이드해 비동기 Agent 실행이 막히지 않게 하세요. 생성자 설정을 직렬화하려면 to_dict()를 사용하세요. 프로토콜의 기본 from_dict()는 일반 생성자 값을 처리하며, Secret이나 중첩 컴포넌트처럼 직렬화 값이 먼저 재구성돼야 할 때는 이를 오버라이드하세요.
Token counters
기본 ApproximateTokenCounter는 텍스트 길이로 토큰을 추정하며 추가 의존성이 필요 없어요. 모델 또는 제공 업체 특정 추정이 필요하면 다른 내장 또는 커스텀 TokenCounter를 구성할 수 있습니다.
토큰 카운터는 추정에 도구 스키마와 비텍스트 내용도 포함할 수 있어요. 사용 중인 카운터의 페이지를 참고해 이미지와 파일을 어떻게 처리하는지 이해하세요.
Context compaction and tool result offloading
도구 결과 오프로딩(tool result offloading)은 인접한 문제를 해결해요. 큰 도구 결과를 저장소에 쓰고 대화에는 포인터를 남깁니다. 두 접근 방식은 함께 잘 작동합니다. 오프로딩은 도착하는 개별 결과를 작게 유지하고, 압축은 대화 전체의 크기를 제한하니까요.
오프로딩된 결과는 저장된 내용에 대한 참조로 표현됩니다. compactor가 그 메시지를 제거하거나 다시 쓰면, 모델은 내용을 다시 읽을 때 필요한 참조를 잃어버려요.
ToolResultPruningCompactor는 기본적으로 오프로딩으로 표시된 결과를 건너뛰어, 저장된 내용 참조를 보존합니다.
더 알아보기 (Learn more)
- Context Compaction — Haystack 공식 문서