今天進入主軸二:生成式 AI。前八天我們在解決關於 MLOps 的「模型會不會掛」,接下來八天解決「模型會不會智商下線」。
主軸一的讀者會發現,這邊的問題同樣需要工程紀律,只是換了一種形式。
繼續一個在模型上線後的經典情境:
第 1 週:工程師寫了一句 prompt,效果不錯,上線。
第 3 週:發現某些情況答得不好,加了幾句規則。
第 8 週:prompt 長到 300 行,裡面有 27 條「注意:如果……就……」的規則,改一句話會讓另外三個情境壞掉,而且沒有人敢動它。
這就是「prompt 工程」做到極限的樣子。問題不在技巧不夠,在於把所有事情都塞進一段文字裡這個做法本身有上限。
今天要換一個框架來看這件事。
📊 職缺訊號
「Prompt 與脈絡設計」出現在 32.9% 的生成式 AI 職缺中。但要注意:幾乎沒有職缺是「只做 prompt」的——它總是與 RAG(48.1%)、Agent(47.6%)綁在一起。這說明市場已經過了「prompt engineer」這個職稱的階段,提示設計是一項基本功,不是一個職務。
先建立一個基本認知:當你呼叫一次 LLM,模型實際收到的輸入是這樣組成的:

圖 16-1:模型輸入的六個來源(示意架構)。「prompt engineering」通常只處理第一項,而輸出品質由六項共同決定。
這就是 Context Engineering 與 Prompt Engineering 的差別:
當你的系統開始有 RAG 與工具時,第二個問題的重要性會遠超過第一個。
如果脈絡是預算,那放在哪裡跟放什麼一樣重要:

圖 16-2:關鍵資訊在脈絡中的位置對答對率的影響(示意曲線,趨勢依 Liu et al. 2023 的公開發現繪製)。放在開頭或結尾的資訊被使用得最好,放在中段的最容易被忽略。
這個現象有兩個直接的工程結論:

圖 16-3:常用提示技巧與適用情境(示意對照)。技巧的效果高度依賴任務類型,沒有一招通用。
直接看實例。任務是「從客服對話中擷取退貨資訊」:
❌ 弱 prompt:
請幫我看這段對話有沒有要退貨,有的話告訴我。
問題:沒有定義「退貨資訊」包含什麼、沒有指定輸出格式、沒有處理「不確定」的情況。
✅ 強 prompt:
你是客服對話分析助理。請從以下對話中擷取退貨相關資訊。
擷取欄位:
- order_id:訂單編號(格式為 A 開頭加 5 碼數字)
- reason:退貨原因,只能是 ["瑕疵", "不符描述", "不喜歡", "重複下單", "其他"] 之一
- urgency:緊急程度 1-3(3 為最急,客戶明確表達不滿或提到期限時為 3)
規則:
- 只擷取對話中明確提到的資訊,不要推測。
- 若某欄位無法從對話中確定,填入 null。
- 若整段對話與退貨無關,回傳 {"is_return_request": false}。
以 JSON 格式輸出,不要有其他文字。
<對話>
{{ conversation }}
</對話>
四個關鍵改進:定義了欄位與型別、用列舉限制了自由度、明確處理「不確定」與「不適用」、用標籤包住不可信的輸入(這也是 Day 25 防注入的基礎)。
光在提示裡說「請輸出 JSON」是不夠的,生產環境要三件套:
"""結構化輸出的正確做法:約束 → 驗證 → 帶錯誤重試。"""
from pydantic import BaseModel, Field, ValidationError
from typing import Literal, Optional
import json
class ReturnRequest(BaseModel):
is_return_request: bool
order_id: Optional[str] = Field(None, pattern=r"^A\d{5}$")
reason: Optional[Literal["瑕疵", "不符描述", "不喜歡", "重複下單", "其他"]] = None
urgency: Optional[int] = Field(None, ge=1, le=3)
def extract(client, conversation, max_retries=2):
messages = [{"role": "user", "content": PROMPT.replace("{{ conversation }}", conversation)}]
for attempt in range(max_retries + 1):
resp = client.chat.completions.create(
model="...", messages=messages,
response_format={"type": "json_object"}, # 1) 用 API 層的約束
)
raw = resp.choices[0].message.content
try:
return ReturnRequest.model_validate_json(raw) # 2) 應用層驗證
except ValidationError as e:
if attempt == max_retries:
raise
# 3) 把錯誤訊息回饋給模型,讓它自己修
messages += [
{"role": "assistant", "content": raw},
{"role": "user", "content": f"輸出格式錯誤:{e}\n請依 schema 重新輸出。"},
]
第三步是關鍵:把驗證錯誤原文丟回去,模型的修正成功率相當高。這比自己寫正規表達式硬拆字串穩健得多。
prompts/
├── customer_service/
│ ├── v1.jinja2 # 用模板引擎,變數與文字分離
│ ├── v2.jinja2 # 每個版本一個檔案,可 diff、可回滾
│ └── CHANGELOG.md # 改了什麼、為什麼改、評測分數變化
└── tests/
└── test_customer_service.py # 每個版本都要跑評測集(Day 24)
📌 實務建議
提示的每次修改,都必須跑一次評測集。 因為提示的修改是「全域性」的——你為了修好情境 A 而加的一句話,很可能讓情境 B、C 壞掉,而你不會知道。這就是 Day 11 說的品質門檻在生成式 AI 的對應物。
這是本篇最重要的一段。提示工程有明確的天花板,撞到以下情況時,該換工具了:
| 症狀 | 代表什麼 | 該換的工具 |
|---|---|---|
| 需要模型知道它不知道的事實 | 知識問題 | RAG(Day 18–20) |
| 需要模型執行動作(查資料庫、寄信) | 能力問題 | 工具呼叫(Day 17、21) |
| 提示裡塞了大量同類型範例(>10 個) | 格式/風格問題 | 微調(Day 23) |
| 提示長到沒人敢改、規則互相衝突 | 複雜度問題 | 拆成多步驟工作流(Day 21) |
| 每次都要在提示裡叮嚀「不要洩漏內部資訊」 | 安全問題 | Guardrails(Day 25) |
既然說「輸入視窗是一份預算」,就該真的編一份。以 32K 的視窗為例,一個企業知識助理的合理配置:
| 項目 | 預算 | 說明 |
|---|---|---|
| 系統提示與規則 | 800–1,500 token | 放最前面,且逐字固定以利快取(Day 26) |
| few-shot 範例 | 500–2,000 token | 3–5 個代表性範例即可,多了效益遞減 |
| 工具定義 | 300–1,000 token | 工具越多越貴,這是「工具不要太多」的成本理由 |
| 檢索脈絡 | 2,000–4,000 token | 不是越多越好,見圖 16-2 |
| 對話歷史 | 1,000–2,000 token | 超過就摘要(Day 17) |
| 使用者輸入 | 視情況 | |
| 保留給輸出 | 1,000–2,000 token | 最常被忘記的一項 |
留白很重要。 我看過團隊把脈絡塞到 30K,然後發現模型生成到一半就被截斷——因為輸入加輸出超過了視窗上限。編預算時要把輸出算進去。
另一個常被忽略的點:預算不是拿來用滿的。上表的總和刻意遠低於 32K,因為多數情況下,精簡的脈絡表現優於塞滿的脈絡。視窗大小是上限,不是目標。
💡 一個判斷標準
當你發現自己在 prompt 裡寫「如果……就……」超過五次時,你其實是在用自然語言寫程式。自然語言不適合寫控制流——把那些 if 拆出來變成真正的程式邏輯,讓模型只做它擅長的:理解與生成。
有了好的提示,明天把它變成一個真正的服務:串流輸出、函式呼叫、對話狀態、與企業內部系統對接。
明天也是這個系列與 2020 年《用 LINE 串起多媒體系統》 的對話——從六年前的 LINE Bot 到今天的 LLM 應用,介面變了、串接的思維沒變,但多了一件根本性的新事情。