컴팩션

컴팩션 (Compaction)

컴팩션은 에이전트의 대화 히스토리를 모델의 컨텍스트 창 안에 유지하기 위한 전략 메뉴예요. 이 Pydantic AI Capability 클래스들은 각 요청이 나가기 직전에 메시지 히스토리를 편집해요. FallbackCompaction은 선택적으로 토큰 임계값에서 그 체인을 발동하고 조합 가능한 CompactionStrategy로도 작동해요. 편집은 실행의 메시지 히스토리에 지속되어, 트림·클리어·요약이 이후 스텝으로 이어져요. 매 턴 전체 히스토리에서 다시 계산되지 않아요.

출처: 문서

모든 전략은 도구 호출/도구 반환 페어링을 보존해요. Core는 이를 검증하지 않고, 프로바이더가 고아 페어를 거부하므로, 페어링 보장이 이것들을 에이전트에 안전하게 떨어뜨릴 수 있게 해요. 제로-LLM 전략은 결코 모델을 부르지 않아요. SummarizingCompaction만(그리고 그만큼 에스컬레이션하는 TieredCompaction) 토큰을 써요.

OpenAI와 Anthropic에서 core는 프로바이더 네이티브 컴팩션도 제공해요. 프로바이더가 서버 측에서 히스토리를 요약해요. 이 페이지의 전략은 모델 무관 대안이에요. 모든 모델에서 작동하고 컴팩션 로직(과 비용)을 당신의 통제 아래 둬요.

Source

Pydantic AI Harness가 0.x 릴리스인 동안 API는 마이너 릴리스 사이에 바뀔 수 있어요. 그럴 때는 deprecation 경고와 릴리스 노트의 마이그레이션 안내가 정확히 어떻게 업그레이드할지 알려줘요. 버전 정책을 참고하세요.

본문

문제 (The problem)

많은 턴을 실행하는 에이전트는 히스토리를 축적해요. 도구 출력, 파일 읽기, 모델 추론, 반복 콘텐츠. 방치하면 그 히스토리가 모델의 컨텍스트 창을 넘어 다음 요청이 실패해요. 컴팩션은 히스토리를 경계짓고, 올바른 전략은 블로트가 어디 사는지와 회수하는 데 얼마를 쓸 수 있는지에 따라 달라져요.

메뉴 (The menu)

구성요소 비용 무엇을 하나 언제 손대나
ClampOversizedMessages 제로-LLM 단일 초대형 부분(응답 텍스트, 도구 호출 인수)을 head/tail 잘라냄 하나의 폭주 생성이 컨텍스트 상한을 넘었고 다른 전략으로 닿을 수 없음
SlidingWindowCompaction 제로-LLM 가장 오래된 전체 메시지를 꼬리까지 버림 최근 턴만 필요하고 옛 컨텍스트를 완전히 버릴 수 있음
ClearToolResults 제로-LLM 마지막 keep_pairs를 유지하며 옛 도구 결과 콘텐츠를 제자리에서 비움 도구 출력이 컨텍스트를 지배하고 필요 시 다시 가져올 수 있음(싼 첫째 계층)
DeduplicateFileReads 제로-LLM 같은 파일의 새 읽기로 대체된 모든 파일 읽기를 비움 에이전트가 파일을 다시 읽고 최신 버전만 중요함
SummarizingCompaction LLM 호출 하나 옛 메시지를 구조화 요약으로 요약하고 최근 꼬리 유지 옛 컨텍스트가 여전히 중요하지만 압축해야 함; 싼 계층 뒤에 사용
TieredCompaction 에스컬레이션 싼 패스를 먼저 실행하고, 여전히 target_tokens 위면 요약만 합리적 기본값 원함: 필요한 때에만 값비싼 요약
FallbackCompaction 체인에 따라 하나가 오류를 일으키면 다음 전략 시도 요약이 실패할 수 있고 결정적 트렁케이션이 실행을 살려야 함
WarnNearLimits 제로-LLM 한계에 접근하면 URGENT/CRITICAL 경고 주입 에이전트가 히스토리를 다시 쓰기보다 마무리하길 원함
ReportContextUsage 제로-LLM 애플리케이션에 컨텍스트 사용 보고; 히스토리 절대 편집 안 함 UI에 라이브 컨텍스트 게이지 원함

트리거 (Triggers)

지시문 교체·철회 레코드는 전체 렌더링된 시스템 텍스트를 토큰 추정에 기여해요. 새 지시문 기준선 이전의 대체 업데이트는 제외돼요.

모든 크기 기반 전략은 max_messages, max_tokens(추정), max_fraction에서 발동해요. 토큰 수는 쓸 수 있을 때 가장 최근 모델 응답의 프로바이더 보고 사용에 고정돼요. 그 프로바이더 사용은 기준 고정 요청에서 보낸 지시문·도구 정의·FilePart 페이로드를 포함해요. 이후 추가된 메시지만 추정돼요. 기준 뒤의 접미사, 또는 사용 고정이 없는 히스토리는 tokenizer나 ~문자 4개당 토큰 1개 휴리스틱을 쓰고 FilePart 페이로드를 볼 수 없어요. 요청을 위해 새로 공개된 보류 중 도구 스키마는 구현이 보수적으로 추정해요. DeduplicateFileReads는 트리거가 설정되지 않으면 매 요청 실행돼요(싸고 거의 손실 없으니까). TieredCompaction은 단일 target_tokens/target_fraction 예산에서 발동하고 멈춰요. ClampOversizedMessages는 전체 히스토리가 아니라 부분 마다 발동해요(max_part_tokens/max_part_chars). 그것이 노리는 실패는 하나의 초대형 부분이지 큰 총계가 아니에요.

max_fraction: 모든 모델을 위한 하나의 설정

절대 max_tokens는 측정된 모델에만 맞아요. 180_000을 구성하면 1M 컨텍스트 모델이 용량 1/5에서 컴팩션해 필요 없는 요약을 내요. 1_000_000으로 구성된 128K 모델은 프로바이더가 요청을 거부하기 전에 결코 컴팩션하지 않아요.

max_fraction은 요청마다 모델의 실제 컨텍스트 창에 대해 해석돼서, 하나의 구성이 어디서든 맞아요.

from pydantic_ai import Agent
from pydantic_ai_harness import SummarizingCompaction

agent = Agent(
    'anthropic:claude-sonnet-5',
    capabilities=[SummarizingCompaction(max_fraction=0.9, keep_messages=20)],
)

그것은 1M 모델에서 900K, 128K 모델에서 115K에 컴팩션해요. WarnNearLimitsmax_context_fraction과 같은 모양을, TieredCompactiontarget_fraction을 취해요. max_tokensmax_fraction은 상호 배타적이에요. 둘 다 받는 전략은 하나를 골라 다른 것을 버려야 하고, 호출자가 어떤 예산이 시행 중인지 말할 수 없게 해요.

창은 pydantic-ai-slim의 이미 의존성인 genai-prices에서 와요. resolve_context_window가 숫자 자체를 원하면 내보내져요. Pydantic AI는 아직 그것을 노출하지 않아요(ModelProfilecontext_window 필드 없음). 그래서 그때가 되면 그 함수 하나가 전환돼요. 아무것도 캐시되지 않아요. 레지스트리 확인된 숫자만 실제 창으로 취급돼요.

상담되는 모델은 ModelRequestContext.model, 곧 요청이 보내질 모델이지 실행이 시작된 모델이 아니에요. 더 일찍 순서 정렬된 capability가 그것을 교체하고 예산이 따라요.

창이 해석되지 않을 때

모든 모델이 레지스트리에 있는 건 아니에요. 로컬 엔드포인트, 맞춤 배포, bedrock:us.anthropic.claude-sonnet-5 같은 Bedrock 접두사 참조, 레지스트리가 기록된 창 없이 아는 모델, 그리고 어떤 FallbackModel(model_idfallback:... 복합체)도 아무것도 해석하지 않아요. 그럴 때 분율은 보수적인 200K(DEFAULT_CONTEXT_WINDOW) 기본인 fallback_context_window에 취해져요. 필요한 것보다 일찍 컴팩션하는 것은 요약 하나를, 과대평가는 전체 요청을 희생해요.

분율을 받는 모든 capability가 폴백도 받아, 크기를 아는 모델에서 200K에 갇히지 않아요.

from pydantic_ai import Agent
from pydantic_ai_harness import SummarizingCompaction

agent = Agent(
    'bedrock:us.anthropic.claude-sonnet-5',
    capabilities=[SummarizingCompaction(max_fraction=0.9, fallback_context_window=1_000_000)],
)

