iT邦幫忙

2025 iThome 鐵人賽

DAY 4
0
生成式 AI

AI 產品與架構設計之旅:從 0 到 1,再到 Day 2系列 第 4

Day 4: 別讓 Prompt 變成你的技術債 - GenKit dotPrompt 的版本控制方法

  • 分享至 

  • xImage
  •  

嗨大家,我是 Debuguy。

昨天我們聊完了如何讓 LLM 知道「自己是誰」,System Prompt 的重要性。今天想來聊聊一個很實務的問題:當 Prompt 越來越複雜時,該怎麼管理?

從昨天的程式碼開始反思

我昨天寫了什麼鬼東西?

讓我們回顧一下昨天的程式碼:

const chatFlow = ai.defineFlow(
  {
    name: 'chatFlow',
    inputSchema: z.object({
      text: z.string(),
      user: z.string(),
      ts: z.string(),
    }),
  },
  async ({ user, ts, text }) => (await ai.generate({
    model: googleAI.model('gemini-2.5-flash-lite'),
    system: `
      You are a helpful AI assistant operating as a Slack bot. 
      Your Slack ID is U09BASU9P6K.
      The user who mentioned you is the one you must address in your reply.
      // ... 一大串 system prompt
    `,
    prompt: `${user}@${ts}: ${text}`,
  })).text
);

乍看之下還好,但仔細想想:

「咦?這個 System Prompt 如果要改怎麼辦?」
「如果我想測試不同的 prompt 版本呢?」 _
「模型參數寫死在程式碼裡,要調整不就要重新部署?」_

更麻煩的是,隨著功能增加,這個 system prompt 會變得越來越複雜:

// 想像一下如果變成這樣...
system: `
  You are a helpful AI assistant operating as a Slack bot. 
  Your Slack ID is ${process.env.SLACK_BOT_USER_ID}.
  Your name is ${botConfig.name}.
  You are currently in ${channel.name} channel.
  The current time is ${new Date().toISOString()}.
  Available tools: ${availableTools.join(', ')}.
  Company name: ${companyConfig.name}.
  Working hours: ${companyConfig.workingHours}.
  Today's weather: ${weatherData.summary}.
  // ... 還有更多變數
`,

「天啊,如果有這麼多變數需要放進去,程式碼會充滿 ${var} 的字串插值!」
「更慘的是,如果我要 A/B 測試不同的 prompt,難道要寫兩套程式碼?」

突然意識到,我把 Prompt 直接寫在程式碼裡,這不就是Hard Code 的一種嗎?

Prompt 也需要版本控制的三個理由

理由 1: Prompt 比程式碼更常變動

程式碼的邏輯相對穩定,但 Prompt 需要不斷調整:

  • 語氣調整:太正式?太隨便?
  • 功能擴充:新增指令、新增限制
  • 效果優化:發現某些表達方式效果更好
  • Bug 修復:發現某些場景下會產生奇怪的回應

我光是昨天的 System Prompt 就已經改了至少 5 版:

v1: "You are a helpful AI assistant."
v2: "You are a helpful AI assistant operating as a Slack bot."
v3: "You are a helpful AI assistant operating as a Slack bot. Your Slack ID is U09BASU9P6K."
v4: "...加上互動規則..."
v5: "...加上 Response Style..."

理由 2: 需要快速回滾能力

「天啊,新版本的 prompt 讓 Bot 變得很奇怪,快退回上一版!」

如果 Prompt 寫死在程式碼裡,回滾就意味著:

  1. 找到之前的 prompt 版本(通常散落在 commit history 中)
  2. 修改程式碼
  3. 重新部署
  4. 禱告不要影響其他功能

這個流程至少要 20-30 分鐘,但如果 Prompt 是獨立管理的,rollback 可能只需要幾分鐘。

理由 3: 團隊協作的必要

當團隊裡有多個人在優化 Prompt 時:

  • 產品經理想調整語氣和互動風格
  • UX 設計師想優化對話流程
  • 工程師需要添加技術約束和錯誤處理
  • 測試人員發現了 edge case 需要在 prompt 中處理

如果 Prompt 寫在程式碼裡,每個人都需要懂程式碼才能參與優化,這會大大降低協作效率。

不只是 Prompt,整套配置都需要版本控制

我最初的盲點

一開始,我以為「Prompt 版本控制」就是把 System Prompt 的文字另外存起來。

但實際開發後發現,需要版本控制的不只是文字內容,還包括:

1. 模型選擇

model: googleai/gemini-2.5-flash-lite  # 還是 gemini-2.5-pro?

2. 模型參數

config:
  temperature: 0.2
  topP: 0.95
  topK: 30

3. 其他配置

config:
  thinkingConfig:
    includeThoughts: true  # 要不要顯示思考過程?
    thinkingBudget: -1     # 思考的預算限制

這些參數的任何一個改變,都會影響 LLM 的行為表現。如果沒有一起做版本控制,你永遠不知道某次的改進是因為 Prompt 調整,還是參數的調整。

GenKit dotPrompt 拯救了我

什麼是 dotPrompt?

GenKit 的 dotPrompt 是一個很棒的設計,它把所有相關的配置都放在一個檔案裡:

---
model: googleai/gemini-2.5-flash-lite
config:
  temperature: 0.2
  topP: 0.95
  topK: 30
  thinkingConfig:
    includeThoughts: true
    thinkingBudget: -1
input:
  schema:
    botUserId: string
    prompt: string
---
{{role "system"}}
You are a helpful AI assistant operating as a Slack bot. 
Your Slack ID is {{botUserId}}.
// ... rest of system prompt

{{role "user"}}
{{prompt}}

