跳至主要内容

概念指南

本节包含对 LangChain 关键部分的介绍。

架构

LangChain 作为框架,由多个包组成。

langchain-core

此包包含不同组件的基本抽象及其组合方式。此处定义了核心组件(如 LLM、向量存储、检索器等)的接口。此处未定义任何第三方集成。依赖项有意保持非常轻量级。

langchain

主要的 langchain 包包含构成应用程序认知架构的链、代理和检索策略。这些**不是**第三方集成。此处的所有链、代理和检索策略都**不特定于**任何一个集成,而是跨所有集成通用。

langchain-community

此包包含由 LangChain 社区维护的第三方集成。关键合作伙伴包已分离出来(见下文)。它包含各种组件(LLM、向量存储、检索器)的所有集成。此包中的所有依赖项都是可选的,以使包尽可能轻量级。

合作伙伴包

虽然大量的集成位于langchain-community中,但我们将流行的集成拆分到它们自己的包中(例如langchain-openailangchain-anthropic等)。这样做是为了更好地支持这些重要的集成。

langgraph

langgraphlangchain的扩展,旨在通过将步骤建模为图中的边和节点,构建健壮且有状态的多参与者大语言模型应用程序。

LangGraph 提供了用于创建常见代理类型的上层接口,以及用于组合自定义流程的下层 API。

langserve

一个将 LangChain 链部署为 REST API 的包。使启动和运行生产就绪的 API 变得容易。

LangSmith

一个开发者平台,可用于调试、测试、评估和监控大语言模型应用程序。

Diagram outlining the hierarchical organization of the LangChain framework, displaying the interconnected parts across multiple layers.Diagram outlining the hierarchical organization of the LangChain framework, displaying the interconnected parts across multiple layers.

LangChain 表达式语言 (LCEL)

LangChain 表达式语言LCEL是一种声明式的方式来链接 LangChain 组件。LCEL 从一开始就旨在支持将原型投入生产,无需代码更改,从最简单的“提示 + 大语言模型”链到最复杂的链(我们已经看到人们成功地在生产环境中运行了包含数百个步骤的 LCEL 链)。为了突出您可能想要使用 LCEL 的一些原因

  • 一流的流支持:当您使用 LCEL 构建链时,您可以获得最佳的第一个标记时间(第一个输出块出现之前经过的时间)。对于某些链来说,这意味着例如我们将标记直接从大语言模型流式传输到流式输出解析器,并且您以与大语言模型提供程序输出原始标记相同的速率获得解析后的增量输出块。

  • 异步支持:任何使用 LCEL 构建的链都可以使用同步 API(例如,在原型设计时在您的 Jupyter 笔记本中)以及异步 API(例如,在LangServe服务器中)调用。这使得能够在原型和生产中使用相同的代码,并获得出色的性能,以及在同一服务器中处理许多并发请求的能力。

  • 优化的并行执行:无论何时您的 LCEL 链具有可以并行执行的步骤(例如,如果您从多个检索器中获取文档),我们都会自动执行它,无论是在同步还是异步接口中,以获得尽可能小的延迟。

  • 重试和回退:为 LCEL 链的任何部分配置重试和回退。这是一种在规模上提高链可靠性的好方法。我们目前正在努力为重试/回退添加流支持,以便您无需任何延迟成本即可获得额外的可靠性。

  • 访问中间结果:对于更复杂的链,在生成最终输出之前访问中间步骤的结果通常非常有用。这可以用于让最终用户知道某些事情正在发生,或者甚至只是调试您的链。您可以流式传输中间结果,它在每个LangServe服务器上都可用。

  • 输入和输出模式输入和输出模式为每个 LCEL 链提供从链结构推断出的 Pydantic 和 JSONSchema 模式。这可以用于验证输入和输出,并且是 LangServe 不可分割的一部分。

  • 无缝 LangSmith 追踪随着链变得越来越复杂,了解每个步骤中到底发生了什么变得越来越重要。使用 LCEL,所有步骤都会自动记录到LangSmith,以实现最大的可观察性和可调试性。

LCEL 旨在围绕行为和自定义提供一致性,而不是像LLMChainConversationalRetrievalChain这样的传统子类链。许多这些传统链隐藏了重要的细节,例如提示,并且随着各种可行的模型出现,自定义变得越来越重要。

如果您目前正在使用这些传统链之一,请参阅此指南了解如何迁移

有关如何使用 LCEL 执行特定任务的指南,请查看相关的操作指南

可运行接口

为了尽可能轻松地创建自定义链,我们实现了一个"可运行"协议。许多 LangChain 组件实现了可运行协议,包括聊天模型、大语言模型、输出解析器、检索器、提示模板等等。还有一些用于处理可运行对象的实用原语,您可以在下面阅读。

这是一个标准接口,使定义自定义链以及以标准方式调用它们变得容易。标准接口包括

  • stream:流式返回响应的块
  • invoke:在输入上调用链
  • batch:在输入列表上调用链

这些还具有相应的异步方法,应与asyncioawait语法一起用于并发

  • astream:异步流式返回响应的块
  • ainvoke:异步在输入上调用链
  • abatch:异步在输入列表上调用链
  • astream_log:除了最终响应外,还流式返回发生的中间步骤。
  • astream_events测试版流式传输链中发生的事件(在langchain-core 0.1.14 中引入)

输入类型输出类型因组件而异

组件输入类型输出类型
提示字典PromptValue
聊天模型单个字符串、聊天消息列表或 PromptValueChatMessage
大语言模型单个字符串、聊天消息列表或 PromptValue字符串
输出解析器大语言模型或聊天模型的输出取决于解析器
检索器单个字符串文档列表
工具单个字符串或字典,取决于工具取决于工具

所有可运行对象都公开输入和输出模式以检查输入和输出

  • input_schema:从可运行对象的结构自动生成的输入 Pydantic 模型
  • output_schema:从可运行对象的结构自动生成的输出 Pydantic 模型

组件

LangChain 为各种组件提供标准的可扩展接口和外部集成,这些组件对于使用大语言模型构建很有用。LangChain 实现了一些组件,我们依赖第三方集成来实现一些组件,而其他组件则混合使用。

聊天模型

使用一系列消息作为输入并返回聊天消息作为输出(而不是使用纯文本)的语言模型(与使用纯文本相反)。这些传统上是较新的模型(较旧的模型通常是大语言模型,见下文)。聊天模型支持为对话消息分配不同的角色,有助于区分来自 AI、用户和指令(如系统消息)的消息。

虽然底层模型是输入消息、输出消息,但 LangChain 包装器也允许这些模型以字符串作为输入。这意味着您可以轻松地将聊天模型用作大语言模型的替代。

当字符串作为输入传递时,它会被转换为HumanMessage,然后传递给底层模型。

LangChain 不托管任何聊天模型,而是依赖于第三方集成。

在构建 ChatModels 时,我们有一些标准化的参数

  • model:模型的名称
  • temperature:采样温度
  • timeout:请求超时
  • max_tokens:要生成的最多标记数
  • stop:默认停止序列
  • max_retries:重试请求的最大次数
  • api_key:模型提供商的 API 密钥
  • base_url:发送请求的端点

一些需要注意的重要事项

  • 标准参数仅适用于公开具有预期功能的参数的模型提供商。例如,某些提供商不公开最大输出标记的配置,因此在这些提供商上不支持 max_tokens。
  • 标准参数目前仅在具有自身集成包的集成(例如langchain-openailangchain-anthropic等)上执行,在langchain-community中的模型上不执行。

ChatModels 还接受特定于该集成的其他参数。要查找 ChatModel 支持的所有参数,请转到该模型的 API 参考。

重要

一些聊天模型经过微调以进行工具调用并为此提供专用的 API。通常,此类模型在工具调用方面比未经微调的模型更好,并且建议用于需要工具调用的用例。请参阅工具调用部分以获取更多信息。

