24_LangGraph子图可控性

LangGraph子图可控性

引言

在构建复杂的多代理系统时,我们通常需要将不同的功能模块化,使系统更易于管理和扩展。LangGraph提供了子图(Subgraph)机制,允许开发者将大型系统分解为多个独立但相互协作的组件。本教程将详细介绍LangGraph中的子图可控性,帮助开发者理解如何构建模块化、可扩展的代理系统。

1. 子图的概念与重要性

子图是LangGraph中的一个重要概念,它允许将复杂的工作流分解为更小、更可管理的部分。

1.1 子图的定义

子图是一个完整的StateGraph实例,可以作为另一个图(父图)中的节点。这种嵌套结构使得复杂系统的设计变得更加模块化和可维护。

1.2 子图的优势

  1. 模块化设计:将大型系统分解为功能独立的组件
  2. 代码重用:相同的子图可以在多个父图中使用
  3. 状态隔离:每个子图可以有自己的私有状态
  4. 并行处理:不同子图可以并行执行,提高效率
  5. 可维护性:更容易测试、调试和更新单个组件

2. 子图状态管理

在LangGraph中,子图之间的状态共享是其强大功能之一。主图可以通过状态对象与子图交互,确保各子图能够访问和更新全局状态。

2.1 状态继承与传递

子图会继承父图的状态,同时可以定义自己的私有状态。这种机制使得:

  1. 子图可以访问父图中定义的所有状态变量
  2. 子图可以更新父图的状态,这些更新会反映回父图
  3. 子图可以有自己的私有状态,这些状态对父图和其他子图不可见

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 状态更新与传播

子图对继承字段的更新会传播回父图:

  1. 父图将状态传递给子图
  2. 子图处理并可能更新这些状态
  3. 子图完成执行后,更新后的状态会传播回父图

这种机制确保了父图和子图之间的状态一致性。

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_1subgraph_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_1subgraph_2并行执行,而subgraph_3只有在两者都完成后才会执行。

6. 实际应用案例:日志分析系统

下面我们将构建一个完整的日志分析系统,展示如何在实际应用中使用子图。

6.1 系统架构

我们的系统将接收系统日志,并执行两个独立的子任务:

  1. 对日志进行总结,提供系统状态概览
  2. 分析日志中的故障模式,生成故障报告

这两个任务将在不同的子图中执行,最后合并结果生成最终报告。

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 状态设计原则

  1. 明确状态边界:清晰定义哪些状态应该在父图和子图之间共享,哪些应该是子图私有的
  2. 最小化共享状态:只共享必要的状态,减少不必要的依赖
  3. 状态命名一致性:在父图和子图中使用一致的状态字段名称,确保正确的状态继承

7.2 子图设计原则

  1. 单一职责:每个子图应该专注于一个明确的任务或功能
  2. 内聚性:相关的功能应该组织在同一个子图中
  3. 可重用性:设计通用的子图,使其可以在多个父图中重用
  4. 可测试性:子图应该可以独立测试,不依赖于特定的父图

7.3 子图交互原则

  1. 明确接口:定义清晰的输入和输出状态字段
  2. 避免循环依赖:子图之间不应该形成循环依赖
  3. 控制并行度:根据任务的依赖关系和系统资源,合理安排子图的并行执行
  4. 错误处理:子图应该能够优雅地处理错误,不影响整个系统的稳定性

8. 总结

LangGraph的子图可控性为构建复杂的多代理系统提供了强大的支持。通过将系统分解为多个协作的子图,开发者可以:

  1. 创建更加模块化、可维护的代码
  2. 实现不同功能模块之间的状态共享与隔离
  3. 支持并行处理,提高系统效率
  4. 重用通用组件,加速开发

子图机制是LangGraph最强大的特性之一,它使得构建复杂的、可扩展的智能系统变得更加简单和直观。通过合理设计状态和子图结构,开发者可以构建出功能强大、结构清晰的多代理协作系统。

参考资料

相关推荐
掘我的金6 小时前
23_LangGraph持久化管理
langchain
掘我的金1 天前
22_LangGraph核心组件
langchain
聚客AI1 天前
📚LangChain框架下的检索增强:5步构建高效智能体系统
人工智能·langchain·llm
灵海之森1 天前
langgraph快速搭建agent后端和react前端
langchain
大志说编程1 天前
LangChain框架入门18: 十分钟带你搞定LLM工具调用
python·langchain·ai编程
玲小珑1 天前
LangChain.js 完全开发手册(一)AI 应用开发入门
前端·langchain·ai编程
ReedFoley2 天前
【笔记】动手学Ollama 第五章 Ollama 在 LangChain 中的使用 - Python 集成
笔记·langchain
AI大模型2 天前
万字长文!从 0 到 1 搭建基于 LangGraph 的 AI Agent
langchain·llm·agent
悲欢笑miki2 天前
Api调用大模型(实用)
langchain