Langchain-初学者入门-2025-笔记-全-

Langchain-初学者入门-2025-笔记-全-在本节课中 我们将要学习 Langchain 的基础知识 了解其核心概念 应用场景以及本课程的学习路径 无论你是开发者 创业者还是对 AI 感兴趣的初学者 本课程都将为你提供一个清晰的起点 Langchain 是一个用于构建由大型语言模型驱动的应用程序的框架 它简化了将语言模型集成到实际应用中的过程 使开发者能够更高效地创建智能代理 自动化任务和构建复杂的对话系统

大家好,我是讯享网,很高兴认识大家。这里提供最前沿的Ai技术和互联网信息。



在本节课中,我们将要学习Langchain的基础知识,了解其核心概念、应用场景以及本课程的学习路径。无论你是开发者、创业者还是对AI感兴趣的初学者,本课程都将为你提供一个清晰的起点。

Langchain是一个用于构建由大型语言模型驱动的应用程序的框架。它简化了将语言模型集成到实际应用中的过程,使开发者能够更高效地创建智能代理、自动化任务和构建复杂的对话系统。

本入门课程旨在帮助你从零开始掌握Langchain。我们将从最基础的概念讲起,逐步深入到实际项目的构建。课程结束时,你将能够运用Langchain解决真实的业务问题。

以下是本课程涵盖的核心内容:

  • 构建智能聊天机器人:学习如何创建功能丰富的对话界面。
  • 创建自定义代理与工具:了解如何扩展Langchain的功能以满足特定需求。
  • 利用Langchain自动化任务:掌握使用框架简化工作流程的方法。

如果你对这些术语感到陌生,无需担心。我们将从最基础的部分开始讲解。

为了确保学习过程顺利,我们提供了以下支持材料:

  • 课程章节:视频下方提供了清晰的课程章节索引,方便你按需学习。
  • 代码仓库:所有课程中编写的代码均可在Github仓库中找到,链接位于视频描述中。
  • 学习社区:我们建立了一个免费的社区,供学员交流、提问并获取关于AI最新发展的信息。这是一个可以学习、 networking 甚至发现新机会的平台,链接同样位于描述中。

我是Harishneil,在Freshworks、Zoho和Samsung等公司担任软件开发和AI相关职位。我拥有机器学习和人工智能的学位,并且热衷于分享这些知识。

本节课我们一起学习了Langchain课程的引言部分,明确了学习目标、课程内容以及可用的学习资源。我们从了解Langchain是什么及其用途开始,为后续逐步构建实际应用打下了基础。下一节,我们将正式进入Langchain的核心概念世界。

在本节课中,我们将要学习Langchain课程的整体结构与核心内容。我们将了解课程涵盖的五大核心组件,并理解它们如何协同工作以构建强大的AI应用。

首先,我们将探讨什么是Langchain,为什么需要使用它,以及它实际解决了什么问题。

接着,我们将设置Python开发环境,以便能够开始使用Langchain并与各种API进行交互。

环境设置完成后,我们将深入探讨Langchain的核心组件。

以下是本课程将要深入学习的五大核心组件。

我们将从聊天模型开始。这意味着我们将学习如何与OpenAI、Claude等聊天模型进行基础交互。

Langchain的第二个核心组件是提示模板。其基本思想是将发送给AI的提示制作成模板,并在其中加入占位符,以便接收动态输入的值。

下一个组件从这里开始变得非常有趣。Langchain的第三个组件是链。链在Langchain中就像一条装配线。😊

每个步骤处理一个特定任务,并将其传递给下一个步骤。这里有一个现实世界的例子。

想象一下制作咖啡的链条。首先,你需要研磨咖啡豆,然后冲泡,接着打奶泡,最后端上咖啡。我们在Langchain中所做的正是如此。

因此,如果有一个复杂的工作流程,我们会将其分解为更小的任务。第一个任务完成后,其结果会传递给第二个任务。这正是它被称为“链”的原因,甚至Langchain的Logo也包含链条的图案,因为它将模型、聊天模型、提示、数据库调用等各种过程链接成一个统一的工作流。本课程中也有许多相关的现实案例。

这是课程中一个非常重要的部分。它是帮助企业提升生产力的关键技术之一。

如果你听说过基于公司私有数据(数据形式可以是PDF、数据库等)训练的定制聊天机器人,并且用户可以与之“对话”,那么背后使用的就是RAG,即检索增强生成技术。

最后,我们将以Langchain的另一个关键组件——智能体与工具来结束本课程。你可以将AI智能体想象成一个人类代理,它能够看到问题,并使用各种工具来解决特定问题。

它可以与API交互、自动发送邮件、从网站抓取数据、运行Python脚本,甚至查询数据库。我举个例子:想象一个AI智能体接到预订会议的任务,就像人类一样。它可以检查你的日历、发送邮件邀请、同时更新CRM系统,这一切都是自动完成的。这就是Langchain智能体的能力,它们能为每一步选择并使用正确的工具。

这里有一个快速提示:如果你想先获得完整的概览,可以尝试以两倍速观看整个课程,然后再回过头来专注于你最需要的部分。我就是这样学习的,因为它让我先看到了全局,并理解各部分是如何组合在一起的。

在下一节中,我们将探讨什么是Langchain,以及我们为什么需要它。

本节课中,我们一起学习了Langchain 2025入门课程的整体框架,明确了即将深入学习的五大核心组件:聊天模型、提示模板、链、RAG以及智能体与工具,并了解了它们如何构成构建AI应用的基础。

在本节课中,我们将要学习LangChain是什么,理解它如何作为大型语言模型(LLM)与现实世界之间的桥梁,以及为什么它在构建AI应用时如此重要。

为了理解LangChain是什么,让我们从一个简单的问题开始。

想象一下,你想规划一次假期,并希望向ChatGPT寻求帮助。你可能会输入:“我想本周六去巴黎旅行,你能帮我订机票吗?同时预订同一天的酒店,并推荐一些好餐厅。”

在按下回车键之前,让我们看看幕后会发生什么。当你按下回车键,这个查询会被发送给一个LLM模型。ChatGPT这类应用可能使用多种模型,例如GPT-3.5、GPT-4、Claude等。这些就是你在右侧看到的大型语言模型。ChatGPT应用本身只是一个面向用户的界面。

现在,让我们看看实际会发生什么。模型可能会回复:“我无法直接进行预订,但我可以帮你规划。” 这是大型语言模型最大的局限性之一:它们很聪明,可以讨论旅行,但无法真正与现实世界互动。

LLM本身只是“大脑”。它们可以在特定数据上进行训练并进行推理,但无法在此范围之外执行任何操作。例如,它不能直接调用预订API或发送邮件。

假设你想构建一个应用,它既需要具备LLM的推理能力,又需要能够与现实世界通信,例如与API、数据库交互或发送邮件。为了实现这一点,我们需要一个位于中间的框架。这就是LangChain发挥作用的地方。

LangChain充当了LLM与现实世界之间的桥梁。

简单来说,LangChain是目前最流行的、用于帮助构建基于LLM的应用程序的框架。

以下是LangChain提供的主要优势:

  1. 标准化接口:它为不同的LLM(如OpenAI、Anthropic、Hugging Face的模型)提供了一个统一的调用接口。这意味着你可以轻松切换底层模型,而无需重写大量代码。
    代码示例llm = ChatOpenAI(model="gpt-4") 可以轻松替换为 llm = ChatHuggingFace(model="mistral-7b")



  1. 模块化组件:LangChain将复杂应用拆分为可重用的模块,例如提示模板、记忆模块、检索器和输出解析器。
  2. “链”式编排:它允许你将多个步骤(调用LLM、查询数据、处理结果)连接成一个可执行的“链”(Chain),从而构建复杂的工作流。
  3. 代理(Agent)能力:这是LangChain最强大的功能之一。代理可以理解目标,并自主决定调用哪些工具(如搜索引擎、计算器、API)来完成任务,从而让AI能够在现实世界中行动。

通过LangChain,我们正在开发的AI可以在现实世界中做更多事情。以下是一些例子:

  • 它可以访问众多API,例如从Booking.com或OpenTable.com获取航班和餐厅预订信息。
  • 它可以访问私有公司数据库来回答客户查询。
  • 它可以发送电子邮件。
  • 它可以浏览谷歌、维基百科。
  • 它可以抓取网站信息,以及完成更多任务。

因此,LangChain不仅仅是让AI变得更聪明,它赋予了AI在现实世界中行动的能力。这只是一个非常小的例子,随着课程的深入,我们将探索更多的用例。

本节课中,我们一起学习了LangChain的核心概念。我们了解到,LLM本身存在无法与现实世界交互的局限性。LangChain作为一个框架,通过提供标准化接口、模块化组件和强大的代理能力,在LLM与现实世界之间架起了桥梁。它使开发者能够构建出不仅能够思考,更能够行动的智能应用程序。在接下来的课程中,我们将开始动手实践,探索如何使用LangChain构建各种应用。

在本节课中,我们将学习开始学习Langchain课程前需要完成的准备工作。这包括安装必要的软件、创建账户以及获取关键的API密钥。


为了能够顺利跟随本课程,你需要准备好以下三项内容。

以下是具体的要求列表:

  1. Python 3.8或更高版本:这是运行Langchain和相关代码的基础环境。
  2. 代码编辑器:用于编写和运行Python代码。课程中将使用Visual Studio Code (VS Code)。
  3. OpenAI账户:因为课程中将使用OpenAI的API来访问其大型语言模型。

上一节我们列出了所需的软件和账户,本节中我们来看看如何具体获取和配置它们。

如果你尚未安装Python,可以按照描述区中提供的教程链接进行安装。教程涵盖了Windows和Mac操作系统。安装完成后,请返回视频继续学习。

你可以访问 Visual Studio Code官网 下载适用于你操作系统的版本。VS Code的安装过程非常简单直接。

我们需要OpenAI账户的原因是,必须使用API密钥才能调用其API端点来访问模型。

以下是获取API密钥的步骤:

  1. 访问 OpenAI平台。
  2. 如果你已有账户,请登录;如果没有,请点击“Sign up”进行注册。
  3. 登录后,点击左侧边栏的“Dashboard”选项卡。
  4. 在左侧菜单中找到并点击“API keys”。
  5. 如果你是首次创建,可能会看到一个空列表。点击“Create new secret key”按钮。
  6. 为密钥命名(例如:LangchainTutorial),权限保持默认的“All”即可,然后点击“Create”。
  7. 重要:创建成功后,请立即将生成的API密钥妥善保存在你的电脑中。因为一旦关闭此页面,你将无法再次查看完整的密钥,若丢失则需重新创建。

到目前为止,我们已经安装了Python 3和VS Code,并创建了OpenAI账户及API密钥。

在下一节中,我们将使用Python来设置我们的开发环境,以便真正开始使用Langchain。


本节课中我们一起学习了开始Langchain之旅前的所有先决条件:安装Python和VS Code,以及创建并保存OpenAI API密钥。确保完成这些步骤,为后续的实践操作做好准备。

在本节课中,我们将学习如何为Langchain项目设置一个Python开发环境。我们将创建一个项目文件夹,初始化一个虚拟环境,并确保一切准备就绪,以便在后续课程中顺利安装和使用Langchain。


首先,创建一个名为 la chain 的文件夹。这个文件夹将存放我们所有的项目文件。

接下来,在Visual Studio Code中打开这个文件夹。你可以通过将文件夹拖放到Visual Studio Code窗口中来完成此操作。

现在,让我们打开终端并创建一个虚拟环境。使用虚拟环境是Python项目的标准做法,它可以确保我们安装的包只对当前项目有效,而不会影响全局环境。

以下是创建虚拟环境的命令:

python3 -m venv .venv 

命令解释

  • python3:调用Python 3解释器(在Windows上通常使用 python)。
  • -m venv:告诉Python运行 venv 模块来创建虚拟环境。
  • .venv:这是我们将要创建的虚拟环境文件夹的名称。开头的点号 . 使其成为隐藏文件夹,你也可以选择不使用点号。

执行命令后,虚拟环境文件夹就创建好了。

创建虚拟环境只是第一步,接下来需要激活它,使终端指向这个环境。这样,后续安装的所有包都会被隔离在这个环境中。

当前,终端可能指向的是基础环境(如 base)。在Visual Studio Code中,编辑器通常会弹出一个提示,让你选择新创建的 .venv 环境作为解释器。如果未弹出提示,你可以手动点击编辑器右下角或状态栏上的Python解释器选择按钮,然后选择 .venv 文件夹下的 python 可执行文件路径。

激活成功后,你会在终端提示符前看到 (.venv) 字样,或者在新打开的终端窗口中看到类似标识。这表明我们已经成功进入了虚拟环境。

至此,我们的Python开发环境已完全设置好。

为了确保环境正常工作,我提前在项目文件夹中创建了一个简单的Python文件,名为 chat_models_starter.py

在文件中输入以下代码:

GPT plus 代充 只需 145print("Let's learn Langchain!") 

运行该文件。如果能在终端中看到输出的文本,就证明我们的开发环境已完美就绪。


本节课中,我们一起完成了Langchain项目的开发环境设置。我们创建了项目目录,使用 venv 模块建立了独立的Python虚拟环境,并成功激活了它。现在,我们已经准备好开始学习Langchain的核心组件。

在下一节中,我们将探讨Langchain中第一个也是最重要的组件:聊天模型(Chat Models)

