在大語言模型(LLM)的應用開發中,如何讓模型具備調用外部工具的能力是一個關鍵問題。我們不希望模型只是“生成答案”,而是能像一個智能體(Agent)一樣,按照推理鏈條自主決定調用搜索、計算、或數據庫查詢等工具,再結合結果給出最終答案。
本文將通過一段簡潔的 Python 代碼,演示如何實現一個迷你版的 ReAct Agent(Reasoning + Acting)。這個智能體能與用戶進行交互,自動選擇調用 Wikipedia 查詢、計算器 或 博客搜索 API 來輔助推理,并逐步生成最終答案。
1. 背景:ReAct 模式與工具調用
ReAct(Reason+Act)是一種大模型交互模式,流程大致為:
- Thought:模型根據問題思考下一步的策略。
- Action:模型選擇一個工具并傳入參數。
- Observation:外部環境返回結果。
- 循環:模型繼續思考并執行下一個動作,直到能直接給出最終答案。
這種模式能讓 LLM 從“單純生成”轉變為“與環境交互”,具備更強的可擴展性。
2. 核心代碼結構
我們先來看一段簡化的實現:
import re
import httpx
from langchain_openai import ChatOpenAIclient = ChatOpenAI(base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",api_key="<your_secret_key>",model="qwen2.5-72b-instruct"
)
這里我們使用 ChatOpenAI
封裝了一個大模型客戶端(可替換為任意兼容 OpenAI 接口的模型,例如 Qwen、GPT-4、Claude 等)。
接下來定義了一個 ChatBot
類,用于管理消息上下文:
class ChatBot:def __init__(self, system=""):self.system = systemself.messages = []if self.system:self.messages.append({"role": "system", "content": system})def __call__(self, message):self.messages.append({"role": "user", "content": message})result = self.execute()self.messages.append({"role": "assistant", "content": result})return resultdef execute(self):completion = client.invoke(input=self.messages)return completion.content
關鍵點:
self.messages
保存了完整的對話歷史(system prompt + user prompt + assistant response)。__call__
讓ChatBot
實例可以直接作為函數調用,便于迭代。- 每次執行都調用
client.invoke()
,并把所有上下文交給大模型。
3. Prompt 設計:引導 LLM 遵循 ReAct 格式
prompt = """
You run in a loop of Thought, Action, PAUSE, Observation.
At the end of the loop you output an Answer
Use Thought to describe your thoughts about the question you have been asked.
Use Action to run one of the actions available to you - then return PAUSE.
Observation will be the result of running those actions.Your available actions are:calculate:
e.g. calculate: 4 * 7 / 3
Runs a calculation and returns the number - uses Python so be sure to use floating point syntax if necessarywikipedia:
e.g. wikipedia: Django
Returns a summary from searching Wikipediasimon_blog_search:
e.g. simon_blog_search: Django
Search Simon's blog for that termAlways look things up on Wikipedia if you have the opportunity to do so.Example session:Question: What is the capital of France?
Thought: I should look up France on Wikipedia
Action: wikipedia: France
PAUSEYou will be called again with this:Observation: France is a country. The capital is Paris.You then output:Answer: The capital of France is Paris
""".strip()
這段 system prompt 明確規定了交互格式:
- 模型必須先寫 Thought。
- 如果需要調用工具,則寫
Action: 工具名: 參數
,然后返回PAUSE
。 - 工具執行結果會以
Observation: ...
的形式喂回給模型。 - 最后,模型才能輸出
Answer:
。
通過嚴格約束,我們讓模型進入一個 循環推理-調用-觀察 的流程。
4. 動作解析與執行
利用正則表達式匹配模型輸出中的 Action:
action_re = re.compile('^Action: (\w+): (.*)$')
在主循環 query()
里:
def query(question, max_turns=5):i = 0bot = ChatBot(prompt)next_prompt = questionwhile i < max_turns:i += 1result = bot(next_prompt)print(result)actions = [action_re.match(a) for a in result.split('\n') if action_re.match(a)]if actions:# There is an action to runaction, action_input = actions[0].groups()if action not in known_actions:raise Exception("Unknown action: {}: {}".format(action, action_input))print(" -- running {} {}".format(action, action_input))observation = known_actions[action](action_input)print("Observation:", observation)next_prompt = "Observation: {}".format(observation)else:return
這里的邏輯是:
- 把用戶問題送進模型,獲取輸出。
- 如果輸出里有
Action
,則調用對應工具函數。 - 把工具的結果作為
Observation
再送回模型。 - 如果模型直接輸出
Answer
,就結束循環。
5. 工具實現
目前實現了三個工具:
def wikipedia(q):return httpx.get("https://en.wikipedia.org/w/api.php", params={"action": "query","list": "search","srsearch": q,"format": "json"}).json()["query"]["search"][0]["snippet"]def simon_blog_search(q):results = httpx.get("https://datasette.simonwillison.net/simonwillisonblog.json", params={"sql": """selectblog_entry.title || ': ' || substr(html_strip_tags(blog_entry.body), 0, 1000) as text,blog_entry.createdfromblog_entry join blog_entry_fts on blog_entry.rowid = blog_entry_fts.rowidwhereblog_entry_fts match escape_fts(:q)order byblog_entry_fts.ranklimit1""".strip(),"_shape": "array","q": q,}).json()return results[0]["text"]def calculate(what):return eval(what)known_actions = {"wikipedia": wikipedia,"calculate": calculate,"simon_blog_search": simon_blog_search
}
- Wikipedia:通過官方 API 獲取搜索摘要。
- Simon Blog Search:調用 Simon Willison 的博客 API 進行全文檢索。
- Calculate:直接用 Python
eval()
計算表達式(僅演示,實際生產中要做安全防護)。
6. 效果演示
執行:
query("What does India share borders with?")
可得到以下推理過程:
Thought: To answer this question, I need to look up information about India's geography and its neighboring countries on Wikipedia.
Action: wikipedia: India
PAUSE-- running wikipedia India
Observation: <span class="searchmatch">India</span>, officially the Republic of <span class="searchmatch">India</span>, is a country in South Asia. It is the seventh-largest country by area; the most populous country since 2023;
Thought: The provided excerpt does not contain the specific information about the countries that share borders with India. I need to refine my search to get more detailed geographical information.
Action: wikipedia: Geography of India
PAUSE-- running wikipedia Geography of India
Observation: <span class="searchmatch">Of</span> <span class="searchmatch">India</span>. 2007. ISBN 978-81-230-1423-4. Wikimedia Commons has media related to <span class="searchmatch">Geography</span> <span class="searchmatch">of</span> <span class="searchmatch">India</span>. Singh, R.L. (1971). <span class="searchmatch">India</span> A Regional <span class="searchmatch">Geography</span>. National
Thought: The current observation still does not provide the specific information about the countries that share borders with India. I will try a more direct search to find this information.
Action: wikipedia: Borders of India
PAUSE-- running wikipedia Borders of India
Observation: The Republic <span class="searchmatch">of</span> <span class="searchmatch">India</span> shares <span class="searchmatch">borders</span> with several sovereign countries; it shares land <span class="searchmatch">borders</span> with China, Bhutan, Nepal, Pakistan, Bangladesh, and Myanmar
Answer: India shares borders with China, Bhutan, Nepal, Pakistan, Bangladesh, and Myanmar.
可以看到,模型先思考,再調用 Wikipedia API,拿到結果后生成最終答案。
7. 可擴展的方向
這個簡單的 Demo 展示了 ReAct 智能體的核心循環。在實際應用中,讀者朋友們可以進一步擴展:
- 增加更多工具:如數據庫查詢、文件系統、搜索引擎、第三方 API 等。
- 錯誤處理:對
eval()
和網絡請求增加異常捕獲和安全限制。 - 并行工具調用:讓模型一次調用多個工具,合并結果后繼續推理。
- LangChain/LangGraph 集成:結合更強的智能體框架,實現任務規劃、子任務拆解與狀態管理。
8. 總結
通過不到 200 行代碼,我們實現了一個簡潔的 ReAct 風格智能體。它展示了以下關鍵點:
- 利用 system prompt 約束 LLM 輸出格式。
- 通過 Action/Observation 循環 讓模型與外部環境交互。
- 把 工具調用 抽象成函數,方便擴展和維護。
這類模式是構建 大模型智能體 的核心思路,未來讀者朋友們可以在此基礎上擴展成更強大的多工具、多任務智能體。