导语

在人工智能范畴的不断发展中,言语模型扮演着重要的人物。特别是大型言语模型(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亿美元。

一文带你入门LangChain

1.2. LangChain为什么这么火

LangChain现在是有两个言语版别(python和nodejs),从下图能够看出来,短短半年的时刻该项意图python版别现已取得了54k+的star。nodejs版别也在短短4个月收货了7k+的star,这无疑利好前端同学,不需求会python也能快速上手LLM运用开发。

一文带你入门LangChain
笔者认为Langchain作为一个大言语模型运用开发结构,处理了现在开发人工智能运用的一些切实痛点。以GPT模型为例:

  1. 数据滞后,现在练习的数据是到 2021 年9月。
  2. token数量约束,假如让它对一个300页的pdf进行总结,直接运用则力不从心。
  3. 不能进行联网,获取不到最新的内容。
  4. 不能与其他数据源链接。 别的作为一个胶水层结构,极大地提高了开发功率,它的效果能够类比于jquery在前端开发中的人物,使得开发者能够更专注于创新和优化产品功用。

1.3.LLM运用架构

LangChian作为一个大言语模型开发结构,是LLM运用架构的重要一环。那什么是LLM运用架构呢?其实便是指依据言语模型的运用程序规划和开发的架构。 LangChian能够将LLM模型、向量数据库、交互层Prompt、外部常识、外部东西整合到一同,进而能够自由构建LLM运用。

2. LangChain组件

一文带你入门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。

一文带你入门LangChain

2.1.3. 大言语模型

LLMS是LangChain的核心,从官网能够看到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

次序链的最简略形式,其中每个进程都有一个单一的输入/输出,而且一个进程的输出是下一步的输入。

一文带你入门LangChain
如下便是将两个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

行为署理: 在每个时刻步,运用一切从前动作的输出来决议下一个动作。下图展现了行为署理履行的流程。

一文带你入门LangChain

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("今天是几号?历史上的今天发生了什么事情")

一文带你入门LangChain
能够看到,正确的回来了日期(有时差),而且回来了历史上的今天。而且经过设置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 总结出的成果再进行一次总结。

一文带你入门LangChain
refine: 这种办法会先总结第一个 document,然后在将第一个 document 总结出的内容和第二个 document 一同发给 llm 模型在进行总结,以此类推。这种办法的优点便是在总结后一个 document 的时分,会带着前一个的 document 进行总结,给需求总结的 document 添加了上下文,添加了总结内容的连贯性。
一文带你入门LangChain
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": "一年收入是多少?"})

一文带你入门LangChain
上图中成功的从咱们的给到的数据中获取了正确的答案。

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 的中文入门教程