在 昨天 (Day 6) 我們讓 LLM 學會使用工具,能查天氣、看文件、甚至寄出 email。
這樣的 AI 已經會「動手」,但它還缺少一個關鍵能力:安排順序。
想像你交代 AI:「幫我規劃今天去美泉宮、美景宮、聖史蒂芬大教堂。」
如果它只是單純查詢資訊、逐一回覆,最後你拿到的仍然是一堆零散結果,而不是一個完整的行程表。
這就是 Planning(規劃) 的價值:
能把一個目標拆解成一系列可執行步驟,並考量前後順序與限制,讓 AI 的行動更有條理。
Planning Pattern 的精神是 Plan → Execute:先規劃,再執行。
圖:Planning Pattern 流程圖,展現「先規劃、再執行」的典型模式。計劃一次生成,不會在過程中重排,只能在執行階段根據情況微調。
以下用「一日三景點行程」來展示最小可行範例。
規劃今天從上午 9 點出發,參觀維也納三個景點:
美泉宮、美景宮、聖史蒂芬大教堂。
條件:
- 每個景點至少 2 小時
- 景點之間交通 30 分鐘
- 必須在 18:00 前結束
import google.generativeai as genai
import json
from datetime import datetime, timedelta
# ---- 初始化 LLM ----
genai.configure(api_key="你的_API_KEY")
llm = genai.GenerativeModel("gemini-2.0-flash")
# Step 1: 請 AI 生成計劃(要求 JSON 格式,含 place)
query = """
請規劃今天從上午 9 點出發,參觀維也納三個景點:
美泉宮、美景宮、聖史蒂芬大教堂。
條件:
- 每個景點至少 2 小時
- 景點之間交通 30 分鐘
- 必須在 18:00 前結束
請只輸出 JSON 格式,每個步驟包含:
title, place, minutes, indoor (是否室內, true/false)。
"""
response = llm.generate_content(query).text
print("AI 原始輸出:", response)
# Step 2: 清理 JSON(避免 LLM 輸出帶有 ```json ... ```)
raw = response.strip()
if raw.startswith("```"):
raw = raw.split("```")[1]
raw = raw.replace("json", "", 1).strip()
plan = json.loads(raw)
# Step 3: Tool (模擬天氣 API)
def get_weather(city: str):
return "rain" # 假設今天下雨
# Step 4: 執行計劃
weather = get_weather("Vienna")
print("\n今日天氣:", weather)
time = datetime.strptime("09:00", "%H:%M")
end = datetime.strptime("18:00", "%H:%M")
for task in plan:
duration = task["minutes"]
if weather == "rain" and "前往" in task["title"]:
duration = int(duration * 1.5) # 下雨天交通延長 1.5 倍
task["title"] += "(因下雨延誤)"
finish = time + timedelta(minutes=duration)
print(f"{time.strftime('%H:%M')}–{finish.strftime('%H:%M')} {task['title']}")
time = finish
print("行程可行!" if time <= end else "行程超時!")
圖:Demo 1 執行結果。行程表完全依照 AI 事先產生的靜態計劃執行,下雨只影響交通時間,但仍能在限制時間內完成行程。
你可能會注意到:這份計劃裡 沒有「查天氣」這個步驟。這不是錯誤,而是 Planning Pattern 的特性。
在 Plan-and-Execute 模式中:
換句話說,計劃本身是靜態的,外部世界的變化只會在執行時影響最終結果。
這也是為什麼在後續的章節,我們會介紹 ReAct,讓 AI 不僅能規劃,還能邊想邊查。
假設今天臨時發現 美景宮休館,在 Planning Pattern 下,AI 不會自動幫你「重排新計劃」,而是系統在執行階段偵測到問題後,刪掉無法執行的步驟,然後繼續照原本的計劃執行下去。
import google.generativeai as genai
import json
from datetime import datetime, timedelta
# ---- 初始化 LLM ----
genai.configure(api_key="你的_API_KEY")
llm = genai.GenerativeModel("gemini-2.0-flash")
# Step 1: 請 AI 生成計劃(包含 place 欄位)
query = """
請規劃今天從上午 9 點出發,參觀維也納三個景點:
美泉宮、美景宮、聖史蒂芬大教堂。
條件:
- 每個景點至少 2 小時
- 景點之間交通 30 分鐘
- 必須在 18:00 前結束
請只輸出 JSON 格式,每個步驟包含:
title, place, minutes, indoor (是否室內, true/false)。
"""
response = llm.generate_content(query).text
# Step 2: 清理 JSON(避免 LLM 輸出帶有 ```json ... ```)
raw = response.strip()
if raw.startswith("```"):
raw = raw.split("```")[1]
raw = raw.replace("json", "", 1).strip()
plan = json.loads(raw)
# Step 3: Tool (模擬天氣 API)
def get_weather(city: str):
return "rain" # 假設今天下雨
# Step 4: Tool (檢查景點是否開放)
def check_open(place: str):
closed_places = ["美景宮"] # 假設美景宮休館
return place not in closed_places
# Step 5: 執行計劃
weather = get_weather("Vienna")
print("\n今日天氣:", weather)
time = datetime.strptime("09:00", "%H:%M")
end = datetime.strptime("18:00", "%H:%M")
for task in plan:
# 如果是參觀景點,先檢查是否開放
if "參觀" in task["title"]:
if not check_open(task["place"]):
print(f"{task['place']} 今日休館,跳過此步驟")
continue
duration = task["minutes"]
# 下雨 → 戶外交通延長 1.5 倍
if weather == "rain" and "前往" in task["title"]:
duration = int(duration * 1.5) # 交通延長 1.5 倍
task["title"] += "(因下雨延誤)"
finish = time + timedelta(minutes=duration)
print(f"{time.strftime('%H:%M')}–{finish.strftime('%H:%M')} {task['title']}")
time = finish
print("行程可行!" if time <= end else "行程超時!")
圖:Demo 2 執行結果。遇到美景宮休館時,AI 僅刪除該步驟,沒有重新安排行程。
可以看到,在 Planning Pattern 下:
這就是 Plan-and-Execute 的限制:計劃雖然清楚,但缺乏靈活性。
如果要讓 AI 能夠「臨時改去其他景點」或「重新規劃完整路線」,就需要更進階的方法,例如 ReAct 或 Multi-Agent。
圖:維也納卡爾教堂(Karlskirche)。夜色下的巴洛克建築展現嚴謹的對稱與秩序,如同 Planning Pattern 所強調的「先規劃、再執行」:AI 不只是行動,更需要結構化的安排,才能讓複雜任務井然有序。(攝影:作者自攝)