本文 AI 產出,尚未審核

LangChain 課程 – Agents:自主智能體

主題:ReAct Agent(思考‑行動‑觀察)


簡介

在大型語言模型(LLM)日益普及的今天,單純的「一次性」提示已難以滿足需要多步推理、外部工具呼叫或動態決策的應用。ReAct(Reason‑Act‑Observe)Agent 正是為了解決這類需求而設計的,它將 思考(Reasoning)行動(Action)觀察(Observation) 三個環節緊密結合,使智能體能在執行過程中不斷迭代、修正自己的策略。

ReAct 的核心概念是讓 LLM 先產生「思考」的文字描述,再根據描述決定要呼叫哪個工具(如搜尋、計算、資料庫),最後把工具回傳的結果視為「觀察」回饋給模型,進一步產出下一輪的思考。這樣的迴圈不僅提升了 答案的正確性,也讓開發者能以 模組化、可追蹤 的方式構建複雜的對話系統。

在本單元,我們將從概念說明、程式碼實作、常見陷阱與最佳實踐,最後延伸到實務應用,帶你一步步掌握 ReAct Agent 的使用方法。


核心概念

1. ReAct 的三大步驟

步驟 目的 範例
思考 (Reason) LLM 產生自然語言的推理或指令說明 「我需要先查詢今天的天氣,才能決定是否建議外出。」
行動 (Act) 根據思考結果呼叫對應的工具(Tool) 呼叫 search_weather API
觀察 (Observe) 收集工具回傳的資訊,作為下一輪思考的輸入 取得「晴,最高 28°C」的天氣回應

這三個環節在 LangChain 中透過 AgentExecutorTool 物件自動串接,開發者只需要定義好工具與提示模板,剩下的迴圈由框架負責。

2. Prompt Template:把思考與行動寫進 Prompt

ReAct 需要在提示中明確告訴模型「如果需要執行工具,請以 Action: 開頭」的格式。例如:

Question: {input}
Thought: {your reasoning}
Action: {tool_name}[{tool_input}]
Observation: {tool_output}

LangChain 提供 ReActPromptTemplate(或自行組合 ChatPromptTemplate)來自動產生這樣的結構。

3. Tool 的設計與註冊

每個外部工具必須實作 Tool 介面,最重要的屬性有:

  • name:工具名稱,必須與 Prompt 中的 Action 名稱對應。
  • description:說明工具的功能,會被放入系統訊息讓模型了解可使用的工具。
  • run(input_str: str) -> str:執行工具的實際邏輯,回傳文字型別的結果。

程式碼範例

以下範例採用 Python(LangChain 官方語言),每段程式碼皆附有說明註解,方便初學者快速上手。

範例 1:最簡單的 ReAct Agent(搜尋與計算)

# 1️⃣ 載入必要套件
from langchain.agents import AgentExecutor, ReActAgent
from langchain.tools import Tool
from langchain.llms import OpenAI
from langchain.prompts import ChatPromptTemplate

# 2️⃣ 定義兩個簡易工具
class SearchTool(Tool):
    name = "search"
    description = "在網路上搜尋關鍵字,回傳搜尋結果的摘要。"

    def _run(self, query: str) -> str:
        # 這裡僅示範,實際可接 Google SERP API
        return f"搜尋結果:{query} 的相關資訊..."

class CalculatorTool(Tool):
    name = "calculator"
    description = "計算數學表達式,回傳結果。"

    def _run(self, expression: str) -> str:
        try:
            return str(eval(expression))
        except Exception:
            return "計算失敗"

# 3️⃣ 建立 Prompt(ReAct 樣板)
prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "你是一個能使用工具的助理。"),
        ("human", "{input}"),
    ]
)

# 4️⃣ 建立 LLM 與 Agent
llm = OpenAI(model="gpt-3.5-turbo", temperature=0)
react_agent = ReActAgent.from_llm_and_tools(
    llm=llm,
    tools=[SearchTool(), CalculatorTool()],
    prompt=prompt,
)

