构建 MCP:我们如何把 6,000 个 Token 压缩到 500 个
在构建 MCP2 的过程中,我们遇到的最大挑战之一,就是 Token 消耗过高。
在最初的版本中,还没开始对话,模型的上下文就已经“爆表”了。
具体来说:
- 系统提示(System Prompt):约 2,000 个 token
- 工具定义(42 个工具):约 4,000 个 token(包括 Digital Twin 工具和 Navigator 工具)
- 总计:约 6,000 个 token,仅仅是初始化阶段 而当上下文越长,模型往往会变得越“笨”:
响应变慢、推理变弱、成本上升、准确率下降。
一、使用 Embedding 动态加载工具
为了解决这个问题,我们不再在启动时加载全部 42 个工具。
我们构建了一个基于 Embedding 的工具检索系统,让 MCP 可以在需要时 动态导入工具,大幅减少了默认上下文的体积。
二、引入 Tool Registry(工具注册表)
我们还引入了一个 工具注册表机制(Tool Registry),通过两个简单的元工具实现:
- discover_tool:列出所有可用工具的名称和简短说明
- describe_tool:按需加载某个工具的详细定义(包括输入输出和示例) 这样,模型只在真正需要使用某个工具时才会加载其定义,而不是一次性加载所有工具描述。
三、统一工具接口,减少重复定义
我们发现很多工具的功能高度重叠。于是我们:
- 将所有数据库相关的工具(如 entity_query、entity_phrase_query、entity_attribute_filter)合并为一个 DSL 风格的查询工具:digital_twin.query。
- 将所有与前端交互的工具合并为单一接口 navigator.patch,用于统一处理 UI 更新。 这种统一极大减少了工具定义的冗余,也让模型的调用逻辑更清晰。
四、控制上下文长度与 Token 预算
我们为每轮对话设置了 Token 预算:
- 保留最近的 前 4 条和后 4 条消息;
- 当总 token 超过 6,000 时,自动 摘要中间部分,压缩上下文。 同时,我们重新设计并简化了工具文档格式,让描述更紧凑统一,不再使用冗长的 JSON Schema 或大段示例。
五、规划器与执行器分离
在 MCP2 中,我们将系统拆分为:
- 轻量级规划器(Planner):只负责生成宏观步骤,Prompt 不超过 300 token;
- 执行器(Executor):按需加载工具定义并执行操作。 这种架构让系统更加模块化,也进一步降低了每轮对话的上下文占用。
六、优化结果:更快、更便宜、更聪明
经过这一系列优化:
- 工具相关 Token 从约 6,000 → 500
- 系统提示 Token 从约 1,000 → 400 最终,模型的响应速度明显提升,推理更稳定,成本也更低。
七、有趣的“格式模仿”现象
在实践中我们还发现一个非常有趣的现象:
AI 会模仿你提供的输出格式。
例如:
- 如果你提供 JSON 示例,它之后的回复大多会自动保持 JSON 格式;
- 如果你使用点号语法(如 config.value.max),它也会持续用这种风格。 这种“格式锚定(Format Anchoring)”现象,让模型在多轮对话中保持更强的一致性和风格稳定性,也让工具调用更加顺畅。
八、结语
优化 Token 使用,不仅仅是为了降低成本,
更重要的是让系统 更清晰、更快速、更智能。
通过动态加载工具、统一接口、压缩上下文、严格控制 Token 预算,
我们让 它变得更加有效和快速。