昨天我們聊過 LLM 為什麼容易「幻覺」,甚至一本正經地說出錯誤。那我們要怎麼辦?
最快速的方式就是寫出好提示詞,也就是一直都很夯的提示工程。
提示工程各家教法其實大同小異,這邊比較推Google 在 Prompting Essentials 的教學課程,課程中提供一組簡潔的五步法,這五步是 Task / Context / References / Evaluate / Iterate
,口訣是「深思熟慮地創造優質輸入」(Thoughtfully Create Really Excellent Inputs)。
Task(任務):大多數幻覺其實源自「輸入太模糊」。對 LLM 來說,如果你只說「幫我寫程式」,它會根據訓練語料庫裡「最常見的程式」來湊答案,結果不是 Python 就是 JavaScript,而且完全不會管你的真實需求。 這就像工程團隊裡沒有 API contract 的情況:只丟一句「幫我做會員系統」,開發出來可能是 SOAP API、REST API 或 GraphQL,完全對不上。
這就像工程團隊裡沒有 API contract 的情況:只丟一句「幫我做會員系統」,開發出來可能是 SOAP API、REST API 或 GraphQL,完全對不上。
所以 Task 的核心是要「定錨」
,告訴模型:
這些都會直接影響模型在「語料搜尋空間」中的選擇範圍,越清楚,幻覺機率越低。
[角色] 你是資深後端工程師(Java 17 / Spring Boot 3.3)。
[任務] 實作一支查詢使用者 API:
- 方法與路由:GET /api/v1/users/{id}
- 需求:回傳指定 id 的基本資料,找不到要回 RFC 7807 問題細節
- 相依:PostgreSQL、Spring Web、Spring Validation、springdoc-openapi
[輸入約束]
- path variable id:UUID 格式(如 8-4-4-4-12)
- 接受 Header:X-Request-Id(若缺省,自動生成)
[輸出格式]
- 200:application/json,欄位 { id, name, email }
- 404:application/problem+json(RFC 7807),欄位 { type,title,status,detail,instance }
- 422:同上,detail 說明 id 格式錯誤
[非功能性]
- 程式碼風格:Controller→Service→Repository 三層
- 測試覆蓋:對 Service 寫 3 組單元測試(成功/不存在/無效UUID)
[交付]
1) Controller/Service 介面與骨架程式碼
2) 1 份 OpenAPI YAML(/v3/api-docs 可導出)
3) 測試案例名稱清楚標明情境
Context(背景):沒有背景的提示,就像把新人丟進陌生專案卻不講需求。模型不知道「受眾是誰」「語氣要怎樣」「應用情境在哪裡」,它只能輸出「一般」的答案。而上下文的價值在於:
這對應到昨天提到的「上下文衰減」問題:重要的規則要在前面就給出,否則模型可能忽略。
在工程世界裡,Context 就像 需求文件的使用情境(Use Case / User Story),缺少它,輸出永遠對不齊 stakeholder 的期待。
[角色] 你是帶新人入門的後端教練。
[讀者] 剛入職 3 個月的 Java 新人,懂基本語法,但不了解 Spring Boot 架構。
[任務] 以教學筆記解說下列 Service 程式碼的設計意圖與常見錯誤。
[呈現格式]
- 1 段「這段程式在解決什麼問題」
- 3 條「關鍵設計決策」(每條 ≤ 40 字)
- 1 段「新手常犯錯」+對應修正
- 1 小段「延伸閱讀」:列 2 個關鍵字(如 “Transactional boundary”, “DTO vs Entity”)
[語氣] 親和、避免術語堆砌,必要術語加括號解釋。
[篇幅] 300–450 字。
[素材] (貼上你的程式碼片段)
References(參考資料):讓模型知道「只能用這些知識回答」。就像你把設計 ERD 丟進去,AI 就不敢亂幫你加「password」欄位;如果你沒給,它就會照過往訓練資料隨機編。
這和昨天講的 RAG Recall 不足完全呼應:模型不是故意亂掰,而是「找不到正確依據,只好硬湊」。最重要的是 「禁止臆測條款」:明確告訴模型「沒有參考資料就回答『無資料』」,這能直接砍掉大部分幻覺。
實務對照:
API 設計 → 就像 Swagger 定義先行,程式照 schema 走。
DB schema → 若不提供欄位設計,AI 可能隨便補一個「last_login_time」。
文件生成 → 附上官方 spec,避免 AI 胡亂創建不存在的參數。
❌ 壞例子:請幫我設計資料庫表格
✅ 好例子:以下是 ERD,請根據這張圖生成對應的 PostgreSQL CREATE TABLE 語句
[角色] 你是資深資料庫設計師(PostgreSQL 15)。
[任務] 根據以下 ERD 產生 CREATE TABLE 語句,並加上必要索引與外鍵約束。
[ERD 摘要] (貼上關鍵欄位/關聯;若很長,請先附你整理的重點表)
[設計規則]
- 命名:表 snake_case,單數名詞;主鍵 {table}_id
- 時間:全部使用 timestamptz;欄位 created_at, updated_at
- 文字:email 用 citext;大量文字用 text
- 外鍵:on delete restrict,除非註明 cascade
- 索引:對查詢條件與外鍵欄位建立 btree 索引;email 加 unique
- 禁止臆測:若 ERD 無資訊,輸出“無資料”,不要補創任何欄位
[輸出格式]
- 以多段 SQL 區塊呈現,每段前加註解說明該表用途
- 最後輸出「核對清單」:列出 1) 外鍵完整性 2) 索引覆蓋 3) 命名一致性 是否滿足(是/否)
[素材] (貼 ERD/欄位表)
Evaluate(評估):很多人卡在「AI 輸出看起來很合理」,就直接拿去用,結果踩坑。這跟工程實務一樣:只看程式跑得動,不代表它就正確、可維護。Evaluate 的重點在於:
這等於幫模型多加一層 自我 QA,也是把昨天講到的「自信但錯誤」反過來利用:讓它先替自己挑毛病。工程比喻來看:Evaluate 就是 Code Review + 測試驗收,沒有這一步,AI 的產出只能算是「未審核的原始碼」。
[角色] 你是測試工程師(JUnit5/AssertJ)。
[任務] 針對 UserService.findById(UUID) 產生單元測試。
[覆蓋需求]
- 成功:存在的 id 回傳正確 User
- 邊界:不存在 id → 拋 NotFoundException,訊息含該 id
- 無效:非 UUID 格式 → 拋 IllegalArgumentException
- 性能:方法耗時 < 50ms(使用 System.nanoTime 粗測)
[輸出格式]
- 測試方法清單(方法名、Arrange/Act/Assert 摘要)
- 關鍵斷言片段(只貼必要 5–10 行)
- 自我檢查:列出可能缺漏的 2 項測試並說明為何未納入
[輸入資料] 使用 Fakes,不連資料庫
Iterate(迭代):很多人用 AI 的方式還停留在「一次丟一大坨,結果不滿意就重來」。這其實效率極低。
正確的做法是 差分式迭代:
你的 OpenAPI YAML 缺少 422 錯誤碼
)僅修正錯誤碼部分,其他保持不變
)若輸出不符合 RFC 7807 schema,請自行再修正一次
)這樣模型會在「上一版」基礎上改,而不是每次重新生成。
這正好呼應昨天提到的「解碼策略不穩定」問題:透過迭代,你能用人為 feedback loop 去強化模型的穩定度。工程比喻來看:這就像 CI/CD Pipeline → 每次提交小修小補,跑過驗收,再往下一步,而不是推倒重建。
[情境] 你上次輸出的 OpenAPI YAML 有 3 個問題:
1) 缺少 422 Unprocessable Entity 的 schema
2) 路由 /api/v1/users/{id} 少了 path 參數格式範例
3) 描述文字過長(>120 字)
[任務] 僅針對上述 3 點修正,其他內容保持不變(差分編輯)。
[評分規準]
- 正確性:是否包含 422 且 schema 符合 RFC 7807(0/1)
- 完整性:path 參數是否附 UUID 範例(0/1)
- 可讀性:每段描述 ≤ 120 字(0/1)
→ 得分未達 3/3 時,自行再迭代一次並附「修正說明」
[輸出格式]
- 只輸出修正後的 YAML 區塊
- 最後附上「本次修正說明」3 行內
稍微 Summry