LLM 앱 비용 최적화

LLM 앱은 처음 만들 때보다 운영하면서 더 어려워진다. 프로토타입 단계에서는 모델 API를 한 번 호출하고 응답을 스트리밍하면 그럴듯한 제품처럼 보인다. 하지만 사용자가 늘고, 대화가 길어지고, 도구 호출이 붙고, 내부 문서 검색까지 연결되면 비용 구조가 빠르게 복잡해진다.

문제는 단순히 “비싼 모델을 썼다”가 아니다. 대부분의 비용 낭비는 모델 선택 이전에 생긴다.

  • 같은 시스템 프롬프트를 매번 다시 보낸다.
  • 필요 없는 대화 기록을 계속 포함한다.
  • 쉬운 요청에도 가장 비싼 모델을 호출한다.
  • 실패한 도구 호출을 반복한다.
  • 비동기 처리해도 되는 작업을 실시간 경로에서 처리한다.
  • 비용 로그가 없어 어떤 기능이 돈을 쓰는지 모른다.

LLM 앱의 비용 최적화는 “싼 모델로 바꾸기”가 아니라, 요청이 모델까지 가는 전체 경로를 다시 설계하는 일에 가깝다.

1. 먼저 비용을 기능 단위로 쪼개야 한다

최적화의 출발점은 관측성이다. 많은 팀이 월말 청구서를 보고 나서야 비용 문제를 인식한다. 이 방식으로는 늦다. 어떤 기능, 어떤 사용자 그룹, 어떤 프롬프트, 어떤 도구 호출이 비용을 만드는지 알 수 없기 때문이다.

LLM 호출 로그에는 최소한 다음 필드가 남아야 한다.

  • 기능 이름: 예를 들어 chat, document_summary, code_review, support_triage
  • 모델 이름
  • 입력 토큰 수
  • 출력 토큰 수
  • 캐시 적용 여부
  • 검색 문서 수
  • 도구 호출 횟수
  • 지연시간
  • 실패 여부
  • 사용자 또는 워크스페이스 단위 식별자

여기서 중요한 것은 원문 프롬프트를 무조건 저장하라는 뜻이 아니다. 프롬프트에는 개인정보, 내부 문서, 시크릿이 섞일 수 있다. 운영 로그에는 토큰 수, 비용 추정치, 호출 경로, 익명화된 식별자처럼 비용 분석에 필요한 메타데이터를 우선 남기는 편이 안전하다.

비용을 기능 단위로 쪼개면 판단이 달라진다. 전체 비용이 높다는 말은 막연하지만, “문서 요약 기능이 전체 LLM 비용의 절반을 쓰고 있다”는 말은 액션으로 이어진다.

2. 긴 시스템 프롬프트는 캐싱 대상으로 설계한다

AI 앱은 시스템 프롬프트가 점점 길어진다. 역할 설명, 정책, 출력 형식, 금지 사항, 도구 설명, 예시까지 넣다 보면 사용자 입력보다 시스템 컨텍스트가 더 커지는 경우도 흔하다.

이때 같은 프롬프트 prefix를 매번 다시 보내면 낭비가 생긴다. OpenAI, Anthropic, Google Vertex AI 같은 주요 플랫폼은 모두 어떤 형태로든 prompt caching 또는 context caching 기능을 제공한다. 세부 조건과 과금 방식은 공급자마다 다르지만, 공통된 방향은 같다. 반복되는 긴 컨텍스트를 매번 새로 처리하지 않도록 만드는 것이다.

캐싱을 잘 쓰려면 프롬프트를 다음처럼 나누는 편이 좋다.

[고정 영역]
- 시스템 역할
- 정책
- 출력 스키마
- 도구 설명
- 안정적인 예시

[변동 영역]
- 사용자 질문
- 최근 대화 일부
- 검색 결과
- 현재 작업 상태

고정 영역이 자주 바뀌면 캐시 이점이 줄어든다. 따라서 시스템 프롬프트에 날짜, 임시 상태, 사용자별 값, 요청별 검색 결과를 섞어 넣는 습관은 피해야 한다. 바뀌는 값은 뒤쪽 변동 영역으로 분리하는 것이 낫다.

캐싱은 마법이 아니다. 짧은 프롬프트나 매번 완전히 다른 요청에는 효과가 작다. 하지만 긴 정책 프롬프트, 긴 도구 설명, 반복되는 문서 기반 워크플로우에서는 가장 먼저 검토할 만한 비용 절감 수단이다.

3. 모든 요청에 최고 모델을 쓰지 않는다

LLM 앱에서 가장 흔한 낭비는 라우팅 부재다. 사용자의 모든 요청을 가장 강한 모델로 보내면 구현은 단순하지만 비용 구조는 나빠진다.

요청은 난이도에 따라 나눌 수 있다.

작은 모델로 충분한 요청

  • 문장 분류
  • 태그 추출
  • 짧은 요약
  • 정형 JSON 변환
  • 금칙어/정책 1차 판단
  • FAQ성 질문

