Skip to content

Latest commit

 

History

History
403 lines (266 loc) · 10.9 KB

File metadata and controls

403 lines (266 loc) · 10.9 KB

ReAct代码实现

具体的代码工程点击这里跳转至GitHub:

从零理解 ReAct Agent:一个可运行的极简 AI Agent 教学项目

下面是内容是上述GitHub的ReadMe的复制,你可以直接打开上述的GitHub查看:

这是一个面向初学者的、尽量少依赖的 ReAct Agent 示例项目。它用最基础的 Python 标准库和少量第三方库,演示了一个 AI Agent 是如何:

  • 接收用户问题
  • 让大模型分步思考
  • 调用工具获取外部信息
  • 把工具结果反馈给模型
  • 最终给出答案

如果你想学习的不是“如何接一个现成 Agent 框架”,而是“一个 Agent 的核心闭环到底怎么自己写出来”,这个项目就是为这个目标准备的。

项目特点

  • 基于经典 ReAct 思路:Thought -> Action -> Observation
  • 使用本地 Ollama 作为模型后端,便于离线或低成本实验
  • 工具系统足够简单,便于看懂和扩展
  • 支持流式打印模型输出
  • 代码量小,适合逐文件学习
  • 注释偏教学风格,适合初学者边跑边读

这个项目能做什么

当前内置了 5 个工具:

  • Calculator:计算数学表达式
  • Search:做网页搜索,返回标题、摘要和 URL
  • ReadWeb:读取网页正文文本
  • GetWeather:获取指定城市天气
  • GetCurrentTime:获取当前日期和时间

因此它比较适合演示这几类问题:

  • 需要查询事实的问题
  • 需要多步搜索再阅读网页的问题
  • 需要计算的问题
  • 需要时间或天气等外部信息的问题

先看整体原理

这个项目的核心不是“让模型一口气直接回答”,而是让模型按固定格式一轮一轮地工作:

  1. 模型先输出一条 Thought
  2. 模型再输出一条 Action
  3. Python 程序解析这条 Action
  4. 程序调用对应工具,得到 Observation
  5. 程序把 Observation 追加回历史
  6. 模型继续下一轮,直到输出 Finish[...]

也就是说,真正的“Agent”不是单独某个模型,而是这三个部分的组合:

  • LLM:负责推理与决策
  • Tools:负责与外部世界交互
  • Loop:负责约束格式、执行动作、反馈结果

项目结构

.
├── main.py          # 项目入口:交互模式 / demo 模式
├── llm_client.py    # 对 Ollama API 的轻量封装,负责流式读取模型输出
├── react_agent.py   # ReAct 主循环:解析 Thought/Action,执行工具,回填 Observation
├── tools.py         # 工具注册、工具实现、网页读取和文本清洗
├── requirements.txt # 第三方依赖
└── README.md

建议阅读顺序:

  1. main.py
  2. react_agent.py
  3. tools.py
  4. llm_client.py

这样更容易先看懂主流程,再看细节。

运行环境

  • Python 3.9+
  • 本地已安装并启动 Ollama
  • 本地已有可用模型

项目默认使用的模型名是 gemma4:26b。如果你的本地 Ollama 没有这个模型,可以通过环境变量覆盖。

安装步骤

1. 克隆项目

git clone <your-repo-url>
cd react

2. 安装 Python 依赖

pip install -r requirements.txt

当前依赖非常少,主要是:

  • ddgs:给 Search 工具做网页搜索

3. 安装并启动 Ollama

如果你还没有安装 Ollama,请先参考 Ollama 官网

启动服务后,确保下面这个地址可访问:

http://localhost:11434

4. 准备模型

你可以使用你本地已经安装好的任意兼容聊天接口的模型。

例如,如果你想使用代码当前默认值对应的模型:

ollama pull gemma4:26b

如果你想改用别的模型,比如注释里也提到过的 qwen3.5:9b,可以这样:

ollama pull qwen3.5:9b
export OLLAMA_MODEL=qwen3.5:9b

如何运行

交互模式

python main.py

启动后你可以直接输入问题,例如:

  • 今天是几号?星期几?
  • 北京现在天气怎么样?
  • 地球的半径大约是多少公里?它的表面积大约是多少平方公里?

退出方式:

  • 输入 quit
  • 输入 exit
  • 输入 q

Demo 模式

python main.py --demo

它会自动运行几道预设问题,适合第一次观察 Agent 的执行过程。

你会看到什么输出

运行时,终端通常会出现类似这样的结构:

============================================================
  问题: 爱因斯坦和牛顿,谁出生得更早?早了多少年?
============================================================

--- Step 1/10 ---
Thought: 我需要先查询两人的出生年份。
Action: Search[爱因斯坦 牛顿 出生年份]
Observation: ...

--- Step 2/10 ---
Thought: 我已经得到候选信息,还需要确认并计算差值。
Action: Calculator[1643 - 1879]
Observation: -236

--- Step 3/10 ---
Thought: 我已经有足够信息,可以给出最终答案。
Action: Finish[牛顿出生得更早,约早了236年。]

这就是 ReAct 闭环的完整工作过程。

demo效果展示:

核心代码怎么工作

1. main.py

作用:组装所有模块,然后启动交互循环。

它主要做三件事:

  • 创建 LLMClient
  • 创建默认工具集
  • 创建 ReActAgent

2. llm_client.py

作用:把消息发给 Ollama,并流式读取返回结果。

