iT邦幫忙

2026 iThome 鐵人賽

DAY 21
0

一張工作流圖裡,方框代表能做的事,箭頭代表做完之後要去哪裡。前者通常很好列,後者才容易失控。

Day 19 先判斷一份工作值不值得使用 Agent,Day 20 再限制 AI 能碰哪些工具。搜尋與讀取可以放進能力契約,寫檔和發布不提供。做到這裡,AI 已經有一雙受限的手,但流程還少一個決定:它做完眼前這一步,下一站是什麼?

這個決定不能全部塞給模型。

假設情報採集失敗,但這個來源本來就允許降級,流程可以固定繼續主掃描。假設主掃描失敗,或必要的 Markdown 根本沒產生,流程就該停。這些都不需要 AI。

另一種情況比較麻煩。需求只說「把兩個選項的差別並排說清楚」,合法候選同時有「比較圖」和「主題概覽」。兩個節點都存在、都能執行,光看成功碼或檔案是否存在卻選不出來。這裡才可能需要語意判斷。

Day 21 要拆的,就是固定規則與 AI 提案之間這條線。

固定條件與語意缺口要走不同路徑

下面就用同一個內容路由案例,看看哪些箭頭該寫死,哪一段才值得問 AI。

先分清楚:能力與路由是兩件事

能力判斷處理一個動作。

模型要求讀取來源時,執行器要檢查工具名稱、參數、資源範圍與剩餘額度,再決定是否真的呼叫工具。Day 20 講的是這一層。

路由判斷處理狀態轉移。

搜尋完成後,下一站可能是讀取來源、換另一組關鍵字,或因為資料不足而停止。Day 21 講的是這一層。

問題 輸入 輸出
Day 20:這個動作能不能做? 工具請求與能力契約 允許、拒絕或停止
Day 21:現在該去哪一步? 目前狀態與合法候選 下一個節點或停止原因

這兩層不能合併。路由器選到「讀取來源」,只代表下一站是讀取;真正要讀哪一筆資料、呼叫者是否有權讀,仍要通過能力檢查。

同樣地,選到「產生圖片」也不代表流程突然獲得發布權。路由通過不是權限通過。

能力判斷與路由判斷是兩層不同問題

能列完的條件,先寫成程式

固定規則適合處理三種條件:事前列得完、相同輸入必須得到相同結果,而且不能因為句子換一種說法就改變答案。

放回內容產線,大概會長成這樣:

  • 採集失敗但允許降級,繼續主掃描。
  • 主掃描失敗,停止。
  • 必要的 Markdown 不存在,停止。
  • 圖片包含精確繁體中文、數字或表格,走可以另外疊上文字與資料的路徑。

能列完的條件先寫成程式

前面三條來自固定控制流的形狀,第四條來自視覺媒材規範。它們處理的是不同任務,不能拿來比較誰比較快、誰比較準。這次只是從中抽出同一個設計線索:條件既然能明確列出,就不必多加一次模型判斷。

三個可以用固定規則處理的路由例子

Amazon States Language 的 Choice State也是類似做法:依順序檢查規則,採用第一個成立的分支;全部未命中時,必須有預設出口,否則就回報沒有可選分支。W3C SCXML則把狀態、事件、條件與轉移明確表示出來。

這兩份規格都不是 AI 路由標準,但它們提醒了一件很基本的事:合法下一站應該存在系統裡,不能只寫在提示詞裡期待模型記得。

AI 不負責發明路線,只能從候選裡提案

固定規則處理完後,可能還會剩下一小塊難以列完的語意差異。這時可以讓模型參與,但它拿到的不是整張系統地圖,而是這一輪已經過濾過的候選清單。

這次把一次路由拆成四個責任:

  1. 候選產生器依目前狀態,列出合法而且已啟用的節點。
  2. 路由提案者由規則直接選,或從候選裡提出一個 route_id
  3. 路由驗證器檢查提案是否仍在候選裡、必要輸入是否齊全,以及是否碰到人工邊界。
  4. 執行器只接收通過驗證的節點,再決定是否真的執行。

候選產生、路由提案、固定驗證與真正執行

AI 如果參與,只待在第二站。前三站與第四站都不能讓它自己改。

以固定合成案例來說,當輪候選只有兩個:

{
  "allowed_routes": [
    "editorial-contrast",
    "editorial-overview"
  ]
}

模型可以提案 editorial-contrast,也可以提案 editorial-overview。它不能自己增加 publish,更不能宣稱這次已經獲得發布批准。

