摘要
1) 一句话总结 本文介绍了如何使用 LangGraph 框架,通过定义状态、工具、节点和边,从零构建一个具备自主决策能力的足球问答 AI 智能体。
2) 关键要点
- 技术演进:AI 智能体通过引入状态(State)、决策(Decision-making)和记忆(Memory),解决了单纯“LLM + RAG”在处理复杂、未知路径问题时的局限性。
- LangGraph 核心机制:基于图结构构建智能体,其中“节点(Nodes)”代表随时间演变的状态,“边(Edges)”定义节点间的控制流与转换条件。
- 环境初始化:构建前需加载环境变量(如
OPENAI_API_KEY),示例中初始化了一个基于 GPT-5-nano 模型的智能体。 - 状态定义(State):通过继承
pydantic.BaseModel定义状态类(如PlayerState),作为在图节点间传递和处理的数据实体。 - 工具定义(Tools):使用
@tool装饰器定义了 3 个模拟工具(获取俱乐部/国家队、球衣号码、FIFA 评分);必须提供清晰的参数名和文档字符串(Docstrings),以便智能体自主决定调用。 - 常规节点定义:定义了提取球员姓名和检索出场时间等常规节点,这些节点不由智能体自主调度,而是按固定流程执行。
- 规划器与条件边:规划器(Planner)根据上下文自主决定调用 0 到 3 个工具;通过
add_conditional_edges()方法及路由函数(如route_tools())处理其动态输出逻辑。 - 图的构建与运行:使用
StateGraph依次添加节点(add_node)、有向边(add_edge)、起点/终点(START/END),编译后通过invoke方法传入包含question字段的字典即可运行。
3) 风险与不足
- 适用性风险:对于简单的任务或问题,使用 LangGraph 框架可能会显得过于繁琐和大材小用(更适合具有大型图结构的复杂项目)。
- 示例局限性:为简化说明,教程中的工具仅使用了模拟数据,未涉及生产环境中从真实外部数据源检索数据的实际操作。
正文
“AI 智能体(AI Agent)”是当下最热门的概念之一。在大型语言模型(LLM)的热潮之后,人们逐渐意识到,尽管最新的 LLM 能力令人惊叹,但它们只能执行经过明确训练的任务。从这个意义上说,普通的 LLM 缺乏能够让它们处理知识范围之外事务的工具。
背景:从 RAG 到智能体
为了解决 LLM 的局限性,**检索增强生成(RAG)**技术应运而生。它通过从外部数据源检索额外的上下文并将其注入到提示词中,使 LLM 能够掌握更多信息。可以说,RAG 让 LLM 变得更加“博学”。然而,面对更复杂的问题,当解决路径无法提前预知时,单纯的“LLM + RAG”方法依然会失效。
智能体(Agents)则是围绕 LLM 构建的一个卓越概念,它引入了状态(State)、决策(Decision-making)和记忆(Memory)。智能体可以被视为一组预定义的工具集合,它能够在生成最终答案之前,分析结果并将其存储在记忆中以供后续使用。
LangGraph 核心概念
LangGraph 是一个用于创建智能体的流行框架。顾名思义,它是通过包含“节点(Nodes)”和“边(Edges)”的图(Graphs)来构建智能体的:
- 节点(Nodes): 代表智能体的状态,该状态会随着时间的推移而演变。
- 边(Edges): 通过指定节点之间的转换规则和条件来定义控制流。
为了更好地在实践中理解 LangGraph,我们将通过一个详细的示例来进行说明。虽然对于下面这个简单的问题来说,使用 LangGraph 可能显得有些繁琐,但它在处理具有大型图结构的复杂问题时,通常能发挥巨大的作用。
实战:构建足球问答智能体
我们将构建一个能够回答有关足球问题的智能体,其思考过程将基于检索到的球员统计数据。
1. 环境准备与初始化
首先,我们需要安装并导入必要的库和模块。接着,创建一个 .env 文件并在其中添加 OPENAI_API_KEY,通过 load_dotenv() 将环境变量加载到系统中。
此外,我们可以准备一个辅助函数,用于直观地显示构建好的图结构。
最后,使用简单的命令初始化一个基于 GPT-5-nano 的智能体。
2. 定义状态(State)
我们需要定义一个状态,它是一个包含 LLM 所需的所有球员信息的实体。
为了定义状态,我们需要编写一个继承自 pydantic.BaseModel 的类(例如 PlayerState)。当在 LangGraph 的节点之间移动时,每个节点都会接收一个 PlayerState 实例作为输入,并指定如何处理该状态。我们的任务就是定义这些状态的具体处理方式。
3. 定义工具(Tools)
工具可以粗略地理解为智能体可以调用的附加函数,用于检索回答用户问题所需的信息。为了简化示例,我们将使用模拟数据而不是从外部源检索真实数据(后者通常是生产应用程序的做法)。
定义工具需要编写带有 @tool 装饰器的函数。清晰的参数名称和函数文档字符串(Docstrings)至关重要,因为智能体会根据输入上下文参考它们来决定是否调用该工具。
我们将定义以下三个工具:
- 工具一: 根据姓名返回球员的俱乐部和国家队信息。
- 注:为了实现清晰的职责分离并方便在多个图节点中复用,我们会将这个工具放在另一个兼容 LangGraph 框架的函数(如
fetch_player_information)中。外层函数接收状态参数并提取姓名,然后调用在参数级别操作的工具函数。
- 注:为了实现清晰的职责分离并方便在多个图节点中复用,我们会将这个工具放在另一个兼容 LangGraph 框架的函数(如
- 工具二: 检索球员的球衣号码。
- 工具三: 获取球员的 FIFA 评分。
4. 定义其他图节点
接下来,我们编写几个用于检索外部数据的图节点函数。这些节点不会像前面那样被标记为“工具”,这意味着它们不是由智能体自主决定是否调用的:
- 一个用于检索过去几个赛季出场时间的节点。
- 一个从用户问题中提取球员姓名的节点。
5. 规划器与最终生成节点
现在进入最有趣的部分。基于前面定义的三个工具,我们可以创建一个规划器(Planner)。规划器会要求智能体根据情境上下文,自主决定调用哪个特定工具来生成足球球员的摘要。工具下方的文档字符串在这里发挥了重要作用,它们为智能体提供了关于工具的额外上下文。
最后,我们定义最终的图节点。它将接收从前面步骤中检索到的多个字段,并调用 LLM 生成最终的摘要。
图的构建与编译
现在我们拥有了构建图的所有元素:
- 初始化图: 使用
StateGraph构造函数初始化图。 - 添加节点: 使用
add_node()方法逐个添加节点。该方法接收两个参数:分配给节点的名称(字符串),以及与该节点关联的、仅接收图状态作为参数的可调用函数。 - 添加边: LangGraph 中的边是有向的,通过
add_edge()方法添加,需指定起点和终点节点的名称。 - 处理条件边(规划器): 规划器的行为与其他节点略有不同,它可以返回包含 0 到 3 个输出节点的
selected_tools字段。为此,我们需要使用add_conditional_edges()方法,它接收三个参数:- 规划器节点的名称;
- 一个可调用函数(接收 LangGraph 节点并返回一个字符串列表,指示应调用的节点名称列表);
- 一个将第二个参数中的字符串映射到节点名称的字典。
- 在我们的例子中,我们将定义一个
route_tools()节点,简单地将state.selected_tools字段作为规划器函数的结果返回。
- 设置起点与终点: 使用 LangGraph 的常量
START和END来定义图的起点和终点。 - 编译与可视化: 最后一步是编译图。我们可以选择使用前面提到的辅助函数将其可视化。
运行示例
现在我们终于可以使用构建好的图了!通过调用 invoke 方法,并传入一个包含自定义用户问题的字典(带有 question 字段)即可运行。
运行后,我们可以观察图的完整状态,并分析智能体为了生成最终答案所选择的工具。最终生成的摘要结果将会非常出色!
总结
AI 智能体为 LLM 开启了新的篇章。配备了先进的工具和决策能力后,我们现在拥有了解决复杂任务的巨大潜力。
本文通过一个示例介绍了 LangGraph——构建智能体最受欢迎的框架之一。它的简单性和优雅性使得构建复杂的决策链成为可能。虽然对于本文的简单示例来说,使用 LangGraph 可能显得有些大材小用,但对于状态和图结构复杂得多的大型项目而言,它将变得极其有用。