想象你经营着一家小餐馆,明天晚上要承办一场百人婚宴。如果所有菜都让主厨一个人按顺序做——先炒这盘,待会儿再炒下一盘——客人吃完前菜大概要等到天亮。聪明的做法是:把菜单拆成几十份小任务,多位帮厨各占一个灶台同时开火,菜品陆续出锅后统一送到传菜口,由主厨核对摆盘再端上桌。负责“拆菜单”的是 Map,多位厨师同时开工是“并行执行”,最后收拢菜品的是 Reduce。 LangGraph 的并行节点就是这套“数字化后厨”。它把一个大任务拆成多个互不依赖的子任务,让它们在同一轮里并发执行,再按规则合并成最终结果。你会发现,LangGraph 里的 Channel 是后厨公告板,Send 指令是派单条,super-step 是整轮齐步走的号令,而并发调度就是灶台排班表。读完这篇文章,你就能理解一个 Agent 如何从“做完一件事再做下一件”,进化成“同时忙活一堆事,最后合成一个漂亮答案”。

岗位关联

对 AI 从业者来说,这是让 Agent 从“单线程苦力”升级为“任务调度主管”的关键能力。掌握并行节点的并发执行模型,你就能在检索、批处理、多方案对比等场景里把吞吐量提升数倍,这也是高级 Agent 框架面试中绕不开的硬核考点。

目录
  1. 可视化演示
  2. Map并行拆分:任务扇出
  3. 并行执行机制:Super-step 同步
  4. Reduce结果合并:扇入汇聚
  5. 并发调度控制
  6. 错误处理策略
  7. 适用场景案例
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示 Map-Reduce 并行执行模型:Map 阶段分发任务到多个 Worker 并发执行,Reduce 阶段合并结果。以及并发控制与错误处理策略。

本节配有交互动效(进入视口后自动加载,约 3KB)。

Map-Reduce 并行流程 Input:任务分发入口节点 Map:多个Worker并发执行子任务 Reduce:合并所有Worker结果 总耗时 ≈ max(T_worker_i) + T_reduce Map阶段并行执行,Reduce阶段合并结果

Map并行拆分:任务扇出

婚宴筹备最紧张的时刻,是主厨在开工前把厚厚一沓菜单摊开,飞快地勾画出每个帮厨该负责的菜品。这个“拆菜单、派活”的动作,对应 LangGraph 里的 Map 阶段。入口节点收到一个输入后,并不直接处理它,而是把它拆成许多互不依赖的子任务,每个子任务带着自己的一份“小菜单”(独立的状态快照)流向不同的执行分支。

在 LangGraph 中,这个“派单”由一个叫 Send 的指令完成。你可以把 Send 想象成一张张派单小票:每张票上写着一个子任务的函数和数据。更巧妙的是,这个小票可以动态开具——Map 阶段根据输入内容决定开几张票、每张票的数据结构是什么。这就像主厨看到客人有海鲜过敏备注,临时把某道菜拆成“免海鲜”版单独派一张单,不需要事先列好所有情况。

因为我们想让每位帮厨全神贯注炒自己的菜,所以每个子任务都拥有独立的子状态。它们可能共享一份“总菜单”(全局配置),但各自的进度、结论都写在自己的小本子上,互不干扰。这样设计的好处是:任何两个分支之间不会因为抢同一支笔而打架,后续的并发执行才有安全的基础。当一个入口节点调用 send() 生成多个任务时,LangGraph 会把这些分支当作同一个父节点的多个“分身”,并发窗口会同时调度它们。

1. 子任务与并发窗口的比值:N 是待处理的子任务数,M 是允许同时执行的并发窗口(比如线程池大小)。

NM\displaystyle \frac{N}{M}

2. 上取整:⌈·⌉ 表示向上取整。7 个子任务、3 个并发窗口,需要 3 轮而不是 2 轮,因为最后一轮哪怕只有 1 个任务也得执行完。

⌈73⌉=3\displaystyle \left\lceil \frac{7}{3} \right\rceil = 3

