ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 도구 100개, LLM에게 필요할 때만 꺼내는 법 — Anthropic Deferred Loading 해부
    IT 2026. 6. 16. 21:00
    도구 100개를 코딩 어시스턴트에 '6개'로 보여주기 — 3단 계층 디스커버리

    내 개인 개발 서버에는 커맨드라인 도구가 수십 개 깔려 있다. 앱을 빌드하는 도구, 디바이스에 연결하는 도구, 에뮬레이터를 띄우는 도구, 패키징·서명하는 도구, 디버거… 각 도구는 또 수개에서 수십 개의 서브커맨드를 가진다. 어느 날 이런 생각이 들었다. "이걸 전부 코딩 어시스턴트가 자연어로 불러 쓰게 하면 어떨까?"

    여기서 코딩 어시스턴트란 Claude Code나 Cursor처럼 LLM이 외부 도구를 직접 호출할 수 있는 개발 도구를 말한다. 이들은 대부분 MCP(Model Context Protocol, AI 모델과 외부 도구·데이터를 잇기 위해 공개된 표준 규약 — "AI용 USB-C 포트"에 비유되곤 한다)를 지원한다. MCP 서버를 하나 띄워 도구들을 등록하면, 어시스턴트가 그 도구들을 자기 손처럼 쓴다.

    그런데 막상 시작하니 곧바로 벽에 부딪혔다. 도구가 너무 많다는 것이다.

    문제: 도구를 전부 펼치면 LLM이 멍청해진다

    LLM에게 도구를 노출하는 가장 단순한 방법은 전부 평면으로 등록하는 것이다. 도구 A의 서브커맨드 12개, 도구 B의 17개, 도구 C의 9개… 이걸 다 합치면 수백 개의 호출 가능한 엔드포인트(API 호출 단위)가 된다. 그리고 이 수백 개의 정의가 통째로 LLM의 system prompt(모델에게 매 요청마다 먼저 주입되는 지시문)에 들어간다.

    규모가 구체적으로 얼마나 심각한가. GitHub MCP 서버 하나가 93개 도구를 갖고 있고, 그 정의를 직렬화하면 약 55,000 토큰이다. Slack, Sentry, Grafana, Splunk까지 다섯 서버를 연결하면 도구 정의만으로 77,000 토큰에 육박한다 — Claude의 최대 컨텍스트 200K 중 거의 40%가 사용자 대화 한 마디 오가기도 전에 소진된다. Anthropic의 내부 툴링은 이 수치가 134,000 토큰까지 불어났다고 밝혔다.

    컨텍스트 낭비만이 문제가 아니다. 더 심각한 건 정확도 저하다. LLM이 매 요청마다 수백 개 도구의 정의를 받아 들고 "이 중에서 골라" 상황에 처하면, 비슷비슷한 도구 사이에서 엉뚱한 걸 선택하거나 파라미터를 혼동한다. Anthropic의 실측에서 도구 선택 정확도가 Claude Opus 4 기준 49%에 그쳤다. 사람도 메뉴가 200줄이면 주문을 못 한다. LLM도 마찬가지다.

    diagram

    Anthropic의 해법: 도구를 필요할 때만 꺼낸다

    2026년 1월, Anthropic은 이 문제를 API 레이어에서 정면으로 해결했다. 핵심 아이디어는 단순하다. 도구 정의를 처음부터 컨텍스트에 싣지 않는다. 필요할 때 검색해서 꺼낸다.

    두 가지 요소가 맞물린다. 하나는 defer_loading 플래그다. API 요청 시 도구에 이 플래그를 달면, 그 도구의 정의는 컨텍스트에 주입되지 않는다. 서버는 "이런 도구가 있다"는 사실만 기억하되, 그 명세는 LLM에게 보이지 않는다. 다른 하나는 Tool Search Tool이다. 이 특수 메타 도구는 항상 컨텍스트에 올라가 있고, LLM이 뭔가를 하려 할 때 먼저 이 도구로 카탈로그를 검색한다. 검색 결과로 관련 도구 3~5개의 정의가 그 자리에서 컨텍스트에 추가되고, LLM은 그 도구들을 즉시 사용할 수 있게 된다.

    이것이 JIT(Just-In-Time) 도구 로딩이다. 프로그래밍 언어의 JIT 컴파일이 코드를 실행 직전에 컴파일하듯, 도구를 필요한 순간 직전에 컨텍스트에 로드한다.

    diagramdiagram

    다이어그램 설명. 첫 번째 그래프는 기존 방식 — 100개 도구의 정의가 통째로 컨텍스트를 채우고 대화 공간은 바닥에 쪼그라든다. 두 번째 그래프는 Deferred Loading — Tool Search Tool 하나만 컨텍스트에 상주하고 Tool Catalog는 밖에 있다. LLM이 "build"를 검색하면 카탈로그에서 관련 도구 3~5개만 그 자리에서 JIT 로드되고, 나머지 도구는 이 대화에서 한 토큰도 차지하지 않는다.

    diagram

    실제로 어떻게 동작하는가

    여기서 가장 헷갈리기 쉬운 지점을 먼저 짚자. "요청 본문에 담는 도구 수"와 "모델이 실제로 보는 도구 수"는 다르다. 직관적으로는 "BM25로 먼저 걸러서 몇 개만 보내겠지"라고 생각하기 쉽지만, 실제는 반대다. 도구 100개를 전부 요청에 담아 보낸다. 서버가 카탈로그를 검색하려면 100개 정의가 전부 있어야 하기 때문이다. 대신 defer_loading: true가 붙은 도구는 모델의 컨텍스트(system prompt)에 펼쳐지지 않는다. 토큰 절감은 "적게 보낸다"가 아니라 "보낸 것 중 모델에게 적게 보여준다"에서 나온다.

    이 구조에는 네 가지 주체가 있다. 사용자는 Claude Code에게만 말을 건넨다. Claude Code는 중간자로서 나머지 두 곳과 통신한다. MCP 서버는 도구 목록을 보관하고 실제 실행을 담당한다. Anthropic API(서버)는 LLM 추론과 도구 검색(BM25)을 모두 서버 안에서 처리한다. 즉 BM25 검색은 Claude Code가 로컬에서 하는 게 아니라 Anthropic 서버가 하는 server-side tool이다.

    diagram

    다이어그램 설명. 통신은 두 종류다. ① Claude Code ↔ MCP 서버 (JSON-RPC): 도구 목록을 받아오는 tools/list(100개 전부)와 도구를 실행하는 tools/call. 실제 빌드는 MCP 서버가 한다. ② Claude Code ↔ Anthropic API (REST): Claude Code가 도구 100개를 본문에 전부 실어 보낸다. defer_loading: true를 붙여 보내면 서버는 그 99개를 모델 컨텍스트에 렌더링하지 않는다 — 모델이 처음 보는 것은 Tool Search Tool 하나뿐이다. 모델이 tool_search("build")를 호출하면 서버 안에서 BM25 검색이 돌아 매칭된 3개 정의가 그 자리에서 컨텍스트에 펼쳐진다(tool_reference → 전체 정의로 자동 확장). 이 검색은 클라이언트로 왕복하지 않는다 — Anthropic 서버가 자체 처리하는 server-side 도구다. 그래서 모델은 100개를 받았지만 실제로는 검색 도구 + 빌드 도구 3개만 컨텍스트에서 본다.

    Anthropic이 공개한 실측 수치는 극적이다. 5개 서버·50개 도구 기준으로 컨텍스트 토큰이 77,000 → 8,700으로 줄었다(88% 감소). 그리고 도구 선택 정확도는 Claude Opus 4에서 49% → 74%로, Claude Sonnet 4.5에서 79.5% → 88.1%로 올랐다. 도구를 덜 보여주자 LLM이 더 잘 고른다는 역설이 수치로 증명됐다.

    diagram

    Claude Code에서 활성화하는 방법

    위 흐름은 settings.jsonENABLE_TOOL_SEARCH를 설정하는 것만으로 켜진다. MCP 도구 정의 합계가 컨텍스트 창의 10%를 넘으면 Claude Code가 자동으로 deferred loading 모드로 전환한다. 그 아래라면 기존 방식으로 전부 로드한다.

    {
      "mcpServers": {
        "my-dev-tools": {
          "command": "node",
          "args": ["./mcp-server/index.js"],
          "env": { "ENABLE_TOOL_SEARCH": "auto" }
        }
      },
      "tools": [
        { "name": "read_file", "alwaysLoad": true }
      ]
    }

    JSON 설명. alwaysLoad: true는 특정 도구를 deferred에서 제외하는 탈출구다. 매 요청에서 거의 반드시 쓰는 도구 — 파일 읽기·쓰기처럼 모든 작업의 기반이 되는 것들 — 는 검색 라운드트립 없이 늘 손에 잡히도록 고정하는 게 낫다. 나머지는 deferred로 두고, 핵심 몇 개만 alwaysLoad로 고정하는 것이 이상적인 조합이다.

    diagram

    검색이 작동하려면 — 도구 이름 짓기가 정확도를 결정한다

    JIT 로딩의 품질은 결국 검색 정확도에 달려 있다. Tool Search Tool의 BM25 방식은 문자열 기반 키워드 검색이다. LLM이 "build"로 검색했을 때 build_app이 결과에 잡히려면, 도구 이름이나 description에 그 단어가 실제로 있어야 한다.

    이것이 도구 설계에서 의외로 중요한 제약이 된다. 도구 이름과 description은 이제 문서이자 검색 인덱스다. 도구를 만들 때 "나중에 어떤 단어로 이 도구를 찾을까?"를 먼저 생각해야 한다.

    효과가 큰 패턴이 두 가지 있다. 첫째, 서비스 접두사를 도구 이름에 붙이는 것이다. build_app보다 sdk_build_app이 낫다. "sdk"로 검색하면 해당 플랫폼 도구가 한 번에 잡히고, "build"로 검색하면 플랫폼 무관하게 빌드 도구가 잡힌다. 서버가 여러 개 붙어 있을 때 이 접두사가 검색 정밀도를 크게 높인다.

    둘째, description에는 이 도구가 하는 일뿐 아니라 언제 쓰는지를 함께 적는 것이다. "앱을 빌드한다"보다 "디바이스 배포를 위해 앱을 빌드한다. configuration(Debug/Release)과 타겟 arch(arm64/x86)를 받는다"가 검색에 더 잘 잡힌다. LLM이 "디바이스에 배포하려는데"라고 생각할 때 description의 "디바이스 배포"가 검색 키워드로 작동한다. description이 짧을수록 검색에서 놓칠 가능성이 높다.

    diagram

    왜 이게 중요한가

    deferred loading이 해결하는 건 단순한 성능 최적화가 아니다. MCP 도구의 확장 한계를 근본적으로 바꾼다. 기존에는 도구를 늘릴수록 컨텍스트가 부풀고 정확도가 떨어지는, 늘릴수록 나빠지는 구조였다. Anthropic은 이 한계를 10,000개 도구로 끌어올렸다 — 카탈로그 크기가 LLM 성능에 미치는 영향을 사실상 분리한 것이다.

    MCP 명세 레벨에서는 이 문제가 아직 해결되지 않았다. 여러 표준화 제안이 초안 상태로 있지만, 2026년 MCP 로드맵에 이 의제는 없다. deferred loading은 Anthropic이 명세를 기다리지 않고 API 레이어에서 단독으로 구현한 해법이다. Claude Code를 사용한다면 이미 이 혜택 위에 있다.

    그 바탕 위에서 남은 실질적인 설계 결정은 두 가지다. 도구 이름과 description을 검색 가능하게 짓는 것, 그리고 어떤 도구를 alwaysLoad로 고정할지 판단하는 것. 도구를 더 똑똑하게 부르게 만드는 첫걸음은, 역설적으로 도구를 덜 보여주는 것이었다.


    이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.

Designed by Tistory.