에이전틱스
도메인 2. 모델 선택과 프롬프트·컨텍스트 엔지니어링

2.4컨텍스트 윈도우 최적화와 토큰 예산

예상 학습 시간 40

긴 컨텍스트를 지원하는 모델에서도 토큰은 여전히 배분해야 하는 자원입니다. 이 수업에서는 토큰 예산을 설계 제약으로 다루는 방법, 컨텍스트를 줄이는 세 가지 서로 다른 수단의 차이, 멀티 에이전트에서의 컨텍스트 격리, 그리고 측정 없이 최적화하지 않는 원칙을 다룹니다.

예산은 상한이 아니라 배분이다

컨텍스트 윈도우를 "넘지 않으면 되는 상한"으로 보면 설계가 늦어집니다. 상한에 부딪히는 시점은 이미 시스템이 오래 돌아간 뒤이고, 그때 할 수 있는 일은 급하게 잘라내는 것뿐입니다.

아키텍트는 처음부터 예산을 배분 문제로 놓아야 합니다. 하나의 요청에서 토큰을 쓰는 주체는 대략 다음과 같습니다.

항목성격통제 수단
시스템 프롬프트고정계층 정리, 중복 제거
툴 정의고정툴 수 축소, 지연 로딩
대화 이력누적편집, 압축
툴 결과누적, 예측 불가결과 크기 제한, 외부화
검색 결과요청별청크 수와 크기 조절
출력·사고요청별출력 예산, 사고 강도

이 표에서 가장 위험한 줄은 툴 결과입니다. 시스템 프롬프트는 길어도 예측 가능하지만, 툴 결과는 조회 대상에 따라 수십 배로 뛸 수 있습니다. 로그 조회 툴 하나가 컨텍스트를 통째로 삼키는 사고는 흔합니다.

대응은 툴 쪽에서 합니다.

{
    "name": "query_logs",
    "description": (
        "로그를 조회합니다. 결과는 최대 50행으로 잘리며, "
        "잘린 경우 total_count와 함께 안내가 포함됩니다. "
        "범위를 좁히려면 service와 time_range를 지정하십시오."
    ),
    "input_schema": {
        "type": "object",
        "properties": {
            "service": {"type": "string"},
            "time_range": {"type": "string", "description": "예: 15m, 1h"},
            "limit": {"type": "integer", "description": "기본 50, 최대 200"},
        },
        "required": ["service", "time_range"],
    },
}

핵심은 상한을 툴 구현에 두고, 그 사실을 설명에 적는 것입니다. 상한만 있고 설명이 없으면 모델은 결과가 전부라고 믿고 잘못된 결론을 냅니다. 잘렸다는 사실과 전체 건수를 함께 돌려주면 모델이 범위를 좁혀 다시 조회합니다.

또 하나 자주 놓치는 항목이 사고 토큰입니다. 사고가 켜져 있으면 출력 예산을 사고와 응답이 나눠 씁니다. 예산이 빠듯하면 사고가 대부분을 쓰고 답변이 잘립니다. 사고를 켤 때는 출력 예산을 함께 올려야 합니다.

줄이는 수단은 셋이고, 서로 다르다

컨텍스트를 줄이는 방법을 하나로 뭉뚱그리면 잘못된 선택을 합니다. 세 가지는 목적과 손실 방식이 다릅니다.

1. 편집(context editing) — 지운다

오래된 툴 결과나 사고 블록을 대화에서 제거합니다. 요약하지 않고 삭제하므로 그 정보는 완전히 사라집니다. 이미 반영된 중간 결과를 치우는 데 적합합니다.

2. 압축(compaction) — 요약한다

이전 맥락을 요약본으로 대체합니다. 대화의 흐름은 유지되지만 세부는 사라집니다. 여기서 중요한 함정이 있습니다. 압축 결과를 다음 요청에 다시 넣어야 합니다. 응답의 본문 텍스트만 뽑아 이력에 넣는 구현은 압축 상태를 잃어버리고, 다음 요청에서 압축이 처음부터 다시 일어납니다.

# 안티패턴: 텍스트만 뽑아 넣으면 압축 블록이 사라진다
messages.append({"role": "assistant", "content": response.content[0].text})

