导语:至此,我们已经掌握了构建单个复杂 Agent 的核心技术。但真正的“智能”往往是群体的涌现。无论是多个 AI Agent 之间的分工协作,还是在关键节点引入人类智慧进行决策,都是构建更强大、更可靠系统的必由之路。在本章中,我们将探索 LangGraph 最令人兴奋的两大高级特性:人机协同(Human-in-the-loop)与多智能体并行协作。你将学会如何让你的 Agent 在需要时“暂停”,等待人类的批准;以及如何编排一个由“研究员”、“程序员”和“质检员”组成的 AI 团队,让它们并行工作,协同解决复杂问题。
目录
- 当 Agent 遇到“选择困难症”:人机协同的价值
- 为什么 100% 的自动化并不总是最优解?
- 场景分析:危险操作确认、模糊指令澄清、效果反馈收集
- LangGraph 的中断与恢复机制:
interrupt_before和interrupt_after
- 代码实战:实现一个“危险操作需人类批准”的 Agent
- 目标:Agent 在调用一个“危险”工具(如
delete_file)前,必须暂停并等待用户确认。 - 第一步:在图中定义一个专门用于“暂停”的节点。
- 第二步:利用条件路由,将“危险操作”的意图导向“暂停”节点。
- 第三步:使用
app.update_state()来注入人类的反馈(批准或拒绝)。 - 第四步:Agent 从暂停中“醒来”,并根据人类的反馈继续执行。
- 目标:Agent 在调用一个“危险”工具(如
- 三个臭皮匠,顶个诸葛亮:多智能体协作模式
- 顺序协作(Sequential Collaboration):任务在一个 Agent 链条上依次传递。
- 并行协作(Parallel Collaboration):将任务分解,交给多个 Agent 同时处理,最后汇总结果。
- 层级协作(Hierarchical Collaboration):一个“主管” Agent 负责任务分解和调度,多个“下属” Agent 负责具体执行。
- 代码实战:构建一个并行的“研究-编码”协作团队
- 团队角色定义:
research_agent: 负责根据主题搜索网络,生成研究报告。code_gen_agent: 负责根据相同主题编写一个示例 Python 脚本。
- 图的设计:
- 使用 LangGraph 的
Concurrent执行能力,让两个 Agent 节点并行运行。 - 设计一个
aggregator_node(聚合节点),等待两个并行分支都完成后,负责汇总它们的结果。
- 使用 LangGraph 的
- 代码实现:
- 创建两个独立的 Agent
Runnable。 - 在图中定义并行分支,并设置聚合节点。
- 运行图,观察两个 Agent 如何同时开始工作。
- 创建两个独立的 Agent
- 团队角色定义:
- LangGraph for Swarms: 动态调整的 Agent 群体
- Agent Swarm (群体智能) 概念简介
- LangGraph 如何通过修改状态来动态地增加或移除工作流中的 Agent
- 一个简单的思路:主管 Agent 可以根据任务进展,决定“雇佣”一个新的“测试”Agent 加入到工作流中。
- 总结:从“独行侠”到“交响乐团指挥”
1. 当 Agent 遇到“选择困难症”:人机协同的价值
我们总希望 AI 能实现 100% 的自动化,但在许多现实场景中,这既不现实,也不安全。
- 危险操作:执行一个
rm -rf /或者向全公司发送一封邮件的指令,你敢让 AI 在没有任何监督的情况下执行吗? - 模糊指令:用户输入“帮我分析一下市场”,AI 应该分析哪个市场?是股市、房地产还是某个特定产品的市场?在盲目执行昂贵的分析任务前,寻求人类的澄清是更明智的选择。
- 质量控制:AI 生成的研究报告或代码,在正式交付前,可能需要一个人类专家来审核其质量和准确性。
在这些“关键决策点”,引入人类智慧(Human-in-the-loop),让 AI 与人协同工作,是构建可靠、可信系统的最佳实践。
LangGraph 的中断与恢复机制
LangGraph 为此提供了内置的支持。通过在 app.compile() 中指定 interrupt_before 或 interrupt_after 参数,我们可以告诉图在执行到某个特定节点之前或之后“暂停”。
当图暂停时,它的所有状态都会被 Checkpointer 保存。此时,人类用户可以审查当前的状态,然后决定下一步该怎么做。用户做出决策后,可以通过 app.update_state() 方法将新的信息(如批准或拒绝)注入到状态中,然后让图从暂停的地方继续运行。
2. 代码实战:实现一个“危险操作需人类批准”的 Agent
让我们来实现这个最典型的人机协同场景。
目标:创建一个 Agent,它有一个普通工具 search 和一个危险工具 delete_file。当它计划调用 delete_file 时,流程必须暂停,等待用户输入 "yes" 确认。
第一步:定义工具和 Agent
# human_in_the_loop.py
from langchain_core.tools import tool
@tool
def search(query: str):
"""Searches the web."""
print(f"Searching for: {query}")
return "The search results are..."
@tool
def delete_file(filename: str):
"""
Deletes a file. THIS IS A DANGEROUS OPERATION.
Requires user confirmation before execution.
"""
print(f"DANGEROUS: Deleting file {filename}")
return f"File '{filename}' deleted successfully."
tools = [search, delete_file]
# ... (此处省略创建 llm_with_tools, AgentState, agent_node, tool_executor_node 的代码, 与 2.2 节类似)
我们在 delete_file 工具的描述中明确地标注了其危险性,以引导 LLM 谨慎调用。
第二步:设计图,增加“暂停”节点和新路由
我们的路由逻辑需要改变。不再是简单的“continue/end”,而是需要第三条路径:“request_confirmation”。
# human_in_the_loop.py (续)
from langgraph.graph import StateGraph, END
import json
# 1. 定义新的路由函数
def router_function(state: AgentState):
last_message = state['messages'][-1]
if not last_message.tool_calls:
return "end"
# 检查是否有危险操作
for tool_call in last_message.tool_calls:
if tool_call['name'] == 'delete_file':
print("--- Dangerous tool call detected. Requesting confirmation. ---")
return "request_confirmation"
return "call_tools"
# 2. 定义一个专门用于暂停的节点
def request_confirmation_node(state: AgentState):
print("--- Pausing for user confirmation ---")
# 这个节点什么都不做,它的唯一目的就是触发图的中断
return {}
# 3. 组装图
workflow = StateGraph(AgentState)
workflow.add_node("agent", agent_node)
workflow.add_node("tool_executor", tool_executor_node)
workflow.add_node("confirmation_prompt", request_confirmation_node) # 新增暂停节点
workflow.set_entry_point("agent")
workflow.add_conditional_edges(
"agent",
router_function,
{
"request_confirmation": "confirmation_prompt", # 新增路径
"call_tools": "tool_executor",
"end": END
}
)
# 从暂停节点,需要一条路径回到工具执行节点
workflow.add_edge("confirmation_prompt", "tool_executor")
workflow.add_edge("tool_executor", "agent")
# 4. 编译图,并设置中断点
# 我们希望在 'confirmation_prompt' 节点执行完毕后暂停
app = workflow.compile(interrupt_after=["confirmation_prompt"])
第三步:运行并与 Agent 交互
# human_in_the_loop.py (续)
from langchain_core.messages import HumanMessage
thread_id = "human-in-loop-thread-1"
config = {"configurable": {"thread_id": thread_id}}
# 第一次调用,触发危险操作
inputs = {"messages": [HumanMessage(content="Please delete the file 'important.txt'")]}
# stream() 会在中断点暂停
for output in app.stream(inputs, config):
for key, value in output.items():
print(f"Output from node '{key}': {value}")
# 此时,程序已经暂停。你可以检查当前的状态
current_state = app.get_state(config)
last_message = current_state.values['messages'][-1]
print("\n--- Agent paused. Waiting for confirmation. ---")
print(f"Tool calls to be executed: {last_message.tool_calls}")
# 第四步:注入人类反馈
user_confirmation = input("Do you approve? (yes/no): ")
if user_confirmation.lower() == "yes":
print("--- User approved. Resuming... ---")
# 注入一个空消息,触发图从暂停处继续运行
# 因为 tool_calls 已经存在于状态中,ToolNode 会直接执行它们
resume_input = None
for output in app.stream(resume_input, config):
for key, value in output.items():
print(f"Output from node '{key}': {value}")
else:
print("--- User denied. Aborting. ---")
# 如果用户拒绝,我们可以选择什么都不做,或者注入一个消息让 Agent 知道操作被取消
当你运行这段代码,你会看到:
- Agent 节点生成了调用
delete_file的tool_calls。 router_function将流程导向confirmation_prompt节点。- 图在
confirmation_prompt节点后暂停,并打印出等待确认的信息。 - 程序等待你的
input()。 - 如果你输入
yes,第二次调用app.stream()会从confirmation_prompt节点之后继续,也就是走向tool_executor节点,最终执行危险操作。 - 如果你输入
no,程序结束,危险操作不会被执行。
我们成功地在自动化的 Agent 工作流中,开辟了一个由人类掌控的“决策关口”。
3. 三个臭皮匠,顶个诸葛亮:多智能体协作模式
当任务变得庞大时,让一个 Agent 包揽所有工作会使其不堪重负,并且效果不佳。更优的模式是“分而治之”,让多个具有不同专长的 Agent 协同工作。
顺序协作(Sequential Collaboration)
这是最简单的协作模式。就像工厂里的流水线,任务从一个 Agent 传递给下一个 Agent。
- 例子:一个“需求分析” Agent 负责与用户沟通,明确需求后,将结构化的需求文档传递给一个“软件设计” Agent。
- LangGraph 实现:用
add_edge将多个 Agent 节点线性地连接起来。agent1 -> agent2 -> agent3。
并行协作(Parallel Collaboration)
当一个任务可以被分解为多个互不依赖的子任务时,并行处理能极大地提升效率。
- 例子:为了撰写一份“特斯拉市场分析报告”,一个“主管” Agent 将任务分解为:“搜索特斯拉最近的财报”、“搜索竞争对手(比亚迪、蔚来)的动态”、“搜索社交媒体上对特斯拉的评价”。然后将这三个子任务同时分发给三个并行的“研究员” Agent。
- LangGraph 实现:LangGraph 的
node本身支持传入一个list的Runnable,它会自动将这些Runnable并发执行。
层级协作(Hierarchical Collaboration)
这是一种更高级的模式,结合了顺序和并行。一个“主管” Agent 负责顶层规划、任务分解和结果汇总,多个“下属” Agent 负责执行具体的子任务。
- 例子:上面提到的“特斯拉市场分析报告”,就是一个典型的层级协作。
- LangGraph 实现:通常表现为一个星形的图结构,一个
supervisor节点作为中心,连接着多个并行的worker节点,并有一个最终的aggregator节点来汇总结果。
4. 代码实战:构建一个并行的“研究-编码”协作团队
让我们来实现一个简单的并行协作。
团队角色定义
research_agent: 接收一个主题,进行网络搜索,并输出一段研究文本。code_gen_agent: 接收相同的主题,编写一段相关的 Python 示例代码。aggregator_node: 等待上述两个 Agent 都完成后,将它们的结果汇总成一份完整的报告。
图的设计与代码实现
# parallel_agents.py
import operator
from typing import TypedDict, Annotated, List
from langchain_core.messages import BaseMessage
from langgraph.graph import StateGraph, END
# --- 1. 定义状态 ---
# 我们需要一个能聚合多个 Agent 输出的状态
class TeamState(TypedDict):
messages: Annotated[List[BaseMessage], operator.add]
team_outputs: Annotated[dict, operator.ior_] # 使用 ior_ 来合并字典
# --- 2. 定义并行的专家 Agent (简化版) ---
# 假设 research_runnable 和 code_gen_runnable 是已经创建好的 Runnable
# research_runnable = research_prompt | llm
# code_gen_runnable = code_gen_prompt | llm
# 为了演示,我们用假函数模拟
def research_runnable(topic: str) -> str:
print(f"Researching topic: {topic}")
return f"Research report on {topic}: AI is advancing rapidly."
def code_gen_runnable(topic: str) -> str:
print(f"Generating code for topic: {topic}")
return f"# Python code for {topic}\nprint('Hello, AI!')"
# --- 3. 定义图的节点 ---
def research_node(state: TeamState):
topic = state['messages'][-1].content
output = research_runnable(topic)
return {"team_outputs": {"researcher": output}}
def code_gen_node(state: TeamState):
topic = state['messages'][-1].content
output = code_gen_runnable(topic)
return {"team_outputs": {"coder": output}}
def aggregator_node(state: TeamState):
final_report = "## Final Report\n\n"
final_report += "### Research Findings\n\n"
final_report += state['team_outputs']['researcher']
final_report += "\n\n### Code Example\n\n```python\n"
final_report += state['team_outputs']['coder']
final_report += "\n```"
return {"messages": [HumanMessage(content=final_report, name="Aggregator")]}
# --- 4. 构建图 ---
workflow = StateGraph(TeamState)
# LangGraph 中,可以通过将多个节点名放在一个 list 里来表示并行
# 但更标准的做法是为并行任务创建一个统一的入口
workflow.add_node("parallel_entry", lambda x: {})
workflow.add_node("researcher", research_node)
workflow.add_node("coder", code_gen_node)
workflow.add_node("aggregator", aggregator_node)
workflow.set_entry_point("parallel_entry")
# 从入口,同时走向两个并行的专家节点
workflow.add_edge("parallel_entry", "researcher")
workflow.add_edge("parallel_entry", "coder")
# 只有当两个专家都完成后,才走向聚合节点
# LangGraph 会自动处理这种情况:当一个节点有多个输入边时,它会等待所有输入都准备好
workflow.add_edge("researcher", "aggregator")
workflow.add_edge("coder", "aggregator")
workflow.add_edge("aggregator", END)
app = workflow.compile()
# --- 5. 运行图 ---
inputs = {"messages": [HumanMessage(content="The future of AI")]}
for output in app.stream(inputs):
for key, value in output.items():
print(f"Output from node '{key}':\n{value}\n---")
(注意:LangGraph 的并行执行在底层依赖于 asyncio,上面的同步代码是为了演示拓扑结构。真正的并行需要将节点函数和 Runnable 都改为 async 版本。)
当你运行(或设想运行)这个图时:
- 流程从
parallel_entry开始,然后同时分叉到researcher和coder两个节点。 research_node和code_gen_node会并行执行。aggregator节点因为有两条输入边,它会等待,直到researcher和coder都执行完毕。- 一旦两个输入都到达,
aggregator节点开始执行,汇总结果。 - 流程结束。
5. LangGraph for Swarms: 动态调整的 Agent 群体
LangGraph 的灵活性甚至允许我们构建动态的 Agent 群体(Agent Swarms)。
State 对象不仅可以包含数据,还可以包含配置信息,甚至是 Runnable 对象本身。这意味着,一个主管 Agent 可以在运行时,通过修改 State,动态地改变下游的工作流。
一个简单的思路:
- 初始
State中包含一个workers列表,里面有research_agent。 supervisorAgent 运行后,发现任务需要编码,它返回一个对State的更新:{"workers": [research_agent, code_gen_agent]}。- 下游的并行处理逻辑读取
State中的workers列表,并动态地将任务分发给所有当前的worker。
通过这种方式,Agent 群体的结构不再是静态预定义的,而是可以根据任务的演化,自适应地进行调整、扩张和收缩。这为实现更高级的“群体智能”提供了可能。
6. 总结:从“独行侠”到“交响乐团指挥”
本章我们解锁了 LangGraph 的两大“超能力”。
- 人机协同:通过中断和恢复,我们为自动化的工作流安装了“安全阀”和“校准器”,让 AI 的力量被置于人类的监督之下,变得更加可靠和可信。
- 并行协作:通过图的并行分支,我们学会了如何将一个庞大的任务分解,让多个专家 Agent 协同作战,极大地提升了解决问题的效率和广度。
至此,你作为 Agent 开发者的角色,已经发生了根本性的转变。你不再是一个只关心单个 Agent 能力的“独行侠”,而是开始成为一个懂得如何组织、调度和协调多个智能单元(无论是 AI 还是人)的“交响乐团指挥”。你手中的 LangGraph,就是你的指挥棒,指引着由代码和智能组成的乐团,奏响解决复杂问题的华美乐章。