iT邦幫忙

2026 iThome 鐵人賽

DAY 2
0
ChatGPT & Codex

ChatGPT + Codex 打造高效能 AI 開發工作流系列 第 2

Day 02: Prompt Engineering 工作流:給開發者的精準 Prompt 調校與 Context 設計

  • 分享至 

  • xImage
  •  

Day 02: Prompt Engineering 工作流:給開發者的精準 Prompt 調校與 Context 設計 (Prompt Engineering Workflow)

本日核心價值 (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)

  • 把 Role 寫成人格劇本: 「你是熱愛整潔程式碼的大師」會誘發無關 Refactoring。修法:Role 只寫技術棧與權限邊界(可改哪些層、不可 invent 類型)。
  • Task 一次塞三件事: 「修折扣、順便加 logging、再寫 Migration」。模型會產出高風險大 diff。修法:一個 Prompt 一個驗收指令;SQL 用 sql_needed 顯式宣告。
  • Context 用 cat 整檔: 失敗測試只要 15 行,卻附上 800 行 Service。修法:預設 L1+L2+L3;L4 用行號切片,禁止 L5 當預設。
  • Output contract 只寫「用 JSON」: 沒有欄位表,模型會自創 todosnotes。修法:列出必填鍵與 enum;Day 03 再用 JSON Schema 在 API 層強制。
  • 用聊天歷史當 Context: 舊回合的錯誤介面會污染新任務。修法:每任務用腳本重建 Prompt;需要記憶的規則放進即將在 Day 05 介紹的 AGENTS.md,不要放進無限對話。

本日總結 (Takeaways)

  • 開發者 Prompt 固定五段:Role / Task / Context / Constraints / Output contract。
  • Context 分 L0–L5,預設只給檔案樹、介面、失敗測試,不要整檔傾倒。
  • 用腳本填 {{placeholders}},同一任務才能重跑、才能做 A/B。
  • 先鎖輸出契約,再減 Context,最後才改語氣;調校要可觀測。
  • 一個 Task 對一個測試指令;需要 SQL 就走 sql_needed,不要「順便」改 schema。

明日預告 (Next)
明日進入 Day 03 結構化輸出工作流 (Structured Output):利用 JSON Schema 實現穩定 API 對接,把今天的 Output contract 升級成可驗證的 CodeChangePlan Schema,並用 OpenAI response_format + Pydantic 做 fail closed。


上一篇
Day 01: 序章:從純聊天到工作流 (Workflow)——ChatGPT & Codex 帶來的開發範式轉移
下一篇
Day 03: 結構化輸出工作流 (Structured Output):利用 JSON Schema 實現穩定 API 對接
系列文
ChatGPT + Codex 打造高效能 AI 開發工作流3
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言