Prompt 不是真實來源

發表於

Prompt 不是真實來源。

LLM 生成程式碼的速度令人驚嘆;真正的問題是,生成出來的東西可不可信。當生成空間近乎無限——當同一個意圖可以用一萬種合法的方式表達——再謹慎的 prompt 也釘不住結果。信賴無法建立在更好的 prompt 之上,問題空間必須從根本上被縮小。

抽象化與領域特定語言(DSL)提供的正是這樣一種機制。DSL 不是輔助工具,而是一副結構性的韁繩:從一開始就把 LLM 約束在正確的方向上,並讓結果在事後可以被檢查。本文談的是 DSL 如何與 LLM 協同運作,以及為什麼在 LLM 時代,DSL 可能成為軟體系統真正的真實來源。

規格是假設,不是藍圖

建構大型系統涉及大量微小的設計決策。它們無法事先得知,也無法完全由一份高層次規格驅動。一份規格充其量只是一個起點假設:真正的限制、取捨與邊界案例,是在持續實作的過程中,以迭代的方式被發現的。第一份規格從來不是一份完成的藍圖,而是一個有待修正的猜測。

自然的回應是迭代:修正規格、生成程式碼、檢視結果、把學到的東西回饋到下一輪。這個循環是有效的——只要每一輪只產生小而可檢視的變更。

檢視不等於設計

這裡有一個關鍵區別:檢視程式碼並不等同於撰寫程式碼。 檢視生成的程式碼時,我們是逐段驗證是否符合意圖、尋找潛在陷阱。但檢視很少迫使我們去與設計決策搏鬥。

撰寫程式碼會。它迫使我們決定某個職責該歸屬何處、該暴露什麼邊界才能讓設計進一步擴展——而正是在做出這些決策的過程中,設計才最完整地展現自己。

程式碼有兩個截然不同卻相互交織的目的:它是給機器的指令,同時也是問題領域的概念模型。一個設計良好的程式碼庫,是某個領域詞彙的具體呈現——而那正是 LLM 之後被要求用來書寫的詞彙。我們選擇的語言與範式(paradigm)也會塑造我們獲得的設計洞察:函數式方法與物件導向方法會揭示設計的不同面向,以及各自自然衍生的慣用寫法(idiom)與模式。

LLM 的兩個角色

在這個框架下,LLM 扮演兩個截然不同的角色:

  • 共同設計者:當我們形塑設計及其詞彙時,LLM 是腦力激盪夥伴,幫助探索設計空間、發現正確的抽象化。
  • 自然語言介面:一旦詞彙確立,LLM 就成為通往該詞彙的絕佳入口,將自然語言請求轉換為精確的領域表達式。

這兩個角色對應到不同的工作階段,我們在後文回到這個分工。

韁繩、閘門,與領域的詞彙

一個有用的切入角度是領域驅動設計(DDD)。其核心洞見是:在程式碼中建立一個共享的領域概念模型——DDD 稱之為「共通語言」(ubiquitous language)——然後用這個模型來演化程式碼庫,同時賦予團隊一個可以思考與溝通的詞彙。DSL 就是建構在這個模型之上的受限語法:一種用來表達該領域概念與操作的語言,除此之外什麼都不表達。

而 DSL 與 LLM 搭配得極好。證據到處都是。PlantUML、Mermaid、Graphviz 是用於視覺建模的 DSL;SQL 是用於查詢資料庫的 DSL;Kubernetes YAML 是用於描述雲端基礎設施的 DSL。這些都不是通用程式語言,全都是刻意受限的,設計用來表達單一領域中一組狹窄的概念。LLM 特別擅長從自然語言描述生成這些語言的合法輸出。

為什麼?三個結構性的原因。

  1. 一組把變異剝除掉的詞彙。 像 Java 這樣的通用語言,提供了許多合法的方式來表達同樣的意圖。DSL 把這些變異剝除掉,留下的是一組狹窄的合法表達式——小到少數幾個 in-context example 就足以傳達怎麼用,也熟悉到一線模型(訓練期間已大量接觸過常見 DSL)不必從零開始。DSL 詞彙的品質標準很簡單:傳達它所需要的範例越少,生成就越可靠。
  2. 一道從不眨眼的閘門。 DSL 幾乎總是附帶一個確定性的驗證器:parser、JSON schema、型別檢查器或編譯器。對以自主「生成並檢查」循環運作的 agent 而言,這是一個決定性的優勢。Agent 生成一個候選結果、讓它通過驗證器、再根據錯誤訊息修復它,全程不需要人類介入。若是建構為內部 DSL,宿主編譯器會免費驗證:格式錯誤的生成結果會以編譯錯誤的形式回報,精確指向違法的位置,而不是等到執行時期才出現意外。
  3. 說領域語言的錯誤訊息。 DSL 的驗證錯誤是以領域詞彙表達的,而不是埋在生成程式碼深處的 stack trace。這讓 agent 的自我修正迴圈更加精確有效。錯誤訊息不是一堵 agent 得翻過去的牆,而是一個方向。

