6 個內建工具

LLM 在評審過程中可呼叫的工具——完整 schema 與範例

各階段工具可用性

工具PlanMain用途
task_done✗✓「我完成了」——終止迴圈
code_comment✗✓發出一條帶行範圍 + 建議的評審評論
file_read✗✓讀變更後快照中某檔的一段
file_read_diff✓✓讀另一檔的 diff 確認跨檔關切
file_find✓✓依檔名關鍵字定位檔案
code_search✓✓全倉 grep(git grep)

task_done 與 code_comment 在 plan 階段有意不可用——plan 是唯讀的。

脈絡工具是唯讀脈絡,不是評論目標。file_read / file_read_diff / file_find / code_search 讓模型更好理解目前檔的 diff;收集脈絡時發現的問題按設計被忽略。跨檔關切只有在目前檔 diff 中可觀察時,才會成為評論。

task_done

{ "name": "task_done", "input": { "state": "DONE" } }

agent 看到 task_done 後停止呼叫 LLM,開始處理已累積的 code_comment。state 可為 DONE 或 FAILED。

code_comment

{
  "name": "code_comment",
  "input": {
    "comments": [{
      "content": "`tx.Rollback()` is never deferred — early returns leak the transaction.",
      "existing_code": "tx, err := db.Begin()\nif err != nil {\n    return err\n}",
      "suggestion_code": "tx, err := db.Begin()\nif err != nil {\n    return err\n}\ndefer tx.Rollback()"
    }]
  }
}

comments 是陣列,一次可發多條。每條錨定到 existing_code 片段,OCR 自動算行號。

錨定演算法

  1. hunk 新側——context + added 行;失敗重試 hunk 舊側。
  2. 全新檔掃描——對整個變更後檔案逐行比對。
  3. 重新定位任務——仍失敗則跑 RE_LOCATION_TASK。

比對對空白不敏感。最後手段以 start_line=0 交付——問題是真的,但需自行定位。

file_read

{ "name": "file_read", "input": { "file_path": "src/foo.go", "start_line": 10, "end_line": 80 } }

讀變更後形式的一段行(每行以 1 起始行號 + | 前綴)。每次最多 500 行。

file_read_diff

{ "name": "file_read_diff", "input": { "path_array": ["src/api/handler.go", "src/db/queries.go"] } }

讀同一變更集中其他檔的 diff。路徑不在變更集則靜默省略。

file_find

{ "name": "file_find", "input": { "query_name": "UserService", "case_sensitive": false } }

與每個檔的 basename 做子串匹配,最多 100 條。無匹配回 // The file was not found。

code_search

{
  "name": "code_search",
  "input": {
    "search_text": "TODO|FIXME",
    "file_patterns": ["*.go", ":(exclude)vendor/"],
    "case_sensitive": false,
    "use_perl_regexp": true
  }
}

由 git grep 驅動,理解 pathspec、遵循 .gitignore。每檔命中上限 100。

pathspec 速查

目標file_patterns
所有 Go 檔["*.go"]
除測試外所有 Go["*.go", ":(exclude)*_test.go"]
僅一個目錄["src/api/"]
多型別、排除 vendor["*.go", "*.ts", ":(exclude)vendor/", ":(exclude)node_modules/"]

自訂工具

新增新工具名需在 Go 側接入(internal/tool/definitions.go)——單靠 JSON 無法加新行為。

📖 教學解說:6 個工具深入

plan 階段的工具可用性

Plan 階段只能用 file_read_diff、file_find、code_search——三個脈絡工具。task_done 和 code_comment 被禁用。為什麼?Plan 是唯讀分析階段——只收集脈絡,不做決策。這確保 plan 產出是純指引,不附帶評論。

code_comment 的錨定三階段

錨定不是一次性嘗試:

  1. hunk 新側——在 diff 的 context + added 行中搜尋 existing_code
  2. 全新檔掃描——如果 hunk 匹配失敗,掃描整個變更後檔案
  3. RE_LOCATION_TASK——仍失敗,跑 RE_LOCATION_TASK 請模型重新錨定

比對對空白不敏感。最後手段以 start_line=0 交付——問題是真的,但需自行定位。

脈絡工具的「忽略」機制

file_read、file_read_diff、file_find、code_search 都是唯讀脈絡工具。它們讓模型更好理解目前檔的 diff,但收集脈絡時發現的問題按設計被忽略。只有在目前檔 diff 中可觀察的跨檔問題才會成為評論。這是刻意的——避免「噪音評論」。

練習 / 驗收清單

  • 能說出 6 個工具各做什麼
  • 能區分哪些在 plan 階段可用
  • 能解釋脈絡工具不會成為評論目標的原因
  • 能描述 code_comment 的錨定三階段
看完這頁你應該能說出:6 個工具各做什麼、哪些在 plan 階段可用、為什麼脈絡工具不會成為評論目標、以及 code_comment 的錨定三階段。

延伸閱讀:架構 · 程式碼:internal/tool · MCP 伺服器

① 進階真實情境 Worked Example:用 tools.json 重塑 Agent 的審查行為

情境

