# 三种编辑格式 · 为什么改文件要三套协议

> 同样是「让模型改文件」，grok 提供三种模型协议：精确的 search/replace、省 token 的 concise 版、以及基于行锚点的 hashline。这一章讲清三者的格式差异、各自防的失败模式，以及 editor_infra 里那把设计好却还没接线的文件锁。

- 作者：David（道雾轩）
- 专栏：Grok Build 源码解读（https://daiw.org/manual/grok-build.md）
- 最后更新：2026-09-15
- 原文：https://daiw.org/manual/grok-build/edit-formats
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 三种编辑格式 · 为什么改文件要三套协议

「让模型改文件」听起来是一个工具的事，grok 却提供了**三种**。它们干的是同一件事(最终都产出 `SearchReplaceOutput`)，区别在**给模型的编辑协议**——各自针对不同的失败模式和 token 预算。

## 一、`search_replace`(默认)：精确查找替换

最直觉的格式：给 `old_string` 和 `new_string`,`old_string` 必须在文件里**恰好匹配一次**(除非 `replace_all`)；空 `old_string` 表示新建文件。两个生产级细节：

- **改前必读(软约束)**：`requires_expr()` 声明了一条依赖——除非配置里开了 `skip_read_before_edit`，否则工具集里必须同时有一个 Read 工具。但这只是**配置期**的要求：源码注释写明「先读再改」靠工具描述和 RL 训练来引导，**运行时并不强制**(`skip_read_before_edit` 在运行时已是个废弃的空操作)。它和 [hunk 追踪](https://daiw.org/manual/grok-build/hunk-tracker)一起，构成防「盲改」的护栏。
- **匹配的鲁棒性**：精确匹配失败时，可以再走一层 **Unicode 易混字符归一化**的兜底匹配(模型有时把 `-` 写成别的破折号、把直引号写成弯引号)——不过这是 `unicode_normalized_fallback` 开关，默认关。默认开着的是 `include_user_edit_hint`：报「没找到」时顺带提示「用户可能改过这个文件」，引导模型重读，而不是拿着旧内容盲目重试。

## 二、concise 版：省 token

`grok_build_concise` 的 `search_replace` **格式完全一样**，但两点不同：**没有改前必读的要求**(也不要求工具集里有 Read 工具)、回给模型的输出换成更短的 `tool_output_for_prompt_concise`。它存在的唯一理由是**省 token**——在长会话里，编辑工具的往返消息累加起来很可观。

## 三、`hashline_edit`：基于行锚点

这个格式**真的不一样**。它不给字符串，而是给**行锚点**：`edits: [{op, anchor, content}]`。锚点形如 `LINE:HASH`(默认方案还会多带一段块指纹，形如 `22:abc:rst`)，是配套的 `hashline_read` / `hashline_grep` 在每行前**发给模型**的(模型绝不能自己编锚点)。操作有 `replace`(锚点，可带 `end_anchor` 表区间)、`insert_after`(特殊锚点 `"0:"`=文件头、`"EOF"`=文件尾)、`write`(整文件)。

它的 apply 语义很严谨(`edit/apply.rs`,2000+ 行)：所有编辑先对**编辑前快照**校验、再**自底向上原子应用**；**任何一个锚点失效，整批拒绝**；区间重叠也拒绝。妙在**失败时返回目标附近的新锚点**，让模型**不用重读文件就能重试**。

`scheme.rs` 里实现了三种锚点方案，强弱不同：`ContentOnly`(只哈希本行内容，最弱)、`ChunkFingerprint`(本行 + 固定块指纹，**默认**)、`CheckpointChain`(校验点链，最强但改动后锚点变得多)。不过配置(`HashlineSchemeParams`)目前只放出 `chunk` 和 `content_only` 两档，`CheckpointChain` 还只是候选。

<Callout type="info">
  **为什么要三种？** 这是把「编辑」当成一个可调的设计空间：精确 search/replace 直观但费 token 且对空白敏感；concise 省 token;hashline 用锚点解决「大文件里重复片段难定位」和「改完还要重读才能再改」的痛点。同一个 `ToolKind::Edit`，三种模型协议，按场景选——这是 grok 工具系统「一种行为、多种 wire 格式」组织法的最佳例证。
</Callout>

## 一把还没接上的文件锁

`editor_infra` 里有一个 **`FileOperationLockManager`**：**按路径**加锁——同一路径的操作串行、不同路径并发；`Write`/`StrReplace` 这类改动用排他锁挡住一切，还专门防**写饥饿**(排队的排他等待者不会被后来的读插队)。设计很周到，但要诚实地说：当前代码里它只被导出，**没有任何工具调用它**(对照的 7 月版本也是如此)——所以别把「并发编辑不打架」算在它头上。

## 和《从 LLM 到 Coding Agent》对照

对照 [tool-abstraction](https://daiw.org/manual/llm-to-agent/tool-abstraction)：教学实现里，「编辑文件」就一种格式、一个函数。grok 的增量是把编辑协议本身当成**可选、可调、要防各种模型出错方式**的设计维度——精确性、token 成本、大文件定位、重试友好，一样都不能少。配合下一章的 [hunk 追踪](https://daiw.org/manual/grok-build/hunk-tracker)，就是 grok「敢让模型大改代码」的底气。

---

上一篇：[读与搜](https://daiw.org/manual/grok-build/read-and-search) · 下一篇：[hunk 追踪](https://daiw.org/manual/grok-build/hunk-tracker)