在本节中,我们将学习LangChain的第一个核心组件——聊天模型。我们将了解它的官方定义、核心优势,以及为什么在构建复杂应用时,使用LangChain的接口比直接调用大语言模型的API更高效。

上一节我们介绍了LangChain的整体架构,本节中我们来看看其中的核心组件之一:聊天模型。

根据官方定义,LangChain中的聊天模型是一个旨在以结构化方式与LLM进行通信的组件。这里的LLM可以是GPT-4,可以是Hugging Face上的开源模型,也可以是各类云服务商的API。LangChain的聊天模型提供了一个统一的接口,让你能够与你想要的几乎所有LLM进行通信。

如果你查看LangChain的官方文档,会发现有多种聊天模型可供选择:

  • 有针对Anthropic公司LLM的LangChain聊天模型。
  • 有针对其他服务商(如Myel)的模型。
  • 由于本课程主要使用OpenAI的LLM,因此我们将使用 ChatOpenAI 这个模型类,这也是我们需要安装的包。

每个模型都具备不同的能力,例如:

  • 有些模型支持使用工具(我们将在课程后续部分讨论工具)。
  • 有些模型支持结构化输出(例如JSON格式)。

既然我们可以直接调用LLM的API,为什么还需要LangChain这个“中间人”呢?答案是:对于小型应用,直接调用当然可以。但随着应用变得越来越复杂,直接管理各种API调用会迅速变得混乱不堪。

使用LangChain聊天模型接口而非直接调用API,主要有以下优势。了解这些优势很重要,它能让你从更高层面理解我们为何这样做,从而在课程结束后也能更好地设计你自己的解决方案。

以下是几个关键优势:

1. 统一的接口
LangChain聊天模型统一了不同LLM API的调用方式,使你无需分别管理每个API独特的设置和细节。



2. 易于切换模型
如果你想从一个LLM切换到另一个,LangChain聊天模型让这个过程变得非常简单,无需大量修改代码。



3. 上下文管理
使用LangChain聊天模型有助于管理对话历史,让你能够无缝地在多次交互中保持上下文连贯。



4. 高效编排
你可以将多个LLM调用和任务连接到一个结构化的管道中,而手动设置这种编排可能会比较棘手。



5. 可扩展性
随着项目增长,LangChain的接口能够支持更复杂的工作流,让你专注于功能开发,而非API管理。



在本课程中,我们将主要使用OpenAI的API,因此我们需要 ChatOpenAI 这个模型。点击官方文档中相应的链接,你会发现其设置方法非常易于理解。

接下来,就让我们实际动手设置一个聊天模型,并开始与OpenAI的API进行交互吧。

本节课中我们一起学习了LangChain聊天模型的核心概念、主要优势以及它存在的必要性。我们了解到,聊天模型作为统一接口,简化了与不同LLM的交互,为构建可维护、可扩展的复杂应用奠定了基础。在下一节,我们将进行实践,亲手配置并使用 ChatOpenAI 模型。

在本节课中,我们将开始学习如何使用Langchain的聊天模型。主要内容包括安装必要的包、初始化模型、调用API以及处理API密钥等基本操作。


上一节我们介绍了Langchain的基本概念,本节中我们来看看如何设置并使用聊天模型。由于我们将主要使用OpenAI的API,因此需要安装对应的Langchain包。

以下是安装 langchain-openai 包的步骤:

  1. 打开终端。
  2. 运行安装命令:pip install langchain-openai

安装完成后,我们可以在代码中导入所需的类。

from langchain_openai import ChatOpenAI 

导入类之后,我们可以初始化一个聊天模型实例。在初始化时,需要指定要使用的模型。

GPT plus 代充 只需 145llm = ChatOpenAI(model="gpt-4o") 

这里我们选择了 gpt-4o 模型。它是OpenAI发布的最新模型之一,功能强大但调用成本可能较高。如果预算有限,可以选择 gpt-3.5-turbogpt-4 等模型。这个 llm 变量将作为我们与OpenAI API通信的接口。

初始化模型后,我们可以通过 invoke 方法向模型发送提示并获取回复。

result = llm.invoke("what is the square root of 49?") print(result) 

首次运行时,你可能会遇到API密钥缺失的错误。这是因为我们没有提供访问OpenAI服务的凭证。

为了解决API密钥错误,我们需要创建一个环境变量文件来安全地存储密钥。

以下是配置API密钥的步骤:

  1. 在项目根目录创建一个名为 .env 的文件。
  2. 在文件中添加你的OpenAI API密钥:OPENAI_API_KEY=你的密钥
  3. 在Python代码中加载环境变量。首先安装 python-dotenv 包:pip install python-dotenv
  4. 在代码开头加载环境变量文件。
GPT plus 代充 只需 145from dotenv import load_dotenv load_dotenv() # 加载 .env 文件中的环境变量 

完成这些步骤后,再次运行代码,应该就能成功获取模型的回复了。

模型返回的 result 是一个包含丰富信息的对象。对于我们当前的需求,通常只关心其中的文本内容。

我们可以通过访问 content 属性来直接获取回复的文本。

print(result.content) # 输出:The square root of 49 is 7. 

这样,我们就得到了清晰简洁的答案。

如果调用API时遇到余额不足的错误,需要为OpenAI账户充值。

以下是检查和充值余额的步骤:

  1. 访问 OpenAI 平台网站 (platform.openai.com)。
  2. 登录后,进入 Settings -> Billing
  3. 在账单页面可以进行充值。对于学习用途,充值5美元通常足够使用一段时间。

本节课中我们一起学习了Langchain聊天模型的基本设置。我们安装了必要的包,初始化了模型实例,学会了如何调用API并处理常见的API密钥错误,最后还了解了如何解析响应和管理账户余额。在下一节,我们将学习如何向模型发送完整的对话历史,使其能基于上下文给出更准确的回复。

Langchain初学者入门2025:P08:聊天模型与历史记录传递

在本节课中,我们将学习如何将过去的对话历史传递给大型语言模型,以增强其对上下文的理解能力。这对于构建能够进行连贯多轮对话的应用程序至关重要。

上一节我们介绍了如何调用聊天模型,本节中我们来看看如何构建并传递包含历史记录的对话。

在Langchain中,对话由不同类型的消息组成。理解这些类型是构建对话历史的基础。

以下是Langchain中三种核心的消息类型:

  • 系统消息:用于定义AI的角色并设定对话的上下文。它通常在对话开始时发送。
    • 示例代码:SystemMessage(content="你是一名社交媒体营销专家。")
  • 人类消息:代表用户向AI提出的输入或问题。
    • 示例代码:HumanMessage(content="如何在Instagram上创建吸引人的帖子?")
  • AI消息:包含AI基于之前消息生成的回复。
    • 示例代码:AIMessage(content="专注于讲故事,使用吸引人的图片或视频。")

了解了消息类型后,我们来看看如何在代码中使用它们来构建一个对话历史列表。

首先,需要从Langchain的核心包中导入相应的消息类。

GPT plus 代充 只需 145from langchain_core.messages import SystemMessage, HumanMessage, AIMessage 

导入完成后,我们可以初始化一个消息列表来模拟一段对话历史。以下是一个简单的例子:

# 初始化一个消息列表,模拟对话历史 messages = [ SystemMessage(content="你是一名社交媒体内容策略专家。"), HumanMessage(content="给一个在Instagram上创建吸引人帖子的简短建议。") ] 

现在,我们已经有了一个包含系统指令和用户问题的消息列表。接下来,我们需要将这个列表传递给聊天模型以获取回复。

调用模型的方式与我们之前传递单个字符串时类似,但这次我们传入的是消息列表。

GPT plus 代充 只需 145from langchain_openai import ChatOpenAI # 初始化模型 llm = ChatOpenAI(model="gpt-3.5-turbo") # 将消息列表传递给模型 result = llm.invoke(messages) # 打印AI的回复内容 print(result.content) 

运行上述代码,模型将基于我们提供的系统角色和用户问题生成一个简短的回复,例如:“要在Instagram上创建吸引人的帖子,可以专注于讲故事,使用引人注目的图片或视频,并配以简洁、 relatable 的标题。”

为了模拟更真实的多轮对话,我们可以将AI的回复也加入到历史记录中,然后继续添加新的人类消息。

# 扩展对话历史,加入AI的回复和新的用户问题 messages.append(AIMessage(content=result.content)) # 添加上一轮AI的回复 messages.append(HumanMessage(content="能再给一个关于使用话题标签的建议吗?")) # 添加新的用户问题 # 再次调用模型,传入更新后的完整历史记录 new_result = llm.invoke(messages) print(new_result.content) 

通过这种方式,AI在回答第二个问题时,能够“记住”之前的整个对话上下文(包括它自己之前的回答),从而给出更连贯、相关的建议。

虽然目前我们是手动硬编码了对话历史,但理解这个机制至关重要。在实际应用程序中,你可以动态地管理和存储这些消息列表,从而实现真正的、有记忆的对话功能。

本节课中我们一起学习了Langchain聊天模型中传递对话历史的核心方法。我们首先认识了系统消息人类消息AI消息这三种基本组件。然后,我们通过代码演示了如何构建一个消息列表来代表对话历史,并将其传递给模型以获得上下文感知的回复。最后,我们看到了如何通过追加消息来扩展对话,模拟多轮交互。掌握这些概念是开发现实世界中智能对话应用的基础。在下一节,我们将探讨如何利用Langchain轻松地在不同的大语言模型之间进行切换。

在本节课中,我们将学习如何在Langchain框架中轻松地切换不同的聊天模型。之前我们一直使用OpenAI的模型,但实际应用中,你可能需要根据成本、性能或特定任务需求更换为其他模型,如Google的Gemini或Anthropic的Claude。Langchain通过统一的接口抽象了与不同模型API交互的复杂性,使得切换过程变得简单且一致。

首先,我们回顾一下之前使用OpenAI模型的基础代码结构。以下代码定义了一个简单的消息列表,并调用OpenAI的聊天模型进行处理。

GPT plus 代充 只需 145from langchain_openai import ChatOpenAI # 定义消息 messages = [ {"role": "system", "content": "你是一个有用的助手。"}, {"role": "human", "content": "你好!"} ] # 初始化OpenAI聊天模型 chat_model = ChatOpenAI(model="gpt-3.5-turbo") response = chat_model.invoke(messages) print(response.content) 

这段代码你应该已经熟悉。它包含一个系统消息和一条人类消息,并使用ChatOpenAI类进行调用。

上一节我们介绍了OpenAI模型的基本用法。本节中,我们来看看如何将代码中的模型替换为其他提供商的模型,例如Google Gemini或Anthropic Claude。Langchain为不同的模型提供商提供了相应的封装类,这使得切换模型就像更换一个类名和模型参数一样简单。

以下是切换模型的关键步骤:

  1. 安装必要的包:首先,你需要安装目标模型对应的Langchain集成包。
  2. 导入正确的类:在代码中,从相应的模块导入聊天模型类(例如,从langchain_google_genai导入ChatGoogleGenerativeAI)。
  3. 更新初始化代码:使用新的模型类初始化聊天对象,并传入对应的模型名称参数。
  4. 保持调用方式不变:调用invoke方法的方式与使用OpenAI模型时完全相同。

要使用Anthropic的Claude模型,你需要安装langchain-anthropic包,并按照以下方式修改代码:

from langchain_anthropic import ChatAnthropic # 使用Anthropic Claude模型 chat_model = ChatAnthropic(model="claude-3-haiku-") response = chat_model.invoke(messages) print(response.content) 

你可以看到,除了导入的类和指定的模型名称不同,整个调用过程与使用OpenAI模型没有区别。

类似地,要使用Google的Gemini模型,你需要安装langchain-google-genai包。以下是相应的代码示例:

GPT plus 代充 只需 145from langchain_google_genai import ChatGoogleGenerativeAI # 使用Google Gemini模型 chat_model = ChatGoogleGenerativeAI(model="gemini-1.5-flash") response = chat_model.invoke(messages) print(response.content) 

同样,Langchain抽象了与Gemini API交互的细节,你只需要关注使用哪个模型。

了解如何切换模型非常实用。在实际开发产品或解决方案时,根据手头的任务,不同的模型可能各有优势。以下是选择模型时可能需要考虑的因素:

  • 性能:某些模型在特定任务(如代码生成、逻辑推理)上可能更准确。
  • 速度:不同模型的响应延迟可能不同。
  • 成本:模型API的调用价格差异可能很大。
  • 功能特性:某些模型可能支持更长的上下文或特定的输出格式。

Langchain使得根据这些因素灵活调整模型变得非常容易。你可以查阅官方文档来了解每个提供商下所有可用的模型列表。

本节课中我们一起学习了在Langchain中切换不同聊天模型的方法。核心在于理解Langchain通过统一的接口(如.invoke(messages))封装了不同模型提供商的API,使得开发者只需更换模型类的导入和初始化参数,即可无缝切换使用OpenAI、Google Gemini或Anthropic Claude等模型。这为构建灵活、可维护的AI应用提供了极大的便利。

在下一节,我们将进行一个非常有趣的实践:在本地终端与LLM进行对话,类似于ChatGPT的体验,但完全在你的本地环境中运行。敬请期待!

在本节课中,我们将学习如何使用Langchain的聊天模型,在本地终端中实现一个类似ChatGPT的实时对话应用。我们将通过一个循环,动态地管理对话历史,并与大语言模型进行交互。


