学 AI 产品 · 专业 AI 产品经理播客第 2 章 · T2 解剖 Grok Build:Rust 写的生产级 Coding Agent · EP 03
第 2 章 · EP 03

实现族、注册表与动态 MCP · 估算、百分比与严格阈值

时长 13:41音色 云健 · 男声

同步字幕

章节导航(点击跳转)

0:00开场 · 工具层和容量计算1:28
1:28实现族 · 多套协议怎么组织才不乱2:04
3:32动态 MCP · 两个固定入口换列表稳定2:13
5:46稳定投影 · canonical input 不是原始输入2:05
7:52两种来源 · 本地估算和服务端计量1:58
9:51阈值算术 · 等号边界和预留空间2:05
11:56收尾 · 六条可以带走的结论1:44
解读全文

ep06 · 实现族与动态 MCP · Canonical 投影 · 估算与阈值算术

本集对应课程章节:CHAPTER 12(实现族、注册表与动态 MCP / Canonical input 是稳定投影 / 估算、百分比与严格阈值)
内容来源:xueai.miyang.cn(小山学堂 · 洛小山《学 AI 产品,从入门到精通》),本页为二次演绎的解读与音频稿件版本。

一、本集解决什么工程问题

本集处理两个看似无关、实则同源的问题:

  • 工具层:一个产品里同时存在多套工具协议,代码怎么组织才不乱?外部 MCP 工具如何在不撑爆模型工具列表的前提下接入?
  • 容量层:那些百分比到底怎么算?85% 的等号边界如何判定?预留空间(headroom)如何前移触发点?

共同点:两者都在处理「同一件事有多个来源」。工具有多套协议,token 有本地估算与服务端计量两种来源。解法也一致——定义一层稳定的公共契约。

能力地图

维度读完本集你能做到对应证据
实现族从 namespace 找到对应实现族ToolNamespace 枚举
桥接层说明工具定义如何进入 registry 并由 ToolBridge 执行pub struct ToolBridge / register_mcp_tools
动态 MCP写出 SearchTool 与 UseTool 的真实输入字段UseToolInput { tool_name, tool_input }
归一化背出八个 canonical fields 与七个 CanonicalToolMeta 字段pub mod field 常量与结构体定义
投影语义解释 input 为何允许缺字段或整体省略input: Option<serde_json::Value>
数据来源区分本地 bytes/4 估算与服务端 usage 观测estimate_tokens
阈值算术读懂三个函数并手算 85% 等号边界与 headroom 前移量exceeds_threshold_with_headroom

二、实现族、注册表与动态 MCP

源码中的实现族

实现族内容
grok_build主要产品工具族:ReadFile、SearchReplace、Bash、Task 等
grok_build_concise精简工具族:read_file、search_replace、bash
grok_build_hashline带 hashline 语义的 read_file、edit、grep
codex兼容实现:apply_patch、read_file、list_dir、grep_files
opencoderead、write、edit、bash、glob、grep、skill、todowrite
memory / lsp / skills按能力拆出的实现模块
MCP(namespace 枚举值)运行时接入的外部工具

为何按族分? 各协议参数命名与返回格式不同,硬统一成本高且不必要。按族分开放、各自保留形态,再通过归一化层对外提供共同词汇。

三层职责

层职责
实现族解决多套协议的代码组织
registry(FinalizedToolset)负责组合与运行时注册
ToolBridge把 registry 接入会话层;含 Arc<FinalizedToolset> 与可选 TerminalBackend,提供 register_mcp_tools

动态 MCP:两个稳定入口

元工具作用输入字段
SearchTool在 ToolIndex 中按 BM25 搜索 MCP 工具,结果按 server 分组并返回描述与 input_schemaquery: String;limit: Option<u8>(默认 5)
UseTool接收发现后的合格工具名,经 InnerDispatch 或 managed gateway 调用tool_name: String(通常 server__tool);tool_input: Value(按发现的 schema 构造)

一次完整外部调用:模型先调 search_tool(传 query)→ 从返回读 input_schema → 把 tool_name 与按 schema 构造的 tool_input 传给 use_tool → 由 ToolBridge 把结果交回会话。

收益与代价:模型工具列表跨轮次稳定(永远只有两个元工具),利于提示词缓存;代价是多一轮检索,且模型必须知道搜什么关键词,未命中则工具不可用。