3. 总时间构成:轮数乘以单任务耗时得到执行时间,再加上 Map 拆分开销和 Reduce 合并开销,就得到整体耗时。

tmap+treduce\displaystyle t_{\text{map}} + t_{\text{reduce}}

这个公式一眼就能看出并行收益来自哪里:如果 N 很大而 M 很小,时间主要花在排队轮次上;如果 M 足够覆盖 N,一轮就能跑完,剩下就是拆与合的固定开销。主厨看完各灶台的完成情况后,判断的正是这个“怎么排班最省时间”的问题。

text1 行
\displaystyle T = \left\lceil \frac{N}{M} \right\rceil \times \tau + t_{\text{map}} + t_{\text{reduce}}

并行执行机制:Super-step 同步

派单结束后,后厨进入最热闹的环节:六口灶台同时开火,油锅滋滋作响。每一位帮厨都不知道旁边的人在炒什么,只专心做好手上的菜。LangGraph 的并行执行机制与此惊人地相似——多个并行分支同时处于 running 状态,每个分支只处理自己的子状态,彼此之间不需要直接通信。

但热闹背后必须有秩序。LangGraph 采用了一种叫 “super-step“(超级步)的同步模型:每一轮被唤醒的节点全部执行完毕后,框架才会推进到下一轮。你可以把它理解为后厨的”齐步走”口令——所有灶台火候到点同时起锅,不会出现某个灶台还在炒、另一位已经乱入下一轮的情况。任何依赖上一步结果的节点,都必须等这一轮所有并行分支归档之后才能被唤醒,这正是数字化后厨和真实后厨一样需要的纪律。

那各位帮厨做完菜,如何把自己的成果“登记在案”?答案是 Channel——后厨公告板。每个并行分支执行结束后,会把结果写到指定的 Channel 上。如果多个分支同时往同一个 Channel 写入,就由 Channel 的写入规则决定如何处置:有些规则是“保留最后一个值”,这相当于公告板上最后贴上的一张便利贴盖住了之前的;有些规则是“追加成列表”,像把每位厨师的作品单依次钉在公告板下面。写入规则的协调,保证了所有分支对状态的修改以可预期的方式合并。

super-step 的同步保证换来的是状态一致性:读到的每个值都代表“上一轮所有分支的最终结果”,不会出现读到一半的脏数据。正如后厨的秩序保证了每道菜都是完整出锅——虽然六口灶台同时开火,但传菜口永远只接收已经完成的菜品。

Y={ f(xi)∣xi∈X, i∈[1,N] }\displaystyle Y = \{\, f(x_i) \mid x_i \in X,\ i \in [1,N] \,\}
这是 Map 阶段并行执行的抽象表达:对输入集合 X 中的每一个元素 x_i,都应用同一个处理函数 f,所有 f(x_i) 的结果构成输出集合 Y。每个元素彼此独立,所以可以并行处理。
  1. 输入集合X 是待处理的所有输入元素,N 是元素的个数。比如 100 份订单、50 个城市的天气数据。
    X={x1,x2,…,xN}\displaystyle X = \{x_1, x_2, \dots, x_N\}
  2. 处理函数f 是每个并行分支要执行的节点逻辑。所有分支使用同一个 f,只是输入数据不同,就像所有帮厨遵循同一套菜谱但各自炒不同的菜。
    f(xi)\displaystyle f(x_i)
  3. 输出集合Y 是所有分支结果的有序或无序集合。这个集合就是后续 Reduce 阶段的“半成品仓库”。
    Y={y1,y2,…,yN}, yi=f(xi)\displaystyle Y = \{y_1, y_2, \dots, y_N\},\ y_i = f(x_i)

Reduce结果合并:扇入汇聚

帮厨们陆续熄火,端着成品走向传菜口。此时的主厨不再下锅炒菜,而是站在摆盘区,把一盘盘菜按宴席顺序排列、核对、装饰。这个“收拢”动作就是 LangGraph 的 Reduce 阶段:它把所有并行分支的输出汇聚到一个节点上,按照预订规则整合成最终结果。

