跟 AI 溝通,其實也是一種需要反覆調整的介面設計
上一篇 Day16 把 Bedrock 這個平台介紹完了,這篇要處理的是實際「跟模型講話」這件事——怎麼問,答案品質差很多。
什麼是 Prompt?
Prompt 就是送給 LLM 的那段輸入文字,裡面通常包含:要它做什麼、給它看什麼資料、要它用什麼格式回答。寫 Prompt 常被誤會成「寫一段話叫 AI 做事」這麼單純,但實務上它更像是在設計一個介面——同一件事,講法不同,AI 的理解跟輸出穩定度可以差很多。這個專案的兩個 prompt(分析用、菜單生成用)都是經過好幾輪真實測試才穩定下來的。
以下分享一些下prompt時踩過的雷
1. 明確的分類規則,比籠統的形容詞可靠。
一開始想讓 AI 判斷「重口味」,發現「吃辣」跟「重鹹」很容易被混在一起判斷。後來在 prompt 裡明講:heavySalt 只認「重口味/加重調味料」這種明確主訴,跟 spicy 分開判斷——把模糊的形容詞轉換成明確的判斷規則,AI 的輸出穩定度明顯提升。
2. 候選清單要分區塊、要有數量上限。
這個雷跟「Token」這個概念有關——可以先理解成「AI 一次能讀的文字量是有上限的,讀太多字會直接被擋下來,不是讀不懂而已」。接上 Food DB 之後,我第一版很天真地把全部 1915 筆食物資料整包塞進 prompt,想說「反正資料都給它,AI 自己會挑」。結果換算下來這包 JSON 資料大概是 30 萬個 token,而 Bedrock 這個模型單次能接受的輸入上限是 20 萬 token,直接超標XD
**prompt 不是一個可以無限塞資料的黑洞,塞進去的資料量本身就是要設計的一部分。**修法會是,先用程式碼依這一餐的分類 mealSlot(例如早餐/午餐…)跟熱量預算篩過一輪,每個餐次只留 15 筆候選食物,並用亂數抽樣(不是每次都直接拿清單最前面那幾筆),避免每次生成的菜單都撞到同樣幾樣東西。
3. 用「規則」取代「舉例」,AI 更聽話。
之前幾個prompt版本是靠舉幾個範例引導 AI,例如「不要每餐都配生菜沙拉」,但發現這樣效果不穩定;後來改成明確的硬性規則「每一餐蔬菜/水果類候選最多選 1 個」,AI 遵守度明顯提高。對規則遵循類的任務,明確的約束比舉例更可靠。同樣的模式後來也用在別的地方:
mealSlot 這個分類欄位(例如 mealSlot=breakfast、mealSlot=main、mealSlot=side…),變成一條硬規則:「早餐只從 mealSlot=breakfast 挑、午晚餐一定要有一個 mealSlot=main」,比維護一堆範例句更精確、也更好維護(詳見 Day21)。4. 建議文字不要讓 AI 掰理由。
一開始的 suggestion 文字會寫「提供優質蛋白,增加纖維⋯⋯」這種營養學術語理由句,讀起來很像出自專業判斷。但後來發現一個更根本的問題:這些理由句是 AI 現場編出來的,不是真的依據任何計算或查證,它只是「知道營養建議通常會這樣寫」,照著這個語言模式生成出來而已。這跟之後會談到的”幻讀” (hallucination) 是同一種現象:內容聽起來很有道理,但沒有真正的依據支撐。後來就在 prompt 規定只能列品牌+品項名稱,不寫任何理由句。
這是實際跑在雲端上的 prompt(呼叫 #1)
與其說規則,不如直接看真正送進 Bedrock 的文字長什麼樣。以下是 analyzeDietaryFreeText()(自由文字 → DietaryAnalysis)實際使用的完整 prompt,${...} 是程式碼執行時動態帶入的變數,其他都是寫死的固定文字:
你是一位專業的飲食型態分析助理,任務是「觀察」使用者的飲食習慣,不是設計飲食介入方案。
使用者用一段自由文字描述自己最近的飲食習慣,這是一次性輸入,你沒有機會追問細節。如果文字裡沒有明確提到某項資訊,請不要瞎猜或幻覺,用合理的預設值或空值表示「不確定/未提及」。
也請注意:不要嘗試把使用者提到的食物換算成精確的營養素或份量,這一步只需要記錄「觀察到的型態」,不做精準的營養計算。
---
補充資訊(使用者在表單填寫的生活型態):
${JSON.stringify(lifestyle)}
使用者的自由文字描述:
"""
${freeText}
"""
---
補充規則:
- heavySalt 只根據使用者明確提到「重口味」「喜歡加重的調味料」等主訴才視為 true,不要因為使用者提到吃辣就判斷 heavySalt——辣(spicy)是獨立的口味維度,跟鹹淡無關,兩者分開判斷。
- eatsDessertRegularly 判斷邏輯跟 drinksSugaryBeverages 一樣:只根據使用者明確提到常吃甜點/蛋糕/含糖點心等習慣才視為 true,不要用「聚餐偶爾吃」這種偶發描述判斷成 true。
- mealsPerDay 計算時,如果 hasLateNightSnack 為 true,宵夜也算一餐,要一併計入總餐數。
- eatingOutPattern 禁止直接複製上面補充資訊裡的 eatingOutRatio 等級值(例如 "often")。這個欄位的目的是從自由文字裡分析出補充資訊沒有的細節,請描述「哪一餐傾向外食、哪一餐傾向在家煮」,例如「早餐與午餐以外食為主,晚餐多為家中自煮」。如果自由文字完全沒提到這方面的線索,才可以退回用 eatingOutRatio 的等級當作籠統描述。
請嚴格依照以下 JSON 格式輸出分析結果,不要輸出任何 JSON 以外的文字或說明:
{
"mealPattern": {
"mealsPerDay": number,
"skipsBreakfast": boolean,
"hasLateNightSnack": boolean,
"approxMealTimes": string[]
},
"recallObservations": [
{ "meal": string, "description": string }
],
"eatingOutPattern": string,
"preferences": {
"drinksSugaryBeverages": boolean,
"eatsDessertRegularly": boolean,
"spicy": boolean,
"heavySalt": boolean,
"likedFoods": string[],
"dislikedFoods": string[]
}
}
對照上面第 1 點跟第 2 點,可以直接在這段 prompt 裡找到 heavySalt/spicy 的判斷規則、跟禁止複製 eatingOutRatio 的那句話,就是寫在這裡。
這幾條加起來的共通心得是:Prompt Engineering 不是一次寫對,是把每一次「AI 給的答案怪怪的」都當成一個要修的 bug,跟寫程式碼除錯的心態其實很像。Prompt 調好了,下一個問題是:AI 回的是一段話,但程式要的是一個能直接解析的物件,這中間的轉換要來想辦法處理