ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 에이전트의 기억과 비용을 지키는 법: 컨텍스트 예산(Budgeting)과 구조화 압축
    AI 2026. 9. 20. 08:32
    반응형

    참고: 본 글은 장기 실행(Long-running) 및 멀티턴 자율 에이전트 구축 시 직면하는 토큰 한계와 문맥 손실 문제를 해결하기 위한 결정론적 예산 할당(Context Budgeting)구조화 압축(Structured Compression) 아키텍처 패턴을 다룹니다.


    📌 한 줄 요약

    멀티턴 에이전트의 컨텍스트 초과와 비용 폭증을 방어하기 위해, 매 턴마다 고정 예약(시스템 프롬프트·도구 스키마·출력 버퍼)을 먼저 차감하여 가용 입력 예산을 동적으로 산출하고, 최근 $N$턴은 원문을 보존하되 이전 대화는 fact / decision / todo 삼분법으로 구조화 요약하여 정보 왜곡 없는 결정론적 하네스(Harness)를 구축하는 패턴입니다.


    1. 배경: 멀티턴 에이전트가 겪는 3대 컨텍스트 위기

    LLM 기반 자율 에이전트(Claude Code, Devin, 커스텀 코딩 어시스턴트 등)가 복잡한 업무를 수행하다 보면 대화가 10~20턴을 훌쩍 넘어가기 일쑤입니다. 이때 컨텍스트 관리를 모델 자체나 단순 슬라이딩 윈도에 맡겨두면 세 가지 치명적인 문제가 발생합니다.

    1) 컨텍스트 오염 및 드리프트 (Context Drift)

    대화가 길어지면 수만 토큰의 이전 대화 속 사소한 잡음(디버깅 로그, 실패한 시도, 지나간 잡담)이 모델의 Attention 메커니즘을 분산시킵니다. 그 결과 에이전트가 원래 달성해야 할 핵심 목표를 잊어버리거나 엉뚱한 환각(Hallucination)에 빠집니다.

    2) 비용 폭증과 지연 시간(TTFT) 악화

    과거 대화를 원문 그대로 계속 누적해 전달하면 턴이 진행될수록 입력 토큰이 기하급수적으로 불어납니다. 이는 API 호출 비용을 수직 상승시킬 뿐 아니라, 첫 번째 토큰이 출력되기까지 걸리는 시간(Time-To-First-Token, TTFT)을 수 초에서 수십 초까지 지연시킵니다.

    3) 임의 절단(FIFO Sliding Window)의 위험

    토큰 한도를 넘지 않기 위해 오래된 메시지부터 단순 선입선출(FIFO)로 잘라내면, 초반 턴에서 합의된 안전 제약 조건(Safety Rules), 사용자 핵심 코딩 컨벤션, 아직 해결되지 않은 TODO가 소리 없이 유실됩니다.


    2. 컨텍스트 예산 공식 (Deterministic Context Budgeting)

    이 문제를 해결하는 첫 번째 원칙은 "남는 공간에 대화를 채워 넣는 것이 아니라, 먼저 고정 슬롯을 예약하고 남은 예산에 맞춰 대화를 가공하는 것"입니다.

    에이전트는 턴이 시작될 때마다 다음 공식으로 가용 입력 예산(Available Input Budget)을 계산합니다.

    $$\text{Available Input Budget} = \text{total_budget} - (\text{system_budget} + \text{tool_schema_budget} + \text{response_reserved})$$

    ┌────────────────────────────────────────────────────────────────────────┐
    │                        Total Context Window                            │
    ├──────────────┬──────────────────┬─────────────────┬────────────────────┤
    │ System /     │ Tool Schemas     │ Model Response  │ Dynamic Input Slot │
    │ Safety Rules │ & Descriptions   │ Reservation     │ (Conversation +    │
    │ (Fixed 100%) │ (Fixed ~15%)     │ (Fixed ~20%)    │  Retrieved Memory) │
    └──────────────┴──────────────────┴─────────────────┴────────────────────┘
    

    1) 고정 보존 영역 (Fixed Reserves)

    • system_budget (절대 압축 불가): 시스템 프롬프트, 보안·안전 정책, 에이전트 페르소나, 사용자 장기 프로필. 이 영역은 어떤 압축이나 삭제도 허용되지 않습니다.
    • tool_schema_budget (통상 10~20%): 에이전트가 호출할 수 있는 도구/MCP 함수들의 JSON Schema 파라미터 정의. 도구 수가 많을수록 비중이 커지므로 필요시 도구 동적 필터링을 병행해야 합니다.
    • response_reserved (통상 20%): 모델이 안정적으로 장문의 응답이나 코드를 출력할 수 있도록 사전에 비워두는 출력 전용 공간(Max Output Tokens).

    2) 동적 입력 슬롯 (Dynamic Input Slot)

    • 전체 윈도에서 고정 영역들을 뺀 나머지 공간만이 실제 대화 히스토리와 외부 검색 기억(Retrieved Memory)이 사용할 수 있는 진짜 예산입니다.

    3. 구조화 압축 3단계 파이프라인

    동적 슬롯에 담아야 할 히스토리가 가용 입력 예산을 초과하면, 무작위 요약 대신 명확한 3단계 파이프라인으로 점진적 압축을 수행합니다.

    [입력 턴 수신]
          │
          ▼
    [가용 입력 예산 계산] 
          │
          ├─ 예산 범위 내 ──> [원문 그대로 프롬프트 조립]
          │
          ▼ (예산 초과 시)
    [1단계: 최근 N턴(8턴) 원문 분리]
          │
          ▼
    [2단계: 이전 과거 턴 -> fact / decision / todo 구조화 요약]
          │   ├─ 체크섬 캐시 확인 (기존 요약본 재사용)
          │   └─ 미캐시 구간 요약 생성 + 메타데이터 포인터 부착
          ▼
    [3단계: 여전히 초과 시 외부 시맨틱 리콜 top-k 동적 축소 (5 -> 3 -> 1)]
          │
          ▼
    [최종 최적화 프롬프트 완성]
    

    3.1 최근 $N$턴 원문 유지 (Recency Preservation)

    • 최근 6~10턴(기본 권장값 $N=8$)은 절대 압축하지 않고 원문 그대로 전달합니다.
    • 직전 대화에서 주고받은 코드 스니펫, 구체적인 파일 경로, 직전 발화의 뉘앙스를 왜곡 없이 보존하기 위한 필수 장치입니다.

    3.2 삼분법 구조화 요약 (fact / decision / todo)

    $N$턴 이전의 과거 대화는 만연체 일반 텍스트가 아니라, 정보 손실과 왜곡을 막기 위해 엄격한 3가지 스키마로 강제 변환합니다.

    필드 설명 예시
    fact 확인된 환경 정보, 객관적 제약, 사실 관계 "Node.js v20 환경", "PostgreSQL 16 사용 중"
    decision 논의 끝에 결정된 아키텍처, 채택/기각된 기술 스펙 "인증 방식은 JWT 대신 Session 채택", "Redis 캐시는 제외하기로 합의"
    todo 아직 완료되지 않은 잔여 작업 및 후속 확인 항목 "결제 실패 웹훅 멱등성 검증 미구현", "DB 인덱스 부하 테스트 필요"

    자유 양식으로 요약하면 LLM이 중요 제약 사항이나 미완료 태스크를 '자연스러운 문장' 속으로 뭉개버리지만, 이처럼 JSON/YAML 기반 구조화 스키마를 강제하면 핵심 엔티티가 후속 턴까지 손실 없이 전달됩니다.

    3.3 체크섬 기반 캐시와 재압축 깊이 제한

    • 체크섬 요약 캐시 (summary_cache_ttl = 24h):
      대화 블록의 SHA-256 해시를 키로 삼아 요약 결과를 로컬 캐시에 저장합니다. 다음 턴이 진행될 때 이미 요약된 과거 블록을 매번 비싼 LLM API로 다시 요약하는 비용과 지연을 차단합니다.
    • 재압축 깊이 상한 (max_recompression_depth = 2):
      대화가 극단적으로 길어지면 "요약본들을 모아서 다시 요약"하는 2차 압축이 일어납니다. 이때 재압축 깊이를 최대 2회로 엄격히 제한합니다. '말전달 게임(Whisper down the lane)'처럼 요약이 반복될수록 사실 왜곡과 디테일 유실이 누적되는 현상을 방지하기 위함입니다.

    4. 외부 시맨틱 리콜(Semantic Recall)과의 결합

    대화 히스토리를 구조화 압축하여 동적 슬롯에 여유 공간이 생기면, 에이전트의 외부 영구 기억(SQLite / 벡터 DB / Memento)에서 현재 질문과 관련된 장기 기억을 선별 주입합니다.

    • recall_top_k = 5: 질문과 유사도가 높은 상위 5건 검색.
    • importance_threshold = 0.6: 유사도 점수가 기준치에 미치지 못하는 노이즈 항목은 자동 필터링.
    • 동적 다운스케일링: 만약 압축 후에도 현재 턴의 사용자 프롬프트 자체가 거대하여 예산이 빠듯하다면, 리콜 개수를 $5 \to 3 \to 1 \to 0$으로 점진 축소하여 고정 규칙과 최근 대화가 밀려나는 사태를 방지합니다.

    5. 아키텍처 비교 분석

    항목 나이브 슬라이딩 윈도 (FIFO) Claude Code 방식 컨텍스트 예산 & 구조화 압축 외부 메모리 주입 (Memento/RAG)
    관리 방식 오래된 메시지 순차 삭제 while(true) 루프 내 주기적 Compaction 결정론적 사전 예산 공식 + 3분법 요약 하이브리드 검색 기반 온디맨드 리콜
    장점 구현이 매우 단순함 긴 코딩 세션도 단절 없이 지속 비용 예측 가능, 합의·TODO 보존, 캐시 친화적 과거 지식의 영구 보존 및 재사용
    단점/한계 초기 합의 사항·TODO 유실 위험 압축 트리거 턴에서 지연 발생 초기 하네스 파라미터 튜닝 필요 대화 윈도 자체의 실시간 압축은 별도 필요
    적합한 환경 3~5턴 내의 단순 질의응답 단일 CLI 기반 개발 에이전트 프로덕션급 멀티턴 서비스 에이전트 장기 프로젝트 및 멀티 프로젝트 지식 허브

    💻 실무 구현 코드 예시 (TypeScript / Bun)

    아래는 에이전트 하네스에서 턴마다 컨텍스트 예산을 계산하고 분기하는 핵심 로직 스니펫입니다.

    interface ContextBudgetConfig {
      totalWindow: number;       // 예: 128,000 토큰
      systemFixedTokens: number; // 시스템 프롬프트 + 안전 규칙
      toolSchemaTokens: number;  // 활성화된 도구 스키마 토큰 합계
      reservedOutputTokens: number; // 모델 답변 예약 (예: 4,096)
      recentTurnsToKeep: number;    // 원문 유지 턴 수 (기본: 8)
    }
    
    class ContextHarness {
      constructor(private config: ContextBudgetConfig) {}
    
      public calculateAvailableBudget(): number {
        const fixedReserve = 
          this.config.systemFixedTokens + 
          this.config.toolSchemaTokens + 
          this.config.reservedOutputTokens;
    
        const available = this.config.totalWindow - fixedReserve;
        if (available <= 0) {
          throw new Error("설정된 고정 예약 토큰이 전체 컨텍스트 윈도를 초과했습니다.");
        }
        return available;
      }
    
      public prepareContext(history: ChatTurn[], memoryRecallTokens: number): PreparedContext {
        const availableBudget = this.calculateAvailableBudget();
        const recentTurns = history.slice(-this.config.recentTurnsToKeep);
        const pastTurns = history.slice(0, -this.config.recentTurnsToKeep);
    
        const recentTokens = this.estimateTokens(recentTurns);
    
        // 가용 예산 내에 충분히 들어가는 경우
        if (recentTokens + this.estimateTokens(pastTurns) + memoryRecallTokens <= availableBudget) {
          return { turns: history, needsCompression: false };
        }
    
        // 예산 초과: 과거 턴을 fact/decision/todo 구조체로 압축
        const compressedPast = this.structuredCompress(pastTurns);
        return {
          structuredSummary: compressedPast,
          turns: recentTurns,
          needsCompression: true
        };
      }
    
      private structuredCompress(turns: ChatTurn[]): string {
        // 1. 체크섬 캐시 확인 (SHA-256)
        // 2. 캐시 미스 시 프롬프트로 fact, decision, todo 추출 요청
        return "## Context Summary\n- Facts: [...]\n- Decisions: [...]\n- Pending Todos: [...]";
      }
    
      private estimateTokens(turns: ChatTurn[]): number {
        // 경량 토크나이저 계산
        return turns.reduce((acc, t) => acc + t.content.length / 3.5, 0);
      }
    }
    

    💡 엔지니어링 교훈

    1. 프롬프트에 애원하지 말고 코드로 제어하라:
      "이전 규칙을 절대 잊지 마세요"라는 문구를 프롬프트에 추가하는 것은 근본적인 해결책이 되지 못합니다. 결정론적 예산 계산과 스키마 검증기라는 소프트웨어적 하네스(Harness)가 시스템 레벨에서 안전장치를 쥐고 있어야 합니다.

    2. 압축할 때는 서술형보다 구조화(Structured Schema)가 답이다:
      과거 대화를 자유 형식 줄글로 요약하면 회차를 거듭할수록 핵심 수치와 미결 태스크가 흐려집니다. fact, decision, todo처럼 명확한 카테고리로 강제할 때 모델의 정보 보존율이 극대화됩니다.

    3. 체크섬 캐시와 깊이 제한으로 복리 비용을 막아라:
      에이전트가 똑똑해질수록 컨텍스트 관리 자체에 소모되는 LLM 호출 횟수가 늘어납니다. 대화 블록 캐싱과 재압축 상한선을 두어야 장기 실행 환경에서도 지연과 비용의 선형성을 지켜낼 수 있습니다.

    반응형

    'AI' 카테고리의 다른 글

    AI 시대 직업의 미래  (1) 2025.03.18
    AI가 바꿀 미래  (1) 2025.03.17
    AI 미래 예측 2025  (4) 2025.03.16
    최신 AI 산업별 활용법  (2) 2025.03.15
    AI 기술 2025 트렌드  (0) 2025.03.14
Designed by Tistory.