큰 모델이 필요한 요청

  • 복잡한 추론
  • 긴 코드 리뷰
  • 여러 문서 간 비교
  • 모호한 요구사항 분석
  • 도구 호출 계획 수립
  • 실패 원인 분석

모델 라우팅은 꼭 복잡한 ML 분류기로 시작할 필요가 없다. 처음에는 규칙 기반으로도 충분하다.

if 입력이 짧고 정형 작업이면 작은 모델
if 첨부 문서가 많거나 코드 diff가 크면 큰 모델
if 작은 모델의 confidence가 낮으면 큰 모델로 승격
if 사용자가 명시적으로 고품질 모드를 선택하면 큰 모델

이 구조의 장점은 비용뿐 아니라 안정성이다. 쉬운 작업을 작은 모델이 처리하면 큰 모델은 정말 어려운 작업에 집중할 수 있다. 반대로 모든 요청을 작은 모델로 보내는 것도 위험하다. 비용은 줄어들지만 실패율이 올라가고, 사람이 다시 처리해야 하는 비용이 생긴다.

좋은 라우팅은 “싼 모델 우선”이 아니라 “요청 난이도에 맞는 모델 선택”이다.

4. 컨텍스트를 많이 넣는 것보다 잘라내는 능력이 중요하다

긴 컨텍스트 모델이 등장하면서 많은 앱이 컨텍스트 관리를 느슨하게 한다. 하지만 컨텍스트 창이 커졌다고 해서 모든 대화 기록과 모든 문서를 넣어도 된다는 뜻은 아니다. 긴 컨텍스트는 비용, 지연시간, 품질 문제를 동시에 만든다.

문제는 세 가지다.

  • 입력 토큰이 늘면 비용이 증가한다.
  • 모델이 중요하지 않은 정보에 주의를 빼앗길 수 있다.
  • 오래된 대화나 문서가 현재 요청과 충돌할 수 있다.

컨텍스트 절약의 기본 원칙은 다음이다.

  1. 최근 대화 전체가 아니라 현재 요청에 필요한 부분만 넣는다.
  2. 오래된 대화는 요약본으로 대체한다.
  3. 검색 결과는 상위 몇 개 문서로 제한한다.
  4. 문서 전체가 아니라 관련 문단 중심으로 넣는다.
  5. 모델에게 필요 없는 UI 상태, 내부 ID, 중복 메타데이터를 제거한다.

RAG도 같은 관점에서 봐야 한다. RAG의 목적은 단순히 문서를 많이 넣는 것이 아니다. 필요한 근거를 작고 정확하게 넣는 것이다. 검색 결과 20개를 통째로 넣는 RAG는 비용도 높고 품질도 불안정하다.

좋은 LLM 앱은 컨텍스트를 크게 만드는 앱이 아니라, 컨텍스트를 선별하는 앱이다.

5. 자주 반복되는 결과는 응답 캐시를 쓴다

모델 호출 캐싱과 별도로, 애플리케이션 레벨의 응답 캐시도 중요하다. 모든 요청이 생성형일 필요는 없다. 같은 입력에 대해 거의 같은 답을 돌려줘도 되는 기능이 많다.

예를 들면 다음과 같다.

  • 릴리스 노트 요약
  • 문서 섹션 요약
  • FAQ 답변
  • 코드 스타일 설명
  • 정책 문서 기반 안내
  • 고정 데이터에 대한 질의응답

응답 캐시를 쓸 때는 캐시 키 설계가 중요하다.

cache_key = hash(
  model_name,
  prompt_version,
  normalized_user_input,
  document_version,
  output_schema_version
)

프롬프트 버전과 문서 버전을 포함하지 않으면 오래된 답변을 계속 재사용할 수 있다. 반대로 사용자별 개인정보가 들어간 요청을 전역 캐시에 넣으면 보안 문제가 생길 수 있다.

응답 캐시는 특히 문서 요약, 정책 안내, 내부 지식 검색처럼 입력 데이터가 자주 바뀌지 않는 기능에서 효과가 크다. 실시간 채팅의 모든 턴을 캐시하려 하기보다, 반복성이 높은 하위 기능부터 캐시하는 편이 안전하다.

6. 실시간이 필요 없는 작업은 배치로 보낸다

모든 LLM 작업이 즉시 응답을 요구하지는 않는다. 사용자가 기다리고 있는 채팅 응답과, 백그라운드에서 처리해도 되는 작업은 분리해야 한다.

실시간 경로에 적합한 작업:

  • 사용자가 보고 있는 채팅 응답
  • 인터랙티브 코드 설명
  • 즉시 필요한 분류/추천

배치 처리에 적합한 작업:

  • 대량 문서 요약
  • 로그 후처리
  • 야간 리포트 생성
  • 긴 데이터셋 라벨링
  • 여러 PR/이슈의 주기적 분석