# 올바른 방식: 응답 콘텐츠 전체를 그대로 넣는다
messages.append({"role": "assistant", "content": response.content})

3. 외부화 — 컨텍스트 밖에 둔다

정보를 파일이나 저장소에 두고, 필요할 때 읽어 오게 합니다. 컨텍스트에는 "어디에 무엇이 있다"는 색인만 남습니다. 장기 실행 에이전트에서 가장 확장성이 좋은 방식이며, 세션이 끝나도 정보가 남는다는 부수 효과가 있습니다.

셋의 선택 기준은 그 정보가 나중에 다시 필요한가입니다.

  • 다시 볼 일 없다 → 편집
  • 요지만 있으면 된다 → 압축
  • 나중에 정확히 다시 봐야 한다 → 외부화

압축을 반복하면 요약의 요약이 쌓여 원래 사실에서 멀어집니다. 정확도가 중요한 정보는 압축에 맡기지 말고 외부화하는 편이 안전합니다.

멀티 에이전트는 컨텍스트 격리 장치다

서브에이전트를 여러 목적으로 씁니다. 아키텍처 관점에서 가장 중요한 효용은 병렬성이 아니라 컨텍스트 격리입니다.

읽기 위주의 작업은 입력 토큰을 대량으로 소모합니다. 문서 스무 건을 조사해야 하는 작업을 한 컨텍스트에서 처리하면, 스무 건의 원문이 모두 쌓인 채로 이후 모든 턴이 진행됩니다. 같은 작업을 서브에이전트에 맡기면 각자의 컨텍스트에서 원문을 읽고 결론만 돌려줍니다. 오케스트레이터의 컨텍스트에는 요약된 보고만 남습니다.

이 구조가 성립하려면 위임하는 쪽이 지켜야 할 규칙이 있습니다.

  • 작업을 자기완결적으로 넘깁니다. 서브에이전트는 오케스트레이터의 대화 이력을 보지 못합니다. 필요한 경로, 제약, 보고 형식을 지시 안에 모두 담아야 합니다.
  • 보고 형식을 지정합니다. 형식을 정하지 않으면 원문을 그대로 돌려주는 경우가 생겨 격리 효과가 사라집니다.
  • 공유 지점은 파일로 둡니다. 서브에이전트끼리는 대화 이력을 공유하지 않지만 파일 시스템은 공유할 수 있습니다. 중간 산출물은 파일로 주고받는 편이 안정적입니다.

위임에도 비용이 있습니다. 서브에이전트마다 맥락을 다시 세우고, 보고를 만들고, 그 보고를 오케스트레이터가 다시 읽습니다. 한두 번의 툴 호출로 끝날 일을 위임하면 순손실입니다. 위임은 읽을 양이 많거나 갈래가 독립적일 때 값어치를 합니다.

모델 세대에 따라 위임 성향이 다르다는 점도 알아둘 만합니다. 어떤 모델은 위임을 지나치게 자주 하고, 어떤 모델은 지시하지 않으면 거의 하지 않습니다. 그래서 위임 기준은 시스템 프롬프트에 명시적으로 적어야 하며, 모델을 바꿀 때 이 지시도 함께 재검토해야 합니다.

측정 없이 최적화하지 않는다

컨텍스트 최적화는 체감으로 판단하기 어려운 영역입니다. 비용은 청구서에 뒤늦게 나타나고, 품질 저하는 특정 사례에서만 드러납니다.

요청 전에 세는 방법요청 후에 확인하는 방법을 모두 갖춰야 합니다.

# 보내기 전: 예산 초과 여부를 미리 확인
count = client.messages.count_tokens(
    model=MODEL_ID,
    system=system_blocks,
    tools=tools,
    messages=messages,
)
if count.input_tokens > BUDGET:
    messages = shrink(messages)

# 보낸 뒤: 실제 구성 확인
u = response.usage
print(u.input_tokens, u.cache_read_input_tokens, u.output_tokens)

여기서 자주 오해하는 것이 input_tokens의 의미입니다. 이 값은 캐시되지 않은 나머지입니다. 전체 프롬프트 크기는 캐시 생성분과 읽기분까지 더해야 나옵니다. 에이전트가 오래 돌았는데 input_tokens가 작게 나온다면 나머지가 캐시에서 읽혔다는 뜻이지, 컨텍스트가 작다는 뜻이 아닙니다.

