当 LLM 不再只是“聊天机器人”,而是能调用工具、自主决策的“智能体”,我们离通用人工智能又近了一步。本文将带你手搓一个最小可用的 ReAct Agent,并深入探讨其背后的设计哲学与工程陷阱。
大语言模型(LLM)展示了惊人的推理能力,但其本质仍是“静态知识库”——它无法主动获取实时信息,也无法执行外部操作。例如,当你问“今天石家庄天气如何?”模型只能根据训练数据(可能截止到 2026 年初)给出过时回答。
AI 智能体(Agent) 的出现弥补了这一鸿沟。它赋予 LLM 两个关键能力:
在众多 Agent 范式中,ReAct(Reasoning + Acting) 因其简洁、可解释性强而成为入门首选。
ReAct 由 Yao 等人在 2022 年提出,其核心循环为:
Thought -> Action -> Observation -> (Repeat) -> Final Answer这种“思考-行动-观察”的循环让模型能像人类一样,边做边想,逐步解决问题。
我们将使用 Python 3.10+,并借助 OpenAI API(或兼容接口)来驱动 LLM。为方便演示,所有工具均为本地模拟。
pip install openai python-dotenv工具以字典形式注册,包含名称、描述和调用函数。这里实现三个示例工具:
get_current_time:获取当前时间(模拟实时信息)。search_web:模拟搜索引擎(实际可替换为 SerpAPI)。calculate:执行数学表达式。import json
import math
from datetime import datetime
def get_current_time():
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def search_web(query: str):
# 模拟搜索,实际可接入真实 API
mock_db = {
"石家庄天气": "2026-07-23 石家庄:多云,25°C ~ 32°C,东南风 3 级。",
"Python 3.11 新特性": "包括异常组、性能优化等。"
}
return mock_db.get(query, f"未找到关于 '{query}' 的结果。")
def calculate(expression: str):
try:
# 安全起见,仅允许简单算术
allowed = set("0123456789+-*/(). ")
if not all(c in allowed for c in expression):
return "错误:表达式包含非法字符。"
result = eval(expression, {"__builtins__": {}}, {})
return f"计算结果:{result}"
except Exception as e:
return f"计算错误:{e}"
TOOLS = {
"get_current_time": {
"name": "get_current_time",
"description": "获取当前的精确时间(年-月-日 时:分:秒)。无需参数。",
"func": get_current_time,
"parameters": {}
},
"search_web": {
"name": "search_web",
"description": "模拟网络搜索,输入查询字符串,返回相关信息。",
"func": search_web,
"parameters": {"query": "str"}
},
"calculate": {
"name": "calculate",
"description": "执行数学计算,输入算式,如 '3 * (5 + 2)'。",
"func": calculate,
"parameters": {"expression": "str"}
}
}LLM 本身不会自动按 ReAct 格式输出,我们需要通过精心设计的系统提示来“指导”它。提示必须包含:
Thought: / Action: / Action Input: / Final Answer:)。SYSTEM_PROMPT = """你是一个能够使用工具的智能助手。请严格按照以下格式回复:
Thought: 你当前的推理过程(中文)。
Action: 你要调用的工具名称,必须从以下列表中选择:{tool_names}
Action Input: 调用该工具所需的输入参数(JSON 格式)。
如果你已经有足够信息回答用户问题,则输出:
Final Answer: 最终回答内容(中文)。
注意:每次只调用一个工具,等待观察结果后再继续。
"""
def build_system_prompt():
tool_names = ", ".join(TOOLS.keys())
return SYSTEM_PROMPT.format(tool_names=tool_names)维护一个消息列表,包含系统提示、用户问题以及每次循环产生的 assistant 和 user(工具结果)消息。
主循环负责:
Thought 和 Action。Observation。Final Answer。import re
def parse_response(text: str):
"""解析 LLM 输出,返回 (thought, action, action_input, final_answer)"""
thought_match = re.search(r"Thought:\s*(.*?)(?=\nAction:|\nFinal Answer:|$)", text, re.DOTALL)
action_match = re.search(r"Action:\s*(\w+)", text)
action_input_match = re.search(r"Action Input:\s*(\{.*\})", text, re.DOTALL)
final_match = re.search(r"Final Answer:\s*(.*)", text, re.DOTALL)
thought = thought_match.group(1).strip() if thought_match else None
action = action_match.group(1).strip() if action_match else None
action_input = action_input_match.group(1).strip() if action_input_match else None
final_answer = final_match.group(1).strip() if final_match else None
return thought, action, action_input, final_answerimport openai
class ReActAgent:
def __init__(self, model="gpt-4o-mini", max_iterations=5):
self.model = model
self.max_iterations = max_iterations
self.messages = [
{"role": "system", "content": build_system_prompt()}
]
def run(self, user_query: str):
self.messages.append({"role": "user", "content": user_query})
iteration = 0
while iteration < self.max_iterations:
iteration += 1
print(f"\n--- 迭代 {iteration} ---")
# 调用 LLM
response = openai.chat.completions.create(
model=self.model,
messages=self.messages,
temperature=0.0, # 保持确定性
)
reply = response.choices[0].message.content
self.messages.append({"role": "assistant", "content": reply})
print(f"LLM 回复:\n{reply}")
thought, action, action_input, final_answer = parse_response(reply)
if final_answer:
print(f"\n✅ 最终回答:{final_answer}")
return final_answer
if not action:
print("⚠️ 未解析到 Action,强制终止。")
return "抱歉,我无法继续推理。"
# 执行工具
tool = TOOLS.get(action)
if not tool:
observation = f"错误:未知工具 '{action}'。"
else:
try:
# 解析参数
if action_input:
params = json.loads(action_input)
else:
params = {}
func = tool["func"]
observation = func(**params)
except Exception as e:
observation = f"工具执行异常:{e}"
print(f"🔧 执行 {action} -> 观察:{observation}")
# 将观察结果作为 user 消息追加
self.messages.append({
"role": "user",
"content": f"Observation: {observation}\n请继续思考并给出下一步 Action 或 Final Answer。"
})
return "达到最大迭代次数,未得出最终答案。"# 设置 OpenAI API Key(或兼容端点)
import os
openai.api_key = os.getenv("OPENAI_API_KEY")
openai.base_url = os.getenv("OPENAI_BASE_URL") # 可选
agent = ReActAgent()
question = "现在石家庄天气如何?另外,计算 123 * 456 等于多少?"
answer = agent.run(question)
print(answer)预期输出片段(实际可能略有不同):
--- 迭代 1 ---
LLM 回复:
Thought: 用户想知道石家庄的天气,并计算一个乘法。我需要先搜索天气信息。
Action: search_web
Action Input: {"query": "石家庄天气"}
...
🔧 执行 search_web -> 观察:2026-07-23 石家庄:多云,25°C ~ 32°C,东南风 3 级。
...
--- 迭代 2 ---
LLM 回复:
Thought: 已获得天气信息。现在需要计算 123*456。
Action: calculate
Action Input: {"expression": "123 * 456"}
...
🔧 执行 calculate -> 观察:计算结果:56088
...
--- 迭代 3 ---
LLM 回复:
Final Answer: 当前石家庄天气为多云,气温 25°C ~ 32°C,东南风 3 级。123 乘以 456 等于 56088。工具描述必须精准,因为 LLM 依赖它来选择正确工具。例如,get_current_time 描述中注明“无需参数”,可避免模型传入空 JSON 时出错。
实际应用中,模型可能输出畸形的 JSON,甚至不包含 Action Input。建议:
json5 库宽容解析。设置最大迭代次数(max_iterations),并在提示中加入“如果重复相同 Thought 超过两次,请直接给出 Final Answer”。
eval 等危险操作需限制上下文(如上例中清空 __builtins__)。本文从零构建了一个基于 ReAct 模式的 AI 智能体,核心代码仅百行左右,却展示了 LLM 自主规划、工具调用和推理的全过程。ReAct 的可解释性使其成为生产级 Agent 的可靠起点。
但请记住,Agent 不是“万能药”——它受限于基座模型的能力和工具质量。在实际落地中,需结合错误处理、监控和评估体系,才能让 Agent 真正可靠。
希望这篇文章能帮助你快速上手 AI Agent 开发,并在思否社区激发更多技术讨论。欢迎在评论区留下你的实践心得或问题!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。