iT邦幫忙

2026 iThome 鐵人賽

DAY 19
0
Build on Google AI

今天學什麼?30 天用 Google AI 打造智慧學習卡系列 第 19 篇

Day 19|API 接通了,但 Prompt 太簡單:把學習條件整理成 Prompt Template

  • 分享至 

  • xImage
  •  

昨天終於把 Gemini API 接進 Learning Card App。

從表單輸入:

學習對象
學習主題
卡片數量

按下「產生學習卡」後,資料會經過 Server 送到 Gemini,再把產生結果帶回 React。

整條流程已經可以跑通:

React Form
    ↓
Form Data
    ↓
Server
    ↓
Gemini API
    ↓
Gemini Response

不過昨天測試兩組不同條件時,我發現了一個新的問題。

API 是接通了,但我的 Prompt 好像有點太簡單了。


昨天的 Prompt 到底有多簡單?

Day 18 使用的 Prompt 基本上只有一句:

請幫我產生適合 {ageGroup} 的『{topic}』學習卡,共 {cardCount} 張。

例如表單選擇:

{
  "ageGroup": "3~4 歲幼兒",
  "topic": "日常好習慣",
  "cardCount": 7
}

就會變成:

請幫我產生適合 3~4 歲幼兒 的『日常好習慣』學習卡,共 7 張。

Gemini 的確看得懂。

它也真的產生了 7 張「日常好習慣」學習卡。

問題是,我只告訴 Gemini:

給誰、什麼主題、要幾張。

至於「一張 Learning Card 到底應該長什麼樣子」,幾乎全部交給 Gemini 自己決定。


同樣叫 Learning Card,格式卻完全不一樣

昨天第一次測試:

7~8 歲兒童
浩瀚太空
3 張

Gemini 自己設計出:

卡片主題
主要顏色
卡片插畫建議
學習內容
你知道嗎?
動動腦時間

第二次改成:

3~4 歲幼兒
日常好習慣
7 張

格式又變成:

卡片名稱
說明文字
情境圖示建議

最後甚至還自己補了一段「使用建議」。

兩次回答其實都沒有錯。

因為我根本沒有告訴 Gemini Learning Card 應該有哪些欄位。

換個角度想,這就像只跟前端工程師說:

幫我做一個學習卡頁面。

然後完全沒有 Design、Spec、資料格式。

最後做出來跟想像不一樣,好像也不能怪他(笑)。

所以今天的目標,就是把這句過度簡單的 Prompt 整理得更完整。


今天不是 Structured Output

前面 Day 09~Day 12 已經實驗過 Structured Output,也知道可以透過 Schema 限制 Gemini 的輸出結構。

不過今天先不急著把它接回來。

因為我想先看看:

如果只把 Prompt 寫得更清楚,Gemini 的 Response 會有什麼變化?

所以今天仍然維持:

Gemini Response = Text

不使用:

Structured Output
JSON Schema
application/json
LearningCard[]

先單純處理 Prompt。


把 Prompt 也當成程式的一部分

Day 18 的 Prompt 還比較像 API 裡的一段字串。

但隨著條件越來越多,如果繼續全部寫在 API 邏輯裡,很快就會變成一大坨不好維護的文字。

所以這次我請 Gemini 幫我建立獨立的 Prompt Builder:

buildLearningCardPrompt({
  ageGroup,
  topic,
  cardCount
})

並把 Prompt 集中管理。

這次使用 Gemini 3.8 Flash,執行約 51 秒,總共修改 3 個檔案:

src/prompts/learningCardPrompt.ts
server.ts
src/components/CardGeneratorForm.tsx

其中最重要的是新增:

src/prompts/learningCardPrompt.ts

也就是把「怎麼跟 Gemini 說話」從其他程式邏輯中抽出來。


新的 Prompt Template

這次不再只給 Gemini 一句話,而是把 Prompt 分成幾個部分。

首先定義角色:

角色:
你是一位協助製作學習教材的 AI。

接著放入來自表單的動態條件:

學習條件:

- 學習對象:{ageGroup}
- 學習主題:{topic}
- 卡片數量:{cardCount}
- 語言:繁體中文

再明確告訴 Gemini,每張 Learning Card 希望有哪些內容:

每張 Learning Card 需要包含:

- Emoji
- Title
- Content
- Question
- Answer

最後再加入內容規則:

內容要求:

- 內容符合指定學習對象的理解程度
- 使用簡單、清楚的繁體中文
- 每張卡片只介紹一個主要概念
- Question 必須能從 Content 找到答案
- Answer 請簡短回答 Question
- 不要加入上述欄位以外的額外欄位
- 不要加入額外的使用說明或教學建議

整體概念就變成:

Prompt Template
│
├─ 固定規則
│   ├─ Role
│   ├─ Language
│   ├─ Fields
│   └─ Content Rules
│
└─ 動態資料
    ├─ ageGroup
    ├─ topic
    └─ cardCount

這樣之後如果想調整 Learning Card 的生成規則,就不需要一直去 API 呼叫邏輯裡找那一長串 Prompt。

Day19 new prompt


Form Data 真的有進入 Prompt Template 嗎?

