LangGraph多智能体系统设计:从需求到实现的端到端指南
LangGraph多智能体系统设计:从需求到实现的端到端指南
1. 引入与连接
1.1 从科幻到现实:多智能体系统的崛起
想象一下这样的场景:你早上醒来,智能助手已经根据你的日程安排、交通状况和天气情况,为你规划了最佳出行路线,同时已经让咖啡机开始工作,还根据你的健康数据准备好了早餐建议。在工作中,多个AI助手协同工作——一个负责数据分析,另一个负责撰写报告,第三个负责与客户沟通,它们无缝协作,高效完成任务。这不是科幻电影中的场景,而是正在快速变为现实的多智能体系统应用。
近年来,大型语言模型(LLMs)的快速发展为构建智能、灵活的多智能体系统提供了前所未有的可能性。然而,构建这样的系统并非易事,需要解决协调、通信、状态管理等一系列复杂问题。这正是LangGraph框架应运而生的背景。
1.2 为什么我们需要LangGraph?
在构建基于LLM的应用时,开发者通常会遇到以下挑战:
- 状态管理的复杂性:传统的LLM应用往往是无状态的,但多智能体系统需要处理复杂的状态转换和持久化。
- 智能体间的协调:多个智能体如何有效通信、分工合作、避免冲突?
- 工作流的灵活性:真实世界的任务往往不是线性的,需要支持分支、循环、条件判断等复杂流程。
- 错误处理与恢复:当某个智能体失败时,系统如何优雅地恢复而不是完全崩溃?
LangChain团队推出的LangGraph框架正是为了解决这些问题而设计的。它提供了一种结构化的方式来定义状态、节点(智能体)和边(智能体间的连接),使开发者能够更容易地构建复杂的多智能体系统。
1.3 本文将带你探索什么
在这篇文章中,我们将从零开始,全面深入地探索LangGraph多智能体系统的设计与实现。我们将:
- 建立对LangGraph核心概念的直观理解
- 深入探讨其工作原理和底层机制
- 通过实际案例学习如何设计和实现多智能体系统
- 分析LangGraph的优势和局限性
- 展望多智能体系统的未来发展趋势
无论你是刚接触AI应用开发的新手,还是有丰富经验的工程师,这篇指南都将为你提供有价值的见解和实用的知识。
1.4 学习路径预览
我们将按照以下路径展开我们的探索:
- 基础层:首先了解LangGraph的核心概念和基本组件
- 连接层:探索这些概念如何相互关联,形成完整的系统
- 深度层:深入理解LangGraph的工作原理和实现细节
- 整合层:通过实际项目将所有知识整合起来
让我们开始这段激动人心的学习之旅吧!
2. 概念地图
2.1 核心概念与关键术语
在深入探索LangGraph之前,让我们先建立一个清晰的概念框架。以下是LangGraph中的核心概念:
| 概念 | 简短定义 |
|---|---|
| State(状态) | 系统的全局数据结构,存储所有智能体共享的信息 |
| Node(节点) | 执行特定任务的智能体或函数,接收并返回状态 |
| Edge(边) | 定义节点间的连接关系,控制信息流和执行顺序 |
| Graph(图) | 由节点和边组成的完整工作流定义 |
| Conditional Edge(条件边) | 根据当前状态决定下一个执行节点的边 |
| Entry Point(入口点) | 图执行的起始节点 |
| Finish Node(结束节点) | 标志图执行完成的特殊节点 |
2.2 LangGraph在AI开发生态中的位置
LangGraph不是一个孤立的框架,而是LangChain生态系统的重要组成部分。理解它与其他组件的关系有助于我们更好地把握其定位:
- LangChain:提供了与各种LLM、工具和数据源交互的标准化接口
- LangGraph:专注于构建有状态、多步骤的应用程序工作流
- LangServe:将LangChain应用部署为API服务
- LangSmith:用于调试、测试和监控LangChain应用
这种模块化设计使开发者可以根据需要选择和组合不同的组件,而不必受限于单一框架。
2.3 核心概念关系图
让我们通过一个概念图来可视化这些核心概念之间的关系:
2.4 LangGraph与其他工作流工具的对比
为了更好地理解LangGraph的独特价值,让我们将其与其他常见的工作流工具进行比较:
| 特性 | LangGraph | Apache Airflow | Temporal | Prefect |
|---|---|---|---|---|
| 设计重点 | LLM应用的多智能体协调 | 数据管道编排 | 持久化工作流执行 | 现代数据工作流 |
| 状态管理 | 内置、专为LLM设计 | 有限、主要用于任务状态 | 强大、事件溯源 | 灵活、依赖外部存储 |
| 学习曲线 | 低(基于Python) | 中高 | 中高 | 中 |
| 动态性 | 高(支持条件分支、循环) | 中(主要是有向无环图) | 高 | 中高 |
| 工具集成 | 深度集成LangChain生态 | 广泛的插件系统 | 有限的内置集成 | 良好的现代数据工具集成 |
| 适用场景 | 多智能体LLM应用、对话系统 | ETL、定时任务 | 长期运行的业务流程 | 数据科学工作流 |
从这个对比中可以看出,LangGraph的独特优势在于它专为LLM应用和多智能体系统设计,提供了简洁而强大的状态管理和工作流定义方式。
3. 基础理解
3.1 什么是LangGraph?一个生活化的类比
让我们从一个生活化的类比开始,来理解LangGraph到底是什么。想象你正在组织一场派对,你需要多个朋友帮忙:
- 小明负责采购食物和饮料
- 小红负责装饰场地
- 小刚负责播放音乐和控制氛围
- 小丽负责接待客人
这些朋友就像是LangGraph中的"节点"(Node),每个人都有自己的专长和任务。但要让派对成功,仅仅有这些分工是不够的,还需要协调:
- 小明需要先采购,然后小红才能开始布置(需要采购装饰品)
- 场地布置好后,小刚才能设置音响设备
- 一切准备就绪后,小丽才开始接待客人
- 如果小明忘记买冰块,他可能需要返回商店(一个条件分支)
这些协调规则就是LangGraph中的"边"(Edge)。而整个派对的状态(如"已采购的物品"、“场地布置进度”、“已到达的客人"等)就是LangGraph中的"状态”(State)。
LangGraph本质上就是这样一个"派对组织者",它帮助你定义谁(节点)做什么,按什么顺序(边)做,以及如何共享信息(状态)。
3.2 LangGraph的核心组件详解
现在让我们更正式地介绍LangGraph的三个核心组件:状态(State)、节点(Node)和边(Edge)。
3.2.1 状态(State):系统的共享记忆
状态是LangGraph中最基础也是最重要的概念。它就像是系统的"共享内存",所有节点都可以读取和修改它。
在最简单的形式中,状态可以是一个Python字典,但LangGraph也支持更复杂的状态定义,包括TypedDict、Pydantic模型等。
from typing import TypedDict
class AgentState(TypedDict):
# 用户的原始输入
input: str
# 对话历史
chat_history: list
# 当前步骤的思考
current_thought: str
# 最终答案
answer: str
这种状态定义方式有几个重要优势:
- 类型安全:使用类型提示可以帮助开发者避免常见的错误
- 清晰的契约:明确定义了系统中共享的数据结构
- 易于扩展:可以轻松添加新的字段而不破坏现有代码
3.2.2 节点(Node):执行特定任务的智能体
节点是LangGraph中的"工作者",它们接收当前状态,执行某些操作,然后返回更新后的状态。节点可以是简单的Python函数,也可以是复杂的LangChain链或智能体。
让我们看一个简单的节点例子:
def analyze_input(state: AgentState) -> AgentState:
"""分析用户输入,理解用户意图"""
user_input = state["input"]
# 这里可以调用LLM来分析输入
# 为了演示,我们使用一个简单的模拟
thought = f"用户输入了: {user_input}。我需要确定这是什么类型的请求。"
return {
**state,
"current_thought": thought
}
这个节点接收当前状态,提取用户输入,进行某种分析(在实际应用中可能会调用LLM),然后返回更新后的状态。
3.2.3 边(Edge):定义节点间的连接和执行顺序
边定义了节点之间的连接关系,控制着执行流程。LangGraph中有两种主要类型的边:
- 普通边:简单地将一个节点连接到另一个节点
- 条件边:根据当前状态决定下一个执行的节点
让我们看一个条件边的例子:
from typing import Literal
def decide_next_step(state: AgentState) -> Literal["search", "answer", "clarify"]:
"""根据当前状态决定下一步做什么"""
thought = state["current_thought"]
# 在实际应用中,这里可能会调用LLM来做决策
# 这里我们使用简单的规则模拟
if "?" in thought:
return "clarify"
elif "search" in thought.lower():
return "search"
else:
return "answer"
这个函数根据当前状态中的思考内容,决定下一步是执行搜索、直接回答还是追问用户。
3.3 构建你的第一个LangGraph应用
理论知识固然重要,但没有什么比亲手构建一个应用更能帮助理解了。让我们构建一个简单的问答系统,它可以决定是直接回答用户的问题,还是需要先搜索信息。
3.3.1 环境准备
首先,我们需要安装必要的依赖:
pip install langgraph langchain-openai python-dotenv
然后,我们需要设置OpenAI API密钥。可以创建一个.env文件:
OPENAI_API_KEY=your-api-key-here
3.3.2 定义状态
首先,我们定义系统的状态:
from typing import TypedDict, Annotated, List, Literal
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage
from langgraph.graph import StateGraph, END
import operator
class AgentState(TypedDict):
# 消息历史
messages: Annotated[List[BaseMessage], operator.add]
# 下一步要执行的操作
next_step: str
# 是否需要搜索
need_search: bool
# 搜索结果
search_results: str
# 最终答案
answer: str
这里我们使用了Annotated和operator.add,这是LangGraph的一个强大特性,它允许我们定义如何合并来自不同节点的状态更新。在这个例子中,我们使用operator.add来追加新消息到消息历史中。
3.3.3 定义节点
接下来,我们定义几个节点:
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
# 初始化LLM
llm = ChatOpenAI(model="gpt-3.5-turbo")
def analyze_query(state: AgentState) -> AgentState:
"""分析用户查询,决定是否需要搜索"""
messages = state["messages"]
# 创建一个提示模板,让LLM决定是否需要搜索
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个助手。请分析用户的问题,决定是否需要搜索最新信息。"
"如果需要搜索,请返回'search',否则返回'answer'。"
"只返回一个词,不要有其他内容。"),
("human", "{query}")
])
chain = prompt | llm
response = chain.invoke({"query": messages[-1].content})
need_search = response.content.strip().lower() == "search"
return {
"need_search": need_search,
"next_step": "search" if need_search else "answer"
}
def search_web(state: AgentState) -> AgentState:
"""模拟搜索网络"""
# 在实际应用中,这里可以调用真实的搜索API
# 这里我们使用模拟数据
query = state["messages"][-1].content
search_results = f"搜索结果: 关于'{query}'的最新信息显示..."
return {
"search_results": search_results,
"next_step": "answer"
}
def generate_answer(state: AgentState) -> AgentState:
"""根据可用信息生成答案"""
messages = state["messages"]
search_results = state.get("search_results", "")
# 创建提示模板
if search_results:
system_message = "你是一个助手。请根据搜索结果回答用户的问题。"
human_message = f"用户问题: {messages[-1].content}\n\n搜索结果: {search_results}"
else:
system_message = "你是一个助手。请回答用户的问题。"
human_message = messages[-1].content
prompt = ChatPromptTemplate.from_messages([
("system", system_message),
("human", human_message)
])
chain = prompt | llm
response = chain.invoke({})
return {
"messages": [AIMessage(content=response.content)],
"answer": response.content
}
3.3.4 定义边和图
现在,我们将这些节点连接起来:
# 创建图
workflow = StateGraph(AgentState)
# 添加节点
workflow.add_node("analyze_query", analyze_query)
workflow.add_node("search_web", search_web)
workflow.add_node("generate_answer", generate_answer)
# 设置入口点
workflow.set_entry_point("analyze_query")
# 添加条件边
def choose_next_step(state: AgentState) -> Literal["search_web", "generate_answer"]:
return state["next_step"]
workflow.add_conditional_edges(
"analyze_query",
choose_next_step,
{
"search": "search_web",
"answer": "generate_answer"
}
)
# 添加普通边
workflow.add_edge("search_web", "generate_answer")
workflow.add_edge("generate_answer", END)
# 编译图
app = workflow.compile()
3.3.5 测试应用
最后,让我们测试一下我们的应用:
# 测试一个不需要搜索的问题
print("测试1: 不需要搜索的问题")
result = app.invoke({"messages": [HumanMessage(content="法国的首都是哪里?")]})
print(f"答案: {result['answer']}\n")
# 测试一个需要搜索的问题
print("测试2: 需要搜索的问题")
result = app.invoke({"messages": [HumanMessage(content="今天的天气怎么样?")]})
print(f"答案: {result['answer']}")
恭喜!你已经构建了你的第一个LangGraph应用。虽然这个例子很简单,但它展示了LangGraph的核心概念和工作原理。
3.4 常见误解澄清
在学习LangGraph的过程中,有几个常见的误解需要澄清:
-
误解:LangGraph是另一个LLM框架。
事实:LangGraph不是一个LLM框架,而是一个用于构建有状态、多步骤应用程序的工作流框架。它与LLM框架(如LangChain)配合使用,而不是替代它们。 -
误解:LangGraph只能用于构建多智能体系统。
事实:虽然LangGraph非常适合构建多智能体系统,但它也可以用于构建任何需要状态管理和复杂工作流的应用程序,如对话系统、业务流程自动化等。 -
误解:使用LangGraph会增加应用的复杂性。
事实:对于简单的应用,LangGraph可能确实显得有些"大材小用"。但对于需要状态管理、条件分支、错误恢复等功能的复杂应用,LangGraph实际上可以大大简化代码结构,提高可维护性。 -
误解:LangGraph只能与LangChain一起使用。
事实:虽然LangGraph与LangChain集成得很好,但它也可以独立使用,或者与其他框架一起使用。节点可以是任何Python函数,不一定是LangChain组件。
4. 层层深入
4.1 第一层:LangGraph的基本原理与运作机制
现在我们已经有了LangGraph的基础理解,让我们深入一层,探索它的基本原理和运作机制。
4.1.1 状态管理的内部工作原理
LangGraph的状态管理是其核心特性之一。让我们来看看它是如何工作的。
当我们定义一个状态类型时,LangGraph会做几件事:
- 验证状态结构:确保所有节点的输入和输出符合状态定义
- 管理状态更新:根据注释中的合并策略(如
operator.add)合并状态更新 - 提供类型安全:在开发时捕获类型错误
让我们通过一个更复杂的例子来理解状态合并:
from typing import TypedDict, Annotated, List
from langgraph.graph import StateGraph, END
import operator
class ComplexState(TypedDict):
# 使用operator.add合并 - 列表会被追加
messages: Annotated[List[str], operator.add]
# 没有注释 - 后续更新会覆盖之前的值
current_topic: str
# 使用自定义合并函数
counter: Annotated[int, lambda a, b: a + b if a is not None else b]
# 自定义合并函数示例
def merge_counters(current: int, update: int) -> int:
"""合并计数器,如果当前值为None,则使用更新值"""
return current + update if current is not None else update
# 现在我们可以在状态定义中使用这个函数
class CustomMergeState(TypedDict):
counter: Annotated[int, merge_counters]
这种灵活的状态合并机制是LangGraph的一个强大特性,它允许我们根据不同的数据类型和使用场景选择最合适的合并策略。
4.1.2 图执行流程详解
当我们调用app.invoke()时,LangGraph内部会执行以下步骤:
- 初始化状态:创建初始状态对象
- 确定入口点:从设置的入口节点开始执行
- 执行节点:调用当前节点的函数,传入当前状态
- 更新状态:合并节点返回的状态更新到当前状态
- 确定下一个节点:根据边的定义确定下一个要执行的节点
- 重复执行:重复步骤3-5,直到到达END节点
让我们通过一个流程图来可视化这个过程:
这个看似简单的流程实际上包含了许多微妙但强大的特性,如条件分支、循环、状态持久化等,我们将在接下来的部分深入探讨。
4.2 第二层:细节、例外与特殊情况
掌握了基本原理后,让我们来探索一些更高级的主题,处理例外情况和特殊需求。
4.2.1 处理循环:创建迭代工作流
在许多实际应用中,我们需要处理循环,例如让智能体反复思考直到找到满意的答案,或者多次尝试解决一个问题。LangGraph对此提供了原生支持。
让我们创建一个简单的"反思"智能体,它会反复改进自己的答案:
from typing import TypedDict, Annotated, Literal
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage
llm = ChatOpenAI(model="gpt-3.5-turbo")
class ReflectionState(TypedDict):
messages: Annotated[list[BaseMessage], operator.add]
original_question: str
current_answer: str
reflection_count: int
max_reflections: int
is_satisfied: bool
def generate_answer(state: ReflectionState) -> ReflectionState:
"""生成初始答案或改进答案"""
if state["reflection_count"] == 0:
# 初始答案生成
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个有帮助的助手。请回答用户的问题。"),
("human", "{question}")
])
else:
# 改进答案
last_message = state["messages"][-1]
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个有帮助的助手。请根据反馈改进你的答案。"),
("human", f"原始问题: {state['original_question']}\n\n之前的答案: {state['current_answer']}\n\n反馈: {last_message.content}")
])
chain = prompt | llm
response = chain.invoke({"question": state["original_question"]})
return {
"messages": [AIMessage(content=response.content)],
"current_answer": response.content,
"reflection_count": state["reflection_count"] + 1
}
def reflect_on_answer(state: ReflectionState) -> ReflectionState:
"""反思答案,提出改进建议"""
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个批判性思考者。请分析这个答案,提出改进建议。"
"如果你认为答案已经足够好,请说'满意'。"),
("human", f"问题: {state['original_question']}\n\n答案: {state['current_answer']}")
])
chain = prompt | llm
response = chain.invoke({})
is_satisfied = "满意" in response.content
return {
"messages": [HumanMessage(content=response.content)],
"is_satisfied": is_satisfied
}
def should_continue(state: ReflectionState) -> Literal["generate_answer", END]:
"""决定是否继续反思循环"""
if state["is_satisfied"] or state["reflection_count"] >= state["max_reflections"]:
return END
else:
return "generate_answer"
# 构建图
workflow = StateGraph(ReflectionState)
workflow.add_node("generate_answer", generate_answer)
workflow.add_node("reflect_on_answer", reflect_on_answer)
workflow.set_entry_point("generate_answer")
workflow.add_edge("generate_answer", "reflect_on_answer")
workflow.add_conditional_edges("reflect_on_answer", should_continue)
app = workflow.compile()
# 测试
result = app.invoke({
"original_question": "如何提高编程技能?",
"reflection_count": 0,
"max_reflections": 3,
"is_satisfied": False
})
print(f"最终答案 (经过{result['reflection_count']}次反思):\n{result['current_answer']}")
这个例子展示了如何在LangGraph中创建循环工作流。智能体先生成一个答案,然后反思这个答案,决定是否需要改进,如果需要,就再次生成答案,如此反复,直到满意或达到最大迭代次数。
4.2.2 错误处理与恢复机制
在任何实际应用中,错误处理都是一个重要的考虑因素。LangGraph提供了几种处理错误的机制:
- 节点级错误处理:在节点内部捕获和处理异常
- 图级错误处理:使用
on_completion和on_error回调 - 回退机制:定义备用节点或路径
让我们看一个实现错误处理的例子:
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, END
from langgraph.prebuilt import ToolNode
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
import operator
import random
llm = ChatOpenAI(model="gpt-3.5-turbo")
class ErrorHandlingState(TypedDict):
messages: Annotated[list, operator.add]
error_count: int
max_errors: int
success: bool
# 定义一个可能会失败的工具
@tool
def flaky_tool(query: str) -> str:
"""一个不稳定的工具,有时会失败"""
if random.random() < 0.5: # 50%的失败率
raise ValueError("工具调用失败!")
return f"工具处理结果: {query}"
tools = [flaky_tool]
tool_node = ToolNode(tools)
def agent_node(state: ErrorHandlingState) -> ErrorHandlingState:
"""智能体节点,可能会调用工具"""
try:
# 这里简化处理,实际上应该使用LLM决定是否调用工具
# 为了演示,我们直接尝试调用工具
result = flaky_tool.invoke("测试查询")
return {
"messages": [result],
"success": True
}
except Exception as e:
return {
"messages": [f"错误: {str(e)}"],
"error_count": state["error_count"] + 1,
"success": False
}
def fallback_node(state: ErrorHandlingState) -> ErrorHandlingState:
"""回退节点,当主路径失败时使用"""
return {
"messages": ["使用备用方案处理..."],
"success": True
}
def decide_next_step(state: ErrorHandlingState) -> str:
"""决定下一步做什么"""
if state["success"]:
return END
elif state["error_count"] >= state["max_errors"]:
return "fallback"
else:
return "agent"
# 构建图
workflow = StateGraph(ErrorHandlingState)
workflow.add_node("agent", agent_node)
workflow.add_node("fallback", fallback_node)
workflow.set_entry_point("agent")
workflow.add_conditional_edges("agent", decide_next_step, {
END: END,
"agent": "agent",
"fallback": "fallback"
})
workflow.add_edge("fallback", END)
app = workflow.compile()
# 测试
result = app.invoke({
"error_count": 0,
"max_errors": 3,
"success": False
})
print(f"执行结果: {'成功' if result['success'] else '失败'}")
print(f"错误次数: {result['error_count']}")
print("消息历史:")
for msg in result["messages"]:
print(f"- {msg}")
这个例子展示了如何实现几种错误处理策略:
- 重试机制:在放弃前尝试多次
- 回退机制:当主路径失败时使用备用方案
- 优雅降级:即使在错误情况下也能提供某种程度的功能
4.3 第三层:底层逻辑与理论基础
现在让我们深入LangGraph的底层,探索它的理论基础和设计思想。
4.3.1 状态机理论与LangGraph
LangGraph的设计受到了有限状态机(FSM)和更广义的状态转换系统的启发。让我们回顾一下这些理论概念,以及它们如何应用于LangGraph。
有限状态机是一种计算模型,它由以下部分组成:
- 一组有限的状态
- 一组输入符号
- 一个初始状态
- 一个转换函数,它决定在给定当前状态和输入符号时,下一个状态是什么
LangGraph可以看作是一种增强版的有限状态机,具有以下扩展:
- 状态不仅是离散的符号,而是可以包含复杂数据结构
- 转换不仅依赖于输入,还可以依赖于整个当前状态
- 转换可以执行任意计算,而不仅仅是状态转换
- 支持条件分支、循环等更复杂的控制流
这种将状态机理论与现代编程语言特性结合的设计,使LangGraph既具有理论的严谨性,又具有实践的灵活性。
4.3.2 消息传递与Actor模型
LangGraph的节点间通信也受到了Actor模型的启发。Actor模型是一种并行计算的数学模型,它将"actor"作为并行计算的基本单元。每个actor具有以下特性:
- 可以接收消息
- 可以发送消息给其他actor
- 可以创建新的actor
- 可以改变自己的内部状态
- 可以决定如何处理下一条消息
虽然LangGraph不是一个纯Actor模型实现,但它借鉴了许多Actor模型的思想:
- 节点类似于actor,封装了状态和行为
- 状态传递类似于消息传递
- 每个节点独立执行,通过状态变化进行通信
这种设计使LangGraph天然适合构建分布式系统和并发应用,尽管当前版本主要集中在单机执行上。
4.4 第四层:高级应用与拓展思考
在掌握了LangGraph的核心概念和工作原理后,让我们探索一些更高级的应用场景和拓展可能性。
4.4.1 构建多智能体协作系统
LangGraph最强大的应用之一是构建多智能体协作系统,其中多个专门的智能体协同工作解决复杂问题。
让我们设计一个简单的多智能体系统,包含以下智能体:
- 研究智能体:负责收集和分析信息
- 写作智能体:负责撰写内容
- 编辑智能体:负责审阅和改进内容
from typing import TypedDict, Annotated, List, Literal, Dict
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.messages import BaseMessage
import operator
llm = ChatOpenAI(model="gpt-3.5-turbo")
class MultiAgentState(TypedDict):
# 原始任务
task: str
# 各智能体的输出
researcher_output: str
writer_output: str
editor_output: str
# 消息历史
messages: Annotated[List[BaseMessage], operator.add]
# 编辑反馈次数
edit_rounds: int
# 最大编辑次数
max_edit_rounds: int
# 是否批准最终内容
approved: bool
def researcher_agent(state: MultiAgentState) -> MultiAgentState:
"""研究智能体:收集和分析信息"""
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个研究专家。请针对给定的主题收集和总结关键信息。"),
("human", "{task}")
])
chain = prompt | llm
response = chain.invoke({"task": state["task"]})
return {
"researcher_output": response.content,
"messages": [("system", f"研究智能体完成工作: {response.content[:100]}...")]
}
def writer_agent(state: MultiAgentState) -> MultiAgentState:
"""写作智能体:基于研究结果撰写内容"""
# 检查是否有编辑反馈
editor_feedback = state.get("editor_output", "")
if editor_feedback and state["edit_rounds"] > 0:
# 根据编辑反馈修改
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个专业作家。请根据编辑反馈修改你的文章。"),
("human", f"原始文章: {state['writer_output']}\n\n编辑反馈: {editor_feedback}")
])
else:
# 初始写作
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个专业作家。请根据研究结果撰写一篇引人入胜的文章。"),
("human", f"主题: {state['task']}\n\n研究结果: {state['researcher_output']}")
])
chain = prompt | llm
response = chain.invoke({})
return {
"writer_output": response.content,
"messages": [("system", f"写作智能体完成工作: {response.content[:100]}...")],
"edit_rounds": state["edit_rounds"] + 1 if state["edit_rounds"] else 1
}
def editor_agent(state: MultiAgentState) -> MultiAgentState:
"""编辑智能体:审阅和改进内容"""
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个专业编辑。请审阅以下文章,提供改进建议。"
"如果你认为文章已经足够好,请明确说'批准'。"),
("human", f"主题: {state['task']}\n\n文章: {state['writer_output']}")
])
chain = prompt | llm
response = chain.invoke({})
approved = "批准" in response.content
return {
"editor_output": response.content,
"approved": approved,
"messages": [("system", f"编辑智能体完成工作: {response.content[:100]}...")]
}
def decide_next_step(state: MultiAgentState) -> Literal["writer", END]:
"""决定下一步做什么"""
if state["approved"] or state["edit_rounds"] >= state["max_edit_rounds"]:
return END
else:
return "writer"
# 构建图
workflow = StateGraph(MultiAgentState)
workflow.add_node("researcher", researcher_agent)
workflow.add_node("writer", writer_agent)
workflow.add_node("editor", editor_agent)
workflow.set_entry_point("researcher")
workflow.add_edge("researcher", "writer")
workflow.add_edge("writer", "editor")
workflow.add_conditional_edges("editor", decide_next_step)
app = workflow.compile()
# 测试
result = app.invoke({
"task": "人工智能对未来教育的影响",
"edit_rounds": 0,
"max_edit_rounds": 3,
"approved": False
})
print(f"最终结果: {'已批准' if result['approved'] else f'达到最大编辑次数{result[\"max_edit_rounds\"]}'}")
print(f"\n最终文章:\n{result['writer_output']}")
这个例子展示了如何使用LangGraph构建一个多智能体协作系统,其中每个智能体都有自己的专长,它们协同工作完成一个复杂任务。
4.4.2 持久化与长期记忆
在许多实际应用中,我们需要跨会话持久化状态,或者为智能体提供长期记忆能力。LangGraph本身不直接提供持久化功能,但它的设计使其很容易与各种持久化解决方案集成。
让我们看一个如何为LangGraph应用添加持久化的例子:
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.sqlite import SqliteSaver
from langchain_openai import ChatOpenAI
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage
import operator
llm = ChatOpenAI(model="gpt-3.5-turbo")
class PersistentState(TypedDict):
messages: Annotated[list[BaseMessage], operator.add]
user_preferences: dict
session_count: int
def chat_node(state: PersistentState) -> PersistentState:
"""处理用户消息并生成回复"""
# 构建提示,包含用户偏好
system_message = "你是一个有帮助的助手。"
if state.get("user_preferences"):
prefs = state["user_preferences"]
system_message += f" 用户偏好: {prefs}"
# 创建消息历史
messages = [("system", system_message)] + state["messages"]
# 调用LLM
response = llm.invoke(messages)
# 更新会话计数
new_session_count = state.get("session_count", 0) + 1
return {
"messages": [response],
"session_count": new_session_count
}
# 构建图
workflow = StateGraph(PersistentState)
workflow.add_node("chat", chat_node)
workflow.set_entry_point("chat")
workflow.add_edge("chat", END)
# 创建SQLite检查点保存器
memory = SqliteSaver.from_conn_string(":memory:") # 或者使用文件路径: "checkpoints.sqlite"
# 编译图,包含检查点
app = workflow.compile(checkpointer=memory)
# 使用持久化状态
config = {"configurable": {"thread_id": "user-123"}}
# 第一次交互
print("第一次交互:")
result = app.invoke(
{"messages": [HumanMessage(content="你好,我叫张三")], "session_count": 0},
config=config
)
print(f"AI回复: {result['messages'][-1].content}")
print(f"会话计数: {result['session_count']}\n")
# 第二次交互 - 注意我们不需要传递所有历史
print("第二次交互:")
result = app.invoke(
{"messages": [HumanMessage(content="我叫什么名字?")]},
config=config
)
print(f"AI回复: {result['messages'][-1].content}")
print(f"会话计数: {result['session_count']}")
这个例子展示了如何使用LangGraph的检查点功能来持久化状态。通过使用SqliteSaver,我们可以将状态保存到SQLite数据库中,实现跨会话的状态持久化。
5. 多维透视
5.1 历史视角:LangGraph的发展脉络
要全面理解LangGraph,我们需要将其放在AI应用开发的历史背景中。让我们来看看LangGraph是如何发展而来的。
5.1.1 从链式调用到图结构
在LLM应用开发的早期阶段,最简单的应用形式是"链式调用",即将多个LLM调用按顺序连接起来,前一个调用的输出作为后一个调用的输入。LangChain最初就是为了简化这种链式调用而设计的。
然而,随着应用变得越来越复杂,开发者很快发现简单的链式调用不足以满足需求:
- 需要处理条件分支
- 需要管理复杂的状态
- 需要支持循环和重试
- 需要多个智能体间的协调
这些需求促使了从"链式"到"图式"的转变。图结构提供了更大的灵活性,可以表示更复杂的控制流和状态管理。
5.1.2 LangGraph的诞生
LangGraph由LangChain团队于2023年底推出,旨在解决构建复杂LLM应用时遇到的挑战。它的设计借鉴了多个领域的思想:
- 工作流编排系统(如Airflow、Temporal)
- 状态机理论
- Actor模型
- 函数式编程
自推出以来,LangGraph经历了快速的发展,添加了许多重要功能:
- 持久化支持
- 更好的错误处理
- 更灵活的状态管理
- 子图支持
5.1.3 发展里程碑
让我们通过一个表格来总结LangGraph的发展历程:
| 时间 | 事件 | 主要功能/改进 |
|---|---|---|
| 2023年Q3 | LangGraph概念提出 | 基本图结构、节点和边的概念 |
| 2023年Q4 | 首次公开发布 | 基本功能实现、简单示例 |
| 2024年Q1 | 检查点功能 | 状态持久化、中断和恢复 |
| 2024年Q2 | 子图支持 | 模块化设计、可复用组件 |
| 2024年Q3 | 改进的错误处理 | 回退机制、更好的调试工具 |
| 2024年Q4 | 性能优化 | 并行执行、内存优化 |
5.2 实践视角:LangGraph的应用场景
LangGraph的灵活性使其适用于多种应用场景。让我们探索一些最常见和最有前景的应用领域。
5.2.1 对话系统与聊天机器人
LangGraph非常适合构建复杂的对话系统,因为:
- 它可以轻松管理对话历史和上下文
- 它支持条件逻辑,可以根据对话状态做出不同反应
- 它可以集成多种工具和数据源
一个基于LangGraph的对话系统可能包含以下节点:
- 意图识别节点:理解用户想要做什么
- 信息检索节点:查找必要的信息
- 响应生成节点:生成自然语言回复
- 反馈收集节点:收集用户反馈,改进系统
5.2.2 多智能体协作系统
正如我们之前看到的,LangGraph是构建多智能体协作系统的理想选择。这种系统在以下领域特别有用:
- 内容创作:研究、写作、编辑等多步骤过程
- 软件开发:需求分析、代码生成、测试等智能体协作
- 医疗诊断:多个专业领域的医学智能体协作
- 财务分析:市场研究、风险评估、建议生成等
5.2.3 业务流程自动化
LangGraph也可以用于自动化复杂的业务流程,例如:
- 客户服务工单处理:分类、分配、解决、跟踪
- 文档审批流程:起草、审阅、修改、批准
- 订单处理:验证、库存检查、支付处理、发货
5.2.4 研究与分析工具
对于研究和分析任务,LangGraph可以帮助自动化和编排复杂的工作流:
- 市场研究:数据收集、分析、报告生成
- 学术研究:文献检索、综合分析、论文撰写
- 竞争情报:信息收集、竞争对手分析、战略建议
5.3 批判视角:LangGraph的局限性与挑战
虽然LangGraph是一个强大的工具,但它不是万能的。了解它的局限性和挑战对于做出明智的技术选择至关重要。
5.3.1 当前局限性
-
集中式执行:当前版本的LangGraph主要设计为在单台机器上集中执行,不支持分布式执行。这限制了它在超大规模系统中的应用。
-
有限的实时交互支持:LangGraph主要设计为批处理模式,对于需要实时、低延迟交互的场景,可能不是最佳选择。
-
陡峭的学习曲线:虽然LangGraph比从头构建类似系统要简单,但对于新手来说,理解状态管理、图构建等概念仍需要一定的学习成本。
-
调试和可观测性挑战:随着图变得越来越复杂,调试和理解系统行为可能会变得困难。虽然有一些工具支持,但仍有改进空间。
-
资源消耗:对于非常大的状态和复杂的图,LangGraph可能会消耗大量内存和计算资源。
5.3.2 常见挑战与解决方案
让我们看看使用LangGraph时可能遇到的一些常见挑战,以及如何解决它们:
| 挑战 | 解决方案 |
|---|---|
| 状态增长过大 | 实现状态修剪策略,只保留必要的信息;使用更高效 |
更多推荐
所有评论(0)