LangGraph子图可控性
引言
在构建复杂的多代理系统时,我们通常需要将不同的功能模块化,使系统更易于管理和扩展。LangGraph提供了子图(Subgraph)机制,允许开发者将大型系统分解为多个独立但相互协作的组件。本教程将详细介绍LangGraph中的子图可控性,帮助开发者理解如何构建模块化、可扩展的代理系统。
1. 子图的概念与重要性
子图是LangGraph中的一个重要概念,它允许将复杂的工作流分解为更小、更可管理的部分。
1.1 子图的定义
子图是一个完整的StateGraph实例,可以作为另一个图(父图)中的节点。这种嵌套结构使得复杂系统的设计变得更加模块化和可维护。
1.2 子图的优势
- 模块化设计:将大型系统分解为功能独立的组件
- 代码重用:相同的子图可以在多个父图中使用
- 状态隔离:每个子图可以有自己的私有状态
- 并行处理:不同子图可以并行执行,提高效率
- 可维护性:更容易测试、调试和更新单个组件
2. 子图状态管理
在LangGraph中,子图之间的状态共享是其强大功能之一。主图可以通过状态对象与子图交互,确保各子图能够访问和更新全局状态。
2.1 状态继承与传递
子图会继承父图的状态,同时可以定义自己的私有状态。这种机制使得:
- 子图可以访问父图中定义的所有状态变量
- 子图可以更新父图的状态,这些更新会反映回父图
- 子图可以有自己的私有状态,这些状态对父图和其他子图不可见
2.2 状态传递示意图
css
父图状态 (ParentState)
|
├── 共享状态变量 A
├── 共享状态变量 B
|
├── 子图1 (SubgraphState1)
| ├── 继承的变量 A
| ├── 继承的变量 B
| └── 私有变量 C
|
└── 子图2 (SubgraphState2)
├── 继承的变量 A
├── 继承的变量 B
└── 私有变量 D
3. 构建子图系统
下面我们将通过一个完整的示例来展示如何构建和使用子图。
3.1 定义状态类型
首先,我们需要为父图和子图定义状态类型:
python
from typing import TypedDict, List, Optional, Dict, Any
from langchain_core.messages import BaseMessage
# 父图状态
class ParentState(TypedDict):
docs: List[str] # 输入文档
summary_report: Optional[str] # 摘要报告
failure_report: Optional[str] # 故障报告
# 摘要子图状态
class SummaryState(TypedDict):
docs: List[str] # 从父图继承
summary_report: Optional[str] # 将传回父图
internal_summaries: List[str] # 子图私有状态
# 故障分析子图状态
class FailureAnalysisState(TypedDict):
docs: List[str] # 从父图继承
failure_report: Optional[str] # 将传回父图
failure_patterns: List[Dict[str, Any]] # 子图私有状态
3.2 创建子图
接下来,我们创建两个子图:一个用于文档摘要,另一个用于故障分析:
python
from langgraph.graph import StateGraph, START, END
from langchain_openai import ChatOpenAI
# 创建LLM
llm = ChatOpenAI(model="gpt-3.5-turbo")
# 创建摘要子图
def create_summary_subgraph():
# 创建子图
summary_graph = StateGraph(SummaryState)
# 定义节点函数
def analyze_docs(state: SummaryState):
"""分析文档并生成内部摘要"""
docs = state["docs"]
internal_summaries = []
for doc in docs:
# 使用LLM生成每个文档的摘要
summary = llm.invoke(f"请总结以下文档内容:\n\n{doc}")
internal_summaries.append(summary.content)
return {"internal_summaries": internal_summaries}
def generate_summary_report(state: SummaryState):
"""生成最终摘要报告"""
internal_summaries = state["internal_summaries"]
# 使用LLM生成综合摘要报告
combined_summary = "\n\n".join(internal_summaries)
report = llm.invoke(f"请基于以下摘要生成一份综合报告:\n\n{combined_summary}")
return {"summary_report": report.content}
# 添加节点
summary_graph.add_node("analyze_docs", analyze_docs)
summary_graph.add_node("generate_summary_report", generate_summary_report)
# 添加边
summary_graph.add_edge(START, "analyze_docs")
summary_graph.add_edge("analyze_docs", "generate_summary_report")
summary_graph.add_edge("generate_summary_report", END)
# 编译子图
return summary_graph.compile()
# 创建故障分析子图
def create_failure_analysis_subgraph():
# 创建子图
failure_graph = StateGraph(FailureAnalysisState)
# 定义节点函数
def extract_failures(state: FailureAnalysisState):
"""从文档中提取故障模式"""
docs = state["docs"]
failure_patterns = []
for doc in docs:
# 使用LLM识别故障模式
analysis = llm.invoke(f"请从以下日志中识别所有故障模式和错误:\n\n{doc}")
# 简单处理,实际应用中可能需要更复杂的结构化提取
failure_patterns.append({
"source": doc[:50] + "...", # 文档标识
"failures": analysis.content
})
return {"failure_patterns": failure_patterns}
def generate_failure_report(state: FailureAnalysisState):
"""生成故障报告"""
failure_patterns = state["failure_patterns"]
# 使用LLM生成综合故障报告
failures_text = "\n\n".join([f"文档: {f['source']}\n故障: {f['failures']}" for f in failure_patterns])
report = llm.invoke(f"请基于以下故障模式生成一份综合故障报告:\n\n{failures_text}")
return {"failure_report": report.content}
# 添加节点
failure_graph.add_node("extract_failures", extract_failures)
failure_graph.add_node("generate_failure_report", generate_failure_report)
# 添加边
failure_graph.add_edge(START, "extract_failures")
failure_graph.add_edge("extract_failures", "generate_failure_report")
failure_graph.add_edge("generate_failure_report", END)
# 编译子图
return failure_graph.compile()
3.3 创建父图并集成子图
现在,我们创建父图并将两个子图作为节点集成进来:
python
def create_parent_graph():
# 创建子图实例
summary_subgraph = create_summary_subgraph()
failure_subgraph = create_failure_analysis_subgraph()
# 创建父图
parent_graph = StateGraph(ParentState)
# 定义节点函数
def prepare_final_report(state: ParentState):
"""准备最终报告"""
summary_report = state.get("summary_report", "")
failure_report = state.get("failure_report", "")
# 组合报告
final_report = f"""
# 系统分析报告
## 摘要
{summary_report}
## 故障分析
{failure_report}
"""
# 这里我们只是打印报告,实际应用中可能会保存到文件或数据库
print(final_report)
return {}
# 添加子图作为节点
parent_graph.add_node("summarize_docs", summary_subgraph)
parent_graph.add_node("analyze_failures", failure_subgraph)
parent_graph.add_node("prepare_final_report", prepare_final_report)
# 添加边
parent_graph.add_edge(START, "summarize_docs")
parent_graph.add_edge(START, "analyze_failures")
parent_graph.add_edge("summarize_docs", "prepare_final_report")
parent_graph.add_edge("analyze_failures", "prepare_final_report")
parent_graph.add_edge("prepare_final_report", END)
# 编译父图
return parent_graph.compile()
3.4 运行完整系统
最后,我们运行完整的系统:
python
def run_analysis_system(docs: List[str]):
# 创建父图
parent_graph = create_parent_graph()
# 准备初始状态
initial_state = {
"docs": docs,
"summary_report": None,
"failure_report": None
}
# 运行图
final_state = parent_graph.invoke(initial_state)
return final_state
# 示例使用
sample_docs = [
"""
系统日志 2024-05-15:
08:00:01 INFO: 系统启动
08:15:23 WARNING: 数据库连接延迟增加
08:32:45 ERROR: 数据库连接失败,重试中
09:05:12 INFO: 数据库连接恢复
09:30:56 INFO: 处理用户请求 #12345
""",
"""
系统日志 2024-05-16:
07:58:01 INFO: 系统启动
08:10:34 INFO: 处理用户请求 #12346
08:45:23 WARNING: 内存使用率超过85%
09:12:45 ERROR: 服务A响应超时
09:15:12 ERROR: 服务A不可用,启动备用服务
09:30:56 INFO: 备用服务正常运行
"""
]
final_state = run_analysis_system(sample_docs)
4. 子图间的状态共享与交互
子图之间的状态共享和交互是LangGraph子图可控性的核心。下面我们详细解析这一机制。
4.1 状态继承机制
当子图作为父图的节点运行时,它会自动继承父图中与其状态类型匹配的字段。这种继承是基于字段名称的匹配:
python
# 父图状态
class ParentState(TypedDict):
field_a: str
field_b: int
# 子图状态
class SubgraphState(TypedDict):
field_a: str # 将从父图继承
field_c: list # 子图私有字段
在这个例子中,field_a
会从父图继承到子图,而field_b
不会被子图访问,field_c
则是子图私有的。
4.2 状态更新与传播
子图对继承字段的更新会传播回父图:
- 父图将状态传递给子图
- 子图处理并可能更新这些状态
- 子图完成执行后,更新后的状态会传播回父图
这种机制确保了父图和子图之间的状态一致性。
4.3 私有状态管理
子图可以定义和使用父图中不存在的私有状态字段。这些字段对父图和其他子图是不可见的,从而实现了状态的隔离。
python
def subgraph_node(state: SubgraphState):
# 访问继承的状态
inherited_value = state["field_a"]
# 更新将传播回父图的状态
updated_value = f"{inherited_value}_updated"
# 更新私有状态
private_data = ["item1", "item2"]
return {
"field_a": updated_value, # 将传播回父图
"field_c": private_data # 仅在子图内可见
}
5. 子图的并行与顺序执行
LangGraph允许子图以并行或顺序方式执行,这取决于父图中的边定义。
5.1 并行执行子图
要并行执行多个子图,可以从同一个节点(通常是START)添加指向多个子图的边:
python
parent_graph.add_edge(START, "subgraph_1")
parent_graph.add_edge(START, "subgraph_2")
这样,subgraph_1
和subgraph_2
将在同一个超级步骤中并行执行。
5.2 顺序执行子图
要顺序执行子图,可以定义子图之间的依赖关系:
python
parent_graph.add_edge(START, "subgraph_1")
parent_graph.add_edge("subgraph_1", "subgraph_2")
这样,subgraph_2
只有在subgraph_1
完成后才会执行。
5.3 混合执行模式
也可以组合并行和顺序执行:
python
parent_graph.add_edge(START, "subgraph_1")
parent_graph.add_edge(START, "subgraph_2")
parent_graph.add_edge("subgraph_1", "subgraph_3")
parent_graph.add_edge("subgraph_2", "subgraph_3")
在这个例子中,subgraph_1
和subgraph_2
并行执行,而subgraph_3
只有在两者都完成后才会执行。
6. 实际应用案例:日志分析系统
下面我们将构建一个完整的日志分析系统,展示如何在实际应用中使用子图。
6.1 系统架构
我们的系统将接收系统日志,并执行两个独立的子任务:
- 对日志进行总结,提供系统状态概览
- 分析日志中的故障模式,生成故障报告
这两个任务将在不同的子图中执行,最后合并结果生成最终报告。
6.2 完整代码实现
python
from typing import TypedDict, List, Optional, Dict, Any
from langchain_core.messages import BaseMessage
from langgraph.graph import StateGraph, START, END
from langchain_openai import ChatOpenAI
import os
# 设置API密钥
os.environ["OPENAI_API_KEY"] = "your-openai-api-key"
# 创建LLM
llm = ChatOpenAI(model="gpt-3.5-turbo")
# 定义状态类型
class LogAnalysisState(TypedDict):
logs: List[str] # 输入日志
summary_report: Optional[str] # 摘要报告
failure_report: Optional[str] # 故障报告
final_report: Optional[str] # 最终报告
class SummaryState(TypedDict):
logs: List[str] # 从父图继承
summary_report: Optional[str] # 将传回父图
log_summaries: List[str] # 子图私有状态
class FailureAnalysisState(TypedDict):
logs: List[str] # 从父图继承
failure_report: Optional[str] # 将传回父图
detected_failures: List[Dict[str, Any]] # 子图私有状态
# 创建摘要子图
def create_summary_subgraph():
summary_graph = StateGraph(SummaryState)
def summarize_individual_logs(state: SummaryState):
"""为每个日志生成摘要"""
logs = state["logs"]
log_summaries = []
for log in logs:
prompt = f"""
请总结以下系统日志,重点关注主要事件和系统状态:
{log}
请提供简洁的摘要,不超过3-5句话。
"""
summary = llm.invoke(prompt)
log_summaries.append(summary.content)
return {"log_summaries": log_summaries}
def create_summary_report(state: SummaryState):
"""创建综合摘要报告"""
log_summaries = state["log_summaries"]
prompt = f"""
基于以下日志摘要,创建一份综合系统状态报告:
{"\n\n".join(log_summaries)}
报告应包括:
1. 系统整体状态概览
2. 主要事件时间线
3. 需要关注的关键点
"""
report = llm.invoke(prompt)
return {"summary_report": report.content}
# 添加节点和边
summary_graph.add_node("summarize_logs", summarize_individual_logs)
summary_graph.add_node("create_report", create_summary_report)
summary_graph.add_edge(START, "summarize_logs")
summary_graph.add_edge("summarize_logs", "create_report")
summary_graph.add_edge("create_report", END)
return summary_graph.compile()
# 创建故障分析子图
def create_failure_analysis_subgraph():
failure_graph = StateGraph(FailureAnalysisState)
def detect_failures(state: FailureAnalysisState):
"""检测日志中的故障模式"""
logs = state["logs"]
detected_failures = []
for i, log in enumerate(logs):
prompt = f"""
请分析以下系统日志,识别所有错误、警告和故障模式:
{log}
对于每个故障,请提供:
1. 故障类型(错误、警告等)
2. 故障描述
3. 可能的原因
4. 潜在影响
以JSON格式返回结果。
"""
analysis = llm.invoke(prompt)
detected_failures.append({
"log_id": i,
"analysis": analysis.content
})
return {"detected_failures": detected_failures}
def generate_failure_report(state: FailureAnalysisState):
"""生成故障报告"""
failures = state["detected_failures"]
prompt = f"""
基于以下故障分析,创建一份综合故障报告:
{"\n\n".join([f"日志 {f['log_id']}:\n{f['analysis']}" for f in failures])}
报告应包括:
1. 故障摘要和严重性评估
2. 常见故障模式和趋势
3. 建议的修复或缓解措施
"""
report = llm.invoke(prompt)
return {"failure_report": report.content}
# 添加节点和边
failure_graph.add_node("detect_failures", detect_failures)
failure_graph.add_node("generate_report", generate_failure_report)
failure_graph.add_edge(START, "detect_failures")
failure_graph.add_edge("detect_failures", "generate_report")
failure_graph.add_edge("generate_report", END)
return failure_graph.compile()
# 创建父图
def create_log_analysis_system():
# 创建子图
summary_subgraph = create_summary_subgraph()
failure_subgraph = create_failure_analysis_subgraph()
# 创建父图
parent_graph = StateGraph(LogAnalysisState)
def generate_final_report(state: LogAnalysisState):
"""生成最终综合报告"""
summary_report = state.get("summary_report", "未生成摘要报告")
failure_report = state.get("failure_report", "未生成故障报告")
prompt = f"""
请将以下两份报告整合为一份完整的系统分析报告:
## 系统状态摘要
{summary_report}
## 故障分析报告
{failure_report}
最终报告应包括:
1. 执行摘要
2. 系统状态概览
3. 详细故障分析
4. 建议措施
5. 结论
"""
final_report = llm.invoke(prompt)
return {"final_report": final_report.content}
# 添加节点
parent_graph.add_node("summarize", summary_subgraph)
parent_graph.add_node("analyze_failures", failure_subgraph)
parent_graph.add_node("generate_final_report", generate_final_report)
# 添加边 - 并行执行子图
parent_graph.add_edge(START, "summarize")
parent_graph.add_edge(START, "analyze_failures")
# 等待两个子图都完成后生成最终报告
parent_graph.add_edge("summarize", "generate_final_report")
parent_graph.add_edge("analyze_failures", "generate_final_report")
parent_graph.add_edge("generate_final_report", END)
return parent_graph.compile()
# 运行系统
def analyze_logs(logs: List[str]):
system = create_log_analysis_system()
initial_state = {
"logs": logs,
"summary_report": None,
"failure_report": None,
"final_report": None
}
final_state = system.invoke(initial_state)
print("=== 最终系统分析报告 ===")
print(final_state["final_report"])
return final_state
# 示例使用
sample_logs = [
"""
系统日志 2024-05-15:
08:00:01 INFO: 系统启动
08:15:23 WARNING: 数据库连接延迟增加
08:32:45 ERROR: 数据库连接失败,重试中
09:05:12 INFO: 数据库连接恢复
09:30:56 INFO: 处理用户请求 #12345
10:15:23 INFO: 备份开始
10:45:34 INFO: 备份完成
11:05:12 WARNING: CPU使用率超过80%
12:30:56 INFO: 系统例行维护
13:45:23 INFO: 维护完成
14:32:45 INFO: 处理用户请求 #12346
""",
"""
系统日志 2024-05-16:
07:58:01 INFO: 系统启动
08:10:34 INFO: 处理用户请求 #12347
08:45:23 WARNING: 内存使用率超过85%
09:12:45 ERROR: 服务A响应超时
09:15:12 ERROR: 服务A不可用,启动备用服务
09:30:56 INFO: 备用服务正常运行
10:05:12 WARNING: 磁盘空间低于20%
11:32:45 INFO: 清理临时文件
12:15:23 INFO: 磁盘空间恢复正常
13:30:56 ERROR: 数据库查询超时
14:05:12 INFO: 数据库优化开始
14:45:23 INFO: 数据库优化完成
"""
]
analyze_logs(sample_logs)
7. 子图设计最佳实践
基于上述内容,我们总结一些子图设计的最佳实践:
7.1 状态设计原则
- 明确状态边界:清晰定义哪些状态应该在父图和子图之间共享,哪些应该是子图私有的
- 最小化共享状态:只共享必要的状态,减少不必要的依赖
- 状态命名一致性:在父图和子图中使用一致的状态字段名称,确保正确的状态继承
7.2 子图设计原则
- 单一职责:每个子图应该专注于一个明确的任务或功能
- 内聚性:相关的功能应该组织在同一个子图中
- 可重用性:设计通用的子图,使其可以在多个父图中重用
- 可测试性:子图应该可以独立测试,不依赖于特定的父图
7.3 子图交互原则
- 明确接口:定义清晰的输入和输出状态字段
- 避免循环依赖:子图之间不应该形成循环依赖
- 控制并行度:根据任务的依赖关系和系统资源,合理安排子图的并行执行
- 错误处理:子图应该能够优雅地处理错误,不影响整个系统的稳定性
8. 总结
LangGraph的子图可控性为构建复杂的多代理系统提供了强大的支持。通过将系统分解为多个协作的子图,开发者可以:
- 创建更加模块化、可维护的代码
- 实现不同功能模块之间的状态共享与隔离
- 支持并行处理,提高系统效率
- 重用通用组件,加速开发
子图机制是LangGraph最强大的特性之一,它使得构建复杂的、可扩展的智能系统变得更加简单和直观。通过合理设计状态和子图结构,开发者可以构建出功能强大、结构清晰的多代理协作系统。