공지TechKnowGenie 블로그를 오픈했습니다.
AI•읽는 데 약 28분

사내 챗봇에 MCP를 붙였다가 일주일 만에 1,400만 원 청구서를 받은 이유

Anthropic의 표준 규격 MCP를 프로덕션에 붙였다가 질문 단 1건에 55,000 토큰이 증발하고 비용이 10배 폭증한 실화. 93개 도구의 스키마 비대화 원인 규명부터 동적 라우터와 프롬프트 캐시 브레이크포인트로 85% 절감한 전 과정을 분석합니다.

관리자
목차 (6)
  1. tools/list 핸드셰이크와 238KB JSON 페이로드의 진실
  2. 시스템 프롬프트 제약과 스키마 텍스트 절삭이 부른 역효과
  3. 프롬프트 캐시 적중률 18.4%의 범인: 접두사 오염과 비결정적 직렬화
  4. 의도 기반 동적 스키마 프루닝과 캐시 브레이크포인트 고정
  5. 프로덕션 배포 후 2주간의 실측 지표와 도구 선택 정확도의 변화
  6. 300초 유휴 만료(TTL Eviction)를 방어하는 백그라운드 킵얼라이브 워커

지난달 10월 8일 출근길, 슬랙 #dev-finops 채널에 뜬 Anthropic 결제 경고 알림을 열어보고 순간 얼어붙었습니다.

[Anthropic Usage Alert: Organization Platform-Core]
• 기간 누적 청구 금액:   $10,854.20 USD (전월 대비 +780% 폭증 🚨)
• 총 투입 토큰 수:       3,618,066,600 tokens
• 프롬프트 캐시 적중률:  18.4% (정상 기준치 85% 대비 처참한 붕괴)
• 원인 엔드포인트:       POST /v1/messages (Client: internal-agent-runner)

저희 개발 조직은 70명 남짓입니다. 사내 지식 베이스 검색과 깃허브 코드 리뷰를 돕는 내부 챗봇 하나를 돌리고 있었는데, 단 일주일 만에 1,400만 원에 육박하는 API 청구서가 찍힌 것입니다. 처음에는 API 키가 유출되어 외부 누군가가 모델을 무단 채굴하고 있는 줄 알았습니다. 즉시 키를 폐기하고 유입 요청 로그를 까보았습니다. 그런데 이상하게도 요청을 보낸 IP는 전부 사내 판교 오피스 고정 IP였고, 질문 내용도 지극히 평범했습니다.

동료 프론트엔드 개발자가 남긴 질문은 고작 “어제 frontend-v2 레포에 머지된 PR 목록 몇 개만 요약해 줘.“라는 짧은 문장이었습니다. 한글 25글자, BPE 토큰 수로는 채 18토큰도 되지 않는 가벼운 질의였습니다. 그런데 이 요청 하나를 처리하면서 Anthropic 엔드포인트로 전송된 실제 usage.input_tokens 값은 자그마치 54,892 토큰이었습니다. 질문 18글자를 보내는데 55,000토큰이 실려 나가고 있었던 셈입니다. 비용으로 환산하면 질문 한 번에 약 $0.165(약 230원)가 증발하고 있었고, 개발자 70명이 매일 수십 번씩 질문을 던지다 보니 일주일 만에 천만 원이 녹아내렸습니다.

범인은 우리가 AI 에이전트의 표준 어댑터라며 의심 없이 도입했던 앤트로픽의 Model Context Protocol(MCP)이었습니다.

tools/list 핸드셰이크와 238KB JSON 페이로드의 진실

기존에는 LLM에 사내 깃허브나 데이터베이스를 연동하려면 개발자가 일일이 함수 호출(Function Calling) 스키마를 하드코딩하고 파이프라인을 구축해야 했습니다. 반면 MCP는 레포지토리에 설정 파일 몇 줄만 적어두면 GitHub, Slack, Postgres 등의 도구를 표준 규격으로 자동 연결해 줍니다. 저희 팀 역시 깃허브 공식 오픈소스 패키지인 @modelcontextprotocol/server-github를 배포하고 사내 에이전트에 MCP 클라이언트를 붙였습니다. 연동은 10분 만에 끝났고, 이제 사내 챗봇도 깃허브 API를 자유자재로 다룬다며 모두가 만족해했습니다.

