Skip to main content

概念指南

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

架构

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

langchain-core

该包包含不同组件的基本抽象以及将它们组合在一起的方法。 核心组件的接口,如LLMs、向量存储、检索器等在此定义。 此处未定义第三方集成。 依赖项故意保持非常轻量。

合作伙伴包

虽然长尾集成在 langchain-community 中,但我们将流行的集成拆分到各自的包中(例如 langchain-openailangchain-anthropic 等)。这样做是为了提高对这些重要集成的支持。

langchain

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

langchain-community

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

langgraph

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

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

langserve

一个将 LangChain 链部署为 REST API 的包。使得快速搭建一个生产就绪的 API 变得简单。

LangSmith

一个开发平台,让您调试、测试、评估和监控LLM应用程序。

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

LangChain 表达语言 (LCEL)

LangChain 表达语言,或称 LCEL,是一种声明式的方法,用于链式组合 LangChain 组件。LCEL 从第一天起就被设计为支持在不更改代码的情况下将原型投入生产,从最简单的“提示 + LLM”链到最复杂的链(我们看到有人成功在生产环境中运行包含数百个步骤的 LCEL 链)。以下是您可能想要使用 LCEL 的几个原因:

一流的流式支持 使用 LCEL 构建链时,您将获得最佳的首次令牌时间(从发出第一个输出块开始到输出的时间)。对于某些链,这意味着例如我们直接从 LLM 流式传输令牌到流式输出解析器,您将以与 LLM 提供者输出原始令牌相同的速度返回解析后的增量输出块。

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

优化的并行执行 每当您的 LCEL 链有可以并行执行的步骤(例如,如果您从多个检索器获取文档),我们会在同步和异步接口中自动执行,以实现尽可能小的延迟。

重试和回退 为 LCEL 链的任何部分配置重试和回退。这是提高链在大规模下可靠性的好方法。我们目前正在努力为重试/回退添加流式支持,这样您就可以在不增加延迟成本的情况下获得额外的可靠性。

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

输入和输出架构 输入和输出架构为每个 LCEL 链提供从链的结构推断出的 Pydantic 和 JSONSchema 架构。这可以用于输入和输出的验证,并且是 LangServe 的一个重要组成部分。

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

LCEL 旨在提供比传统的子类链(如 LLMChainConversationalRetrievalChain)更一致的行为和定制。许多传统链隐藏了重要的细节,例如提示,随着更广泛的有效模型的出现,定制变得越来越重要。

如果您当前正在使用这些传统链之一,请参阅此指南以获取迁移指导

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

Runnable 接口

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

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

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

这些也有相应的异步方法,应该与 asyncioawait 语法一起使用以实现并发:

  • astream: 异步以流的方式返回响应的块
  • ainvoke: 异步在输入上调用链
  • abatch: 异步在输入列表上调用链
  • astream_log: 除了最终响应外,实时流回中间步骤
  • astream_events: beta 实时流回链中的事件(在 langchain-core 0.1.14 中引入)

输入类型输出类型 根据组件而异:

组件输入类型输出类型
提示字典PromptValue
ChatModel单个字符串、聊天消息列表或 PromptValueChatMessage
LLM单个字符串、聊天消息列表或 PromptValue字符串
OutputParserLLM 或 ChatModel 的输出取决于解析器
Retriever单个字符串文档列表
Tool单个字符串或字典,具体取决于工具取决于工具

所有 runnable 都暴露输入和输出 模式 以检查输入和输出:

  • input_schema: 从 Runnable 的结构自动生成的输入 Pydantic 模型
  • output_schema: 从 Runnable 的结构自动生成的输出 Pydantic 模型

组件

LangChain 提供了标准的、可扩展的接口和外部集成,适用于构建 LLM 的各种组件。 一些组件是 LangChain 实现的,一些组件依赖于第三方集成,其他则是两者的结合。

聊天模型

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

尽管底层模型是消息输入、消息输出,但 LangChain 的包装器也允许这些模型接受字符串作为输入。这意味着您可以轻松地用聊天模型替代 LLMs。

当字符串作为输入传入时,它会被转换为 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 参考。

info

一些聊天模型已针对 工具调用 进行了微调,并提供了专用 API。通常,这些模型在工具调用方面的表现优于未微调的模型,建议在需要工具调用的用例中使用。有关更多信息,请参见 工具调用部分

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

