AgentScope 框架源码深度解读:ReAct = Reasoning + Acting 的实现原理

AgentScope 框架源码深度解读:ReAct = Reasoning + Acting 的实现原理

🎯 本文目标 :通过深入 AgentScope 框架源码,揭示 ReAct 模式如何做到"推理 + 行动"的自动循环

📦 源码版本 :AgentScope 1.0.16.dev0

🔍 阅读建议 :结合代码示例,理解每个核心方法的职责

⏱️ 预计时间:30-45 分钟


📖 目录

  1. 核心问题:ReAct 如何实现?\](#核心问题 react 如何实现)

  2. ReAct 循环的核心引擎\](#react 循环的核心引擎)

  3. 行动(Acting)过程详解\](#行动 acting 过程详解)

  4. 完整执行流程图解
  5. 实战:在 alioo-agent 中的应用\](#实战在 alioo-agent 中的应用)


核心问题:ReAct 如何实现?

从一个实际场景开始

当用户对 alioo-agent 说:

复制代码
"帮我读一下这个 PDF:/path/to/file.pdf"

黑盒过程

  1. Agent "思考":用户想读取 PDF → 需要调用 read_pdf 工具
  2. Agent "行动":执行 read_pdf("/path/to/file.pdf")
  3. Agent "观察":获取 PDF 内容
  4. Agent "再思考":内容太长,需要总结
  5. Agent "再行动":生成总结并返回

关键问题

  • 这个"思考 - 行动"循环是如何自动进行的?
  • 谁在控制循环的开始和结束?
  • LLM 的输出如何被解析成工具调用指令?
  • 工具执行结果如何反馈给 LLM?

让我们深入源码寻找答案。


类的继承体系

三层继承结构

复制代码
┌─────────────────────────────────────┐
│  AgentBase (基类)                   │
│  - 状态管理                          │
│  - Hook 机制                         │
│  - 打印功能                          │
└─────────────────────────────────────┘
              ↑
┌─────────────────────────────────────┐
│  ReActAgentBase (抽象基类)          │
│  - 定义_pre_reasoning 接口           │
│  - 定义_acting 接口                  │
│  - Hook 类型声明                     │
└─────────────────────────────────────┘
              ↑
┌─────────────────────────────────────┐
│  ReActAgent (具体实现)              │
│  - reply() 主入口                   │
│  - _reasoning() 推理实现            │
│  - _acting() 行动实现               │
│  - _summarizing() 总结实现          │
└─────────────────────────────────────┘

源码位置

python 复制代码
# _agent_base.py
class AgentBase:
    """所有 Agent 的基类"""
    
# _react_agent_base.py
class ReActAgentBase(AgentBase, metaclass=_ReActAgentMeta):
    """ReAct 代理的抽象基类"""
    
# _react_agent.py
class ReActAgent(ReActAgentBase):
    """ReAct 模式的具体实现"""

元类的作用

_ReActAgentMeta 元类自动为 _reasoning_acting 方法包裹 Hook:

python 复制代码
# _react_agent_base.py
class _ReActAgentMeta(type):
    """元类,自动为 reasoning 和 acting 添加 Hook 包装"""
    
    def __new__(mcs, classname, bases, class_dict):
        # 获取父类的 hook 包装函数
        pre_reasoning_hook = get_hook_wrapper("pre_reasoning")
        post_reasoning_hook = get_hook_wrapper("post_reasoning")
        pre_acting_hook = get_hook_wrapper("pre_acting")
        post_acting_hook = get_hook_wrapper("post_acting")
        
        # 如果子类定义了_reasoning 方法,用 Hook 包装它
        if "_reasoning" in class_dict:
            class_dict["_reasoning"] = pre_reasoning_hook(
                post_reasoning_hook(class_dict["_reasoning"])
            )
        
        # 同理包装_acting 方法
        if "_acting" in class_dict:
            class_dict["_acting"] = pre_acting_hook(
                post_acting_hook(class_dict["_acting"])
            )
        
        return type.__new__(mcs, classname, bases, class_dict)

作用:允许开发者在 reasoning/acting 前后插入自定义逻辑,无需修改框架代码。


ReAct 循环的核心引擎

reply() 方法:ReAct 循环的总控开关

这是整个 ReAct 模式最核心的方法,位于 _react_agent.py 第 376-537 行。

python 复制代码
# _react_agent.py
@trace_reply
async def reply(
    self,
    msg: Msg | list[Msg] | None = None,
    structured_model: Type[BaseModel] | None = None,
) -> Msg:
    """Generate a reply based on the current state and input arguments."""
    
    # Step 1: 记录用户输入到记忆
    await self.memory.add(msg)
    
    # Step 2: 从长期记忆和知识库检索相关信息
    await self._retrieve_from_long_term_memory(msg)
    await self._retrieve_from_knowledge(msg)
    
    # Step 3: 管理结构化输出
    tool_choice = None
    if structured_model:
        self.toolkit.register_tool_function(
            getattr(self, self.finish_function_name),
        )
        tool_choice = "required"
    
    # ★★★★★★★★ Step 4: ReAct 循环主体 ★★★★★★★★
    structured_output = None
    reply_msg = None
    
    for _ in range(self.max_iters):  # 最多迭代 max_iters 次
        # 4.1 记忆压缩(如果需要)
        await self._compress_memory_if_needed()
        
        # 4.2 【Reasoning】推理过程
        msg_reasoning = await self._reasoning(tool_choice)
        
        # 4.3 【Acting】行动过程
        futures = [
            self._acting(tool_call)
            for tool_call in msg_reasoning.get_content_blocks("tool_use",)
        ]
        
        # 并行或串行执行工具
        if self.parallel_tool_calls:
            structured_outputs = await asyncio.gather(*futures)
        else:
            structured_outputs = [await _ for _ in futures]
        
        # 4.4 检查退出条件
        if self._required_structured_model:
            # 处理结构化输出的情况
            if structured_outputs:
                structured_output = structured_outputs[-1]
                # ... 处理逻辑
                break
        elif not msg_reasoning.has_content_blocks("tool_use"):
            # ★★★ 关键退出条件 ★★★
            # 如果没有工具调用,说明 LLM 决定直接回答
            msg_reasoning.metadata = structured_output
            reply_msg = msg_reasoning
            break
    
    # Step 5: 超过最大迭代次数时的总结
    if reply_msg is None:
        reply_msg = await self._summarizing()
        reply_msg.metadata = structured_output
    
    return reply_msg

循环的关键要素

1. 循环控制器
python 复制代码
for _ in range(self.max_iters):  # 默认 10 次
  • 防止无限循环的安全机制
  • 每次迭代 = 一次 Reasoning + 一次(或多次)Acting
2. 退出条件

条件 A:完成结构化输出

python 复制代码
if self._required_structured_model:
    if structured_outputs:
        # 成功生成结构化输出 → 退出
        break

条件 B:LLM 决定直接回答

python 复制代码
elif not msg_reasoning.has_content_blocks("tool_use"):
    # LLM 没有调用工具 → 直接返回文本响应
    reply_msg = msg_reasoning
    break

条件 C:达到最大迭代次数

python 复制代码
if reply_msg is None:
    # 循环结束但未生成回复 → 强制总结
    reply_msg = await self._summarizing()

推理(Reasoning)过程详解

_reasoning() 方法源码

位于 _react_agent.py 第 540-648 行。

python 复制代码
async def _reasoning(
    self,
    tool_choice: Literal["auto", "none", "required"] | None = None,
) -> Msg:
    """Perform the reasoning process."""
    
    # Step 1: 如果有计划笔记本,添加推理提示
    if self.plan_notebook:
        hint_msg = await self.plan_notebook.get_current_hint()
        await self.memory.add(hint_msg, marks=_MemoryMark.HINT)
    
    # Step 2: 准备发送给 LLM 的消息
    prompt = await self.formatter.format(
        msgs=[
            Msg("system", self.sys_prompt, "system"),
            *await self.memory.get_memory(),
        ],
    )
    
    # 清除临时提示消息
    await self.memory.delete_by_mark(mark=_MemoryMark.HINT)
    
    # Step 3: ★★★ 调用 LLM,获取推理结果 ★★★
    res = await self.model(
        prompt,
        tools=self.toolkit.get_json_schemas(),  # ← 关键:传入工具列表
        tool_choice=tool_choice,
    )
    
    # Step 4: 处理 LLM 响应
    msg = Msg(name=self.name, content=[], role="assistant")
    
    if self.model.stream:
        # 流式处理
        async for content_chunk in res:
            msg.content = content_chunk.content
            await self.print(msg, False)
    else:
        # 非流式:直接获取完整响应
        msg.content = list(res.content)
    
    # Step 5: 将推理结果加入记忆
    await self.memory.add(msg)
    
    return msg

关键点解析

1. 工具信息的传递
python 复制代码
res = await self.model(
    prompt,
    tools=self.toolkit.get_json_schemas(),  # ← 这里!
    tool_choice=tool_choice,
)

tools 参数的作用

  • 告诉 LLM 有哪些工具可用
  • 每个工具的参数格式
  • LLM 基于这些信息决定调用哪个工具

工具 Schema 示例

json 复制代码
{
  "type": "function",
  "function": {
    "name": "read_pdf",
    "description": "读取 PDF 文件内容",
    "parameters": {
      "type": "object",
      "properties": {
        "file_path": {
          "type": "string",
          "description": "PDF 文件路径"
        },
        "max_pages": {
          "type": "integer",
          "description": "最大读取页数"
        }
      },
      "required": ["file_path"]
    }
  }
}
2. LLM 的响应格式

LLM 可能返回两种内容:

A. 纯文本响应(不需要工具)

json 复制代码
{
  "content": [
    {
      "type": "text",
      "text": "我已经理解了您的需求..."
    }
  ]
}

B. 工具调用(需要执行工具)

json 复制代码
{
  "content": [
    {
      "type": "tool_use",
      "id": "call_abc123",
      "name": "read_pdf",
      "input": {
        "file_path": "/path/to/file.pdf"
      }
    }
  ]
}
3. 格式化器的作用
python 复制代码
prompt = await self.formatter.format(
    msgs=[...],
)

不同模型提供商需要不同的消息格式:

  • DashScopeChatFormatter → 通义千问格式
  • OpenAIChatFormatter → OpenAI 格式

行动(Acting)过程详解

_acting() 方法源码

位于 _react_agent.py 第 650-708 行。

python 复制代码
async def _acting(self, tool_call: ToolUseBlock) -> dict | None:
    """Perform the acting process."""
    
    # Step 1: 创建工具结果消息
    tool_res_msg = Msg(
        "system",
        [
            ToolResultBlock(
                type="tool_result",
                id=tool_call["id"],
                name=tool_call["name"],
                output=[],
            ),
        ],
        "system",
    )
    
    try:
        # Step 2: ★★★ 执行工具函数 ★★★
        tool_res = await self.toolkit.call_tool_function(tool_call)
        
        # Step 3: 处理流式结果
        async for chunk in tool_res:
            tool_res_msg.content[0]["output"] = chunk.content
            
            await self.print(tool_res_msg, chunk.is_last)
            
            # 处理中断
            if chunk.is_interrupted:
                raise asyncio.CancelledError()
            
            # 如果是 finish 函数且成功,返回结构化输出
            if (
                tool_call["name"] == self.finish_function_name
                and chunk.metadata
                and chunk.metadata.get("success", False)
            ):
                return chunk.metadata.get("structured_output")
        
        return None
        
    finally:
        # Step 4: 将工具结果加入记忆
        await self.memory.add(tool_res_msg)

关键点解析

1. 工具执行的委托
python 复制代码
tool_res = await self.toolkit.call_tool_function(tool_call)

Toolkit 的职责

  • 根据 tool_call.name 找到对应的函数
  • 使用 tool_call.input 作为参数调用函数
  • 返回 ToolResponse 对象
2. 工具结果的格式
python 复制代码
ToolResultBlock(
    type="tool_result",
    id=tool_call["id"],      # 与调用 ID 匹配
    name=tool_call["name"],  # 工具名称
    output=...,              # 工具返回的内容
)
3. 结果反馈给 LLM
python 复制代码
await self.memory.add(tool_res_msg)

为什么重要

  • 工具执行结果会被加入对话记忆
  • 下一轮 Reasoning 时,LLM 能看到之前的工具执行结果
  • 形成完整的"思考 - 行动 - 观察"闭环

工具调用的自动化机制

Toolkit:工具管理的核心

工具注册
python 复制代码
# agent.py
def _create_toolkit(self) -> Toolkit:
    toolkit = Toolkit()
    
    # 注册工具函数
    toolkit.register_tool_function(read_pdf)
    toolkit.register_tool_function(fetch_news_summary)
    
    return toolkit
工具 Schema 自动生成

当你注册一个函数时,Toolkit 会自动分析:

python 复制代码
def read_pdf(
    file_path: Union[str, Path],
    max_pages: Optional[int] = None
) -> ToolResponse:
    """读取 PDF 文件内容
    
    Args:
        file_path: PDF 文件路径
        max_pages: 最大读取页数
    """

自动生成的 Schema

json 复制代码
{
  "name": "read_pdf",
  "description": "读取 PDF 文件内容",
  "parameters": {
    "file_path": {"type": "string", "description": "PDF 文件路径"},
    "max_pages": {"type": "integer", "description": "最大读取页数"}
  }
}
工具执行流程
python 复制代码
# toolkit.py (简化版)
async def call_tool_function(self, tool_call: ToolUseBlock):
    # 1. 根据名称查找函数
    func = self.tools[tool_call["name"]]
    
    # 2. 准备参数
    args = tool_call["input"]
    
    # 3. 调用函数
    result = await func(**args)
    
    # 4. 返回结果
    return result

自动化闭环

复制代码
┌──────────────────────────────────────────┐
│  1. LLM 生成工具调用指令                  │
│     {                                     │
│       "name": "read_pdf",                 │
│       "input": {"file_path": "..."}       │
│     }                                     │
└──────────────────────────────────────────┘
              ↓
┌──────────────────────────────────────────┐
│  2. Framework 解析指令                    │
│     - 提取工具名称                         │
│     - 提取参数                             │
└──────────────────────────────────────────┘
              ↓
┌──────────────────────────────────────────┐
│  3. Toolkit 执行工具函数                  │
│     - 查找函数                            │
│     - 调用函数                            │
│     - 获取返回值                          │
└──────────────────────────────────────────┘
              ↓
┌──────────────────────────────────────────┐
│  4. 结果加入记忆                          │
│     Msg(                                  │
│       role="system",                      │
│       content=tool_result                 │
│     )                                     │
└──────────────────────────────────────────┘
              ↓
┌──────────────────────────────────────────┐
│  5. 下一轮 Reasoning                      │
│     - LLM 看到工具结果                     │
│     - 决定下一步行动                       │
└──────────────────────────────────────────┘

完整执行流程图解

从用户输入到最终响应的全链路

需要工具
直接回答
有 tool_use
无 tool_use
用户输入

帮我读一下这个 PDF
memory.add

记录到记忆
ReAct 循环开始
_reasoning

推理过程
准备 Prompt

sys_prompt + memory
调用 LLM

传入 tools 参数
LLM 决策
生成 tool_use

read_pdf 调用
生成 text

直接返回
_acting

行动过程
Toolkit 执行

read_pdf 函数
获取工具结果

PDF 内容
memory.add

记录工具结果
检查退出条件
退出循环
返回 reply_msg

多轮迭代示例

假设用户要求:"读一下这个 PDF,然后总结主要内容,再翻译成英文"

第 1 轮迭代

复制代码
Reasoning:
  Thought: 用户要读取 PDF → 调用 read_pdf
  Action: read_pdf("/path/to/file.pdf")

Acting:
  执行:read_pdf("/path/to/file.pdf")
  结果:【第 1 页】...【第 2 页】...(共 15 页内容)

第 2 轮迭代

复制代码
Reasoning:
  Thought: PDF 已读取,但内容太长,需要总结
  Action: finish(response="这份 PDF 的核心内容是...")

Acting:
  执行:finish(...)
  结果:Successfully generated response.

第 3 轮迭代(如果还需要翻译):

复制代码
Reasoning:
  Thought: 用户还要求翻译 → 调用 translate_text
  Action: translate_text("这份 PDF 的核心内容是...", target_lang="en")

Acting:
  执行:translate_text(...)
  结果:The core content of this PDF is...

实战:在 alioo-agent 中的应用

AliooAgent 的初始化

python 复制代码
# agent.py
class AliooAgent(ReActAgent):
    def __init__(self, name: str = "Alioo"):
        config = load_config()
        
        # 1. 创建工具包
        toolkit = self._create_toolkit()
        
        # 2. 构建系统提示词
        sys_prompt = self._build_system_prompt()
        
        # 3. 创建模型和格式化器
        model, formatter = create_model_and_formatter()
        
        # 4. 调用父类初始化
        super().__init__(
            name=name,
            model=model,
            sys_prompt=sys_prompt,
            toolkit=toolkit,
            formatter=formatter,
            max_iters=config.agent.max_iters,  # 默认 50 次
        )

系统提示词的设计

python 复制代码
def _build_system_prompt(self) -> str:
    return """你是一个简洁高效的个人智能助手,名叫 Alioo。

你的核心能力:
1. **PDF 阅读**:帮助用户读取 PDF 文件,提取关键信息,总结内容
   - 当用户要求解读、阅读或分析 PDF 时,使用 read_pdf(file_path) 函数读取
   - 【重要】读取 PDF 后,必须主动总结核心要点,而不是直接返回原文

2. **新闻摘要**:为用户提供实时热门新闻
   - 使用 fetch_news_summary(sources) 函数获取新闻

工作要求:
- 回答简洁明了,重点突出
- 对于工具返回的长文本,必须进行归纳总结
"""

作用

  • 定义 Agent 的角色和职责
  • 明确告知哪些工具可用
  • 指导 LLM 如何使用工具(包括何时使用、如何使用)

实际执行日志

查看 logs/model_interaction.log

复制代码
================================================================================
模型请求
模型:qwen-turbo
  [system] : 你是一个简洁高效的个人智能助手...
  [user] : 帮我读一下这个 PDF:/Users/mac/report.pdf
  
================================================================================
模型响应
{
  "content": [
    {
      "type": "tool_use",
      "id": "call_abc123",
      "name": "read_pdf",
      "input": {
        "file_path": "/Users/mac/report.pdf"
      }
    }
  ]
}
Usage:
  Input Tokens: 1250
  Output Tokens: 45
  Time: 0.8s
================================================================================

[工具执行] read_pdf("/Users/mac/report.pdf")
[工具结果] 【第 1 页】AI Agent 技术报告...

================================================================================
模型请求
模型:qwen-turbo
  [system] : 你是一个简洁高效的个人智能助手...
  [user] : 帮我读一下这个 PDF:/Users/mac/report.pdf
  [assistant] : [tool_use: read_pdf]
  [system] : [tool_result: PDF 内容...]
  
================================================================================
模型响应
{
  "content": [
    {
      "type": "text",
      "text": "这份文档是一份 AI Agent 技术报告,共 15 页。\n\n核心要点:\n1. 介绍了 AI Agent 的基本概念\n2. 详细讲解了 ReAct 模式的工作原理\n3. 提供了多个实践案例"
    }
  ]
}
================================================================================

总结与启示

核心要点回顾

1. ReAct 循环的本质
复制代码
Reasoning(推理) → Acting(行动) → Observation(观察) → 循环
  • Reasoning:LLM 基于当前上下文决定下一步行动
  • Acting:执行 LLM 选择的工具
  • Observation:将工具结果反馈给 LLM
2. 自动化的关键
组件 作用
reply() 控制循环的开始和结束
_reasoning() 调用 LLM,获取工具调用指令
_acting() 执行工具函数
Toolkit 管理工具函数,执行调用
Memory 存储对话历史和工具结果
3. LLM 的核心作用

LLM 不是被动执行命令,而是:

  • 理解决策:分析用户意图
  • 自主选择:决定使用哪个工具
  • 评估结果:判断是否完成任务
  • 调整策略:根据工具结果调整下一步行动

与传统编程的对比

特性 传统方式 ReAct 方式
流程控制 硬编码 if-else LLM 自主决策
工具路由 手动编写路由逻辑 自动匹配工具名称
错误处理 try-catch + 预定义规则 LLM 理解错误并调整
可扩展性 需修改路由代码 注册新工具即可
灵活性 低(固定流程) 高(动态适应)

设计启示

1. 关注点分离
复制代码
LLM → 决策层(做什么)
Framework → 执行层(怎么做)
Tools → 能力层(具体能力)
2. 透明化设计
  • 所有工具调用对 LLM 透明
  • LLM 知道有哪些工具、参数格式、返回值
  • LLM 能看到工具执行结果
3. 循环而非线性

传统:输入 → 处理 → 输出(单向)

ReAct:输入 → [推理 → 行动 → 观察]×N → 输出(循环)

进一步学习

源码阅读建议
  1. 第一遍 :理解 reply() 的主循环逻辑
  2. 第二遍 :深入 _reasoning()_acting() 细节
  3. 第三遍:研究 Hook 机制和扩展点
实践方向
  • 添加自定义工具
  • 实现自定义 Hook
  • 优化系统提示词
  • 调试工具调用过程

❓ 常见问题 FAQ

Q1: 为什么 LLM 知道要调用哪个工具?

A:

  1. 在每次调用 LLM 时,会通过 tools 参数传入所有可用工具的 Schema
  2. LLM 基于工具的名称、描述和参数,理解每个工具的用途
  3. 系统提示词也会指导 LLM 在什么场景下使用什么工具

Q2: 如果 LLM 选择了错误的工具怎么办?

A:

  1. 工具执行结果会反馈给 LLM
  2. LLM 可以看到错误信息(如"文件不存在")
  3. LLM 会在下一轮 Reasoning 中调整策略
  4. 可以通过优化系统提示词减少错误

Q3: 如何调试 ReAct 循环?

A:

  1. 查看 logs/model_interaction.log
  2. 启用 debug 日志级别
  3. _reasoning()_acting() 中添加断点
  4. 观察每次迭代的输入输出

Q4: 支持并行工具调用吗?

A : 支持!通过 parallel_tool_calls=True 开启:

python 复制代码
agent = ReActAgent(
    ...,
    parallel_tool_calls=True,  # 并行执行多个工具
)

Q5: 如何限制最大迭代次数?

A : 在初始化时设置 max_iters

python 复制代码
agent = ReActAgent(
    ...,
    max_iters=10,  # 最多迭代 10 次
)

📚 参考资料


相关推荐
ZTLJQ2 小时前
深入理解One-Class SVM:无监督异常检测的精准利器
人工智能·机器学习·支持向量机
Cobyte2 小时前
面试官:大模型是怎么调用工具的呢 ?
前端·后端·aigc
youyoulg2 小时前
AI与大模型-机器学习
人工智能·机器学习
weiyvyy2 小时前
无人机嵌入式开发实战-姿态解算与稳定控制
人工智能·机器学习·机器人·无人机
Codefengfeng2 小时前
如何本地部署大模型(以PaddleOCR-VL-1.5为例)
vscode·visualstudio·docker·语言模型·aigc·ocr
XPoet2 小时前
AI 编程工程化:AI 时代程序员的基本功
aigc·ai编程·claude
EdisonZhou5 小时前
MAF快速入门(19)给Agent Skill添加脚本执行能力
llm·agent·.net core
TechFind6 小时前
我用 OpenClaw 搭了一套运营 Agent,每天自动生产内容、分发、追踪数据——独立开发者的运营平替
人工智能·agent