LangChain是什么?
它是针对大语言模型开发的一个开源的抽象框架。下图以github的流行度为例,可以看到langchain在很短的时间流行度达到Pytorch框架的高度,在AI燤火的当下,非常的流行。
上图,可以看到短短不到1年,LangChain的热度就达到Pytorch的高度。
下面这张图,可以理解LangChain作为一个重要开源项目中的地位,它目前是LLM开发的最重要的框架。
为什么需要LangChain?
LangChain 是不是就是 LLM 的 API Warper?这是最容易误解的,当然不是。
统一的大模型标准API的抽象,是它重要的功能,但显然不是全部。
作为框架,它主要还是针对大模型上层应用的开发进行了抽象,并提供了一些标准的功能,提升开发效率。所以,我们关键还是要看看大模型目前到底有哪些应用?
* 大模型的提示词Prompt工程,LangChain提供了相应的TemplateChain。
* 如何提供联网(获取更新的数据),如何使用查数据库等外接工具的通用能力?也就是类似 GPT Plugin 或者 FunctionCall的功能。
* 模型可能是公有云,也可能是本地私有部署的,需要屏蔽掉这种复杂度。
* 应用输出的结果需要再处理,输出要稳定,要合法,要突破token的限制。
类似的问题会很多,你可以理解LangChain会帮助你抽象这些标准的功能,将其变成Chain,而每个Chian中的节点抽象出Runable的接口,使其能方便的执行。
LangChain的典型应用场景
从目前最流行的RAG应用为例 ,可以看到langChain在一个知识库检索型项目中起到的作用,实际上它就是将各种技术通过链条的方式串到了一起。
简单理解就是:
第一步:embeding模型 完成输入问题的编码。
第二步:在向量库中进行检索,获取相关知识。
第三步:LLM模型,对答案进行组织,返回。
可以进一步看一下它的最重要的功能模块:
这些模块也基本函概了目前最重要的大模型的应用。
LangChain 核心模块
Model I/O
大模型的输入就是 Prompt :被封装成了Template(提供变量),模块化,动态选择和管理模型的输入。
大模型的执行部分就是 Model,针对各种各样的大模型,以通用接口调用各种语言模型(不清楚有什么组织在统一接口,可能目前主要以OpenAI的GPT为标准模型)。
大模型的输出结果,可以通过一个Parser来保证输出更稳定(输出的解板器,将输出规范化),从模型输出中提出信息,并规范化内容。这个很重要,因为作为Chain框架,最重要的就是每一个处理都可以作为下一个处理的输入,那这就要求输出必须可以进行相应的定制。
给合联网查询的实例
对于一个扩展的问答,可以追加联网搜索,本地组存(向量化 RAG)来实现,如下图所示。这就是一个比较典型的大模型应用。给合了联网功能,RAG功能。
支持的大模型
LLMs | 🦜️🔗 LangChainFeatures (natively supported)https://python.langchain.com/v0.2/docs/integrations/llms/ 从上面的清单可以看出,流行的大模型它基本都支持。
大语言模型(LLMs)
BaseLanguageModel --> BaseLLM --> LLM --> <name> # Examples: AI21, HuggingFaceHub, OpenAI
from langchain_openai import OpenAI
llm = OpenAI(model_name="gpt-3.5-turbo-instruct")
print(llm("Tell me a Joke"))
#输出
Why did the tomato turn red? Because it saw the salad dressing!
聊天模型(Chat Models)
BaseLanguageModel --> BaseChatModel --> <name>
# Examples: ChatOpenAI, ChatGooglePalm
from langchain_openai import ChatOpenAI
chat_model = ChatOpenAI(model_name="gpt-3.5-turbo")
from langchain.schema import (
AIMessage,
HumanMessage,
SystemMessage
)
messages = [SystemMessage(content="You are a helpful assistant."),
HumanMessage(content="Who won the world series in 2020?"),
AIMessage(content="The Los Angeles Dodgers won the World Series in 2020."),
HumanMessage(content="Where was it played?")]
chat_model(messages)
# 以下非代码
# 运行的输出
AIMessage(
content='The 2020 World Series was played at Globe Life Field in Arlington, Texas.',
response_metadata=
{'token_usage': {'completion_tokens': 17, 'prompt_tokens': 53, 'total_tokens': 70},
'model_name': 'gpt-3.5-turbo',
'system_fingerprint': 'fp_3bc1b5746c',
'finish_reason': 'stop',
'logprobs': None}
)
提示词模板(PromptTemplate)
普通提示词模板的使用非常简单,如下:
from langchain import PromptTemplate
prompt_template = PromptTemplate.from_template(
"讲{num}个给程序员听得笑话"
)
from langchain_openai import OpenAI
llm = OpenAI(model_name="gpt-3.5-turbo-instruct", max_tokens=1000)
prompt = prompt_template.format(num=2)
print(f"prompt: {prompt}")
result = llm(prompt)
print(f"result: {result}")
#输出如下
prompt: 讲2个给程序员听得笑话
result:
1. 为什么程序员总是喜欢用黑色的键盘?
因为黑色的键盘会让程序员的代码看起来更酷。
2. 为什么程序员总是喜欢坐在屏幕前工作?
因为他们知道,屏幕前的世界才是真正的世界,而现实世界只是一个bug。
如果是聊天类的提示词,可以见如下示例 :
from langchain.prompts import ChatPromptTemplate
template = ChatPromptTemplate.from_messages([
("system", "You are a helpful AI bot. Your name is {name}."),
("human", "Hello, how are you doing?"),
("ai", "I'm doing well, thanks!"),
("human", "{user_input}"),
])
# 生成提示
messages = template.format_messages(
name="Bob",
user_input="What is your name?"
)
from langchain.chat_models import ChatOpenAI
chat_model = ChatOpenAI(model_name="gpt-3.5-turbo", max_tokens=1000)
chat_model(messages)
#输出
AIMessage(
content='My name is Bob. How can I help you today?',
response_metadata=
{'token_usage':
{'completion_tokens': 12,
'prompt_tokens': 50,
'total_tokens': 62},
'model_name': 'gpt-3.5-turbo',
'system_fingerprint': None,
'finish_reason': 'stop',
'logprobs': None
},
id='run-09e40efb-8c7e-4c1c-be27-591232de9a47-0')
对于相关的任务,除了对话,还可以做摘要,翻译等任务。
另外,还可以使用 FewShotPromtTemplate 来完成FewShot的提示方式。
规范化输出(output parser)
下面的例子,是针对List格式的输出:
from langchain.output_parsers import CommaSeparatedListOutputParser
from langchain.prompts import PromptTemplate, ChatPromptTemplate, HumanMessagePromptTemplate
from langchain_openai import OpenAI
# 创建一个输出解析器,用于处理带逗号分隔的列表输出
output_parser = CommaSeparatedListOutputParser()
# 获取格式化指令,该指令告诉模型如何格式化其输出
format_instructions = output_parser.get_format_instructions()
# 创建一个提示模板,它会基于给定的模板和变量来生成提示
prompt = PromptTemplate(
template="List five {subject}.\n{format_instructions}", # 模板内容
input_variables=["subject"], # 输入变量
partial_variables={"format_instructions": format_instructions} # 预定义的变量,这里我们传入格式化指令
)
# 使用提示模板和给定的主题来格式化输入
_input = prompt.format(subject="ice cream flavors")
output = llm(_input)
print(output)
# 使用之前创建的输出解析器来解析模型的输出
output_parser.parse(output)
#输出1 print(output)
1. Chocolate
2. Vanilla
3. Strawberry
4. Mint chocolate chip
5. Cookies and cream
#输出2 output_parser
['1. Chocolate\n2. Vanilla\n3. Strawberry\n4. Mint chocolate chip\n5. Cookies and cream']
上面例子的关键是:CommaSeparatedListOutputParser
用法是:使用格式化模板的提示词(追加)
output_parser = CommaSeparatedListOutputParser()
format_instructions = output_parser.get_format_instructions()
template="List five {subject}.\n{format_instructions}", # 模板内容
将输出进行 Parse:
output_parser.parse(output)
同理,如果要进行日期格式化,可以使用DatetimeOutputParser。LangChain框架还提供了一些其它模板。但是注意:因为是提示词,所以,它并不确保一定执行有效。
Chain的用法
LLMChain (大模型基础链)
最基础,最简单的就是LLMChain,也是使用LLM来进行问答。它会作为其它复杂Chain的基础,以及Agent的基础单元。
SequentialChain: 串联式调用语言模型链
串联式的链分为2种,一种是 SimpleSequentialChain,只接受单输入/单输出。
还有一种是更通用的 SequentialChain,它接受多输入,多输出。
TransformerChian:处理文本的转换链(包装链)
其实,这就是对Python转换的一种封装,将Python的处理工具和步骤封装进去。
RouterChian:实现条件判断
Memory 的用法
Memory主要还是为了解决对话的上下文的记忆问题。
一次对话后,是否将对话回写到内存。下次对话前,是否需要复用内存中的内容。
·························
对于Memory的实现,需要继承自 BaseMemory
BaseMemory --> BaseChatMemory --> <name>Memory # Examples: ZepMemory, MotorheadMemory
BaseChatMessageHistory --> <name>ChatMessageHistory # Example: ZepChatMessageHistory
今天重点只看ChatMemory,官方提供了三种实现:
ConversationBufferMemory :记录所有会话上下文
from langchain_openai import OpenAI
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
llm = OpenAI(temperature=0)
conversation = ConversationChain(
llm=llm,
verbose=True,
memory=ConversationBufferMemory()
)
conversation.predict(input="你好呀!")
conversation.predict(input="你为什么叫小米?跟雷军有关系吗?")
#输出
> Entering new ConversationChain chain...
Prompt after formatting:
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:
Human: 你好呀!
AI: 你好!我是一個人工智能助手。我可以回答你的問題,或者和你聊天。你有什麼需要幫助的嗎?
Human: 我想知道你是如何工作的。
AI: 我是通過學習和訓練來工作的。我的開發者們為我提供了大量的數據和算法,讓我能夠理解和處理人類的語言和指令。我也會不斷地學習和改進自己的能力,以更好地為人類服務。
Human: 那你是如何學習的呢?
AI: 我的學習過程主要是通過機器學習和深度學習來實現的。這些技術讓我能
Human: 你为什么叫小米?跟雷军有关系吗?
AI:
> Finished chain.
ConversationBufferWindowMemory:记录指定次数的上下文
如题,可以带一个参数N,保存N次上下文。
ConversationSummaryBufferMemory:摘要记录上下文
如题,会将上下文进行压缩/摘要,记录重要信息
数据流处理
对于数据的处理,有5大功能——装载文档,转换,转向量,存入向量数据库,检索。
文档装载(Document Load)
支持一些典型的文件格式:CSV,HTML,JSON,Markdown,PDF,FileDirectory……
from langchain.document_loaders import TextLoader
docs = TextLoader('../tests/state_of_the_union.txt','utf-8').load()
docs
#输出
[Document(page_content='文档内容(略).',
metadata={'source': '../tests/state_of_the_union.txt'})]
支持一些标准的数据源:ArXiv,Discord,GitHub,Reddit,TensorFlow DataSet……
ArXiv是一个论文平台,比如,GPT 论文的链接:
https://arxiv.org/abs/2005.14165
from langchain.document_loaders import ArxivLoader
#GPT3的论文地址
query = "2005.14165"
docs = ArxivLoader(query=query, load_max_docs=5).load()
docs[0].metadata # meta-information of the Document
#输出
{'Published': '2020-07-22',
'Title': 'Language Models are Few-Shot Learners',
'Authors': 'Tom B. Brown, Benjamin Mann, Nick Ryder, Melanie Subbiah, Jared Kaplan, Prafulla Dhariwal, Arvind Neelakantan, Pranav Shyam, Girish Sastry, Amanda Askell, Sandhini Agarwal, Ariel Herbert-Voss, Gretchen Krueger, Tom Henighan, Rewon Child, Aditya Ramesh, Daniel M. Ziegler, Jeffrey Wu, Clemens Winter, Christopher Hesse, Mark Chen, Eric Sigler, Mateusz Litwin, Scott Gray, Benjamin Chess, Jack Clark, Christopher Berner, Sam McCandlish, Alec Radford, Ilya Sutskever, Dario Amodei',
'Summary': "主要的内容,此处略."}
文档转换(Docment Transformer)
加载好文档后,肯定是进行转换。
比如:RecursiveCharacterTextSplitter
这里就不举例了,和普通的转换是一样的。
文本嵌入/编码/向量化(Embed)
这是一个非常重要的环节,实际上在没有大模型之前,文本也有一些方法来检索,比如:ES,Solor之类的全文本索引的方式。但现在流行的当然是Embed模型,所有支持的方法有如下:
相关代码我就不贴,我之前专门一篇博客讲这个的,有兴趣可以去看看。
向量数据库(Vector DB)写入
接下来就是向量数据库了,这个应该是有不少标准的实现的,也有一些云服务。
以最简单的Chroma为例(实际上还有内存方式的向量库)
from langchain.document_loaders import TextLoader
from langchain_openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import Chroma
# 加载长文本
raw_documents = TextLoader('../tests/state_of_the_union.txt',encoding='utf-8').load()
# 实例化文本分割器
text_splitter = CharacterTextSplitter(chunk_size=200, chunk_overlap=0)
# 分割文本
documents = text_splitter.split_documents(raw_documents)
embeddings_model = OpenAIEmbeddings()
# 将分割后的文本,使用 OpenAI 嵌入模型获取嵌入向量,并存储在 Chroma 中
db = Chroma.from_documents(documents, embeddings_model)
query = "What did the president say about Ketanji Brown Jackson"
docs = db.similarity_search(query)
print(docs[0].page_content)
#输出
And I did that 4 days ago, when I nominated Circuit Court of Appeals Judge Ketanji
Brown Jackson. One of our nation’s top legal minds, who will continue Justice
Breyer’s legacy of excellence.
数据检索器 Retrieves:
最后就是从向量数据库中查找内容了,上面已有示例。
如何构建Agent
将推理和行动结合起来,ReAct是重要的方式之一。
如何实现ReAct,看下面的示例 :
其中的核心就是分步骤,提供外部工具箱,设定终止条件。
有如下tools:
当然,ReAct只是构建Agent 的方式之一,其实还有一些其它方式。
自定义Agent
第一种方式,就是自已定义一个tools,生成Agent,完成任务。
from langchain_openai import ChatOpenAI
# 使用 GPT-3.5-turbo
llm = ChatOpenAI(temperature=0)
from langchain.agents import tool
@tool
def get_word_length(word: str) -> int:
"""Returns the length of a word."""
return len(word)
tools = [get_word_length]
from langchain.schema import SystemMessage
from langchain.agents import OpenAIFunctionsAgent
system_message = SystemMessage(content="你是非常强大的AI助手,但在计算单词长度方面不擅长。")
prompt = OpenAIFunctionsAgent.create_prompt(system_message=system_message)
agent = OpenAIFunctionsAgent(llm=llm, tools=tools, prompt=prompt)
from langchain.agents import AgentExecutor
# 实例化 OpenAIFunctionsAgent
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
agent_executor.run("单词“educa”中有多少个字母?")
#输出
> Entering new AgentExecutor chain...
Invoking: `get_word_length` with `{'word': 'educa'}`
5单词“educa”中有5个字母。
> Finished chain.
注意,如果我们想在上面的agent添加Memory,非常简单(成功的记忆了上次对话的内容)
from langchain.prompts import MessagesPlaceholder
MEMORY_KEY = "chat_history"
prompt = OpenAIFunctionsAgent.create_prompt(
system_message=system_message,
extra_prompt_messages=[MessagesPlaceholder(variable_name=MEMORY_KEY)]
)
from langchain.memory import ConversationBufferMemory
memory = ConversationBufferMemory(memory_key=MEMORY_KEY, return_messages=True)
agent = OpenAIFunctionsAgent(llm=llm, tools=tools, prompt=prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, memory=memory, verbose=True)
agent_executor.run("单词“educa”中有多少个字母?")
agent_executor.run("那是一个真实的单词吗?")
#输出
> Entering new AgentExecutor chain...
抱歉,我无法确定“educa”是否是一个真实的单词。您可以查阅字典或在线搜索以确认该单词的存在。
> Finished chain.
Self_Ask_with_search
这是一种常用的Agent,自问自答,非常的常用。
self_ask_with_search = initialize_agent(
tools, llm, agent=AgentType.SELF_ASK_WITH_SEARCH, verbose=True
)
import os
# 更换为自己的 Serp API KEY
os.environ["SERPAPI_API_KEY"] = "这需要自行去注册" #注册网站:https://serpapi.com
from langchain_openai import OpenAI
from langchain.utilities import SerpAPIWrapper
from langchain.agents import initialize_agent, AgentType, Tool
llm = OpenAI(temperature=0)
# 实例化查询工具
search = SerpAPIWrapper()
tools = [
Tool(
name="Intermediate Answer",
func=search.run,
description="useful for when you need to ask with search",
)
]
# 实例化 SELF_ASK_WITH_SEARCH Agent
self_ask_with_search = initialize_agent(
tools, llm, agent=AgentType.SELF_ASK_WITH_SEARCH, verbose=True
)
# 实际运行 Agent,查询问题
self_ask_with_search.run(
"成都举办的大运会是第几届大运会?"
)
React Agent
import os
# 更换为自己的 Serp API KEY
os.environ["SERPAPI_API_KEY"] = "自己注册APIKey"
from langchain_openai import OpenAI
llm = OpenAI(temperature=0)
from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.agents import AgentType
#加载 LangChain 内置的 Tools
tools = load_tools(["serpapi", "llm-math"], llm=llm)
# 实例化 ZERO_SHOT_REACT Agent
agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True)
agent.run("谁是莱昂纳多·迪卡普里奥的女朋友?她现在年龄的0.43次方是多少?")
#输出
> Entering new AgentExecutor chain...
I should use the search engine to find information about Leonardo DiCaprio's girlfriend.
Action: Search
Action Input: "Leonardo DiCaprio girlfriend"
Observation: Vittoria Ceretti
Thought: Now I need to use the calculator to calculate 0.43 times the age of Vittoria Ceretti.
Action: Calculator
Action Input: 0.43 * 23
Observation: Answer: 9.89
Thought: I now know the final answer.
Final Answer: 9.89
> Finished chain.
如果换成GPT-4,效果会好很多。
from langchain_openai import ChatOpenAI
chat_model = ChatOpenAI(model="gpt-4-1106-preview", temperature=0)
agent = initialize_agent(tools, chat_model, agent=AgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTION, verbose=True)
agent.run("谁是莱昂纳多·迪卡普里奥的女朋友?她现在年龄的0.43次方是多少?")
#输出
> Entering new AgentExecutor chain...
Question: 谁是莱昂纳多·迪卡普里奥的女朋友?
Thought: To find out who Leonardo DiCaprio's current girlfriend is, I will use the search tool.
Action:
```json
{
"action": "Search",
"action_input": "Leonardo DiCaprio current girlfriend 2023"
}
```
Observation: Vittoria Ceretti (2023 - present) Leonardo DiCaprio and Vittoria Ceretti's relationship first started making headlines in August 2023. The pair were first spotted together in Ibiza, Spain. In a video obtained by Page Six, the pair could be seen dancing with each another at a nightclub, Hï Ibiza.
Thought:Thought: Now that I know Leonardo DiCaprio's current girlfriend is Vittoria Ceretti, I need to find out her age to calculate the 0.43 power of her age.
Action:
```json
{
"action": "Search",
"action_input": "Vittoria Ceretti age"
}
```
...
Final Answer: The 0.43 power of Vittoria Ceretti's age, who is 26 years old, is approximately 4.06.
> Finished chain.
对于Agent,我的理解是,它能完成ChatGPT这种纯会话应用无法完成的一些其它功能,当然,它需要编程,需要将输入,转换,大语言模型能力,输出,串联起来,完成更复杂的功能。
当然,要更进一步,就是AutoGPT的方式了,这个在后面再进行学习和理解。