# 5️⃣ 執行 Agent
executor = AgentExecutor(agent=react_agent, tools=[SearchTool(), CalculatorTool()], verbose=True)

question = "台北今天的天氣如何?請算出最高溫度減去最低溫度的差值。"
response = executor.invoke({"input": question})
print(response["output"])

重點

  • verbose=True 可讓你看到 Thought / Action / Observation 的完整迭代過程,方便除錯。
  • eval 僅作示範,實務上需使用安全的計算庫(如 sympy)。

範例 2:結合資料庫查詢

假設我們有一個 SQLite 資料庫,裡面存放產品資訊。以下示範如何把資料庫查詢封裝為 Tool,讓 ReAct Agent 能根據使用者問題自動查詢。

import sqlite3
from langchain.tools import StructuredTool

# 建立資料庫連線(一次性建立,後續可重用)
conn = sqlite3.connect("products.db")
cursor = conn.cursor()

class ProductLookupTool(StructuredTool):
    name = "product_lookup"
    description = "根據產品名稱查詢庫存與價格。輸入 JSON,例如 {'name': 'iPhone 15'}"

    def _run(self, input_json: str) -> str:
        import json
        data = json.loads(input_json)
        name = data.get("name")
        cursor.execute(
            "SELECT stock, price FROM products WHERE name = ?", (name,)
        )
        row = cursor.fetchone()
        if row:
            stock, price = row
            return f"產品 {name} 庫存 {stock} 件,價格 ${price}"
        return f"找不到產品 {name}"

# 重新建立 Agent(加入新工具)
react_agent = ReActAgent.from_llm_and_tools(
    llm=llm,
    tools=[SearchTool(), CalculatorTool(), ProductLookupTool()],
    prompt=prompt,
)

executor = AgentExecutor(agent=react_agent, tools=[SearchTool(), CalculatorTool(), ProductLookupTool()], verbose=True)

question = "我想知道 iPhone 15 現在的庫存與價格,還有今天台北的天氣。"
print(executor.invoke({"input": question})["output"])

說明

  • StructuredTool 允許我們定義 JSON 輸入,讓模型更精確地傳遞參數。
  • 使用 json.loads 解析後再執行 SQL,避免 SQL 注入。

範例 3:多輪對話與記憶(使用 ConversationBufferMemory)

在實務應用中,使用者往往會在同一對話中提出多個相關問題。下面示範如何把 記憶 (Memory) 加入 ReAct Agent,使其能參考前一次的觀察結果。

from langchain.memory import ConversationBufferMemory

# 建立記憶物件
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)

# 重新建立 Prompt,加入記憶占位符
prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "你是一個會使用工具的助理,請善用先前的對話記錄。"),
        ("human", "{chat_history}\nUser: {input}"),
    ]
)

react_agent = ReActAgent.from_llm_and_tools(
    llm=llm,
    tools=[SearchTool(), CalculatorTool()],
    prompt=prompt,
    memory=memory,
)

executor = AgentExecutor(agent=react_agent, tools=[SearchTool(), CalculatorTool()], verbose=True)

# 第一次詢問
print(executor.invoke({"input": "今天紐約的天氣如何?"})["output"])

# 第二次詢問(與第一次相關)
print(executor.invoke({"input": "如果要出門,請告訴我需要帶的衣物。"})["output"])

關鍵

  • ConversationBufferMemory 會自動把每次的 HumanAssistantObservation 訊息累積到 chat_history,在 Prompt 中提供上下文。
  • 這樣的設計讓 Agent 能在 多輪對話 中保持一致性與邏輯。

範例 4:自訂 ReAct 思考模板(加入「自我檢查」)

有時候我們希望模型在執行行動前先做一次 自我檢查,確保指令正確。下面示範如何在 Prompt 中加入「Check」區塊。

custom_prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "你是一個能使用工具的助理,請遵循以下流程:\n"
                    "1. 思考 (Thought)\n"
                    "2. 檢查 (Check) – 確認是否需要工具\n"
                    "3. 行動 (Action) – 若需要則呼叫工具\n"
                    "4. 觀察 (Observation)"),
        ("human", "{input}"),
    ]
)

