Agent 的核心循環:ReAct、Plan-and-Execute 與 Reflection

深入三種主流 Agent 設計模式,用 Python 程式碼演示它們如何讓 LLM 從被動回答變成主動解決問題。

Agent 的核心循環:ReAct、Plan-and-Execute 與 Reflection

上一篇我們理解了 Agent 的基本概念:它不再是只會生成文字的 Chatbot,而是能感知、推理、行動、記憶與反思的自主系統。

但「觀察 → 思考 → 行動 → 反思」這個循環,具體要怎麼實作?

LLM 要如何在每一步決定「現在該想什麼、該做什麼」?

這一篇,我們要深入三種最主流的 Agent 設計模式:ReAct、Plan-and-Execute 與 Reflection,並用實際的 Python 程式碼演示它們的運作邏輯。

ReAct | Plan-and-Execute | Reflection

一、為什麼需要設計模式?

如果你直接對 LLM 說:「幫我規劃一趟東京五天四夜的行程」,它可能會直接生成一份看起來合理的行程表。

但這份行程表有幾個問題:

  • 它可能不知道最新的航班時間
  • 它可能不知道某些景點已經關閉
  • 它可能沒有考慮匯率、交通時間、營業時間
  • 它可能只是「生成」了一份聽起來合理的內容

要讓 Agent 真正解決問題,我們需要給它一套結構化的運作方式,讓它知道:

  • 什麼時候該思考?
  • 什麼時候該行動?
  • 什麼時候該停下來檢查?
  • 什麼時候該重新規劃?

這就是 Agent 設計模式要解決的問題。

二、ReAct:推理與行動交錯進行

什麼是 ReAct?

ReAct 是 Reasoning + Acting 的縮寫,是目前最廣泛使用的 Agent 設計模式之一。

它的核心思想非常直觀:

讓 LLM 在每一步交替進行「推理」與「行動」。

具體來說,ReAct 的運作流程如下:

  1. Thought(思考):模型先推理當前狀態,決定下一步要做什麼。
  2. Action(行動):模型選擇一個工具並執行。
  3. Observation(觀察):模型接收工具回傳的結果。
  4. 重複上述步驟,直到任務完成。

Thought → Action → Observation → Repeat

程式碼演示:ReAct 迴圈

以下是一個簡化的 Python 實作,展示 ReAct 的核心迴圈:

import json
import re

# 1. 定義工具
def search_weather(city: str, day: str) -> str:
    """模擬天氣查詢 API"""
    fake_data = {
        ("台北", "今天"): "多雲,降雨機率 60%,氣溫 22-28°C",
        ("台北", "明天"): "晴時多雲,降雨機率 20%,氣溫 24-30°C",
    }
    return fake_data.get((city, day), "查無資料")

def search_weather_alert(city: str) -> str:
    """模擬天氣警報查詢 API"""
    return "目前無豪雨特報,但山區午後有陣雨機率"

# 2. 工具註冊表
TOOLS = {
    "search_weather": search_weather,
    "search_weather_alert": search_weather_alert,
}

# 3. ReAct 系統提示
REACT_PROMPT = """你是一個 ReAct Agent,必須按照以下格式回答:

Thought: 你的推理過程
Action: 工具名稱
Action Input: 工具參數(JSON 格式)

當你得到足夠資訊後,使用:
Thought: 我已經知道答案了
Final Answer: 你的最終回答

可用工具:
- search_weather(city, day): 查詢指定城市與日期的天氣
- search_weather_alert(city): 查詢指定城市的天氣警報
"""

def parse_action(text: str):
    """從 LLM 輸出中解析 Action 與 Action Input"""
    action_match = re.search(r"Action:\s*(\w+)", text)
    input_match = re.search(r"Action Input:\s*(\{.*?\})", text, re.DOTALL)
    if action_match and input_match:
        return action_match.group(1), json.loads(input_match.group(1))
    return None, None

