这篇文章我们将开始学习 AgentScope 最核心的功能,即 ReActAgent,它为开发者提供了开箱即用的、ReAct 架构的(Reasoning + Acting)模式的智能体。
什么是 ReAct?
ReAct (Reasoning + Acting) 是一种 Agent 架构模式,由 Yao 等人在 2022 年提出。其核心思想是将 Agent 的行为分解为两个交替进行的阶段:
- Reasoning(推理):LLM 思考下一步该做什么
- Acting(行动):执行具体操作(如调用工具)
1 | ┌─────────────────────────────────────────────────────────┐ |
AgentScope 的 ReActAgent 除了实现 Reasoning-Acting 循环之外,还实现了知识库集成、记忆压缩、任务规划等功能,是一个可在生产环境中直接使用的智能体。
整体结构
ReActAgent 的实现主要位于文件 _react_agent_base.py 和 _react_agent.py 中,类的继承关系如下:
1 | StateModule |
ReActAgent 除了实现本身的 推理-行动 之外,还包括了智能体所需要的 LLM 调用、工具调用、记忆机制、知识库、任务规划等等,因此 ReActAgent 依赖于我们之前所介绍的各种组件:
1 | ┌─────────────────────────────────────────────────────────────────────┐ |
ReActAgentBase
ReActAgent 的基类是 ReActAgentBase,其定义如下:
1 | class ReActAgentBase(AgentBase, metaclass=_ReActAgentMeta): |
- ReActAgentBase 继承自 AgentBase,关于
AgentBase的功能和实现,已经在上一篇文章详细介绍 - ReActAgentBase 的元类是
_ReActAgentMeta,而_ReActAgentMeta继承自_AgentMeta,_ReActAgentMeta在_AgentMeta的原有钩子(reply、print、observe)的基础上,继续支持了对_reasoning、_acting两个方法支持 hook 函数的执行
1 | class _ReActAgentMeta(_AgentMeta): |
而 ReActAgentBase 的核心就是定义了 _reasoning、_acting 两个抽象方法,以及对这两个抽象方法支持 hooks 机制,包括类级别、实例级别的 pre/post hooks 函数。
1 | class ReActAgentBase(AgentBase, metaclass=_ReActAgentMeta): |
ReActAgent
接下来分析 ReActAgent 类的实现,首先分析其初始化函数:
1 | class ReActAgent(ReActAgentBase): |
- name 为 Agent 的名称,sys_prompt 表示系统提示词
model: ChatModelBase:所使用的 LLM 模型formatter: FormatterBase:所使用的 formattertoolkit: Toolkit:工具集合memory: MemoryBase:短期记忆long_term_memory: LongTermMemoryBase:所使用的长期记忆long_term_memory_mode:长期记忆的添加方式enable_meta_tool:是否动态加载工具,参见 agentscope 源码分析(1):Toolkit 类实现parallel_tool_calls:是否允许并行工具调用knowledge:所使用的知识库enable_rewrite_query:是否允许改写输入,如果开启的话(默认),允许根据 RAG 知识库中的相关文档,改写用户输入plan_notebook: PlanNotebook:计划管理print_hint_msg:是否输出 hint 消息max_iters:最大迭代次数tts_model TTSModelBase:所使用的 tts 模型compression_config CompressionConfig:压缩配置
因为 ReActAgent 使用组合的方式来使用 Toolkit、PlanNotebook 等各个基础组件,因此在 __init__ 函数中,其主要工作就是将这些参数赋值给 ReActAgent 的属性,另外就是根据一些控制属性,完成 Toolkit 中相关工具的初始化:
- 如果长期记忆是由 Agent 自己控制,注册
long_term_memory.record_to_memory和long_term_memory.retrieve_from_memory工具 - 如果开启了
enable_meta_tool,则注册reset_equipped_tools工具 - 如果提供了
plan_notebook,则添加PlanNotebook所提供的工具
由于 ReActAgent 最终也会继承 StateModule,因此可以完整状态的管理,手动注册了如下状态属性,其他继承自 StateModule 的状态属性会自动进行状态跟踪,无需手动注册,参见 agentscope 源码分析(6):Agent 的元类与基类:
1 | self.register_state("name") |
Reply 实现
上篇文章介绍 AgentBase 类时说过,所有具体的 Agent 实现类都需要实现 reply 方法,这个方法是智能体的核心方法,用于处理输入消息并产生回复,而 智能 的魔法就是在 reply() 方法中实现的。我们先从整体上梳理下 reply() 方法的核心逻辑:
1 | ┌─────────────────────────────────────────────────────────────────┐ |
而下面则是简化版的 reply() 方法实现:
1 | ```python |
-
首先将当前的消息添加到智能体的
记忆,这个记忆有时候也被称为短期记忆。因为大模型的上下文窗口是有限的,短期记忆是算法/系统侧面的管理策略,用来决定哪些内容留在上下文窗口里、什么时候对短期记忆里的内容进行压缩/总结。关于 ReActAgent 的短期记忆实现与管理,后续文章再详细分析。总而言之,可以认为,短期记忆是指智能体在当前任务或当前会话中实时处理和保留信息的能力,或者更简单认为,短期记忆就是管理当前任务会话的 LLM 上下文窗口 -
根据当前 Msg,分别从
长期记忆和知识库中检索相关的信息,这些检索出来的信息都会以 Msg 的形式添加到短期记忆中,为后续的模型推理提供更多有用的信息 -
如果希望从 Agent 获得结构化输出,则会将
self.finish_function_name所对应的方法注册为一个工具,并将工具的输出模型设置为用户所要求的structured_model
1 | self.toolkit.register_tool_function( |
- 默认的
finish_function_name为generate_response,它的核心逻辑就是将模型传递的输入进行验证,判断是否符合structured_model的要求,如果符合则返回成功,并在 ToolResponse 的 metadata 中包含这个结构化数据,否则返回失败,Agent 的循环会继续执行,直到 LLM 返回符合要求的结构化的数据为止
1 | def generate_response(self, **kwargs: Any,) -> ToolResponse: |
- 完成上述步骤之后,则进行 ReAct 循环了,每次 ReAct 循环开始,都判断是否需要对当前的上下文进行压缩。之后调用
self._reasoning()进行一次推理,然后获取推理结果中的所有tool_use,对每个tool_use调用self._acting()进行行动 - 每一轮循环结束后,都要判断是否可以退出 ReAct 循环了,以下详细分析各种情况下 ReAct 循环是继续还是终止:
-
如果需要需要结构化输出 (
structured_model已指定)- 成功生成结构化输出 + 有文本回复 → 直接退出
- 成功生成结构化输出 +
无文本回复,此时添加提示,要求模型继续生成回复文本→ 继续一轮后退出 - 无结构化输出 + 无工具调用,要求使用
generate_response工具来获得结构化输出 → 继续循环,强制工具 - 无结构化输出 + 有工具调用(普通的工具调用) → 继续循环(正常工作流)
-
如果不需要结构化输出
- 无工具调用,直接返回响应文本→ 直接退出
- 有工具调用,正常执行工具 → 继续循环
-
产生的结构化输出总是会保存到到 reply_msg 的 metadata 中,Agent 应用可以通过这个字段获取结构化输出
-
1 | reply_msg = Msg( |
- 从这个终止流程可以看出,无论是否用户要求结构化输出,ReAct 循环最终都需要给出一段回复文本,防止 Agent 默默完成任务后不给用户任何反馈。
- 如果超过最大迭代次数,仍然有效的应答消息,则把当前过程进行总结,并返回这个汇总消息
- 最后,将整个会话过程记录到长期记忆中
推理过程 (_reasoning)
以上我们分析了 ReActAgent 的 reply() 实现,接下来我们分析其中的一个关键步骤,即推理函数 _reasoning() 的实现,以下是其核心逻辑:
1 | async def _reasoning(self, tool_choice) -> Msg: |
- 可以看到,其核心逻辑是根据当前的
短期记忆来生成 prompt,然后调用模型进行推理 - 这里我们也可以,在 memory 中
_MemoryMark.HINT标记的 Msg,只用于为当前推理步骤来生成提示词,可以认为是标记一次性的提示消息,当前推理完成之后,就会将 HINT 标记的 Msg 从短期记忆中删除。因为这种类型的信息只是为了推动当前步骤的完成,它本身提供的信息对整个任务来说是没有帮助的,因此可以直接删除 - 推理完成后,会将得到的结果添加到
短期记忆中
以上流程只是 _reasoning() 的核心逻辑,其还会处理音频模型、用户中断(需要保证已经产生的 ToolUse 有对应的 ToolResult)等逻辑,这里就不再展开了。
行动过程 (_acting)
接下来再来看其行动过程 _acting() 的实现,以下是其核心逻辑:
1 | async def _acting(self, tool_call: ToolUseBlock) -> dict | None: |
_acting()的核心就是执行工具调用- 如果调用的是
finish_function_name工具,则会从其 metadata 中判断产生的结果是否符合用户要求的structured_model,如果符合则直接返回这个结构化数据。这块逻辑与generate_response(默认的finish_function_name相匹配)。如果是其他工具调用或者产生的结果不符合要求,则返回 None _acting()的返回值逻辑其实对reply()中判断是否已经获取结构化输出是很重要的
结构化输出
以上我们就分析了整个 ReActAgent reply() 的核心实现原理,其中比较复杂的部分就是对 结构化输出 的处理,以下列出了 结构化输出 的工作原理,帮助理解。这个过程在上面的代码分析中也有详细的解释:
1 | 用户请求结构化输出 |
ReActAgent 实例
接下来通过一个实际例子来展示 ReActAgent 的使用,并通过其实际运行中所产生的消息记录,帮助理解其工作原理:
1 | # -*- coding: utf-8 -*- |
实际运行结果:
1 | # 消息记录部分 |
若能理解上述示例的运行结果,便说明已正确掌握 ReActAgent 智能体的运行原理。
小结
推理-行动 这种 Agent 架构模式是当前非常主流的一种智能体架构,这篇文章我们详细分析了 ReActAgent 的实现原理,包括其 Reply() 方法的核心逻辑,以及其中的 _reasoning() 和 _acting() 方法是如何实现的,最后通过一个实际例子演示了 ReActAgent 的使用方法。