악몽은 그 자동화된 추상화 계층 뒤편에서 시작되었습니다. MCP 서버가 클라이언트와 초기 JSON-RPC 핸드셰이크를 맺고 나면, 클라이언트는 tools/list 메서드를 호출해 서버가 노출하는 모든 도구 명세를 긁어옵니다. 그리고 앤트로픽 클로드 SDK는 이 도구 목록을 매번 POST /v1/messages API를 호출할 때마다 요청 바디의 tools JSON 배열에 통째로 직렬화하여 밀어 넣습니다. 저희가 보낸 18토큰짜리 질문의 실제 HTTP 전송 패킷을 프록시에서 덤프해 보고 나서야 사태의 전모를 파악할 수 있었습니다.

POST /v1/messages HTTP/1.1
Host: api.anthropic.com
Content-Type: application/json

{
  "model": "claude-3-5-sonnet-20241022",
  "system": "당신은 사내 개발자들의 코드 리뷰를 돕는 AI 엔지니어링 어시스턴트입니다.",
  "messages": [
    {"role": "user", "content": "어제 frontend-v2 레포에 머지된 PR 목록 몇 개만 요약해 줘."}
  ],
  "tools": [
    {
      "name": "create_issue",
      "description": "Create a new issue in a GitHub repository...",
      "input_schema": { "/* 다수의 인자 및 중첩 타입 정의 */" }
    },
    {
      "name": "create_pull_request_review",
      "description": "Create a review on a pull request...",
      "input_schema": { "/* 다수의 인자 및 중첩 타입 정의 */" }
    }
    /* ... 이런 도구 정의가 93개나 끝없이 이어집니다 ... */
  ]
}

GitHub REST API의 표면적은 방대합니다. 레포지토리 생성, 브랜치 프로텍션 룰 수정, 웹훅 등록, 시크릿 키 관리, 커밋 서명 검증, 릴리즈 태그 생성까지 공식 MCP 패키지는 무려 93개의 도구를 기본 노출하고 있었습니다. 각 도구마다 모델이 파라미터를 정확히 추론할 수 있도록 상세한 영문 설명서와 JSON Schema 타이핑 정의가 붙어 있다 보니, 도구 하나당 평균 스키마 크기가 2.5KB에서 3.2KB에 달했습니다. 93개 도구의 순수 JSON 스키마 바이트 합계는 243,810 Bytes(약 238 KB)였고, 이를 Claude BPE 토크나이저에 통과시키면 정확히 54,892 토큰이 찍혔습니다. 사용자가 “안녕”이라고 두 글자 인사말만 건네도, 클로드 3.5 소넷의 200,000 토큰 컨텍스트 창 중 27%가 깃허브 API 백과사전으로 채워진 채 네트워크를 타고 넘어가고 있었던 것입니다.

시스템 프롬프트 제약과 스키마 텍스트 절삭이 부른 역효과

원인을 파악한 직후 저희 팀이 즉흥적으로 시도했던 응급조치들은 연달아 실패로 돌아갔습니다. 가장 먼저 시도했던 방법은 시스템 프롬프트 하단에 강력한 도구 제한 지시문을 덧붙이는 것이었습니다.

[초기 응급 패치로 추가했던 시스템 프롬프트]
"주의: 너는 오직 PR 조회와 이슈 검색에 관련된 도구만 사용해야 해. 
권한 관리, 웹훅 설정, 시크릿 관리와 관련된 도구는 일체 쳐다보지도 말고 무시해."

당연하게도 API 요금은 단 1원도 줄어들지 않았습니다. LLM의 과금 체계와 트랜스포머의 프리필(Prefill) 메커니즘을 혼동한 탓이었습니다. Anthropic은 모델이 최종적으로 어떤 도구를 선택해 호출(tool_use)했느냐를 기준으로 과금하지 않습니다. HTTP Request Payload의 tools 배열에 실려 모델의 어텐션 레이어로 투입된 입력 토큰(Input Tokens) 전체에 대해 프리필 연산 비용을 청구합니다. 모델에게 특정 도구를 보지 말라고 지시하는 문장 자체가 이미 55,000토큰의 도구 명세를 KV 캐시에 모두 적재한 뒤에야 읽히는 텍스트였으므로 아무런 비용 절감 효과가 없었습니다.

곧바로 방향을 틀어 공식 @modelcontextprotocol/server-github 저장소를 포크한 뒤, 각 도구의 장황한 영문 description과 파라미터 설명을 한 줄짜리 단문으로 대거 쳐내는 작업을 감행했습니다.