上一节我们介绍了如何构建一个简单的对话链。本节中,我们将更进一步,创建一个交互式的终端对话程序。这个程序会持续运行,允许用户输入问题,模型会基于完整的对话历史给出回答,从而实现连贯的上下文对话。

以下是实现终端实时对话的核心代码步骤。

首先,我们初始化模型并加载环境变量。

# 初始化聊天模型 from langchain.chat_models import ChatOpenAI from langchain.schema import HumanMessage, AIMessage, SystemMessage import os chat = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0.7) 

接下来,我们创建一个列表来动态存储对话历史。对话总是以一个系统消息开始,用于设定AI的角色。

GPT plus 代充 只需 145# 初始化一个空列表来存储对话历史 chat_history = [] # 首先添加系统消息,设定AI的角色 system_message = SystemMessage(content="你是一个乐于助人的助手。") chat_history.append(system_message) 

程序的核心是一个while循环,它持续运行直到用户输入退出指令。

以下是循环内的关键步骤:

  1. 获取用户输入:程序提示用户在终端中输入问题。
  2. 检查退出条件:如果用户输入“EXIT”,则跳出循环,结束程序。
  3. 添加用户消息:将用户的输入作为HumanMessage添加到chat_history列表中。
  4. 调用模型并获取回复:将包含全部历史的chat_history列表发送给大语言模型。
  5. 添加AI回复:将模型返回的回复作为AIMessage添加到chat_history列表中,为下一轮对话提供上下文。
while True: # 1. 获取用户输入 query = input(“You: “) # 2. 检查退出条件 if query.upper() == “EXIT”: break # 3. 添加用户消息到历史 chat_history.append(HumanMessage(content=query)) # 4. 调用模型,传入整个对话历史 response = chat(chat_history) # 5. 添加AI回复到历史 chat_history.append(AIMessage(content=response.content)) print(f“AI: {response.content}”) 

当运行上述程序时,交互过程如下:

  • 程序启动,显示You:提示符。
  • 用户输入:What is the square root of 49?
  • 模型回复:7
  • 用户继续输入:How did you get that value?
  • 模型能基于之前的对话历史,解释其计算过程。
  • 用户输入:Do the same for 81.
  • 模型理解上下文,直接回复:9
  • 用户输入:EXIT,程序结束。

这个示例展示了模型如何利用chat_history列表来记住对话上下文,从而实现连贯的多轮对话。

本节课中我们一起学习了如何使用Langchain在终端中构建一个实时对话应用。关键点在于使用一个列表来动态维护对话历史,并在每次调用模型时将整个历史传入,这使得模型具备了上下文记忆能力。目前,历史数据存储在程序的内存变量中。在实际生产应用中,通常需要将会话历史保存到数据库或云存储中,以实现数据的持久化。下一节,我们将探讨如何实现对话历史的持久化存储。

在本节课中,我们将学习如何将聊天历史从本地内存迁移到云端数据库(Firebase Firestore)。这样,即使用户关闭应用后重新打开,也能继续之前的对话。


上一节我们介绍了如何在内存中管理聊天历史。本节中,我们将看看如何将其存储在云端,以实现持久化和跨会话的对话连续性。我们将使用 Firebase Firestore 作为云端数据库。

以下是设置 Firebase Firestore 数据库的步骤。

首先,访问 Firebase 控制台 并创建一个新项目。例如,将项目命名为 la-chain。创建过程中可以暂时禁用 Google Analytics。

在项目控制台中,从左侧导航栏的“构建”下拉菜单中选择 Firestore Database。点击“创建数据库”,保留默认位置设置,并选择以“测试模式”启动,以便快速开始。

核心概念:Firestore 是一个文档型 NoSQL 数据库。数据以“集合”和“文档”的形式组织:

  • 集合:类似于文件夹,用于分组文档。
  • 文档:存储实际数据的单元,格式为键值对。
    结构通常是:集合 -> 文档 -> 子集合 -> 子文档



应用需要知道操作哪个 Firebase 项目。在 Firebase 控制台的“项目设置”中,找到并复制 项目 ID。我们将在代码中用到它。

为了让本地应用获得操作 Firebase 的权限,需要安装并认证 Google Cloud CLI。

  1. 访问 Google Cloud SDK 下载页面,根据你的操作系统下载并安装。
  2. 安装后,在终端中运行初始化命令进行认证:
    GPT plus 代充 只需 145gcloud init 
  3. 接着,设置应用默认凭据:
    gcloud auth application-default login 

除了 LangChain 的核心包,我们还需要安装专为 Firebase 集成的包:

GPT plus 代充 只需 145pip install langchain-google-firestore 

完成以上步骤后,云端数据库的基础设置就准备好了。接下来,我们进入代码实现部分。

现在,我们来看如何修改之前的代码,使其能够与云端 Firestore 交互。

首先,确保导入了必要的模块:

from langchain_google_firestore import FirestoreChatMessageHistory from google.cloud import firestore 

与之前仅在内存中维护一个列表不同,现在我们需要初始化一个连接到 Firestore 的聊天历史对象。

GPT plus 代充 只需 145# 1. 初始化 Firestore 客户端,传入你的项目 ID client = firestore.Client(project="your-firebase-project-id") # 2. 创建 Firestore 聊天历史对象 chat_history = FirestoreChatMessageHistory( session_id="user_session_123", # 当前聊天会话的唯一标识符 collection="chat_history", # Firestore 中存储消息的集合名称 client=client # 上一步初始化的客户端 ) 

参数解释

  • session_id:这是对话的唯一标识。在实际应用中,应为每个用户或每个对话会话生成一个唯一的 ID(例如,使用长随机字符串)。
  • collection:在 Firestore 中创建的集合名称,用于存储所有消息。
  • client:已认证的 Firestore 客户端实例。

其余代码与上一节几乎完全相同。主要的区别在于,我们不再使用一个普通的 Python 列表作为 chat_history,而是使用上面创建的 FirestoreChatMessageHistory 对象。

from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain.chains import LLMChain # 初始化模型和提示模板(与之前相同) prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个友好的助手。"), MessagesPlaceholder(variable_name="chat_history"), # 历史消息将自动插入此处 ("human", "{input}") ]) llm = ChatOpenAI(model="gpt-3.5-turbo") chain = LLMChain(llm=llm, prompt=prompt) # 对话循环 while True: user_input = input("你:") if user_input.lower() == 'exit': break # 调用链,传入当前输入和云端的历史记录 response = chain.invoke({"input": user_input, "chat_history": chat_history.messages}) print(f"AI:{response['text']}") # 将用户消息和AI回复添加到历史记录对象中 # FirestoreChatMessageHistory 会自动处理将这些消息保存到云端 chat_history.add_user_message(user_input) chat_history.add_ai_message(response['text']) 

关键点FirestoreChatMessageHistory 类封装了与 Firestore 交互的复杂性。当我们调用 add_user_messageadd_ai_message 时,它不仅会更新本地对象的状态,还会自动将消息持久化到云端的 Firestore 数据库中。同样,当我们初始化它时,它会自动从云端加载指定 session_id 的所有历史消息。

运行上述代码后:

  1. 首次运行:在 Firestore 的 chat_history 集合下,会创建一个以 session_id 命名的文档,其中包含对话消息的子集合。你的消息和 AI 的回复会作为文档存储在其中。
  2. 停止程序后重新运行:只要使用相同的 session_id,程序启动时就会自动从 Firestore 加载之前的所有对话历史,从而实现无缝的对话延续。

本节课中我们一起学习了如何将 LangChain 聊天应用的历史记录从内存迁移到 Firebase Firestore 云端数据库。我们完成了 Firebase 项目的创建、数据库设置、本地环境认证,并最终使用 langchain-google-firestore 包提供的 FirestoreChatMessageHistory 类,以极少的代码改动实现了聊天历史的云端持久化。这为构建可投入生产环境、支持多会话的聊天应用奠定了基础。

接下来,我们将进入 LangChain 另一个核心组件——提示模板(Prompt Templates) 的学习。它将帮助我们更高效、规范地构建和管理发送给 AI 模型的指令。

在本节课中,我们将要学习LangChain的第二个核心组件:提示模板。提示模板是一种强大的工具,它允许我们创建可复用的提示结构,并通过动态替换占位符来生成具体的提示内容。这在构建生产级应用时非常有用。

提示模板的核心思想是将一个包含占位符的字符串(模板)转换为LangChain能够理解的格式,然后通过传入具体的值来填充这些占位符,最终生成一个完整的提示发送给大语言模型。

为了更好地理解提示模板,让我们先看一个简单的例子。假设我们需要一个生成求职邮件的提示。

以下是一个基本的提示字符串,其中包含了一些占位符,如{tone}{company}{position}{skill}

GPT plus 代充 只需 145template_string = “写一封语气为 {tone} 的邮件给 {company} 公司,表达对 {position} 职位的兴趣,并提及 {skill} 作为关键优势。” 

这些占位符可以被自定义的值替换。例如:

  • tone 可以是“热情的”、“自信的”、“专业的”或“温暖的”。
  • company 可以是“谷歌”、“亚马逊”、“三星”等任何公司。
  • position 可以是“设计师”、“开发工程师”等职位。
  • skill 是求职者具备的技能。

如果我们传入具体值,例如:tone=专业, company=三星, position=AI工程师, skill=机器学习,那么经过替换后,我们将得到一个完整的提示字符串:“写一封语气为 专业 的邮件给 三星 公司,表达对 AI工程师 职位的兴趣,并提及 机器学习 作为关键优势。”

如果你正在构建一个根据这些不同参数生成邮件的产品,使用提示模板可以轻松实现这一功能。

现在,让我们看看如何在代码中实现上述功能。首先,我们创建一个新文件并初始化一个聊天模型,这与我们之前所做的没有区别。

# 导入必要的库 from langchain_openai import ChatOpenAI # 初始化聊天模型 llm = ChatOpenAI(model=“gpt-3.5-turbo”) 

第一步是创建一个包含占位符的提示字符串。请注意,这个字符串目前只是我们人类能够理解的格式。

GPT plus 代充 只需 145# 步骤1:定义模板字符串 template_string = “写一封语气为 {tone} 的邮件给 {company} 公司,表达对 {position} 职位的兴趣,并提及 {skill} 作为关键优势。” 

下一步是将这个提示字符串转换为LangChain能够理解的格式。为此,我们需要导入ChatPromptTemplate类。

# 步骤2:导入ChatPromptTemplate并转换模板 from langchain_core.prompts import ChatPromptTemplate prompt_template = ChatPromptTemplate.from_template(template_string) 

langchain_core包在我们之前安装langchain-openai时已自动安装。现在,如果我们打印prompt_template变量,可以看到一个包含各种属性(如输入变量)的对象。

第三步是使用具体的值来填充模板中的占位符。我们使用invoke方法来完成这个操作。

GPT plus 代充 只需 145# 步骤3:填充占位符 prompt = prompt_template.invoke({ “tone”: “充满活力的”, “company”: “三星”, “position”: “AI工程师”, “skill”: “深度学习” }) 

运行后,prompt变量将包含一个消息列表(通常是一个HumanMessage),其内容中的占位符已被替换。这正是发送给LLM所需的格式。

最后,我们可以将这个构建好的提示发送给聊天模型以获取回复。

# 步骤4:发送给LLM result = llm.invoke(prompt) print(result.content) 

运行代码,你将收到一封根据指定参数(如“充满活力的”语气)生成的求职邮件。

上面介绍的方法有一个局限性:它总是创建一个只包含单条人类消息的列表。但有时我们需要更多的控制,例如,创建一个同时包含可定制系统消息和人类消息的提示。

实现这一点也非常简单。我们可以使用一个元组列表来定义消息序列。

GPT plus 代充 只需 145# 定义包含系统消息和人类消息的模板 message_templates = [ (“system”, “你是一个专业的求职助手。请根据以下要求生成邮件。”), (“human”, “写一封语气为 {tone} 的邮件给 {company} 公司,表达对 {position} 职位的兴趣,并提及 {skill} 作为关键优势。”) ] # 使用 from_messages 方法创建模板 complex_prompt_template = ChatPromptTemplate.from_messages(message_templates) # 填充占位符 final_prompt = complex_prompt_template.invoke({ “tone”: “专业的”, “company”: “三星”, “position”: “AI工程师”, “skill”: “强化学习” }) print(final_prompt) 

运行这段代码,你将看到一个包含SystemMessageHumanMessage的列表,其中的占位符也已被正确替换。如果你的产品需要这种功能,现在你就知道如何实现了。同样,你可以将这个final_prompt传递给LLM来获取回复。

本节课我们一起学习了LangChain的核心组件——提示模板。我们掌握了如何:

  1. 创建一个包含占位符的字符串模板。
  2. 使用ChatPromptTemplate.from_template()将其转换为LangChain格式。
  3. 使用.invoke()方法并传入字典来动态填充占位符。
  4. 将生成的完整提示发送给大语言模型以获取响应。
  5. 通过ChatPromptTemplate.from_messages()构建包含多种角色(如系统消息)的更复杂提示序列。

提示模板是一个相对简单但极其重要的组件,它使我们的提示变得可复用和可动态配置,为构建复杂的AI应用奠定了基础。

在下一节中,我们将探讨另一个核心组件:链。这是一个功能强大且充满趣味的组件,其潜力几乎是无限的。敬请期待!

在本节中,我们将介绍Langchain的第三个核心组件——链。链是我个人最喜欢的组件,因为它允许你将多个任务连接起来,从而创建一个统一的工作流。

