AI Agent 开发实践:从手写 ReAct 到框架选型

AI Agent 开发实践:从手写 ReAct 到框架选型

从概念到实践

上一篇文章中,我们介绍了 AI Agent 的基本概念。接下来,我们将进入实际开发环节。

在开始编写 Agent 之前,我们需要先了解目前常见的 Agent 开发方式,并认识一种经典的 Agent 设计范式:ReAct。

Agent 的开发方式

目前,常见的 Agent 开发方式大致可以分为三类:

  1. 直接调用大模型的API
  2. 使用Agent开发框架(LangChain,LangGraph,AG2)
  3. 使用可视化工作流平台(Dify,n8n)

本次实践,我们主要研究前两种方式:先直接调用大模型 API,手动实现一个最小 Agent,然后引入 Agent 开发框架,探索如何将它应用于具体场景。

这里先附上后续可能用到的框架链接:

LangChain
LangGraph
AG2

ReAct范式

在开始编写 Agent 之前,我们需要先了解一种经典的 Agent 设计范式:ReAct。

ReAct 是 Reasoning and Acting 的缩写,可以理解为"推理与行动"。它最初由相关研究工作系统化提出,核心思想是将模型的推理过程与外部行动结合起来,让模型不仅能够分析当前任务,还能够根据需要采取行动,并利用行动的结果继续推理。

简单来说,ReAct 的执行过程可以概括为:

复制代码
接收任务
   ↓
Reasoning(推理)
   ↓
Acting(行动)
   ↓
Observation(观察结果)
   ↓
再次推理
   ↓
继续行动,或结束任务

例如,用户提出一个问题:

"帮我查询某个产品的价格,并计算购买 3 件需要多少钱。"

Agent 可以按照以下过程执行:

  1. Reasoning(推理): 分析任务,判断需要先查询产品价格。
  2. Acting(行动): 调用产品查询工具。
  3. Observation(观察): 获取工具返回的价格,例如每件 100 元。
  4. Reasoning(推理): 根据查询结果,判断可以计算总价。
  5. Acting(行动): 调用计算工具,或者直接进行简单计算。
  6. Observation(观察): 得到总价 300 元。
  7. 结束任务: 整理结果并回复用户。

从这个例子可以看出,Agent 并不是在任务开始时就必须确定全部执行步骤。它可以根据工具返回的结果,决定接下来应该做什么。

ReAct 与普通的模型调用有什么区别?

普通的模型调用通常是将输入交给模型,再获得一次输出;而 ReAct 更强调推理、行动与观察之间的循环。

当然,普通模型调用也可以实现工具调用。ReAct 的重点并不是某一种特定的 API 或编程接口,而是一种组织 Agent 执行过程的思路。

ReAct 是所有 Agent 都必须使用的范式吗?

不是。

ReAct 是一种经典且有代表性的 Agent 范式,但并不是所有 Agent 都需要采用它。对于执行步骤固定的任务,我们可以直接使用普通程序或工作流;对于需要根据外部信息动态决定下一步行动的任务,ReAct 这样的模式就很有参考价值。

在接下来的实践中,我们将先通过模型 API,手动实现一个包含 Loop、Tool 和简单记忆的最小 Agent,理解这些基础机制如何配合工作。

手写一个最小的 Agent

现在我们来动手写一个最小的Agent,基于ReAct范式

主要包含以下的几个核心功能

  • Loop: 判断是否要继续执行,还是直接返回结果
  • Tool: 给模型加上外部的工具
  • Memory: 保存历史对话,让模型可以有"记忆"

最小Demo的目标

我们设计一个简单的任务,然后编写一个Agent去实现他

查询商品A的价格

买三件要多少钱

这里就涉及到了Loop,Tool,Memory

代码实现

这里也贴一份DeepSeek的API文档:

api-docs.deepseek.com/zh-cn/api/c...

python 复制代码
import ast
import json
import operator
import requests