해석이 실패할 때만 상담되므로 레지스트리가 아는 모델에서는 아무 비용이 없어요.

TestModel은 해석하지 않는 모델 중 하나예요. model_idtest:test라 분율이 fallback_context_window에 취해지고 max_fraction=0.9가 180,000토큰 트리거가 돼요. TestModel에 대해서만 연습한 컴팩션 구성은 결코 발동하지 않는 것처럼 보여요. 테스트에서 context_window=fallback_context_window=를 넘겨 트리거를 닿을 곳에 두세요.

창이 틀린 숫자로 해석될 때

해석이 성공해도 틀릴 수 있고, fallback_context_window가 도울 수 없어요. 해석 실패 시에만 적용되니까요. 세 경우:

  • 레지스트리 항목 자체가 틀림. Harness는 genai-prices를 읽고 검증할 수 없어요. genai-prices 0.1.3에 대해 측정하면: anthropic:claude-sonnet-4-5는 1,000,000을 기록하지만 실제 창은 200,000. anthropic:claude-opus-4-6은 200,000을 기록하지만 실제는 1,000,000. 과대 기록된 창이 실행을 깨는 방향이에요. claude-sonnet-4-5에서 max_fraction=0.9는 200,000토큰 창에 대한 900,000토큰 트리거로 해석돼, 컴팩션이 결코 발동하지 않고 프로바이더가 요청을 거부해요. claude-sonnet-4-5context_window=200_000을 명시적으로 넘기세요. claude-sonnet-5의 기록된 1,000,000은 Anthropic 모델 문서와 맞으니 덮어쓸 필요가 없어요. 쓰는 다른 Sonnet id는 해석된 숫자에 의존하기 전에 프로바이더 자체 문서와 대조하세요. 과소 기록된 창은 안전하지만 낭비예요. 필요한 것보다 일찍 컴팩션하니까요.
  • 레지스트리는 모델이 받아들일 수 있는 최대를 기록해요. 그 최대가 베타 헤더·요금 계층으로 게이팅되면 평범한 요청은 더 적게 받고, 기록된 숫자의 분율은 프로바이더가 요청을 거부하기 전에 결코 발동하지 않아요.
  • 자체 호스팅·프록시 엔드포인트가 레지스트리 항목이 다른 누군가의 배포를 설명하는 모델 id를 보고해요.

context_window는 분율을 받는 모든 capability에서 해석을 단독으로 덮어써요.

from pydantic_ai import Agent
from pydantic_ai_harness import SummarizingCompaction

agent = Agent(
    'openai:gpt-5.6-luna',  # served by a local endpoint with a smaller window than the registry records
    capabilities=[SummarizingCompaction(max_fraction=0.9, context_window=32_000)],
)

분율에 무엇이 세나

사용 고정이 있으면 프로바이더 보고 사용이 기준 고정 요청에 대해 청구된 전부를 포함해요. 지시문, 도구 정의, FilePart 페이로드 포함. 기준 후의 접미사와 고정 없는 전체 히스토리는 추정기가 tokenizer가 제공되면 그것을, 아니면 ~문자 4개당 토큰 1개 휴리스틱을 써요. 그 추정 부분은 FilePart 페이로드를 볼 수 없어요. 요청을 위해 새로 공개된 보류 중 도구 스키마는 구현이 보수적으로 추정해요. 이전 고정이 다루지 않으니까요.

이미 절대 max_tokens를 설정했다면 다시 확인하세요. 추정기가 예전엔 사용자·시스템 프롬프트, 도구 반환, 응답 텍스트, 도구 호출만 세었어요. ThinkingPart/CompactionPart 콘텐츠, RetryPromptPart 콘텐츠, NativeToolCallPart/NativeToolReturnPart, 가장 최근 ModelRequest.instructions가 이제 세어져, 같은 히스토리가 더 높게 측정되고 바뀌지 않은 max_tokens가 더 일찍 컴팩션해요. 얼마나 일찍은 히스토리가 thinking 블록, 재시도, 지시문을 얼마나 갖느냐에 달려요. thinking 중심 도구 호출 히스토리에서는 옛 수의 몇 배일 수 있어요. 각 전략이 비우는 것은 그대로예요. 바뀌는 건 언제 실행되냐뿐이에요.

사용 보고: ReportContextUsage

전략은 언제 행동할지 알지만 실행이 한계에 얼마나 가까운지 말하지 않아, context: 73%를 보여주려는 애플리케이션이 히스토리를 다시 세고 분모를 추측하게 돼요. ReportContextUsage는 둘 다 안 해요. 같은 추정기와 같은 해석된 창을 재사용하고 관찰만 해요.

from pydantic_ai import Agent
from pydantic_ai_harness import ReportContextUsage, SummarizingCompaction
from pydantic_ai_harness.compaction import ContextUsageEvent

agent = Agent(
    'anthropic:claude-sonnet-5',
    capabilities=[
        SummarizingCompaction(max_fraction=0.9, keep_messages=20),
        ReportContextUsage(),
    ],
)

@agent.on_event(ContextUsageEvent)
async def show(ctx, event):
    print(f'{event.fraction:.0%}')

각 판독은 used_tokens, window_tokens, fraction, resolved를 실어 나라요. 창이 모델의 실제 창이 아니라 폴백일 때 False라 게이지가 퍼센트가 추측임을 보여줄 수 있어요. 마이그레이션: on_usage는 지원되지만 deprecate됐어요. 콜백 본문을 ContextUsageEvent 구독으로 옮기세요. 순서가 중요해요. 같은 사이클 컴팩션 후 수정된 현재 히스토리를 보려면 컴팩션 capability 뒤에 모니터를 등록하고, 무엇이 컴팩션을 발동했는지 보려면 앞에 등록하세요.

used_tokens는 위 회계를 따라요. 프로바이더 사용 고정은 기준 고정 요청의 지시문·도구 정의·FilePart 페이로드를 포함해요. 고정 후 접미사나 고정 없는 히스토리는 tokenizer나 ~4자/token 휴리스틱을 쓰고 FilePart 페이로드를 볼 수 없어요. 보류 중 새로 공개된 도구 스키마는 구현이 보수적으로 추정해요. 컴팩션 capability가 같은 사이클에서 더 일찍 실행되면 그 뒤에 등록된 모니터는 고정의 고정 프로바이더 오버헤드를 유지하면서 재작성의 휴리스틱 회수를 빼요.

실행 밖 컴팩션: compact_now

전략의 compactRunContext를 받는데, 실행 사이에 대화를 쥔 애플리케이션은 그게 없어요. 사용자가 /compact를 칠 때가 정확히 그때예요. compact_now는 에이전트가 쓰는 같은 전략을 명령 핸들러에서 구동하도록 일회용 컨텍스트를 만들어요.

from pydantic_ai_harness import SummarizingCompaction
from pydantic_ai_harness.compaction import compact_now

strategy = SummarizingCompaction(max_fraction=0.9, keep_messages=20)
history = await compact_now(
    strategy,
    history,
    model='anthropic:claude-sonnet-5',
    focus='the auth refactor, not the earlier CSS work',
)

compact_now는 자체 트리거를 적용하지 않아, compact가 무조건적인 전략은 히스토리 크기가 어떻든 실행돼요. 자체 정지 조건을 정의하는 전략은 여전히 그것을 지켜요. TieredCompaction은 히스토리가 타깃에 맞을 때까지만 에스컬레이션해서, 이미 타깃 아래인 히스토리는 그대로 돌아와요. 상관없이 실행해야 하면 계층을 직접 넘겨요.

focus는 산문을 쓰는 전략(SummarizingCompaction, 내보내진 SupportsFocus 프로토콜의 with_focus 통해)을 이끌고, 규칙으로 콘텐츠를 버리거나 비우는 전략은 넘겨받은 게 없어 건너뛰어요. TieredCompaction은 어떤 계층이 focus 가능해도 focus 가능해서, focus가 래퍼에서 멈추지 않고 요약 계층에 닿아요.

히스토리를 바꾸는 컴팩션은 실행 내 경로가 내는 것과 같은 compact_messages 스팬을 내보내, 계측된 애플리케이션이 컴팩션이 어떻게 발동됐든 하나의 모양을 봐요. tracer=를 넘겨 기록하고, 없으면 스팬이 no-op 트레이서로 가요.

권장 기본: TieredCompaction

분야 합의(Anthropic, OpenCode, Letta)는 먼저 지우고 dedupe하고, 충분하지 않을 때만 요약하라는 것이에요. 요약은 입력 토큰을 프리미엄 청구·직렬 생성되는 출력 토큰으로 바꿔서 진짜 비싸요. 제로-LLM 전략은 더 싼 입력 쪽만 건드려요.

