源码地址:https://github.com/kingglory/langchain-chinese-learning/tree/main
LLM 调用
- 支持多种模型接口,比如 OpenAI、Hugging Face、AzureOpenAI …
- Fake LLM,用于测试
- 缓存的支持,比如 in-mem(内存)、SQLite、Redis、SQL
- 用量记录
- 支持流模式(就是一个字一个字的返回,类似打字效果)
Prompt管理,支持各种自定义模板
拥有大量的文档加载器,比如 Email、Markdown、PDF、Youtube …
对索引的支持
- 文档分割器
- 向量化
- 对接向量存储与搜索,比如 Chroma、Pinecone、Qdrand
Chains
- LLMChain
- 各种工具Chain
- LangChainHub
Loader 加载器
顾名思义,这个就是从指定源进行加载数据的。比如:文件夹 DirectoryLoader、Azure 存储 AzureBlobStorageContainerLoader、CSV文件 CSVLoader、印象笔记 EverNoteLoader、Google网盘 GoogleDriveLoader、任意的网页 UnstructuredHTMLLoader、PDF PyPDFLoader、S3 S3DirectoryLoader/S3FileLoader、
Youtube YoutubeLoader 等等,上面只是简单的进行列举了几个,官方提供了超级的多的加载器供你使用。
- https://python.langchain.com/en/latest/modules/indexes/document_loaders.html
Document 文档
当使用loader加载器读取到数据源后,数据源需要转换成 Document 对象后,后续才能进行使用。
Text Spltters 文本分割
顾名思义,文本分割就是用来分割文本的。为什么需要分割文本?因为我们每次不管是做把文本当作 prompt 发给 openai api ,还是还是使用 openai api embedding 功能都是有字符限制的。
比如我们将一份300页的 pdf 发给 openai api,让他进行总结,他肯定会报超过最大 Token 错。所以这里就需要使用文本分割器去分割我们 loader 进来的 Document。
Vectorstores 向量数据库
因为数据相关性搜索其实是向量运算。所以,不管我们是使用 openai api embedding 功能还是直接通过向量数据库直接查询,都需要将我们的加载进来的数据 Document 进行向量化,才能进行向量运算搜索。转换成向量也很简单,只需要我们把数据存储到对应的向量数据库中即可完成向量的转换。
官方也提供了很多的向量数据库供我们使用。
- https://python.langchain.com/en/latest/modules/indexes/vectorstores.html
Chain 链
我们可以把 Chain 理解为任务。一个 Chain 就是一个任务,当然也可以像链条一样,一个一个的执行多个链。
Agent 代理
Embedding
用于衡量文本的相关性。这个也是 OpenAI API 能实现构建自己知识库的关键所在。
他相比 fine-tuning 最大的优势就是,不用进行训练,并且可以实时添加新的内容,而不用加一次新的内容就训练一次,并且各方面成本要比 fine-tuning 低很多。
- 具体比较和选择可以参考这个视频:https://www.youtube.com/watch?v=9qq6HTr7Ocw
接下来,我们就来搞点有意思的。我们来让我们的 OpenAI api 联网搜索,并返回答案给我们。
这里我们需要借助 Serpapi 来进行实现,Serpapi 提供了 google 搜索的 api 接口。
首先需要我们到 Serpapi 官网上注册一个用户,https://serpapi.com/ 并复制他给我们生成 api key。
然后我们需要像上面的 openai api key 一样设置到环境变量里面去。
我们可以看到,他正确的返回了日期(有时差),并且返回了历史上的今天。
在 chain 和 agent 对象上都会有 verbose 这个参数,这个是个非常有用的参数,开启他后我们可以看到完整的 chain 执行过程。
可以在上面返回的结果看到,他将我们的问题拆分成了几个步骤,然后一步一步得到最终的答案。
关于agent type 几个选项的含义(理解不了也不会影响下面的学习,用多了自然理解了):
-
zero-shot-react-description: 根据工具的描述和请求内容的来决定使用哪个工具(最常用)
-
react-docstore: 使用 ReAct 框架和 docstore 交互, 使用Search 和Lookup 工具, 前者用来搜, 后者寻找term, 举例: Wipipedia 工具
-
self-ask-with-search 此代理只使用一个工具: Intermediate Answer, 它会为问题寻找事实答案(指的非 gpt 生成的答案, 而是在网络中,文本中已存在的), 如 Google search API 工具
-
conversational-react-description: 为会话设置而设计的代理, 它的prompt会被设计的具有会话性, 且还是会使用 ReAct 框架来决定使用来个工具, 并且将过往的会话交互存入内存
- reAct 介绍可以看这个:https://arxiv.org/pdf/2210.03629.pdf
- LLM 的 ReAct 模式的 Python 实现: https://til.simonwillison.net/llms/python-react-pattern
- agent type 官方解释:
https://python.langchain.com/en/latest/modules/agents/agents/agent_types.html?highlight=zero-shot-react-description
有一点要说明的是,这个 serpapi 貌似对中文不是很友好,所以提问的 prompt 建议使用英文。
上面那个任务一共搜索了两次( serpapi 一个月免费搜索100次)
假如我们想要用 openai api 对一个段文本进行总结,我们通常的做法就是直接发给 api 让他总结。但是如果文本超过了 api 最大的 token 限制就会报错。
这时,我们一般会进行对文章进行分段,比如通过 tiktoken 计算并分割,然后将各段发送给 api 进行总结,最后将各段的总结再进行一个全部的总结。
如果,你用是 LangChain,他很好的帮我们处理了这个过程,使得我们编写代码变的非常简单。
首先我们对切割前和切割后的 document 个数进行了打印,我们可以看到,切割前就是只有整篇的一个 document,切割完成后,会把上面一个 document 切成 331 个 document。
最终输出了对前 5 个 document 的总结:
这本书的内容是《地藏心经》,作者是铸剑师无名。第一第十五章讲述了渝州陆家的家族史,他们是以皮货起家的西北商户,出售江南的皮货占了三成,但也只是一头肥硕的羔羊,只待他人宰杀。陆氏三代家主都极具雄韬伟略,以千金买官,以万金开路,更是在蛮夷南侵之时,倾尽家资招兵买马,拒十万蛮夷铁骑于侯关外。经过百年来的积累,陆家族已形成了雄踞渝、豫两地的世家阀门,掌握着江南数万水军,朝廷无权,皇帝无兵,南朝形同虚设,而西北、南方、东方各地也都有世家阀门割据一方,抗击蛮夷合并后的金国,但是秦家也并未被淹没,他们依旧带着士族风范,举止优雅,虽未能抗衡陆家,却也不会被压迫,最终在年关前,秦家依然布置了一些家业,安定下来。本书讲述了陆家的家族史,他们以皮货起家,凭借着雄韬伟略的家主们,百年来积累而形成的雄踞渝、豫两地的世家阀门,掌握着江南数万水军,朝廷无权,皇帝无兵,南朝形同虚设,而西北、南方、东方各地也都有世家阀门割据一方,抗击蛮夷合并后的金国。尽管秦家无法抗衡陆家,他们仍然不会被压迫,在年关前,他们也布置了一些家业,安定下来。
几个参数需要注意:
文本分割器的 chunk_overlap 参数
这个是指切割后的每个 document 里包含几个上一个 document 结尾的内容,主要作用是为了增加每个 document 的上下文关联。比如,chunk_overlap=0时, 第一个 document 为 aaaaaa,第二个为 bbbbbb;当 chunk_overlap=2 时,第一个 document 为 aaaaaa,第二个为 aabbbbbb。
不过,这个也不是绝对的,要看所使用的那个文本分割模型内部的具体算法。
- 文本分割器可以参考这个文档:https://python.langchain.com/en/latest/modules/indexes/text_splitters.html
chain 的 chain_type 参数
这个参数主要控制了将 document 传递给 llm 模型的方式,一共有 4 种方式:
- stuff: 这种最简单粗暴,会把所有的 document 一次全部传给 llm 模型进行总结。如果document很多的话,势必会报超出最大 token 限制的错,所以总结文本的时候一般不会选中这个。
- map_reduce: 这个方式会先将每个 document 进行总结,最后将所有 document 总结出的结果再进行一次总结。
-
map_rerank: 这种一般不会用在总结的 chain 上,而是会用在问答的 chain 上,他其实是一种搜索答案的匹配方式。首先你要给出一个问题,他会根据问题给每个 document 计算一个这个 document 能回答这个问题的概率分数,然后找到分数最高的那个 document ,在通过把这个 document 转化为问题的 prompt 的一部分(问题+document)发送给 llm 模型,最后 llm 模型返回具体答案。
在这个例子会介绍如何从本地读取多个文档构建知识库,并且使用 Openai API 在知识库中进行搜索并给出答案。
这个是个很有用的教程,比如可以很方便的做一个可以介绍公司业务的机器人,或是介绍一个产品的机器人。
查询结果,科大讯飞2023 年第一季度收入28.88亿元,与文档里的内容一致!
- 关于 Openai embeddings 详细资料可以参看这个连接: https://platform.openai.com/docs/guides/embeddings
上个案例里面有一步是将 document 信息转换成向量信息和embeddings的信息并临时存入 Chroma 向量数据库。
因为是临时存入,所以当我们上面的代码执行完成后,上面的向量化后的数据将会丢失。如果想下次使用,那么就还需要再计算一次embeddings,这肯定不是我们想要的。
下面通过 Chroma 和 Pinecone 这两个数据库来讲一下如何做向量数据持久化。
- 因为 LangChain 支持的数据库有很多,所以这里就介绍两个用的比较多的,更多的可以参看文档:https://python.langchain.com/en/latest/modules/indexes/vectorstores/getting_started.html
Chroma
chroma 是个本地的向量数据库,他提供的一个 persist_directory 来设置持久化目录进行持久化。读取时,只需要调取 from_document 方法加载即可。
Pinecone
Pinecone 是一个在线的向量数据库。第一步依旧是注册,然后拿到对应的 api key。https://app.pinecone.io/ (免费版如果索引14天不使用会被自动清除。)
然后创建我们的数据库:
Index Name:这个随意
Dimensions:OpenAI 的 text-embedding-ada-002 模型为 OUTPUT DIMENSIONS 为 1536,所以我们这里填 1536
Metric:可以默认为 cosine
选择starter plan
一个简单从数据库获取 embeddings,并回答的代码如下
在 chatgpt api(也就是 GPT-3.5-Turbo)模型出来后,因钱少活好深受大家喜爱,所以 LangChain 也加入了专属的链和模型.
使用流式回答也很方便
执行多个chain
因为他是链式的,所以他也可以按顺序依次去执行多个 chain
结构化输出
有时候我们希望输出的内容不是文本,而是像 json 那样结构化的数据。
爬取网页并输出JSON数据
有些时候我们需要爬取一些 结构性比较强 的网页,并且需要将网页中的信息以JSON的方式返回回来。
我们就可以使用 类去实现,具体可以参考下面代码
- 为了方便理解,在例子中直接使用了Prompt的方法去格式化输出结果,而没用使用上个案例中用到的 StructuredOutputParser去格式化,也算是提供了另外一种格式化的思路
自定义agent中所使用的工具
自定义工具里面有个比较有意思的地方,使用哪个工具的权重是靠 工具中描述内容 来实现的,和我们之前编程靠数值来控制权重完全不同。
比如 Calculator 在描述里面写到,如果你问关于数学的问题就用他这个工具。我们就可以在上面的执行过程中看到,他在我们请求的 prompt 中数学的部分,就选用了Calculator 这个工具进行计算。
使用Memory实现一个带记忆的对话机器人
上一个例子使用的是通过自定义一个列表来存储对话的方式来保存历史的。
当然,你也可以使用自带的 memory 对象来实现这一点。
使用 Hugging Face 模型
使用 Hugging Face 模型之前,需要先设置环境变量
使用在线的 Hugging Face 模型
将 Hugging Face 模型直接拉到本地使用
将模型拉到本地使用的好处:
- 训练模型
- 可以使用本地的 GPU
- 有些模型无法在 Hugging Face 运行
通过自然语言执行SQL命令
通过 SQLDatabaseToolkit 或者 SQLDatabaseChain 都可以实现执行SQL命令的操作