静态 vs 动态:内置工具在启动时静态注册进 registry,模型直接可见;MCP 工具运行时注册,默认不可见,必须走检索通路。两套机制共存于同一 registry,靠 register_mcp_tools 打通。

三、Canonical input 是稳定投影

不同 harness 可以使用不同原始参数名(如 file_path vs path)。Grok Build 把少量稳定语义投影到 x.ai/tool 元数据中,让展示、遥测和跨工具分析拥有共同词汇。

八个 canonical fields

字段含义
path文件或搜索路径
offset归一化起始位置
limit读取或结果上限
command待执行命令
description命令描述
cwd工作目录词汇
directory目录列表目标
pattern搜索模式
只有这八个。源码中不存在 content canonical field——旧页面中的虚构元数据字段与版本示例已移除。

CanonicalToolMeta 七字段


pub struct CanonicalToolMeta {
    pub version: u32,          // 数字 1,不是字符串
    pub name: String,
    pub kind: ToolKind,
    pub namespace: ToolNamespace,
    pub label: Cow<'static, str>,
    pub read_only: bool,
    pub input: Option<serde_json::Value>,  // 无稳定投影时整体省略
}
pub const TOOL_META_VERSION: u32 = 1;

input 是投影,不是镜像

input 是 canonical projection,不能当作 raw input 镜像:

  • 非共享字段(如 grep flags、replace_all)可能被丢弃;
  • 体量大的字段(编辑前后文本、完整写入内容)不进投影;
  • 完整原始输入由 raw_input 承载。

实例:一次编辑调用,原始输入 file_path / old_string / new_string / replace_all → canonical input 中只有 path 进投影,old_string 与 new_string(大字段)、replace_all(非共享字段)留在 raw_input。

公开行为对照:Claude Code 公开文档中 Read 工具使用 file_path / offset / limit;Grok Build 的归一化层把自身各工具输入映射到共同字段(如统一为 path)。此处只比较公开可见的工具输入命名,不推断其内部实现。

四、估算、百分比与严格阈值

两种数据来源

来源说明时机
本地估算 estimate_tokensUTF-8 字节长度 除以 4;单张低分辨率图片固定估值 765 token请求前、工具输出加入后,用于快速预测
服务端 usage 观测已完成请求的实际计量,真实值请求返回后,用于准确判断与对账

关键:百分比函数不获取数据、不判断来源,只处理调用方传入的数值。因此调用链可在不同阶段选用估算总量或已更新 usage。

收益是算术与来源解耦;代价是调用方必须清楚此刻传的是哪一种,否则会把估算当实测,得出「看起来精确、实际不可信」的百分比。
除以四是经验近似(英文约四字符一 token),中文与代码误差更大,只适合量级判断。源码中没有按中英文、代码类型分别计价的公式。

三个核心函数


pub fn usage_percentage(used: u64, total: u64) -> f64 {
    if total == 0 { 0.0 } else { ((used as f64) / (total as f64) * 100.0).min(100.0) }
}

pub fn exceeds_threshold(used: u64, context_window: u64, threshold_percent: u8) -> bool {
    if context_window == 0 { return false; }
    used.saturating_mul(100) >= context_window.saturating_mul(threshold_percent as u64)
}

pub fn exceeds_threshold_with_headroom(
    used: u64, context_window: u64, threshold_percent: u8, headroom: u64,
) -> bool {
    if context_window == 0 { return false; }
    used.saturating_mul(100) >= context_window
        .saturating_mul(threshold_percent as u64)
        .saturating_sub(headroom.saturating_mul(100))
}
函数语义边界处理
usage_percentage(used / total × 100).min(100)total == 0 返回 0;结果上限压到 100,不会出现 120
exceeds_thresholdused × 100 >= window × pctwindow == 0 返回 false;整数饱和乘法避免浮点舍入
..._with_headroomused × 100 >= window × pct - headroom × 100window == 0 仍返回 false;减法用 saturating_sub

边界手算

场景计算结果
exceeds_threshold(850, 1000, 85)850 × 100 = 85000 vs 1000 × 85 = 85000true(取等号)
exceeds_threshold(849, 1000, 85)84900 < 85000false
窗口 100,000 / 85% / 无 headroom100000 × 85 / 100触发点 85,000
窗口 100,000 / 85% / headroom 4,000右侧减 4000 × 100 = 400000触发点前移至 81,000
窗口 128,000 / 85% / 无 headroom128000 × 85 / 100触发点 108,800
窗口 128,000 / 85% / headroom 4,000再前移 4,000触发点 104,800

