装上就跑 · 第一次对话发生了什么

先把 grok 装起来跑一遍——预编译二进制或从源码 cargo run——再顺着「你按下回车之后」这条线,把第一次对话经过的组件走一遍,为后面逐层深入建立实感。

作者 David更新于 第 2 篇(共 60 篇)

装上就跑 · 第一次对话发生了什么

读源码之前,先让它在你机器上转起来。有实感,后面看代码才不悬空。

怎么用:两种装法

一、装预编译二进制(最快,对应官方 user-guide 01-getting-started):

curl -fsSL https://x.ai/cli/install.sh | bash   # macOS / Linux / Git Bash
irm https://x.ai/cli/install.ps1 | iex          # Windows PowerShell
grok --version

二、从源码构建(我们要读源码,这条更相关):

cargo run -p xai-grok-pager-bin              # 构建并启动 TUI
cargo build -p xai-grok-pager-bin --release  # release 二进制:target/release/xai-grok-pager
cargo check -p xai-grok-pager-bin            # 只做快速校验,不产出二进制

几个要点:

  • 工具链由 rust-toolchain.toml 钉死,rustup 首次构建会自动装对应版本。
  • proto 代码生成需要 protoc:仓库通过 DotSlash 解析 bin/protoc,所以构建前先装好 dotslash 并放进 PATH。
  • 二进制产物叫 xai-grok-pager,官方安装包把它装成 grok——记住这个名字对应关系,后面找入口不会迷路。

首次启动会打开浏览器完成认证(细节见 user-guide 02-authentication,实现我们放到第 6 部分)。

入口在哪:组合根

cargo run -p xai-grok-pager-bin 里的 xai-grok-pager-bin 是整个 workspace 的组合根(composition root)——它本身几乎不含业务逻辑,职责是把几十个 crate 拼装成一个能跑的程序,然后把控制权交给 TUI crate xai-grok-pager。

「组合根」是个值得留意的模式:所有依赖在这一个包里被创建、连接、注入,其余 crate 之间尽量只依赖接口。这让近百个 crate 能各自独立测试、独立编译。第 6 部分会回来看它到底拼了些什么。

你按下回车之后

装好之后,随手问它一句、让它改个文件,屏幕上发生的事,顺着这条线走一遍(每一步后面都有专章,这里只建立骨架):

  1. TUI 收到输入 —— xai-grok-pager 的输入层拿到你这行字,交给 agent 运行时。
  2. 开一个 turn —— xai-grok-shell 的会话 actor(SessionActor)启动一轮:system prompt 由 xai-grok-agent 的 prompt/ 模块事先组装好,你的话拼进消息历史(xai-chat-state)。
  3. 请求模型 —— 交给采样器 xai-grok-sampler:它以 actor 的方式发起流式请求,token 一边到一边往回吐。
  4. 模型要调工具 —— 流里出现工具调用,agent 把它路由到 xai-grok-tools 里对应的实现(比如「编辑文件」或「跑命令」)。
  5. 工具真正干活 —— 改文件走编辑工具 + diff 追踪(xai-hunk-tracker),跑命令走 xai-grok-tools 的终端工具,可能还套一层沙箱。
  6. 结果回灌 —— 工具输出作为新消息喂回模型,循环继续,直到这一轮没有更多工具要调。
  7. 渲染 —— 全程的文字、diff、工具状态由 TUI 渲染进滚动缓冲(scrollback),就是你看到的画面。
图表加载中…

补充:模型怎么「知道」该调工具了?

上面第 4 步一句「流里出现工具调用」带过,这里值得停下来讲清一个初学者常困惑的点:模型自己并不会去执行任何工具。它唯一能做的事,是输出文本(或结构化数据)。所谓「调用工具」,不过是模型输出了一段标准化的调用意图(tool call),再由 Agent 或宿主环境把它解析出来、真正执行、把结果回传给模型——执行永远发生在模型之外。

这正是当下业界的主流范式:不追求让模型「亲自动手」,而是让它表达意图,把「做」交给可信的宿主。唯其如此才谈得上权限、沙箱、审计——因为真正动手的是你的代码,不是模型。

一个 OpenAI Function Calling 风格的例子

以 OpenAI 的 Function Calling 协议为例(grok 走自己的流式协议,道理完全一样)。请求里除了对话消息,你还多带一个 tools 数组,用 JSON Schema 声明每个工具「叫什么名字、收哪些参数」:

{
  "messages": [{ "role": "user", "content": "北京现在天气怎么样?" }],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "查询某座城市的当前天气",
        "parameters": {
          "type": "object",
          "properties": { "city": { "type": "string" } },
          "required": ["city"]
        }
      }
    }
  ]
}

模型收到后有两条路:

  • 觉得直接回答就够了,就正常返回一段文字 content,finish_reason 为 stop;
  • 判断需要外部能力,就不返回普通文字,转而返回一个 tool_calls,finish_reason 变成 tool_calls:
{
  "role": "assistant",
  "content": null,
  "tool_calls": [
    {
      "id": "call_abc123",
      "type": "function",
      "function": {
        "name": "get_weather",
        "arguments": "{\"city\": \"北京\"}"
      }
    }
  ]
}

判断「模型要调工具了」的信号,就是响应里出现了 tool_calls 而非 content。 还有个细节值得留意:arguments 是一段 JSON 字符串、而不是对象——模型生成的本就是文本,参数也是它一个字一个字「写」出来的,所以宿主拿到后必须自己解析并校验,不能盲信(模型完全可能写出不合 schema 的参数)。

接下来全由你的代码接手:解析出 name 与 arguments → 调用真正的 get_weather("北京") → 把返回值作为一条 role: "tool" 的新消息追加进对话历史,再发起下一轮请求:

{
  "role": "tool",
  "tool_call_id": "call_abc123",
  "content": "{\"temp\": 32, \"desc\": \"晴\"}"
}

模型这时才第一次「看到」天气数据,据此生成给用户的最终答复。一个完整回合,就是「模型出意图 → 宿主执行 → 结果回灌 → 模型续写」这样一次乃至多次往返。

回到 grok

对照本章第 4 到 6 步:grok 流里冒出的工具调用,正是上面 tool_calls 的流式版本;xai-grok-shell 的会话循环扮演那个「宿主」,把意图路由到 xai-grok-tools 执行,再把结果喂回——和 OpenAI 协议是同一套骨架,只换了传输格式。想通这一层,你也就明白第 2 部分为什么能对工具做权限门控、沙箱、审计:因为动手的始终是 grok 的代码,模型只递了一张「意图单」。

一句话记住:模型负责「说要做什么」,宿主负责「真去做」。 这条边界是所有 coding agent 安全模型的地基——模型不可全信,但它递出的每一个意图,都能被宿主逐条审查、放行、拦截或改写。

主流模型如今对工具调用支持到什么程度?

一句话:工具调用如今已是主流模型的标配——几乎所有前沿闭源模型(Claude、Gemini、GPT 系)和主流开源模型(GLM、DeepSeek、Qwen、Kimi、MiniMax 等)都原生支持。但「支持」分层次:能吐出一个格式合法的 tool_call 只是最低门槛;能不能选对工具、把参数填得合乎 schema、多步链式调用不跑偏、出错后自己恢复——差距全在这里,而这恰恰是 coding agent 最吃重的能力。

有两个公开榜单,正好从两个角度把这件事看清(数据截至 2026 年 7 月,两榜均实时更新,请以线上为准):

  • Agent Arena 榜 量的是用得好不好:基于上百万真实 agent 会话,按工具可靠性、任务完成、可纠偏性、工具幻觉率等信号排名。截至 7 月,前排是 Claude Fable 5、GPT 5.6 Sol、Claude Opus 4.8、GPT 5.5、Claude Sonnet 5,开源阵营里 GLM 5.2 挤进前十。它衡量的正是 grok 这类 agent 真正依赖的「把工具用稳」的本事。
  • OpenRouter Tool Calls 榜 量的是被用得多不多:按各模型在 OpenRouter 上工具调用用量的占比排名,反映真金白银的生产选择。格局和能力榜几乎倒过来——走量领先的是中国开源 MoE 和各家廉价的「Flash」档:Tencent Hy3(free)约 18.7%、Xiaomi MiMo 约 14.9%、DeepSeek V4 Flash 约 9.2% 居前,GLM 5.2、MiniMax M3、谷歌 Gemini Flash 系紧随;而能力榜前排的 Claude、GPT 顶配,反倒挤不进这个占比榜的前列——因为「份额」奖励的是便宜、免费、能自托管的走量选手。