有关如何使用聊天模型的具体信息,请参阅此处相关的操作指南

多模态

一些聊天模型是多模态的,接受图像、音频甚至视频作为输入。这些仍然不太常见,这意味着模型提供商尚未标准化定义 API 的“最佳”方式。多模态输出甚至更不常见。因此,我们使我们的多模态抽象相当轻量级,并计划随着该领域的成熟进一步巩固多模态 API 和交互模式。

在 LangChain 中,大多数支持多模态输入的聊天模型也接受 OpenAI 的内容块格式中的这些值。到目前为止,这仅限于图像输入。对于像 Gemini 这样支持视频和其他字节输入的模型,API 也支持原生、特定于模型的表示。

有关如何使用多模态模型的具体信息,请参阅此处相关的操作指南

有关具有多模态模型的 LangChain 模型提供商的完整列表,请查看此表

大语言模型

注意

纯文本输入/输出的LLM往往比较旧或级别较低。许多新的流行模型最好用作聊天补全模型,即使在非聊天用例中也是如此。

您可能正在寻找上面部分的内容

将字符串作为输入并返回字符串的语言模型。这些传统上是较旧的模型(较新的模型通常是聊天模型,请参见上文)。

尽管底层模型是字符串输入,字符串输出,但LangChain包装器也允许这些模型将消息作为输入。这使它们与聊天模型具有相同的接口。当消息作为输入传递时,它们会在后台格式化为字符串,然后再传递给底层模型。

LangChain不托管任何LLM,而是依赖于第三方集成。

有关如何使用LLM的详细信息,请参阅使用指南

消息

一些语言模型将消息列表作为输入并返回一条消息。消息有几种不同的类型。所有消息都具有rolecontentresponse_metadata属性。

role描述了谁在发送消息。标准角色包括“用户”、“助手”、“系统”和“工具”。LangChain针对不同的角色提供了不同的消息类。

content属性描述了消息的内容。这可以是以下几种情况:

  • 字符串(大多数模型处理此类型的内容)
  • 字典列表(用于多模态输入,其中字典包含有关该输入类型和输入位置的信息)

可选地,消息可以具有name属性,该属性允许区分具有相同角色的多个说话者。例如,如果聊天记录中有两个用户,则区分它们可能很有用。并非所有模型都支持此功能。

HumanMessage

这表示角色为“用户”的消息。

AIMessage

这表示角色为“助手”的消息。除了content属性外,这些消息还具有:

response_metadata

response_metadata属性包含有关响应的其他元数据。此处的數據通常特定於每个模型提供商。这就是存储诸如日志概率和令牌使用情况等信息的位置。

tool_calls

这些表示语言模型调用工具的决策。它们作为AIMessage输出的一部分包含在内。可以通过.tool_calls属性访问它们。

此属性返回一个ToolCall列表。ToolCall是一个字典,包含以下参数:

  • name:应调用的工具的名称。
  • args:传递给该工具的参数。
  • id:该工具调用的ID。

SystemMessage

这表示角色为“系统”的消息,它告诉模型如何运行。并非每个模型提供商都支持此功能。

ToolMessage

这表示角色为“工具”的消息,其中包含调用工具的结果。除了rolecontent之外,此消息还具有:

  • 一个tool_call_id字段,用于传达调用生成此结果的工具的ID。
  • 一个artifact字段,可用于传递工具执行的任意工件,这些工件有助于跟踪,但不能发送到模型。

对于大多数聊天模型,只有在AIMessage具有填充的tool_calls字段后,ToolMessage才能出现在聊天历史记录中。

(旧版)FunctionMessage

这是一种旧版消息类型,对应于OpenAI的旧版函数调用API。应使用ToolMessage代替,以对应于更新的工具调用API。

这表示函数调用的结果。除了rolecontent之外,此消息还具有一个name参数,用于传达调用生成此结果的函数的名称。

提示模板

提示模板有助于将用户输入和参数转换为语言模型的指令。这可以用来指导模型的响应,帮助它理解上下文并生成相关且连贯的基于语言的输出。

提示模板将字典作为输入,其中每个键代表提示模板中要填充的变量。

提示模板输出PromptValue。此PromptValue可以传递给LLM或ChatModel,也可以转换为字符串或消息列表。PromptValue存在的原因是为了简化字符串和消息之间的切换。

提示模板有几种不同的类型:

字符串提示模板

这些提示模板用于格式化单个字符串,通常用于更简单的输入。例如,构建和使用PromptTemplate的一种常见方法如下所示:

from langchain_core.prompts import PromptTemplate

prompt_template = PromptTemplate.from_template("Tell me a joke about {topic}")

prompt_template.invoke({"topic": "cats"})
API参考:PromptTemplate

聊天提示模板

这些提示模板用于格式化消息列表。这些“模板”本身由模板列表组成。例如,构建和使用ChatPromptTemplate的一种常见方法如下所示:

from langchain_core.prompts import ChatPromptTemplate

prompt_template = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant"),
("user", "Tell me a joke about {topic}")
])

prompt_template.invoke({"topic": "cats"})
API参考:ChatPromptTemplate

在上面的示例中,此ChatPromptTemplate在被调用时将构造两条消息。第一条是系统消息,没有要格式化的变量。第二条是HumanMessage,将由用户传入的topic变量进行格式化。

MessagesPlaceholder

此提示模板负责在特定位置添加消息列表。在上面的ChatPromptTemplate中,我们看到了如何格式化两条消息,每条消息都是一个字符串。但是,如果我们希望用户传入一个消息列表,我们将其插入到特定位置呢?这就是使用MessagesPlaceholder的方法。

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import HumanMessage

prompt_template = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant"),
MessagesPlaceholder("msgs")
])

prompt_template.invoke({"msgs": [HumanMessage(content="hi!")]})

这将生成一个包含两条消息的列表,第一条是系统消息,第二条是我们传入的HumanMessage。如果我们传入5条消息,那么它将总共生成6条消息(系统消息加上传入的5条)。这对于让消息列表插入到特定位置很有用。

无需显式使用MessagesPlaceholder类即可完成相同操作的另一种方法是:

prompt_template = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant"),
("placeholder", "{msgs}") # <-- This is the changed part
])

有关如何使用提示模板的详细信息,请参阅此处相关的使用指南

示例选择器

为了获得更好的性能,一种常见的提示技巧是将示例作为提示的一部分包含在内。这被称为少样本提示。这为语言模型提供了关于如何运行的具体示例。有时这些示例被硬编码到提示中,但对于更高级的情况,动态选择它们可能很好。示例选择器是负责选择并将示例格式化为提示的类。

有关如何使用示例选择器的详细信息,请参阅此处相关的使用指南

输出解析器

注意

此处的资料指的是解析器,它们获取模型的文本输出并尝试将其解析为更结构化的表示形式。越来越多的模型支持函数(或工具)调用,这会自动处理此问题。建议使用函数/工具调用而不是输出解析。请参阅此处的相关文档。

输出解析器负责获取模型的输出并将其转换为更适合下游任务的格式。当您使用LLM生成结构化数据或规范化来自聊天模型和LLM的输出时非常有用。

LangChain有很多不同类型的输出解析器。这是一个LangChain支持的输出解析器列表。下表包含各种信息:

  • **名称**:输出解析器的名称。
  • **支持流式传输**:输出解析器是否支持流式传输。
  • **是否具有格式指令**:输出解析器是否具有格式指令。这通常可用,除非(a)所需的模式未在提示中指定,而是在其他参数(如OpenAI函数调用)中指定,或者(b)当OutputParser包装另一个OutputParser时。
  • **调用LLM**:此输出解析器本身是否调用LLM。这通常仅由尝试更正格式错误输出的输出解析器执行。
  • **输入类型**:预期的输入类型。大多数输出解析器都适用于字符串和消息,但有些(如OpenAI函数)需要具有特定关键字参数的消息。
  • **输出类型**:解析器返回的对象的输出类型。
  • **描述**:我们对该输出解析器的评论以及何时使用它。