LangGraph 里的 Reduce 有两种实现思路。第一种是“站着收”——设置一个 Reducer 节点,它等所有并行分支完成后被唤醒,拿到完整的输出列表,做排序、筛选、聚合这类重量级操作。另一种是“边收边合”——依靠 Channel 的写入规则增量合并,比如统计型 reducer 每收到一个值就累加一次,不用等全部结束就已有部分结果。选择哪种,取决于你要的是“全部到齐再算总分”还是“边收分边记录”。

这里最关键的概念是 Reducer 函数本身。它定义了如何把多条结果合并成一个:是拼接、求和、加权平均,还是取置信度最高的那条?自定义合并策略的灵活性,让并行模式不只适用于同质化任务。你可以让一个分支写 SQL 查询结果、另一个分支写向量检索结果,最后在 Reduce 阶段用加权融合把它们变成统一的推荐列表——像主厨把冷菜、热菜、甜点编排成完整的婚宴菜单。

主厨在传菜口每收一道菜就勾掉菜单上的一项,全部的菜到齐才能开席——Reduce 就是这样一道“验收关”。它保证了并行分支各自为战的成果,最终被组织成一个有序、完整的整体交付给下游节点。

z=y1⊕y2⊕y3⊕⋯⊕yN\displaystyle z = y_1 \oplus y_2 \oplus y_3 \oplus \cdots \oplus y_N
z 是最终的合并结果,⊕ 代表你定义的合并运算。如果 ⊕ 是“按城市拼接”,结果就是一份城市列表;如果 ⊕ 是“取最大值”,结果就是最佳选项。
  1. 并行输出y_1 到 y_N 是 Map 阶段各个分支的输出,它们此时全部就绪,等待合并。
    yi, i=1,…,N\displaystyle y_i,\ i=1,\dots,N
  2. 合并运算⊕ 不是固定的加号,而是你自定义的归并逻辑——可以是字符串拼接、数值累加、字典更新或打分排序。
    a⊕b\displaystyle a \oplus b
  3. 最终结果z 是所有输出按照合并运算顺序作用后的最终值。合并运算的结合性很重要:(a⊕b)⊕c 应当等于 a⊕(b⊕c),这样并行合并(两两合并再合并)才不会出乱子。
    z=(y1⊕y2)⊕⋯⊕yN\displaystyle z = (y_1 \oplus y_2) \oplus \cdots \oplus y_N

并发调度控制

只有六口灶台的后厨,却接到了二十道菜的任务。主厨必须做出选择:一口气全塞给帮厨,灶台不够、案板不够、调味料也会抢破头。真正聪明的调度是“分批上灶”——每轮只推六道菜,做完一轮再上新菜。LangGraph 的并发调度控制,处理的就是这个“灶台排班”问题。

LangGraph 底层通常跑在线程池或异步执行器上,max_workers 之类的参数直接决定了并发窗口的大小。并发窗口不是越大越好:窗口太大,大量分支同时抢占内存和回调栈,反而让单任务变慢;窗口太小,任务排队时间变长,并行红利所剩无几。实用的策略是让窗口大小接近执行器的核心并发度,就像六口灶台的后厨,派七道菜不如派六道菜节奏更稳。

除了窗口大小,LangGraph 还允许你控制“分批派单”的节奏。Map 阶段可以不是一次性生成全部子任务,而是每轮生成一部分,等上一批完成后再继续派下一批——这对应 Send API 的连续调用。你可以把它想成主厨不会一次性把所有菜单都拍到帮厨面前,而是按上菜节奏一盘一盘地派。配合 recursion limit(递归深度上限),这种动态分批调度能避免“任务爆炸”:在循环或递归场景中,只要还有剩余配额就继续派单,配额耗尽则优雅终止。

调度的艺术是“有限资源做最多的事”。当你在生产环境里调并行度时,那个最合适的窗口值往往要通过压测来找——正如主厨知道六口灶台最稳,但偶尔也会根据客流量临时调整到五口或七口,核心原则是不让任何一口灶台闲太久,也不让任何一位帮厨手忙脚乱。