- "description": "Create a new issue in a GitHub repository. Use this to track bugs, enhancements, and tasks. Accepts milestone, assignees, and labels...",
+ "description": "Create github issue",

스키마 용량을 240KB에서 80KB(약 18,000 토큰) 수준으로 65% 줄이는 데는 성공했지만, 배포 직후 사내 슬랙이 아수라장으로 변했습니다. 모델의 도구 호출 성공률이 바닥으로 곤두박질쳤기 때문입니다. 인자의 구체적인 포맷 제약, 예컨대 since 파라미터가 ISO-8601 타임스탬프(YYYY-MM-DDTHH:MM:SSZ) 형태여야 한다거나 state 필드가 특정 열거형 값만 허용한다는 상세 명세가 사라지자, Claude는 단순 날짜 문자열(2026-10-01)이나 유닉스 밀리초 타임스탬프를 인자로 넘기기 시작했습니다. GitHub API 서버는 연달아 422 Unprocessable Entity 에러를 반환했고, 에이전트가 에러를 복구하느라 재시도 루프를 돌면서 오히려 턴당 총 소비 토큰이 늘어나는 기현상까지 겹쳤습니다. 도구 호출 에러율은 평소 2% 미만에서 34.8%로 폭증했고, 결국 반나절 만에 롤백해야 했습니다.

프롬프트 캐시 적중률 18.4%의 범인: 접두사 오염과 비결정적 직렬화

여기서 자연스럽게 한 가지 의문이 고개를 들었습니다. Anthropic에는 프롬프트 캐싱(Prompt Caching) 기능이 존재하고, 55,000토큰이라도 캐시가 적중(cache_read_input_tokens)하면 기본 단가의 10%($0.30/1M 토큰)만 과금됩니다. 도구 정의 스키마처럼 변하지 않는 정적 데이터는 프롬프트 캐시를 걸어두면 저렴하게 재사용되어야 정상인데, 왜 저희 대시보드의 캐시 적중률은 고작 18.4%에 머물며 매번 정가의 1.25배에 달하는 캐시 생성(cache_creation_input_tokens, $3.75/1M 토큰) 요금을 물고 있었을까요?

패킷 덤프를 바이트 단위로 비교 분석한 끝에, 프롬프트 캐시의 해시 트리를 매번 박살 내고 있던 세 가지 원인을 특정했습니다. 가장 치명적인 범인은 사내 에이전트 베이스 코드 최상단에 박혀 있던 한 줄짜리 시간 주입 로직이었습니다.

system_prompt = f"당신은 개발 도우미입니다. 현재 시각은 {datetime.now()} 입니다.\n" + base_prompt

Anthropic의 프롬프트 캐시는 프리필 단계의 KV 캐시 텐서 블록을 해싱하여 보관하기 때문에, 엄격한 접두사 일치(Exact Prefix Matching) 방식으로 동작합니다. 내부적으로는 [tools 배열] ➔ [system 프롬프트] ➔ [messages 배열] 순서로 토큰 스트림을 이어 붙인 뒤, 0번째 토큰부터 cache_control 브레이크포인트가 지정된 위치까지 단 한 토큰의 오차도 없이 동일해야만 기존 KV 블록을 재사용합니다. 그런데 시스템 프롬프트 첫머리에 초 단위로 바뀌는 타임스탬프가 박혀 있었으니, 그 영역 이후의 모든 캐시 해시가 매초 새로 계산되며 캐시 쓰기 비용만 반복해서 청구되고 있었던 것입니다.

더 교묘했던 문제는 파이썬 딕셔너리의 JSON 직렬화 순서였습니다. 여러 대의 MCP 클라이언트 워커 파드가 도구 목록을 병합하고 가공하는 과정에서 set 자료구조와 비동기 태스크 수집(asyncio.gather)을 거치다 보니, 요청마다 tools 배열 내 도구의 나열 순서나 input_schema.properties 내부 키 순서(owner와 repo의 선후 관계)가 미세하게 뒤바뀌어 직렬화되고 있었습니다. 사람의 눈에는 동일한 JSON 객체이지만, 바이트 스트림을 토큰화하는 BPE 엔진 입장에서는 전혀 다른 토큰 시퀀스로 인식되어 캐시 미스를 유발했습니다.

