langchain系列(六)- LangGraph入门以及介绍

目录

一、导读

二、LangGrah介绍

1、基础介绍

2、LangChain各版本图示

三、为什么使用LangGraph 

1、链(Chain)的局限性

2、AgentExecutor的局限性

3、LangGraph解决的问题

四、LangGraph核心思想

1、核心概念

(1)state 状态

(2)node 节点

(3)edge 边

2、流程说明

五、代码实战

1、代码实现

2、输出

3、生成图片


一、导读

环境:OpenEuler、Windows 11、WSL 2、Python 3.12.3 langchain 0.3 langgraph 0.2

背景:前期忙碌的开发阶段结束,需要沉淀自己的应用知识,过一遍LangGraph

时间:20250226

说明:技术梳理,langgraph的基础原理以及为什么使用langgraph

二、LangGrah介绍

1、基础介绍

LangGraph是LangChain生态系统中的一个新成员,它提供了一个基于图(Graph)的框架来构建复杂的LLM应用。通过将应用逻辑组织成有向图的形式,LangGraph使得构建复杂的对话流程变得更加直观和灵活。

2、LangChain各版本图示

LangChain 0.1版本的图示

LangChain 0.2版本的图示

LangChain 0.3版本的图示 

从LangChain官网的图示即可看出,现在核心功能均在向LangGraph偏移。可以这样理解,核心框架使用LangGraph,而其代码语法使用LangChain实现。 

三、为什么使用LangGraph 

1、链(Chain)的局限性

在LangChain中,链(Chain)是一种基本的构建块,用于将多个LLM(语言模型)调用和工具调用链接在一起。然而,链在处理复杂、动态的对话流程时存在一些局限性:

线性流程:链通常是线性的,这意味着它们只能按照预定义的顺序执行步骤。这种线性结构限制了在对话中进行动态路由和条件分支的能力。

状态管理:链在处理多轮对话时,状态管理变得复杂。每次调用链时,都需要手动传递和更新状态,这增加了代码的复杂性和出错的可能性。

工具集成:虽然链可以调用外部工具,但在链的结构中集成和协调多个工具的使用并不直观,尤其是在需要根据对话上下文动态选择工具时。

2、AgentExecutor的局限性

AgentExecutor是LangChain中用于执行代理(Agent)的组件,它允许代理根据输入动态选择工具和操作。尽管AgentExecutor提供了一定的灵活性,但它仍然存在一些局限性:

复杂性:AgentExecutor的配置和使用相对复杂,尤其是在处理复杂的对话流程和多轮对话时。需要手动管理代理的状态和工具调用,这增加了开发的难度。

动态路由:AgentExecutor虽然支持动态选择工具,但在处理复杂的条件分支和动态路由时,仍然不够灵活。缺乏一种直观的方式来定义和执行复杂的对话流程。

状态持久性:AgentExecutor在处理长时间运行的对话时,缺乏内置的状态持久性机制。每次对话重启时,都需要从头开始,无法恢复之前的对话状态。

3、LangGraph解决的问题

面对链和AgentExecutor的局限性,LangGraph应运而生。LangGraph的设计目标是解决这些局限性,提供一个更灵活、更强大的框架来构建复杂的智能体应用:

图结构:LangGraph采用图(Graph)结构来表示对话流程,允许开发者定义复杂的非线性流程和条件分支。这种图结构提供了更大的灵活性,使得动态路由和条件分支变得直观和简单。

状态管理:LangGraph内置了强大的状态管理机制,可以无缝地管理多轮对话的状态。开发者无需手动传递和更新状态,LangGraph会自动处理状态的持久化和恢复。

工具集成:LangGraph简化了工具的集成和使用,开发者可以轻松地将多个工具集成到对话流程中,并根据对话上下文动态选择和调用工具。

持久性:LangGraph提供了内置的状态持久性机制,支持长时间运行的对话。开发者可以随时暂停和恢复对话,无需担心状态丢失。