名称支持流式传输是否具有格式指令调用LLM输入类型输出类型描述
JSONstr | MessageJSON对象返回指定的JSON对象。您可以指定一个Pydantic模型,它将返回该模型的JSON。对于获取不使用函数调用的结构化数据,这可能是最可靠的输出解析器。
XMLstr | Messagedict返回标签字典。当需要XML输出时使用。与擅长编写XML的模型(如Anthropic的模型)一起使用。
CSV

str | Message字符串列表返回一个由逗号分隔的值组成的列表。
输出修复str | Message包装另一个输出解析器。如果该输出解析器出错,则将错误消息和错误输出传递给 LLM,并要求其修复输出。
重试并带错误str | Message包装另一个输出解析器。如果该输出解析器出错,则将原始输入、错误输出和错误消息传递给 LLM,并要求其修复它。与 OutputFixingParser 相比,此解析器还会发送原始指令。
Pydanticstr | Messagepydantic.BaseModel接收用户定义的 Pydantic 模型,并以该格式返回数据。
YAMLstr | Messagepydantic.BaseModel接收用户定义的 Pydantic 模型,并以该格式返回数据。使用 YAML 进行编码。
PandasDataFramestr | Messagedict对于使用 Pandas DataFrame 进行操作非常有用。
枚举str | Message枚举将响应解析为提供的枚举值之一。
日期时间str | Messagedatetime.datetime将响应解析为日期时间字符串。
结构化str | MessageDict[str, str]返回结构化信息的输出解析器。它不如其他输出解析器强大,因为它仅允许字段为字符串。当您使用较小的 LLM 时,这可能很有用。

有关如何使用输出解析器的详细信息,请参阅此处的相关操作指南

聊天历史

大多数 LLM 应用程序都具有对话界面。对话的一个基本组成部分是能够参考在对话早期引入的信息。至少,对话系统应该能够直接访问过去消息的一些窗口。

ChatHistory的概念指的是 LangChain 中的一个类,可用于包装任意链。此ChatHistory将跟踪底层链的输入和输出,并将它们作为消息附加到消息数据库中。未来的交互将加载这些消息,并将它们作为输入的一部分传递到链中。

文档

LangChain 中的 Document 对象包含有关某些数据的信息。它有两个属性

  • page_content: str:此文档的内容。目前仅为字符串。
  • metadata: dict:与此文档关联的任意元数据。可以跟踪文档 ID、文件名等。

文档加载器

这些类加载 Document 对象。LangChain 与各种数据源(例如 Slack、Notion、Google Drive 等)有数百个集成,可以从中加载数据。

每个 DocumentLoader 都有其自己的特定参数,但它们都可以通过.load方法以相同的方式调用。一个示例用例如下所示

from langchain_community.document_loaders.csv_loader import CSVLoader

loader = CSVLoader(
... # <-- Integration specific parameters here
)
data = loader.load()
API 参考:CSVLoader

有关如何使用文档加载器的详细信息,请参阅此处的相关操作指南

文本分割器

加载文档后,您通常希望对其进行转换以更好地适应您的应用程序。最简单的示例是,您可能希望将长文档拆分为更小的块,以便适合模型的上下文窗口。LangChain 有许多内置的文档转换器,可以轻松地拆分、组合、过滤和以其他方式操作文档。

当您要处理长文本时,需要将该文本拆分为块。虽然听起来很简单,但这里存在许多潜在的复杂性。理想情况下,您希望将语义相关的文本片段放在一起。“语义相关”的含义可能取决于文本类型。此笔记本展示了几种执行此操作的方法。

在高级别上,文本分割器的工作原理如下

  1. 将文本拆分为小的、语义上有意义的块(通常是句子)。
  2. 开始将这些小块组合成更大的块,直到达到一定大小(以某种函数衡量)。
  3. 达到该大小后,将该块作为其自身的文本片段,然后开始创建新的文本块,并具有一定的重叠(以保持块之间的上下文)。

这意味着您可以沿着两个不同的轴自定义文本分割器

  1. 文本的分割方式
  2. 块大小的衡量方式

有关如何使用文本分割器的详细信息,请参阅此处的相关操作指南

嵌入模型

嵌入模型创建文本片段的向量表示。您可以将向量视为一个数字数组,它捕获文本的语义含义。通过以这种方式表示文本,您可以执行数学运算,从而执行诸如搜索语义上最相似的其他文本片段等操作。这些自然语言搜索功能是许多类型上下文检索的基础,在这些检索中,我们为 LLM 提供了有效响应查询所需的相关数据。

Embeddings类是一个专为与文本嵌入模型交互而设计的类。有许多不同的嵌入模型提供商(OpenAI、Cohere、Hugging Face 等)和本地模型,此类旨在为所有这些模型提供标准接口。

LangChain 中的基类 Embeddings 提供两种方法:一种用于嵌入文档,另一种用于嵌入查询。前者将多个文本作为输入,而后者将单个文本作为输入。将它们作为两种单独方法的原因是,某些嵌入提供商对文档(要搜索的文档)和查询(搜索查询本身)具有不同的嵌入方法。

有关如何使用嵌入模型的详细信息,请参阅此处的相关操作指南

向量存储

存储和搜索非结构化数据最常见的方法之一是将其嵌入并存储生成的嵌入向量,然后在查询时嵌入非结构化查询并检索与嵌入查询“最相似”的嵌入向量。向量存储负责为您存储嵌入数据并执行向量搜索。

大多数向量存储还可以存储有关嵌入向量的元数据,并在相似性搜索之前支持对该元数据进行过滤,从而使您可以更好地控制返回的文档。

向量存储可以通过以下方式转换为检索器接口

vectorstore = MyVectorStore()
retriever = vectorstore.as_retriever()

有关如何使用向量存储的详细信息,请参阅此处的相关操作指南

检索器

检索器是一个接口,它根据非结构化查询返回文档。它比向量存储更通用。检索器不需要能够存储文档,只需要返回(或检索)它们即可。检索器可以从向量存储创建,但也足够广泛,可以包括维基百科搜索Amazon Kendra

检索器以字符串查询作为输入,并以 Document 列表作为输出。

有关如何使用检索器的详细信息,请参阅此处的相关操作指南

键值存储

对于某些技术,例如每个文档使用多个向量进行索引和检索缓存嵌入,使用某种形式的键值 (KV) 存储很有帮助。

LangChain 包含一个BaseStore接口,该接口允许存储任意数据。但是,需要 KV 存储的 LangChain 组件接受更具体的BaseStore[str, bytes]实例,该实例存储二进制数据(称为ByteStore),并在内部处理编码和解码数据以满足其特定需求。

这意味着作为用户,您只需要考虑一种存储类型,而不是针对不同类型的数据使用不同的存储类型。

接口

所有BaseStores都支持以下接口。请注意,该接口允许一次修改多个键值对

  • mget(key: Sequence[str]) -> List[Optional[bytes]]:获取多个键的内容,如果键不存在则返回None
  • mset(key_value_pairs: Sequence[Tuple[str, bytes]]) -> None:设置多个键的内容
  • mdelete(key: Sequence[str]) -> None:删除多个键
  • yield_keys(prefix: Optional[str] = None) -> Iterator[str]:生成存储中的所有键,可以选择按前缀进行过滤

有关键值存储实现,请参阅此部分

工具

工具是旨在由模型调用的实用程序:它们的输入旨在由模型生成,它们的输出旨在传递回模型。每当您希望模型控制代码的一部分或调用外部 API 时,都需要使用工具。

工具包括

  1. 工具的名称
  2. 工具作用的描述
  3. 定义工具输入的JSON 模式
  4. 一个函数(以及可选的函数异步变体)。

