<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>并行工作流 on HanGR的碎碎念</title><link>https://han-gr.github.io/tags/%E5%B9%B6%E8%A1%8C%E5%B7%A5%E4%BD%9C%E6%B5%81/</link><description>Recent content in 并行工作流 on HanGR的碎碎念</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Sun, 02 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://han-gr.github.io/tags/%E5%B9%B6%E8%A1%8C%E5%B7%A5%E4%BD%9C%E6%B5%81/index.xml" rel="self" type="application/rss+xml"/><item><title>多个 Agent 怎样同时工作：理解 LangGraph 的分支与汇合</title><link>https://han-gr.github.io/p/2026-08-02-voyager-ai-05-%E5%A4%9A%E4%B8%AA-agent-%E6%80%8E%E6%A0%B7%E5%90%8C%E6%97%B6%E5%B7%A5%E4%BD%9C%E7%90%86%E8%A7%A3-langgraph-%E7%9A%84%E5%88%86%E6%94%AF%E4%B8%8E%E6%B1%87%E5%90%88/</link><pubDate>Sun, 02 Aug 2026 00:00:00 +0000</pubDate><guid>https://han-gr.github.io/p/2026-08-02-voyager-ai-05-%E5%A4%9A%E4%B8%AA-agent-%E6%80%8E%E6%A0%B7%E5%90%8C%E6%97%B6%E5%B7%A5%E4%BD%9C%E7%90%86%E8%A7%A3-langgraph-%E7%9A%84%E5%88%86%E6%94%AF%E4%B8%8E%E6%B1%87%E5%90%88/</guid><description>&lt;img src="https://han-gr.github.io/p/2026-08-02-voyager-ai-05-%E5%A4%9A%E4%B8%AA-agent-%E6%80%8E%E6%A0%B7%E5%90%8C%E6%97%B6%E5%B7%A5%E4%BD%9C%E7%90%86%E8%A7%A3-langgraph-%E7%9A%84%E5%88%86%E6%94%AF%E4%B8%8E%E6%B1%87%E5%90%88/graph.jpg" alt="Featured image of post 多个 Agent 怎样同时工作：理解 LangGraph 的分支与汇合" /&gt;&lt;h2 id="顺序执行并不总是必要"&gt;顺序执行并不总是必要&#10;&lt;/h2&gt;&lt;p&gt;Destination、Food 和 Budget 都读取同一份用户请求。预算计算不需要等待餐饮建议，FoodAgent 也不依赖 DestinationAgent 的介绍。如果仍然一个接一个运行，只会增加总等待时间。&lt;/p&gt;&#10;&lt;p&gt;普通 Python 可以用并发任务处理，但还要自己收集结果、处理异常并合并字典。LangGraph 允许直接用多条边表示这种关系。&lt;/p&gt;&#10;&lt;h2 id="从一个节点分出三条边"&gt;从一个节点分出三条边&#10;&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_edge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;planner&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;destination&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_edge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;planner&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;food&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_edge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;planner&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;budget&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;这种“从一个节点分到多个节点”的结构常被称为 fan-out，也就是扇出。&lt;/p&gt;&#10;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR&#10; P["Planner"] --&gt; D["Destination"]&#10; P --&gt; F["Food"]&#10; P --&gt; B["Budget"]&lt;/pre&gt;&lt;p&gt;Planner 完成后，三个节点都具备运行条件。它们读取 Planner 更新后的状态，各自返回不同字段。&lt;/p&gt;&#10;&lt;p&gt;这里说“可以并行”，重点是节点没有数据依赖。实际运行是否占用多个线程或进程，还取决于节点是同步还是异步、模型客户端如何实现。图描述的是依赖关系，不是对底层并发方式的承诺。&lt;/p&gt;&#10;&lt;h2 id="三份结果怎样汇合"&gt;三份结果怎样汇合&#10;&lt;/h2&gt;&lt;p&gt;Reviewer 必须等目的地、餐饮和预算都完成。LangGraph 用列表形式声明汇合边：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;span class="lnt"&gt;4&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_edge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;destination&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;food&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;budget&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;reviewer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR&#10; D["Destination&lt;br/&gt;destination_info"] --&gt; R["Reviewer"]&#10; F["Food&lt;br/&gt;foods"] --&gt; R&#10; B["Budget&lt;br/&gt;budget"] --&gt; R&lt;/pre&gt;&lt;p&gt;这不是“任何一个完成就进入 Reviewer”，而是一个 barrier（屏障）：三条分支都到达后，Reviewer 才能继续。&lt;/p&gt;&#10;&lt;h2 id="并行写状态为什么没有互相覆盖"&gt;并行写状态为什么没有互相覆盖&#10;&lt;/h2&gt;&lt;p&gt;三个 Agent 返回不同字段：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;span class="lnt"&gt;4&#10;&lt;/span&gt;&lt;span class="lnt"&gt;5&#10;&lt;/span&gt;&lt;span class="lnt"&gt;6&#10;&lt;/span&gt;&lt;span class="lnt"&gt;7&#10;&lt;/span&gt;&lt;span class="lnt"&gt;8&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# DestinationAgent&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;destination_info&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# FoodAgent&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;foods&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# BudgetAgent&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;budget&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;因此合并没有冲突。如果两个并行节点都写 &lt;code&gt;foods&lt;/code&gt;，就必须明确使用哪个结果，或者为该字段配置 reducer（归并规则）。Reducer 可以理解成“多个更新同时到来时，应该怎样组合”。&lt;/p&gt;&#10;&lt;p&gt;Voyager-AI 当前不需要复杂 reducer，因为并行节点有各自的写入范围。这也是拆 Agent 时明确输出边界的重要性。&lt;/p&gt;&#10;&lt;h2 id="测试怎样证明汇合有效"&gt;测试怎样证明汇合有效&#10;&lt;/h2&gt;&lt;p&gt;工作流测试会替换节点，记录实际调用情况。Reviewer 运行时检查三份专家结果是否都已经出现在状态中，而不是只看最终计划有没有生成。&lt;/p&gt;&#10;&lt;p&gt;图结构测试还会读取编译图，确认 Planner 有三条出边，并且三个专家共同连接 Reviewer。这样修改某条边时，流程契约会立即失败。&lt;/p&gt;&#10;&lt;h2 id="总结"&gt;总结&#10;&lt;/h2&gt;&lt;p&gt;多个 Agent 能不能同时工作，不取决于角色名称，而取决于数据依赖。只读取共同输入、写入不同字段的任务，适合从同一节点分支；需要完整结果的审核步骤，则应该放在汇合点之后。&lt;/p&gt;&#10;&lt;p&gt;LangGraph 的分支和汇合把这种依赖直接画进工作流。它减少的不只是运行时间，也避免把并发控制、结果收集和下一步调用散落在各个 Agent 中。&lt;/p&gt;&#10;</description></item></channel></rss>