两榜对照,能得出一个对写 agent 的人很实用的结论:「能力最强」与「被用最多」几乎是两拨模型——质量榜前排的 Claude、GPT 在用量占比榜上并不靠前,而占比榜前列的开源 MoE 更多是靠便宜、免费与可自托管走量。前者由质量决定,后者被价格、延迟、部署方式等工程因素推着走;真到自己选模型时,这两个维度都得一起掂量。

对 grok 这类 agent 而言,好在换模型成本不高:它的采样器(xai-grok-sampler)刻意做成传输与格式无关,能对接 OpenAI 兼容、Anthropic 等多种 API——上面这些主流模型,大多都能作为后端接上。这套「一份契约、多种后端」的设计,留到第 1 部分讲采样器时再细看。

OpenAI 与 Anthropic 的工具调用格式差在哪?

上面反复说模型输出「标准化的调用意图」,但**「标准」其实各家不一**。同一件事——定义工具、模型发起调用、回灌结果——OpenAI 与 Anthropic 的报文格式有好几处明显不同(下表以 OpenAI 最经典的 Chat Completions API 与 Anthropic 的 Messages API 对照,字段名取自两家官方文档):

环节OpenAI · Chat CompletionsAnthropic · Messages
工具定义裹在 function 里,schema 键为 parameters平铺,schema 键为 input_schema
模型的调用独立的 tool_calls 数组与文本并列的 tool_use 内容块
参数类型arguments 是 JSON 字符串(要自己 parse)input 是已解析的对象
回灌结果一条 role: "tool" 消息role: "user" 消息里的 tool_result 块
关联 idcall_… ↔ tool_call_idtoolu_… ↔ tool_use_id

挑三处最扎眼的看具体报文:

① 定义工具(放进请求的 tools):OpenAI 把元数据裹进 function、schema 键叫 parameters;Anthropic 平铺、键叫 input_schema。

OpenAI · Chat Completions

{ "type": "function",
  "function": { "name": "get_weather", "parameters": { "type": "object", "properties": { "city": { "type": "string" } } } } }

Anthropic · Messages

{ "name": "get_weather",
  "input_schema": { "type": "object", "properties": { "city": { "type": "string" } } } }

② 模型发起调用:OpenAI 把调用单独挂在 tool_calls 数组里、参数是字符串;Anthropic 把它作为一个 tool_use 内容块和文本并列、参数已是对象。

OpenAI · Chat Completions

{ "role": "assistant", "content": null,
  "tool_calls": [ { "id": "call_abc", "type": "function",
    "function": { "name": "get_weather", "arguments": "{\"city\":\"北京\"}" } } ] }

Anthropic · Messages

{ "role": "assistant",
  "content": [
    { "type": "text", "text": "我查一下" },
    { "type": "tool_use", "id": "toolu_01A", "name": "get_weather", "input": { "city": "北京" } } ] }

③ 回灌结果:OpenAI 用一条 role: "tool" 的独立消息、靠 tool_call_id 对应;Anthropic 把结果塞进一条 role: "user" 消息的 tool_result 块、靠 tool_use_id 对应。

OpenAI · Chat Completions

{ "role": "tool", "tool_call_id": "call_abc", "content": "{\"temp\":32}" }

Anthropic · Messages

{ "role": "user", "content": [
    { "type": "tool_result", "tool_use_id": "toolu_01A", "content": "{\"temp\":32}" } ] }

一句话概括这套差异:Anthropic 把工具调用当成对话内容的一等公民(text、tool_use、tool_result 都是并列的 content block,参数直接是对象);OpenAI 则把它当成消息的旁挂字段(tool_calls 与 content 分开,参数是待解析的字符串,结果另用一种 role)。顺带一提,连 OpenAI 自己都不止一套——较新的 Responses API 又把工具定义拍平、把这些字段改叫 function_call / call_id / function_call_output。

对 grok 这类要跨模型跑的 agent,这种「各家标准不一」正是采样器必须做成格式无关的另一半理由——上一节讲的是换后端,这一节是换报文格式。采样器内部只认一套统一的工具表示,到边界再翻译成各家线上格式(嵌套还是平铺、字符串还是对象、role: "tool" 还是 tool_result、call_ 还是 toolu_),这层「翻译」的落点同样在第 1 部分。

这张时序图就是整本书的目录——后面每一部分,都是把其中一步放大来看。下一章先把这些 crate 的全景与分层摆清楚,你才知道每一步落在代码的哪一层。


上一篇:grok 到底是什么 · 下一篇:Rust workspace 全景

本页目录