尼恩:LLM大模型学习圣经PDF的起源

在40岁老架构师 尼恩的读者交流群(50+)中,经常性的指导小伙伴们改造简历。

经过尼恩的改造之后,很多小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试机会,拿到了大厂机会。

然而,其中一个成功案例,是一个9年经验 网易的小伙伴,当时拿到了一个年薪近80W的大模型架构offer,逆涨50%,那是在去年2023年的 5月。

不到1年,小伙伴也在团队站稳了脚跟,成为了名副其实的大模型架构师。

目前,他管理了10人左右的团队,包括一个2-3人的python算法小分队,包括一个3-4人Java应用开发小分队,包括一个2-3人实施运维小分队。并且他们的产品也收到了丰厚的经济回报, 他们的AIGC大模型产品,好像已经实施了不下10家的大中型企业客户。

当然,尼恩更关注的,主要还是他的个人的职业身价。

小伙伴反馈,不到一年,他现在是人才市场的香馍馍。怎么说呢?

他现在职业机会不知道有多少, 而是大部分都是P8+ (年薪200W+)的顶级机会。

回想一下,去年小伙伴来找尼恩的时候, 可谓是 令人唏嘘。

当时,小伙伴被网易裁员, 自己折腾 2个月,没什么好的offer, 才找尼恩求助。

当时,小伙伴其实并没有做过的大模型架构, 仅仅具备一些 通用架构( JAVA 架构、K8S云原生架构) 能力,而且这些能力还没有完全成型。

特别说明,他当时 没有做过大模型的架构,对大模型架构是一片空白。

本来,尼恩是规划指导小伙做通用架构师的( JAVA 架构、K8S云原生架构),

毫无疑问,大模型架构师更有钱途,所以, 当时候尼恩也是 壮着胆子, 死马当作活马指导他改造为 大模型架构师。

回忆起当时决策的出发点,主要有3个:

(1)架构思想和体系,本身和语言无关,和业务也关系不大,都是通的。

(2)小伙伴本身也熟悉一点点深度学习,懂python,懂点深度学习的框架,至少,demo能跑起来。

(3)大模型架构师稀缺,反正面试官也不是太懂 大模型架构。

基于这个3个原因,尼恩大胆的决策,指导他往大模型架构走,先改造简历,然后去面试大模型的工程架构师,特别注意,这个小伙伴面的不是大模型算法架构师。

没想到,由于尼恩的大胆指导, 小伙伴成了。

没想到,由于尼恩的大胆指导, 小伙伴成了, 而且是大成,实现了真正的逆天改命。

相当于他不到1年时间, 职业身价翻了1倍多,可以拿到年薪 200W的offer了。

既然有一个这么成功的案例,尼恩能想到的,就是希望能帮助更多的社群小伙伴, 成长为大模型架构师,也去逆天改命。

于是,从2024年的4月份开始,尼恩开始写 《LLM大模型学习圣经》,帮助大家穿透大模型,走向大模型之路。

图片

尼恩架构团队的大模型《LLM大模型学习圣经》是一个系列,初步的规划包括下面的内容:

本文是第2篇,作者是43岁老架构师尼恩(带给大家一种俯视技术,技术自由的高度)。

尼恩架构团队会持续迭代和更新,后面会有实战篇,架构篇等等出来。 并且录制配套视频。

在尼恩的架构师哲学中,开宗明义:架构和语言无关,架构的思想和模式,本就是想通的。

架构思想和体系,本身和语言无关,和业务也关系不大,都是通的。

所以,尼恩用自己的架构内功,以及20年时间积累的架构洪荒之力,通过《LLM大模型学习圣经》,给大家做一下系统化、体系化的LLM梳理,使得大家内力猛增,成为大模型架构师,然后实现”offer直提”, 逆天改命。

尼恩 《LLM大模型学习圣经》PDF 系列文档,会延续尼恩的一贯风格,会持续迭代,持续升级。

这个文档将成为大家 学习大模型的杀手锏, 此文当最新PDF版本,可以来《技术自由圈》公号获取。

本文目录

- 尼恩:LLM大模型学习圣经PDF的起源

- 本文目录

- LangChain学习圣经:从0到1精通LLM大模型应用开发的基础框架

- 1. LangChain 简介

 - 1.1. LangChain 发展史

 - 1.2. LangChain 为什么这么火

 - 1.3. LangChain 核心概念

 - 1.4. LangChain 如何工作?

 - 1.5. LangChain 应用场景

- 2. LangChain  应用架构 和核心组件

 - LangChain  应用架构

 - LangChain  的核心组件

- 2.1. Models(模型)

- 2.1.1. Chat Models 聊天模型

 - Chat Models 聊天模型使用场景

 - Chat Models 聊天模型使用例子

 - Chat Models 聊天模型的上下文缓存

 - 流式响应在 LangChain 中的应用

- 2.1.2. 嵌入 模型(embeddings model)

 - 通过OpenAIEmbeddings 使用 嵌入模型(Embedding Model)

- 2.1.3. LLMS 大语言模型

 - 嵌入 模型  和 大模型的区别

- 2.2. Prompts(提示词)

 - 2.2.1. Prompt Templates

 - 2.2.2. PipelinePrompt 提示词模版组合

 - 2.2.3. Few-shot Example(小样本提示)

 - 2.2.4. Example Selector

 - 2.2.5. 聊天提示模板

- 2.3. Indexes (RAG 索引)

 - 2.3.1. Document Loaders

 - 2.3.2. Document Transformers

 - 2.3.3. Embedding Models

 - 2.3.4. VectorStores

 - 2.3.5. Retrievers

- 2.4. Chains(链)

- 2.4.1. 简单的链 LLMChain

 - 使用ChainVS不使用Chain的代码对比

- 2.4.2 LLMChain 的调用方法

 - 1. 直接调用链对象

 - 2. 通过run方法

 - 3. 通过invoke方法

 - 4. 通过predict方法

 - 5. 通过apply方法

 - 6. 通过generate方法

- 2.4.3. 简单顺序链 SimpleSequentialChain

- 2.4.4.通用顺序链 SequentialChain

- 2.4.5. 转换链TransformChain

- 2.4.6. LangChain表达式语言 (LCEL)

    -LCEL 语法样例
    -管道运算符的工作原理

- 2.4.7  LangChain预置链

    -LCEL Chains:链构造器
    -Legacy Chains:遗留链

- 2.5. LangChain Memory(记忆)

 - 上下文管理能力 如何实现?

 - 2.5.1. 简单实现对话 ConversationBufferMemory

 - 2.5.2. 时间窗口记忆ConversationBufferWindowMemory

 - 2.5.3. ConversationSummaryMemory

 - 2.5.4. ConversationSummaryBufferMemory

 - 2.5.5. ConversationTokenBufferMemory

 - 2.5.6. 对话知识图谱记忆ConversationKGMemory

 - 2.5.7. 基于向量存储记忆 VectorStoreRetrieverMemory

- 2.6. Agents(代理)

 - 2.6.1. Action agents

 - 2.6.2. Plan-and-execute agents

- 3. LangChain 实战

 - 3.1. 案例1:完成一次问答

 - 3.2. 案例二:通过谷歌搜索并返回答案

 - 3.3. 案例三:对超长文本进行总结

 - 3.4. 构建本地知识库问答机器人

 - 3.5. 构建本地Chroma向量索引数据库

 - 3.6. 实战: 基于 Langchain+私有模型,构建一个生成级RAG 聊天机器人

 - 3.7. 实战: 基于 Langchain+公有模型,构建一个生成级RAG 聊天机器人

 - 3.8. 基于 LangChain 构建的开源应用

- 说在最后:有问题找老架构取经

LangChain学习圣经:从0到1精通LLM大模型应用开发的基础框架

在AI和机器学习领域,每天都有新技术和框架涌现。

目前来说, LangChain 是LLM大模型应用开发的基础框架, 是一个非常火的开源框架

图片

https://python.langchain.com/docs/get_started/introduction

那么,LangChain到底是什么,它为什么这么受欢迎,以及它是如何工作的呢?

别担心,我会用大白话帮你一步步弄明白!

1. LangChain 简介

想象一下,如果你能让聊天机器人不仅仅回答通用问题,还能从你自己的数据库或文件中提取信息,并根据这些信息执行具体操作,比如发邮件,那会是什么情况?LangChain 正是为了实现这一目标而诞生的。

LangChain 是一个开源框架,它允许开发人员将像 GPT-4 这样的大型语言模型与外部的计算和数据源结合起来。目前,它提供了 Python 和 JavaScript(确切地说是 TypeScript)的软件包。

设想一下,倘若你有一位聊天机器人,不仅能够应对通用问题,还能从你自身的数据库或文件中提取信息,并据此执行具体操作,例如发送邮件,那将会是何等情景?这正是 LangChain 的初衷,旨在实现这一目标。

再想象一下,这种情景:你可以轻松自如地操控你的聊天机器人,它不仅能为你解答各种常见问题,还能深入你的数据仓库,提取有价值的信息,进而执行具体的任务,比如发送邮件。这种能力,真是令人叹为观止。LangChain 就是为了成就这一梦想而横空出世的。

LangChain,这一开源框架,犹如孙悟空的金箍棒,具有变化万千的功能,能帮助开发人员将像 GPT-4 这样的大型语言模型与外部的计算和数据源结合起来,达到天衣无缝的效果。

LangChain,这一利器,犹如武松手中的哨棒,功能强大,应用广泛。

LangChain允许开发人员将 GPT-4 等大型语言模型与外部的计算和数据源紧密结合,实现无缝对接,发挥出最大的效用。

目前,LangChain提供了 Python 和 JavaScript(确切地说是 TypeScript)的软件包,如同唐三藏的三部经书,全面且完善。或者说,LangChain 提供了 Python 和 JavaScript(具体来说是 TypeScript)的软件包,犹如兵器谱上的绝世神器,助力开发者无往不利,攻无不克。

LangChain 的三个核心组件:

图片

上图展示了LangChain的工作原理,这是一个用于提升大型语言模型(LLMs)功能的框架。它通过三个核心组件实现增强:

  • 首先是 Compents“组件”,为LLMs提供接口封装、模板提示和信息检索索引;

  • 其次是 Chains“链”,它将不同的组件组合起来解决特定的任务,比如在大量文本中查找信息;

  • 最后是 Agents“代理”,它们使得LLMs能够与外部环境进行交互,例如通过API请求执行操作。

LangChain 的这种结构设计使LLMs不仅能够处理文本,还能够在更广泛的应用环境中进行操作和响应,大大扩展了它们的应用范围和有效性。

总体来说,LangChain是一个以 LLM (大语言模型)模型为核心的开发框架,LangChain的主要特性:

  • 可以连接多种数据源,比如网页链接、本地PDF文件、向量数据库等

  • 允许语言模型与其环境交互

  • 封装了Model I/O(输入/输出)、Retrieval(检索器)、Memory(记忆)、Agents(决策和调度)等核心组件

  • 可以使用链的方式组装这些组件,以便最好地完成特定用例。

1.1. LangChain 发展史

LangChain 的作者是 Harrison Chase,最初是于 2022 年 10 月开源的一个项目,在 GitHub 上获得大量关注之后迅速转变为一家初创公司。

2017 年 Harrison Chase 还在哈佛上大学,如今已是硅谷的一家热门初创公司的 CEO,这对他来说是一次重大而迅速的跃迁。

Insider 独家报道,人工智能初创公司 LangChain 在种子轮一周后,再次获得红杉领投的 2000 万至 2500 万美元融资,估值达到 2 亿美元。

图片

1.2. LangChain 为什么这么火

LangChain 目前是有两个语言版本(python 和 nodejs),

从下图可以看出来,短短半年的时间该项目的 python 版本已经获得了 54k+的 star。

nodejs 版本也在短短 4 个月收货了 7k+的 star,这无疑利好前端同学,不需要会 python 也能快速上手 LLM 应用开发。

图片

笔者认为 Langchain 作为一个大语言模型应用开发框架,解决了现在开发人工智能应用的一些切实痛点。

以 GPT 模型为例:

1.数据滞后,现在训练的数据是到 2021 年 9 月。

2.token 数量限制,如果让它对一个 300 页的 pdf 进行总结,直接使用则无能为力。

3.不能进行联网,获取不到最新的内容。

4.不能与其他数据源链接。

而Langchain大大弥补了大模型开发生态的不足, Langchain 作为一个胶水层框架,极大地提高了开发效率,它的作用可以类比于 jquery 在前端开发中的角色,使得开发者可以更专注于创新和优化产品功能。

1.3. Langchain 核心概念

Langchain 由几个核心概念组成:

  • LLM Wrappers:这些包装器允许你连接到大型语言模型,如 GPT-4 或 Hugging Face 提供的模型。

  • Prompt Templates:这些模板让你避免硬编码文本输入。你可以动态地将用户输入插入到模板中,并发送给语言模型。

  • Indexes:索引帮助你从语言模型中提取相关信息。

  • Chains:链允许你将多个组件组合在一起,解决特定的任务,并构建完整的语言模型应用程序。

  • Agents:代理允许语言模型与外部API交互。

图片

上图展示了一个复杂的语言处理系统,其中包含模型、提示、链、代理和嵌入与向量存储。

  • 模型 Models 负责理解和生成语言,

  • 提示Prompts 用于引导模型输出;

  • 链条 Chains 代表将多个步骤串联起来完成复杂任务的过程;

  • 代理 Agents 则用于让模型与外部环境互动,比如执行API调用。

  • Embedding 嵌入与向量存储 VectorStore 是数据表示和检索的手段,为模型提供必要的语言理解基础。

图中的鹦鹉是一个比喻或者象征,表示这个系统的自然语言处理能力,或者可能暗示系统的输出可以像鹦鹉一样“复述”或者是“回应”用户的输入。

如此,这整个系统构成了一个高度集成的框架,能够处理高级语言任务并在多种环境下进行动态交互。

1.4. LangChain 如何工作?

LangChain 的工作流程可以概括为以下几个步骤:

  • 提问:用户提出问题。

  • 向语言模型查询:问题被转换成向量表示,用于在向量数据库中进行相似性搜索。

  • 获取相关信息:从向量数据库中提取相关信息块,并将其输入给语言模型。

  • 生成答案或执行操作:语言模型现在拥有了初始问题和相关信息,能够提供答案或执行操作。

图片

举例:如上图所示展示了一个智能问答系统的工作流程,它从用户提出的问题(Question)开始,然后通过相似性搜索(Similarity Search)在一个大型数据库或向量空间中找到与之相关的信息。

得到的信息与原始问题结合后,由一个处理模型分析,以产生一个答案(Answer)。

这个答案接着被用来指导一个代理采取行动(Action),这个代理可能会执行一个API调用或与外部系统交互以完成任务。

整个流程反映了数据驱动的决策过程,其中包含了从信息检索到处理,再到最终行动的自动化步骤。

1.5. LangChain 应用场景

Langchain 的应用场景非常广泛,包括但不限于:

  • 个人助手:可以帮助预订航班、转账、缴税等。

  • 数据分析和数据科学:连接到公司的客户数据或市场数据,极大地促进数据分析的进展。

  • 数据连接:Langchain 允许你将大型语言模型连接到你自己的数据源,比如数据库、PDF文件或其他文档。这意味着你可以使模型从你的私有数据中提取信息。

  • 行动执行:不仅可以提取信息,Langchain 还可以帮助你根据这些信息执行特定操作,如发送邮件。无需硬编码:它提供了灵活的方式来动态生成查询,避免了硬编码的需求。

