LangChain 教程——如何构建自定义知识聊天机器人
1 概述
我们将通过一个示例来说明如何设计和实现 LLM 支持的聊天机器人。这个聊天机器人将能够进行对话并 记住以前的互动 。
请注意,我们构建的这个聊天机器人将仅使用语言模型进行对话。您可能正在寻找其他几个相关概念:
- 对话式 RAG:通过外部数据源启用聊天机器人体验
- 智能体:构建可执行作的聊天机器人
本教程将介绍对这两个更高级的主题有帮助的基础知识,但如果您愿意,请直接跳到那里。
2 设置
2.1 Jupyter 笔记本
本指南(以及文档中的大多数其他指南)使用 Jupyter 笔记本,并假设读者也使用 Jupyter 笔记本。Jupyter 笔记本非常适合学习如何使用 LLM 系统,因为很多时候事情可能会出错(意外输出、API 关闭等),在交互式环境中浏览指南是更好地了解它们的好方法。
本教程和其他教程可能在 Jupyter 笔记本中运行最方便。有关如何安装的说明,请参阅 此处 。
2.2 安装 LangChain
1 | pip install langchain |
1 | conda install langchain -c conda-forge |
如果你想尝试使用LangSmith,可以访问 这里 学习
2.3 快速入门
首先,让我们学习如何单独使用语言模型。LangChain 支持许多不同的语言模型,您可以互换使用——请在下方选择您想要使用的语言模型!
1 | pip install -qU langchain-openai |
1 | pip install -qU langchain-openai |
1 | import getpass |
1 | from langchain_openai import ChatOpenAI |
1 | import getpass |
我们首先直接使用模型。ChatModel是 LangChain “Runnables” 的实例,这意味着它们公开了一个用于与它们交互的标准接口。要简单地调用模型,我们可以将消息列表传递给.invoke 方法。
1 | from langchain_core.messages import HumanMessage |
API 参考: HumanMessage
1 | AIMessage(content='Hello Bob! How can I assist you today?', response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 12, 'total_tokens': 22}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-d939617f-0c3b-45e9-a93f-13dafecbd4b5-0', usage_metadata={'input_tokens': 12, 'output_tokens': 10, 'total_tokens': 22}) |
该模型本身没有任何状态的概念。例如,如果您提出后续问题:
1 | model.invoke([HumanMessage(content="What's my name?")]) |
1 | AIMessage(content="I'm sorry, I don't have access to personal information unless you provide it to me. How may I assist you today?", response_metadata={'token_usage': {'completion_tokens': 26, 'prompt_tokens': 12, 'total_tokens': 38}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-47bc8c20-af7b-4fd2-9345-f0e9fdf18ce3-0', usage_metadata={'input_tokens': 12, 'output_tokens': 26, 'total_tokens': 38}) |
我们可以看到,它没有将之前的对话转化为上下文,也无法回答问题。这会导致糟糕的聊天机器人体验!
为了解决这个问题,我们需要将整个对话历史传递到模型中。让我们看看当我们这样做时会发生什么:
1 | from langchain_core.messages import AIMessage |
API 参考: AIMessage
1 | AIMessage(content='Your name is Bob. How can I help you, Bob?', response_metadata={'token_usage': {'completion_tokens': 13, 'prompt_tokens': 35, 'total_tokens': 48}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-9f90291b-4df9-41dc-9ecf-1ee1081f4490-0', usage_metadata={'input_tokens': 35, 'output_tokens': 13, 'total_tokens': 48}) |
我们可以看到,模型能够记住之前的对话并回答问题。
这是支撑聊天机器人进行对话交互能力的基本思想。那么我们如何最好地实现这一点呢?
3 Message History(消息历史记录)
我们可以使用 Message History 类来包装我们的模型并使其有状态。这将跟踪模型的输入和输出,并将它们存储在某个数据存储中。然后,未来的交互将加载这些消息,并将它们作为 input 的一部分传递到链中。让我们看看如何使用它!
首先,让我们确保安装 langchain-community ,因为我们将使用其中的集成来存储消息历史记录。
之后,我们可以导入相关的类并设置我们的链,该链包装模型并添加此消息历史记录。这里的一个关键部分是我们作为 get_session_history 传入的函数。此函数应接收 session_id 并返回 Message History 对象。此session_id用于区分单独的对话,在调用新链时应作为配置的一部分传入(我们将展示如何执行此作)。
1 | from langchain_core.chat_history import ( |
API 参考: BaseChatMessageHistory | InMemoryChatMessageHistory | RunnableWithMessageHistory
我们现在需要创建一个config,每次都传递给 runnable。此配置包含的信息不是直接输入的一部分,但仍然有用。在本例中,我们希望包含一个 session_id。这应该看起来像:
1 | config = {"configurable": {"session_id": "abc2"}} |
1 | response = with_message_history.invoke( |
1 | 'Hi Bob! How can I assist you today?' |
1 | response = with_message_history.invoke( |
1 | 'Your name is Bob. How can I help you today, Bob?' |
我们的聊天机器人现在记住了关于我们的事情。 如果我们更改配置以引用不同的session_id,我们可以看到它开始了全新的对话。
1 | config = {"configurable": {"session_id": "abc3"}} # session_id 改为abc3 |
1 | "I'm sorry, I cannot determine your name as I am an AI assistant and do not have access to that information." |
我们可以看到它没有记住之前的对话。
我们也可以返回到之前的对话,只需将 session_id 更改为 abc2 即可。
1 | config = {"configurable": {"session_id": "abc2"}} |
1 | 'Your name is Bob. How can I help you today, Bob?' |
这就是我们支持聊天机器人与许多用户进行对话的方式!
现在,我们所做的只是在模型周围添加一个简单的持久层。我们可以通过添加提示模板来开始使它变得更加复杂和个性化。
4 Prompt templates(提示模板)
提示模板有助于将原始用户信息转换为 LLM 可以使用的格式。在本例中,原始用户输入只是一条消息,我们将其传递给 LLM。现在让我们让它稍微复杂一点。首先,让我们添加一条带有一些自定义指令的系统消息(但仍将消息作为输入)。接下来,除了消息之外,我们还将添加更多输入。
首先,让我们添加一条系统消息。为此,我们将创建一个 ChatPromptTemplate。我们将使用 MessagesPlaceholder 来传递所有消息。
1 | from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder |
API 参考: ChatPromptTemplate | MessagesPlaceholder
请注意,这会略微改变输入类型 - 我们现在传入的 不是消息列表 ,而是传入一个带有 messages 键的 字典 ,其中包含一个消息列表。
1 | response = chain.invoke({"messages": [HumanMessage(content="hi! I'm bob")]}) |
1 | 'Hello Bob! How can I assist you today?' |
现在,我们可以像以前一样将其包装在相同的 Messages History 对象中
1 | with_message_history = RunnableWithMessageHistory(chain, get_session_history) |
1 | config = {"configurable": {"session_id": "abc5"}} |
1 | response = with_message_history.invoke( |
1 | 'Hi Jim! How can I assist you today?' |
1 | response = with_message_history.invoke( |
1 | 'Your name is Jim. How can I help you today, Jim?' |
1 | response = with_message_history.invoke( |
1 | 'Your name is Jim. How can I help you today, Jim?' |
现在让我们的提示稍微复杂一点。我们假设提示模板现在如下所示:
1 | prompt = ChatPromptTemplate.from_messages( |
请注意,我们已向提示添加了新的language输入。现在,我们可以调用链并传入我们选择的语言。
1 | response = chain.invoke( |
1 | '¡Hola Bob! ¿Cómo puedo ayudarte hoy?' |
现在让我们将这个更复杂的链包装在 Message History 类中。这一次,由于输入中有多个 key,我们需要指定正确的 key 来保存聊天记录。
1 | with_message_history = RunnableWithMessageHistory( |
1 | config = {"configurable": {"session_id": "abc11"}} |
1 | response = with_message_history.invoke( |
1 | '¡Hola Todd! ¿En qué puedo ayudarte hoy?' |
1 | response = with_message_history.invoke( |
1 | 'Tu nombre es Todd.' |
提示
这里我认为有几个比较重要的概念:
- HumanMessage/AIMessage 是消息的类型
- session_id 是确定使用哪组消息的关键
- RunnableWithMessageHistory 是将消息历史记录与模型结合在一起的关键它需要传入model变量和get_session_history函数,而get_session_history函数需要传入session_id来获取消息历史记录
- 如果使用了prompt_template,那么我们需要传给invoke的参数是一个字典,而不是一个消息列表,字典的key是prompt_template中定义的变量名
5 管理对话历史记录
构建聊天机器人时要了解的一个重要概念是如何管理对话历史记录。
如果不进行管理,消息列表将变得不受限制,并可能使 LLM 的上下文窗口溢出。
因此,添加一个限制您传入的消息大小的步骤非常重要。
重要的是,你要在加载“消息历史”里的旧消息之后,再进行提示模板的操作。
为此,我们可以在 prompt 前面添加一个简单的步骤,以适当地修改 messages 键,然后将该新链包装在 Message History 类中。
LangChain 附带了一些内置的 helpers,用于 管理消息列表 。在本例中,我们将使用 trim_messages 帮助程序来减少我们发送到模型的消息数量。 修剪器 允许我们指定要保留的令牌数量,以及其他参数,例如我们是否要始终保留系统消息以及是否允许部分消息:
1 | from langchain_core.messages import SystemMessage, trim_messages |
API 参考: SystemMessage | trim_messages
1 | [SystemMessage(content="you're a good assistant"), |
要在我们的链中使用它,我们只需要在将 messages input 传递给 prompt 之前运行 trimmer。
现在,如果我们尝试向模型询问我们的名字,它不会知道它,因为我们修剪了聊天记录的那部分:
1 | from operator import itemgetter |
API 参考: RunnablePassthrough
1 | "I'm sorry, but I don't have access to your personal information. How can I assist you today?" |
但是,如果我们询问最后几封邮件中的信息,它会知道:
1 | response = chain.invoke( |
1 | 'You asked "what\'s 2 + 2?"' |
现在让我们将其包装在 Message History 中
1 | with_message_history = RunnableWithMessageHistory( |
1 | response = with_message_history.invoke( |
1 | "I'm sorry, I don't have access to that information. How can I assist you today?" |
正如预期的那样,我们声明我们名称的第一条消息已被修剪。此外,聊天记录中现在有两条新消息(我们的最新问题和最新回复)。这意味着过去在我们的对话历史记录中可以访问的更多信息不再可用!在这种情况下,我们的初始数学问题也已从历史记录中修剪下来,因此模型不再知道它:
1 | response = with_message_history.invoke( |
1 | "You haven't asked a math problem yet. Feel free to ask any math-related question you have, and I'll be happy to help you with it." |
6 Streaming
现在我们有一个正常运行的聊天机器人。但是,聊天机器人应用程序的一个非常重要的 UX 考虑因素是流式传输。LLM 有时可能需要一段时间才能响应,因此,为了改善用户体验,大多数应用程序所做的一件事是在生成每个令牌时将其流回。这样,用户就可以看到进度。
这实际上非常简单!
所有链都开放.stream 方法,使用消息历史记录的链也不例外。我们可以简单地使用该方法来获取流式响应。
1 | config = {"configurable": {"session_id": "abc15"}} |
1 | |Hi| Todd|!| Sure|,| here|'s| a| joke| for| you|:| Why| couldn|'t| the| bicycle| find| its| way| home|?| Because| it| lost| its| bearings|!| 😄|| |
原文: LangChain Tutorial – How to Build a Custom-Knowledge Chatbot
你可能已经了解到过去几个月中发布的大量人工智能应用程序。你甚至可能已经开始使用其中的一些。
ChatPDF 和 CustomGPT AI 等人工智能工具对人们非常有用,这是有道理的。你需要翻阅长达 50 页的文档才能找到一个简单答案的时代已经一去不复返了。取而代之的是,你可以依靠人工智能来完成繁重的工作。
但是,这些开发人员究竟是如何创建和使用这些工具的呢?他们中的许多人都在使用一个名为 LangChain 的开源框架。
在本文中,我将向你介绍 LangChain,并向你展示如何将其与 OpenAI 的 API 结合使用,以创建这些改变游戏规则的工具。希望我的介绍能激发你们的灵感,创造出属于自己的工具。那么,让我们开始吧!
什么是 LangChain

LangChain 是一个开源框架,允许人工智能开发人员将 GPT-4 等大型语言模型(LLM)与外部数据相结合。它提供 Python 或 JavaScript(TypeScript)包。
大家可能知道,GPT 模型是在 2021 年之前的数据上训练出来的,这可能是一个很大的局限。虽然这些模型的常识很不错,但如果能将它们与自定义数据和计算连接起来,就会打开很多大门。这正是 LangChain 所要做的。
从本质上讲,它可以让你的 LLM 在得出答案时参考整个数据库。因此,你现在可以让你的 GPT 模型访问报告、文档和网站信息等形式的最新数据。
最近,LangChain 的受欢迎程度大幅上升,尤其是在三月份推出 GPT-4 之后。这要归功于它的多功能性,以及与功能强大的 LLM 配合后所带来的多种可能性。
LangChain 是怎样工作的
可能会觉得 LangChain 听起来很复杂,但实际上它非常容易上手。
简而言之,LangChain 就是将大量数据组成一个 LLM 可以轻松引用的数据链,而且计算能力越低越好。它的工作原理是将大量数据(例如 50 页的 PDF 文件)分解成 块(chunks) ,然后将这些 块(chunks) 嵌入到 向量存储(Vector Store) 中。

以上是创建向量存储(Vector Store) 的简单示意图。
现在,我们已经有了大型文档的向量化表示,可以将其与 LLM 结合使用,在创建 提示-完成对
(prompt-completion pair) 时,只检索我们需要引用的信息。
当我们在新聊天机器人中插入 提示(prompt) 时,LangChain 会查询 向量存储库(Vector Store) 以获取相关信息。把它想象成你文档的迷你谷歌。一旦检索到相关信息,我们就会将其与 提示(prompt ) 信息一起输入 LLM,生成我们的答案。
![]()
以上展示了 LangChain 如何与 OpenAI 的 LLM 协同工作。
LangChain 还允许你创建可以执行操作的应用程序,例如上网、发送电子邮件和完成其他与 API 相关的任务。请查看 AgentsGPT ,这就是一个很好的例子。
这有很多可能的用例,以下是我想到的几个:
- 个人 AI 电子邮件助理
- 人工智能学习伙伴
- 人工智能数据分析
- 定制公司客户服务聊天机器人
- 社交媒体内容创建助手
还有更多。我将在今后的文章中介绍正确的构建教程,敬请期待。
怎样开始使用 LangChain
LangChain 应用程序由 5 个主要部分组成:
- Models (LLM Wrappers)
- Prompts
- Chains
- Embeddings and Vector Stores
- Agents
我将为你逐一介绍,以便你对 LangChain 的工作原理有一个高层次的了解。接下来,你应该能够应用这些概念,开始制作自己的用例并创建自己的应用程序。
我将用 Rabbitmetrics 的简短代码片段( Github )来解释一切。他就这一主题提供了很好的教程。通过这些代码片段,你可以完成所有设置并准备使用 LangChain。
首先,我们来设置环境。你可以用 pip 安装 3 个需要的库:
1 | pip install -r requirements.txt |
1 | python-dotenv==1.0.0 |
Pinecone 是我们将与 LangChain 结合使用的向量存储。使用这些工具时,请确保将 OpenAI、Pinecone Environment 和 Pinecone API 的 API 密钥存储到环境文件中。你可以在它们各自的网站上找到这些信息。然后,我们只需在环境文件中加载以下内容即可:
1 | # Load environment variables |
现在,我们可以开始了!
Models(LLM Wrappers)
为了与我们的 LLM 进行交互,我们将为 OpenAI 的 GPT 模型实例化一个封装器。在本例中,我们将使用 OpenAI 的 GPT-3.5-turbo,因为它最具性价比。但如果你有授权,也可以使用功能更强大的 GPT4。
1 | # import schema for chat messages and ChatOpenAI in order to query chatmodels GPT-3.5-turbo or GPT-4 |
本质上, SystemMessage 为 GPT-3.5-turbo 模块提供了上下文,它将为每个提示-完成对引用该模块。 HumanMessage 指的是你在 ChatGPT 界面中输入的内容(你的提示)。
但对于定制知识聊天机器人,我们经常抽象出 提示(prompt)中的重复组件。例如,如果我正在创建一个推文生成器应用程序,我不想继续输入 给我写一条关于...的推文 。其实, AI 写作工具 就是这么简单开发出来的!
那么让我们看看如何使用 提示模板(prompt templates) 将其抽象出来。
Prompts(提示)
LangChain 提供 PromptTemplates,允许你根据用户输入动态更改提示,类似于使用正则表达式。
1 | # Import prompt and define PromptTemplate |
你可以通过不同的方式改变它们以适合你的用例。 如果你熟悉使用 ChatGPT,这对你来说应该很舒服。
Chains(链)
链(Chains)允许你采用简单的 PromptTemplates 并在它们之上构建功能。本质上,链就像 复合函数(composite functions) ,允许你将 PromptTemplates 和 LLM 集成在一起。
使用之前的装饰器(wrappers) 和 PromptTemplates,我们可以使用单个链运行相同的提示,该链采用 PromptTemplate 并将其与 LLM 组合:
1 | # Import LLMChain and define chain with language model and prompt as arguments. |
最重要的是,顾名思义,我们可以将它们链接在一起以创建更大的作品。
例如,我可以从一个链中获取结果并将其传递到另一个链中。 在此片段中,Rabbitmetrics 从第一个链中获取补全内容,并将其传递到第二个链中,以便向 5 岁的孩子解释。
然后,你可以将这些链组合成一个更大的链并运行它。
1 | # Define a second prompt |
通过链,你可以创建大量的功能,这就是 LangChain 如此多功能的原因。 但它真正的亮点在于将其与前面讨论的向量存储结合使用。 我们来介绍一下这个组件。
Embeddings(嵌入)和 Vector Stores(向量存储)
这就是我们整合 LangChain 的自定义数据方面的地方。 如前所述,嵌入(embeddings) 和向量存储背后的想法是将大数据分成块并在相关时存储要查询的数据。
LangChain 有一个文本分割器函数(text splitter function) 可以做到这一点:
1 | # Import utility for splitting up texts and split up the explanation given above into document chunks |
分割文本需要两个参数:块有多大 (chunk_size) 以及每个块重叠的程度 (chunk_overlap)。 每个块之间的重叠对于帮助识别相关的相邻块非常重要。
每个块都可以这样检索:
1 | texts[0].page_content |
获得这些块后,我们需要将它们转化为嵌入(embeddings)。这允许向量存储在查询时查找并返回每个块。我们将使用 OpenAI 的 嵌入模型(embedding model) 来完成此操作。
1 | # Import and instantiate OpenAI embeddings |
最后,我们需要有一个地方来存储这些 矢量化嵌入(vectorized embeddings)。如前所述,我们将为此使用 Pinecone。使用之前环境文件中的 API key,我们可以初始化 Pinecone 来存储我们的嵌入。
1 | # Import and initialize Pinecone client |
现在,我们能够从 Pinecone 向量存储中查询相关信息了!剩下要做的就是把我们学到的知识结合起来,创建我们的特定用例。这就是我们的专业人工智能 Agents 。
Agents
Agents 本质上是一种自主的人工智能,它接收输入并按顺序完成这些任务,直至达到最终目标。这就需要我们的人工智能使用其他应用程序接口,从而完成发送电子邮件或做数学题等任务。结合我们的 LLM + 提示链(prompt chains),我们可以串联起一个合适的人工智能应用程序。
现在,要解释这部分内容会很费劲,因此这里有一个简单的例子,说明如何在 LangChain 中使用 Python Agents 来解决一个简单的数学问题。本例中的 Agents 通过连接我们的 LLM 来运行 Python 代码,并使用 NumPy 求根来解决问题:
1 | # Import Python REPL tool and instantiate Python Agents |
定制知识聊天机器人本质上是一个 Agents,它可以将查询向量化存储的 提示(prompts) 和操作串联起来,获取结果,并将其与原始问题串联起来!
如果你想了解更多关于人工智能 Agents 的信息, 这个 是一个很好的资源。
别的因素
即使你刚刚对 LangChain 的功能有了基本的了解,我相信你此时也会有很多想法。
但到目前为止,我们只研究了一个 OpenAI 模型,那就是基于文本的 GPT-3.5-turbo。OpenAI 有一系列模型可供 LangChain 使用,包括使用 Dall-E 生成图像。应用我们讨论过的相同概念,我们可以创建 人工智能艺术生成器 代理、网站生成器代理等。
花点时间探索一下人工智能领域,我相信你会有越来越多的想法。
总结
我希望你已经对所有这些新人工智能工具的幕后工作有了更多了解。作为一名程序员,了解 LangChain 的工作原理是一项宝贵的技能,它可以为你的人工智能开发带来无限可能。
如果你喜欢这篇文章,并想了解更多有关人工智能创造者们正在构建的新工具的信息,你可以通过我的 Byte-Sized AI Newsletter 随时了解最新信息。我希望你能加入我们的社区。
你也可以在 Twitter 上关注我,我们也可以在那里保持联系。
除此之外,请开始尝试使用 LangChain 并创建一些有趣的人工智能项目。