讀完這篇,你會帶走
- 寄外部訊息、更新或刪除資料等工具,可在執行前加人工核准。
- 審核者應看得到工具名稱、目標和 AI 準備送出的參數;只看最終答案不夠。
- 等待、拒絕與未知工具都要有停止規則;範例只模擬分類,未連線 n8n。
AI Agent 寫好一封信,主管看過文字才按送出,和 Agent 自己呼叫寄信工具,是兩件事。後者可能直接向外傳送資訊或改動資料。n8n 官方文件提供對指定工具加上 Human review 的做法:Agent 準備呼叫工具時先暫停,交由人核准或拒絕,核准後才執行。[1][3] 企業首先要定義的,是哪些動作值得放在這個關卡後面。
01 / 先分清兩種『轉人工』
| 情況 | 人工介入的位置 | 要避免的誤會 |
|---|---|---|
| Agent 答不出問題 | 改請人回答或接手對話 | 有人補答,不代表工具動作已被審核 |
| Agent 要寄信、更新或刪除 | 特定工具執行之前,審核本次參數 | 看過最終回覆,不代表工具尚未執行 |
n8n 另有『答不出問題時送往 Slack 請人協助』的範例。[2] 它解決的是無法回答的交接;本文討論的是高影響工具呼叫前的核准。兩者可以同時存在,但不能互相取代。
02 / 把關卡放在會改變外部狀態的工具上
n8n 文件列出的例子包括外部訊息、修改紀錄與刪除資料;可對全部工具或部分工具要求人工核准。[1] 編輯建議是先列出每個工具能做的最重動作,再決定是否需要審核。唯讀查詢也要受原系統權限控制,不能因為不寫入就任意開放資料。
| 示範工具 | 核准前要看到 | 建議規則 |
|---|---|---|
| 寄外部 Email | 收件對象與內容摘要 | 先核准,再允許寄送 |
| 更新客戶紀錄 | 紀錄 ID、欄位與新舊值 | 指定負責人核對 |
| 刪除資料 | 目標 ID、影響範圍與回復方式 | 沒有明確核准就停止 |
03 / 審核卡要顯示『做什麼』和『對誰做』
n8n 的 Human review 設定可用 $tool.name 和 $tool.parameters 組出審核訊息,讓審核者看見 Agent 要使用的工具與輸入參數。[1] 實務上還應標出核准者、請求時間、資料來源與對外影響;這是編輯建議,不是 n8n 保證自動產生的欄位。訊息只放審核所需的最少資料,避免把完整敏感內容複製到通知頻道。
官方說明:拒絕時工具不執行,Agent 會收到拒絕結果;文件也建議在系統提示中說明哪些工具需核准、被拒時如何回應。[1] 沒有回覆的請求在本文的演練中一律維持等待。正式上線前還應由團隊決定逾時、代理審核者與撤銷規則;不要假設文件已替你定好這些政策。
04 / 用六筆虛構請求演練放行與阻擋
下面的 CSV、Python 與輸出都使用虛構 ID 與 .invalid 信箱。它們只模擬『工具白名單+核准狀態』的分類,不會寄信、刪資料或連線 n8n。把前兩段各存成 requests.csv 與 approval_check.py,再執行指令核對結果。
request_id,tool,target,review
r-001,read_internal_note,note-demo-01,none
r-002,send_external_email,reviewer@example.invalid,approved
r-003,update_record,record-demo-17,denied
r-004,delete_record,record-demo-09,pending
r-005,send_external_email,team@example.invalid,none
r-006,transfer_funds,account-demo-03,approved"""Practice approval decisions with fictional tool calls; this never calls a tool."""
import csv
import re
import sys
from pathlib import Path
FIELDS = ["request_id", "tool", "target", "review"]
READ_ONLY = {"read_internal_note"}
GATED = {"send_external_email", "update_record", "delete_record"}
REVIEWS = {"none", "pending", "approved", "denied"}
def load_requests(path):
with Path(path).open(encoding="utf-8-sig", newline="") as stream:
reader = csv.DictReader(stream)
if reader.fieldnames != FIELDS:
raise ValueError("欄位必須是 request_id,tool,target,review")
rows = []
seen = set()
for number, raw in enumerate(reader, 2):
if None in raw or any(value is None for value in raw.values()):
raise ValueError(f"第 {number} 列欄位數不符")
row = {key: value.strip() for key, value in raw.items()}
if not re.fullmatch(r"r-[0-9]{3}", row["request_id"]):
raise ValueError(f"第 {number} 列 ID 格式錯誤")
if row["request_id"] in seen:
raise ValueError(f"第 {number} 列 ID 重複")
if not re.fullmatch(r"[a-z_]{2,40}", row["tool"]):
raise ValueError(f"第 {number} 列工具名稱格式錯誤")
if not re.fullmatch(r"[A-Za-z0-9@._-]{1,80}", row["target"]):
raise ValueError(f"第 {number} 列目標格式錯誤")
if row["review"] not in REVIEWS:
raise ValueError(f"第 {number} 列核准狀態錯誤")
seen.add(row["request_id"])
rows.append(row)
if not rows:
raise ValueError("至少需要一筆虛構請求")
return rows
def outcome(row):
tool = row["tool"]
if tool in READ_ONLY:
return "唯讀示範可繼續"
if tool not in GATED:
return "阻擋未知工具"
return {
"approved": "核准後可進下一步",
"denied": "拒絕並停止",
"pending": "等待人工核准",
"none": "等待人工核准",
}[row["review"]]
def report(rows):
lines = [
"# 虛構 AI 工具核准演練", "",
"本程式只分類請求,不寄信、不修改資料、不呼叫 n8n。", "",
"| ID | 工具 | 目標 | 審閱狀態 | 分類結果 |",
"| --- | --- | --- | --- | --- |",
]
for row in rows:
lines.append("| {} | {} | {} | {} | {} |".format(
row["request_id"], row["tool"], row["target"], row["review"], outcome(row)
))
return "\n".join(lines) + "\n"
if __name__ == "__main__":
if len(sys.argv) != 2:
raise SystemExit("用法:python approval_check.py requests.csv")
try:
print(report(load_requests(sys.argv[1])), end="")
except (OSError, ValueError, csv.Error) as exc:
raise SystemExit(f"停止:{exc}") from Nonepython -X utf8 approval_check.py requests.csv# 虛構 AI 工具核准演練
本程式只分類請求,不寄信、不修改資料、不呼叫 n8n。
| ID | 工具 | 目標 | 審閱狀態 | 分類結果 |
| --- | --- | --- | --- | --- |
| r-001 | read_internal_note | note-demo-01 | none | 唯讀示範可繼續 |
| r-002 | send_external_email | reviewer@example.invalid | approved | 核准後可進下一步 |
| r-003 | update_record | record-demo-17 | denied | 拒絕並停止 |
| r-004 | delete_record | record-demo-09 | pending | 等待人工核准 |
| r-005 | send_external_email | team@example.invalid | none | 等待人工核准 |
| r-006 | transfer_funds | account-demo-03 | approved | 阻擋未知工具 |實際執行得到六筆分類:一筆唯讀示範可繼續、一筆已核准可進下一步、一筆拒絕、兩筆等待、一筆未知工具即使標示 approved 仍阻擋。程式沒有執行任何真正的後續動作;因此這項演練只驗證示範政策,不代表 n8n 已完成設定或安全測試。
05 / 接到正式流程前,再問四個問題
- 所有可寄送、寫入或刪除的工具是否都經過關卡?是否還有未列出的旁路?
- 審核者能否從參數看清目標與影響?通知是否只暴露必要資料?
- 拒絕、逾時、代理審核與重試時,誰負責處理並留下紀錄?
- 用無敏感資料演練核准、拒絕與沒有回覆,確認外部系統的實際結果。
資料來源
- Human-in-the-loop for tools ↗n8n Docs
2026-10-09 以 Firecrawl maxAge=0 查閱官方全文;頁面未列固定發表日期。核對執行前核准、Approve/Deny、$tool 參數及系統提示建議。
- Set a human fallback for AI workflows ↗n8n Docs
2026-10-09 以 Firecrawl maxAge=0 查閱官方全文;頁面未列固定發表日期。此範例是答不出問題時轉人工,與工具執行前核准不同。
- Tools Agent ↗n8n Docs
2026-10-09 以 Firecrawl maxAge=0 查閱官方全文;頁面未列固定發表日期。核對 Tools Panel 中的 Human review 設定與工具連接方式。
來源查閱:2026-10-10。本文含 AI 協助整理;已完成編輯核准。