여기에 더해 Anthropic 프롬프트 캐시의 기본 수명(TTL)인 5분(300초)이 실제 개발자들의 업무 호흡과 어긋난다는 점도 뼈아팠습니다. 개발자가 챗봇에게 “PR 변경점 분석해 줘”라고 시킨 뒤 답변을 받으면, 깃허브 코드 창을 띄워놓고 최소 6~8분 동안 수정 라인을 꼼꼼히 검토합니다. 그리고 6분 30초 뒤에 “그럼 이 함수 유닛 테스트는 어떻게 짜야 해?“라고 후속 질문을 던지는 순간, 이미 5분의 TTL은 만료되어 증발한 뒤였고 55,000토큰은 다시 처음부터 프리필 연산을 거치며 캐시 생성 요금을 물어야 했습니다.

의도 기반 동적 스키마 프루닝과 캐시 브레이크포인트 고정

근본적인 해결책은 두 갈래로 명확히 좁혀졌습니다. 질문이 유입되었을 때 모델에게 93개 도구를 전부 넘기지 않고 해당 질문의 의도(Intent)에 부합하는 소수의 핵심 도구만 선별해서 주입하되, 선별된 도구 묶음이 바이트 단위로 결정론적(Deterministic) 순서를 유지하도록 정렬하여 프롬프트 캐시 적중률을 극대화하는 것이었습니다.

만약 도구를 고르는 라우팅 작업을 또 다른 LLM에게 맡기면 추가 네트워크 RTT와 토큰 비용이 발생하므로, 저희는 MCP 클라이언트와 Anthropic API 게이트웨이 사이에 1.2ms 이내로 동작하는 초경량 동적 프록시 미들웨어(Semantic Tool Pruner & Cache Boundary Controller)를 배치했습니다.

MCP 도구 스키마 토큰 압축 및 캐시 최적화

sequenceDiagram
    actor Dev as 개발자
    participant Proxy as 경량 프록시 (1.2ms)
    participant Claude as Claude 3.5 Sonnet

    Dev->>Proxy: "frontend 레포 최근 PR 요약" (18 토큰)
    Note over Proxy: 93개 도구 ➔ 핵심 도구군 필터링<br/>정적 도구 끝에 cache_control 고정
    Proxy->>Claude: 최적화 요청 (8,210 토큰)
    Claude-->>Dev: ⚡ 캐시 적중 94.2% (TTFT 385ms)

실제 프로덕션 환경에서 일일 수만 건의 에이전트 트래픽을 처리하고 있는 핵심 라우터와 페이로드 빌더의 파이썬 구현체입니다.

# dynamic_mcp_proxy.py
import re
import json
from typing import List, Dict, Any
from fastapi import FastAPI

app = FastAPI(title="Smart MCP Token Pruner Proxy")

class DynamicToolPruner:
    def __init__(self, raw_mcp_manifest: List[Dict[str, Any]]):
        # JSON 키 순서를 사전순으로 완전 고정하여 바이트 해시 불일치 차단
        self.tool_store = {
            t["name"]: json.loads(json.dumps(t, sort_keys=True)) 
            for t in raw_mcp_manifest
        }
        
        # 도메인별 핵심 작업 도구 클러스터 정의
        self.clusters = {
            "PULL_REQUEST": [
                "get_pull_request",
                "list_pull_requests",
                "list_pull_request_files",
                "create_pull_request_review"
            ],
            "ISSUE_TRACKING": [
                "list_issues",
                "get_issue",
                "create_issue",
                "add_issue_comment"
            ],
            "CODE_SEARCH": [
                "search_code",
                "get_file_contents",
                "list_commits"
            ]
        }
        self.fallback = ["get_file_contents", "search_code"]
        self.patterns = {
            "PULL_REQUEST": re.compile(r"(pr|pull\s*request|리뷰|머지|diff|커밋)", re.IGNORECASE),
            "ISSUE_TRACKING": re.compile(r"(이슈|issue|버그|에러|티켓|담당자)", re.IGNORECASE)
        }

    def prune_tools_for_query(self, user_query: str) -> List[Dict[str, Any]]:
        target_keys = set(self.fallback)
        for cluster, regex in self.patterns.items():
            if regex.search(user_query):
                target_keys.update(self.clusters[cluster])
                break
        
        pruned = [self.tool_store[name] for name in target_keys if name in self.tool_store]
        # 도구 배열 순서까지 알파벳순으로 엄격히 고정하여 Prefix Cache Hit 보장
        return sorted(pruned, key=lambda x: x["name"])


