昨天我們讓程式跟 AI 說上話了。今天要談的是:怎麼把話說好。
這件事的重要性常常被低估。同樣的需求,換個講法,結果可能差很多:
A:「幫我看天氣」
B:「以下是臺北市未來 36 小時的天氣預報。
請判斷明天白天適不適合安排戶外行程,
用兩句話說明理由,並給出穿著建議。
<weather>...</weather>」
A 得到的是一段泛泛的回應。B 得到的是可以直接用的結論。
而在 Agent 裡,Prompt 不是使用者打的字,是我們程式生成的。所以 Prompt 設計對我們來說不是「聊天技巧」,是程式碼的一部分。
我自己整理出來的框架,大概是六個部分:
1. 角色與情境 你是誰、在什麼場合
2. 任務 要做什麼(一句話講清楚)
3. 輸入資料 提供的材料
4. 規則與限制 要遵守什麼、不能做什麼
5. 輸出格式 結果要長什麼樣
6. 範例 示範一次(可選,但很有效)
不是每個 prompt 都需要全部六項。但如果結果不如預期,可以照這個清單檢查是哪一項沒寫清楚。
AI 不會因為你客氣就表現更好,但會因為你講得模糊而猜錯。
# ❌ 模糊
"幫我整理一下這些待辦事項"
# ✅ 明確
"""把以下待辦事項依照優先度與截止日排序,
輸出成編號清單,每項一行,格式為:
[優先度] 標題(剩餘 N 天)
只輸出清單,不要加任何說明文字。"""
「整理」可以是排序、可以是分類、可以是刪除重複、可以是摘要。你知道你要什麼,但 AI 不知道。
# ❌ 效果較差
"不要寫太長"
# ✅ 效果較好
"用三句話以內回答"
負面指令需要模型先想到那件事再避開它。正面指令直接給目標。
當 prompt 裡混著「指令」和「資料」,模型容易搞混。用標記把它們分開:
prompt = f"""請根據以下天氣資料,判斷明天是否適合戶外活動。
<weather_data>
{weather_summary}
</weather_data>
<user_todos>
{todo_summary}
</user_todos>
請用三句話回答:
1. 明天適不適合戶外活動
2. 理由(引用具體的天氣數字)
3. 一個具體建議
"""
用 XML 標籤(<weather_data>)是我很推薦的做法,因為:
<weather_data> 中的降雨機率」最後這點在 Agent 裡特別重要。任何來自外部的內容(API 回應、檔案內容、使用者輸入)都應該被包起來,並且明確標示為「資料」而不是「指令」。
這是「聊天」跟「程式整合」最大的差別。
# ❌ 程式沒辦法用
"分析這段文字的情緒"
# ✅ 程式可以用
"""分析這段文字的情緒。
只輸出一個詞,從以下選項中擇一:
正面 / 中立 / 負面
不要加任何解釋或標點符號。"""
更嚴格的做法是要求 JSON:
"""從使用者的話中抽取行程資訊。
只輸出 JSON,不要加 markdown 的程式碼框,不要加任何說明。
格式:
{
"title": "行程標題",
"date": "YYYY-MM-DD",
"time": "HH:MM 或 null",
"location": "地點或 null"
}
使用者說:明天下午三點跟客戶在信義區開會
"""
不過用 prompt 要求 JSON 不是百分之百可靠——模型偶爾還是會加上 ```json 或說一句「好的,以下是結果」。明天(Day 20)會介紹更可靠的做法。
當任務有特定風格或格式,示範一次比解釋十句有效。
FEW_SHOT_PROMPT = """把使用者的自然語言轉成待辦事項。
範例:
輸入:提醒我禮拜三要繳電費
輸出:{"title": "繳電費", "priority": "高", "due": "2026-10-07"}
輸入:有空的時候整理一下書櫃
輸出:{"title": "整理書櫃", "priority": "低", "due": null}
輸入:明天早上九點前要把報告寄出去
輸出:{"title": "寄出報告", "priority": "高", "due": "2026-10-03"}
現在換你:
輸入:{user_input}
輸出:"""
看這三個範例,模型就學會了:
null
這些規則我一條都沒有明說,但範例都示範了。
Few-shot 的實務建議:
null)對需要推理的任務,直接要答案的正確率比較低。給它空間思考:
prompt = """根據天氣與待辦清單,判斷明天最適合外出辦事的時段。
<weather>
06:00-18:00 短暫陣雨,降雨機率 60%,25-30°C
18:00-06:00 多雲,降雨機率 20%,23-26°C
</weather>
<todos>
- 去郵局寄包裹(郵局營業到 17:30)
- 買菜
</todos>
請依照以下步驟思考:
1. 先列出每個待辦事項的時間限制
2. 再看哪些時段天氣較好
3. 最後給出建議時段與理由
依序輸出這三個步驟。"""
這就是 Chain of Thought(思維鏈)。把大問題拆成步驟,讓模型一步一步來。
💡 不過現在的推理模型(像 Claude Opus 5)內建就會先思考再回答,不太需要特別下「請一步一步想」這類指令了。但「把任務拆成明確步驟」這個做法仍然有用——它讓輸出的結構更可預測。
Prompt 散落在程式碼各處是個惡夢。集中管理:
"""prompts.py — 集中管理所有 prompt 模板。"""
from string import Template
# --- System Prompts ---
ASSISTANT_SYSTEM = """你是「小幫」,一個台灣使用者的生活助理。
你的職責:
- 協助管理行程、待辦事項
- 依據天氣提供外出與穿著建議
- 在使用者需要時主動提醒重要事項
回應規則:
- 使用繁體中文與台灣用語(例如「網路」不是「網絡」)
- 簡潔具體,一般情況三句話以內
- 提供建議時,引用具體數字當理由
- 不知道的事情直接說不知道,絕對不要編造
- 涉及時間的判斷,一律以工具提供的資訊為準,不要用你自己的認知
你**不會**:
- 編造天氣、行程或任何事實資料
- 替使用者做不可逆的決定(刪除資料、發送訊息、付款)
"""
# --- Task Prompts ---
DAILY_BRIEF = Template("""請根據以下資訊,為使用者產生今日簡報。
<current_time>
$current_time
</current_time>
<weather>
$weather
</weather>
<todos>
$todos
</todos>
請輸出:
1. 一句話的天氣重點(是否帶傘、穿著建議)
2. 今天最該優先處理的 1-2 件事,並說明為什麼
3. 一個貼心提醒(根據天氣與行程判斷)
總共不超過 120 字。用自然的口語,不要用條列符號。
""")
EXTRACT_TODO = Template("""從使用者的話中抽取待辦事項資訊。
今天是 $today($weekday)。
範例:
輸入:提醒我禮拜三要繳電費
輸出:{"title": "繳電費", "priority": "高", "due": "2026-10-07"}
輸入:有空的時候整理一下書櫃
輸出:{"title": "整理書櫃", "priority": "低", "due": null}
只輸出 JSON,不要加程式碼框,不要加說明。
輸入:$user_input
輸出:""")
用 string.Template 而不是 f-string 的理由:
Prompt 裡常常有大括號(JSON 範例就有),用 f-string 會衝突,要寫成 {{ }} 很醜。Template 用 $name 就沒這問題。
用起來:
from datetime import datetime
import prompts
prompt = prompts.DAILY_BRIEF.substitute(
current_time=datetime.now().strftime("%Y-%m-%d %H:%M"),
weather=weather_summary,
todos=todo_summary,
)
response = llm.call([{"role": "user", "content": prompt}])
substitute() 在缺少變數時會報錯(好事,早點發現);safe_substitute() 則會留著 $name 不替換。
這點我想特別強調。Prompt 是程式碼,程式碼就該測試。
但 AI 的輸出不是確定性的,怎麼測?做法是:準備一組測試案例,跑過去看結果。
"""test_prompts.py — Prompt 的測試案例。"""
TEST_CASES = [
{
"input": "提醒我明天要繳電費",
"expect": {"title_contains": "電費", "priority": "高"},
},
{
"input": "有空整理書櫃",
"expect": {"title_contains": "書櫃", "priority": "低"},
},
{
"input": "今天天氣真好", # 邊界情況:這不是待辦事項
"expect": {"should_be_null": True},
},
{
"input": "買牛奶,還有記得繳停車費", # 邊界情況:兩件事
"expect": {"multiple": True},
},
]
def run_prompt_tests(llm):
passed = 0
for i, case in enumerate(TEST_CASES, 1):
result = extract_todo(llm, case["input"])
ok = check(result, case["expect"])
status = "✅" if ok else "❌"
print(f"{status} #{i} {case['input']}")
print(f" → {result}")
passed += ok
print(f"\n通過 {passed}/{len(TEST_CASES)}")
每次修改 prompt,就跑一次這組測試。你會很驚訝地發現,某些「小幅調整」會讓某些案例壞掉。
我的經驗是:邊界案例最有價值。上面第三、四個案例(不是待辦、有兩件事)就是最容易出問題的地方。
一般的 prompt 是「人對 AI 說話」。Agent 的 prompt 有三個特點:
完整 prompt = system prompt
+ 工具定義(Day 15 寫的 description)
+ 對話歷史
+ 工具執行結果
+ 當前使用者輸入
所以 Day 15 講「tool description 是 prompt」不是比喻——它真的會被組進最終送給模型的內容裡。
一般對話,模型不知道就說不知道。Agent 則要能判斷:
這些行為要在 system prompt 裡明講:
"""當你需要資訊才能回答時,使用提供的工具取得,不要猜測。
當工具回報錯誤時,把錯誤原因用白話告訴使用者,不要重複嘗試超過兩次。
當使用者的要求不明確時,直接反問,不要自己假設。"""
Agent 會實際做事,所以要明確劃出紅線:
"""在執行以下操作前,一定要先向使用者確認:
- 刪除任何資料
- 發送訊息給其他人
- 任何無法復原的動作
如果使用者的要求超出你的工具能力範圍,直接說明你做不到,
不要用文字假裝你完成了。"""
最後那句很重要。模型有時候會「演」——說「我已經幫你設好提醒了」,但其實它根本沒有那個工具。明講這條規則可以大幅減少這種情況。
把重要的規則放在最後
模型對 prompt 開頭和結尾的注意力較高。最關鍵的指令可以在結尾再重複一次。
用分隔線增加結構
---
以上是背景資料,以下是你的任務。
---
要求模型確認理解(除錯時很有用)
在回答之前,先用一句話重述你理解的任務。
這樣你馬上就知道它是不是誤解了。確認 prompt 正確後再把這句拿掉。
不要一次塞太多任務
一個 prompt 做一件事。要做五件事就拆成五次呼叫,或設計成 workflow(Day 23)。
string.Template 管理 prompt 模板,避免跟 JSON 的大括號打架明天要解決一個今天沒解決乾淨的問題:怎麼讓 AI 的輸出「一定」是程式能用的格式。