总之,Langchain 打开了一个充满可能性的新世界,让AI技术更加贴近我们的实际需求和数据,使得机器学习应用的发展更加多样化和个性化。

2. LangChain 应用架构 和核心组件

LangChain 应用架构

LangChain 作为一个大语言模型开发框架,是 LLM 应用架构的重要一环,犹如珠玉之冠上的明珠,起着至关重要的作用。

那么,究竟什么是 LLM 应用架构呢?说白了,就是指基于语言模型的应用程序设计和开发的架构,犹如一栋大厦的蓝图,决定了整个建筑的风貌和功能。

LangChain 的魅力在于它能够将 LLM 模型、向量数据库、交互层 Prompt、外部知识、外部工具整合到一起,这就如同将各路英雄豪杰汇聚一堂,共襄盛举,进而可以自由构建 LLM 应用,犹如搭建一个自由王国,任由你驰骋驰骋。

LangChain 作为一个大语言模型开发框架,真的是 LLM 应用架构中的璀璨明珠,真的是一环扣一环的重要组成部分。

那到底什么是 LLM 应用架构呢?其实,通俗点说,就是指基于语言模型的应用程序设计和开发的架构,犹如一幅完整的蓝图,决定了大厦的结构和风格。

LangChain 的独特之处在于它能将 LLM 模型、向量数据库、交互层 Prompt、外部知识、外部工具,这些看似独立的元素,巧妙地整合在一起,就像把各种宝物聚集在一个宝箱中,进而可以自由构建 LLM 应用,犹如神笔马良般,描绘出一个自由而强大的应用天地。

图片

总之,LangChian 可以将 LLM 模型、向量数据库、交互层 Prompt、外部知识、外部工具整合到一起,进而可以自由构建 LLM 应用。

LangChain 的核心组件

如图,LangChain 包含六部分组成,分别为:Models、Prompts、Indexes、Memory、Chains、Agents。

图片

2.1. Models(模型)

下面我们以具体示例分别阐述下 Chat Modals, Embeddings, LLMs。

2.1.1. Chat Models 聊天模型

LangChain 为使用聊天模型提供了一个标准接口。

聊天模型是语言模型的接口(包括入口和出口,类似函数的输入和输出)。虽然聊天模型在内部使用语言模型,但它们所提供的接口略有不同。

图片

聊天模型 不是暴露一个 “输入文本,输出文本” 的 API,而是提供了一个以 “聊天消息” 作为输入和输出的接口。

所以,聊天模型的接口是基于消息, 而不是原始文本。

LangChain 目前支持的 Chat Modals 聊天模型的 消息类型有

  • AIMessage、

  • HumanMessage、

  • SystemMessage

  • hatMessage,

其中 ChatMessage 接受一个任意的角色参数。

在构建对话系统时,特别是在使用大型语言模型(如 GPT-3)时,不同类型的消息(Messages)有助于更好地组织和管理对话内容。以下是一些常见的消息类型及其用途:

1. HumanMessage

HumanMessage 代表由用户或人类生成的消息。它是用户输入到对话系统中的内容。

  • 用途:用于记录和传输由用户输入的消息内容。

  • 例子:

    <section><span>{<br>    "type": "HumanMessage",<br>    "content": "请问今天的天气如何?"<br>}<br></span></section>
    

2. AIMessage

AIMessage 代表由人工智能或聊天机器人生成的消息。它通常是对用户输入的响应或系统触发的自动回复。

  • 用途:用于记录和传输由 AI 生成的回复内容。

  • 例子:

    <section><span>{<br>    "type": "AIMessage",<br>    "content": "你好,有什么我可以帮你的吗?"<br>}<br></span></section>
    

3. SystemMessage

SystemMessage 代表由系统生成的消息,通常用于传递系统状态、指令或元信息。

这类消息不直接参与对话,但对控制对话流程和环境设置有重要作用。

  • 用途:用于系统状态更新、环境设置或元信息传递。

  • 例子:

    <section><span>{<br>    "type": "SystemMessage",<br>    "content": "对话已经开始。"<br>}<br></span></section>
    

4. ChatMessage

ChatMessage 是一个通用类型,可以包含任何类型的消息内容。

ChatMessage可以包含 AI 生成的消息、用户输入的消息以及系统消息。

  • 用途:用于记录和传输对话中的任何消息,无论其来源。

  • 例子:

    <section><span>json复制代码{<br>    "type": "ChatMessage",<br>    "role": "human",<br>    "content": "我想了解一下最新的新闻。"<br>},<br>{<br>    "type": "ChatMessage",<br>    "role": "ai",<br>    "content": "当然,以下是最新的新闻摘要。"<br>},<br>{<br>    "type": "ChatMessage",<br>    "role": "system",<br>    "content": "会话已重置。"<br>}<br></span></section>
    

Chat Models 聊天模型使用场景

  1. AIMessage 和 HumanMessage
  • 在对话中区分用户输入和 AI 响应。

  • 帮助追踪对话的来回交流,便于分析和改进对话系统的性能。

  1. SystemMessage
  • 用于管理对话的状态和控制信息流。

  • 可以包含会话开始、结束、重置等系统事件的信息。

  1. ChatMessage
  • 提供一种统一的方式来存储和传输各种类型的消息。

  • 适用于需要处理多种消息类型的复杂对话场景。

通过这些不同类型的消息,开发者可以更好地组织和管理对话系统的逻辑,使系统能够更有效地处理用户请求、生成适当的响应,并维护对话的上下文和状态。

大多数情况下,您只需要处理 HumanMessage、AIMessage 和 SystemMessage。

Chat Models 聊天模型使用例子

向聊天模型发问的例子

<section><span># 导入OpenAI的聊天模型,及消息类型<br><span>from</span> langchain.chat_models <span>import</span> ChatOpenAI<br><span>from</span> langchain.schema <span>import</span> (<br>    AIMessage,<br>    HumanMessage,<br>    SystemMessage<br>)<br><br># 初始化聊天对象<br>chat = ChatOpenAI(openai_api_key=<span>"..."</span>)<br><br># 向聊天模型发问<br>chat([HumanMessage(content=<span>"Translate this sentence from English to French: I love programming."</span>)])<br></span></section>

如果需要 多个消息作为输入。怎么办呢?

这是一个系统和用户消息聊天模式的例子:

<section><span>messages = [<br>    SystemMessage(content=<span>"You are a helpful assistant that translates English to French."</span>),<br>    HumanMessage(content=<span>"I love programming."</span>)<br>]<br>chat(messages)<br></span></section>

当然也可以进行批量处理,批量输出。

<section><span>batch_messages = [<br>    [<br>        SystemMessage(content=<span>"You are a helpful assistant that translates English to French."</span>),<br>        HumanMessage(content=<span>"I love programming."</span>)<br>    ],<br>    [<br>        SystemMessage(content=<span>"You are a helpful assistant that translates English to French."</span>),<br>        HumanMessage(content=<span>"I love artificial intelligence."</span>)<br>    ],<br>]<br>result = chat.generate(batch_messages)<br>result<br></span></section>

上面介绍了聊天的角色设置,以及如何进行批量处理消息。

Chat Models 聊天模型的上下文缓存

我们都知道向 openAI 调用接口都是要花钱的,如何减少成本。

如果用户问同一个问题,对结果进行了缓存,这样就可以减少接口的调用并且也能加快接口返回的速度。

LangChain 也很贴心的提供了缓存的功能。

并且提供了两种缓存方案,内存缓存方案和数据库缓存方案,当然支持的数据库缓存方案有很多种。

<section><span># 导入聊天模型,SQLiteCache模块<br><span>import</span> os<br>os.environ[<span>"OPENAI_API_KEY"</span>] = <span>'your apikey'</span><br><span>import</span> langchain<br><span>from</span> langchain.chat_models <span>import</span> ChatOpenAI<br><span>from</span> langchain.cache <span>import</span> SQLiteCache<br><br># 设置语言模型的缓存数据存储的地址<br>langchain.llm_cache = SQLiteCache(database_path=<span>".langchain.db"</span>)<br><br># 加载 llm 模型<br>llm = ChatOpenAI()<br><br># 第一次向模型提问<br>result = llm.predict(<span>'tell me a joke'</span>)<br>print(result)<br><br># 第二次向模型提问同样的问题<br>result2 = llm.predict(<span>'tell me a joke'</span>)<br>print(result2)<br></span></section>

流式响应在 LangChain 中的应用

另外聊天模式也提供了一种流媒体回应。这意味着,而不是等待整个响应返回,你就可以开始处理它尽快。

LangChain 提供了多种工具和组件,可以轻松集成和实现流式响应这一功能。

LangChain 流式响应的实现

  1. 设置 流式生成器: LangChain 提供了生成器函数的支持,可以逐步生成响应内容。这些生成器函数可以在生成部分响应时立即返回给用户,而不是等待完整响应生成完毕。

  2. 配置流式响应: 使用 LangChain 的流媒体功能时,需要配置相应的接口,使得系统可以处理和返回流式响应。

示例实现

以下是一个使用 LangChain 和 FastAPI 实现流式响应的示例。在这个示例中,我们展示了如何设置一个生成器函数来逐步生成响应内容,并使用 FastAPI 的 StreamingResponse 返回流式响应。

示例代码

<section><span>from fastapi import FastAPI<br>from fastapi.responses import StreamingResponse<br>import time<br>from langchain.chains import ConversationalRetrievalChain<br>from langchain.llms import OpenAI<br><br>app = FastAPI()<br><br># 初始化 Langchain 模型<br>llm = OpenAI(api_key='your-api-key')<br>chain = ConversationalRetrievalChain(llm)<br><br>def generate_stream_response(prompt):<br>    # 使用 Langchain 生成响应<br>    responses = chain.generate_responses(prompt)<br>    for response in responses:<br>        yield response<br>        time.sleep(1)  # 模拟生成过程中的延迟<br><br>@app.post("/chat")<br>async def chat(prompt: str):<br>    return StreamingResponse(generate_stream_response(prompt), media_type="text/plain")<br><br># 运行应用程序:uvicorn example:app --reload<br></span></section>

详细解释

  1. LangChain 模型初始化: 首先初始化一个 Langchain 模型,这里使用的是 OpenAI 的 GPT 模型。可以根据需要替换成其他支持的模型。

  2. 生成器函数: generate_stream_response函数是接收流式数据的方法 ,这个方法使用 Langchain 的 ConversationalRetrievalChain 来逐步生成响应内容。每次生成一部分响应后,通过 yield 返回给调用方。

  3. FastAPI 的 StreamingResponse: def chat 是接口,前端可访问。暴露在 FastAPI 的 /chat 地址,前端可访问。我们使用 StreamingResponse 将生成器函数返回的内容逐步发送给客户端。先利用 langchain 框架调用 openai 接口进行对话,最后使用 StreamingResponse 流式响应类返回,里面传入流式处理的过程,也就是上面的generate_stream_response类。

LangChain 流式响应优势

  • 即时反馈:用户不需要等待完整的响应生成完毕即可看到部分内容,从而提高响应速度和用户体验。

  • 资源优化:逐步生成和返回内容可以更好地利用计算和带宽资源,避免一次性处理大量数据带来的压力。

  • 改进互动体验:在长时间生成内容的情况下,用户可以逐步看到生成的结果,减少等待的焦虑。

LangChain 流式响应总结

通过在 Langchain 中实现流式响应,开发者可以显著提升对话系统的用户体验和响应速度。流式响应不仅能提供即时反馈,还能更好地优化资源利用,是构建高效对话系统的重要技术手段。结合 Langchain 的强大功能,流式响应能够为用户带来更加流畅和自然的交互体验

2.1.2. 嵌入 模型(embeddings model)

嵌入 模型更多的是用于文档、文本或者大量数据的总结、问答场景,一般是和向量库一起使用,实现向量匹配。

嵌入 模型其实就是把文本等内容转成多维数组,可以后续进行相似性的计算和检索。

下图是 LangChain 两种语言包支持的 embeddings 模型。

