
我打造 AI 想在航空公司機組復原上打敗求解器。它輸了,而那場失敗成了產品。
我為了贏而打造的基準測試,卻輸了
我打造 StormCrew 的第一版,是為了打敗求解器。那是我腦中的全部賣點。航空公司營運控制仰賴沿用數十年的最佳化引擎,所以如果我能訓練出更聰明的東西,我就會有值得講的故事。我花了好幾週。然後我把復原引擎拿去和 CBC,一個成熟的開源混合整數求解器,早在我會寫 for 迴圈之前就已經久經實戰,結果 CBC 贏了。不是四捨五入那種誤差。
我還記得盯著那兩欄數字,感受到那種特定的空洞——當你設計來證明自己正確的實驗,反而證明你錯了。求解器更快。它的計畫更便宜。它從未交回一份不可行的班表。我那套自作聰明的版本在三項全輸。
於是我做了當時能想到唯一誠實的事:我改的是主張,不是數字。
我打造 AI 是為了打敗求解器。求解器贏了。有意思的部分,原來是那場對決所掩蓋的一切。
那場翻轉,才是 StormCrew 真正長成的脊梁,而且我認為這比我原本打算講的故事更有用。你可以自己跑完整套流程:veriprajna.com/zh-Hant/demos/airline-crew-scheduling-ai,但讓我帶你走過是什麼改變了我的想法,因為那個轉向才是重點。
風暴關閉樞紐時,真正垮掉的是什麼?
在 CBC 讓我謙卑之後,我回頭去讀那些事故後檢討,幾乎沒有任何失敗是「數學只是稍微次佳」。不正常營運,業界稱為 IROPS——讓航空公司損失大約 每年 600 億美元(IATA)。那次經典災難,西南航空 2022 年 12 月,大約耗掉 12 億美元,約 16,900 班取消,約 200 萬名旅客滯留。當我追溯那些日子實際如何瓦解時,最佳化器從來不是反派。
垮掉的是另外三件事。復原太慢:當風暴關閉一個樞紐,下游連鎖的重新派組,很大程度上仍是一次 4 到 12 小時的人工搶救(有來源的基準數字,不是我捏造的)。它也風險太高:每一次重新派組都必須遵守 FAA Part 117 的執勤與休息限制,以及各航司的 工會 CBA,而一次違規就是合規事件,不是腳註。它還太不透明:剛失去機組的下游航班連鎖,直到那些航班已經在取消時才看得見。
最後這一點,正是傳統工具漏掉的,也是我讓示範首先展示的事。在最繁忙的樞紐注入一場風暴,應用程式會標出衝擊範圍:已停飛的航班,加上因輪班銜接而失去機組的一跳下游航班。在種子化情境中,全網有 53 班航班 處於風險。

自從 DOT 自動退款規則(2024 年 10 月) 上路以來,每一段超過 3 小時的連鎖延誤如今也自動變成財務打擊。於是,太慢、違法或盲目的代價上升了,工具卻原地踏步。這三項失敗,沒有一項能靠更好的目標函數修好。我一直在最佳化那件本來就已經沒問題的事。
我為什麼不再試圖打敗 CBC,而開始餵給它?
我與輸給 CBC 和解的方式,是給它一份不同的工作。與其和求解器競爭,我把它包起來。整條管線都是真實、決定性、有種子的程式碼:合成航空網路與機組狀態、靠輪班圖可達性計算衝擊範圍的中斷注入器、執勤產生器,然後是 CBC 作為引擎 來挑選計畫,再對「什麼都不做」做影子比較,最後產出一份簽名憑證。
在種子化風暴上執行時,產生器產出 1,762 條合法復原執勤(其中 52 條是把機組調到需要之處的空飛調派),外加 53 條取消備援,合計 1,815 個候選欄位。CBC 求解由此產生的 1,815 變數、115 約束 最低成本集合分割問題至 OPTIMAL,並在約 0.11 秒 內回傳計畫。該情境結果:53 班中 52 班重新派組(98%)、1 班取消、使用 34 組機組(25 組航線、9 組備勤)。

航空公司的問題從來不是求解器太弱,而是復原太慢、風險太高,而且直到來不及才看得見。
請注意那張截圖的誠實。復原引擎就是 CBC,面板上寫得清清楚楚。 耐久的主張不是我的程式碼比求解器更會最佳化,而是計畫在遠低於一秒內到達——而有來源的人工流程要花 4 到 12 小時,應用程式公開量測這道落差。我想把範圍講清楚,因為這是示範,我拒絕把它洗白成超過它的東西:那些精確數字是一個種子化合成網路的結果,不是開放世界保證。真正站得住腳的是 速度相對人工 這項主張。
合法性保證屬於程式碼,不屬於模型的判斷
我有一個強烈看法,是靠打造這套東西才賺來的,所以讓我直說。合法性保證不能活在模型的判斷裡。它必須靠構造活在決定性程式碼裡。 讓非法機組執勤永遠不被建議的方法,不是訓練模型去避開它,也不是在目標函數加懲罰項然後希望最佳化器繞過去。而是一開始就讓非法執勤不可能被產生。
因此約束在產生時就強制,而不是事後打分。Part 117 把執勤時段上限設為 780 分鐘、飛航時間上限 480 分鐘,並要求最短過站 30 分鐘;樣本 CBA 則把執勤上限設為 4 個航段。只有全部滿足這些條件的執勤才會成為候選欄位。這就是動作遮罩。非法指派不會被懲罰,它是 不可表示的。無論 CBC 怎麼處理交給它的欄位,無論可選的 copilot 之後對計畫說什麼,兩者都無法讓一條非法執勤復活——因為它從未進入集合。
這給我的是不變量,而不是分數:0 次非法指派,永遠,並經單元測試(測試套件 3/3 通過,檢查衝擊範圍非空、只產生合法欄位,以及復原計畫是合法分割)。分數可以退化。不變量可以承諾。