关键点:

  • 请求 Ollama /api/chat
  • 开启 stream=True
  • 按行读取返回的 JSON chunk
  • 从每个 chunk 中提取 thinkingcontent
  • 实时打印,并把最终正文拼接起来返回

这个文件主要帮助你理解:模型流式输出是如何被 Python 一边接收一边处理的

3. react_agent.py

作用:实现 ReAct Agent 的主循环。

这里是项目最核心的部分。run(question) 大致流程如下:

  1. 构建系统提示词和当前轮 user prompt
  2. 调用模型
  3. 从模型输出中解析 ThoughtAction
  4. 判断是否为 Finish[...]
  5. 如果不是,就执行对应工具
  6. Observation 写回历史
  7. 继续下一轮

为了提高稳定性,这里还做了几件很重要的事:

  • 检查 Action 格式是否合法
  • 防止模型连续输出非法格式
  • 防止模型重复调用本质相同的动作
  • 限制最大步数,避免无限循环

4. tools.py

作用:定义工具注册中心,以及各个具体工具的实现。

这里你可以学到两个很重要的工程点:

  • 如何设计一个简单但清晰的工具接口
  • 如何把网页搜索、网页读取、文本清洗等能力独立封装

ToolExecutor 的职责很简单:

  • 注册工具
  • 根据工具名分发执行
  • 生成给 LLM 看的工具描述文本

为什么这个项目适合初学者

很多 Agent 框架一上来就会出现很多概念:

  • memory
  • planner
  • graph
  • node
  • router
  • callback
  • middleware
  • state machine

这些概念并不是没用,但对初学者来说,很容易在还没搞懂“Agent 最小闭环”的时候,就被框架细节淹没。

这个项目刻意保持简洁,目的就是让你先真正理解下面这件事:

Agent 的本质不是“神秘框架”,而是“模型 + 工具 + 受控循环”。

当你把这个最小闭环真正看懂,再去学更复杂的框架,理解会快很多。

一些重要设计选择

为什么用文本格式约束 Thought / Action

因为这是最容易看懂的 ReAct 实现方式。
模型输出自由文本,程序再用规则把它解析成结构化动作。

这不是最强壮的工业方案,但非常适合教学。

为什么要做重复动作检测

如果不做这件事,模型可能会不断重复:

  • Search[奥巴马 出生年份]
  • Search[奥巴马 出生年份]
  • Search[奥巴马 出生年份]

这在真实运行中非常常见。
所以代码会把工具名和输入做归一化,识别“本质重复”的动作。

为什么要有最大步数

因为大模型并不总能可靠地收敛。
没有步数上限时,Agent 可能会一直循环下去。

已知局限

这是一个教学项目,所以它是“清晰优先”,不是“生产可用优先”。

当前局限包括:

  • Action 解析依赖固定文本格式
  • 搜索结果依赖外部网络环境,可能超时
  • 网页正文提取是启发式做法,不是完整阅读器
  • 没有持久化 memory
  • 没有 tool schema / function calling
  • 没有多 Agent 协作
  • 没有测试覆盖和完善的容错体系

这些局限并不影响它作为一个很好的学习样例。

如果你想继续扩展

你可以尝试继续做这些练习:

  • Search 增加重试和备用搜索源
  • ReadWeb 增加更稳的正文抽取
  • 改造 Action 解析逻辑,让它更鲁棒
  • 给工具系统增加参数校验
  • 把日志输出改成结构化日志
  • 增加单元测试
  • 改造成支持 JSON schema / function calling 的版本
  • 增加记忆模块或对话历史压缩

这些都很适合作为“从教学版走向工程版”的下一步。

一个典型学习路径

如果你是第一次接触 Agent,建议按下面顺序学习:

  1. 先运行 python main.py --demo
  2. 观察终端里每一步的 Thought / Action / Observation
  3. 阅读 main.py
  4. 阅读 react_agent.pyrun() 主循环
  5. 阅读 ToolExecutor 和各个工具实现
  6. 最后再看 llm_client.py 的流式读取细节

这样你会更容易建立整体心智模型。

常见问题

1. 为什么搜索会失败?

Search 工具依赖外部网络和搜索后端。如果当前网络环境较慢、受限或目标搜索引擎超时,就可能返回:

  • Search 失败: Request timed out ...

这通常不是 Agent 主循环的问题,而是外部网络请求失败。

2. 为什么模型没有按格式输出?

大模型并不是严格编译器。即使 prompt 已经要求输出:

Thought: ...
Action: ...

它仍然可能偶尔偏离格式。
这也是 react_agent.py 中要做解析失败纠偏和非法 Action 检测的原因。

3. 为什么项目没有直接使用 LangChain / LangGraph?

因为这个仓库的目标是教学:帮助你先理解 Agent 的底层闭环,而不是先学习框架 API。

适合谁

这个仓库特别适合以下读者:

  • 想从零理解 Agent 工作原理的人
  • 会一点 Python,但还没自己写过 Agent 的人
  • 想看一个“小而完整”的 ReAct 教学实现的人
  • 想把 Agent 原理讲给别人听的老师或学习者

License

如果你准备开源到 GitHub,建议补充一个明确的开源许可证,例如:

  • MIT
  • Apache-2.0

最后

如果这个项目对你理解 AI Agent 有帮助,欢迎你继续自己扩展它。
最推荐的学习方式不是“看完就结束”,而是边看边改:

  • 自己加一个工具
  • 自己改一个 prompt
  • 自己处理一个异常
  • 自己让它回答一种新问题

当你能亲手改动这些地方时,你对 Agent 的理解才会真正扎实起来。

参考资料