PocketFlow编排能力进一步增强的思考
PocketFlow的设计理念非常出色。在当前Agent应用场景中,大部分都是基于工作流模式的,因此采用Flow的方式确实能带来显著效果。经过深入使用后,我认为可以进一步优化其组件设计,将其原子化程度提升到更高水平。理想状态下,开发者只需编写简单的流程编排代码即可完成复杂任务,例如:QuestionNode >> LLMNode >> RAGNode。更进一步说,如果原子化能力足够强大,甚至可以将Dify这样的低代码编排流程转换为PocketFlow的高代码实现。我已经验证过这个方向的可行性,目前遇到的主要挑战在于数据共享机制。当前PocketFlow通过shared对象在Node间共享数据,每个Node可以自由定义输出到shared的数据。然而在实际应用中,由于每个Node的输入需求各不相同,导致在流程编排时经常需要重新调整每个Node的输入输出配置,这给使用带来了诸多不便。
举一个简单的例子:
class QuestionNode(Node):
def prep(self, shared):
return shared
def exec(self, prep_res):
question = prep_res["question"]
return question
def post(self, shared, prep_res, exec_res):
shared["question"] = exec_res
class LLMNode(Node):
def prep(self, shared):
llm_messages = shared["llm_messages"]
return llm_messages
def exec(self, llm_messages):
response = call_llm(llm_messages)
return response.choices[0].message.content
def exec_fallback(self, prep_res, exc):
return "There was an error processing your request in LLMNode."
def post(self, shared, prep_res, exec_res):
shared["llm_response"] = exec_res
在编排QuestionNode >> LLMNode时,发现节点间的输入输出不兼容。目前考虑两种解决方案:
(1)修改QuestionNode的输出格式以适配LLMNode的输入。虽然这能解决当前问题,但存在明显的局限性。当需要将QuestionNode与其他类型的节点(如XXNode)连接时,可能又需要再次修改QuestionNode的输出格式,这会导致:
- 代码维护困难,需要不断调整QuestionNode的实现
- 可能破坏现有QuestionNode与LLMNode的连接逻辑
- 违反开闭原则,对扩展开放但对修改关闭
(2)通过继承机制,创建QuestionNode或LLMNode的子类并重写prep/post方法。这种方法虽然更符合设计原则,但也存在以下问题:
- 代码冗余增加,需要为每种连接组合创建专门的适配类
- 系统复杂度提升,增加了类的数量
- 使用体验下降,需要开发者了解并选择正确的适配类
因此,希望对共享数据shared可以进一步改进。或者这样有没有更好的实现方式,希望给一些指导,谢谢。
通常需要一个declaratively well defined 的 shared data structure e.g., https://github.com/The-Pocket/PocketFlow-Tutorial-Codebase-Knowledge/blob/main/docs/design.md#shared-store 然后所有的nodes都遵守这个design 来prep & post 我发现如果shared store is well define, adapt to a new structure完全可以来copilot (like Claude code my current favorite)轻松实现
@mossexplore 我最近也在思考这个问题,在dify这样的可视化页面中编辑节点,然后转为pocketflow这样灵活的框架代码,用于二次开发,这是非常吸引人的点。 卡点也出现在节点之间的连接问题。 @zachary62 提出的well defined shared data structure是一个方向,但是当graph增长的时候,这个shared data structure内部的管理就会变得十分混乱,以至于无法进行二次开发。
此处我建议参考go语言的框架eino,他们要求前一个运行节点的输出值类型等于下一个节点的输入值类型,这样二者就可以进行直接连接。这在graph的build阶段完成类型判断。
参考https://www.cloudwego.io/zh/docs/eino/core_modules/chain_and_graph_orchestration/orchestration_design_principles/
而share data,是在运行时注入state变量,用于在graph nodes中流动。
我之所以推荐这样的方式,是因为我尝试过突破这种模式,用pocketflow的思想重写它,但最终发现,eino的模式能同时很好地兼容chain和graph两种构建模式。
如果对此有兴趣,能否拉一个讨论组一起讨论一下这里的编排问题。
@RanFeng 是的,作者提供的这种方式适合在小规模场景,按照业界设计思路,一定需要先定义图,然后编译,类似langgraph这种,但是这样太复杂了。大佬,你使用python实现了eino类似pocketflow的思想了么?是否可以分享下
"前一个运行节点的输出值类型等于下一个节点的输入值类型" Trivial implementation: just in shared store {"last_node_output": xxx} 然后pre 读 post overwrite 但我担心不好maintain ...
我实现了一个功能增强的框架,实现了节点的链式调用以及分支,循环,并发,聚合,通过pydantic实现对入参和出参 基于annotation的校验,使用dependency-inject实现依赖注入线程安全的上下文,节点之间不需要手动传递参数,目前基本功能可用,打算逐步优化,感兴趣可以看看https://github.com/12306hujunjie/AetherFlow
“将Dify这样的低代码编排流程转换为PocketFlow的高代码实现”,请教下这种工具的适用对象是非代码人员还是代码人员?会写代码的直接基于 pocketflow 构建也很轻松,不会写代码的直接使用 dify、n8n、OpenAI Agent 可视化编排就行。
“将Dify这样的低代码编排流程转换为PocketFlow的高代码实现”,请教下这种工具的适用对象是非代码人员还是代码人员?会写代码的直接基于 pocketflow 构建也很轻松,不会写代码的直接使用 dify、n8n、OpenAI Agent 可视化编排就行。
面向团队的,由非技术人员使用可视化编程搭建初始框架,验证可行性,然后导出代码,交给技术人员进行二次开发