U=Mbusy×τM×(τ+twait)\displaystyle U = \frac{M_{\text{busy}} \times \tau}{M \times (\tau + t_{\text{wait}})}
U 衡量执行资源的利用率。分子是实际干活的时间,分母是资源被占用的总时间(干活时间加等待时间)。利用率越接近 1,说明并发资源越饱和。
  1. 忙碌资源M_busy 是同时处于执行状态的并发任务数量,M 是并发窗口总量。
    Mbusy≤M\displaystyle M_{\text{busy}} \le M
  2. 执行与等待τ 是任务实际执行的平均耗时,t_wait 是任务排队等待的平均耗时。总占用时间就是两者之和乘上 M。
    M×(τ+twait)\displaystyle M \times (\tau + t_{\text{wait}})
  3. 利用率实际干活的时间占比。窗口太小利用率低,因为 M_busy 很小;窗口太大利用率也可能低,因为资源争抢导致 τ 变大、t_wait 变长。
    U=Mbusy×τM×(τ+twait)\displaystyle U = \frac{M_{\text{busy}} \times \tau}{M \times (\tau + t_{\text{wait}})}

错误处理策略

热闹的后厨也会出事:一位帮厨火候没看住,把红烧肉烧糊了。这时候整桌宴席怎么办?是让这位帮厨重做一份,还是干脆取消这道菜,让宴席继续?LangGraph 并行执行的错误处理,本质上就是这些预案的工程化实现。

默认行为是“整桌停下”:只要任何一个并行分支抛出异常,整个 super-step 立刻终止,图进入失败状态。这听起来很残酷,但你换位思考一下——主厨无法确定其他灶台的结果是否会被这道糊菜影响,与其端上一桌东拼西凑的菜,不如让宴席停下来统一处理。不过,LangGraph 也提供了“重做这一道”的选项:给节点配置重试策略(retry policy),设置最大重试次数和退避间隔。这就像帮厨被允许再炒一次,第二次心里更有底,成功率自然更高。

还有更进阶的降级方案:在 Reduce 阶段手动捕获部分分支的结果,即使某个分支失败,也把其他成功分支的结果做合并输出。这种“部分降级”像是主厨说:“红烧肉没了,但其他菜照常上,我们用备用的梅菜扣肉顶上。”它最适合那些“部分成功仍有价值”的场景,比如多源检索时一个数据库挂了,其他数据库的结果依然可以用来回答问题。

这个公式揭示了一个反直觉的真相:并行不是“人多力量大”,而是“人多摊上事的概率也大”。十个人各端一杯水,总有一个人可能手抖。所以优秀的 Agent 工程师在设计并行图时,总会在关键分支上加重试和降级策略,避免一场婚宴因为一道菜毁于一旦。

Pfail=1−(1−p)N\displaystyle P_{\text{fail}} = 1 - (1 - p)^{N}
P_fail 是整个并行任务的失败概率。p 是单个分支的成功率,N 是并行分支数。N 越大,全部同时成功的概率越低——并行越多,越需要容错设计。
  1. 单分支成功每个分支的成功率为 p,失败率为 1-p。
    P(分支成功)=p\displaystyle P(\text{分支成功}) = p
  2. 所有分支同时成功N 个独立分支全部成功的概率是各自的成功率相乘。
    P(全部成功)=(1−p)N\displaystyle P(\text{全部成功}) = (1 - p)^N
  3. 整体失败概率用 1 减去“全部成功”的概率,得到至少一个分支失败的概率。当 N 逐渐增大时,即使单分支成功率很高,整体失败率也会显著上升。
    Pfail=1−(1−p)N\displaystyle P_{\text{fail}} = 1 - (1 - p)^{N}

适用场景案例

不是每道菜都适合让多位厨师同时做——佛跳墙需要文火慢炖,中途加人反而添乱。判断一个任务是否适合 LangGraph 并行节点,核心标准是看它能不能被拆成“互不依赖的平行工序”。如果子任务之间需要频繁交换中间结果,或者每一步都依赖上一步的输出,那就不该上并行,老老实实串行反而更可靠。

