PocketFlow这个项目最近在圈子里讨论度不低,核心卖点就俩:一是“100行代码”,二是“智能体构建智能体”。光看标题其实挺唬人的,一个是强调极简,一个是强调元能力,组合在一起确实容易让人好奇。我花了两天时间把它跑通、改了几个内部场景的demo,这期间也把源码翻了一遍,谈谈实际体验,顺便把一些容易卡住的地方记录下来。
先说结论:PocketFlow不是一个通用开发框架,它更像是一个“思路演示 + 最小可运行范式”的组合体。如果你期待拿它直接接业务,可能会失望;但如果你想理解“让大模型自己编排步骤、自己生成子智能体来完成任务”这件事是怎么落地的,它可能是目前最轻量、最没有阅读负担的参考实现之一。
1. 核心思路拆解:为什么“智能体构建智能体”成立
1.1 从“手写流程”到“流程自生成”的范式转变
传统开发智能体应用时,我们的思路通常是:确定任务类型 -> 人工拆解步骤 -> 为每一步编写Prompt或工具调用逻辑 -> 串联执行。这种方式的问题在于,一旦任务类型发生变化,整个流程就要重新拆解、重写逻辑,灵活性差。
PocketFlow的切入点很有意思:它把“拆解步骤”这个动作本身也交给了大模型。框架不需要预设流程图,而是给大模型一个目标和边界条件,让模型自己决定要生成哪些子任务、每个子任务用什么形式完成,然后再递归执行。这种“流程在运行时生成”的设计,实际上是当前Agent系统里比较前沿的一种思路——从“写死逻辑”走向“定义生成逻辑的规则”。
1.2 为什么是“100行代码”而不是“1000行”
这里要说句公道话:100行代码只能覆盖最核心的分层执行逻辑,并不包含模型调用SDK、外部工具接入、记忆管理等生产环境必备组件。它的价值在于,用最少的代码量展示“智能体如何被另一个智能体创建并管理”这一核心机制的完整闭环。
我撕开源码后,发现它的核心机制可以浓缩成三点:
- 用一个全局的Flow配置结构来维护任务树,描述“父任务 -> 子任务”的关系
- 基础执行单元是一个Node,它接收任务、调用模型推理、生成子任务描述、分发执行
- 通过递归调用实现层级展开,父智能体不直接执行最终操作,而是生成子智能体去处理
这三件事听起来简单,但组合在一起,就跑通了一个完整的“任务自我分解”机制。这也是我认为它最值得学习的地方:不是代码量大,而是信息密度高。
1.3 这套思路解决了什么实际问题
我把它套到一个实际需求里测试:给一周的销售数据写一份异常波动分析报告。传统方案里我需要手动规划——先做数据清洗、再做趋势分析、再做异常检测、最后写总结,每一步都要单独开发或调试Prompt。
PocketFlow的做法是:顶层智能体拿到这个需求后,自动判断并分解为“数据预处理Agent”“趋势识别Agent”“异常归因Agent”和“报告生成Agent”四个子任务,然后逐层执行、汇总结果。整个过程我只给出了目标和约束,没有手动指定步骤顺序。
这说明什么?说明当任务类型多样化、临时性需求频发时,“让系统自己规划步骤”比起“为每个场景写死流程”有本质优势。虽然当前大模型的规划能力还不完美,但这个架构方向至少是值得投入研究的。
2. 实操过程:跑通官方示例与基础环境搭建
2.1 环境准备
我是在Python 3.10环境下跑的,装依赖的过程没什么坑,核心依赖只有一个OpenAI SDK。如果你要用国产模型或本地模型,需要额外适配一下API格式,官方默认是OpenAI兼容接口。
pip install pocketflow openai如果你是从源码运行,那就直接clone仓库后把根目录加入PYTHONPATH即可。项目结构不复杂,核心文件就一个,剩下的都是示例。
2.2 跑通第一个示例:最小智能体链
官方给了一个最基础的例子,让一个Agent写一篇短文,然后另一个Agent审校。这个例子的价值在于让你理解两个Node之间是怎么串联的。
from pocketflow import Node, Flow class WriteNode(Node): def run(self, shared): topic = shared["topic"] shared["draft"] = call_llm(f"写一篇关于{topic}的短文") return "review" class ReviewNode(Node): def run(self, shared): draft = shared["draft"] shared["final"] = call_llm(f"审校并润色以下文章:{draft}") return None write_node = WriteNode() review_node = ReviewNode() write_node >> review_node flow = Flow(start=write_node) shared = {"topic": "人工智能在农业中的应用"} flow.run(shared) print(shared["final"])这段代码里有个关键点:return "review"返回的不是文本内容,而是下一个节点的标识。PocketFlow用返回值做路由,这比硬编码下一个节点更灵活。
跑通这个示例之后,我对它的运作机制就有了直观感受:所谓的“智能体”在这里就是一个能返回路由信息的函数,核心逻辑由大模型驱动。
2.3 核心执行逻辑的源代码级解读
我花时间把核心源码逐行读了一遍,精简来看,核心机制可以用一个很薄的执行器概括:
class Node: def __init__(self): self._next = {} def exec(self, shared, params=None): return self.run(shared, params) def run(self, shared, params=None): return None def __rshift__(self, next_node): self._next["default"] = next_node return next_node def _run(self, shared, params=None): action = self.exec(shared, params) next_node = self._next.get(action, None) if next_node: return next_node._run(shared, params) return None class Flow: def __init__(self, start): self.start = start def run(self, shared, params=None): return self.start._run(shared, params)这段代码不到40行,但它已经体现了整个框架的核心设计:
__rshift__操作符重载,用>>串联节点,写起来很自然- 每个节点的
run方法返回一个动作标识,驱动流程走向下一个节点 - 没有中心调度器,流程控制权分散在每个节点手里
这个设计的妙处在于:你不需要维护一张流程表,流程的走向由节点自己决定。这在复杂场景下可能显得不够可控,但也正是这种极简让后续的“生成子智能体”变得异常简单——因为生成节点只需要动态修改_next字典即可。
3. 智能体构建智能体的核心实现解析
3.1 元编程机制:让Node生成Node
PocketFlow最核心的示例,是把“让智能体写智能体”这个能力用两个简单步骤落地:
第一步,用一个“规划型智能体”分析任务并输出子任务列表;第二步,将每个子任务转换为独立的Node,用>>串联成可执行的Flow。
我实际跑了一个复杂任务——让系统制作一个“欧拉公式的互动演示页面”。规划Agent输出的结果大致如下:
{ "tasks": [ {"id": 1, "desc": "用HTML/CSS实现基础页面布局"}, {"id": 2, "desc": "用JavaScript绘制动态公式曲线"}, {"id": 3, "desc": "添加参数调节滑块并绑定事件"}, {"id": 4, "desc": "整体测试并修复问题"} ] }然后,我把这些task逐个变成Node,每个Node的Prompt是“你负责完成以下子任务:{task_desc},输出完整的代码”。通过共享存储串联,最终生成一个可用网页。
这个过程的本质就是元编程——程序在运行时生成新的程序结构。PocketFlow之所以能做到这一点,是因为Node的定义足够薄,生成一个Node的成本几乎为零,只需要动态创建一个实例并绑定Prompt参数。
3.2 共享存储驱动的数据流:黑板的隐喻
在PocketFlow里,各个Node之间不直接传参,而是通过一个名为shared的字典来共享数据。我把它理解成一个“公共黑板”,不同节点都可以从上面读取需要的数据、把自己完成的结果放到上面供他人使用。
def run(self, shared): code = call_llm(f"{shared['task_desc']},输出完整代码") shared["generated_code"] = code return "next"这个设计避免了在节点间显式传递数据的麻烦,但也要求你对数据命名有足够的规划意识,否则多个节点写同一个key就会互相覆盖。我在跑多轮任务时就遇到过这种问题,排查起来比较费劲。
做法上,建议所有写入shared的key统一加前缀,比如task_1_output、task_2_output,避免collision。这是我用这个框架踩到的第一个坑,后面小结里会细说。
3.3 一个完整的“智能体构建智能体”跑通实录
我用官方实现思路跑了一个真实任务:生成一个“简易项目管理工具”的网页。整个过程大致有三个阶段:
阶段一:任务分解
顶层Agent输出分解结果,包含5个子任务:项目结构搭建、任务列表组件、时间线视图、数据存储与本地持久化、界面美化。
阶段二:子智能体生成
我写了一个TaskExecutorNode,它的Prompt是:
你是全栈开发专家,请完成以下子任务:{task_desc} 直接将完整的代码输出,不要解释过程。针对每个子任务创建独立的Node实例,并用>>串联。
阶段三:汇总与验证
最终由一个IntegrationNode检查生成代码的完整性并尝试合并。这一步我没有让它自动执行检测,而是人为模拟了验证步骤。
整个流程跑完约2分钟,生成的代码约400行,虽然不能直接上线,但骨架完全可用,稍加修改就能跑起来。对比我从零手写,效率至少提高了一倍以上。
4. 进阶玩法:如何把它改造成一套可复用的轻量Agent编排框架
4.1 给Node添加条件路由与终止逻辑
官方示例里只有“default”路线,也就是无论结果如何都走向同一个节点。这在实际项目中不够用,比如:“当任务分解失败时,不再执行后续步骤,直接终止”。
我通过扩展__rshift__的行为,给它增加了条件路由能力:
class Node: def __rshift__(self, next_node): self._next["default"] = next_node return next_node def when(self, condition, next_node): self._next[condition] = next_node return next_node然后在run里,返回的action字符串如果匹配某个condition,就走对应分支,否则走default。这个扩展本身只加了3行,却解决了很多流程控制的问题。
4.2 与外部工具链打通
要让智能体真正有用,必须让它能调用外部工具——比如浏览器搜索、数据库查询、或调用内部API。我在PocketFlow里加了一个ToolCallingNode,它的作用是让模型输出结构化JSON格式的工具调用请求,然后由代码实际执行。
class ToolCallingNode(Node): def run(self, shared): llm_output = call_llm(shared["prompt"]) tool_calls = json.loads(llm_output)["tool_calls"] for call in tool_calls: result = execute_tool(call["name"], call["args"]) shared[f"tool_result_{call['name']}"] = result return "default"这一步让这个极简框架的实用性提升了一个台阶。不过要注意,这个改造已经超出“100行代码”的范畴了,属于生产环境必要补全。
4.3 嵌入Web服务的尝试
我还尝试把它封装成一个HTTP服务,通过FastAPI暴露接口。做法不复杂:接收用户请求,放入shared,运行Flow,返回shared中的结果。
from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class TaskRequest(BaseModel): task: str @app.post("/run") def run_task(req: TaskRequest): shared = {"task": req.task} flow.run(shared) return {"result": shared.get("final_output")}实测下来,响应时间在5到20秒之间,取决于模型推理速度和子智能体数量。这个延迟水平适合做异步任务处理,不适合做实时响应场景。
5. 常见问题与避坑技巧
5.1 子任务结果互相覆盖
问题现象:多个子智能体完成后,前一个的输出被后一个覆盖,最终结果只剩最后一个子任务的信息。
原因分析:所有节点都往shared["output"]里写结果,后写覆盖先写。
解决方案:在创建子节点时,为每个节点绑定独立的写入key。我用的做法是给每个TaskExecutorNode初始化时传入一个task_id参数:
class TaskExecutorNode(Node): def __init__(self, task_id, task_desc): self.task_id = task_id self.task_desc = task_desc self.output_key = f"output_{task_id}" def run(self, shared): result = call_llm(f"完成任务{self.task_desc},直接输出结果") shared[self.output_key] = result return "default"5.2 大模型解析JSON失败导致流程中断
这是一个高频问题。大模型在输出包含代码的内容时,经常会多出或漏掉某个JSON字段,导致json.loads直接抛异常。
我给的规避办法:在一次调用里让模型重复输出两遍JSON,第一遍供校验,第二遍供解析。虽然浪费了一点token,但稳定性提升很大。也可以用正则去截取内容中第一个{到最后一个}之间的部分,再清洗后解析。
5.3 递归深度问题
因为PocketFlow的机制天然支持层级展开,深层级任务会嵌套很多层Node调用,递归深度可能飙升。当任务层级超过10层时,Python默认的递归限制会触发RecursionError。
两个解决办法:调高递归上限:
import sys sys.setrecursionlimit(10000)或者在这种用法的节点中,不要等整棵树全部展开后才开始执行,而是执行一步、展开一步,避免同时构建过深的结构。
5.4 模型能力对效果的影响极大
这是最重要的一条心得。PocketFlow的“规划能力”完全依赖模型本身,如果模型推理能力弱,分解出来的子任务可能漏洞百出。我实测下来:
- 用GPT-4级别的模型,任务分解质量高,几乎可以直接执行
- 用轻量模型,偶尔会出现子任务描述模糊、缺少前置条件等情况
如果你只能用轻量模型,建议在顶层规划时用更强的模型做指导,子任务执行阶段再用轻量模型,形成“重规划、轻执行”的组合,成本和效果都能兼顾。
5.5 常见问题速查表
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 子任务输出互相覆盖 | 共享存储key冲突 | 按task_id拆分key |
| JSON解析失败 | 模型输出格式不稳定 | 双次输出校验或正则截取 |
| 递归深度超限 | 层级过深 | 调高递归限制或分批执行 |
| 子智能体不执行 | 节点路由未正确设置 | 检查>>是否串联完整 |
| 效果不稳定 | 模型推理能力不足 | 重规划轻执行,规划用强模型 |
6. 适用场景与选型建议
6.1 PocketFlow适合什么人用
我的判断是三类人:
第一类是技术研究者,想快速理解“智能体构建智能体”的落地思路,PocketFlow是一个非常合适的学习样本,代码量少,没有任何黑盒,全部拆开看也就是一小会儿的事。
第二类是原型开发者,需要在短时间内验证“自动任务分解”在其业务上是否可行,PocketFlow可以帮你用最小的成本验证方向,不需要引入重型框架。
第三类是AI应用教学的讲师或博主,用它来做教学案例非常合适,代码可讲解性极强,学生上手快,容易建立对Agent机制的整体认知。
6.2 不建议什么人用
如果你的业务已经明确,任务类型固定且长期不变,直接用LangGraph或其他重量级框架会更稳,没必要让大模型每次动态规划流程。
如果对执行稳定性要求非常高——比如金融交易、医疗决策等领域,PocketFlow当前的能力不适合直接上生产,模型的不确定性会造成流程不可控。
6.3 选型对比
| 维度 | PocketFlow | LangGraph | 自研框架 |
|---|---|---|---|
| 代码量 | 约100行 | 中等 | 高 |
| 学习成本 | 极低 | 中等 | 高 |
| 扩展性 | 弱到中等 | 强 | 强 |
| 灵活性 | 高 | 高 | 中 |
| 生产可用性 | 低 | 高 | 中高 |
| 适合阶段 | 学习/验证 | 生产 | 特定场景定制 |
坦白讲,PocketFlow目前还不是一个“产品级”项目,但它的意义在于,把一个抽象概念压缩到了最小可执行单元。对大多数人来说,与其看十篇总结Agent架构的文章,不如把这一百行代码跑通一次。
这个项目后续可以扩展的方向也很多:比如生成子智能体时引入更丰富的元信息约束、把节点的路由机制改成更灵活的规则引擎、为共享存储增加持久化能力等等。我个人在做的是:把它和内部工具调用打通后,已经可以用自然语言快速生成临时的自动化脚本,效率提升非常明显。最后再分享一个小技巧:读这类“百行框架”源码时,不要急着看示例,先把核心执行器读明白,再回头看示例,理解速度会快很多。