使用 Langchain 和 LangGraph 进行 Agent-to-Agent (A2A) 开发全攻略
引言:拥抱 Agent-to-Agent 的协作智能
随着大型语言模型(LLM)能力的飞速发展,构建能够执行复杂任务的智能化系统已成为可能。单个 LLM Agent 擅长处理特定领域的任务,但在面对跨领域、需要多步骤协作或涉及不同专业知识的问题时,单一 Agent 的局限性便显现出来。Agent-to-Agent (A2A) 系统应运而生,它通过多个 Agent 之间的协同、沟通与合作,能够模拟人类团队协作的方式,分解复杂任务、整合多方信息、甚至进行谈判与决策,从而解决单个 Agent 无法完成的难题。
Langchain 作为 LLM 应用开发领域的流行框架,为构建 Agent 提供了强大的基础能力,包括与各种 LLM、工具和数据源的集成。而 LangGraph,作为 Langchain 的一个重要扩展,专注于有状态的、长期运行的 Agent 工作流编排,天然适合构建和管理复杂的多 Agent 协作系统。
本教程旨在为开发者提供一份详尽的 A2A 开发指南,重点讲解如何利用 Langchain 构建基础 Agent,并借助 LangGraph 实现 Agent 之间的通信、状态管理与协作流程编排,最终构建出强大的多 Agent 协作系统。我们将深入剖析 LangGraph 的核心概念,提供丰富的代码示例,覆盖从环境搭建到复杂 A2A 架构模式的实现,帮助您掌握 A2A 开发的关键技术,并将所学应用于实际项目。
"多 Agent 系统通过分解复杂问题、整合多元专业知识,能够解决单个 Agent 力所不能及的任务,是通向更高级人工智能应用的关键一步。"
第一章:A2A 系统与 Langchain/LangGraph 的基础
1.1 什么是 Agent?多 Agent 系统?A2A 通信?
Agent (智能体): 在 LLM 应用开发领域,Agent 是指能够利用 LLM 作为其推理核心,并根据当前状态和目标,动态决定并执行一系列动作(调用工具、与环境交互等)以达成目标的系统。与传统的硬编码执行流程不同,Agent 的行为更具灵活性和自主性,能够根据上下文进行规划、学习和适应。 Langchain Agent 的核心在于使用 LLM 作为决策引擎,通过提示工程和工具调用实现动态行为。
例如,一个“研究 Agent”可能接收一个主题,然后自己决定什么时候去搜索网页、什么时候去阅读文档、什么时候去提取关键信息,这些动作不是预先设定好的固定流程,而是由其内置的 LLM 根据情况判断并执行的。
多 Agent 系统 (Multi-Agent System, MAS): 多 Agent 系统是由两个或多个 Agent 组成的集合,这些 Agent 能够相互影响、协作或竞争,共同完成一个或一系列任务。每个 Agent 可能拥有特定的能力、知识或目标,通过分工与协作,整个系统可以展现出比单个 Agent 更高级和复杂的能力。
多 Agent 系统的优势包括:
模块化:将复杂任务分解给多个 Agent,降低开发和维护复杂度。
专业化:每个 Agent 可以专注于特定领域或任务,提高效率和性能。
鲁棒性:某个 Agent 失败时,其他 Agent 可能可以接管或弥补。
分布式:Agent 可以部署在不同的环境中,实现分布式计算和协作。
一个简单的例子:在一个客服系统中,可能有一个“意图识别 Agent”负责理解用户问题,一个“常见问题解答 Agent”处理标准问题,一个“技术支持 Agent”处理复杂技术问题,以及一个“财务 Agent”处理支付问题。这些 Agent 协同工作,共同为用户提供支持。
Agent-to-Agent (A2A) 通信: 在多 Agent 系统中,A2A 通信是指 Agent 之间进行信息交换、状态同步、任务分配和协调行为的过程。有效的 A2A 通信是实现 Agent 协同工作的关键。 Agent 之间可能通过共享内存、消息队列、API 调用或者更高级的 Agent 协议进行通信。
A2A 通信模式多样,例如:
消息传递: Agent 发送和接收消息,消息可能包含数据、指令或状态更新。
状态共享: Agent 访问和修改共享的全局或部分状态,实现信息协同。
任务委托:一个 Agent 将子任务分配给另一个 Agent 执行。
协商与拍卖: Agent 之间通过协商或拍卖机制分配资源或任务。
在 LangGraph 中,A2A 通信主要通过状态共享(修改共享状态)和Handoffs(控制权移交)来实现。
1.2 Langchain 在 Agent 开发中的角色与优势
Langchain 为构建 LLM 驱动的 Agent 提供了标准化的接口和丰富的组件:
模型集成:轻松接入各种 LLM 提供商(OpenAI, Google, Anthropic, 本地模型等)。
工具抽象:将外部服务(搜索引擎、数据库、API 等)封装为 Agent 可调用的工具。提供了大量的预置工具,并支持自定义工具。
提示工程:提供了灵活的提示模板,方便构建 Agent 的系统指令、少样本示例和 ReAct 推理格式。
AgentExecutor:用于运行 Agent,管理 LLM 和工具之间的交互循环,处理错误和日志。
Memory/State Management:提供多种方法管理 Agent 的会话历史和长期记忆,为 A2A 系统提供状态管理基础。
通过 Langchain,开发者可以快速构建一个能够感知、推理、行动的基础 Agent,为后续的多 Agent 协作奠定基础。
1.3 LangGraph 在 A2A 开发中的角色与优势
LangGraph 是在 Langchain 之上构建的库,专门用于构建有状态的、基于图形的 Agent 和多 Agent 系统。它解决了单个 Agent 或简单链式结构在处理复杂、多步、有状态任务时的不足。
LangGraph 的核心优势在于:
基于图的编排:使用图(Graph)结构清晰地定义 Agent(节点)和它们之间的交互流程(边)。这使得复杂工作流的可视化和管理变得容易。
状态管理:内置强大的状态管理能力,Agent 之间通过共享状态进行通信和协作。状态可以持久化,支持长期运行和中断恢复。
灵活的控制流:支持条件分支、循环、子图等复杂的控制流逻辑,能够根据不同的事件或状态变化动态地路由任务给不同的 Agent。
持久执行与人机协作 (Human-in-the-Loop):支持复杂Agent的持久执行,即使系统中断也能从中断点恢复。可以方便地在图的任意节点插入人类干预点。
Agent 定义与集成:LangGraph 可以轻松集成 Langchain Agent 作为图中的节点,或者直接定义节点函数实现 Agent 的部分逻辑。
调试与可视化:通过与 LangSmith 的集成,可以方便地追踪 Agent 系统的执行流程、状态变化和 Agent 间的通信,极大地简化了调试。
对于 A2A 开发,LangGraph 提供了理想的框架来:
定义不同的 Agent 节点,每个节点负责特定的任务或领域。
设计 Agent 之间的通信通道,通过共享状态传递信息。
编排 Agent 的协作流程,决定任务如何从一个 Agent 传递到另一个 Agent。
管理整个 Agent 系统的状态,确保信息在 Agent 之间的一致性和可追溯性。
可以说,Langchain 提供了构建 Agent 的“砖瓦”,而 LangGraph 提供了将这些“砖瓦”搭建成复杂多层建筑的“蓝图”和“骨架”,特别适合构建需要 Agent 之间紧密协作和状态共享的 A2A 系统。
第二章:开发环境搭建
2.1 安装 Python 和必要的库
首先,确保您安装了 Python 3.8 或更高版本。推荐使用虚拟环境来管理项目依赖。
创建一个虚拟环境(例如使用 venv):
激活虚拟环境:
在 macOS 和 Linux 上:
在 Windows 上:
激活虚拟环境后,安装 Langchain、LangGraph 以及一些常用的依赖库:
2.2 配置 LLM 访问权限
Langchain 和 LangGraph 需要使用 LLM。您需要根据选择的 LLM 提供商进行相应的配置。以 OpenAI 为例,您需要设置 OPENAI_API_KEY 环境变量:
如果您使用 Google 的 Gemini 模型(通过 Vertex AI),需要配置 Google Cloud 项目并进行身份认证。具体步骤参考 Google Cloud Vertex AI 文档²。
对于其他 LLM 提供商(如 Anthropic、Mistral 等),请查阅相应的 Langchain 文档了解如何设置 API 密钥或连接本地模型。
2.3 (可选)配置 LangSmith 进行调试和追踪
LangSmith 是 Langchain 提供的开发者平台,用于调试、测试和监控 LLM 应用程序。它与 LangGraph 深度集成,可以可视化 Agent 系统的执行图和状态变化,极大地提高了 A2A 系统的调试效率。强烈推荐配置 LangSmith。
注册 LangSmith 账号后,获取您的 API 密钥并设置环境变量:
设置这些环境变量后,Langchain 和 LangGraph 的运行痕迹将自动发送到 LangSmith 平台,您可以在 LangSmith UI 中查看详细的执行流程和状态变化。
2.4 验证安装
安装完成后,可以编写一个简单的 Python 脚本来验证安装:
运行此脚本,如果能看到 LLM 的回复并且没有错误信息,说明环境已成功搭建。
第三章:LangGraph 核心概念深入剖析
LangGraph 的核心是利用有向无环图 (DAG) 或有向图 (DG) 来定义 Agent 工作流。图中的每个 Agent 或处理步骤表示为一个节点 (Node),节点之间的连接表示为边 (Edge)。整个图有一个状态 (State),在节点之间共享并更新。
3.1 图 (Graph) 的基本构成:节点 (Nodes) 和边 (Edges)
节点 (Node): 在 LangGraph 中,节点代表图中的一个处理步骤或 Agent。一个节点可以是一个函数、一个 Langchain Runnable(如 LLM 调用、工具调用、另一个链或 AgentExecutor)或一个 LangGraph 子图。当图的执行流进入一个节点时,节点的功能被执行,并可能产生输出。
节点函数的签名通常接受当前的图状态作为输入,并返回一个用于更新状态的值。
例如,一个节点可以是:
调用某个 LLM Agent 来生成回复。
执行一个工具调用(如搜索、计算)。
对当前状态进行某种转换或判断。
调用另一个子图来处理更复杂的任务。
边 (Edge): 边连接图中的节点,定义了执行流的路径。边可以是:
普通边 (Normal Edge):从一个节点到另一个节点的简单转移。当前一个节点执行完成后,执行流无条件地转移到由边指向的下一个节点。
条件边 (Conditional Edge):从一个节点根据其执行结果有条件地转移到多个可能的下一个节点中的一个。这是实现复杂逻辑判断和流程分支的关键。
LangGraph 的图定义通常包括定义节点和定义边(以及入口点和可能的循环)。
3.2 状态管理 (State)
状态是 LangGraph 的核心概念之一。整个图的执行过程共享一个状态对象。每个节点在执行时都可以访问当前状态,并返回一个值来更新状态。LangGraph 会将节点返回的值与当前状态合并(默认是字典的 update 操作),形成新的状态,供下一个节点访问。
定义状态 (State Schema): 状态通常定义为一个 Python
TypedDict或普通的dict。TypedDict提供了类型提示,有助于代码的清晰性和健壮性。状态包含了整个工作流中共享的所有信息。例如,一个简单的状态可以包含消息列表:
这里的
Annotated和operator.add是 LangGraph 用于定义状态更新方式的一种机制。Annotated[List[BaseMessage], operator.add]表示messages字段是一个BaseMessage列表,当一个节点返回一个列表来更新状态时,LangGraph 会使用operator.add(即列表拼接) 将返回的列表添加到当前的messages列表中。对于简单的字段,如字符串或整数,直接指定类型即可,默认更新方式是覆盖(如果返回非 None 值)。更新状态: 节点函数通过返回一个与 State Schema 兼容的字典来更新状态。LangGraph 负责合并过程。
持久化状态 (Checkpointing): LangGraph 支持状态的持久化,这意味着工作流可以在执行过程中保存当前状态,并在需要时从保存的状态恢复执行。这对于构建长期运行、可中断的 A2A 系统至关重要。通过配置检查点 (checkpoint) 后端(例如内存、SQLite、数据库),可以在图执行时自动保存状态。
持久化状态是 enabling A2A 系统长期记忆和跨会话协作的关键。
3.3 条件边 (Conditional Edges)、入口点 (Entry Point)、循环 (Cycles)
条件边 (Conditional Edges): 条件边是 LangGraph 实现动态流程控制的核心机制。从一个节点出发的条件边需要指定一个判断函数。这个判断函数接收当前状态作为输入,并返回一个字符串,表示下一个应该转移到的节点的名称。
定义条件边时,需要:
指定起始节点 (source node)。
指定判断函数 (condition function)。
指定一个字典,将判断函数的返回结果映射到下一个节点 (mapping)。
入口点 (Entry Point): 图的执行总需要一个起点。LangGraph 使用特殊的
START符号来标识图的入口点。您需要使用set_entry_point(node_name)方法指定图的第一个执行节点。执行将从START节点开始,然后通过边转移到指定的入口节点。循环 (Cycles): LangGraph 支持在图结构中存在循环。当执行流通过边回到已经访问过的节点时,就会形成循环。循环在 Agent 系统中非常常见,例如:
ReAct 模式中的“思考-行动-观察”循环。
多轮对话中 Agent 不断处理用户输入并生成回复。
Agent 在未达到目标前反复尝试调用工具或与其他 Agent 交互。
通过条件边,可以控制何时跳出循环(例如,达到某个条件、获取到足够信息、用户指示结束等)。LangGraph 的状态管理使得在循环中保持上下文信息成为可能。
3.4 特殊节点:END
ENDEND 是 LangGraph 中一个特殊的内置节点,表示图的执行终止。当执行流到达 END 节点时,整个 Agent 工作流结束。通常通过条件边将某些终止条件导向 END 节点。
第四章:一个基础的 LangGraph Agent 示例
本章我们将构建一个简单的 LangGraph Agent,它能够接收用户输入,调用 LLM 生成回复,并根据 LLM 的回复决定是否结束对话。这展示了 LangGraph 的基本结构、状态管理和条件分支。
代码解释:
AgentState定义:我们定义了一个AgentStateTypedDict,其中最重要的字段是messages,用于存储BaseMessage对象的列表。Annotated和operator.add告诉 LangGraph 如何合并新返回的消息列表。call_llm节点:这是一个简单的函数,接收AgentState,从中取出消息列表,调用ChatOpenAI,并将 LLM 的回复作为一个AIMessage列表返回。 LangGraph 会将这个返回的{ "messages": [response] }字典与当前状态合并,使用operator.add将新消息添加到messages列表中。should_continue节点:这是一个判断函数,接收AgentState,检查最新的消息(通常是 LLM 回复)。根据消息内容,它返回"continue"或"end"。图结构定义:
创建
StateGraph(AgentState)实例。add_node("call_llm", call_llm):将call_llm函数作为一个名为"call_llm"的节点添加到图中。set_entry_point("call_llm"):设置"call_llm"为图的起始节点。add_conditional_edges(...):设置从"call_llm"节点出发的条件边。我们指定should_continue函数作为条件,并提供一个映射:如果函数返回"continue",转移到"call_llm"节点(形成循环);如果返回"end",转移到END特殊节点(终止执行)。
编译图:
workflow.compile(checkpointer=memory)将图结构编译成一个可执行的 LangGraph 应用。checkpointer配置开启了状态持久化。运行和状态持久化:我们使用
app.invoke()调用编译后的图。传入{"messages": [...]}作为初始状态。config={"configurable": {"thread_id": "..."}}确保了状态可以按照指定的thread_id进行持久化和恢复。在循环中,每一次invoke如果使用相同的config,都会先尝试从 checkpointer 加载该 thread_id 的最新状态,然后再处理本次输入。
这个基础示例展示了 LangGraph Agent 的基本构建模块:状态、节点、边、条件分支和循环,以及状态持久化。这是构建复杂 A2A 系统的基础。
第五章:高级 A2A 协作示例
基于 LangGraph 的核心概念,我们可以构建更复杂的 A2A 协作系统。本章将展示两种常见的 A2A 模式及其 LangGraph 实现。
5.1 示例 1: 研究员 Agent + 报告撰写 Agent 协作
场景描述:
一个用户需要一份关于某个主题的摘要报告。我们将有两个 Agent 协作完成任务:
研究员 Agent (Researcher Agent):负责根据主题进行网络搜索,收集相关信息。
报告撰写 Agent (Report Writer Agent):负责接收研究员 Agent 提供的资料,然后撰写一份摘要报告给用户。
协作流程:
用户提交研究主题。
研究员 Agent 接收主题,执行搜索。
研究员 Agent 将搜索结果传递给报告撰写 Agent。
报告撰写 Agent 接收搜索结果,撰写报告。
报告撰写 Agent 将最终报告返回给用户,任务结束。
LangGraph 实现思路:
状态: 需要共享的状态包括用户请求、研究主题、搜索结果、最终报告等。
节点:
一个节点用于初始化任务(接收主题)。
“研究员 Agent”节点:接收主题,调用搜索工具,更新状态中的搜索结果。
“报告撰写 Agent”节点:接收搜索结果,使用 LLM 生成报告,更新状态中的报告。
边:
从开始到初始化节点。
从初始化节点到“研究员 Agent”节点。
从“研究员 Agent”节点到“报告撰写 Agent”节点。
从“报告撰写 Agent”节点到
END节点。
代码示例 (关键部分):
首先,我们需要定义工具(这里以模拟搜索工具为例)和 Agent 节点函数。
代码解释:
ResearchReportState:定义了包含messages,research_topic,search_results,final_report的状态,用于在 Agent 之间传递信息。search_the_web工具:一个简单的工具封装,用于模拟网络搜索。在实际应用中,这会使用 Langchain 的Tool或Runnable。researcher_agent节点:从状态中读取research_topic,调用search_the_web获取结果,并将结果存回状态字典中。report_writer_agent节点:从状态中读取search_results和research_topic,使用 LLM 根据这些信息生成报告,并将报告存回状态字典中。initialize_research_task节点:这是一个前置节点,负责从用户输入的初始消息中提取研究主题,并添加到状态中。图构建与边:使用
StateGraph构建图,通过add_node添加三个 Agent 节点(initialize_task,researcher,report_writer)。使用add_edge定义了简单的顺序执行流程:初始化 -> 研究员 -> 报告撰写 -> 结束。运行:
app.invoke(initial_input)执行图。 LangGraph 会按照定义的边依次执行节点,每个节点执行后更新状态,直到到达END节点。
这个示例展示了如何通过 LangGraph 组织两个 Agent 按照预定的顺序协作完成一个任务,信息通过共享的 ResearchReportState 传递。
5.2 示例 2: 任务分解 Agent + 多个执行 Agent + 结果汇总 Agent
场景描述:
一个复杂的用户请求,需要分解成多个子任务,由不同的 Agent 并行或顺序执行,最后将结果汇总。我们将构建一个包含多个 Agent 的模式:
任务分解 Agent (Task Decomposer Agent):接收原始用户请求,将其分解为若干个独立的子任务列表。
执行 Agent (Executor Agents):负责执行特定类型的子任务(例如,搜索任务由搜索 Agent 执行,计算任务由计算 Agent 执行)。可能有多个不同类型的执行 Agent。
结果汇总 Agent (Result Aggregator Agent):接收所有子任务的执行结果,将它们整合、提炼,生成最终的统一答复给用户。
协作流程:
用户提交复杂请求。
任务分解 Agent 接收请求,分解成子任务列表,添加到状态。
一个路由节点根据子任务类型,将任务分发给相应的执行 Agent。
执行 Agent 完成任务,将结果添加到状态。
所有子任务完成后,路由节点或另一个判断节点将流程转移到结果汇总 Agent。
结果汇总 Agent 接收所有结果,生成最终答复,添加到状态。
流程结束,返回最终答复。
LangGraph 实现思路:
状态: 需要共享的状态包括原始用户请求、子任务列表、每个子任务的状态(待执行、进行中、已完成、失败)、每个子任务的结果、最终汇总结果等。
节点:
“任务分解 Agent”节点。
一个或多个“执行 Agent”节点(例如,“搜索执行 Agent”、“计算执行 Agent”)。
一个条件路由节点:根据当前待执行的子任务类型,将执行流路由到对应的执行 Agent 节点。
一个判断所有子任务是否完成的节点。
“结果汇总 Agent”节点。
边:
从开始到任务分解 Agent。
从任务分解 Agent 到路由节点。
从路由节点到各个执行 Agent。
从各个执行 Agent 回到判断所有子任务是否完成的节点。
从判断节点有条件边:如果还有待执行任务,回到路由节点;如果所有任务完成,转移到结果汇总 Agent。
从结果汇总 Agent 到
END节点。
循环:这里会形成一个循环:路由 -> 执行 Agent -> 判断完成 -> (未完成) 回到路由。
代码示例 (关键部分):
由于这个示例涉及多个 Agent、复杂的路由和状态管理,我们将重点展示其核心结构和关键节点函数的设计。完整的代码会比较长。
代码解释:
SubTask和TaskCollaborationState:定义了SubTask结构来描述每个子任务,以及包含原始请求和子任务列表的TaskCollaborationState。注意到sub_tasks使用了operator.add,但在节点函数中直接修改列表的元素并返回整个列表来更新状态可能需要更精细的处理,或者使用其他状态合并策略。一个更安全的方法可能是返回更新后的子任务列表的副本或者使用自定义的合并函数。在 LangGraph 的最新版本中,直接修改 mutable 对象(如列表、字典)并在节点函数中返回包含这些对象的字典通常会按引用传递并更新,但这在理解上可能比不可变数据结构的合并更复杂。这里示例直接修改并返回列表,假定其能正确反映状态变化。task_decomposer_agent节点:接收用户请求,调用 LLM 将其分解为结构化的子任务列表,并初始化子任务的状态(pending)。route_task节点:这是实现任务分发的关键。它遍历sub_tasks列表,找到第一个状态为pending的任务,并根据其type返回对应的执行 Agent 节点的命名字符串。如果找不到pending任务,则返回"aggregate_results"。这个函数既是节点本身的功能,也作为从该节点出发的条件边的判断函数。执行 Agent 节点 (
search_executor_agent,calculate_executor_agent):这些节点负责执行特定类型的子任务。它们会找到一个待执行的相应类型任务,调用相应的工具,更新任务的状态和结果,并返回修改后的sub_tasks列表。check_all_tasks_completed节点 (可选):一个辅助判断节点,用于检查所有子任务是否都已完成。虽然在上面的简化图中没有作为单独节点使用(逻辑集成到route_task或边条件中),但它可以作为一个清晰的流程步骤。result_aggregator_agent节点:在所有子任务完成后执行。它收集所有已完成任务的结果,调用 LLM 整合信息,生成最终给用户的答复。图构建与边:Graph 连接了分解器、路由和执行 Agent。 核心是条件边从
route_task到不同的执行 Agent,以及从执行 Agent 回到route_task,形成处理子任务的循环。 一旦route_task判断所有子任务完成,流程通过条件边转移到aggregate_results节点。
这个复杂示例展示了 LangGraph 如何通过状态管理和条件边实现动态的任务分发和协作循环,多个 Agent 可以根据任务类型和系统状态被动态调用。
第六章:A2A 架构模式与 LangGraph 实现
多 Agent 系统可以采用多种不同的架构模式,以适应不同的需求和复杂性。 LangGraph 的灵活性使其能够支持多种这些模式的实现。
6.1 常见的 A2A 架构模式
顺序协作 (Sequential Collaboration): 最简单的模式,Agent 按照预定的顺序依次执行。一个 Agent 的输出作为下一个 Agent 的输入(通过共享状态)。示例 1 (研究员+报告撰写) 就属于这种模式。
LangGraph 实现: 使用
add_edge(node_a, node_b)按顺序连接节点。层级式 (Hierarchical): 有一个或多个“监督 Agent”负责整体协调、任务分解和结果汇总。底层是执行具体任务的“工作 Agent”。监督 Agent 将任务分发给工作 Agent,并接收其结果报告。示例 2 (任务分解+执行+汇总) 就属于层级式的一种变体。
LangGraph 实现: 使用一个节点作为 Supervisor,该节点(或它指向的条件边)根据任务类型路由到不同的 Worker 节点。Worker 完成后,流程回到 Supervisor 节点(或一个检查节点),进行结果处理或决定下一个任务。
分布式/网络式 (Distributed/Network): 没有严格的层级关系,Agent 之间可以自由通信并动态决定下一个将要接收控制权的 Agent。Agent 可能会将任务“移交 (handoffs)”给最合适的 Agent。
LangGraph 实现: 每个 Agent 节点内部的逻辑(通常通过 LLM 的推理决定)决定返回哪个下一个 Agent 节点的名称,然后通过条件边转移。可以使用 LangGraph 的
Command(goto=...)机制实现更灵活的控制流移交³⁵。市场式 (Market-based): Agent 通过某种形式的“市场”(如拍卖、投标)来获取任务或资源。这通常需要额外的协调机制和协议。 LangGraph 实现: 可以在 LangGraph 内部模拟市场机制,例如一个节点负责“发布任务”,其他 Agent 节点“投标”,然后一个节点“分配任务”。但这会比前面几种模式复杂得多,可能需要结合外部服务。
6.2 LangGraph 对架构模式的支持
LangGraph 通过其核心组件提供了对这些架构模式的强大支持:
节点 (
add_node):可以代表任何类型的 Agent(监督、工作、专业执行器等)。普通边 (
add_edge):实现顺序执行流。条件边 (
add_conditional_edges, 条件判断函数):实现层级式和分布式架构中的动态路由和任务分发。状态 (
StateGraph,TypedDict):提供 Agent 间共享信息和同步状态的通道。循环:支持 Agent 之间的多轮交互和迭代过程(例如 ReAct 循环、任务分解/执行循环)。
子图 (SubGraph):可以将一个复杂的 Agent 或一个 Agent 团队封装成一个子图节点,然后在主图中使用。这支持构建更复杂的层级结构。
Handoffs (
Command(goto=...)):在网络式和分布式架构中,Agent 可以直接指定下一个接收控制权的 Agent 名称,实现流畅的移交³⁵。
通过组合这些功能,开发者可以灵活地设计和实现各种 A2A 架构模式。
第七章:代理间通信:消息传递与状态共享
在 LangGraph 构建的 A2A 系统中,Agent 之间的通信主要通过两种方式实现:共享状态的更新和消息列表的传递。这是实现 Agent 协作的关键。
7.1 基于共享状态的通信
如前所述,LangGraph 中的所有 Agent 节点都共享一个 AgentState 对象。Agent 通过修改和访问这个共享状态来进行信息交换。
数据传递: 一个 Agent 完成其任务后,可以将结果数据存储在
AgentState的相应字段中。下一个需要这些数据的 Agent 节点可以从状态中读取这些数据进行处理。控制信号/标志: Agent 可以在状态中设置标志或状态变量,以指示其完成状态、遇到的问题或下一步建议。其他 Agent 或路由节点可以根据这些标志来改变流程走向。
复杂数据结构: 共享状态可以包含复杂的 Python 对象,如列表、字典、甚至是自定义类的实例,只要它们可以被序列化以便持久化(如果开启了状态持久化)。对于列表和字典等可变对象,更新状态时需要注意合并策略(如使用
operator.add进行列表拼接,或自定义函数进行更复杂的字典更新)。
7.2 基于消息列表的通信 (MessagesState)
MessagesState)Langchain 的 BaseMessage 列表是 LLM 交互的标准格式。 LangGraph 通常会将消息列表作为 AgentState 的核心部分 (MessagesState)。 Agent 之间的许多通信都是通过向这个共享的消息列表中添加消息来实现的。
ReAct 模式通信: 在基于 ReAct 模式构建的 Agent 中,LLM 的思考过程 (
scratchpad)、工具调用 (ToolMessage) 和工具输出 (ToolMessage) 都被格式化为BaseMessage类型并添加到消息列表中。 Agent 下一次运行时,LLM 可以通过完整的消息历史来回忆之前的步骤、思考过程和工具执行结果。这是 Agent 内部推理的重要通信方式。Agent 间对话: 在一些多 Agent 模式中,Agent 之间可以通过模拟对话的方式进行通信。一个 Agent 生成一条消息(例如
AIMessage或自定义消息类型),包含它要传达的信息或指令,并将其添加到共享消息列表中。下一个 Agent 则可以读取列表中的新消息,理解其内容并做出反应。为了区分消息来源,可以使用消息的name字段或在消息内容中明确标记发送 Agent 的身份。这种消息传递方式的好处是所有通信都可以记录在统一的消息历史中,便于 LangSmith 等工具进行追踪和调试。它模拟了 Agent 之间通过“聊天记录”进行协作³⁵。
7.3 实现 Agent 间通信的技巧
清晰的状态定义: 精心设计
AgentStateTypedDict的结构,确保所有 Agent 需要共享的信息都有明确的字段。使用Annotated配合合适的合并策略(operator.add、operator.setitem或自定义函数)来控制状态更新行为。规范消息格式: 如果通过消息列表通信,考虑定义一套内部消息格式或约定,例如使用特定的前缀、标签或消息的
name字段来标识消息的类型、发送者和接收者。利用条件边路由: Agent 在完成任务或需要将控制权移交给另一个 Agent 时,可以在返回状态时包含一个指示下一个步骤的信息(例如,一个状态字段或一个特殊的 Command 对象),然后利用条件边根据这个信息路由到下一个 Agent 节点。
自定义状态合并 (
StateGraph(..., reducer=...)): 对于复杂的、多 Agent 同时修改状态的情况(尽管 LangGraph 的同步执行模型通常一次只有一个节点在运行),或者需要复杂的逻辑(如冲突解决、数据聚合)来合并状态更新时,可以自定义状态的 reducer 函数。Handoffs (
Command对象): LangGraph 的Command对象提供了一种显式的控制流移交机制。 Agent 节点可以返回Command(goto="next_agent_name", update={"state_key": value})来指定下一个节点并同时更新状态³⁵。这比仅仅通过条件边判断更加直接和灵活。在图构建时,边需要能够响应这种 Command 返回。LangGraph 通常会自动处理以
Command对象作为返回值的节点的边。
通过有效利用共享状态(特别是消息列表)和条件边(以及 Handoffs),您可以设计出 Agent 之间流畅、高效且可追踪的通信和协作逻辑。
第八章:调试和测试 A2A 系统
多 Agent 系统的复杂性使得调试和测试变得尤为重要,也更具挑战性。 Agent 之间的交互、动态的控制流和状态变化都增加了排查问题的难度。 LangGraph 和 Langchain 生态系统提供了一些工具和技术来帮助进行调试和测试。
8.1 使用 Print 语句和日志
最基础的调试方法是在 Agent 节点函数中加入 print 语句或使用 Python 内置的 logging 模块输出关键信息,例如:
Agent 节点的开始和结束。
从状态中读取的关键数据。
调用外部工具的输入和输出。
条件判断函数的返回结果。
状态更新前后的内容。
这种方法简单直接,适用于快速验证小规模的 Agent 工作流。但对于复杂的图结构、循环和多轮交互,仅仅依靠 print 输出信息可能会非常零散,难以追踪完整的执行路径和状态演变。
8.2 使用 LangSmith 进行可视化追踪
如前文所述,强烈推荐使用 LangSmith。将 LangSmith 环境变量配置好后,LangGraph 的每次 invoke 调用都会在 LangSmith UI 中生成一个详细的追踪记录(Trace)。
LangSmith Trace 提供:
可视化的执行图: 展示了 Agent 系统的执行流程图,每个节点、每条边的转移都清晰可见。
节点详细信息: 点击图中的节点,可以查看该节点的输入状态、输出、执行时间、调用的 LLM 的输入和输出、工具调用的详细信息等。
状态变化: 可以查看进入和离开每个节点时 AgentState 的具体内容,帮助理解状态是如何在 Agent 之间传递和更新的。
错误信息: 如果某个节点执行失败,LangSmith 会标记错误,并提供详细的错误堆栈信息。
LLM 调用和工具调用细节: Langchain 提供的 LangSmith 集成会展示 LLM 调用和工具调用的完整请求和响应,便于排查 LLM 理解提示词或工具调用参数的问题。
通过 LangSmith,您可以直观地看到数据流和控制流在 Agent 之间如何流动,快速定位问题发生的节点和原因。这对于调试复杂的 Agent 协作和状态管理问题至关重要。
8.3 单元测试和集成测试
单元测试: 对每个 Agent 节点函数、条件判断函数、自定义状态合并函数等核心组件进行单元测试。隔离测试可以确保每个 Agent 或功能模块按照预期工作。
测试节点函数: 模拟输入状态,调用节点函数,断言返回的状态更新字典是否正确,以及函数是否执行了预期的副作用(如工具调用)。
测试条件判断函数: 模拟不同状态作为输入,断言函数返回的下一个节点名称是否正确。
集成测试: 测试整个 LangGraph 工作流。模拟初始输入,调用
app.invoke()执行图,断言最终状态是否符合预期。可以测试不同输入条件下,Agent 系统的执行路径和最终结果是否正确。
单元测试和集成测试的结合能够有效地保证 Agent システム的质量和稳定性。可以使用 pytest 等测试框架来组织和运行测试。
8.4 模拟和桩 (Mocking and Stubbing)
在测试依赖外部服务(如 LLM API、数据库、网络搜索)的 Agent 时,为了避免真实调用带来的成本、延迟和不稳定性,可以使用模拟 (mocking) 和桩 (stubbing) 技术。
模拟 (Mocking): 替换真实的依赖对象,记录对其方法的调用,并控制其返回值。例如,模拟 LLM 的
invoke方法,使其返回预设的回复。桩 (Stubbing): 提供简化的、预设行为的替代品,用于在测试中满足依赖。例如,一个模拟的搜索工具函数,只返回固定的字符串结果,而不是执行真实的网络搜索。
Python 的 unittest.mock 模块或 pytest-mock 插件可以方便地实现模拟和桩。
通过模拟外部依赖,您可以更专注于 Agent 内部逻辑和 Agent 之间的交互逻辑的测试。
第九章:部署 A2A Agent 的考量
将基于 Langchain 和 LangGraph 开发的 A2A Agent 系统从开发环境部署到生产环境需要考虑多个方面,包括可伸缩性、可靠性、安全性、监控和成本管理。
9.1 可伸缩性与性能
无状态 vs 有状态: LangGraph 是有状态的,其状态需要存储。选择合适的 State Checkpointer Backend 至关重要。内存 checkpointer 只适用于开发或单机场景。生产环境中需要使用支持持久化和并发访问的后端,如数据库(SQLite 文件或更强大的数据库如 PostgreSQL, MongoDB 等)或者专门的状态存储服务。
异步执行: 对于需要执行耗时操作(如网络请求、LLM 调用)的节点,考虑使用异步 I/O (asyncio) 来提高并发处理能力。 LangGraph 的
compile方法支持async模式。 Agent 节点函数需要定义为async def,并在内部使用await调用异步操作²。Agent 并行化: 在某些 A2A 架构中,可能需要并行执行多个子任务或调用多个 Agent。虽然 LangGraph 的核心执行模型是同步的图遍历,但可以通过将并行逻辑封装在一个节点中(例如,在该节点内部使用
asyncio.gather并行调用多个 Runnable 或工具),或者设计 Agent 模式本身支持并行执行(如 MapReduce 模式)。LLM 调用吞吐量: LLM API 的调用可能是瓶颈。考虑使用支持高吞吐量的 LLM 模型和提供商。对于大量并发请求,需要合理设计 Agent 工作流,避免 LLM 的过度或重复调用。
9.2 可靠性与故障恢复
状态持久化 (Checkpointer): 这是确保可靠性的基础。如果 Agent 系统在执行过程中崩溃,通过持久化的状态,可以在恢复后从中断点继续执行,避免从头开始。选择可靠且具备备份机制的 checkpointer 后端。
错误处理: 在 Agent 节点函数中实现健壮的错误处理逻辑(try...except 块)。可以选择在节点内部处理错误并修改状态(例如,将任务状态标记为
failed),或者让错误冒泡,由 LangGraph 提供的错误处理机制来处理(例如,使用add_edge(node, ERROR, error_handler_node)将错误路由到专门的错误处理节点)。幂等性: 设计 Agent 节点函数使其尽可能具有幂等性。这意味着多次执行同一个节点函数,如果输入状态没有改变,结果也是一样的。这有助于在重试或故障恢复时避免不一致。
9.3 安全性
API 密钥管理: 绝不将 API 密钥硬编码在代码中。使用环境变量、Secret Manager 或其他安全的密钥管理服务来存储和访问密钥。
工具访问权限: Agent 调用的工具可能访问敏感数据或执行破坏性操作。确保 Agent 只能访问其完成任务所需的最小权限工具。在多 Agent 系统中,对 Agent 之间的通信和状态访问实施合适的访问控制。
输入验证和沙箱: 对用户输入和 Agent 之间传递的数据进行验证,防止注入攻击或恶意指令。考虑在沙箱环境中运行 Agent,限制其对外部系统的访问能力⁴。
Agent 协议安全: 如果使用 A2A 协议进行跨系统通信,需要考虑身份认证(如 OAuth2, mTLS)、授权、传输加密(TLS/SSL)以及消息完整性验证¹²。
9.4 监控与日志
结构化日志: 使用结构化日志记录关键事件,如 Agent 开始/结束执行、状态变化、工具调用、错误等。这有助于在生产环境中进行集中式日志管理、搜索和分析。
应用监控: 使用应用性能监控(APM)工具来追踪 Agent 系统的性能指标,如请求延迟、错误率、吞吐量等。
业务指标: 监控与业务相关的指标,如任务完成率、用户满意度等,以评估 Agent 系统的实际效果。
LangSmith (生产环境): LangSmith 不仅用于开发调试,也可以在生产环境中用于监控和跟踪。它可以帮助您理解生产流量下 Agent 系统的行为,发现潜在问题。
9.5 成本管理
LLM 调用成本: LLM 调用是主要的成本来源。优化提示词、选择成本效益更高的模型、缓存重复的 LLM 调用、以及确保 Agent 不执行不必要的、昂贵的 LLM 调用是关键。
基础设施成本: 根据 Agent 系统的负载和并发需求,选择合适的計算資源和狀態存儲服務。异步处理和Agent并行化可以提高资源利用率。
工具使用成本: 某些工具(如付费 API)也可能产生费用。监控工具的使用情况,确保合理调用。
9.6 部署方式
基于 Python 和 LangGraph 的 A2A Agent 系统可以部署在多种环境中:
Web 应用后端: 集成到 Flask、Django、FastAPI 等 Web 框架中,通过 API 接收请求并驱动 Agent 工作流。
容器化 (Docker): 将 Agent 系统打包到 Docker 容器中,便于在不同环境部署和管理。
Serverless 函数: 对于请求量波动较大的场景,可以考虑将 Agent 工作流或部分节点部署为 Serverless 函数(如 AWS Lambda, Google Cloud Functions),按需付费。
云平台服务: 利用云平台提供的 Agent 或工作流编排服务(如 Google Cloud Agent Builder/Vertex AI Agent Framework,或其他平台提供的 Langchain/LangGraph 托管服务)。Google Cloud 的文档详细介绍了如何在 Vertex AI 上开发和部署 Langchain Agent²。
选择合适的部署方式取决于您的技术栈、运维能力、预算和伸缩性需求。
第十章:最佳实践和常见问题解答
构建健壮、可维护的 A2A Agent 系统需要遵循一些最佳实践。
10.1 最佳实践
模块化设计:
将每个 Agent 的核心逻辑封装在独立的函数或类中。
将工具定义清晰且功能单一。
将复杂的 A2A 工作流分解为更小的子图,提高可理解性和复用性。
状态定义应结构清晰,避免过于庞大和混乱。
明确的 Agent 职责: 每个 Agent 应该有清晰、单一的职责。避免 Agent 过于“全能”,导致其逻辑复杂、难以维护,并降低专业化优势。
规范的 Agent 间通信: 无论使用状态共享还是消息传递,建立一套规范的通信协议或数据格式。这有助于 Agent 之间准确理解信息,降低集成难度。例如,约定任务完成结果存放在状态的哪个字段,或者消息应该包含哪些关键信息。
充分利用状态管理: 将所有需要在 Agent 之间共享或需要长期记忆的信息都存储在
AgentState中。合理设计状态结构和更新逻辑,确保状态的一致性和可追溯性。可视化和追踪: 从项目一开始就集成 LangSmith 或其他追踪工具。可视化的工作流和详细的执行细节是调试和优化复杂 A2A 系统的杀手锏。
持续测试: 建立自动化测试流程,包括单元测试和集成测试。测试覆盖 Agent 节点的逻辑、条件边的跳转以及关键的工作流路径。
考虑异步和并行: 对于性能敏感的场景,评估 Agent 工作流中哪些部分可以并行执行,并利用 LangGraph 的异步能力和设计相应的 Agent 模式。
错误处理和容错: 仔细考虑各种可能的错误情况(LLM 调用失败、工具执行失败、状态不一致等),并在 Agent 节点和图级别实现相应的错误处理机制。
文档和注释: 详细记录每个 Agent 的功能、状态字段的含义、边和条件边的工作原理。清晰的代码注释和文档能够提高团队协作效率和系统的可维护性。
增量开发: 从简单的顺序协作模式开始,逐步引入更复杂的架构模式(如层级式、动态路由), incrementally 构建和测试 A2A 系统。
10.2 常见问题解答 (FAQ)
Q: 如何选择合适的 LLM 模型? A: 选择 LLM 模型取决于您的需求、成本预算和延迟要求。
对于需要强大推理、规划和遵循复杂指令的任务,考虑使用高性能模型(如 GPT-4 系列, Claude, Gemini Ultra)。
对于成本敏感或只需处理简单任务的场景,可以考虑使用成本效益更高的模型(如 GPT-4o-mini, Claude 3 Haiku, Gemini Pro)。
选择支持 function calling 或 tool use 能力的模型,可以简化 Agent 调用工具的实现。
本地模型可以用于对数据隐私要求极高的场景。
Q: 如何在 Agent 之间传递复杂数据结构? A: 将复杂数据结构(如搜索结果列表、解析后的文档内容、中间计算结果等)直接存储在
AgentState的相应字段中。确保这些数据类型在状态持久化时是可序列化的(通常 Python 基本类型、列表、字典、序列化后的对象都可以)。对于可变对象(列表、字典),在更新状态时注意合并逻辑¹。Q: 如何实现 Agent 之间的循环对话? A: 使用 LangGraph 的条件边和状态管理。设计一个条件判断函数,检查对话是否需要继续(例如,根据用户输入、LLM 回复的结束标记或任务完成状态)。如果需要继续,条件边将流程导回生成回复的 Agent 节点或等待用户输入的节点(形成循环)。共享的消息列表 (
MessagesState) 自动提供了完整的对话历史上下文。Q: 如何处理某个 Agent 节点执行失败的情况? A:
节点内部处理: 在 Agent 节点函数中使用
try...exceptBlock 捕获异常。在except中,更新状态以反映错误(例如,设置任务状态为failed,在消息列表中添加错误消息),并返回更新后的状态。可以在错误处理后,根据状态决定是否需要重试、跳过任务或终止整个流程。图级别处理: LangGraph 支持错误处理边。可以使用
add_edge(source_node, ERROR, error_handler_node)将从source_node抛出的错误路由到一个专门的错误处理节点,在该节点中进行统一的日志记录、通知或尝试恢复。
Q: 如何在 LangGraph 中实现并行执行? A: LangGraph 的核心执行是顺序沿着边进行的。要实现并行,可以在一个 Agent 节点内部使用 Python 的异步编程能力(如
asyncio.gather)并行调用多个 LLM、工具或其他Runnable。或者,设计 Agent 工作流时,让一个 Agent 将多个子任务分配出去,由不同的 LangGraph 实例或独立的 Worker 进程/线程并行处理,最后再由另一个 Agent 节点收集和汇总结果。Q: 如何将人类反馈集成到 A2A 工作流中 (Human-in-the-Loop)? A: 在 LangGraph 图中,可以将一个节点设计为“人类审核节点”。当 Agent 工作流需要人类干预时,流程转移到此节点。此节点可以暂停执行,将当前状态(包括 Agent 的最新思考、待决策事项等)发送给人类用户界面。接收到人类反馈后,将反馈添加到状态中,并根据反馈结果通过条件边决定流程的下一步(例如,返回某个 Agent 进行修改,或者直接进入下一个阶段)。 LangGraph 的状态持久化特性对于实现长期的人类参与至关重要。
Q: LangChain Agent 和 LangGraph 节点有什么关系? A: LangGraph 节点可以是一个简单的 Python 函数,也可以更高级地封装一个完整的 LangChain AgentExecutor。将 LangChain AgentExecutor 作为 LangGraph 节点可以充分利用 LangChain Agent 的 ReAct 推理、工具调用、记忆管理等功能。在 LangGraph 中定义 Agent 节点时,您可以选择直接在函数中编写 Agent 逻辑(调用 LLM、工具),或者创建 Langchain AgentExecutor 实例作为节点。
Q: 如何在 LangGraph 中管理多个独立的会话? A: 使用 LangGraph 的状态持久化 (
checkpointer)。每次invoke或stream调用时,在config中传入一个唯一的thread_id。 LangGraph 会根据thread_id加载或保存对应的会话状态。这使得同一个 LangGraph 应用实例可以同时处理多个独立的 Agent 会话。
总结与展望
本教程作为一份详尽的 Langchain + LangGraph A2A 开发指南,从 Agent 和多 Agent 系统的基础概念出发,深入剖析了 LangGraph 的核心 구성、状态管理和控制流机制,并通过研究报告生成和任务分解协作两个具体的 A2A 示例,展示了如何使用 LangGraph 构建实际的多 Agent 工作流。我们还探讨了不同的 A2A 架构模式、Agent 间的通信机制、调试与测试方法以及生产部署的关键考量。
通过学习本教程,您应该已经掌握了:
Langchain 和 LangGraph 在 A2A 开发中的作用和优势。
LangGraph 的图结构、节点、边和状态管理的核心概念。
使用 Python 和 LangGraph 构建基础 Agent 和复杂 A2A 工作流的方法。
实现 Agent 之间信息传递和协作的技术。
使用 LangSmith 进行可视化调试和追踪。
A2A 系统开发、测试和部署的最佳实践和注意事项。
A2A 系统是构建更智能、更自主、更具协作能力的 AI 应用的关键发展方向。 Langchain 和 LangGraph 作为领先的框架,为开发者提供了强大的工具集。 随着 LLM 能力的不断提升和 Agent 技术的发展,未来的 A2A 系统将能够处理更加复杂、开放域的任务,实现更紧密的 Agent 协同,并在更多领域(如企业自动化、科学研究、智能制造、创意产业)展现出巨大的潜力。
希望本教程能为您在 A2A Agent 开发的旅程中提供坚实的基础和实用的指导。 现在,是时候动手尝试,构建您自己的 Agent 协作系统了!
Agent-to-Agent (A2A) 协议的标准化将进一步促进跨平台、跨厂商的 Agent 协作生态系统的发展,支持去中心化 Agent 网络的构建,未来 Agent 可能会像 Web 服务一样通过开放协议进行交互¹²⁵。
不断探索、实践和创新,用 Agent 技术构建更智能的未来!
最后更新于