接 LLM API 也需要防腐層

大型語言模型供應商的 API 形狀、參數與計價方式經常變動,若應用程式的領域層直接依賴這些外部規格,將導致模型詞彙滲透並扭曲核心設計。本文從限界上下文與上下文映射的角度,說明為何應將模型供應商視為外部限界上下文,並透過防腐層吸收回應格式、參數與錯誤差異,讓領域層維持純淨,最後給出可直接落實的架構建議。

🔖標籤 #ddd #ddd思考 #ai

[!AI] 本文摘要
大型語言模型供應商的 API 形狀、參數與計價方式經常變動,若應用程式的領域層直接依賴這些外部規格,將導致模型詞彙滲透並扭曲核心設計。本文從限界上下文與上下文映射的角度,說明為何應將模型供應商視為外部限界上下文,並透過防腐層吸收回應格式、參數與錯誤差異,讓領域層維持純淨,最後給出可直接落實的架構建議。


大型語言模型(LLM)供應商的 API 規格變動頻繁,回應格式、生成參數、配額限制與計價模式都可能隨著模型版本更新而改變。開發團隊在整合這些外部服務時,若直接將供應商提供的軟體開發套件(SDK)型別貫穿整個應用程式,會讓模型專屬的詞彙與互動模式滲透到核心業務邏輯中。這種做法短期內看來開發速度很快,但隨著功能增加,更換供應商或調整呼叫方式將演變成大規模的程式碼修改工程。

在領域驅動設計(DDD)的觀點中,這是一個典型的限界上下文邊界管理問題。模型供應商擁有自己的通用語言與運作語意,應用程式則有屬於業務領域的通用語言,兩者不該直接混合。我們需要在應用程式與外部模型之間建立防腐層,負責處理所有翻譯與轉換工作,確保外部系統的變更不會污染內部領域模型的設計。

模型供應商作為外部限界上下文

限界上下文定義了特定模型適用的明確邊界。模型供應商的 API 本身就是一個獨立的限界上下文,它內部有訊息、角色、生成溫度、權杖限制與完成原因等概念。這些概念對供應商的基礎設施是合理的,但對應用程式的業務領域來說,往往是無關的實作細節。

Patrick Kearns 在 Designing an Anti-Corruption Layer Around an LLM in .NET 一文中指出,當供應商的型別開始在應用程式中流動時,聊天訊息會出現在應用服務裡,模型名稱會進入業務功能的設定物件,權杖限制會影響領域工作流程。這會讓更換 AI 供應商或改變模型呼叫方式,需要修改許多原本不該知道這些細節的程式碼。

Microsoft Learn 在 Anti-Corruption Layer pattern 中說明,當應用程式依賴開發團隊無法控制的外部系統時,會迫使新系統遵守外部系統的 API 或語意,進而腐蝕原本乾淨的現代應用程式設計。將模型供應商視為外部限界上下文,並透過上下文映射中的防腐層模式來隔離,是解決此問題的標準做法。

防腐層要吸收哪些差異

防腐層的核心職責是翻譯。Nawaz Dhandala 在 How to Build the Anti-Corruption Layer Pattern 一文中說明,防腐層通常包含三個元件:轉譯器負責領域模型轉換、外觀提供簡化介面、轉接器處理通訊協定層級的顧慮。在 LLM 整合場景中,防腐層需要吸收以下幾類差異。

  • 回應格式轉換:將供應商的選項、內容區塊、完成原因等結構,轉換為應用程式領域語言中的值物件或實體。
  • 參數與設定的封裝:將提示詞組裝、模型識別碼、生成溫度等基礎設施參數隔離在轉接器內部,不讓它們外露到領域層。
  • 錯誤與失敗模式的映射:將供應商特有的配額超限或內容審核失敗,映射為應用程式可處理的領域事件或例外狀況。

防腐層不僅是轉發請求,還要驗證回傳結構、處理重試邏輯,並將接受的回應轉換為應用程式型別。Patrick Kearns 在文中強調,這個邊界有明確的責任:應用程式提供業務請求並接收業務回應,防腐層擁有合約與供應商之間所需的所有翻譯。

給應用程式自己的語言

應用程式應該要求它需要的能力,而不是描述取得該能力的機制。Patrick Kearns 以從保險提交文件中提取續保資訊為例,說明應用程式合約不該出現訊息或權杖等概念,而是定義一個以業務語言表達的介面。

以下程式碼範例取自 Patrick Kearns 的文章,展示應用程式合約應如何以領域語言定義:

public interface IRenewalSubmissionInterpreter
{
    Task<RenewalInterpretation> InterpretAsync(
        RenewalSubmission submission,
        CancellationToken stopToken);
}

public sealed record RenewalInterpretation(
    string InsuredName,
    DateOnly? InceptionDate,
    Money? ExpiringPremium,
    IReadOnlyList<InterpretationWarning> Warnings);

這些型別描述了業務操作,沒有提及訊息、權杖、溫度或完成原因。呼叫端可以使用這個能力,而不需要知道底層是使用某家模型、多個模型、確定性解析或人工審查佇列。即使供應商從未更換,這種隔離仍有價值,因為模型家族會演進,結構化輸出機制會改變,不同使用情境也適合不同的路由方式。

Stack Exchange 上關於 What is an Anti-Corruption layer, and how is it used? 的討論中,也用程式碼示範了防腐層的核心精神:確保主程式碼不直接參照雜亂的外部類別,而是透過一個專門的層級存取它,並以更合理的方式暴露給主程式碼。這個層級內部仍有與外部系統互動的複雜性,但已被隱藏,使主程式碼保持乾淨。

落實防腐層的考量

在建立防腐層時,需要權衡幾個實務層面的問題。Microsoft Learn 指出,防腐層會增加呼叫之間的延遲,也會增加一個必須管理與維護的額外服務。團隊需要考慮防腐層的擴展計畫,以及是否需要多個防腐層來處理不同的外部服務。

在 LLM 整合中,防腐層還需要處理輸入驗證與清理。Microsoft Learn 提到,因為防腐層居中協調可能具有不同信任層級的系統,應考慮在此邊界強制執行輸入驗證與清理。對於 LLM 應用來說,這意味著防腐層應在此邊界強制執行輸入驗證與清理。

此外,防腐層應規劃可觀測性,包括關聯識別碼與結構化日誌,以便診斷翻譯失敗。當翻譯失敗時,防腐層必須記錄足夠的資訊,讓開發團隊能追蹤問題,而不會將這些技術細節外露到領域層。

重點

  1. 將 LLM 供應商視為外部限界上下文,其 API 型別與概念不應直接出現在應用程式的領域層或應用服務中。
  2. 防腐層應吸收回應格式、生成參數、錯誤模式與配額限制等差異,並將供應商特有的詞彙翻譯為應用程式的通用語言。
  3. 應用程式合約應以業務能力表達,而非描述模型呼叫的機制,讓領域層維持穩定,不受基礎設施變動影響。
  4. 落實防腐層時需權衡延遲與維護成本,並規劃輸入驗證、清理機制與可觀測性,確保跨邊界通訊的可追蹤性與安全性。