def react_agent(question: str, max_steps: int = 5):
    """ReAct Agent 主迴圈"""
    scratchpad = f"Question: {question}\n"

    for step in range(max_steps):
        # 1. 呼叫 LLM,產生 Thought + Action
        llm_output = call_llm(REACT_PROMPT + scratchpad)
        scratchpad += llm_output + "\n"

        # 2. 檢查是否已完成
        if "Final Answer:" in llm_output:
            return llm_output.split("Final Answer:")[-1].strip()

        # 3. 解析並執行工具
        action, action_input = parse_action(llm_output)
        if action is None:
            scratchpad += "Observation: 無法解析 Action,請重新輸出。\n"
            continue

        if action not in TOOLS:
            observation = f"錯誤:找不到工具 {action}"
        else:
            try:
                observation = TOOLS[action](**action_input)
            except Exception as e:
                observation = f"工具執行錯誤:{e}"

        # 4. 把觀察結果寫回 scratchpad
        scratchpad += f"Observation: {observation}\n"

    return "已達到最大步數,任務未完成。"

實際執行結果

假設使用者問:「台北今天適合爬山嗎?」

Step 1:
Thought: 我需要先查今天台北的天氣。
Action: search_weather
Action Input: {"city": "台北", "day": "今天"}
Observation: 多雲,降雨機率 60%,氣溫 22-28°C

Step 2:
Thought: 降雨機率偏高,我需要再查一下是否有豪雨特報。
Action: search_weather_alert
Action Input: {"city": "台北"}
Observation: 目前無豪雨特報,但山區午後有陣雨機率

Step 3:
Thought: 我已經知道答案了
Final Answer: 今天台北降雨機率 60%,山區午後有陣雨,
              不建議爬山。建議改期或選擇室內活動。

ReAct 的優點與缺點

優點

  • 直觀易懂,思考與行動交錯,符合人類解決問題的方式
  • 靈活應變,每一步都根據上一步的結果調整
  • 易於實作,只需要在 Prompt 中定義好格式

缺點

  • 容易短視,每一步只關注當下,可能缺乏全局規劃
  • 可能繞遠路,沒有事先規劃,可能走冤枉路
  • 容易陷入迴圈,如果一直找不到答案,可能反覆嘗試類似行動

三、Plan-and-Execute:先規劃,再執行

什麼是 Plan-and-Execute?

ReAct 是「邊想邊做」,而 Plan-and-Execute 則是「先想清楚,再動手」。

它的核心思想是:

先讓 LLM 制定一個完整的計畫,再逐步執行每個步驟。

具體流程如下:

  1. Planning(規劃):LLM 根據目標,生成一個步驟清單。
  2. Execution(執行):逐步執行每個步驟,可能使用工具。
  3. Re-planning(重新規劃):如果執行結果不如預期,重新調整計畫。

Plan → Execute → Re-plan

程式碼演示:Plan-and-Execute

from dataclasses import dataclass, field

@dataclass
class Plan:
    steps: list[str] = field(default_factory=list)
    current_step: int = 0
    results: dict = field(default_factory=dict)

PLANNER_PROMPT = """你是一個規劃器。請將以下任務拆解成有序的步驟清單。
每個步驟應該是一個具體、可執行的動作。

任務:{task}

請以 JSON 格式輸出,例如:
{{"steps": ["步驟1", "步驟2", "步驟3"]}}
"""

EXECUTOR_PROMPT = """你是一個執行器。請根據以下步驟執行任務。

原始任務:{task}
完整計畫:{plan}
已完成步驟與結果:{results}
當前步驟:{current_step}

請執行當前步驟,並輸出結果。如果需要使用工具,請呼叫對應工具。
"""

REPLANNER_PROMPT = """你是一個重新規劃器。
原始任務:{task}
原計畫:{plan}
目前執行結果:{results}

請判斷是否需要調整計畫。如果需要,輸出新的步驟清單(JSON 格式);
如果不需要,輸出 {{"steps": null}}
"""

def plan_and_execute(task: str, max_replans: int = 2):
    """Plan-and-Execute Agent 主流程"""
    # 1. 規劃階段
    plan_json = call_llm(PLANNER_PROMPT.format(task=task))
    plan = Plan(steps=json.loads(plan_json)["steps"])
    print(f"計畫:{plan.steps}")

    # 2. 執行階段
    for i, step in enumerate(plan.steps):
        plan.current_step = i
        print(f"\n執行步驟 {i+1}{step}")

        result = call_llm(EXECUTOR_PROMPT.format(
            task=task,
            plan=plan.steps,
            results=plan.results,
            current_step=step,
        ))
        plan.results[step] = result
        print(f"結果:{result}")

        # 3. 重新規劃(可選)
        if i < len(plan.steps) - 1:
            replan_json = call_llm(REPLANNER_PROMPT.format(
                task=task,
                plan=plan.steps,
                results=plan.results,
            ))
            new_steps = json.loads(replan_json).get("steps")
            if new_steps:
                print(f"重新規劃:{new_steps}")
                plan.steps = plan.steps[:i+1] + new_steps

    return plan.results