class CheckTool(Tool):
    name = "check"
    description = "檢查是否需要使用其他工具,回傳 'yes' 或 'no'。"

    def _run(self, _: str) -> str:
        # 這裡僅示範,實際可根據需求實作
        return "yes"

react_agent = ReActAgent.from_llm_and_tools(
    llm=llm,
    tools=[SearchTool(), CalculatorTool(), CheckTool()],
    prompt=custom_prompt,
)

executor = AgentExecutor(agent=react_agent, tools=[SearchTool(), CalculatorTool(), CheckTool()], verbose=True)

print(executor.invoke({"input": "請告訴我今天的匯率,然後把它乘以 100。"})["output"])

技巧

  • 透過自訂 Prompt,我們可以 擴充 ReAct 流程,加入額外的驗證或安全檢查步驟。
  • 在實務上,這類「Check」工具常用於 授權驗證成本控制(如限制 API 呼叫次數)等。

常見陷阱與最佳實踐

陷阱 說明 最佳實踐
工具名稱不一致 Prompt 中的 Action: 必須與 Tool.name 完全相同,否則模型找不到對應工具。 測試階段 使用 verbose=True,確認每次 Action: 的字串正確。
輸入格式不明確 若工具需要 JSON、特定語法,模型有時會產生不符合格式的字串。 Tool.description 中加入 範例,並在 Prompt 加入「請使用以下格式」的指示。
迴圈無止境 當模型無法正確產生 Finish 訊息時,Agent 可能會無限循環。 設定 max_iterations(例如 5)或在 Tool.run 中加入 安全檢查
安全風險 直接使用 eval、SQL 拼接等會產生執行任意程式碼的危險。 使用 安全的計算庫參數化查詢,或在 Tool 前加入 沙箱 檢查。
記憶體爆炸 長時間對話會讓 ConversationBufferMemory 變大,影響效能。 定期 截斷(例如保留最近 10 條訊息)或改用 SummarizerMemory

其他實用技巧

  1. 使用 tool_choice="auto":讓 LLM 自行決定是否需要工具,減少手動判斷的錯誤。
  2. 加入 tool_error 處理:在 AgentExecutor 中捕捉工具執行失敗,回傳給模型作為新的 Observation,促使模型重新思考。
  3. 觀察日誌verbose=True 會印出完整的 Thought/Action/Observation 訊息,建議在開發階段開啟,正式上線後可改為寫入日誌檔。

實際應用場景

場景 為何適合使用 ReAct Agent
客服機器人 需要即時查詢訂單、庫存、物流資訊,且要在同一回合內完成多步驗證。
金融分析助理 先從網路抓取最新股價,接著計算技術指標,最後根據結果給出投資建議。
旅遊規劃 先搜尋目的地天氣、景點評價,再計算最佳路線,最終產出行程表。
醫療問診 先根據症狀搜尋相關文獻,再使用計算工具估算藥物劑量,最後回覆患者。
內部知識庫搜尋 先使用向量搜尋找出相關文件,然後根據文件內容做摘要或計算統計。

在這些情境下,ReAct 能夠 動態決定何時呼叫外部資源,避免一次性提示的資訊過載,同時保證 答案可追溯(每一步都有 Observation 記錄)。


總結

ReAct Agent 為 LangChain 中最具彈性、最實務導向的智能體模型之一。透過 思考 → 行動 → 觀察 的迴圈,我們可以:

  1. 把 LLM 的自然語言推理與外部工具結合,形成可執行的工作流程。
  2. 以 Prompt 模板明確指示模型,降低誤用工具的風險。
  3. 加入記憶、檢查與安全機制,讓智能體在多輪對話或高風險環境中仍保持可靠。

只要遵守 工具名稱一致、輸入格式明確、迭代次數受控 等最佳實踐,ReAct Agent 就能在客服、金融、旅遊、醫療等多種領域發揮威力。希望本篇教學能幫助你快速上手,並在自己的專案中構建出更聰明、更可靠的自主智能體!祝開發順利 🎉