DSPy 教學:為什麼 Signature 比直接寫 Prompt 更容易做自動優化

主題: Agent 工程

發布日期:

最後修改:

DSPy Signature vs Prompt

最近在做專案時遇到一個很值得單獨拿出來講的東西:DSPy。

在搭 Canviz 的內容生成 pipeline 時,我反覆遇到一個工程問題:題目講解品質和畫板腳本可用性這兩個目標,很難只靠 prompt 同時維持穩定。一旦換模型,或加入新年級,就得把那串字重新調一遍。DSPy 給了我一個更系統化的做法。

Prompt 工程的根本矛盾

在談 DSPy 之前,先把一件事說清楚:為什麼手寫 prompt 是工程問題,而不只是技巧問題?

傳統 prompt 有一個致命缺陷:它把「我要做什麼」和「我要怎麼告訴模型做」混在一起了。

那段自然語言提示詞,同時承擔了兩件事:

  1. 描述任務邏輯,也就是接受什麼輸入、輸出什麼結果。
  2. 扮演針對目前模型調出來的「咒語」。

以數學教學場景為例,「講解一道雞兔同籠題」這個任務的邏輯本身是穩定的;但要讓 GPT 講好,和讓 Claude Sonnet 講好,需要的那段咒語可能完全不同。一旦換模型,或年級從三年級變成五年級,這段咒語就可能失效。更麻煩的是,通常沒有系統性的修復方法,只能憑感覺試錯。

這其實就是軟體工程裡的硬編碼問題。一般程式裡,我們早就知道不該把核心邏輯寫死;但在 AI pipeline 裡,核心邏輯卻常常被鎖進一個脆弱字串裡。

DSPy 的作者、史丹佛的 Omar Khattab 對這件事有一個很準確的描述:

現有的 LM pipeline 通常用硬編碼的 prompt 模板實作,這些字串靠試錯找到,異常脆弱。

DSPy 是什麼?它的核心洞察是什麼?

DSPy(Declarative Self-improving Python) 是史丹佛 NLP 實驗室在 2023 年開源、並發表於 ICLR 2024 的框架。它的核心主張是:

Programming language models, not prompting them.

對 AI 編程,而不是對 AI 下提示。

它提供的解法很優雅:把任務介面描述與具體 prompt 實作徹底分開。

你只需要告訴 DSPy:

  • 這一步輸入什麼、輸出什麼。
  • 整個 pipeline 的邏輯結構是什麼。
  • 你的評估標準是什麼。

接著由 DSPy 的編譯器(Compiler)和最佳化器(Optimizer)去替你搜尋更適合當前模型、資料與指標的 prompt 實現。

如果借用官方類比,這有點像從組合語言跳到高階語言,或者從手寫 SQL 跳到 ORM。

三個核心概念,看懂 DSPy 的全貌

1. Signature:任務的型別簽名

Signature 是 DSPy 的介面描述。你用類似型別宣告的方式告訴框架這一步要做什麼,而不是怎麼做:

import dspy

class ExplainMathProblem(dspy.Signature):
    """為指定年級的學生講解一道數學題,語言要符合該年級的認知水平。"""
    problem: str = dspy.InputField(desc="數學題目原文")
    grade: int = dspy.InputField(desc="學生年級,如 3 表示三年級")
    explanation: str = dspy.OutputField(desc="適合該年級的分步驟講解,語言親切易懂")
    key_concept: str = dspy.OutputField(desc="這道題考察的核心知識點,一句話說清楚")

這裡完全沒有手寫 prompt。只有介面語義,沒有「你是一位溫柔耐心的數學老師」這種模型導向的文字。

2. Module:可組合的功能模組

Module 是 DSPy 的執行單元,靈感來自 PyTorch 的 nn.Module。你可以像搭積木一樣,把多個步驟組成完整 pipeline:

class MathLessonPipeline(dspy.Module):
    def __init__(self):
        # 第一步:講解題目
        self.explain = dspy.ChainOfThought(ExplainMathProblem)
        # 第二步:根據講解生成對應的大角幾何可視化腳本
        self.generate_diagram = dspy.Predict(
            "problem, explanation -> dinogsp_script: str"
        )
        # 第三步:出一道同類型的練習題
        self.make_exercise = dspy.Predict(
            "problem, key_concept, grade -> exercise: str, answer: str"
        )

    def forward(self, problem, grade):
        # 講解
        step1 = self.explain(problem=problem, grade=grade)
        # 生成圖形
        step2 = self.generate_diagram(
            problem=problem,
            explanation=step1.explanation
        )
        # 出練習題
        step3 = self.make_exercise(
            problem=problem,
            key_concept=step1.key_concept,
            grade=grade
        )
        return dspy.Prediction(
            explanation=step1.explanation,
            dinogsp_script=step2.dinogsp_script,
            exercise=step3.exercise,
            answer=step3.answer
        )

整條三步 pipeline 沒有手寫任何 prompt,寫的全部都是邏輯結構。

DSPy 內建幾個常見的推理策略模組:

模組對應方式在教學中的應用
dspy.Predict直接預測題目難度分級、知識點標註
dspy.ChainOfThought鏈式思維分步驟解題講解
dspy.ReAct思考-行動循環呼叫外部工具驗證腳本
dspy.ProgramOfThought程式化思維生成可執行的數學計算程式碼