将工具绑定到模型时,名称、描述和 JSON 模式将作为上下文提供给模型。给定工具列表和一组指令,模型可以请求使用特定输入调用一个或多个工具。典型用法可能如下所示

tools = [...] # Define a list of tools
llm_with_tools = llm.bind_tools(tools)
ai_msg = llm_with_tools.invoke("do xyz...")
# -> AIMessage(tool_calls=[ToolCall(...), ...], ...)

模型返回的AIMessage可能与tool_calls相关联。阅读此指南,以获取有关响应类型可能是什么样子的更多信息。

一旦调用了选定的工具,结果就可以传递回模型,以便它可以完成正在执行的任何任务。通常有两种不同的方法来调用工具并传递回响应

仅使用参数调用

当您仅使用参数调用工具时,您将获得原始工具输出(通常是字符串)。这通常如下所示

# You will want to previously check that the LLM returned tool calls
tool_call = ai_msg.tool_calls[0]
# ToolCall(args={...}, id=..., ...)
tool_output = tool.invoke(tool_call["args"])
tool_message = ToolMessage(
content=tool_output,
tool_call_id=tool_call["id"],
name=tool_call["name"]
)

请注意,content字段通常会传递回模型。如果您不希望将原始工具响应传递给模型,但仍希望保留它,您可以转换工具输出,但也可以将其作为工件传递(在此处阅读有关ToolMessage.artifact的更多信息)

... # Same code as above
response_for_llm = transform(response)
tool_message = ToolMessage(
content=response_for_llm,
tool_call_id=tool_call["id"],
name=tool_call["name"],
artifact=tool_output
)

使用ToolCall调用

调用工具的另一种方法是使用模型生成的完整ToolCall来调用它。当你这样做时,工具将返回一个ToolMessage。这样做的好处是,你无需自己编写逻辑将工具输出转换为ToolMessage。通常看起来像这样

tool_call = ai_msg.tool_calls[0]
# -> ToolCall(args={...}, id=..., ...)
tool_message = tool.invoke(tool_call)
# -> ToolMessage(
# content="tool result foobar...",
# tool_call_id=...,
# name="tool_name"
# )

如果你以这种方式调用工具,并希望为ToolMessage包含一个工件,则需要让工具返回两件事。有关此处定义返回工件的工具的更多信息,请阅读。

最佳实践

在设计供模型使用的工具时,务必牢记以下几点

  • 具有显式工具调用API的聊天模型在工具调用方面会比未经过微调的模型表现更好。
  • 如果工具具有精心选择的名称、描述和JSON模式,则模型的性能会更好。这是另一种提示工程的形式。
  • 对于模型来说,简单、范围狭窄的工具比复杂的工具更容易使用。

有关如何使用工具的具体信息,请参阅工具操作指南

要使用预构建工具,请参阅工具集成文档

工具包

工具包是为特定任务一起使用而设计的工具集合。它们具有方便的加载方法。

所有工具包都公开了一个get_tools方法,该方法返回工具列表。因此,您可以执行以下操作

# Initialize a toolkit
toolkit = ExampleTookit(...)

# Get list of tools
tools = toolkit.get_tools()

代理

语言模型本身无法执行操作——它们只会输出文本。LangChain 的一个主要用例是创建代理。代理是使用LLM作为推理引擎来确定要采取哪些操作以及这些操作的输入应该是什么的系统。然后,可以将这些操作的结果反馈给代理,并由它确定是否需要更多操作,或者是否可以结束。

LangGraph 是 LangChain 的一个扩展,专门用于创建高度可控和可定制的代理。请查看该文档以更深入地了解代理概念。

LangChain 中有一个我们正在逐步弃用的旧版agent概念:AgentExecutor。AgentExecutor 本质上是代理的运行时。它是一个很好的入门场所,但是,当您开始拥有更多自定义代理时,它就不够灵活了。为了解决这个问题,我们构建了 LangGraph 作为这个灵活且高度可控的运行时。

如果您仍在使用 AgentExecutor,请不要担心:我们仍然有关于如何使用 AgentExecutor的指南。但是,建议您开始过渡到 LangGraph。为了协助您完成此操作,我们整理了一个关于如何进行过渡的指南

ReAct 代理

构建代理的一种流行架构是ReAct。ReAct 将推理和行动结合在一个迭代过程中——事实上,“ReAct”这个名称代表“推理”和“行动”。

一般流程如下

  • 模型将“思考”如何响应输入和任何先前的观察结果来采取下一步操作。
  • 然后,模型将从可用工具中选择一个操作(或选择响应用户)。
  • 模型将为该工具生成参数。
  • 代理运行时(执行器)将解析选定的工具并使用生成的参数调用它。
  • 执行器将工具调用的结果作为观察结果返回给模型。
  • 此过程重复,直到代理选择做出响应。

有一些基于通用提示的实现不需要任何特定于模型的功能,但最可靠的实现使用诸如工具调用之类的功能来可靠地格式化输出并减少差异。

有关更多信息,请参阅LangGraph 文档,或参阅此操作指南以获取有关迁移到 LangGraph 的具体信息。

回调

LangChain 提供了一个回调系统,允许您挂接到 LLM 应用程序的各个阶段。这对于日志记录、监控、流式传输和其他任务很有用。

您可以使用 API 中随处可见的callbacks参数订阅这些事件。此参数是处理程序对象列表,这些对象预计将实现下面更详细描述的一种或多种方法。

回调事件

事件事件触发器关联方法
聊天模型开始当聊天模型开始时on_chat_model_start
LLM 开始当 llm 开始时on_llm_start
LLM 新令牌当 llm 或聊天模型发出新令牌时on_llm_new_token
LLM 结束当 llm 或聊天模型结束时on_llm_end
LLM 错误当 llm 或聊天模型出错时on_llm_error
链开始当链开始运行时on_chain_start
链结束当链结束时on_chain_end
链错误当链出错时on_chain_error
工具开始当工具开始运行时on_tool_start
工具结束当工具结束时on_tool_end
工具错误当工具出错时on_tool_error
代理操作当代理采取操作时on_agent_action
代理完成当代理结束时on_agent_finish
检索器开始当检索器开始时on_retriever_start
检索器结束当检索器结束时on_retriever_end
检索器错误当检索器出错时on_retriever_error
文本当运行任意文本时on_text
重试当运行重试事件时on_retry

回调处理程序

回调处理程序可以是syncasync

在运行时,LangChain 配置一个合适的回调管理器(例如,CallbackManagerAsyncCallbackManager),当事件触发时,该管理器将负责在每个“已注册”回调处理程序上调用相应的方法。

传递回调

callbacks属性在 API 中的大多数对象(模型、工具、代理等)的两个不同位置都可用

  • 请求时回调:除了输入数据外,还在请求时传递。在所有标准Runnable对象上可用。这些回调会被其定义所在对象的全部子对象继承。例如,chain.invoke({"number": 25}, {"callbacks": [handler]})
  • 构造函数回调chain = TheNameOfSomeChain(callbacks=[handler])。这些回调作为参数传递给对象的构造函数。回调仅作用于其定义所在的范围,并且不会被该对象的任何子对象继承。
警告

构造函数回调仅作用于其定义所在的范围。它们不会被对象的子对象继承。

如果您正在创建自定义链或可运行对象,则需要记住将请求时回调传播到任何子对象。

Python<=3.10 中的异步

任何RunnableLambdaRunnableGenerator或调用其他可运行对象并在 python<=3.10 中以async方式运行的Tool都必须手动将回调传播到子对象。这是因为在这种情况下,LangChain 无法自动将回调传播到子对象。

这是您可能无法看到从自定义可运行对象或工具发出事件的常见原因。

有关如何使用回调的具体信息,请参阅此处相关的操作指南

技术

流式传输

