iT邦幫忙

2026 iThome 鐵人賽

DAY 19
0
AI Engineering

《矽墟》:我把一部科幻小說當成軟體專案來管系列 第 19

Day 19|同一張插圖生了九次,真正的產出是那份文件

  • 分享至 

  • xImage
  •  

模組三|AI 生圖與生片產線

昨天講角色卡怎麼產、怎麼校驗。那些到頭來都是紙上談兵——真正的驗收是拿它去做事。

這一回要一張插圖,四個角色同框:香案上躺著一枚薄晶片,綠語伸手擋開調律者的探針,訊號香的煙在半空裂成兩道。

生了九次。

用的是 Day 12 講過的那條插圖產線:illustrations.json 寫規格,generate-shortform-illustrations.mjs 讀 manifest、附上參考圖、打 OpenAI Images 的 /v1/images/editsgpt-image-1)。

「參考圖要先指定職責、角色數超過上限就縮小完整角色數」那條結論,Day 11 已經補進去了,這裡不重講。這篇講的是那條結論沒有涵蓋的兩個坑,以及第九次之後我改做的事。

兩個坑都跟角色卡直接相關,而且都是靜默失敗。

坑一:我把 A 管線的規則搬進了 B 管線

第 4 次,霜瞳被畫成第二個焰刃——畫面上兩個紅髮、沒有銀髮狙擊手。

原因藏在腳本裡。這條產線會依角色名注入身分約束:

if (characters.some((name) => name.includes('霜瞳'))) {
  identityNotes.push('霜瞳 is a silver-haired marksman android girl with pale blue eyes, ...');
}

而我在改 prompt 的時候,把角色的中文名從場景描述裡拿掉了——因為我剛寫完角色卡,滿腦子都是昨天講的那條「出圖提示詞不准出現人名」。

名字一拿掉,identityNotes 就不再注入。霜瞳的身分約束靜默失效,模型就拿手上另一張參考圖去補那個位置。

兩條規則都是對的,只是前提不同:

規則 為什麼
skill 的角色卡 禁止人名 卡片是通用產物,可能餵給任何模型,而模型對它認得的名字有既有印象
這條插圖產線 必須有人名 身分約束靠名字比對觸發,而「霜瞳」這種名字模型根本不認得,不會污染

同一個字串,在一條管線裡是污染源,在另一條管線裡是觸發器。

規則不能離開它成立的前提被搬運。 而搬錯的代價是靜默的——沒有錯誤訊息,只有一張看起來還行、但少了一個人的圖。

坑二:我從沒打開過綠語的立繪

改成兩人構圖之後畫面乾淨了,但綠語看起來「沒穿衣服」——只有一身白色裝甲板,沒有外衣。

因為我在角色卡和 manifest 裡都只寫了 white-and-teal medical exo-frame。模型照著畫,畫出一具醫療外骨架,完全合理。

而她的立繪上是:白色長版開襟醫療外套、寬袖長擺、青綠分層裙、白色護士帽、球形治療無人機。

我從頭到尾沒有打開過那張立繪。 我讀了焰刃、霜瞳、調律者三張,漏了綠語。角色卡裡那段外觀描述,是我從 mj-prompts/03-綠語.txt 的文字轉寫的,而那份文字本身也只寫了 exo-frame。

於是 Day 11 那條「圖打敗文字」在這裡以另一種方式成立:文字沒寫到的東西,圖上有;而你只讀文字,就永遠不知道自己漏了什麼。

這個坑比坑一更難防。坑一至少有跡可循——回頭讀腳本就會看到 identityNotes 是按名字觸發的。而坑二沒有任何線索:那份文字設定檔本身完全通順,看不出它漏了東西。你只有打開圖,才會知道文字漏了什麼。

第九次之後:把規格交出去

我倒了九次,底下一顆都沒接到,真正留下來的全在篩網上

九次之後我停手,不再往同一個 API 灌參數。

我把手上的東西整理成一份可交接的出圖規格

04.01 插圖生成需求
├── 參考圖:char-tuner.png / char-03.png(絕對路徑)
├── 輸出:assets/shortform/04.01-painless-residue.png,1536×1024
├── Prompt:分叉的煙(主體)→ 兩個前景角色(逐項 canon 描述)→ 背景剪影 → 渲染質感
├── Negative:24 條,每一條都對應一次實際失敗
└── 驗收檢查點:8 條,生完逐條比對

然後把這份規格丟給桌面版 codex 去跑。

一次過。煙乾淨地分成一個大 V、綠語外套裙擺完整、探針停在掌心前、背景剛好兩個剪影。

規格一個字都沒改。換的只是執行的引擎。

這件事的意義不在於「codex 比較強」——換一個模型當然會有不同結果,那沒什麼好講的。意義在於:當我終於能把需求寫成一份別人可以照著做的文件時,它才第一次做對。

前面九次失敗,有六次是因為我腦子裡有規格但沒寫出來(綠語有外套、名字要留著、四個人不能塞太多參考圖)。我以為我在調 prompt,其實我在把自己知道的東西一點一點補進文件裡。

九次失敗真正的產出不是那張圖,是那份 brief。

然後我發現自己又只寫了註解

那份 brief 一開始躺在暫存目錄,session 結束就會消失。

於是我把它收進 novel/短篇/illustration-briefs/,manifest 加一個 briefPath 指過去,reviewNotes 寫明「這是九次失敗的最後一版 prompt,不要拿它重生」。