通过这些特性,LangGraph使得构建复杂、可扩展的智能体应用变得更加容易和高效。

四、LangGraph核心思想

LangGraph 受 Pregel 和 Apache Beam 的启发,其公共接口借鉴了 NetworkX。LangGraph 由 LangChain Inc 创建,该公司也是 LangChain 的创建者,但 LangGraph 可以独立于 LangChain 使用。

chain是链,而graph是图,图具备连通性,对于LangGraph而言,最重要的概念是state、edge、node,如果这三个概念尚未弄明白,不建议写代码,否则会有N多的坑

1、核心概念

(1)state 状态

状态是LangGraph应用的基础,它可以是一个简单的字典或者Pydantic模型。状态包含了应用运行时需要的所有信息以及模型生成的信息

(2)node 节点

节点通常是Python函数,用于处理状态并返回更新后的状态

(3)edge 边

边定义了节点之间的连接关系和路由逻辑

2、流程说明

程序运行后,用户输入信息以及相关参数保存在state中,state起始在开始节点,按照代码逻辑沿着edge进入下一个节点;每一个节点(基本)都是函数,按照需求的逻辑进行处理,并更新state中的数据,一直到end节点。

以上是LangGraph最基础的理念,一定要明白,下图是一个LangGraph实现的代理RAG

上图使用的技术有tool和RAG两种技术,其起始node为__start__,结束node为__end__。state中存储的数据在start中,下一步到agent中,接下来是条件边(edge分为normal和conditional,conditional称之为条件边),一般情况下,实线代表普通边,虚线代表条件边。条件边根据当前的具体条件而选择那一条边执行,选择不同的边,则到达的节点不同。所以,agent下一步可能是结束,也可能是retrieve。retrieve节点下一步可能rewrite或是generate,rewrite下一个node必然抵达agent;而generate下一个node必然是__end__。图中可见,agent、retrieve、rewrite三个节点可能存在死循环,可以通过设置循环次数来避免这个bug。

上图实现的功能是:用户提问,如果检测该问题较为模糊,无法准确回答,则走retrieve节点,反之则结束。如果在retrieve中确认需要重写问题,则是rewrite,然后agent,最后__end__;如果找到相关答案,则将retrieve的输出,输入到generate中,并在generate中生成答案,结束

五、代码实战

下面使用最简单的逻辑实现一个graph,熟悉其基础组件概念以及使用方法

1、代码实现

from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import MessagesState

llm = ChatOpenAI(
    base_url="https://lxxxxx.enovo.com/v1/", 
    api_key="sxxxxxxxwW",
    model_name="qwen2.5-instruct"
    )

# 创建graph
graph_builder = StateGraph(MessagesState)

# 创建节点函数
def chatbot(state: MessagesState):
    return {"messages": [llm.invoke(state["messages"])]}

# 添加节点,命名为chatbot
graph_builder.add_node("chatbot", chatbot)

# 添加起始边
graph_builder.add_edge(START, "chatbot")

# 添加结束边
graph_builder.add_edge("chatbot", END)

# 代码编译为图
graph = graph_builder.compile()

# 生成图的流程图片,写到当前目录下
qa_async_png = graph.get_graph().draw_mermaid_png()
with open("chatbot.png", "wb") as f:
    f.write(qa_async_png)

# 流式调用大模型
for event in graph.stream({"messages": [{"role": "user", "content": "中国最好的大学是哪所?"}]}):
    for value in event.values():
        print("Assistant:", value["messages"][-1].content)

2、输出

Assistant: 中国有很多优秀的大学,每一所在其特定领域都有卓越的表现。如果要提及最知名的几所,人们往往会提到清华大学和北京大学。这两所学校在中国以及国际上都享有很高的声誉,分别在工科和文科方面有着出色的表现。

当然,除了清华和北大之外,还有许多其他优秀的高等教育机构,比如复旦大学、浙江大学、上海交通大学等,它们在各自的领域内也有很高的成就。选择哪所学校最好,往往取决于个人的专业兴趣和职业规划。

