AI Agent 开发实践:从手写 ReAct 到框架选型
从概念到实践
上一篇文章中,我们介绍了 AI Agent 的基本概念。接下来,我们将进入实际开发环节。
在开始编写 Agent 之前,我们需要先了解目前常见的 Agent 开发方式,并认识一种经典的 Agent 设计范式:ReAct。
Agent 的开发方式
目前,常见的 Agent 开发方式大致可以分为三类:
- 直接调用大模型的API
- 使用Agent开发框架(LangChain,LangGraph,AG2)
- 使用可视化工作流平台(Dify,n8n)
本次实践,我们主要研究前两种方式:先直接调用大模型 API,手动实现一个最小 Agent,然后引入 Agent 开发框架,探索如何将它应用于具体场景。
这里先附上后续可能用到的框架链接:
ReAct范式
在开始编写 Agent 之前,我们需要先了解一种经典的 Agent 设计范式:ReAct。
ReAct 是 Reasoning and Acting 的缩写,可以理解为"推理与行动"。它最初由相关研究工作系统化提出,核心思想是将模型的推理过程与外部行动结合起来,让模型不仅能够分析当前任务,还能够根据需要采取行动,并利用行动的结果继续推理。
简单来说,ReAct 的执行过程可以概括为:
接收任务
↓
Reasoning(推理)
↓
Acting(行动)
↓
Observation(观察结果)
↓
再次推理
↓
继续行动,或结束任务
例如,用户提出一个问题:
"帮我查询某个产品的价格,并计算购买 3 件需要多少钱。"
Agent 可以按照以下过程执行:
- Reasoning(推理): 分析任务,判断需要先查询产品价格。
- Acting(行动): 调用产品查询工具。
- Observation(观察): 获取工具返回的价格,例如每件 100 元。
- Reasoning(推理): 根据查询结果,判断可以计算总价。
- Acting(行动): 调用计算工具,或者直接进行简单计算。
- Observation(观察): 得到总价 300 元。
- 结束任务: 整理结果并回复用户。
从这个例子可以看出,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文档:
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构建我们真实的应用,由于本人也是小白,所以更新的速度会逐步放缓,尽量实现一周双更这个系列。
加油加油!如果文中有错误的地方也欢迎大家评论指正我!