大多數在 n8n 中整合 AI 的工作流,雖然在測試階段能順利通過,卻常因一個常見原因在實際運行時出錯:模型將其 JSON 輸出包裹在 Markdown 程式碼區塊中。例如,當你要求 { "score": 7 } 時,模型可能會回傳 ``json { "score": 7 } ``。
這會導致 JSON.parse($json.message.content) 拋出錯誤。更糟的是,像 {{ $json.score }} 這樣的表達式可能會靜默地解析為 undefined,讓工作流在空資料上繼續運行。這種靜默失敗是最危險的。
解決方案是在 LLM 節點和所有下游節點之間加入一個 Code 節點。這個節點會執行三件事:首先,它會移除開頭和結尾的程式碼區塊。其次,它會在 try/catch 區塊內執行 JSON.parse。
最重要的是,如果解析失敗,它不會拋出錯誤,而是以已知格式回傳原始文本。這樣一來,你就可以將其導向審核分支,實際查看模型回傳了什麼內容。以下是該 Code 節點的程式碼範例:
const raw = $input.first().json.message?.content ?? '';
const cleaned = String(raw).trim()
.replace(/^```(?:json)?/i, '')
.replace(/```$/, '')
.trim();
let parsed = {};
let parse_ok = true;
try {
parsed = JSON.parse(cleaned);
} catch (e) {
parse_ok = false; // do not throw: keep the item alive
}
return [{ json: { parse_ok, ...parsed, _raw: parse_ok ? undefined : cleaned } }];
接著,你可以使用一個 IF 節點來判斷 parse_ok 的值:如果為 true,則工作流正常繼續;如果為 false,則記錄 _raw 內容,或者發送通知提醒你處理這個錯誤的輸出。
有三種常見的錯誤我一直看到:第一,沒有處理程式碼區塊,即使是強大的模型,尤其是在長輸出時,也會這樣做。第二,在表達式內部呼叫 JSON.parse,因為表達式拋出錯誤會終止項目,讓你無法取得除錯所需的原始回覆。
第三,過度信任模型回傳的 schema。即使是有效的 JSON,也可能出現 action_items: null 的情況。在對陣列進行 .map() 操作之前,務必使用 Array.isArray(x) ? x : [] 進行防護。
一個非常實用的提示詞技巧是:要求嚴格的格式,並指定預設值。例如,你可以寫「如果備註中沒有提及所有者,請寫『未提及』」。這幾乎可以消除所有模型憑空捏造的所有者。
我將這個防護機制應用在一個小型「會議記錄轉待辦事項」的工作流中:手動觸發 → 設定(筆記)→ OpenAI(嚴格 JSON)→ 這個解析加防護 Code 節點 → 設定輸出。這個工作流會回傳摘要、決策、一個包含所有者和截止日期的待辦事項 Markdown 表格,以及一封後續追蹤的草稿電子郵件。
它還會計算未完成的待辦事項數量以供分支判斷,並且從未因錯誤的完成內容而中斷。你如何處理模型回傳的無效 JSON 呢?是使用像這樣的防護機制、自動重試一次,還是依賴 AI Agent 的結構化輸出?我一直在「防護加警報」和「防護加重試一次」之間猶豫不決,所以很想聽聽大家在實際生產環境中,哪種方法更有效。