這也是為什麼,當安全故事是「模型學會不要這樣做」時,我不再覺得「自主 AI 營運」的賣點有說服力。在這次打造中,我剛好信任過模型去遵守一條硬規則一次——很早的時候——在那一個輸入之前都沒事,直到那個輸入讓它出事。在單一違規就是監管事件的領域裡,「通常合法」等於「不合法」。我寧願刪除可能性,也不願監督它。
一年中最糟的那天會發生什麼?
我差點就發布一個會自動核准任何事的版本,慶幸有一個情境攔住了我。把示範切到 severe,事件嚴重到備勤耗盡,只剩大約 30% 的機組仍在。CBC 仍能在約 0.05 秒 內找到完全合法的計畫,仍然是 0 illegal。但那份計畫會取消 53 班中的 20 班,也就是衝擊範圍的 38%,遠高於 OCC 的 15% 自動核准門檻。
正確做法不是默默蓋章一份取消超過三分之一受影響網路的計畫。於是狀態翻成 ESCALATE,需要人工簽核,並顯示原因。計畫仍被算出、仍合法、仍呈現給調度員(33 班復原,佔衝擊範圍 62%)。只是不會被自動核准。

多數「自主」賣點跳過的部分,是知道何時正確行動是不要行動,把這一天交給人。
打造那道閘門,改變了我對整個類別的感覺。升級不是系統失敗。那是系統對糟糕一天的誠實。 一個總是回傳自信答案的顧問,容易示範、危險信任。偶爾說「這次超過你的底線,你來決定」的那個,才是我真會在凌晨三點放在調度員旁邊的。
若我是調度員,我想要的那份產物
我不斷問自己,營運調度員隔天早上需要什麼,答案不是儀表板,而是紀錄。所以每一次復原都會封存成一份簽名的 recovery_plan.json:干擾、逐項選定的計畫與機組及航班、每項動作對照上限所檢查的具體 Part 117 與 CBA 條款、復原實耗時間,以及節省數字。這是 OCC 對 為什麼 建議這次復原的稽核紀錄,一鍵即可匯出。

還有一個可選的計畫 copilot,我想說清楚它站在哪裡。它是連到 LLM 的薄配接器(預設 Claude、可換供應商,或免金鑰本機橋接),用來 以淺白英文解釋計畫。它沒有金鑰就完全棄權,其他一切離線且免金鑰執行,而且它活在 決策核心之外。決定的是決定性遮罩、CBC 與升級閘門。模型只在事後敘事。我刻意把它放在那裡,因為一旦語言模型影響執勤是否合法,我就失去了整趟打造換來的保證。
關於節省數字,同樣的紀律適用。在一般情境,影子比較顯示 避免 52 次取消,以及大約 避免 237 萬美元的 DOT 退款曝險(每位旅客 300 美元模型)相對於什麼都不做。那是最討喜的框架,因為基線是讓整個衝擊範圍都滯留,而且標註為該單一情境的示意。它不是頭條,更不是我的程式碼更會最佳化任何東西的證明。我第一天就把那個論點輸給了 CBC。我不會用行銷數字悄悄贏回來。
那麼「強化,而非取代」到底是什麼意思?
我以前覺得強化是膽怯的選擇——當你建不出大膽東西時才說的話。現在我想法相反。這裡的買家已經擁有不錯的求解器堆疊,Jeppesen 或 IBS,無法容忍推倒重來、鎖定,或在一年最糟的那天出現無法解釋的建議。告訴那位買家「丟掉它,換成我更聰明的模型」並不大膽,那是我已經用基準測試對自己證偽的主張。
我能誠實提供的,是環繞他們已信任之求解器的營運層。讓連鎖在咬人之前看得見。讓非法動作不可能產生,而不只是被勸阻。把數小時壓成數秒。並知道何時這一天糟到正確答案是升級、而非自動核准。示範裡的一切都是合成且有種子的——網路、機組、中斷、美元數字——沒有任何真實航空公司資料。真實的是機制,你可以端到端看它跑起來:veriprajna.com/zh-Hant/demos/airline-crew-scheduling-ai。
如果你寧願看它跑,而不是讀我描述,這裡就是整套端到端執行。
自從 CBC 打敗我以來,有個問題我一直轉個不停。當你競爭的求解器已經夠好,而買家已經擁有它時,剩下要打造的不是更好的答案,而是與答案更好的關係:更快、可證明合法、可見,並且謙卑到會升級。那麼,今年賣給你的 AI,有多少真的在解難題,又有多少只是在重解從未壞掉的那一塊?