单个LLM调用通常比传统的资源请求运行时间长得多。当您构建需要多个推理步骤的更复杂的链或代理时,这种情况会加剧。

幸运的是,LLM 以迭代方式生成输出,这意味着在最终响应准备就绪之前,可以显示合理的中间结果。因此,一旦输出可用就立即使用它已成为围绕使用LLM构建应用程序的UX的重要组成部分,以帮助缓解延迟问题,而LangChain旨在对流式传输提供一流的支持。

下面,我们将讨论LangChain中关于流式传输的一些概念和注意事项。

.stream().astream()

LangChain 中的大多数模块都包含.stream()方法(以及用于异步环境的等效.astream()方法)作为符合人体工程学的流式传输接口。.stream()返回一个迭代器,您可以使用简单的for循环来使用它。以下是一个使用聊天模型的示例

from langchain_anthropic import ChatAnthropic

model = ChatAnthropic(model="claude-3-sonnet-20240229")

for chunk in model.stream("what color is the sky?"):
print(chunk.content, end="|", flush=True)
API 参考:ChatAnthropic

对于本身不支持流式传输的模型(或其他组件),此迭代器只会产生一个块,但您仍然可以在调用它们时使用相同的通用模式。使用.stream()还将自动以流式模式调用模型,无需提供其他配置。

每个输出块的类型取决于组件的类型——例如,聊天模型会产生AIMessageChunks。由于此方法是LangChain 表达式语言的一部分,因此您可以使用输出解析器处理来自不同输出的格式差异,以转换每个产生的块。

您可以查看本指南,以详细了解如何使用.stream()

.astream_events()

虽然.stream()方法很直观,但它只能返回链中最终生成的值。对于单个 LLM 调用来说,这很好,但当您构建多个 LLM 调用组成的更复杂的链时,您可能希望将链的中间值与最终输出一起使用——例如,在构建基于文档的聊天应用程序时,将源与最终生成一起返回。

您可以使用回调或通过以某种方式构建您的链来实现这一点,这种方式会将中间值与类似于链式.assign()调用的某些内容一起传递到末尾,但 LangChain 还包含一个.astream_events()方法,该方法将回调的灵活性与.stream()的易用性相结合。调用时,它返回一个迭代器,该迭代器会产生各种类型的事件,您可以根据项目的需要对其进行过滤和处理。

这是一个小的示例,它只打印包含流式聊天模型输出的事件

from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_anthropic import ChatAnthropic

model = ChatAnthropic(model="claude-3-sonnet-20240229")

prompt = ChatPromptTemplate.from_template("tell me a joke about {topic}")
parser = StrOutputParser()
chain = prompt | model | parser

async for event in chain.astream_events({"topic": "parrot"}, version="v2"):
kind = event["event"]
if kind == "on_chat_model_stream":
print(event, end="|", flush=True)

您可以将其粗略地视为回调事件的迭代器(尽管格式不同)——并且您可以在几乎所有 LangChain 组件上使用它!

请参阅本指南,以获取有关如何使用.astream_events()的更多详细信息,包括列出可用事件的表格。

回调

在 LangChain 中,通过回调系统以最低级别的方式从 LLM 流式传输输出。您可以传递一个回调处理程序,该处理程序处理on_llm_new_token事件到 LangChain 组件中。当该组件被调用时,组件中包含的任何LLM聊天模型都会使用生成的标记调用回调。在回调中,您可以将标记管道传输到其他目标,例如 HTTP 响应。您还可以处理on_llm_end事件以执行任何必要的清理。

您可以查看本操作指南部分,以获取有关使用回调的更多详细信息。

回调是在 LangChain 中引入的第一个流式传输技术。虽然功能强大且通用,但对于开发人员来说可能很笨拙。例如

  • 您需要显式初始化和管理一些聚合器或其他流来收集结果。
  • 执行顺序没有明确保证,理论上回调可能会在.invoke()方法完成之后运行。
  • 提供商通常会让您传递一个额外的参数来流式传输输出,而不是一次返回所有输出。
  • 您通常会忽略实际模型调用的结果,而青睐回调结果。

标记

大多数模型提供商用来衡量输入和输出的单位是称为**标记**的单位。标记是语言模型在处理或生成文本时读取和生成的的基本单位。标记的确切定义可能因模型训练的具体方式而异——例如,在英语中,标记可以是一个单词,如“apple”,也可以是单词的一部分,如“app”。

当您向模型发送提示时,提示中的单词和字符会使用**标记器**编码为标记。然后,模型会流式传输回生成的输出标记,标记器会将这些标记解码为人类可读的文本。以下示例显示了 OpenAI 模型如何标记LangChain is cool!

您可以看到它被拆分为 5 个不同的标记,并且标记之间的边界与单词边界并不完全相同。

语言模型使用标记而不是更直观的“字符”的原因与它们如何处理和理解文本有关。在高级别上,语言模型根据初始输入和之前的生成迭代地预测其下一个生成的输出。使用标记训练模型使语言模型能够处理具有意义的语言单位(如单词或子词),而不是单个字符,这使得模型更容易学习和理解语言的结构,包括语法和上下文。此外,使用标记还可以提高效率,因为与字符级处理相比,模型处理的文本单元更少。

函数/工具调用

信息

我们将术语工具调用函数调用互换使用。虽然函数调用有时指的是单个函数的调用,但我们将所有模型都视为可以在每条消息中返回多个工具或函数调用。

工具调用允许聊天模型通过生成与用户定义的模式匹配的输出来响应给定的提示。

虽然名称暗示模型正在执行某些操作,但实际上并非如此!模型仅生成工具的参数,而实际运行工具(或不运行)则取决于用户。一个常见的示例,您**不**希望使用生成的参数调用函数,是如果您想从非结构化文本中提取与某个模式匹配的结构化输出。您将向模型提供一个“提取”工具,该工具采用与所需模式匹配的参数,然后将生成的输出视为您的最终结果。

Diagram of a tool call by a chat model

工具调用并非普遍适用,但许多流行的 LLM 提供商都支持它,包括AnthropicCohereGoogleMistralOpenAI,甚至通过Ollama用于本地运行的模型。

LangChain 为工具调用提供了一个标准化的接口,该接口在不同的模型之间保持一致。

标准接口包括

  • ChatModel.bind_tools():一种指定模型可调用哪些工具的方法。此方法接受LangChain 工具以及Pydantic对象。
  • AIMessage.tool_calls:模型返回的AIMessage上的一个属性,用于访问模型请求的工具调用。

工具使用

模型调用工具后,您可以通过调用工具,然后将参数传递回模型来使用该工具。LangChain 提供了Tool抽象来帮助您处理此问题。

一般流程如下

  1. 根据查询使用聊天模型生成工具调用。
  2. 使用生成的工具调用作为参数调用相应的工具。
  3. 将工具调用的结果格式化为ToolMessages
  4. 将整个消息列表传递回模型,以便它可以生成最终答案(或调用更多工具)。

Diagram of a complete tool calling flow

这就是工具调用代理执行任务和回答查询的方式。

查看下面一些更集中的指南

结构化输出

LLM 能够生成任意文本。这使模型能够适当地响应各种输入,但对于某些用例,将 LLM 的输出限制为特定格式或结构可能很有用。这称为**结构化输出**。

例如,如果输出要存储在关系数据库中,那么如果模型生成的输出符合定义的模式或格式,则会容易得多。从非结构化文本中提取特定信息是另一个特别有用的情况。最常见的输出格式是 JSON,但其他格式(如YAML)也可能有用。下面,我们将讨论一些从 LangChain 中的模型获取结构化输出的方法。

.with_structured_output()

为方便起见,某些 LangChain 聊天模型支持.with_structured_output()方法。此方法只需要一个模式作为输入,并返回一个字典或 Pydantic 对象。通常,此方法仅存在于支持下面描述的更高级方法之一的模型上,并且会在内部使用其中一种方法。它负责导入合适的输出解析器并以正确的格式为模型格式化模式。

