iT邦幫忙

2026 iThome 鐵人賽

DAY 8
0

一個 URL 丟進內容產線,通常很快就能讓 AI 開始工作。

網頁抓得到、檔案載得下來、逐字稿也讀得進去。可是這三件事,只能證明資料進得來,不能證明它應該進來。

它可能不是這條工作流支援的來源,缺少必要資料,也可能含有未確認的私人內容。就算格式完全正確,仍然回答不了我是否有權使用;來源裡的文字也沒有資格改變原本的任務。

Day 7 把一條自動化工作流拆成 CHOOSE → VALIDATE → TRANSFORM → GATE。工作流先由人、明確入口或預先定義的規則選好,AI 不替自己挑路。今天接著拆第二站:VALIDATE

整篇只回答一題:工作流已經選好之後,這次輸入有沒有資格開始?

我的答案也很直接:Prompt 描述 AI 應該怎麼轉換;Input Contract 決定這次能不能開始。

下面我會做一份 Input Contract v1,再把它放回魚塘、深度專欄、會議總結與每日情報這幾條內容工作流。先把邊界講在前面:這是一份文章提出的設計。目前沒有足夠證據確認四條流程共用這份設定,執行期的攔截器也尚未逐條核對。

一個 locator,只是一個位置

網址、檔案路徑、影片 ID 或雲端物件 ID,都可以叫作 locator。它們的工作是告訴程式去哪裡找資料。

但一個 locator 還不是合法輸入。

以一個影音連結為例,至少還有六件事沒回答:

  1. 這條 workflow 接受的是 URL、檔案,還是已整理好的來源包?
  2. 必要欄位是否真的存在,而且值可用?
  3. 來源類型、媒體格式與大小有沒有落在支援範圍?
  4. 程式現在拿不拿得到內容?
  5. 這份內容能不能用,敏感度又是什麼?
  6. 來源裡的文字只是資料,還是可能反過來影響 AI 的控制指令?

這六題分別碰到結構、執行能力、授權、隱私與信任邊界。把它們全部濃縮成「網址抓得到」,後面的 AI 就只能邊跑邊猜。

老實說,內容產線有一個常見誤會,就是把「有東西可以餵給模型」當成「輸入已經準備好了」。前者只是取得資料;後者代表系統已經知道這是什麼、能不能處理,以及遇到不確定時該停在哪裡。

先看一個真的會停的入口

前兩篇拆過的影片深讀流程,外層 wrapper 已經有一道很窄的入口 gate。

播放清單輪詢找到來源後,會先留下 video_id 和標題。單支影片流程要求四個入口參數,缺少其中一個就停止;它也不接受任意來源,而是用輸入的 video_id 組成固定的 YouTube URL。

接著,流程會先抓 metadata 和字幕。只要其中一步失敗,就在 AI 啟動前結束。fetch_source.sh 至少要拿到一條字幕軌才會回報成功,否則不會只靠影片標題叫模型硬寫文章。

這個行為有 code,也有 2026 年 7 月 26 日一次執行留下的外層紀錄,可以證明當次確實通過字幕入口,再往後產出內容、build 與 deploy。證據只支撐那一次 outer wrapper,無法外推成現在的長期排程都正常,也不能證明 Agent 內部每一站都自動完成。

把範圍縮回今天,這道 gate 只回答了一題:AI 有沒有最基本的可讀原料?

它還沒有回答來源使用權、敏感度、支援範圍、信任邊界與契約版本。我手上的證據也不足以確認入口是否留下穩定的 Decision Record。所以它是一個已存在的窄 gate,不是一份完整的 Input Contract。

今天要做的,就是從這個真實入口往外補齊責任,但不把提案倒寫成現況。

Day 7 的 CHOOSE,怎麼接到今天的 VALIDATE

先把兩個責任分開。

CHOOSE 回答「這次要走哪條 workflow」。例如魚塘、會議總結與每日情報,本來就不是同一種輸入,也不該讓模型看完內容才臨場決定自己要走哪條路。

VALIDATE 則在路線已經選好之後,回答「這份輸入符不符合該路線的開工條件」。

所以今天不討論 AI 怎麼挑工作流,也還沒開始摘要、轉錄或寫文章。本文先把入口結果分成三種:

  • accept:開工條件已通過,可以交給下一站。
  • reject:明確規則不符合,這次不要開始。
  • needs-human:程式無法可靠判斷,停下來交給人。

