昨天收尾時,我們補上了身分鏈的最後一段——Portal 自己去敲下游引擎的門時,怎麼用一張短命的 OIDC(OpenID Connect)token(權杖)證明「來的真是我」。那段的結尾我順手埋了個鉤子:那套 s2s 認證之所以能「換一種認證方式只改一行設定,主流程不動」,靠的是同一套抽換哲學。今天就是兌現那句話的日子。從這篇開始進入第四章,主題從「身分」整個換成「供應商」。我想先不講任何底層原理,純粹站在使用者(也就是維運和開發這套平台的人)的角度,給你看那個招牌甜頭長什麼樣:把一家 LLM 換成另一家、把線上守門(guardrail)換成離線規則,你動的 Java 程式碼是零行。
本篇結構:
先說清楚這篇要解決的處境。「0 行程式碼不動」背後有前提、也有代價,得先把處境攤開,這句話才立得住。
Portal 這座平台,骨子裡幾乎不自己產生答案——答案出自後端引擎,知識出自知識庫。它的價值在於把外面一堆供應商接進來,再統一管好。光是 LLM 這一個插槽(plugin 掛載點),它就要同時容納好幾家;守門、地圖也各有數種實作。把這些零件按插槽攤開看,其實長這樣:
| 插槽 | 可選實作 | 說明 |
|---|---|---|
| LLM | mock、openai-http、gemini-http、spring-ai |
離線 demo 用 mock;直連 REST 的 openai-http、gemini-http;再加上走 spring-ai 那套框架的實作 |
| 守門(guardrail) | mock、regex、openai-moderation |
什麼都不做的 mock、純本地跑的 regex、呼叫線上分類服務的 openai-moderation |
| 地圖(maps) | mock、google |
地圖查詢兩種 |
這些零件來源不同、節奏不同,而麻煩的是它們會被頻繁換來換去。為什麼會頻繁?因為場景太多了:
mock 把整套服務空跑起來。如果每換一次、每加一家供應商,都得回去改一輪業務 Java——找到呼叫 LLM 的地方、把這家的 client 換成那家的、重新編譯、重新部署——那這個「可抽換」就是假的。換供應商會變成一件需要工程師排期、要走 code review、會動到主流程因而有風險的事。真正的目標是:讓換供應商退化成一件維運動作,跟改個逾時數值一樣輕,最好連重新部署都省掉。
先看最常用的那一招——宣告式切換。哪個插槽用哪一家,全寫在設定檔裡,一個插槽一行:
portal:
llm:
provider: gemini-http # mock | openai-http | gemini-http | spring-ai
guardrail:
provider: regex # mock | regex | openai-moderation
maps:
provider: google # mock | google
audit:
provider: slf4j # slf4j | postgres
想把對話模型從一家換成另一家?把第三行的 gemini-http 改成 openai-http,重啟服務,完事。想在本機離線跑整套 demo、一把金鑰都不帶?把這幾個插槽全填 mock:
portal:
llm:
provider: mock # 不打任何外部 API,回罐頭答案
guardrail:
provider: mock
maps:
provider: mock
這整段改動裡,沒有任何一個 Java 檔案被碰過。業務碼——也就是真正在編排「先查知識庫、再查地圖、組裝上下文、呼叫模型」那條主流程的程式——它從頭到尾不認得 gemini-http 或 openai-http 這些名字。它只跟一個抽象的「能對話的 LLM」打交道,至於這次實際接上的是哪一家,是設定檔說了算,不是程式寫死的。我們在 Day 4 講技術選型全景時提過「宣告式抽換」是 Portal 三個核心選型之一,這就是它最直觀的樣子:把「用誰」這個決定,從編譯期的程式碼,挪到了部署期的設定檔。
改 YAML 雖然輕,但它畢竟是「整個服務都換」——一改,所有人接下來都走新那家。有時候你要的是更細的粒度:只想對「這一個請求」臨時換家供應商,其他流量原封不動。
這在兩個場景特別好用:
這時候不改設定,改成在請求上帶一個 header:
POST /chat
X-LLM-Provider: openai-http
X-Guardrail-Provider: openai-moderation
Content-Type: application/json
{"message":"我的員編是 123456,AD 帳號被鎖了,順便想查內湖分行的營業時間。","history":[]}
這一筆請求就會臨時改用 openai-http 這家 LLM、openai-moderation 這套守門來處理,跑完就結束,什麼都不留下,不影響任何其他流量,也不必重啟、不必改設定檔。沒帶這個 header 的請求,照樣走 YAML 裡設定的那家。換句話說,header 的優先序蓋過設定檔,但作用範圍只限這一筆。
要注意一件事:不是每個插槽都支援這種逐請求覆寫。這裡有一條界線:
| 可逐請求覆寫 | 開機定下就不動 | |
|---|---|---|
| 哪些插槽 | LLM、地圖、入境守門 | 登入方式、稽核寫到哪、s2s 認證怎麼做 |
| 為什麼 | 換了頂多輸出不同,不動搖全場規則 | 身分與稽核的規則本來就該全場一致 |
左欄再多說兩句。LLM 換了頂多答案風格不同;地圖機制上一樣可換,只是 PoC 尚未開出專屬的 header;入境守門雖然也開放逐筆換,但它能被換成 mock(等於把守門關掉),所以是「可換、卻必須控管誰能換」的敏感插槽。右欄則相反:身分驗證的規則在一次部署裡本來就該全場一致,能靠 header 逐筆改掉,反而是開了一個漏洞。
哪些插槽走「可逐筆換」、哪些走「開機定下就不動」,這個判準和它背後的機制,我們留到 Day 14 專門講逐請求覆寫時再展開,今天先記得有這條界線就好。
到這裡你大概會問:程式碼一行不動,光改個設定字串,憑什麼就換掉了一整家供應商的實作?這不是魔法,我用一個會貫穿第四章的比喻,先把直覺建起來——插座、電器、配線。
把 Portal 想成一面牆,牆上有好幾種規格的插座:一個 LLM 插座、一個守門插座、一個地圖插座。三者的對應關係是這樣:
| 比喻 | 軟體裡對應 | 它管的事 |
|---|---|---|
| 插座規格 | 介面 | 規定形狀——「插上我的東西,必須會做這幾件事」 |
| 電器 | 各家供應商的具體實作 | 內部怎麼做都行,只要插頭符合插座規格 |
| 配線 | 依賴注入(DI)容器 | 決定「這個插座此刻通到哪台電器」 |
插座規格就是「介面」。 它規定的是形狀——「插上我的東西,必須會做這幾件事」,比如 LLM 插座要求「你得能接收一段對話、吐回一段回覆」。它不在乎你內部是燒油還是用電,只要你的插頭符合規格、會做那幾件事,就插得上。
各家供應商的具體實作,就是電器。 openai-http 是一台電器,gemini-http 是另一台,離線用的 mock 是一台不耗電的展示樣品機。它們內部天差地別——一個打這家的 REST,一個打那家的,mock 根本不連網直接回罐頭答案——但因為都做成符合 LLM 插座規格的插頭,所以對牆來說,它們是可以互換的同一類東西。換電器,牆完全不必動。
那「改一行設定就換好」這件事,對應的就是配線——牆背後決定「這個插座此刻通到哪台電器」的那套線路。在軟體裡,扮演配線角色的是依賴注入(DI)容器:它在系統啟動時,把現場所有電器都認過一遍、記在一張名冊上,執行時你說一聲「LLM 這個插座給我接 gemini-http」,它就把線接到那台電器上。你改 YAML 或帶那個 header,本質上就是在跟配線說「改接另一台」,而牆(介面)和其他電器(其他實作)全程不動。
這就是為什麼換廠商能做到 0 行 Java 不動:業務碼只對著插座規格寫,從不直接抓某一台電器。 它說「給我牆上的 LLM 插座」,而不是說「給我那台特定牌子的機器」。誰被接到插座上,是配線的事,不是業務碼的事。延伸一下昨天 Day 11 的甜頭:那篇結尾說的「換零件可攜」跟今天是同一回事——取 OIDC token 的方式也是一台電器,認證介面是插座,換雲平台等於換一台電器插上同一個插座,主流程一樣不動。
把使用者視角的甜頭講完了,得誠實補上代價,免得讓人以為這是一頓白吃的午餐。
首先,「插座規格」——也就是那些介面——必須設計得夠穩、夠通用。它得抽象到能同時容下行為差很多的好幾台電器:mock 不連網,openai-http 打一家 REST,spring-ai 又走另一套框架,這個介面得是它們的最大公約數。一旦這個公約數沒抓好,哪天得回頭去動介面的形狀,那麻煩就大了——所有插上來的電器都得跟著改插頭,「0 行不動」當場破功。所以這套東西的彈性不是天上掉下來的,是用「介面設計的克制」換來的:加新電器很自由,動既有插座規格卻得非常謹慎。 這條穩定邊界怎麼守,是 Day 13 的核心。
其次,要誠實標清楚 PoC(概念驗證)階段的幾個邊界:
/chat 上任何呼叫端都能帶這個 header,沒有任何權限把關,正式版必須補上(Day 14 會把這個缺口完整攤開)。這些帳先記著,後面幾天會陸續還。
但即便帶著這些妥協,這筆交易對一座定位是「對外閘道、要長期容納多家供應商」的平台來說,依然非常划算:你用「前期把介面想清楚」這一次性的設計成本,換來了之後每一次換供應商都退化成維運動作、不必動主流程、不必走 code review 的長期紅利。對一個供應商會來來去去、會漲價會出包的真實環境,這個紅利複利滾起來,值得。
明天 Day 13,我們就把今天這層「使用者看到的甜頭」掀開,鑽進插件框架的引擎室——看那面牆、那些插座、那本配線名冊,到底是用哪三樣小到出乎意料的東西拼出來的,以及它怎麼做到加一家供應商真的就只是「新增一個檔案」。