class AnthropicPayloadOptimizer:
    @staticmethod
    def build_payload(static_system_prompt: str, user_messages: list, pruned_tools: list) -> dict:
        final_tools = [dict(t) for t in pruned_tools]
        if final_tools:
            # 정렬된 도구 배열의 맨 마지막 요소에만 캐시 브레이크포인트 앵커링
            final_tools[-1] = dict(final_tools[-1])
            final_tools[-1]["cache_control"] = {"type": "ephemeral"}

        return {
            "model": "claude-3-5-sonnet-20241022",
            "max_tokens": 4096,
            "system": [
                {
                    "type": "text",
                    "text": static_system_prompt,
                    "cache_control": {"type": "ephemeral"}
                }
            ],
            "tools": final_tools,
            "messages": user_messages
        }

이 미들웨어는 사용자의 질의가 들어오면 정규식 및 경량 키워드 매칭을 통해 0.1ms 만에 작업 도메인을 판별하고, 93개의 전체 매니페스트 중 해당 도메인 해결에 필수적인 4~6개의 도구 스키마만 발라냅니다. 이때 json.dumps(..., sort_keys=True)와 도구 이름 기준 정렬(sorted)을 강제하여 어떤 워커 파드에서 요청을 처리하든 항상 100% 동일한 바이트 시퀀스가 생성되도록 만들었습니다. 아울러 초 단위로 변하던 현재 시각 정보는 시스템 프롬프트에서 완전히 들어내고, 사용자의 마지막 메시지 블록(messages[-1]) 하단에 메타데이터 태그로 덧붙여 정적 프리픽스 영역이 오염되지 않도록 격리했습니다.

프로덕션 배포 후 2주간의 실측 지표와 도구 선택 정확도의 변화

프록시 미들웨어를 프로덕션 라우터 앞단에 배포하고 2주 동안 수집한 텔레메트리 지표는 극적인 변화를 보여주었습니다.

검증 지표 (Audit Metric) AS-IS 순정 MCP (93개 풀 도구) TO-BE 동적 프록시 (핵심 도구군 선별) 개선 효과 (FinOps Impact)
도구 스키마 페이로드 93개 도구 전체 (240 KB JSON) 의도 기반 핵심 도구군 (18 KB JSON) 스키마 크기 92.5% 경량화
턴당 입력 토큰 소비 54,892 토큰 (사용자 질의의 3,049배) 8,210 토큰 (선별 도구군 주입) 턴당 46,682 토큰 소멸 (-85.0%)
프롬프트 캐시 적중률 18.4% (동적 타임스탬프·키 순서 변동) 94.2% (키 정렬 & 브레이크포인트 고정) +75.8%p 캐시 안정 안착
단일 질의당 청구 비용 $0.1582 (질문당 약 215원) $0.0141 (질문당 약 19원) 호출 단가 1/11로 급감 (-91.1%)
첫 토큰 지연시간 (TTFT) 1,840ms (55K 프리필 연산 병목) 385ms (KV 캐시 즉각 재사용) 체감 응답 속도 4.8배 가속
도구 호출 정확도 89.2% (유사 도구 간 혼동 및 환각) 98.6% (정확한 타깃 함수 매핑) 도구 선택 오류 87% 소멸
월 10만 건 환산 청구액 $15,820 USD (약 2,135만 원) $1,410 USD (약 190만 원) 월 1,945만 원 순절감

비용 절감 못지않게 사내 엔지니어들의 체감 만족도를 끌어올린 대목은 에이전트의 도구 호출 정확도 향상이었습니다. 93개의 도구가 한꺼번에 펼쳐져 있을 때 Claude 3.5 Sonnet은 의미가 겹치는 함수들 사이에서 자주 갈팡질팡했습니다. 예를 들어 사용자가 “어제 올라온 이슈 목록 보여줘”라고 요청하면, list_issues, search_issues_and_pull_requests, get_issue 중 무엇을 호출해야 할지 망설이다가 불필요하게 두세 개의 도구를 연달아 찔러보거나, 검색 쿼리 문법(is:issue is:open) 파라미터를 누락해 빈 배열을 돌려받는 식이었습니다. 반면 프록시가 후보군을 4~6개로 압축해 주자 모델의 어텐션 엔트로피가 급격히 낮아지면서 도구 선택 실패 및 파라미터 환각률이 10.8%에서 1.4%로 수직 하락했습니다.

300초 유휴 만료(TTL Eviction)를 방어하는 백그라운드 킵얼라이브 워커

