iT邦幫忙

2026 iThome 鐵人賽

DAY 11
1
AI 自動化

我以為我保留了五道閘門系列 第 11

Day 11|那顆「生成腳本」的按鈕,做了,但它是壞的

  • 分享至 

  • xImage
  •  

模組二|選題與素材庫(Day 6–11)

模組二的最後一天,講一個我不太想寫的東西。

素材庫有一個前端儀表板,可以瀏覽條目、依日期跟情緒標籤篩選。上面有一顆按鈕,叫「生成腳本」:選一則條目,按下去,它應該產出一段口播稿。

它是壞的。而我知道它壞了很久,沒有修。

它壞在哪

按下去之後,前端會打一個 /api/proxy 端點,帶著條目內容去呼叫一個 flash 級的語言模型。

/api/proxy 沒有人在接。

錯誤訊息裡有提示,說要先跑一支本地伺服器腳本。我去找那支腳本:

它不存在於 repo,也不在 git 歷史裡。

不是被刪掉(被刪掉的話 git log 找得到),是從來沒有被 commit 過。它可能曾經存在於我的某台機器上,也可能從頭到尾就沒寫完。

所以完整的狀態是:前端寫好了、按鈕在那裡、錯誤處理也寫了,而後端從來沒有進過版控。

同一個決定的第二個後果

我按下那顆按鈕,一條線沒人接,另一條把鑰匙送進了郵筒

這支前端檔案裡還有一組東西:一個明文的 API 金鑰,寫在 React 的 useState 預設值裡。

// 大概是這個形狀(金鑰內容不貼)
const [apiKey, setApiKey] = useState('<39 個字元的金鑰>');

這支檔案被 git 追蹤,前後 commit 過 11 次。我用 git log -S 查過:金鑰在 git 歷史裡。

意思是:現在把它從檔案裡刪掉,也還在。 要真正清除,得輪替金鑰,加上改寫 git 歷史。前者是必須做的,後者對一個有其他協作痕跡的 repo 來說很麻煩。

而這兩件事(功能不能用、金鑰外洩)是同一個決定造成的:

把語言模型的呼叫放在前端。

放在前端,就需要金鑰在瀏覽器拿得到,所以它被寫進原始碼。
放在前端,就需要一個 proxy 繞過 CORS 跟隱藏金鑰,所以有了 /api/proxy
而那個 proxy 沒寫完,所以功能是壞的。

一個架構決定,同時製造了一個不能用的功能跟一個不可回收的洩漏

為什麼沒修

老實的答案:因為它想解決的問題,已經被別的東西解決了。

口播稿實際上是 CLI 的一個子命令產出的:export-voice-scripts。它跑在本機、金鑰在環境變數裡、輸出是 markdown 檔案。它能用,而且我一直在用。

而它贏的地方不只是「能跑」。它產出的稿子是五段固定結構,每一段還有硬編的字元上限:

開場   ≤ 95 字元   (約 10–15 秒)
重點一 ≤ 115 字元  (約 20–30 秒)
重點二 ≤ 115 字元  (約 20–30 秒)
查核段 ≤ 110 字元  (約 20–30 秒)
收尾   ≤ 100 字元  (約 10–15 秒)

用字元數控時長。 這是一個很土的近似(中文口播速度大概每秒四到五個字,所以字元數乘一個係數就是秒數),但它有一個決定性的好處:它在產出的當下就把「這段會唸多久」變成可檢查的。

那顆前端按鈕做不到這件事。它產出的是一段自由文字,長度不受控,唸下去可能三十秒也可能三分鐘。而 rundown 上每一段是有時間預算的(Day 15 會講),一段超時,整集就要重排。

所以它不是「壞掉所以被取代」,是「即使能跑也不夠用」。 這讓修它變得更沒有動機,修好了也還是要重寫產出邏輯。

所以那顆按鈕從「主要路徑」變成了「一個沒人走的路徑」,然後就被留在那裡。