你發現 Agent 在審 API 檔時,file_read 只讀變更附近的 10 行——抓不到「這個 handler 依賴的 service 方法定義」,導致評論像瞎子摸象。你要重新描述 file_read,強制 Agent 讀更廣的範圍,並禁用 code_search(你不想它在每次評審都全 repo grep 一遍,token 太貴)。

Step 1 — 複製內建 tools.json

ocr --help 2>/dev/null | rg -i tool   # 找內建 tools.json 位置(或從上游 repo 抓)
cp /path/to/internal/tool/tools.json ./my-tools.json

Step 2 — 重新描述 file_read

jq '.tools[] | select(.name=="file_read")' my-tools.json
# 修改 description:
#   "讀變更後快照中某檔的一段。當你審查 API handler 時,
#    務必同時讀取該 handler 依賴的 service/mapper 定義範圍(跨 50 行),
#   以理解資料流,而不只是變更附近的 10 行。"

# Step 3 — 禁用 code_search(從陣列刪除)
jq 'del(.tools[] | select(.name=="code_search"))' my-tools.json > /tmp/t.json && mv /tmp/t.json my-tools.json

# Step 4 — 用 --tools 跑評審
ocr review --tools ./my-tools.json --format json --audience agent

Step 5 — 驗證行為真的變了(看 viewer)

開 viewer 的 main_task 泳道,確認:file_read 的 end_line - start_line 變寬、code_search 不再出現。行為驗證比「設定成功」重要。

為什麼選這條審查路徑

工具是「Agent 的手」——描述改變行為、禁用改變成本。重新描述 file_read 讓 Agent「讀得更廣」(品質),禁用 code_search 省 token(成本)。這兩個都透過 --tools 的 JSON 達成,不用改 Go 源碼——因為你只改「描述」,沒加「新工具」(加新工具才需要 internal/tool/definitions.go)。

預期產出

my-tools.json: 6 tools → 5 tools (code_search removed)
file_read description: 擴充為「讀 50 行範圍」
review: 4 comments(原 3 條),file_read 平均範圍 12 → 48 行
token: 每檔省 ~15% (code_search 往返消失)

② 深入原理擴充:錨定演算法的「對空白不敏感」與「假錨定」

錨定是「模糊匹配」,不是「精確定位」

code_comment 的 existing_code 不是直接查行號——它被當成「樣本」,用滑動視窗在 diff 的 hunk 新側/舊側、或整個變更後檔案中找「最像」的位置。比對對空白不敏感(縮排差異不影響)。這讓「模型重複貼了略縮排的程式碼」也能配到——但代價是可能配到「看起來像」的地方。

「看起來通過 review 但其實錨錯了」的案例

# Agent 的 existing_code:
"return err"

# 檔案有兩處:
# 函式 A (行 20):  if err != nil { return err }   ← 真正的問題在這
# 函式 B (行 77):  if err != nil { return err }   ← 只是長得像
# 滑動視窗先配到第一處 → 評論貼到函式 A 附近 → 看似錨定成功

# 但真正的問題在函式 B —— 評論貼錯了位置,reviewer 看到 A 覺得「沒問題啊」

錨定「成功」≠ 錨定「正確」——短的、重複的 existing_code 會誤配到第一個相似處。隱患:評論存在、行號也非零、但貼在錯誤位置,比「貼不出來」更危險(因為你看不到 start_line: 0 的警告)。防禦:existing_code 應包含「足以區分位置」的上下文(多帶一兩行);配合 viewer 的 re_location_task 泳道觀察是否頻繁觸發。

③ 診斷式疑難排解

症狀可能原因解決方案
自訂 tools.json 沒生效JSON 語法錯誤或 --tools 路徑錯jq . my-tools.json 驗證 JSON;確認 --tools 指向對的檔
禁用工具後 Agent 一直報錯Agent 被 prompt 教導「有 code_search 可用」,但執行時找不到禁用工具時同步檢查模板 prompt 是否還提到它(或用 description 引導)
評論行號看似正確其實錯位短的 existing_code 誤配到第一個相似處讓 existing_code 帶更多上下文;看 re_location_task 泳道
file_read 讀了但 Agent 沒用讀取的範圍還是太窄,或 prompt 沒要求把「讀 50 行」寫進 file_read description;調高 max-tokens 給餘裕
加了「新工具」沒用新工具名需 Go 側接入,JSON 無法加新行為用內建工具重新組合,或改 Go 源碼(internal/tool/definitions.go)

④ 進階挑戰題

  1. 挑戰一:設計一個 file_find 的新 description,讓 Agent 在找不到檔時「改用 code_search 的 pathspec 找同 basename」。這是否違反「脈絡工具」原則?
  2. 挑戰二:解釋「對空白不敏感」的錨定在縮排不同的程式語言(Python vs Go)下,分別是幫助還是阻礙?
  3. 挑戰三:你禁用 code_search 省 token,但這可能降低跨檔發現。設計一個「混合策略」:只在特定路徑下允許 code_search(提示:tools.json 能不能依檔案條件切換?如果不能,你有什麼替代?)