iT邦幫忙

2026 iThome 鐵人賽

DAY 9
0
Build on Google AI

單鐵的人生如履薄冰!AI 教練 APP 30天開發旅程,你說能走到最後嗎?系列 第 9

Day 9 | Prompt Engineering 實戰:設計能穩定產出 JSON 格式「馬拉松課表」的 Prompt 範本

  • 分享至 

  • xImage
  •  

前言:從靜態工具到真正的 AI 專屬教練

經過前幾天的努力,我們的 Spring Boot 終於能跟 Gemini 順暢對話,並且擁有 Firestore 的記憶力了。但身為一個專業的馬拉松教練,Kakeru不能只記住你的名字,而是要能提出專業的知識,光靠單次生成與Hardcode 的 Prompt 是不夠的。

真實的訓練往往充滿變數:學員剛報名時可能連賽事時間或個人跑量都說不清;訓練過程中可能因為連續加班或肌肉痠痛無法吃下原訂計畫;此外,跑者最愛看的就是配速與心率數據截圖。

因此,今天我們將利用 Markdown (.md) 範本化 集中管理提示詞,一次實作三大核心情境:4 週月週期生成(含互動問診)4 週動態微調數據圖表視覺分析,並結合 Gemini 多模型分流與 HTTP 429 自動降級 (Quota Fallback) 機制,讓前後端對接絕對穩定!

觀念解說 1:Prompt 不落地與降級容錯 (Fallback) 設計

複雜的系統提示詞 (System Prompt) 如果 Hardcode 寫死在 Java 程式碼裡,不僅難以閱讀,微調邏輯時還得重新編譯部署。

我們將提示詞獨立為 Markdown (.md) 範本存於 classpath:prompts/ 中。比起 .txt.md 的 # Header 與區塊標籤更容易讓 Gemini 等大語言模型理解語意階層:

@Value("classpath:prompts/generate-marathon.md")
private Resource generatePromptResource;
@Value("classpath:prompts/adjust-schedule.md")
private Resource adjustPromptResource;
@Value("classpath:prompts/analyze-chart.md")
private Resource analyzePromptResource;

觀念解說 2:多模型分流與 HTTP 429 (Quota Limit) 自動降級

先前我們使用gemini-3.5-flash-lite,但在各種測試下,用單一模型的用量似乎過多,因此可以的話有另一個模型來作為緩衝我覺得是必要的選擇。但免費或付費 API 在尖峰時刻常遇到 HTTP 429 (Too Many Requests / Quota Exceeded) 限流。我們設計了雙模型備援機制:

  1. 核心業務(生成/調整 4 週課表、分析圖表):預設呼叫模型 gemini-flash-lite-latest。若觸發 429 或 503 額度限制,系統會印出 Log 警告並自動重試切換輕量模型 gemini-3.5-flash-lite
  2. 一般聊天與追問 (/chat):直接預設存取輕量模型 gemini-flash-lite-latest 節省額度。
