导语
在人工智能范畴的不断发展中,言语模型扮演着重要的人物。特别是大型言语模型(LLM),如ChatGPT,现已成为科技范畴的抢手话题,并受到广泛认可。在这个背景下,LangChain作为一个以LLM模型为核心的开发结构出现,为自然言语处理敞开了一个充满或许性的国际。借助LangChain,咱们能够创立各种运用程序,包括谈天机器人和智能问答东西。
1. LangChain简介
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模型为例:
- 数据滞后,现在练习的数据是到 2021 年9月。
- token数量约束,假如让它对一个300页的pdf进行总结,直接运用则力不从心。
- 不能进行联网,获取不到最新的内容。
- 不能与其他数据源链接。 别的作为一个胶水层结构,极大地提高了开发功率,它的效果能够类比于jquery在前端开发中的人物,使得开发者能够更专注于创新和优化产品功用。
1.3.LLM运用架构
LangChian作为一个大言语模型开发结构,是LLM运用架构的重要一环。那什么是LLM运用架构呢?其实便是指依据言语模型的运用程序规划和开发的架构。 LangChian能够将LLM模型、向量数据库、交互层Prompt、外部常识、外部东西整合到一同,进而能够自由构建LLM运用。
2. LangChain组件
如上图,LangChain包括六部分组成,分别为:Models、Prompts、Indexes、Memory、Chains、Agents。
2.1. Models(模型)
下面咱们以详细示例分别论述下Chat Modals, Embeddings, LLMs。
2.1.1. 谈天模型
LangChain为运用谈天模型供给了一个标准接口。谈天模型是言语模型的一种变体。虽然谈天模型在内部运用言语模型,但它们所供给的接口略有不同。它们不是暴露一个 “输入文本,输出文本” 的API,而是供给了一个以 “谈天音讯” 作为输入和输出的接口。 谈天模型的接口是依据音讯而不是原始文本。LangChain 现在支撑的音讯类型有 AIMessage、HumanMessage、SystemMessage 和 ChatMessage,其中 ChatMessage 承受一个任意的人物参数。大多数情况下,您只需求处理 HumanMessage、AIMessage 和 SystemMessage。
# 导入OpenAI的谈天模型,及音讯类型
from langchain.chat_models import ChatOpenAI
from langchain.schema import (
AIMessage,
HumanMessage,
SystemMessage
)
# 初始化谈天目标
chat = ChatOpenAI(openai_api_key="...")
# 向谈天模型发问
chat([HumanMessage(content="Translate this sentence from English to French: I love programming.")])
OpenAI谈天模式支撑多个音讯作为输入。这是一个系统和用户音讯谈天模式的比如:
messages = [
SystemMessage(content="You are a helpful assistant that translates English to French."),
HumanMessage(content="I love programming.")
]
chat(messages)
当然也能够进行批量处理,批量输出。
batch_messages = [
[
SystemMessage(content="You are a helpful assistant that translates English to French."),
HumanMessage(content="I love programming.")
],
[
SystemMessage(content="You are a helpful assistant that translates English to French."),
HumanMessage(content="I love artificial intelligence.")
],
]
result = chat.generate(batch_messages)
result
上面介绍了谈天的人物处理以及怎么进行批量处理音讯。咱们都知道向openAI调用接口都是要花钱的,假如用户问同一个问题,对成果进行了缓存,这样就能够削减接口的调用而且也能加速接口回来的速度。LangChain也很贴心的供给了缓存的功用。而且供给了两种缓存方案,内存缓存方案和数据库缓存方案,当然支撑的数据库缓存方案有许多种。
# 导入谈天模型,SQLiteCache模块
import os
os.environ["OPENAI_API_KEY"] = 'your apikey'
import langchain
from langchain.chat_models import ChatOpenAI
from langchain.cache import SQLiteCache
# 设置言语模型的缓存数据存储的地址
langchain.llm_cache = SQLiteCache(database_path=".langchain.db")
# 加载 llm 模型
llm = ChatOpenAI()
# 第一次向模型发问
result = llm.predict('tell me a joke')
print(result)
# 第2次向模型发问相同的问题
result2 = llm.predict('tell me a joke')
print(result2)
别的谈天模式也供给了一种流媒体回应。这意味着,而不是等待整个呼应回来,你就能够开端处理它赶快。
2.1.2. 嵌入
这个更多的是用于文档、文本或者许多数据的总结、问答场景,一般是和向量库一同运用,完结向量匹配。其实便是把文本等内容转成多维数组,能够后续进行类似性的核算和检索。他比较 fine-tuning 最大的优势便是,不用进行练习,而且能够实时添加新的内容,而不用加一次新的内容就练习一次,而且各方面本钱要比 fine-tuning 低许多。 下面以代码展现下embeddings是什么。
# 导入os, 设置环境变量,导入OpenAI的嵌入模型
import os
from langchain.embeddings.openai import OpenAIEmbeddings
os.environ["OPENAI_API_KEY"] = 'your apikey'
# 初始化嵌入模型
embeddings = OpenAIEmbeddings()
# 把文本经过嵌入模型向量化
res = embeddings.embed_query('hello world')
/*
[
-0.004845875, 0.004899438, -0.016358767, -0.024475135, -0.017341806,
0.012571548, -0.019156644, 0.009036391, -0.010227379, -0.026945334,
0.022861943, 0.010321903, -0.023479493, -0.0066544134, 0.007977734,
0.0026371893, 0.025206111, -0.012048521, 0.012943339, 0.013094575,
-0.010580265, -0.003509951, 0.004070787, 0.008639394, -0.020631202,
-0.0019203906, 0.012161949, -0.019194454, 0.030373365, -0.031028723,
0.0036170771, -0.007813894, -0.0060778237, -0.017820721, 0.0048647798,
-0.015640393, 0.001373733, -0.015552171, 0.019534737, -0.016169721,
0.007316074, 0.008273906, 0.011418369, -0.01390117, -0.033347685,
0.011248227, 0.0042503807, -0.012792102, -0.0014595914, 0.028356876,
0.025407761, 0.00076445413, -0.016308354, 0.017455231, -0.016396577,
0.008557475, -0.03312083, 0.031104341, 0.032389853, -0.02132437,
0.003324056, 0.0055610985, -0.0078012915, 0.006090427, 0.0062038545,
... 1466 more items
]
*/
下图是LangChain两种言语包支撑的embeddings。
2.1.3. 大言语模型
LLMS是LangChain的核心,从官网能够看到LangChain承继了十分多的大言语模型。
2.2. Prompts(提示词)
2.2.1. Prompt Templates
LangChain供给了PromptTemplates,答应你能够依据用户输入动态地更改提示,假如你有编程根底,这应该对你来说很简略。当用户需求输入多个类似的 prompt 时,生成一个 prompt 模板是一个很好的处理方案,能够节省用户的时刻和精力。下面是一个示例,将 LLM 作为一个给新开商铺命名的顾问,用户只需告知 LLM 商铺的首要特点,它将回来10个新开商铺的姓名。
from langchain.llms import OpenAI
# 界说生成商铺的办法
def generate_store_names(store_features):
prompt_template = "我正在开一家新的商铺,它的首要特点是{}。请帮我想出10个商铺的姓名。"
prompt = prompt_template.format(store_features)
llm = OpenAI()
response = llm.generate(prompt, max_tokens=10, temperature=0.8)
store_names = [gen[0].text.strip() for gen in response.generations]
return store_names
store_features = "时髦、创意、独特"
store_names = generate_store_names(store_features)
print(store_names)
这样,用户只需告知 LLM 商铺的首要特点,就能够取得10个新开商铺的姓名,而无需重复输入类似的 prompt 内容。别的LangChainHub包括了许多能够经过LangChain直接加载的Prompt Templates。趁便咱们也能够经过学习他们的Prompt 规划来给咱们以启示。
2.2.2. Few-shot examples
Few-shot examples是一组可用于协助言语模型生成更好呼应的示例。 要生成具有few-shot examples的prompt,能够运用FewShotPromptTemplate。该类承受一个PromptTemplate和一组few-shot examples。然后,它运用这些few-shot examples格式化prompt模板。 咱们再看一个比如,需求是依据用户输入,让模型回来对应的反义词,咱们要经过示例来告知模型什么是反义词, 这便是few-shot examples(小样本提示)
import os
os.environ["OPENAI_API_KEY"] = 'your apikey'
from langchain import PromptTemplate, FewShotPromptTemplate
from langchain.llms import OpenAI
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_text = few_shot_prompt.format(input="粗")
# 调用OpenAI
llm = OpenAI(temperature=0.9)
print(llm(prompt_text))
2.2.3. Example Selectors
假如你有许多的示例,则能够运用ExampleSelector来挑选最有信息量的一些示例,以协助你生成更或许产生杰出呼应的提示。接下来,咱们将运用LengthBasedExampleSelector,依据输入的长度挑选示例。当你忧虑构造的提示将超越上下文窗口的长度时,此办法十分有用。对于较长的输入,它会挑选包括较少示例的提示,而对于较短的输入,它会挑选包括更多示例。 别的官方也供给了依据最大边际相关性、文法重叠、语义类似性来挑选示例。
import os
os.environ["OPENAI_API_KEY"] = 'your apikey'
from langchain.prompts import PromptTemplate, FewShotPromptTemplate
from langchain.prompts.example_selector import LengthBasedExampleSelector
from langchain.prompts.example_selector import LengthBasedExampleSelector
# These are a lot of examples of a pretend task of creating antonyms.
examples = [
{"word": "happy", "antonym": "sad"},
{"word": "tall", "antonym": "short"},
{"word": "energetic", "antonym": "lethargic"},
{"word": "sunny", "antonym": "gloomy"},
{"word": "windy", "antonym": "calm"},
]
# 比如格式化模版
example_formatter_template = """
Word: {word}
Antonym: {antonym}\n
"""
example_prompt = PromptTemplate(
input_variables=["word", "antonym"],
template=example_formatter_template,
)
# 运用 LengthBasedExampleSelector来挑选比如
example_selector = LengthBasedExampleSelector(
examples=examples,
example_prompt=example_prompt,
# 最大长度
max_length=25,
)
# 运用'example_selector'创立小样本提示词模版
dynamic_prompt = FewShotPromptTemplate(
example_selector=example_selector,
example_prompt=example_prompt,
prefix="Give the antonym of every input",
suffix="Word: {input}\nAntonym:",
input_variables=["input"],
example_separator="\n\n",
)
longString = "big and huge and massive and large and gigantic and tall and much much much much much bigger than everything else"
print(dynamic_prompt.format(input=longString))
2.3. Indexes(索引)
索引是指对文档进行结构化的办法,以便LLM能够更好的与之交互。该组件首要包括:Document Loaders(文档加载器)、Text Splitters(文本拆分器)、VectorStores(向量存储器)以及Retrievers(检索器)。
2.3.1. Document Loaders
指定源进行加载数据的。将特定格式的数据,转化为文本。如CSV、File Directory、HTML、 JSON、Markdown、PDF。别的运用相关接口处理本地常识,或者在线常识。如AirbyteJSON Airtable、Alibaba Cloud MaxCompute、wikipedia、BiliBili、GitHub、GitBook等等。
2.3.2. Text Splitters
由于模型对输入的字符长度有约束,咱们在碰到很长的文本时,需求把文本切开成多个小的文本片段。 文本切开最简略的办法是依照字符长度进行切开,可是这会带来许多问题,比如说假如文本是一段代码,一个函数被切开到两段之后就成了没有意义的字符,所以整体的原则是把语义相关的文本片段放在一同。 LangChain中最基本的文本切开器是CharacterTextSplitter ,它依照指定的分隔符(默许“\n\n”)进行切开,而且考虑文本片段的最大长度。咱们看个比如:
from langchain.text_splitter import CharacterTextSplitter
# 初始字符串
state_of_the_union = "..."
text_splitter = CharacterTextSplitter(
separator = "\\n\\n",
chunk_size = 1000,
chunk_overlap = 200,
length_function = len,
)
texts = text_splitter.create_documents([state_of_the_union])
除了CharacterTextSplitter 以外,LangChain还支撑多个高档文本切开器,如下:
LatexTextSplitter | 沿着Latex标题、标题、枚举等切开文本。 |
---|---|
MarkdownTextSplitter | 沿着Markdown的标题、代码块或水平规则来切开文本。 |
NLTKTextSplitter | 运用NLTK的切开器 |
PythonCodeTextSplitter | 沿着Python类和办法的界说切开文本。 |
RecursiveCharacterTextSplitter | 用于通用文本的切开器。它以一个字符列表为参数,尽或许地把一切的阶段(然后是语句,然后是单词)放在一同 |
SpacyTextSplitter | 运用Spacy的切开器 |
TokenTextSplitter | 依据openAI的token数进行切开 |
2.3.3. VectorStores
存储提取的文本向量,包括Faiss、Milvus、Pinecone、Chroma等。如下是LangChain集成的向量数据库。
VectorStore | 介绍 |
---|---|
AnalyticDB | 阿里云自主研制的云原生数据仓库 |
Annoy | 一个带有Python bindings的C ++库,用于查找空间中给定查询点的近邻点。 |
AtlasDB | 一个非结构化数据集平台 |
Chroma | 一个开源嵌入式数据库 |
Deep Lake | 多模向量存储,能够存储嵌入及其元数据,包括文本、jsons、图像、音频、视频等。 |
DocArrayHnswSearch | 一个轻量级的文档索引完结 |
DocArrayInMemorySearch | 一个由Docarray供给的文档索引,将文档存储在内存中 |
ElasticSearch | ElasticSearch |
FAISS | Facebook AI类似性查找服务 |
LanceDB | 一个用于向量查找的开源数据库,它采用耐久性存储 |
Milvus | 用于存储、索引和管理由深度神经网络和其他机器学习(ML)模型产生的许多嵌入向量的数据库 |
MyScale | 一个依据云的数据库,为人工智能运用和处理方案而优化 |
OpenSearch | 一个可扩展的、灵敏的、可延伸的开源软件套件,用于查找、剖析和可调查性运用 |
PGVector | 一个用于Postgres的开源向量类似性查找服务 |
Pinecone | 一个具有广泛功用的向量数据库 |
Qdrant | 一个向量类似性查找引擎 |
Redis | 依据redis的检索器 |
SupabaseVectorStore | 一个开源的Firebase 替代品,供给一系列后端功用 |
Tair | 一个Key/Value结构数据的处理方案 |
Weaviate | 一个开源的向量查找引擎 |
Zilliz | 数据处理和剖析平台 |
2.3.4. Retrievers
检索器是一种便于模型查询的存储数据的办法,LangChain约好检索器组件至少有一个办法get_relevant_texts,这个办法接纳查询字符串,回来一组文档。下面是一个简略的列子:
from langchain.chains import RetrievalQA
from langchain.llms import OpenAI
from langchain.document_loaders import TextLoader
from langchain.indexes import VectorstoreIndexCreator
loader = TextLoader('../state_of_the_union.txt', encoding='utf8')
# 对加载的内容进行索引
index = VectorstoreIndexCreator().from_loaders([loader])
query = "What did the president say about Ketanji Brown Jackson"
# 经过query的办法找到语义检索的成果
index.query(query)
2.4. Chains(链)
链答应咱们将多个组件组合在一同以创立一个单一的、连贯的任务。例如,咱们能够创立一个链,它承受用户输入,运用 PromptTemplate 对其进行格式化,然后将格式化的呼应传递给 LLM。别的咱们也能够经过将多个链组合在一同,或者将链与其他组件组合来构建更杂乱的链。
2.4.1. LLMChain
LLMChain 是一个简略的链,它围绕言语模型添加了一些功用。它在整个LangChain中广泛运用,包括在其他链和署理中。它承受一个提示模板,将其与用户输入进行格式化,并回来 LLM 的呼应。
from langchain import PromptTemplate, OpenAI, LLMChain
prompt_template = "What is a good name for a company that makes {product}?"
llm = OpenAI(temperature=0)
llm_chain = LLMChain(
llm=llm,
prompt=PromptTemplate.from_template(prompt_template)
)
llm_chain("colorful socks")
除了一切Chain目标共享的__call__和run办法外,LLMChain还供给了一些调用得办法,如下是不同调用办法的说明.
- __call__办法回来输入和输出键值。 别的能够经过将return_only_outputs设置为True,能够将其配置为只回来输出键值。
llm_chain("corny", return_only_outputs=True)
{'text': 'Why did the tomato turn red? Because it saw the salad dressing!'}
- run办法回来的是字符串而不是字典。
llm_chain.run({"adjective": "corny"})
'Why did the tomato turn red? Because it saw the salad dressing!'
- apply 办法答应你对一个输入列表进行调用
input_list = [
{"product": "socks"},
{"product": "computer"},
{"product": "shoes"}
]
llm_chain.apply(input_list)
[{'text': '\n\nSocktastic!'},
{'text': '\n\nTechCore Solutions.'},
{'text': '\n\nFootwear Factory.'}]
- generate办法类似于 apply办法,但它回来的是 LLMResult 而不是字符串。LLMResult 通常包括有用的生成信息,例如令牌运用情况和完结原因。
llm_chain.generate(input_list)
LLMResult(generations=[[Generation(text='\n\nSocktastic!', generation_info={'finish_reason': 'stop', 'logprobs': None})], [Generation(text='\n\nTechCore Solutions.', generation_info={'finish_reason': 'stop', 'logprobs': None})], [Generation(text='\n\nFootwear Factory.', generation_info={'finish_reason': 'stop', 'logprobs': None})]], llm_output={'token_usage': {'prompt_tokens': 36, 'total_tokens': 55, 'completion_tokens': 19}, 'model_name': 'text-davinci-003'})
- predict 办法类似于 run 办法,不同之处在于输入键被指定为关键字参数,而不是一个 Python 字典。
# Single input example
llm_chain.predict(product="colorful socks")
2.4.2. SimpleSequentialChain
次序链的最简略形式,其中每个进程都有一个单一的输入/输出,而且一个进程的输出是下一步的输入。 如下便是将两个LLMChain进行组合成次序链进行调用的案例。
from langchain.llms import OpenAI
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain.chains import SimpleSequentialChain
# 界说第一个chain
llm = OpenAI(temperature=.7)
template = """You are a playwright. Given the title of play, it is your job to write a synopsis for that title.
Title: {title}
Playwright: This is a synopsis for the above play:"""
prompt_template = PromptTemplate(input_variables=["title"], template=template)
synopsis_chain = LLMChain(llm=llm, prompt=prompt_template)
# 界说第二个chain
llm = OpenAI(temperature=.7)
template = """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.
Play Synopsis:
{synopsis}
Review from a New York Times play critic of the above play:"""
prompt_template = PromptTemplate(input_variables=["synopsis"], template=template)
review_chain = LLMChain(llm=llm, prompt=prompt_template)
# 经过简略次序链组合两个LLMChain
overall_chain = SimpleSequentialChain(chains=[synopsis_chain, review_chain], verbose=True)
# 履行次序链
review = overall_chain.run("Tragedy at sunset on the beach")
2.4.3. SequentialChain
比较SimpleSequentialChain只答应有单个输入输出,它是一种更通用的次序链形式,答应多个输入/输出。 特别重要的是: 咱们怎么命名输入/输出变量称号。在上面的示例中,咱们不用考虑这一点,由于咱们仅仅将一个链的输出直接作为输入传递给下一个链,但在这儿咱们的确需求忧虑这一点,由于咱们有多个输入。 第一个LLMChain:
# 这是一个 LLMChain,依据戏曲的标题和设定的年代,生成一个简介。
llm = OpenAI(temperature=.7)
template = """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.
# 这儿界说了两个输入变量title和era,并界说一个输出变量:synopsis
Title: {title}
Era: {era}
Playwright: This is a synopsis for the above play:"""
prompt_template = PromptTemplate(input_variables=["title", "era"], template=template)
synopsis_chain = LLMChain(llm=llm, prompt=prompt_template, output_key="synopsis")
第二个LLMChain:
# 这是一个 LLMChain,依据剧情简介编撰一篇戏曲谈论。
llm = OpenAI(temperature=.7)
template = """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.
# 界说了一个输入变量:synopsis,输出变量:review
Play Synopsis:
{synopsis}
Review from a New York Times play critic of the above play:"""
prompt_template = PromptTemplate(input_variables=["synopsis"], template=template)
review_chain = LLMChain(llm=llm, prompt=prompt_template, output_key="review")
履行次序链
overall_chain({"title":"Tragedy at sunset on the beach", "era": "Victorian England"})
履行成果,能够看到会把每一步的输出都能打印出来。
> Entering new SequentialChain chain...
> Finished chain.
{'title': 'Tragedy at sunset on the beach',
'era': 'Victorian England',
'synopsis': "xxxxxx",
'review': "xxxxxxx"}
2.4.4. TransformChain
转化链答应咱们创立一个自界说的转化函数来处理输入,将处理后的成果用作下一个链的输入。如下示例咱们将创立一个转化函数,它承受超长文本,将文本过滤为仅前 3 段,然后将其传递到 LLMChain 中以总结这些内容。
from langchain.chains import TransformChain, LLMChain, SimpleSequentialChain
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
# 模拟超长文本
with open("../../state_of_the_union.txt") as f:
state_of_the_union = f.read()
# 界说转化办法,入参和出参都是字典,取前三段
def transform_func(inputs: dict) -> dict:
text = inputs["text"]
shortened_text = "\n\n".join(text.split("\n\n")[:3])
return {"output_text": shortened_text}
# 转化链:输入变量:text,输出变量:output_text
transform_chain = TransformChain(
input_variables=["text"], output_variables=["output_text"], transform=transform_func
)
# prompt模板描述
template = """Summarize this text:
{output_text}
Summary:"""
# prompt模板
prompt = PromptTemplate(input_variables=["output_text"], template=template)
# llm链
llm_chain = LLMChain(llm=OpenAI(), prompt=prompt)
# 运用次序链
sequential_chain = SimpleSequentialChain(chains=[transform_chain, llm_chain])
# 开端履行
sequential_chain.run(state_of_the_union)
# 成果
"""
' The speaker addresses the nation, noting that while last year they were kept apart due to COVID-19, this year they are together again.
They are reminded that regardless of their political affiliations, they are all Americans.'
"""
2.5. Memory(回忆)
了解openai的都知道,openai供给的谈天接口api,本身是不具备“回忆的”才能。假如想要使谈天具有回忆功用,则需求咱们自行保护谈天记载,即每次把谈天记载发给gpt。详细进程如下 第一次发送
import openai
openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello"},
]
)
第2次发送就要带上咱们第一次的记载即
import openai
openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello"},
{"role": "assistant", "content": "Hello, how can I help you?"},
{"role": "user", "content": "who is more stylish Pikachu or Neo"},
]
)
那假如咱们一直谈天下去,发送的内容也越来越多,那很或许就碰到token的约束。聪明的同学会发现,其实咱们只保存最近几回的谈天记载就能够了。没错,其实LangChain也是这样完结的,不过LangChain供给了更多的办法。 langchain供给了不同的Memory组件完结内容回忆,如下是现在供给的组件。
2.5.1. ConversationBufferMemory
该组件类似咱们上面的描述,只不过它会将谈天内容记载在内存中,而不需求每次再手动拼接谈天记载。
2.5.2. ConversationBufferWindowMemory
比较较第一个回忆组件,该组件添加了一个窗口参数,会保存最近看k论的谈天内容。
2.5.3. ConversationTokenBufferMemory
在内存中保存最近交互的缓冲区,并运用token长度而不是交互次数来确认何时改写交互。
2.5.4. ConversationSummaryMemory
比较第一个回忆组件,该组件只会存储一个用户和机器人之间的谈天内容的摘要。
2.5.5. ConversationSummaryBufferMemory
结合了上面两个思路,存储一个用户和机器人之间的谈天内容的摘要并运用token长度来确认何时改写交互。
2.5.6. VectorStoreRetrieverMemory
它是将一切之前的对话经过向量的办法存储到VectorDB(向量数据库)中,在每一轮新的对话中,会依据用户的输入信息,匹配向量数据库中最类似的K组对话。
2.6. Agents(署理)
一些运用程序需求依据用户输入灵敏地调用LLM和其他东西的链。署理接口为这样的运用程序供给了灵敏性。署理能够拜访一套东西,并依据用户输入确认要运用哪些东西。咱们能够简略的理解为他能够动态的帮咱们挑选和调用chain或者已有的东西。署理首要有两种类型Action agents和Plan-and-execute agents。
2.6.1. Action agents
行为署理: 在每个时刻步,运用一切从前动作的输出来决议下一个动作。下图展现了行为署理履行的流程。
2.6.2. Plan-and-execute agents
预先决议完好的操作次序,然后履行一切操作而不更新方案,下面是其流程。
- 接纳用户输入
- 方案要采纳的完好进程次序
- 按次序履行进程,将过去进程的输出作为未来进程的输入传递
3. LangChain实战
3.1. 完结一次问答
LangChain 加载 OpenAI 的模型,而且完结一次问答。 先设置咱们的 openai 的 key,然后,咱们进行导入和履行。
# 导入os, 设置环境变量,导入OpenAI模型
import os
os.environ["OPENAI_API_KEY"] = '你的api key'
from langchain.llms import OpenAI
# 加载 OpenAI 模型,并指定模型姓名
llm = OpenAI(model_name="text-davinci-003",max_tokens=1024)
# 向模型发问
result = llm("怎么评价人工智能")
3.2. 经过谷歌查找并回来答案
为了完结咱们的项目,咱们需求运用 Serpapi 供给的 Google 查找 API 接口。首先,咱们需求在 Serpapi 官网上注册一个用户,并复制由 Serpapi 生成的 API 密钥。接下来,咱们需求将这个 API 密钥设置为环境变量,就像咱们之前设置 OpenAI API 密钥相同。
# 导入os, 设置环境变量
import os
os.environ["OPENAI_API_KEY"] = '你的api key'
os.environ["SERPAPI_API_KEY"] = '你的api key'
然后,开端编写我的代码。
# 导入加载东西、初始化署理、署理类型及OpenAI模型
from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.agents import AgentType
from langchain.llms import OpenAI
# 加载 OpenAI 模型
llm = OpenAI(temperature=0)
# 加载 serpapi、言语模型的数学东西
tools = load_tools(["serpapi", "llm-math"], llm=llm)
# 东西加载后都需求初始化,verbose 参数为 True,会打印全部的履行详情
agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True)
# 履行署理
agent.run("今天是几号?历史上的今天发生了什么事情")
能够看到,正确的回来了日期(有时差),而且回来了历史上的今天。而且经过设置verbose这个参数为True,能够看到完好的 chain 履行进程。将咱们的问题拆分成了几个进程,然后一步一步得到最终的答案。
3.3. 对超长文本进行总结
假如咱们想要用 openai api 对一个段文本进行总结,咱们通常的做法便是直接发给 api 让他总结。可是假如文本超越了 api 最大的 token 约束就会报错。这时,咱们一般会进行对文章进行分段,比如经过 tiktoken 核算并切开,然后将各段发送给 api 进行总结,最终将各段的总结再进行一个全部的总结。 LangChain很好的帮咱们处理了这个进程,使得咱们编写代码变的十分简略。
# 导入os,设置环境变量。导入文本加载器、总结链、文本切开器及OpenAI模型
import os
os.environ["OPENAI_API_KEY"] = '你的api key'
from langchain.document_loaders import TextLoader
from langchain.chains.summarize import load_summarize_chain
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain import OpenAI
# 获取当时脚本地点的目录
base_dir = os.path.dirname(os.path.abspath(__file__))
# 构建doc.txt文件的途径
doc_path = os.path.join(base_dir, 'static', 'open.txt')
# 经过文本加载器加载文本
loader = TextLoader(doc_path)
# 将文本转成 Document 目标
document = loader.load()
# 初始化文本切开器
text_splitter = RecursiveCharacterTextSplitter(
chunk_size = 800,
chunk_overlap = 0
)
# 切分文本
split_documents = text_splitter.split_documents(document)
# 加载 llm 模型
llm = OpenAI(model_name="text-davinci-003", max_tokens=1500)
# 创立总结链
chain = load_summarize_chain(llm, chain_type="refine", verbose=True)
# 履行总结链
chain.run(split_documents)
这儿解说下文本切开器的 chunk_overlap 参数和chain 的 chain_type 参数。 chunk_overlap 是指切开后的每个 document 里包括几个上一个 document 结束的内容,首要效果是为了添加每个 document 的上下文相关。比如,chunk_overlap=0时, 第一个 document 为 aaaaaa,第二个为 bbbbbb;当 chunk_overlap=2 时,第一个 document 为 aaaaaa,第二个为 aabbbbbb。 chain_type首要操控了将 document 传递给 llm 模型的办法,一共有 4 种办法: stuff: 这种最简略粗暴,会把一切的 document 一次全部传给 llm 模型进行总结。假如document许多的话,必然会报超出最大 token 约束的错,所以总结文本的时分一般不会选中这个。 map_reduce: 这个办法会先将每个 document 进行总结,最终将一切 document 总结出的成果再进行一次总结。 refine: 这种办法会先总结第一个 document,然后在将第一个 document 总结出的内容和第二个 document 一同发给 llm 模型在进行总结,以此类推。这种办法的优点便是在总结后一个 document 的时分,会带着前一个的 document 进行总结,给需求总结的 document 添加了上下文,添加了总结内容的连贯性。 map_rerank: 这种一般不会用在总结的 chain 上,而是会用在问答的 chain 上,他其实是一种查找答案的匹配办法。首先你要给出一个问题,他会依据问题给每个 document 核算一个这个 document 能回答这个问题的概率分数,然后找到分数最高的那个 document ,在经过把这个 document 转化为问题的 prompt 的一部分(问题+document)发送给 llm 模型,最终 llm 模型回来详细答案。
3.4. 构建本地常识库问答机器人
经过这个能够很方便的做一个能够介绍公司业务的机器人,或是介绍一个产品的机器人。这儿首要运用了Embedding(相关性)的才能。
导入os,设置环境变量。导入OpenAI嵌入模型、Chroma向量数据库、文本切开器、OpenAI模型、向量数据库数据查询模块及文件夹文档加载器
import os
os.environ["OPENAI_API_KEY"] = '你的api key'
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.text_splitter import CharacterTextSplitter
from langchain import OpenAI,VectorDBQA
from langchain.document_loaders import DirectoryLoader
# 获取当时脚本地点的目录
base_dir = os.path.dirname(os.path.abspath(__file__))
# 构建doc.txt文件的途径
doc_Directory = os.path.join(base_dir, 'static')
# 加载文件夹中的一切txt类型的文件
loader = DirectoryLoader(doc_Directory, glob='**/*.txt')
# 将数据转成 document 目标,每个文件会作为一个 document
documents = loader.load()
# 初始化加载器
text_splitter = CharacterTextSplitter(chunk_size=100, chunk_overlap=0)
# 切开加载的 document
split_docs = text_splitter.split_documents(documents)
# 初始化 openai 的 embeddings 目标
embeddings = OpenAIEmbeddings()
# 将 document 经过 openai 的 embeddings 目标核算 embedding 向量信息并暂时存入 Chroma 向量数据库,用于后续匹配查询
docsearch = Chroma.from_documents(split_docs, embeddings)
# 创立问答目标
qa = VectorDBQA.from_chain_type(llm=OpenAI(), chain_type="stuff", vectorstore=docsearch,return_source_documents=True)
# 进行问答
result = qa({"query": "一年收入是多少?"})
上图中成功的从咱们的给到的数据中获取了正确的答案。
3.5. 构建向量索引数据库
Home | Chroma 咱们上个案例里面有一步是将 document 信息转化成向量信息和embeddings的信息并暂时存入 Chroma 数据库。 由于是暂时存入,所以当咱们上面的代码履行完结后,上面的向量化后的数据将会丢掉。假如想下次运用,那么就还需求再核算一次embeddings,这肯定不是咱们想要的。 LangChain 支撑的数据库有许多,这个案例介绍下经过 Chroma 个数据库来讲一下怎么做向量数据耐久化。 chroma 是个本地的向量数据库,他供给的一个 persist_directory 来设置耐久化目录进行耐久化。读取时,只需求调取 from_document 办法加载即可。
from langchain.vectorstores import Chroma
# 耐久化数据
docsearch = Chroma.from_documents(documents, embeddings, persist_directory="D:/vector_store")
docsearch.persist()
# 从已有文件中加载数据
docsearch = Chroma(persist_directory="D:/vector_store", embedding_function=embeddings)
3.6. 依据LangChain构建的开源运用
依据LangChain的优秀项目资源库 依据LangChain和ChatGLM-6B等系列LLM的针对本地常识库的主动问答
4. 总结
随着LangChain不断迭代和优化,它的功用将变得越来越强大,支撑的规模也将更广泛。无论是处理杂乱的言语模型仍是处理各种实际问题,LangChain都将展现出更高的实力和灵敏性。但是,我有必要供认,我的理解才能和解说才能是有限的,或许会出现过错或者解说不行明晰。因而,恳请读者们谅解。
5. 参考文献
- LangChain | ️ LangChain
- LangChain 中文入门教程 – LangChain 的中文入门教程