模組 02 · 第 2 課

給例子:少樣本提示

把使用者留言分成四類,不給例子答對 18 條,給 4 個例子答對 20 條。講清楚例子怎麼挑、放幾個、會帶來什麼副作用。

  • 約 30 分鐘
  • 難度:入門
  • 實測:2026-09-14 deepseek-flash

程式碼和執行結果保留原樣(簡體中文),與實際執行時完全一致。

有些要求很難用文字說清楚。"缺陷"和"使用問題"的邊界在哪?使用者問"cookie 在重定向之後丟了,是我用法不對嗎",算哪一類?你可以寫一大段定義,但往往不如直接給幾個例子來得快。

給模型看幾個"輸入 → 輸出"的例子,讓它照著做,這叫少樣本提示(few-shot prompting)。不給例子直接做,叫零樣本(zero-shot)。

實驗:給使用者留言分類

httpx 的維護者每天會收到各種留言。我們想讓模型把它們自動分成四類:缺陷、功能建議、使用問題、其他。我準備了 20 條留言,每條都人工標好了正確類別,比如:

TESTS = [
    ("用 AsyncClient 并发 100 个请求,程序直接卡死,CPU 占满", "缺陷"),
    ("能不能加一个像 requests 那样的 Session 重试适配器?", "功能建议"),
    ("怎么给单个请求设置不同的超时时间?", "使用问题"),
    ("你们的文档网站打不开了", "其他"),
    # ……一共 20 条,每类 5 条,完整列表见 code/02-prompting/few_shot.py
]

零樣本的提示詞只說明分類規則:

ZERO_SHOT = """把用户留言分成以下四类之一:缺陷、功能建议、使用问题、其他。
只输出类别名称。"""

少樣本的提示詞在後面加了 4 個例子,每類一個,而且這 4 條都不在測試集裡:

FEW_SHOT = ZERO_SHOT + """

例子:
留言:调用 client.close() 之后再发请求没有报错,而是静默返回了旧的响应
类别:缺陷

留言:想要一个参数,能在请求失败时自动打印完整的请求和响应
类别:功能建议

留言:base_url 和 url 拼接的规则是什么?结尾的斜杠有没有影响
类别:使用问题

留言:这个项目和 aiohttp 比哪个更好
类别:其他"""

每條留言用 留言:{text}\n类别: 的格式發給模型,溫度設為 0,關掉思考,然後和人工標註比對:

def classify(system, text):
    response = client.chat.completions.create(
        model=MODEL,
        messages=[{"role": "system", "content": system}, {"role": "user", "content": f"留言:{text}\n类别:"}],
        temperature=0,
        extra_body={"thinking": {"type": "disabled"}},
    )
    return response.choices[0].message.content.strip()

我執行的結果:

零样本:答对 18/20,格式不对 0 条 []
    怎么给单个请求设置不同的超时时间?  标注=使用问题  模型=功能建议
    你们的文档网站打不开了  标注=其他  模型=缺陷
少样本:答对 20/20,格式不对 0 条 []

零樣本錯在哪

看看零樣本錯的兩條。

"怎麼給單個請求設定不同的超時時間?"被分成了功能建議。模型大概理解成了"使用者想要一個給單個請求設超時的功能"。但這是 httpx 早就支援的用法,使用者只是不知道怎麼寫。

"你們的文件網站打不開了"被分成了缺陷。從字面看,網站打不開確實是個 bug,但我們的"缺陷"指的是 httpx 這個庫本身的問題,文件網站屬於"其他"。

兩個錯誤都不是模型笨,而是分類標準有模糊地帶,而我的規則裡沒寫清楚。給了例子之後,模型從"base_url 和 url 拼接的規則是什麼"這個例子裡看出了"問怎麼用 = 使用問題",從"這個專案和 aiohttp 比哪個更好"看出了"和庫本身無關的 = 其他"。例子替我把規則說清楚了。

當然,你也可以不用例子,而是把規則寫得更細:"問'怎麼做'的算使用問題,哪怕看起來像在要新功能"、"只有 httpx 庫本身的行為問題才算缺陷"。這也有效,而且更省詞元。實踐中兩者經常一起用:規則寫主幹,例子補細節。

例子怎麼挑

覆蓋每一種輸出。四個類別各給一個例子。如果只給了"缺陷"和"使用問題"的例子,模型就不太會輸出另外兩類。

挑邊界上的例子。最有價值的例子不是最典型的,而是最容易分錯的。"呼叫 close() 之後再發請求沒有報錯"就是個好例子:它聽起來像是在問用法,實際上是庫的行為有問題。