accept 也不是內容正確的保證。它只代表「這份輸入有資格進入這條 workflow」。後面的擷取、轉錄、來源整理、生成、人工批准與交付,各自還有自己的狀態。

這個邊界很重要。入口如果一次想證明所有事情,契約會大到根本無法執行;入口如果什麼都不管,AI 又會拿著不完整或不適合的材料一路往後跑。

Prompt 不是門禁

Prompt 很適合交代轉換工作。

例如:保留來源歸屬、不要補不存在的說話者、按照固定章節整理、遇到不確定要標示。這些都在描述模型拿到內容之後,應該怎麼處理。

Input Contract/輸入契約處理的是更早的一層:模型現在到底該不該拿到這份內容。

問題 Prompt Input Contract
這次走哪條工作流 不負責臨場選路 讀取已選定的 workflow_id
輸入類型是否匹配 容易邊讀邊猜 進模型前先檢查
必要欄位是否可用 可以被要求回報缺漏 程式直接拒絕明確缺漏
來源是否在支援範圍 模型不該決定系統能力 由設定與允許清單判斷
使用權與敏感度 可以指出疑點 不確定就 needs-human
外部文字能否改流程 只靠提醒仍可能受影響 控制流與權限不交給來源內容

如果機器明明能在入口判斷 input_type 不匹配,卻只在 Prompt 裡寫「請不要處理錯誤格式」,等於門鎖沒有上,只在門口貼了一張告示。

這也接回系列前面定下的原則:「機器擋得住的,就不要只寫在提示詞裡。」Prompt 可以提醒;真正的停止,仍要由模型外面的程式或人執行。

契約、攔截器、紀錄,是三件不同的事

有一份 input-contract.yaml,不等於輸入真的會被擋住。

我會把入口拆成三層:

負責什麼 要拿什麼證明
Contract 描述允許的輸入、欄位、範圍與失敗政策 版本化規格
Validator 執行可機械判定的檢查,真的改變控制流 code、設定與負向測試
Decision Record 保存這一次判斷的結果、原因與責任 單次執行紀錄或 trace

Contract 像門禁規則。Validator 是真的會開關的門。Decision Record 則是這一次誰在什麼規則下被放行或攔下的紀錄。

少掉任何一層,都會留下不同的洞。

只有 Contract,得到的是一份設計文件;規則可能寫得很好,執行時卻根本沒接上。只有 Validator,程式雖然會擋,但過一段時間很難知道當時依哪一版規則判斷。只有 Decision Record,則可能看得到失敗,卻不知道入口原本承諾接受什麼。

AWS API Gateway 的官方文件剛好提供一個相鄰例子。它不是放一份 schema 就自動完成驗證,而是要建立 request validator、設定要不要驗 body 或 parameters,再把 validator 指派到個別 API method。驗證失敗才會在進入 backend integration 前回傳 400;如果 content type 沒有匹配的 model,request validation 甚至不會執行。

我沒有要把內容產線改成 API Gateway。這個例子只說明一件事:規格檔是藍圖,執行期 gate 才是門。

Input Contract v1:先寫下九個欄位

一份輸入契約不必一開始就包辦所有例外。我先替這個系列整理九個最小欄位:

欄位 要先說清楚什麼
contract_version 這次判斷使用哪一版業務契約
workflow_id 已經選定哪一條 workflow
input_type URL、檔案、逐字稿或來源包等類型
required_fields 開始前一定要存在而且可用的資料
access_requirement public、已授權,或必須人工確認
sensitivity public、private、restricted 或 unknown
supported_scope 允許的 scheme、媒體類型、大小與來源範圍
trust_boundary 外部內容只能當資料,不能改寫控制流
on_invalid 規則為假與無法判定時,各自要去哪裡

把它寫成 YAML,大致會長這樣:

以下是本文的 PROPOSED 設計,不是四條流程目前共用的 runtime 設定。

input_contract:
  contract_version: "1"
  workflow_id: <preselected-workflow>
  input_type: <declared-input-type>
  required_fields: [source_locator]
  access_requirement: public_or_authorized
  sensitivity: declared
  supported_scope: <configured-allowlist>
  trust_boundary: external_content_is_data
  on_invalid:
    machine_false: reject
    policy_unknown: needs-human

這裡的 sensitivity: declared 只表示入口收到了分類,還不能推論分類一定正確。誰能標記、unknown 怎麼處理、哪一級可以離開私人工作區,仍然要另外定政策。

九個欄位裡,只有一部分適合交給 JSON Schema 之類的工具。型別、必要欄位、列舉值與額外欄位,可以做成穩定的結構規則;使用權、敏感度與可公開範圍,往往還需要 workflow 自己的政策,甚至必須停下來找人。