TieredCompaction은 그 에스컬레이션을 인코딩해요. 각 계층을 순서대로 실행하고 매번 토큰 수를 다시 측정하고 대화가 target_tokens에 맞는 즉시 멈춰요. 계층을 싼-비싼 순으로 정렬해 값비싼 요약 계층이 싼 패스가 충분히 회수하지 못할 때만 닿게 해요.

from pydantic_ai import Agent
from pydantic_ai_harness import ClearToolResults, DeduplicateFileReads, SummarizingCompaction, TieredCompaction
from pydantic_ai.messages import ToolCallPart


def my_file_key(call: ToolCallPart) -> str | None:
    if call.tool_name != 'read_file':
        return None
    return call.args_as_dict().get('path')


agent = Agent(
    'openai:gpt-5.6-luna',
    capabilities=[
        TieredCompaction(
            tiers=[
                DeduplicateFileReads(file_key=my_file_key),
                ClearToolResults(max_tokens=1, keep_pairs=3),
                SummarizingCompaction(max_messages=1, keep_messages=20),  # model inherits the run's
            ],
            target_tokens=120_000,
        )
    ],
)

TieredCompaction 안의 계층은 오케스트레이터가 직접 구동하고, 각 계층 후 다시 측정해 target_tokens 아래가 되면 멈춰요. 계층의 자체 max_* 트리거는 TieredCompaction 안에서 실행될 때 무관해요. 유효한 아무거나로 설정하고 예: ClearToolResults(max_tokens=1). async def compact(messages, ctx) -> list[ModelMessage](CompactionStrategy 프로토콜)이 있는 모든 객체가 계층이 될 수 있어, 자신의 것을 꽂을 수 있어요.

FallbackCompaction: 전략이 실패할 때 회복

TieredCompaction은 성공한 계층이 충분히 회수하지 못할 때 진행해요. FallbackCompaction은 전략이 fallback_on이 고른 예외(기본 Pydantic AI ModelAPIErrorFallbackExceptionGroup)를 일으킬 때만 진행해요. 후자는 FallbackModel의 모든 모델이 실패할 때 올라와요. 각 시도는 원본 메시지 객체를 담은 새 목록을 받아, 실패한 전략의 목록 수준 변경이 폴백에 영향을 주지 않아요. 전략은 여전히 CompactionStrategy 계약을 지키고 메시지 객체를 변형하지 말아야 해요. 모든 전략이 실패하면 마지막 예외가 다시 올라와요. 비일치 예외, 취소, 다른 BaseException 하위클래스는 즉시 통과하고, fallback_onException에서 파생하지 않는 타입을 거부해요.

요약이 결정적 트렁케이션으로 폴백해야 할 때 직접 등록하세요.

from pydantic_ai_harness import FallbackCompaction, SlidingWindowCompaction, SummarizingCompaction

fallback = FallbackCompaction(
    max_fraction=0.85,
    fallback_chain=[
        SummarizingCompaction(max_messages=1, keep_tokens=20_000),
        SlidingWindowCompaction(max_messages=1, keep_tokens=20_000),
    ]
)

fallbackAgent(..., capabilities=[fallback])로 직접 등록하세요. 선택적 max_tokens/max_fraction 트리거는 추정 컨텍스트 토큰이 임계값을 넘을 때만 체인을 실행해요. 분율은 요청의 모델에 대해 해석되고, context_window는 그 창을 덮어쓰며 fallback_context_window는 알 수 없는 모델의 창을 공급해요. tokenizer는 토큰 추정을 커스터마이즈해요. 훅은 고정된 부분을 보존하고 컴팩트된 히스토리를 지속하며, 히스토리가 바뀌면 표준 compact_messages 스팬을 내보내요.

트리거가 둘 다 구성되지 않으면 요청 훅은 아무것도 안 해요. 직접 compact()compact_now() 호출은 임계값과 무관하게 체인을 실행해, 다른 조합 전략 안과 수동 컴팩션에 여전히 쓸 수 있어요. 각 자식의 자체 트리거는 체인이 그 compact() 메서드를 부를 때 우회돼요.

ClampOversizedMessages: 폭주 생성에서 살아남기

반복 공백의 단일 모델 응답, 또는 거대한 페이로드의 단일 도구 호출이 다음 요청이 프로바이더 컨텍스트 상한을 넘게 하는 한 부분을 만들 수 있어요. 다른 전략은 그걸 닿을 수 없어요. SlidingWindowCompaction은 가장 오래된 메시지를 버리는데 범인은 가장 새로운 것이고, ClearToolResults는 도구 결과 만 건드리며, WarnNearLimits는 히스토리를 절대 편집하지 않고, SummarizingCompaction에 히스토리를 먹이면 같은 상한에 닿아요.

ClampOversizedMessages는 범인 부분을 제자리에서 잘라내 head 조각과 tail 조각을 [clamped: removed N of M characters] 표시와 함께 유지해요. 퇴화 생성은 저엔트로피 반복이라 head/tail 조각이 거의 잃지 않아요.

from pydantic_ai import Agent
from pydantic_ai_harness import ClampOversizedMessages

agent = Agent(
    'openai:gpt-5.6-terra',
    capabilities=[
        ClampOversizedMessages(max_part_tokens=50_000, keep_head_chars=2_000, keep_tail_chars=2_000)
    ],
)

부분은 초대형 이고 클램프가 실제로 줄일 때만 클램프돼요. keep_head_chars + keep_tail_chars를 부분별 임계값보다 훨씬 아래로 두세요.

ModelResponse 안에서 두 종류의 부분을 클램프해요.

  • 응답 텍스트(TextPart) — 임계 경우, 폭주 모델 응답 텍스트 부분.
  • 도구 호출 인수(ToolCallPart), clamp_tool_call_args=True(기본)일 때 — 거대한 페이로드(예: 폭주 write_plan)의 같은 실패 형태. 인수는 작은 JSON 객체 {"_clamped": "<head>...<tail>"}로 바뀌어 유효한 함수 인수로 남아요. 원본 호출은 이미 실행됐으므로 이것은 히스토리 사본만 줄여요. clamp_tool_call_args=False를 설정하면 응답 텍스트만 클램프해요. 프레임워크 타이핑된 호출 부분(core의 search_toolsload_capability 호출)은 결코 클램프되지 않아요. 그 타이핑된 인수는 영속 히스토리가 복원될 때(예: StepPersistence 재개) 검증되어 _clamped 객체가 그 왕복에서 실패할 테니까요.

요청 측 부분(사용자 프롬프트, 도구 반환, 시스템 프롬프트)은 의도적으로 범위 밖이에요. 사용자 입력을 조용히 다시 쓰면 안 되고, 초대형 도구 반환은 ClearToolResults의 일이에요.

TieredCompaction의 첫 계층으로, ClearToolResults 전에 쓰세요.

from pydantic_ai_harness import ClampOversizedMessages, ClearToolResults, TieredCompaction

TieredCompaction(
    tiers=[
        ClampOversizedMessages(max_part_tokens=50_000),
        ClearToolResults(max_tokens=1, keep_pairs=3),
    ],
    target_tokens=120_000,
)

ClearToolResults: 싼 첫째 계층

도구 출력은 전형적으로 에이전트 컨텍스트를 지배하고, 에이전트는 데이터가 다시 필요하면 보통 도구를 다시 실행할 수 있어요. ClearToolResults는 가장 오래된 도구 결과 콘텐츠를 짧은 플레이스홀더로 바꾸면서 가장 최근 keep_pairs 도구 호출/도구 반환 페어는 온전히 유지해요. 도구 호출은 이제 비워진 결과와 페어링된 채 남아, 히스토리가 유효하게 유지돼요.

프레임워크 타이핑된 도구 결과(core의 search_toolsload_capability 반환)는 그대로 두어요(작은 토큰 바닥). 그 구조화 콘텐츠는 이후 요청에서 다시 파싱되고 dataclasses.replace로 다시 쓰면 검증을 우회해 부분을 손상시킬 테니까요.

from pydantic_ai import Agent
from pydantic_ai_harness import ClearToolResults

agent = Agent(
    'openai:gpt-5.6-luna',
    capabilities=[ClearToolResults(max_tokens=100_000, keep_pairs=3)],
)

clear_tool_inputs=True를 설정하면 지워진 호출의 인수도 비우고, exclude_tools를 결과가 결코 지워지지 않는 도구 이름 집합으로 설정해요.

DeduplicateFileReads: 대체된 읽기 버리기

같은 파일이 한 번 이상 읽히면 최신 읽기만 콘텐츠를 유지하고, 이전 읽기는 페어링 보존과 함께 플레이스홀더로 비워져요.

