一、LangChain是什么
LangChain是一个框架,用于开发由LLM驱动的应用程序。可以简单认为是LLM领域的Spring,以及开源版的ChatGPT插件系统。核心的2个功能为>
1)可以将 LLM 模型与外部数据源进行连接。
2)允许与 LLM 模型与环境进行交互,通过Agent使用工具。
二、LangChain核心组件
LangChain提供了各种不同的组件帮助使用LLM,如下图所示,核心组件有Models、Indexes、Chains、Memory以及Agent。
2.1 Models
LangChain本身不提供LLM,提供通用的接口访问LLM,可以很方便的更换底层的LLM以及自定义自己的LLM。主要有2大类的Models>
1)LLM:将文本字符串作为输入并返回文本字符串的模型,类似OpenAI的text-davinci-003,2)Chat Models:由语言模型支持但将聊天消息列表作为输入并返回聊天消息的模型。一般使用的ChatGPT以及Claude为Chat Models。
与模型交互的,基本上是通过给予Prompt的方式,LangChain通过PromptTemplate的方式方便我们构建以及复用Prompt。
2.2 Indexes
索引和外部数据进行集成,用于从外部数据获取答案。如下图所示,主要的步骤有,1)通过Document Loaders加载各种不同类型的数据源,,2)通过Text Splitters进行文本语义分割,3)通过Vectorstore进行非结构化数据的向量存储,4)通过Retriever进行文档数据检索,
2.2.1 Document Loaders
LangChain通过Loader加载外部的文档,转化为标准的Document类型。Document类型主要包含两个属性:page_content 包含该文档的内容。meta_data 为文档相关的描述性数据,类似文档所在的路径等。
2.2.2 Text Splitters
LLM一般都会限制上下文窗口的大小,有4k、16k、32k等。针对大文本就需要进行文本分割,常用的文本分割器为RecursiveCharacterTextSplitter,可以通过separators指定分隔符。其先通过第一个分隔符进行分割,不满足大小的情况下迭代分割。
文本分割主要有2个考虑:
1)将语义相关的句子放在一块形成一个chunk。一般根据不同的文档类型定义不同的分隔符,或者可以选择通过模型进行分割。
2)chunk控制在一定的大小,可以通过函数去计算。默认通过len函数计算,模型内部一般都是使用token进行计算。token通常指的是将文本或序列数据划分成的小的单元或符号,便于机器理解和处理。使用OpenAI相关的大模型,可以通过tiktoken包去计算其token大小。
2.2.3 Vectorstore
通过Text Embedding models,将文本转为向量,可以进行语义搜索,在向量空间中找到最相似的文本片段。目前支持常用的向量存储有Faiss、Chroma等。
Embedding模型支持OpenAIEmbeddings、HuggingFaceEmbeddings等。通过HuggingFaceEmbeddings加载本地模型可以节省embedding的调用费用。
2.2.4 Retriever
Retriever接口用于根据非结构化的查询获取文档,一般情况下是文档存储在向量数据库中。可以调用 get_relevant_documents 方法来检索与查询相关的文档。
2.3 Chains
Langchain通过chain将各个组件进行链接,以及chain之间进行链接,用于简化复杂应用程序的实现。其中主要有LLMChain、Sequential Chain以及Route Chain,2.3.1 LLMChain
最基本的链为LLMChain,由PromptTemplate、LLM和OutputParser组成。LLM的输出一般为文本,OutputParser用于让LLM结构化输出并进行结果解析,方便后续的调用。
类似下面的示例,给评论进行关键词提前以及情绪分析,通过LLMChain组合PromptTemplate、LLM以及OutputParser,可以很简单的实现一个之前通过依赖小模型不断需要调优的事情。
输出>
2.3.2 Sequential Chain
SequentialChains是按预定义顺序执行的链。SimpleSequentialChain为顺序链的最简单形式,其中每个步骤都有一个单一的输入/输出,一个步骤的输出是下一个步骤的输入。SequentialChain 为顺序链更通用的形式,允许多个输入/输出。
输出>
2.3.3 Router Chain
RouterChain是根据输入动态的选择下一个链,每条链处理特定类型的输入。
RouterChain由两个组件组成>
1)路由器链本身,负责选择要调用的下一个链,主要有2种RouterChain,其中LLMRouterChain通过LLM进行路由决策,EmbeddingRouterChain 通过向量搜索的方式进行路由决策。
2)目标链列表,路由器链可以路由到的子链。
初始化RouterChain以及destination_chains完成后,通过MultiPromptChain将两者结合起来使用。
2.3.4 Documents Chain
下面的4种Chain主要用于Document的处理,在基于文档生成摘要、基于文档的问答等场景中经常会用到,在后续的落地实践里也会有所体现。
2.3.4.1 Stuff
StuffDocumentsChain这种链最简单直接,是将所有获取到的文档作为context放入到Prompt中,传递到LLM获取答案。
这种方式可以完整的保留上下文,调用LLM的次数也比较少,建议能使用stuff的就使用这种方式。其适合文档拆分的比较小,一次获取文档比较少的场景,不然容易超过token的限制。
2.3.4.2 Refine
RefineDocumentsChain是通过迭代更新的方式获取答案。先处理第一个文档,作为context传递给llm,获取中间结果intermediate answer。然后将第一个文档的中间结果以及第二个文档发给llm进行处理,后续的文档类似处理。
Refine这种方式能部分保留上下文,以及token的使用能控制在一定范围。
2.3.4.3 MapReduce
MapReduceDocumentsChain先通过LLM对每个document进行处理,然后将所有文档的答案在通过LLM进行合并处理,得到最终的结果。
MapReduce的方式将每个document单独处理,可以并发进行调用。但是每个文档之间缺少上下文。
2.3.4.4 MapRerank
MapRerankDocumentsChain和MapReduceDocumentsChain类似,先通过LLM对每个document进行处理,每个答案都会返回一个score,最后选择score最高的答案。
MapRerank和MapReduce类似,会大批量地调用LLM,每个document之间是独立处理。
2.4 Memory
正常情况下Chain无状态的,每次交互都是独立的,无法知道之前历史交互的信息。LangChain使用Memory组件保存和管理历史消息,这样可以跨多轮进行对话,在当前会话中保留历史会话的上下文。Memory组件支持多种存储介质,可以与Monogo、Redis、SQLite等进行集成,以及简单直接形式就是Buffer Memory。常用的Buffer Memory有,1)ConversationSummaryMemory :以摘要的信息保存记录,2)ConversationBufferWindowMemory:以原始形式保存最新的n条记录,3)ConversationBufferMemory:以原始形式保存所有记录,通过查看chain的prompt,可以发现{history}变量传递了从memory获取的会话上下文。下面的示例演示了Memory的使用方式,可以很明细看到,答案是从之前的问题里获取的。
输出>
2.5 Agent
Agent字面含义就是代理,如果说LLM是大脑,Agent就是代理大脑使用工具Tools。目前的大模型一般都存在知识过时、逻辑计算能力低等问题,通过Agent访问工具,可以去解决这些问题。目前这个领域特别活跃,诞生了类似AutoGPT、BabyAGI、AgentGPT等一堆优秀的项目。传统使用LLM,需要给定Prompt一步一步地达成目标,通过Agent是给定目标,其会自动规划并达到目标。
2.5.1 Agent核心组件
Agent:代理,负责调用LLM以及决定下一步的Action。其中LLM的prompt必须包含agent_scratchpad变量,记录执行的中间过程,Tools:工具,Agent可以调用的方法。LangChain已有很多内置的工具,也可以自定义工具。注意Tools的description属性,LLM会通过描述决定是否使用该工具。
ToolKits:工具集,为特定目的的工具集合。类似Office365、Gmail工具集等,Agent Executor:Agent执行器,负责进行实际的执行。
2.5.2 Agent的类型
一般通过initialize_agent函数进行Agent的初始化,除了llm、tools等参数,还需要指定AgentType。
该Agent为一个zero-shot-react-description类型的Agent,其中zero-shot表明只考虑当前的操作,不会记录以及参考之前的操作。react表明通过ReAct框架进行推理,description表明通过工具的description进行是否使用的决策。
其他的类型还有chat-conversational-react-description、conversational-react-description、react-docstore、self-ask-with-search等,类似chat-conversational-react-description通过memory记录之前的对话,应答会参考之前的操作。
可以通过agent.agent.llm_chain.prompt.template方法,获取其推理决策所使用的模板。
2.5.3 自定义Tool
有多种方式可以自定义Tool,最简单的方式是通过@tool装饰器,将一个函数转为Tool。注意函数必须得有docString,其为Tool的描述。
输出为>
三、LangChain落地实践
3.1 文档生成总结
1)通过Loader加载远程文档,2)通过Splitter基于Token进行文档拆分,3)加载summarize链,链类型为refine,迭代进行总结
3.2 基于外部文档的问答
1)通过Loader加载远程文档,2)通过Splitter基于Token进行文档拆分,3)通过FAISS向量存储文档,embedding加载HuggingFace的text2vec-base-chinese模型,4)自定义QA的prompt,通过RetrievalQA回答相关的问题
随着大模型的发展,LangChain应该是目前最火的LLM开发框架,能和外部数据源交互、能集成各种常用的组件等等,大大降低了LLM应用开发的门槛。其创始人Harrison Chase也和Andrew Ng联合开发了2门短课程,帮忙大家快速掌握LangChain的使用。
目前大模型的迭代升级特别快,作为一个框架,LangChain也得保持特别快的迭代速度。其开发特别拼,每天都会提交大量的commit,基本隔几天就会发布一个新版本,其Contributor也达到了1200多人,特别活跃。
个人认为,除了和业务结合落地LLM应用外,还有2个大的方向可以进一步去探索>
1)通过低代码的形式进一步降低LLM应用的开发门槛。类似langflow这样的可视化编排工具发展也很快,2)打造更加强大的Agent。Agent之于大模型,个人觉得类似SQL之于DB,能大幅度提升LLM的应用场景