class ReActLoop:
    def __init__(self, api_key, base_url, model_name, system_prompt):
        self.api_key = api_key
        self.base_url = base_url.rstrip("/")
        self.model_name = model_name
        self.memory = [{"role": "system", "content": system_prompt}]

        # 工具名称 -> Python 函数
        self.tool_map = {
            "query_goods_price": self.query_goods_price,
            "calculator": self.calculator,
        }

        # 提供给模型的工具描述
        self.tools = [
            {
                "type": "function",
                "function": {
                    "name": "query_goods_price",
                    "description": "根据商品名称查询商品单价",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "goods_name": {
                                "type": "string",
                                "description": "商品名称,例如商品A",
                            }
                        },
                        "required": ["goods_name"],
                        "additionalProperties": False,
                    },
                },
            },
            {
                "type": "function",
                "function": {
                    "name": "calculator",
                    "description": "执行基本数学表达式计算,例如 99.2 * 3",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "expr": {
                                "type": "string",
                                "description": "数学表达式",
                            }
                        },
                        "required": ["expr"],
                        "additionalProperties": False,
                    },
                },
            },
        ]

    def llm_call(self):
        """调用大模型 API。"""
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json",
        }

        payload = {
            "model": self.model_name,
            "messages": self.memory,
            "tools": self.tools,
            "tool_choice": "auto",
            "temperature": 0,
        }

        response = requests.post(
            f"{self.base_url}/chat/completions",
            headers=headers,
            json=payload,
            timeout=60,
        )
        response.raise_for_status()
        return response.json()

    def parse_action(self, response_json):
        """根据 finish_reason 判断模型下一步的动作。"""
        choice = response_json["choices"][0]
        message = choice["message"]
        finish_reason = choice["finish_reason"]

        if finish_reason == "stop":
            return "answer", message.get("content") or ""

        elif finish_reason == "tool_calls":
            return "action", message

        else:
            return "other", {
                "finish_reason": finish_reason,
                "message": message,
            }

    def query_goods_price(self, goods_name):
        """查询商品单价,模拟数据库。"""
        goods_db = {
            "商品A": 99.2,
            "商品B": 29.9,
            "商品C": 199.0,
        }

        price = goods_db.get(goods_name)

        if price is not None:
            return f"{goods_name} 的单价是:{price} 元"

        return f"未找到商品:{goods_name}"

    def calculator(self, expr):
        """只允许数字和基本四则运算,不直接执行任意 Python 代码。"""
        allowed_operators = {
            ast.Add: operator.add,
            ast.Sub: operator.sub,
            ast.Mult: operator.mul,
            ast.Div: operator.truediv,
            ast.USub: operator.neg,
            ast.UAdd: operator.pos,
        }

        def evaluate(node):
            if isinstance(node, ast.Expression):
                return evaluate(node.body)

            if (
                isinstance(node, ast.Constant)
                and type(node.value) in (int, float)
            ):
                return node.value

            if (
                isinstance(node, ast.BinOp)
                and type(node.op) in allowed_operators
            ):
                left = evaluate(node.left)
                right = evaluate(node.right)
                return allowed_operators[type(node.op)](left, right)

            if (
                isinstance(node, ast.UnaryOp)
                and type(node.op) in allowed_operators
            ):
                return allowed_operators[type(node.op)](
                    evaluate(node.operand)
                )

            raise ValueError("只允许数字和 +、-、*、/ 运算")

        try:
            tree = ast.parse(expr, mode="eval")
            result = evaluate(tree)
            return f"计算结果:{result}"
        except Exception as exc:
            return f"计算失败:{exc}"

    def execute_tool_calls(self, assistant_message):
        """执行模型请求的工具,并将工具结果写入对话历史。"""

        # 先保存模型发出的工具调用消息
        self.memory.append(assistant_message)

        for tool_call in assistant_message.get("tool_calls", []):
            tool_call_id = tool_call["id"]
            function_info = tool_call["function"]
            tool_name = function_info["name"]

            try:
                arguments = json.loads(
                    function_info.get("arguments") or "{}"
                )

                tool_function = self.tool_map.get(tool_name)

                if tool_function is None:
                    result = f"工具执行失败:未知工具 {tool_name}"
                else:
                    result = tool_function(**arguments)

            except Exception as exc:
                result = f"工具执行失败:{exc}"

            # tool_call_id 必须对应模型发出的工具调用
            self.memory.append({
                "role": "tool",
                "tool_call_id": tool_call_id,
                "content": str(result),
            })

    def run(self, user_input, max_steps=10):
        """Agent 主循环。"""
        self.memory.append({
            "role": "user",
            "content": user_input,
        })

        for _ in range(max_steps):
            response_json = self.llm_call()
            action_type, result = self.parse_action(response_json)

            if action_type == "answer":
                self.memory.append({
                    "role": "assistant",
                    "content": result,
                })
                return result

            elif action_type == "action":
                self.execute_tool_calls(result)
                continue

            else:
                raise RuntimeError(
                    f"模型未正常完成生成:{result['finish_reason']}"
                )

        return f"达到最大循环次数({max_steps}),任务尚未完成。"