OpenAI Batch API처럼 일부 플랫폼은 비동기 대량 처리 경로를 별도로 제공한다. 플랫폼마다 할인율, 처리 시간, 지원 모델은 다르기 때문에 세부 조건은 공식 문서를 확인해야 한다. 중요한 설계 원칙은 명확하다. 사용자가 기다리지 않아도 되는 작업을 실시간 고비용 경로에 태우지 않는 것이다.

이 분리만 해도 제품 경험이 좋아진다. 사용자가 보는 응답은 빠르게 유지하고, 무거운 분석은 백그라운드에서 처리할 수 있다.

7. 도구 호출과 재시도에도 예산을 둔다

에이전트형 LLM 앱은 모델 호출만 비용을 쓰지 않는다. 도구 호출도 비용을 만든다. 검색 API, 벡터 DB, 코드 실행 샌드박스, 브라우저 자동화, 외부 SaaS API가 모두 비용과 지연시간을 만든다.

특히 위험한 패턴은 무제한 재시도다.

모델이 도구 호출을 잘못 계획한다
→ 도구가 실패한다
→ 모델이 다시 시도한다
→ 비슷한 실패가 반복된다
→ 모델 토큰과 외부 API 비용이 함께 증가한다

에이전트에는 예산이 필요하다.

  • 한 요청당 최대 모델 호출 횟수
  • 한 요청당 최대 도구 호출 횟수
  • 검색 결과 최대 개수
  • 코드 실행 최대 시간
  • 재시도 최대 횟수
  • 사용자 승인 없이 실행 가능한 비용 한도

예산은 제품 품질을 낮추기 위한 장치가 아니다. 오히려 실패를 빨리 드러내는 장치다. 모델이 세 번 시도해도 같은 도구 호출에 실패한다면, 네 번째 호출이 문제를 해결할 가능성은 낮다. 이때는 사용자에게 실패 이유를 보여주거나, 다른 경로로 전환하는 편이 낫다.

비용 최적화가 품질을 망치지 않게 하려면

비용을 줄이겠다고 무조건 작은 모델, 짧은 컨텍스트, 강한 캐시를 적용하면 품질이 망가질 수 있다. 그래서 비용 최적화는 항상 품질 지표와 함께 봐야 한다.

함께 측정할 지표는 다음이다.

  • 작업 성공률
  • 사용자 재시도율
  • 답변 수정 요청 비율
  • hallucination 신고율
  • 평균 지연시간
  • p95 지연시간
  • 기능별 비용
  • 사용자당 비용

비용만 보면 작은 모델이 좋아 보일 수 있다. 하지만 작은 모델이 실패해서 사용자가 같은 요청을 세 번 반복한다면 실제 비용은 더 커질 수 있다. 반대로 큰 모델을 한 번 호출해서 정확히 끝내는 편이 더 쌀 수도 있다.

LLM 비용 최적화의 목표는 최저 단가가 아니라, 성공한 작업 하나당 비용을 낮추는 것이다.

실무 체크리스트

LLM 앱을 운영한다면 다음 순서로 점검하는 것이 좋다.

  1. 기능별 토큰 사용량과 비용을 로깅하고 있는가?
  2. 고정 시스템 프롬프트와 변동 컨텍스트가 분리되어 있는가?
  3. prompt caching 또는 context caching을 적용할 수 있는 반복 prefix가 있는가?
  4. 쉬운 작업과 어려운 작업을 다른 모델로 라우팅하고 있는가?
  5. 대화 기록과 검색 결과를 무제한으로 넣고 있지 않은가?
  6. 반복 가능한 결과에 애플리케이션 응답 캐시를 쓰고 있는가?
  7. 비동기 처리 가능한 작업을 실시간 경로에서 분리했는가?
  8. 도구 호출, 재시도, 검색 개수에 예산이 있는가?
  9. 비용 절감 후 품질 지표가 함께 유지되는가?

결론

LLM 앱의 비용 문제는 사용량이 늘어난 뒤에 갑자기 보인다. 하지만 원인은 대부분 초기에 들어간 단순한 설계에 있다. 모든 요청을 같은 모델로 보내고, 모든 컨텍스트를 넣고, 모든 작업을 실시간으로 처리하면 비용은 예측하기 어렵게 커진다.

좋은 구조는 반대다.

  • 반복되는 프롬프트는 캐싱한다.
  • 쉬운 작업과 어려운 작업을 나눈다.
  • 컨텍스트를 선별한다.
  • 반복 결과는 재사용한다.
  • 무거운 작업은 배치로 보낸다.
  • 도구 호출에는 예산을 둔다.
  • 비용과 품질을 같이 측정한다.

결국 LLM 앱 비용 최적화는 모델 가격표를 보는 일이 아니라, 제품의 실행 경로를 설계하는 일이다. 모델은 비싸질 수도 싸질 수도 있지만, 낭비가 많은 구조는 어떤 가격표에서도 오래 버티기 어렵다.

참고