跳到主要內容
AI 原理與協定

ReAct:讓語言模型邊想邊做,而不是閉著眼睛猜

為什麼語言模型會自信地編出一個錯答案?因為它只會「想」,碰不到外面的世界。ReAct(Reason + Act)把推理和行動交織成一個迴圈:想一步、做一步、看一步。本文用一段實際的 trace 和一個最小可跑的 Python 迴圈,講清楚 ReAct 的原理,以及它和今天 AI agent 的關係。

問語言模型一個需要查資料的問題,你大概看過這種場面:

你:「台北那家鼎泰豐,禮拜一有開嗎?」

模型:「鼎泰豐每週一公休。」

講得斬釘截鐵,但其實鼎泰豐天天營業,這天根本沒公休。模型沒有去查任何東西——它只是根據訓練時看過的文字,了一個聽起來很合理的答案。這就是大家都遇過的 hallucination(幻覺)

問題的根源不是模型「不夠聰明」,而是它被關在一個只能「想」、碰不到外面世界的盒子裡。ReAct(Reason + Act) 要解決的,正是這件事。


兩種半套的解法

在 ReAct 之前,要讓模型把問題答對,主要有兩條路,但兩條都只做了一半。

只會想:Chain-of-Thought

Chain-of-Thought(CoT,思維鏈) 讓模型在回答前先一步步把推理寫出來:

Q: 一打雞蛋有 12 顆,買了 3 打,用掉 5 顆,還剩幾顆?
A: 3 打是 3 × 12 = 36 顆,用掉 5 顆,36 - 5 = 31 顆。答案是 31。

把推理攤開來寫,模型在數學、邏輯這類題目上的正確率確實大幅提升。但 CoT 有個天花板:它全程在模型腦袋裡空想,沒有任何一步能接觸外部世界。

碰到「鼎泰豐禮拜一有沒有開」這種需要查事實的題目,CoT 一樣只能編。它推理得頭頭是道,但前提如果是錯的,整串推理只是把錯誤包裝得更像真的。

只會做:Action-only

另一條路反過來:給模型一堆工具(搜尋、計算機、API),讓它直接呼叫,不要囉嗦推理。

問題是,少了推理,模型不知道「為什麼」要呼叫這個工具、下一步又該幹嘛。 它可能一口氣搜了三個不相干的關鍵字,拿到一堆結果卻不知道怎麼串起來。沒有 thought 當方向盤,行動就是亂按。

一邊只會想、碰不到現實;一邊只會做、沒有章法。ReAct 的想法很直接:把兩個合起來。


ReAct:想一步、做一步、看一步

ReAct 來自 2022 年一篇同名論文。它的核心是一個迴圈,由三種步驟交織而成:

  • Thought(思考):模型推理現在的狀況、決定下一步要做什麼
  • Action(行動):模型呼叫一個工具,例如搜尋某個關鍵字
  • Observation(觀察):工具回傳的真實結果,由系統填入

關鍵在「交織」。模型不是先想完再一次做完,而是想一步、做一步、看一步,再根據看到的結果想下一步。回到鼎泰豐那個問題,一段真實的 ReAct trace 長這樣:

Question: 台北那家鼎泰豐,禮拜一有開嗎?

Thought: 我不確定它的公休日,不該用猜的。先查鼎泰豐的營業資訊。
Action: search[鼎泰豐 台北 信義店 營業時間]
Observation: 鼎泰豐信義店,台北市信義區市府路 45 號 B1。
             營業時間:週一至週日 11:00–20:30。

Thought: 資料顯示週一到週日都營業,沒有標示週一公休,
         跟我原本的印象相反。那禮拜一是有開的。
Action: finish[鼎泰豐信義店週一至週日皆營業,平日 11:00 開始,
        因此禮拜一有開,並非公休。]

注意最後一步。模型真的去查了營業資訊,發現週一其實有開,於是把答案改成正確的——而不是像開頭那樣憑印象硬掰一個「週一公休」。這就是 ReAct 和純 CoT 最大的差別:它的結論被真實的觀察值綁住了。


用程式碼跑一次 ReAct 迴圈

把上面的概念落成程式碼,骨架其實很小。下面是一個最小可跑的 ReAct 迴圈,用 Anthropic 的 SDK 驅動模型:

import re
import anthropic

client = anthropic.Anthropic()

SYSTEM = """你以 ReAct 模式運作,依下列循環回答問題:

Thought: 思考下一步該做什麼
Action: 要採取的動作,格式為 工具名[參數]
(接著系統會填入 Observation: 工具的執行結果)

這個循環可以重複多次。當你有足夠資訊時,用 finish 結束:
Action: finish[最終答案]

可用工具:
- search[查詢]:用關鍵字查網路/地圖,回傳摘要
- finish[答案]:回傳最終答案,結束流程

只生成到 Action 為止,不要自己編造 Observation。"""


def react(question, tools, max_steps=6):
    transcript = f"Question: {question}\n"

    for _ in range(max_steps):
        msg = client.messages.create(
            model="claude-opus-4-8",
            max_tokens=1024,
            system=SYSTEM,
            messages=[{"role": "user", "content": transcript}],
            stop_sequences=["Observation:"],   # 生成到 Action 就停,把控制權交回來
        )
        step = msg.content[0].text
        transcript += step

        # 解析這一步的 Action:工具名[參數]
        match = re.search(r"Action:\s*(\w+)\[(.*?)\]", step, re.DOTALL)
        if not match:
            break
        tool, arg = match.group(1), match.group(2)

        if tool == "finish":
            return arg                         # 拿到最終答案,跳出迴圈

        observation = tools[tool](arg)         # 真的去執行工具
        transcript += f"Observation: {observation}\n"   # 把真實結果餵回去

    return None

