詠唱入門

《提示工程》讀書筆記

當今大詠唱時代,作為一個工程師,難免就會遇到某些需求需要跟大語言模型互動;近日閱讀 Boonstra, L. (2025, February). Prompt Engineering. 白皮書,嘗試稍微多了解一點詠唱要領。

來,開始溝通吧

本文為個人流水筆記,以下文字應該不會重新做整理,僅依照章節順序排列。

提示工程(Prompt engineering)

大語言模型是在預測接下來會出現的文字,而提示工程則是要設計出高品質的提示、以讓 LLM 輸出更高精確度的答案。

我自己在大量使用 ChatGPT 這類對話介面後的確開始遺忘它是在做文字補全(completion);對話溝通過程中可能帶有更多的上下文資訊,而 API 請求則否,因此在現行的技術下想要以問答形式拿到精確的回答可能就會相對困難。

輸出設置

當選擇了要使用的模型,我們首先得注意到它的輸出的設置,有效的提示工程需要仰賴正確的設定。

輸出長度

更長的輸出長度意味著更多的能源消耗、更慢的回應速度跟更高的成本(💰),縮短輸出長度不會讓模型變笨、它只會在輸出長度超過限制時進行中斷,所以當我們只需要簡潔的輸出時,直接設定較小的輸出長度會是個適當的行為。甚至在進行 ReAct prompting 時可以防止浪費過多的 token 在製造我們不想拿到的回應。

採樣數管制