链的核心思想是将多个独立的处理步骤串联起来。每个步骤的输出可以作为下一个步骤的输入。这类似于工厂的流水线,每个环节处理特定的任务,最终共同完成一个复杂的目标。

链主要分为三种类型,每种类型适用于不同的场景。以下是这三种类型的详细介绍。

顺序链是最直观的链类型。任务一个接一个地执行,前一个任务的输出是后一个任务的输入。

公式表示: 任务A -> 任务B -> 任务C

例如,一个简单的工作流可能包含以下步骤:

  1. 使用提示模板生成问题。
  2. 将生成的问题发送给大语言模型获取答案。
  3. 将大语言模型的答案翻译成另一种语言(如法语)。
  4. 将翻译后的结果通过电子邮件发送出去。

这个流程就是典型的顺序链,每个环节都依赖于前一个环节的结果。

与顺序链不同,并行链中的多个任务可以同时执行,彼此之间没有依赖关系。所有任务执行完毕后,其结果可以汇聚到下一个环节。

代码逻辑描述:

# 伪代码示例 task1_result = run_parallel(task_brew_coffee) task2_result = run_parallel(task_make_toast) task3_result = run_parallel(task_fry_egg) final_result = serve_breakfast(task1_result, task2_result, task3_result) 

例如,准备一份早餐可能涉及三个并行任务:煮咖啡、烤面包、煎鸡蛋。这三个任务可以同时进行,互不干扰,最后将它们的产出组合成一份完整的早餐。






条件链引入了分支逻辑。任务的执行路径会根据某个条件或决策点的结果而改变,只有满足条件的那个分支会被执行。

代码逻辑描述:

GPT plus 代充 只需 145# 伪代码示例 user_choice = get_user_input() if user_choice == “技术问题”: run_chain(troubleshooting_sequence) elif user_choice == “账单问题”: run_chain(billing_inquiry_sequence) else: run_chain(general_faq_sequence) 

例如,在一个客服聊天机器人中,根据用户选择的问题类型(技术问题、账单问题或一般咨询),系统会触发完全不同的处理流程。每个流程都是一个独立的链,系统只会执行与用户选择相匹配的那一个。

本节课我们一起学习了Langchain中“链”的概念。我们了解到,链是将多个组件连接成工作流的核心工具。我们介绍了三种主要的链类型:顺序链用于线性依赖的任务,并行链用于可同时执行的独立任务,而条件链则用于实现基于不同条件的分支逻辑。理解这些链的类型是构建复杂AI应用的基础。在接下来的章节中,我们将通过代码实践,学习如何具体实现这些链。

在本节课中,我们将要学习Langchain中“链”的基础概念。链允许我们将多个任务(如创建提示词、调用大语言模型、解析输出)串联成一个流畅的工作流程,从而简化代码并提高可读性。

上一节我们介绍了提示词模板的创建。本节中,我们来看看如何利用“链”将这些独立的步骤高效地组合起来。

首先,我们需要初始化大语言模型并创建一个提示词模板。这与之前章节的步骤类似。

# 初始化OpenAI聊天模型 from langchain_openai import ChatOpenAI model = ChatOpenAI(model="gpt-3.5-turbo") # 使用消息列表创建提示词模板 from langchain_core.prompts import ChatPromptTemplate prompt = ChatPromptTemplate.from_messages([ ("system", "你是一位动物专家,了解任何动物的知识,例如猫、狗、大象等。"), ("human", "告诉我关于{animal}的{count}个事实。") ]) 

我们采用这种方式创建模板,是为了在模板中包含更多信息,例如系统消息和人类用户消息。系统消息定义了AI的角色,而人类消息则包含了需要填充的占位符,如 {animal}{count}

在之前的章节中,我们采用分步调用的方式:

  1. 调用提示词模板填充占位符。
  2. 将填充后的提示词传递给大语言模型。
  3. 处理模型的响应。

虽然这是正确的流程,但我们需要重复调用 .invoke() 方法,代码显得较为繁琐。

链的核心思想是使用管道操作符 | 将不同的任务连接起来。这使代码更加简洁和易读。

以下是创建和调用链的步骤:

  1. 构建链:使用 | 操作符将提示词模板、模型和一个输出解析器串联起来。
  2. 调用链:使用 chain.invoke() 方法,并传入一个包含所有占位符值的字典。
  3. 传递数据:传入的字典数据在整个链的所有任务中都是可用的。

以下是具体代码示例:

GPT plus 代充 只需 145from langchain_core.output_parsers import StrOutputParser # 创建链:提示词模板 -> 模型 -> 输出解析器 chain = prompt | model | StrOutputParser() # 调用链,传入占位符的值 response = chain.invoke({"animal": "猫", "count": "2"}) print(response) 

在这个链中:

  • 第一个任务 prompt 接收输入并生成完整的提示词。
  • 第二个任务 model 接收上一步的提示词并生成AI响应。
  • 第三个任务 StrOutputParser() 是一个内置函数,它从模型返回的复杂响应对象中提取出纯文本内容。

运行上述代码,我们将得到关于猫的两个事实。如果我们改变输入,例如 {"animal": "大象", "count": "1"},链将相应地输出关于大象的一个事实。

通过链,我们用几行代码就完成了提示词生成、模型调用和输出解析的整个流程。

相比之下,传统的分步调用方法需要编写更多的中间步骤和变量赋值代码。链显著简化了复杂工作流的构建,使代码意图更清晰,更易于维护。

本节课中我们一起学习了Langchain中“链”的基础用法。我们了解了如何将提示词模板、大语言模型和输出解析器连接成一个连贯的工作流,并看到了链如何使代码变得更加简洁高效。

链的核心优势在于其可组合性简洁性,公式可以概括为:chain = task1 | task2 | task3 ...

在下一节中,我们将探索链的内部工作原理。这将帮助我们理解如何创建自定义任务。例如,本节我们使用了预置的 StrOutputParser 来提取文本内容,但有时我们可能需要编写自己的解析函数来实现更特定的逻辑。为了做到这一点,我们需要理解链在底层是如何运作的。

在本节课中,我们将学习Langchain中“链”的内部工作原理。通过理解其底层机制,你将能够根据自身业务需求更灵活地定制工作流。我们将通过手动构建一个链,来揭示使用管道操作符(|)时背后实际发生的事情。

在上一节中,我们使用管道操作符将不同的任务连接在一起。我们创建了一个提示词模板,其结果传递给模型,然后大语言模型(LLM)的结果再传递给另一个仅提取content属性的任务。本节中,我们将尝试在不使用管道操作符的情况下实现相同的功能,这有助于我们看清链在底层是如何运作的。

首先,我们创建一个新文件,并复制上一节中编写的所有代码。这包括创建模型、提示词模板以及定义占位符。

# 此处是上一节的所有代码,用于创建模型和提示词模板 

我们的目标是创建一个工作流,它需要完成三个任务:

  1. 使用提供的值调用提示词模板,生成最终的提示词字符串。
  2. 将生成的提示词字符串传递给聊天模型,调用LLM。
  3. 从LLM的响应中仅提取content属性。

为了手动连接这些任务,我们需要引入两个新概念:RunnableLambdaRunnableSequence

RunnableLambda是一个简单的包装器,它允许我们将每个任务封装成一个独立、可复用的单元。每个RunnableLambda接收输入,对其进行处理(例如填充提示词或调用模型),然后输出结果。这样,我们可以将每个步骤平滑地连接起来。

以下是创建第一个任务(格式化提示词)的示例:

GPT plus 代充 只需 145from langchain_core.runnables import RunnableLambda, RunnableSequence # 任务1:格式化提示词 task1 = RunnableLambda(lambda x: prompt.format_prompt(x)) 

在这个RunnableLambda中,我们提供了一个函数(这里使用了Python的lambda匿名函数),它接收输入值x(一个字典)并返回计算后的值。format_prompt(x)方法会用字典x中的值替换提示词模板中的所有占位符。

请注意:这里我们使用的是format_prompt方法,而不是上一节中直接使用的invoke方法。invoke方法不仅会替换占位符,还会将数据转换为适合发送给LLM的格式。而format_prompt仅负责替换占位符,数据格式的转换将在我们最终调用整个链时完成。

现在,我们继续定义第二个和第三个任务。

# 任务2:调用LLM模型 task2 = RunnableLambda(lambda x: model.invoke(x)) # 任务3:从响应中提取content属性 task3 = RunnableLambda(lambda x: x.content) 

至此,我们有了三个独立的Runnable(即可运行任务单元),但它们尚未连接成一个统一的工作流。

接下来,我们需要将这些任务链接起来。Langchain提供了RunnableSequence类来实现这一功能。

RunnableSequence类总是接收三个参数:

  • 第一个参数:工作流的第一个任务(单个Runnable)。
  • 中间参数:一个包含所有中间任务的列表(List[Runnable])。无论中间有一个、两个还是一千个任务,都放在这个列表里。
  • 最后一个参数:工作流的最后一个任务(单个Runnable)。

以下是创建链的代码:

GPT plus 代充 只需 145# 使用RunnableSequence将任务连接成链 chain = RunnableSequence(task1, [task2], task3) 

现在,我们得到了一个完整的链chain,可以通过提供占位符值来调用它:

# 调用链并传入参数 result = chain.invoke({"topic": "cats", "count": 2}) print(result) 

运行代码,我们将得到关于猫的两个事实,这与我们的预期一致。

实际上,使用RunnableSequence类和使用管道操作符(|)在底层是等价的。管道操作符是Langchain表达式语言(LCEL)的一部分,它提供了一种更简洁、更易读的方式来连接Runnable

将两种方式对比来看:

GPT plus 代充 只需 145# 方式一:使用RunnableSequence类(本节介绍的方法) chain_v1 = RunnableSequence(task1, [task2], task3) # 方式二:使用管道操作符(上一节使用的方法,LCEL) chain_v2 = task1 | task2 | task3 

显然,使用管道操作符的代码更加简洁明了。在大多数实际开发中,推荐使用LCEL和管道操作符来构建链。

本节课中,我们一起深入探讨了Langchain链的内部工作原理。我们学习了如何:

  1. 使用RunnableLambda将每个处理步骤封装成独立的任务单元。
  2. 使用RunnableSequence类手动将这些任务单元按顺序连接成一个完整的工作流。
  3. 理解了这种手动构建方式与使用管道操作符(|)的LCEL方式在功能上是等效的,但后者在语法上更简洁。

理解这些底层机制为你未来定制复杂链或调试工作流打下了坚实基础。在下一节中,我们将探索更有趣的内容:不同类型的链,如扩展链、并行链和条件分支链,并了解它们在实际场景中的应用。

在本节课中,我们将学习如何扩展已构建的链,以及如何持续添加更多的可运行组件。我们将通过一个具体示例,演示如何将多个任务按顺序连接起来,形成一个完整的工作流。


上一节我们介绍了链的基本概念。本节中,我们来看看如何将多个可运行组件按顺序链接,构建更复杂的处理流程。

我们使用与上一节相同的提示词模板,但为其赋予了一个更清晰的名称。

prompt_template = PromptTemplate(...) 

我们还添加了更多提示词。现在,让我们直接查看链的核心部分,从头开始理解其工作原理。

首先,我们已有前三个步骤。第一个提示词模板会指示模型生成关于特定动物的若干条事实。在本例中,是生成关于猫的两条事实。

GPT plus 代充 只需 145# 生成关于猫的两条事实 chain_part1 = prompt_template | model | StrOutputParser() 

我们在此处仅提取文本内容。很好,现在我们得到了两条事实。

那么接下来呢?

假设我的应用场景是运营一个法语推特页面,而非英语页面。模型生成的响应是英文的。因此,我需要做的下一件事是将这段文本发送给大语言模型,要求其翻译成法语。

在调用大语言模型之前,我们总是需要做一件事:准备一个提示词模板,用于指示模型将文本翻译成特定语言。提示词模板需要一个对象作为输入。然而,上一步的输出只是一个字符串,并非对象。因此,我们需要准备输入,使其能够传入提示词模板。

为此,我们编写了一个名为 prepare_input_for_translation 的可运行组件。

def prepare_input_for_translation(previous_output): # 接收上一步的输出 # 返回一个包含文本和语言信息的字典对象 return {"text": previous_output, "language": "French"} 

这个组件接收上一个任务(生成事实)的输出。你可以看到,它同时提供了输出文本和目标语言,并将其作为一个对象返回给下一个任务。

现在,这个对象被传入翻译的提示词模板。模板指示模型:“你是一名翻译,你的工作是将文本转换为指定语言”。我们已在对象中提供了文本和语言信息。最终,完整的提示词被生成。

随后,这个提示词被发送给模型,模型被调用,我们最终将翻译后的内容打印到控制台。整个过程就是如此简单。

让我们实际运行这个文件。点击运行按钮,稍等几秒钟。

完美。我们可以看到关于猫的两条事实已被翻译成法语(虽然我也看不懂)。我们甚至可以进一步扩展这个链。

例如,我们可以创建另一个可运行组件,让它调用推特API,将这条翻译后的内容作为一条推文发布出去。

GPT plus 代充 只需 145# 伪代码示例:发布到推特 def post_to_twitter(translated_text): # 调用推特API的逻辑 pass 

由此可见,在使用链时,可能性是无限的。我们可以持续地、按顺序在一条直线上添加越来越多的链。这就是顺序链的核心概念。

在下一节中,我们将探讨一种称为“并行链”的链类型。敬请期待。