改完之後,當然還是要實際測試。

這次 Console 也把完整流程印了出來。

例如我選擇:

{
  "ageGroup": "3~4 歲幼兒",
  "topic": "日常好習慣",
  "cardCount": 7
}

Client 最後送出的 Prompt 已經變成:

角色:
你是一位協助製作學習教材的 AI。

學習條件:

- 學習對象:3~4 歲幼兒
- 學習主題:日常好習慣
- 卡片數量:7
- 語言:繁體中文

每張 Learning Card 需要包含:

- Emoji
- Title
- Content
- Question
- Answer

內容要求:

- 內容符合指定學習對象的理解程度
- 使用簡單、清楚的繁體中文
- 每張卡片只介紹一個主要概念
- Question 必須能從 Content 找到答案
- Answer 請簡短回答 Question
- 不要加入上述欄位以外的額外欄位
- 不要加入額外的使用說明或教學建議

請依照 cardCount 產生指定數量的 Learning Card。

Server 收到後,再把這份 Prompt 送給:

gemini-2.5-flash

所以現在的資料流程變成:

Form Data
    ↓
buildLearningCardPrompt()
    ↓
Prompt Template
    ↓
Server
    ↓
Gemini API
    ↓
Text Response

Test A:7~8 歲+浩瀚太空+3 張

為了跟昨天比較,我先使用完全相同的測試條件:

學習對象:7~8 歲兒童
學習主題:浩瀚太空
卡片數量:3 張

這次 Gemini 真的產生了 3 張:

✨ 浩瀚的太空
🪐 神秘的行星
🚀 太空探險家

而且每一張都按照:

Emoji
Title
Content
Question
Answer

例如第一張:

Emoji: ✨
Title: 浩瀚的太空
Content: 太空是個超級大、超級廣闊的地方,裡面有好多亮晶晶的星星,還有地球、月亮和太陽喔!白天我們看不到星星,但到了晚上,如果天氣好,就能看到好多美麗的星星在閃爍呢!
Question: 晚上如果天氣好,我們能看到太空裡的什麼呢?
Answer: 亮晶晶的星星。

跟 Day 18 相比差異非常明顯。

昨天 Gemini 自己增加的:

主要顏色
插畫建議
你知道嗎?
動動腦時間

這次都沒有出現。

Day19 testa 7-8 space 3 cards 1


Content、Question、Answer 也開始有關係了

這次不只是欄位名稱比較整齊。

Prompt Template 裡還特別要求:

Question 必須能從 Content 找到答案

例如第二張:

Content:
太陽系總共有八顆大行星

Question:
太陽系裡總共有幾顆大行星?

Answer:
八顆。

第三張則是:

Content:
太空人就是搭乘火箭到太空去探險

Question:
太空人是搭乘什麼到太空去探險的?

Answer:
火箭。

至少這一次測試中,Gemini 不只是照著欄位名稱填內容,連欄位之間的關係也有遵守 Prompt 裡的要求。

Day19 7-8 space 3 cards 2


Test B:3~4 歲+日常好習慣+7 張

接著再測一次昨天的另一組條件:

學習對象:3~4 歲幼兒
學習主題:日常好習慣
卡片數量:7 張

Server 最後成功取得 Response:

[Server] 模型 gemini-2.5-flash 回應成功,字數: 1272

而 Gemini 真的產生了 7 張:

🧼 飯前飯後洗洗手
🦷 刷牙亮晶晶
🍎 吃飯不挑食
😴 乖乖上床睡覺囉
🧸 玩具收好好
🙏 有禮貌說「請」和「謝謝」
☀️🌙 見面打招呼

每張同樣維持:

Emoji
Title
Content
Question
Answer

而 Day 18 曾經出現的「情境圖示建議」和最後額外附上的「使用建議」,這次也沒有再出現。

Day19 3-4 hobby 7 cards 1

Day19 3-4 hobby 7 cards 2


年齡不同,內容也真的有變

這次還有另一個滿明顯的差異。

7~8 歲的「浩瀚太空」會出現:

太陽系
行星
太空人
太空船

到了 3~4 歲的「日常好習慣」,語句則變成:

小手髒髒有細菌

搓搓泡泡,沖沖水

吃了飯飯

身體長大大

幫玩具找家家

雖然只測了兩組資料,還不能因此說 Gemini 每次都一定會完美掌握年齡差異,但至少在這兩次實驗裡,可以看到 ageGroup 不只是出現在 Prompt 裡,生成內容的用詞也跟著發生變化。

這正好對應到 Prompt Template 裡的要求:

內容符合指定學習對象的理解程度
使用簡單、清楚的繁體中文

Prompt 寫清楚,就完全不會跑掉了嗎?

看起來好像已經很完美。

但仔細看第二次 Response,還是可以找到一個有趣的小地方:

Emoji: 🧼
Title: 🧼 飯前飯後洗洗手

我其實已經有獨立的 Emoji 欄位,所以原本比較期待的是:

Emoji: 🧼
Title: 飯前飯後洗洗手

但 Gemini 又自己把 Emoji 放進 Title。