語言模型並非僅會預測接下來的第一個 token,它們會基於多個接下來可能的 token 計算機率。模型設置中會有一些參數(如溫度)可用於影響模型計算機率的過程、並影響 token 被選上(輸出)的可能性。

  • 溫度(temperature)

    溫度參數會影響輸出的隨機性,較低的溫度可以產生較高的確認性(deterministic)。在較高的溫度下,各種 token 被選上的機率會愈加接近,進而使得較高的隨機性產生。

  • Top-K 與 Top-P

    Top-K 及 Top-P 是被應用於 LLM 中、來限制模型進行預測的數量的手段:

  • 系統機制上會選取 K 個最可能的 token 來計算分布,故愈高的 K 值意味著愈多元的選擇,而 $K=1$ 的設置則意味著貪婪(greedy

  • Top-P (nucleus sampling) 則會讓模型將多個、累積機率達到 P 值的 token 一起納入選擇,$P=0$ 的情況則會成為貪婪(因為選取了任一 token 皆會達標)

選擇使用 Top-K 或 Top-P 目前沒有訣竅——試一下、然後看結果哪個好。

交互關係

  • 當溫度設為 0,那麼 top-K 及 top-P 都無關要緊了,反正最有可能的下一個 token 就該直接被選上;而過高的溫度($\geq 1$)則會導致 token 幾乎是亂數選取
  • 如果 K 設為 1,那麼每次選擇 token 時只有 1 個 token 可以被選擇,則 P 值變得無關要緊。
  • 如果 P 設為非常小的值($\approx 0$),大多數的模型就只會選擇最有可能的路徑,進而使得溫度與 K 值變得無意義。如果 P 設為 1,則導致沒 token 能被選擇。

書中提供可以參考的預設值如下表,我順便查了幾家大廠的 API 的預設值附上:

溫度 top-P top-K
一般用途 0.2 0.95 30
期待比較有創意的回應 0.9 0.99 40
期待比較制式的回應 0.1 0.90 20
需要完全的確認性 0 - -
OpenAI Chat Completion API 預設值 1 0
Google Text generation API 預設值 0.95
xAI Chat completions API 預設值 1 1

有點可惜 Anthropic Messages API 文件沒有寫預設值。

提示技巧

一般情境/零樣本提示

零樣本提示(Zero-shot)會是最常見的形式,而它的輸入可以是任何東西——一個提問、一個故事的開端或者一個引導,然後沒有附上範例。

在開發過程中提示詞通常需要修修改改,書中建議我們要記得以有結構、規律的形式保存提示詞,下表呈現一組完整的提示詞與該被記錄下來的餐數組合:

22365_3_Prompt Engineering_v7-p15.jpg

題外話,筆者注意到這個範例在提示的最尾巴補上了:

Sentiment:

應該算是展現了前面提及的基礎——語言模型是在預測接下來會出現的文字——與其重複地修正「問題」來期待「答案」,不如直接嘗試讓提示與輸出得以自然地連接為同一段文字,來得到更高品質的結果。

少量樣本提示

在建立提示詞時,給予一些樣本會十分有幫助,這能讓模型更易於理解你想要拿到什麼樣的回應。

  • 單一樣本提示(one-shot)顧名思義就是只給一個樣本,這可以讓模型嘗試去模仿這個樣本來給出回應。
  • 少量樣本提示(few-shot)則是給予多個樣本,相較於單一樣本的提示詞,模型能有更高機率遵循模板來回應。

模型需要的樣本數會受到諸多因素影響,而一般而言 3-5 個範例會是個好的開始。以下為書中提供的範例:

22365_3_Prompt Engineering_v7-p16.png
  • 在選擇範例時,使用與要執行的工作有相關的範例
  • 在選擇多個範例時,使範例多元化、高品質的範例
  • 如果期待輸出的內容可以更加穩健(robust),把邊角情況(edge case)包含進去範例裡面

分層提示框架

本書建議我們可以用不同的層級切分提示詞,縱使這些層級間的權責劃分有所重疊,使用這個劃分法切分提示詞可以展現出較為清楚的意圖,並且比較容易分析每一段提示詞是怎麼影響產出的:

  • 系統提示(system prompting)定義模型的基本功能與整體目的
  • 上下文提示(contextual prompting)提供詳細的背景資訊、尤其是跟當下的工作有關的細節,讓模型得以製造出更切合需求的回應
  • 角色提示(role prompting)指派特定的角色或身份給模型,使模型以更貼近特定該角色或身份的立場給出回應

編按:這裏提及的內容跟我們在操作 LLM API 時用到的 message role 不同。這裡的提示框架並不會被映射為 message role,而 message role 也不是 role prompting 的子類別。

系統提示

系統提示的作用是「為系統加上更多工作」、使產出符合預期的規格,以下為書中提供的系統提示範例:

22365_3_Prompt Engineering_v7-p20.jpg

文中提及使用 JSON 格式在跟程式整合的時後會非常方便,畢竟開發者可以不用手動整理輸出,另外他提及使用 JSON 格式可以一定程度地限制幻覺(hallucination)的發生。

系統提示不一定需要很複雜,書中的舉例是單純補上:

You should be respectful in your answer.

就有助於讓回應較為安全並且減少惡意內容。

筆者會聯想到現在在呼叫 API 時很常用到的 structured output,照這裡的解釋來看應該也算是一種系統提示,而且搭配 SDK 來使用時挺懶人的。

角色提示

角色提示指派特定的角色或身份給模型,使模型以更貼近特定該角色或身份的立場給出回應;舉例來說,可以使用提示將模型設定為一個導遊,然後叫它幫忙規劃行程。

22365_3_Prompt Engineering_v7-p22.jpg

將模型設定為特定角色可以調整其回應的基調,進而推使回應內容更趨向專家的回應。

上下文提示

上下文提示可以讓模型更快地理解當下的需求並產生相對應的輸出:

22365_3_Prompt Engineering_v7-p24.jpg

退一步提示法

退一步提示法(Step-back prompting)是另一個可以改善效果的手法,其操作方式為先退一步——讓模型先對於目標工作的相關背景進行一般詢答、然後才對目標工作本身進行解決。這樣的手法可以使模型先梳理過相關的背景知識與可能的解決方案,如同人們面對困難的問題時會先嘗試釐清問題、然後才開始進行解決。

書中使用的範例為讓模型為一個第一人稱射擊遊戲(FPS)撰寫故事,如果我們直接要求模型撰寫一篇故事,那麼產出的文字大略會是:

玩家的小隊便在一片密集的都市區遭到敵對勢力伏擊。玩家必須穿梭於錯綜的小巷與破敗的建築之間,運用潛行與精確打擊,在混亂中殺出一條血路⋯⋯(下略)

而採用「退一步」手法時,我們要先要求模型思考在什麼樣的場景出現在第一人稱射擊遊戲中會比較吸引人:

22365_3_Prompt Engineering_v7-p27.jpg

然後將這段資訊當成上下文、再來請模型撰寫故事,便能撰寫出較為豐富的結果:

22365_3_Prompt Engineering_v7-p28.jpg

思路鏈

思路鏈(Chain of Thought, CoT)技巧要求模型先行輸出思路、才進行總結,這個手法在有邏輯推理需求的工作上有不錯的效果。

這個手法能帶來一些好處:

  • 增加可解釋性(interpretability):當我們使用思路鏈時,會要求模型輸出其思考過程,這讓我們有機會了解其產出結果的原因。
  • 穩健性(robustness):使用思路鏈可以在模型版本變動時展顯較高的穩健性,產出的效果比較不會亂飄

而缺點方面——貴,因為要產出解釋,所以 output token 消耗量會比較大 💸💸💸

書中用了年齡問題去戳:

When I was 3 years old, my partner was 3 times my age. Now, I am 20 years old. How old is my partner?

63 years old

而正如我們的刻板印象,語言模型在數學問題上完全沒救,但當我們補上了神奇咒語:

Let's think step by step.

完整提示為:

When I was 3 years old, my partner was 3 times my age. Now, I am 20 years old. How old is my partner? Let's think step by step.

那麼模型就會逐步講解、並給出合理的結果。

書中主張,可以透過「談話」解決的問題都適合以思路鏈處理——如果一個問題可以在談話過程中解釋其中的步驟並且解決,那麼我們就可以試試看使用思路鏈。

自我一致性

在應用思路鏈時語言模型展現出了類似於人類在解決問題時的思考能力,但是思路鏈是透過貪婪解碼(greedy decoding)策略來處理工作、這並不意味語言模型有思考與邏輯推理的能力。

自我一致性(Self-consistency)技巧結合了抽樣與多數投票,讓模型產生多樣化的推理路徑,並選擇最一致的答案。這種方法提升了大型語言模型(LLM)所生成回應的準確性與連貫性,並且提供了較高可能性1的答案。

實現自我一致性的方法如下:

  1. 產生多個不同的意見:在較高的溫度下、多次重複地給予模型相同的提示,使模型給出多樣化的答案
  2. 比較多次產生的意見,選擇重複率最高的一個

這個手法所需的成本很明顯地會高很多。

書中的範例為在零樣本提示的條件下去判斷一封 E-mail 是否為重要郵件:

EMAIL:

\<EMAIL CONTENT>

Classify the above email as IMPORTANT or NOT IMPORTANT. Let's think step by step and explain why.

而手法則是在使用思路鏈的前提下、進行了多次的詢答,並選擇出現率較高的答案。

思維樹

思維樹(Tree of Thoughts, ToT)可以視為更泛化的思路鏈。不同於思路鏈線性地解釋可能性,思維樹藉由有條理的文字作為中間層、並賦予模型「走出」不同的路徑的能力,思維樹具有解決複雜問題的能力。

22365_3_Prompt Engineering_v7-p36.jpg

書中沒有直接附上範例,而是建議讀者參閱 Long (2023) Large Language Model Guided Tree-of-Thought 的實作;該論文的範例是利用思維樹讓 GPT-3.5 解決數獨問題,其程式碼為 jieyilong/tree-of-thought-puzzle-solver;核心邏輯包含:

  1. 基於當前節點,隨機以佐以任一可能的手段對模型發出請求
  2. 檢查模型給的答案,判定為找到正解、有效、或錯誤
  3. 如果找到正解,就跳脫循環並回傳答案
  4. 如果答案仍有效,則設為子節點,並繼續尋找可能性
  5. 如果發生錯誤,則轉返(rollback)到母節點並繼續尋找其他可能性

在該實作裡面,我注意到它已經設有幾個可能的解決手段,如:

For example, apply the "only possibility" rule, and fill in the obvious cell first

故此論文中並沒有要解決「怎麼讓模型自主拓展出新的可能性」的問題。 另外,撇開這裡使用到語言模型,看過去覺得有點與蒙地卡羅樹搜尋相似。

ReAct

推理與行動(Reason and act, ReAct)是一種讓模型藉由反覆推理、搜集資料及論證的過程來解決複雜問題的典範(paradigm)。

如同人類在現實世界也是如此解決問題的,透過一些外部工具提供更多的資料並反覆論證,ReAct 方法可以在不同的領域中達到跟其他提示工程相當的效果。

ReAct 的實作為一套「思考——行動」循環的框架:

  1. 讓模型對於問題進行拆解與推理,並列出行動方針
  2. 框架依照行動方針去呼叫工具,將取得的結果(資料)回饋給模型
  3. 模型根據結果給出下一步推理,反覆循環直到問題被解決

書中給出的範例不是提示語本身,而是一段使用 VertexAI 實作 LLM Agent 的範例程式;換句話說,目前發展上 agent 其實就是 ReAct 框架的實作了。時過境遷,如果需要實作 agent 的話,使用 Google ADK 或是 OpenAI Agents SDK 至少會是個資源比較完整的(比較多範例可以抄)的選擇。

自動提示工程

寫提示詞很麻煩、學會上面那一堆技巧也很麻煩,於是人們期待語言模型來寫提示詞——而這個方法真的存在,稱為自動提示工程(automatic prompt engineering, APE),表面上是幫人們偷懶,但實際上它甚至可以有不錯的表現,也許畢竟它們說著相同的語言,知子莫若父也。

我們要做的事情是:

  • 讓模型產出幾個提示詞
  • 評估每個提示語的效果
  • 挑一個效果好的,小改一下、然後回到上一動看看改完有沒有比較好

聽起來跟奧客在盧設計師改案子的手法差不多

29066223_1617527298361476_2887755329146191872_n.jpg

(作者:謝東霖

至於評估的手法:

  • 讓模型製造出多種需要應對的輸入來作為測試資料,例如:

    We have a band merchandise t-shirt webshop, and to train a chatbot we need various ways to order: "One Metallica t-shirt size S". Generate 10 variants, with the same semantics but keep the same meaning.

  • 選擇一個指標來量化提示詞效果的好壞,例如:

  • 雙語替換評測(bilingual evaluation understudy, BLEU)

  • Recall-Oriented Understudy for Gisting Evaluation) (ROUGE)

利用選擇的指標與測資來評估提示詞的效果好壞,然後繼續當奧客

最佳實踐

書中提供了幾條準則:

  • 永遠記得給範例

    在提示詞內提供範例,使其成為單樣本/少量樣本提示(one shot / fews shot)會是最簡單的且十分有效的手段。

  • 簡化提示詞

    如果這個提示詞讓你自己來讀覺得有點複雜,那大語言模型不會做得比你好。把不必要的資訊蓋掉,然後用明確的動詞描述需要的行動。

    好用的動詞列表:

    Act, Analyze, Categorize, Classify, Contrast, Compare, Create, Describe, Define, Evaluate, Extract, Find, Generate, Identify, List, Measure, Organize, Parse, Pick, Predict, Provide, Rank, Recommend, Return, Retrieve, Rewrite, Select, Show, Sort, Summarize, Translate, Write

  • 把輸出格式講明白

    不要單純指揮語言模型幫忙產出「一篇文章」,給出更明確的方向,例如「一篇在講解最熱門的 5 個遊戲的文章」

  • 使用引導,而非限制

    正向表列出希望模型做到的事情,而不是一直追加條件叫它不要去做什麼事。用鼓勵的模型會做得比較好,用限制的會減少它提出更好的選項的機率。

    編按:我到底是在整理提示工程筆記還是育兒筆記啊...

  • 控制最大輸出量

    算是回應最最前面輸出設置,設個最大輸出量,不然過多的廢話也只是在浪費錢跟減少北極熊的生存空間。

  • 在提示詞內使用變數

    提示詞內是可以使用變數的,書中表示使用變數可以讓提示詞更有彈性、並且可以減少需要再實作中寫死參數的麻煩。

    書中給的範例提示是:

    VARIABLES {city} = "Amsterdam"

    PROMPT You are a travel guide. Tell me a fact about the city: {city}

    不過筆者覺得當今程式語言能夠做變數代換的手段應該多到這不太會是問題啦。

  • 實驗不同的輸入格式與寫作風格

    不同的模型、參數、提示格式甚至遣辭用句都會導致產出的變化,故我們應該多方嘗試與修改來找到最佳的提示語。

    以介紹 Dreamcast 的發展歷程這件事來舉例,分別可以用提問、陳述與引導三種形式來進行:

    • 提問(question): What was the Sega Dreamcast and why was it such a revolutionary console?
    • 陳述(statement): The Sega Dreamcast was a sixth-generation video game console released by Sega in 1999. It... (註:這裏的刪節號來自原文,應該是用於讓模型直接開始進行文字補全)
    • 引導(instruction): Write a single paragraph that describes the Sega Dreamcast console and explains why it was so revolutionary.
  • 對於分類問題,在範例中把類別打亂

    理論上來說範例的順序沒啥關係,但是打亂順序效果會比較可靠。這是為了避免模型用把順序記憶下來然後照抄,讓範例的順序跟目標的類別順序不一致可以讓模型更專注在每個分類各自的特徵。 一個好的開始會是用 6 個或以上的範例來告訴模型該怎麼分類。

  • 整合模型的更新

    站在巨人的肩膀上,試試新的模型,效果會不錯(💸💸💸💸💸)

  • 實驗不同的輸出格式

    對於非創意型的工作,例如資料抽取、挑選、排序或排名,可以嘗試使用結構化的輸出格式,如 JSON 及 XML。

  • JSON 格式修復

    雖然使用 JSON 格式作為輸出很方便,但需要考慮模型輸出的 JSON 可能不工整,導致程式端不能正確地解析。對應的處理手法是引入例如 mangiucugna/json_repair 這樣的工具來修復輸出。

  • 提供輸入資料的綱目

    綱目(schema)不僅止於在設定輸出格式很方便,提供輸入資料的綱目也能增進模型對於輸入資料的理解,應用方法與輸出相同——把 JSON schema 一併附上就行。

  • 跟其他 咒言師 提示工程師合作

    即使你有好好地讀過整篇文章然後好好地跟著這些最佳實踐走,你還是該跟你的同事聊聊看。畢竟 10 個人會寫出 11 種提示詞,這時常會有意外地收穫。

  • 對於思考性工作,先思考再結論

    當使用思路鏈/自我一致性等思考形式的手法時,記得讓模型先講完理由再給出答案。

  • 思路鏈不需要創意

    使用思路鏈時把溫度設為 0。思路鏈就是要模型進行貪婪解碼,把最有可能的解釋、步驟說清楚講明白,並根據它給出的理由決策出一個最高機率的答案,故我們不要模型天馬行空,把溫度設為 0 會是最佳解。

  • 把用過的提示詞記下來

    每個輸入都會影響成效,然後你不可能記得你做過的所有實驗,所以你應該把所有用過的提示詞都以文件的形式記錄下來,這個紀錄能在未來幫助你進行追蹤甚至偵錯。以下為書中建議需要

    如果你有使用檢索增強生成(retrieval-augmented generation, RAG),那麼 RAG 系統的參數也該被一併記下來。

  • 系統整合

    當提示詞已經完美,要被整合為系統的一部分之時,建議將提示詞與腳本分開在不同檔案,會比較好維護;另外,記得加上自動化測試。

  1. 原文中使用的文字十分保守——提供的答案具有更高偽機率概率(pseudo-probability likelihood),筆記中簡化為較高可能性,以使文字較為流暢