多模态

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

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

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

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

LLMs

caution

纯文本输入/输出的 LLMs 通常比较旧或级别较低。许多流行模型最好用作 聊天完成模型,即使在非聊天用例中也是如此。

您可能想查看 上面的部分

语言模型接受字符串作为输入并返回字符串。这些通常是较旧的模型(较新的模型一般是 聊天模型,见上文)。

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

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

有关如何使用 LLMs 的具体信息,请参见 相关的操作指南

消息

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

role 描述了谁在说这条消息。 LangChain 为不同角色提供了不同的消息类。

content 属性描述了消息的内容。 这可以是几种不同的东西:

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

HumanMessage

这表示来自用户的消息。

AIMessage

这表示来自模型的消息。除了 content 属性,这些消息还有:

response_metadata

response_metadata 属性包含有关响应的额外元数据。这里的数据通常是特定于每个模型提供者的。 这就是像 log-probs 和 token 使用情况等信息可能存储的地方。

tool_calls

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

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

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

SystemMessage

这表示系统消息,指示模型如何行为。并非每个模型提供者都支持此功能。

ToolMessage

这表示工具调用的结果。除了 rolecontent,此消息还有:

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

(Legacy) FunctionMessage

这是一种遗留消息类型,对应于 OpenAI 的遗留函数调用 API。应使用 ToolMessage 来对应更新的工具调用 API。

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

提示模板

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

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

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

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

字符串提示模板

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

from langchain_core.prompts import PromptTemplate

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

prompt_template.invoke({"topic": "cats"})

聊天提示模板

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

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"})

在上述示例中,当调用这个聊天提示模板时,将构造两条消息。第一条是系统消息,没有变量需要格式化。第二条是 HumanMessage,将由用户传入的 topic 变量格式化。

消息占位符

这个提示模板负责在特定位置添加消息列表。在上面的聊天提示模板中,我们看到如何格式化两条消息,每条都是字符串。但是如果我们希望用户传入一系列消息,并将其插入特定位置呢?这就是使用 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}") # <-- 这是更改的部分
])

有关如何使用提示模板的具体信息,请参见 相关的操作指南

示例选择器

一种常见的提示技巧是将示例作为提示的一部分,以实现更好的性能。这为语言模型提供了具体的示例,说明它应该如何表现。有时,这些示例是硬编码到提示中的,但在更高级的情况下,动态选择它们可能更为理想。示例选择器是负责选择并将示例格式化为提示的类。

有关如何使用示例选择器的具体信息,请参见相关的操作指南

输出解析器

note

此信息指的是从模型获取文本输出并尝试将其解析为更结构化表示的解析器。 越来越多的模型支持函数(或工具)调用,这会自动处理此过程。 建议使用函数/工具调用而不是输出解析。 有关详细信息,请参见这里

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

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

名称:输出解析器的名称

支持流式传输:输出解析器是否支持流式传输。

有格式说明:输出解析器是否有格式说明。通常情况下这是可用的,除非(a)所需的模式未在提示中指定,而是在其他参数中指定(如 OpenAI 函数调用),或(b)当 OutputParser 包装另一个 OutputParser 时。

调用 LLM:此输出解析器是否自己调用 LLM。通常只有尝试修正格式错误输出的输出解析器会这样做。

输入类型:预期的输入类型。大多数输出解析器在字符串和消息上都能工作,但某些解析器(如 OpenAI Functions)需要包含特定 kwargs 的消息。

输出类型:解析器返回的对象的输出类型。

描述:我们对该输出解析器的评论以及何时使用它。