기본 file_key는 없어요. 파일 읽기 식별은 에이전트 특정이라, 틀린 추측이 라이브 데이터를 버릴 수 있으니까요. ToolCallPart를 안정적 파일 키에 매핑하는 callable을 공급하거나, 호출이 파일 읽기가 아니면 None을 돌려주세요.

from pydantic_ai import Agent
from pydantic_ai.messages import ToolCallPart
from pydantic_ai_harness import DeduplicateFileReads


def file_key(call: ToolCallPart) -> str | None:
    if call.tool_name != 'read_file':
        return None
    return call.args_as_dict().get('path')


agent = Agent('openai:gpt-5.6-terra', capabilities=[DeduplicateFileReads(file_key=file_key)])

max_messagesmax_tokens 트리거가 없으면 DeduplicateFileReads는 매 요청 실행돼요. 싸고 거의 손실이 없어서 그 기본이 보통 원하는 것이에요.

SlidingWindowCompaction: 최근 꼬리만 유지

대화가 구성된 임계값을 넘으면 SlidingWindowCompaction은 도구 호출/도구 반환 페어를 보존하며 가장 오래된 전체 메시지를 꼬리까지 버려요. 최근 턴만 필요하고 옛 컨텍스트를 완전히 버릴 수 있을 때 손대세요.

from pydantic_ai import Agent
from pydantic_ai_harness import SlidingWindowCompaction

agent = Agent(
    'openai:gpt-5.6-terra',
    capabilities=[SlidingWindowCompaction(max_messages=80, keep_messages=40)],
)

기본적으로 preserve_first_user_message=True는 창 밖에 떨어져도 첫 사용자 턴(시스템 프롬프트에 더해)을 유지해 에이전트가 원래 작업을 잃지 않아요. keep_messages 대신 keep_tokens를 넘기면 메시지 수가 아니라 토큰 예산으로 자르게 해요.

SummarizingCompaction: 버리지 말고 압축하세요

옛 컨텍스트가 여전히 중요하지만 압축되어야 할 때 SummarizingCompaction은 전용 모델 호출로 옛 메시지를 요약하고 단일 구조화 요약으로 교체하며, 최근 꼬리와 도구 호출 무결성을 보존해요. 비싼 계층이므로 더 싼 패스 뒤에서 쓰는 것이 최고예요(TieredCompaction 참고).

from pydantic_ai import Agent
from pydantic_ai_harness import SummarizingCompaction

agent = Agent(
    'anthropic:claude-opus-5',
    capabilities=[
        SummarizingCompaction(
            model='anthropic:claude-sonnet-5',
            max_messages=60,
            keep_messages=20,
        )
    ],
)

model은 모델 이름이나 Model을 받고, None으로 두면 실행하는 에이전트의 모델을 상속해요. 중첩 요약 실행은 부모 사용 한도를 상속하고, 유한 요청 한도에서 보류 중 부모 요청용 요청 하나를 예약해요. model_settings를 넘겨 그 모델이 지니는 기본과 다른 전용 요약 호출 설정을 줘요. 공급된 설정은 모델이나 설정 사전을 변형하지 않고 모델 기본 위에 병합돼요. 기본적으로 incremental=True가 가장 오래된 기존 요약을 앵커로 갱신해요. 이전 릴리스와 요약 호출 프롬프트가 바뀌어요. 이전 재생성 동작을 유지하려면 incremental=False를 설정하세요.

요약 요청의 두 프롬프트 표면 모두 필드예요. summary_prompt는 사용자 턴 템플릿({messages} 플레이스홀더를 포함해야 해요)이고, instructions는 Pydantic AI가 요청의 시스템 프롬프트로 보내는 내부 에이전트의 정적 지시문을 설정해요. 요약기 엔드포인트가 고정 선두 지시문을 요구하면 instructions를 덮어써요.

그 템플릿에 제공되는 메시지는 텍스트로 렌더링되고, 각 도구 반환은 유지된 사용자 턴과 같은 명시적 트렁케이션 표시로 반환당 tool_return_max_chars(기본 500) 문자에서 한정돼요. 요약기의 컨텍스트 창이 페이로드를 흡수할 만큼 크면 올리거나 None으로 설정해 각 반환을 전체 렌더링해요. max_tokenskeep_tokens는 컴팩션이 언제 실행되고 어떤 히스토리 메시지가 유지되는지 제어해요. 요약 요청 페이로드를 한정하지 않아요.

요약 요청은 event_stream_handler가 설정되지 않으면 비스트리밍이에요. 핸들러를 공급해 요약이 쓰이는 걸 지켜보거나, 이벤트를 처리하지 않고 스트리밍 요청 경로를 타려면 drain_summary_events를 넘기세요. 비스트리밍 요청을 거부하는 요약기 엔드포인트가 필요한 것이에요.

from pydantic_ai import Agent
from pydantic_ai_harness.compaction import SummarizingCompaction, drain_summary_events

agent = Agent(
    'openai:gpt-5.6-terra',
    capabilities=[
        SummarizingCompaction(max_messages=60, event_stream_handler=drain_summary_events),
    ],
)

어느 전송도 어디서나 작동하지 않아서, 이것이 기본이 아니라 선택이에요. 일부 엔드포인트는 비스트리밍 요청을 거부하고 다른 건 스트리밍을 거부해요. 핸들러는 요약 실행의 자체 RunContext와 이벤트 스트림을 받아요. 바깥 Agent.run(...) 핸들러는 상속되지 않고 요약 토큰 델타를 결코 보지 못해요.

사용 회계

요약 호출은 모델에 대한 실제 요청이라, 그 완전한 사용 — 토큰 요청 자체 — 이 실행의 ctx.usage로 접혀요. 이것은 의도적이에요. 비용을 정직하게 유지하고, 요청 수를 일관되게 하며(하나로 세지 않은 모델 요청이 놀라움이 될 테니까), UsageLimits 요청 한도가 폭주 컴팩션을 잡게 해요. 중첩 실행은 다른 부모 한도를 그대로 받고, 유한 요청 한도는 부모 요청에 이미 승인된 슬롯을 쓸 수 없게 하나 줄여요. 그래서 실행·요청·반복 제한기는 요청 중 컴팩션 호출을 보게 돼요.

durable-execution capability가 붙으면 요약 호출은 기여된 durable 작업으로 실행돼, 재생이 모델을 다시 부르는 대신 기록된 요약을 써요. model이 설정되지 않으면 작업이 실행의 모델을 써요. capability는 안정적 기본 id를 지니고, durable execution이 같은 정체성으로 작업을 회복하는 데 써요. 커스텀 값으로 덮어쓰면 진행 중 워크플로우의 기록된 작업을 고아로 만들어, 워크플로우가 살아 있는 동안 고정으로 두세요.

WarnNearLimits: 다시 쓰기보다 경고

WarnNearLimits는 히스토리를 절대 편집하지 않아요. 실행이 구성된 한계에 접근하면 URGENT(그다음 CRITICAL) 경고를 끝자리 사용자 턴으로 주입해, 모델이 그 밑에서 컨텍스트가 다시 쓰이기보다 마무리하게 해요. 모델은 시스템 메시지보다 사용자 메시지에 더 주의를 기울이는 경향이 있어서 경고가 사용자 턴이에요. 이 capability의 이전 경고들은 새것을 주입할지 결정하기 전에 제거돼요.

from pydantic_ai import Agent
from pydantic_ai_harness import WarnNearLimits

agent = Agent(
    'google:gemini-3.6-flash',
    capabilities=[
        WarnNearLimits(
            max_iterations=40,
            max_context_tokens=100_000,
        )
    ],
)

경고는 warning_threshold(기본 0.7, 한계의 분율)에서 시작하고, 남은 요청 수가 critical_remaining_iterations(기본 3)로 떨어지면 반복에 대해 CRITICAL이 돼요. 세 종류 한계(max_iterations, max_context_tokens(또는 max_context_fraction), max_total_tokens)를 보고 기본적으로 구성된 것 모두에 대해 경고해요. warn_on으로 좁혀요.

캐시 트레이드오프 (Cache tradeoff)

지우기, dedupe, 클램프, 요약 모두 메시지 콘텐츠를 다시 써서 편집 지점부터 프로바이더 프롬프트 캐시를 무효화해요. 다음 요청이 캐시 쓰기를 내요. ClearToolResults에는 min_clear_tokens를 써서 캐시를 깨뜨릴 만큼 회수하지 못하는 지우기를 건너뛰어요. ClampOversizedMessages에는 캐시 깨짐이 피할 수 없어요. 대안이 실패한 요청이니까요.

추적 (Tracing)

