LangChain介绍
介绍
LangChain自身并不开发LLMs,它的核心理念是为各种LLMs实现通用的接口,把LLMs相关的组件“链接”在一起,简化LLMs应用的开发难度,方便开发者快速地开发复杂的LLMs应用.
组件类型
-
Models:模型,各种类型的模型和模型继承,比如GPT-4
-
Prompts:提示,包括提示管理、提示优化和提示序列化
-
Memory:记忆,用来保存和模型交互时的上下文状态
-
Indexes:
索引,用来结构化文档,以便和模型交互 -
Chains:链,一系列对各种组件的调用
-
Agents:代理,决定模型采取哪些行动,执行并且观察流程,直到完成为止
Models组件
LLMs
大语言模型接收文本字符作为输入,返回的也是文本字符。
from langchain_community.llms import Ollama # from langchain.llms import Ollama import shutup shutup.please() model = Ollama(model='qwen2.5:7b') res = model('给我讲个鬼故事吧') print(res)
Chat Models
基于LLMs, 不同的是它接收聊天消息(一种特定格式的数据)作为输入,返回的也是聊天消息。
Chat Models和LLMs效果在某些场景表现基本类似,但是使用时需要按照约定传入合适的值。
聊天模式:
from langchain_community.chat_models import ChatOllama from langchain_core.messages import AIMessage, HumanMessage, SystemMessage model = ChatOllama(model='qwen2.5:7b') # message = [ # SystemMessage(content='现在你是一个诗人'), # HumanMessage(content='给我写一首唐诗') # ] # # res = model.invoke(message) # print(res) # print(res.content) message = [ SystemMessage(content='现在你是一个诗人'), # 类似指定任务类型,同时可以指定返回格式 HumanMessage(content='给我写一首唐诗'), # 提出问题 AIMessage(content='清风拂柳绿,江水映碧空。\n鸟鸣山更幽,心静万物融。'), # 用于存储历史信息 HumanMessage(content='再写一首宋词') ] res = model.invoke(message) print(res) print(res.content)
Embedding Models
文本嵌入模型接收文本作为输入, 返回的是浮点数列表。
Embeddings Models特点:将字符串作为输入,返回一个浮动数的列表。在NLP中,Embedding的作用就是将数据进行文本向量化。
from langchain_community.embeddings import OllamaEmbeddings # model = OllamaEmbeddings(model='mxbai-embed-large', temperaure=0) model = OllamaEmbeddings(model='qwen2.5:7b', temperature=0) res1 = model.embed_query('这是一个测试文档') print(res1) print(len(res1)) # qwen2.5:7b: 3584 mxbai-embed-large:1024 res2 = model.embed_documents(['这是第一个测试文档', '这是第二个测试文档']) # print(res2)
Prompts组件
Prompt是指当用户输入信息给模型时加入的提示,这个提示的形式可以是zero-shot或者few-shot等方式,目的是让模型理解更为复杂的业务场景以便更好的解决问题。
提示模板:如果你有了一个起作用的提示,你可能想把它作为一个模板用于解决其他问题,LangChain就提供了PromptTemplates组件,它可以帮助你更方便的构建提示。
zero-shot
from langchain import PromptTemplate from langchain_community.llms import Ollama model = Ollama(model='qwen2.5:7b') template = "我的邻居姓{lastname},他的妻子姓{wname},他生了个儿子,给他儿子取个名字" prompt = PromptTemplate( input_variables=['lastname', 'wname'], template=template ) prompt_text =prompt.format(lastname='杜', wname='李') print(prompt_text) res = model(prompt_text) print(res)
few-shot
# from langchain import PromptTemplate, FewShotPromptTemplate from langchain import PromptTemplate, FewShotPromptTemplate from langchain_community.llms import Ollama model = Ollama(model='qwen2.5:7b') examples = [ {'word': '开心', 'antonym': '难过'}, {'word': '高', 'antonym': '矮'} ] example_template = """ 单词: {word} 反义词: {antonym}\n """ example_prompt = PromptTemplate( input_variables=['word', 'antonym'], template=example_template ) few_shot_prompt = FewShotPromptTemplate( examples=examples, example_prompt=example_prompt, prefix='给出每个单词的反义词', suffix='单词: {input}\n反义词:', input_variables=['input'], example_separator='\n' ) prompt_tex = few_shot_prompt.format(input='粗') print(model(prompt_tex))
Chains组件
单一链
from langchain import PromptTemplate from langchain_community.llms import Ollama from langchain.chains import LLMChain template = "我的邻居姓{lastname},他生了个儿子,给他儿子取个名字" prompt = PromptTemplate( input_variables=['lastname'], template=template ) llm = Ollama(model='qwen2.5:7b') # chain = LLMChain(llm=llm, prompt=prompt) chain = prompt | llm # 效果同上 print(chain.invoke('杜'))
多条链
from langchain import PromptTemplate from langchain_community.llms import Ollama from langchain.chains import LLMChain, SimpleSequentialChain # 创建第一条链 first_prompt = PromptTemplate( input_variables=['name'], template="我的邻居姓{name},他生了个儿子,给他儿子取个名字" ) llm = Ollama(model='qwen2.5:7b') # 第一条链 # first_chain = first_prompt | llm first_chain = LLMChain(llm=llm, prompt=first_prompt) # 创建第二条链 second_prompt = PromptTemplate( input_variables=['child_name'], template="邻居的儿子叫{child_name},给他儿子取个小名" ) # 第二条链 # second_chain = second_prompt | llm second_chain = LLMChain(llm=llm, prompt=second_prompt) # 组装两条链 overall_chain = SimpleSequentialChain(chains=[first_chain, second_chain], verbose=True) print(overall_chain) print('*'*80) # 执行链 res = overall_chain.invoke('杜') print(res)
Agents组件
在 LangChain 中 Agents 的作用就是根据用户的需求,来访问一些第三方工具(比如:搜索引擎或者数据库),进而来解决相关需求问题。
因为大模型虽然非常强大,但是也具备一定的局限性,比如不能回答实时信息、处理数学逻辑问题仍然非常的初级等等。因此,可以借助第三方工具来辅助大模型的应用。
from langchain.agents import load_tools, initialize_agent, AgentType from langchain_community.llms import Ollama from langchain_core.prompts import PromptTemplate llm = Ollama(model='qwen2.5:7b') tools = load_tools(['llm-math'], llm=llm) agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True) prompt_template = """ 解以下方程:3x + 4(x + 2) - 84 = y; 其中x为3,请问y是多少? """ prompt = PromptTemplate.from_template(prompt_template) print('prompt-->', prompt) res = agent.run(prompt) print(res) from langchain_community.agent_toolkits.load_tools import get_all_tool_names results = get_all_tool_names() print(results) # ['sleep', 'wolfram-alpha', 'google-search', 'google-search-results-json', 'searx-search-results-json', 'bing-search', # 'metaphor-search', 'ddg-search', 'google-books', 'google-lens', 'google-serper', 'google-scholar', 'google-finance', # 'google-trends', 'google-jobs', 'google-serper-results-json', 'searchapi', 'searchapi-results-json', 'serpapi', # 'dalle-image-generator', 'twilio', 'searx-search', 'merriam-webster', 'wikipedia', 'arxiv', 'golden-query', 'pubmed', # 'human', 'awslambda', 'stackexchange', 'sceneXplain', 'graphql', 'openweathermap-api', 'dataforseo-api-search', # 'dataforseo-api-search-json', 'eleven_labs_text2speech', 'google_cloud_texttospeech', 'read_file', 'reddit_search', # 'news-api', 'tmdb-api', 'podcast-api', 'memorize', 'llm-math', 'open-meteo-api', 'requests', 'requests_get', # 'requests_post', 'requests_patch', 'requests_put', 'requests_delete', 'terminal']
Memory组件
大模型本身不具备上下文的概念,它并不保存上次交互的内容,ChatGPT之所以能够和人正常沟通对话,因为它进行了一层封装,将历史记录回传给了模型。
因此 LangChain 也提供了Memory组件, Memory分为两种类型:短期记忆和长期记忆。
短期记忆一般指单一会话时传递数据,长期记忆则是处理多个会话时获取和更新信息。
记录
from langchain_community.chat_message_histories import ChatMessageHistory import shutup shutup.please() history = ChatMessageHistory() history.add_user_message('在吗?') history.add_ai_message('干啥') # print(history.messages) # [HumanMessage(content='在吗?', additional_kwargs={}, response_metadata={}), # AIMessage(content='干啥', additional_kwargs={}, response_metadata={})] from langchain import ConversationChain from langchain_community.llms import Ollama llm = Ollama(model='qwen2.5:7b') conv = ConversationChain(llm=llm) res1 = conv.predict(input='小杜有1个纸片人') print(res1) res2 = conv.predict(input='小红有两只猫') print(res2) res3 = conv.predict(input='他们一共有多少物品') print(res3)
保存
from langchain_community.chat_message_histories import ChatMessageHistory from langchain.schema import messages_from_dict, messages_to_dict import shutup shutup.please() history = ChatMessageHistory() history.add_user_message('在吗?') history.add_ai_message('干啥') src_dict = messages_to_dict(history.messages) print(src_dict) new_dict = messages_from_dict(src_dict) print(new_dict) """ [{ 'type': 'human', 'data': { 'content': '在吗?', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': None, 'example': False } }, { 'type': 'ai', 'data': { 'content': '干啥', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': None, 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None } }] [HumanMessage(content='在吗?', additional_kwargs={}, response_metadata={}), AIMessage(content='干啥', additional_kwargs={}, response_metadata={})] """
Indexes组件
Indexes组件的目的是让LangChain具备处理文档处理的能力,包括:文档加载、检索等。注意,这里的文档不局限于txt、pdf等文本类内容,还涵盖email、区块链、视频等内容。
文档加载器
from langchain_community.document_loaders import TextLoader, PyPDFLoader import shutup shutup.please() # loader = TextLoader('./data/衣服属性.txt', encoding='utf8') loader = PyPDFLoader('./data/物流行业智能问答RAG系统.pdf') docs = loader.load() print(docs[0]) print(docs)
支持文档类型
文档分割器
由于模型对输入的字符长度有限制,我们在碰到很长的文本时,需要把文本分割成多个小的文本片段。
文本分割最简单的方式是按照字符长度进行分割,但是这会带来很多问题,比如说如果文本是一段代码,一个函数被分割到两段之后就成了没有意义的字符,所以整体的原则是把语义相关的文本片段放在一起。
from langchain.text_splitter import CharacterTextSplitter my_spliter = CharacterTextSplitter( # 若分割前后包含空格,会自动将空格去除 separator=' ', # 分割标识,默认:\n\n chunk_size=5, # 分割后每个片段的大小 chunk_overlap=1 # 分割后两个片段的重叠部分大小 ) a = my_spliter.split_text('a b c d e f') print(a) # ['a b c', 'c d e', 'e f'] b = my_spliter.create_documents(['a b c d e f', 'e f g h']) print(b) # [Document(metadata={}, page_content='a b c'), # Document(metadata={}, page_content='c d e'), # Document(metadata={}, page_content='e f'), # Document(metadata={}, page_content='e f g'), # Document(metadata={}, page_content='g h')]
VectorStores
VectorStores是一种特殊类型的数据库,它的作用是存储由嵌入创建的向量,提供相似查询等功能。
我们使用其中一个 Chroma 组件作为例子(pip install chromadb):
from langchain_community.embeddings import OllamaEmbeddings from langchain.text_splitter import CharacterTextSplitter from langchain_community.vectorstores import Chroma with open('./data/pku.txt', encoding='utf-8') as f: content = f.read() my_spliter = CharacterTextSplitter(chunk_size=100, chunk_overlap=0) # 虽然设置了最大切分长度,但是优先根据语义信息进行切分 texts = my_spliter.split_text(content) print(texts) print(len(texts)) embed = OllamaEmbeddings(model='mxbai-embed-large') docsearch = Chroma.from_texts(texts, embed) q = '1937年北京大学发生了什么事情?' print(docsearch.similarity_search(q, k=1))
支持的向量数据库
检索器
检索器(retriever)是一种便于模型查询的存储数据的方式,LangChain约定检索器组件至少有一个方法
get_relevant_texts
,这个方法接收查询字符串,返回一组文档。(pip install faiss-cpu)
from langchain_community.document_loaders import TextLoader from langchain.text_splitter import CharacterTextSplitter from langchain_community.vectorstores import FAISS from langchain_community.embeddings import OllamaEmbeddings loader = TextLoader('./data/pku.txt', encoding='utf-8') documents = loader.load() my_spliter = CharacterTextSplitter( chunk_size=100, chunk_overlap=0 ) texts = my_spliter.split_documents(documents) print(texts) print(len(texts)) embed = OllamaEmbeddings(model='mxbai-embed-large') db = FAISS.from_documents(texts, embed) retriever = db.as_retriever(search_kwargs={'k': 1}) print(retriever.get_relevant_documents('北京大学是什么时候创建的?'))