适合的场景长什么样?第一类是批处理数据:把一份包含一千篇文档的语料库拆成十个片段,每个片段独立做摘要,最后合并成一份总报告。这像宴席上的凉菜拼盘——每盘独立制作,最后摆在一起。第二类是多源检索:同一个查询同时发给多个知识库或搜索引擎,收集所有结果后做排序融合,能显著缩短用户等待时间。第三类是子 Agent 规划:让多个子 Agent 分别调研市场、竞品、成本,最后汇总成商业分析报告,每个子 Agent 都是独立动作、独立产出。

但也有明显的局限性。如果所有分支都要竞争同一份全局状态,频繁写入引起冲突,状态一致性就难以保证;如果任务之间存在隐形依赖——比如分支 A 需要分支 B 中途的一个数值才能继续——硬塞进并行只会得到错误结果。判断的方式一句话:能不能把任务画成“一棵树”而不是“一条链”?树可以并行修剪枝干,链只能逐节推进。掌握了这个判断标准,你就能在合适的场景里放心地开足火力。

主厨深知:哪怕后厨有三十口灶台,如果菜单设计本身必须一道菜接一道菜地炖,那并行也救不了宴席的进度。你能做的,是把可并行的部分尽量拆细、摊平,让每一口灶台都有活干,同时接受“总有些步骤快不起来”的现实。懂取舍,才是用好并行节点的最高境界。

S=1(1−ppar)+pparM\displaystyle S = \frac{1}{(1 - p_{\text{par}}) + \dfrac{p_{\text{par}}}{M}}
这是阿姆达尔定律的并行加速比公式。p_par 是任务中可并行部分的比例,M 是并发窗口。即使 M 无限大,加速比也被串行部分 (1-p_par) 死死卡住。
  1. 串行占比1 - p_par 是任务中无法并行的部分比例,比如数据加载、Reduce 合并的固定开销。
    1−ppar\displaystyle 1 - p_{\text{par}}
  2. 并行部分的加速p_par 除以并发窗口 M,表示并行部分被压缩后的时间占比。
    pparM\displaystyle \frac{p_{\text{par}}}{M}
  3. 总加速比原始耗时 1 除以加速后的总耗时占比。当 M 趋于无穷大时,加速比趋近于 1/(1-p_par),这就是并行收益的上限。
    S→11−ppar\displaystyle S \to \frac{1}{1 - p_{\text{par}}}

AI 应用场景

多源情报并行检索 Agent

阶段:数据获取与融合

解决的问题:传统检索 Agent 串行访问多个数据源,每次查询要等待前一个接口返回才发起下一个请求,用户等待时间是所有数据源延迟的总和。使用 LangGraph 并行节点后,所有数据源同时发起检索,总延迟从“累加”变成“最大”,且 Reduce 阶段可以统一去重排序。

具体案例:某金融风控 Agent 需要同时查询企业工商信息、司法涉诉、舆情新闻和黑名单库四个数据源。用 Map 阶段拆出 4 个查询分支并行执行,每个分支独立调用一个 API,Reduce 阶段按企业名称关联聚合,生成统一的风险画像。原来 4 秒的串行等待降到 1.2 秒,用户体感提升明显。

批量文档智能审查管道

阶段:任务分解与批量处理

解决的问题:合同审查、论文查重、代码审计等场景中,需要处理大量文档。传统做法是逐篇送审,GPU 和大模型算力闲置而耗时线性增长。并行节点将文档按批次拆分成多个子任务,每个子任务独立调用大模型做要点抽取,Reduce 阶段汇总成审查报告并标注风险等级。

具体案例:某法律科技公司的合同审查 Agent 一次接收 50 份合同,Map 阶段按合同类型动态分组派单,每组分配独立的大模型调用并发执行,最后合并所有审查意见并按风险分值排序输出。处理吞吐量提升约 4 倍,而成本几乎不变。

多模型方案投票决策 Agent

阶段:推理与决策

解决的问题:关键决策场景中单一模型可能偏差较大,需要多个模型/策略并行出方案再投票。并行节点让不同模型在各自分支里独立推理,互不污染上下文,Reduce 阶段做加权投票或置信度融合,输出最优方案。相比串行逐模型评估,等待时间缩短为原来的一半以下。

