本文是「LangGraph 教程系列」第 10 篇。写作时基于 langgraph 1.2.10、langchain 1.3.14、langchain-openai 1.4.1、Python 3.12。配套代码仓库 https://github.com/wxj006007/deep-research-assistant 本篇对应 tagv3。v2.3 已经能把回答、进度和状态变化分开送出去。可当我把那张图铺开时另一个问题更刺眼读取记忆、规划、审批、检索、评估、成稿、保存摘要全堆在同一个 builder 里。图还能运行读起来却开始费劲。想改检索循环时要先穿过长期记忆和成稿逻辑。想复用写作流程时又得把整张图一起搬走。这不是节点太多就该机械拆文件。真正该拆的是稳定职责。本篇把研究助手分成 planner、researcher、writer 三个子图让父图回到它该做的事安排交接而不是参与每一个细节。一、单图为什么会开始难维护到 v2.2研究流程已经有两类节点。一类是业务主线规划、检索、评估和成稿。另一类是横切能力加载长期记忆、保存摘要、取消任务和 checkpoint。它们放在一起当然能跑但阅读图时要不断在不同层级切换。v3 的拆分依据不是模型数量也不是每个图必须有多少节点。planner 负责把问题和记忆变成首条查询。researcher 负责审批、搜索、评估和继续搜索的循环。writer 只负责根据资料和记忆写出回答。它们都是同一个研究助手的内部模块。子图是组合和封装不是多智能体。真正引入 supervisor、专家角色与跨 agent 协作是下一篇的主题。二、父图负责交接子图负责自己的工作拆分后的外层结构很短。渲染错误:Mermaid 渲染失败: Parse error on line 8: ...摘要] persist -- end([END]) cance ----------------------^ Expecting AMP, COLON, PIPE, TESTSTR, DOWN, DEFAULT, NUM, COMMA, NODE_STRING, BRKT, MINUS, MULT, UNICODE_TEXT, got endresearcher 自己仍有一张循环图。第 7 篇的人工审批没有消失只是终于回到了它所属的研究职责里。researcher 子图通过拒绝继续充分人工审批检索结束评估资料返回父图父图只关心 researcher 最后是带着资料回来还是带着 cancelled 回来。它不需要知道研究子图循环了几轮。这个边界一旦稳定检索策略增加重排序、来源过滤或并行工具时父图不必跟着膨胀。三、状态交接依赖字段契约把编译后的图直接作为 add_node 的节点时LangGraph 会按字段名在父图与子图间传递数据。这里最重要的不是语法而是双方事先约定哪些字段可以交接。子图读取字段输出字段plannerquestion、memory_contextcurrent_queryresearcherquestion、current_querydocs、executed_queries、round、verdict、cancelledwriterquestion、docs、memory_contextanswer代码把这份约定写进三个 TypedDict。classPlannerState(TypedDict,totalFalse):question:strmemory_context:MemoryContext current_query:strclassWriterState(TypedDict,totalFalse):question:strdocs:Annotated[list[Doc],operator.add]memory_context:MemoryContext answer:str父图声明同一批共享字段。对于 docs 和 executed_queries父图和 researcher 都使用 operator.add reducer。这样子图中的每轮检索先累积在自己的 state 里返回父图时字段语义也不会改变。字段同名不是约束偷懒而是一份小而明确的接口。若某个子图想用完全不同的数据结构就在父图和子图间加一个适配节点显式转换输入和输出。不要让一个子图悄悄依赖父图所有字段那只是在另一种形式里把大图搬回来了。四、三个子图如何编译planner 只有一个 plan 节点也值得成为子图。职责已经稳定日后它可以扩展为问题拆解、查询改写或预算控制父图的调用方式不用改变。defbuild_planner_subgraph():builderStateGraph(PlannerState)builder.add_node(plan,plan_node)builder.add_edge(START,plan)builder.add_edge(plan,END)returnbuilder.compile()researcher 复用前文的节点和路由函数把循环封在内部。defbuild_researcher_subgraph():builderStateGraph(ResearcherState)builder.add_node(review,review_plan_node)builder.add_node(search,search_node)builder.add_node(evaluate,evaluate_node)builder.add_node(cancel,cancel_node)builder.add_edge(START,review)builder.add_conditional_edges(review,route_after_review,{search:search,cancel:cancel},)builder.add_edge(search,evaluate)builder.add_conditional_edges(evaluate,route_after_evaluate,{review:review,synthesize:END},)returnbuilder.compile()writer 的结构也很小它只调用已有的 synthesize_node。这里不复制提示词、模型工厂和记忆格式化逻辑v3 是组合已有能力不是重新实现它们。五、持久化仍只有一份子图编译时没有传入 MemorySaver 或 InMemoryStore。builder.add_node(planner,build_planner_subgraph())builder.add_node(researcher,build_researcher_subgraph())builder.add_node(writer,build_writer_subgraph())returnbuilder.compile(checkpointercheckpointerorMemorySaver(),storestoreorInMemoryStore(),)checkpointer 和 store 在父图统一配置后子图会继承这次运行的资源。thread_id 仍然是整次研究的会话标识ResearchContext 仍然提供 user_id 与 research_id。子图不另开一条会话也不另建一份用户记忆。这点在 interrupt 上最容易看清。researcher 的 review_plan_node 暂停后调用方仍然向父图传 Command(resume…)。initialgraph.invoke({question:子图如何继承父图的 checkpoint},configconfig,contextcontext,)finalresume_until_complete(graph,initial,config,context,)调用方不需要知道 interrupt 停在哪一个子图。相同的 thread_id 能让 LangGraph 沿嵌套 checkpoint 命名空间定位到正确的 review 节点。调试时可以请求包含子图的快照。snapshotgraph.get_state(config,subgraphsTrue)fortaskinsnapshot.tasks:print(task.name,task.state)运行到审批点时父图任务列表里会有 researcher 的嵌套状态。它很适合排查流程和展示子图边界但不应被当成业务接口直接暴露给用户。六、取消与长期记忆的边界researcher 收到拒绝决定后返回 cancelled。父图据此路由到 cancel直接结束。defroute_after_researcher(state:V3ResearchState,)-Literal[writer,cancel]:returncancelifstate.get(cancelled)elsewriter只有 writer 成功产出 answer 后父图才会进入 persist_memory。这样一次被人工取消的研究不会以完成摘要的形式落进 Store。这段路由看起来简单却守住了 v2.2 就建立的长期记忆边界。长期记忆是跨会话资产不能因为图拆开了就失去原先的写入条件。七、跑起来代码在src/v3_subgraphs.py。python-msrc.v3_subgraphs脚本会检查首次调用在 researcher 内部暂停父图可以用同一条 thread 恢复writer 最终得到资料并写出答案成功任务才会保存摘要。它还会读取 subgraphsTrue 的嵌套状态并验证拒绝路径不会增加长期记忆。回到那张越来越大的图。拆成子图没有减少实际工作研究仍会规划、审批、检索和写作。它减少的是理解一段流程时必须同时握住的东西。父图看调度子图看职责字段契约看交接。下一篇模块边界已经有了。我们再让不同角色真的开始协作从单兵研究助手走向多专家团队。赞或收藏 关注 我们下次再见