和測試集分開。例子不能出現在測試集裡,否則等於把答案告訴了模型,準確率會虛高。這個實驗裡的 4 個例子是我另外寫的。

格式和真實輸入一致。例子用的是"留言:……類別:……"的格式,真實請求也用同樣的格式。模型會嚴格模仿例子的格式,所以例子的格式就是你想要的輸出格式。這個實驗裡零樣本和少樣本都沒有出現格式錯誤,但當你要求更復雜的輸出(比如 JSON)時,一個格式正確的例子能大大減少格式問題。

放幾個

一般 3~5 個就夠了。例子越多,每次請求的輸入越長,花錢越多。好在例子是固定的,放在 system 訊息裡能命中快取。

如果效果還不夠好,先想想是不是例子挑得不好,而不是急著加數量。10 個相似的例子,不如 3 個覆蓋不同邊界情況的例子。

例子的副作用

例子很有效,所以也很容易帶偏模型。

模型會模仿例子的一切。例子裡的回答都很短,模型的回答也會變短;例子都是中文,遇到英文留言它可能還是用中文分類名(這個倒是我們想要的)。你沒打算讓它模仿的特徵,它也會模仿。

模型可能照抄例子的內容。在生成類的任務裡(比如寫產品描述),如果例子寫的都是咖啡,模型寫茶葉的描述時也可能冒出咖啡的詞。所以例子要多樣化,在內容上彼此不同。

順序和比例有影響。如果 5 個例子裡有 4 個是"缺陷",模型會傾向於多分成"缺陷"。各類別的例子數量儘量平衡。

常見問題

例子放在 system 訊息裡,還是做成多輪對話? 兩種都可以。上面的做法是把例子寫進 system 訊息。另一種寫法是把每個例子做成一對 user 和 assistant 訊息,放在真正的問題前面,好像模型之前已經這樣回答過幾次。後一種對模仿格式特別有效,但訊息列表會變長,也更難維護。我一般先用 system 訊息的寫法,格式出問題了再換。

分類的類別很多,比如 30 個,每類一個例子太長了怎麼辦? 先考慮能不能把類別分成兩層,先分大類再分小類。或者只給容易混淆的那幾類配例子。更進一步的做法是:先用第 01 模組第 5 課的向量嵌入,從一個大的例子庫裡找出和當前輸入最相似的幾個例子,只把它們放進提示詞。這叫動態少樣本,原理和 04 模組的 RAG 一樣。

這個實驗說明不了什麼

20 條測試資料太少了。18 條和 20 條的差距,換一批資料可能就變成 19 和 20,甚至反過來。這個實驗能說明的是"少樣本在這類任務上有幫助"的一個具體例子,不能說明"少樣本能把準確率提高 10%"。要可靠地比較兩個提示詞,需要更多的測試資料,這是第 5 課的內容。

練習

  1. 把少樣本提示裡的例子減到 2 個(只留"缺陷"和"使用問題"),再執行,看看"其他"和"功能建議"兩類的準確率怎麼變。
  2. 故意放 4 個全是"缺陷"的例子,看模型是不是更傾向於分成"缺陷"。
  3. 自己再寫 10 條留言,儘量寫那些連你都要想一想才能分對的,加到測試集裡重新跑。零樣本和少樣本的差距變大了還是變小了?
  4. 不用例子,改為在零樣本的規則裡補充兩條說明("問怎麼用的,哪怕看起來像要新功能,也算使用問題"、"文件、社群相關的算其他"),看看能不能達到和少樣本一樣的效果。

自測

1. 為什麼少樣本提示裡的例子不能出現在測試集裡?

例子等於告訴了模型這些輸入的正確答案。如果測試集裡有同樣的輸入,模型只要照抄就能答對,測出來的準確率會虛高,不能反映它在新資料上的真實表現。

2. 你給了模型 5 個例子,其中 4 個是"缺陷"。可能會有什麼問題?

模型會傾向於把更多留言分成"缺陷",因為例子的比例暗示了"大多數留言都是缺陷"。例子裡各個類別的數量應該儘量平衡,每一種輸出都至少覆蓋到。

3. 挑例子時,應該挑最典型的,還是最容易分錯的?

優先挑最容易分錯的、處在邊界上的例子。典型的例子模型本來就能分對,邊界上的例子才能幫它弄清楚模糊地帶的規則。

提問與討論

這一課沒看懂的地方,在這裡問。看到別人的問題,也歡迎你來回答。

提問 +3 點,回答別人 +6 點。內容經審核後公開。

正在載入討論…