这是一个示例

from typing import Optional

from pydantic import BaseModel, Field


class Joke(BaseModel):
"""Joke to tell user."""

setup: str = Field(description="The setup of the joke")
punchline: str = Field(description="The punchline to the joke")
rating: Optional[int] = Field(description="How funny the joke is, from 1 to 10")

structured_llm = llm.with_structured_output(Joke)

structured_llm.invoke("Tell me a joke about cats")
Joke(setup='Why was the cat sitting on the computer?', punchline='To keep an eye on the mouse!', rating=None)

我们建议将此方法作为处理结构化输出的起点

  • 它在幕后使用其他特定于模型的功能,而无需导入输出解析器。
  • 对于使用工具调用的模型,不需要特殊的提示。
  • 如果支持多种底层技术,您可以向method参数提供切换使用哪一个

如果您可能想要或需要使用其他技术,则

  • 您正在使用的聊天模型不支持工具调用。
  • 您正在处理非常复杂的模式,并且模型难以生成符合要求的输出。

有关更多信息,请查看此操作指南

您还可以查看此表,了解支持with_structured_output()的模型列表。

原始提示

获得模型结构化输出最直观的方法是礼貌地请求。除了您的查询之外,您还可以提供说明,描述您想要的输出类型,然后使用输出解析器解析输出,将原始模型消息或字符串输出转换为更易于操作的内容。

原始提示的最大好处在于其灵活性

  • 原始提示不需要任何特殊的模型功能,只需要足够强的推理能力来理解传递的模式。

  • 您可以提示任何您想要的格式,而不仅仅是 JSON。如果您的模型在某种特定类型的数据上训练得更充分,例如 XML 或 YAML,这将非常有用。

但是,也有一些缺点。

  • 大型语言模型 (LLM) 是非确定性的,提示 LLM 始终以完全正确的格式输出数据以实现流畅的解析可能出乎意料地困难且特定于模型。
  • 各个模型根据其训练数据具有不同的特性,优化提示可能非常困难。有些模型可能更擅长解释JSON 模式,其他模型可能最适合 TypeScript 定义,而另一些模型可能更喜欢 XML。

虽然模型提供商提供的功能可能会提高可靠性,但无论您选择哪种方法,提示技术对于微调结果仍然很重要。

JSON 模式

一些模型,例如MistralOpenAITogether AIOllama,支持一项称为**JSON 模式**的功能,通常通过配置启用。

启用后,JSON 模式会将模型的输出限制为始终是某种有效的 JSON。通常它们需要一些自定义提示,但通常比完全原始的提示轻松得多,并且更像是"你必须始终返回 JSON"。该输出通常也更容易解析

它通常也比工具调用更简单、更易于使用且更普遍可用,并且可以提供比工具调用更多关于提示和塑造结果的灵活性。

这是一个示例

from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain.output_parsers.json import SimpleJsonOutputParser

model = ChatOpenAI(
model="gpt-4o",
model_kwargs={ "response_format": { "type": "json_object" } },
)

prompt = ChatPromptTemplate.from_template(
"Answer the user's question to the best of your ability."
'You must always output a JSON object with an "answer" key and a "followup_question" key.'
"{question}"
)

chain = prompt | model | SimpleJsonOutputParser()

chain.invoke({ "question": "What is the powerhouse of the cell?" })
{'answer': 'The powerhouse of the cell is the mitochondrion. It is responsible for producing energy in the form of ATP through cellular respiration.',
'followup_question': 'Would you like to know more about how mitochondria produce energy?'}

有关支持 JSON 模式的模型提供商的完整列表,请参阅此表

工具调用

对于支持它的模型,工具调用对于结构化输出非常方便。它消除了围绕如何最好地提示模式的猜测,转而使用内置的模型功能。

它的工作原理是首先将所需的模式直接或通过LangChain 工具绑定到聊天模型,使用.bind_tools()方法。然后,模型将生成一个包含tool_calls字段的AIMessage,该字段包含与所需形状匹配的args

在 LangChain 中,您可以使用几种可接受的格式将工具绑定到模型。以下是一个示例

from pydantic import BaseModel, Field
from langchain_openai import ChatOpenAI

class ResponseFormatter(BaseModel):
"""Always use this tool to structure your response to the user."""

answer: str = Field(description="The answer to the user's question")
followup_question: str = Field(description="A followup question the user could ask")

model = ChatOpenAI(
model="gpt-4o",
temperature=0,
)

model_with_tools = model.bind_tools([ResponseFormatter])

ai_msg = model_with_tools.invoke("What is the powerhouse of the cell?")

ai_msg.tool_calls[0]["args"]
API 参考:ChatOpenAI
{'answer': "The powerhouse of the cell is the mitochondrion. It generates most of the cell's supply of adenosine triphosphate (ATP), which is used as a source of chemical energy.",
'followup_question': 'How do mitochondria generate ATP?'}

工具调用是使模型生成结构化输出的一种普遍一致的方式,并且是当模型支持时,.with_structured_output()方法使用的默认技术。

以下操作指南是使用函数/工具调用进行结构化输出的良好实用资源

有关支持工具调用的模型提供商的完整列表,请参阅此表

少样本提示

提高模型性能最有效的方法之一是向模型提供有关您希望它执行的操作的示例。将示例输入和预期输出添加到模型提示的技术称为“少样本提示”。该技术基于语言模型是少样本学习者论文。在进行少样本提示时,需要考虑以下几点

  1. 如何生成示例?
  2. 每个提示中有多少个示例?
  3. 如何在运行时选择示例?
  4. 如何在提示中格式化示例?

以下是每个方面的注意事项。

1. 生成示例

少样本提示的第一步也是最重要的一步是提出一个好的示例数据集。好的示例应该在运行时相关、清晰、信息丰富,并提供模型未知的信息。

从高层次来看,生成示例的基本方法是

  • 手动:一个人或一些人生成他们认为有用的示例。
  • 更好的模型:更好的(可能更昂贵/更慢)模型的响应用作较差(可能更便宜/更快)模型的示例。
  • 用户反馈:用户(或标记者)对与应用程序的交互提供反馈,并根据该反馈生成示例(例如,所有具有积极反馈的交互都可以转换为示例)。
  • LLM 反馈:与用户反馈相同,但该过程通过让模型自我评估来自动化。

哪种方法最好取决于您的任务。对于需要真正理解少量核心原则的任务,手工制作几个真正好的示例可能很有价值。对于行为空间更广阔、更细致入微的任务,以更自动化的方式生成许多示例可能很有用,这样任何运行时输入都更有可能存在一些高度相关的示例。

单轮与多轮示例

在生成示例时需要考虑的另一个维度是示例实际上展示了什么。

最简单的示例类型只有一个用户输入和一个预期的模型输出。这些是单轮示例。

一种更复杂的示例类型是示例是一个完整的对话,通常在其中模型最初响应不正确,然后用户告诉模型如何更正其答案。这称为多轮示例。多轮示例可用于更细致入微的任务,在这些任务中,显示常见错误并详细说明它们错误的原因以及应该采取哪些措施很有用。

2. 示例数量

一旦我们拥有一个示例数据集,我们就需要考虑每个提示中应该包含多少个示例。关键的权衡是,更多的示例通常会提高性能,但更大的提示会增加成本和延迟。并且超过某个阈值,拥有太多示例会开始混淆模型。找到合适的示例数量高度依赖于模型、任务、示例的质量以及您的成本和延迟限制。根据经验,模型越好,它需要执行良好的示例越少,并且在添加更多示例时越快达到急剧递减的回报。但是,可靠地回答这个问题的最佳/唯一方法是使用不同数量的示例运行一些实验。

3. 选择示例