寫完就發現這違反了昨天那篇的第一條結論——那全都是註解,不是機制。腳本不讀 briefPath--force 04.01 照樣會用舊 prompt 把好圖蓋掉,而且不會有任何提示。

所以補成會叫的東西,十幾行:

// 有些回的成品是在外部工具用一份手寫 brief 生出來的,
// manifest 裡的 prompt 只是當初失敗的最後一版。這種回直接擋下來,
// 不然一個 --force 就會把好圖蓋成爛圖,而且完全沒有提示。
if (!entry.briefPath || overrideBrief) return false;
console.error(`
這一回的圖不是這支腳本生的,所以不重生。
  現有的圖 ${entry.outputPath}
  生圖的規格 ${entry.briefPath}
要改圖的話,去改上面那份規格,然後用它原本的工具再生一次。
`);

現在跑 --force 04.01 會停下來、印出 brief 的路徑、exit 1。要硬幹得加 --override-brief

順手修掉一個會讓防呆失效的東西

我新裝的那個籃子一次都沒接到東西,因為上面早就被擋住了

第一次測這道守門,我看到的是這行:

OPENAI_API_KEY is missing. Source .env.local before running this script.

守門根本沒跑到。因為金鑰檢查寫在最上層,沒設環境變數就直接 exit

改成真的要打 API 時才檢查。防呆訊息被另一個錯誤蓋掉,等於沒有防呆。

這件事值得單獨記一筆,因為它很容易發生:守門通常是後來補的,而補的位置很自然會在既有的檢查後面。你以為你加了一道防線,實際上那道防線在大部分情況下根本不會被執行到。

加完守門一定要實際觸發它一次,看它到底印出什麼。

代價

代價一:那九次生圖是真的花掉了。

一張 gpt-image-1 高品質 1536×1024 不是免費的,九次就是九次。而其中六次的失敗原因,都是我自己漏掉的資訊,不是模型的問題。

換算下來,讀那張立繪要三十秒,而沒讀它的代價是兩次重生。做這種事之前,把所有來源打開看過一遍是最便宜的一步,我卻是在第八次之後才回頭做。

代價二:成品和產生它的規格,現在是分家的。

illustrations.jsonprompt 欄位跟實際生出這張圖的 brief 不是同一份東西。守門擋住了「不小心蓋掉」,但沒有解決根本問題——這一回的規格活在 manifest 外面,而 manifest 才是那條產線的主控檔。

真正乾淨的做法是讓腳本能直接吃 brief。目前沒有,所以這一回在 manifest 裡是個特例。

代價三:驗收清單是人在看的。

八條檢查點沒有任何一條是自動的。服裝有沒有退化、角色有沒有被複製、探針跟晶片之間的空隙還在不在——全靠我自己逐條看。

而這正是我在昨天那篇批評過的形狀:寫在文件裡的規則會爛。 差別只在於這次我知道它會爛,也知道自動化它要花多少工(影像判讀,不划算),所以選擇了接受。

代價四:這條路只在「一回一張圖」的量級成立。

兩個角色的上限、逐條人工驗收,都是因為插圖一回只有一張才撐得住。如果哪天要一天出十張,這整套都得換。

帶走什麼

一、規則不能離開它成立的前提被搬運。

在 prompt 裡寫角色名、產品名、公司名,你以為只是在標示對象,實際上是在啟用模型腦中關於那個名字的全部既有印象——這是「禁止人名」那條規則的理由。

但我把它搬到另一條管線就炸了,因為那條管線的身分約束正是靠名字觸發的。

每一條規則都有它成立的前提,而前提通常沒有寫在規則旁邊。 搬規則的時候先問:它是為了防什麼?那個東西在新的地方也存在嗎?

這條在工程上到處都是:從別的專案抄來的 lint 設定、複製的 CI 流程、沿用的 code review 準則——它們在原本的地方都對,因為那裡有你沒看見的前提。

二、做不出來的時候,先寫規格,不要一直調參數。

我在同一個 API 上灌了九次參數。真正解決問題的,是停下來把需求寫成一份別人能照著做的文件,然後換一個引擎執行。

一次就對了,而規格一個字都沒改。

判斷句:如果我現在要把這件事交給另一個人或另一個工具做,我交得出東西嗎?

  • 交得出 → 那份東西就是規格,換引擎不用重來
  • 交不出 → 你還在憑感覺,再調一百次也只是在慢慢把規格想出來

這條完全不限於生圖。做不出來的功能、修不好的 bug、談不攏的需求,卡住的時候寫一份能交接的規格,通常比再試一次有用。

三、只讀文字,永遠不知道自己漏了什麼。

綠語的外套不在任何一份文字設定檔裡,只在那張立繪上。而那份文字設定檔看起來完全通順——沒有任何跡象顯示它漏了東西。

所以「把所有來源打開看過一遍」不是龜毛,是唯一能發現這類漏洞的方法。三十秒的事,我省下來換了兩次重生。

四、加完守門,一定要實際觸發它一次。

我補的那道防呆,第一次測的時候根本沒跑到——被上面的金鑰檢查攔截了。

守門通常是後來補的,補的位置很自然會在既有檢查後面。一道從來沒被觸發過的防線,跟沒有防線是一樣的。


明天 Day 20,模組四開始:秤兒的角色頁為什麼需要 3D,以及在無 build step、純靜態站的限制下,技術選型前該先寫死哪些條件。


上一篇
Day 18|出圖提示詞裡,不准出現角色的名字
系列文
《矽墟》:我把一部科幻小說當成軟體專案來管19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言