本日核心價值 (Core Focus): 用 Role / Task / Context / Constraints / Output contract 五段式結構,把開發者 Prompt 做成可重跑的模組;用分層 Context 取代整檔傾倒,讓模型改對檔、測對案例。
概念說明與實戰情境 (Overview)
開發者 Prompt 失敗,多半不是「寫得不夠長」,而是角色、任務、Context、約束與輸出契約混成一段口語。模型因此改錯檔、捏造不存在的介面、或交自由散文。解法是五段式模板:每段只放一種資訊,Context 只給檔案樹、公開介面與失敗測試。昨天的 JSON 計畫能穩定,靠的就是這份契約,而不是更長的聊天紀錄。
關鍵操作與範例 (Implementation & Example)
五段各有單一職責。混在一起時,模型會把「背景說明」當成「可以改的範圍」,或把「風格偏好」蓋過「測試必須過」。分開寫之後,每一段都可以獨立版本化(Day 04 會做成檔案模組)。
| 區段 | 放什麼 | 不放什麼 |
|---|---|---|
| Role | 職能、技術棧、決策權(可改 / 不可改) | 本次任務細節 |
| Task | 單一可驗證目標 | 架構哲學、職涯建議 |
| Context | 檔案樹、介面、失敗測試、錯誤訊息 | 整份 Controller / 整個 Migration |
| Constraints | 硬限制:語言、套件、SQL 方言、diff 大小 | 柔性形容詞(「盡量優雅」) |
| Output contract | Schema、禁止 markdown、失敗時的 blocked |
範例程式碼(除非是 few-shot 的輸入/輸出對) |
Context 必須分層,由小到大遞增,而不是一次把 repo 倒進去。第一層能解就不要開第二層。
| 層級 | 內容 | 何時使用 | Token 風險 |
|---|---|---|---|
| L0 任務 | 一句話目標 + 驗收指令 | 永遠 | 低 |
| L1 地圖 | tree 或目錄清單(深度 2–3) |
永遠 | 低 |
| L2 契約 | 公開介面、DTO、SQL schema 片段 | 改行為、改 API | 中 |
| L3 失敗證據 | 失敗測試全文、測試輸出、stack trace | 修 bug / 補測試 | 中 |
| L4 鄰近實作 | 目標符號前後 40–80 行 | L2+L3 仍不足 | 高 |
| L5 整檔 | 完整檔案 | 禁止當預設;僅在 L4 仍缺符號時 | 極高 |
打包 Context 用腳本,不要手貼。下面這份模板是可重用的開發者 Prompt;{{FILE_TREE}}、{{INTERFACE}}、{{FAILING_TEST}} 由腳本填入。輸出契約與 Day 01 / Day 03 的 CodeChangePlan 對齊。
ROLE
You are a backend engineer for a C# (.NET 8) + PostgreSQL service.
You may modify only files listed in the plan. You may not invent types,
endpoints, or tables that are absent from CONTEXT.
TASK
{{TASK}}
Acceptance: the command `{{TEST_COMMAND}}` exits 0.
CONTEXT
[L1 file tree]
{{FILE_TREE}}
[L2 public contract]
{{INTERFACE}}
[L3 failing test]
{{FAILING_TEST}}
CONSTRAINTS
- Smallest diff that makes the failing test pass.
- PostgreSQL dialect only. No MySQL-specific functions.
- No new dependencies.
- If TASK requires schema change, set sql_needed=true and provide SQL.
- If CONTEXT is insufficient, set blocked=true and ask questions.
Do not guess method signatures.
OUTPUT CONTRACT
Return ONLY JSON (no markdown fences) with keys:
summary, blocked, questions, files, risk, test_command, sql_needed, sql
files[].path, files[].action (create|modify|delete), files[].reason
risk is one of: low, medium, high
test_command must equal "{{TEST_COMMAND}}" unless you must change it,
in which case explain why inside summary.
用目錄清單 + 介面 + 失敗測試,通常已足夠讓模型定位。以下是一段可直接重現的 Context 打包結果(訂單折扣)。注意:沒有把 OrderService.cs 全文貼上。
[L1 file tree]
src/
Orders/OrderService.cs
Orders/IOrderService.cs
Orders/Order.cs
tests/
Orders/OrderServiceTests.cs
db/
migrations/
[L2 public contract]
public interface IOrderService
{
decimal GetTotal(Order order, decimal discountRate);
}
public sealed class Order
{
public IReadOnlyList<OrderLine> Lines { get; init; }
}
public sealed class OrderLine
{
public decimal UnitPrice { get; init; }
public int Quantity { get; init; }
}
[L3 failing test]
[Fact]
public void GetTotal_applies_discount_to_subtotal()
{
var svc = new OrderService();
var order = new Order
{
Lines = new[]
{
new OrderLine { UnitPrice = 100m, Quantity = 2 },
},
};
Assert.Equal(180m, svc.GetTotal(order, 0.10m));
}
把模板與 Context 組裝自動化,避免每次手改 Prompt。下面腳本讀 prompts/dev-plan.md(模板)與三個 Context 檔,輸出最終 user message。
from pathlib import Path
def load(path: str) -> str:
return Path(path).read_text(encoding="utf-8")
def build_prompt(task: str, test_command: str) -> str:
template = load("prompts/dev-plan.md")
return (
template.replace("{{TASK}}", task)
.replace("{{TEST_COMMAND}}", test_command)
.replace("{{FILE_TREE}}", load("context/file-tree.txt"))
.replace("{{INTERFACE}}", load("context/interface.cs"))
.replace("{{FAILING_TEST}}", load("context/failing-test.cs"))
)
if __name__ == "__main__":
print(
build_prompt(
task="Make OrderService.GetTotal apply discountRate to the line subtotal.",
test_command="dotnet test tests/Orders --filter GetTotal_applies_discount_to_subtotal",
)
)
調校順序建議:先鎖 Output contract(否則你無法自動驗),再砍 Context(只留 L1–L3),最後才改 Role 用詞。若計畫一直 blocked,補 L4 鄰近實作,而不是把整個 OrderService.cs 丟進去。若模型改到未列出的檔案,是 Constraints 不夠硬,不是模型「不夠聰明」。
L1 用 git ls-files 或固定深度目錄清單即可,不必每次手工畫樹。L2 只剪公開介面與 DTO,不要連 private method 一起貼。L3 給「失敗測試全文 + runner 最後約 30 行」;stack trace 的信號通常高過 Service 實作全文。仍 blocked 時才開 L4:用搜尋定位符號,只取前後 60 行。這個遞增策略把一次任務的 Token 變成可預算、可回歸的 diff,而不是每次重新傾倒 repo。
同一模板要能服務 C# 與測試專案:Task 只換驗收句,Context 只換 L1–L3 檔案。不要為每個 ticket 新寫一段 Role。Role 變更屬於「指令庫版本升級」,應走進 Day 04 的 prompts/,而不是複製聊天紀錄。Constraints 用否定句寫死(不准新套件、不准猜簽章、不准改無關檔),比「請盡量小心」有效。輸出契約一旦列出必填鍵,就不要在同一 Prompt 再要求「順便給一段說明散文」——那會把 JSON 再次打壞。
注意事項與常見失敗 (Pitfalls)
sql_needed 顯式宣告。cat 整檔: 失敗測試只要 15 行,卻附上 800 行 Service。修法:預設 L1+L2+L3;L4 用行號切片,禁止 L5 當預設。todos、notes。修法:列出必填鍵與 enum;Day 03 再用 JSON Schema 在 API 層強制。AGENTS.md,不要放進無限對話。本日總結 (Takeaways)
{{placeholders}},同一任務才能重跑、才能做 A/B。sql_needed,不要「順便」改 schema。明日預告 (Next)
明日進入 Day 03 結構化輸出工作流 (Structured Output):利用 JSON Schema 實現穩定 API 對接,把今天的 Output contract 升級成可驗證的 CodeChangePlan Schema,並用 OpenAI response_format + Pydantic 做 fail closed。