结论:85% 边界采用 >=,等于阈值时立即为 true;headroom 把触发点前移,前移量恰好等于 headroom 本身。


五、审查清单

  • 能否从 namespace 说出对应实现族及其代表工具?
  • 能否说明实现族、registry、ToolBridge 三层的职责分工?
  • 能否写出 SearchTool 的两个输入字段及 limit 默认值?
  • 能否写出 UseTool 的两个输入字段及 tool_name 的通常形式?
  • 能否说清「两个固定入口」换来了什么、代价是什么?
  • 能否背出八个 canonical fields,并指出 content 是否存在?
  • 能否说出 CanonicalToolMeta 七字段与 version 的值?
  • 能否解释 input 为何不是 raw input 镜像,哪些字段会留在 raw_input?
  • 能否区分本地 bytes/4 估算与服务端 usage 观测的适用时机?
  • 能否说明百分比函数为什么不关心数据来源?
  • 能否手算 exceeds_threshold(850, 1000, 85) 与 (849, 1000, 85)?
  • 能否算出 headroom 为 4,000 时触发点前移多少?

六、约束说明

  1. 来源约束:本集全部结论来自小山学堂 CHAPTER 12 相应三节,未引入外部资料或虚构数据。
  2. 版本约束:源码核对依据本地仓库 grok-build-main,核对日期 2026-07-17;另据本地同步副本核对的部分没有 .git 元数据,不声称对应具体 commit。
  3. 字段存在性约束:content canonical field 在源码中不存在;不得写入复述。
  4. 公式约束:不存在按中英文、代码类型分别计价的虚构公式;estimate_tokens 就是字节长度除以四。
  5. 对照约束:与 Claude Code 的比较仅限公开可见的工具输入命名,不推断其内部实现。
  6. 音频约束:配套口播稿为二次演绎配音版,已去除不便朗读的代码块与符号;题库类内容不进入音频。

七、实践提示

提示一 · 多协议先分族,再归一化

面对多套协议不要急着统一。先按来源分族、各自保留形态,再挑出少量真正共享的语义做成公共契约。Grok Build 只挑了八个字段——契约越小越稳定。

提示二 · 外部工具用「发现 + 调用」两步入场

不要让外部工具直接撑大模型的工具列表。给两个固定元工具:先检索发现(拿 schema),再按 schema 调用。这换来列表跨轮次稳定,对提示词缓存极其友好。

提示三 · 投影字段要明示「会丢什么」

设计归一化投影时,必须在文档里写清哪些字段可能被丢弃、哪些大字段不进投影、从哪儿能取回完整数据。否则下游会误把投影当完整输入,做出错误分析。

提示四 · 算术函数不要关心数据来源

把计算逻辑与数据来源解耦:函数只处理传入数值,不承担获取职责。收益是可在不同阶段复用同一套算术;代价是调用方必须显式标注自己传的是估算还是实测。

提示五 · 阈值比较用整数且明确等号语义

百分比阈值用整数交叉相乘(used × 100 >= window × pct),避开浮点舍入;并显式约定等号语义与零分母行为。写单元测试时,边界值必须正反各测一次(850 触发 / 849 不触发)。


八、Takeaway

  • 实现族解决多套工具协议的代码组织,registry 负责组合与运行时注册,ToolBridge 连接会话。动态 MCP 通过 SearchTool 发现 schema,再由 UseTool 按 schema 分发执行。
  • canonical 层追求跨 harness 的稳定公共语义。当前字段只有 path / offset / limit / command / description / cwd / directory / pattern,元数据版本为数字 1。input 可以省略敏感或体量大的字段。
  • 本地 bytes/4 估算服务于及时预测,服务端 usage 提供已完成请求的观测。共享函数负责统一算术。85% 边界采用 >=,等于阈值时立即为 true,headroom 会把触发点进一步前移。

*来源:xueai.miyang.cn(小山学堂 · 洛小山《学 AI 产品,从入门到精通》)*

*本页为二次演绎解读稿,配套音频为配音版。*