本节课中,我们一起学习了如何构建顺序链。我们从一个生成事实的简单链开始,通过添加一个翻译组件将其扩展,并理解了如何通过准备合适的输入对象来连接不同的任务。顺序链允许我们将多个步骤线性组合,构建出功能强大的自动化工作流。

Langchain初学者入门2025:P17:并行链 🚀

在本节中,我们将学习如何在Langchain中使用并行链。并行链允许我们同时启动并运行多个独立的处理流程,并在所有流程完成后汇总结果,这能显著提升复杂任务的执行效率。

为了理解其工作原理,让我们回顾一下基本概念。并行链的执行模式是:首先触发一个初始任务,这个任务会同时启动两个、三个甚至上百个独立的处理链。这些链会并行运行。一旦所有链都执行完毕,我们就可以对所有链的输出结果进行后续处理。

我们将通过一个简单的例子来演示。假设我们要撰写一篇关于电影《盗梦空间》的博客文章。我们希望博客的第一部分分析电影情节,第二部分分析电影角色。

以下是实现此目标的步骤:

  1. 首先,我们从大语言模型获取电影的摘要。
  2. 得到摘要后,我们同时启动两个独立的链:
    • 第一个链负责分析情节。
    • 第二个链负责分析角色。

这个例子虽然简单,但其模式可以应用于各种场景:触发一个任务,并行启动多个处理链,最后汇总所有结果。

现在,让我们深入代码,一步步查看具体实现。

首先,我们需要从LLM获取电影摘要。在调用LLM之前,我们总是需要先构建提示词。

# 定义获取摘要的提示词模板 summary_template = ChatPromptTemplate.from_messages([ ("system", "你是一位影评人。请为我提供电影《{movie}》的简要摘要。") ]) 

获取摘要后,有趣的部分开始了。我们将使用Langchain提供的 RunnableParallel 类来并行运行多个链。

GPT plus 代充 只需 145from langchain_core.runnables import RunnableParallel # 创建并行链 parallel_chain = RunnableParallel( branches={ "plot_analysis": plot_chain, "character_analysis": character_chain } ) 

RunnableParallel 中,我们定义了一个 branches 字典,为每个要并行运行的链指定一个名称。这里我们有两个分支:

  • plot_chain: 分析情节的链。
  • character_chain: 分析角色的链。

每个分支本身都是一个完整的链。例如,plot_chain 会接收上一步得到的电影摘要,构造一个要求分析情节的提示词,然后调用LLM获取输出。character_chain 的结构与之类似。

当所有这些并行链都执行完毕后,我们会进入链的下一阶段,在那里我们将所有结果组合起来。

# 假设这是组合结果的方法 def combine_results(results): # results 是一个字典,包含各个分支的输出 plot = results["plot_analysis"] characters = results["character_analysis"] return f"情节分析:{plot} 角色分析:{characters}" 

RunnableParallel 的输出是一个对象,其 branches 属性包含了每个分支名称及其对应的结果。我们只需将这些结果提取并组合即可。

运行这段代码,我们会先得到电影摘要,然后并行进行情节和角色分析。最终输出将包含两部分清晰的分析内容。

这个简单的例子展示了并行链的基础用法。你可以根据业务需求,将其应用于多种场景。我再举一个例子,以便你更好地理解这个概念的应用。

假设你需要就同一个主题在多个社交媒体渠道(如Twitter、LinkedIn、Instagram、Facebook)上发布内容。每个渠道都有其独特的格式和风格要求。

我们可以这样设计流程:

  1. 初始任务:收集关于该主题的所有必要信息。
  2. 并行处理:为每个社交媒体渠道启动一个独立的链。每个链负责:
    • 根据目标渠道的风格格式化内容。
    • (可选)调用该渠道的API,将内容发布为帖子或保存为草稿以供审核。

通过这种方式,我们高效地完成了跨平台的内容适配与发布任务。并行链的可能性几乎是无限的,它为我们处理需要多路并发的复杂工作流提供了强大支持。

本节课我们一起学习了Langchain中的并行链。我们了解了其同时触发、独立运行、汇总结果的核心工作模式,并通过电影博客分析的例子实践了如何使用 RunnableParallel 来构建并行处理流程。最后,我们还探讨了它在多社交媒体渠道内容发布等真实场景中的应用。掌握并行链将帮助你设计出更高效、更强大的AI应用工作流。

在下一节中,我们将学习最后一种链类型:条件链。敬请期待。

在本节课中,我们将学习最后一种常用的链类型:条件链。我们将通过一个电商网站用户反馈处理的自动化案例,来理解如何根据不同的条件,让AI流程走向不同的分支。

上一节我们介绍了并行分支链,它会让所有分支同时执行。本节中我们来看看条件链,它的核心是根据一个判断条件,让控制流选择性地进入多个分支中的某一个。这就像编程中的 if-elseswitch-case 语句。

假设你运营一个电商网站,用户可以对购买的产品留下反馈。根据反馈的类型(正面、负面、中性或需要升级处理),客服的回应方式会完全不同。我们的目标是使用Langchain和AI来自动化这个客服响应流程。

其工作流程如下图所示:






流程分为两步:

  1. 分析反馈:首先,我们将用户反馈发送给模型,让它判断反馈属于“正面”、“负面”、“中性”还是“需要升级”。
  2. 条件分支:然后,根据上一步的判断结果,控制流会进入对应的分支链,生成特定的客服回应。

让我们通过代码来具体看看如何实现这个条件链。我们将采用从整体到局部的方式进行分析。

首先,我们需要一个链来分析用户反馈的情感。以下是这个分类链的核心代码结构:

GPT plus 代充 只需 145# 1. 创建提示模板 classification_prompt = ChatPromptTemplate.from_template( “”” 你是一个乐于助人的助手。 请将以下反馈的情感分类为:positive, negative, neutral 或 escalate。 反馈:{feedback} “”” ) # 2. 构建分类链 classification_chain = classification_prompt | llm | StrOutputParser() 

这个链的作用是接收用户 feedback,并输出一个分类关键词(positive/negative/neutral/escalate)。这对应了流程图中的第一步。

获得反馈类型后,我们需要根据它路由到不同的处理链。这通过 RunnableBranch 实现,它类似于一个 switch-case 语句。

以下是定义分支的代码逻辑:

from langchain.schema.runnable import RunnableBranch # 定义不同的处理分支 branches = RunnableBranch( (lambda x: x[“sentiment”] == “positive”, positive_chain), (lambda x: x[“sentiment”] == “negative”, negative_chain), (lambda x: x[“sentiment”] == “neutral”, neutral_chain), escalate_chain # 默认分支 ) 

每个分支都是一个独立的链。例如,positive_chain 的提示可能是:“请为这条正面反馈生成一封感谢信。反馈:{feedback}”。

最后,我们将分类链和分支链组合起来,形成完整的工作流。

GPT plus 代充 只需 145# 组合链:先分类,再根据结果路由到对应分支 full_chain = | branches # 路由到分支 

当运行这个完整链时,它会自动执行“分析->路由->生成响应”的完整流程。

我们用一个硬编码的负面反馈来测试:“产品太差了,只用了一次就坏了。”

运行程序后,AI会先将其分类为“negative”,然后路由到负面反馈处理链。该链可能会生成如下回应:

“对于您糟糕的体验我们深表歉意。感谢您提出这个问题。为了能更好地帮助您,能否提供更多您遇到的细节?”

你可以尝试替换不同的反馈内容(如正面、中性、需升级的反馈),观察控制流如何进入不同的分支并生成相应的回应。

本节课我们一起学习了条件链。我们了解了如何:

  1. 使用一个链(分类链)来生成判断条件。
  2. 利用 RunnableBranch 根据条件将控制流路由到多个分支链中的一个。
  3. 构建一个完整的、能根据输入内容动态选择处理路径的AI应用。

掌握条件链后,你应该有信心根据实际业务问题来设计和构建自己的链式工作流了。

在下一节中,我们将进入Langchain另一个极为重要且强大的模块:RAG(检索增强生成)。它正在深刻改变许多业务的运作方式,能极大提升效率。虽然理解起来略有挑战,但掌握它将使你成为一名非常有价值的开发者。我们下节课见!

在本节课中,我们将开始学习Langchain的第四个核心概念:RAGs,即检索增强生成。我们将了解RAGs是什么,它解决了什么问题,以及它如何工作。


RAGs主要解决大语言模型的一个核心问题:为LLMs提供额外的知识。换句话说,RAGs为LLM提供了一个外部信息来源,使其能更好地回答用户的问题。

如果这听起来有些抽象,让我们通过一个简单的例子来探索。

假设你在一家公司工作,公司内部有数百份文档,例如政策指南、技术规格书、客户支持文档等。这些都是LLM所不知道的私有文档。

当你有一个问题,而答案可能存在于这些文档中的任何一份时,在没有AI的情况下,你通常需要手动翻阅每一份文档来寻找答案,或者询问同事。

然而,借助AI,特别是RAGs技术,我们可以极大地简化这一过程。我们不是手动翻阅所有文件,而是让LLM能够访问所有这些私有数据。这样,下次你有问题时,只需用简单的英语提问。由于LLM现在可以访问你的所有私有信息,它能够梳理这些数据,并为你提供一个信息充分且准确的答案。这就是RAGs的力量。


但这引出了另一个重要挑战:上下文限制。你可能遇到过这种情况:当你向LLM输入大量文本时,它很难回答问题,甚至开始“幻觉”或忘记之前的内容。这是因为上下文窗口或限制变得非常高。

因此,我们不能简单地将所有私有文档都“倾倒”到ChatGPT等模型的提示词中,然后提问,因为这是行不通的。

RAGs解决的第二个问题正是上下文限制。它让你可以将所有PDF等文档提供给LLM,而无需实际将它们全部塞进一个提示词里。RAGs的解决方式是:仅根据用户提供的提示,从文档中提取相关的部分

通过这种方式,我们保持了信息的精简,只从所有文档中提取与用户问题相关的特定“块”,然后发送给LLM。这也正是它被称为“检索增强生成”的原因。


让我们来看一个更准确的定义:

RAGs是一种将LLMs与检索系统相结合的方法。

这个检索系统可以在LLM需要额外知识以提供更好答案时,搜索海量的外部信息来源,如文档、数据库、知识库等。

同时,它确保LLM不会被过大的问题所淹没。


本节课只是对RAGs的一个高层次介绍。我们不要求你现在就完全理解它。

在接下来的几节中,当我们真正开始将整个RAG模块拆解成其各个组成部分时,一切都会变得清晰明了。这正是我们下一节要开始做的事情。

在本节课中,我们将深入拆解检索增强生成系统的完整工作流程,并逐一剖析其核心组件。通过理解每个部分如何协同工作,你将发现实际的编码实现会变得非常简单。我们将使用上一节提到的例子,从处理大量私有文档开始,直到将其存储到向量数据库中。

上一节我们介绍了RAG系统的整体概念,本节中我们来看看其数据处理流程的第一步。

在流程图中,我们从一个巨大的PDF文档开始,它可能包含上千万个标记。首先,我们需要理解什么是“标记”。

在语言模型的语境中,一个标记是模型处理的基本文本单位。标记可以短至一个字符,或长至一个单词,具体取决于语言和文本结构。

例如:

  • hello 是一个标记。
  • 短语 I am 通常被拆分为两个标记:Iam

标记至关重要,因为所有大语言模型一次能处理的标记数量都有限制,这个限制被称为上下文窗口。这意味着,如果一个PDF有1000万个标记,我们无法一次性将其全部输入给模型。

以下是不同流行模型的上下文窗口限制:

  • GPT-3.5 Turbo(免费版ChatGPT使用的模型):约 16,000 个标记。
  • GPT-4:约 128,000 个标记。
  • Google Gemini:高达 1,000,000 个标记。

在我们的例子中,文档有1000万个标记,远超任何模型的上下文窗口。因此,直接向LLM提问是不可行的,模型会报错提示内容过长。这也不是一个可扩展的解决方案,因为未来文档可能会增长,而模型的上下文窗口可能保持不变。

这正是RAG系统中引入分块步骤的原因。分块是将大型文档分割成多个较小片段的过程。

如图所示,我们将大文档分割成许多小块。假设每个块包含1000个标记,那么1000万标记的文档就会被分成大约10,000个块。

我们进行分块是因为无法将整个大文件扔给ChatGPT。但如果我们创建了小块,系统就可以根据用户的提问,通过检索器组件只查询那些相关的块,然后将这些块与用户问题一起传递给模型。

通过这种方式,我们可以传递多个块。例如,如果每个块是1000个标记,而免费版ChatGPT的上下文窗口是16000个标记,那么我们大约可以传递13-14个不同的块,同时为用户的问题留出足够的空间。

如果你好奇分块具体是什么,它们本质上就是纯文本。想象一本500页的书,如果每个块只能有1000个标记,那么每个块可能就相当于4到5行文字或一个小段落。下一个块则是接下来的4到5行,顺序是保持连续的。

到目前为止,我们已经将大文本分割成了块。我们的下一个目标是:如何只提取与用户问题相关的块,并将这些特定块与问题一起发送给模型?

我们如何查询仅相关的块?当然,我们可以暴力搜索,寻找与问题完全匹配的单词,但这是种过时且低效的方法。我们需要根据含义而非确切的单词来收集块。

这时,嵌入向量数据库的概念就登场了。它们允许我们进行语义搜索,即基于含义进行搜索,而不是寻找完全相同的单词或字母。