名称支持流式传输有格式说明调用 LLM输入类型输出类型描述
JSONstr | MessageJSON 对象返回指定的 JSON 对象。您可以指定一个 Pydantic 模型,它将返回该模型的 JSON。可能是获取结构化数据的最可靠输出解析器,且不使用函数调用。
XMLstr | Messagedict返回标签的字典。当需要 XML 输出时使用。与擅长编写 XML 的模型(如 Anthropic 的模型)一起使用。
CSVstr | MessageList[str]返回一个以逗号分隔的值的列表。
OutputFixingstr | Message包装另一个输出解析器。如果该输出解析器出错,则此解析器将把错误消息和错误输出传递给 LLM,并请求其修复输出。
RetryWithErrorstr | Message包装另一个输出解析器。如果该输出解析器出错,则此解析器将把原始输入、错误输出和错误消息传递给 LLM,并请求其修复。与 OutputFixingParser 相比,此解析器还会发送原始指令。
Pydanticstr | Messagepydantic.BaseModel接受用户定义的 Pydantic 模型并以该格式返回数据。
YAMLstr | Messagepydantic.BaseModel接受用户定义的 Pydantic 模型并以该格式返回数据。使用 YAML 编码。
PandasDataFramestr | Messagedict适用于进行 pandas DataFrame 操作。
Enumstr | MessageEnum将响应解析为提供的枚举值之一。
Datetimestr | Messagedatetime.datetime将响应解析为日期时间字符串。
Structuredstr | MessageDict[str, str]一个返回结构化信息的输出解析器。由于它仅允许字段为字符串,因此其功能不如其他输出解析器强大。当您使用较小的 LLM 时,这可能会很有用。

有关如何使用输出解析器的具体信息,请参见相关的操作指南这里

聊天记录

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

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

文档

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

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

文档加载器

这些类加载文档对象。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()

有关如何使用文档加载器的具体信息,请参见相关操作指南

文本拆分器

一旦加载了文档,您通常会想要对它们进行转换,以更好地适应您的应用程序。最简单的例子是,您可能想将一篇长文档拆分成适合您模型上下文窗口的小块。LangChain 提供了多种内置文档转换器,使得拆分、组合、过滤和其他操作文档变得简单。

当您想处理长文本时,有必要将该文本拆分成小块。尽管这听起来很简单,但这里有很多潜在的复杂性。理想情况下,您希望将语义相关的文本片段放在一起。“语义相关”意味着什么可能取决于文本的类型。本笔记本展示了几种实现方法。

从高层次来看,文本拆分器的工作原理如下:

  1. 将文本拆分成小的、语义上有意义的块(通常是句子)。
  2. 开始将这些小块组合成一个较大的块,直到达到某个大小(通过某种函数进行测量)。
  3. 一旦达到该大小,将该块作为独立的文本,然后开始创建一个新的文本块,带有一些重叠(以保持块之间的上下文)。

这意味着您可以在两个不同的轴上自定义文本拆分器:

  1. 文本的拆分方式
  2. 块大小的测量方式

有关如何使用文本拆分器的具体信息,请参见 相关使用指南

嵌入模型

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

Embeddings 类是一个用于与文本嵌入模型接口的类。市面上有许多不同的嵌入模型提供者(如 OpenAI、Cohere、Hugging Face 等)和本地模型,而这个类旨在为它们提供一个标准接口。

LangChain 中的基础嵌入类提供了两个方法:一个用于嵌入文档,一个用于嵌入查询。前者接受多个文本作为输入,而后者接受单个文本。将这两个方法分开的原因是某些嵌入提供者对文档(待搜索的内容)和查询(搜索查询本身)有不同的嵌入方法。

有关如何使用嵌入模型的具体信息,请参见相关的操作指南

向量存储

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

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

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

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

有关如何使用向量存储的具体信息,请参阅相关的操作指南

检索器

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

检索器接受一个字符串查询作为输入,并返回一个文档列表作为输出。

有关如何使用检索器的具体信息,请参见 相关的操作指南

键值存储

对于某些技术,例如 使用多个向量进行索引和检索缓存嵌入,拥有一种键值(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]: 生成存储中的所有键, optionally 通过前缀过滤

有关键值存储实现,请参见 本节

工具

工具是设计用于被模型调用的实用程序:它们的输入旨在由模型生成,输出则设计为返回给模型。 当您希望模型控制代码的某些部分或调用外部 API 时,需要使用工具。

工具由以下部分组成:

  1. 工具的名称。
  2. 工具的功能描述。
  3. 定义工具输入的 JSON 架构。
  4. 一个函数(可选地,还有该函数的异步变体)。

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

tools = [...] # 定义工具列表
llm_with_tools = llm.bind_tools(tools)
ai_msg = llm_with_tools.invoke("do xyz...")
# -> AIMessage(tool_calls=[ToolCall(...), ...], ...)

从模型返回的 AIMessage 可能与 tool_calls 相关联。 请阅读 此指南 以获取有关响应类型的更多信息。

