概念指南
本节包含对 LangChain 关键部分的介绍。
建筑
LangChain 作为一个框架,由许多包组成。
langchain-core
此包包含不同组件的基本抽象以及将它们组合在一起的方法。此处定义了 LLM、向量存储、检索器等核心组件的接口。此处未定义第三方集成。有意将依赖项保持为非常轻量级。
合作伙伴套餐
虽然集成的长尾部分在 中langchain-community
,但我们将流行的集成拆分到它们自己的包中(例如langchain-openai
、langchain-anthropic
等)。这样做是为了改进对这些重要集成的支持。
langchain
主langchain
包包含构成应用程序认知架构的链、代理和检索策略。这些不是第三方集成。此处的所有链、代理和检索策略都不是特定于任何一种集成的,而是所有集成的通用策略。
langchain-community
此包包含由 LangChain 社区维护的第三方集成。主要合作伙伴包已分离出来(见下文)。它包含各种组件(LLM、向量存储、检索器)的所有集成。此包中的所有依赖项都是可选的,以使包尽可能轻量。
langgraph
langgraph
是一种扩展,langchain
旨在通过将步骤建模为图中的边和节点,使用 LLM 构建健壮且有状态的多参与者应用程序。
LangGraph 公开了用于创建常见类型代理的高级接口,以及用于编写自定义流程的低级 API。
langserve
一个将 LangChain 链部署为 REST API 的软件包。可轻松启动和运行可用于生产的 API。
朗史密斯
一个开发人员平台,可让您调试、测试、评估和监控 LLM 应用程序。
LangChain 表达语言 (LCEL )
LangChain 表达式语言 (LCEL) 是一种声明式的链接 LangChain 组件的方法。LCEL 从第一天开始就被设计为支持将原型投入生产,无需更改代码,从最简单的“prompt + LLM”链到最复杂的链(我们已经看到人们在生产中成功运行了包含 100 多个步骤的 LCEL 链)。以下是您可能想要使用 LCEL 的几个原因:
一流的流式传输支持 当您使用 LCEL 构建链时,您将获得最佳的第一个令牌时间(直到第一个输出块出现为止所经过的时间)。对于某些链,这意味着例如我们将令牌直接从 LLM 流式传输到流式输出解析器,然后您会以与 LLM 提供程序输出原始令牌相同的速率获得解析后的增量输出块。
异步支持 使用 LCEL 构建的任何链都可以使用同步 API(例如在原型设计时在 Jupyter 笔记本中)以及异步 API(例如在LangServe服务器中)调用。这使得原型和生产中使用相同的代码成为可能,具有出色的性能,并且能够在同一服务器中处理许多并发请求。
优化并行执行 每当您的 LCEL 链具有可以并行执行的步骤时(例如,如果您从多个检索器获取文档),我们都会在同步和异步接口中自动执行此操作,以尽可能减少延迟。
重试和回退 为 LCEL 链的任何部分配置重试和回退。这是让您的链在规模上更可靠的好方法。我们目前正在努力添加对重试/回退的流式支持,这样您就可以获得额外的可靠性而无需任何延迟成本。
访问中间结果 对于更复杂的链,在产生最终输出之前访问中间步骤的结果通常非常有用。这可用于让最终用户知道正在发生的事情,甚至只是调试您的链。您可以流式传输中间结果,并且它在每个LangServe服务器上都可用。
输入和输出模式 输入和输出模式为每个 LCEL 链提供从链结构推断出的 Pydantic 和 JSONSchema 模式。这可用于验证输入和输出,是 LangServe 不可或缺的一部分。
无缝 LangSmith 跟踪 随着您的链条变得越来越复杂,了解每一步究竟发生了什么变得越来越重要。使用 LCEL,所有步骤都会自动记录到LangSmith,以实现最大的可观察性和可调试性。
LCEL 旨在为行为和定制提供一致性,以超越和等传统子类链LLMChain
。 ConversationalRetrievalChain
许多传统链隐藏了提示等重要细节,随着越来越多可行模型的出现,定制变得越来越重要。
如果您目前正在使用其中一条旧链,请参阅本指南以获取有关如何迁移的指导。
有关如何使用 LCEL 执行特定任务的指南,请查看相关的操作指南。
可运行接口
为了尽可能轻松地创建自定义链,我们实现了“Runnable”协议。许多 LangChain 组件都实现了该Runnable
协议,包括聊天模型、LLM、输出解析器、检索器、提示模板等。还有几个用于处理 Runnable 的有用原语,您可以在下面阅读。
这是一个标准接口,可以轻松定义自定义链并以标准方式调用它们。标准接口包括:
stream
:流回响应块invoke
:在输入上调用链batch
:在输入列表上调用链
它们还具有相应的异步方法,应与asyncio await
语法一起使用以实现并发:
astream
:异步流回响应块ainvoke
:在输入异步时调用链abatch
:异步调用输入列表上的链astream_log
:除了最终响应之外,还实时回传中间步骤astream_events
:链中发生的betalangchain-core
流事件(在0.1.14 中引入)
输入类型和输出类型因组件而异:
成分 | 输入类型 | 输出类型 |
---|---|---|
迅速的 | 字典 | 提示值 |
聊天模型 | 单个字符串、聊天消息列表或 PromptValue | 聊天消息 |
法学硕士 | 单个字符串、聊天消息列表或 PromptValue | 细绳 |
输出解析器 | LLM 或 ChatModel 的输出 | 取决于解析器 |
猎犬 | 单弦 | 文件清单 |
工具 | 单个字符串或字典,取决于工具 | 取决于工具 |
所有可运行对象都公开输入和输出模式以检查输入和输出:
input_schema
:从 Runnable 结构自动生成的输入 Pydantic 模型output_schema
:从 Runnable 结构自动生成的输出 Pydantic 模型
组件
LangChain 为各种组件提供标准、可扩展的接口和外部集成,这些组件可用于使用 LLM 进行构建。有些组件是 LangChain 实现的,有些组件我们依赖第三方集成,还有一些则是混合的。
聊天模型
语言模型使用一系列消息作为输入,并返回聊天消息作为输出(而不是使用纯文本)。这些传统上是较新的模型(较旧的模型通常是LLMs
,见下文)。聊天模型支持为对话消息分配不同的角色,帮助区分来自人工智能、用户和系统消息等指令的消息。
尽管底层模型是消息输入、消息输出,但 LangChain 包装器还允许这些模型将字符串作为输入。这意味着您可以轻松地使用聊天模型代替 LLM。
当一个字符串作为输入传入时,它会被转换为HumanMessage
,然后传递给底层模型。
LangChain 不托管任何聊天模型,而是依赖第三方集成。
在构建ChatModel时,我们有一些标准化的参数:
model
:模型的名称temperature
:采样温度timeout
:请求超时max_tokens
:生成的最大令牌数stop
:默认停止序列max_retries
:重试请求的最大次数api_key
:模型提供者的 API 密钥base_url
:发送请求的端点
需要注意的一些重要事项:
- 标准参数仅适用于公开具有预期功能的参数的模型提供程序。例如,某些提供程序不公开最大输出令牌的配置,因此这些提供程序不支持 max_tokens。
- 标准参数当前仅在具有自己的集成包(例如
langchain-openai
,langchain-anthropic
等)的集成中强制执行,而不会在中的模型中强制执行langchain-community
。
ChatModel 还接受特定于该集成的其他参数。要查找 ChatModel 支持的所有参数,请前往该模型的 API 参考。
部分聊天模型针对工具调用进行了微调,并提供了专用 API。一般而言,此类模型在工具调用方面的表现优于非微调模型,建议用于需要工具调用的用例。有关详细信息,请参阅工具调用部分。
有关如何使用聊天模型的详细信息,请参阅此处的相关操作指南。
多模态
一些聊天模型是多模式的,接受图像、音频甚至视频作为输入。这些仍然不太常见,这意味着模型提供商尚未就定义 API 的“最佳”方式进行标准化。多模式输出甚至更少见。因此,我们将多模式抽象保持在相当轻量级的水平,并计划随着该领域的成熟进一步巩固多模式 API 和交互模式。
在 LangChain 中,大多数支持多模式输入的聊天模型也接受 OpenAI 内容块格式的值。到目前为止,这仅限于图像输入。对于支持视频和其他字节输入的模型(如 Gemini),API 还支持本机、特定于模型的表示。
有关如何使用多模式模型的详细信息,请参阅此处的相关操作指南。
有关拥有多模式模型的 LangChain 模型提供商的完整列表,请查看此表格。
法学硕士
语言模型以字符串作为输入并返回字符串。这些传统上是较旧的模型(较新的模型通常是聊天模型,见上文)。
尽管底层模型是字符串输入、字符串输出,但 LangChain 包装器还允许这些模型将消息作为输入。这为它们提供了与聊天模型相同的接口。当消息作为输入传入时,它们将在后台被格式化为字符串,然后再传递给底层模型。
LangChain 不托管任何 LLM,而是依赖第三方集成。
有关如何使用 LLM 的具体信息,请参阅此处的相关操作指南。
消息
一些语言模型将消息列表作为输入并返回一条消息。有几种不同类型的消息。所有消息都具有role
、content
和response_metadata
属性。
描述role
谁在说这条消息。LangChain 针对不同的角色有不同的消息类别。
该content
属性描述消息的内容。这可以是以下几种不同的东西:
- 字符串(大多数模型处理这种类型的内容)
- 字典列表(用于多模式输入,其中字典包含有关该输入类型和输入位置的信息)
人性化信息
这代表来自用户的消息。
AI消息
这表示来自模型的消息。除了属性之外content
,这些消息还具有:
response_metadata
该response_metadata
属性包含有关响应的其他元数据。此处的数据通常特定于每个模型提供商。此处可能存储诸如日志问题和令牌使用情况之类的信息。
tool_calls
这些代表语言模型调用工具的决定。它们包含在输出中AIMessage
。可以通过属性从那里访问它们.tool_calls
。
此属性返回 s 的列表ToolCall
。AToolCall
是一个具有以下参数的字典:
name
:应调用的工具的名称。args
:该工具的参数。id
:该工具调用的id。
系统信息
这代表一条系统消息,它告诉模型如何表现。并非每个模型提供商都支持此功能。
工具消息
这表示工具调用的结果。除了 和 之外role
,content
此消息还具有:
tool_call_id
传达对产生此结果所调用工具的调用 ID 的字段。- 一个
artifact
字段,可用于传递工具执行的任意工件,这些工件对于跟踪有用,但不应发送给模型。
(旧版) FunctionMessage
这是一种遗留消息类型,对应于 OpenAI 的遗留函数调用 API。ToolMessage
应改用它以对应更新的工具调用 API。
这表示函数调用的结果。除了 和 之外role
,content
此消息还有一个name
参数,用于传达产生此结果所调用的函数的名称。
提示模板
提示模板有助于将用户输入和参数转换为语言模型的指令。这可用于指导模型的响应,帮助其理解上下文并生成相关且连贯的基于语言的输出。
提示模板以字典作为输入,其中每个键代表提示模板中要填写的变量。
Prompt Templates 输出 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"})
聊天提示模板
这些提示模板用于格式化消息列表。这些“模板”由模板本身的列表组成。例如,构建和使用 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"})
在上面的例子中,这个 ChatPromptTemplate 在调用时会构造两个消息。第一个是系统消息,没有要格式化的变量。第二个是 HumanMessage,将由topic
用户传入的变量格式化。
消息占位符
此提示模板负责在特定位置添加消息列表。在上面的 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 函数)需要带有特定 kwargs 的消息。
输出类型:解析器返回的对象的输出类型。
描述:我们对此输出解析器的评论以及何时使用它。
姓名 | 支持流媒体 | 有格式说明 | 要求 LLM | 输入类型 | 输出类型 | 描述 |
---|---|---|---|---|---|---|
JSON | ✅ | ✅ | str | Message |
JSON 对象 | 返回指定的 JSON 对象。您可以指定一个 Pydantic 模型,它将返回该模型的 JSON。可能是获取不使用函数调用的结构化数据的最可靠的输出解析器。 | |
XML | ✅ | ✅ | str | Message |
dict |
返回标签字典。需要 XML 输出时使用。与擅长编写 XML 的模型(如 Anthropic 的模型)一起使用。 | |
CSV | ✅ | ✅ | str | Message |
List[str] |
返回以逗号分隔的值的列表。 | |
输出修复 | ✅ | str | Message |
包装另一个输出解析器。如果该输出解析器出错,则这将把错误消息和错误输出传递给 LLM,并要求其修复输出。 | |||
重试错误 | ✅ | str | Message |
包装另一个输出解析器。如果该输出解析器出错,则这将把原始输入、错误输出和错误消息传递给 LLM 并要求其修复。与 OutputFixingParser 相比,这个还会发送原始指令。 | |||
派丹蒂克 | ✅ | str | Message |
pydantic.BaseModel |
采用用户定义的 Pydantic 模型并以该格式返回数据。 | ||
YAML | ✅ | str | Message |
pydantic.BaseModel |
采用用户定义的 Pydantic 模型并以该格式返回数据。使用 YAML 对其进行编码。 | ||
Pandas数据框架 | ✅ | str | Message |
dict |
对于使用 pandas DataFrames 进行操作很有用。 | ||
枚举 | ✅ | str | Message |
Enum |
将响应解析为提供的枚举值之一。 | ||
日期时间 | ✅ | str | Message |
datetime.datetime |
将响应解析为日期时间字符串。 | ||
结构化 | ✅ | str | Message |
Dict[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()
有关如何使用文档加载器的详细信息,请参阅此处的相关操作指南。
文本分割器
加载文档后,您经常需要对它们进行转换,以更好地适应您的应用程序。最简单的例子是,您可能希望将较长的文档拆分为较小的块,以便放入模型的上下文窗口。LangChain 有许多内置的文档转换器,可轻松拆分、合并、过滤和以其他方式操作文档。
当您想要处理长文本时,需要将文本拆分成块。虽然这听起来很简单,但这里却存在很多潜在的复杂性。理想情况下,您希望将语义相关的文本片段放在一起。“语义相关”的含义可能取决于文本的类型。本笔记本展示了几种实现此目的的方法。
从高层次来看,文本分割器的工作原理如下:
- 将文本分成小的、具有语义意义的块(通常是句子)。
- 开始将这些小块组合成更大的块,直到达到一定大小(由某些函数衡量)。
- 一旦达到该大小,就将该块作为其自己的文本,然后开始创建具有一些重叠的新文本块(以保留块之间的上下文)。
这意味着您可以沿着两个不同的轴自定义文本分割器:
- 文本如何分割
- 如何测量块大小
有关如何使用文本分割器的详细信息,请参阅此处的相关操作指南。
嵌入模型
嵌入模型会创建一段文本的向量表示。您可以将向量视为捕捉文本语义的数字数组。通过以这种方式表示文本,您可以执行数学运算,从而可以执行诸如搜索含义最相似的其他文本之类的操作。这些自然语言搜索功能支持多种类型的上下文检索,我们为 LLM 提供有效响应查询所需的相关数据。
该类Embeddings
是专为与文本嵌入模型交互而设计的类。有许多不同的嵌入模型提供商(OpenAI、Cohere、Hugging Face 等)和本地模型,该类旨在为所有这些模型提供标准接口。
LangChain 中的基础 Embeddings 类提供了两种方法:一种用于嵌入文档,一种用于嵌入查询。前者接受多个文本作为输入,而后者接受单个文本。将它们作为两个独立方法的原因是,某些嵌入提供程序对文档(要搜索的文档)和查询(搜索查询本身)有不同的嵌入方法。
有关如何使用嵌入模型的详细信息,请参阅此处的相关操作指南。
向量存储
存储和搜索非结构化数据的最常见方法之一是嵌入数据并存储生成的嵌入向量,然后在查询时嵌入非结构化查询并检索与嵌入查询“最相似”的嵌入向量。向量存储负责存储嵌入数据并为您执行向量搜索。
大多数向量存储还可以存储有关嵌入向量的元数据,并支持在相似性搜索之前对该元数据进行过滤,从而使您能够更好地控制返回的文档。
可以通过执行以下操作将向量存储转换为检索器接口:
vectorstore = MyVectorStore()
retriever = vectorstore.as_retriever()
有关如何使用向量存储的详细信息,请参阅此处的相关操作指南。
猎犬
检索器是一种接口,可根据非结构化查询返回文档。它比向量存储更通用。检索器不需要能够存储文档,只需返回(或检索)文档即可。检索器可以从向量存储中创建,但也足够广泛,包括Wikipedia 搜索和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]
:生成存储中的所有键,可选择按前缀进行过滤
对于键值存储实现,请参阅本节。
工具
工具是供模型调用的实用程序:它们的输入由模型生成,输出则传回给模型。每当你想要模型控制部分代码或调用外部 API 时,就需要工具。
工具包括:
- 工具的名称。
- 关于该工具功能的描述。
- 定义工具输入的 JSON 模式。
- 一个函数(以及可选的函数的异步变体)。
当工具绑定到模型时,名称、描述和 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 中有一个遗留的代理概念,我们正逐步弃用它:AgentExecutor
。AgentExecutor 本质上是代理的运行时。它是一个很好的起点,但是,当您开始拥有更多自定义代理时,它不够灵活。为了解决这个问题,我们构建了 LangGraph 作为这种灵活、高度可控的运行时。
如果您仍在使用 AgentExecutor,请不要担心:我们仍然有关于如何使用 AgentExecutor 的指南。但是,建议您开始过渡到 LangGraph。为了协助您,我们整理了一份关于如何过渡的指南。
ReAct代理
构建代理的一个流行架构是ReAct。ReAct 在一个迭代过程中结合了推理和行动 —— 事实上,“ReAct” 这个名字代表了“Reason” 和“Act”。
总体流程如下:
- 该模型将“思考”根据输入和任何先前的观察结果采取什么步骤。
- 然后,模型将从可用工具中选择一个动作(或选择响应用户)。
- 该模型将为该工具生成参数。
- 代理运行时(执行器)将解析所选工具并使用生成的参数调用它。
- 执行器将把工具调用的结果作为观察结果返回给模型。
- 该过程重复进行,直到代理选择响应。
有一般的基于提示的实现不需要任何特定于模型的功能,但最可靠的实现使用工具调用等功能来可靠地格式化输出并减少差异。
请参阅LangGraph 文档以获取更多信息,或参阅本操作指南以获取有关迁移到 LangGraph 的具体信息。
回调
LangChain 提供了一个回调系统,可让您连接到 LLM 应用程序的各个阶段。这对于日志记录、监控、流式传输和其他任务非常有用。
您可以使用callbacks
API 中提供的参数订阅这些事件。此参数是处理程序对象的列表,这些对象应实现下面更详细描述的一个或多个方法。
回调事件
事件 | 事件触发器 | 相关方法 |
---|---|---|
聊天模型开始 | 当聊天模型开始时 | on_chat_model_start |
法学硕士 (LLM) 开始 | 当法学硕士开始的时候 | on_llm_start |
LLM 新令牌 | 当 llm OR 聊天模型发出新令牌时 | on_llm_new_token |
法学硕士 (LLM) 结束 | 当 LLm OR 聊天模型结束时 | on_llm_end |
LLM 错误 | 当 LLm OR 聊天模型出现错误时 | 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 |
回调处理程序
回调处理程序可以是sync
或async
:
- 同步回调处理程序实现BaseCallbackHandler接口。
- 异步回调处理程序实现AsyncCallbackHandler接口。
在运行时,LangChain 配置适当的回调管理器(例如,CallbackManager或AsyncCallbackManager,它将负责在事件触发时调用每个“已注册”回调处理程序上的适当方法。
传递回调
该callbacks
属性在整个 API(模型、工具、代理等)的大多数对象上均可用,位于两个不同位置:
回调在整个 API(模型、工具、代理等)的大多数对象上可用,位于两个不同的地方:
- 请求时间回调:除了输入数据之外,在请求时传递。适用于所有标准
Runnable
对象。这些回调由定义它们的对象的所有子对象继承。例如chain.invoke({"number": 25}, {"callbacks": [handler]})
。 - 构造函数回调:
chain = TheNameOfSomeChain(callbacks=[handler])
。这些回调作为参数传递给对象的构造函数。回调的作用域仅限于它们定义的对象,不会被对象的任何子对象继承。
构造函数回调的作用域仅限于定义它们的对象。它们不会被对象的子对象继承。
如果您正在创建自定义链或可运行程序,则需要记住将请求时间回调传播到任何子对象。
任何RunnableLambda
调用其他可运行对象并在 python<=3.10 中异步运行的RunnableGenerator
、 或Tool
都必须手动将回调传播给子对象。这是因为在这种情况下 LangChain 无法自动将回调传播给子对象。
这是您无法看到从自定义可运行程序或工具发出的事件的常见原因。
有关如何使用回调的详细信息,请参阅此处的相关操作指南。
技术
流媒体
单个 LLM 调用通常比传统资源请求耗时更长。当您构建需要多个推理步骤的更复杂链或代理时,这种情况会更加严重。
幸运的是,LLM 会迭代生成输出,这意味着可以在最终响应准备就绪之前显示合理的中间结果。因此,在输出可用时立即使用输出已成为使用 LLM 构建应用程序的用户体验的重要组成部分,有助于缓解延迟问题,而 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)
对于本身不支持流式传输的模型(或其他组件),此迭代器只会产生单个块,但您仍然可以在调用它们时使用相同的通用模式。使用.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()
方法完成后运行回调。 - 提供商通常会让您传递一个额外的参数来流输出,而不是一次返回所有参数。
- 您通常会忽略实际模型调用的结果而倾向于回调结果。
代币
大多数模型提供商用来衡量输入和输出的单位是称为token 的单位。token 是语言模型在处理或生成文本时读取和生成的基本单位。token 的确切定义可能因模型训练的具体方式而异 - 例如,在英语中,token 可以是单个单词(如“apple”),也可以是单词的一部分(如“app”)。
当你向模型发送提示时,提示中的单词和字符会使用tokenizer编码为 token 。然后,模型会将生成的输出 token 流回,tokenizer 会将其解码为人类可读的文本。以下示例展示了 OpenAI 模型如何 tokenize LangChain is cool!
:
您可以看到它被分成 5 个不同的标记,并且标记之间的边界与单词边界并不完全相同。
语言模型之所以使用 token 而不是更直观的“字符”等词,是因为语言模型处理和理解文本的方式不同。从高层次上讲,语言模型会根据初始输入和之前的生成内容迭代地预测下一个生成的输出。使用 token 语言模型训练模型,以处理带有含义的语言单位(如单词或子单词),而不是单个字符,这使得模型更容易学习和理解语言结构,包括语法和上下文。此外,使用 token 还可以提高效率,因为与字符级处理相比,模型处理的文本单位更少。
函数/工具调用
我们将术语“工具调用”与“函数调用”互换使用。尽管函数调用有时是指单个函数的调用,但我们将所有模型视为可以在每条消息中返回多个工具或函数调用。
工具调用允许聊天模型通过生成与用户定义模式匹配的输出来响应给定的提示。
虽然名称暗示模型正在执行某些操作,但事实并非如此!模型仅生成工具的参数,而实际运行该工具(或不运行)取决于用户。一个常见的例子是,如果您想要从非结构化文本中提取与某些模式匹配的结构化输出,那么您不会想使用生成的参数调用函数 。您可以为模型提供一个“提取”工具,该工具采用与所需模式匹配的参数,然后将生成的输出视为最终结果。
工具调用不是通用的,但许多流行的 LLM 提供商都支持它,包括Anthropic、 Cohere、Google、 Mistral、OpenAI,甚至通过Ollama支持本地运行的模型。
LangChain提供了标准化的工具调用接口,该接口在不同的模型之间是一致的。
标准接口包括:
ChatModel.bind_tools()
:一种指定模型可以调用哪些工具的方法。此方法接受LangChain 工具以及Pydantic对象。AIMessage.tool_calls
:从模型返回的属性AIMessage
,用于访问模型请求的工具调用。
工具使用
模型调用工具后,您可以通过调用工具,然后将参数传回模型来使用该工具。LangChain 提供了抽象Tool
来帮助您处理此问题。
一般流程是这样的:
- 使用聊天模型生成工具调用来响应查询。
- 使用生成的工具调用作为参数来调用适当的工具。
- 将工具调用的结果格式化为
ToolMessages
。 - 将整个消息列表传回模型,以便它可以生成最终答案(或调用更多工具)。
这就是工具调用代理执行任务和回答查询的方式。
请参阅以下一些更重点的指南:
结构化输出
LLM 能够生成任意文本。这使模型能够对各种输入做出适当响应,但对于某些用例,将 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):
"""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模式
一些模型,例如Mistral、OpenAI、 Together AI和Ollama ,支持称为JSON 模式的功能,通常通过配置启用。
启用后,JSON 模式将限制模型的输出始终为某种有效的 JSON。它们通常需要一些自定义提示,但通常比完全原始提示负担要小得多,而且更接近于。"you must always return 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()
。然后,该模型将生成一个AIMessage
包含与所需形状匹配的tool_calls
字段的模型。args
您可以使用多种可接受的格式将工具绑定到 LangChain 中的模型。以下是一个例子:
from langchain_core.pydantic_v1 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"]
{'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()
模型支持时该方法使用的默认技术。
以下操作指南是使用函数/工具调用进行结构化输出的良好实用资源:
有关支持工具调用的模型提供商的完整列表,请参阅此表。
检索
LLM 是在大型但固定的数据集上进行训练的,这限制了它们对私人或近期信息进行推理的能力。使用特定事实对 LLM 进行微调是缓解这种情况的一种方法,但通常不适合事实回忆并且成本高昂。检索是向 LLM 提供相关信息以改进其对给定输入的响应的过程。检索增强生成 (RAG) 是使用检索到的信息为 LLM 生成(输出)打下基础的过程。
- 查看我们的 Scratch代码和视频系列中的 RAG 。
- 有关检索的高级指南,请参阅有关 RAG 的教程。
RAG 的好坏取决于检索到的文档的相关性和质量。幸运的是,可以采用一组新兴技术来设计和改进 RAG 系统。我们专注于对其中许多技术进行分类和总结(见下图),并将在以下部分分享一些高级战略指导。您可以而且应该尝试将不同的部分组合在一起。您可能还会发现此 LangSmith 指南对于展示如何评估应用的不同迭代很有用。
查询翻译
首先,考虑用户对 RAG 系统的输入。理想情况下,RAG 系统可以处理各种输入,从措辞不当的问题到复杂的多部分查询。 使用 LLM 来审查和选择性修改输入是查询翻译背后的核心思想。这充当一般缓冲区,为您的检索系统优化原始用户输入。例如,这可以像提取关键字一样简单,也可以像为复杂查询生成多个子问题一样复杂。
姓名 | 何时使用 | 描述 |
---|---|---|
多查询 | 当你需要涵盖一个问题的多个观点时。 | 从多个角度重写用户问题,检索每个重写问题的文档,返回所有查询的唯一文档。 |
分解 | 当一个问题可以分解成更小的子问题时。 | 将问题分解为一组子问题/问题,可以按顺序解决(使用第一个问题的答案+检索来回答第二个问题),也可以并行解决(将每个答案合并为最终答案)。 |
后撤步 | 当需要更高层次的概念理解时。 | 首先提示 LLM 提出一个关于高级概念或原则的通用后退问题,并检索有关它们的相关事实。使用此基础来帮助回答用户问题。 |
海德 | 如果您在使用原始用户输入检索相关文档时遇到困难。 | 使用 LLM 将问题转换为回答该问题的假设文档。使用嵌入的假设文档检索真实文档,前提是 doc-doc 相似性搜索可以产生更多相关匹配。 |
路由
其次,考虑您的 RAG 系统可用的数据源。您希望跨多个数据库或跨结构化和非结构化数据源进行查询。使用 LLM 审查输入并将其路由到适当的数据源是一种简单有效的跨源查询方法。
姓名 | 何时使用 | 描述 |
---|---|---|
逻辑路由 | 当您可以提示 LLM 使用规则来决定将输入路由到何处时。 | 逻辑路由可以使用 LLM 来推理查询并选择最合适的数据存储。 |
语义路由 | 当语义相似性是确定输入路由位置的有效方法时。 | 语义路由嵌入查询和一组提示。然后根据相似性选择适当的提示。 |
请参阅我们关于路由的Scratch 视频中的 RAG 。
查询构造
第三,考虑您的任何数据源是否需要特定的查询格式。许多结构化数据库都使用 SQL。向量存储通常具有将关键字过滤器应用于文档元数据的特定语法。使用 LLM 将自然语言查询转换为查询语法是一种流行且强大的方法。 特别是,文本到 SQL、文本到 Cypher和元数据过滤器的查询分析分别是与结构化、图形和向量数据库交互的有用方法。
姓名 | 何时使用 | 描述 |
---|---|---|
文本到 SQL | 如果用户提出的问题需要存储在关系数据库中的信息,则可以通过 SQL 访问。 | 这使用 LLM 将用户输入转换为 SQL 查询。 |
文本转密码 | 如果用户提出的问题需要存储在图形数据库中的信息,则可以通过 Cypher 访问。 | 这使用 LLM 将用户输入转换为 Cypher 查询。 |
自查 | 如果用户提出的问题可以通过获取基于元数据而不是与文本的相似性的文档来更好地回答。 | 它使用 LLM 将用户输入转换为两部分:(1) 一个用于语义查找的字符串,(2) 一个用于与之配合的元数据过滤器。这很有用,因为问题通常与文档的元数据有关(而不是内容本身)。 |
索引
第四,考虑文档索引的设计。一个简单而有效的想法是将您索引的文档与您传递给 LLM 进行生成的文档分离。索引经常使用带有向量存储的嵌入模型,将文档中的语义信息压缩为固定大小的向量。
许多 RAG 方法专注于将文档拆分成块,并根据与 LLM 输入问题的相似性检索一些数字。但是,如果块大小和块数不能为 LLM 提供回答问题的完整上下文,则它们可能难以设置并影响结果。此外,LLM 处理数百万个标记的能力越来越强。
两种方法可以解决这种矛盾:(1)多向量检索器使用 LLM 将文档转换为任何适合索引的形式(例如,通常是摘要),但将完整文档返回给 LLM 进行生成。(2)ParentDocument检索器嵌入文档块,但也返回完整文档。这个想法是两全其美:使用简洁的表示(摘要或块)进行检索,但使用完整文档进行答案生成。
姓名 | 索引类型 | 使用法学硕士学位 | 何时使用 | 描述 |
---|---|---|---|---|
向量存储 | 向量存储 | 不 | 如果您刚刚开始并正在寻找一些快速简便的方法。 | 这是最简单的方法,也是最容易上手的方法。它涉及为每段文本创建嵌入。 |
父母文件 | 向量存储+文档存储 | 不 | 如果您的网页包含许多较小的不同信息片段,最好单独编制索引,但最好一起检索。 | 这涉及为每个文档索引多个块。然后,您会找到嵌入空间中最相似的块,但您会检索整个父文档并返回该文档(而不是单个块)。 |
多向量 | 向量存储+文档存储 | 有时在索引期间 | 如果您能够从您认为比文本本身更与索引相关的文档中提取信息。 | 这涉及为每个文档创建多个向量。每个向量可以通过多种方式创建 - 例如文本摘要和假设问题。 |
时间加权向量存储 | 向量存储 | 不 | 如果你的文档有时间戳,并且想要检索最新的时间戳 | 这将根据语义相似性(如在正常向量检索中)和新近度(查看索引文档的时间戳)的组合来获取文档 |
- 观看我们关于索引基础知识的RAG from Scratch 视频
- 观看有关多向量检索器的 RAG from Scratch 视频
第五,考虑提高相似性搜索本身质量的方法。嵌入模型将文本压缩为固定长度(向量)表示,以捕获文档的语义内容。这种压缩对于搜索/检索很有用,但会给单个向量表示带来沉重的负担,以捕获文档的语义细微差别/细节。在某些情况下,不相关或冗余的内容会削弱嵌入的语义实用性。
ColBERT是一种有趣的方法,它使用更高粒度的嵌入来解决这个问题:(1)为文档和查询中的每个标记生成受上下文影响的嵌入,(2)对每个查询标记和所有文档标记之间的相似度进行评分,(3)取最大值,(4)对所有查询标记执行此操作,(5)对所有查询标记取最大分数之和(在步骤 3 中)以获得查询文档相似度分数;这种基于标记的评分可以产生很好的结果。
还有一些额外的技巧可以提高检索质量。嵌入擅长捕获语义信息,但可能难以处理基于关键字的查询。许多向量存储提供内置的混合搜索,以结合关键字和语义相似性,从而结合了两种方法的优点。此外,许多向量存储具有最大边际相关性,它试图使搜索结果多样化,以避免返回相似和冗余的文档。
姓名 | 何时使用 | 描述 |
---|---|---|
科尔伯特 | 当需要更高粒度的嵌入时。 | ColBERT 对文档和查询中的每个标记使用受上下文影响的嵌入来获得细粒度的查询-文档相似度分数。 |
混合搜索 | 当结合基于关键词和语义的相似度时。 | 混合搜索结合了关键字和语义相似性,融合了两种方法的优点。 |
最大边际相关性(MMR) | 当需要使搜索结果多样化时。 | MMR 尝试使搜索结果多样化,以避免返回相似和冗余的文档。 |
观看我们在ColBERT上的 Scratch 视频中的 RAG 。
后期处理
第六,考虑过滤或排序检索到的文档的方法。如果您要合并从多个来源返回的文档,这将非常有用,因为它可以降低不太相关的文档的排名和/或压缩类似的文档。
姓名 | 索引类型 | 使用法学硕士学位 | 何时使用 | 描述 |
---|---|---|---|---|
上下文压缩 | 任何 | 有时 | 如果您发现检索到的文档包含太多不相关的信息并且分散了 LLM 的注意力。 | 这将后处理步骤置于另一个检索器之上,并从检索到的文档中仅提取最相关的信息。这可以通过嵌入或 LLM 来完成。 |
合奏 | 任何 | 不 | 如果您有多种检索方法并想尝试将它们结合起来。 | 这将从多个检索器中获取文档,然后将它们合并。 |
重新排序 | 任何 | 是的 | 如果您想根据相关性对检索到的文档进行排序,尤其是当您想组合多种检索方法的结果时。 | 给定一个查询和一个文档列表,Rerank 会按与查询语义最相关到最不相关的顺序对文档进行索引。 |
请参阅我们关于RAG-Fusion的 RAG from Scratch 视频,了解跨多个查询的后处理方法:从多个角度重写用户问题,为每个重写的问题检索文档,并合并多个搜索结果列表的排名,以使用倒数排名融合 (RRF)产生单一、统一的排名。
世代
最后,考虑在 RAG 系统中构建自我纠正的方法。RAG系统可能会遭受低质量检索(例如,如果用户问题超出索引的范围)和/或生成中的幻觉。简单的检索-生成管道无法检测或自我纠正这些类型的错误。在代码生成的上下文中引入了“流程工程”的概念:使用单元测试迭代构建代码问题的答案以检查和自我纠正错误。有几项工作已经应用了这种 RAG,例如 Self-RAG 和 Corrective-RAG。在这两种情况下,都会在 RAG 答案生成流程中执行对文档相关性、幻觉和/或答案质量的检查。
我们发现图表是可靠地表达逻辑流程的好方法,并且已经使用 LangGraph实现了几篇论文中的想法,如下图所示(红色 - 路由,蓝色 - 回退,绿色 - 自我修正):
姓名 | 何时使用 | 描述 |
---|---|---|
自身RAG | 当需要用幻觉或不相关的内容来修正答案时。 | Self-RAG 在 RAG 答案生成流程中检查文档相关性、幻觉和答案质量,迭代构建答案并自我纠正错误。 |
矫正-RAG | 当需要针对低相关性文档的回退机制时。 | 如果检索到的文档与查询不相关,则 Corrective-RAG 会包含回退(例如,回到网络搜索),从而确保更高的质量和更相关的检索。 |
观看一些展示 RAG 与 LangGraph 的视频和手册:
查看我们与合作伙伴的 LangGraph RAG 食谱:
文本分割
LangChain 提供多种不同类型的text splitters
。这些都包含在langchain-text-splitters
包中。
表格列:
- 名称:文本分割器的名称
- 类:实现此文本分割器的类
- 拆分方式:此文本拆分器如何拆分文本
- 添加元数据:此文本分割器是否添加有关每个块来源的元数据。
- 描述:分离器的描述,包括何时使用它的建议。
姓名 | 课程 | 拆分 | 添加元数据 | 描述 |
---|---|---|---|---|
递归 | 递归字符文本分割器,递归Json分割器 | 用户定义字符列表 | 递归拆分文本。此拆分试图将相关的文本片段保持在一起。这是recommended way 开始拆分文本。 |
|
HTML | HTMLHeaderTextSplitter和HTMLSectionSplitter | HTML 特定字符 | ✅ | 根据 HTML 特定字符拆分文本。值得注意的是,这会添加有关该块来源的相关信息(基于 HTML) |
Markdown | MarkdownHeaderTextSplitter, | Markdown 特定字符 | ✅ | 根据 Markdown 特定字符分割文本。值得注意的是,这会添加有关该块来自何处的相关信息(基于 Markdown) |
代码 | 多种语言 | 编码(Python、JS)特定字符 | 根据编码语言特定的字符拆分文本。有 15 种不同的语言可供选择。 | |
代币 | 许多课程 | 代币 | 按标记拆分文本。存在几种不同的方法来测量标记。 | |
特点 | 字符文本分割器 | 用户定义的角色 | 根据用户定义的字符分割文本。这是较简单的方法之一。 | |
语义分块器(实验性) | 语义分块器 | 句子 | 首先对句子进行拆分。如果语义足够相似,则将相邻的句子合并。摘自Greg Kamradt | |
集成:AI21语义 | AI21语义文本分割器 | ✅ | 识别形成连贯文本的不同主题,并沿着这些主题进行拆分。 |
评估
评估是评估 LLM 驱动的应用程序的性能和有效性的过程。它涉及根据一组预定义的标准或基准测试模型的响应,以确保其符合所需的质量标准并实现预期目的。此过程对于构建可靠的应用程序至关重要。
LangSmith通过以下几种方式帮助完成此过程:
- 通过其跟踪和注释功能,可以更轻松地创建和管理数据集
- 它提供了一个评估框架,可帮助您定义指标并根据数据集运行应用程序
- 它允许您跟踪一段时间内的结果,并按计划或作为 CI/Code 的一部分自动运行评估器
要了解更多信息,请查看此 LangSmith 指南。
追踪
跟踪本质上是应用程序从输入到输出所采取的一系列步骤。跟踪包含称为的各个步骤runs
。这些步骤可以是来自模型、检索器、工具或子链的单独调用。跟踪可让您观察链和代理内部,这对于诊断问题至关重要。
如需深入了解,请查看此 LangSmith 概念指南。
版权声明:《 【LangChain0.2 】概念指南 》为漠北星火原创文章,转载请注明出处!
最后编辑:2024-8-20 15:08:00