本文是 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 生成的工具参数格式不对 当前代码已处理,会自动报错而非崩溃

动手练习

  1. 添加一个新工具:get_weather(city)(用 mock 数据),让 Agent 能查天气
  2. 注释掉 max_iterations 的检查逻辑,问 Agent 一个复杂问题,观察会发生什么
  3. 故意在工具里抛一个异常,看 Agent 能否优雅处理并继续

思考题

  1. 如果没有 max_iterations 限制,Agent 可能在什么情况下陷入死循环?
  2. 第 3 课的 tool_calling.py 也能调用工具,但它和本课的 Agent 循环有什么本质区别?
  3. 如果你想让 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 的关键一步。


如果您觉得有用,欢迎 点赞、转发、评论、关注

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