에이전틱스
도메인 5. 모델 선택과 최적화

5.4비용과 토큰 관리

예상 학습 시간 25

비용은 대개 조용히 늘어납니다. 이 수업에서는 사용량을 추적하는 방법과, 캐싱·배치처럼 구조로 비용을 낮추는 수단을 다룹니다.

무엇을 세어야 하는가

응답마다 사용량 정보가 함께 옵니다. 최소한 이 값들을 기록해야 나중에 원인을 찾을 수 있습니다.

  • 입력 토큰과 출력 토큰
  • 캐시에 쓰인 토큰과 캐시에서 읽은 토큰
  • 어느 모델이 답했는지
  • 어느 기능·어느 사용자의 요청이었는지
log.info("usage", extra={
    "feature": "summarize",
    "model": resp.model,
    "input": resp.usage.input_tokens,
    "output": resp.usage.output_tokens,
    "cache_read": getattr(resp.usage, "cache_read_input_tokens", 0),
})

기능별로 나눠 기록하는 것이 중요합니다. 총액만 보면 어디서 늘었는지 알 수 없습니다. 기능 단위로 보면 대개 한두 곳이 대부분을 쓰고 있고, 거기만 손봐도 큰 폭이 줄어듭니다.

출력 단가가 입력보다 높은 것이 일반적이므로, 같은 토큰 수라도 출력이 비용에 미치는 영향이 큽니다. 불필요하게 긴 답을 요구하고 있지 않은지 보는 것이 첫 점검 항목입니다.

캐싱으로 줄이기

반복되는 앞부분이 있으면 캐싱이 가장 효과가 큽니다. 긴 지침이나 참고 문서를 매 요청에 보내는 구조라면 대부분 여기서 줄어듭니다.

조건은 앞에서부터 같아야 한다는 것입니다. 그래서 배치 순서를 정하는 것이 곧 캐시 설계입니다.

[ 잘 안 바뀌는 것 ]  ← 시스템 지침, 도구 정의, 참고 문서
[ 가끔 바뀌는 것 ]  ← 사용자별 설정
[ 매번 바뀌는 것 ]  ← 이번 질문

캐시가 무효화되는 흔한 경우를 알아 두면 사고를 줄일 수 있습니다.

  • 앞부분에 시각이나 요청 식별자를 넣는 경우
  • 도구 정의를 바꾸는 경우 — 그 뒤가 전부 무효가 됩니다
  • 모델 버전이 바뀌는 경우

적용했으면 반드시 확인해야 합니다. 캐시 읽기 토큰이 실제로 잡히는지 보지 않으면, 설정만 있고 효과는 없는 상태로 오래 갑니다.

구조로 줄이기

설정보다 구조가 더 크게 줄이는 경우가 많습니다.

배치로 옮기기 — 급하지 않은 작업을 실시간 호출에서 배치로 옮기면 크게 저렴해집니다. 야간 처리나 사후 분석이 대상입니다.

도구 결과 다듬기 — 앞서 본 것처럼 컨텍스트를 가장 빨리 채우는 것이 도구 결과입니다. 필요한 필드만 남기면 이후 모든 턴의 입력이 줄어듭니다.

이력 압축 — 긴 대화에서 오래된 부분을 요약합니다.

등급 낮추기 — 단순한 작업에 과한 등급을 쓰고 있지 않은지 봅니다.

출력 길이 제한 — 필요 이상으로 긴 답을 요구하고 있지 않은지 봅니다. 형식을 지정하면 자연히 짧아지는 경우가 많습니다.

이 중 무엇을 먼저 할지는 어디서 토큰이 나가는지 재 보면 정해집니다. 입력이 압도적이면 캐싱과 도구 결과 다듬기가 답이고, 출력이 크면 형식과 길이를 봐야 합니다. 재지 않고 손대면 효과가 없는 곳을 고치게 됩니다.

예산과 상한

줄이는 것과 별개로 폭주를 막는 장치가 필요합니다. 에이전트처럼 루프를 도는 구조에서는 한 번의 실패가 큰 비용으로 이어질 수 있습니다.

  • 호출 단위 상한max_tokens로 한 응답의 길이를 제한합니다.
  • 루프 단위 상한 — 반복 횟수와 누적 토큰에 한도를 둡니다.
  • 기능 단위 예산 — 하루 또는 시간당 사용량이 기준을 넘으면 경고하거나 차단합니다.
class Budget:
    def __init__(self, max_tokens: int):
        self.max, self.used = max_tokens, 0
    def add(self, usage) -> None:
        self.used += usage.input_tokens + usage.output_tokens
        if self.used > self.max:
            raise BudgetExceeded(f"예산 {self.max} 초과: {self.used}")

상한에 걸렸을 때 조용히 멈추지 않는 것이 중요합니다. 사용자에게는 절반만 끝난 결과가 완성본으로 보일 수 있습니다. 중단 사유를 알리고 지금까지의 결과를 함께 넘겨야 합니다.

시험 함정

  • 총 사용량만 기록하고 기능별로 나누지 않는 관리 — 어디서 늘었는지 알 수 없습니다.
  • 입력과 출력 토큰의 비용 영향이 같다고 보는 서술 — 출력 단가가 대체로 더 높습니다.
  • 캐싱을 설정하면 효과를 확인하지 않아도 된다는 접근 — 캐시 읽기 토큰으로 검증해야 합니다.
  • 도구 정의를 바꿔도 캐시에 영향이 없다는 서술 — 그 뒤의 접두어가 전부 무효가 됩니다.
  • 어디서 토큰이 나가는지 재지 않고 최적화를 시작하는 순서 — 효과 없는 곳을 고치게 됩니다.
  • 예산 상한에 걸렸을 때 조용히 중단하는 처리 — 절반짜리 결과가 완성본으로 오해됩니다.

실습 시나리오

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

문서 질의응답 기능의 비용이 예산을 넘었습니다. 계측해 보니 입력 토큰이 출력의 20배이고, 매 요청에 같은 정책 문서 3만 토큰이 실려 갑니다. 가장 효과가 큰 조치는 무엇입니까?

빌드 연습 · 사용량을 계측하고 가장 큰 항목부터 줄이기

30
  1. 1.기능별 계측

    요청마다 기능 이름과 입력·출력 토큰을 기록합니다.

    기대 결과 · 기능별 합계가 표로 나옵니다.

  2. 2.비중 확인

    입력과 출력 중 어느 쪽이 큰지, 어느 기능이 대부분인지 봅니다.

    기대 결과 · 상위 한두 기능이 식별됩니다.

  3. 3.캐싱 적용

    반복되는 앞부분을 고정 배치하고 캐시를 켭니다.

    기대 결과 · 두 번째 호출부터 캐시 읽기 토큰이 잡힙니다.

  4. 4.효과 측정

    적용 전후의 건당 입력 토큰을 비교합니다.

    기대 결과 · 감소폭이 수치로 남습니다.

  5. 5.예산 장치

    누적 토큰 상한을 두고 초과 시 사유와 함께 중단합니다.

    기대 결과 · 중단 시 진행 상황이 함께 보고됩니다.

출처 및 더 읽기