整個迴圈只有三個動作,對應 ReAct 的三種步驟:

  1. 讓模型生成到 Action 為止stop_sequences=["Observation:"] 是關鍵——它讓模型吐完 Thought 和 Action 就停手,不准自己幻想 Observation。觀察值必須來自真實世界,不能由模型代筆。
  2. 解析並執行 Action。用正則抓出工具名和參數,真的去呼叫 search 或別的工具。
  3. 把真實結果接回 transcript,再進下一輪。模型下一步的 Thought,就是看著這個真實的 Observation 長出來的。

search 工具本身你自己實作——可能是打地圖 API、查商家資料庫,或呼叫任何外部服務。ReAct 不在意工具怎麼實作,它在意的是推理和行動輪流上場這件事。


為什麼「交織」就有效

ReAct 不是把 CoT 和工具呼叫硬湊在一起,兩者之間有真正的化學反應。

推理引導行動。 Thought 決定了下一個 Action 要查什麼、為什麼查。模型不是亂搜關鍵字,而是「我不確定公休日 → 那就查它的營業時間 → 看到週一到週日都營業 → 確認有開」。推理替行動指了方向。

行動把推理拉回現實。 這是 ReAct 對付幻覺的核心。純 CoT 的每一步都建立在模型的記憶上,記錯了就一路錯到底;ReAct 每隔一步就插入一個來自外部、模型沒辦法竄改的 Observation。推理一旦偏離事實,下一個觀察值就會把它拉回來。

兩者互補:光有推理會空想,光有行動會亂撞。交織起來,模型才能像人查資料那樣——一邊想假設,一邊找證據,再根據證據修正想法。


ReAct 與今天的 Agent

如果你覺得這個「想一步、做一步、看一步」的迴圈很眼熟,那是因為今天幾乎所有 AI agent 都是這個形狀。ReAct 是現代 agent 的思考骨架。

差別在於工程上的進化。上面那段程式碼用 re.search 去 regex 模型吐出來的文字——這很脆弱,模型只要格式跑掉一點,解析就爆掉。現代的 tool use / function calling 把這層標準化了:模型不再吐一段你得自己解析的文字,而是直接吐出結構化的工具呼叫(工具名 + JSON 參數),API 幫你切好。LangChain、各家 agent 框架的底層迴圈,本質上都還是 Thought → Action → Observation。

但 ReAct 不是萬靈丹,實作 agent 時這些坑你遲早會踩到:

  • 迴圈可能失控。 模型有時會卡在「再查一次」的鬼打牆裡停不下來,所以上面的程式碼一定要有 max_steps 這道剎車。
  • Observation 會吃光 context。 工具回傳的內容(整頁網頁、長 JSON)會不斷累積進 transcript,跑幾輪就把 context window 撐爆。實務上要對觀察值做裁切或摘要。
  • 錯誤會累積。 某一步的 Observation 誤導了模型,後面的推理可能整串歪掉。ReAct 比純 CoT 不容易幻覺,但不是免疫。
  • 何時該停是個難題。 模型要自己判斷「資訊夠了」才呼叫 finish。判斷太早會答得草率,判斷太晚會浪費一堆工具呼叫。

另一條路:Plan-and-Execute

ReAct 是「走一步、想一步」。但還有一派 agent 反過來做,叫 Plan-and-Execute(先規劃、再執行)

它把流程切成兩個角色。Planner(規劃者) 在動手前,先一口氣把完整的步驟清單排出來;Executor(執行者) 再照著清單一步步執行,每步可以呼叫工具。整份計畫跑完、或中途卡住了,才回頭重新規劃(re-plan)

兩者的差別,本質上是「什麼時候做計畫」:

面向ReActPlan-and-Execute
規劃時機走一步想一步,沒有全局計畫動手前先把整份計畫排好
適應性高——每步都看最新的 Observation 調整低——計畫定了才執行,要靠 re-plan 修正
迷路風險較高,長任務容易愈走愈偏題較低,全局計畫把大方向釘住
模型成本每一步都呼叫一次模型,步數一多就貴規劃用強模型排一次,執行可換便宜模型
適合場景步數少、高度依賴中途結果的探索型任務步驟多、結構清楚、能預先拆解的長任務

簡單說:ReAct 像沒帶地圖、邊走邊問路的人,靈活但容易繞遠路;Plan-and-Execute 像出發前先把路線規劃好,方向穩,但路一旦封了就得重畫。

而實務上,這兩者常常被縫在一起——先用 Plan-and-Execute 排出任務骨架,每個步驟內部再用 ReAct 邊想邊做。LangChain 這類框架兩種 agent 都有,要挑哪一種,取決於你的任務是「需要隨機應變」還是「需要照計畫走」。


結語

開頭那個編出「週一公休」的模型,問題從來不是它笨,而是它被迫在一個碰不到現實的盒子裡作答——除了猜,它沒有別的選擇。

ReAct 做的事,本質上是把盒子打開

Chain-of-Thought 給了模型「想」的能力,工具給了模型「做」的能力,而 ReAct 把兩者接成一個迴圈——想一步、做一步、看一步——讓模型的每一個結論都能回到真實世界驗證一次。

下次你看到一個 agent 在那裡 Thought、Action、Observation 地跑,你會知道那不是什麼新魔法。它只是在做一件很樸素、卻很關鍵的事:一邊想,一邊查,而不是閉著眼睛猜。