LangChain概念指南(3)
对话历史 (Chat history)
大多数大型语言模型(LLM)应用程序都具有对话界面。对话的一个基本组成部分是能够引用对话中先前引入的信息。至少,一个对话系统应该能够直接访问过去消息的某个窗口。
"对话历史"(ChatHistory)的概念指的是 LangChain 中的一个类,可以用来包装任意链。这个 ChatHistory 会跟踪底层链的输入和输出,并将它们作为消息添加到消息数据库中。未来的交互将加载这些消息,并将它们作为输入的一部分传递给链。
文档 (Documents)
LangChain 中的文档对象包含了一些数据的信息。它有两个属性:
page_content: str - 该文档的内容。目前只是一个字符串。
metadata: dict - 与该文档相关联的任意元数据。可以跟踪文档 ID、文件名等。
文档加载器 (Document loaders)
这些类加载文档对象。LangChain 与各种数据源有数百种集成,可以从中加载数据:Slack、Notion、Google Drive 等。
每个 DocumentLoader 都有自己的特定参数,但它们都可以使用 .load 方法以相同的方式调用。以下是一个示例用例:
from langchain_community.document_loaders.csv_loader import CSVLoader loader = CSVLoader( ... # <-- 在这里指定集成特定的参数 ) data = loader.load()
有关如何使用文档加载器的详细信息,请参见相关指南。
文本分割器 (Text splitters)
一旦您加载了文档,您通常希望将它们转换以更好地适应您的应用程序。一个简单的例子是,您可能希望将长文档分割成可以适应您模型上下文窗口的较小块。LangChain 有许多内置的文档转换器,可以轻松地分割、合并、过滤和以其他方式操纵文档。
当您想要处理长文本时,有必要将文本分割成块。尽管这听起来很简单,但实际上这里有很多潜在的复杂性。理想情况下,您希望将语义相关的文本片段保持在一起。"语义相关"的含义可能取决于文本的类型。这个笔记本展示了几种实现方法。
在高层次上,文本分割器的工作原理如下:
将文本分割成小的、语义上有意义的块(通常是句子)。
开始将这些小块组合成一个较大的块,直到达到某个大小(由某个函数测量)。
一旦达到该大小,使该块成为自己的文本块,然后开始创建一个新的文本块,有一些重叠(以保持块之间的上下文)。
这意味着您可以根据两个不同的轴自定义您的文本分割器:
文本如何分割
如何测量块大小
有关如何使用文本分割器的详细信息,请参见此处的相关指南。
嵌入模型 (Embedding models)
嵌入模型创建了一段文本的向量表示。您可以将向量视为捕捉文本语义含义的数字数组。通过这种方式表示文本,您可以执行数学运算,例如搜索其他在语义上最相似的文本片段。这些自然语言搜索功能是许多类型的上下文检索的基础,我们为 LLM 提供了有效响应查询所需的相关数据。
嵌入模型的 "Embeddings" 类是为与文本嵌入模型交互而设计的类。有许多不同的嵌入模型提供商(OpenAI、Cohere、Hugging Face 等)和本地模型,这个类被设计为为所有模型提供标准接口。
LangChain 中的基本 Embeddings 类提供了两种方法:一种用于嵌入文档,一种用于嵌入查询。前者接受多个文本作为输入,后者接受单个文本。之所以将它们作为两种不同的方法,是因为一些嵌入提供商对文档(要搜索的)和查询(搜索查询本身)有不同的嵌入方法。
有关如何使用嵌入模型的详细信息,请参见相关指南。
向量存储 (Vector stores)
存储和搜索非结构化数据最常见的方法之一是将其嵌入并存储生成的嵌入向量,然后在查询时嵌入非结构化查询并检索与嵌入查询“最相似”的嵌入向量。向量存储负责为您存储嵌入数据和执行向量搜索。
大多数向量存储还可以存储有关嵌入向量的元数据,并在相似性搜索之前支持基于该元数据的过滤,允许您更多地控制返回的文档。
向量存储可以通过以下方式转换为检索器接口:
vectorstore = MyVectorStore() retriever = vectorstore.as_retriever()
有关如何使用向量存储的详细信息,请参见相关指南。
检索器 (Retrievers)
检索器是一个接口,它根据非结构化查询返回文档。它比向量存储更通用。检索器不需要能够存储文档,只需要返回(或检索)它们。检索器可以从向量存储中创建,但也足够广泛,包括 Wikipedia 搜索和 Amazon Kendra。
检索器接受一个字符串查询作为输入,并返回一个文档列表作为输出。
有关如何使用检索器的详细信息,请参见此处的相关指南。
工具 (Tools)
工 具是代理、链或聊天模型/LLM 可以用来与世界交互的接口。
一个工具由以下组件组成:
工具的名称
工具功能描述
工具输入的 JSON 模式
要调用的函数
是否应将工具的结果直接返回给用户(仅适用于代理)
名称、描述和 JSON 模式作为上下文提供给 LLM,允许 LLM 适当地决定如何使用该工具。
鉴于可用工具列表和提示,LLM 可以请求使用适当的参数调用一个或多个工具。
通常,在为聊天模型或 LLM 设计工具时,需要记住以下几点:
针对工具调用进行微调的聊天模型将比未微调的模型更擅长工具调用。
未微调的模型可能根本不能使用工具,特别是如果工具复杂或需要多次工具调用。
如果工具具有精心选择的名称、描述和 JSON 模式,模型将表现更好。
通常,更简单的工具对模型来说比更复杂的工具更容易使用。
有关如何使用工具的详细信息,请参见相关指南。
要使用现有的预构建工具,请查看此处的预构建工具列表。
工具包 (Toolkits)
工具包是为特定任务设计的工具集合。它们具有方便的加载方法。
所有工具包都公开了一个 get_tools 方法,该方法返回工具列表。因此,您可以这样做:
# 初始化一个工具包 toolkit = ExampleToolkit(...) # 获取工具列表 tools = toolkit.get_tools()
代理 (Agents)
本身,语言模型不能采取行动 - 它们只输出文本。LangChain 的一个重要用例是创建代理。代理是使用 LLM 作为推理引擎来确定要采取哪些行动以及这些行动的输入应该是什么的系统。然后,这些行动的结果可以反馈到代理中,代理决定是否需要更多行动,或者是否可以完成。
LangGraph 是 LangChain 的扩展,专门用于创建高度可控和可定制的代理。请查看该文档以获得更深入的代理概念概述。
在 LangChain 中有一个我们正逐渐弃用的遗留代理概念:AgentExecutor。AgentExecutor 本质上是代理的运行时。这是一个开始的好地方,但是随着您开始拥有更多定制化的代理,它不够灵活。为了解决这个问题,我们构建了 LangGraph 作为一个灵活、高度可控的运行时。
如果您仍在使用 AgentExecutor,请不要担心:我们仍然有如何使用 AgentExecutor 的指南。然而,建议您开始过渡到 LangGraph。为了协助这一过程,我们已经整理了一个迁移到 LangGraph 的过渡指南。
ReAct 代理
构建代理的一个流行架构是 ReAct。ReAct 将推理和行动结合在一个迭代过程中 - 事实上,"ReAct" 的名称代表 "Reason" 和 "Act"。
通用流程如下:
模型将“思考”针对输入和任何先前观察结果采取哪个步骤。
然后,模型将从可用工具中选择一个行动(或选择响应用户)。
模型将为该工具生成参数。
代理运行时(执行器)将解析出所选工具,并使用生成的参数调用它。
执行器将工具调用的结果作为观察结果返回给模型。
这个过程一直重复,直到代理选择响应。
有一些基于提示的实现不需要任何特定于模型的功能,但最可靠的实现使用诸如工具调用之类的功能来可靠地格式化输出并减少变异。
有关更多信息,请参见 LangGraph 文档,或此如何迁移到 LangGraph 的指南。
回调 (Callbacks)
LangChain 提供了一个回调系统,允许您在 LLM 应用程序的各个阶段进行挂钩。这对于日志记录、监控、流媒体传输和其他任务非常有用。
您可以通过在整个 API 中可用的 callbacks 参数来订阅这些事件。这个参数是一个处理器对象列表,它们被期望实现下面更详细描述的一个或多个方法。
回调事件 (Callback Events)
事件 事件触发 关联方法
对话模型开始 对话模型开始时 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 |
回调处理器 (Callback Handlers)
回调处理器可以是同步的或异步的:
同步回调处理器实现 BaseCallbackHandler 接口。
异步回调处理器实现 AsyncCallbackHandler 接口。 在运行时,LangChain 配置适当的回调管理器(例如 CallbackManager 或 AsyncCallbackManager),负责在触发事件时调用每个“注册”回调处理器的适当方法。
传递回调 (Passing Callbacks)
大多数对象的 API(模型、工具、代理等)上的 callbacks 属性在两个不同的地方可用:
请求时回调:在请求时与输入数据一起传递。在所有标准 Runnable 对象上都可用。这些回调由它们定义的对象的所有子对象继承。例如,chain.invoke({"number": 25}, {"callbacks": [handler]})。
构造器回调:chain = TheNameOfSomeChain(callbacks=[handler])。这些回调作为参数传递给对象的构造器。这些回调仅限定于它们定义的对象,并且不由对象的任何子对象继承。
注意
构造器回调仅限定于它们定义的对象。它们不由对象的子对象继承。
如果您正在创建自定义链或可运行对象,则需要记住将请求时回调传播到任何子对象。
Python<=3.10 中的异步
任何 RunnableLambda、RunnableGenerator 或调用其他可运行对象的工具,并且在 python<=3.10 中异步运行,将不得不手动将回调传播到子对象。 这是因为 LangChain 无法在这种情况下自动将回调传播到子对象。
这是您可能无法看到从自定义可运行对象或工具发出的事件的常见原因。
有关如何使用回调的详细信息,请参见相关指南。