LangChain 是一个基于大语言模型(LLMs)用于构建端到端语言模型应用的框架,它可以让开发者使用语言模型来实现各种复杂的任务,例如文本到图像的生成、文档问答、聊天机器人等。它提供了一系列工具、套件和接口,可以简化创建由 LLMs 和聊天模型提供支持的应用程序的过程。
API 文档:python.langchain.com/api_referen…
开源项目地址:github.com/langchain-a…
- 设计目标:使数据感知且具有代理性的 AI 类型的应用成为可能,并帮助我们最大限度地释放大语言模型的潜能。
- 数据感知: 能够将语言模型与其他数据源连接起来,从而实现对更丰富、更多样化数据的理解和利用。
- 具有代理性: 能够让语言模型与其环境进行交互,使得模型能够对其环境有更深入的理解,并能够进行有效的响应。
模型 Models
模型主要指大语言模型,可以想象成一种巨大的预测机器,其训练过程主要基于“猜词”(给定一段文本的开头,它的任务就是预测下一个词是什么),通常使用深度学习技术来理解和生成人类语言,“大”量的参数使得它们能够理解和生成高度复杂的语言模式。
- 本质:通过复杂的数学函数学习到的语言模式,一个概率模型来做预测
- 局限:没有人类的情感、意识或理解力,实际上不能完全理解语言
补充问题:大语言模型除了文本生成式模型,还有哪些类别的模型?比如说有名的Bert模型,是不是文本生成式的模型?
模型类型
TextModel
接受一个字符串问题作为输入
ChatModel
可以接受一个消息列表作为输入,而不仅仅是一个字符串。这个消息列表可以包含 system、user 和assistant 的历史信息,从而在处理交互式对话时提供更多的上下文信息。
- 适用场景:对话或者多轮次交互的情况
预训练 + 微调模式
Pre-Training:在大规模无标注文本数据上进行模型的训练,目标是让模型学习自然语言的基础表达、上下文信息和语义知识,为后续任务提供一个通用的、丰富的语言表示基础
Fine-Tuning:在预训练模型的基础上,可以根据特定的下游任务对模型进行微调
- motivation:现今的预训练模型的趋势是参数越来越多,模型也越来越大,训练一次的费用可达几百万美元。这样大的开销和资源的耗费,只有世界顶级大厂才能够负担得起,普通的学术组织和高等院校很难在这个领域继续引领科技突破,这种现象开始被普通研究人员所诟病。但经过预训练的大模型中所习得的语义信息和所蕴含的语言知识,能够非常容易地向下游任务迁移。NLP 应用人员可以对模型的头部或者部分参数根据自己的需要进行适应性的调整,这通常涉及在相对较小的有标注数据集上进行有监督学习,让模型适应特定任务的需求。
六大组件
提示模版 Prompts(Model I/O)
模型的使用过程可以拆分为输入提示(Format)、调用模型(Predict)和输出解析(Parse)
- 提示模版:肯定不存在那么一两个神奇的模板,能够骗过所有模型,但基本原则就是
- 给予模型清晰明确的指示
- 让模型慢慢地思考
prompt_template = """您是一位专业的鲜花店文案撰写员。\n
对于售价为 {price} 元的 {flower_name} ,您能提供一个吸引人的简短描述吗?
"""
- 输出解析:LangChain 的提示模板可以嵌入对输出格式的定义,以便在后续处理过程中比较方便地处理已经被格式化了的输出。
# 定义我们想要接收的响应模式
response_schemas = [
ResponseSchema(name="description", description="鲜花的描述文案"),
ResponseSchema(name="reason", description="问什么要这样写这个文案"),
]
# 创建输出解析器
output_parser = StructuredOutputParser.from_response_schemas(response_schemas)
# 获取格式指示
format_instructions = output_parser.get_format_instructions()
# 根据模板创建提示,同时在提示中加入输出解析器的说明
prompt = PromptTemplate.from_template(
prompt_template, partial_variables={"format_instructions": format_instructions}
)
提示结构
指令 + 上下文 + 提示输入 + 输出指示器
FewShot 思想
- Few-Shot(少样本):听说过一些对应案例,可以通过给予几个示例,以帮助模型理解任务,并生成正确的响应
- One-Shot(单样本):听说过一次对应案例
- Zero-Shot(零样本):没听说过对应案例,模型只根据任务的描述生成响应,不需要任何示例
Chain of Thought 思维链
如果生成一系列的中间推理步骤,就能够显著提高大型语言模型进行复杂推理的能力
- Few-Shot CoT:给出一两个示例,然后在示例中写清楚推导的过程
- Zero-Shot CoT:只要简单地告诉模型 “Let's think step by step”,模型就能够给出更好的答案
数据检索 Retrieval(Indexes)
Retrieval(包括Indexes),研究如何把离散的文档及其他信息做嵌入,存储到向量数据库中,然后再提取的过程
Retrieval-Augmented Generation(RAG)
检索增强生成,核心是结合检索和生成的能力,为文本序列生成任务引入外部知识。RAG 将传统的语言生成模型与大规模的外部知识库相结合,使模型在生成响应或文本时可以动态地从这些知识库中检索相关信息。这种结合方法旨在增强模型的生成能力,使其能够产生更为丰富、准确和有根据的内容,特别是在需要具体细节或外部事实支持的场合。
基本步骤:
- 检索:对于给定的输入(问题),模型首先使用检索系统从大型文档集合中查找相关的文档或段落。这个检索系统通常基于密集向量搜索,例如 ChromaDB、Faiss 这样的向量数据库。
- 上下文编码:找到相关的文档或段落后,模型将它们与原始输入(问题)一起编码。
- 生成:使用编码的上下文信息,模型生成输出(答案)。这通常当然是通过大模型完成的。
文本加载
RAG 的第一步是文档加载。LangChain 提供了多种类型的文档加载器,以加载各种类型的文档(HTML、PDF、代码),并与该领域的其他主要提供商如 Airbyte 和 Unstructured.IO 进行了集成
文本转换
加载文档后,下一个步骤是对文本进行转换,而最常见的文本转换就是把长文档分割成更小的块(或者是片,或者是节点),以适合模型的上下文窗口。LangChain 有许多内置的文档转换器,可以轻松地拆分、组合、过滤和以其他方式操作文档。
- 最常见方法:文本分割器
- 将文本分成小的、具有语义意义的块(通常是句子)
- 开始将这些小块组合成一个更大的块,直到达到一定的大小。
- 一旦达到该大小,一个块就形成了,可以开始创建新文本块。这个新文本块和刚刚生成的块要有一些重叠,以保持块之间的上下文。
分割要考量的因素包括 LLM 的限制(如上下文窗口令牌大小限制),文本任务需求(细致查看小分块,全面了解大分块),文本性质(文本结构很强大分块,文本结构弱小分块)
- 其他方法
- 过滤冗余的文档:使用 EmbeddingsRedundantFilter 工具可以识别相似的文档并过滤掉冗余信息。这意味着如果你有多份高度相似或几乎相同的文档,这个功能可以帮助识别并删除这些多余的副本,从而节省存储空间并提高检索效率。
- 翻译文档:通过与工具 doctran 进行集成,可以将文档从一种语言翻译成另一种语言。
- 提取元数据:通过与工具 doctran 进行集成,可以从文档内容中提取关键信息(如日期、作者、关键字等),并将其存储为元数据。元数据是描述文档属性或内容的数据,这有助于更有效地管理、分类和检索文档。
- 转换对话格式:通过与工具 doctran 进行集成,可以将对话式的文档内容转化为问答(Q/A)格式,从而更容易地提取和查询特定的信息或回答。这在处理如访谈、对话或其他交互式内容时非常有用。
注:文档转换不仅限于简单的文本拆分,还可以包含附加的操作,这些操作的目的都是更好地准备和优化文档,以供后续生成更好的索引和检索功能
文本嵌入
文本块形成之后,我们就通过 LLM 来做嵌入(Embeddings),将文本转换为数值表示,使得计算机可以更容易地处理和比较文本。OpenAI、Cohere、Hugging Face 中都有能做文本嵌入的模型。 Embeddings 会创建一段文本的向量表示,让我们可以在向量空间中思考文本,并执行语义搜索之类的操作,在向量空间中查找最相似的文本片段。
- embed_documents 文档创建嵌入:接收多个文本作为输入,意味着可以一次性将多个文档转换为它们的向量表示
- embed_query 查询创建嵌入:只接收一个文本作为输入,通常是用户的搜索查询
存储嵌入
计算嵌入可能是一个时间消耗大的过程。为了加速这一过程,我们可以将计算出的嵌入存储或临时缓存,这样在下次需要它们时,就可以直接读取,无需重新计算。
- 缓存存储 ****CacheBackedEmbeddings:一个支持缓存的嵌入式包装器,它可以将嵌入缓存在键值存储中。具体操作是:对文本进行哈希处理,并将此哈希值用作缓存的键
- InMemoryStore 策略:在内存中缓存嵌入。主要用于单元测试或原型设计。如果需要长期存储嵌入,请勿使用此缓存。
- LocalFileStore 策略:在本地文件系统中存储嵌入。适用于那些不想依赖外部数据库或存储解决方案的情况。
- RedisStore 策略:在 Redis 数据库中缓存嵌入。当需要一个高速且可扩展的缓存解决方案时,这是一个很好的选择。
- 向量存储库
选择向量数据库的考量因素:
- 数据规模和速度需求:考虑你的数据量大小以及查询速度的要求。一些向量数据库在处理大规模数据时更加出色,而另一些在低延迟查询中表现更好。
- 持久性和可靠性:根据你的应用场景,确定你是否需要数据的高可用性、备份和故障转移功能。
- 易用性和社区支持:考虑向量数据库的学习曲线、文档的完整性以及社区的活跃度。
- 成本:考虑总体拥有成本,包括许可、硬件、运营和维护成本。
- 特性:考虑你是否需要特定的功能,例如多模态搜索等。
- 安全性:确保向量数据库符合你的安全和合规要求。
数据检索
数据检索模块的核心入口,它通过非结构化查询返回相关的文档
- 向量数据检索器:向量存储类的轻量级包装器,使其符合检索器接口。它使用向量存储中的搜索方法(例如相似性搜索和 MMR)来查询向量存储中的文本
- 其他检索器:
索引
在复杂的信息检索任务中,有效地管理和索引文档是关键的一步,LangChain 提供的索引 API 为开发者带来了一个高效且直观的解决方案
- 基本原理:LangChain 利用了记录管理器(RecordManager)来跟踪哪些文档已经被写入向量存储。在进行索引时,API 会对每个文档进行哈希处理,确保每个文档都有一个唯一的标识。这个哈希值不仅仅基于文档的内容,还考虑了文档的元数据,一旦哈希完成,以下信息会被保存在记录管理器中(文档哈希、写入时间、源 ID),确保了即使文档经历了多次转换或处理,也能够精确地跟踪它的状态和来源,确保文档数据被正确管理和索引
记忆 Memory
记录了之前的对话上下文,并且把这个上下文作为提示的一部分,在最新的调用中传递给了模型
基本格式
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.
Current conversation:
{history}
Human: {input}
AI:
类型对比
链 Chains
如果你想开发更复杂的应用程序,那么就需要通过 “Chain” 来链接 LangChain 的 各个组件和功能 —— 模型之间彼此链接,或模型与其他组件链接
- 链其实可以被视为 LangChain 中的一种基本功能单元
案例分析:多类问题分链
假设咱们的鲜花运营智能客服 ChatBot 通常会接到两大类问题鲜花养护和鲜花装饰,存在需求如果接到的是第一类问题,你要给ChatBot A指示;如果接到第二类的问题,你要给ChatBot B指示
- 解决方案:路由链 LLMRouterChain + MultiPromptChain
-
构建处理模板:为鲜花护理和鲜花装饰分别定义两个字符串模板。
-
提示信息:使用一个列表来组织和存储这两个处理模板的关键信息,如模板的键、描述和实际内容。
-
初始化语言模型:导入并实例化语言模型。
-
构建目标链:根据提示信息中的每个模板构建了对应的LLMChain,并存储在一个字典中。
-
构建 LLM 路由链:这是决策的核心部分。首先,它根据提示信息构建了一个路由模板,然后使用这个模板创建了一个 LLMRouterChain。
-
构建默认链:如果输入不适合任何已定义的处理模板,这个默认链会被触发。
-
构建多提示链:使用 MultiPromptChain 将 LLM 路由链、目标链和默认链组合在一起,形成一个完整的决策系统。
-
原理:负责查看用户输入的问题,引导大模型查看用户输入的问题并确定问题的类型的,主要是通过给大模型提前加一些 prompt 实现,如:
Given a raw text input to a language model select the model prompt best suited for the input.
You will be given the names of the available prompts and a description of what the prompt is best suited for.注:该部分同时可以为模型提供了一个格式化的框架,其中它将接收一个名为 {input} 的输入,并在此后的部分输出结果。
代理 Agents
代理就像一个多功能的接口,它能够接触并使用一套工具。根据用户的输入,代理会决定调用哪些工具。它不仅可以同时使用多种工具,而且可以将一个工具的输出数据作为另一个工具的输入数据。让大模型现在从一个仅仅可以通过自己内部知识进行对话聊天的 Bot,飞升为了一个有手有脚能使用工具的智能代理。
为什么要代理?
仅仅应用思维链推理并不能解决大模型的固有问题:无法主动更新自己的知识,导致出现事实幻觉。(缺乏和外部世界的接触,大模型只拥有训练时见过的知识)
Agent 和 Chain 的区别?
- 在链中,一系列操作被硬编码(在代码中)。
- 在代理中,语言模型被用作推理引擎来确定要采取哪些操作以及按什么顺序执行这些操作,即操作的序列并非硬编码在代码中,而是使用语言模型来选择执行的操作序列
- 大模型:提供逻辑的引擎,负责生成预测和处理输入。
- 与之交互的外部工具:可能包括数据清洗工具、搜索引擎、应用程序等。
常用搜索工具:google 搜索工具 serpapi
- 控制交互的代理:调用适当的外部工具,并管理整个交互过程的流程。
代理需要思考的问题?
- 什么时候开始在本地知识库中搜索(这个比较简单,毕竟是第一个步骤,可以预设)?
- 怎么确定本地知识库的检索已经完成,可以开始下一步?
- 调用哪一种外部搜索工具(比如Google引擎)?
- 如何确定外部搜索工具返回了想要找的内容?
- 如何确定信息真实性的检索已经全部完成,可以开始下一步?
eg. 你在运营花店的过程中,经常会经历天气变化而导致的鲜花售价变化,那么,每天早上你会如何为你的鲜花定价?也许,我会去Google上面查一查今天的鲜花成本价啊(行动),也就是我预计的进货的价格,然后我会根据这个价格的高低(观察),来确定我要加价多少(思考),最后计算出一个售价(行动)!
Reasoning-Acting 框架(ReAct)
- 核心观点:大语言模型可以通过生成推理痕迹和任务特定行动来实现更大的协同作用,同时 ReAct 的每一个推理过程都会被详细记录在案,这也改善大模型解决问题时的可解释性和可信度,而且这个框架在各种语言和决策任务中都得到了很好的效果。
- Reasoning:包括对当前环境和状态的观察,并生成推理轨迹。这使模型能够诱导、跟踪和更新操作计划,甚至处理异常情况
- Acting:指导大模型采取下一步的行动,比如与外部源(如知识库或环境)进行交互并且收集信息,或者给出最终答案
eg1. 在一个虚拟环境中找到一个胡椒瓶并将其放在一个抽屉里任务
eg2. 目前市场上玫瑰花的平均价格是多少?如果我在此基础上加价15%卖出,应该如何定价?
Agent 的关键组件
- 代理(Agent):这个类决定下一步执行什么操作。它由一个语言模型和一个提示(prompt)驱动。提示可能包含代理的性格(也就是给它分配角色,让它以特定方式进行响应)、任务的背景(用于给它提供更多任务类型的上下文)以及用于激发更好推理能力的提示策略(例如ReAct)。LangChain中包含很多种不同类型的代理。
- 工具(Tools):工具是代理调用的函数。这里有两个重要的考虑因素:一是让代理能访问到正确的工具,二是以最有帮助的方式描述这些工具。如果你没有给代理提供正确的工具,它将无法完成任务。如果你没有正确地描述工具,代理将不知道如何使用它们。LangChain提供了一系列的工具,同时你也可以定义自己的工具。
- 工具包(Toolkits):工具包是一组用于完成特定目标的彼此相关的工具,每个工具包中包含多个工具。比如LangChain的Office365工具包中就包含连接Outlook、读取邮件列表、发送邮件等一系列工具。当然LangChain中还有很多其他工具包供你使用。
- 代理执行器(AgentExecutor):代理执行器是代理的运行环境,它调用代理并执行代理选择的操作。执行器也负责处理多种复杂情况,包括处理代理选择了不存在的工具的情况、处理工具出错的情况、处理代理产生的无法解析成工具调用的输出的情况,以及在代理决策和工具调用进行观察和日志记录。
注:可以 debug AgentExecutor 的源码,会有更深刻的理解
代理策略
Structured Tool Chat(结构化工具对话)代理
组合调用其中的多个工具,完成批次相关的任务集合
- 文件管理工具集:支持所有文件系统操作,如写入、搜索、移动、复制、列目录和查找。
- Web 浏览器工具集:官方的 PlayWright 浏览器工具包,允许代理访问网站、点击、提交表单和查询数据。
- ...
Self-Ask with Search(自主询问搜索)代理
利用一种叫做 “Follow-up Question(追问)”加“Intermediate Answer(中间答案)”的技巧,来辅助大模型寻找事实性问题的过渡性答案,从而引出最终答案,一般用于解决 “多跳问题”。
- 多跳问题(Multi-hop question):指为了得到最终答案,需要进行多步推理或多次查询。这种问题不能直接通过单一的查询或信息源得到答案,而是需要跨越多个信息点,或者从多个数据来源进行组合和整合。即问题的答案依赖于另一个子问题的答案,这个子问题的答案可能又依赖于另一个问题的答案。这就像是一连串的问题跳跃,对于人类来说,解答这类问题可能需要从不同的信息源中寻找一系列中间答案,然后结合这些中间答案得出最终结论
Plan and execute(计划与执行) 代理
计划和执行代理通过首先计划要做什么,然后执行子任务来实现目标。即首先,制定一个计划,并将整个任务划分为更小的子任务;然后按照该计划执行子任务(分治法)
- 特点:计划和执行可以不使用同一个代理完成,如计划由一个大语言模型代理(负责推理)完成,而执行由另一个大语言模型代理(负责调用工具)完成。
Callbacks
函数 A 作为参数传给另一个函数 B,然后在函数 B 内部执行函数 A。当函数 B 完成某些操作后,会调用(即“回调”)函数A。这种编程模式常见于处理异步操作,如事件监听、定时任务或网络请求。
LangChain 的 Callback 机制允许你在应用程序的不同阶段进行自定义操作,如日志记录、监控和数据流处理,这个机制通过 CallbackHandler(回调处理器)来实现。
工具 Tool&Toolkits
工具是代理身上延展出的三头六臂,是代理的武器,代理通过工具来与世界进行交互,控制并改造世界。LangChain 通过提供一个统一的框架来集成功能的具体实现。在这个框架中,每个功能都被封装成一个工具,每个工具都有自己的输入和输出,以及处理这些输入和生成输出的方法。
当代理接收到一个任务时,它会根据任务的类型和需求,通过大模型的推理,来选择合适的工具处理这个任务。这个选择过程可以基于各种策略,例如基于工具的性能,或者基于工具处理特定类型任务的能力。一旦选择了合适的工具,LangChain 就会将任务的输入传递给这个工具,然后工具会处理这些输入并生成输出。这个输出又经过大模型的推理,可以被用作其他工具的输入,或者作为最终结果,被返回给用户。