其他卡片也一樣:

Emoji: 🦷
Title: 🦷 刷牙亮晶晶

這不是什麼嚴重的錯誤。

仔細想想,Prompt 也確實沒有寫:

Title 裡不能重複 Emoji。

所以 Gemini 只是按照自己的理解,做了一個它認為合理的決定。

這也提醒我一件事:

Prompt 寫得更完整,可以讓輸出更接近需求,但模型仍然需要「理解」自然語言中的規則。


Day 18 和 Day 19 差在哪?

把兩天放在一起看會更明顯。

Day 18 Day 19
Prompt 一句簡單指令 完整 Prompt Template
動態條件 ageGroup / topic / cardCount ageGroup / topic / cardCount
Learning Card 欄位 Gemini 自己決定 指定 Emoji / Title / Content / Question / Answer
內容規則 幾乎沒有 年齡、語言、Q&A 關係等
額外內容 會自行增加 兩次測試皆未增加
Response Text Text

最後一列其實最重要。

雖然今天的 Response 已經整齊很多,但它依然是:

Text

看起來有結構,不代表它是結構化資料

例如 Gemini 現在回傳:

Emoji: 🧼
Title: 🧼 飯前飯後洗洗手
Content: ...
Question: ...
Answer: ...

以人的角度來看:

這不是已經很有結構了嗎?

確實。

但對程式來說,目前拿到的仍然只是一整段文字。

概念上還是:

const response: string = "...";

而不是:

const cards: LearningCard[] = [
  {
    emoji: "🧼",
    title: "飯前飯後洗洗手",
    content: "...",
    question: "...",
    answer: "..."
  }
];

如果現在就要把這段文字塞進 React Learning Card UI,我還得自己想辦法:

找到 Emoji
↓
找到 Title
↓
找到 Content
↓
找到 Question
↓
找到 Answer
↓
再組成 Object

而且只要 Gemini 哪次稍微換個格式,Parser 可能又要跟著調整。

所以今天最大的發現反而是:

Prompt Template 可以讓 Response「看起來有結構」,但看起來有結構,不代表它已經是結構化資料。


Prompt 也開始變成 Application Logic

以前我使用 Gemini 時,Prompt 比較像:

打一段文字 → 看 AI 回什麼。

但做到今天,感覺開始不太一樣了。

現在 Prompt 裡面已經包含:

學習對象
學習主題
卡片數量
輸出欄位
內容難度
Question / Answer 關係
額外內容限制

它已經不只是「問 AI 的一句話」,而是會直接影響 App 行為的一部分。

所以把它抽成:

buildLearningCardPrompt()

集中管理,也開始變得合理。

未來如果想調整教材生成規則,就可以直接從 Prompt Builder 下手,而不用把 Prompt 散落在 API 或 Component 裡。


今天的小結

今天沒有增加新的畫面,也沒有把 Gemini Response 放進 Learning Card UI。

做的事情其實只有:

簡單 Dynamic Prompt
        ↓
Prompt Builder
        ↓
Prompt Template

但實際測試後,差異滿明顯。

同樣使用 Day 18 的兩組條件:

7~8 歲+浩瀚太空+3 張

3~4 歲+日常好習慣+7 張

這次 Gemini 都按照:

Emoji
Title
Content
Question
Answer

產生指定數量的 Learning Card,也沒有再自行加入插畫建議或使用說明。

而學習對象改變後,內容用詞也跟著有所不同。

所以至少從今天這兩次測試來看:

把 Prompt 的角色、學習條件、欄位與內容規則寫清楚,確實讓 Gemini 的 Response 更接近 App 真正需要的 Learning Card。

但「更接近」還不是「保證」。

目前我們只是用自然語言告訴 Gemini:

請按照這個格式回答。

而 API 回來的東西,本質上依然是一段 Text。

這讓我又回到前面曾經遇過的問題:

如果程式真的需要固定資料結構,只靠 Prompt 夠嗎?

看來前面研究過的 Structured Output,終於要正式回到 App 裡了。


明天預告

Day 20|讓 Gemini API 也回傳固定格式:把 Structured Output 接進程式

今天的 Gemini Response:

Emoji: 🧼
Title: ...
Content: ...
Question: ...
Answer: ...

人看起來已經很整齊。

但 React 真正想要的是:

{
  "cards": [
    {
      "emoji": "🧼",
      "title": "飯前飯後洗洗手",
      "content": "...",
      "question": "...",
      "answer": "..."
    }
  ]
}

明天就來把 Day 09~Day 12 在 Google AI Studio 測試過的 Structured Output 與 JSON Schema,正式接進 Gemini API。

看看能不能讓現在的:

Prompt → Text Response

真正變成:

Prompt
  ↓
Gemini API + Schema
  ↓
Structured JSON

也就是之前整理過的那句話:

Prompt 管內容,Schema 管結構。


上一篇
Day 18|按下產生之後呢?從 React 呼叫 Gemini API
下一篇
Day 20|讓 Gemini API 也回傳固定格式:把 Structured Output 接進程式
系列文
今天學什麼?30 天用 Google AI 打造智慧學習卡 共 20 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言