운영에서 볼 지표는 최소한 다음 네 가지입니다.

  1. 요청당 입력 토큰의 분포. 평균이 아니라 분포를 봐야 합니다. 상위 5%가 예산을 태우는 경우가 흔합니다.
  2. 캐시 읽기 비율. 떨어졌다면 프롬프트 어딘가에 무효화 요인이 생긴 것입니다.
  3. 세션 길이 대비 토큰 증가율. 선형을 넘어 증가한다면 이력 관리가 동작하지 않는 것입니다.
  4. 압축·편집 발생 빈도. 너무 자주 일어난다면 애초에 컨텍스트에 들어가는 양이 과합니다.

한 가지 주의할 점이 있습니다. 남은 토큰 수를 모델에게 보여주는 것은 신중해야 합니다. 잔여 예산이 컨텍스트에 노출되면 모델이 작업을 서둘러 마무리하거나 새 세션을 제안하는 행동을 보일 수 있습니다. 예산 관리는 시스템이 하고, 모델에게는 필요한 지시만 전달하는 편이 안전합니다.

시험 함정

  • 압축 결과에서 본문 텍스트만 뽑아 이력에 넣는 구현 — 압축 상태가 사라져 다음 요청에서 처음부터 다시 압축됩니다.
  • 편집과 압축을 같은 기능으로 보는 선택지 — 편집은 삭제하고 압축은 요약합니다.
  • input_tokens를 전체 프롬프트 크기로 해석하는 서술 — 그 값은 캐시되지 않은 나머지입니다.
  • 툴 결과에 상한만 두고 잘렸다는 사실을 알리지 않는 설계 — 모델이 부분 결과를 전체로 오해합니다.
  • "위임하면 총 토큰이 항상 줄어든다"는 선택지 — 맥락 재수립과 보고 왕복 비용이 더해집니다.
  • 잔여 토큰 수를 모델 컨텍스트에 노출하는 구성 — 조기 종료나 세션 분할 제안을 유발할 수 있습니다.

실습 시나리오

실제 시험과 같은 형식의 시나리오 문제입니다.

장시간 실행되는 조사 에이전트가 세션 후반부터 초반에 확인한 수치를 잘못 인용하기 시작했습니다. 컨텍스트 압축은 정상 동작하고 있으며, 오류는 항상 압축이 일어난 뒤 구간에서 발생합니다. 아키텍트가 취할 조치로 가장 적절한 것은 무엇입니까?

빌드 연습 · 장기 실행 에이전트의 토큰 예산 배분 설계하기

50
  1. 1.구성 요소별 계측

    실제 세션 하나를 골라 시스템 프롬프트, 툴 정의, 이력, 툴 결과가 각각 몇 토큰을 쓰는지 측정합니다.

    기대 결과 · 항목별 토큰 비중이 표로 정리되고 가장 큰 소비처가 드러납니다.

  2. 2.툴 결과 상한 도입

    가장 큰 툴 결과에 행 수 상한을 걸고, 잘렸다는 사실과 전체 건수를 함께 반환하도록 고칩니다.

    기대 결과 · 상한 초과 시 모델이 범위를 좁혀 재조회하는 동작이 관찰됩니다.

  3. 3.세 수단 배치

    이력의 각 유형을 편집·압축·외부화 중 하나로 분류하고 근거를 적습니다.

    기대 결과 · 다시 정확히 봐야 하는 정보가 외부화로 분류되어 있습니다.

  4. 4.증가율 관찰

    동일 작업을 20턴 이상 진행하며 턴마다 입력 토큰을 기록해 증가 곡선을 그립니다.

    기대 결과 · 선형을 크게 벗어나는 구간이 있는지 확인되고, 있다면 원인 항목이 지목됩니다.

  5. 5.격리 효과 비교

    읽기 위주 작업 하나를 서브에이전트로 옮기고 오케스트레이터의 토큰 증가율을 다시 측정합니다.

    기대 결과 · 위임 전후의 총 토큰과 오케스트레이터 컨텍스트 크기가 함께 비교됩니다.

출처 및 더 읽기