core 계측이 활성이면(Instrumentation capability, agent.instrument, Agent.instrument_all()) 각 전략이 실제로 컴팩션하는 순간, 즉 before_model_request에서 전략의 임계값이 넘어서면(ClampOversizedMessages는 부분이 실제로 클램프될 때만) 실행의 트레이서에 compact_messages 스팬을 내보내요. TieredCompaction은 각 계층이 아니라 전체 에스컬레이션에 대해 단일 스팬을 내보내요. 각 계층의 compact를 직접 구동하니까요. 계측이 없으면 트레이서가 no-op라 스팬이 오버헤드를 더하지 않아요.

스팬 이름은 정적 compact_messages예요. 전략은 이름의 일부가 아니라 속성이라 스팬 카디널리티를 낮게 유지해요. 속성: gen_ai.conversation.compacted(bool, 항상 true, 컴팩트된 컨텍스트를 위한 OpenTelemetry GenAI 규약 플래그), compaction.strategy(전략 클래스 이름), compaction.messages_before, compaction.messages_after, compaction.tokens_before, compaction.tokens_after. 토큰 수는 전략의 tokenizer가 설정되면 그것을, 아니면 ~4자/token 휴리스틱을 써요. 원본 메시지 콘텐츠는 기록되지 않아요.

SummarizingCompaction은 요약기를 summarizing_compaction 이름의 중첩 Agent로 실행해, Agent.instrument_all()(또는 logfire.instrument_pydantic_ai()) 아래에서 그 실행들이 agent_name = summarizing_compaction을 지녀요. 그것으로 필터링해 부모 에이전트와 별도로 요약 사용·비용을 추적할 수 있어요.

컴팩션 영수증 (Compaction receipts)

컴팩션은 모델이 거부할 수도, 종종 감지할 수도 없는 메모리 지움이에요. 재개 드리프트 를 초대해요. 모델이 더 이상 없는 히스토리로 연속성을 꾸며내는 것이죠. 영수증은 지움을 읽기 쉽게 해요. 경계를 가로지르는 전략이 히스토리를 다시 쓴 뒤 짧고 결정적인 메모를 붙여 얼마가 컴팩트됐는지 기록하고, 살아남은 것이 간접적임을 경고하며, 핸들 프로바이더가 붙으면 영속 실행 히스토리 식별자를 붙여요.

from pydantic_ai_harness import SlidingWindowCompaction, SummarizingCompaction

SummarizingCompaction(max_messages=60, keep_messages=20, receipts=True)
SlidingWindowCompaction(max_messages=80, keep_messages=40, receipts=True)

영수증 텍스트는 타임스탬프를 지니지 않아 컴팩션의 순수 함수예요. 메시지 부분은 여전히 평범한 요청 타임스탬프를 가져요.

문구는 실제로 살아남은 것을 따른다. SummarizingCompaction은 요약을 남기므로 영수증이 위 요약이 간접적이라고 말해요. SlidingWindowCompaction은 히스토리를 단독으로 버리므로 영수증이 그 컨텍스트가 사라졌다고 말해요. 제자리 비움 전략(ClearToolResults, DeduplicateFileReads, ClampOversizedMessages)은 모든 메시지를 유지하고 경계를 안 가로질러, 영수증을 내지 않아요.

compaction_transcript_handle() -> str | None을 노출하는 어떤 capability라도 — TranscriptHandleProvider 프로토콜 — 붙이면 영수증이 Persisted run handle: <handle> 포인터를 얻어요. StepPersistence가 그것을 구현해 run_id를 반환하므로, 붙이기만 하면 돼요. 각 영수증은 또한 compact_messages 스팬의 compaction.receipt 이벤트로 내보내져 compaction.receipt.strategy, .messages_dropped, .tokens_dropped, .by, 핸들을 찾으면 .handle을 실어 나라요.

핸들은 영속 실행을 가리키지 완벽한 원본지가 아니에요. 컴팩션의 편집이 실행의 메시지 히스토리에 지속돼 실행의 최신 스냅샷이 컴팩트된 히스토리를 반영해요. 다시 읽어도 영수증이 버렸다고 말하는 것을 회복하지 않아요. 스텝별 스냅샷을 유지하는 저장소는 자체 보존(배송된 저장소의 max_snapshots_per_run)에 따라 컴팩션 전 스텝을 여전히 쥘 수 있어요. 핸들을 실행 포인터로 다루고, 에이전트에게 원본을 읽을 수 있다고 약속하기 전에 저장소 보존을 확인하세요.

영수증의 by 귀속은 브리지 접두사와 같은 거친 가족 휴리스틱을 같은 근사로 써요. 아래 메모를 보세요.

영수증은 선택이에요(receipts=False 기본). 영수증 텍스트가 콘텐츠라서요. 정확한 문구는 벤치마크 eval-rig 패스까지 잠정적인 반면, 메커니즘은 구조적이에요.

고정: 컴팩션에서 살아남는 콘텐츠

pin은 모든 배송 전략이 반드시 보존해야 하는 콘텐츠를 표시해요.

from pydantic_ai_harness.compaction import pin

# In a ModelRequest placed in the run's message history (by a capability or the user):
pinned = pin('Durable task state the model must never lose across compaction.')

고정된 부분은 결코 요약되거나 버려지지 않아요. 전략이 그것을 버렸다면 살아남은 히스토리 위쪽 근처에 다시 주입해요. is_pinned은 주어진 부분이 표시를 지니는지 보고해요.

핀은 모델에 안 보이는 TextContent.metadata를 써서, 콘텐츠는 평범한 사용자 컨텍스트로 남아 컴팩션이 사용자 턴과 구별할 수 있어요.

Planning capability는 고정이 필요 없어요. 그 계획은 wrap_model_request에서 매 요청 일시적으로 다시 주입되므로 이미 구조상 컴팩션을 살아남아요. 고정은 히스토리 안에 사는 durable 태스크 상태와 스크래치패드용이에요.

사용자 메시지 유지 (keep_user_messages)

사용자 턴은 대화에서 토큰당 신호가 가장 높은 콘텐츠이고, 그것을 잃는 것이 재개 드리프트의 주 원인이다. SummarizingCompaction(keep_user_messages=True)는 요약과 함께 요약된 접두사에서 가장 새로운 사용자 턴을 보존해요. 그것들은 기존 keep_messages 꼬리 예산을 소비해서, 그만큼 많은 유지 사용자 메시지와 꼬리 메시지가 함께 살아남아요. 따라서 컴팩션이 각 사이클에 유지 사본을 키우지 않아요. keep_tokens가 설정되면 그 유지된 사용자 메시지와 꼬리 메시지도 같이 토큰 예산을 공유해요. 안 맞는 사용자 턴은 대신 요약돼요. 각 유지 턴은 keep_user_messages_max_chars(기본 20k)로 경계지고 넘으면 명시적 트렁케이션 표시가 붙어요. 문자 예산은 부분별로 적용되고 다중 부분 프롬프트의 텍스트 항목에 공유돼요. 이미지, 오디오, 캐시 포인트는 그대로 통과해요. 이건 첫 턴만 유지하는 preserve_first_user_message를 대체해요.

from pydantic_ai_harness import SummarizingCompaction

SummarizingCompaction(max_tokens=120_000, keep_messages=20, keep_user_messages=True)

사용자 턴을 유지하면 요약, 어떤 영수증, 유지 턴이 인접한 ModelRequest로 남아요. 턴당 요청 하나를 요구하는 프로바이더(Bedrock Converse와 Gemini 포함)는 그 모양을 결코 보지 못해요. Pydantic AI가 before_model_request 훅 실행 후 _merge_consecutive_messages로 히스토리를 정규화해 인접 요청을 디스패치 전 단일 턴으로 합치니까요. 그래서 keep_user_messages는 프로바이더별 처리가 필요 없어요.

앵커된 증분 요약과 크로스 모델 브리지

incremental=True(기본)에서 이전 요약은 다시 요약되지 않아요. 연속 컴팩션으로 요약의 요약이 퇴화하니까요. 그것은 업데이트 지시문과 함께 앵커된 <previous-summary> 블록으로 다시 공급돼요. 여전히 사실인 세부를 보존하고, 낡은 것을 제거하고, 새 사실을 병합해요. 요약은 고정 구조 아래 제자리에서 갱신되는 살아있는 문서가 돼요.

동작 변경: incremental=True가 기본

이 릴리스부터 모든 기존 SummarizingCompaction 사용자가 다른 요약 호출 프롬프트를 받아요. 이전 요약이 존재하면 요약기가 대화에서 하나를 재생성하는 대신 <previous-summary> 아래에서 업데이트 하도록 요청받아요. 생산하는 요약이 다르게 읽혀요. 이전 처음부터-재생성 동작을 유지하려면 incremental=False를 설정하세요.