在下一部分,我们将简要了解一下嵌入和向量数据库是什么,然后再回到这个RAG流程中。


本节课总结:我们一起学习了RAG工作流程的第一部分。我们明白了由于大语言模型存在上下文窗口限制,无法直接处理超长文档。因此,我们引入了分块技术,将大文档切割成小块。同时,我们提出了一个新问题:如何从成千上万个块中,智能地找出与用户问题最相关的那些?这为下一节引入嵌入向量数据库来解决语义检索问题做好了铺垫。

在本节课中,我们将学习嵌入(Embeddings)和向量数据库(Vector Databases)的核心概念,并了解它们如何协同工作,为检索增强生成(RAG)等应用提供支持。

嵌入是一种数学表示方法,用于将单词、句子甚至图像转换为计算机可以理解的格式。简单来说,它把文本等数据转换成一串数字(即向量)。

例如,单词“cat”的向量嵌入可能看起来像这样:[34, 0.9, -1.2, ...]。这是计算机理解“cat”这个词的方式。ChatGPT等大型语言模型在底层也使用了类似的技术。

那么,这些数字代表什么呢?它们可以被视为描述某个概念的多个维度或特征。

数组中的每个数字可以代表单词的某个特定方面。

  • 第一个数字:可能代表物体的大小。例如,“cat”的第一个数值是34。那么“kitten”(小猫)的向量中,第一个数值会接近34,因为它们在大小上相似。相比之下,“elephant”(大象)嵌入的第一个数值会与34相差甚远。
  • 第二个数字:可能代表它是否是宠物。由于猫和狗都是宠物,“dog”向量的第二个数值会与“cat”的第二个数值非常接近。

我们可以把每个向量的值看作是多维空间中的坐标

让我们看一个简单的二维图表示例。我们知道“cat”和“kitten”在含义上几乎相似,它们的向量坐标也非常接近。因此,在向量空间中,“cat”和“kitten”这两个点会靠得很近。

而像“mango”(芒果)这样的词,在几乎所有方面都不同,其坐标也会完全不同,因此在向量空间中会离“cat”和“kitten”非常远。














这只是一个简单的2D示例。实际上,嵌入向量通常有数百甚至数千个维度,包含了关于词语、句子之间关系的丰富信息。语义相似的词或句子,其向量在空间中的位置也会彼此靠近;不相关的则会相距甚远。

一旦我们创建了向量嵌入,就需要将其存储起来。专门用于存储和检索这些向量嵌入的数据库,就称为向量数据库

与传统数据库基于精确匹配(如关键词)来检索信息不同,向量数据库的核心能力是基于语义或含义进行检索。它能够找到与查询向量在意义上最相近的向量,即使它们没有使用相同的词语。

上一节我们介绍了RAG(检索增强生成)。现在,让我们看看嵌入和向量数据库是如何在RAG中发挥关键作用的。

在RAG流程中:

  1. 首先,将您的文档(如知识库、文章)分割成较小的块(chunks)。
  2. 然后,使用嵌入模型将这些文本块转换为向量嵌入
  3. 接着,将这些向量存储到向量数据库中。
  4. 当用户提出一个问题时,系统同样将这个问题转换为向量(查询向量)。
  5. 向量数据库会执行相似性搜索,快速找出与问题向量最相似的文档块向量。
  6. 最后,将这些检索到的相关文本块作为上下文,与原始问题一起发送给大语言模型(LLM),从而生成更准确、基于知识的回答。






本节课我们一起学习了嵌入和向量数据库的核心概念。

  • 嵌入是将非结构化数据(如文本)转换为计算机可处理的数值向量,其中语义相似的数据在向量空间中位置相近。
  • 向量数据库是专门为高效存储和检索这些向量而设计的数据库,支持基于语义的相似性搜索。
  • RAG等应用中,这两项技术结合使用,使大语言模型能够访问和利用外部知识,生成更可靠的回答。

理解嵌入和向量数据库是掌握现代AI应用,特别是那些需要处理和理解大量文本信息应用的基础。

在本节课中,我们将继续学习RAG(检索增强生成)工作流程的第一部分。我们将具体了解如何将文档块转换为向量嵌入,并将其存储到向量数据库中,为后续的检索步骤做好准备。


上一节我们介绍了嵌入和向量数据库的基本概念。本节中,我们来看看如何将分割好的文档块转化为向量,并存入向量数据库。

我们已经将一份大文档、一本厚书或任何数据分割成了若干“块”。这是流程的第一步。

第二步,我们需要将每一个文档块转换成向量嵌入。记住,嵌入是文本块在数学上的表示形式。

那么如何转换呢?我们可以使用大语言模型提供的嵌入器。市面上有很多选择,而OpenAI提供了一个嵌入API,可以将普通文本转换为嵌入向量。在本课程中,我们将使用这个API。

转换完成后,我们将得到每个文档块对应的嵌入向量。

最后,我们会将所有内容存储到向量数据库中。请注意,我们不仅存储每个块的嵌入向量版本,也会存储其原始纯文本版本。

因此,向量数据库中的每个条目都包含嵌入向量纯文本。至此,我们整本书、PDF或任何私有数据就完整地存在于向量数据库中了。

正因为如此,我们现在可以根据用户的问题,只查询相关的文档块,并取回对应的原始文本。这引出了我们工作流程的第二部分,我们将在下一节进行探讨。


本节课中,我们一起学习了RAG流程中数据准备阶段的核心步骤:将分割后的文档块通过嵌入模型(如OpenAI的API)转换为向量,并将向量与原文一并存储到向量数据库中。这为后续基于用户查询进行精准检索奠定了数据基础。

在本节课中,我们将深入学习检索增强生成(RAG)工作流程的第二部分。我们将详细探讨用户提问如何被处理,系统如何从向量数据库中检索相关信息,以及大语言模型(LLM)如何结合这些信息生成最终答案。


上一节我们介绍了RAG工作流程中数据准备和向量化的部分。本节中,我们来看看当用户提出问题时,系统是如何运作的。

工作流程从用户提出一个问题开始。假设我们有一本关于《指环王》的书,用户的问题是:“弗罗多的剑叫什么名字?”

接下来,系统会将这个用户问题(即提示词)也转换成嵌入向量。嵌入向量是文本的数学表示形式。

现在,我们有一个检索器组件。它接收代表用户问题的嵌入向量,然后向向量数据库发起查询:“请给我与这个问题最相关的五个文本块。”

向量数据库具有极低的延迟,这意味着它的速度非常快,因为它处理的是数字(即向量)。凭借其高速特性,向量数据库会返回相关性分数最高的五个文本块。

此时,我们拥有两部分信息:

  1. 用户的原始问题(英文文本)。
  2. 从书中检索到的、与用户问题相关的五个文本块(也是纯文本)。

现在,系统会将用户的原始问题文本和检索到的相关文本块,一并发送给大语言模型

这样,大语言模型就掌握了所有相关的文本段落以及用户的问题。它能够基于这些信息进行推理,从而给出一个信息充分的答案。

最后,大语言模型将生成的答案返回给用户。


我希望现在你已经对RAG及其工作原理有了完整的认识。RAG在为我们的大语言模型提供私有数据方面非常出色,本质上是赋予了它额外的知识库,使得我们可以与自己的数据进行对话。这是一个主要的应用场景。

我们还可以从另一个角度来理解RAG:可以将其视为赋予了大语言模型长期记忆

让我举一个简单的例子来帮助你更好地理解。假设有一个客户支持聊天机器人。

所有与特定用户的过往对话都可以存储在一个向量数据库中。这样,将来如果用户提出了一个与历史相关的问题,RAG系统就可以去检索那个特定的、相关的对话片段,从而给出更准确的答案。


在接下来的几个章节中,我们将使用刚才看到的同一个例子,来学习如何用代码实现这一切。你会发现,它实际上比看起来要简单得多。我们下节课再见。

在本节课中,我们将学习如何实现检索增强生成(RAG)系统的第一步:文档加载、分块、向量化与存储。我们将以《霍比特人》一书为例,演示如何将文本数据转换为向量并存入本地向量数据库。

上一节我们介绍了RAG系统的整体架构,本节中我们来看看如何用代码实现其数据准备部分。

首先,我们需要加载文档。以下是实现步骤:

  1. 确定文档路径:使用Python标准库获取当前脚本所在目录,并定位到文档文件。
  2. 初始化向量存储:指定一个本地目录用于存储向量数据库文件,确保数据持久化。
  3. 检查避免重复处理:如果向量数据库已存在,则跳过后续步骤,避免重复调用API产生额外费用。
import os from langchain.document_loaders import TextLoader from langchain.text_splitter import CharacterTextSplitter from langchain.embeddings import OpenAIEmbeddings from langchain.vectorstores import Chroma # 1. 获取文档路径 current_dir = os.path.dirname(os.path.abspath(__file__)) book_path = os.path.join(current_dir, 'documents', 'the_hobbit.txt') # 2. 指定向量存储的持久化目录 persist_directory = os.path.join(current_dir, 'folder_db', 'chroma_db') # 3. 检查是否已存在向量存储,避免重复处理 if not os.path.exists(persist_directory): print("持久化目录不存在,正在初始化向量存储...") # 后续处理代码将放在这里 

接下来,我们加载文档并将其分割成更小的文本块。分块是处理长文档的关键步骤,它有助于模型更有效地理解和检索信息。

以下是分块过程的核心步骤:

  1. 加载文档:使用 TextLoader 将文本文件加载到内存中。
  2. 初始化文本分割器:使用 CharacterTextSplitter,并设置块大小和重叠字符数。
  3. 执行分块:将加载的文档分割成指定大小的文本块数组。
GPT plus 代充 只需 145 # 加载文档 loader = TextLoader(book_path) documents = loader.load() # 初始化文本分割器 # chunk_size: 每个文本块的最大字符数 # chunk_overlap: 相邻文本块之间的重叠字符数,用于保留上下文 text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=50) docs = text_splitter.split_documents(documents) print(f"文档已被分割成 {len(docs)} 个块。") print("第一个块的内容预览:", docs[0].page_content[:200]) 

分块重叠 是一个重要概念。它定义了相邻两个文本块之间共享的字符数量。设置重叠有助于在分块时保留关键上下文信息,防止重要内容在块与块之间被切断。例如,若 chunk_overlap=100,则一个块的末尾100个字符会出现在下一个块的开头。

文档分块完成后,下一步是将每个文本块转换为向量嵌入,并存储到向量数据库中。

以下是该过程的实现步骤:

  1. 选择嵌入模型:使用OpenAI的 text-embedding-3-small 模型将文本转换为数值向量。该模型性能强大且成本较低。
  2. 创建向量存储:使用 Chroma.from_documents 方法,传入文本块 (docs) 和嵌入模型,将向量和原始文本一并存入指定的持久化目录。
 # 初始化嵌入模型 embeddings = OpenAIEmbeddings(model="text-embedding-3-small") # 创建并持久化向量存储 vectordb = Chroma.from_documents( documents=docs, embedding=embeddings, persist_directory=persist_directory ) vectordb.persist() print("向量嵌入已生成并成功存储到数据库。") 

运行以上完整代码后,系统将加载《霍比特人》文档,将其分割成约40个文本块,为每个块生成向量嵌入,并最终将所有数据保存到本地的 chroma_db 数据库中。控制台会输出处理日志和第一个文本块的内容预览。

本节课中我们一起学习了RAG系统数据准备阶段的核心操作:从定位和加载文档开始,到使用重叠分块策略处理文本,最后利用嵌入模型将文本转换为向量并持久化存储。至此,我们私有数据的向量化存储已完成,为后续的检索与生成环节打下了基础。

在本节课中,我们将学习RAG系统的第二部分:如何根据用户的问题,从向量数据库中检索出最相关的文本片段。我们将一步步配置检索器,并探索不同参数对检索结果的影响。

上一节我们介绍了如何将书籍向量化并存入数据库,本节中我们来看看如何从数据库中检索信息。

首先,我们需要让数据库在当前文件中可用。我们通过指定持久化目录的路径来加载之前创建的Chroma向量数据库。

GPT plus 代充 只需 145# 指定数据库文件路径并加载向量存储 persistent_directory = "./chroma_db" vectorstore = Chroma(persist_directory=persistent_directory, embedding_function=embedding_model) 

接下来,我们需要一个嵌入模型来处理用户的问题。这里有一个关键点:用于嵌入用户问题的模型,必须与当初嵌入私有数据时使用的模型完全相同。在本例中,我们始终使用 text-embedding-3-small 模型。

# 使用与嵌入数据时相同的模型 embedding_model = OpenAIEmbeddings(model="text-embedding-3-small") 

检索器的作用是根据用户的提示,从数据库中收集最相关的文本片段。我们可以配置检索器的搜索类型和参数。

以下是配置检索器的步骤:

  1. 指定搜索类型:本例使用“相似度分数阈值”搜索。后续章节会探索其他类型。
  2. 设置搜索参数
    • search_kwargs={“k”: 3}:指定检索器返回相关性最高的前3个文本片段。
    • search_kwargs={“score_threshold”: 0.5}:设定相似度分数的最低阈值为0.5(范围0到1)。检索器只会返回相似度高于此值的片段。

GPT plus 代充 只需 145# 创建并配置检索器 retriever = vectorstore.as_retriever( search_type="similarity_score_threshold", search_kwargs={"k": 3, "score_threshold": 0.5} ) 