這個過程沒有任何一刻是「決定不修」。它是逐漸變得不重要,然後被遺忘。

但它不是零成本

我原本以為「壞掉但沒人用」等於沒事。列出來之後不是:

一、它會誤導人。 任何打開這個前端的人(包括未來的我)會看到一顆功能按鈕,然後花時間搞清楚為什麼它不動。

二、金鑰還在,而且會一直在。 這個成本跟按鈕壞不壞完全無關,它是獨立存在的、而且不會隨時間衰減。

三、它讓「這個前端能做什麼」變得不可信。 當一個介面上有一顆假的按鈕,其他按鈕的可信度也會下降,我下次看到別的功能,會先懷疑它是不是也是壞的。

壞掉的功能不會安靜地待著。它會消耗每一個看到它的人的注意力,而且它帶著的東西不會自己消失。

這一天跟閘門的關係

我拎著鑰匙從自己設的閘門走過去,它一聲都沒響

Day 3 那五道閘門的第一道是「不得自動 commit」。

而這次是我自己 commit 的。

金鑰不是 AI 推上去的,是我寫完那支前端、覺得先能跑再說、然後推上去的。閘門一條都沒有違反,因為閘門管的是 AI,不管我。

這件事讓我看清楚那五道閘門的一個假設:它們預設「風險來自 AI,人是那道防線」。

而人也會累、也會趕、也會覺得「先能跑再說」。當防線本身就是風險來源時,這個設計沒有備援。

真正該擋這件事的東西不是閘門,是一支會掃金鑰特徵的 pre-commit hook。那東西不管是誰在 commit,它都擋。

閘門分辨的是「誰在做」,而好的檢查不該關心誰在做

代價

這一篇的代價是我到現在還沒輪替那組金鑰。

寫這篇的時候我又確認了一次,它還在歷史裡。這不是不知道要做,是知道而沒做,跟這個系列裡其他每一件「知道但沒做」的事一樣。

而它跟其他幾件不同的地方在於:其他的是延後的收益,這一件是持續的風險。 模板 8% 合規不會有人受害,一組活著的金鑰在公開歷史裡會。

(這個 repo 是 private,所以嚴重性打了折。但「private repo 所以沒關係」是一個很脆的理由:它假設它永遠不會變 public、永遠不會被誤加協作者。)

第二個代價:我沒有把那顆按鈕拿掉。 拿掉它是五分鐘的事,而我留著它到寫這一篇的今天。

帶走什麼

一個架構決定會同時製造多個後果,而你通常只看到第一個

「把 LLM 呼叫放前端」的第一個後果是「需要一個 proxy」:我看到了,所以我開始寫 proxy。第二個後果是「金鑰必須在前端可得」:我沒看到,或者看到了但覺得之後再處理。

決定的時候可以多問一句:這個決定會讓什麼東西「必須存在於它不該存在的地方」?

金鑰必須在瀏覽器拿得到、密碼必須寫在設定檔、憑證必須放進映像檔,這一類「必須」通常就是第二個後果的形狀。

第二條,關於壞掉的功能:

「壞掉但沒人用」不等於零成本。 它至少有三個持續成本:誤導後來的人、帶著的東西不會消失、拉低整個介面的可信度。

處理方式很簡單:刪掉它,或者在它旁邊寫清楚它是壞的。 兩個都是幾分鐘的事,而放著不管是唯一一個會持續累積成本的選項。

第三條,也是這一天最刺的一條:

如果你的安全機制是「靠人謹慎」,那它在人累的時候就不存在。 而那正好是最容易出事的時候。

模組二到這裡結束。明天進模組三:這條產線上唯一一個真正活下來的規則,是一支 64 行的 bash。


上一篇
Day 10|一則條目,四小時內改了 11 次
下一篇
Day 12|64 行 bash,退出碼 0 才算完成
系列文
我以為我保留了五道閘門12
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言