# application.yml
gemini:
  api:
    key: ${sm://projects/kakeru-ai/secrets/GEMINI_API_KEY/versions/latest}
    base-url: https://generativelanguage.googleapis.com/v1beta/models
    primary-model: gemini-flash-lite-latest
    chat-model: gemini-3.5-flash-lite

動手實作 1:三大核心 Prompt 範本設計

我們要在發送請求時,利用 API 的 responseMimeType: "application/json" 參數,搭配以下精準的 Prompt 範本:

情境一:4 週月課表生成與問診評估 (generate-marathon.md)

不盲目給予課表,而是規劃 4 週週期(基礎適應 ➔ 跑量提升 ➔ 強度峰值 ➔ 減量恢復,共 28 天)。當學員輸入資訊不足時,教練會主動問診。

# 角色定義
你是一位擁有 20 年經驗的專業馬拉松教練。請評估以下學員資料:
- 目標賽事:{{TARGET_RACE}}
- 目前體能:{{CURRENT_LEVEL}}

## 評估與問診邏輯
1. **資訊不足**:若學員輸入的資料過於簡略(缺少目標時間、每週可練天數或跑量),請將 `status` 設為 `"NEEDS_CLARIFICATION"`,並在 `inquiryQuestions` 中列出 2-4 個具體問診追問。
2. **資訊充足**:若資料充足,請將 `status` 設為 `"READY"`,並規劃為期 4 週(第 1 週基礎適應、第 2 週跑量提升、第 3 週強度峰值、第 4 週減量恢復,共 28 天)的週期訓練計劃。

## 嚴格輸出限制
必須輸出為 JSON 物件,屬性包含:
- `status`: ("READY" 或 "NEEDS_CLARIFICATION")
- `coachAdvice`: 給學員的教練整體評估與提醒
- `inquiryQuestions`: 追問問題陣列(status 為 NEEDS_CLARIFICATION 時填寫,否則為空陣列)
- `schedule`: 課表陣列(包含 `weekNumber` (1-4), `dayOfWeek` (1-7), `workoutType`, `distanceKm`, `description`)

情境二:4 週月課表動態調整 (adjust-schedule.md)

當學員遭遇突發狀況時觸發,餵入原訂 4 週課表與目前進度(CURRENT_WEEKCURRENT_DAY),重新滾動規劃本月剩餘訓練。

# 角色定義
你是一位專業馬拉松教練。學員本月遭遇了突發狀況,無法完全執行原訂 4 週課表。
請根據以下資訊,重新規劃本月剩餘天數與週次的訓練,優先考慮運動修復與避免受傷。

## 輸入資訊
- **原始本月 4 週課表**:{{ORIGINAL_SCHEDULE_JSON}}
- **突發狀況描述**:{{USER_FEEDBACK}}
- **目前進度**:第 {{CURRENT_WEEK}} 週,星期 {{CURRENT_DAY}}

## 嚴格輸出限制
必須輸出為 JSON 物件,屬性包含:
- `adjustmentReason`: 給學員的鼓勵與調整原因說明
- `revisedSchedule`: JSON 陣列,格式同基礎課表,包含 `weekNumber` (1-4), `dayOfWeek` (1-7), `workoutType`, `distanceKm`, `description`,僅包含本月剩餘天數
- `recoveryAdvice`: 針對該突發狀況的物理或營養修復建議

情境三:數據圖表視覺分析 (analyze-chart.md)

Gemini 多模態視覺診斷,觀察配速與心率趨勢。

# 角色定義
你是一位精通數據分析的跑步教練。使用者上傳了一張近期的跑步數據截圖(包含配速、心率或步頻)。
請仔細觀察圖表中的趨勢與波動,給予專業的數據診斷。

## 嚴格輸出限制
必須輸出為 JSON 物件,屬性包含:
- `detectedMetrics`: 字串陣列,例如 `["心率飄移", "配速穩定", "步頻過低"]`
- `performanceAnalysis`: 針對圖表趨勢的詳細分析
- `actionableAdvice`: 針對下一次訓練的具體改進建議

動手實作 2:Spring Boot 實作多模態、JSON 鎖定與 Session 記憶整合

在實作層 MarathonCoachServiceImpl 中,實作超額捕獲與降級重試:

private String callGeminiApiWithFallback(List<GeminiRequest.Part> parts) {
    try {
        log.info("[Gemini Model] 嘗試呼叫主要模型: {}", primaryModel);
        return executeCall(primaryModel, parts);
    } catch (org.springframework.web.client.RestClientResponseException e) {
        int statusCode = e.getStatusCode().value();
        if (statusCode == 429 || statusCode == 503 || isQuotaExceededError(e.getResponseBodyAsString())) {
            log.warn("[Gemini Fallback] 主要模型 ({}) 遭遇次數超額 [HTTP {}], 自動降級嘗試次要模型 ({})", primaryModel, statusCode, chatModel);
            return executeCall(chatModel, parts);
        }
        throw e;
    }
}

各個 API 端點總覽與追問機制

我們將端點收攏於Controller層 MarathonCoachController (/*),前端統一以此進行互動:

HTTP Method Endpoint 說明
POST /generate 4 週月課表生成與問診評估(帶入 sessionId 自動存入記憶)
POST /adjust 4 週月課表動態微調(帶入 currentWeekcurrentDay
POST /analyze-chart 數據圖表視覺分析(支援 Multipart 與 Base64,相容 file / base64Image key)
POST /chat 結合同一 sessionId 之多輪追問與對話

今日總結與明日預告

今天完成了 Prompt 的 Markdown 模組化解耦,並打造了高可用的馬拉松教練:從 4 週週期問診月課表動態微調多模態圖表診斷 到 Gemini HTTP 429 超額自動降級備援,極大化了系統韌性。

明天(Day 10),我們將針對整體 API 進行進一步的容錯與後備 (Fallback) 機制優化,確保就算在全線 AI 服務暫時不可用時,也能回傳預設結構化保底數據,敬請期待!


上一篇
Day 8 | 賦予 AI 記憶力:Spring Boot 實作多輪對話的上下文管理
下一篇
Day 10 | 錯誤處理與防禦性設計:API 重試策略、AI 護欄與實戰除錯全紀錄
系列文
單鐵的人生如履薄冰!AI 教練 APP 30天開發旅程,你說能走到最後嗎?13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言