一旦选择的工具被调用,结果可以返回给模型,以便它完成正在执行的任务。 通常有两种不同的方法来调用工具并返回响应:

仅使用参数调用

当您仅使用参数调用工具时,您将返回原始工具输出(通常是字符串)。 这通常看起来像:

# 您需要事先检查 LLM 是否返回了工具调用
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 这里)。

... # 与上面的代码相同
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 包含一个 artifact,您需要让工具返回两个东西。 有关 定义返回工件的工具的更多信息,请阅读这里

最佳实践

在设计供模型使用的工具时,重要的是要记住:

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

相关

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

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

工具包

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

所有工具包都暴露一个 get_tools 方法,该方法返回工具列表。 因此,您可以这样做:

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

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

代理

单靠语言模型无法采取行动 - 它们只是输出文本。
LangChain 的一个重要用例是创建 代理
代理是利用 LLM 作为推理引擎来确定采取哪些行动以及这些行动的输入应该是什么的系统。
这些行动的结果可以反馈给代理,代理将判断是否需要更多的行动,或者是否可以结束。

LangGraph 是 LangChain 的一个扩展,专门旨在创建高度可控和可定制的代理。
请查看该文档以获取代理概念的更深入概述。

在 LangChain 中有一个遗留的代理概念,我们正逐步弃用:AgentExecutor
AgentExecutor 本质上是代理的运行时。
这是一个很好的入门地方,然而,随着你开始拥有更多定制化的代理,它并不够灵活。
为了应对这一问题,我们构建了 LangGraph,使其成为灵活且高度可控的运行时。

如果你仍在使用 AgentExecutor,请不要担心:我们仍然有关于 如何使用 AgentExecutor 的指南。
然而,建议你开始过渡到 LangGraph。
为此,我们整理了一份 过渡指南 来帮助你完成这一过程。

ReAct 代理

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

一般流程如下:

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

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

有关更多信息,请参阅 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 中大多数对象(模型、工具、代理等)中有两个不同的位置:

回调在 API 中大多数对象(模型、工具、代理等)中有两个不同的位置:

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

构造函数回调仅作用于定义它们的对象。它们 被该对象的子对象继承。

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

Python<=3.10 中的异步

任何调用其他可运行对象的 RunnableLambdaRunnableGeneratorTool,如果在 python<=3.10 中以异步方式运行,则必须手动将回调传播到子对象。这是因为在这种情况下,LangChain 无法自动将回调传播到子对象。

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

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

技术

流式传输

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

幸运的是,LLM 生成输出是迭代进行的,这意味着在最终响应准备好之前,可以显示合理的中间结果。因此,尽快消费可用的输出已经成为使用 LLM 构建应用程序时用户体验的重要部分,以帮助缓解延迟问题,而 LangChain 旨在提供一流的流式传输支持。

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

.stream().astream()

LangChain 中的大多数模块都包括 .stream() 方法(以及适用于 async 环境的等效 .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)

对于不原生支持流式传输的模型(或其他组件),此迭代器将仅返回一个块,但您仍然可以在调用它们时使用相同的一般模式。使用 .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 个不同的标记,并且标记之间的边界与单词边界并不完全相同。

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

函数/工具调用

info

我们将工具调用与函数调用交替使用。尽管函数调用有时指的是对单个函数的调用,但我们将所有模型视为可以在每条消息中返回多个工具或函数调用。

工具调用允许聊天模型通过生成符合用户定义的架构的输出,来响应给定的提示。

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

聊天模型的工具调用示意图

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

LangChain 提供了一个标准化的工具调用接口,适用于不同模型的一致性。

标准接口包括:

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

工具使用

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

一般流程如下:

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

完整工具调用流程示意图

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

请查看以下一些更为专注的指南:

结构化输出

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

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

.with_structured_output()

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

以下是一个示例:

from typing import Optional

from langchain_core.pydantic_v1 import BaseModel, Field


class Joke(BaseModel):
"""给用户讲的笑话。"""

setup: str = Field(description="笑话的开头")
punchline: str = Field(description="笑话的结尾")
rating: Optional[int] = Field(description="笑话的幽默程度,从1到10")

structured_llm = llm.with_structured_output(Joke)

structured_llm.invoke("给我讲一个关于猫的笑话")
Joke(setup='为什么猫坐在电脑上?', punchline='为了监视鼠标!', 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"键和"followup_question"键的JSON对象。'
"{question}"
)

