ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

LangGraph 和 PocketFlow的框架对比

2026/9/4 5:01:52 拓冰建站 浏览量
LangGraph 和 PocketFlow的框架对比 同一个 Agent我写了两套框架LangGraph 和 PocketFlow 的实测对比这是我自己从零手搓的一个天气出行助手。同一个业务逻辑我分别用 PocketFlow 和 LangGraph 各写了一遍。写这篇一是想搞清楚两套框架到底差在哪二是给我自己留个记录。项目地址github.com/Wen-111-zheng/weather-travel-agent一、为什么我要写两遍事情是这样的。我在做天气出行助手的时候很快就撞上了第一个绕不开的问题几个 Agent 之间到底怎么串起来我的助手要干四件事听懂你想问什么意图识别、去查真实天气、结合知识库给出行建议、闲聊兜底。这些功能单看都不难难的是它们之间怎么协作——谁先跑、谁后跑、查失败了怎么办、闲聊要不要也去查一遍天气这活儿手写 if-else 也能干但代码会越写越乱。所以框架就登场了。市面上主流的有两个LangGraph生态大、偏产品级和PocketFlow极简、核心就几百行、偏教学友好。看文档能看出个大概但我总觉得差点意思——文档是别人总结的结论不是我自己的手感。所以我给自己定了个小实验同一个业务逻辑两套框架各写一遍看差异到底在哪。不是为了炫技就是想知道换框架这件事到底换掉了什么。二、实验台先把业务逻辑从框架里摘出来做这个实验有个前提业务逻辑必须和框架无关。要是四个 Agent 里都塞满框架特有的写法那对比的就是两坨不同的代码没意义。所以我把四段逻辑抽成了纯函数放在agents/core.py里# agents/core.py节选defintent(question):# 意图识别 抽城市/偏好defweather(cities):# 通过 MCP 工具查天气defadvice(weather_data,preferencesNone):# RAG 检索 生成建议defchat(question):# 闲聊兜底这四个函数只干业务不知道外面用哪个框架。天气走 MCP 工具、知识库走 RAG、用户偏好走一个 json 记忆文件——这些全是挂在函数外面的能力跟编排层没关系。这样一来换框架就真的只是换一层壳core.py一行不动只换怎么把这四个函数串起来的那点代码。这个实验能不能成立全看这一步。后来我发现这也是整个项目里最值钱的一个设计决定。![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?三、第一版PocketFlow轻得像自己写的代码PocketFlow 的核心概念少得可怜一个Flow、一个Node外加一个贯穿全程的shared字典。整个编排长这样这是完整代码25 行# flow.py完整defcreate_weather_travel_flow():intentIntentAgent()weatherWeatherAgent()adviceAdviceAgent()chatChatAgent()intent-weatherweather intent-travelweather intent-chatchat weather-adviceadvicereturnFlow(startintent)三个特点我很快就摸清了状态是一个可变字典。所有节点共享shared谁都能读写没有类型约束改起来很随意。节点是类固定三段生命周期prep准备数据 →exec干活 →post把结果写回shared。路由靠post返回一个字符串。返回advice就走 advice返回chat就走 chat——图就是这么串起来的。最让我惊喜的是BatchNode。我这个助手支持一次问多个城市按理说得写个 for 循环逐个查。用BatchNode之后这事框架帮你兜了classWeatherAgent(BatchNode):defprep(self,shared):self.prefsshared.get(preferences,[])returnshared.get(cities,[])# 返回列表框架自动展开defexec(self,city):# 每个城市各跑一次returnweather([city])[0]defpost(self,shared,prep_res,exec_res_list):shared[weather_data]exec_res_listfori,cityinenumerate(shared.get(cities,[])):ifnotexec_res_list[i].get(fallback):record_query(city,self.prefs)# 写长期记忆returnadvice# 路由到下一站prep返回一个列表框架自动拆开、每个元素跑一次exec最后把结果汇总进post。多城市的处理我一行 for 循环都没写。四、第二版LangGraph仪式感更重类型更严LangGraph 那边完全是另一个世界。同样的事它这么写节选classAgentState(TypedDict):# 状态要先声明类型question:strintent:dictcities:List[str]preferences:List[str]weather_data:List[dict]answer:strdefintent_node(state:AgentState)-dict:# 节点是普通函数resintent(state[question])# 调的还是同一个 corereturn{intent:res,cities:res.get(cities,[]),preferences:res.get(preferences,[])}# 返回局部更新defroute_intent(state):# 路由独立的 router 函数actionstate.get(intent,{}).get(action,chat)returnweatherifactionin(weather,travel)elsechatgStateGraph(AgentState)g.add_node(intent,intent_node)g.add_conditional_edges(intent,route_intent,{weather:weather,chat:chat})g.add_edge(weather,advice)g.add_edge(advice,END)# 必须显式声明结束差异一眼就出来了状态shared字典 → 先声明一个TypedDict节点不直接改状态而是返回一个局部更新由框架合并。类型和字段都是显式的。节点类 三段生命周期 → 就是一个普通函数接 state、返回 dict。路由post返回字符串 → 抽出一个独立的 router 函数配合add_conditional_edges用。结束没有后继边就自然停 →必须显式写END。写完的第一感觉是LangGraph 的仪式感更重——你得先声明状态长什么样、路由得单独抽个函数、结束还得显式写 END。好处是约束清楚、类型可控代价是第一次上手要写不少跟业务无关的样板代码。多城市这块它也没有BatchNode那种自动展开我是自己一把梭data [client.get_weather(c) for c in cities]。![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url%E5%9B%BE3_LangGraph%E7%BC%96%E6%8E%92%E6%B5%81%E7%A8%8B.pngpos_idimg-vGzX4Y4N-1788364940542五、同一份逻辑两个世界跑通两版之后我把差异整理成了一张表——这才是写两遍真正的收获维度PocketFlowLangGraph状态可变shared字典节点直接写回TypedDict节点返回局部更新、框架合并节点形态类继承Node/BatchNodeprep→exec→post普通函数xxx(state) - dict路由post返回 action 字符串独立 router 函数 add_conditional_edges多城市BatchNode自动 fan-out自己列表推导无 fan-out结束无后继边自然结束必须显式END代码量编排 25 行 4 个 Agent 类单文件 95 行搞定依赖源码引入零第三方包需要pip install langgraph顺带说一个我自己埋的细节core.weather()是纯查询、不写记忆的长期记忆统一由编排层来写——PocketFlow 写在WeatherAgent.postLangGraph 写在weather_node里。当时我特意在core.py的注释里写了一句保证两套框架行为一致。现在回头看那行注释比这张对比表值钱。因为它是我第一次意识到所谓解耦不是嘴上说说是要落实到副作用归谁管这种细节上的。六、写两遍的意外收获两个坑如果这篇文章只有上面那些那它只是篇普通的框架测评。真正让我觉得值的是过程中踩的两个坑。坑 1默认框架选 langgraphdemo 现场卡死了先说背景main.py里我让--framework默认走 langgraph。有一次录 demo敲完命令终端就停在[框架] langgraph那一行半天没反应。第一次遇到我以为是网络断了重跑——又好了。后来又卡了一次我才认真去查。排查其实很快utils/weather_client.py里我给天气接口设了 8 秒超时天气那块不可能卡死卡点只能在 DeepSeek 的调用上。顺着报错栈往下翻看到了 langgraph 内部的run_with_retry——它在反复重试。结论是DeepSeek 偶发慢/丢包时langgraph 内部的重试循环会一直转外面看起来就是卡住不动。PocketFlow 那边链路短、没这层重试同样的问题表现为快速失败重跑就好。我学到的是框架那些看不见的兜底行为重试、状态合并、超时策略会影响你 demo 的稳定性。你以为框架在帮你它其实在替你做决定——而你根本不知道它做了什么决定。坑 2长期记忆记性太好把下一轮污染了这个坑更典型。我的助手会用user_profile.json记住用户常问的城市和偏好跨轮复用。听起来挺聪明直到我做了这个测试第一次问“深圳天气我带宝宝出门” → 答得挺好该带什么都说了第二次问“广州天气我一个人出门” → 它还在给我推荐婴儿车防雨罩、宝宝汗巾我明明说了一个人。翻代码先在user_profile.json里找到了元凶——preferences: [带宝宝]上一轮随口说的被永久记下来了。再顺着找到agents/core.py第 59 行merged_prefslist(dict.fromkeys(preferencesprofile.get(preferences,[])))这行把本轮偏好和历史偏好无脑拼在了一起不分青红皂白。但根因比这行代码更深一层是我把这一趟出行的临时上下文和我这个人的稳定画像混为一谈了。带宝宝是这一趟的事不是我的属性——我一个人出门的时候不需要带宝宝建议。可我的代码把它当稳定画像存了自然就污染了下一轮。修法是按标签清把带宝宝/带老人/陪老人这些归为临时上下文跑完本轮直接从记忆里删掉通勤/防晒/骑行这种才是稳定偏好留着跨轮复用。TEMPORARY_PREFERENCE_TAGS{带宝宝,带小孩,带老人,陪老人,...}clear_temporary_preferences()# main.py 每跑完一次调一次改完再问我一个人出门回答干干净净。我学到的是记忆不是记得越多越好关键是分清什么该记、什么该忘。七、结论框架不是重点边界才是写完两版之后最大的收获不是哪个框架更好——说实话两个都能把活干完选谁更多是习惯和团队的事。真正的收获是这三个换框架只换壳这件事是可以成立的前提是你一开始就把业务逻辑和编排分开。写第二版的时候我core.py一行没改不到一小时就跑通了。值钱的不是框架是边界。什么归业务、什么归编排、什么归工具MCP、什么归记忆——边界划清楚了换什么都只是换实现。框架的隐藏行为要提前知道。重试策略、状态合并方式、错误兜底这些看不见的部分才最容易在你最需要它稳定的时候坑你一把。所以我现在main.py里一行参数就能在两套实现之间切换两套都留着。不是为了炫技——是想用同一份逻辑跑两套编排反复验证解耦这件事到底真不真。写到这里这个项目我还在继续改。下一步想折腾的是能不能再换一套完全不同的编排比如自己写一个极简调度器看看core.py是不是依然不用动。代码全在 GitHub 上github.com/Wen-111-zheng/weather-travel-agent欢迎来喷也欢迎一起聊 Agent 到底怎么编排更顺手。