想象你拿到一套 200 平米的毛坯房,要在两个月内交付精装。如果只派一个全能师傅从水电做到油漆,他要么累垮、要么返工、要么拖期。聪明的装修公司会怎么做?把工程拆成几个专业小队——水电组、瓦工组、木工组、油漆组,每组只干自己最擅长的事,项目经理统一排期协调。组与组之间只需要交接“电管布好了、地面找平了”这样的标准接口,谁先干谁后干清清楚楚。这就是模块化拆分的魔力:大工程变清晰,小队可复用,哪个环节出问题就只修哪块。 LangGraph 里的子图(Subgraph)干的就是这件事。你面对的不是毛坯房,而是复杂的 Agent 流程——比如一个客服 Agent 要完成意图识别、知识库检索、情绪安抚、工单生成、人工交接……如果把所有逻辑塞进一张大图,就像让一个全能师傅干完所有工种,图会变得臃肿难维护。而 LangGraph 的 Subgraph 机制,让你能把大流程像装修工程一样拆成独立的小图,各自定义状态、各自编译、再像模组一样拼进主图。这篇文章会带你从概念、定义、嵌套、传参、复用一路讲到最佳实践,让你真正学会用子图拆分复杂 Agent。
对 AI 从业者来说,子图是 LangGraph 架构师的核心工具。学会它,你就能把千行大图拆成可维护、可测试、可复用的模块,这在大模型应用工程化落地中几乎是必修课。
可视化演示
演示子图嵌套结构、状态传递映射机制及子图复用架构,展示LangGraph模块化拆分Agent流程的核心设计模式。
本节配有交互动效(进入视口后自动加载,约 3KB)。
子图嵌套结构 子图本身是完整LangGraph图,含独立节点边和State 子图封装为单个节点嵌入父图使用 支持多层嵌套,实现复杂Agent模块化拆分 ParentGraph = Node₁ → SubGraph₁ → Node₂ → SubGraph₂ 子图嵌套实现复杂Agent的模块化设计
子图核心概念
回到装修的比喻。你拿到 200 平米毛坯房,如果把它当成一个“超级大任务”,让一个师傅从头干到尾,那这个“全能师傅”要同时懂水电、泥瓦、木工和油漆——他很容易出错,而且一旦中途有人离职,整个工程就瘫痪了。子图的思路很简单:把大任务拆成几个小任务,每个小任务由一支专业小队负责,每支小队有自己独立的施工图纸(State 定义)、独立的验收标准(入口/出口),而项目经理(主图)只负责排期和协调。
在 LangGraph 里,“子图”指的就是一个编译完成的 StateGraph,它本身是一张完整的图,有自己独立的节点、边和状态定义。当一个图被编译后,它可以被当作一个普通节点嵌入到另一张更大的图中。这种“图里有图”的设计,本质上是把复杂 Agent 的工作流变成了乐高积木——每一块积木内部再复杂,对外只露几个插孔。
为什么需要拆分?核心原因是降低复杂度。假设一张大图有 N 个节点,节点间的连线可能达到 O(N²) 的量级,任何一次修改都可能牵一发动全身。而拆成 k 个子图后,每个子图内部节点数为 Nᵢ,子图间只需维护少量接口连线,整体复杂度大大降低。
理解了拆分逻辑,你会发现一个深刻的道理:子图的本质不是“切碎代码”,而是“定义接口”。就像装修公司不关心水电工用什么牌子的电钻,主图也不关心子图内部是怎么推理的,只关心它能不能按时交付标准结果。这种“外部只认接口、内部自由发挥”的特性,正是子图成为大型 Agent 架构基石的原因。
- 求和符号\displaystyle \sum_{i=1}^{k} 表示把 k 个子任务各自的复杂度全部加起来。
- 每项 T(N_i)T(N_i) 是第 i 个子图的复杂度,N_i 是它内部的节点数,节点越少复杂度越低。
- 编排成本 CC 是主图协调这些子图的开销,只要接口设计得好,C 通常很小。
子图定义方式
装修公司在报价时,不会让一个施工队“什么都干”,而是把工程拆成“水电改造套餐”“泥瓦工程套餐”“整体木作套餐”——每个套餐都有明确的服务边界和交付清单。LangGraph 里定义子图的方式,就是先画一张独立的小图,再把它“打包成套餐”挂到主图上。
定义一个子图最直接的方式:先创建一个 StateGraph,给它加上节点和边,然后调用 compile()。编译后的图就能作为一个节点函数嵌入父图。此时父图节点函数像这样:
你只需要在父图的 StateGraph.add_node("child", call_child) 里挂载它,一张子图就正式成为大图的一员了。这个“编译后当节点用”的设计,让子图结构完全内部自治。
定义子图时还有几个关键点(即“边界节点”)。子图需要明确的入口(set_entry_point)和出口(set_finish_point),父图只跟这两个“门”打交道。为了让子图能配合父图工作,子图的 State 结构通常需要与父图对接——要么共享同一个 State 类型,要么在调用时显式传参数。当你用 LangGraph 的 add_node 把一个编译好的图放进去时,LangGraph 会自动识别它的输入输出字段,就像装修验收只看交付结果一样。
计算公式上并没有新的数学,但从接口设计的角度,子图的“契约”可以形式化描述为:父图对子图的调用等于一个函数复合。
想记住“子图定义方式”的精髓,可以拿装修公司做类比:每个子图就是一支有营业执照的施工队,它有资质(State 类型)、有报价单(接口文档)、有验收标准(入口/出口)。主图招投标(add_node)时只看资质不看流水线,这就是“编译后当节点用”的工程智慧。
- F_子(S_请求)子图是一个函数,吃进请求状态,产出结果。
- F_父(S_入口,……)父图是更大的函数,它把自己的入口状态和子图结果一起处理。
- 函数嵌套整个表达式是一个嵌套调用,对应主图“调用一个子图节点”的动作。
def call_child(state): return child_app.invoke(state)
子图嵌套层级
装修界有总包和分包。总包接了整个楼盘的外墙工程,又把外墙拆成东区、西区、北区三个标段;东区标段又分包给三个班组。子图嵌套也是这个道理——子图内部还可以再挂子图,形成“根图 → 子图 → 孙图”的层级结构,每一层只跟相邻层通信。
LangGraph 的嵌套没有深度硬限制,但实际工程中常见的是两到三层。根图(Root Graph)是总入口,负责定义整体流程。它调用的子图负责某个子流程,子图内部又可以调用更小的子图。嵌套的层级越高,状态映射(请求/响应字段的对应关系)就越复杂,所以实践中要克制。
嵌套时最核心的机制是“请求/响应映射”。父图调用子图时,需要显式说明把父状态的哪些字段传给子图,子图返回后把哪些字段写回父状态。LangGraph 官方推荐用这个模式:
这里的关键是:子图运行在自己的状态空间里,它完全不知道父图还有别的字段,也拿不到父图的全部状态。这种隔离让嵌套变得更加安全——修改孙图不会波及根图。
从数学上看,嵌套的深度 L 与每层的状态映射维度相关。假设每一层保留的接口字段数为 m,则状态传递的“信息流”可写成一个递推关系。
在实际项目中,嵌套层级越深,排错越难——就像总包都不清楚东区标段的三号班组用的什么砂浆。所以最好的实践是:能两层解决就不上三层,每一层的传参显式写清楚,不靠隐式共享。记住,嵌套的本质是“层级隔离”,每一层只对上一层负责。
- 投影函数 P\mathcal{P} 表示从上层状态筛选出本层需要的字段,这是一个映射操作。
- 下标 ll 表示当前嵌套的层数,l=0 是根图,l=1 是第一层子图。
- 递推关系整个式子描述了状态在嵌套层级间逐层传递的路径。
def call_child(state): child_request = { "query": state["user_query"], "history": state["conversation"] } result = child_app.invoke(child_request) return {"subgraph_answer": result["answer"]}
子图传参通信
装修队之间的配合靠“交接单”:水电组干完,在墙上留一张单子写明“电管走向已标注、水管打压完成”,瓦工组照单施工。谁也不会把整个装修图纸搬过去。子图传参通信的精髓正是这种“交接单文化”——父图给子图一张“请求单”,子图回一张“结果单”。
LangGraph 中传参通信有两条路:一是共享同一个 State 类型(图结构定义时用同一个 Schema),二是调用时显式构造请求/响应字典。显式传参更推荐,因为它把“交接单”的内容摆在了明面上。子图内部有自己独立的 State,即使和父图 Schema 相同,运行时也是隔离的两份数据,不会互相污染。
具体来说,父图节点里调用子图时,LangGraph 会用你返回的字典关键字段去覆盖父状态里同名字段。这既是通信通道,也是数据更新的方式——你返回 {"subgraph_answer": ...},父图就把 subgraph_answer 更新为新值。这种 State 覆盖机制让信息流动变得可追踪:每一步改了什么字段一目了然。
用集合的语言来描述这种通信:父图的完整状态集合为 P,子图的应答集合为 R,一次通信本质上是字段投影的复合。
把通信理解成“交接单”,你就能避免两个常见错误:一是别指望子图自动拿到父图的全部状态(谁也不会把图纸全给水电工);二是别让子图直接改父图的深层嵌套字段(交接单只写结果不写过程)。显式、小巧、命名清晰——这是传参通信的黄金法则。当你的按钮按下,整栋大厦的电路由一根根独立的管线串联起来时,你才知道当年那句“接口比实现更重要”有多正确。
- 交集字段P ∩ R 表示两个状态集合中名字相同的字段,这些字段直接映射传递。
- 集合差 R \ P表示子图独有的输出字段,父图需要“吸收”这些新信息。
- 直和符号\oplus 表示两组字段合并成一个完整的状态更新包。
子图复用策略
装修公司最赚钱的能力不是施工,而是“复制经验”:这套水电工艺在 A 小区用过,在 B 小区几乎原样再来一遍。子图复用也是同一个商业逻辑——同一张子图可以在主图的不同分支、不同位置反复挂载,每次调用都是独立运行,互不干扰。
LangGraph 中的复用有几种常见姿势。第一,多实例复用:同一张编译好的子图,在主图的多个节点上调用,每个 Node 的调用彼此独立,子图运行状态不会串。第二,多图组合:多张子图可以组合成一张更大的图,子图之间像搭积木一样串联或并联。第三,条件入口:主图根据当前状态动态选择调用哪张子图,相当于给施工队派单时按户型分流。第四,工厂函数:写一个返回子图的函数,根据参数生成不同配置的子图实例——就像装修公司针对大平层和复式楼分别定制施工方案。
复用带来的收益可以用一个简单的数学模型衡量。假设开发一张子图的固定成本是 C_dev,单次运行成本是 c_run,如果重复开发 m 次不复用,总成本是 m × (C_dev + c_run);而复用只需要开发一次,后续每次只付运行成本。
复用子图最大的价值在于一致性——同一个水电施工队在 100 个工地干出来的活标准一样,Agent 也一样:同一个子图在不同流程中表现完全一致,消除了“同一逻辑在不同地方有不同行为”的口碑危机。当然,复用要克制。如果两张子图只有 10% 的共性,硬凑一张反而绑手绑脚;装修队也不是一支队伍干所有活,专家队的复制才有意义。
- 开发成本项(m-1) × C_dev 表示省下了 m-1 次重复开发的开销。
- 趋近于 0 的 C_dev'复用后每次接入的边际开发成本趋近于零。
- 运行成本 c_min唯一不可省的是每次运行的资源开销,但这是必需支出。
模块化最佳实践
装修干得多了,优秀的项目经理会总结出一套“施工标准答辩手册”:图纸统一编号、材料统一采购、每个工种进场前先看前一道工序的验收单。模块化编程的最佳实践,本质上就是这些工程经验的数字化。
在 LangGraph 子图拆分中,最有价值的四个实践是:
第一个是依赖注入。子图不应该在内部硬编码外部资源(比如 API Key、模型名),而应通过入参或配置传入,就像装修队不自己烧砖,而是由总包统一供料。这样同一个子图换一套配置就能适配不同环境。
第二个是关注点分离。每个子图只做一件事:检索子图只检索,生成子图只生成,不要在一个子图里既检索又生成。检查你拆分的粒度是否合适,就看“这个子图你会不会给它起个动词开头的名字”。
第三个是命名规范。节点名、子图绑定的 key 都要语义清晰,比如 rag_retrieve、escalate_to_human,不要用 node1、node2。装修队的对讲机里喊“张三李四”还是喊“水电三组”完全影响现场效率。
第四个是接口收敛。子图对外暴露的 State 字段越少越好——请求给两个字段、返回一个字段,就是最理想的迷你接口。对外暴露的字段一旦增多,复用的阻力就变大。
这个“接口数量”的哲学可以量化:假设子图对外暴露的字段数为 p,每增加一个字段,主图与子图间的熵耦合就多一分。耦合度可以写成:
装修界有句老话:“好木匠不换工具,换工具的不是好木匠。”模块化最佳实践的核心,是让子图成为一把“好工具”——内部复杂但外部简单。依赖注入让它适应不同环境,关注点分离让它职责单一,命名规范让协作不吃力,接口收敛让它随处可嵌。有了这些实践,复杂 Agent 才能像装修流水线一样,每个模块都靠谱、每个接口都干净。
- p_共享指多个父图节点都会读写的字段数量,这类字段造成耦合。
- p_总子图对外暴露的全部字段总数。
- 比值共享字段占比越小,比值越接近 0,耦合度越低。
实际应用场景
当一支训练有素的装修工程队走上正轨,它能接的活儿就远不止一套毛坯房了。LangGraph 的子图模块化一旦落地,复杂 Agent 的场景几乎都能“一键接入”。下面看三个典型场景。
第一个是智能客服工单全流程。整套系统拆成四张子图:意图识别子图(听懂用户说什么)、知识库检索子图(找标准答案)、情绪安抚子图(态度决定天)、工单生成子图(处理不了自动建单)。主图负责调度:先走意图识别,再按分支决定走检索还是情绪安抚,最终汇聚到工单生成。传统做法里这些逻辑全写在一个大图里,改一处全线回归测试;拆成子图后,团队分成四人组各自维护一张图,改情绪安抚词库完全不影响检索逻辑。
第二个是数据分析流水线。数据分析 Agent 拆成数据采集子图、数据清洗子图、统计分析子图、报告生成子图。其中“数据清洗子图”尤其值得复用——它像一个标准工程队,无论是跑电商销售数据还是跑服务器日志,它都能干;主图换掉输入输出映射,同一张清洗图就能服务完全不搭界的业务线。
第三个是内容审阅发布流水线。这个场景把流程拆成初稿生成子图、合规检查子图、风格润色子图、多平台发布子图。合规检查子图常常是独立部署的——因为它的审核标准和发布业务全无关系,独立成图后甚至可以发布给其他团队复用。这就是子图模块化的终极形态:不是某个 Agent 的一部分,而是可供整个公司调用的公共设施。
这些场景揭示了同一个规律:找到了 30% 的公共子图,就找到了 70% 的复用杠杆。子图拆分的是代码,撬动的是工程效率和团队协作。
AI 应用场景
智能客服工单全流程
阶段:流程编排与模块化
解决的问题:传统客服 Agent 将所有逻辑塞进一张大图,遇到需求变更(比如新增“退款申请”流程)要改动主链路,风险高、测试慢。用子图拆分后,每个环节独立迭代互不干扰,工单生成逻辑调整不需要回归整个客服系统。
具体案例:某电商平台客服 Agent 拆为“意图识别、检索、情绪安抚、工单生成”四张子图。某次大促前紧急新增“仅退款”政策,团队只改了工单生成子图的语义规则,两小时内完成上线,其余子图零改动。
数据周报自动生成系统
阶段:数据管道编排与复用
解决的问题:数据分析 Agent 最怕“数据源换了一个,全部脚本重写”。抽取公共子图后,数据清洗和统计计算模块可以跨业务线复用,新的数据源只需写一个采集子图接入。
具体案例:某公司为三条业务线分别搭建周报 Agent,两期后发现清洗和统计逻辑高度重合,于是抽成公共子图库,两条新业务线的开发周期从两周缩短到三天。
媒体内容审阅发布流水线
阶段:业务链路模块化分工
解决的问题:内容平台的“生成-审核-发布”链路涉及产运、法务、编辑多个团队,如果一张大图被所有团队共同修改,权限和变更管理都是灾难。子图拆分后,每个团队只管自己那张子图,通过接口对接。
具体案例:某 MCN 机构把选题生成、合规审核、多平台发布拆成三张子图。合规审核子图单独维护,法务更新规则只发布那一个子图版本,发布子图毫不知情,整个系统稳定运行零事故九个月。
Python 演示
# 简易 LangGraph 子图拆分演示(纯 Python 实现核心思路)class GraphState(dict): """状态容器,类似 LangGraph 的 State""" pass class StateGraph: """简化版 StateGraph,支持子图嵌入""" def __init__(self, state_schema=None): self.nodes = {} self.edges = [] self.entry = None self.finish = None self.schema = state_schema or GraphState def add_node(self, name, fn): self.nodes[name] = fn return self def add_edge(self, start, end): self.edges.append((start, end)) return self def set_entry(self, name): self.entry = name return self def set_finish(self, name): self.finish = name return self def compile(self): """编译后返回可调用对象,即'子图'""" return CompiledGraph(self) class CompiledGraph: """编译后的图,可被父图当作节点调用""" def __init__(self, graph): self.graph = graph self.node_map = graph.nodes def invoke(self, state): cur = self.graph.entry while cur != self.graph.finish: fn = self.node_map[cur] state = GraphState(state) result = fn(state) if result: state.update(result) nxt = None for s, e in self.graph.edges: if s == cur: nxt = e break cur = nxt return dict(state) # ============ 装修公司案例:总图调用三支"施工队"子图 ============def make_water_node(): """水电子图""" g = StateGraph() g.add_node("lay_pipes", lambda s: {"pipe_status": "done"}) g.add_node("check_leak", lambda s: {"leak_check": "passed"}) g.add_edge("lay_pipes", "check_leak") g.set_entry("lay_pipes") g.set_finish("check_leak") return g.compile() def make_tile_node(): """瓦工子图""" g = StateGraph() g.add_node("level_floor", lambda s: {"floor_level": "ok"}) g.add_node("polish", lambda s: {"tile_status": "done"}) g.add_edge("level_floor", "polish") g.set_entry("level_floor") g.set_finish("polish") return g.compile() def make_paint_node(): """油漆子图""" g = StateGraph() g.add_node("prime_wall", lambda s: {"prime": "done"}) g.add_node("coat", lambda s: {"paint_status": "finished"}) g.add_edge("prime_wall", "coat") g.set_entry("prime_wall") g.set_finish("coat") return g.compile() def build_master_graph(): """主图:编排三支施工队""" water_sub = make_water_node() tile_sub = make_tile_node() paint_sub = make_paint_node() def call_water(state): return water_sub.invoke(state) def call_tile(state): return tile_sub.invoke(state) def call_paint(state): return paint_sub.invoke(state) master = StateGraph() master.add_node("water_team", call_water) master.add_node("tile_team", call_tile) master.add_node("paint_team", call_paint) master.add_edge("water_team", "tile_team") master.add_edge("tile_team", "paint_team") master.set_entry("water_team") master.set_finish("paint_team") return master.compile() if __name__ == "__main__": app = build_master_graph() result = app.invoke({"project": "200平毛坯房"}) print("装修结果:", result)
class StateGraph:定义简化版状态图类,add_node挂载节点,add_edge连边,模拟 LangGraph 的图构建 API。def compile(self): return CompiledGraph(self)编译后返回一个新对象,这个对象就是“子图”,可被父图当成普通节点。def invoke(self, state):子图的核心入口:接受一个状态字典,按边拓扑执行,返回更新后的状态。state = GraphState(state)每次调用生成新的状态容器,实现了子图运行时的状态隔离。result = fn(state); state.update(result)节点函数的返回值会覆盖更新状态,这正是 State 覆盖通信机制的精髓。make_water_node()工厂函数:每调用一次就生成一张全新的“水电子图”编译实例,多个父图互不干扰。master.add_node("water_team", call_water)主图把整张编译好的子图包装成节点函数挂载,对应 LangGraph 的add_node("child", child_graph.invoke)。build_master_graph()组装总图:主图只认识water_team / tile_team / paint_team三个接口,对子图内部一无所知。
常见误区
误区编译完的子图既然是个 Python 对象,那我随时可以调用 `add_node` 再往里面塞一个节点。
正解编译后的 CompiledGraph 与构建时的 StateGraph 是隔离的,compile() 之后子图结构已冻结。想要不同的结构,要么改源码重新编译,要么用工厂函数生成不同配置的子图实例。这就像装修队进场后不能临时换图纸,要改方案得回到设计部重新出图。
为何常见:初学者把“编译”理解成 Java 的运行时对象,以为编译只是加了一层壳,忽略了 LangGraph 中“编译后成为节点”这个身份转变的不可逆性。
误区子图是用父图的状态 Schema 定义的,所以调用时父图整个状态都会被塞进子图。
正解LangGraph 只传递你显式构造的字段。你在父图节点函数里 child_app.invoke({"query": ...}),子图就只知道 query。这是一个防错设计:子图的输入输出越窄,耦合越少。总包不会把 200 页图纸全给水电工,只给水电专项图。
为何常见:因为从代码看父图和子图用的是同一个字典类型,新手容易把“类型相同”误解为“数据互通”,忽略了显式传参这一层。
误区为了能传参,子图必须 import 父图的那个 State 类,否则字段对不上。
正解这恰恰是反向最优实践。子图最好定义自己独立的 State 接口,用请求/响应字段映射对接。LangGraph 支持任意可序列化的状态字典作为子图输入输出,你完全可以给子图另起一套精简的字段名。接口越独立,子图越容易被其他项目复用。
为何常见:共享 Schema 在写第一个 Demo 时最省事,于是被误当成了标准做法。真正的模块化设计恰恰要摆脱“共享结构”的惯性。
误区每张子图在父图里出现一次,就像每个工种只干一个岗位。
正解同一张编译好的子图可以在父图的不同分支、不同节点反复调用。每次 invoke 都是独立的状态空间,运行结果互不影响。你可以在改写产品文案和改写营销邮件两个分支里调用同一个“风格润色子图”,两份文案各自走各自的流程,互不串扰。
为何常见:把“Python 函数被调用多次”的直觉丢了——明明函数能复用,编译后的子图却被默认为单次资源。
误区模块化嘛,把每两个节点之间的逻辑都单独抽成子图,绝对是最优雅的架构。
正解每次子图调用都有通信成本和状态映射成本,拆分过细会让主图变成一张纯调度网——主图上全是“调A、调B、调C”的胶水节点,真正的业务逻辑反而淹没在接口转换里。好的拆分粒度是“一张子图对应一个业务阶段”,比如装修只分水电、瓦工、木作、油漆四段,而不是把“贴砖”再细分成“拌浆”“抹平”“放砖”“敲实”四个子图。
为何常见:常见“为了模块化而模块化”的冲动,忽略了模块化的目的是降低总复杂度,而不是把复杂度转移到接口对接上。
面试题
Q1. 在 LangGraph 中,一个编译完成的 StateGraph 可以被如何处理?
- A.作为节点嵌入父图← 正确答案
- B.只能在单独进程中运行
- C.不能再被任何图引用
- D.只能作为根图运行
答案:A。编译后的子图本质是一个可调用的图对象,LangGraph 允许你把它作普通节点挂到父图上。就像一支装修队可以承接任何总包派发的活。B/C/D 都违反了“子图即节点”的核心特性。
Q2. 下列关于子图传参的说法正确的是?
- A.父图状态全量自动传入子图
- B.子图只能返回空字典
- C.需显式构造请求字段传给子图← 正确答案
- D.子图与父图必须共享内存
答案:C。LangGraph 子图通过显式传参通信,只传递你构造的字段。A 错在“全量自动”,C 对,D 完全不符合状态隔离设计。这就像总包只给施工队发交接单,而不是把所有图纸全塞过去。
Q3. 子图嵌套时,请求/响应映射的主要作用是?
- A.提高子图运行速度
- B.将上层状态投影为子图需要的字段← 正确答案
- C.自动合并所有状态字段
- D.删除父图的冗余数据
答案:B。嵌套层级间通过投影函数 P 将上层响应映射为下层请求,实现状态隔离与定向传参。B 正是“投影”的含义。A/D 与传参无关,C 违反了“显式传参”原则。
Q4. 一张子图在父图里被复用的最佳方式是什么?
- A.每次复制源码重新编译
- B.同一编译实例多次 invoke← 正确答案
- C.为每个调用方重写一次
- D.只能在条件分支里用一次
答案:B。复用应直接复用编译好的子图实例,每次 invoke 独立状态空间互不干扰。A/C 都重复造轮子,D 限制了复用场景。这就像装修队承接多个楼盘的不同标段,同一个队进场 N 次。
Q5. 下面哪些属于子图模块化的最佳实践?
- A.依赖注入外部配置← 正确答案
- B.子图内做所有业务逻辑
- C.对外暴露最少字段← 正确答案
- D.命名采用 node1/node2
答案:AC。A 和 C 是标准最佳实践,分别保证可配置性和低耦合度。B 违背关注点分离,D 违背命名规范。好接口讲究“小而清晰”,就像交接单只写必要项。
Q6. 关于子图内部状态与父图状态的关系,正确的理解是?
- A.两者是同一份数据的引用
- B.子图内部有独立状态空间← 正确答案
- C.子图可以修改父图全部字段
- D.共享 Schema 意味着数据互通
答案:B。子图拥有独立状态空间,通过返回值与父图交互。A 错在引用共享,C 错在“全部字段”,D 是典型误区——共享类型不代表共享数据。
Q7. 在设计复杂 Agent 时,哪些信号说明应该引入子图?
- A.单张图节点数超过几十个← 正确答案
- B.存在独立可复用的业务阶段← 正确答案
- C.多个团队需要并行维护不同模块← 正确答案
- D.图中所有逻辑一次性执行完毕
答案:ABC。A 图规模大、B 有可拆分阶段、C 有协作需求,都是拆分子图的信号。D 恰好相反——流程过于简单线性时引入子图反而是过度设计。
Q8. LangGraph 中子图定义完成后,父图如何接入它?
- A.直接用 apply 方法连接
- B.通过 add_node 注册子图函数← 正确答案
- C.必须修改子图源码
- D.重新写一张合并大图
答案:B。父图用 add_node("child", child_app.invoke) 注册后加边即可。B 是标准接入方式。A 是 API 误记,C/D 都是绕远路,好比总包拿到施工队电话直接调度。
Q9. 状态覆盖机制中,子图返回 {"result": x} 后发生了什么?
- A.父图所有状态被清空
- B.父图状态里 result 字段被更新← 正确答案
- C.子图内部状态被覆盖
- D.父图状态完全不变
答案:B。子图返回的字典以字段为键覆盖父状态中同名字段。B 是正确的 State 更新语义。A/C/D 错得离谱,这就像施工队交回验收单,总包只登记单上写的项目。
Q10. 关于子图复用收益的表述,下列哪些是正确的?
- A.复用消除了多份逻辑不一致的风险← 正确答案
- B.复用能省去每次的运行成本
- C.固有一定成本后边际接入成本趋零← 正确答案
- D.任何子图复用次数越多越划算
答案:AC。A 对,复用保证同一逻辑各处行为一致。B 错,每次运行仍有资源成本。C 对,边际开发成本趋零正是复用模型的核心。D 错,共性低的子图强行复用反增耦合——就像让油漆队去通下水道,省不了钱还坏事。