Source: https://aiprimetech.io/ko/blog/reduce-llm-token-usage/ 2026년 20일, 유월 · 7분 · API # 품질 저하 없이 토큰 사용량을 줄이는 엔지니어링 A 작성자 [AI Prime Tech](https://aiprimetech.io/ko/) · AI 활용 지난달에 검토한 고객 지원 에이전트는 단 한 단어를 작성하기도 전에 티켓당 입력 토큰을 18,000~42,000개씩 사용하고 있었습니다. 실제 사용자 문제는 대개 300토큰 미만이었습니다. 나머지는 모두 ‘혹시 몰라서’ 넣은 컨텍스트였습니다. 전체 대화 기록, 키워드 검색으로 가져온 모든 내부 정책 페이지, 그대로 복사한 도구 결과 JSON개, 그리고 여섯 달 동안 내용이 계속 덧붙여진 900토큰짜리 시스템 프롬프트였습니다. 해결책은 ‘더 작은 모델을 사용하자’거나 ‘다음을 낮추자’가 아니었습니다. max_tokens 그리고 잘되기를 바라자는 식도 아니었습니다. 컨텍스트 정리, 더 좁은 범위의 근거 검색, 상태 요약, 출력 스키마 강제 적용, 정확한 토큰 측정이라는 평범한 엔지니어링 작업으로 답변 품질을 안정적으로 유지하면서 토큰 사용량 중앙값을 약 68% 줄였습니다. 이 글은 그런 작업을 다룹니다. 프롬프트를 극단적으로 줄이는 기법도, 모델 성능을 떨어뜨리는 요령도 아닙니다. 시스템을 덜 똑똑하게 만들지 않으면서 토큰을 적게 쓰는 실용적인 방법입니다. ## 토큰 사용량은 아키텍처 문제입니다 대부분의 팀은 토큰 비용을 모델 가격의 문제로 봅니다. 하지만 실제로는 대개 정보 라우팅의 문제입니다. 일반적인 운영 환경의 LLM 호출에는 네 가지 토큰 범주가 있습니다. - 지시 토큰: 시스템 프롬프트, 개발자 프롬프트, 형식 규칙, 안전 제약 조건. - 컨텍스트 토큰: 채팅 기록, 검색된 문서, 도구 출력, 데이터베이스 레코드. - 추론/작업 토큰: 실제 사용자 요청과 중간 작업 세부 정보. - 출력 토큰: 생성된 응답, JSON 객체, SQL 쿼리, 요약 또는 도구 호출. 토큰 사용량을 통제하기 어려워지면 대개 컨텍스트와 출력이 원인입니다. 사용자가 어려운 질문을 해서 모델 비용이 높은 것이 아닙니다. 애플리케이션이 온갖 정보를 프롬프트에 밀어 넣고 모델이 장황하게 답하도록 허용하기 때문에 비용이 높아지는 것입니다. 유용한 첫 단계는 전체 요청 크기뿐 아니라 섹션별 토큰 사용량을 기록하는 것입니다. ``` { "request_id": "ticket_81291", "model": "sonnet-4.6", "tokens": { "system": 742, "history": 3890, "retrieval": 12640, "tool_results": 2110, "user": 184, "output": 931 }, "latency_ms": 4820 } ``` 이 내역을 보면 최적화 방향이 분명해집니다. Claude Opus 4.8, Sonnet 4.6, GPT-5.5, Gemini 3, Fable 5, 또는 Qwen이나 DeepSeek 같은 개방형 모델 중 무엇이 추상적으로 ‘최고’인지 논쟁할 필요는 없습니다. 먼저 관련 없는 검색 결과 토큰 12,000개를 보내는 일을 멈춰야 합니다. ## 경로별 토큰 예산부터 설정하세요 저는 전역 예산보다 제품 경로별로 예산을 할당하는 방식을 선호합니다. 법률 계약 검토, 코딩 에이전트, FAQ 봇에 동일한 토큰 가정을 적용해서는 안 됩니다. 지원 어시스턴트의 예산 예시: | 이용 경로 | 입력 예산 | 출력 예산 | 모델 등급 | 참고 사항 | 티켓 분류 | 600 | 50 | 저렴하고 빠른 모델 | 라벨, 긴급도, 언어 | 근거 검색 | 1,500 | 0 | 임베딩/검색 | 생성 불필요 | 답변 초안 작성 | 4,000 | 600 | 성능이 뛰어난 중급/고급 모델 | 선별된 근거 사용 | 대화 스레드 요약 | 1,000 | 250 | 저렴하고 빠른 모델 | 메모리 상태 갱신 | 상위 담당자 이관 분석 | 6,000 | 900 | 성능이 뛰어난 모델 | 복잡한 사례에만 사용 이 수치가 어디서나 맞는다는 뜻은 아닙니다. 의도적으로 작게 잡은 초기 제약 조건입니다. 중요한 것은 모든 단계가 자신이 사용하는 토큰의 필요성을 입증하도록 하는 것입니다. 흔히 놓치는 점이 있습니다. 다음만 설정하면 max_tokens 출력에 대해서만 설정하면 입력은 여전히 눈에 띄지 않게 불어날 수 있습니다. 출력 제한은 긴 답변을 막을 뿐, 검색 결과를 과도하게 넣는 것은 막지 못합니다. ## 검색 후가 아니라 검색 전에 컨텍스트를 정리하세요 많은 시스템이 최신 사용자 메시지로 문서를 검색하면서도 최종 프롬프트에는 전체 대화 기록을 포함합니다. 그러면 오래된 곁가지 이야기, 이전에 실패한 도구 호출, 더 이상 유효하지 않은 가정이 컨텍스트에 남게 됩니다. 대신 간결한 작업 상태를 유지하세요. ``` { "user_goal": "Get refund for duplicate annual subscription charge", "known_facts": [ "User paid twice on 2025-01-08", "Invoice IDs: inv_1042 and inv_1077", "Account email: user@example.com" ], "constraints": [ "Refund policy allows duplicate-charge refunds within 90 days", "User prefers email follow-up" ], "open_questions": [ "Whether one charge has already been reversed" ], "last_updated_turn": 12 } ``` 그러면 최종 호출에는 다음이 포함됩니다. - 간결한 상태. - 최신 사용자 메시지. - 검색된 정책 중 관련 있는 발췌문만. - 어조의 연속성이 중요하다면 최근 한두 차례의 대화도 포함할 수 있습니다. 이전 메시지를 모두 무조건 포함하지 마세요. 채팅 기록은 메모리가 아니라 대화록입니다. 메모리는 작업을 이어가는 데 필요한 정보를 추려낸 상태입니다. 실제로 저는 세 가지 계층을 사용합니다. - 최근 대화: 작업의 민감도에 따라 최근 1~4차례의 대화. - 누적 요약: 변하지 않는 사실과 결정 사항. - 외부 검색: 문서, 데이터베이스 레코드, 도구 출력. 이 방식은 대개 원본 대화록을 그대로 밀어 넣는 것보다 낫습니다. 모델에 노이즈와 서로 충돌하는 지시가 덜 전달되기 때문입니다. ## 검색은 장식이 아니라 선별 과정이어야 합니다 검색 증강 생성은 품질 개선을 위해 시작했다가 토큰 낭비의 원인이 되는 경우가 많습니다. 이런 실패 양상은 쉽게 알아볼 수 있습니다. top-k는 10 또는 20로 설정되어 있고, 청크는 각각 1,000토큰이며, 최종 프롬프트에는 어렴풋이 관련된 문서가 잔뜩 들어 있습니다. 더 나은 검색은 대개 비용도 더 적게 듭니다. 실용적인 검색 파이프라인: ``` def build_context(query, user_state): search_query = rewrite_for_search(query, user_state) candidates = vector_search( query=search_query, top_k=30, filters={"product": user_state["product"]} ) reranked = rerank_cross_encoder( query=search_query, docs=candidates, top_k=6 ) selected = [] token_budget = 1800 for doc in reranked: snippet = extract_relevant_span(doc.text, query=search_query, max_tokens=300) if token_count(selected) + token_count(snippet) <= token_budget: selected.append({ "doc_id": doc.id, "title": doc.title, "text": snippet }) return selected ``` 중요한 세부 사항: - 포함할 계획보다 더 많은 후보를 검색하세요. - 생성 전에 순위를 재조정하세요. - 전체 청크를 전달하는 대신 관련 구간을 추출하세요. - 검색 결과에 토큰 예산을 엄격히 적용하세요. - 모델이 내부적으로 근거를 인용하거나 참조할 수 있도록 문서 ID를 유지하세요. 청크 크기는 중요합니다. 청크가 너무 작으면 모델이 맥락을 잃습니다. 너무 크면 일치하는 항목마다 관련 없는 텍스트까지 따라옵니다. 제품 문서와 정책 페이지의 경우 저는 보통 제목을 보존하면서 청크당 약 300~600토큰으로 시작한 뒤, 실패 사례를 바탕으로 조정합니다. 검색 컨텍스트를 너무 많이 넣으면 실제로 어떤 일이 벌어질까요? 모델의 정확성이 떨어지는 경우가 많습니다. 비슷하지만 관련 없는 정책을 서로 맞추려 하거나, 더 이상 유효하지 않은 주의 사항을 반영하거나, 질문을 다른 의미로 해석해 답합니다. 토큰을 줄이면 주의를 분산시키는 요소가 사라져 품질이 향상될 수 있습니다. ## 요약은 책임이 따르는 압축입니다 요약은 컨텍스트를 크게 줄일 수 있지만, 부실한 요약은 위험합니다. 요약에서 제약 조건이 빠지면 에이전트가 자신 있게 잘못된 작업을 수행할 수 있습니다. 저는 서술형 요약보다 구조화된 요약을 선호합니다. ``` { "task": "Debug failing OAuth callback in staging", "environment": { "service": "auth-api", "branch": "release-2025-02-14", "runtime": "node 20" }, "confirmed": [ "Callback returns 302 to /login", "Provider sends code and state", "State validation passes" ], "suspected": [ "Session cookie is not persisted after callback" ], "rejected": [ "Provider credentials are invalid", "Redirect URI mismatch" ], "next_steps": [ "Inspect Set-Cookie attributes in staging response", "Compare SameSite and Secure flags with production" ] } ``` 이 방식은 다음보다 토큰 효율과 신뢰성이 더 높습니다: 사용자는 OAuth를 디버깅하고 있으며 여러 가지 방법을 시도했습니다… 좋은 요약 단계는 다음을 보존해야 합니다: - 사용자의 목표. - 확실한 사실. - 이미 내려진 결정. - 제약 조건과 선호 사항. - 실패한 시도. - 아직 해결되지 않은 질문. - ID, 파일명, 타임스탬프 및 기타 정확한 식별 정보. 정확한 값이 정말로 무관한 경우가 아니라면 요약 과정에서 없애지 마세요. “사용자에게 청구서 문제가 있음”은 토큰은 적게 들지만 쓸모가 없습니다. “중복 청구: inv_1042 및 inv_1077 청구일 2025-01-08”과 같은 압축이 필요합니다. 장시간 실행되는 에이전트의 경우 저는 요약의 버전도 저장합니다. 모델이 잘못된 요약을 만들면 상태가 언제부터 어긋났는지 확인할 수 있어야 합니다. ## 스키마로 장황한 출력 방지하기 놀라울 정도로 많은 토큰 낭비가 출력에서 발생합니다. 프롬프트에는 “간결하게 작성하라”고 되어 있지만, 모델은 서문, 주의 사항, 요약, 도움을 주겠다는 제안까지 작성합니다. 시스템이 처리하는 출력에는 스키마를 사용하세요. 막연히 “JSON 응답”을 요청하지 말고, 구조를 정의한 뒤 그 외의 출력은 거부하세요. 분류 호출 예시: ``` { "type": "object", "additionalProperties": false, "required": ["category", "priority", "needs_human", "reason"], "properties": { "category": { "type": "string", "enum": ["billing", "bug", "account", "how_to", "other"] }, "priority": { "type": "string", "enum": ["low", "normal", "high", "urgent"] }, "needs_human": { "type": "boolean" }, "reason": { "type": "string", "maxLength": 160 } } } ``` 사용자에게 표시할 응답에는 엄격한 상한이 있는 형식을 지정하세요: ``` Write the answer in this structure: - First sentence: direct answer, max 25 words. - Then 2-4 bullets with concrete steps. - Do not include a greeting. - Do not include "let me know if you need anything else." - Stop after the final bullet. ``` 이 방식은 모델에 구체적인 구조를 제공하므로 “짧게 작성하라”보다 효과적입니다. 흔히 놓치는 함정: 낮게 설정된 max_tokens 값은 유용한 출력을 생각이 마무리되기도 전에 잘라버릴 수 있습니다. 스키마나 제한된 형식을 사용하면 완결성을 유지하면서 출력 길이를 줄일 수 있습니다. 잘라내기는 최후의 수단이지, 형식을 지정하는 전략이 아닙니다. ## 중단 시퀀스를 신중하게 사용하기 중단 시퀀스는 생성 결과에 자연스러운 경계가 있을 때 유용합니다. 예를 들어 모델이 템플릿 문서의 한 섹션을 생성하는 경우, 다음 섹션 표시에서 생성을 중단할 수 있습니다. ``` { "model": "gpt-5.5", "messages": [ { "role": "user", "content": "Generate only the Risk Summary section for this review..." } ], "max_tokens": 500, "stop": ["\n## Mitigations", "\n## Appendix"] } ``` 중단 시퀀스는 정해진 형식이 없는 어시스턴트 응답에는 덜 유용합니다. 유효한 내용까지 잘라버릴 수 있기 때문입니다. 또한 API와 모델 계열에 따라 동작이 다르며, 특히 도구 호출이나 구조화된 출력이 포함될 때 차이가 두드러집니다. 주된 압축 기법이 아니라 경계를 지키는 장치로 다루세요. 적절한 사용 사례: - 알려진 템플릿의 한 섹션 생성. - 모델이 예시까지 이어서 생성하지 못하도록 방지. - 일괄 처리에서 구분자를 만나면 중단. - 기존 텍스트 완성 방식의 프롬프트에 제약 적용. 위험한 사용 사례: - 흔한 문장 부호에서 중단. - 일반적인 답변에 등장할 수 있는 단어에서 중단. - 적절한 출력 스키마 대신 중단 시퀀스 사용. - Claude, GPT, Gemini 및 개방형 모델 게이트웨이에서 동작이 같다고 가정. ## 더 저렴한 모델에 작업 나누어 맡기기 모든 단계에 가장 강력한 모델이 필요한 것은 아닙니다. 저는 프로덕션 시스템에서 하위 작업을 더 작거나 저렴한 모델로 라우팅하는 경우가 많습니다: | 하위 작업 | 일반적인 요구 사항 | 적합한 선택 | 언어 감지 | 간단한 분류 | 소형·저비용 모델 | 티켓 분류 | 구조화된 레이블 출력 | 소형 또는 중형 모델 | 쿼리 재작성 | 짧은 텍스트 변환 | 소형 또는 중형 모델 | 문서 재순위화 | 전용 재순위화 모델 또는 소형 모델 | 가능하면 비생성형 방식 | 최종 답변 | 추론과 어조 | 더 강력한 모델 | 정책 예외 | 미묘한 차이와 위험 | 가장 강력한 모델 이런 상황에서 여러 모델에 접근할 수 있다는 것이 운영상 유용해집니다. 대부분의 에이전트 기반 글쓰기에는 Sonnet 4.6, 까다로운 추론 경로에는 GPT-5.5, 특정 멀티모달 또는 긴 컨텍스트 워크플로에는 Gemini 3, 1M 컨텍스트 윈도우가 정말 필요한 경우에는 Fable 5, 통제된 내부 작업에는 Llama, Qwen, DeepSeek, MiniMax, Kimi 같은 개방형 모델을 사용할 수 있습니다. 엔지니어링의 핵심은 브랜드 충성도가 아니라 라우팅입니다. 한계도 있습니다. 오케스트레이션에는 실제로 오버헤드가 발생합니다. 모델 호출이 추가될 때마다 지연 시간, 실패 가능성, 로깅의 복잡성, 평가해야 할 범위가 늘어납니다. 저렴한 분류기가 300개의 토큰을 절약하더라도 600ms의 지연과 또 다른 재시도 경로를 추가한다면, 그만한 가치가 없을 수 있습니다. 제가 선호하는 패턴: ``` if route == "simple_faq": model = "haiku-4.5" elif route == "standard_support": model = "sonnet-4.6" elif route == "legal_or_financial_exception": model = "opus-4.8" else: model = "sonnet-4.6" ``` 구체적인 모델 이름은 바뀔 것입니다. 하지만 원칙은 유지되어야 합니다. 작업의 품질 기준을 안정적으로 충족하는 모델 중 가장 저렴한 모델을 사용하세요. ## 입력과 출력 토큰을 정확하게 측정하기 팀이 요청당 평균 비용만 살펴보면 토큰 최적화는 실패합니다. 라우트와 단계별로 나눈 분포가 필요합니다. 최소한 다음 항목을 추적하세요: - 프롬프트 섹션별 입력 토큰 수. - 출력 토큰 수. - 검색된 문서 수와 검색 결과의 토큰 수. - 모델 이름과 버전. - 지연 시간 백분위수. - 프롬프트 또는 컨텍스트 캐싱을 사용하는 경우 캐시 적중률. - 재시도 횟수. - 사용자가 체감하는 품질 지표. - 사람에게 이관되거나 수정되는 비율. 유용한 대시보드는 p50, p90, p99를 보여줍니다. 토큰 관련 버그는 분포의 꼬리 부분에 숨어 있는 경우가 많습니다. 로그 행 예시: ``` { "route": "support.draft_answer", "model": "sonnet-4.6", "input_tokens": 3820, "output_tokens": 514, "retrieved_docs": 4, "retrieval_tokens": 1320, "latency_ms": 3100, "cache_hit": false, "quality": { "user_thumb": "up", "human_edited": false } } ``` “토큰을 40% 줄였다”는 주장에는 주의하세요. 재시도가 늘었나요? 사람에게 이관되는 사례가 늘었나요? 답변이 지나치게 짧아져 사용자가 후속 질문을 더 많이 했나요? 저렴한 모델이 유효하지 않은 JSON 출력을 더 많이 생성해 수정 호출이 필요해졌나요? 정직한 지표는 호출당 토큰 수가 아닙니다. 성공적으로 완료한 작업당 토큰 수입니다. ## 긴 컨텍스트가 안일한 설계를 허용하는 것은 아닙니다 큰 컨텍스트 윈도우는 실제로 유용합니다. Fable 5의 1M급 컨텍스트, Gemini의 긴 컨텍스트 처리 강점, 최첨단 모델 전반의 긴 컨텍스트 모드는 몇 년 전만 해도 수행하기 까다로웠던 워크플로를 가능하게 합니다. 저장소 전체의 코드 분석, 대규모 문서 검토, 여러 시간 분량의 대화록 처리, 방대한 연구 자료 묶음 분석 등이 그 예입니다. 하지만 긴 컨텍스트는 하나의 기능이지, 기본 아키텍처가 아닙니다. 다음과 같은 경우에는 긴 컨텍스트 호출이 적절할 수 있습니다: - 답변이 입력에서 서로 멀리 떨어진 여러 부분에 의존하는 경우. - 검색으로는 문서 간 관계를 놓칠 수 있는 경우. - 탐색적인 작업이라 무엇이 중요한지 아직 모르는 경우. - 세부 정보를 잃는 비용이 더 많이 읽는 비용보다 큰 경우. 다음과 같은 경우에는 대체로 낭비입니다: - 사용자가 범위가 좁은 사실 확인 질문을 하는 경우. - 몇 개의 원문 발췌만으로 충분한 경우. - 컨텍스트에 중복되거나 오래된 정보가 포함된 경우. - 모델을 데이터베이스 필터로 사용하는 경우. 흔한 패턴은 오프라인 분석에 긴 컨텍스트를 사용하고, 온라인 서비스에는 간결한 산출물을 제공하는 것입니다. 예를 들어 전체 정책 문서 모음을 한 번 분석한 뒤, 구조화된 정책 맵, 임베딩, 요약, 테스트 케이스를 생성할 수 있습니다. 실행 중인 어시스턴트가 티켓마다 전체 문서 모음을 다시 읽어서는 안 됩니다. ## 실용적인 최적화 워크플로 토큰을 많이 사용하는 LLM 시스템을 인계받으면, 저는 보통 다음 순서로 작업합니다: - 먼저 계측하기 섹션별 토큰 집계를 추가하세요. 측정 없이 최적화하지 마세요. - 검색 컨텍스트에 상한 설정하기 검색 결과에 사용할 토큰 예산의 엄격한 상한을 설정하세요. 재순위화하고 필요한 텍스트 구간을 추출하세요. - 대화록을 밀어 넣는 대신 상태 사용하기 최근 대화 턴과 구조화된 메모리를 유지하세요. - 출력에 제약 적용하기 시스템이 처리하는 출력에는 스키마를, 사용자에게 표시하는 출력에는 명시적인 형식을 사용하세요. - 하위 작업 라우팅하기 안정적으로 처리할 수 있다면 분류, 재작성, 요약 작업을 더 저렴한 모델로 옮기세요. - 작업 단위로 품질 평가하기 호출당 비용만 보지 말고, 작업을 성공적으로 해결한 결과를 비교하세요. - 꼬리 구간 주시하기 p95와 p99의 토큰 사용량 급증을 조사하세요. 검색이 제대로 작동하지 않거나 대화 기록이 끝없이 누적되는 문제를 드러내는 경우가 많습니다. 제가 서비스에서 사용해 온 간단한 안전장치는 다음과 같습니다. ``` TOKEN_LIMITS = { "support.classify": {"input": 800, "output": 80}, "support.retrieve": {"input": 500, "output": 0}, "support.draft": {"input": 4500, "output": 700}, "support.summarize": {"input": 1800, "output": 300}, } def assert_token_budget(route, prompt_sections, max_output_tokens): input_tokens = { name: count_tokens(text) for name, text in prompt_sections.items() } total_input = sum(input_tokens.values()) limits = TOKEN_LIMITS[route] if total_input > limits["input"]: raise TokenBudgetError(route, total_input, input_tokens) if max_output_tokens > limits["output"]: raise TokenBudgetError(route, max_output_tokens, {"output": max_output_tokens}) ``` 잘못된 검색 쿼리 하나가 관련 없는 PDF에 예산을 소진했다는 사실을 일주일 뒤에 발견하는 것보다 일찍 실패하는 편이 낫습니다. ## 실무 핵심 사항 - 토큰 사용량을 줄이려면 먼저 가시성을 확보해야 합니다. 프롬프트 섹션, 라우트, 모델별로 입력 및 출력 토큰을 기록하세요. - 검색은 필요한 정보만 선별할 때 컨텍스트에 자료를 무작정 넣는 방식보다 효과적입니다. 재순위를 매기고, 필요한 구간을 추출하고, 컨텍스트 예산을 엄격히 적용하세요. - 요약은 모호한 서술이 아니라 구조화된 상태 정보여야 합니다. 정확한 사실, ID, 제약 조건, 실패한 시도를 보존하세요. - 출력 스키마는 단순히 낮추는 것보다 더 안전하게 장황한 출력을 줄여 줍니다 max_tokens. - 중지 시퀀스는 유용한 경계 안전장치지만, 구조화된 출력을 대체하지는 못합니다. - 추가 지연과 실패 양상을 감수할 가치가 있을 때만 단순한 하위 작업을 더 저렴한 모델로 라우팅하세요. - 개별 호출당 토큰 수가 아니라 성공한 작업당 토큰 수로 절감 효과를 판단하세요. - 긴 컨텍스트를 지원하는 모델은 강력하지만, 정보 아키텍처의 필요성까지 없애 주지는 않습니다. [추론](/ko/blog/tag/inference/)[프롬프트](/ko/blog/tag/prompts/)[모델](/ko/blog/tag/models/)[API](/ko/blog/tag/api/) A [AI Prime Tech](https://aiprimetech.io/ko/) 이 블로그는 AI Prime Tech가 발행합니다. 기사는 AI의 도움을 받아 초안을 작성합니다. 모델 ID, 가격, 한도 등 버전별 세부 정보는 활용하기 전에 공식 문서와 대조해 확인하세요. 더 저렴하게 Claude API 이용하기 API 키 하나로 Claude Opus 5.5, Sonnet 5, Haiku 4.5, Fable 5.1 및 GPT-6개 모델을 이용하세요. 구독 없이 사용한 만큼만 결제합니다. [API 키 받기 →](/register) AI Prime Tech는 독립적인 타사 API 게이트웨이입니다. Claude™ 및 Anthropic®는 Anthropic, PBC의 상표입니다. 제휴 또는 보증 관계를 의미하지 않습니다.