配置好检索器后,我们就可以根据用户的问题查询相关片段了。我们使用 invoke 方法来触发检索。

# 定义用户问题并执行检索 user_question = "Where does Gandalf meet Frodo?" relevant_docs = retriever.invoke(user_question) # 打印检索到的片段 for doc in relevant_docs: print(doc.page_content) print("---") 

运行代码后,我们得到了三个最相关的文档片段。用户的问题是“甘道夫在哪里遇见了弗罗多?”,让我们检查检索结果是否准确:

  • 第一个片段提到:“It was in the heart of Hobbiton, at the home of Frodo Baggins, that Gandalf came...”。这直接回答了问题:在霍比屯的弗罗多家中。
  • 第二个片段提到:“Gandalf came to Hobbiton to visit Frodo one summer day.” 同样提供了答案。
  • 第三个片段提到:“Frodo was in his home in Hobbiton when Gandalf arrived...” 再次确认了地点。

检索器成功找到了包含答案的精确段落。

我们可以通过调整参数来观察检索结果的变化,这有助于在实际应用中找到**平衡点。

以下是调整参数的两个示例:

  1. 增加返回数量,降低阈值:如果将 k 改为10,并降低 score_threshold,检索器会返回更多结果,但其中可能包含相关性较低的片段。
  2. 提高阈值,要求更严格:如果将 score_threshold 提高到0.9,再次运行查询,可能会发现检索器无法找到任何符合条件的相关文档。这说明阈值设置过高,过滤掉了所有片段。

这个实验告诉我们,在构建自己的RAG应用时,需要在召回率(找到相关结果)和精确率(结果高度相关)之间找到合适的平衡点,不能将阈值设置得过高或过低。

本节课中我们一起学习了RAG检索的核心流程:加载数据库、配置检索器、执行查询并分析结果。我们成功实现了一个针对单本书籍的RAG搜索,并理解了关键参数对检索效果的影响。

在下一节中,我们将学习如何为每个文本片段添加元数据。元数据可以包含片段来源的额外信息,例如来自哪本书、第几章、第几段等。这样,在未来检索时,我们不仅能得到答案,还能清晰地看到答案的出处。我们也将探讨这在真实应用场景中的重要性。

在本节课中,我们将学习如何在RAG系统中为文本块添加元数据。元数据能帮助我们追踪信息的来源,例如知道某个答案来自哪本书或哪个文档的哪个部分,这对于验证信息真实性和进行深入探索至关重要。

上一节我们介绍了基础的RAG系统构建,本节中我们来看看如何通过引入元数据来增强它。

假设我们将10本不同的私人文档或书籍存入向量数据库。当我们提出一个问题时,除了获取相关的文本块,如果还能知道这些文本块的来源会更有帮助。例如,可以知道它来自某本书的第四章第三段,或某个文档的第七节。这为我们提供了关于信息出处的额外信息。

这类似于ChatGPT的联网搜索功能:它回答问题时会附带来源网址,让我们知道答案并非凭空捏造。另一个例子是,我们可以将会议记录存入向量数据库,两年后向模型提问“那天讨论了什么”,模型不仅能给出答案,还能提供来源链接,方便我们手动点击查看和进一步探索。

可能性是无限的。

为了演示这个例子,我添加了更多书籍,并为本节以及后续几节关于RAG的内容准备了代码。

现在让我们进入代码部分,你会发现这与我们之前编写的代码并没有太大不同。

首先,我们需要将所有新书籍存入向量数据库,以便开始提问。

我在这里添加了更多书籍,如《弗兰肯斯坦》、《德古拉》等,这些都是相当长的书籍。现在,让我们来分块处理这些书籍,进行嵌入,然后存入向量数据库。

以下是第一步,我们直接获取文档的引用。

GPT plus 代充 只需 145# 指定包含书籍文本文件的文件夹路径 documents_directory = "./documents" 

同时,我们告诉Langchain创建一个新的向量数据库文件,用于存放所有这些带元数据的书籍。

# 指定新的向量数据库路径 vector_db_path = "./db/ChromaDb_with_Meta" 

这次我们使用一个不同的数据库文件。我已经运行过这个文件,所以数据库中已经包含了所有书籍的完整嵌入版本。

其余代码与我们看到的第一个例子非常相似。我们检查数据库是否已创建,如果已创建,则不会进入if代码块。

接下来的部分略有不同。基本流程与上次相同,但这次我们嵌入的是多本书籍。

以下是具体步骤:

首先,进入documents文件夹,搜索所有具有.txt扩展名的文件。这行代码将获取所有这些书籍文件名并放入一个数组中。

GPT plus 代充 只需 145import os book_files = [f for f in os.listdir(documents_directory) if f.endswith('.txt')] 

接着,我们遍历所有书籍文件名。对于每个文件名,使用加载器将其加载到内存中,并添加到最终的文档数组中。

这与我们之前所做的非常相似,但这里处理的是多个文档。

此外,我们还在做一件事:在加载每个文档时,为其添加元数据。

我们可以添加任意多的信息,但目前我只添加source(来源),并将书籍文件名赋给它。

from langchain.document_loaders import TextLoader all_documents = [] for book_file in book_files: file_path = os.path.join(documents_directory, book_file) loader = TextLoader(file_path) documents = loader.load() # 为每个文档块添加元数据,记录来源书籍 for doc in documents: doc.metadata = {"source": book_file} all_documents.extend(documents) 

例如,如果我加载的是《弗兰肯斯坦》这本书,那么book_file就是Frankenstein.txt

从这里开始,我们将经历相同的过程:分块处理整个书籍列表,指定我们将用于嵌入的嵌入模型,最后通过提供文档、嵌入模型以及最终要创建的数据库文件路径来创建向量存储。

GPT plus 代充 只需 145from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain.embeddings import OpenAIEmbeddings from langchain.vectorstores import Chroma # 1. 分块 text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) chunks = text_splitter.split_documents(all_documents) # 2. 初始化嵌入模型 embeddings = OpenAIEmbeddings() # 3. 创建并持久化向量存储 vectorstore = Chroma.from_documents( documents=chunks, embedding=embeddings, persist_directory=vector_db_path ) vectorstore.persist() 

正如开始时提到的,我已经运行过这个文件,所以已经创建好了包含所有嵌入文本块的数据库文件。

现在让我们进入下一部分,在这里我们可以实际向RAG系统提问。

我们将要做的几乎相同:将文件指向我们刚刚创建的新数据库位置。

# 加载已存在的向量数据库 vectorstore = Chroma( persist_directory=vector_db_path, embedding_function=embeddings ) 

这是我们准备提出的问题:“德古拉的城堡位于哪里?”如果你熟悉德古拉,就知道他是一个住在城堡里的吸血鬼。我们的问题就是关于城堡的位置,显然在书中的某个地方会提到。

我们超级智能的RAG系统将在运行此文件时检索相关的文本块。

让我们看下一段代码,这里我们配置检索器。

我们仍然使用基于相似度分数的搜索,并指定返回数量为3,阈值设为0.3。这个数值对我有效,因为如果降低它,可能得不到任何结果。在你的应用中,需要找到合适的平衡点。

GPT plus 代充 只需 145# 配置检索器 retriever = vectorstore.as_retriever( search_type="similarity_score_threshold", search_kwargs={"k": 3, "score_threshold": 0.3} ) 

最后,我们使用invoke这个“魔法”关键字。它主要做两件事:将用户问题转换为嵌入向量;现在,我们有了向量化格式的查询,而书籍内容也是向量化格式的嵌入。它据此查询,返回最相关的三个文本块,并最终打印出所有块。

运行这个文件令人兴奋,让我们看看是否能在检索到的块中找到答案。运行可能需要4到10秒。

运行结果令人惊喜!我们现在得到了三个文本块,它们都相当大。我们知道德古拉存在于一个叫特兰西瓦尼亚的地方。让我们看看“Transylvania”这个词是否出现在任何检索到的块中。

正如你所见,第三行和第七行都提到了“Transylvania”。例如:“我曾参观过大英博物馆,并在图书馆的书籍和地图中搜索了关于特兰西瓦尼亚的信息。”下面甚至给出了德古拉城堡的具体位置。

仅凭这一段,即使是人类也能推断出德古拉住在特兰西瓦尼亚。更重要的是,我们还可以看到文本块的来源,它显示为dracula.txt。这是因为我们记得,我们将书名作为元数据添加到了每个文本块中。

现在让我们继续看下一个块。在第二个和第三个块中搜索“Transylvania”一词,可能没有直接列出,但它们可能提供了更多关于德古拉确实住在特兰西瓦尼亚的信息。例如,它写道:“因此,当我们找到这个人的住所时……”(这里“这个人”指的是德古拉),所以它仍然在讨论德古拉的位置。

目前我们只有三本书,但想象一下如果是上百本书、上千本书,或是公司的私有文档。RAG系统如何使得用自然语言与其对话并获取准确答案成为可能。希望你现在开始看到RAG系统的力量了。

以上就是我们如何为每个文本块添加元数据的方法。下一步,基本上就是将所有检索到的文本块连同用户的问题一起发送给大语言模型。这样,LLM就可以查看那些可能包含该问题答案的文本块,并给出答案——在本例中就是“Transylvania”。

我们将在下一节中学习如何实现这一步。

本节课中,我们一起学习了如何在RAG系统中为文本块嵌入元数据,以追踪信息来源,并通过一个查询德古拉城堡位置的实例,演示了带元数据的检索过程。这为构建更可靠、可追溯的问答系统奠定了基础。

在本节课中,我们将学习如何构建一个完整的检索增强生成(RAG)系统。我们将把之前章节中获取的相关文档片段与用户问题结合,提交给大语言模型(LLM),从而获得基于特定文档的精确答案。

上一节我们介绍了如何根据用户问题检索最相关的文档片段。本节中,我们将利用这些片段来生成最终答案。

我复制了上一节的所有代码到一个新文件中,并在此基础上添加了更多功能。让我们逐步来看具体做了什么。

首先,我们有一个用户问题以及根据该问题检索到的相关文档片段。我要做的第一件事很简单:准备一个组合提示词提交给LLM。

这个提示词的结构如下:

  1. 包含实际用户问题,例如:“德古拉的城堡位于何处?”
  2. 提供所有相关的文档片段文本。我们筛选出最相关的三个片段,并将它们连接在一起。
  3. 为确保LLM仅基于提供的文档回答,我们添加指令:“请仅根据提供的文档给出大致答案。如果答案未在文档中找到,请回复‘我不确定’。”

以下是构建提示词的代码示例:

# 假设 user_question 和 relevant_chunks 已定义 combined_prompt = f""" 请根据以下文档回答问题。 用户问题:{user_question} 相关文档: {‘ ‘.join(relevant_chunks)} 请仅根据提供的文档给出大致答案。如果答案未在文档中找到,请回复‘我不确定’。 """ 

接下来,我们实例化模型并调用它。这一步我们已经做过多次。

以下是调用过程的代码:

GPT plus 代充 只需 145# 定义消息数组 messages = [ {"role": "system", "content": "你是一个乐于助人的助手。"}, {"role": "user", "content": combined_prompt} ] # 调用模型并打印结果(仅内容) response = model.invoke(messages) print(response.content) 

现在,让我们运行这个文件。几秒钟后,我们得到了最终结果。

基于提供的文档,LLM回答:“德古拉的城堡位于特兰西瓦尼亚的最东端,就在特兰西瓦尼亚、摩尔达维亚和布科维纳的边界上,喀尔巴阡山脉之中。德古拉伯爵命名的邮政城镇是Bistritz。”

这非常棒!LLM能够基于文档片段中的所有数据识别出这个信息。为了验证,我们可以复制“Bistritz”这个词并在片段中搜索,会发现它在多个地方出现。

为了进一步测试,让我们问另一个问题。众所周知,德古拉讨厌大蒜。那么,让我们提问:“德古拉最害怕什么?”

再次运行文件,我们得到响应:“德古拉害怕很多东西:他不能被邀请就不能进入某些地方,不喜欢大蒜、圣水……” 它给出了答案,甚至包括一些我们原本不知道的信息。

让我们确认LLM是否是基于我们发送的片段给出这个答案的。复制“garlic”(大蒜)这个词并向上滚动搜索,可以看到它在片段中被多次提及。例如,片段中提到“他受大蒜困扰”以及“他也不喜欢十字架”。

通过这种方式,我们知道LLM仅基于提供的文档进行回答,没有动用其自身知识或进行网络搜索。因此,在你的项目中,你可以放入LLM无法知晓的私有数据,赋予其额外的知识并与之对话。

我们甚至可以更进一步。与其进行一次性问答,你可以构建一个支持多轮对话的系统。还记得我们在聊天模型模块中已经实现过这个功能吗?你可以在这里实现同样的逻辑。

至此,我们已经完成了对RAG的深入探讨。花点时间欣赏一下我们已经走了多远。你现在已经跻身于能够使用Langchain构建RAG系统和智能检索系统的前5%开发者行列了。

你现在能够:

  • 构建智能文档检索系统。
  • 创建具有上下文感知能力的对话式AI。
  • 将AI连接到你的私有数据。
  • 甚至集成网络爬取能力。

我们的旅程尚未结束。接下来,我们将深入Langchain的最后一个核心组件:智能体(Agents)和工具(Tools)。想象一下,基本上就是赋予你的AI实际使用工具和像真人一样做出决策的能力。如果这让你感到兴奋,我们将在下一节中探索这个领域。