實際執行結果

假設任務是:「幫我規劃一趟東京五天四夜的行程。」

計畫:
1. 查詢台北到東京的航班與價格
2. 查詢東京五天四夜的天氣預報
3. 查詢熱門景點與營業時間
4. 查詢交通方式與所需時間
5. 根據以上資訊,生成每日行程
6. 檢查行程是否合理,必要時調整

執行步驟 1:查詢航班
結果:找到長榮、華航、虎航等選項,來回約 12,000 元

執行步驟 2:查詢天氣
結果:第一天晴、第二天雨、第三天晴、第四天多雲、第五天晴

...

執行步驟 6:檢查行程
結果:發現第二天排了戶外行程但會下雨,調整為室內行程

Plan-and-Execute 的優點與缺點

優點

  • 全局視野,先規劃再執行,能考慮整體結構與資源分配
  • 效率較高,避免重複嘗試,減少不必要的工具呼叫
  • 易於除錯,每個步驟清楚分明,出錯時容易定位問題

缺點

  • 規劃可能過時,如果執行過程中出現新資訊,原本的計畫可能不再適用
  • 缺乏靈活性,如果計畫不夠詳細,執行時可能卡住
  • 規劃品質依賴 LLM,如果 LLM 規劃能力不足,整個流程都會受影響

四、Reflection:讓 Agent 學會自我修正

什麼是 Reflection?

無論是 ReAct 還是 Plan-and-Execute,Agent 都可能犯錯。

Reflection 的核心思想是:

讓 Agent 檢視自己的行動與結果,從錯誤中學習,並調整後續策略。

Reflection 通常不是獨立的模式,而是附加在 ReAct 或 Plan-and-Execute 之上的機制。

具體做法包括:

  • 自我批評(Self-Critique):Agent 檢視自己的輸出,找出問題。
  • 自我修正(Self-Correction):根據批評,重新生成或調整。
  • 經驗累積(Experience Replay):將失敗經驗存入記憶,避免重複犯錯。

Draft → Critique → Revise → Repeat

程式碼演示:Reflection

GENERATOR_PROMPT = """請根據以下要求生成內容:

任務:{task}
參考資料:{context}
"""

CRITIC_PROMPT = """你是一個嚴格的審查者。請檢視以下輸出,找出問題:

任務:{task}
輸出:{output}

請從以下角度評估:
1. 是否完整回答了任務?
2. 是否有事實錯誤或幻覺?
3. 是否缺乏具體細節?
4. 結構是否清晰?

請以 JSON 格式輸出:
{{"has_issue": true/false, "critique": "具體問題描述"}}
"""

REVISER_PROMPT = """請根據批評意見修正以下輸出:

任務:{task}
原輸出:{output}
批評意見:{critique}

請輸出修正後的版本。
"""

def reflection_agent(task: str, max_iterations: int = 3):
    """帶有 Reflection 的 Agent"""
    # 1. 初始生成
    output = call_llm(GENERATOR_PROMPT.format(task=task, context=""))
    print(f"初稿:\n{output}\n")

    for i in range(max_iterations):
        # 2. 自我批評
        critique_json = call_llm(CRITIC_PROMPT.format(task=task, output=output))
        critique = json.loads(critique_json)

        if not critique["has_issue"]:
            print(f"第 {i+1} 輪審查:無問題,任務完成。")
            break

        print(f"第 {i+1} 輪批評:{critique['critique']}")

        # 3. 自我修正
        output = call_llm(REVISER_PROMPT.format(
            task=task,
            output=output,
            critique=critique["critique"],
        ))
        print(f"修正後:\n{output}\n")

    return output

實際執行結果

假設任務是:「寫一份無線耳機的產品說明。」