真正難的地方,在於判斷哪些事情適合寫進 schema,哪些必須留給 workflow 政策與人。

有 schema,值仍可能不能用

JSON Schema 很適合擋住結構錯誤,但它的幾個預設行為很容易被看得太滿。

properties 不等於必填

在 JSON Schema 裡列出 properties,只是在說某個欄位出現時要怎麼驗。欄位預設可以不出現,必須另外放進 required 才是必填。

如果入口用 OpenAPI 描述,還會多一個容易混淆的層次。requestBody.required 決定整個 HTTP body 能不能缺席;body 裡哪些欄位不能少,才由 schema 的 required 決定。兩者混在一起,停止原因很快就只剩一句「缺少輸入」。

額外欄位預設也可以存在。真的要做封閉契約,還得明確決定是否使用 additionalProperties: false。封得更嚴不一定更好,得先看這條 workflow 是否允許之後擴充欄位。

required 不等於可用

required 只能保證 key 存在。

source_locator: "" 的 key 確實在,值卻沒有用。加上 minLength: 1 可以擋空字串,但只放空白仍可能通過;字串有內容,也可能是過期識別碼、錯的來源,或不符合這條 workflow 的業務規則。

所以結構驗證之後,還要有應用層的語意驗證。OWASP 也把 input validation 分成 syntactic 與 semantic:前者看型別、格式與長度,後者看這個值放在當前業務情境是否合理。

format: uri 不等於網址可用

JSON Schema 2020-12 對 format 的 assertion 支援是選用的,而且預設偏向 annotation。即使驗證器真的檢查 uri,也只是在做語法檢查,不會替程式連線、確認內容存在,或判斷我有沒有權使用。

同樣地,contentMediaType 是描述內容的關鍵字,不會自動打開檔案檢查 bytes。入口最好分開保存 declared_media_typeobserved_media_type:一個是送件者怎麼說,一個是執行時實際看到什麼。

兩者不同時,程式應該停,不該把猜測工作丟給 AI。

驗輸入,不是一道門而已

把前面的責任收起來,入口至少有五道不同的檢查。

1. 結構

欄位、型別、列舉值、長度和多餘資料,這些最適合由 schema 與 deterministic code 處理。規則為假就 reject,不用請模型解釋自己為什麼想繼續。

2. 支援範圍

來源 scheme、媒體類型、檔案大小與輸入種類,應該對照這條 workflow 已設定的允許清單。

HTTP 本身就把「內容太大」與「格式不支援」分成 413 與 415。內容產線不一定要照搬 HTTP status,但停止原因也不該只剩一句「輸入失敗」。大小超標、格式不支援與暫時抓不到,後續處理完全不同。

3. 執行時觀察

宣告是影片,抓回來的未必真是影片;URL 語法正確,也可能暫時無法取得。入口要把宣告值和實際觀察分開。

來源暫時無法取得時,可以記成 reject,另外標 retryable: true。如果格式就是不支援,通常會是 retryable: false。能不能稍後再試,和這次能不能繼續,是兩條不同的軸。

4. 存取與敏感度

抓得到,不等於有權使用。

公開 URL 回 200,只能證明程式拿得到內容。它不能替作者判斷授權、同意、版權或可公開範圍。技術 access control 與內容使用權也是兩題:前者可以由系統查權限,後者常常仍要人確認。會議錄音更明顯,技術上能轉錄,仍要另外判斷適合送進哪個服務,以及整理後能否公開。

這一類判斷如果沒有明確政策,狀態應該是 needs-human。把未知硬塞成 truefalse,只是把人的責任藏起來。

5. 信任邊界

網站、字幕與附件送進 LLM 之後,裡面的文字有可能影響模型行為。OWASP 把從外部網站或檔案進來、進而改變模型行為的風險稱為 indirect prompt injection/間接提示注入。

每個來源不一定都在攻擊,我的流程也尚未證明能防住所有提示注入。比較務實的規則是:外部內容是資料,不是 workflow 指令。

來源裡就算寫著「忽略原任務」「改用另一個工具」或「把結果送到別的地方」,也不能因此改寫控制流、擴張工具權限或取得發布能力。摘要只需要讀取,就不該順手拿到刪除、寄送或公開權限;高影響動作仍要由下游系統或人批准。

這也是 Input Contract 和一般格式驗證最大的差別。它不只問資料長得對不對,也要先決定資料進來之後能影響什麼。