具体案例:某医疗辅助诊断 Agent 同时调用 3 个专科模型判断影像报告,Map 阶段并行分发,Reduce 阶段按“至少两票一致”的规则输出结论,并附上分歧提示给医生复核。

Python 演示

python39 行
# -*- coding: utf-8 -*-"""LangGraph Map-Reduce 并发执行模拟:多城市空气质量查询与汇总"""from concurrent.futures import ThreadPoolExecutor, as_completedimport time  def fetch_city_aqi(city):    time.sleep(0.3)    aqi = sum(ord(ch) for ch in city) % 100    return {"city": city, "aqi": aqi}  class CityAqiPipeline:    def __init__(self, cities, workers):        self._cities = cities        self._workers = workers        self._results = []     def map_phase(self):        with ThreadPoolExecutor(max_workers=self._workers) as pool:            futures = [pool.submit(fetch_city_aqi, c) for c in self._cities]            for fut in as_completed(futures):                self._results.append(fut.result())     def reduce_phase(self):        total = sum(item["aqi"] for item in self._results)        best = max(self._results, key=lambda item: item["aqi"])        avg = total / len(self._results)        return {"best_city": best["city"], "best_aqi": best["aqi"], "avg_aqi": avg}  if __name__ == "__main__":    cities = ["北京", "上海", "广州", "深圳", "成都", "杭州"]    pipe = CityAqiPipeline(cities, workers=3)    start = time.time()    pipe.map_phase()    summary = pipe.reduce_phase()    print("汇总结果:", summary)    print("总耗时: %.2f 秒" % (time.time() - start))
目的:用线程池模拟 LangGraph 的 Map 阶段并行查询 6 个城市的空气质量指数,再通过 Reduce 阶段计算最优城市和平均值。改动 workers 参数可以直观感受并发窗口对耗时的影响。
  1. from concurrent.futures import ThreadPoolExecutor, as_completed引入线程池工具,相当于 LangGraph 底层的执行器。
  2. with ThreadPoolExecutor(max_workers=self._workers) as pool:创建并发窗口为 _workers 的线程池,超过窗口的任务会排队等待,这就是“灶台排班”。
  3. [pool.submit(fetch_city_aqi, c) for c in self._cities]对每个城市提交一个异步任务,对应 Map 阶段的 Send 派单。
  4. for fut in as_completed(futures):每完成一个任务就取一次结果,对应 LangGraph 在 super-step 内持续收集分支输出。
  5. self._results.append(fut.result())把每个并行分支的输出收集到列表,对应 Channel 的追加式写入规则。
  6. total = sum(item["aqi"] for item in self._results)Reduce 阶段的第一部分:将所有子结果累加,对应自定义 reducer 的求和逻辑。
  7. best = max(self._results, key=lambda item: item["aqi"])用 max 取最优城市,对应“取最大值”风格的自定义合并策略。
  8. with ThreadPoolExecutor(...) as pool使用上下文管理器确保所有线程资源在 Map 阶段结束后自动释放,防止资源泄漏。
  9. time.sleep(0.3)模拟真实网络请求的耗时,让并行效果可视化地体现为总耗时的显著下降。

常见误区

误区我以为 Map 阶段就是最开始把所有数据切成碎片,然后一次性全部送进并行分支里。如果有一千个子任务,那就一千个分支同时跑。

正解LangGraph 的 Map 阶段更像持续派单的传菜口,而不是一次性倒出的进货仓。你可以用 Send 分批生成子任务,每轮只生成当前并发窗口能处理的量,上一批完成后再派下一批。配合递归限制,可以优雅控制任务总量,防止“任务爆炸”吞掉内存。

为何常见:很多人把 Map-Reduce 理解成 Hadoop 的全量切片模型,忽略了 LangGraph 作为图执行引擎的动态派单能力,误以为分支数等于并发数。

误区既然各分支都在同一个图里,我想让它们共享一个全局计数器或缓存字典,直接读写就行了吧?

