本集对应课程章节:CHAPTER 12(实现族、注册表与动态 MCP / Canonical input 是稳定投影 / 估算、百分比与严格阈值)
内容来源:xueai.miyang.cn(小山学堂 · 洛小山《学 AI 产品,从入门到精通》),本页为二次演绎的解读与音频稿件版本。
本集处理两个看似无关、实则同源的问题:
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 |
| 实现族 | 内容 |
|---|---|
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 |
opencode | read、write、edit、bash、glob、grep、skill、todowrite |
memory / lsp / skills | 按能力拆出的实现模块 |
MCP(namespace 枚举值) | 运行时接入的外部工具 |
为何按族分? 各协议参数命名与返回格式不同,硬统一成本高且不必要。按族分开放、各自保留形态,再通过归一化层对外提供共同词汇。
| 层 | 职责 |
|---|---|
| 实现族 | 解决多套协议的代码组织 |
registry(FinalizedToolset) | 负责组合与运行时注册 |
ToolBridge | 把 registry 接入会话层;含 Arc<FinalizedToolset> 与可选 TerminalBackend,提供 register_mcp_tools |
| 元工具 | 作用 | 输入字段 |
|---|---|---|
SearchTool | 在 ToolIndex 中按 BM25 搜索 MCP 工具,结果按 server 分组并返回描述与 input_schema | query: 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 打通。
不同 harness 可以使用不同原始参数名(如 file_path vs path)。Grok Build 把少量稳定语义投影到 x.ai/tool 元数据中,让展示、遥测和跨工具分析拥有共同词汇。
| 字段 | 含义 |
|---|---|
path | 文件或搜索路径 |
offset | 归一化起始位置 |
limit | 读取或结果上限 |
command | 待执行命令 |
description | 命令描述 |
cwd | 工作目录词汇 |
directory | 目录列表目标 |
pattern | 搜索模式 |
只有这八个。源码中不存在 content canonical field——旧页面中的虚构元数据字段与版本示例已移除。
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 是 canonical projection,不能当作 raw input 镜像:
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_tokens | UTF-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_threshold | used × 100 >= window × pct | window == 0 返回 false;整数饱和乘法避免浮点舍入 |
..._with_headroom | used × 100 >= window × pct - headroom × 100 | window == 0 仍返回 false;减法用 saturating_sub |
| 场景 | 计算 | 结果 |
|---|---|---|
exceeds_threshold(850, 1000, 85) | 850 × 100 = 85000 vs 1000 × 85 = 85000 | true(取等号) |
exceeds_threshold(849, 1000, 85) | 84900 < 85000 | false |
| 窗口 100,000 / 85% / 无 headroom | 100000 × 85 / 100 | 触发点 85,000 |
| 窗口 100,000 / 85% / headroom 4,000 | 右侧减 4000 × 100 = 400000 | 触发点前移至 81,000 |
| 窗口 128,000 / 85% / 无 headroom | 128000 × 85 / 100 | 触发点 108,800 |
| 窗口 128,000 / 85% / headroom 4,000 | 再前移 4,000 | 触发点 104,800 |
结论:85% 边界采用 >=,等于阈值时立即为 true;headroom 把触发点前移,前移量恰好等于 headroom 本身。
ToolBridge 三层的职责分工?SearchTool 的两个输入字段及 limit 默认值?UseTool 的两个输入字段及 tool_name 的通常形式?content 是否存在?CanonicalToolMeta 七字段与 version 的值?input 为何不是 raw input 镜像,哪些字段会留在 raw_input?bytes/4 估算与服务端 usage 观测的适用时机?exceeds_threshold(850, 1000, 85) 与 (849, 1000, 85)?grok-build-main,核对日期 2026-07-17;另据本地同步副本核对的部分没有 .git 元数据,不声称对应具体 commit。content canonical field 在源码中不存在;不得写入复述。estimate_tokens 就是字节长度除以四。面对多套协议不要急着统一。先按来源分族、各自保留形态,再挑出少量真正共享的语义做成公共契约。Grok Build 只挑了八个字段——契约越小越稳定。
不要让外部工具直接撑大模型的工具列表。给两个固定元工具:先检索发现(拿 schema),再按 schema 调用。这换来列表跨轮次稳定,对提示词缓存极其友好。
设计归一化投影时,必须在文档里写清哪些字段可能被丢弃、哪些大字段不进投影、从哪儿能取回完整数据。否则下游会误把投影当完整输入,做出错误分析。
把计算逻辑与数据来源解耦:函数只处理传入数值,不承担获取职责。收益是可在不同阶段复用同一套算术;代价是调用方必须显式标注自己传的是估算还是实测。
百分比阈值用整数交叉相乘(used × 100 >= window × pct),避开浮点舍入;并显式约定等号语义与零分母行为。写单元测试时,边界值必须正反各测一次(850 触发 / 849 不触发)。
ToolBridge 连接会话。动态 MCP 通过 SearchTool 发现 schema,再由 UseTool 按 schema 分发执行。path / offset / limit / command / description / cwd / directory / pattern,元数据版本为数字 1。input 可以省略敏感或体量大的字段。bytes/4 估算服务于及时预测,服务端 usage 提供已完成请求的观测。共享函数负责统一算术。85% 边界采用 >=,等于阈值时立即为 true,headroom 会把触发点进一步前移。*来源:xueai.miyang.cn(小山学堂 · 洛小山《学 AI 产品,从入门到精通》)*
*本页为二次演绎解读稿,配套音频为配音版。*