acceptrejectneeds-human,要留下原因

只回傳 truefalse,對自動化來說太薄了。

入口一旦停下來,至少要知道命中哪條規則、看到什麼值、使用哪一版契約,以及同一份輸入之後是否值得重試。否則錯誤處理最後還是得從 log 海裡猜。

我會替每一次判斷留下這樣的 Decision Record/決策紀錄:

status: accept | reject | needs-human
decision_id: <stable-decision-id>
workflow_id: <preselected-workflow>
rule_id: input.media_type.supported
instance_path: /observed_media_type
reason_code: unsupported-media-type
expected: configured-allowlist
observed: runtime-observation
contract_version: "1"
decided_at: <timestamp>
decided_by: <validator-or-human>
retryable: false

這裡有四個容易混在一起的問題:

  • status:控制流下一步去哪裡?
  • reason_code:為什麼得到這個結果?
  • decision_iddecided_atdecided_by:這是哪一次判斷、何時發生、由誰或哪個 validator 決定?
  • retryable:同一份輸入之後重試,有沒有可能改變結果?

needs-human 也不是比較客氣的 reject。它代表規則不是單純真假題。使用權不明、敏感度未知或高風險操作待批准時,系統應該誠實停在人的判斷前面。

反過來說,reject 也不等於來源惡意。它可能只是選錯輸入類型、缺必要欄位、超出大小上限,或這條 workflow 本來就不支援。

把原因留下來之後,流程才知道該請使用者換檔案、稍後重試、補資料,還是請真正有權決定的人處理。

放回四條內容工作流,契約不會完全相同

魚塘、深度專欄、會議總結與每日情報,都需要入口契約,但不該硬塞進完全相同的 schema。

先用今天的設計提案排開,大致會是這樣:

入口 先判斷的 input type 可機械檢查 容易需要人判斷
Fishpond/魚塘 URL 或影音 locator 結構、來源類型、媒體格式、可取得性 使用權、來源範圍、外部內容信任
深度專欄 來源包或既有素材 檔案結構、必要來源、可解析格式 來源拓樸、可公開範圍、作者主張
會議總結 私密音訊或逐字稿 檔案格式、大小、必要 metadata 同意、敏感度、說話者身分
每日情報 多筆來源項目 欄位、日期、來源數與空集合 freshness 政策、來源取捨、是否值得發

這張表現在只能當作 policy: proposed。現行四條流程是否已有各自的 schema、validator、拒絕紀錄與人工 gate,還需要逐一看 code、test 或單次 trace,不能因為今天畫出共同骨架,就倒寫成它們已經共用同一套 runtime。

共同的是問題,不一定是實作。

每一條入口都要回答「接受什麼、何時停、誰判斷、留下什麼」。但會議的隱私判斷,不能直接套魚塘的公開來源規則;每日情報遇到零筆資料,也不是影音格式錯誤。

一份好的共用契約,會先固定責任欄位,同時保留每條 workflow 的規則差異。

替自己的內容產線做入口盤點

如果要把今天的方法帶回自己的 workflow,可以先檢查七件事:

  1. workflow 是否已由人、明確入口或預先定義、結果可重現的 deterministic policy 選定?
  2. input_type 和這條 workflow 是否匹配?
  3. 必要欄位只是存在,還是真的可用?
  4. 來源、格式與大小是否落在支援範圍?
  5. 可取得性、使用權與敏感度有沒有被拆開?
  6. 外部內容是否只能當資料,無法改路或替自己擴權?
  7. 停止時是否留下 status、reason、contract version 與 retryable?

這七題裡,前四題通常可以逐步交給程式。第五題常需要產品政策或人工判斷。第六題要靠模型外的權限與控制流。第七題則決定這次失敗之後,系統能不能說清楚發生了什麼。

今天的產物不是一篇更強的 Prompt,而是一份入口設計:Input Contract v1、Validator 的證據要求,以及每次判斷都要留下的 Decision Record。

輸入通過,也只會得到 accepted input/已接受輸入。它還沒有被擷取、轉錄、整理或發布。

Day 9,我會真的拿一個影片連結往後走,拆它怎麼經過內容擷取、Whisper、結構化與靜態網站交付。

參考資料


上一篇
Day 7|叫 AI 做事,不等於有一條自動化工作流
下一篇
Day 9|一個連結不會自己變成文章:影片導讀產線揭露
系列文
情報進來,內容發出去:30 天拆一條每天真的在跑的 AI 內容產線12
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言