chain = prompt | model | SimpleJsonOutputParser()

chain.invoke({ "question": "细胞的动力源是什么?" })
{'answer': '细胞的动力源是线粒体。它负责通过细胞呼吸产生ATP形式的能量。',
'followup_question': '您想了解更多关于线粒体如何产生能量吗?'}

有关支持JSON模式的模型提供者的完整列表,请参见此表格

工具调用

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

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

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

from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_openai import ChatOpenAI

class ResponseFormatter(BaseModel):
"""始终使用此工具来构建您对用户的响应。"""

answer: str = Field(description="用户问题的答案")
followup_question: str = Field(description="用户可能会问的后续问题")

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

model_with_tools = model.bind_tools([ResponseFormatter])

ai_msg = model_with_tools.invoke("细胞的动力源是什么?")

ai_msg.tool_calls[0]["args"]
{'answer': "细胞的动力源是线粒体。它产生大部分细胞的三磷酸腺苷(ATP),ATP作为化学能的来源。",
'followup_question': '线粒体如何产生ATP?'}

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

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

有关支持工具调用的模型提供者的完整列表,请查看此表格

检索

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

tip

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

查询翻译

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

名称使用时机描述
多查询当您需要涵盖问题的多个视角时。从多个视角重写用户问题,为每个重写的问题检索文档,返回所有查询的唯一文档。
分解当一个问题可以分解为更小的子问题时。将一个问题分解为一组子问题/问题,可以顺序解决(使用第一个答案 + 检索来回答第二个)或并行解决(将每个答案整合为最终答案)。
回退当需要更高层次的概念理解时。首先提示 LLM 提出关于更高层次概念或原则的一般性回退问题,并检索相关事实。使用这种基础来帮助回答用户问题。
HyDE如果您在使用原始用户输入检索相关文档时遇到挑战。使用 LLM 将问题转换为假设文档,以回答该问题。使用嵌入的假设文档来检索真实文档,前提是文档间相似性搜索可以产生更相关的匹配。
tip

请查看我们的 RAG from Scratch 视频,了解几种不同的具体方法:

路由

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

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

请查看我们的 RAG from Scratch 视频,了解 路由

查询构建

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

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

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

索引

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

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

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

名称索引类型使用 LLM使用时机描述
向量存储向量存储如果您刚刚开始并寻找简单快捷的解决方案。这是最简单的方法,也是最容易入门的方法。它涉及为每个文本片段创建嵌入。
ParentDocument向量存储 + 文档存储如果您的页面包含大量较小的独立信息片段,最好单独索引,但最好一起检索。这涉及为每个文档索引多个片段。然后您找到在嵌入空间中最相似的片段,但您检索整个父文档并返回该文档(而不是单个片段)。
Multi Vector向量存储 + 文档存储有时在索引期间如果您能够从文档中提取您认为比文本本身更相关的信息进行索引。这涉及为每个文档创建多个向量。每个向量可以通过多种方式创建——例如包括文本摘要和假设问题。
Time-Weighted Vector store向量存储如果您有与文档相关的时间戳,并且您想检索最新的文档这基于语义相似性(如正常的向量检索)和最近性(查看索引文档的时间戳)组合来获取文档。
tip

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

ColBERT 是一种有趣的方法,通过更高的粒度嵌入来解决此问题:(1)为文档和查询中的每个标记生成上下文影响的嵌入,(2)对每个查询标记与所有文档标记之间的相似性进行评分,(3)取最大值,(4)对所有查询标记执行此操作,以及(5)对所有查询标记的最大分数(在第 3 步中)求和以获得查询-文档相似性分数;这种逐标记评分可以产生良好的结果。

还有一些额外的技巧可以提高检索质量。嵌入在捕获语义信息方面表现出色,但可能在基于关键字的查询中遇到困难。许多 向量存储 提供内置的 混合搜索,以结合关键字和语义相似性,融合两种方法的优点。此外,许多向量存储具有 最大边际相关性,试图使搜索结果多样化,以避免返回相似和冗余的文档。

名称使用时机描述
ColBERT当需要更高粒度的嵌入时。ColBERT 使用上下文影响的嵌入为文档和查询中的每个标记获取细粒度的查询-文档相似性分数。
混合搜索当结合基于关键字和语义相似性时。混合搜索结合了关键字和语义相似性,融合了两种方法的优点。
最大边际相关性 (MMR)当需要多样化搜索结果时。MMR 尝试使搜索结果多样化,以避免返回相似和冗余的文档。
tip