在本节课中,我们将要学习Langchain的最后一个核心组件:智能体与工具。你可能会觉得“AI智能体”听起来非常复杂,但事实上,它的核心概念比想象中要简单得多。通过本节的学习,你将理解智能体如何自主决策,以及工具如何赋予AI执行特定任务的能力。

上一节我们学习了链和检索增强生成,它们遵循我们设定的特定指令。本节中我们来看看智能体,它们将自主决策的能力提升到了一个新的高度。

智能体可以被视为AI世界的问题解决者。它们是能够独立思考、自主做出决策的人工智能。

你可以将智能体想象成一位厨师。一位优秀的厨师知道在烹饪不同菜肴时,何时该用刀、何时该用搅拌器、何时该用烤箱。同样,一个AI智能体能够自主决定何时该使用浏览器搜索、何时该调用计算器、何时该向数据库发起API请求。

在这里,我们注意到一个关键词:工具

工具是AI用来完成特定任务的函数。就像厨师的厨房工具(刀用于切割、烤箱用于烘焙、搅拌器用于混合)一样,工具是我们赋予AI智能体的特殊能力。

例如,我们可以给AI一个计算器工具、一个搜索引擎工具或一个日历工具。许多应用程序(如Perplexity)的核心就是建立在工具之上的。

现在你应该对为何要使用智能体有了一些概念。本质上,智能体帮助语言模型在解决问题时,决定采取何种行动。它们能在合适的时机选择正确的工具,而无需我们明确指示使用哪一个。

让我们看一个使用工具的简单例子。如果我向一个AI智能体提问:“巴黎的天气温度加5是多少?”,这个过程会变得非常有趣。

AI智能体并非随机选择工具,它实际上在像侦探破案一样思考整个过程。

首先,智能体识别出问题包含两个部分:查询天气和进行数学计算。它知道需要先获取天气,因为你无法对一个未知的数值进行加法运算。然后,它再使用计算器工具进行加5操作。

你可能会问:智能体如何知道先做什么、后做什么?

这就引出了ReAct的概念。这是当今构建AI智能体的**模式之一。这里的“ReAct”并非指前端框架,而是AI领域的术语,代表 Reasoning(推理) + Acting(行动)。这个概念模拟了人类的思考方式。

当我们面对一个问题时,首先会思考如何解决它。这正是智能体所做的第一步:Think(思考)

第二步是 Act(行动)。智能体可以使用各种可用的工具来执行任务。

第三步是 Observe(观察) 行动的结果。

最后,如果需要,重复整个过程。

让我们看看“巴黎天气加5”这个问题在ReAct模式下的具体流程:

  1. 思考:我需要先找到巴黎的温度。
  2. 行动:使用天气API工具。
  3. 观察:API返回“巴黎气温20摄氏度”。
  4. 再次思考:现在我可以给这个数值加5。
  5. 再次行动:使用计算器工具。
  6. 最终观察/答案:25摄氏度。

可以把这想象成拼图。ReAct模式不是直接展示完成后的图片,而是让我们看到每一片拼图是如何被放置到正确位置的。

这是一个非常小的例子。想象一下,如果我提出一个更复杂的问题:“我下周去伦敦旅行需要带伞吗?以及我应该预订哪些餐厅?”

你可以想象,这是一个多步骤的问题。我们的AI智能体可能会通过多次循环 思考 -> 行动 -> 观察 的过程来解决它。

我希望你现在对智能体和工具有了一个初步的图景。如果你还没有完全理解,请不要担心。在下一节中,我们将从零开始构建自定义智能体,并查看大量实际示例。

本节课中我们一起学习了Langchain智能体与工具的核心概念。我们了解到,智能体是能够自主决策的AI问题解决者,而工具则是它们执行任务的具体手段。通过ReAct(推理+行动)模式,智能体能够像人类一样思考步骤、选择工具、观察结果并迭代执行,从而解决复杂的多步骤问题。这为AI应用赋予了前所未有的灵活性和自主性。

在本节课中,我们将学习如何从零开始构建一个AI智能体。通过本节内容,你将掌握为各种业务用例构建智能体的基本方法。由于这是一个新概念,我们将循序渐进地进行讲解。

首先,我们将展示当前大型语言模型存在的一个局限性,然后探讨如何通过构建智能体来解决这个问题。

上一节我们介绍了智能体的概念,本节中我们来看看如何用代码实现。首先,我们需要设置基础环境。

以下是初始化步骤的代码:

# 加载环境变量 # 初始化LLM,使用GPT-4模型 llm = ChatOpenAI(model="gpt-4") query = "what is the current time?" 

这段代码将问题输入到链中,链会将其转换为提示词并发送给LLM。这是一个非常基础的操作。

运行这段代码,让我们看看GPT-4如何回答当前时间的问题。

运行结果会显示,LLM无法访问实时信息。这是因为LLM本身无法获取实时数据。它们基于特定数据集进行训练,并且通常有一个数据截止日期。如果询问今天早上发生的事情,它很可能不知道。

这是一个完美的例子,展示了我们可以通过创建智能体来增强LLM的能力。智能体可以使用工具来获取当前时间。

为了解决上述限制,我们需要安装一些额外的包。

首先安装 langchain-hub 包。这个包允许我们从互联网上使用他人创建的提示词模板,而无需自己编写。

在安装的同时,我们深入了解一下这个包。简单来说,它让我们能够复用社区中优秀的提示模板。

在这个例子中,我们需要使用ReAct提示模板。稍后我们会详细查看这个模板的内容,但现在我们先使用它。

以下是引入模板的代码:

GPT plus 代充 只需 145from langchain import hub prompt = hub.pull("hwchase17/react") 

这段代码从LangChain Hub拉取指定的ReAct提示模板。hwchase17是LangChain创建者Harrison Chase的组织空间,react是我们需要的模板文件。

现在,让我们创建一些工具供智能体使用。

工具是一个列表,我们可以为智能体提供多种工具。根据智能体在特定时刻需要解决的问题,它会选择合适的工具来完成任务。工具可以是谷歌搜索工具、邮件发送工具等。

在这个案例中,我们需要创建一个获取系统时间的工具。

接下来,我们需要创建智能体。为此,我们需要从LangChain导入一些类。

以下是相关代码:

from langchain.agents import create_react_agent, AgentExecutor tools = [] # 工具列表,稍后填充 agent = create_react_agent(llm=llm, tools=tools, prompt=prompt) agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True) result = agent_executor.invoke({"input": query}) 

我们移除了之前的简单链,现在声明一个智能体执行器。将verbose参数设置为True,这样我们可以看到智能体在每个步骤中的思考和行动,便于调试和调整。

快速回顾一下:我们有查询问题、从Hub拉取的提示模板、工具列表(尚未编写),以及将这些信息传递给执行器。最后,我们用查询来调用智能体。

如果现在运行,由于LLM没有获取时间的工具,显然会失败。运行后,我们可以看到智能体逐步执行的日志。

ReAct智能体遵循“推理(Reason)-行动(Act)-观察(Observe)-重复”的循环,日志清晰地展示了这一过程。

首先,它进行推理:“我需要检查我当前位置的当前时间”。推理出问题或需要做的事情后,它需要采取行动:“使用工具来找出当前时间”。不幸的是,我们尚未为智能体提供时间工具,因此它报告没有工具来解决这个问题。最终,它给出无法找到当前时间的答案,因为它自己不知道如何解决。

现在让我们修复这个问题,为智能体提供一个工具。

我们将创建一个简单的Python函数来返回当前时间。我们希望智能体以特定的时间格式给出答案。

以下是函数定义:

GPT plus 代充 只需 145import datetime from langchain.agents import tool @tool def get_system_time(format: str = "%Y-%m-%d %H:%M:%S"): """ 以指定格式返回当前的日期和时间。 """ current_time = datetime.datetime.now() formatted_time = current_time.strftime(format) return formatted_time 

我们使用了Python标准的datetime库来获取系统时间,并按照指定格式进行格式化。如果LLM没有传入特定格式,函数将使用默认格式。

但是,LangChain本身并不知道如何处理这个函数。我们需要为这个函数添加一个Python装饰器@tool。这个装饰器会将方法转换为LangChain易于理解的格式,并提供一个描述字符串,让LLM了解这个方法的上下文信息,从而判断是否应该为特定任务使用这个工具。

确保始终为工具函数提供清晰、良好的描述或文档字符串。

现在,剩下的就是将这个工具作为一项添加到工具列表中。

tools = [get_system_time] 

在运行文件之前,让我们梳理一下控制流程,以便更清楚地了解所有部分是如何协同工作的。

以下是智能体的工作步骤:

  1. 提示与初始化:你向智能体提供一个提示,例如ReAct提示。该提示包含对LLM的任务指令、示例、逐步推理说明以及决定何时使用工具的指导。其次,提示中还包括可用工具的描述,包括它们的名称和使用细节。
  2. 智能体执行:当用户向智能体提供查询时,LLM解释查询并生成响应。LLM不直接执行工具,而是建议调用哪些工具,并可能基于其推理提供参数。
  3. 工具调用:智能体框架(LangChain)拦截LLM的输出,检查它是否建议调用工具。如果建议调用工具,LangChain会调用你Python代码中的工具,并传入LLM提议的任何参数。
  4. 工具执行:该工具在你的Python环境中运行,可以访问你的Python资源(如当前时间、文件系统、API等)。工具将其结果返回给LangChain。
  5. LLM响应:LangChain将工具的输出发送回LLM,供其进一步推理或为用户生成最终响应。

理论讲解完毕,现在让我们运行代码,看看一切是如何协同工作的。

运行后,智能体现在可以访问工具了。从日志中可以看到,它首先进行推理,需要访问get_system_time工具。推理完成后,第二步是行动。LangChain现在调用我们的函数,并传入LLM提供的输入(在这个例子中,它采用了默认格式)。然后生成最终答案,接着发生另一个LLM调用,LLM根据最终数据给出回答。

本质上,智能体遍历所有工具,找到能帮助它完成任务的那个并使用它。它能够通过我们提供的描述来识别工具。

我们不必对查询格式过于严格。可以提出略有不同的问题,例如只询问当前时间而不包含日期。

再次运行文件,可以看到LLM这次向工具提供了略有不同的输入格式,最终答案中只显示了时间而没有日期。在推理中,它能够识别出只需要调整格式就能给出正确答案。因此,LLM建议了一个不同的格式,而LangChain为我们执行了工具。

在我们的例子中,循环只运行了一次,因为这是一个单步问题:找到当前时间,所以它使用工具后就结束了。但对于更复杂的查询,这个循环可能会根据用户提示的复杂性运行三次、四次或五次。这就是ReAct智能体的工作原理。

让我们再稍微改变一下提示。这次我们让它告诉我们伦敦的当前时间。我们可以告诉它“你在印度”,因为我目前在印度。

运行后,智能体首先推理需要检查印度的系统时间,然后将其转换为伦敦时间。它也知道伦敦时间比印度晚大约5.5小时。它思考了这一点,然后行动,调用get_system_time函数,获取了印度的当前时间。它根据查询为工具传入了正确的格式。一旦有了正确的印度时间,它再次推理:“现在我们有了印度时间,需要通过减去5.5小时来计算伦敦时间”。这正是日志中显示的内容。推理后的下一步行动是“从时间中减去5.5小时”,然后给出最终答案,即当前的伦敦时间。

对于数学运算,我们可以提供自己的计算工具。大多数时候,LLM应该能够自行处理,但在生产环境中,提供自己的工具总是更安全,因为有时它会说找不到数学工具来进行减法运算。这是一个很好的演示,说明了工具的重要性。没有get_system_time工具,LLM就无法知道我的当前时间。

到目前为止,我们已经看到了智能体经历推理、行动、观察循环的日志,但LLM究竟是如何做到这一点的呢?我们知道这得益于ReAct提示模板。让我们实际看看这个模板包含什么内容,使得它能够进行这种重复循环直到得到答案。

访问 smith.langchain.com/hub/hwchase17 可以查看ReAct提示模板。这是我们在应用中使用的系统级提示。

模板内容大致如下:

  • 指令:“尽你所能回答以下问题。你可以使用以下工具:[工具列表]”。
  • 格式要求:指示LLM遵循“Thought(思考): … Action(行动): … Action Input(行动输入): … Observation(观察): …”的格式。
  • 循环机制:LLM首先思考下一步该做什么(Thought),然后决定采取哪个行动(Action)并提供输入(Action Input)。框架执行工具后返回结果(Observation)。LLM观察结果,判断是否已得到最终答案,或者是否需要再次进入“Thought-Action-Observation”循环。
  • 最终答案:一旦确信解决了问题的每个部分,就进入“Final Answer(最终答案): …”。

就像在终端中看到的那样,“思考-行动-观察”的循环持续进行,直到智能体确信它已经解决了你提示中的每个部分。这就像看着侦探一步步破案。

本节课中我们一起学习了如何利用LangChain从零开始构建一个AI智能体。我们了解了LLM在获取实时信息方面的局限性,并通过创建工具(如获取系统时间)来扩展其能力。我们探索了ReAct智能体的工作流程,包括其“推理-行动-观察”的循环机制,并深入查看了驱动这一过程的提示模板。通过实际代码演示,我们看到了智能体如何选择并使用合适的工具来回答复杂问题。这为构建能够处理现实世界任务的AI应用奠定了坚实基础。

小讯
上一篇 2026-03-27 13:52
下一篇 2026-03-27 13:50

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/249045.html