這個設計的優雅之處:

  1. 一個檔案包含所有配置:不用在多個地方找設定
  2. 版本控制友善:文字檔案,Git 可以完美處理

程式碼變得超簡潔

原本的複雜程式碼:

const chatFlow = ai.defineFlow(
  {
    name: 'chatFlow',
    inputSchema: z.object({
      text: z.string(),
      user: z.string(),
      ts: z.string(),
    }),
  },
  async ({ user, ts, text }) => (await ai.generate({
    model: googleAI.model('gemini-2.5-flash-lite'),
    system: `
      You are a helpful AI assistant operating as a Slack bot. 
      Your Slack ID is U09BASU9P6K.
      The user who mentioned you is the one you must address in your reply.
      // ... 一大串 system prompt
    `,
    prompt: `${user}@${ts}: ${text}`,
  })).text
);

變成了優雅的新版本:

const chatFlow = ai.defineFlow(
  {
    name: 'chatFlow',
    inputSchema: z.object({
      text: z.string(),
      user: z.string(),
      ts: z.string(),
    }),
  },
  async ({ user, ts, text }) => (await ai.prompt('chatbot')({
	botUserId: process.env['SLACK_BOT_USER_ID']!,
	prompt: `${user}@${ts}: ${text}`,
  })).text
);

差異立見:

  • 配置外部化:不用修改程式碼就能調整行為
  • 可讀性提升:邏輯清晰,職責分離

dotPrompt 的其他驚喜功能

Role Area

可以利用 {{role "system"}} {{role "user"}} 來定義區塊

Handlebars

dotPrompt 採用了 Handlebars 樣板語言,所以想要寫 condition, loop 都不是問題

條件邏輯範例

{{#if isLoggedIn}}
  歡迎回來,{{username}}!
{{else}}
  請先登入
{{/if}}

{{#unless isGuest}}
  你有 {{messageCount}} 則未讀訊息
{{/unless}}

迴圈範例

購物清單:
{{#each items}}
  {{@index}}. {{name}} - ${{price}}
{{/each}}

團隊成員:
{{#each team}}
  - {{name}} ({{role}})
  {{#if isActive}}✅ 在線中{{else}}⏸️ 離線{{/if}}
{{/each}}

其他

{{#with user}}
  姓名:{{firstName}} {{lastName}}
  Email:{{email}}
{{/with}}

這些基本的 Handlebars 語法在 dotPrompt 中都可以使用,讓你的 prompt 變得更加動態和靈活!

GenKit Developer UI

GenKit 提供了一個超好用的 Development UI,讓 prompt tuning 變得像玩遊戲一樣簡單!

啟動 Developer UI

原本啟動的 command 是這樣的話

bun run --watch src/index.ts

只需要改成

genkit start -- bun run --watch src/index.ts

開啟瀏覽器的 http://localhost:4000,你會看到一個漂亮的介面。

用 Dev UI 進行 Prompt Tuning

1. 測試不同的 Prompt 版本

  • 即時預覽 prompt 的渲染結果
  • 不用重新啟動服務就能測試新版本

2. 調整模型參數

  • 用滑桿調整 temperature、topP、topK
  • 即時看到參數變化對回應的影響
  • 找到最適合的參數組合

用 Dev UI 進行 Flow Tuning

1. 視覺化 Flow 執行流程

  • 清楚看到 Flow 中每個步驟的執行順序
  • 檢視每個步驟的輸入和輸出資料
  • 了解資料在整個 Flow 中的轉換過程

2. 即時測試 Flow

  • 在 UI 中直接輸入測試資料
  • 一鍵執行整個 Flow
  • 即時查看執行結果和中間狀態

3. Flow 執行歷史

  • 查看所有執行過的 Flow 記錄
  • 比較不同執行結果的差異
  • 追蹤 Flow 的效能表現

小結:Prompt 管理的最佳實踐

經過這段時間的實驗,我得出幾個 Prompt 管理的最佳實踐:

1. 配置外部化

  • 永遠不要把 prompt 寫死在程式碼裡
  • 模型參數也要外部化,方便調整

2. 版本控制一切

  • Prompt 文字、模型選擇、參數配置都要版本控制
  • 使用有意義的版本號和 commit message

3. 小步快跑

  • 每次只調整一個變數
  • 記錄每次調整的原因和期望效果
  • 量化比較結果(回應品質、token 使用量、使用者滿意度)

4. 隨時記錄

既使沒有要做 prompt / flow tuning 都要用 genkit start 作為啟動指令
在我的範例程式碼中 bun run dev 就是如此
原因是因為 genkit 會幫我們記錄所有的執行 history
所以當我們需要 tuning 的時候
直接打開 Developer UI 就可以用相同的 input 來做回歸測試
這樣一來就不用一直從 Slack trigger 來測試結果

5. 最後

Prompt 可能看起來只是「一些文字」,但實際上它是 AI 產品的核心邏輯。用管理程式碼的標準來管理 Prompt,你的 AI 產品才能健康成長。

今天我們聊完工具
明天我們來回到產品面:如何從單輪對話升級到多輪對話或是持續對話,這是另一個從「工具」到「夥伴」的重要轉換點。


完整的原始碼在這裡,有興趣的人可以下載玩玩看!


AI 的發展變化很快,目前這個想法以及專案也還在實驗中。但也許透過這個過程大家可以有一些經驗和想法互相交流,歡迎大家追蹤這個系列。

也歡迎追蹤我的 Threads @debuguy.dev


上一篇
Day 3: 讓 LLM 知道「我是誰」- Slack ChatBot 的 System Prompt 設計哲學
系列文
AI 產品與架構設計之旅:從 0 到 1,再到 Day 24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言