3. Optimizer:自動調優引擎

這是 DSPy 最有辨識度的部分。

你只需要提供:

  • 一個評估資料集,例如 100 道題和對應的人工作答樣本。
  • 一個評估指標函式,用來判斷輸出是否足夠好。

之後呼叫最佳化器,它會自動搜尋更好的 prompt 指令與 few-shot 範例組合:

# 定義評估指標:講解是否適齡、圖形腳本是否能被解析
def lesson_quality_metric(example, prediction, trace=None):
    explanation_ok = len(prediction.explanation) > 50  # 基本長度
    script_parseable = validate_dinogsp(prediction.dinogsp_script)  # 腳本可用
    grade_appropriate = check_vocabulary_level(
        prediction.explanation, example.grade
    )  # 用詞適齡
    return explanation_ok and script_parseable and grade_appropriate

# 用 MIPROv2 進行優化
optimizer = dspy.MIPROv2(metric=lesson_quality_metric, auto="medium")
optimized_pipeline = optimizer.compile(
    MathLessonPipeline(),
    trainset=annotated_lessons
)

# 保存結果,生產環境直接載入,不需要重新優化
optimized_pipeline.save("./optimized_math_lesson.json")

一次 medium 級優化會有一定時間和成本,但換來的是一套針對指定模型、指定資料、指定指標自動調整過的內容生成系統。

看看資料

DSPy 官方有一組很讓人印象深刻的數據。

在 HotPotQA 多跳推理任務上,用 dspy.ReAct 搭配 gpt mini 系列:

  • 優化前約 24% 準確率。
  • 用 MIPROv2 在 500 個樣本上優化後,提升到約 51%。

重點不是換了更貴的模型,而是讓同一個模型更會做這類任務。

它和 LangChain / LlamaIndex 的本質差異

很多人會問:如果已經在用 LangChain,還需要 DSPy 嗎?

LangChain / LlamaIndex 更像工具鏈編排框架。它們擅長把 LLM、向量資料庫、工具呼叫等元件串起來,但 prompt 本身往往還是人手寫的字串。換模型時,很多時候還是得自己回去改 prompt。

DSPy 更像 AI 程式編譯框架。它不只負責串接元件,也嘗試接管 prompt 的生成與優化。開發者主要寫邏輯,而 DSPy 負責把這些邏輯翻譯成對特定模型更有效的自然語言指令。

放到數學教學場景裡會更直觀:

  • 如果你用 LangChain 做了一套「三年級講解」pipeline,明天要支持五年級,通常還是得回頭手改 prompt。
  • 如果你用 DSPy,更多時候改的是輸入條件和訓練集,然後重新 compile,讓框架替新的目標搜尋更合適的提示策略。

如果一定要打比方,LangChain 比較像自動化流水線,DSPy 比較像帶編譯器的高階語言。

我的開發者視角:它解決了什麼,還差什麼

DSPy 真正解決的問題

  • 模型遷移的痛苦:從 GPT-5.4 換到更便宜的模型時,不需要手改所有 prompt,可以重新 compile。
  • 多步驟聯合優化:講解品質和圖形腳本可用性這兩個目標,以前很難一起調,DSPy 可以把它們放進同一個優化過程。
  • 實驗可重現:優化結果可以存成 JSON,方便團隊共享與版本管理。

目前的侷限

  • 評估指標最難寫:像 validate_dinogsp() 這樣的函式必須自己定義,而且要寫得夠嚴格,否則優化器很容易鑽漏洞。
  • 優化不是免費的:資料量、模型單價與優化輪數疊加後,成本會明顯上升。
  • 除錯體驗仍在成熟中:如果優化後表現仍不理想,通常很難立刻判斷問題是資料、指標,還是模型能力邊界。

什麼時候應該用 DSPy?

適合的場景

  • 你在做多步驟的 LLM pipeline。
  • 你需要在不同模型之間切換或遷移。
  • 你有評估資料集,而且效果目標可以量化。
  • 你受夠了靠感覺手調 prompt。
  • 你需要長期維護一個生產級 AI 系統。

不太適合的場景

  • 只是快速驗證一個 idea。
  • 任務沒有清楚指標,沒辦法給最佳化器穩定回饋。

寫在最後

我覺得 DSPy 最值得重視的,不只是它能自動調 prompt,而是它提出了一種更可靠的工程思維:

AI pipeline 裡的 prompt,本質上更像程式參數,而不是程式原始碼。

就像我們不會把神經網路權重硬編碼進原始檔一樣,也不應該把某個模型剛好調順的 prompt 當成穩定邏輯本身。這些 prompt 更適合作為可學習、可優化、可保存、可遷移的產物。

教學內容的邏輯是穩定的,例如分步驟、有圖示、適齡表達;但如何引導模型做到這些,會隨著模型更新、年級擴張、題型增加而持續變動。DSPy 把兩者拆開,才讓 AI 教學系統真正具備可維護性。

如果你也在做 AI 教育、內容生成或多步驟 Agent pipeline,這個思路值得認真看一眼。

參考資料