這裡需要誠實以對:這並非一體適用的解決方案。優勢只有在 DSL 維持夠小、夠受限,讓少數幾個 in-context example 就能傳達其用法時才成立。設計與維護一種語言及其語意模型,有真實的前期成本。報酬集中在那些結構良好、真正受限,並有驗證器作為後盾的 DSL 上。

語法底下:語意模型

在較複雜的領域中,僅有語法是不夠的。我們需要更豐富的語意模型(semantic model)來呈現領域中的概念,以及我們在程式碼庫中做出的設計決策。

語意模型本身就是一種脈絡。提示詞所提到的概念,在程式碼庫中都存在為具體的型別,所以 LLM 不是在發明執行緒模型或網路層——它是在一個固定、充分理解的基座上填補領域邏輯。執行緒、時序、網路傳遞不再是每個 prompt 中都要重新決定的開放問題。留給開發者的,只剩下真正的領域邏輯。

輕量版本

DSL 是這個頻譜的一端,而且並不容易建構。在著手打造自己的語言之前,值得注意:一組乾淨的抽象化本身就是同一個想法的輕量版本。一個函式庫中具名的型別與方法,本身就是一個可以讓模型紮根的詞彙。好的抽象化——而不只是 DSL——能與 LLM 良好搭配,正是因為它們限縮了可探索的狀態空間。

這是抽象化本身的價值,而不是語法的價值。

兩個角色底下的那個迴圈

從以上討論中浮現出一個清晰的模式:LLM 以兩種截然不同的方式發揮作用,對應到兩個不同的階段。

第一階段:設計抽象化。 在這裡,LLM 是腦力激盪夥伴,而不是程式碼生成器。構成語意模型的那些設計決策無法全部事先指定——我們是在實作的過程中發現限制、取捨與邊界案例。這個階段本質上是迭代的、由回饋驅動的:提出一個結構、拿真實案例試試看、看看哪裡變得彆扭、然後把學到的東西回饋到下一輪。LLM 加速了這個循環——它勾勒替代方案、批判一個設計、把一個想法從一種語言移植到另一種——但人類始終穩穩地坐在駕駛座上,因為這些正是需要被理解與擁有的決策。

第二階段:把 DSL 當成自然語言介面。 抽象化就定位之後,一段自然語言描述幾乎可以直接對應到已定義的詞彙上。LLM 之所以是可靠的生成器,正是因為抽象化同時提供了讓 prompt 紮根的脈絡,以及檢查結果的韁繩。

真實來源在哪裡

有一個日益成長的趨勢,是把 prompt 當作主要的真實來源。一個設計良好的 DSL 從根本上改變了這個動態。

DSL 的關鍵優勢之一是:生成的程式本身往往成為人類維護的成品。因為 DSL 密集、富有表現力,而且基本上沒有多餘的 boilerplate,它以一種在生成之後許久仍然可讀的形式,捕捉了解決方案的核心意圖。如果未來這個產出需要改變,不需要找回原始 prompt 並重新生成一切——DSL 本身就有足夠的脈絡讓 LLM 理解意圖並與之合作。

真正持久的資產不是 prompt,而是 DSL 與它的語意模型。

一項策略性投資

DSL 與 LLM 的協同關係,本質上是一種複雜度管理策略。LLM 的生成能力極強,但生成空間越大,信賴越難建立。透過限縮詞彙、提供確定性驗證,以及以領域語言表達錯誤,DSL 將 LLM 從一個難以預測的程式碼生成器,轉變為一個可預測、可驗證、可迭代的領域工具。

抽象化與 DSL 的建構本身就是一種設計活動——它需要迭代、需要回饋、需要人類的判斷。LLM 在這個過程中是加速器,而不是替代品。一旦 DSL 就定位,它的價值將超越單次生成:它成為系統的長期真實來源,以領域詞彙捕捉意圖,並為未來的演化提供穩固的基礎。

在 LLM 時代,建構良好的 DSL 不是奢侈品,而是一項策略性的技術投資。

回到研究與洞察