bridge_prefix=True는 요약기의 모델 가족이 히스토리를 만든 가족과 다를 때만(히스토리의 model_name과 요약기 구성에서 파생) 요약에 한 줄 메모를 앞에 붙여요. 요약을 크로스 모델 인계로 표시해 재개 모델이 스스로 그 일을 했다고 꾸며내는 대신 그것을 토대로 이어가게 해요. 흔한 동일 모델 경우에는 결코 발동하지 않아 싸요. 메모가 프롬프트 콘텐츠라 기본 False예요.

가족 토큰은 거친 근사예요. provider: 접두사를 떼고 첫 -/ 앞의 선두 토큰을 취해요. 평범한 참조에서 gptclaude를 분리해요(openai:gpt-5.6-luna -> gpt, google:gemini-3.6-flash -> gemini) 실제 몇 개를 잘못 읽어요. us.anthropic.claude-sonnet-4-5-v1:00으로, ollama/llama3ollama으로, fallback: 모델 문자열 은 첫 모델이 아니라 마지막 나열 모델로 줄여요. FallbackModel 객체는 첫 모델에서 올바르게 읽혀요. 그래서 브리지·영수증 귀속은 최선 노력이에요. 잘못 읽은 가족이 브리지 메모를 억제하거나 두 동일 가족 모델 사이에서 발동시킬 수 있어요. 어느 결과도 컴팩션이 무엇을 유지하거나 버리는지 바꾸지 않아요.

영수증처럼 갱신 지시문과 브리지 접두사 문구는 콘텐츠이고, eval-rig 패스까지 최소·중립으로 배송돼요. 앵커링과 가족 게이팅 메커니즘은 구조적이에요.

범위 밖 (Out of scope)

이 전략들은 창 안에서 컨텍스트를 압축하거나 버려요. 큰 도구 출력을 창 밖으로 옮기는 것(에이전트(또는 서브에이전트)가 요청 시 쿼리할 수 있는 파일로 오버플로)은 손실 트렁케이션이 아니라 별도 capability예요(tool output limits). SummarizingCompaction 안에서 tool_return_max_chars는 요약기의 반환당 상한을 조정 가능하게 하거나(None은 반환을 전체 렌더링) 하지만, 요약 요청은 여전히 손실 렌더링을 읽어요. 페이로드가 전체로 쿼리 가능해야 할 때는 tool output limits를 선호하세요.

API 참조 (API reference)

권장 기본은 TieredCompaction이고, 아래 다른 전략들은 단독으로 쓰거나 그 계층으로 꽂을 수 있어요.

TieredCompaction

Bases: AbstractCapability[AgentDepsT]

일련의 컴팩션 전략에 대한 에스컬레이션 오케스트레이터.

각 계층을 순서대로 실행하고 매번 토큰 수를 다시 측정하며 대화가 target_tokens에 맞는 즉시 멈춰요. 계층을 싼-비싼 순으로 정렬해(예: 도구 결과 지우기, 읽기 dedupe, 그다음 요약) 값비싼 요약 계층이 싼 패스가 충분히 회수하지 못할 때만 닿게 해요.

각 계층의 자체 트리거는 우회돼요. TieredCompactioncompact 메서드로 계층을 직접 구동하고 언제 멈출지 결정하니까요.

속성
  • tiers — 순서대로 적용할 전략, 싼-비싼. 마지막이 전형적으로 요약기. Type: Sequence[CompactionStrategy[AgentDepsT]]
  • target_tokens — 추정 토큰 수가 이 값 이하가 되면 에스컬레이션을 멈춰요. target_fraction과 상호 배타. 정확히 하나가 설정되어야 해요. Default: None
  • target_fraction — 모델의 컨텍스트 창의 분율로 표현된 타깃, 요청마다 해석. 같은 에이전트가 다른 창의 모델에서 돌 때 target_tokens 대신 써요. target_tokens와 상호 배타. Default: None
  • context_window — 토큰 단위 창 덮어쓰기. None은 요청의 모델에서 해석. fallback_context_window와 달리 해석 성공 여부와 무관하게 적용돼요. 레지스트리가 확실히 틀릴 때 손대세요. Default: None
  • fallback_context_window — 모델이 가격 레지스트리에 없을 때 가정하는 창. target_fraction 옆에서만 상담. Default: DEFAULT_CONTEXT_WINDOW
  • tokenizer — 정확한 토큰 계산을 위한 선택적 토큰라이저. 주어진 문자열의 토큰 수를 반환하는 callable. None이면 ~4자/token 휴리스틱. Default: None
메서드
  • with_focusdef with_focus(focus: str) -> TieredCompaction[AgentDepsT]. focus 가능 계층이 focus를 우선시하는 사본 반환. 어떤 계층이든 focus 가능하면 계층적 전략이 focus 가능해요. 요약 계층이 산문을 쓰므로 힌트가 래퍼에서 멈추지 않고 그것에 닿아야 해요. focus를 지킬 수 없는 계층은 그대로 통과.
  • compact@async def compact(messages, ctx) -> list[ModelMessage]. 히스토리가 타깃에 맞거나 계층이 소진될 때까지 계층을 순서대로 적용.
  • before_model_request@async def before_model_request(ctx, request_context) -> ModelRequestContext. 대화가 타깃을 넘으면 계층을 통해 에스컬레이션.

ClampOversizedMessages

Bases: AbstractCapability[AgentDepsT]

단일 초대형 메시지 부분의 제로 비용 head/tail 트렁케이션.

폭주 생성(반복 공백의 모델 응답, 거대한 도구 호출 페이로드)이 다음 요청이 프로바이더 컨텍스트 상한을 넘게 하는 한 부분을 만들 수 있어요. 크기 기반 전략은 도울 수 없어요. SlidingWindowCompaction은 가장 오래된 메시지를 버리고(범인은 가장 새것), ClearToolResults는 도구 결과 만 건드리며, SummarizingCompaction에 히스토리를 먹이면 같은 상한에 닿아요. 이 전략은 범인 부분을 제자리에서 잘라내 head 조각과 tail 조각을 유지하고 제거된 중간에 표시를 넣어요. 퇴화 생성은 저엔트로피 반복이라 head/tail 조각이 거의 잃지 않아요. LLM 호출이 없어요.

ModelResponse에서 무엇을 클램프하나:

  • TextPart 콘텐츠(임계 경우 — 폭주 모델 응답 텍스트 부분).
  • ToolCallPart 인수, clamp_tool_call_args가 설정되면(거대한 도구 호출 페이로드의 같은 실패 형태). 인수는 작은 JSON 객체로 바뀌어 유효한 함수 인수로 남아요. 원본 호출은 이미 실행됐으므로 히스토리 사본만 줄여요. 프레임워크 타이핑된 하위클래스(ToolSearchCallPart, LoadCapabilityCallPart)는 절대 클램프되지 않아요. 그 타이핑된 인수는 지속성이 의존하는 ModelMessagesTypeAdapter 왕복을 견뎌야 하니까요.

요청 측 부분(사용자 프롬프트, 도구 반환, 시스템 프롬프트)은 범위 밖이에요. 사용자 입력을 조용히 다시 쓰면 안 되고, 초대형 도구 반환ClearToolResults의 일이에요.

클램프는 메시지 콘텐츠를 다시 써서 클램프된 메시지부터 프로바이더 프롬프트 캐시를 무효화해요. 여기서는 피할 수 없어요. 대안이 실패한 요청이니까요.

부분은 초대형 이고 클램프가 실제로 줄일 때만 클램프돼요. keep_head_chars + keep_tail_chars를 부분별 임계값보다 훨씬 아래로 두세요.

TieredCompaction의 첫 계층으로 조합돼요(ClearToolResults 전에 실행). 폭주 생성 후 실행을 살아있게 하는 유일한 제로-LLM 방법이에요.

속성
  • max_part_tokens — 추정 토큰 수가 이 값을 넘는 부분을 클램프. None은 이 트리거 비활성화. Default: None
  • max_part_chars — 문자 수가 이 값을 넘는 부분을 클램프. None은 이 트리거 비활성화. Default: None
  • keep_head_chars — 유지할 부분 head의 문자. Default: 2000
  • keep_tail_chars — 유지할 부분 tail의 문자. Default: 2000
  • clamp_tool_call_argsTrue일 때 응답 텍스트뿐 아니라 초대형 ToolCallPart 인수도 클램프. Default: True
  • tokenizer — 선택적 토큰라이저. None이면 ~4자/token 휴리스틱. Default: None
