Lazy Discovery와 Progressive Disclosure
도구 목록은 토큰을 먹는다
섹션 제목: “도구 목록은 토큰을 먹는다”에이전트 시스템에서 사용 가능한 도구가 수십 개에서 수백 개로 늘어나면, 모든 도구를 컨텍스트에 포함시키는 것은 현실적이지 않습니다.
도구 하나의 정의(이름 + 설명 + 파라미터 스키마)는 평균 50~200 토큰을 차지합니다. 100개 도구 = 최대 20,000 토큰 소모입니다. 128K 컨텍스트의 15%가 도구 목록에 낭비됩니다.
전체 컨텍스트: 128,000 토큰도구 100개 × 150토큰 = 15,000 토큰 (11.7% 소모)실제 태스크에 사용 가능: 113,000 토큰
현재 태스크가 실제로 사용하는 도구: 평균 3~5개낭비되는 도구 정의: 95~97개 × 150토큰 ≈ 14,250 토큰Lazy Discovery는 필요한 도구만 적시에 컨텍스트에 로드하는 전략입니다.
Eager Loading vs Lazy Loading
섹션 제목: “Eager Loading vs Lazy Loading”| 구분 | Eager Loading | Lazy Loading |
|---|---|---|
| 도구 로드 시점 | 세션 시작 시 전부 | 필요할 때 검색하여 로드 |
| 컨텍스트 사용 | 전체 도구 목록 | 관련 도구만 |
| 발견 지연 | 없음 | 검색 1회 지연 |
| 적합한 규모 | 도구 10개 이하 | 도구 20개 이상 |
| 캐시 친화성 | 낮음 (도구 변경 시 캐시 파괴) | 높음 (시스템 프롬프트 안정) |
키워드 점수 기반 도구 검색
섹션 제목: “키워드 점수 기반 도구 검색”MCP의 listTools는 전체 도구 목록을 반환합니다. Lazy Discovery는 이 목록을 모두 로드하지 않고, 현재 태스크와 관련성이 높은 도구만 검색합니다.
interface ToolMetadata { name: string; description: string; keywords: string[]; // 도구 등록 시 부여하는 키워드 category: string;}
function scoreToolRelevance( tool: ToolMetadata, query: string): number { const queryTokens = tokenize(query.toLowerCase()); let score = 0;
for (const token of queryTokens) { // 이름 일치: 높은 가중치 if (tool.name.toLowerCase().includes(token)) score += 3;
// 키워드 일치: 중간 가중치 if (tool.keywords.some((k) => k.toLowerCase().includes(token))) score += 2;
// 설명 일치: 낮은 가중치 if (tool.description.toLowerCase().includes(token)) score += 1; }
return score;}
async function discoverTools( allTools: ToolMetadata[], taskDescription: string, topK: number = 5): Promise<ToolMetadata[]> { const scored = allTools.map((tool) => ({ tool, score: scoreToolRelevance(tool, taskDescription), }));
return scored .filter(({ score }) => score > 0) .sort((a, b) => b.score - a.score) .slice(0, topK) .map(({ tool }) => tool);}점진적 공개 (Progressive Disclosure)
섹션 제목: “점진적 공개 (Progressive Disclosure)”태스크가 진행되면서 필요한 도구가 달라집니다. 처음부터 모든 도구를 제공하는 대신, 단계별로 필요한 도구를 추가 제공합니다.
class ProgressiveToolDisclosure { private loadedTools = new Set<string>();
async getToolsForStep( stepDescription: string, allTools: ToolMetadata[] ): Promise<LoadedTool[]> { // 이미 로드된 도구 유지 + 새로 관련된 도구 추가 const relevant = await discoverTools(allTools, stepDescription, 5);
const newTools = relevant.filter( (t) => !this.loadedTools.has(t.name) );
// 새 도구를 로드된 목록에 추가 for (const tool of newTools) { this.loadedTools.add(tool.name); }
// 현재까지 로드된 모든 도구 반환 return allTools .filter((t) => this.loadedTools.has(t.name)) .map((t) => loadFullToolDefinition(t.name)); }}실제 사용 흐름:
스텝 1: "파일 읽기" → read_file, list_directory 로드 (2개)스텝 2: "코드 분석" → analyze_code, run_tests 추가 (4개)스텝 3: "수정 사항 저장" → write_file 추가 (5개)
vs Eager Loading: 전 스텝에서 100개 전부 로드동적 네임스페이싱
섹션 제목: “동적 네임스페이싱”도구가 많을 때 카테고리 기반 네임스페이스로 구조화하면 검색 효율이 높아집니다.
const toolNamespaces = { 'file.*': ['file.read', 'file.write', 'file.delete', 'file.list'], 'git.*': ['git.commit', 'git.push', 'git.diff', 'git.log'], 'web.*': ['web.search', 'web.fetch', 'web.screenshot'], 'code.*': ['code.lint', 'code.format', 'code.test', 'code.analyze'],};
// 네임스페이스 매칭으로 관련 도구 그룹 빠르게 발견function getNamespaceTools(query: string): string[] { const matchedNamespaces = Object.entries(toolNamespaces) .filter(([ns]) => queryMatchesNamespace(query, ns)) .flatMap(([, tools]) => tools);
return matchedNamespaces;}로짓 마스킹으로 도구 비활성화
섹션 제목: “로짓 마스킹으로 도구 비활성화”Manus에서 사용하는 고급 기법입니다. KV-Cache를 보존하기 위해 도구를 컨텍스트에서 제거하는 대신, 디코딩 단계에서 특정 도구 호출 토큰의 로짓을 마스킹합니다.
일반적 비활성화 방법: 시스템 프롬프트에서 도구 제거 → 캐시 무효화 → 재계산 비용 발생
로짓 마스킹 방법: 시스템 프롬프트는 그대로 유지 (캐시 보존) → 디코딩 시 비활성 도구 토큰의 확률을 -∞으로 설정 → 모델이 해당 도구를 선택할 수 없게 됨# 개념적 구현 (추론 엔진 레벨)def apply_tool_mask(logits, disabled_tools, tokenizer): for tool_name in disabled_tools: # 도구 호출 시작 토큰 시퀀스 찾기 tool_tokens = tokenizer.encode(f'"{tool_name}"') for token_id in tool_tokens: logits[token_id] = float('-inf') # 선택 불가 return logits전략 선택 가이드
섹션 제목: “전략 선택 가이드”| 상황 | 권장 전략 |
|---|---|
| 도구 수 < 20 | Eager Loading, 단순하게 유지 |
| 도구 수 20~100 | 키워드 점수 기반 Lazy Discovery |
| 도구 수 > 100 | 네임스페이스 + 점진적 공개 조합 |
| 자체 호스팅 모델 | 로짓 마스킹으로 KV-Cache 보존 |
| API 기반 서비스 | 컨텍스트에서 도구 제거 방식 |
Lazy Discovery는 필요한 도구만 적시에 로드하여 컨텍스트 예산을 절약합니다. 키워드 점수 기반 검색으로 태스크와 관련된 상위 K개 도구를 선별하고, 점진적 공개로 태스크 단계마다 필요한 도구를 추가합니다. 자체 호스팅 환경에서는 로짓 마스킹으로 KV-Cache를 보존하면서 도구를 비활성화할 수 있습니다.