假设我们没有将整个示例数据集添加到每个提示中,我们需要有一种方法可以根据给定的输入从我们的数据集中选择示例。我们可以这样做

  • 随机
  • 通过输入的(语义或基于关键词的)相似性
  • 基于某些其他约束,例如令牌大小

LangChain 有许多ExampleSelectors,这使得使用任何这些技术变得很容易。

通常,按语义相似性选择会导致最佳模型性能。但这有多重要,这再次取决于模型和任务,并且值得尝试。

4. 格式化示例

如今,大多数最先进的模型都是聊天模型,因此我们将重点放在为这些模型格式化示例。我们的基本选项是插入示例

  • 作为系统提示中的字符串
  • 作为它们自己的消息

如果我们将示例作为字符串插入系统提示中,我们需要确保模型清楚地知道每个示例从哪里开始,以及哪些部分是输入与输出。不同的模型对不同的语法反应更好,例如ChatML、XML、TypeScript 等。

如果我们将示例作为消息插入,其中每个示例都表示为人类、AI 消息序列,我们可能还想为我们的消息分配名称,例如"example_user""example_assistant",以明确这些消息对应于与最新输入消息不同的参与者。

格式化工具调用示例

当我们的示例输出包含工具调用时,将示例格式化为消息的一个棘手领域。这是因为不同的模型对生成任何工具调用时允许的消息序列类型有不同的约束。

  • 一些模型要求任何包含工具调用的 AIMessage 之后必须紧跟着每个工具调用的 ToolMessages,
  • 一些模型还要求任何 ToolMessages 之后必须紧跟着下一个 HumanMessage 之前的 AIMessage,
  • 一些模型要求如果聊天历史记录中存在任何工具调用/ToolMessages,则将工具传递给模型。

这些要求特定于模型,应检查您正在使用的模型。如果您的模型要求工具调用后有 ToolMessages 和/或 ToolMessages 后有 AIMessages,并且您的示例仅包含预期的工具调用而不包含实际的工具输出,您可以尝试在每个示例的末尾添加具有通用内容的虚拟 ToolMessages/AIMessages 以满足 API 约束。在这些情况下,特别值得尝试将示例作为字符串插入与作为消息插入,因为具有虚拟消息可能会对某些模型产生不利影响。

您可以查看 Anthropic 和 OpenAI 在两个不同的工具调用基准测试上如何响应不同少样本提示技术的案例研究此处

检索

LLM 在大型但固定的数据集上进行训练,限制了它们对私有或最新信息进行推理的能力。使用特定事实微调 LLM 是缓解此问题的一种方法,但通常不适合事实回忆,并且可能成本高昂检索是指向 LLM 提供相关信息以改进其对给定输入的响应的过程。检索增强生成 (RAG) 论文是使用检索到的信息对 LLM 生成(输出)进行接地的过程。

提示

RAG 的效果取决于检索到的文档的相关性和质量。幸运的是,我们可以采用一系列新兴技术来设计和改进 RAG 系统。我们专注于对许多这些技术进行分类和总结(见下图),并在以下部分分享一些高级战略指导。您可以并且应该尝试将不同的部分组合在一起使用。您可能还会发现 此 LangSmith 指南 对展示如何评估应用程序的不同迭代很有用。

查询转换

首先,考虑您 RAG 系统的用户输入。理想情况下,RAG 系统可以处理各种输入,从措辞不佳的问题到复杂的多部分查询。**使用 LLM 来审查并选择性地修改输入是查询转换的核心思想。** 这充当一个通用缓冲区,优化原始用户输入以用于您的检索系统。例如,这可以像提取关键词一样简单,也可以像为复杂查询生成多个子问题一样复杂。

名称何时使用描述
多查询当您需要涵盖问题的多个方面时。从多个角度重写用户问题,为每个重写的问题检索文档,返回所有查询的唯一文档。
分解当问题可以分解成更小的子问题时。将问题分解成一系列子问题/子任务,这些子问题/子任务可以依次解决(使用第一个+检索的结果来回答第二个)或并行解决(将每个答案合并成最终答案)。
回溯当需要更高层次的概念理解时。首先提示 LLM 提问一个关于更高层次的概念或原则的通用回溯问题,并检索相关的事实。使用此基础来帮助回答用户问题。论文
HyDE如果您在使用原始用户输入检索相关文档时遇到挑战。使用 LLM 将问题转换为假设文档,这些文档可以回答问题。使用嵌入式假设文档来检索真实文档,前提是文档-文档相似性搜索可以产生更相关的匹配。论文
提示

查看我们的 RAG 从零开始的视频,了解一些不同的具体方法。

路由

其次,考虑 RAG 系统可用的数据源。您希望跨多个数据库或跨结构化和非结构化数据源进行查询。**使用 LLM 来审查输入并将其路由到适当的数据源是跨源查询的一种简单有效的方法。**

名称何时使用描述
逻辑路由当您可以提示 LLM 使用规则来决定将输入路由到哪里时。逻辑路由可以使用 LLM 推理查询并选择最合适的数据存储。
语义路由当语义相似性是确定将输入路由到何处的有效方法时。语义路由嵌入查询和通常一组提示。然后根据相似性选择合适的提示。
提示

查看我们关于 路由 的 RAG 从零开始的视频。

查询构建

第三,考虑您的任何数据源是否需要特定的查询格式。许多结构化数据库使用 SQL。向量存储通常具有用于将关键字过滤器应用于文档元数据的特定语法。**使用 LLM 将自然语言查询转换为查询语法是一种流行且强大的方法。** 特别是,文本到 SQL文本到 Cypher用于元数据过滤器的查询分析 分别是与结构化、图和向量数据库交互的有用方法。

名称何时使用描述
文本到 SQL如果用户提出的问题需要关系数据库中存储的信息,可以通过 SQL 访问。这使用 LLM 将用户输入转换为 SQL 查询。
文本到 Cypher如果用户提出的问题需要图数据库中存储的信息,可以通过 Cypher 访问。这使用 LLM 将用户输入转换为 Cypher 查询。
自我查询如果用户提出的问题可以通过基于元数据而不是文本相似性获取文档来更好地回答。这使用 LLM 将用户输入转换为两件事:(1) 用于语义查找的字符串,(2) 随附的元数据过滤器。这很有用,因为通常问题是关于文档的元数据(而不是内容本身)。
提示

查看我们的 博客文章概述 和 RAG 从零开始的关于 查询构建 的视频,即文本到 DSL 的过程,其中 DSL 是与给定数据库交互所需的特定领域语言。这将用户问题转换为结构化查询。

索引

第四,考虑文档索引的设计。一个简单而强大的想法是**将您用于检索的索引文档与传递给 LLM 用于生成的文档分离**。索引通常使用嵌入模型和向量存储,这些模型将文档中的语义信息压缩为固定大小的向量

许多 RAG 方法侧重于将文档拆分为块,并根据与输入问题的相似性检索一定数量的块供 LLM 使用。但是,块大小和块数量可能难以设置,如果它们没有为 LLM 提供回答问题的完整上下文,则会影响结果。此外,LLM 越来越能够处理数百万个标记。

两种方法可以解决这种矛盾:(1) 使用 LLM 将文档转换为任何适合索引的形式(例如,通常转换为摘要)的多向量检索器,但将完整文档返回给 LLM 用于生成。(2) ParentDocument检索器嵌入文档块,但也返回完整文档。其想法是兼得两者的优势:使用简洁的表示(摘要或块)进行检索,但使用完整文档进行答案生成。

