2.4 人机协同与并行协作:构建可控、可扩展的智能体系统

1 阅读1分钟

导语:至此,我们已经掌握了构建单个复杂 Agent 的核心技术。但真正的“智能”往往是群体的涌现。无论是多个 AI Agent 之间的分工协作,还是在关键节点引入人类智慧进行决策,都是构建更强大、更可靠系统的必由之路。在本章中,我们将探索 LangGraph 最令人兴奋的两大高级特性:人机协同(Human-in-the-loop)多智能体并行协作。你将学会如何让你的 Agent 在需要时“暂停”,等待人类的批准;以及如何编排一个由“研究员”、“程序员”和“质检员”组成的 AI 团队,让它们并行工作,协同解决复杂问题。

目录

  1. 当 Agent 遇到“选择困难症”:人机协同的价值
    • 为什么 100% 的自动化并不总是最优解?
    • 场景分析:危险操作确认、模糊指令澄清、效果反馈收集
    • LangGraph 的中断与恢复机制:interrupt_beforeinterrupt_after
  2. 代码实战:实现一个“危险操作需人类批准”的 Agent
    • 目标:Agent 在调用一个“危险”工具(如 delete_file)前,必须暂停并等待用户确认。
    • 第一步:在图中定义一个专门用于“暂停”的节点。
    • 第二步:利用条件路由,将“危险操作”的意图导向“暂停”节点。
    • 第三步:使用 app.update_state() 来注入人类的反馈(批准或拒绝)。
    • 第四步:Agent 从暂停中“醒来”,并根据人类的反馈继续执行。
  3. 三个臭皮匠,顶个诸葛亮:多智能体协作模式
    • 顺序协作(Sequential Collaboration):任务在一个 Agent 链条上依次传递。
    • 并行协作(Parallel Collaboration):将任务分解,交给多个 Agent 同时处理,最后汇总结果。
    • 层级协作(Hierarchical Collaboration):一个“主管” Agent 负责任务分解和调度,多个“下属” Agent 负责具体执行。
  4. 代码实战:构建一个并行的“研究-编码”协作团队
    • 团队角色定义
      • research_agent: 负责根据主题搜索网络,生成研究报告。
      • code_gen_agent: 负责根据相同主题编写一个示例 Python 脚本。
    • 图的设计
      • 使用 LangGraph 的 Concurrent 执行能力,让两个 Agent 节点并行运行。
      • 设计一个 aggregator_node (聚合节点),等待两个并行分支都完成后,负责汇总它们的结果。
    • 代码实现
      • 创建两个独立的 Agent Runnable
      • 在图中定义并行分支,并设置聚合节点。
      • 运行图,观察两个 Agent 如何同时开始工作。
  5. LangGraph for Swarms: 动态调整的 Agent 群体
    • Agent Swarm (群体智能) 概念简介
    • LangGraph 如何通过修改状态来动态地增加或移除工作流中的 Agent
    • 一个简单的思路:主管 Agent 可以根据任务进展,决定“雇佣”一个新的“测试”Agent 加入到工作流中。
  6. 总结:从“独行侠”到“交响乐团指挥”

1. 当 Agent 遇到“选择困难症”:人机协同的价值

我们总希望 AI 能实现 100% 的自动化,但在许多现实场景中,这既不现实,也不安全。

  • 危险操作:执行一个 rm -rf / 或者向全公司发送一封邮件的指令,你敢让 AI 在没有任何监督的情况下执行吗?
  • 模糊指令:用户输入“帮我分析一下市场”,AI 应该分析哪个市场?是股市、房地产还是某个特定产品的市场?在盲目执行昂贵的分析任务前,寻求人类的澄清是更明智的选择。
  • 质量控制:AI 生成的研究报告或代码,在正式交付前,可能需要一个人类专家来审核其质量和准确性。

在这些“关键决策点”,引入人类智慧(Human-in-the-loop),让 AI 与人协同工作,是构建可靠、可信系统的最佳实践。

LangGraph 的中断与恢复机制

LangGraph 为此提供了内置的支持。通过在 app.compile() 中指定 interrupt_beforeinterrupt_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 知道操作被取消

当你运行这段代码,你会看到:

  1. Agent 节点生成了调用 delete_filetool_calls
  2. router_function 将流程导向 confirmation_prompt 节点。
  3. 图在 confirmation_prompt 节点后暂停,并打印出等待确认的信息。
  4. 程序等待你的 input()
  5. 如果你输入 yes,第二次调用 app.stream() 会从 confirmation_prompt 节点之后继续,也就是走向 tool_executor 节点,最终执行危险操作。
  6. 如果你输入 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 本身支持传入一个 listRunnable,它会自动将这些 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 版本。)

当你运行(或设想运行)这个图时:

  1. 流程从 parallel_entry 开始,然后同时分叉到 researchercoder 两个节点。
  2. research_nodecode_gen_node 会并行执行。
  3. aggregator 节点因为有两条输入边,它会等待,直到 researchercoder 执行完毕。
  4. 一旦两个输入都到达,aggregator 节点开始执行,汇总结果。
  5. 流程结束。

5. LangGraph for Swarms: 动态调整的 Agent 群体

LangGraph 的灵活性甚至允许我们构建动态的 Agent 群体(Agent Swarms)

State 对象不仅可以包含数据,还可以包含配置信息,甚至是 Runnable 对象本身。这意味着,一个主管 Agent 可以在运行时,通过修改 State,动态地改变下游的工作流。

一个简单的思路

  1. 初始 State 中包含一个 workers 列表,里面有 research_agent
  2. supervisor Agent 运行后,发现任务需要编码,它返回一个对 State 的更新:{"workers": [research_agent, code_gen_agent]}
  3. 下游的并行处理逻辑读取 State 中的 workers 列表,并动态地将任务分发给所有当前的 worker

通过这种方式,Agent 群体的结构不再是静态预定义的,而是可以根据任务的演化,自适应地进行调整、扩张和收缩。这为实现更高级的“群体智能”提供了可能。

6. 总结:从“独行侠”到“交响乐团指挥”

本章我们解锁了 LangGraph 的两大“超能力”。

  • 人机协同:通过中断和恢复,我们为自动化的工作流安装了“安全阀”和“校准器”,让 AI 的力量被置于人类的监督之下,变得更加可靠和可信。
  • 并行协作:通过图的并行分支,我们学会了如何将一个庞大的任务分解,让多个专家 Agent 协同作战,极大地提升了解决问题的效率和广度。

至此,你作为 Agent 开发者的角色,已经发生了根本性的转变。你不再是一个只关心单个 Agent 能力的“独行侠”,而是开始成为一个懂得如何组织、调度和协调多个智能单元(无论是 AI 还是人)的“交响乐团指挥”。你手中的 LangGraph,就是你的指挥棒,指引着由代码和智能组成的乐团,奏响解决复杂问题的华美乐章。