![图片](http://zshipu.com/t/index.html?url=data:image/svg+xml,%3C%3Fxml version=‘1.0’ encoding=‘UTF-8’%3F%3E%3Csvg width=‘1px’ height=‘1px’ viewBox=‘0 0 1 1’ version=‘1.1’ xmlns=‘http://www.w3.org/2000/svg' xmlns:xlink=‘http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke=‘none’ stroke-width=‘1’ fill=‘none’ fill-rule=‘evenodd’ fill-opacity=‘0’%3E%3Cg transform=‘translate(-249.000000, -126.000000)’ fill=’%23FFFFFF’%3E%3Crect x=‘249’ y=‘126’ width=‘1’ height=‘1’%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)

什么是 嵌入模型(Embedding Model)?

嵌入模型是一种将离散的高维数据(如单词、句子、图片等)映射到连续的低维向量空间的技术。

这个低维向量空间中的点称为“嵌入”(embedding)。

嵌入模型的主要目的是捕捉输入数据中的语义或特征信息,使得相似的输入在嵌入空间中距离更近。

嵌入模型(Embedding Model)特点

  1. 低维表示:嵌入模型将高维、稀疏的离散数据转换为低维、密集的向量表示。

  2. 语义信息:嵌入向量捕捉了输入数据的语义或特征信息,向量之间的距离反映了输入数据之间的相似性。

  3. 效率高:嵌入模型通常计算高效,适合用于大规模数据的相似性搜索和分类任务。

常见嵌入模型

  1. Word2Vec:通过Skip-Gram或CBOW方法训练,生成单词的嵌入向量。

  2. GloVe:基于全局词共现矩阵训练,生成单词嵌入。

  3. FastText:扩展了Word2Vec,能够处理未见过的词汇。

嵌入模型(Embedding Model)应用场景

  1. 文本相似度计算:比较两个文本的嵌入向量,计算相似度。

  2. 信息检索:通过嵌入向量进行高效的相似性搜索。

  3. 分类和聚类:使用嵌入向量进行文本或图像的分类和聚类。

通过OpenAIEmbeddings 使用 嵌入模型(Embedding Model)

下面以代码展示下 embeddings 是什么。

<section><span># 导入os, 设置环境变量,导入OpenAI的嵌入模型<br><span>import</span> os<br><span>from</span> langchain.embeddings.openai <span>import</span> OpenAIEmbeddings<br>os.environ[<span>"OPENAI_API_KEY"</span>] = <span>'your apikey'</span><br><br># 初始化嵌入模型<br>embeddings = OpenAIEmbeddings()<br><br># 把文本通过嵌入模型向量化<br>res = embeddings.embed_query(<span>'hello world'</span>)<br><span>/*<br>[<br>   -0.004845875,   0.004899438,  -0.016358767,  -0.024475135, -0.017341806,<br>    0.012571548,  -0.019156644,   0.009036391,  -0.010227379, -0.026945334,<br>    0.022861943,   0.010321903,  -0.023479493, -0.0066544134,  0.007977734,<br>   0.0026371893,   0.025206111,  -0.012048521,   0.012943339,  0.013094575,<br>   -0.010580265,  -0.003509951,   0.004070787,   0.008639394, -0.020631202,<br>  -0.0019203906,   0.012161949,  -0.019194454,   0.030373365, -0.031028723,<br>   0.0036170771,  -0.007813894, -0.0060778237,  -0.017820721, 0.0048647798,<br>   -0.015640393,   0.001373733,  -0.015552171,   0.019534737, -0.016169721,<br>    0.007316074,   0.008273906,   0.011418369,   -0.01390117, -0.033347685,<br>    0.011248227,  0.0042503807,  -0.012792102, -0.0014595914,  0.028356876,<br>    0.025407761, 0.00076445413,  -0.016308354,   0.017455231, -0.016396577,<br>    0.008557475,   -0.03312083,   0.031104341,   0.032389853,  -0.02132437,<br>    0.003324056,  0.0055610985, -0.0078012915,   0.006090427, 0.0062038545,<br>  ... 1466 more items<br>]<br>*/</span><br></span></section>

2.1.3. LLMS 大语言模型

大型语言模型(Large Language Models, LLMs)定义

大语言模型是一种深度学习模型,通常基于Transformer架构,具有数以亿计甚至数以百亿计的参数。LLM通过在大规模文本语料库上进行训练,能够生成、理解和处理自然语言文本。

大型语言模型(Large Language Models, LLMs)特点

  1. 大规模:大语言模型拥有大量参数和复杂的网络结构,能够捕捉丰富的语言信息。

  2. 生成能力:能够生成高质量的自然语言文本,进行对话、写作等任务。

  3. 多任务处理:能够处理多种NLP任务,如翻译、总结、问答等。

常见大型语言模型(Large Language Models, LLMs)

  1. GPT-3:由OpenAI开发的生成型预训练模型,具有1750亿参数。

  2. BERT:由Google开发的双向编码器表示模型,适用于文本分类、命名实体识别等任务。

  3. T5:由Google开发的文本到文本转换模型,能够处理多种文本生成任务。

大型语言模型(Large Language Models, LLMs)应用场景

  1. 对话系统:用于构建智能对话助手,如ChatGPT。

  2. 文本生成:自动写作、内容创作、翻译等。

  3. 问答系统:回答用户的问题,提供信息检索和知识问答服务。

LangChain 和大型语言模型(Large Language Models, LLMs)是什么关系呢?

LangChain 是一个开源框架,旨在将大型语言模型(如 GPT-4)与外部数据源和计算资源结合起来,以实现复杂的 NLP 应用。LangChain 提供了各种工具和组件,帮助开发者构建、部署和管理基于大型语言模型的应用程序。

大型语言模型(如 GPT-4、BERT)是基于深度学习的复杂模型,通常具有数以亿计甚至数以百亿计的参数。这些模型通过在大规模文本语料库上进行训练,能够生成和理解自然语言文本,并执行多种 NLP 任务,如文本生成、翻译、摘要、问答等。

LangChain 和LLMS 大型语言模型的关系,大致如下:

  1. 集成和扩展: LangChain 提供了一个框架,使开发者可以轻松地集成和扩展大型语言模型。通过 LangChain,开发者可以将 LLM 的强大能力与其他工具和数据源结合,创建更复杂和强大的 NLP 应用。例如,可以将 LLM 与向量数据库、API 接口、外部计算资源等结合,增强其功能和应用场景。

  2. 组件化设计: LangChain 提供了各种组件,使开发者能够模块化地构建应用。这些组件包括模型管理、数据处理、任务调度、交互界面等。开发者可以根据需要选择和组合这些组件,以实现特定的应用需求。

  3. 增强的交互性: 通过 LangChain,开发者可以实现更复杂的用户交互。例如,可以设计多轮对话系统、复杂的问答系统,或者根据用户输入动态调用外部 API 进行信息查询和处理。这使得基于 LLM 的应用不仅限于单一的任务处理,而是能够实现更复杂的交互逻辑。

  4. 提升性能和效率: LangChain 通过优化模型调用、缓存机制、并行处理等技术手段,提升了 LLM 的性能和效率。尤其是在处理高并发请求或大规模数据时,LangChain 的设计能够显著降低延迟,提高响应速度。

所以,LLMS 是 LangChain 的核心,从官网可以看到 LangChain 继承了非常多的大语言模型。

图片

LangChain 定义一个LLM语言模型,并使用predict 方法, 实现 请求发送,和阻塞式获取结果 :

<section><span><span>import</span> { OpenAI } <span>from</span> <span>"langchain/llms/openai"</span>;<br><span>// 实例化一个模型</span><br><span>const</span> model = <span>new</span> OpenAI({ <br>    <span>// OpenAI内置参数</span><br>    <span>openAIApiKey</span>: <span>"YOUR_KEY_HERE"</span>,<br>    <span>modelName</span>: <span>"text-davinci-002"</span>, <span>//gpt-4、gpt-3.5-turbo</span><br>    <span>maxTokens</span>: <span>25</span>, <br>    <span>temperature</span>: <span>1</span>, <span>//发散度</span><br>    <span>// LangChain自定义参数</span><br>    <span>maxRetries</span>: <span>10</span>, <span>//发生错误后重试次数</span><br>    <span>maxConcurrency</span>: <span>5</span>, <span>//最大并发请求次数</span><br>    <span>cache</span>: <span>true</span> <span>//开启缓存</span><br>});<br><span>// 使用模型</span><br><span>const</span> res = <span>await</span> model.predict(<span>"Tell me a joke"</span>);<br></span></section>

使用 LLM的 call方法, 实现 请求发送,取消请求和超时处理:

<section><span><span>import</span> { OpenAI } <span>from</span> <span>"langchain/llms/openai"</span>;<br><br><span>const</span> model = <span>new</span> OpenAI({ <span>temperature</span>: <span>1</span> });<br><span>const</span> controller = <span>new</span> AbortController();<br><br><span>const</span> res = <span>await</span> model.call(<br>  <span>"What would be a good name for a company that makes colorful socks?"</span>,<br>  { <br>    <span>signal</span>: controller.signal, <span>//调用controller.abort()即可取消请求</span><br>    <span>timeout</span>: <span>1000</span> <span>//超时时间设置</span><br>  }<br>);<br></span></section>

流式响应:通常,当我们请求一个服务或者接口时,服务器会将所有数据一次性返回给我们,然后我们再进行处理。但是,如果返回的数据量很大,那么我们需要等待很长时间才能开始处理数据。

而流式响应则不同,它将数据分成多个小块,每次只返回一部分数据给我们。我们可以在接收到这部分数据之后就开始处理,而不需要等待所有数据都到达。

<section><span><span>import</span> { OpenAI } <span>from</span> <span>"langchain/llms/openai"</span>;<br><br><span>const</span> model = <span>new</span> OpenAI({<br>  <span>maxTokens</span>: <span>25</span>,<br>});<br><br><span>const</span> stream = <span>await</span> model.stream(<span>"Tell me a joke."</span>);<br><br><span>for</span> <span>await</span> (<span>const</span> chunk <span>of</span> stream) {<br>  <span>console</span>.log(chunk);<br>}<br><br><span>/*<br><br>Q<br>:<br> What<br> did<br> the<br> fish<br> say<br> when<br> it<br> hit<br> the<br> wall<br>?<br><br>A<br>:<br> Dam<br>!<br>*/</span><br></span></section>

此外,所有的语言模型都实现了Runnable 接口,默认实现了invoke,batch,stream,map等方法, 提供了对调用、流式传输、批处理和映射请求的基本支持

嵌入 模型 和 大模型的区别

那么,前面说的嵌入 模型 和 大模型的区别是什么呢?

嵌入模型(Embedding Model)和大语言模型(Large Language Model, LLM)是自然语言处理(NLP)领域中的两类重要模型,它们在设计目的、功能和应用场景上有明显的区别。

以下是对嵌入模型和大语言模型的详细介绍和比较。

特性 嵌入模型 大语言模型
目标

|

将离散数据映射到低维向量空间

|

生成和理解自然语言文本

| |

表示形式

|

低维向量

|

大规模参数模型

| |

复杂度

|

较低

|

较高

| |

生成能力

|

|

| |

应用场景

|

相似度计算、分类、信息检索

|

对话系统、文本生成、问答系统

| |

训练数据

|

需要大量标注数据

|

需要大规模文本语料

|

嵌入模型和大语言模型各有其独特的优势和适用场景。嵌入模型擅长于将高维数据转换为低维向量表示,适用于相似度计算和分类任务。而大语言模型通过复杂的网络结构和大量参数,具备强大的自然语言生成和理解能力,适用于对话系统、文本生成和问答系统等复杂的NLP任务。两者的结合和应用,能够为自然语言处理领域提供更加全面和强大的解决方案。

嵌入 模型相比大语言模型的 fine-tuning 最大的优势就是,不用进行训练,并且可以实时添加新的内容,而不用加一次新的内容就训练一次,并且各方面成本要比 fine-tuning 低很多。

Basic Language Model基础语言模型有哪些?

基础语言模型是指只在大规模文本语料中进行了预训练的模型,未经过指令和下游任务微调、以及人类反馈等任何对齐优化。

图片

  • 当前绝大部分的大语言模型都是 Decoder-only 的模型结构,;

  • 大部分大语言模型都不开源,而 OPT、BLOOM、LLaMA 三个模型是主要面向开源促进研究和应用的,中文开源可用的是 GLM,后续很多工作都是在这些开源的基础模型上进行微调优化的。

2.2. Prompts(提示词)

前面说明了 嵌入 模型相比大语言模型 的区别。

再看看 聊天模型 相比 大语言模型 的区别。

聊天模型是 大语言模型 的一种变体。

聊天模型在底层使用大语言模型 ,或者说, 聊天模型是大语言模型 的应用层接口。

前面介绍了 LangChain 目前支持的 Chat Modals 聊天模型的4种 消息类型

除了使用 4种 消息类型 直接写 聊天模型 之外, LangChain 提供了 编写 chat 模型的新方式,就是通过提示词(prompt) 间接实现 chat Modals 。

什么是 提示词(prompt) ?

一个 提示词(prompt) 指的是输入模型的内容。

这个输入通常由多个组件构成。

LangChain 提供了多个类和函数,使构建和处理提示变得简单。

  • 提示词模板(Prompt templates): 为模型输入添加参数

  • 示例选择器(Example selectors): 动态选择在提示中包含的示例

2.2.1. Prompt Templates

LangChain 提供了 PromptTemplates,允许你可以根据用户输入动态地更改提示,如果你有编程基础,这应该对你来说很简单。

什么是提示词模板?

提示词模板是一种预定义的文本结构,其中包含变量和固定文本部分,用于引导语言模型生成特定类型的输出。这些模板可以帮助模型更准确地理解上下文,并生成符合预期的响应。

提示词模板的核心组件

  1. 固定文本部分:这是提示词模板中的静态部分,用于提供上下文或引导语言模型的生成方向。

  2. 变量部分:这是提示词模板中的动态部分,可以根据具体输入进行替换。这些变量通常用占位符表示。

当用户需要输入多个类似的 prompt 时,生成一个 prompt 模板是一个很好的解决方案,可以节省用户的时间和精力。

如何创建提示模板?

可以使用 PromptTemplate 类创建一个简单的硬编码提示。

提示模板可以接受任意数量的输入变量,并可以格式化生成提示。

<section><span><span>from</span> langchain <span>import</span> PromptTemplate<br><br><span># An example prompt with no input variables</span><br>no_input_prompt = PromptTemplate(input_variables=[], template=<span>"Tell me a joke."</span>)<br>no_input_prompt.format()<br><span># -&gt; "Tell me a joke."</span><br><br><span># An example prompt with one input variable</span><br>one_input_prompt = PromptTemplate(input_variables=[<span>"adjective"</span>], template=<span>"Tell me a {adjective} joke."</span>)<br>one_input_prompt.format(adjective=<span>"funny"</span>)<br><span># -&gt; "Tell me a funny joke."</span><br><br><span># An example prompt with multiple input variables</span><br>multiple_input_prompt = PromptTemplate(<br>    input_variables=[<span>"adjective"</span>, <span>"content"</span>], <br>    template=<span>"Tell me a {adjective} joke about {content}."</span><br>)<br>multiple_input_prompt.format(adjective=<span>"funny"</span>, content=<span>"chickens"</span>)<br><span># -&gt; "Tell me a funny joke about chickens."</span><br></span></section>

如果 不想手动指定 input_variables,也可以使用 from_template 类方法创建 PromptTemplateLangChain 将根据传递的 template 自动推断 input_variables

<section><span>template = <span>"Tell me a {adjective} joke about {content}."</span><br><br>prompt_template = PromptTemplate.from_template(template)<br>prompt_template.input_variables<br><span># -&gt; ['adjective', 'content']</span><br>prompt_template.format(adjective=<span>"funny"</span>, content=<span>"chickens"</span>)<br><span># -&gt; Tell me a funny joke about chickens.</span><br></span></section>

您可以创建自定义的提示模板,以任何您想要的方式格式化提示。有关更多信息,请参阅 自定义提示模板。

下面是一个示例,将 LLM 作为一个给新开商店命名的顾问,用户只需告诉 LLM 商店的主要特点,它将返回 10 个新开商店的名字。

2.2.2. PipelinePrompt 提示词模版组合

可以通过 PipelinePrompt将多个PromptTemplate提示模版进行组合,组合的优点是可以很方便的进行复用。

比如常见的系统角色提示词,一般都遵循以下结构:{introduction} {example} {start},比如一个【名人采访】角色的提示词:

图片

使用PipelinePrompt组合实现:

<section><span><span>import</span> { PromptTemplate, PipelinePromptTemplate } <span>from</span> <span>"langchain/prompts"</span>;<br><br><span>const</span> fullPrompt = PromptTemplate.fromTemplate(<span>`{introduction}<br><br>{example}<br><br>{start}`</span>);<br><br><span>const</span> introductionPrompt = PromptTemplate.fromTemplate(<br>  <span>`You are impersonating {person}.`</span><br>);<br><br><span>const</span> examplePrompt =<br>  PromptTemplate.fromTemplate(<span>`Here's an example of an interaction:<br>Q: {example_q}<br>A: {example_a}`</span>);<br><br><span>const</span> startPrompt = PromptTemplate.fromTemplate(<span>`Now, do this for real!<br>Q: {input}<br>A:`</span>);<br><br><span>const</span> composedPrompt = <span>new</span> PipelinePromptTemplate({<br>  <span>pipelinePrompts</span>: [<br>    {<br>      <span>name</span>: <span>"introduction"</span>,<br>      <span>prompt</span>: introductionPrompt,<br>    },<br>    {<br>      <span>name</span>: <span>"example"</span>,<br>      <span>prompt</span>: examplePrompt,<br>    },<br>    {<br>      <span>name</span>: <span>"start"</span>,<br>      <span>prompt</span>: startPrompt,<br>    },<br>  ],<br>  <span>finalPrompt</span>: fullPrompt,<br>});<br><br><span>const</span> formattedPrompt = <span>await</span> composedPrompt.format({<br>  <span>person</span>: <span>"Elon Musk"</span>,<br>  <span>example_q</span>: <span>`What's your favorite car?`</span>,<br>  <span>example_a</span>: <span>"Telsa"</span>,<br>  <span>input</span>: <span>`What's your favorite social media site?`</span>,<br>});<br><br><span>console</span>.log(formattedPrompt);<br><br><span>/*<br>  You are impersonating Elon Musk.<br><br>  Here's an example of an interaction:<br>  Q: What's your favorite car?<br>  A: Telsa<br><br>  Now, do this for real!<br>  Q: What's your favorite social media site?<br>  A:<br>*/</span><br></span></section>

提示词模板(Prompt Template)在自然语言处理和生成任务中,是设计和优化模型输入的一种方法。提示词模板可以帮助大型语言模型(LLM)更好地理解和生成目标内容。以下是提示词模板的介绍及其在不同场景中的应用。

2.2.3. Few-shot example(小样本提示)

Few-shot examples 是一组可用于帮助语言模型生成更好响应的示例。

要生成具有 few-shot examples 的 prompt,可以使用 FewShotPromptTemplate。

该类接受一个 PromptTemplate 和一组 few-shot examples。

然后,它使用这些 few-shot examples 格式化 prompt 模板。

看一个例子,需求是根据用户输入,让模型返回对应的反义词,我们要通过示例来告诉模型什么是反义词, 这就是 few-shot examples(小样本提示)。

<section><span><span>import</span> os<br>os.environ[<span>"OPENAI_API_KEY"</span>] = <span>'your apikey'</span><br><span>from</span> langchain <span>import</span> PromptTemplate, FewShotPromptTemplate<br><span>from</span> langchain.llms <span>import</span> OpenAI<br><br>examples = [<br>    {<span>"word"</span>: <span>"黑"</span>, <span>"antonym"</span>: <span>"白"</span>},<br>    {<span>"word"</span>: <span>"伤心"</span>, <span>"antonym"</span>: <span>"开心"</span>},<br>]<br><br>example_template = <span>"""<br>单词: {word}<br>反义词: {antonym}\\n<br>"""</span><br><br># 创建提示词模版<br>example_prompt = PromptTemplate(<br>    input_variables=[<span>"word"</span>, <span>"antonym"</span>],<br>    template=example_template,<br>)<br><br># 创建小样本提示词模版<br>few_shot_prompt = FewShotPromptTemplate(<br>    examples=examples,<br>    example_prompt=example_prompt,<br>    prefix=<span>"给出每个单词的反义词"</span>,<br>    suffix=<span>"单词: {input}\\n反义词:"</span>,<br>    input_variables=[<span>"input"</span>],<br>    example_separator=<span>"\\n"</span>,<br>)<br><br># 格式化小样本提示词<br>prompt_text = few_shot_prompt.format(input=<span>"粗"</span>)<br><br># 调用OpenAI<br>llm = OpenAI(temperature=<span>0.9</span>)<br><br>print(llm(prompt_text))<br></span></section>

2.2.4. Example Selector

如果你有大量的示例,则可以使用 ExampleSelector 来选择最有信息量的一些示例,以帮助你生成更可能产生良好响应的提示。

为了大模型能够给出相对精准的输出内容,通常会在prompt中提供一些示例描述,如果包含大量示例会浪费token数量,甚至可能会超过最大token限制。为此,LangChain提供了示例选择器,可以从用户提供的大量示例中,选择最合适的部分作为最终的prompt。

通常有2种方式:按长度选择和按相似度选择。

  • 按长度选择:对于较长的输入,它将选择较少的示例来;而对于较短的输入,它将选择更多的示例。

  • 按相似度选择:查找与输入具有最大余弦相似度的嵌入示例

接下来,我们将使用 LengthBasedExampleSelector,根据输入的长度选择示例。

当你担心构造的提示将超过上下文窗口的长度时,此方法非常有用。

对于较长的输入,它会选择包含较少示例的提示,而对于较短的输入,它会选择包含更多示例。

<section><span><span>import</span> os<br>os.environ[<span>"OPENAI_API_KEY"</span>] = <span>'your apikey'</span><br><span>from</span> langchain.prompts <span>import</span> PromptTemplate, FewShotPromptTemplate<br><span>from</span> langchain.prompts.example_selector <span>import</span> LengthBasedExampleSelector<br><span>from</span> langchain.prompts.example_selector <span>import</span> LengthBasedExampleSelector<br><br><br># These are a lot <span>of</span> examples <span>of</span> a pretend task <span>of</span> creating antonyms.<br>examples = [<br>    {<span>"word"</span>: <span>"happy"</span>, <span>"antonym"</span>: <span>"sad"</span>},<br>    {<span>"word"</span>: <span>"tall"</span>, <span>"antonym"</span>: <span>"short"</span>},<br>    {<span>"word"</span>: <span>"energetic"</span>, <span>"antonym"</span>: <span>"lethargic"</span>},<br>    {<span>"word"</span>: <span>"sunny"</span>, <span>"antonym"</span>: <span>"gloomy"</span>},<br>    {<span>"word"</span>: <span>"windy"</span>, <span>"antonym"</span>: <span>"calm"</span>},<br>]<br># 例子格式化模版<br>example_formatter_template = <span>"""<br>Word: {word}<br>Antonym: {antonym}\n<br>"""</span><br>example_prompt = PromptTemplate(<br>    input_variables=[<span>"word"</span>, <span>"antonym"</span>],<br>    template=example_formatter_template,<br>)<br><br># 使用 LengthBasedExampleSelector来选择例子<br>example_selector = LengthBasedExampleSelector(<br>    examples=examples,<br>    example_prompt=example_prompt,<br>    # 最大长度<br>    max_length=<span>25</span>,<br>)<br><br># 使用<span>'example_selector'</span>创建小样本提示词模版<br>dynamic_prompt = FewShotPromptTemplate(<br>    example_selector=example_selector,<br>    example_prompt=example_prompt,<br>    prefix=<span>"Give the antonym of every input"</span>,<br>    suffix=<span>"Word: {input}\nAntonym:"</span>,<br>    input_variables=[<span>"input"</span>],<br>    example_separator=<span>"\n\n"</span>,<br>)<br><br>longString = <span>"big and huge and massive and large and gigantic and tall and much much much much much bigger than everything else"</span><br><br>print(dynamic_prompt.format(input=longString))<br></span></section>

另外官方也提供了根据最大边际相关性、文法重叠、语义相似性来选择示例。

下面是一个按相似度选择的例子:查找与输入具有最大余弦相似度的嵌入示例

<section><span>...<br><span>// 定义相似度选择器</span><br><span>const</span> exampleSelector = await SemanticSimilarityExampleSelector<span>.fromExamples</span>(<br>  [<br>    { <span>input</span>: <span>"happy"</span>, <span>output</span>: <span>"sad"</span> },<br>    { <span>input</span>: <span>"tall"</span>, <span>output</span>: <span>"short"</span> },<br>    { <span>input</span>: <span>"energetic"</span>, <span>output</span>: <span>"lethargic"</span> },<br>    { <span>input</span>: <span>"sunny"</span>, <span>output</span>: <span>"gloomy"</span> },<br>    { <span>input</span>: <span>"windy"</span>, <span>output</span>: <span>"calm"</span> },<br>  ],<br>  <span>new</span> OpenAIEmbeddings(),<br>  HNSWLib,<br>  { k: <span>1</span> }<br>);<br>...<br><span>// 跟天气类相关的示例</span><br>console<span>.log</span>(await dynamicPrompt<span>.format</span>({ adjective: <span>"rainy"</span> }));<br><span>/*<br>  Give the antonym of every input<br><br>  Input: sunny<br>  Output: gloomy<br><br>  Input: rainy<br>  Output:<br>*/</span><br><span>// 跟尺寸相关的示例</span><br>console<span>.log</span>(await dynamicPrompt<span>.format</span>({ adjective: <span>"large"</span> }));<br><span>/*<br>  Give the antonym of every input<br><br>  Input: tall<br>  Output: short<br><br>  Input: large<br>  Output:<br>*/</span><br></span></section>

2.2.5. 聊天提示模板

前面介绍了 聊天模型 四种 消息`。 LangChain 目前支持的 Chat Modals 聊天模型的 消息类型有

  • AIMessage、

  • HumanMessage、

  • SystemMessage

  • hatMessage,

其中 ChatMessage 接受一个任意的角色参数。

这些聊天消息与原始字符串不同,因为每个消息都与一个 role 相关联。

例如,在 OpenAI 的 聊天补全 API 中,一个聊天消息可以与 AI、人类或系统角色相关联。模型应更密切地遵循系统聊天消息的指令。

LangChain 提供了几个聊天 消息 提示模板,以便更轻松地构建和处理提示。

在查询聊天模型时,建议您使用这些与聊天相关的提示模板,以充分发挥底层聊天模型的潜力。

<section><span><span>from</span> langchain.prompts <span>import</span> (<br>    ChatPromptTemplate,<br>    PromptTemplate,<br>    SystemMessagePromptTemplate,<br>    AIMessagePromptTemplate,<br>    HumanMessagePromptTemplate,<br>)<br><span>from</span> langchain.schema <span>import</span> (<br>    AIMessage,<br>    HumanMessage,<br>    SystemMessage<br>)<br></span></section>

要创建与角色相关联的消息模板,可以使用 MessagePromptTemplate

为了方便起见,模板上暴露了 from_template 方法。

如果您要使用此模板,它将如下所示:

<section><span>template=<span>"You are a helpful assistant that translates {input_language} to {output_language}."</span><br>system_message_prompt = SystemMessagePromptTemplate.from_template(template)<br>human_template=<span>"{text}"</span><br>human_message_prompt = HumanMessagePromptTemplate.from_template(human_template)<br></span></section>

如果您想更直接地构建 MessagePromptTemplate,您可以在外部创建一个 PromptTemplate,然后将其传递进去,例如:

<section><span>prompt=PromptTemplate(<br>    template=<span>"You are a helpful assistant that translates {input_language} to {output_language}."</span>,<br>    input_variables=[<span>"input_language"</span>, <span>"output_language"</span>],<br>)<br>system_message_prompt_2 = SystemMessagePromptTemplate(prompt=prompt)<br><br><span>assert</span> system_message_prompt == system_message_prompt_2<br></span></section>

之后,您可以从一个或多个 MessagePromptTemplates 构建一个 ChatPromptTemplate

您可以使用 ChatPromptTemplate 的 format_prompt 方法 - 这将返回一个 PromptValue,您可以将其转换为字符串或 Message 对象,具体取决于您是否希望将格式化的值用作 llm 或 chat 模型的输入。

<section><span>chat_prompt = ChatPromptTemplate.from_messages([system_message_prompt, human_message_prompt])<br><br><span># get a chat completion from the formatted messages</span><br>chat_prompt.format_prompt(input_language=<span>"English"</span>, output_language=<span>"French"</span>, text=<span>"I love programming."</span>).to_messages()<br></span></section>

2.3. Indexes (RAG 索引)

原始的大语言模型存在幻觉等问题,一些LLM应用通常需要特定的外部数据(外挂知识库)做增强,这些数据不属于模型训练集的一部分。

可以通过检索增强生成(RAG)的方式,检索外部数据(外挂知识库),然后在执行生成步骤时,将其传递给 LLM。

图片

索引是指对外挂知识库文档进行 索引的方法,以便 LLM 能够更好的与之交互。

LangChain 提供了 RAG 应用程序的所有构建模块,包含以下几个关键模块:

  • Document Loaders(文档加载器)、

  • Text Splitters(文本拆分器)、

  • VectorStores(向量存储器)

  • Retrievers(检索器)。

2.3.1. Document Loaders

Document loaders可以从各种数据源加载文档。

Document loaders将特定格式的数据,转换为文本。如 CSV、File Directory、HTML、JSON、Markdown、PDF等。

LangChain 提供了许多不同的文档加载器以及与对应的第三方集成工具。下图中,黄色颜色代表Loaders对应的npm第三方依赖库。

图片

Document Loaders 返回的文档对象格式如下:

<section><span><span>interface</span> Document {<br>  pageContent: <span>string</span>;<br>  metadata: Record&lt;<span>string</span>, <span>any</span>&gt;;<br>}<br></span></section>

另外使用相关接口处理本地知识,或者在线知识。如 AirbyteJSON Airtable、Alibaba Cloud MaxCompute、wikipedia、BiliBili、GitHub、GitBook 等等。

2.3.2. Document Transformers

加载文档后,通常需要进行数据处理,比如:将长文档分割成更小的块、过滤不需要的HTML标签以及结构化处理等。

LangChain提供了许多内置的文档转换器,可以轻松地拆分、组合、过滤和以其他方式操作文档。

图片

其中:

  • CharacterTextSplitter 它按照指定的分隔符(默认“\n\n”)进行分割,并且考虑文本片段的最大长度。当处理大量任意文档集合时,简单的文本分割可能会出现重叠文本的文档,CharacterTextSplitter可以用元数据标记文档,从而解决矛盾来源的信息等问题。

  • RecursiveCharacterTextSplitter 除了可以按指定分隔符进行分割外,还支持根据特定于语言的语法分割文本,比如:JavaScript、Python、Solidity 和 Rust 等流行语言,以及 Latex、HTML 和 Markdown。

  • 当提取 HTML 文档以供以后检索时,我们通常只对网页的实际内容而不是语义感兴趣。HtmlToTextTransformer和MozillaReadabilityTransformer都可以从文档中剥离HTML标签,从而使检索更加有效

  • MetadataTagger转换器可以自动从文档中提取元数据,以便以后进行更有针对性的相似性搜索。

CharacterTextSplitter 例子

由于模型对输入的字符长度有限制,我们在碰到很长的文本时,需要把文本分割成多个小的文本片段。

文本分割最简单的方式是按照字符长度进行分割,但是这会带来很多问题,比如说如果文本是一段代码,一个函数被分割到两段之后就成了没有意义的字符,所以整体的原则是把语义相关的文本片段放在一起。

LangChain 中最基本的文本分割器是 CharacterTextSplitter ,它按照指定的分隔符(默认“\n\n”)进行分割,并且考虑文本片段的最大长度。我们看个例子:

<section><span><span>from</span> langchain.text_splitter <span>import</span> CharacterTextSplitter<br><br># 初始字符串<br>state_of_the_union = <span>"..."</span><br><br>text_splitter = CharacterTextSplitter(<br>    separator = <span>"\\n\\n"</span>,<br>    chunk_size = <span>1000</span>,<br>    chunk_overlap  = <span>200</span>,<br>    length_function = len,<br>)<br><br>texts = text_splitter.create_documents([state_of_the_union])<br></span></section>

除了CharacterTextSplitter 之外,LangChain 多个高级文本分割器,如下:

图片

RecursiveCharacterTextSplitter 示例

下面是一个 RecursiveCharacterTextSplitter 示例:

<section><span><span>import</span> { RecursiveCharacterTextSplitter } <span>from</span> <span>"langchain/text_splitter"</span>;<br><br><span>const</span> text = <span>`Hi.\n\nI'm Harrison.\n\nHow? Are? You?\nOkay then f f f f.<br>This is a weird text to write, but gotta test the splittingggg some how.\n\n<br>Bye!\n\n-H.`</span>;<br><span>const</span> splitter = <span>new</span> RecursiveCharacterTextSplitter({<br>  <span>separators</span>: [<span>"\n\n"</span>, <span>"\n"</span>, <span>" "</span>, <span>""</span>], <span>//默认分隔符</span><br>  <span>chunkSize</span>: <span>1000</span>, <span>//最终文档的最大大小(以字符数计),默认1000</span><br>  <span>chunkOverlap</span>: <span>200</span>, <span>//块之间应该有多少重叠,默认200</span><br>});<br><br><span>const</span> output = <span>await</span> splitter.createDocuments([text]);<br></span></section>

2.3.3. Embedding Models

嵌入模型(Text embedding models)是,一个用于创建文本数据的数值表示的模型。

文本要进行相似查询和检索,需要用到 嵌入模型(Text embedding models)。

嵌入模型(Text embedding models)可以将文本转换为向量表示,这种 向量表示 就可以存储到 向量数据库,也可以从 向量库中进行 相似查询。

嵌入 模型其实就是把文本等内容转成多维数组,可以后续进行相似性的计算和检索。

嵌入 模型更多的是用于文档、文本或者大量数据的总结、问答场景,一般是和向量库一起使用,而向量库用于实现向量的存储和匹配。

下图是 LangChain 两种语言包支持的 embeddings 模型。

图片

嵌入模型的主要目的是捕捉输入数据中的语义或特征信息,使得相似的输入在嵌入空间中距离更近。

一个OpenAI的嵌入示例:通常要结合文档(Document)和向量存储(Vector stores)一起使用。

<section><span><span>import</span> { OpenAIEmbeddings } <span>from</span> <span>"langchain/embeddings/openai"</span>;<br><br><span>/* Create instance */</span><br><span>const</span> embeddings = <span>new</span> OpenAIEmbeddings();<br><br><span>/* Embed queries */</span><br><span>const</span> res = <span>await</span> embeddings.embedQuery(<span>"Hello world"</span>);<br><span>/*<br>[<br>   -0.004845875,   0.004899438,  -0.016358767,  -0.024475135, -0.017341806,<br>    0.012571548,  -0.019156644,   0.009036391,  -0.010227379, -0.026945334,<br>    0.022861943,   0.010321903,  -0.023479493, -0.0066544134,  0.007977734,<br>  ... 1436 more items<br>]<br>*/</span><br><br><span>/* Embed documents */</span><br><span>const</span> documentRes = <span>await</span> embeddings.embedDocuments([<span>"Hello world"</span>, <span>"Bye bye"</span>]);<br><span>/*<br>[<br>  [<br>    -0.0047852774,  0.0048640342,   -0.01645707,  -0.024395779, -0.017263541,<br>      0.012512918,  -0.019191515,   0.009053908,  -0.010213212, -0.026890801,<br>      0.022883644,   0.010251015,  -0.023589306,  -0.006584088,  0.007989113,<br>    ... 1436 more items<br>  ],<br>  [<br>      -0.009446913,  -0.013253193,   0.013174579,  0.0057552797,  -0.038993083,<br>      0.0077763423,    -0.0260478, -0.0114384955, -0.0022683728,  -0.016509168,<br>      0.041797023,    0.01787183,    0.00552271, -0.0049789557,   0.018146982,<br>    ... 1436 more items<br>  ]<br>]<br>*/</span><br></span></section>

2.3.4. VectorStores

存储提取的文本向量,包括 Faiss、Milvus、Pinecone、Chroma 等。

如下是 LangChain 集成的向量数据库。

图片

示例:读取本地文档,创建MemoryVectorStore和检索

<section><span><span>import</span> { MemoryVectorStore } <span>from</span> <span>"langchain/vectorstores/memory"</span>;<br><span>import</span> { OpenAIEmbeddings } <span>from</span> <span>"langchain/embeddings/openai"</span>;<br><span>import</span> { TextLoader } <span>from</span> <span>"langchain/document_loaders/fs/text"</span>;<br><br><span>// Create docs with a loader</span><br><span>const</span> loader = <span>new</span> TextLoader(<span>"src/document_loaders/example_data/example.txt"</span>);<br><span>const</span> docs = <span>await</span> loader.load();<br><br><span>// Load the docs into the vector store</span><br><span>const</span> vectorStore = <span>await</span> MemoryVectorStore.fromDocuments(<br>  docs,<br>  <span>new</span> OpenAIEmbeddings()<br>);<br><br><span>// Search for the most similar document</span><br><span>const</span> resultOne = <span>await</span> vectorStore.similaritySearch(<span>"hello world"</span>, <span>1</span>);<br><br><span>console</span>.log(resultOne);<br><br><span>/*<br>  [<br>    Document {<br>      pageContent: "Hello world",<br>      metadata: { id: 2 }<br>    }<br>  ]<br>*/</span><br></span></section>

2.3.5. Retrievers

数据进入数据库,仍然需要检索,LangChain支持多种检索算法。

LangChain支持易于上手的基本方法——即简单语义搜索。

检索器是一种便于模型查询的存储数据的方式,LangChain 约定检索器组件至少有一个方法 get_relevant_texts,这个方法接收查询字符串,返回一组文档。

除了简单语义搜索,LangChain还在此基础上添加了一系列算法以提高性能。这些包括:

  • 父文档检索器(Parent Document Retriever):允许为每个父文档创建多个嵌入,从而允许查找较小的块但返回更大的上下文

  • 自查询检索器(Self Query Retriever):用户问题通常包含对某些内容的引用,这些内容不仅是语义的,而且表达了一些可以最好地表示为元数据过滤器的逻辑。自查询允许从查询中存在的其他元数据过滤器解析出查询的语义部分。

  • Ensemble Retriever:可以更容易的从多个不同的来源检索文档或使用多种不同的算法。

检索器是一个接口,它根据非结构化查询返回文档。

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

注意,检索器的接口并不是 向量,而是非结构化的文档, 所以,检索器底层并不限于向量检索, 也可以是普通的ES查询。

当然,向量存储可以用作检索器的核心存储,但也有其他类型的存储, 比如ES倒排索引。

检索器(Retriever)是一个接口:根据非结构化查询返回文档。

Retriever比Vector Store更通用,创建Vector Store后,将其用作检索器的方法非常简单:

<section><span>...<br>retriever = vectorStore.asRetriever()<br></span></section>

此外,LangChain还提供了他类型的检索器,比如:

  • ContextualCompressionRetriever:用给定查询的上下文来压缩它们,以便只返回相关信息,而不是立即按原样返回检索到的文档,同时还可以减少token数量。

  • MultiQueryRetriever:从不同角度为给定的用户输入查询生成多个查询。

  • ParentDocumentRetriever:在检索过程中,它首先获取小块,然后查找这些块的父 ID,并返回那些较大的文档。

  • SelfQueryRetriever:一种能够查询自身的检索器。

  • VespaRetriever:从Vespa.ai数据存储中检索文档。

针对不同的需求场景,可能需要对应的合适的检索器。以下是一个根据通过计算相似度分值检索的示例:

<section><span><span>import</span> { MemoryVectorStore } <span>from</span> <span>"langchain/vectorstores/memory"</span>;<br><span>import</span> { OpenAIEmbeddings } <span>from</span> <span>"langchain/embeddings/openai"</span>;<br><span>import</span> { ScoreThresholdRetriever } <span>from</span> <span>"langchain/retrievers/score_threshold"</span>;<br><br><span>const</span> vectorStore = <span>await</span> MemoryVectorStore.fromTexts(<br>  [<br>    <span>"Buildings are made out of brick"</span>,<br>    <span>"Buildings are made out of wood"</span>,<br>    <span>"Buildings are made out of stone"</span>,<br>    <span>"Buildings are made out of atoms"</span>,<br>    <span>"Buildings are made out of building materials"</span>,<br>    <span>"Cars are made out of metal"</span>,<br>    <span>"Cars are made out of plastic"</span>,<br>  ],<br>  [{ <span>id</span>: <span>1</span> }, { <span>id</span>: <span>2</span> }, { <span>id</span>: <span>3</span> }, { <span>id</span>: <span>4</span> }, { <span>id</span>: <span>5</span> }],<br>  <span>new</span> OpenAIEmbeddings()<br>);<br><br><span>const</span> retriever = ScoreThresholdRetriever.fromVectorStore(vectorStore, {<br>  <span>minSimilarityScore</span>: <span>0.9</span>, <span>// Finds results with at least this similarity score</span><br>  <span>maxK</span>: <span>100</span>, <span>// The maximum K value to use. Use it based to your chunk size to make sure you don't run out of tokens</span><br>  <span>kIncrement</span>: <span>2</span>, <span>// How much to increase K by each time. It'll fetch N results, then N + kIncrement, then N + kIncrement * 2, etc.</span><br>});<br><br><span>const</span> result = <span>await</span> retriever.getRelevantDocuments(<br>  <span>"What are buildings made out of?"</span><br>);<br><br><span>console</span>.log(result);<br><br><span>/*<br>  [<br>    Document {<br>      pageContent: 'Buildings are made out of building materials',<br>      metadata: { id: 5 }<br>    },<br>    Document {<br>      pageContent: 'Buildings are made out of wood',<br>      metadata: { id: 2 }<br>    },<br>    Document {<br>      pageContent: 'Buildings are made out of brick',<br>      metadata: { id: 1 }<br>    },<br>    Document {<br>      pageContent: 'Buildings are made out of stone',<br>      metadata: { id: 3 }<br>    },<br>    Document {<br>      pageContent: 'Buildings are made out of atoms',<br>      metadata: { id: 4 }<br>    }<br>  ]<br>*/</span><br></span></section>

2.4. Chains(链)

链允许我们将多个组件组合在一起以创建一个单一的、连贯的任务。另外我们也可以通过将多个链组合在一起,或者将链与其他组件组合来构建更复杂的链。

“Chains”(链)概念用于描述一系列任务和操作的组合,通过这些任务和操作,LLM可以实现更复杂的功能和工作流程。

LangChain 框架就是一个典型的工具,旨在将多个任务串联起来,以便构建复杂且功能强大的应用程序。

LLM Chains 是一系列由多个步骤(或任务)组成的流程,这些步骤通常包括数据预处理、模型推理、后处理、外部 API 调用、数据库查询等。

通过将这些步骤串联起来,开发者可以创建复杂的工作流程,使 LLM 能够执行更高级和复杂的任务。

LLM Chains 的核心组件,大致如下:

  1. 任务(Tasks): 每个任务是链中的一个独立步骤,可以是数据预处理、模型推理、结果生成等。

  2. 连接器(Connectors): 用于将任务串联起来,使得每个任务的输出可以作为下一个任务的输入。

  3. 控制流(Control Flow): 决定任务的执行顺序和条件,包括分支、循环等控制结构。

LLM Chains 可以应用于多种复杂场景,如多步骤问答系统、数据分析、自动化流程、内容生成等。

LLM Chains 的设计原则,具体如下:

  1. 模块化: 每个任务应该是独立的模块,便于复用和维护。

  2. 灵活性: 设计链时应考虑到不同应用场景的需求,确保可以灵活调整任务顺序和内容。

  3. 扩展性: 链应能够轻松扩展,以添加新的任务或修改现有任务。

  4. 可维护性: 通过清晰的接口和文档,确保链的维护和更新过程简便。

开发者通过LLM Chains 串联多个任务,开发者可以实现比单一步骤更强大的功能和工作流程。

LangChain 框架为 LLM Chains 提供了丰富的组件和工具,使得构建和管理这些复杂流程变得更加高效和灵活。无论是在问答系统、数据分析、内容生成等应用场景中,LLM Chains 都能显著提升应用的智能性和实用性。

2.4.1. 简单的链 LLMChain

例如,我们可以创建一个链,它接受用户输入,使用 PromptTemplate 对其进行格式化,然后将格式化的响应传递给 LLM。

LLMChain 是一个简单的链,它围绕语言模型添加了一些功能。它在整个 LangChain 中广泛使用,包括在其他链和代理中。

LLMChain 接受一个提示模板,将其与用户输入进行格式化,并返回 LLM 的响应。

<section><span><span>from</span> langchain <span>import</span> PromptTemplate, OpenAI, LLMChain<br><br>prompt_template = <span>"What is a good name for a company that makes {product}?"</span><br><br>llm = OpenAI(temperature=<span>0</span>)<br>llm_chain = LLMChain(<br>    llm=llm,<br>    prompt=PromptTemplate.from_template(prompt_template)<br>)<br>llm_chain(<span>"colorful socks"</span>)<br></span></section>

使用ChainVS不使用Chain的代码对比

先看看:未使用Chain场景,代码的组织方式。

当未使用Chain时,Model I/O的实现分为两个部分:

  • 提示模板的构建

  • 模型的调用

两部分独立的装配和处理, 参考代码如下

<section><span><span># 导入LangChain中的提示模板</span><br><span>from</span> langchain_core.prompts <span>import</span> PromptTemplate<br><br><span># 原始字符串模板</span><br>template = <span>"猪八戒吃{fruit}?"</span><br><span># 创建LangChain模板</span><br>prompt_temp = PromptTemplate.from_template(template)<br><span># 根据模板创建提示</span><br>prompt = prompt_temp.format(fruit=<span>'人参果'</span>)<br><br><span># 导入LangChain中的OpenAI模型接口</span><br><span>from</span> langchain_openai <span>import</span> OpenAI<br><br><span># 创建模型实例</span><br>model = OpenAI(temperature=<span>0</span>)<br><span># 传入提示,调用模型返回结果</span><br>result = model.invoke(prompt)<br>print(result)<br><br></span></section>

执行结果如下:

<section><br><span>猪八戒是《西游记》中的一个角色,他是一只贪吃懒惰的猪妖,平时喜欢吃各种美食。在《西游记》中,猪八戒曾经吃过人参果,但并不是真的吃了人参果,而是被唐僧误认为是人参果而被骗吃了。事实上,人参果是一种神奇的果实,可以让人长生不老,但是只有在特定的条件下才能生长,非常稀少。因此,猪八戒并没有真的吃到人参果。<br><br></span></section>

再看看:使用Chain场景,代码的组织方式。

<section><span><span>from</span> langchain.chains.llm <span>import</span> LLMChain<br><span>from</span> langchain_core.prompts <span>import</span> PromptTemplate<br><span>from</span> langchain_openai <span>import</span> OpenAI<br><br><span># 原始字符串模板</span><br>template = <span>"猪八戒吃{fruit}?"</span><br><span># 创建模型实例</span><br>llm = OpenAI(temperature=<span>0</span>)<br><span># 创建LLMChain</span><br>llm_chain = LLMChain(<br>    llm=llm,<br>    prompt=PromptTemplate.from_template(template))<br><span># 调用LLMChain,返回结果</span><br>result = llm_chain.invoke({<span>"fruit"</span>: <span>"人参果"</span>})<br>print(result)<br><br></span></section>

当使用Chain链时,代码结构则更简洁。

<section><br><span>{'fruit': '人参果', 'text': '\n\n猪八戒是《西游记》中的一个角色,他是一只贪吃懒惰的猪妖,平时喜欢吃各种美食。在《西游记》中,猪八戒曾经吃过人参果,但并不是真的吃了人参果,而是被唐僧误认为是人参果而被骗吃了。事实上,人参果是一种神奇的果实,可以让人长生不老,但是只有在特定的条件下才能生长,非常稀少。因此,猪八戒并没有真的吃到人参果。'}<br><br></span></section>

2.4.2. LLMChain 的调用方法

1. 直接调用链对象

可以直接调用链对象,实际调用对象内部实现的__call__方法。

在新、高版本中不推荐使用且将被弃用。

注意:当像函数一样调用一个对象时,它实际上会调用该对象内部实现的__call__方法。

<section><span><span># 创建LLMChain</span><br>llm_chain = LLMChain(llm=llm, prompt=template)<br><span># 调用LLMChain,返回结果</span><br>result = llm_chain({<span>"role"</span>: <span>"猪八戒"</span>, <span>"fruit"</span>: <span>"人参果"</span>})<br>print(result)<br><br></span></section>

2. 通过run方法

通过run方法,返回的是字符串而不是字典。

run 等价于直接调用_call_函数。

<section><span><span># 创建LLMChain</span><br>llm_chain = LLMChain(llm=llm, prompt=template)<br><span># 调用LLMChain,返回结果</span><br>result = llm_chain.run({<span>"role"</span>: <span>"猪八戒"</span>, <span>"fruit"</span>: <span>"人参果"</span>})<br>print(result)<br></span></section>

run在新、高版本中不推荐使用且将被弃用。

3. 通过invoke方法

通过invoke方法,在调用链的时候,传入一个字典参数。

在新、高版本中推荐使用。

<section><span><span>from</span> langchain.chains.llm <span>import</span> LLMChain<br><span>from</span> langchain_core.prompts <span>import</span> PromptTemplate<br><span>from</span> langchain_openai <span>import</span> OpenAI<br><br><span># 创建模型实例</span><br>template = PromptTemplate(<br>    input_variables=[<span>"role"</span>, <span>"fruit"</span>],<br>    template=<span>"{role}喜欢吃{fruit}?"</span>,<br>)<br><span># 创建LLM</span><br>llm = OpenAI(temperature=<span>0</span>)<br><span># 创建LLMChain</span><br>llm_chain = LLMChain(llm=llm, prompt=template)<br><span># 调用LLMChain,返回结果</span><br><span># 如果提示模板中包含多个变量,在调用链的时候,可以使用字典一次性输入它们。</span><br>result = llm_chain.invoke({<span>"role"</span>: <span>"猪八戒"</span>, <span>"fruit"</span>: <span>"人参果"</span>})<br>print(result)<br><br></span></section>
<section><br><span>{'role': '猪八戒', 'fruit': '人参果', 'text': '\n\n猪八戒是一只贪吃懒惰的猪妖,他最喜欢吃的是猪食。人参果是唐僧师徒在取经途中遇到的一种神奇的水果,具有延年益寿的功效,但并非猪八戒的最爱。'}<br><br></span></section>

4. 通过predict方法

通过predict方法,将输入键指定为关键字参数

<section><span><span>from</span> langchain.chains.llm <span>import</span> LLMChain<br><span>from</span> langchain_core.prompts <span>import</span> PromptTemplate<br><span>from</span> langchain_openai <span>import</span> OpenAI<br><br><span># 创建模型实例</span><br>template = PromptTemplate(<br>    input_variables=[<span>"role"</span>, <span>"fruit"</span>],<br>    template=<span>"{role}喜欢吃{fruit}?"</span>,<br>)<br><span># 创建LLM</span><br>llm = OpenAI(temperature=<span>0</span>)<br><span># 创建LLMChain</span><br>llm_chain = LLMChain(llm=llm, prompt=template)<br><span># 调用LLMChain,返回结果</span><br>result = llm_chain.predict(role=<span>"猪八戒"</span>, fruit=<span>"人参果"</span>)<br>print(result)<br><br></span></section>

5. 通过apply方法

apply方法允许输入列表运行链,一次处理多个输入。

下面是一个简单的案例

<section><span>input_list = [<br>    {<span>"product"</span>: <span>"socks"</span>},<br>    {<span>"product"</span>: <span>"computer"</span>},<br>    {<span>"product"</span>: <span>"shoes"</span>}<br>]<br><br>llm_chain.apply(input_list)<br></span></section>
<section><span>    [{<span>'text'</span>: <span>'\n\nSocktastic!'</span>},<br>     {<span>'text'</span>: <span>'\n\nTechCore Solutions.'</span>},<br>     {<span>'text'</span>: <span>'\n\nFootwear Factory.'</span>}]<br></span></section>

apply方法允许你对一个输入列表进行调用,下面是一个复杂的案例

<section><span><span>from</span> langchain.chains.llm <span>import</span> LLMChain<br><span>from</span> langchain_core.prompts <span>import</span> PromptTemplate<br><span>from</span> langchain_openai <span>import</span> OpenAI<br><br><span># 创建模型实例</span><br>template = PromptTemplate(<br>    input_variables=[<span>"role"</span>, <span>"fruit"</span>],<br>    template=<span>"{role}喜欢吃{fruit}?"</span>,<br>)<br><span># 创建LLM</span><br>llm = OpenAI(temperature=<span>0</span>)<br><span># 创建LLMChain</span><br>llm_chain = LLMChain(llm=llm, prompt=template)<br><span># 输入列表</span><br>input_list = [<br>    {<span>"role"</span>: <span>"猪八戒"</span>, <span>"fruit"</span>: <span>"人参果"</span>}, {<span>"role"</span>: <span>"孙悟空"</span>, <span>"fruit"</span>: <span>"仙桃"</span>}<br>]<br><span># 调用LLMChain,返回结果</span><br>result = llm_chain.apply(input_list)<br>print(result)<br><br></span></section>
<section><span>[{'text': '\n\n猪八戒是一个贪吃的角色,他喜欢吃各种美味的食物,包括人参果。在《西游记》中,猪八戒曾经在取经路上遇到过人参果树,他非常贪婪地摘下来吃,结果被孙悟空和唐僧发现并教训。虽然人参果具有补气养血的功效,但是对于猪八戒来说,它更像是一种美味的水果,他并不在意它的药用价值。因此,可以说猪八戒是喜欢吃人参果的。'}, {'text': '\n\n是的,孙悟空非常喜欢吃仙桃。在《西游记》中,他经常会偷吃仙桃,甚至为了吃仙桃而闹出许多笑话和故事。仙桃也是孙悟空的最爱,因为它们具有神奇的功效,可以让他长生不老。'}]<br><br></span></section>

6. 通过generate方法

generate方法类似于apply,但它返回一个LLMResult对象,而不是字符串。

LLMResult通常包含了在模型生成文本过程中的一些相关信息,例如令牌数量、模型名称等。

<section><span><span>from</span> langchain.chains.llm <span>import</span> LLMChain<br><span>from</span> langchain_core.prompts <span>import</span> PromptTemplate<br><span>from</span> langchain_openai <span>import</span> OpenAI<br><br><span># 创建模型实例</span><br>template = PromptTemplate(<br>    input_variables=[<span>"role"</span>, <span>"fruit"</span>],<br>    template=<span>"{role}喜欢吃{fruit}?"</span>,<br>)<br><span># 创建LLM</span><br>llm = OpenAI(temperature=<span>0</span>)<br><span># 创建LLMChain</span><br>llm_chain = LLMChain(llm=llm, prompt=template)<br>input_list = [<br>    {<span>"role"</span>: <span>"猪八戒"</span>, <span>"fruit"</span>: <span>"人参果"</span>}, {<span>"role"</span>: <span>"孙悟空"</span>, <span>"fruit"</span>: <span>"仙桃"</span>}<br>]<br><span># 调用LLMChain,返回结果</span><br>result = llm_chain.generate(input_list)<br>print(result)<br><br></span></section>
<section><br><span>generations=[[Generation(text='\n\n猪八戒是一个贪吃的角色,他喜欢吃各种美味的食物,包括人参果。在《西游记》中,猪八戒曾经在取经路上遇到过人参果树,他非常贪婪地摘下来吃,结果被孙悟空和唐僧发现并责备他。虽然人参果具有补气养血的功效,但是对于猪八戒来说,它更像是一种美味的水果,他并不在意它的药用价值。因此可以说,猪八戒确实喜欢吃人参果。', generation_info={'finish_reason': 'stop', 'logprobs': None})], [Generation(text='\n\n是的,孙悟空非常喜欢吃仙桃。在《西游记》中,他经常会偷吃仙桃,甚至为了吃仙桃而闹出许多笑话和故事。仙桃也是孙悟空的最爱,因为它们具有神奇的功效,可以让他长生不老。', generation_info={'finish_reason': 'stop', 'logprobs': None})]] llm_output={'token_usage': {'completion_tokens': 309, 'total_tokens': 343, 'prompt_tokens': 34}, 'model_name': 'gpt-3.5-turbo-instruct'} run=[RunInfo(run_id=UUID('1354a070-6820-4005-9436-af859e65ebc3')), RunInfo(run_id=UUID('7d5d6633-b569-487f-ae18-24a3f4ac21db'))]<br><br></span></section>

2.4.3. 简单顺序链 SimpleSequentialChain

顺序链的最简单形式,其中每个步骤都有一个单一的输入/输出,并且一个步骤的输出是下一步的输入。

图片

如下就是将两个 LLMChain 进行组合成顺序链进行调用的案例。

<section><span><span>from</span> langchain.llms <span>import</span> OpenAI<br><span>from</span> langchain.chains <span>import</span> LLMChain<br><span>from</span> langchain.prompts <span>import</span> PromptTemplate<br><span>from</span> langchain.chains <span>import</span> SimpleSequentialChain<br><br># 定义第一个chain<br>llm = OpenAI(temperature=<span>.7</span>)<br>template = <span>"""You are a playwright. Given the title of play, it is your job to write a synopsis for that title.<br><br>Title: {title}<br>Playwright: This is a synopsis for the above play:"""</span><br>prompt_template = PromptTemplate(input_variables=[<span>"title"</span>], template=template)<br>synopsis_chain = LLMChain(llm=llm, prompt=prompt_template)<br><br># 定义第二个chain<br><br>llm = OpenAI(temperature=<span>.7</span>)<br>template = <span>"""You are a play critic from the New York Times. Given the synopsis of play, it is your job to write a review for that play.<br><br>Play Synopsis:<br>{synopsis}<br>Review from a New York Times play critic of the above play:"""</span><br>prompt_template = PromptTemplate(input_variables=[<span>"synopsis"</span>], template=template)<br>review_chain = LLMChain(llm=llm, prompt=prompt_template)<br><br># 通过简单顺序链组合两个LLMChain<br>overall_chain = SimpleSequentialChain(chains=[synopsis_chain, review_chain], verbose=True)<br><br># 执行顺序链<br>review = overall_chain.run(<span>"Tragedy at sunset on the beach"</span>)<br></span></section>

2.4.4. 通用顺序链 SequentialChain

相比 SimpleSequentialChain 只允许有单个输入输出,它是一种更通用的顺序链形式,允许多个输入/输出。

特别重要的是: 我们如何命名输入/输出变量名称。

在上面的示例中,我们不必考虑这一点,因为我们只是将一个链的输出直接作为输入传递给下一个链,但在这里我们确实需要担心这一点,因为我们有多个输入。

第一个 LLMChain:

<section><span># 这是一个 LLMChain,根据戏剧的标题和设定的时代,生成一个简介。<br>llm = OpenAI(temperature=<span>.7</span>)<br>template = <span>"""You are a playwright. Given the title of play and the era it is set in, it is your job to write a synopsis for that title.<br># 这里定义了两个输入变量title和era,并定义一个输出变量:synopsis<br>Title: {title}<br>Era: {era}<br>Playwright: This is a synopsis for the above play:"""</span><br>prompt_template = PromptTemplate(input_variables=[<span>"title"</span>, <span>"era"</span>], template=template)<br>synopsis_chain = LLMChain(llm=llm, prompt=prompt_template, output_key=<span>"synopsis"</span>)<br></span></section>

第二个 LLMChain:

<section><span># 这是一个 LLMChain,根据剧情简介撰写一篇戏剧评论。<br>llm = OpenAI(temperature=<span>.7</span>)<br>template = <span>"""You are a play critic from the New York Times. Given the synopsis of play, it is your job to write a review for that play.<br># 定义了一个输入变量:synopsis,输出变量:review<br>Play Synopsis:<br>{synopsis}<br>Review from a New York Times play critic of the above play:"""</span><br>prompt_template = PromptTemplate(input_variables=[<span>"synopsis"</span>], template=template)<br>review_chain = LLMChain(llm=llm, prompt=prompt_template, output_key=<span>"review"</span>)<br></span></section>

执行顺序链:

<section><span>overall_chain({<span>"title"</span>:<span>"Tragedy at sunset on the beach"</span>, <span>"era"</span>: <span>"Victorian England"</span>})<br></span></section>

执行结果,可以看到会把每一步的输出都能打印出来。

<section><span>    &gt; Entering <span>new</span> SequentialChain chain...<br><br>    &gt; Finished chain.<br><br>    {<span>'title'</span>: <span>'Tragedy at sunset on the beach'</span>,<br>     <span>'era'</span>: <span>'Victorian England'</span>,<br>     <span>'synopsis'</span>: <span>"xxxxxx"</span>,<br>     <span>'review'</span>: <span>"xxxxxxx"</span>}<br></span></section>

2.4.5. 转换链TransformChain

转换链允许我们创建一个自定义的转换函数来处理输入,将处理后的结果用作下一个链的输入。

如下示例我们将创建一个转换函数,它接受超长文本,将文本过滤为仅前 3 段,然后将其传递到 LLMChain 中以总结这些内容。

<section><span><span>from</span> langchain.chains <span>import</span> TransformChain, LLMChain, SimpleSequentialChain<br><span>from</span> langchain.llms <span>import</span> OpenAI<br><span>from</span> langchain.prompts <span>import</span> PromptTemplate<br><br># 模拟超长文本<br><span>with</span> open(<span>"../../state_of_the_union.txt"</span>) <span>as</span> f:<br>    state_of_the_union = f.read()<br><br># 定义转换方法,入参和出参都是字典,取前三段<br>def transform_func(inputs: dict) -&gt; dict:<br>    text = inputs[<span>"text"</span>]<br>    shortened_text = <span>"\n\n"</span>.join(text.split(<span>"\n\n"</span>)[:<span>3</span>])<br>    <span>return</span> {<span>"output_text"</span>: shortened_text}<br><br># 转换链:输入变量:text,输出变量:output_text<br>transform_chain = TransformChain(<br>    input_variables=[<span>"text"</span>], output_variables=[<span>"output_text"</span>], transform=transform_func<br>)<br># prompt模板描述<br>template = <span>"""Summarize this text:<br><br>{output_text}<br><br>Summary:"""</span><br># prompt模板<br>prompt = PromptTemplate(input_variables=[<span>"output_text"</span>], template=template)<br># llm链<br>llm_chain = LLMChain(llm=OpenAI(), prompt=prompt)<br># 使用顺序链<br>sequential_chain = SimpleSequentialChain(chains=[transform_chain, llm_chain])<br># 开始执行<br>sequential_chain.run(state_of_the_union)<br># 结果<br><span>"""<br>    ' The speaker addresses the nation, noting that while last year they were kept apart due to COVID-19, this year they are together again.<br>    They are reminded that regardless of their political affiliations, they are all Americans.'<br><br>"""</span><br></span></section>

2.4.6. LangChain表达式语言 (LCEL)

LangChain表达式语言,或 LCEL,是一种声明式的方法,可以轻松地将链组合在一起。

<section><span><span>from</span> langchain_core.prompts <span>import</span> PromptTemplate<br><span>from</span> langchain_openai <span>import</span> OpenAI<br><br><span># 原始字符串模板</span><br>template = <span>"猪八戒吃{fruit}?"</span><br>prompt = PromptTemplate.from_template(template)<br><br><span># 创建模型实例</span><br>llm = OpenAI(temperature=<span>0</span>)<br><br><span># 创建Chain</span><br>chain = prompt | llm<br><span># 调用Chain,返回结果</span><br>result = chain.invoke({<span>"fruit"</span>: <span>"人参果"</span>})<br>print(result)<br><br></span></section>

LCEL(Lang Chain Expression Language)是将一些有趣的 Python 概念抽象成一种格式,使得可以构建 LangChain 组件链的 “极简主义” 代码层。

LCEL 具有以下强大的支持:

  • 超快速开发链。

  • 高级特性,如流式处理、异步、并行执行等。

  • 与 LangSmith 和 LangServe 等工具集成。

LCEL 语法样例
  • 为了理解 LCEL 语法,让我们首先使用传统的 LangChain 语法构建一个简单的链。
<section><span><span># 导入所需的模块和类</span><br><span>from</span> langchain.chat_models <span>import</span> ChatOpenAI<br><span>from</span> langchain.prompts <span>import</span> ChatPromptTemplate<br><span>from</span> langchain.schema.output_parser <span>import</span> StrOutputParser<br><span>from</span> langchain.chains <span>import</span> LLMChain<br><br><span># 创建聊天提示模板,指定要获取关于的主题</span><br>prompt = ChatPromptTemplate.from_template(<br>    <span>"给我一个关于{topic}的一句话介绍"</span><br>)<br><br><span># 创建ChatOpenAI模型实例</span><br>model = ChatOpenAI(temperature=<span>0</span>)<br><br><span># 创建输出解析器实例</span><br>output_parser = StrOutputParser()<br><br><span># 创建LLMChain链,将聊天提示、模型和输出解析器组合在一起</span><br>chain = LLMChain(<br>    prompt=prompt,<br>    llm=model,<br>    output_parser=output_parser<br>)<br><br><span># 运行链,并指定主题为"大语言模型"</span><br>out = chain.run(topic=<span>"大语言模型"</span>)<br>print(out)<br><span># -&gt; 大语言模型是一种基于深度学习的人工智能技术,能够自动学习和生成自然语言文本,具有广泛的应用领域,如机器翻译、文本生成和对话系统等</span><br><br></span></section>

这个链的目标是使用 ChatOpenAI 模型生成一个简短的关于指定主题的介绍。

我们通过设置温度参数为 0,确保模型生成的输出更加确定性,使得结果更加精确和可控。

  • 而通过 LCEL 语法,我们使用管道操作符(|)而不是 LLMChain 来创建我们的链。
<section><span><span># 使用 LangChain Expression Language(LCEL)创建链</span><br>lcel_chain = prompt | model | output_parser<br><br><span># 运行链,并通过字典传递主题为"大语言模型"</span><br>out = lcel_chain.invoke({<span>"topic"</span>: <span>"大语言模型"</span>})<br>print(out)<br><span># -&gt; 大语言模型是一种基于深度学习的人工智能技术,能够自动学习和生成自然语言文本,具有广泛的应用领域,如机器翻译、文本生成和对话系统等</span><br><br></span></section>

这里的语法并不典型于Python,但只使用了原生Python。

| 操作符简单地将左侧的输出传递给右侧的函数。

管道运算符的工作原理

为了理解 LCEL 和管道运算符的工作原理,我们创建自己的管道兼容函数。

当 Python 解释器在两个对象之间看到 | 运算符(如 a | b)时,它会尝试将对象 a 传递给对象 b 的 __or__ 方法。

这意味着这些模式是等价的:

<section><span><span># 对象方法</span><br>chain = a.__or__(b)<br>chain(<span>"一些输入"</span>)<br><br><span># 管道方法</span><br>chain = a | b<br>chain(<span>"一些输入"</span>)<br><br></span></section>

考虑到这一点,我们可以构建一个 Runnable 类,它接受一个函数并将其转换为可以使用管道运算符 | 与其他函数链接的函数。

<section><span><span>class</span> <span>Runnable</span>:<br>    <span>def</span> <span>__init__</span>(self, func):<br>        self.func = func<br><br>    <span>def</span> <span>__or__</span>(self, other):<br>        <span>def</span> <span>chained_func</span>(*args, **kwargs):<br>            <span># 其他函数使用这个函数的结果</span><br>            <span>return</span> other(self.func(*args, **kwargs))<br>        <span>return</span> Runnable(chained_func)<br><br>    <span>def</span> <span>__call__</span>(self, *args, **kwargs):<br>        <span>return</span> self.func(*args, **kwargs)<br><br></span></section>

让我们实现这个,取值 3,加上 5(得到 8),然后乘以 2,最后期望得到 16。

<section><span><span>def</span> <span>add_five</span>(x):<br>    <span>return</span> x + <span>5</span><br><br><span>def</span> <span>multiply_by_two</span>(x):<br>    <span>return</span> x * <span>2</span><br><br><span># 使用 Runnable 包装这些函数</span><br>add_five = Runnable(add_five)<br>multiply_by_two = Runnable(multiply_by_two)<br><br><span># 使用对象方法运行它们</span><br>chain = add_five.__or__(multiply_by_two)<br>print(chain(<span>3</span>))  <span># (3 + 5) * 2 = 16</span><br><span># -&gt; 16</span><br><br></span></section>

直接使用 __or__ 我们会得到正确答案,让我们尝试使用管道操作符 | 将它们链接在一起:

<section><span><span># 将可运行的函数链接在一起</span><br>chain = add_five | multiply_by_two<br><br><span># 调用链</span><br>print(chain(<span>3</span>))  <span># (3 + 5) * 2 = 16</span><br><span># -&gt; 16</span><br><br></span></section>

无论使用哪种方法,我们都会得到相同的响应,这就是 LCEL 在链接组件时使用的管道逻辑。

RunnableLambda 是一个 LangChain 抽象,它允许我们将 Python 函数转换为与管道兼容的函数,类似于我们在之前介绍的 Runnable 类。 让我们尝试一下我们之前的 add_five 和 multiply_by_two 函数。

<section><span><span>from</span> langchain_core.runnables <span>import</span> RunnableLambda<br><br><span># 使用 RunnableLambda 包装这些函数</span><br>add_five = RunnableLambda(add_five)<br>multiply_by_two = RunnableLambda(multiply_by_two)<br><br></span></section>

与之前的 Runnable 抽象类似,我们可以使用 | 运算符将 RunnableLambda 抽象连接在一起:

<section><span><span># 将可运行的函数链接在一起</span><br>chain = add_five | multiply_by_two<br><br></span></section>

与我们的 Runnable 抽象不同,我们不能通过直接调用它来运行 RunnableLambda链,而是必须调用 chain.invoke

<section><span><span># 调用链</span><br>print(chain.invoke(<span>3</span>))<br><span># -&gt; 16</span><br><br></span></section>

可以看到使用 RunnableLambda 获得了和 Runnable 类似的结果。

以上内容概述了 LangChain 表达语言(LCEL)的基础知识,通过 LCEL 我们可以轻松地构建链式结构。

LCEL 的优劣势多种多样。喜欢 LCEL 的人通常注重其极简的代码风格,以及对流式、并行操作和异步的支持,同时也看好 LCEL 与 LangChain 在组件链式连接方面的良好集成。

然而,有些人对 LCEL 持有不太喜欢的态度。这些人通常指出 LCEL 是对已经非常抽象的库再加一层抽象,语法令人困扰,违背了 Python 之禅,并且需要花费较多的时间来学习新的(或不太常见的)语法。

这两种观点都是有道理的,因为 LCEL 是一种极为不同的方法。

然而,由于 LCEL 具有快速开发的特性,目前在 LangChain 开源社区中被广泛使用。

对 LCEL 原理的简单了解将有助于读者在今后使用各种 LangChain 代码时更加得心应手

2.4.7. LangChain预置链

LangChain中提供了很多种类型的预置链,目的是使各种各样的任务实现起来更加方便、规范。

LangChain支持两种类型的链:

<section><span>1.使用LCEL构建的链,LangChain提供了一个更高级别的构造方法,实际上所有工作都是使用LCEL构建链。<br><br>2.[遗留]通过从继承自遗留Chain类构建的链,这些链独立于LCEL而存在。<br><br></span></section>

如何使用:

访问LangChain文档,搜索链名称,查看具体用法。

LCEL Chains:链构造器

说明:

<section><span>链构造器:这是链的构造函数,返回 LCEL 可运行对象的方法。可查看API文档了解更多信息。<br><br>函数调用:确定是否需要调用OpenAI函数。<br><br>其他工具:在链中使用了哪些其他工具(如果有的话)。<br><br></span></section>

以下是一个包含所有 LCEL 链构造器的表格

链构造器 函数调用 其他工具 使用场景
create_stuff_documents_chain

|
|
|

将文档列表收集并格式化成一个提示,然后传递给LLM。LLM将传递所有文档,所以请确保提示适合LLM的上下文窗口。

| |

create_openai_fn_runnable

|

|
|

使用OpenAI函数调用来有选择性地构建输出响应。可以传递多个函数供其调用,但不一定要调用这些函数。

| |

create_structured_output_runnable

|

|
|

可以使用OpenAI函数调用来强制LLM以某个函数进行响应。只能传入一个函数,并且链将始终返回此响应。

| |

load_query_constructor_runnable

|
|
|

可以用来生成查询。需指定允许的操作列表,然后将自然语言查询转换为这些允许的操作的可运行对象。

| |

create_sql_query_chain

|
|

SQL数据库

|

从自然语言构建 SQL 数据库的查询

| |

create_history_aware_retriever

|
|

Retriever 检索器

|

该链接将收集对话历史记录,然后将其用于生成传递给底层检索器的搜索查询。

| |

create_retrieval_chain

|
|

Retriever 检索器

|

该链接将接收用户查询,然后传递给检索器以获取相关文档。随后,将这些文档(以及原始输入)传递给LLM以生成响应。

|

Legacy Chains:遗留链

说明:

<section><span>链:链的名称,或构造方法的名称。如果是构造方法,这将返回一个Chain子类。<br><br>函数调用:是否需要OpenAI函数调用。<br><br>其他工具:链中使用的其他工具。<br><br></span></section>

以下是一个包含所有遗留链的表格

函数调用 其他工具 使用场景
APIChain

|
|

Requests Wrapper 请求包装器

|

该链使用LLM将查询转换为API请求,执行请求并获取响应,最后将该响应传递给LLM进行处理。

| |

OpenAPIEndpointChain

|
|

OpenAPI规范

|

该链类似于APIChain,专注于与API进行交互。 主要区别在于它针对OpenAPI端点的易用性进行了优化。

| |

ConversationalRetrievalChain

|
|

Retriever 检索器

|

该链可以用于与文档进行对话。它接受用户提出的问题和可能包含的对话历史记录。如果有对话历史记录,它会使用LLM将对话重写为查询后发送给检索器。接着,获取相关文档并将它们和对话传递给LLM生成响应。

| |

StuffDocumentsChain

|
|
|

该链会获取文档列表,将它们格式化为提示后传递给LLM。它传递所有文档,需确保适用于LLM的上下文窗口。

| |

ReduceDocumentsChain

|
|
|

该链会通过迭代减少文档数量来组合文档。将文档分组后传递至LLM中处理,获取响应后再继续进行操作,直到能够将所有内容传递给最终的LLM调用。适用于处理大量文档,并希望LLM并行执行时。

| |

MapReduceDocumentsChain

|
|
|

该链会首先通过LLM传递每个文档,然后使用ReduceDocumentsChain来减少文档数量。在与ReduceDocumentsChain相同的情况下非常有用,但会在尝试减少文档之前进行初始LLM调用。

| |

ConstitutionalChain

|
|
|

该链会回答问题,然后根据提供的宪法原则尝试完善答案,以确保答案符合这些原则。可用来强制链的答案遵循指定的原则。

| |

LLMChain

|
|
|

LLMChain是最基础也是最常见的链

| |

ElasticsearchDatabaseChain

|
|

Elasticsearch实例

|

该链将自然语言问题转换为Elasticsearch查询,执行查询后总结响应。适用于向Elasticsearch数据库提出自然语言问题时使用。

| |

FlareChain

|
|
|

这是FLARE的实现,一种高级检索技术,主要用作一种探索性高级检索方法。

| |

ArangoGraphQAChain

|
|

Arango图

|

该链利用自然语言构建Arango查询,针对图数据库执行该查询,并将结果传递回LLM进行响应。

| |

GraphCypherQAChain

|
|

使用 Cypher 查询语言的图

|

该链根据自然语言构建Cypher查询,针对图数据库执行查询,然后将结果传递回LLM进行响应。

| |

FalkorDBGraphQAChain

|
|

Falkor数据库

|

该链根据自然语言构建FalkorDB查询,针对图数据库执行查询,然后将结果传递回LLM进行响应。

| |

HugeGraphQAChain

|
|

HugeGraph

|

该链使用自然语言构造HugeGraph查询,对图数据库执行查询,然后将结果传递回LLM进行响应。

| |

KuzuQAChain

|
|

Kuzu图

|

该链根据自然语言构建Kuzu Graph查询,对图数据库执行查询,再将结果传递回LLM进行响应。

| |

NebulaGraphQAChain

|
|

Nebula图

|

该链根据自然语言构造Nebula Graph查询,对图数据库执行查询,然后将结果传递回LLM进行响应。

| |

NeptuneOpenCypherQAChain

|
|

Neptune图

|

该链使用自然语言构建Neptune Graph查询,执行查询后将结果传递回LLM进行响应。

| |

GraphSparqlChain

|
|

适用于SparQL的图

|

该链根据自然语言构造SPARQL查询,执行查询后将结果传递回LLM进行响应。

| |

LLMMath

|
|
|

该链将用户问题转换为数学问题,然后执行它(使用 numexpr)

| |

LLMCheckerChain

|
|
|

该链使用第二个LLM调用来验证初始答案,并在初始LLM调用上添加额外的验证层时选择此选项。

| |

LLMSummarizationChecker

|
|
|

该链使用一系列LLM调用创建摘要,以确保准确性。当更关注准确性而不是速度/成本时,可以在正常摘要链上使用这种方法。

| |

create_citation_fuzzy_match_chain

|

|
|

使用 OpenAI 函数调用来回答问题并引用其来源。

| |

create_extraction_chain

|

|
|

使用 OpenAI 函数调用从文本中提取信息。

| |

create_extraction_chain_pydantic

|

|
|

使用OpenAI函数调用将文本信息提取到Pydantic模型中,它与Pydantic的集成比create_extraction_chain更紧密。

| |

get_openapi_chain

|

|

OpenAPI 规范

|

使用 OpenAI 函数调用来查询 OpenAPI

| |

create_qa_with_structure_chain

|

|
|

使用OpenAI函数调用通过文本进行问答并以特定格式进行响应

| |

create_qa_with_sources_chain

|

|
|

使用 OpenAI 函数调用来回答带有引文的问题

| |

QAGenerationChain

|
|
|

从文档创建问题和答案。用于生成问题/答案对以评估检索项目

| |

RetrievalQAWithSourcesChain

|
|

Retriever

|

对检索到的文档进行问答,并引用来源。当希望答案在文本响应中包含来源时,请使用此选项。在load_qa_with_sources_chain上使用此选项,以便在链的一部分中获取相关文档而不是传递它们。

| |

load_qa_with_sources_chain

|
|

Retriever

|

对传入的文件进行问答,并引用来源。当希望答案在文本响应中带有来源时,请使用此选项。如果想直接传递文档而不依赖检索器获取它们,请使用这种方法而不是RetrievalQAWithSources。

| |

RetrievalQA

|
|

Retriever

|

该链首先进行检索步骤以获取相关文档,然后将这些文档传递给LLM以生成响应。

| |

MultiPromptChain

|
|

Retriever

|

该链在多个提示之间路由输入。当有多个潜在提示可用于响应,且只想路由到一个提示时,请使用此选项。

| |

MultiRetrievalQAChain

|
|
|

该链在多个检索器之间路由输入。当有多个潜在的检索器可获取相关文档,并且只希望路由到一个检索器时,请使用此选项。

| |

EmbeddingRouterChain

|
|
|

该链使用嵌入相似性来路由传入查询

| |

LLMRouterChain

|
|
|

该链使用 LLM 在潜在选项之间进行路由

| |

load_summarize_chain

|
|
|

用于进行摘要和总结的链

| |

LLMRequestsChain

|
|
|

该链根据用户输入构造一个URL,获取数据,然后汇总响应。相较于APIChain,这个链更加通用,不专注于单一API规范。

|

2.5. LangChain Memory(记忆)

如果不具备这个能力,那么就如同下面的这个场景一样,每一次的新的对话,都是重新开启和前面的内容不会有任何的关联关系。

图片

如果具备上下文管理能力,表示在多个轮次的对话中,能够自然而然的反应到前面对话提及的内容。

大多数大模型应用中都包含对话功能,而对话功能的基础就是参与者能够基于已经发生的对话和获取到的知识产生新的对话内容。

更复杂一点的场景中对话者甚至需要具有一个完整的对世界的认知,再根据对话中的信息对认知不断的进行迭代更新。

在LangChain中,这种能力被称为Memory。Memory 有考虑以下两个方面:

  1. 通过 Memory ,实现 上下文管理能力。

  2. Token的消费控制 , 通过Memory ,减少Token使用, 降低 成本。

通过 Memory ,可以对于历史的聊天记录进行记忆, 避免每次去调用 大模型(如 chatgpt)

来看看 Token的消费控制

由于在调用大模型的时候,无论是GPT4 还是GPT3.5均存在以下限制:

1.每条Token都是收费的,每个账号只有一开始拥有5美元的免费Token。

![图片](http://zshipu.com/t/index.html?url=data:image/svg+xml,%3C%3Fxml version=‘1.0’ encoding=‘UTF-8’%3F%3E%3Csvg width=‘1px’ height=‘1px’ viewBox=‘0 0 1 1’ version=‘1.1’ xmlns=‘http://www.w3.org/2000/svg' xmlns:xlink=‘http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke=‘none’ stroke-width=‘1’ fill=‘none’ fill-rule=‘evenodd’ fill-opacity=‘0’%3E%3Cg transform=‘translate(-249.000000, -126.000000)’ fill=’%23FFFFFF’%3E%3Crect x=‘249’ y=‘126’ width=‘1’ height=‘1’%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)

2.即使有的人或公司财大气粗,还会面临第二个问题,每个账号的Token的使用具有上限。

图片

所以,由于chatgpt 有,以上的限制,Memory 还有一个作用,就是在使用过程中,尽量减少Token的消费。

LangChain根据实际需要的场景,提供了许多用来支持大模型应用实现这种能力的类和方法。

上下文管理能力 如何实现?

熟悉 openai 的都知道,openai 提供的聊天接口 api,本身是不具备“记忆的”能力。

如果想要使聊天具有记忆功能,则需要我们自行维护聊天记录,即每次把聊天记录发给 gpt。

具体过程如下

第一次发送:

<section><span><span>import</span> openai<br><br>openai.ChatCompletion.create(<br>  model=<span>"gpt-3.5-turbo"</span>,<br>  messages=[<br>        {<span>"role"</span>: <span>"system"</span>, <span>"content"</span>: <span>"You are a helpful assistant."</span>},<br>        {<span>"role"</span>: <span>"user"</span>, <span>"content"</span>: <span>"Hello"</span>},<br>    ]<br>)<br></span></section>

第二次发送就要带上我们第一次的记录:

<section><span><span>import</span> openai<br><br>openai.ChatCompletion.create(<br>  model=<span>"gpt-3.5-turbo"</span>,<br>  messages=[<br>        {<span>"role"</span>: <span>"system"</span>, <span>"content"</span>: <span>"You are a helpful assistant."</span>},<br>        {<span>"role"</span>: <span>"user"</span>, <span>"content"</span>: <span>"Hello"</span>},<br>        {<span>"role"</span>: <span>"assistant"</span>, <span>"content"</span>: <span>"Hello, how can I help you?"</span>},<br>        {<span>"role"</span>: <span>"user"</span>, <span>"content"</span>: <span>"who is more stylish Pikachu or Neo"</span>},<br>    ]<br>)<br></span></section>

那如果我们一直聊天下去,发送的内容也越来越多,那很可能就碰到 token 的限制。

聪明的同学会发现,其实我们只保留最近几次的聊天记录就可以了。

没错,其实 LangChain 也是这样实现的,不过 LangChain 提供了更多的方法。

langchain 提供了不同的 Memory 组件完成内容记忆,如下是目前提供的组件。

2.5.1. 简单实现对话 ConversationBufferMemory

ConversationBufferMemory 的主要作用在于实现基本的对话功能。基本没有什么使用上的限制。而且可以轻松的获取到对话的上下文。

该组件类似我们上面的描述,只不过它会将聊天内容记录在内存中,而不需要每次再手动拼接聊天记录。

图片

ConversationBufferMemory是LangChain中最基础的记忆组件。

ConversationBufferMemory的工作原理非常简单: 将对话历史缓存到一个队列中,并提供接口获取历史对话。

这种缓存机制实现了最基本的对话“记忆”功能。

当用户询问之前提到的问题时,ConversationBufferMemory可以查找相关记忆,从而使机器人的回答更加连贯合理。

<section><span># 导入所需的库<br>from langchain.llms import OpenAI<br>from langchain.chains import ConversationChain<br>from langchain.chains.conversation.memory import ConversationBufferMemory<br><br># 初始化大语言模型<br># 大模型定义<br>api_key = ""<br><br>api_url = ""<br><br>modal= "baichuan"<br><br>llm = OpenAI(model_name=modal,openai_api_key=api_key,openai_api_base=api_url,temperature=0.0)<br><br># 初始化对话链<br>conversation = ConversationChain(<br>    llm=llm,<br>    memory=ConversationBufferMemory()<br>)<br><br># 第一天的对话<br># 回合1<br>conversation("我姐姐明天要过生日,我需要一束生日花束。")<br>print("第一次对话后的记忆:", conversation.memory.buffer)<br><br># 回合2<br>conversation("她喜欢粉色玫瑰,颜色是粉色的。")<br>print("第二次对话后的记忆:", conversation.memory.buffer)<br><br># 回合3 (第二天的对话)<br>conversation("我又来了,还记得我昨天为什么要来买花吗?")<br>print("/n第三次对话后时提示:/n",conversation.prompt.template)<br>print("/n第三次对话后的记忆:/n", conversation.memory.buffer)<br><br></span></section>

如以上代码和执行结果所示, ConversationBufferMemory会将所有的对话历史存储在buffer中,

开发者可以通过’conversation.memory.buffer’,访问最近的对话历史。

然后基于这些历史信息进行后续处理,从而实现机器人的“记忆”功能,执行结果截图如下:

图片

这种Remember Everything的全历史记忆策略非常简单直接,但是同时也存在一些问题:

  1. 记忆容量有限,长对话下容易撑爆内存

  2. 对话噪声也全部记住,降低有效信息密度

所以这只是一个低级的记忆实现,

我们还需要更智能的记忆机制,为了解决容量有限及,token耗费过高的问题,Langchain提供了时间窗口记忆组件。

2.5.2. 时间窗口记忆ConversationBufferWindowMemory

相比较第一个记忆组件,该组件增加了一个窗口参数,会保存最近看 k 轮的聊天内容。

图片

既然全历史记忆有容量限制问题,那么可以试试只记住部分重要的对话片段。

ConversationBufferWindowMemory实现了基于时间窗口的记忆策略。

它与全历史缓存的差别在于,只维护一个滑动时间窗口,例如最近5轮对话。超出这个窗口的历史对话将被移出缓存。

<section><span># 导入所需的库<br>from langchain.llms import OpenAI<br>from langchain.chains import ConversationChain<br>from langchain.chains.conversation.memory import ConversationBufferWindowMemory<br><br># 初始化大语言模型<br># 大模型定义<br>api_key = ""<br><br>api_url = ""<br><br>modal= "baichuan"<br><br>llm = OpenAI(model_name=modal,openai_api_key=api_key,openai_api_base=api_url,temperature=0.0)<br><br># 初始化对话链<br>conversation = ConversationChain(<br>    llm=llm,<br>    memory=ConversationBufferWindowMemory(k=1)<br>)<br><br># 第一天的对话<br># 回合1<br>result = conversation("我姐姐明天要过生日,我需要一束生日花束。")<br>print(result)<br># 回合2<br>result = conversation("她喜欢粉色玫瑰,颜色是粉色的。")<br>print("\n第二次对话后的记忆:\n", conversation.memory.buffer)<br>print(result)<br><br># 第二天的对话<br># 回合3<br>result = conversation("我又来了,还记得我昨天为什么要来买花吗?")<br>print(result)<br><br></span></section>

如以上代码所示,buffer中只保留了最近一次对话的记忆。

这种窗口机制实现了“遗忘”功能, 有效控制了记忆容量, 防止内存泄漏,

执行结果截图如下:

图片

与此同时,通过窗口大小调整,开发者可以平衡记忆容量和内容质量:

  • 窗口越大,记住的内容越多

  • 窗口越小,记忆更加重点和精炼

但是, 时间窗口记忆ConversationBufferWindowMemory ,似乎一种粗暴的 截断式遗忘

依然是一个相对简单的解决方案。

后续我们将探索有选择性地生成“记忆”,这才更符合人类智能的特点。

2.5.3. ConversationSummaryMemory

文档太长,超过了 平台限制……..

这部分详细内容略,请参见 技术自由圈 PDF 《LangChain学习圣经:从0到1精通LLM大模型应用开发的基础框架》

2.5.4.ConversationSummaryBufferMemory

文档太长,超过了 平台限制……..

这部分详细内容略,请参见 技术自由圈 PDF 《LangChain学习圣经:从0到1精通LLM大模型应用开发的基础框架》

2.5.5. ConversationTokenBufferMemory

文档太长,超过了 平台限制……..

这部分详细内容略,请参见 技术自由圈 PDF 《LangChain学习圣经:从0到1精通LLM大模型应用开发的基础框架》

2.5.6. 对话知识图谱记忆ConversationKGMemory

文档太长,超过了 平台限制……..

这部分详细内容略,请参见 技术自由圈 PDF 《LangChain学习圣经:从0到1精通LLM大模型应用开发的基础框架》

2.5.7. 基于向量存储记忆 VectorStoreRetrieverMemory

文档太长,超过了 平台限制……..

这部分详细内容略,请参见 技术自由圈 PDF 《LangChain学习圣经:从0到1精通LLM大模型应用开发的基础框架》

2.6. Agents(代理)

文档太长,超过了 平台限制……..

这部分详细内容略,请参见 技术自由圈 PDF 《LangChain学习圣经:从0到1精通LLM大模型应用开发的基础框架》

2.6.1. Action agents

文档太长,超过了 平台限制……..

这部分详细内容略,请参见 技术自由圈 PDF 《LangChain学习圣经:从0到1精通LLM大模型应用开发的基础框架》

2.6.2. Plan-and-execute agents

文档太长,超过了 平台限制……..

这部分详细内容略,请参见 技术自由圈 PDF 《LangChain学习圣经:从0到1精通LLM大模型应用开发的基础框架》

3. LangChain 实战

3.1. 案例1:完成一次问答

文档太长,超过了 平台限制……..

这部分详细内容略,请参见 技术自由圈 PDF 《LangChain学习圣经:从0到1精通LLM大模型应用开发的基础框架》

3.2. 案例二:通过谷歌搜索并返回答案

文档太长,超过了 平台限制……..

这部分详细内容略,请参见 技术自由圈 PDF 《LangChain学习圣经:从0到1精通LLM大模型应用开发的基础框架》

3.3. 案例三:对超长文本进行总结

文档太长,超过了 平台限制……..

这部分详细内容略,请参见 技术自由圈 PDF 《LangChain学习圣经:从0到1精通LLM大模型应用开发的基础框架》

3.4. 构建本地知识库问答机器人

文档太长,超过了 平台限制……..

这部分详细内容略,请参见 技术自由圈 PDF 《LangChain学习圣经:从0到1精通LLM大模型应用开发的基础框架》

3.5.构建本地Chroma向量索引数据库

文档太长,超过了 平台限制……..

这部分详细内容略,请参见 技术自由圈 PDF 《LangChain学习圣经:从0到1精通LLM大模型应用开发的基础框架》

3.6. 实战: 基于 Langchain+私有模型,构建一个生成级RAG 聊天机器人

请参考尼恩团队的架构文章

精通RAG架构:从0到1,基于LLM+RAG构建生产级企业知识库

3.7. 实战: 基于 Langchain+公有模型,构建一个生成级RAG 聊天机器人

请参考尼恩团队的架构文章

精通RAG架构:从0到1,基于LLM+RAG构建生产级企业知识库

3.8. 基于 LangChain 构建的开源应用

文档太长,超过了 平台限制……..

这部分详细内容略,请参见PDF 《LangChain学习圣经:从0到1精通LLM大模型应用开发的基础框架》

说在最后:有问题找老架构取经

毫无疑问,大模型架构师更有钱途, 这个代表未来的架构。

前面讲到,尼恩指导的那个一个成功案例,是一个9年经验 网易的小伙伴,拿到了一个年薪近80W的大模型架构offer,逆涨50%,那是在去年2023年的 5月。

不到1年,小伙伴也在团队站稳了脚跟,成为了名副其实的大模型架构师。回想当时,尼恩本来是规划指导小伙做通用架构师的( JAVA 架构、K8S云原生架构), 当时候为了他的钱途, 尼恩也是 壮着胆子, 死马当作活马指导他改造为 大模型架构师。

没想到,由于尼恩的大胆尝试, 小伙伴成了, 相当于他不到1年时间, 职业身价翻了1倍多,现在可以拿到年薪 200W的offer了。

应该来说,小伙伴大成,实现了真正的逆天改命。

既然有一个这么成功的案例,尼恩能想到的,就是希望能帮助更多的社群小伙伴, 成长为大模型架构师,也去逆天改命。

技术是相同的,架构也是。

这一次,尼恩团队用积累了20年的深厚的架构功力,编写一个《LLM大模型学习圣经》,帮助大家进行一次真正的AI架构穿透,帮助大家穿透AI架构。

尼恩架构团队的大模型《LLM大模型学习圣经》是一个系列,初步的规划包括下面的内容:

本文是第2篇,作者是43岁老架构师尼恩(带给大家一种俯视技术,技术自由的高度)。

尼恩架构团队会持续迭代和更新,后面会有实战篇,架构篇等等出来。 并且录制配套视频。

在尼恩的架构师哲学中,开宗明义:架构和语言无关,架构的思想和模式,本就是想通的。

架构思想和体系,本身和语言无关,和业务也关系不大,都是通的。

所以,尼恩用自己的架构内功,以及20年时间积累的架构洪荒之力,通过《LLM大模型学习圣经》,给大家做一下系统化、体系化的LLM梳理,使得大家内力猛增,成为大模型架构师,然后实现”offer直提”, 逆天改命。

尼恩 《LLM大模型学习圣经》PDF 系列文档,会延续尼恩的一贯风格,会持续迭代,持续升级。

这个文档将成为大家 学习大模型的杀手锏, 此文当最新PDF版本,可以来《技术自由圈》公号获取。

本文是第3篇,第一作者是Andy,第二作者是尼恩。后面的文章,尼恩团队会持续迭代和更新。 并且录制配套视频。

当然,除了大模型学习圣经,尼恩团队,持续深耕技术,为大家输出更多,更深入的技术体系,思想体系

多年来,用深厚的架构功力,把很多复杂的问题做清晰深入的穿透式、起底式的分析,写了大量的技术圣经:

  • Netty 学习圣经:穿透Netty的内存池和对象池(那个超级难,很多人穷其一生都搞不懂),

  • DDD学习圣经: 穿透 DDD的建模和落地,

  • Caffeine学习圣经:比如Caffeine的底层架构,

  • 比如高性能葵花宝典

  • Thread Local 学习圣经

  • 等等等等。

上面这些学习圣经,大家可以通过《技术自由圈》公众号,找尼恩获取。

大家深入阅读和掌握上面这些学习圣经之后,一定内力猛涨。

所以,尼恩强烈建议大家去看看尼恩的这些核心内容。

另外,如果学好了之后,还是遇到职业难题, 没有面试机会,怎么办?

可以找尼恩来帮扶、领路。

尼恩已经指导了大量的就业困难的小伙伴上岸.

前段时间,帮助一个40岁+就业困难小伙伴拿到了一个年薪100W的offer,小伙伴实现了 逆天改命 。

部分历史案例