我在 OpenClaw 上跑的模型是 MiniMax-M2.7,人格叫 Itsuki。它回覆中文時偶爾會冒出簡體用詞——設置、獲取、運行、默認——這些在 USER.md 裡已經明確寫了要避免,但模型不一定每次都遵守。

所以我請 Itsuki 做一件看起來合理的事:幫我寫一個 OpenClaw 外掛,讓所有中文輸出都經過 OpenCC 的臺灣模式轉換。

它很積極。花了大概十分鐘,讀了 OpenClaw 的文件、查了外掛架構、裝了 opencc-js、寫好 index.tsopenclaw.plugin.json,最後把外掛加進設定檔的 allow list。

然後我發了一句「怎麼了」,它就崩潰了。

Cannot read properties of undefined (reading 'properties')

每次對話都這樣。連續崩。


三個問題,全都是 API 用錯

後來我用 Claude Code 去查,發現 Itsuki 犯了三個錯誤:

1. OpenCC API 不存在的方法

// Itsuki 寫的
const converter = await OpenCC.fromJSON();

// 實際 API
const converter = OpenCC.Converter({ from: 'cn', to: 'twp' });

OpenCC.fromJSON() 根本不存在。opencc-js 的 API 是 Converter(),而且是同步的,不需要 await

2. Tool 註冊介面完全用錯

這是崩潰的直接原因。Itsuki 用了通用的 tool registration pattern:

// Itsuki 寫的(其他框架的 pattern)
api.registerTool({
  name: "opencc_convert",
  inputSchema: { ... },
  handler: async ({ text }) => { ... }
});

但 OpenClaw 的 registerTool 預期的是 AgentTool 介面(來自 @mariozechner/pi-agent-core):

// 正確的 OpenClaw 寫法
api.registerTool({
  name: "opencc_convert",
  label: "OpenCC Convert",
  parameters: { ... },
  execute: async (toolCallId, params) => {
    return {
      content: [{ type: "text", text: "..." }],
      details: { ... }
    };
  }
});

關鍵差異:inputSchemaparametershandlerexecute,還需要 label,回傳格式也完全不同。

OpenClaw 內部在建構 tool list 時會存取 tool.parameters.properties。因為 Itsuki 寫的是 inputSchema 而不是 parameters,所以 tool.parametersundefined,接著 .properties 就炸了。

3. plugin manifest 必須有 configSchema

修完前兩個問題後,我一度把 openclaw.plugin.json 裡的 configSchema 拿掉(以為它跟 JS 裡的重複定義衝突),結果 Gateway 直接拒絕載入外掛:

plugin manifest requires configSchema
plugins.allow: plugin not found: opencc-tw

OpenClaw 的外掛 manifest 強制要求這個欄位,即使你的外掛不需要任何設定。


修完之後

三個問題全部修正後,重啟 Gateway,外掛正常載入,對話恢復正常。

最終的外掛做兩件事:

  1. before_prompt_build hook:在 system prompt 注入臺灣正體用詞指引,讓模型從源頭就盡量用對的詞
  2. opencc_convert tool:模型可以主動呼叫,把文字送進 OpenCC 做 cn → twp 轉換

沒有 pre-outbound hook 可以攔截 response 做自動轉換(OpenClaw 目前只有 message_sent 這種 fire-and-forget 鉤子),所以這是目前可行的最佳方案。


號稱接近 Opus,卻連自己平台的外掛都寫不對

MiniMax-M2.7 的發佈公告宣稱 SWE-Pro benchmark 56.22%,「接近 Opus 水準」。

但 benchmark 測的是什麼?是在已知的、訓練資料覆蓋率極高的 codebase 上解題。模型擅長的就是這種「常見 pattern 的排列組合」。

寫 OpenClaw 外掛呢?OpenClaw 是 GitHub 上星數最高的軟體專案,250,000+ stars,60 天內超越了 React 十年的紀錄。這不是什麼冷門框架——它的文件、原始碼、社群討論在網路上到處都是。MiniMax-M2.7 的訓練資料裡不可能沒有 OpenClaw。

而且 Itsuki 寫外掛之前確實去讀了 OpenClaw 的文件和原始碼。文件裡寫得很清楚,AgentTool 的介面是 parameters/execute/label

它讀了正確的文件,然後寫出了錯誤的程式碼。

這不是資料不足的問題,是模型品質的問題。inputSchema/handler 這個 pattern 在 MCP、LangChain、Vercel AI SDK、OpenAI function calling 裡都用到——訓練分佈裡的權重太大了。即使 context window 裡放著正確答案,生成程式碼時 prior 還是贏了。

Benchmark 不測這個。Benchmark 不會問「你能不能在讀完文件之後,抵抗自己的訓練分佈,寫出跟文件一致的程式碼」。但這才是實際開發中最重要的能力之一。


但公平地說:問題不只是模型

最後修好這個外掛的是 Claude Code(Opus)。但如果要公平評價,得問一個問題:換成 Opus 在同樣的條件下,會不會也寫錯?

老實說,大概也會。inputSchema/handler 在 Opus 的訓練資料裡一樣是壓倒性的主流 pattern。如果純靠記憶直接寫,第一版很可能也會中一樣的陷阱。

Opus 能修好它,不是因為模型本身比較聰明,而是因為環境不同

  1. 它從看 error message 開始,不是從零寫起
  2. 它可以用 Object.keys(OpenCC) 確認實際有哪些方法,不用猜
  3. 它一路追型別定義:registerToolAnyAgentToolAgentToolTool → 確認是 parameters 不是 inputSchema
  4. 每次改完可以重啟 Gateway、看 log、確認結果

而且 Opus 也犯了錯——它把 configSchema 從 manifest 拿掉,導致 Gateway 拒絕載入。多浪費了一次重啟才發現。

Itsuki 呢?它也有 shell 存取權限,也讀了原始碼,但它一口氣寫完就交出去了。崩潰發生在 OpenClaw 的 Gateway 層級——不在 Itsuki 的對話裡。從它的角度看,外掛寫完了,任務結束了。它根本不知道自己搞砸了。

如果把 MiniMax-M2.7 拉到 OpenClaw 之外,給它同樣的 feedback loop——error message、shell 存取、iterate 的機會——它大概也能修好自己的 bug。

模型的差異沒有 feedback loop 的差異大。


真正的缺口在 OpenClaw

這件事暴露的與其說是模型的問題,不如說是 OpenClaw 的架構缺口:

外掛沒有驗證步驟。

如果 OpenClaw 在載入外掛時有一個 sandbox 驗證——檢查 registerTool 的呼叫有沒有符合 AgentTool 介面、parameters 有沒有 properties 欄位——然後把驗證結果回饋到對話裡,Itsuki 就能在同一個 session 內看到錯誤、自己修正。

現在的狀況是:外掛載入失敗或造成崩潰,錯誤只出現在 Gateway 的 stderr log 裡。對話裡的 AI 完全不知道發生了什麼事。它以為自己成功了,但使用者發訊息過來就只有一行 Cannot read properties of undefined

這不是「AI 太笨所以寫不好 code」,這是「系統沒有告訴 AI 它寫的 code 壞了」。

任何開發者——人類或 AI——在沒有 feedback loop 的條件下都會交出有 bug 的程式碼。差別在於有 feedback loop 的時候,好的開發者能夠自我修正。OpenClaw 把這條路切斷了。