正解并行分支直接读写共享可变状态会产生竞态条件,导致结果不可复现。LangGraph 的正确做法是:共享信息通过 Channel 写入,由 Channel 的 reducer 规则决定如何合并多个分支的写入。分支内部修改自己的子状态,跨分支的沟通只能通过图的“公告板”完成,而不能直接抓对方的变量。

为何常见:把单线程编程习惯直接搬进并发模型,忽略了“同时读写”这个在并发世界里最危险的信号。

误区总觉得把 `max_workers` 调大,分支全都拥挤地同时跑,整体速度肯定更快。于是我看到 100 个子任务就把并发窗口调到 50。

正解并发窗口过大会引发资源竞争:线程切换开销、内存占用量暴涨、单个任务变慢,甚至触发执行器的排队雪崩。实际工程中,最合适的窗口值通常接近硬件核数或下游 API 的并发上限,需要通过压测确定。阿姆达尔定律也提醒你:串行部分的耗时不会因为窗口变大而消失。

为何常见:直觉上“更多就是更快”,忽略了真实世界里调度开销和资源瓶颈的存在,这也解释了为什么并行加速比总是低于理论值。

误区有一个分支查询超时了,没关系,其他分支的结果还是好的,让它们继续跑,最后把好的结果汇总出来不就行了?

正解LangGraph 的默认行为是任一分支抛出异常都会终止整个 super-step,图转入失败状态。要容忍部分失败,你必须显式设计降级方案:在分支内捕获异常并返回“失败标记”,或者在 Reduce 阶段过滤掉失败结果。否则,一个分支的错误会像停电一样让整桌宴席停下来。

为何常见:把微服务架构里“单点故障隔离”的经验套用到图执行引擎,忽略了 super-step 的同步屏障语义——一轮内任何一个分支失败都让“齐步走”无法收场。

误区既然并行这么好,我把 Agent 里所有节点都套上 Map-Reduce,是不是整体响应时间都能大幅缩短?

正解只有子任务互不依赖时并行才安全。如果一个分支的输出是另一个分支的输入,或者多个分支需要频繁读写同一份状态,强行并行会引入竞态和一致性灾难。判断标准是“能否画成树”:树状结构适合并行,链状依赖只能串行。把可并行的部分并行,把必须串行的部分老老实实排队,才是正确姿势。

为何常见:被并行的收益诱惑,忽略了任务间的依赖结构分析。贪多求快,反而因状态冲突和重试机制把平均耗时拖得更长。

面试题

Q1. LangGraph 中 Map 阶段的核心职责是什么?

单选题 · easy

  • A.等待所有并行分支完成后汇总结果
  • B.把单个输入拆成多个子任务并派发← 正确答案
  • C.把子任务的输出整合为单个结果
  • D.为每个子任务分配专属执行线程

答案:B。Map 阶段负责“拆菜单”——把输入拆成多个子任务并派发出去,对应选项 B。A 和 C 描述的是 Reduce 阶段的职责,D 误解了 Map 的职责,LangGraph 不直接管理线程分配,线程由底层执行器控制。

Q2. LangGraph 中“super-step 同步”的含义是什么?

单选题 · easy

  • A.所有节点执行完才进入下一轮← 正确答案
  • B.单个快节点执行完就立即推进
  • C.每轮只允许一个节点执行
  • D.节点之间直接共享内存状态

答案:A。super-step 是“齐步走”——当前轮所有被唤醒的并行分支全部执行完毕,框架才推进到下一轮,对应选项 A。B 描述的是异步流水线,C 是串行执行,D 是共享内存通信,都不符合 LangGraph 的状态同步模型。

Q3. 下列哪个场景最适合使用 LangGraph 并行节点?

单选题 · easy

  • A.一个子任务的输出是另一任务的输入
  • B.多个分支共享同一个可变计数器
  • C.批量文档各自独立做摘要← 正确答案
  • D.每个分支需要上一步的中间结果

答案:C。并行节点的前提是子任务互不依赖。批量文档各自独立做摘要天然是“树”状结构,适合并行,对应 C。A 和 D 是链式依赖,B 是共享可变状态的危险设计,都不适合。

