工具懒加载优化 (Tool Lazy Loading) 🚀
"从 53k tokens 到 2.6k tokens —— Claude Code 如何通过动态工具搜索实现 95% 的上下文削减"
1. 问题背景
1.1 传统 MCP 工具加载的困境
在引入工具懒加载之前,系统采用全量预加载模式:
typescript
// 传统方式:会话启动时加载所有工具
async function initializeSession() {
const allTools = [];
for (const server of mcpServers) {
const tools = await server.listTools();
allTools.push(...tools);
}
systemPrompt += formatToolDefinitions(allTools);
// 结果:一个简单的 "hi" 就消耗 53,000 tokens
}问题分析:
| 问题 | 影响 | 数据 |
|---|---|---|
| Token 浪费 | 大量无关工具占用上下文 | 53k tokens |
| 响应延迟 | 加载时间长 | ~5 秒首次响应 |
| 成本高昂 | 每条消息都携带全量工具 | 10x 成本增加 |
| 扩展性差 | 工具数量受限 | 最多 20-30 个工具 |
2. 懒加载架构设计
2.1 核心思想
渐进式工具发现:
传统方式: 启动 → 加载所有工具 → 等待用户输入 → 响应
懒加载方式: 启动 → 加载工具列表 → 用户输入 → 搜索相关工具 → 加载定义 → 响应2.2 三层架构
Layer 1: 工具元数据 (启动时加载)
- 工具名称
- 简短描述 (1 句话)
- 所属服务器
Layer 2: 工具搜索 (用户输入时)
- 语义搜索相关工具
- 关键词匹配
- 返回 top-k 工具
Layer 3: 工具定义 (使用时加载)
- 完整参数定义
- 详细描述
- 使用示例3. 实现细节
3.1 元数据加载
typescript
interface ToolMetadata {
name: string;
description: string; // 简短描述,≤100 字符
server: string;
}
class ToolRegistry {
private metadata: ToolMetadata[] = [];
private fullDefinitions: Map<string, ToolDefinition> = new Map();
async initialize() {
// 只加载元数据
for (const server of this.mcpServers) {
const tools = await server.listTools();
this.metadata.push(...tools.map(t => ({
name: t.name,
description: t.description.slice(0, 100),
server: server.name
})));
}
// 消耗: ~2k tokens (vs 53k)
}
}3.2 语义搜索
typescript
class ToolSearchEngine {
private embeddings: Map<string, number[]> = new Map();
async indexTools(tools: ToolMetadata[]) {
for (const tool of tools) {
const text = `${tool.name} ${tool.description}`;
const embedding = await getEmbedding(text);
this.embeddings.set(tool.name, embedding);
}
}
async search(query: string, limit: number = 5): Promise<ToolMetadata[]> {
const queryEmbedding = await getEmbedding(query);
const scores = [];
for (const [name, embedding] of this.embeddings) {
const similarity = cosineSimilarity(queryEmbedding, embedding);
scores.push({ name, similarity });
}
return scores
.sort((a, b) => b.similarity - a.similarity)
.slice(0, limit)
.map(s => this.getMetadata(s.name));
}
}3.3 按需加载完整定义
typescript
class ToolRegistry {
async getToolDefinition(name: string): Promise<ToolDefinition> {
// 检查缓存
if (this.fullDefinitions.has(name)) {
return this.fullDefinitions.get(name)!;
}
// 加载完整定义
const metadata = this.metadata.find(t => t.name === name);
const server = this.mcpServers.get(metadata.server);
const definition = await server.getTool(name);
// 缓存
this.fullDefinitions.set(name, definition);
return definition;
}
}4. 工作流程
4.1 完整流程
typescript
class LazyToolAgent {
async handleUserInput(input: string) {
// 1. 搜索相关工具
const relevantTools = await this.toolSearch.search(input, 5);
// 2. 加载工具定义
const toolDefinitions = await Promise.all(
relevantTools.map(t => this.toolRegistry.getToolDefinition(t.name))
);
// 3. 注入到提示词
const systemPrompt = this.buildSystemPrompt(toolDefinitions);
// 4. 调用 LLM
const response = await this.llm.chat({
system: systemPrompt,
messages: [{ role: 'user', content: input }],
tools: toolDefinitions
});
return response;
}
buildSystemPrompt(tools: ToolDefinition[]): string {
return `
You have access to the following tools:
${tools.map(t => `- ${t.name}: ${t.description}`).join('\n')}
Use these tools to help the user.
`.trim();
}
}4.2 示例对话
用户: "读取 package.json 文件"
1. 搜索工具:
search("读取 package.json 文件")
→ ["read_file", "list_files", "search_files"]
2. 加载定义:
getToolDefinition("read_file")
→ { name: "read_file", parameters: { path: string }, ... }
3. 注入提示词:
"You have access to: read_file, list_files, search_files"
4. LLM 响应:
tool_use: read_file({ path: "package.json" })5. 性能对比
5.1 Token 消耗
| 场景 | 传统方式 | 懒加载 | 改进 |
|---|---|---|---|
| 启动 | 53,000 | 2,600 | 95% ↓ |
| 简单对话 | 53,000 | 2,600 | 95% ↓ |
| 使用 1 个工具 | 53,000 | 3,200 | 94% ↓ |
| 使用 5 个工具 | 53,000 | 6,500 | 88% ↓ |
5.2 响应时间
| 操作 | 传统方式 | 懒加载 | 改进 |
|---|---|---|---|
| 首次启动 | 5.2s | 0.8s | 85% ↓ |
| 简单查询 | 1.5s | 0.3s | 80% ↓ |
| 工具调用 | 2.1s | 0.9s | 57% ↓ |
5.3 成本节省
传统方式:
- 1000 次对话
- 平均 53k tokens/次
- 总计: 53M tokens
- 成本: $159 (按 $3/M tokens)
懒加载:
- 1000 次对话
- 平均 5k tokens/次
- 总计: 5M tokens
- 成本: $15
节省: $144 (91%)6. 优化技巧
6.1 缓存策略
typescript
class ToolCache {
private cache: Map<string, { definition: ToolDefinition; timestamp: number }> = new Map();
private maxAge = 5 * 60 * 1000; // 5 分钟
async get(name: string): Promise<ToolDefinition | null> {
const cached = this.cache.get(name);
if (cached && Date.now() - cached.timestamp < this.maxAge) {
return cached.definition;
}
return null;
}
set(name: string, definition: ToolDefinition) {
this.cache.set(name, {
definition,
timestamp: Date.now()
});
}
}6.2 预加载常用工具
typescript
class ToolRegistry {
private commonTools = ['read_file', 'write_file', 'run_command'];
async initialize() {
// 加载元数据
await this.loadMetadata();
// 预加载常用工具
await Promise.all(
this.commonTools.map(name => this.getToolDefinition(name))
);
}
}6.3 批量加载
typescript
async getToolDefinitions(names: string[]): Promise<ToolDefinition[]> {
// 分组:已缓存 vs 需要加载
const cached = names.filter(n => this.fullDefinitions.has(n));
const toLoad = names.filter(n => !this.fullDefinitions.has(n));
// 批量加载
const loaded = await Promise.all(
toLoad.map(name => this.loadToolDefinition(name))
);
return [
...cached.map(n => this.fullDefinitions.get(n)!),
...loaded
];
}7. 挑战与解决方案
7.1 搜索准确性
挑战: 语义搜索可能遗漏相关工具
解决方案: 混合搜索
typescript
async search(query: string): Promise<ToolMetadata[]> {
// 1. 语义搜索
const semanticResults = await this.semanticSearch(query, 10);
// 2. 关键词搜索
const keywordResults = this.keywordSearch(query, 10);
// 3. 合并去重
const merged = this.mergeResults(semanticResults, keywordResults);
return merged.slice(0, 5);
}7.2 冷启动延迟
挑战: 首次搜索需要生成 embedding
解决方案: 预计算 embeddings
typescript
async initialize() {
await this.loadMetadata();
// 后台预计算 embeddings
this.indexTools(this.metadata).catch(console.error);
}7.3 工具依赖
挑战: 某些工具依赖其他工具
解决方案: 依赖图
typescript
const toolDependencies = {
'edit_file': ['read_file'],
'run_tests': ['read_file', 'run_command']
};
async getToolWithDependencies(name: string): Promise<ToolDefinition[]> {
const tools = [await this.getToolDefinition(name)];
const deps = toolDependencies[name] || [];
for (const dep of deps) {
tools.push(await this.getToolDefinition(dep));
}
return tools;
}8. 总结
8.1 核心收益
- 95% Token 削减: 53k → 2.6k
- 85% 启动加速: 5.2s → 0.8s
- 91% 成本节省: $159 → $15
- 无限扩展性: 支持数百个工具
8.2 设计原则
- 元数据优先: 启动时只加载最小信息
- 按需加载: 使用时才加载完整定义
- 智能搜索: 语义 + 关键词混合搜索
- 缓存优化: 缓存常用工具和最近使用的工具
8.3 通用模式
typescript
// 通用的懒加载模式
class LazyResourceLoader<T> {
async initialize() {
this.metadata = await this.loadMetadata();
}
async load(query: string): Promise<T[]> {
const relevant = await this.search(query);
return await this.loadFull(relevant);
}
private cache = new Map<string, T>();
}