上一篇在黑盒子裡找確定性,靠的是把話講清楚:情境、格式、不要什麼,全部寫下來。但有些東西很難用話講清楚,例如「我們團隊的 changelog 怎麼分」。這時候最直覺的做法是不講了,直接給它看幾個例子。
先把這一篇會用到的四個詞講清楚:
同一個問題,零樣本和少樣本的差別長這樣:
零樣本:只有指令。
這則 commit 應該放在 changelog 的哪個區塊?新功能、修正,還是內部?
docs: README 補上本機開發步驟
少樣本:指令一樣,但前面先示範幾組。
這則 commit 應該放在 changelog 的哪個區塊?新功能、修正,還是內部?
feat: 支援用 Google 帳號登入 → 新功能
fix: 修正分頁在最後一頁時會重複顯示 → 修正
refactor: 把訂單驗證拆成獨立模組 → 內部
docs: README 補上本機開發步驟 →
少樣本沒有多講任何一條規則,它只是讓模型先看到「前面這樣問、就這樣答」。
上一篇預告過,範例比形容詞有效,但理由不是你想的那個。這一篇我用 Claude 跑了 24 次零樣本與少樣本的對照,把理由找出來。
任務是工程師天天在做的事:把 commit 訊息分到 changelog 的三個區塊,新功能、修正、內部。
先用形容詞問一次,這是零樣本:
把下面每一則 commit 訊息,分到我們團隊 changelog 的三個區塊之一:新功能、修正、內部。請照我們團隊的慣例分類,要精準、一致。
請分類下面這幾則,只輸出「編號. 區塊」,不要解釋:
1. feat: 匯出報表支援 CSV
2. fix: 時區轉換在夏令時間會少算一小時
3. chore(deps): 升級 axios 到最新的修補版本(修補安全性漏洞)
4. chore(deps): 升級 prettier 2 → 3
5. docs: README 補上本機開發步驟
6. perf: 搜尋結果改成串流回傳,第一筆結果出現的時間從 2 秒降到 0.4 秒
7. refactor: 把訂單驗證拆成獨立模組
8. perf: 單元測試改成平行執行,CI 時間減半
再開一個新對話,這次不講慣例,改成在前面放六個範例,這是少樣本:
把下面每一則 commit 訊息,分到我們團隊 changelog 的三個區塊之一:新功能、修正、內部。
<examples>
<example>
commit:feat: 支援用 Google 帳號登入
區塊:新功能
</example>
<example>
commit:fix: 修正分頁在最後一頁時會重複顯示
區塊:修正
</example>
<example>
commit:chore(deps): 升級 lodash 的修補版本(修補安全性漏洞)
區塊:修正
</example>
<example>
commit:chore(deps): 升級 eslint 8 → 9
區塊:內部
</example>
<example>
commit:docs: 補上 API 分頁參數的說明
區塊:新功能
</example>
<example>
commit:perf: 首頁查詢改用索引,載入時間從 1.2 秒降到 0.3 秒
區塊:修正
</example>
</examples>
(接著貼上同樣的「請分類下面這幾則」和那 8 則)
注意範例裡藏了兩條不太符合常識的團隊慣例:文件(docs)一律算「新功能」;效能(perf)一律算「修正」。形容詞版本完全沒提到這兩條,範例版本只用例子暗示,沒有明講。
照這個慣例,正確答案是:新功能、修正、修正、內部、新功能、修正、內部、修正。
每一組都是全新的工作階段、各跑 3 次,全部列出來,沒有挑。模型是 Claude Opus 5,在 Claude Code 裡跑。分數是 8 題答對幾題。
上面那兩個提示詞,其實是第二輪用的。在那之前,我先跑了一輪沒有藏任何怪規則的版本,當作對照。
第一輪:先用一般工程團隊都會同意的分法(文件算內部、使用者感受得到的加速算新功能)
| 提示詞 | 3 次的分數 |
|---|---|
| 零樣本(只有形容詞) | 8、8、8 |
| 少樣本(六個正確的範例) | 8、8、8 |
| 同樣六個範例,標籤全部故意標錯 | 8、8、8 |
| 同樣六個正確範例,換個順序 | 8、8、8 |
第一輪的四個提示詞長這樣。它們的結尾都一樣,是上面那句「請分類下面這幾則,只輸出『編號. 區塊』,不要解釋」加上同樣 8 則 commit;差別只在開頭。
零樣本只有一段說明,沒有任何範例:
把下面每一則 commit 訊息,分到 changelog 的三個區塊之一:新功能、修正、內部。分類要精準、一致,符合一般工程團隊的慣例。
另外三個少樣本版本,開頭都是「把下面每一則 commit 訊息,分到 changelog 的三個區塊之一:新功能、修正、內部。」,接著是用 <example> 包起來的六個範例,格式跟上面那個範例版本一樣。三個版本只差在標籤與順序:
| 範例 commit | 正確的範例 | 標籤故意標錯 | 換順序後的位置 |
|---|---|---|---|
| feat: 支援用 Google 帳號登入 | 新功能 | 修正 | 第 5 個 |
| fix: 修正分頁在最後一頁時會重複顯示 | 修正 | 內部 | 第 3 個 |
| chore(deps): 升級 lodash 的修補版本(修補安全性漏洞) | 修正 | 新功能 | 第 4 個 |
| chore(deps): 升級 eslint 8 → 9 | 內部 | 新功能 | 第 1 個 |
| docs: 補上 API 分頁參數的說明 | 內部 | 修正 | 第 2 個 |
| perf: 首頁查詢改用索引,載入時間從 1.2 秒降到 0.3 秒 | 新功能 | 內部 | 第 6 個 |
「正確的範例」與「標籤故意標錯」都照表格由上到下排列;「換順序」用的是正確標籤,只是把同樣六個範例改排成表格最右欄的位置。
第二輪:換成上面那套違反常識的團隊慣例
| 提示詞 | 3 次的分數 |
|---|---|
| 零樣本(只有形容詞) | 5、5、5 |
| 少樣本(六個範例) | 6、6、6 |
| 同樣六個範例,換個順序 | 7、6、7 |
| 六個範例,再加一句話把規則寫出來 | 8、8、8 |
第二輪的 8 題裡,第 1、2、3、4、7 題每一組每一次都答對,差別全部出在兩條團隊慣例影響到的那三題。把它們的實際答案攤開來看:
| 題目 | 團隊慣例的答案 | 零樣本 | 少樣本 | 少樣本換順序 | 範例加一句規則 |
|---|---|---|---|---|---|
| 5. docs: README 補上本機開發步驟 | 新功能 | 內部 ×3 | 內部 ×3 | 新功能 ×2、內部 ×1 | 新功能 ×3 |
| 6. perf: 搜尋結果改成串流回傳 | 修正 | 新功能 ×3 | 修正 ×3 | 修正 ×3 | 修正 ×3 |
| 8. perf: 單元測試改成平行執行 | 修正 | 內部 ×3 | 內部 ×3 | 內部 ×3 | 修正 ×3 |
零樣本三題全照常識分;少樣本只把第 6 題拉過來;換個順序,第 5 題開始搖擺;直到把規則寫出來,三題才一起到位。
第一輪裡,零樣本和少樣本一樣好,範例沒有多幫任何忙;到了第二輪,差距才出現。兩輪合起來,有兩個意外。
第一輪裡,我把六個範例的標籤全部打亂:feat 標成修正、fix 標成內部。照一般對少樣本的想像,模型應該會被帶歪。
結果三次都是滿分,而且三次都主動附註:範例的標籤看起來標錯了,所以它改成依每則 commit 實際做的事來分。
這其實跟研究的方向一致,只是更極端。Min et al.(2022)在分類與多選任務上,把範例的正確標籤換成隨機標籤,表現只掉了 0 到 5%;他們的結論是,範例真正在起作用的是標籤有哪些選項、輸入長什麼樣子、格式怎麼排,而不是每一組配得對不對。
但別把這句話讀成「標籤不重要」。那是總平均,個別資料集掉得多的也有,而且他們沒有測生成任務。我這裡看到的也只是:當範例跟它的常識衝突得很明顯時,它選擇相信常識。
第二輪才是重點。只給形容詞時,3 次都是 5 分:它照一般慣例分,三條團隊規則全錯。這很合理,「照我們團隊的慣例」這幾個字裡根本沒有資訊。三次它都先去工作目錄裡翻了一下,想找有沒有寫下來的團隊規範(因為是在 Claude Code 裡跑,它看得到檔案),沒找到才自己分。
加上範例之後,分數往上走,但沒有走到滿分。錯在哪裡很有意思:
換句話說,六個範例同時符合好幾條規則,它從裡面挑了一條最合理、最接近一般常識的,而那條剛好不是我們的。它沒有錯讀範例,是範例本身就沒有把規則講死。
最後一組只多了一句話:「docs 一律算新功能;perf 一律算修正,不管使用者感覺不感覺得到。」3 次都是滿分。
這就是範例比形容詞有效、但理由不是你想的那個:範例不是在教它規則,而是在給它線索,讓它自己反推規則。 反推出來的,是它覺得最合理的那一條。
要理解它為什麼會「反推」,得回到第一篇那個請求結構。把範例改寫成 API 的樣子,其實是這樣:
{
"model": "claude-opus-5",
"max_tokens": 64,
"system": "把 commit 訊息分到 changelog 的三個區塊之一:新功能、修正、內部。",
"messages": [
{ "role": "user", "content": "docs: 補上 API 分頁參數的說明" },
{ "role": "assistant", "content": "新功能" },
{ "role": "user", "content": "perf: 首頁查詢改用索引,載入時間從 1.2 秒降到 0.3 秒" },
{ "role": "assistant", "content": "修正" },
{ "role": "user", "content": "docs: README 補上本機開發步驟" }
]
}
那些 assistant 的回答,模型從來沒有說過,是你替它寫的。少樣本不是一個功能,是你在 messages 裡塞了一段偽造的對話歷史。模型看到的只是「前面這樣問就這樣答」,然後把對話延續下去。
所以範例的效力,取決於這段偽造的歷史把模式交代得多清楚。它能從歷史裡看出輸出只有三種、格式是一個詞、大概依什麼分;但如果歷史同時說得通兩種規則,它就只能用自己的常識去補。這跟上一篇是同一件事:黑盒子只看得到脈絡窗口裡的東西,而範例送進去的是例子,不是規則。
至於順序,研究裡的效應比我看到的大很多。Lu et al.(2022)發現,同一組四個範例只換排列,情感分類的準確率可以從 85% 以上掉到 50% 左右,接近亂猜;而且好的順序換一個模型就不成立,範例加多、模型放大也不保證變穩。我這次換順序,分數只在 6 和 7 之間晃,差別出在第 5 題。我不打算從 3 次結果推論 Claude 對順序不敏感,那篇用的是好幾年前的模型、一個情感分類任務,我的也只是 3 次、8 題。能確定的只有一件事:順序會動到結果,而你事先不知道會動到哪一題。
「情境學習」裡的「情境」,指的就是脈絡窗口。上面那段偽造的對話、你的指令、要分類的 8 則 commit,連它正在寫的回答,全部都在同一個窗口裡。這是情境學習唯一的工作空間。
Claude 的官方文件把窗口叫做模型的工作記憶,並且特別強調它跟訓練資料是兩回事:訓練資料變成了權重,窗口則是這一次呼叫臨時看得到的東西。算進窗口的包括系統提示、messages 裡的每一則訊息(連貼進去的文件與圖片)、工具的定義,以及這一輪產生的回答。Claude Opus 5 的窗口是 100 萬詞元(token)。
聽起來很大,但有三件事跟範例直接相關:
prompt is too long)。範例再有效,也得塞得進去。把這三件事和前面的實驗放在一起看,就知道情境學習為什麼長這個樣子:它能用的只有窗口裡的東西,而窗口每一次呼叫都從零開始。 這也帶出一個問題:如果什麼都沒有被存下來,這還算是「學習」嗎?
「情境學習」這個名字確實容易讓人誤會,好像模型讀完範例就「學會」了。先把兩種學習分開:
| 面向 | 訓練(微調) | 情境學習 |
|---|---|---|
| 改了什麼 | 模型的權重 | 什麼都沒改,權重是凍結的 |
| 學到的東西存在哪 | 模型裡 | 只存在這一次的脈絡窗口裡 |
| 下一次呼叫 | 還在 | 不見了,要重送 |
| 靠什麼推出答案 | 訓練資料 | 模型原本就知道的東西,加上你這次給的範例 |
一篇 ICLR 2026 的論文,標題就直接問了這個問題:Is In-Context Learning Learning?(de Wynter)。它的答案分成兩半。
理論上,算。 作者拿機器學習對「學會」的正式定義來檢查,情境學習在數學上是符合的。但他也指出,情境學習不會把看過的例子存下來,每一次都是拿模型原本就知道的東西,加上提示詞裡的範例,臨時推一次。所以它到底學得多好,只能靠實驗量。
實驗上,很有限。 他測了 GPT-4 Turbo、GPT-4o、Mixtral 8x7B 和 Phi-3.5 MoE 四個模型,用九個答案能嚴格判對錯的形式任務(像是判斷奇偶性、走迷宮、把字串反轉),範例數從 0 個給到 100 個,每個模型累積 189 萬次預測。其中幾個結果,寫提示詞的人很值得記住:
他的結論只有一句:情境學習利用的是提示詞裡的統計特徵,而不是資料之間的關係。
回頭看我那 24 次,這句話剛好解釋了第二輪錯在哪裡:
範例沒有告訴它「docs 一律算新功能」,只告訴它「這種 docs 算新功能」。它抓到的是範例表面的規律,一碰到長得不一樣的輸入,就退回自己的常識。 這是我對自己結果的解讀,不是論文的結論:論文測的是形式任務和另外四個模型,不是 Claude,我的也只是 8 題。
所以與其說情境學習是「學會」,不如說是每次呼叫臨時借來的理解:這一次有效,下一次要重借;借到的是範例表面的規律,不是背後的規則。這也是為什麼第二輪只多一句明講的規則就滿分:規則不用再靠反推,它就寫在看得到的地方。
不過,「看得到的地方」也不是永遠都在。對話一長,窗口還可能在你沒注意的時候被改寫。
100 萬詞元聽起來用不完,但在一段很長的對話、或一個會一直讀檔案和跑指令的任務裡,窗口是真的會被塞滿的。這時候有兩種處理方式,兩種都會改寫窗口:一種是直接把最舊的內容丟掉;另一種是壓縮(compaction),把比較早的對話濃縮成一段摘要,用摘要取代原文,對話再接著往下走。
這件事在 Claude 的幾個地方都看得到:
/compact,還能附一句話告訴它摘要時要保留什麼,例如 /compact Focus on code samples and API usage。為什麼要這樣做?官方文件給的理由有三層,而且剛好都是前面講脈絡窗口時提過的事:
但壓縮不是免費的,對情境學習來說尤其要小心:
/clear,那才是不花錢的。所以對寫提示詞的人來說,結論很實際:真正重要的規則與範例,不要只放在對話早期,要放在每次都會被送進窗口的地方。 窗口隨時可能被摘要改寫,而情境學習只認得窗口裡現在有的東西。
第一,範例每次都要重送。 前面說過,模型沒有狀態,那段偽造的對話每一次呼叫都要整段再送一次。六個範例不長,但真實的團隊範例常常是幾十組,每一次都在付錢。
第二,範例會把它鎖進你給的模式。 這是優點也是缺點。它學得到格式、學得到語氣,也會把範例裡碰巧出現的特徵當成規則,或者像第二輪那樣,把你沒打算教的規則一起學走。
第三,你不知道它反推出了哪一條規則。 分數不是滿分的時候,你看不出是範例不夠、順序不對,還是它挑了另一條說得通的規則。這一次我能看出來,是因為它剛好自己寫了解釋。
我現在的做法是:規則能用一句話講清楚的,就直接寫出來;範例拿來示範格式和邊界情況,不拿來偷渡規則。 兩個一起用,第二輪就是 8、8、8。
提示詞變成資產之後,要付的代價。
到這裡,提示詞已經長出了情境、格式、規則和一整段範例,而它們全都擠在同一個脈絡窗口裡,每一次呼叫都要重送。上面說,重要的規則要放在「每次都會被送進窗口的地方」;下一篇就從那個地方講起:把這些東西存起來的做法、存起來之後看不見的代價,以及改了一個字之後,你怎麼知道它真的變好了。