프롬프트 캐싱을 안착시킨 뒤 첫 주말 모니터링에서 발견된 마지막 퍼즐 조각은 앞서 언급한 Anthropic 프롬프트 캐시의 300초 유휴 만료 메커니즘이었습니다. 업무 시간대에는 70명의 개발자가 쉴 새 없이 질문을 던지므로 캐시 적중률이 95% 이상을 유지했지만, 점심시간(12:30~13:30)이나 회의가 몰리는 시간대처럼 도메인별 클러스터의 호출 간격이 5분 이상 벌어지는 순간 캐시 노드의 메모리에서 KV 블록이 축출(Evict)되었습니다. 캐시가 증발한 직후 질문을 던진 첫 번째 개발자는 어김없이 캐시 쓰기 페널티(1.25배 과금)와 1,800ms의 프리필 지연을 뒤집어써야 했습니다.

저희는 이 간헐적 콜드 스타트를 원천 제거하기 위해 평일 업무 시간(09:00~20:00) 동안 동작하는 경량 백그라운드 캐시 웜업(Keep-Alive Heartbeat) 워커를 추가했습니다.

# cache_keep_alive_worker.py
import asyncio
import time
from datetime import datetime
from anthropic import AsyncAnthropic

class PromptCacheWarmer:
    def __init__(self, client: AsyncAnthropic, cluster_payloads: dict):
        self.client = client
        self.cluster_payloads = cluster_payloads
        self.last_activity = {k: time.time() for k in cluster_payloads}

    def record_user_request(self, cluster_key: str):
        """실제 유저 요청이 유입될 때마다 해당 도구 클러스터의 타이머 갱신"""
        self.last_activity[cluster_key] = time.time()

    async def run_loop(self):
        while True:
            await asyncio.sleep(30)
            now_hour = datetime.now().hour
            # 평일 코어 업무 시간대(09시~20시)에만 킵얼라이브 가동
            if not (9 <= now_hour < 20):
                continue

            current_ts = time.time()
            for cluster_key, payload in self.cluster_payloads.items():
                idle_sec = current_ts - self.last_activity[cluster_key]
                # 마지막 요청 후 4분(240초)이 경과하면 만료 직전에 최소 비용 핑 전송
                if 240 <= idle_sec < 290:
                    try:
                        await self.client.messages.create(
                            **payload,
                            max_tokens=1
                        )
                        self.record_user_request(cluster_key)
                    except Exception as exc:
                        print(f"[-] 클러스터 {cluster_key} 캐시 연장 실패: {exc}")

특정 도구 클러스터에 대해 마지막 사용자 요청 이후 4분(240초) 동안 후속 호출이 없으면, 워커가 max_tokens=1짜리 더미 요청을 1회 전송하여 캐시 블록을 읽고(cache_read_input_tokens) 만료 타이머를 다시 300초 뒤로 연장합니다. 8,210토큰 규모의 클러스터 하나를 1회 캐시 리드하는 비용은 고작 $0.0024(약 3.3원)에 불과합니다. 반면 캐시가 깨져서 다시 생성할 때 드는 비용은 $0.0307(약 42원)이므로, 유휴 구간에 한두 번 킵얼라이브 핑을 쏘는 편이 비용 면에서도 훨씬 이득일뿐더러 모든 개발자에게 일관된 380ms대의 응답 속도를 보장해 줍니다.

지금 저희 팀의 Datadog 대시보드에는 다음 수식으로 계산되는 프롬프트 팽창 지수(Prompt Expansion Ratio) 경보가 상시 돌아가고 있습니다.

$$\text{Prompt Expansion Ratio} = \frac{\text{LLM Input Tokens}}{\text{User Query Tokens}}$$

사용자가 입력한 순수 질문 토큰 수 대비 실제 LLM 엔드포인트로 투입된 전체 토큰의 비율입니다. 누군가 새로운 외부 MCP 서버(Slack, Jira, Notion 등)를 연동하면서 매니페스트 필터링을 누락하거나, 시스템 프롬프트 앞단에 동적 변수를 집어넣어 캐시 경계를 무너뜨리는 순간 이 비율과 캐시 미스율이 동시에 치솟으며 슬랙 #dev-finops 채널에 경보가 울립니다. MCP는 에이전트와 외부 세계를 잇는 훌륭한 배관이지만, 그 배관을 통해 매번 백과사전 전체를 흘려보내지 않도록 밸브를 잠그는 일은 결국 인프라 엔지니어의 몫입니다.

← 목록으로