数据截至 (上游 commit f74d023f9360)
04 · 设计模式、巧妙之处与边界
前三章讲透了 100 行内核。本章把视角拉高:怎么用这套原语拼出常见 AI 模式、PocketFlow 最值得借鉴的几个决定、以及它刻意不做什么、会在哪崩。
4.1 用原语拼模式
PocketFlow 的卖点是「一套图,搭一切」。核心就一句:不同的连线方式 = 不同的模式。
Agent 循环 = 带回边的图
「Agent」不神秘——就是一个能自己决定下一步、且能循环的图。决策节点的 post 返回模型选的动作名,图据此分支;干完活的边指回决策节点,形成循环。
┌──────────────────────────────┐
│ │ "decide"(搜完回来)
▼ │
DecideAction ──"search"──▶ SearchWeb
│
└──"answer"──▶ AnswerQuestion(出口,无后继 → Flow 结束)
对应 cookbook/pocketflow-agent/flow.py 的 create_agent_flow(第 01 章已引)。图的走向由运行时模型输出驱动——这正是 agent 与固定 workflow 的分界。
Workflow = 一条(基本)线性的链
把节点用 >> 串成一条线,就是传统 workflow(如「拟大纲 → 写正文 → 套格式」)。没有回边、少分支。见 README 的 pocketflow-workflow 示例。
RAG = 两个 Flow 接力
检索增强通常拆成离线(切块 → 嵌入 → 建索引)和在线(检索 → 拼上下文 → 生成)两条 Flow。每条都是几个节点串起来,再靠 shared 把索引/上下文传下去。见 README 的 pocketflow-rag。
其它
Map-Reduce → BatchNode/BatchFlow(第 03 章);多智能体 → 两个 AsyncFlow 靠 asyncio.Queue 通信(cookbook/pocketflow-multi-agent);人在环 → 在 prep 里 input()(chat 示例)。没有一个模式需要改框架代码。
4.2 巧妙之处(可借鉴的技术)
妙一:公开方法 vs 内部钩子的分层
你覆写 exec;框架覆写 _exec。重试、批、异步全插在 _exec/_run 这层,你的业务逻辑毫不知情。这让「能力叠加」变成纯粹的类组合。见 Node._exec 包住 BaseNode.exec(pocketflow/__init__.py:29-34)。
妙二:用运算符重载把「代码」变成「流程图」
a - "x" >> b 读起来就是流程图上的一条带标签的箭头。__sub__ 返回一个中间对象 _ConditionalTransition 承接动作名,再由它的 __rshift__ 落地(pocketflow/__init__.py:17-24)。三行代码换来极高的可读性。
妙三:节点无状态、copy.copy 保平安
图里的节点是定义不是实例;真正跑的是每步的浅拷贝(Flow._orch,pocketflow/__init__.py:47-48)。于是同一节点能在循环里被反复经过而不串味,successors/shared 又因浅拷贝仍被共享。**「拷执行者、共享图与数据」**是这套设计的精髓。
妙四:None 即 "default"
post 返回 None(什么都不写就是这样)被当作走 "default" 边(get_next_node 的 action or "default",pocketflow/__init__.py:43)。于是「线性流程」零心智负担:节点不管返回值,>> 自然接上。
妙五:附带 .pyi 类型存根
源码本体为了「100 行」写得极度紧凑(分号连写),但配了一份完整的 pocketflow/__init__.pyi,用 Generic[_PrepResult, _ExecResult, _PostResult] 把三段之间的类型关系标出来。紧凑的实现 + 清晰的类型契约分离,兼顾了「短」和「可读/可类型检查」。
4.3 边界与局限(诚实)
PocketFlow 刻意只做「图编排」这一件事。以下都不在框架里,得你自己写或抄 cookbook:
- 没有 LLM 调用。 框架零依赖、不含任何模型 SDK。
call_llm全在cookbook/*/utils.py里由用户实现(如cookbook/pocketflow-chat/utils.py)。 - 没有工具 / 函数调用抽象、没有内置记忆 / 向量库、没有 prompt 模板。 这些都是你在节点里自己拼(agent 示例的 prompt 是手写字符串)。
- 没有可观测性 / 追踪 / 持久化。
shared是内存里的 dict,进程结束即失;要落盘自己在节点里存。 - 没有并发安全。 并行分支共享同一
shared,写同键会竞争,框架不加锁(第 03 章 3.4)。
会在哪「崩」或让人困惑
- 动作名拼错 = 静默结束。
post返回一个没连线的动作名,Flow 只warnings.warn然后停下(get_next_node,pocketflow/__init__.py:44),不报错。忘看告警就会以为「流程没跑完」。 - 忘了用 Flow、直接
node.run()。 单节点run不跑后继(只告警,pocketflow/__init__.py:15)。 - 异步节点用错入口。 对
AsyncNode调同步_run会RuntimeError("Use run_async.")(pocketflow/__init__.py:74)。 - 无限循环。 图里有回边又没退出条件(比如决策节点永远返回 "search"),
_orch的while不会自己停——退出得靠你的post逻辑最终返回一个通向出口的动作。
4.4 横向对比(同 shelf 的取舍)
PocketFlow 在 agent-frameworks 货架上是极简主义的极端。它与 LangGraph 最像(都把程序建模成图),但取舍相反:
| 维度 | PocketFlow | 典型重框架(LangChain/LangGraph 等) |
|---|---|---|
| 代码量 | 100 行、零依赖 | 数万~数十万行、大量依赖(README 对比表:LangChain ~405K 行) |
| 抽象 | 只有「图」 | Agent/Chain/Tool/Memory/Retriever… 成套 |
| LLM/工具/记忆 | 不含,自己写 | 内置大量 wrapper |
| 学到的东西 | 逼你理解「Agent 本质就是带回边的图」 | 拿来即用,但底层被封装 |
| 适合 | 想完全掌控、轻量嵌入、教学 | 想快速搭、要现成集成 |
核心区别:PocketFlow 卖的不是功能,是「一个足够小、能一眼看穿的核心抽象」。 它甚至主张让 AI(Cursor 等)照着这套原语帮你生成 agent 代码(README 的「Agentic Coding」)——框架小到模型能完整装进上下文,是这个主张成立的前提。
代码地图(导航索引)
| 主题 | 文件 | 符号 |
|---|---|---|
| Agent 循环模式 | cookbook/pocketflow-agent/flow.py、nodes.py | create_agent_flow、DecideAction.post |
| 用户侧 LLM 调用(框架不含) | cookbook/pocketflow-chat/utils.py | call_llm |
| None→default 路由 | pocketflow/__init__.py | Flow.get_next_node |
| 单节点误用告警 | pocketflow/__init__.py | BaseNode.run |
| 异步入口保护 | pocketflow/__init__.py | AsyncNode._run |
| 类型契约 | pocketflow/__init__.pyi | BaseNode、Node、Flow 的泛型签名 |