跳到主要内容

pocketflow — 本课题摘录

读了哪几篇: 01-core-abstraction(核心抽象)、02-flow-orchestration(编排主循环)、04-patterns-and-boundaries(设计模式与边界)。 其余一篇(容错、批处理与并行)本轮没读。

这一家在本课题里是"下限"参照:它证明了 agent 循环所需的编排原语可以少到什么程度。

它对本课题回答了什么

决定一:一个节点只有三段 —— 读 / 算 / 写并决定下一步

一个节点 = prep(读) / exec(算) / post(写并决定下一步)

关键在第三段:post 的返回值是一个"动作名",编排靠它决定跳到哪。

决定四:整个编排就是一个 while,而且只有一行

当前 = 起点
while 当前:
动作名 = 当前.跑一遍(共享数据)
当前 = 查后继(当前, 动作名) # 查不到就是 None,循环退出

(依据:Agent 库 · PocketFlow · Flow 编排:主循环、动作跳转与嵌套 —— Flow._orch 是一行 while——跑当前节点拿到 last_action、按动作名查后继、把 curr 换成后继,查不到时后继为 None 循环退出)

"查不到后继"就是终止条件。 不需要布尔标志、不需要状态枚举。

两个细节值得记:

细节说的是什么
空动作即默认边节点没报动作名(最常见)就退回查"默认"那条边
"有后继但没匹配上"才告警压根没连后继是正常终点,不告警;连了后继却报了一个没连线的动作名,才告警

(依据:Agent 库 · PocketFlow · Flow 编排:主循环、动作跳转与嵌套 —— action or "default" 让 post 返回 None 时退回默认边;「有后继但没匹配上」才 warn,压根没连后继是正常终点不告警——这是最常见的调试线索)

第二条是很好的错误设计:区分"正常结束"和"报了个不存在的去向"。 我们的循环里,"模型说了个不存在的工具名"是同一类问题——不该跟"模型说完了"混为一谈。

决定四的关键洞察:agent 循环 = 一张带回边的图

它把这件事说得最直白:

"Agent 不神秘——就是一个能自己决定下一步、且能循环的图。"

┌──────────────────────────────┐
│ │ 「回来继续决定」
▼ │
决策节点 ──「去搜索」──► 搜索节点 ┘

└──「给答案」──► 回答节点(无后继 → 结束)

(依据:Agent 库 · PocketFlow · 设计模式、巧妙之处与边界 —— Agent 循环就是「决策节点的 post 返回模型选的动作名、图据此分支、干完活的边指回决策节点形成循环」的带回边的图;同一套图原语靠不同连法拼出 agent 循环 / workflow / RAG)

同一套原语,连法不同就是不同的东西:

连法就是什么
带回边agent 循环
不带回边、线性工作流
检索 → 拼提示 → 生成RAG

这一条对讲义很有用:它给了"agent 循环"一个不依赖任何框架的定义—— 一张图,其中至少有一条边指回决策点。

决定一补充:节点是定义不是实例,每步拷一份

图里的节点是定义,真正跑的是每步的浅拷贝。 于是同一个节点能在循环里被反复经过而不串味,而后继表和共享数据因为是浅拷贝仍然共享。

它自己把这条概括成一句话:"拷执行者、共享图与数据"。

这条解决了"同一个决策节点在循环里被走了五次,它自己的状态会不会互相污染"这个真问题。 我们的循环如果把"这一轮"做成对象,也会遇到同样的问题。

一处分层设计值得抄

你覆写公开方法,框架覆写内部钩子。 重试、批处理、异步全插在内部那一层,业务逻辑毫不知情。

这让"叠加能力"变成纯粹的类组合。跟 openmanus 的"继承链每层加一个职责"是同一族。

它没回答什么 —— 而且它是刻意不答

它诚实地列了"刻意不做"的清单:

不做说明
没有模型调用框架零依赖、不含任何模型 SDK
没有工具/函数调用抽象你在节点里自己拼
没有内置记忆、向量库、提示模板示例里的提示词是手写字符串
没有可观测性、追踪、持久化共享数据是内存里的字典,进程结束即失
没有并发安全并行分支共享同一份数据,写同键会竞争,框架不加锁

(依据:Agent 库 · PocketFlow · 设计模式、巧妙之处与边界 —— 框架刻意只做图编排——没有 LLM 调用、没有工具/函数调用抽象、没有内置记忆/向量库/prompt 模板、没有可观测性/追踪/持久化、没有并发安全)

这份清单本身就是本课题的一张检查表: 它列出的每一项,都是我们的最小循环迟早要自己回答的。 一个刻意什么都不做的框架,反而把"必须自己写的东西"标得最清楚。

坑与代价

  • 共享数据是一个内存字典,没有并发保护。 并行分支写同一个键会竞争。

    判断(无锚): 对我们的最小原型,"用一个字典当共享状态"是可以的,但并行执行工具时必须让每个工具写自己的键,不能共写。 如果错,会错在: 如果工具之间本来就要互相看结果(比如"先查再改"),那共享键是必需的,那就得像 haystack 那样按读写集合排顺序,而不是靠约定。

  • "一行 while"的代价是它什么护栏都没有。 没有轮数上限、没有卡死检测、没有超时。图里连了环就会无限转。
  • 它是编排原语,不是 agent。 拿它当讲义的"最小骨架"参照可以,但直接用它搭我们的第一版会发现要补的东西比省下的多。