3、生成图片

生成图片访问的网址需要科学上网(之前不记得需要,今天不清楚啥情况)

解决方案(前提是Windows可以科学上网):Linux子系统固定IP和代理设置-CSDN博客

<think>好的,我现在需要帮助用户了解Java中的构造函数,并展示具体的例子说明用法。首先,我得回顾用户之前的问题和之前的回答,确保内容连贯。 用户之前询问了构造函数的基本定义,现在转向Java的具体示例。之前的回答中,用户提供了C++的例子,所以现在需要转换到Java,同时保持结构清晰。用户提供的引用中有几个关键点:默认构造函数、带参数构造函数、构造函数链(使用this)、以及一些注意事项,比如构造函数的命名、关键字限制等。 首先,我需要明确Java构造函数的基本概念,与C++的不同之处。例如,Java的构造函数必须与类同名,没有返回类型,可以重载。根据引用[1],用户提到默认构造函数、带参数的构造函数和构造函数链,这些都需要涵盖。 接下来,需要组织内容结构。用户希望看到具体的例子,所以每个部分都应该有代码示例。例如,默认构造函数和带参数的构造函数,可以分别举例,并展示它们的调用和效果。引用[3]中的例子已经有一个Person类,可以借用并扩展,或者创建新的例子以增加多样性。 然后,构造函数链的部分,即在一个构造函数中调用另一个构造函数,使用this关键字。需要展示如何通过this()实现,减少代码重复,确保参数的正确传递。例如,可以设计一个Employee类,其中有一个构造函数接受姓名和年龄,另一个只接受姓名,默认年龄为0,通过this调用前者。 另外,用户提供的引用[2]中有选择题,其中提到构造方法上不能使用的关键字,比如final、static等。需要解释为什么这些关键字不能用于构造函数,可能的话在注意事项中提到。 还要注意用户提到的“每个类都有一个默认构造函数”,但实际上只有当没有定义任何构造函数时,编译器才会自动生成默认构造函数。如果用户定义了带参数的构造函数,就必须显式定义默认构造函数,否则无法使用无参构造。这一点需要澄清,避免误解。 然后,实际应用示例部分,可以设计一个DatabaseConnector类,类似于之前的C++例子,但用Java实现,展示带参数的构造函数和默认参数的效果。不过Java不支持默认参数,但可以通过方法重载来模拟,比如定义多个构造函数,其中一个调用另一个并提供默认值。 注意事项部分需要包括初始化顺序、this和super的使用限制、访问修饰符的影响等。例如,构造函数可以是public、protected或private,不同的修饰符影响对象的创建方式,单例模式常用private构造函数。 最后,相关问题部分需要根据内容生成,可能涉及构造函数的重载、私有构造函数的作用、构造函数与静态工厂方法的区别等。 现在需要确保所有代码示例语法正确,符合Java规范。例如,在构造函数链中使用this()必须在第一行,成员变量初始化与构造函数体内的赋值区别,以及使用this区分成员变量和参数。 可能还需要指出Java中的构造函数没有返回类型,包括void,这与普通方法不同。同时,不能在构造函数上使用final,因为构造函数不能被继承或覆盖,static也不适用,因为构造函数用于实例化对象。 总结结构: 1. 基本概念:定义、特点 2. 核心用法:默认、带参、构造函数链 3. 示例代码:每个用法对应示例 4. 注意事项:关键字限制、初始化顺序、访问控制 5. 相关问题 需要确保每个部分都有明确的示例和解释,引用提供的资料,但根据用户当前需求调整,突出Java的特性。检查是否有遗漏点,如构造函数与类同名、没有返回类型、自动调用等,确保覆盖所有用户可能关心的问题。</think>### Java构造函数详解与示例 #### 一、构造函数基本概念 **构造函数**是Java中用于初始化对象的特殊方法,具有以下核心特性: 1. **与类同名**:例如类名为`Person`,构造函数名称也为`Person` 2. **无返回类型**(包括`void`) 3. **自动调用**:对象通过`new`创建时自动执行 4. **初始化控制**:为成员变量赋初值,确保对象合法状态[^1] #### 二、核心用法与示例 ##### 1. 默认构造函数(无参构造) 当未定义任何构造函数时,编译器自动生成空实现的默认构造函数。若已定义其他构造函数,需显式声明默认构造: ```java public class Book { private String title; // 显式定义的默认构造函数 public Book() { this.title = "Untitled"; // 初始化默认值 System.out.println("默认构造函数调用"); } } ``` ##### 2. 带参数构造函数 通过参数传递实现灵活初始化: ```java public class Student { private String name; private int score; // 带参数的构造函数 public Student(String name, int score) { this.name = name; // 使用this区分成员变量与参数 this.score = score; System.out.println("带参构造调用:" + name); } } ``` ##### 3. 构造函数链(Constructor Chaining) 使用`this()`调用同类其他构造函数,减少代码重复: ```java public class Employee { private String id; private String department; // 三参数构造函数(完整初始化) public Employee(String id, String name, String dept) { this.id = id; this.name = name; this.department = dept; } // 两参数构造函数(默认部门为"General") public Employee(String id, String name) { this(id, name, "General"); // 调用三参构造 } // 单参数构造函数(默认部门+自动生成ID) public Employee(String name) { this(generateID(), name); // 调用两参构造 } private static String generateID() { return "EMP" + System.currentTimeMillis(); } } ``` #### 三、实际应用示例 ```java public class BankAccount { private String accountNumber; private double balance; // 默认构造函数(生成随机账号) public BankAccount() { this.accountNumber = generateAccountNumber(); this.balance = 0.0; } // 带初始余额的构造函数 public BankAccount(double initialBalance) { this(); // 调用默认构造生成账号 this.balance = initialBalance; } private String generateAccountNumber() { return "ACC-" + UUID.randomUUID().toString().substring(0, 8); } public void display() { System.out.println("Account: " + accountNumber + ", Balance: $" + balance); } public static void main(String[] args) { BankAccount acc1 = new BankAccount(); // 使用默认构造 acc1.display(); // 输出示例:Account: ACC-3f4a1b, Balance: $0.0 BankAccount acc2 = new BankAccount(500.0); acc2.display(); // 输出示例:Account: ACC-7c82d9, Balance: $500.0 } } ``` #### 四、关键注意事项 1. **初始化顺序** - 静态初始化块 → 实例初始化块 → 构造函数 - 成员变量初始化优先于构造函数体执行[^3] 2. **关键字限制** - 构造函数**不能**使用`final`、`static`、`abstract`等修饰符 - `synchronized`和`native`虽然语法允许,但实际极少使用[^2] 3. **访问控制** - `private`构造函数用于单例模式: ```java public class Singleton { private static Singleton instance; private Singleton() {} // 禁止外部实例化 public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } ``` 4. **继承关系** - 子类构造函数默认调用父类无参构造(`super()`) - 需显式调用父类构造时,`super(...)`必须是子类构造的第一条语句: ```java public class Manager extends Employee { private int teamSize; public Manager(String name, int teamSize) { super(name, "Management"); // 先调用父类构造 this.teamSize = teamSize; } } ``` #### 五、常见错误示例 ```java // 错误1:尝试返回类型 public class ErrorExample1 { public void ErrorExample1() { // 实际是普通方法,非构造函数! System.out.println("这不是构造函数"); } } // 错误2:误用static public class ErrorExample2 { public static ErrorExample2() { // 编译错误:构造函数不能是static } } // 错误3:构造函数递归调用 public class ErrorExample3 { public ErrorExample3() { this(); // 编译错误:递归调用自身 } } ``` --相关问题-- 1. 如何在子类构造函数中正确调用父类构造函数? 2. 单例模式为什么要用private构造函数? 3. 构造函数与实例初始化块有何区别? 4. 为什么Java不允许构造函数定义返回类型?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值