iT邦幫忙

2026 iThome 鐵人賽

DAY 15
0

前兩天我們完成了 ReAct Agent,昨天再用 LangGraph 把原本的迴圈拆成比較清楚的節點與狀態。

但不管有沒有框架,ReAct 的核心都一樣:

做一步
↓
看結果
↓
再決定下一步

這很適合「一開始不知道答案在哪裡」的任務。

例如:

主力模型設定在哪裡?

可能讀一個設定檔就找到答案了。

但如果問題變成:

模型端點在哪裡設定?Client 又是怎麼使用這個設定的?

我們大概已經知道至少要看:

設定檔
Client 實作

這種任務如果每讀完一個檔案,都重新問模型「下一步要幹嘛」,不一定最划算。

另一種做法是:

先把需要做的事情列出來,再逐步執行。

這就是今天要看的 Plan-and-Execute。

今天先不讓模型產生 Plan。

先把「一份可執行的計畫到底應該長什麼樣」定義清楚,明天再把 Planner 接上去。

一、Plan 不是一串看起來很合理的文字

假設模型給你這份計畫:

1. 研究專案
2. 分析設定
3. 整理答案

人看得懂。

但 Executor 拿到之後還是不知道:

要讀哪個檔案?
每一步到底要做什麼?
總共有幾步?

所以今天不接受自由文字 Plan,而是直接用 Pydantic 定義結構。

class PlanStep(BaseModel):
    model_config = ConfigDict(extra="forbid")

    purpose: str = Field(
        min_length=1,
        max_length=300,
    )

    path: str = Field(
        min_length=1,
        max_length=200,
    )


class Plan(BaseModel):
    model_config = ConfigDict(extra="forbid")

    steps: list[PlanStep] = Field(
        min_length=1,
        max_length=4,
    )

這次刻意把每一步限制成:

讀一個檔案

例如:

{
  "steps": [
    {
      "purpose": "確認模型端點從哪裡設定",
      "path": "src/ironman/config.py"
    },
    {
      "purpose": "確認 Client 如何使用這份設定",
      "path": "src/ironman/llm.py"
    }
  ]
}

這不是說 Plan-and-Execute 只能讀檔。

只是目前的 Agent 還是唯讀工具,先把操作面縮小,會比較容易看清楚 Planner 和 Executor 的責任。

二、Day 2 的 Pydantic 又派上用場

Plan 如果要真的交給程式執行,就不能只靠 Prompt 說:

請產生合理的計畫。

我們可以直接限制:

至少要有 1 步
最多 4 步
purpose 不能是空字串
path 不能是空字串
不接受未知欄位

這些交給 Pydantic。

但 Schema 驗證通過,還不代表這份 Plan 可以執行。

例如:

{
  "purpose": "讀取環境設定",
  "path": ".env.local"
}

格式完全合法。

但這個檔案不在我們允許模型讀取的範圍內。

所以還要再做應用層檢查:

路徑是否允許
是否有路徑穿越
是否重複讀取同一個檔案
步驟是否符合目前 Executor 的能力

也就是:

Schema 正確
≠
Plan 可以執行

跟前面 Tool 的安全邊界其實是同一個概念。

三、ReAct 和 Plan-and-Execute 差在哪?

最直觀的差別在「什麼時候決定下一步」。

ReAct 是:

執行一步
↓
取得 Observation
↓
再決定下一步

Plan-and-Execute 則是:

先產生 Plan
↓
確認步驟
↓
逐步 Execute
↓
必要時重新規劃

可以先這樣判斷:

情境 較適合的方式 原因
一開始不知道資料在哪 ReAct 下一步高度依賴剛取得的 Observation
已經知道大概要查哪些資料 Plan-and-Execute 可以先列出工作順序,方便追蹤進度
任務有多個固定階段 Plan-and-Execute 比較容易檢查遺漏與完成狀態
操作具有寫入或執行風險 Plan + 額外核准 可以先看計畫,但 Plan 本身不代表授權

兩種方式也不是互斥。

例如:

Planner
↓
產生 3 個大步驟
↓
Executor
↓
每一步內部再用 ReAct

完全可以。

今天只是故意把 Executor 做得很單純,方便先看懂兩個角色怎麼分工。

四、故意放一個不能執行的 Plan

今天先不用模型產生 Plan。

直接手動建立一份合法的兩步計畫:

1. 讀 config.py,確認設定來源
2. 讀 llm.py,確認 Client 如何使用

接著再故意塞入:

.env.local

看驗證層會不會擋下來。

執行:

uv run python days/day15_plan_concept/main.py

uv run pytest -q tests/test_day14_16_frameworks.py

🧪 隔離環境實測
https://ithelp.ithome.com.tw/upload/images/20260928/201612244q9uGQSsXO.png

如果要求:

讀取 .env.local

正確結果不是幫 Planner 猜一條別的路。

而是直接拒絕。

測試也會確認:

空 Plan → 拒絕

重複 path → 拒絕

路徑穿越 → 拒絕

不允許的檔案 → 拒絕

今天這份 Plan 是 我們手動建立的。

模型還沒有參與規劃。

這件事要講清楚,不然看到漂亮的 Plan 輸出,很容易誤以為 Planner 已經做成功了。

今天驗證的是:

我們已經有一份模型未來可以遵守,而且程式能驗證的 Plan 格式。

五、Plan 通過,不代表後面可以放行

假設 Plan 已經驗證完成:

Step 1 → src/ironman/config.py
Step 2 → src/ironman/llm.py

真正執行時,每一步還是要重新經過 Tool 原本的限制。

不能因為:

Plan 已經審過

就讓 Executor 跳過:

路徑驗證
Tool allowlist
參數驗證
權限檢查

因為 Plan 和實際執行之間可能隔了一段時間。

這段時間裡:

檔案可能被移動
權限可能改變
程式可能更新
環境可能不同

所以 Plan 比較像:

預計要做什麼

而不是:

永久通行證

真正動手時,還是要重新驗證。

六、Plan 還有一個麻煩:執行到一半失敗

假設原本有三步:

Step 1 → 成功
Step 2 → 失敗
Step 3 → 尚未執行

這時如果重新規劃,新的 Plan 要不要再跑一次 Step 1?

今天只是讀檔,重複執行頂多浪費一點時間。

但如果以後換成:

寄信
建立 Issue
修改檔案
付款

就不能隨便重做。

所以 Plan-and-Execute 除了 Plan 之外,還需要保存:

哪些 Step 已完成
哪些 Step 失敗
每一步的 Observation
目前剩哪些工作

Re-plan 時才能知道:

哪些事情已經做過,不要再來一次。

這也是明天實作時會補上的狀態。


前幾天的 ReAct 是:

先走一步
↓
看結果
↓
再想下一步

今天換一個角度:

先把路線列出來
↓
驗證這條路能不能走
↓
再開始執行

但目前 Planner 還是我們自己。

今天只完成:

Structured Plan
+
Pydantic Validation
+
Plan Policy

明天才會把規劃交給本機模型:

使用者任務
↓
Planner
↓
Structured Plan
↓
Validator
↓
Executor
↓
Observation

並處理執行失敗後的第一次 Re-plan。

Day 16:

用 Python 實作 Plan-and-Execute:讓 Agent 完成多步任務。


上一篇
Day 14:ReAct 流程越寫越亂?用一天認識 LangGraph
下一篇
Day 16:用 Python 實作 Plan-and-Execute:讓 Agent 完成多步任務
系列文
協定、框架、架構:一條龍搞懂 AI Agent 是怎麼被造出來的 共 18 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言