AI 에이전트는 어떻게 1초 만에 686개의 기술 중 하나를 선택할까?
요약
Claude Code와 같은 AI 에이전트가 방대한 기술(skills)을 효율적으로 선택하기 위한 '시맨틱 라우터' 패턴을 제안합니다. 기존의 점진적 공개 방식이 가진 컨텍스트 윈도우 점유 및 어텐션 저하 문제를 임베딩 기반 검색으로 해결하여 비용과 성능을 최적화하는 실험 결과를 다룹니다.
핵심 포인트
- 시맨틱 라우터 패턴으로 컨텍스트 윈도우 사용량을 약 456배 절감 가능
- 임베딩 기반 검색을 통해 1초 미만의 낮은 지연 시간 달성
- 기술 수가 늘어날수록 발생하는 인덱스 문제와 어텐션 저하 해결
- Top-5 클러스터 정확도 87.5%의 높은 효율성 입증
저는 Claude Code 에이전트를 위한 "시맨틱 라우터로서의 기술 (skills as semantic router)" 패턴에 대해 경험적 테스트를 수행했습니다. 4,556개의 기술로 구성된 커뮤니티 코퍼스(corpus)에서 무작위로 샘플링한 686개의 기술을 mesh-memory에 인덱싱하고, 단일 sentence-transformer 모델로 임베딩(embedding)한 뒤, 8개의 고정된 작업 쿼리 세트를 실행했습니다. 주요 수치는 다음과 같습니다: 엄격한 top-1 정확도(accuracy) 62.5%, top-5 클러스터 정확도 87.5%, 1초 미만의 쿼리 지연 시간(latency), 작업당 약 500개의 토큰 로드. 이는 Anthropic의 점진적 공개(progressive disclosure) 방식이 적용된 기본 동작(시스템 프롬프트에 4,556개 기술 전체의 이름과 설명을 유지하기 위해 약 228K 토큰이 소모됨)과 비교했을 때의 수치입니다. 즉, 적절한 기술이 에이전트의 top-5 후보에 8번 중 7번이나 포함되면서, 약 456배의 컨텍스트 윈도우(context-window) 절감 효과를 거두었습니다. 이 포스트에서는 제가 왜 이 테스트를 수행했는지, 어떻게 설정했는지, 결과가 실제로 무엇을 보여주는지, 그리고 이 패턴이 솔직히 어디에서 한계를 보이는지 설명합니다. 실행기(runner)와 쿼리의 전체 소스는 재현 가능합니다.
규모가 커질 때 점진적 공개(progressive disclosure)가 충분하지 않은 이유
Anthropic의 Claude Code 기술(및 Cursor의 유사 기능, 그리고 다른 모든 에이전트 프레임워크의 기술들)은 폴더 내의 마크다운(markdown) 파일로 제공됩니다. 각 파일의 프론트매터(frontmatter)에는 이름과 짧은 설명이 포함되어 있습니다. 기본 로딩 전략은 Anthropic이 "점진적 공개 (progressive disclosure)"라고 부르는 방식입니다. 즉, 에이전트는 시작 시 모든 기술의 이름과 설명을 시스템 프롬프트로 읽어 들인 다음, 특정 기술을 호출하기로 결정했을 때만 전체 본문을 로드합니다. 점진적 공개는 본문 문제(body problem)를 해결합니다. 즉, 사용하지 않는 기술의 본문에 대한 비용을 지불하지 않아도 됩니다. 하지만 인덱스 문제(index problem)는 해결하지 못합니다. 어떤 작업이 시작되기도 전에, 매 세션마다 모든 기술의 이름과 설명이 로드됩니다. 기술이 50개라면 카탈로그에 약 2.5K 토큰을 사용하게 됩니다. 기술이 200개라면, 질문을 던지기도 전에 카탈로그가 Claude Sonnet 200K 컨텍스트 윈도우의 5%를 차지해 버립니다.
수식은 빠르게 복잡해집니다:
| 기술 수 | 카탈로그 내 이름 + 설명 토큰 | 200K 컨텍스트 점유율 |
|---|---|---|
| 100 | ~5K | 2.5% |
| 500 | ~25K | 12.5% |
| 1,000 | ~50K | 25% |
| 2,000 | ~100K | 50% |
| 4,000 | ~200K | 수용 불가 |
| 4,556 (전체 커뮤니티 코퍼스) | ~228K | 초과 |
카탈로그가 물리적으로 들어갈 수 있는 경우라 하더라도, 유사한 항목이 나열된 긴 목록에서는 어텐션 (Attention) 품질이 저하됩니다. 항목이 약 1,000개를 넘어가면 에이전트는 눈으로 구별 가능한 사례에서도 잘못된 선택을 하기 시작합니다. 또한 가비지 컬렉션 (Garbage collection)이 없습니다. 아무도 오래된 기술을 제거하지 않고, 아무도 중복 항목을 표시하지 않으며, 카탈로그는 계속해서 커지기만 합니다.
시맨틱 라우터 (Semantic router) 패턴은 카탈로그를 프롬프트 (Prompt)로부터 분리합니다. 각 기술의 이름과 설명은 임베딩 인덱스 (Embedding index)에 한 번 저장되며, 디스크 상의 SKILL.md 파일을 가리키는 태그가 함께 저장됩니다. 작업 시점에 에이전트는 작업 설명에 대해 단 한 번의 시맨틱 검색 (Semantic-search) 호출을 실행하여 상위 5개의 후보를 얻고, 그중 하나를 선택한 뒤, 선택된 항목에 대해서만 전체 본문을 읽습니다. 턴 (Turn)당 토큰 비용은 카탈로그 크기와 상관없이 일정합니다.
이것이 이론입니다. 문제는 실제 환경에서 검색이 정말로 관련 있는 기술을 반환하느냐 하는 것입니다.
테스트 설정
코퍼스 (Corpus): antigravity-awesome-skills, 커뮤니티에서 제공하는 Anthropic 형식 기술들의 공개 컬렉션입니다. 디렉토리별로 중복을 제거한 4,556개의 SKILL.md 파일로 구성됩니다. 각 파일은 YAML 프론트매터 (YAML frontmatter, 이름, 설명, 태그 포함)와 마크다운 (Markdown) 본문을 가집니다.
샘플 (Sample): 정렬된 파일 목록에서 random.shuffle(seed=42)을 통해 무작위로 선택된 1,000개의 기술입니다. 이 중 약 200개는 mesh bulk-ingest 엔드포인트에 의해 조용히 거부되었고 (아마도 콘텐츠 검증 필터 때문일 것입니다), 25개는 단일 웨이브 (Wave) 전송 중에 실패했으며, 86개는 임베딩 후
intfloat/multilingual-e5-base를 사용하였으며, sentence-transformers를 통해 로컬에서 실행하였고, 768차원 벡터 (768-dimensional vectors)를 Postgres + pgvector에 저장했습니다. 10개의 병렬 임베딩 워커 (parallel embedding workers)를 사용하여, 단일 CPU 컨테이너에서 분당 약 38개의 문서 (throughput ~38 docs/minute)를 처리했습니다.
쿼리 (Queries). 코퍼스 (corpus)를 확인하기 전에 작성된, 일반적인 개발 작업 범위를 포괄하도록 설계된 8개의 다양한 작업 설명입니다: "deploy docker production" (도커 프로덕션 배포), "analyze stock market data" (주식 시장 데이터 분석), "write marketing email" (마케팅 이메일 작성), "optimize slow SQL query" (느린 SQL 쿼리 최적화), "security audit web app" (웹 앱 보안 감사), "set up CI CD pipeline python" (파이썬 CI/CD 파이프라인 설정), "debug memory leak C++" (C++ 메모리 누수 디버깅), "build React TypeScript component" (React TypeScript 컴포넌트 구축). 각 쿼리에 대해 mesh에게 가장 유사한 상위 5개 기술을 요청하였고, 이름과 코사인 유사도 (cosine similarity) 점수를 검토했습니다.
지표 (Metrics). 엄격한 Top-1 (Strict top-1): 첫 번째 결과가 인간 검토자가 해당 작업에 대해 망설임 없이 선택할 법한 종류의 기술인 경우. 느슨한 Top-1 (Loose top-1): 첫 번째 결과가 올바른 범주에 속하지만 완벽하게 일치하지는 않는 경우 (예: Docker 배포 쿼리에 대해 Azure 배포 기술이 나오는 경우). Top-5 클러스터 (Top-5 cluster): 상위 5개 결과 중 적어도 하나가 에이전트가 합리적으로 읽고 사용할 수 있는 강력한 일치 항목인 경우.
결과 (The results). 686개의 기술이 모두 인덱싱된 후, 쿼리별 결과입니다:
| 쿼리 | Top-1 결과 (유사도) | 클러스터 판정 |
|---|---|---|
| Strict deploy docker production | azd-deployment (0.86) | 5개 중 3개가 배포 기술 (azd, appdeploy, vercel) - 느슨함 |
| analyze stock market data | xvary-stock-research (0.87) + 4위에 alpha-vantage | YES |
| write marketing email | copywriting (0.86) | 클러스터 내에 blog-writing, writer 포함 - YES |
| optimize slow SQL query | food-database-query (0.85) | 4위에 spark-optimization, 실제 SQL 기술 없음 - NO |
| security audit web app | laravel-security-audit (0.88) | aws-security, burp-suite, web-security-testing 포함 - 5개 중 4개가 YES |
| set up CI CD pipeline python | gitlab-ci-patterns (0.87) | 2위에 circleci-automation 포함 - 느슨함 |
| debug memory leak C++ | c-pro (0.86) | gdb-cli, debugger, systematic-debugging 포함 - YES |
| build React TypeScript component | react-flow-node-ts (0.88) | 5개 모두 프론트엔드 관련 - YES |
엄격한 Top-1: 8개 중 5개 = 62.5%. Top-5 클러스터: 8개 중 7개 = 87.5%. Top-1에 대한 코사인 유사도 범위: 0.83-0.88.
쿼리 지연 시간 (Query latency): 모든 실행에서 1초 미만. 수렴 곡선 (The convergence curve) 코퍼스 (Corpus)는 100개의 기술(skill) 단위로 파동(wave)을 일으키며 구축되었으며, 각 파동이 끝날 때마다 전체 쿼리 세트를 다시 실행했습니다. 이를 통해 코퍼스 깊이(corpus depth)에 따라 라우터(router)의 품질이 어떻게 확장되는지 파악할 수 있습니다.
| Wave | Indexed | Strict top-1 | Top-5 cluster | Notable arrivals |
|---|---|---|---|---|
| 0 | 91 | 25% (2/8) | ~70% | debugger, javascript-typescript-scaffold |
| 1 | 177 | 43% (3/7*) | ~85% | web-security-testing, alpha-vantage, performance-optimizer |
| 5 | 500 | ~50% (4/7*) | ~85% | copywriting, laravel-security-audit, gitlab-ci-patterns, c-pro, react-flow-node-ts |
| 9 | 686 | 62.5% (5/8) | 87.5% | xvary-stock-research * |
*실행 중 쿼리 하나가 타임아웃됨
두 가지 관찰 결과가 매우 강력한 시사점을 줍니다. 첫째, Top-5 클러스터 (Top-5 cluster)는 조기에 포화 상태에 도달합니다. 인덱싱된 기술이 500개(전체 코퍼스의 약 11%)에 도달했을 때, 클러스터 지표는 이미 85%에 도달했으며 이후 186개가 추가되어도 거의 움직이지 않았습니다. 대부분의 쿼리에 대해 관련 기술 제품군(family of skills)이 이미 인덱스에 포함되어 있었으며, 나중에 변한 점은 클러스터를 이끄는 구성원이 무엇인지였습니다. 둘째, Strict top-1 (엄격한 Top-1)은 계속해서 상승합니다. 인덱싱된 기술이 500개에서 686개로 늘어나면서 top-1이 50%에서 62.5%로 상승했습니다. 이러한 개선은 특정 기술(xvary-stock-research)이 마침내 샘플링된 부분에 포함되면서 발생했습니다. 각 새로운 파동은 8개의 쿼리 중 하나에 대해 정확히 일치하는 기술(exact-match skill)이 나타날 기회였습니다.
라우터가 정직하게 실패할 때: SQL 쿼리(SQL query)는 결코 실제 top-1을 생성하지 못했습니다. top-1은
실질적인 결과: 이 패턴은 기술 컬렉션(skill collection)이 성장할수록 승리합니다. 기술이 30개 미만일 때는 이를 미리 로드(eager-load)하는 것이 의미가 없습니다. 100개를 넘어서면 수학적 계산이 중요해지기 시작합니다. 1,000개를 넘어서면 이것이 유일하게 지속 가능한 옵션이 됩니다.
작업 턴당 토큰 계산 (The token math Per task turn):
- 기본 로딩 (4,556개 기술의 이름 + 설명): 시스템 프롬프트(system prompt)에서 약 228,000 토큰을 소모합니다. 이는 200K 컨텍스트 윈도우(context window)에 들어가지 않습니다. 1M 컨텍스트 윈도우에서는 카탈로그(catalog)에만 예산의 23%를 소비합니다.
- 점진적 공개(Progressive disclosure)는 인덱스(index)가 아닌 본문(bodies)을 구합니다.
- 라우터(Router): 검색 요청 1회 (무시할 만한 토큰), 상위 5개 결과 반환 (~500 토큰), 선택된 기술에 대한 1개의 전체 SKILL.md 읽기 (크기에 따라 ~500-1,500 토큰).
- 턴당 총합: 2K 토큰 미만. 이는 어떤 기술이 읽히느냐에 따라 100배에서 450배까지의 감소를 의미합니다.
- 핵심: 라우터 비용은 카탈로그에 100개의 기술이 있든 100,000개의 기술이 있든 일정합니다.
솔직하게 밝히는 미결제 이슈 (Open issues):
- 임베딩 정체 누수 (Stuck-on-embed leak): 메쉬 데이터베이스(mesh database)에 들어간 772개의 문서 중 86개(11.1%)가 "대기 중(pending)" 상태에 머물러 임베딩(embedding)이 생성되지 않았습니다. 메쉬 메모리(mesh-memory) 워커(worker) 코드의 MAX_CONSECUTIVE_ERRORS 값이 10으로 설정되어 있어, 잘못된 문서가 발생하면 워커가 중단되고 나머지 큐(queue)를 조용히 남겨둡니다. 업스트림(upstream) 이슈로 제기할 가치가 있으며, 당분간은 새 컨테이너에서 워커를 재시작하여 백로그(backlog)를 해소해야 합니다.
- 조용한 대량 수집 누락 (Silent bulk-ingest drops): 전송된 1,000개의 문서 중 약 200개가 데이터베이스에 전혀 나타나지 않았습니다. 대량 PUT(bulk PUT)은 성공을 반환했지만, 메쉬(mesh)에 저장된 행(row)의 수는 게시된 수보다 적었습니다. 아마도 비어 있거나 거의 비어 있는 문서에 대한 콘텐츠 검증 필터(content-validation filter) 때문일 가능성이 높습니다. 조사할 가치는 있지만 검색 품질 결과에는 영향을 미치지 않았습니다.
- 10개 워커로 증설: 기본 NUM_WORKERS = 3 설정은 단일 CPU 컨테이너에서 병목 현상(bottlenecking)을 일으킵니다. 이를 10으로 늘렸을 때 성능 저하 없이 처리량(throughput)이 4배 증가했습니다 (~9 → ~38 docs/min). 이 변경 사항은 다음 메쉬 릴리스에서 파라미터화(parameterizable)되어야 합니다.
- 쿼리 타임아웃 (Query timeouts): 웨이브-5(wave-5) 실행 중 하나의 쿼리가 기본 30초에서 타임아웃이 발생했으나, 재실행 시 성공했습니다.
검색 정확도(search-correctness)의 문제가 아니라, 아마도 콜드 캐시(cold-cache) 워밍업 문제일 가능성이 높습니다. 직접 재현해 보세요. 이 테스트에 사용된 모든 것은 오픈 소스입니다. 러너(Runner)는 대략 70줄의 Python 코드로 구성되어 있습니다:
- 코퍼스(Corpus): antigravity-awesome-skills
- 메모리 저장소(Memory store): mesh-memory (MIT 라이선스, Claude Code 및 Cursor를 위한 MCP 서버가 포함되어 있음)
- 러너(Runner) + 쿼리(queries) + 원시 결과(raw results): 이 기사와 함께 게시되었습니다.
코퍼스를 mesh에 넣고 몇 가지 쿼리를 실행한 뒤 결과가 어떻게 나오는지 확인해 보세요. 만약 이 패턴이 귀하의 워크플로우에 적합하다면, 연결 방법은 매우 간단합니다. 귀하의 claude.md 파일에 "어떤 작업을 수행하기 전에, 작업 설명과 일치하는 type:skill을 mesh에서 검색한 다음 최상위 결과를 로드하라"라는 단 한 번의 MCP 호출을 추가하면 됩니다.
관련 정보:
아직 읽어보지 않으셨다면, 동반 포스트인 "파일에 파묻히지 않고 Claude 기술을 정리하는 방법(How to Organize Your Claude Skills Without Drowning in Files)"을 확인해 보세요. 이 포스트는 동일한 패턴의 버전 관리 측면을 다룹니다. 즉, 기술을 날짜 태그가 있는 메모리 문서로 저장하여, 최신 버전은 단 한 번의 쿼리로 가져올 수 있고 이전 버전은 삭제할 필요가 없도록 하는 방식입니다. 두 포스트를 함께 읽으면 "기술을 어떻게 합리적으로 저장하는가"와 "기술을 어떻게 합리적으로 검색하는가"를 모두 다루게 됩니다.
원문은 klymentiev.com에 게시되었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기