名称索引类型使用 LLM何时使用描述
向量存储向量存储如果您刚开始使用并寻找快速简便的方法。这是最简单的方法,也是最容易上手的方法。它涉及为每个文本片段创建嵌入。
ParentDocument向量存储 + 文档存储如果您的页面包含许多最好单独索引但最好一起检索的较小的不同信息片段。这涉及为每个文档索引多个块。然后,您找到在嵌入空间中最相似的块,但您检索整个父文档并返回它(而不是单个块)。
多向量向量存储 + 文档存储有时在索引期间如果您能够从文档中提取您认为比文本本身更相关的索引信息。这涉及为每个文档创建多个向量。每个向量都可以通过多种方式创建 - 例如文本摘要和假设问题。
时间加权向量存储向量存储如果您有与文档关联的时间戳,并且您希望检索最新的文档。这基于语义相似性(如普通向量检索)和最近度(查看索引文档的时间戳)的组合来获取文档。
提示

第五,考虑改进相似性搜索本身质量的方法。嵌入模型将文本压缩为固定长度(向量)表示,这些表示捕获文档的语义内容。这种压缩对于搜索/检索很有用,但它给单个向量表示带来了沉重的负担,要求它捕获文档的语义细微差别/细节。在某些情况下,不相关或冗余的内容会稀释嵌入的语义效用。

ColBERT 是一种使用更高粒度嵌入来解决此问题的方法:(1) 为文档和查询中的每个标记生成上下文相关的嵌入,(2) 对每个查询标记和所有文档标记之间的相似度进行评分,(3) 取最大值,(4) 对所有查询标记执行此操作,以及 (5) 对所有查询标记取最大分数(步骤 3)的总和以获得查询-文档相似度分数;这种基于标记的评分可以产生强大的结果。

还有一些其他技巧可以提高检索的质量。嵌入擅长捕获语义信息,但可能难以处理基于关键字的查询。许多 向量存储 提供内置的 混合搜索 来结合关键字和语义相似性,从而结合两种方法的优势。此外,许多向量存储具有 最大边缘相关性,它试图使搜索结果多样化以避免返回相似和冗余的文档。

名称何时使用描述
ColBERT当需要更高粒度的嵌入时。ColBERT 使用文档和查询中每个标记的上下文相关的嵌入来获得粒度查询-文档相似度分数。论文
混合搜索当结合基于关键字和语义相似性时。混合搜索结合关键字和语义相似性,结合两种方法的优势。论文
最大边缘相关性 (MMR)当需要使搜索结果多样化时。MMR 试图使搜索结果多样化以避免返回相似和冗余的文档。
提示

查看我们关于 ColBERT 的 RAG 从零开始的视频。

后处理

第六,考虑过滤或排序检索到的文档的方法。如果您结合来自多个来源返回的文档,这非常有用,因为它可以降低不太相关的文档的排名和/或压缩相似的文档

名称索引类型使用 LLM何时使用描述
上下文压缩任何有时如果您发现检索到的文档包含过多的不相关信息,并且分散了 LLM 的注意力。这在另一个检索器之上添加了一个后处理步骤,并仅从检索到的文档中提取最相关的信息。这可以使用嵌入或 LLM 完成。
集成任何如果您有多种检索方法并希望尝试将它们组合起来。这从多个检索器获取文档,然后将它们组合起来。
重新排序任何如果您希望根据相关性对检索到的文档进行排序,尤其是在您希望组合来自多个检索方法的结果时。

给定一个查询和一个文档列表,重新排序会将文档从最语义相关到最不语义相关地重新排序。
提示

请参阅我们关于RAG-Fusion论文)的“从零开始的RAG”视频,了解跨多个查询进行后处理的方法:从多个角度重写用户问题,为每个重写的问题检索文档,并将多个搜索结果列表的排名组合起来,使用互反秩融合 (RRF)生成一个统一的排名。

生成

最后,考虑如何在您的 RAG 系统中构建自我校正功能。 RAG 系统可能会遇到低质量检索(例如,如果用户问题超出索引范围)和/或生成幻觉。一个简单的检索-生成管道无法检测或自我纠正这些错误。“流程工程”的概念已在“代码生成”的上下文中提出使用单元测试迭代构建代码问题的答案,以检查和自我纠正错误。一些工作已将此应用于RAG,例如 Self-RAG 和 Corrective-RAG。在这两种情况下,都会在 RAG 答案生成流程中检查文档相关性、幻觉和/或答案质量。

我们发现图是可靠地表达逻辑流程的好方法,并且已使用 LangGraph 实现了几篇论文中的想法如下面的图所示(红色 - 路由,蓝色 - 回退,绿色 - 自我校正)。

  • 路由:自适应 RAG(论文)。如上所述,将问题路由到不同的检索方法。
  • 回退:纠正 RAG(论文)。如果文档与查询不相关,则回退到网络搜索。
  • 自我校正:Self-RAG(论文)。修复存在幻觉或未解决问题的答案。

名称何时使用描述
Self-RAG当需要修复包含幻觉或不相关内容的答案时。Self-RAG 在 RAG 答案生成流程中执行文档相关性、幻觉和答案质量检查,迭代构建答案并自我纠正错误。
Corrective-RAG当需要为低相关性文档提供回退机制时。Corrective-RAG 包含一个回退机制(例如,到网络搜索),如果检索到的文档与查询不相关,则确保更高质量和更相关的检索。
提示

查看几个展示使用 LangGraph 的 RAG 的视频和菜谱。

查看我们与合作伙伴合作的 LangGraph RAG 食谱。

文本分割

LangChain 提供了许多不同类型的文本分割器。这些都位于langchain-text-splitters包中。

表格列

  • 名称:文本分割器的名称
  • :实现此文本分割器的类
  • 分割依据:此文本分割器如何分割文本
  • 添加元数据:此文本分割器是否添加有关每个片段来源的元数据。
  • 描述:分割器的描述,包括何时使用它的建议。
名称分割依据添加元数据描述
递归RecursiveCharacterTextSplitterRecursiveJsonSplitter用户定义字符列表递归分割文本。此分割试图将相关的文本片段保留在一起。这是推荐的文本分割起始方式
HTMLHTMLHeaderTextSplitterHTMLSectionSplitterHTML 特定字符基于 HTML 特定字符分割文本。值得注意的是,这会添加有关该片段来源的相关信息(基于 HTML)。
MarkdownMarkdownHeaderTextSplitter,Markdown 特定字符基于 Markdown 特定字符分割文本。值得注意的是,这会添加有关该片段来源的相关信息(基于 Markdown)。
代码多种语言代码(Python、JS)特定字符基于编码语言的特定字符分割文本。可以选择 15 种不同的语言。
令牌多种类令牌基于令牌分割文本。存在几种不同的令牌测量方法。
字符CharacterTextSplitter用户定义字符基于用户定义字符分割文本。一种更简单的方法。
语义分块器(实验性)SemanticChunker句子首先基于句子分割。然后,如果彼此相邻的句子在语义上足够相似,则将它们组合在一起。取自Greg Kamradt
集成:AI21 语义AI21SemanticTextSplitter识别构成连贯文本的不同的主题,并沿这些主题进行分割。

评估

评估是评估您基于 LLM 的应用程序的性能和有效性的过程。它涉及根据一组预定义的标准或基准测试模型的响应,以确保它满足所需的质量标准并实现预期目的。此过程对于构建可靠的应用程序至关重要。

LangSmith 通过多种方式帮助完成此过程。

  • 它通过其跟踪和注释功能简化了数据集的创建和管理。
  • 它提供了一个评估框架,可帮助您定义指标并针对数据集运行您的应用程序。
  • 它允许您跟踪结果随时间的变化,并自动按计划或作为 CI/代码的一部分运行您的评估器。

要了解更多信息,请查看此 LangSmith 指南

跟踪

跟踪本质上是一系列应用程序从输入到输出采取的步骤。跟踪包含称为运行的单个步骤。这些可以是模型、检索器、工具或子链的单个调用。跟踪使您能够观察链和代理内部,这对于诊断问题至关重要。

要深入了解,请查看此 LangSmith 概念指南


此页面是否有帮助?


您还可以留下详细的反馈 在 GitHub 上.