Q4. 多个并行分支同时写同一个 Channel,LangGraph 靠什么决定结果形态?

单选题 · mid

  • A.后写的分支自动覆盖先写分支
  • B.Channel 的 reducer 写入规则决定← 正确答案
  • C.所有写入按字典序自动排列
  • D.每个分支单独分配独立 Channel

答案:B。Channel 的写入行为由它的 reducer 规则决定——可以是“保留最后写入”,也可以是“追加成列表”或自定义聚合,对应 B。A 只是其中一种规则而非通用行为,C 和 D 是真实并不存在的机制。

Q5. 关于并发窗口与实际执行速度的关系,哪一项描述正确?

单选题 · mid

  • A.并发窗口越大,执行速度一定越快
  • B.并发窗口大小对执行速度没有影响
  • C.窗口过大会导致资源竞争拖慢速度← 正确答案
  • D.并发窗口只在单核机器上发挥作用

答案:C。并发窗口过大会引入线程切换开销、内存争抢和排队雪崩,反而拖慢整体速度,对应 C。A 忽略了资源瓶颈,B 明显错误,D 误解了并发窗口的作用机制。

Q6. LangGraph 中 Reduce 阶段自定义合并策略的典型做法是什么?

单选题 · mid

  • A.强制所有分支输出格式完全相同
  • B.只能使用框架内置的求和规则
  • C.编写 Reducer 函数定义合并逻辑← 正确答案
  • D.在 Map 阶段提前合并所有输出

答案:C。自定义合并策略就是编写 Reducer 函数,定义如何把多个输出合并成最终结果(如加权平均、置信度投票),对应 C。A 太死板(自定义合并允许不同格式),B 忽略了框架的扩展性,D 把 Reduce 的职责错安在 Map 头上。

Q7. 如果一个并行分支抛出异常,LangGraph 的默认行为是什么?

单选题 · hard

  • A.该分支重试三次后继续执行
  • B.只跳过失败分支并保留其他结果
  • C.整个 super-step 终止并进入失败← 正确答案
  • D.其他分支的结果自动回滚到上轮

答案:C。默认行为是任一分支异常都会终止整个 super-step,图进入失败状态,对应 C。A 重试需要显式配置 retry policy,B 部分降级需要手动设计,D 回滚不是默认行为,只有显式配置状态持久化才可能实现。

Q8. 单个分支成功率 p=0.95,并行分支数 N=10,整体至少一个分支失败的概率约为多少?

单选题 · hard

  • A.0.95
  • B.0.50
  • C.0.40← 正确答案
  • D.0.05

答案:C。用公式 1-(1-p)^N 计算:1-(0.95)^10 ≈ 1-0.598 = 0.402,约等于 0.40,对应 C。A 和 D 分别把单分支成功率误当成总体指标,B 是粗估的错误值。并行分支越多,整体失败概率越高。

Q9. 下列哪些措施可以有效提升 LangGraph 并行执行的整体稳定性?

多选题 · mid

  • A.给关键分支配置重试策略← 正确答案
  • B.为分支设置超时保护← 正确答案
  • C.任意扩大并发窗口到硬件极限
  • D.在 Reduce 阶段做部分降级处理← 正确答案

答案:ABD。重试策略能让暂时失败的分支自动补偿,超时保护防止卡死分支拖住整轮,部分降级让部分成功仍有价值,三者都是稳定性手段,对应 A、B、D。C 盲目扩大窗口反而可能因资源竞争引发更多错误。

Q10. 关于 LangGraph Map-Reduce 模式,下列说法正确的有哪些?

多选题 · hard

  • A.Map 阶段可以通过 Send 动态分批派单← 正确答案
  • B.super-step 保证同轮分支结果一致可见← 正确答案
  • C.定制 reducer 可以合并异构分支输出← 正确答案
  • D.并行加速比可以随窗口无限线性增长

答案:ABC。Send 支持动态分批派单(A 正确),super-step 的同步屏障保证结果一致性(B 正确),自定义 reducer 可以混合不同结构的数据(C 正确)。D 错误,阿姆达尔定律指出加速比存在上限,受串行部分比例约束。