初稿:
這款產品採用先進技術,品質優良,適合各種場合使用。

第 1 輪批評:
這段文字太籠統,沒有具體說明產品功能、規格與適用對象。
應該補充具體資訊,例如尺寸、材質、使用場景。

修正後:
這款無線耳機採用藍牙 5.3 技術,支援主動降噪,
單次充電續航 8 小時,搭配充電盒可達 32 小時。
重量僅 4.5 克,適合通勤、運動與辦公使用。

第 2 輪審查:無問題,任務完成。

Reflection 的優點與缺點

優點

  • 提升品質,透過自我檢視,能發現並修正錯誤
  • 持續改進,累積經驗後,Agent 的表現會越來越好
  • 適用性廣,可以附加在任何 Agent 模式上

缺點

  • 增加成本,每次反思都需要額外的 LLM 呼叫
  • 可能過度修正,有時原本的答案是對的,反思後反而改錯
  • 需要良好的評估標準,如果沒有明確的判斷依據,反思可能流於形式

五、三種模式的比較

面向ReActPlan-and-ExecuteReflection
核心思想邊想邊做先規劃再執行自我檢視與修正
優點靈活、直觀全局視野、效率高提升品質、持續改進
缺點短視、可能繞路規劃可能過時成本高、可能過度修正
適用場景簡單任務、不確定性高複雜任務、步驟明確需要高品質輸出
是否獨立通常附加於其他模式

在實務中,這三種模式常常混合使用。

例如:

  1. 先用 Plan-and-Execute 制定高階計畫
  2. 再用 ReAct 執行每個步驟
  3. 最後用 Reflection 檢查整體結果

這樣兼顧了全局規劃、靈活執行與品質控管。

六、一個混合範例

假設我們要打造一個「自動研究助理 Agent」,任務是:

幫我整理一份「2024 年 AI Agent 發展趨勢」的報告。

混合模式可能是這樣運作:

def research_assistant(task: str):
    # 第一階段:Plan-and-Execute 制定計畫
    plan = plan_and_execute(f"制定研究計畫:{task}")

    # 第二階段:ReAct 執行每個步驟
    results = []
    for step in plan["steps"]:
        result = react_agent(f"執行以下步驟:{step}")
        results.append(result)

    # 第三階段:Reflection 檢查與修正
    draft = "\n".join(results)
    final = reflection_agent(f"根據以下草稿撰寫最終報告:{draft}")

    return final

實際運作:

第一階段(Planning):
1. 搜尋 2024 年 AI Agent 相關新聞與論文
2. 整理主要發展方向
3. 找出代表性產品與研究
4. 撰寫報告大綱
5. 生成完整報告
6. 檢查與修正

第二階段(ReAct 執行):
Step 1: search_web("2024 AI Agent trends")
        → 找到多篇報導,提到多 Agent 協作、工具使用、自主規劃
Step 2: 整理出三大方向:工具使用、多 Agent 協作、自主規劃
Step 3: 找到 AutoGPT、BabyAGI、CrewAI 等案例
...

第三階段(Reflection):
初稿完成 → 批評:缺少具體案例與最新引用
         → 修正:補充案例與更新來源

七、總結:三種模式,三種思維

讓我們回顧這一篇的核心:

  • ReAct:推理與行動交錯,適合不確定性高、需要靈活應變的任務。
  • Plan-and-Execute:先規劃再執行,適合步驟明確、需要全局視野的複雜任務。
  • Reflection:自我檢視與修正,適合需要高品質輸出、可迭代改進的場景。

這三種模式並不是互斥的,而是可以互補與組合。

理解它們的運作邏輯與適用場景,是設計高效 Agent 的關鍵。

不過,Agent 要能真正「行動」,還需要一個核心能力:工具呼叫(Tool Use)。

LLM 要如何決定呼叫哪個工具?工具的回傳結果要如何整合進推理流程?

這就是下一篇要談的主題。

下一篇預告

《工具呼叫與 Function Calling:讓 LLM 連接外部世界》

我們會解釋 Function Calling 的運作原理、Tool Schema 的設計方式、錯誤處理與重試機制,並用實際的 Python 程式碼演示如何讓 LLM 從「只會說」變成「真的能做」。