用 Vibe Coding 从零搭一个 ReAct 智能体,边做边学 Agent 框架
项目地址:https://github.com/wangzpap/ZpAgent
如果你觉得有帮助,欢迎 Star 支持!感谢!
2025 年,“Vibe Coding” 成了开发者社区的热词。与此同时,AI Agent 开发也走向工程实践,ReAct、Function Calling、MCP 等范式层出不穷。我对这两件事都好奇,于是一个想法自然冒了出来:如果用 Vibe Coding 的方式,从零搭建一个 ReAct 智能体项目,能不能在"不手写代码"的同时,更深入地理解 Agent 的底层机制?
这个想法的产物就是 ZpAgent——一个基于 LangChain + LangGraph + FastAPI + Vue 3 的 ReAct 智能体应用。它支持 SSE 流式输出、动态工具选择、Human-in-the-Loop 审批和双层会话记忆,全部代码通过与 AI 对话完成。
但对我来说,代码本身不是目的。Vibe Coding 改变的是学习路径——过去学一个框架是读文档、写 demo、踩坑;现在是把想法描述给 AI,看它生成代码,然后反过来阅读和理解这些代码。遇到不懂的设计就追问"为什么用策略模式而不是直接 if-else",遇到 bug 就研究"连接池为什么会断连"。AI 写代码,我来理解代码,学起来似乎比自己啃文档更高效一些。
这篇文章会拆解这个项目的设计思路、技术选型和核心实现,也会分享 Vibe Coding 作为学习方法的真实体验。
为什么做这个项目?
我想做一个真正跑起来的全栈 Agent 应用,具备以下特征:
- 完整的 ReAct 推理循环,而不只是单次工具调用
- 实时的流式输出,让用户看到 Agent 的每一步思考
- 可控的工具执行,敏感操作需要人类审批
- 持久化的会话记忆,刷新页面不丢上下文
- 可扩展的工具系统,支持 MCP 协议接入外部工具
同时,这也是一个面试向项目——它覆盖了异步编程、设计模式(策略模式、工厂方法、注册表模式)、流式通信、状态管理等高频考点,每一处设计决策都经得起追问。
界面一览
先看效果,再拆架构。
对话界面——左侧是会话列表,右侧是聊天主区域。Agent 的每一次思考、工具调用和观察结果都以结构化的方式呈现在消息气泡中:

工具选择——底部工具栏可以动态勾选启用哪些工具,支持内置工具和通过 MCP 协议加载的外部工具:

Human-in-the-Loop 审批——当 Agent 想要调用一个被标记为"需要审批"的工具时,前端会弹出审批面板。用户可以选择批准、编辑参数、拒绝,或者自行回复:

整体架构
ZpAgent 采用前后端分离的单体架构,通过 SSE(Server-Sent Events)实现实时通信:
前端是纯手写的 Vue 3 单页应用(没有用任何 UI 组件库),后端是 FastAPI + LangGraph 的异步服务。两层存储都使用策略模式,通过环境变量在 InMemory 和 MySQL 之间无缝切换。
核心亮点拆解
一、ReAct 循环 + SSE 流式输出
ReAct(Reasoning + Acting)是 Agent 领域的经典范式:模型先思考(Thought),再行动(Action)调用工具,然后观察(Observation)工具返回的结果,如此循环直到得出最终答案。
ZpAgent 通过 LangGraph 的状态图实现 ReAct 循环,并结合 SSE 将每一步实时推送到前端:
这里有一个技术细节值得一提:前端没有使用浏览器原生的 EventSource API,因为它只支持 GET 请求,而聊天接口需要 POST。所以我用 fetch + ReadableStream 手动实现了 SSE 解析,处理了分块传输中的 buffer 拼接、不完整事件缓冲等边界情况。
后端定义了 7 种事件类型:start(开始)、token(逐 token 文本)、thinking(思考过程)、tool_result(工具执行结果)、approval_required(需要审批)、done(完成)、error(错误),前端根据事件类型分别渲染到消息气泡的不同区域。
二、Human-in-the-Loop:让 Agent 不再"为所欲为"
这是整个项目中我最满意的功能。大多数 Agent demo 中,工具调用是无条件执行的——但在真实场景中,你不会希望 Agent 不经确认就删除数据库记录或发送邮件。
ZpAgent 通过 LangGraph 的 HumanInTheLoopMiddleware 实现了工具级别的审批控制:
审批策略通过 ToolRegistry(注册表模式)管理,支持三种粒度:False 自动执行、True 全量审批、dict 细粒度决策类型控制。更关键的是,系统处理了多轮审批的场景——当 Agent 在一次推理中连续请求多个需审批的工具时,每次都会独立中断等待,不会因为前一个批准了就直接放行后面的。
还有一个容易忽略的边界情况:如果用户在审批面板弹出后直接发送了新消息(没有处理审批),系统会自动拒绝(auto-reject)那些挂起的工具调用,避免状态不一致。
三、双层会话记忆架构
"记忆"是 Agent 应用的核心挑战之一。ZpAgent 将记忆拆分为两层,各司其职:
应用层的 ConversationStore 管理业务维度的会话元数据(标题、创建时间、最后更新时间),框架层的 CheckpointStore 则直接对接 LangGraph 的 checkpointer 机制,负责持久化对话历史和图执行状态(包括中断恢复)。两层都通过抽象基类定义接口,工厂函数根据 .env 配置创建具体实现,切换后端不需要改一行代码。
MySQL 实现层有一个值得注意的坑:LangGraph 官方的 AIOMySQLSaver 在连接池回收后复用时可能报 Not connected。解决方案是使用 aiomysql 的连接池并设置 pool_recycle,避免单连接被 MySQL 服务端的 wait_timeout 机制关闭。这也是 Vibe Coding 中常见的场景——AI 帮你写了代码,但运行时的坑需要你自己发现并反馈给它。
四、动态工具系统:内置 + MCP
工具系统分为内置工具和 MCP 外部工具两类,统一由 ToolRegistry 管理:
内置工具通过 LangChain 的 @tool 装饰器定义,写一个函数就是一个工具,docstring 自动提取为工具描述,类型注解自动生成 JSON Schema。MCP 工具则通过 mcp_servers.json 配置,支持 stdio、SSE、streamable_http、websocket 四种传输协议。
一个很实用的功能是工具热加载——通过 POST /api/tools/reload 接口可以在不重启服务的情况下重新加载 MCP 工具配置。加载过程是容错的:单个 MCP 服务器连接失败不会影响其他工具的正常使用。
五、前端:Vue 3 应用
前端最核心的设计决策是消息数据结构。一条 Agent 回复不是简单的字符串,而是一个 segments 数组,交替存储文本片段和工具调用片段:
{
role: 'assistant',
segments: [
{ type: 'text', content: '让我查一下当前时间...' },
{ type: 'tool_call', tool: 'get_datetime', args: { timezone: 'Asia/Shanghai' }, observation: '2026-07-04 15:30:00' },
{ type: 'text', content: '当前北京时间是 2026年7月4日 15:30。' }
]
}
这种结构让 ChatMessage.vue 组件可以按照 segments 顺序渲染出"思考 → 工具调用 → 观察 → 最终回答"的完整 ReAct 时间线,而不是把所有内容混在一起。
设计模式速览
项目中用到的设计模式,也是面试中常被问到的:
| 模式 | 应用位置 | 解决的问题 |
|---|---|---|
| 策略模式 | conversation/、checkpoint/ | 存储后端可替换,InMemory / MySQL 透明切换 |
| 工厂方法 | create_conversation_store()、create_llm()、build_agent_graph() | 集中创建逻辑,隐藏构造细节 |
| 注册表模式 | ToolRegistry | 统一管理工具和审批策略,支持动态注册 |
| 中间件 | HumanInTheLoopMiddleware | 在 LangGraph 执行流中横切审批逻辑 |
| 单例 | Settings() | 模块级配置实例,全局共享 |
Vibe Coding 作为学习方法
回过头看,Vibe Coding 最大的收获不是代码本身,而是它逼着我用一种新方式学习。
传统路径是:读文档 → 写 demo → 踩坑 → 理解原理。Vibe Coding 把这个流程反转了——先看结果,再倒推原理。当 AI 生成一段用策略模式实现的存储层时,我不会直接接受,而是追问"为什么不用 if-else 直接切换?",于是对开闭原则有了比教科书更具体的体会。当 AI 用 HumanInTheLoopMiddleware 实现审批时,我去研究 LangGraph 的中断恢复机制,算是弄明白了 interrupt() 和 Command(resume=...) 之间的协作原理——这些东西如果只看文档,很可能一扫而过。
当然,Vibe Coding 也有它不擅长的地方。运行时问题排查、环境配置、依赖兼容性这些,AI 很难替你兜底。比如 MySQL 连接池的 pool_recycle 问题,AI 生成的代码逻辑上完全正确,但跑了一小时后连接被服务端 wait_timeout 断开才暴露出来。这类坑得自己踩,然后把现象反馈给 AI 让它修。
我的感受是: Vibe Coding 不是"不用动脑",而是把动手的时间省下来动脑。你要知道自己想学什么、架构该怎么选、哪些边界情况值得关注。AI 是高效的执行者,但提问者和审查者的角色,始终是你自己。
快速体验
# 克隆项目
git clone https://github.com/wangzpap/ZpAgent.git
cd ZpAgent
# 配置 API Key
cd back && cp .env.example .env
# 编辑 .env,填入你的 LLM API Key
# 创建虚拟环境(仅首次)
python3 -m venv venv && source venv/bin/activate
pip install -r requirements.txt
# 一键启动
cd .. && chmod +x zpagent.sh
./zpagent.sh start
打开 http://localhost:5173 即可体验。详细的环境变量说明和手动启动方式请参阅 README。
写在最后
ZpAgent 不是什么颠覆性的产品,它更像一个"用 Vibe Coding 驱动的学习实验"。通过从零搭建一个完整的全栈 Agent 应用,我把 ReAct 推理循环、SSE 流式传输、Human-in-the-Loop、双层记忆这些概念从论文和博客里拽进了真实可运行的代码中。过程中的每一次追问、每一个 bug、每一次"为什么这么设计"的思考,才是这个项目最大的产出。
如果你也在学 AI Agent,或者对 Vibe Coding 的学习方式感兴趣,欢迎到 GitHub 看看代码,点个 Star,一起交流!
项目地址: https://github.com/wangzpap/ZpAgent
技术栈: Python · FastAPI · LangChain · LangGraph · Vue 3 · Vite · SSE
协议: MIT
2465

被折叠的 条评论
为什么被折叠?



