Day 25 我已經成功做到:
輸入一個大型目標
↓
Python 呼叫 Groq API
↓
openai/gpt-oss-20b
↓
回傳 5~8 個小任務
今天我要處理的問題就是:
怎麼讓 AI 回傳的內容,不只是「看得懂」,而是變成 Python 可以穩定處理的資料。
所以 Day 26 的重點,不再只是「AI 有沒有回答」,而是:
AI 回傳的格式穩不穩
Python 能不能解析
欄位是不是固定
資料不符合規則時能不能擋下來
Day 25 的回傳內容大概像:
1. 確定專題題目與研究範圍:選定題目並列出主要研究目標與問題
2. 進行文獻回顧與資料蒐集:查閱相關資料並整理重要內容
人看到之後,很自然就知道:
標題:
確定專題題目與研究範圍
說明:
選定題目並列出主要研究目標與問題
但 Python 看到的其實比較接近:
"1. 確定專題題目與研究範圍:選定題目...\n2. ..."
也就是一整串字串。
如果 AI 某一次改成:
一、確定專題題目
或:
任務 1:確定專題題目
甚至多加:
以下是幫你整理的任務:
那原本靠文字切割的方式就可能出問題。
所以如果之後真的要把這些內容變成 Web App 裡的一筆一筆 Task,就不能只靠自由文字。
今天我希望 AI 回傳的格式變成:
{
"tasks": [
{
"title": "確定專題題目與研究範圍",
"description": "選定題目並整理主要研究目標"
},
{
"title": "進行文獻回顧與資料蒐集",
"description": "查閱相關資料並整理重要內容"
}
]
}
這樣 Python 就能知道:
tasks
→ 任務清單
title
→ 任務名稱
description
→ 任務說明
而不是自己猜文字要怎麼切。
雖然今天的資料格式已經很接近真正的 Task,但我還是不急著改 app.py。
Day 26 一樣先限制範圍。
今天不做:
今天只處理:
AI
↓
JSON
↓
Python dict / list
先把資料格式處理穩,再考慮資料庫。
我先進入 Plan Mode,然後把需求講清楚:
現在開始 Day 26。
Day 25 已完成:
- Python 3.9.13
- openai==2.47.0
- Groq OpenAI-compatible API
- model=openai/gpt-oss-20b
- groq_api_test.py 已成功從命令列取得大型目標
- AI 已能在終端機回傳 5~8 個繁體中文任務
- API key 與基本錯誤處理已驗證
Day 26 的目標是:
把 AI 原本回傳的自由文字,
改成 Python 可以穩定處理的結構化任務資料。
今天先只需要:
1. AI 回傳 JSON。
2. 最外層格式為:
{
"tasks": [...]
}
3. 每個 task 只有:
- title
- description
4. 任務數量維持 5~8 個。
5. Python 可以把回傳 JSON 解析成 dict / list。
6. 在終端機逐筆印出解析後的 title 和 description。
7. 驗證每個 task 都有 title、description。
希望優先使用 Groq + openai/gpt-oss-20b 支援的 Structured Outputs / JSON Schema,
不要只靠 Prompt 要求「請輸出 JSON」。
今天不要:
- 接 Flask 首頁
- 新增 Flask route
- 寫入 MySQL
- 自動建立 Task
- 修改 Task model
- 讓 AI 產生 status、category、due_date
- 新增 AI 資料表
- 新增 services/ 或 routes/ 分層
- 修改 CLAUDE.md
Python 維持 3.9.13。
OpenAI SDK 維持 2.47.0。
GROQ_API_KEY 的處理方式維持 Day 25。
請先:
1. 讀取 CLAUDE.md
2. 檢查目前 groq_api_test.py
3. 確認 openai 2.47.0 搭配 Groq Chat Completions 要怎麼傳 response_format=json_schema
4. 告訴我預計修改哪些檔案
5. 說明 JSON Schema 的內容
6. 說明成功與失敗要怎麼驗證
先給我 Plan,不要修改檔案。
這次我特別要求:
先給 Plan
不要修改
因為我不想讓 Claude Code 一看到「Day 26」就自己開始改 app.py。
目前我的 Task model 有:
title
description
status
category
due_date
但這次我只讓 AI 回:
title
description
沒有讓 AI 自己決定:
status
category
due_date
原因是這些欄位不一定適合由 AI 猜。
例如使用者只輸入:
我要準備期末專題
AI 並不知道真正的截止日期。
如果硬叫它產生 due_date,很可能就會自己猜。
而新任務的狀態本來就可以由程式預設成:
todo
所以沒有必要讓 AI 多決定一次。
目前把責任切開比較清楚:
AI 負責:
title
description
程式之後負責:
status
category
due_date
如果只是單純在 Prompt 裡寫:
請用 JSON 回答
通常 AI 會照做。
但「通常」還是不夠穩。
它可能回:
以下是 JSON:
再接 JSON。
也可能包成 Markdown:
json
{
...
}
對人來說這沒有什麼差別,但對 Python 來說就多了很多要處理的東西。
所以這次不是只靠 Prompt。
而是改用 Groq 的:
```text
Structured Outputs
搭配:
JSON Schema
讓 API 直接限制回傳格式。
我這次可以先把它簡單理解成:
JSON
→ 真正的資料
JSON Schema
→ 規定資料應該長什麼樣子
例如真正資料:
{
"title": "蒐集資料",
"description": "搜尋專題相關資料"
}
Schema 則是在規定:
title 必須存在
title 必須是字串
description 必須存在
description 必須是字串
這次我的 Schema 概念是:
{
"type": "object",
"properties": {
"tasks": {
"type": "array",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"description": {
"type": "string"
}
},
"required": [
"title",
"description"
],
"additionalProperties": false
}
}
},
"required": [
"tasks"
],
"additionalProperties": false
}
也就是要求:
最外層一定要有 tasks
tasks 一定是 array
每一筆 task 一定要有:
title
description
不能多出:
status
category
due_date
或其他欄位
這次 response_format 使用:
type: json_schema
strict: true
意思是我要模型嚴格按照指定的 Schema 回傳。
也就是不是:
「盡量長得像這樣」
而是:
「必須符合這個資料結構」
這次真正實測之後,也確認:
openai/gpt-oss-20b
+
Groq
+
strict: true
可以正常工作。
沒有被 API 回 HTTP 400。
Day 26 沒有新增一堆新檔案。
只修改原本 Day 25 的:
groq_api_test.py
Day 25 的版本已經留在 Git 歷史裡,所以現在直接把這個測試腳本往下一階段改就可以。
其他地方都沒有動。
這樣可以很清楚知道 Day 26 的變化到底在哪裡。
Day 25 使用的是:
max_completion_tokens=1024
Day 26 改成:
max_completion_tokens=2048
原因是這次不再只是單純文字。
輸出裡還會有:
JSON 結構
欄位名稱
title
description
5~8 筆任務
reasoning
如果剛好因為長度限制被截斷,JSON 很可能會直接變成不完整。
例如:
{
"tasks": [
{
"title": "蒐集資料",
"description":
這種內容根本不能解析。
所以這次多留一點輸出空間。
後面的實測結果也證明,實際每次大約只用了:
500~660 tokens
距離 2048 還很遠。
所以目前不用再調高。
這次最重要的新功能之一是:
parse_tasks(content)
它負責把 AI 回傳的 JSON 字串轉成 Python 可以使用的資料。
第一步:
json.loads(content)
如果不是合法 JSON,就直接失敗。
例如:
這不是 JSON
會得到:
ValueError
而不是繼續往下跑。
parse_tasks() 不只是把 JSON 讀進來而已。
它還會檢查:
最外層是不是 dict
有沒有 tasks
tasks 是不是 list
任務數量是不是 5~8
每一筆是不是 dict
有沒有 title
有沒有 description
有沒有多餘欄位
title 是不是字串
description 是不是字串
title 去除空白後是不是空的
description 去除空白後是不是空的
例如如果 AI 回:
{
"tasks": [
{
"title": "蒐集資料",
"description": "搜尋相關資料",
"status": "todo"
}
]
}
就不符合今天的規則。
因為 Day 26 每筆任務只允許:
title
description
所以 Python 會把它擋下來。
一開始我也會想到:
既然
strict: true已經限制 Schema,為什麼 Python 還要再檢查一次?
因為這兩層負責的事情不完全一樣。
Structured Outputs 比較像是在保證:
資料的形狀
例如:
tasks 是陣列
title 是字串
description 是字串
沒有多餘欄位
但我自己的專案還有其他規則。
例如:
任務一定要 5~8 筆
title 不能是空字串
description 不能只有空白
所以可以把它想成:
Groq Structured Outputs
→ 確保資料格式
parse_tasks()
→ 確保符合這個專案自己的規則
兩層一起做,比單靠其中一層更清楚。
第一個還是使用:
venv\Scripts\python.exe groq_api_test.py "我要準備期末專題"
這次 AI 不再回傳自由文字格式。
而是先回傳符合 Schema 的 JSON,再由 Python 解析後顯示成比較好閱讀的格式。
其中一次得到 8 筆任務。
最後顯示:
共 8 筆,驗證通過
而:
finish_reason: stop
表示正常完成。
接著我測:
venv\Scripts\python.exe groq_api_test.py "Plan a 5-day trip to Japan"
雖然輸入是英文,但因為 Prompt 有要求使用繁體中文,所以最後仍然得到繁體中文:
1. 確定旅行日期與行程時間表
選定5天的旅行日期,制定每天的主要活動與交通安排。
2. 訂機票與住宿
比對航班選項,預訂往返機票;挑選並預約適合的酒店或旅社。
3. 規劃每日行程路線圖
依據目的地規劃每天的景點順序、餐廳與購物點。
...
最後:
共 6 筆,驗證通過
API 資訊:
model: openai/gpt-oss-20b
finish_reason: stop
tokens: prompt=272 completion=288 total=560
這也證明:
英文輸入
↓
仍然可以依 Prompt 回繁體中文
接著測:
venv\Scripts\python.exe groq_api_test.py "學英文"
這次輸入非常短。
結果還是可以正常拆成:
1. 設定學習目標
2. 挑選教材
3. 規劃學習時間表
4. 進行語言實踐
5. 評估與調整
最後:
共 5 筆,驗證通過
token:
prompt=266
completion=182
total=448
代表即使輸入資訊不多,格式還是維持正常。
再來測:
venv\Scripts\python.exe groq_api_test.py "我要在三個月內學會 Python 基礎、Flask、MySQL,最後做出一個可以展示的專題"
這次內容比較長,而且有很多階段。
AI 最後拆成 8 筆:
1. 學習 Python 基礎語法
2. 完成 Python 進階練習題
3. 入門 Flask 框架
4. 學習 MySQL 資料庫基礎
5. 結合 Flask 與 MySQL 建立 CRUD 應用
6. 撰寫專題需求與規劃設計文件
7. 實作並部署專題到線上平台
8. 撰寫專題說明文件與測試報告
最後:
共 8 筆,驗證通過
token:
prompt=293
completion=368
total=661
目前仍然遠低於:
2048
所以輸出長度不是問題。
這次比較長的測試中,有一筆:
實作並部署專題到線上平台
但我輸入的原文其實只是:
最後做出一個可以展示的專題
我沒有說:
一定要部署
AI 自己把「可以展示」延伸成:
部署到 Heroku、Render 或其他雲端平台
這個結果在格式上完全正確。
也通過:
JSON Schema
parse_tasks()
5~8 筆檢查
但內容上卻多做了一點推測。
這件事讓我看到 Day 26 和 Day 27 的差別。
Day 26 現在已經處理好:
格式穩不穩
接下來 Day 27 要開始處理:
內容準不準
也就是:
AI 有沒有自己增加使用者沒有要求的事情?
AI 有沒有過度推測?
Prompt 要怎麼限制?
這個例子剛好可以留到下一天處理。
這次 Day 26 不只呼叫一次。
實際共跑了 6 次主要驗證:
我要準備期末專題
→ 8 筆
我要三個月內學會 Python
→ 6 筆
Plan a 5-day trip to Japan
→ 7 筆
同一目標再連跑 3 次
→ 6、7、6 筆
後面又另外測:
學英文
較長的 Python + Flask + MySQL 目標
種花小技巧
每次都是:
finish_reason: stop
沒有發生:
HTTP 400
也就是這次的:
strict: true
確實可以在 Groq + openai/gpt-oss-20b 上正常使用。
程式現在也處理:
finish_reason = length
如果發生,代表輸出可能被截斷。
這時候程式不會硬做:
json.loads()
而是直接顯示提示並停止。
因為被截斷的 JSON 很可能根本不是合法 JSON。
不過這次實際測試沒有真的遇到。
目前每次大約只有:
500~660 tokens
離:
2048
還很遠。
所以我沒有為了測試而故意把 token 上限調很低。
這個防呆目前先保留就好。
Day 25 是:
大型目標
↓
Groq API
↓
自由文字
Day 26 現在變成:
大型目標
↓
Groq API
↓
openai/gpt-oss-20b
↓
JSON Schema
↓
Structured Outputs
↓
合法 JSON
↓
json.loads()
↓
parse_tasks()
↓
Python list / dict
也就是 AI 回傳的內容,終於不只是拿來看的文字。
現在 Python 已經可以一筆一筆取得:
task["title"]
task["description"]
這就離真正建立 Task 又更近一步。
Day 25 我在確認:
AI 能不能回答?
Day 26 我在確認:
AI 的回答能不能被程式可靠地使用?
這兩件事其實差很多。
AI 回一句:
1. 蒐集資料:搜尋專題相關內容
對人很好懂。
但如果之後要存進資料庫,比較希望拿到:
{
"title": "蒐集資料",
"description": "搜尋專題相關內容"
}
這樣才有辦法穩定對應到:
Task.title
Task.description
所以今天真正完成的不是單純:
JSON 格式比較漂亮
而是:
AI 回傳資料
開始有固定契約
目前可以整理成:
Groq Structured Outputs
openai/gpt-oss-20b strict: true
JSON Schema
response_format=json_schema
json.loads()
parse_tasks()
5~8 筆數量驗證
title / description 欄位驗證
空字串驗證
多餘欄位驗證
14 個離線案例
多次真實 API 測試
英文輸入仍回繁體中文
短輸入可以處理
較長輸入可以處理
finish_reason 全部 stop
Day 25 API Key 錯誤處理仍正常
沒有修改 Flask / MySQL / Task model
目前還沒有做:
AI 結果建立 Task
寫入 MySQL
接進首頁
新增 AI route
所以專案現在停在:
AI 已經能產生
「程式看得懂的任務資料」
但還沒有真正寫進 Web App。