LangGraph实战:构建会思考、能记忆、可人工干预的多智能体AI系统
仅用于站内搜索,没有排版格式,具体信息请跳转上方微信公众号内链接
通过组合几个较小的子智能体来创建强大的AI智能体已成为一种趋势。但这也带来了挑战,例如减少幻觉、管理对话流程、在测试期间密切关注智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。
在本文中,我们将使用监督者方法构建一个多智能体系统。在此过程中,我们将介绍基础知识、在创建复杂的AI智能体架构时可能面临的挑战,以及如何评估和改进它们。
我们将使用LangGraph和LangSmith等工具来帮助我们完成此过程。
我们将从基础开始,通过分步方法来创建这个复杂的多AI智能体架构
LangChain、LangGraph模块构成了一个完整的架构,但是如果我一次性导入所有库,肯定会造成混淆。
所以我们只会在需要时导入模块,因为这将有助于我们以正确的方式学习。
第一步是创建环境变量,用于保存我们的敏感信息,如API密钥和其他类似信息。
LangSmith对你来说可能是一个新术语。如果你不知道它是什么,我们将在下一节讨论它的用途。如果你已经知道了,可以跳过他。
要获取LangSmithAPI密钥,你可以访问他们的网站并创建一个帐户。之后,在设置下,你会找到你的API密钥。
当我们使用LLM构建AI智能体应用程序时,LangSmith可以帮助你理解和改进它们。它就像一个仪表板,显示应用程序内部发生的情况,并允许你:
出现问题时进行调试
测试你的提示和逻辑
评估答案的质量
实时监控你的应用程序
跟踪使用情况、速度和成本
LangSmith使所有这些都易于使用,即使你不是开发人员。
让我们导入它。
我们刚刚从LangSmith导入了稍后将使用的utils,并且追踪设置为true,因为我们之前设置了环境变量LANGSMITH_TRACING=TRUE,这有助于我们记录和可视化AI智能体应用程序的执行情况。
我们将使用Chinook数据库,这是一个用于学习和测试SQL的流行示例数据库。它模拟了数字音乐商店的数据和运营,例如客户信息、购买历史和音乐目录。
它有多种格式,如MySQL、PostgreSQL等,但我们将使用SQLite版本的数据,因为它也有助于我们了解AI智能体如何与数据库交互,这对于刚接触本AI智能体指南的人尤其有用。
让我们定义一个函数来为我们设置SQLite数据库。
我们刚刚定义了第一个函数get_engine_for_chinook_db(),它使用Chinook示例数据集设置一个临时的内存SQLite数据库。
它从GitHub下载SQL脚本,在内存中创建数据库,运行脚本以用表和数据填充它,然后返回一个连接到此数据库的SQLAlchemy引擎。
现在我们需要初始化这个函数,以便创建SQLite数据库。
我们刚刚调用了该函数并初始化了引擎,以便稍后使用AI智能体在该数据库上运行查询操作。
现在我们初始化了数据库,下一步就是寻找组合(LangGraph+LangSmith)的第一个优势,即两种不同类型的内存可用性,但首先要了解什么是内存。
在任何智能体中,内存都扮演着重要的角色。就像人类一样,AI智能体需要记住过去的交互以保持上下文并提供个性化的响应。
在LangGraph中,我们区分短期记忆和长期记忆,以下是它们之间的快速区别:
短期记忆帮助智能体跟踪当前对话。在LangGraph中,这由MemorySaver处理,它保存并恢复对话的状态。
而长期记忆让智能体能够记住不同对话中的信息,例如用户偏好。例如,我们可以使用InMemoryStore进行快速存储,但在实际应用程序中,你会使用更持久的数据库。
让我们初始化它们两者。
我们使用in_memory_store作为长期内存,即使在对话结束后,它也可以让我们保存用户偏好。
同时,MemorySaver(检查点)保持当前对话的上下文完整,从而实现流畅的多轮交互。
这里将从一个简单的ReAct智能体开始,并在工作流中添加额外的步骤,模拟一个逼真的客户支持示例,展示人工介入、长期记忆和LangGraph预构建库。
我们将逐步构建多智能体工作流的每个组件,因为它包含两个子智能体,两个专门的ReAct(推理和行动)子智能体,然后它们将组合起来创建一个包含额外步骤的多智能体工作流。
我们的工作流从以下开始:
human_input,用户在此提供帐户信息。
然后,在verify_info中,系统检查帐户并在需要时阐明用户的意图。
接下来,load_memory检索用户的音乐偏好。
supervisor协调两个子智能体:music_catalog(用于音乐数据)和invoice_info(用于账单)。
最后,create_memory用交互中的新信息更新用户的内存。
所以现在已经了解了基础知识,下面就是开始构建第一个子智能体。
第一个子智能体将是一个音乐目录信息智能体。其主要职责是协助客户处理与我们的数字音乐目录相关的查询,例如搜索艺术家、专辑或歌曲。
我们的智能体将如何记住信息、决定做什么并执行操作?这使我们想到了三个基本的LangGraph概念:状态(State)、工具(Tools)和**节点(Nodes)。
在LangGraph中,状态(State)保存流经图的当前数据快照,基本上是智能体的内存。
对于我们的客户支持智能体,状态包括:
customer_id:识别客户以进行个性化响应和数据检索。
messages:对话中交换的所有消息的列表,为智能体提供上下文。
loaded_memory:加载到对话中的长期用户特定信息(如偏好)。
remaining_steps:计算剩余步骤数以防止无限循环。
随着对话的进行,每个节点都会更新此状态。使用TypedDict进行类型提示,并使用LangGraph消息模块中的Annotated来方便地附加消息,从而定义我们的状态。
这个State类将作为我们多智能体系统中不同部分之间信息管理和传递方式的蓝图。
接下来使用工具(Tools)来扩展智能体的能力。工具是一些函数,可以让LLM做一些它自己无法做的事情,比如调用API或访问数据库。
对于我们的智能体,工具将连接到Chinook数据库以获取与音乐相关的信息。
Python函数,使用langchain_core. tools中的@tool标记它们,以便LLM在需要时可以找到并使用它们。
在此代码块中,定义了四个特定的工具:
get_albums_by_artist:查找给定艺术家的专辑
get_tracks_by_artist:查找艺术家的单曲
get_songs_by_genre:检索属于特定流派的歌曲
check_for_songs:验证目录中是否存在特定歌曲
这些工具中的每一个都通过执行SQL查询与我们的db(我们之前初始化的SQLDatabase包装器)进行交互。然后以结构化格式返回结果。
最后使用llm. bind_tools()将这些music_tools绑定到llm。
这个关键步骤允许LLM根据用户的查询了解何时以及如何调用这些函数。
状态(State)已经定义并且工具(Tools)已经准备就绪,现在就可以定义图的节点(Nodes)**。
节点是LangGraph应用程序中的核心处理单元,它们将图的当前状态作为输入,执行一些逻辑,并返回更新后的状态。
对于ReAct智能体,将定义两种关键类型的节点:
music_assistant是LLM推理节点。它使用当前的对话历史和内存来决定下一个操作,可以是调用工具或生成响应,并更新状态。
music_tool_node运行music_assistant选择的工具。LangGraphToolNode管理工具调用并用结果更新状态。
通过组合这些节点,在多智能体工作流中实现了动态推理和操作。
首先为music_tools创建ToolNode:
定义music_assistant节点。此节点将使用LLM(已绑定music_tools)来确定下一个操作。
它还将任何loaded_memory合并到其提示中,从而实现个性化响应。
还需要创建一个music_assistant函数。
music_assistant节点为LLM构建的详细系统提示,包括通用指令和用于个性化的loaded_memory。
它使用此系统消息和当前对话消息调用llm_with_music_tools。根据其推理,LLM可能会输出最终答案或工具调用。
它只是返回此LLM响应,add_messages(来自状态定义)会自动将其附加到状态中的messages列表中。
在状态和节点就位后,下一步是使用边(Edges)连接它们,边定义了图中的执行流程。
普通边很简单——它们总是从一个特定节点路由到另一个特定节点。
条件边是动态的。这些是Python函数,它们检查当前状态并决定接下来访问哪个节点。
对于我们的ReAct智能体,需要一个条件边来检查music_assistant是否应该:
调用工具:如果LLM决定调用工具,路由到music_tool_node来执行它。
结束流程:如果LLM提供最终响应而没有工具调用,结束子智能体的执行。
为了处理这个逻辑,定义should_continue函数。
should_continue函数检查状态中的最后一条消息。如果它包含tool_calls,则表示LLM想要使用工具,因此该函数返回\“continue\“。
否则,它返回\“end\“,表示LLM已提供直接响应并且子智能体的任务已完成。
现在我们拥有了所有部分:状态、节点和边。
使用StateGraph将它们组装起来以构建完整的ReAct智能体。
在最后一步中,使用定义的状态创建一个StateGraph。为music_assistant和music_tool_node添加节点。
music_tool_node运行后,一条边将流程带回music_assistant,允许LLM处理工具的输出并继续推理。
测试第一个子智能体:
为对话提供了一个唯一的thread_id,我们的问题是关于与滚石乐队相似的音乐推荐,看看AI智能体将使用什么工具来响应。
根据作为查询的人类消息,它使用正确的工具get_tracks_by_artist进行响应,该工具负责根据我们查询中指定的艺术家查找推荐。
现在,我们已经创建了第一个子智能体,让我们创建第二个子智能体。
虽然从头开始构建ReAct智能体对于理解基础知识非常有用,但LangGraph也为常见架构提供了预构建库。
它允许快速设置像ReAct这样的标准模式,而无需手动定义所有节点和边。可以在LangGraph文档中找到这些预构建库的完整列表。
和以前一样,首先为的invoice_information_subagent定义特定的工具和提示。这些工具将与Chinook数据库交互以检索发票详细信息。
发票处理定义了三个专门的工具:
get_invoices_by_customer_sorted_by_date:检索客户的所有发票,按日期排序
get_invoices_sorted_by_unit_price:检索按其中项目单价排序的发票
get_employee_by_invoice_and_customer:查找与特定发票关联的支持员工
和以前一样,必须将所有这些工具附加到一个列表中。
指导发票子智能体行为的提示:
此提示概述了子智能体的角色、可用工具、核心职责以及处理未找到信息情况的指南。
这种有针对性的指令有助于LLM在其专业领域内有效行动。
使用LangGraphcreate_react_agent预构建函数,而不是像之前的子智能体那样手动为ReAct模式创建节点和条件边。
create_react_agent函数接收llm、invoice_tools、智能体的名称(对于多智能体路由很重要)、刚刚定义的提示、我们的自定义State模式,并连接检查点和内存存储。
仅用几行代码,就拥有了一个功能齐全的ReAct智能体,这是使用LangGraph的优势。
测试一下新的invoice_information_subagent,以确保它按预期工作。我们将提供一个需要获取发票和员工信息的查询。
基本上是在询问客户ID为1的发票。让我们看看调用了哪些工具。
我们的多智能体正在与用户进行非常详细的对话。让我们理解一下。
在这个例子中,用户提出了一个同时涉及发票详细信息和音乐目录数据的问题。以下是发生的情况:
监督者收到查询。
它检测到与发票相关的部分(“最近的购买”)并将其发送给invoice_information_subagent。
发票子智能体处理该部分,获取发票,但无法回答U2专辑的问题,因此它将控制权交还给监督者。
然后,监督者将剩余的音乐查询路由到music_catalog_subagent。
音乐子智能体检索U2专辑信息并将控制权返回给监督者。
监督者总结,协调了两个子智能体以完全回答用户的多部分问题。
到目前为止,我们已经构建了一个多智能体系统,可以将客户查询路由到专门的子智能体。然而在现实世界的客户支持场景中,并不总是能够轻易获得customer_id。
在允许智能体访问发票历史等敏感信息之前,通常需要验证客户的身份。
在这一步中,将通过添加客户验证层来增强我们的工作流。这将涉及一个人工介入(human-in-the-loop)组件,如果客户的帐户信息缺失或未经验证,系统可能会暂停并提示客户提供该信息。
为了实现这一点,需要引入了两个新节点:
verify_info节点尝试使用的数据库从用户输入中提取并验证客户身份证明(ID、电子邮件或电话)。
如果验证失败,则触发human_input节点。它会暂停图并提示用户提供缺失的信息。这可以使用LangGraphinterrupt()功能轻松处理。
定义一个用于解析用户输入的Pydantic模式和一个用于LLM可靠地提取此信息的系统提示。
UserInputPydantic模型将预期数据定义为单个标识符。
使用with_structured_output()使LLM以此格式返回JSON。系统提示帮助LLM仅专注于提取标识符。
接下来,需要一个辅助函数来获取提取的标识符(可以是客户ID、电话号码或电子邮件),并在Chinook数据库中查找它以检索实际的customer_id。
这个实用程序函数能够智能地解析提供的标识符,无论是直接的客户ID、电话号码还是电子邮件地址,然后查询数据库以获取相应的数字化客户ID。
接下来定义verify_info节点,该节点负责协调整个标识符提取和验证流程:
该verify_info节点首先检查状态中是否已存在customer_id。如果不存在,它使用structured_llm从用户输入中提取标识符,并使用get_customer_id_from_identifier进行验证。验证成功时,它会更新状态并发送确认消息;验证失败时,它会使用主要LLM和系统指令礼貌地向用户请求信息。
现在创建human_input节点,该节点作为人工干预机制的关键组件:
interrupt()函数是LangGraph的强大功能特性。执行时,它会暂停图的执行并发出需要人工干预的信号。后续的执行函数需要通过提供新输入来处理此中断以恢复图的运行。
现在需要定义条件边函数should_interrupt,该函数决定是否需要人工介入:
现在将这些新的节点和边集成到整体图架构中:
新的图结构从verify_info开始执行。如果验证成功,则移至supervisor;如果验证失败,则路由到human_input,该节点会中断流程并等待用户输入。一旦提供了输入,它会循环回到verify_info以重新尝试验证。supervisor是到达END之前的最后处理步骤。
让我们测试人工干预功能。首先在不提供任何身份证明的情况下提出一个问题:
正如预期,智能体会中断并询问客户ID、电子邮件或电话号码,因为customer_id最初在状态中为None。
现在使用LangGraph的Command(resume=…)从中断处恢复对话并提供所需信息:
用户提供电话号码后,verify_info节点成功识别了customer_id(在Chinook数据库中,此号码对应的customer_id为1)。系统确认验证成功,并按照图中定义的流程将控制权传递给supervisor,然后由监督者路由原始查询。
这证实了人工干预验证机制按预期正常工作。
LangGraph状态管理的一个关键优势是,一旦customer_id得到验证并保存在状态中,它将在整个对话过程中持续存在。这意味着智能体在同一线程的后续问题中不会再次要求验证。
通过在不重新提供ID的情况下提出后续问题来测试这种持久性:
我们在\“短期和长期内存\“部分已经初始化了用于长期内存的InMemoryStore。现在将其完全集成到多智能体工作流中。长期内存的强大之处在于它允许智能体回忆和利用过去对话中的信息,从而实现随时间推移的个性化和上下文感知交互。
添加两个新节点来处理长期内存:load_memory在对话开始时(验证后)从in_memory_store检索用户现有的偏好;create_memory将用户在对话期间分享的任何新音乐兴趣保存到in_memory_store以供将来使用。
首先定义一个辅助函数,用于将用户存储的音乐偏好格式化为可读字符串,以便轻松注入LLM的提示中:
接下来需要一个Pydantic模式来结构化用户配置文件以保存到内存中:
现在定义create_memory节点。此节点将使用LLM-as-a-judge模式来分析对话历史和现有内存,然后使用任何新识别的音乐兴趣更新UserProfile:
将内存节点集成到图中:load_memory节点在验证后立即运行以加载用户偏好;create_memory节点在图结束前运行,保存任何更新。这确保了在每次交互开始时加载内存并在结束时保存内存:
完整的长期内存集成智能体架构如下:
测试这个完全集成的图,我们将提供一个复杂的查询,包括用于验证的标识符和要保存的音乐偏好:
可以直接访问in_memory_store来检查音乐偏好是否已保存:
输出[‘RollingStones’]确认我们的create_memory节点成功提取并将用户的音乐偏好保存到长期内存中。在将来的交互中,load_memory可以加载此信息以提供更个性化的响应。
评估帮助衡量智能体的表现如何,这对于开发至关重要,因为即使是很小的提示或模型更改,LLM的行为也可能发生显著变化。评估为我们提供了一种结构化的方法来捕获故障、比较版本并提高系统可靠性。
评估包含三个核心组件:
数据集是一组测试输入和预期输出;
目标函数是正在测试的应用程序或智能体,它接收输入并返回输出;
评估器是对智能体输出进行评分的工具。
常见的智能体评估类型包括:
最终响应评估检查智能体是否给出了正确的最终答案;
单步评估评估一个步骤(例如,是否选择了正确的工具);
轨迹评估评估智能体为达到答案所采取的完整推理路径。
评估智能体最直接的方法之一是评估其在任务上的整体表现。这就像将智能体视为一个\“黑盒子\“,并简单地评估其最终响应是否成功解决了用户的查询并满足了预期标准。输入是用户的初始查询,输出是智能体最终生成的响应。
首先需要一个包含问题及其相应预期最终响应的数据集。该数据集将作为评估的基准。我们将使用langsmith. Client来创建和上传此数据集:
这里定义了四个示例场景,每个场景都有一个问题(智能体的输入)和一个预期响应(我们认为正确的最终输出)。然后在LangSmith中创建一个数据集,并用这些示例填充它。
接下来定义一个目标函数,该函数封装了智能体(multi_agent_final_graph)应如何运行以进行评估。此函数将从数据集中获取问题作为输入,并返回智能体最终生成的响应:
需要注意的是,我们必须通过向图提供Command(resume=\“\“)来继续通过interrupt()。
也可以定义自定义评估器:
可以使用LLM作为真实答案和AI智能体响应之间的裁判。现在所有组件都已编译完成,让我们运行评估:
当运行此命令且评估完成时,它将输出包含结果的LangSmith仪表板页面。
LangSmith仪表板包含我们的评估结果,显示正确性、最终结果、它们的比较等参数。还有其他评估技术也可以使用,开发者可以在相关文档中找到更详细的介绍。
到目前为止,我们已经使用监督者(Supervisor)方法构建了一个多智能体系统,其中中央智能体管理流程并将任务委派给子智能体。
另一种选择是群体架构(SwarmArchitecture),如LangGraph文档中所述。在群体架构中,智能体相互协作并直接传递任务,没有中央协调器。
监督者架构具有一个指导流量的中央智能体,充当专业子智能体的\“管理者\“角色,遵循分层且更可预测的路径,控制权通常返回给监督者。群体架构由对等智能体组成,它们在没有中央授权的情况下直接相互移交任务,采用分散的和智能体驱动的方式,允许直接、自适应的协作和可能更具弹性的操作。
监督者架构更适合需要明确控制流程和集中决策的场景,而群体架构则在需要灵活协作和分布式处理的环境中表现更佳。选择哪种架构取决于具体的应用需求、复杂性要求和系统的可维护性考虑。
本文详细介绍了使用LangGraph和LangSmith构建企业级多智能体AI系统的完整流程。从基础的单个ReAct智能体开始,逐步构建了一个包含身份验证、人工干预、长期内存管理和性能评估的完整多智能体架构。
通过这个系统化的构建过程,展示了现代AI应用开发中的关键技术要素:模块化的智能体设计、状态管理、工具集成、条件流程控制以及全面的监控评估机制。这些技术组合为构建可靠、可扩展的AI系统提供了坚实的技术基础。
LangGraph和LangSmith的组合为多智能体系统的开发提供了强大的工具支持,从预构建组件的快速原型开发到生产环境的全面监控,都能够满足企业级应用的复杂需求。随着AI技术的不断发展,这种系统化的多智能体架构将在更多领域发挥重要作用。
长按👇关注-数据STUDIO-设为星标,干货速递