请观看我们的 RAG from Scratch 视频,了解 ColBERT

后处理

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

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

请观看我们的 RAG from Scratch 视频,了解 RAG-Fusion,关于跨多个查询的后处理方法:从多个角度重写用户问题,为每个重写的问题检索文档,并结合多个搜索结果列表的排名以生成单个统一排名,使用 互惠排名融合 (RRF)

生成

最后,考虑在您的 RAG 系统中构建自我纠正的方法。 RAG 系统可能会遭受低质量检索(例如,如果用户问题超出了索引的领域)和/或生成中的幻觉。一个简单的检索-生成管道无法检测或自我纠正这些类型的错误。“流工程”("flow engineering")的概念已在 代码生成 的背景下引入:迭代地构建代码问题的答案,并通过单元测试检查和自我纠正错误。一些工作应用了这种 RAG,例如 Self-RAG 和 Corrective-RAG。在这两种情况下,都会在 RAG 答案生成流程中检查文档的相关性、幻觉和/或答案质量。

我们发现图形是一种可靠表达逻辑流程的好方法,并已从几篇论文中实现了这些想法 使用 LangGraph,如下图所示(红色 - 路由,蓝色 - 后备,绿色 - 自我纠正):

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

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

查看几个展示 RAG 与 LangGraph 的视频和食谱:

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

文本拆分

LangChain 提供了多种不同类型的 text splitters。 这些都位于 langchain-text-splitters 包中。

表格列:

  • 名称: 文本拆分器的名称
  • : 实现此文本拆分器的类
  • 拆分依据: 此文本拆分器如何拆分文本
  • 添加元数据: 此文本拆分器是否添加关于每个块来源的元数据。
  • 描述: 拆分器的描述,包括何时使用的建议。
名称拆分依据添加元数据描述
RecursiveRecursiveCharacterTextSplitter, RecursiveJsonSplitter用户定义的字符列表递归地拆分文本。此拆分试图将相关的文本片段放在一起。这是开始拆分文本的 推荐方式
HTMLHTMLHeaderTextSplitter, HTMLSectionSplitterHTML 特定字符基于 HTML 特定字符拆分文本。特别是,这会添加关于该块来源的相关信息(基于 HTML)。
MarkdownMarkdownHeaderTextSplitter,Markdown 特定字符基于 Markdown 特定字符拆分文本。特别是,这会添加关于该块来源的相关信息(基于 Markdown)。
Codemany languages代码(Python, JS)特定字符基于编程语言特定字符拆分文本。可选择 15 种不同的语言。
Tokenmany classes令牌基于令牌拆分文本。存在几种不同的方式来测量令牌。
CharacterCharacterTextSplitter用户定义的字符基于用户定义的字符拆分文本。这是更简单的方法之一。
Semantic Chunker (Experimental)SemanticChunker句子首先基于句子进行拆分。然后,如果它们在语义上足够相似,则将相邻的句子合并。取自 Greg Kamradt
Integration: AI21 SemanticAI21SemanticTextSplitter识别形成连贯文本的不同主题,并沿着这些主题进行拆分。

评估

评估是评估您的 LLM 驱动应用程序的性能和有效性的过程。它涉及将模型的响应与一组预定义的标准或基准进行测试,以确保其符合所需的质量标准并实现预期目的。这个过程对于构建可靠的应用程序至关重要。

LangSmith 在这个过程中提供了几种帮助:

  • 通过其追踪和注释功能,使创建和策划数据集变得更容易
  • 提供一个评估框架,帮助您定义指标并在数据集上运行您的应用程序
  • 允许您随时间跟踪结果,并自动按计划或作为 CI/Code 的一部分运行评估器

要了解更多信息,请查看 这份 LangSmith 指南.

跟踪

跟踪本质上是一系列步骤,描述了您的应用程序如何从输入到输出的过程。 跟踪包含称为 runs 的单个步骤。这些可以是来自模型、检索器、工具或子链的单独调用。 跟踪为您提供了对链和代理内部的可观察性,并在诊断问题时至关重要。

要深入了解,请查看 这篇 LangSmith 概念指南.


此页面是否有帮助?


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