메서드
  • compact@async. 모든 초대형 응답 텍스트 부분(활성이면 도구 호출 인수)을 클램프.
  • before_model_request@async. 요청이 보내지기 전에 초대형 응답 부분을 클램프.

ClearToolResults

Bases: AbstractCapability[AgentDepsT]

옛 도구 결과의 제로 비용 제자리 지우기.

가장 오래된 도구 결과 콘텐츠를 짧은 플레이스홀더로 바꾸면서 가장 최근 keep_pairs 도구 호출/도구 반환 페어는 온전히 유지해요. 도구 호출은 (이제 비워진) 결과와 페어링된 채 남아 히스토리가 유효하게 유지돼요. LLM 호출이 없어요.

이것은 컴팩션의 싼 첫 계층이에요. 도구 결과가 전형적으로 컨텍스트를 지배하고, 에이전트는 데이터가 다시 필요하면 도구를 다시 실행할 수 있으니까요.

캐시 트레이드오프: 지우기는 메시지 콘텐츠를 다시 써서 지우는 지점부터 프로바이더 프롬프트 캐시를 무효화해요(다음 요청이 캐시 쓰기를 내요). 캐시를 깰 만큼 회수하지 못하는 지우기를 건너뛰려면 min_clear_tokens를 쓰세요.

속성
  • max_messages — 메시지 수가 이 값을 넘으면 지우기 발동. None 비활성화. Default: None
  • max_tokens — 추정 토큰 수가 이 값을 넘으면 지우기 발동. None 비활성화. Default: None
  • max_fraction — 추정 토큰이 모델 컨텍스트 창의 이 분율을 넘으면 발동. 요청마다 요청의 모델에서 해석돼 하나의 설정이 어떤 모델에서든 올바르게. max_tokens와 상호 배타. Default: None
  • context_window — 토큰 단위 창 덮어쓰기. Default: None
  • fallback_context_window — 요청의 모델이 가격 레지스트리에 없을 때 가정하는 창. max_fraction 옆에서만 상담. Default: DEFAULT_CONTEXT_WINDOW
  • keep_pairs — 손대지 않는 가장 최근 도구 호출/도구 반환 페어 수. Default: 3
  • placeholder — 지워진 도구 결과의 대체 콘텐츠. Default: '[tool result cleared]'
  • exclude_tools — 결과가 결코 지워지지 않는 도구 이름. Default: frozenset()
  • clear_tool_inputsTrue일 때 지워진 도구 호출의 인수도 비움. Default: False
  • min_clear_tokens — 이만큼의 추정 토큰을 회수할 때만 지움. 사소한 이득으로 프롬프트 캐시가 무효화되는 걸 보호. None은 항상 지움. Default: None
  • tokenizer — 선택적 토큰라이저. Default: None
메서드
  • compact@async. 가장 최근 keep_pairs 너머의 가장 오래된 도구 결과를 비움.
  • before_model_request@async. 대화가 구성된 임계값을 넘으면 옛 도구 결과를 지움.

DeduplicateFileReads

Bases: AbstractCapability[AgentDepsT]

대체된 파일 읽기의 제로 비용 제자리 지우기.

같은 파일이 한 번 이상 읽히면 최신 읽기만 콘텐츠를 유지하고, 이전 읽기는 플레이스홀더로 비워져요. 도구 호출 페어링이 보존돼요. LLM 호출이 없어요.

파일 정체성은 file_key 이음새가 공급해요. ToolCallPart가 주어지면 읽는 파일에 대한 안정적 키를, 호출이 파일 읽기가 아니면 None을 반환해요. 기본이 없어요. 파일 읽기 식별은 에이전트 특정이고, 틀린 추측이 라이브 데이터를 버릴 테니까요.

속성
  • file_key — 도구 호출을 안정적 파일 키에 매핑, 파일 읽기가 아니면 None. Type: Callable[[ToolCallPart], str | None]
  • placeholder — 대체된 파일 읽기의 대체 콘텐츠. Default: '[superseded file read]'
  • max_messages — 선택적 메시지 수 트리거. 둘 다 None이면 호출될 때마다 실행. Default: None
  • max_tokens — 선택적 토큰 수 트리거. 둘 다 None이면 호출될 때마다 실행. Default: None
  • max_fraction — 추정 토큰이 컨텍스트 창의 이 분율을 넘으면 발동. Default: None
  • context_window — 창 덮어쓰기. Default: None
  • fallback_context_window — 폴백 창. max_fraction 옆에서만 상담. Default: DEFAULT_CONTEXT_WINDOW
  • tokenizer — 선택적 토큰라이저. Default: None
메서드
  • compact@async. 이후 같은 파일의 새 읽기로 대체되는 모든 파일 읽기를 비움.
  • before_model_request@async. 파일 읽기를 dedupe, 선택적으로 크기 임계값 게이팅.

SlidingWindowCompaction

Bases: AbstractCapability[AgentDepsT]

제로 비용 슬라이딩 윈도 트리머.

대화가 구성 가능한 임계값(메시지 수 또는 추정 토큰 수)을 넘으면 도구 호출/도구 반환 페어를 보존하며 가장 오래된 메시지를 버려요. LLM 호출이 없어요.

트리밍은 before_model_request에서 일어나 에이전트 실행의 나머지에 투명해요.

속성
  • max_messages — 메시지 수가 이 값을 넘으면 트리밍 발동. None 비활성화. Default: None
  • max_tokens — 추정 토큰 수가 이 값을 넘으면 트리밍 발동. None 비활성화. Default: None
  • max_fraction — 추정 토큰이 컨텍스트 창의 이 분율을 넘으면 발동. Default: None
  • context_window — 창 덮어쓰기. Default: None
  • fallback_context_window — 폴백 창. Default: DEFAULT_CONTEXT_WINDOW
  • keep_messages — 트리밍 후 유지할 꼬리 메시지 수(메시지 수 트리거). Default: 40
  • keep_tokens — 트리밍 후 목표 토큰 예산(토큰 수 트리거). None이면 keep_messages로 폴백. Default: None
  • tokenizer — 선택적 토큰라이저. Default: None
  • preserve_first_user_messageTrue일 때 UserPromptPart를 포함하는 첫 ModelRequest가 시스템 프롬프트에 더해 트리밍 후 항상 유지. Default: True
  • receiptsTrue일 때 얼마나 많은 히스토리가 버려졌는지 기록하는 결정적 컴팩션 영수증을 앞에 붙이고, TranscriptHandleProvider capability가 붙으면 트랜스크립트 핸들 포함. 지금은 선택. Default: False
메서드
  • compact@async. 가장 오래된 메시지를 구성된 꼬리까지 버림.
  • before_model_request@async. 구성된 임계값을 넘으면 메시지 목록 트림.

SummarizingCompaction

Bases: AbstractCapability[AgentDepsT]

LLM 기반 대화 컴팩션.

대화가 구성 가능한 임계값을 넘으면 전용 모델 호출로 옛 메시지를 요약하고 컴팩트한 구조화 요약 메시지로 교체해, 최근 컨텍스트와 도구 호출 무결성을 보존해요.

이것은 비싼 계층이에요. 요약이 입력 토큰을 (더 비싼) 출력 토큰으로 바꾸니까요. 더 싼 패스 뒤에서 쓰는 것이 최고예요(TieredCompaction 참고).

요약 호출의 사용은 부모 실행의 사용으로 접혀요(실제 요청으로 세니까) 비용 회계가 정직하게 유지돼요. 이것이 실행의 요청 수도 올려서 요청 수 제한기가 보게 되지요.

