AI Agent 入门(四):手写 Agent 循环 —— ReAct 模式完整实现
本文是 AI Agent 入门系列的第 4 篇。前两篇我们学会了调 LLM API 和 Tool Use(函数调用),现在进入最关键的一课:把前面的知识串联起来,从零实现一个完整的 Agent 主循环。
学完本篇,你将拥有一个可以自主推理、调用工具、多步完成任务的 AI Agent。
本课目标
- 理解 ReAct(Reasoning + Acting)推理模式
- 从零实现一个完整的 Agent 主循环
- 掌握异常处理和安全边界
- 让 Agent 能自主完成多步骤任务
本课产出
- 新建文件:
lesson04_agent_loop.py,粘贴下文完整代码 - 运行:
python lesson04_agent_loop.py - 效果:一个完整的 Agent 循环,支持多工具注册、带日志追踪的多轮推理、max_iterations 上限、异常保护
一、什么是 ReAct?
ReAct = Reasoning + Acting(推理 + 行动),是 2022 年提出的一种 Agent 推理模式。你可以把它理解成 LLM 的思考循环:
思考 → 行动 → 观察 → 再思考 → 再行动 → ... → 最终答案
用一个具体例子说明,假设用户问"Python 是什么?你知道它最新版本吗?":
① 思考:用户想知道 Python 是什么 + 版本信息
② 行动:调用 search("Python") 获取基础信息
③ 观察:拿到 Python 的定义,但没版本号
④ 思考:需要再算一下 3.14 * 2.71 吗?不用,那是另一回事,给答案
⑤ 最终答案:Python 是...
每一步,LLM 都在决定"下一步做什么"。这就是 Agent 和普通 LLM 的根本区别。
二、Agent 主循环的结构
一个完整的 Agent 主循环只需下面几行伪代码:
for iteration in range(max_iterations): # ① 防死循环
response = llm(messages, tools) # ② 调用 LLM
if response.asks_for_tool: # ③ LLM 想要工具?
result = execute_tool(response) # ④ 执行工具
messages.append(result) # ⑤ 结果喂回
else:
return response.content # ⑥ 给出最终回答
五个关键点:
| 机制 | 作用 | 没有它会怎样 |
|---|---|---|
| max_iterations | 最多循环 N 轮 | 死循环,Agent 永远停不下来 |
| 异常捕获 | API 超时/工具出错不崩溃 | 一个报错就退出,体验很差 |
| 工具白名单 | 只执行注册过的工具 | LLM 可能"幻想"出不存在的工具 |
| 调用日志 | 打印每一步推理过程 | 出了问题完全不知道哪步错 |
| 消息历史管理 | messages 不断追加 | LLM 丢失上下文,答非所问 |
三、消息历史是怎么流转的?
这是 Agent 最容易被忽略的细节。一次多工具调用的 messages 变化过程如下:
# 第 1 轮前:系统 prompt + 用户问题
messages = [
{"role": "system", "content": "你是 AI 助手,可以使用工具..."},
{"role": "user", "content": "什么是 ReAct?算一下 3.14 * 2.71"},
]
# 第 1 轮 LLM 返回:请求调用 search_knowledge("ReAct")
# → 执行工具 → 把工具结果追加到 messages:
messages = [
system, user,
{"role": "assistant", "content": null, "tool_calls": [...]}, # LLM 的工具请求
{"role": "tool", "content": "ReAct 是..."}, # 工具执行结果
]
# 第 2 轮 LLM 返回:请求调用 calculator("3.14 * 2.71")
# → 执行工具 → 继续追加:
messages = [
system, user,
assistant(tool_call_1), tool(result_1),
assistant(tool_call_2), tool(result_2),
]
# 第 3 轮 LLM 返回:"ReAct 是... 3.14 * 2.71 = 8.5094"(不再请求工具)
# → 循环结束,输出最终回答
每一轮 LLM 都能看到之前的所有"思考→行动→观察"历史,这就是 Agent 能够多步推理的秘诀。
四、完整代码
新建 lesson04_agent_loop.py,粘贴以下代码:
"""
AI Agent 入门(四):手写 Agent 循环 —— ReAct 模式完整实现
"""
import json, os
from pathlib import Path
from dotenv import load_dotenv
load_dotenv(Path(__file__).with_name(".env"))
DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY")
if not DEEPSEEK_API_KEY:
print("❌ 未检测到 API Key!请先设置 .env 文件。")
exit(1)
from openai import OpenAI
client = OpenAI(api_key=DEEPSEEK_API_KEY, base_url="https://api.deepseek.com")
MODEL = "deepseek-chat"
# ===========================================
# 工具函数
# ===========================================
def calculator(expr: str) -> str:
"""安全数学计算器"""
try:
allowed = set("0123456789+-*/(). ")
if not all(c in allowed for c in expr):
return f"错误:表达式包含不允许的字符"
if len(expr) > 50:
return "错误:表达式过长"
result = eval(expr)
return f"{expr} = {result}"
except Exception as e:
return f"计算错误:{e}"
def search_knowledge(keyword: str) -> str:
"""知识搜索(mock 数据)"""
knowledge_base = {
"python": "Python 是解释型、面向对象的高级编程语言,由 Guido van Rossum 于 1991 年发布。",
"ai agent": ("AI Agent 是能自主感知环境、做出决策并执行动作的 AI 系统,"
"由 LLM + 工具 + 记忆 + 规划四个核心组件构成。"),
"react": ("ReAct(Reasoning + Acting)是 Agent 推理模式,"
"LLM 交替进行 Thought 和 Action,通过 Observation 迭代推进。"),
"function calling": ("Function Calling 是 LLM 核心能力,"
"让 LLM 输出结构化的工具调用请求,由外部代码执行。"),
}
for k, v in knowledge_base.items():
if k in keyword.lower():
return f"📚 {k}:{v}"
return f"未找到关于「{keyword}」的信息。试试:Python、AI Agent、ReAct、Function Calling"
def get_datetime(_: str = "") -> str:
"""获取当前时间"""
from datetime import datetime
now = datetime.now()
days = ['一','二','三','四','五','六','日']
return f"当前时间:{now.strftime('%Y年%m月%d日 %H:%M:%S')}(星期{days[now.weekday()]})"
# ===========================================
# 工具注册表
# ===========================================
TOOL_DEFINITIONS = [
{
"type": "function",
"function": {
"name": "calculator",
"description": "执行数学表达式计算。支持 + - * / ** 和括号。",
"parameters": {
"type": "object",
"properties": {
"expr": {"type": "string", "description": "数学表达式,如 '2.5*3.7+1.8'"}
},
"required": ["expr"]
}
}
},
{
"type": "function",
"function": {
"name": "search_knowledge",
"description": "搜索知识库。支持:Python、AI Agent、ReAct、Function Calling 等。",
"parameters": {
"type": "object",
"properties": {
"keyword": {"type": "string", "description": "搜索关键词"}
},
"required": ["keyword"]
}
}
},
{
"type": "function",
"function": {
"name": "get_datetime",
"description": "获取当前日期和时间。用于回答'现在几点'、'今天日期'等问题。",
"parameters": {"type": "object", "properties": {}, "required": []}
}
}
]
TOOL_MAP = {"calculator": calculator, "search_knowledge": search_knowledge, "get_datetime": get_datetime}
# ===========================================
# 🔥 核心:Agent 主循环
# ===========================================
def call_tool(tool_call) -> str:
"""执行单个工具调用"""
func_name = tool_call.function.name
try:
func_args = json.loads(tool_call.function.arguments)
except json.JSONDecodeError:
return f"参数格式错误:{tool_call.function.arguments}"
func = TOOL_MAP.get(func_name)
if not func:
return f"未知工具 {func_name},可用:{', '.join(TOOL_MAP.keys())}"
print(f" → {func_name}({json.dumps(func_args, ensure_ascii=False)})")
try:
result = func(**func_args) if func_args else func("")
except TypeError:
result = func("")
print(f" ← {result}")
return result
def run_agent(user_input: str, max_iterations: int = 8):
"""Agent 主循环 —— ReAct 模式"""
messages = [
{"role": "system", "content": (
"你是智能 AI 助手,可使用工具完成任务。遵循 ReAct 模式:\n"
"1. 思考是否需要工具 → 2. 调用工具 → 3. 观察结果 → 4. 重复或给出答案\n"
"数学计算务必使用 calculator,知识查询务必使用 search_knowledge。"
)},
{"role": "user", "content": user_input},
]
print(f"\n{'='*60}\n用户问题:{user_input}\n{'='*60}")
for iteration in range(1, max_iterations + 1):
print(f"\n{'─'*40}\n🔄 第 {iteration} 轮")
# 调用 LLM
try:
response = client.chat.completions.create(
model=MODEL, messages=messages, tools=TOOL_DEFINITIONS,
)
except Exception as e:
print(f"❌ LLM 调用失败:{e}")
break
msg = response.choices[0].message
# LLM 要求调用工具
if msg.tool_calls:
print(f"💭 调用 {len(msg.tool_calls)} 个工具")
messages.append(msg)
for tc in msg.tool_calls:
messages.append({"role": "tool", "tool_call_id": tc.id, "content": call_tool(tc)})
else:
# LLM 给出最终答案
print(f"\n{'='*60}\n✅ 最终回答:\n{msg.content}\n{'='*60}")
return msg.content
print(f"\n⚠️ 达到最大迭代次数 {max_iterations}")
return None
# ===========================================
# 测试
# ===========================================
if __name__ == "__main__":
print("\n🧪 测试 1:单步 → 搜索知识")
run_agent("什么是 ReAct 模式?")
print("\n\n🧪 测试 2:多步 → 搜索 + 计算")
run_agent("Python 是哪一年发布的?今年距离发布那年多少年?")
print("\n\n🧪 测试 3:不需要工具")
run_agent("你好!说一句中文问候就行")
运行结果示例
🧪 测试 1:单步 → 搜索知识
============================================================
用户问题:什么是 ReAct 模式?
============================================================
────────────────────────────────────────
🔄 第 1 轮
💭 调用 1 个工具
→ search_knowledge({"keyword": "ReAct"})
← 📚 react:ReAct(Reasoning + Acting)是一种 Agent 推理模式...
────────────────────────────────────────
🔄 第 2 轮
============================================================
✅ 最终回答:
ReAct(Reasoning + Acting)是一种让 AI Agent 进行多步推理的模式。
它让 LLM 交替进行"思考"和"行动",通过观察工具结果来迭代推进,直到得出最终答案。
============================================================
🧪 测试 2:多步 → 搜索 + 计算
============================================================
用户问题:Python 是哪一年发布的?今年距离发布那年多少年?
============================================================
────────────────────────────────────────
🔄 第 1 轮
💭 调用 1 个工具
→ search_knowledge({"keyword": "Python"})
← 📚 python:Python 是解释型...于 1991 年发布。
────────────────────────────────────────
🔄 第 2 轮
💭 调用 1 个工具
→ get_datetime({})
← 当前时间:2026年07月06日 15:30:00(星期一)
────────────────────────────────────────
🔄 第 3 轮
💭 调用 1 个工具
→ calculator({"expr": "2026 - 1991"})
← 2026 - 1991 = 35
────────────────────────────────────────
🔄 第 4 轮
============================================================
✅ 最终回答:
Python 于 1991 年发布,现在是 2026 年,距离发布已有 35 年。
============================================================
看!Agent 自动执行了 3 个工具(搜索→查时间→计算),4 轮推理后给出了完整答案。
这就是 Agent 的威力——不是 LLM 自己算出来的,是它调动工具查出来的。
常见报错
| 问题 | 原因 | 解决 |
|---|---|---|
| Agent 在第 1 轮就直接回答了(没调工具) | 问题太简单,LLM 觉得不需要工具 | 试试问"现在几点"或包含计算的问题 |
| 达到最大迭代次数 | 任务太复杂,或 LLM 陷入了循环 | 增大 max_iterations,或优化 system prompt |
| 工具返回"未知工具 xxx" | LLM 幻想了一个不存在的工具 | 检查工具定义是否清晰,或在 prompt 中强调"只能用注册的工具" |
JSONDecodeError |
LLM 生成的工具参数格式不对 | 当前代码已处理,会自动报错而非崩溃 |
动手练习
- 添加一个新工具:
get_weather(city)(用 mock 数据),让 Agent 能查天气 - 注释掉
max_iterations的检查逻辑,问 Agent 一个复杂问题,观察会发生什么 - 故意在工具里抛一个异常,看 Agent 能否优雅处理并继续
思考题
- 如果没有
max_iterations限制,Agent 可能在什么情况下陷入死循环? - 第 3 课的
tool_calling.py也能调用工具,但它和本课的 Agent 循环有什么本质区别? - 如果你想让 Agent "记住"之前和你的对话(比如你的名字),需要在 messages 里加什么?
📦 完整代码
本课程所有代码已托管在 GitCode:
git clone git@gitcode.com:gcw_A202cbBm/ai-agent.git
cd ai-agent/code
也可直接访问:https://gitcode.com/gcw_A202cbBm/ai-agent
下一篇预告:AI Agent 入门(五):Agent 的记忆系统 —— 短期记忆、JSON 持久化与向量数据库入门
我们将给 Agent 加上"记忆",让它能跨对话记住用户偏好——这是走向实用 Agent 的关键一步。
如果您觉得有用,欢迎 点赞、转发、评论、关注。
更多推荐



所有评论(0)