def main():
    agent = ReActLoop(
        api_key="YOUR_API_KEY",
        base_url="https://api.deepseek.com",
        model_name="deepseek-chat",
        system_prompt=(
            "你是一个可以调用工具的助手。"
            "需要查询商品价格时使用 query_goods_price;"
            "需要计算时使用 calculator。"
            "请依据工具返回的结果回答,不要编造查询结果。"
        ),
    )

    while True:
        user_input = input("\n你:").strip()

        if user_input.lower() in {"exit", "quit"}:
            break

        if not user_input:
            continue

        try:
            answer = agent.run(user_input)
            print(f"\nAgent:{answer}")

        except requests.RequestException as exc:
            print(f"API 请求失败:{exc}")

        except (KeyError, IndexError, TypeError, ValueError) as exc:
            print(f"API 返回格式或处理错误:{exc}")


if __name__ == "__main__":
    main()

执行结果:

这样就实现了一个最小的agent的执行

从Demo到实际场景

前面我们通过手写 API,实现了一个最小的 Agent。它能够根据用户的需求调用工具,并根据工具返回的结果继续执行任务。

不过,商品查询和价格计算的例子相对简单,主要用于理解 ReAct 的基本运行机制。在实际业务中,Agent 面临的任务往往更加复杂,涉及多个工具、业务规则和执行步骤。

接下来,我们尝试将 Agent 应用于一个更贴近实际经营的场景:

利用AI Agent辅助运营一家电商的OPC公司

在这个场景中,我们假设公司主要由一个人负责经营,通过 AI 和自动化工具辅助完成产品开发以外的各项经营工作,例如市场调研、客户需求分析、销售跟进和订单管理。

我们的业务可能包括

  • 市场调研
  • 产品运营
  • 广告投流
  • 智能客服
  • 采购库存
  • 销售数据汇总反馈

在传统模式下,这些工作通常需要经营者在不同的平台和工具之间切换。例如,在搜索引擎中寻找潜在客户,在文档中整理客户需求,在表格中记录销售进度,再通过邮件或其他沟通工具联系客户。

如果我们希望利用 AI Agent 辅助完成这些工作,就可以尝试将不同的业务能力封装成工具,让 Agent 根据具体任务选择并调用。

Agent 框架选型

为什么需要 Agent 框架

在前面的实践中,我们通过直接调用大模型 API,手动实现了一个最小的 Agent

但是当业务越来越复杂时,如何管理工具、执行流程、任务状态以及多个 Agent 之间的协作,手搓的学习成本就太高了

有了Agent框架我们就可以复用已有能力,降低复杂 Agent 应用的开发与维护成本。

框架则对其中一部分通用能力进行了封装,让开发者能够把更多精力放在业务逻辑上,而不是反复实现底层基础设施。

为什么选择 AG2

目前常见的Agent开发框架包括LangChain,LangGraph,AG2,他们都可以用于构建Agent应用,我选择 AG2,主要是因为它提供了构建和编排多个 Agent 的相关能力。对于 OPC 这样的经营场景,我们可以尝试将市场调研、客户分析、销售支持等不同职责划分给不同的 Agent,再通过框架组织它们之间的协作。

需要注意的是,Agent 的职责划分和业务流程仍然需要由我们自行设计,框架负责提供相应的技术能力。

后续预告

本篇实现了一个手搓的Agent Demo,下一篇我们将会开始使用AG2构建我们真实的应用,由于本人也是小白,所以更新的速度会逐步放缓,尽量实现一周双更这个系列。

加油加油!如果文中有错误的地方也欢迎大家评论指正我!

相关推荐
方方洛2 小时前
ai-agent教程-07-从零实现一个工程化AI-Agent
人工智能·llm·agent
方方洛2 小时前
ai-agent教程-08-多智能体协作
人工智能·llm·agent
simon_1232 小时前
它验证过才删的:agent 的 dry-run 是干净的,线上文件还是没了
agent
高晶4 小时前
Android 计算器开发实战:从界面设计到核心逻辑
程序员·设计
BreezeJiang4 小时前
RAG 答不准,先别急着换模型:给检索管道加上路由、拆解和循环检索
agent
麦客奥德彪4 小时前
别把 Agent 写成一个大循环:读懂 DeepSeek Harness 的接口与运行时设计
agent·源码阅读
hpoenixf4 小时前
为什么我们把 Agent 的路由请求越做越简单?
agent
布列瑟农的星空5 小时前
2026年我们不再谈论运行时Harness
agent
武子康5 小时前
ESP32 唤醒成功,离线命令为什么没有执行?
人工智能·后端·agent