속성
  • model — 요약 생성 모델. None이면 컴팩트되는 요청이 갈 모델 상속. Default: None
  • model_settings — 전용 요약 모델 호출 설정. model이 지니는 기본 위에 병합돼 요약 호출이 실행 에이전트와 다른 정책을 쓰게 하고 모델은 변형하지 않아요. Default: None
  • event_stream_handler — 설정되면 중첩 요약 실행에 전달돼 요약기의 자체 모델 스트리밍 이벤트가 호출자에게 드러나요. 그것을 설정하면 스트리밍 요청 경로도 선택하는데, 비스트리밍 요청을 거부하는 요약기 엔드포인트가 필요한 것이에요. drain_summary_events를 넘기면 이벤트를 처리하지 않고 그 경로를 타요. None으로 두면 요약 요청이 비스트리밍이고, 스트리밍 요청을 거부하는 엔드포인트가 필요한 것이에요. 핸들러는 바깥 실행이 아니라 요약 실행의 자체 RunContext를 받고, 바깥 Agent.run(...) 핸들러는 상속되지 않아요. Default: None
  • max_messages — 메시지 수가 이 값을 넘으면 컴팩션 발동. Default: None
  • max_tokens — 추정 토큰 수가 이 값을 넘으면 컴팩션 발동. Default: None
  • max_fraction — 추정 토큰이 컨텍스트 창의 이 분율을 넘으면 발동. max_tokens와 상호 배타. Default: None
  • context_window — 창 덮어쓰기. Default: None
  • fallback_context_window — 폴백 창. Default: DEFAULT_CONTEXT_WINDOW
  • keep_messages — 컴팩션 후 보존할 꼬리 메시지 수(메시지 수 트리거). Default: 20
  • keep_tokens — 컴팩션 후 보존할 목표 토큰 예산(토큰 수 트리거). None이면 keep_messages 폴백. Default: None
  • summary_prompt — 요약 생성 프롬프트 템플릿. {messages} 플레이스홀더를 포함해야 해요. Default: _DEFAULT_SUMMARY_PROMPT
  • instructions — 요약을 쓰는 내부 에이전트 지시문. 요약기 엔드포인트가 고정 선두 지시문을 요구하면 덮어써요. Default: _DEFAULT_INSTRUCTIONS
  • tokenizer — 선택적 토큰라이저. Default: None
  • preserve_first_user_messageTrue일 때 UserPromptPart를 포함하는 첫 ModelRequest가 컴팩션 후 항상 유지. Default: True
  • incrementalTrue일 때 이전 컴팩션의 기존 요약을 앵커된 <previous-summary> 블록으로 업데이트 지시문과 함께 공급(여전히 사실 보존, 낡은 제거, 새것 병합)해 제자리에서 갱신되게 하며 재요약을 피해요. 요약의 요약 퇴화를 피하는 것. Default: True
  • bridge_prefixTrue이고 요약기의 모델 가족이 히스토리를 만든 가족과 다르면 요약을 크로스 모델 인계로 표시하는 중립 한 줄 메모를 앞에 붙여요. 진짜 가족 불일치에서만 발동해 싸고 흔한 동일 모델 경우엔 꺼져요. Default: False
  • keep_user_messagesTrue일 때 요약과 함께 최근 요약된 사용자 메시지(각각 keep_user_messages_max_chars로 트렁케이트)를 보존. 유지 메시지는 keep_messages 꼬리 예산을 소비해 컴팩션이 경계 있게. preserve_first_user_message 대체. Default: False
  • keep_user_messages_max_charskeep_user_messages를 위한 메시지당 문자 상한. 초대형 메시지는 명시적 표시로 트렁케이트. Default: 20000
  • tool_return_max_chars — 요약기에 도구 결과를 렌더링할 때 반환당 문자 상한. None은 전체 렌더링. Default: 500
  • receiptsTrue일 때 요약 뒤에 얼마나 많은 히스토리가 요약됐는지, 요약이 간접적임을, TranscriptHandleProvider가 붙으면 영속 실행 핸들을 기록하는 결정적 컴팩션 영수증을 붙여요. 선택. Default: False
메서드
  • with_focusdef with_focus(focus: str) -> SummarizingCompaction[AgentDepsT]. 요약 프롬프트가 focus를 우선시하는 사본 반환. compact_now가 써서 사용자 호출 컴팩션이 요약이 잃지 말아야 할 것을 말하게 해요. 프롬프트는 나중에 str.format을 통하므로 사용자·모델 공급 focus의 중괄호는 살아남게 이스케이프돼요.
  • compact@async. 옛 메시지를 요약해 단일 요약 메시지로 교체.
  • before_model_request@async. 임계값이 넘으면 옛 메시지 요약.

WarnNearLimits

Bases: AbstractCapability[AgentDepsT]

에이전트가 구성된 한계에 접근하면 경고 메시지 주입.

경고는 UserPromptPart가 있는 끝자리 ModelRequest로 붙어 모델이 그것을 별개의 사용자 턴으로 다루게 해요(모델은 시스템 메시지보다 사용자 메시지에 더 주의함).

이 capability가 주입한 이전 경고는 새것을 주입할지 결정하기 전에 제거돼요.

속성
  • max_iterations — 실행의 최대 허용 요청. Default: None
  • max_context_tokens — 경고할 최대 컨텍스트 창 크기. Default: None
  • max_context_fraction — 모델의 실제 컨텍스트 창의 분율로 표현된 컨텍스트 한계, 요청마다 해석. max_context_tokens와 상호 배타. Default: None
  • context_window — 창 덮어쓰기. Default: None
  • fallback_context_window — 폴백 창. max_context_fraction 옆에서만 상담. Default: DEFAULT_CONTEXT_WINDOW
  • max_total_tokens — 경고할 최대 누적 실행 토큰 예산. Default: None
  • warn_on — 어떤 한계가 경고를 내야 하는지. 기본은 구성된 모든 한계. Default: None
  • warning_threshold — 경고가 시작하는 한계의 분율(0~1). Default: 0.7
  • critical_remaining_iterations — 반복 경고가 CRITICAL이 되는 남은 요청 수. Default: 3
메서드
  • before_model_request@async. 옛 경고를 제거하고 임계값이 넘으면 새것 주입.

ReportContextUsage

Bases: AbstractCapability[AgentDepsT]

각 모델 요청 전에 애플리케이션에 컨텍스트 사용 보고.

컴팩션 전략은 언제 행동할지 알지만 실행이 한계에 얼마나 가까운지 말하지 않아, "context: 73%"를 보여주려는 애플리케이션이 히스토리를 스스로 다시 세고 분모를 추측해야 해요. 이 capability는 둘 다 안 해요. 전략이 쓰는 같은 추정기와 모델의 실제 컨텍스트 창을 재사용해요.

관찰만 해요. 히스토리를 절대 편집하지 않아요.

순서가 중요해요. 컴팩트된 히스토리를 보려면 컴팩션 capability 뒤에, 무엇이 컴팩션을 발동했는지 보려면 앞에 등록하세요. 앞선 컴팩터가 앵커된 히스토리를 다시 쓴 뒤, 판독은 고정의 고정 프로바이더 오버헤드를 유지하면서 그 컴팩터의 휴리스틱 회수를 빼요.

속성
  • on_usage — 매 모델 요청 전 새 판독의 deprecate된 콜백. ContextUsageEvent 구독으로 대체하세요. 코루틴 함수는 await되고 여기서 올라온 예외는 전파돼 실행을 실패시켜요. Default: None
  • context_window — 창 덮어쓰기. None은 요청 모델에서 해석. Default: None
  • fallback_context_window — 폴백 창. Default: DEFAULT_CONTEXT_WINDOW
  • tokenizer — 선택적 토큰라이저, 컴팩션 전략이 쓰는 것과 일치. Default: None
메서드
  • before_model_request@async. 보류 중 히스토리를 측정, 내보내고, 호환 콜백 호출.

ContextUsage

컨텍스트가 얼마나 찼는지에 대한 단일 판독.

  • used_tokens — 보내려는 메시지 히스토리의 추정 토큰. estimate_context_tokens로 계산. Type: int
  • window_tokens — 판독이 측정되는 컨텍스트 창. Type: int
  • resolvedwindow_tokens가 모델의 실제 창인지 폴백인지. 게이지가 해석 안 된 창을 다르게 렌더링할 수 있어요. 모델이 가격 레지스트리에 없으면 퍼센트는 추측. Type: bool
  • fraction — 창의 분율로서 used_tokens. Type: float

pin

def pin(content: str) -> UserPromptPart

content 를 표시해 모든 배송 컴팩션 전략이 보존하게.

반환된 UserPromptPart는 실행의 메시지 히스토리의 ModelRequest에 놓을 수 있고, 컴팩션이 그것을 그대로 유지해요.

is_pinned

def is_pinned(part: object) -> bool

part 가 핀 메타데이터 표시를 지니면 True 반환.

reinject_pinned

def reinject_pinned(original, compacted) -> list[ModelMessage]

original 의 핀된 부분 중 compacted 가 버린 것을 다시 주입.

compacted 에 이미 있는 핀 부분은 그대로 두고, 없는 것은 단일 ModelRequest로 모아 선두 시스템/요약 메시지 바로 뒤에 놓아 살아남은 히스토리 위쪽 근처에 앉게 해요. original 에 핀이 없거나 전부 살아남으면 no-op라 항상 불러도 안전해요.

TranscriptHandleProvider

Bases: Protocol

영속 실행 히스토리용 핸들을 나눠줄 수 있는 capability.

이 메서드를 구현하는 어떤 capability라도 RunContext.capabilities에서 발견되고, 첫 None이 아닌 핸들이 쓰여요. StepPersistence가 그것을 구현해 run_id를 반환해요.