iT邦幫忙

2026 iThome 鐵人賽

DAY 19
0
AI Engineering

從 Prompt 到自主決策:用 Python × Agentic Workflow 實作生活助理系列 第 19 篇

把話說清楚:Prompt 設計的原則與實作

  • 分享至 

  • xImage
  •  

昨天我們讓程式跟 AI 說上話了。今天要談的是:怎麼把話說好。

這件事的重要性常常被低估。同樣的需求,換個講法,結果可能差很多:

A:「幫我看天氣」
B:「以下是臺北市未來 36 小時的天氣預報。
     請判斷明天白天適不適合安排戶外行程,
     用兩句話說明理由,並給出穿著建議。
     <weather>...</weather>」

A 得到的是一段泛泛的回應。B 得到的是可以直接用的結論。

而在 Agent 裡,Prompt 不是使用者打的字,是我們程式生成的。所以 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)

當任務有特定風格或格式,示範一次比解釋十句有效。

FEW_SHOT_PROMPT = """把使用者的自然語言轉成待辦事項。

範例:
輸入:提醒我禮拜三要繳電費
輸出:{"title": "繳電費", "priority": "高", "due": "2026-10-07"}

輸入:有空的時候整理一下書櫃
輸出:{"title": "整理書櫃", "priority": "低", "due": null}

輸入:明天早上九點前要把報告寄出去
輸出:{"title": "寄出報告", "priority": "高", "due": "2026-10-03"}

現在換你:
輸入:{user_input}
輸出:"""

看這三個範例,模型就學會了:

  • 「禮拜三」「明天」要轉成實際日期
  • 「有空的時候」= 低優先度
  • 「前要」= 高優先度
  • 沒有時限就填 null

這些規則我一條都沒有明說,但範例都示範了。

Few-shot 的實務建議:

  • 2~5 個範例通常就夠,太多會佔 token
  • 涵蓋邊界情況(上面第二個範例示範了 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 模板管理

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 是要測試的

這點我想特別強調。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,就跑一次這組測試。你會很驚訝地發現,某些「小幅調整」會讓某些案例壞掉。

我的經驗是:邊界案例最有價值。上面第三、四個案例(不是待辦、有兩件事)就是最容易出問題的地方。


九、Agent 的 Prompt 有什麼不同

一般的 prompt 是「人對 AI 說話」。Agent 的 prompt 有三個特點:

1. 它是組合出來的

完整 prompt = system prompt
            + 工具定義(Day 15 寫的 description)
            + 對話歷史
            + 工具執行結果
            + 當前使用者輸入

所以 Day 15 講「tool description 是 prompt」不是比喻——它真的會被組進最終送給模型的內容裡。

2. 它要處理「不確定」

一般對話,模型不知道就說不知道。Agent 則要能判斷:

  • 「我缺少資訊」→ 呼叫工具
  • 「工具失敗了」→ 換方法或回報使用者
  • 「我不確定使用者的意思」→ 反問

這些行為要在 system prompt 裡明講:

"""當你需要資訊才能回答時,使用提供的工具取得,不要猜測。
當工具回報錯誤時,把錯誤原因用白話告訴使用者,不要重複嘗試超過兩次。
當使用者的要求不明確時,直接反問,不要自己假設。"""

3. 它要有行為邊界

Agent 會實際做事,所以要明確劃出紅線:

"""在執行以下操作前,一定要先向使用者確認:
- 刪除任何資料
- 發送訊息給其他人
- 任何無法復原的動作

如果使用者的要求超出你的工具能力範圍,直接說明你做不到,
不要用文字假裝你完成了。"""

最後那句很重要。模型有時候會「演」——說「我已經幫你設好提醒了」,但其實它根本沒有那個工具。明講這條規則可以大幅減少這種情況。


十、幾個實用技巧

把重要的規則放在最後
模型對 prompt 開頭和結尾的注意力較高。最關鍵的指令可以在結尾再重複一次。

用分隔線增加結構

---
以上是背景資料,以下是你的任務。
---

要求模型確認理解(除錯時很有用)

在回答之前,先用一句話重述你理解的任務。

這樣你馬上就知道它是不是誤解了。確認 prompt 正確後再把這句拿掉。

不要一次塞太多任務
一個 prompt 做一件事。要做五件事就拆成五次呼叫,或設計成 workflow(Day 23)。


小結

  • Prompt 的結構:角色、任務、輸入、規則、輸出格式、範例
  • 明確勝過禮貌;說「要做什麼」比說「不要做什麼」有效
  • 用 XML 標籤把「資料」和「指令」分開,也能防止外部內容被當成指令
  • 指定輸出格式,程式才用得上
  • Few-shot 範例能示範出難以言說的規則,2–5 個就夠
  • 用 string.Template 管理 prompt 模板,避免跟 JSON 的大括號打架
  • Prompt 要測試,尤其是邊界案例
  • Agent 的 system prompt 要包含:不確定時的行為、工具使用原則、行為邊界

明天要解決一個今天沒解決乾淨的問題:怎麼讓 AI 的輸出「一定」是程式能用的格式。


上一篇
AI 進場:第一次用 Python 呼叫大型語言模型
下一篇
讓 AI 回傳程式能用的東西:結構化輸出
系列文
從 Prompt 到自主決策:用 Python × Agentic Workflow 實作生活助理 共 25 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言