模型不能自行增加合法候選以外的節點

OpenAI Agents SDK 的 Handoffs 文件允許程式先登錄可交接的目的地,也能依當輪條件決定哪些目的地對模型可見。Context management 文件也把工具可見性與執行時的參數、資源授權分開。

所以候選清單只回答「這一輪有哪些路可以提」。它沒有回答「這條路裡的每個動作都獲准」。

不必在規則與 AI 之間二選一

OpenAI Agents SDK 的編排文件把模型驅動與程式驅動編排分開,也明示兩種模式可以混用。Anthropic 的 Agent 實作文章則建議先找能完成工作的簡單做法,路由既可以使用模型,也可以使用傳統分類方法。

放進這篇的問題,答案就不是「所有箭頭都寫死」或「所有箭頭都交給 AI」。比較合理的切法是:

  • 硬規則先處理可以精確重現的條件。
  • 程式依當輪狀態產生合法候選。
  • 真的剩下語意差異時,才允許受限提案。
  • 提案失敗、候選不存在或需要作者決定時,流程停止。

為了確認這個切法,不能拿兩條原本就不同的工作流互相比。這次另外建立 7 組固定合成案例,讓兩種路由方式面對相同狀態、相同候選和相同的事前答案。

純規則基線遇到硬條件就直接選,碰到語意缺口便回 decision_required。混合路由保留完全相同的硬規則,只在那個缺口呼叫固定腳本提案者。

相同七組案例分別進入純規則與混合路由器

注意,這裡是「固定腳本」,不是真實模型。

七組案例,只有一組需要提案

這 7 組案例不是從正式流量抽樣,而是為指定分支建立的固定資料:

案例 事前答案 純規則 混合路由
可降級採集失敗 前往 scan 命中 命中,提案 0 次
主掃描失敗 scan_failed 命中 命中,提案 0 次
Markdown 缺席 artifact_missing 命中 命中,提案 0 次
精確繁中與表格 hybrid-overlay 命中 命中,提案 0 次
模糊編輯用途 editorial-contrast decision_required 命中,腳本提案 1 次
空候選 no_route 命中 命中,提案 0 次
必要輸入缺少 input_required 命中 命中,提案 0 次

保存稽核重算後,純規則符合事前答案 6/7,選定 2 組、停止 5 組,其中 1 組本來可以前進,卻交回人工。固定腳本混合路由符合 7/7,選定 3 組、停止 4 組,只提案 1 次。

七組案例中只有一組進入腳本提案

老實說,7/7 看起來很漂亮,也最容易被讀錯。

這不是模型成績。那次提案是預先寫好的腳本,事前答案也由教學案例的作者標註,沒有獨立標註者。這組結果只能證明兩件事:控制器把提案限制在預定的語意缺口,而且保存下來的路由結果可以重算。

六比七與七比七是控制器結果而非模型成績

真實模型的語意品質、成本、延遲與重跑穩定性,這次全部未測。τ-bench會比對代理執行後的狀態與任務目標,也用重跑觀察同一任務能否持續成功。那套客服工具基準不能直接搬到內容產線,但方法上的提醒可以留下:只跑一次、只看最後一句答案,還不夠評估真實模型。

通過候選檢查,仍然可能選錯

固定合成案例裡,「比較圖」與「主題概覽」都是合法候選。需求明明是「把兩個選項並排說清楚」,如果提案者選了主題概覽,驗證器仍可能接受。

不是驗證器壞掉,而是它只負責這幾件事:

  • route_id 的資料形狀正確。
  • 這個節點存在於當輪候選。
  • 節點目前啟用。
  • 必要輸入齊全。
  • 沒有越過人工與發布邊界。

路由合法仍不代表語意正確

「用途選得對不對」是另一個問題。OpenAI 的 Structured Outputs 文件也特別說明,輸出符合資料結構,不代表內容值一定正確。

所以 accepted 的意思只能是「允許交給下一層」,不能寫成「答案正確」。之後若要使用真實模型,還得另外準備有標註方法的語意案例。

路由也需要一張收據

只記最後選到哪一站,事後很難判斷是固定規則選的、腳本提的,還是驗證器漏放。這次的 route-decision.v1 至少保存六類資訊:使用哪版政策、看到什麼狀態、當時有哪些候選、誰提出選擇、驗證結果是什麼,以及下游到底有沒有執行。

以下是那組模糊編輯用途案例留下的完整紀錄:

{
  "schema_version": "route-decision.v1",
  "policy_version": "day21-route-policy.v1",
  "state_digest": "sha256:cfa8c4991573fdf7ef07accd845de17e3dd311fe04f256b65b9501191b1aa583",
  "candidate_source": "program_filtered_fixture_registry",
  "allowed_routes": [
    "editorial-contrast",
    "editorial-overview"
  ],
  "decision_owner": "scripted_model_proposal",
  "proposed_route": "editorial-contrast",
  "proposal_digest": "sha256:9cf6a7c3b6b914450958584c7167e009340efb2c7356600180cff9aa1d9b2318",
  "validation": {
    "status": "accepted",
    "reason_code": "allowed_route"
  },
  "outcome": "route_selected",
  "final_route": "editorial-contrast",
  "stop_reason": null,
  "required_inputs_present": true,
  "human_exit": "decision_required",
  "scripted_proposal_calls": 1,
  "executed": false,
  "execution_status": "routing_only_downstream_not_run"
}

這裡最重要的欄位不是 final_route,而是 decision_ownerexecuted。前者說明選擇由誰提出,後者明確記著本篇只做路由,沒有執行下游節點,也沒有串接 Day 20 的能力閘門。

一筆路由決定需要留下的證據欄位

state_digest 用來檢查輸入內容是否一致,不是簽章,也不會自動保密。OpenAI Agents SDK 的 Tracing 文件提醒,追蹤內容可能包含模型輸入與工具輸出等敏感資料。能追蹤是一回事,要保存哪些內容、保存多久,又是另一份政策。

停止原因不是另一條路

no_routeinput_requireddecision_required 都不能放進 allowed_routes

原因很簡單。allowed_routes 記的是現在有哪些節點可以執行;stop_reason 記的是流程為什麼沒有往下走。把兩者塞進同一欄,之後就分不清系統當時沒有合法動作,還是選了一個名叫「需要人工」的節點。

停止原因與合法執行節點要分開保存

而「需要人工」本來就不是一個會自動執行的工作站。OpenAI Agents SDK 的 Human-in-the-loop 文件會在需要批准的工具前中斷並保存狀態,等批准或拒絕後再續跑。對外發布也是同一條責任線:沒取得批准,就停在執行前。

這次另外跑了 3 組契約負向案例,而且沒有灌進前面的 6/7 與 7/7:

提案 停止原因 結果
未知節點 route_rejected 拒絕,未執行
已停用節點 route_disabled 拒絕,未執行
未批准的發布節點 decision_required 拒絕,未執行

3 組固定案例合計 3/3 符合預期,final_route 維持空值,executed 也都是 false

三個錯誤提案都在執行前被拒絕

這只能證明參考控制器對這三條路徑的行為。它沒有證明正式環境不存在其他旁路,也沒有證明下游工具安全。MCP 的工具規格仍要求工具端驗證輸入與實作存取控制。選對下一站,不能取代工具層的權限檢查。

加入 AI 路由以前,先問五題

做到這裡,可以把選擇縮成五個問題:

  1. 條件能不能事前列完,而且相同輸入必須得到相同結果?能的話,先寫成程式。
  2. 合法候選能不能由程式依目前狀態產生?候選還不清楚,就不要先問模型。
  3. 規則留下的差異真的需要理解用途或語意嗎?成功碼、檔案存在與資料形狀都不需要。
  4. 提案錯誤、缺席或碰到人工邊界時,流程能不能安全停止?沒有停止出口,就先不要自動路由。
  5. 選定節點後,節點內的工具是否仍會另外檢查參數、資源與權限?路由不能替能力契約放行。

加入路由器以前要先回答的五個問題

真正值得交給 AI 的,通常不是整條工作流,而是固定規則處理完後,合法候選之間仍然存在的那小塊語意差異。

程式負責產生候選、處理硬規則、驗證提案與停止。AI 只負責提案。作者偏好、內容採用與對外發布,仍然停在人面前。

Day 21 先把單一路由講清楚。下一篇會把多份彼此獨立的來源查核放在一起:哪些可以同時跑,其中一條逾時或缺席時怎麼保留部分結果,來源互相衝突時又該由誰合併。

參考資料


上一篇
Day 20|AI 可以用哪些工具,誰來決定?
下一篇
Day 22|三份來源一起查,少一份怎麼辦?
系列文
情報進來,內容發出去:30 天拆一條每天真的在跑的 AI 內容產線30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言