在人工智能領(lǐng)域的不斷發(fā)展中,語言模型扮演著重要的角色。特別是大型語言模型(LLM),如 ChatGPT,已經(jīng)成為科技領(lǐng)域的熱門話題,并受到廣泛認可。
在這個背景下,LangChain 作為一個以 LLM 模型為核心的開發(fā)框架出現(xiàn),為自然語言處理開啟了一個充滿可能性的世界。
借助 LangChain,我們可以創(chuàng)建各種應用程序,包括聊天機器人和智能問答工具。
1.?LangChain 簡介
1.1. LangChain 發(fā)展史
LangChain 的作者是 Harrison Chase,最初是于 2022 年 10 月開源的一個項目,在 GitHub 上獲得大量關(guān)注之后迅速轉(zhuǎn)變?yōu)橐患页鮿?chuàng)公司。2017 年 Harrison Chase 還在哈佛上大學,如今已是硅谷的一家熱門初創(chuàng)公司的 CEO,這對他來說是一次重大而迅速的躍遷。Insider 獨家報道,人工智能初創(chuàng)公司 LangChain 在種子輪一周后,再次獲得紅杉領(lǐng)投的 2000 萬至 2500 萬美元融資,估值達到 2 億美元。

1.2.LangChain 為什么這么火
LangChain 目前是有兩個語言版本(python 和 nodejs),從下圖可以看出來,短短半年的時間該項目的 python 版本已經(jīng)獲得了 54k+的 star。nodejs 版本也在短短 4 個月收貨了 7k+的 star,這無疑利好前端同學,不需要會 python 也能快速上手 LLM 應用開發(fā)。

筆者認為 Langchain 作為一個大語言模型應用開發(fā)框架,解決了現(xiàn)在開發(fā)人工智能應用的一些切實痛點。以 GPT 模型為例:
1.數(shù)據(jù)滯后,現(xiàn)在訓練的數(shù)據(jù)是到 2021 年 9 月。
2.token 數(shù)量限制,如果讓它對一個 300 頁的 pdf 進行總結(jié),直接使用則無能為力。
3.不能進行聯(lián)網(wǎng),獲取不到最新的內(nèi)容。
4.不能與其他數(shù)據(jù)源鏈接。
另外作為一個膠水層框架,極大地提高了開發(fā)效率,它的作用可以類比于 jquery 在前端開發(fā)中的角色,使得開發(fā)者可以更專注于創(chuàng)新和優(yōu)化產(chǎn)品功能。
1.3. LLM 應用架構(gòu)
LangChian 作為一個大語言模型開發(fā)框架,是 LLM 應用架構(gòu)的重要一環(huán)。那什么是 LLM 應用架構(gòu)呢?其實就是指基于語言模型的應用程序設(shè)計和開發(fā)的架構(gòu)。
LangChian 可以將 LLM 模型、向量數(shù)據(jù)庫、交互層 Prompt、外部知識、外部工具整合到一起,進而可以自由構(gòu)建 LLM 應用。
?文章來源:http://www.zghlxwxcb.cn/news/detail-710491.html
2. LangChain 組件

如上圖,LangChain 包含六部分組成,分別為:Models、Prompts、Indexes、Memory、Chains、Agents。
2.1.Models(模型)
下面我們以具體示例分別闡述下 Chat Modals, Embeddings, LLMs。
2.1.1. 聊天模型
LangChain 為使用聊天模型提供了一個標準接口。聊天模型是語言模型的一種變體。雖然聊天模型在內(nèi)部使用語言模型,但它們所提供的接口略有不同。它們不是暴露一個 "輸入文本,輸出文本" 的 API,而是提供了一個以 "聊天消息" 作為輸入和輸出的接口。
聊天模型的接口是基于消息而不是原始文本。LangChain 目前支持的消息類型有 AIMessage、HumanMessage、SystemMessage 和 ChatMessage,其中 ChatMessage 接受一個任意的角色參數(shù)。大多數(shù)情況下,您只需要處理 HumanMessage、AIMessage 和 SystemMessage。
#?導入OpenAI的聊天模型,及消息類型
from?langchain.chat_models?import?ChatOpenAI
from?langchain.schema?import?(
????AIMessage,
????HumanMessage,
????SystemMessage
)
#?初始化聊天對象
chat?=?ChatOpenAI(openai_api_key="...")
#?向聊天模型發(fā)問
chat([HumanMessage(content="Translate?this?sentence?from?English?to?French:?I?love?programming.")])
OpenAI 聊天模式支持多個消息作為輸入。這是一個系統(tǒng)和用戶消息聊天模式的例子:
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 調(diào)用接口都是要花錢的,如果用戶問同一個問題,對結(jié)果進行了緩存,這樣就可以減少接口的調(diào)用并且也能加快接口返回的速度。LangChain 也很貼心的提供了緩存的功能。并且提供了兩種緩存方案,內(nèi)存緩存方案和數(shù)據(jù)庫緩存方案,當然支持的數(shù)據(jù)庫緩存方案有很多種。
#?導入聊天模型,SQLiteCache模塊
import?os
os.environ["OPENAI_API_KEY"]?=?'your?apikey'
import?langchain
from?langchain.chat_models?import?ChatOpenAI
from?langchain.cache?import?SQLiteCache
#?設(shè)置語言模型的緩存數(shù)據(jù)存儲的地址
langchain.llm_cache?=?SQLiteCache(database_path=".langchain.db")
#?加載?llm?模型
llm?=?ChatOpenAI()
#?第一次向模型提問
result?=?llm.predict('tell?me?a?joke')
print(result)
#?第二次向模型提問同樣的問題
result2?=?llm.predict('tell?me?a?joke')
print(result2)
另外聊天模式也提供了一種流媒體回應。這意味著,而不是等待整個響應返回,你就可以開始處理它盡快。
2.1.2. 嵌入
這個更多的是用于文檔、文本或者大量數(shù)據(jù)的總結(jié)、問答場景,一般是和向量庫一起使用,實現(xiàn)向量匹配。其實就是把文本等內(nèi)容轉(zhuǎn)成多維數(shù)組,可以后續(xù)進行相似性的計算和檢索。他相比 fine-tuning 最大的優(yōu)勢就是,不用進行訓練,并且可以實時添加新的內(nèi)容,而不用加一次新的內(nèi)容就訓練一次,并且各方面成本要比 fine-tuning 低很多。
下面以代碼展示下 embeddings 是什么。
#?導入os,?設(shè)置環(huán)境變量,導入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 的核心,從官網(wǎng)可以看到 LangChain 繼承了非常多的大語言模型。

2.2. Prompts(提示詞)
2.2.1. Prompt Templates
LangChain 提供了 PromptTemplates,允許你可以根據(jù)用戶輸入動態(tài)地更改提示,如果你有編程基礎(chǔ),這應該對你來說很簡單。當用戶需要輸入多個類似的 prompt 時,生成一個 prompt 模板是一個很好的解決方案,可以節(jié)省用戶的時間和精力。下面是一個示例,將 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?=?"時尚、創(chuàng)意、獨特"
store_names?=?generate_store_names(store_features)
print(store_names)
這樣,用戶只需告訴 LLM 商店的主要特點,就可以獲得 10 個新開商店的名字,而無需重復輸入類似的 prompt 內(nèi)容。另外LangChainHub包含了許多可以通過 LangChain 直接加載的 Prompt Templates。順便我們也可以通過學習他們的 Prompt 設(shè)計來給我們以啟發(fā)。
2.2.2. Few-shot example
Few-shot examples 是一組可用于幫助語言模型生成更好響應的示例。
要生成具有 few-shot examples 的 prompt,可以使用 FewShotPromptTemplate。該類接受一個 PromptTemplate 和一組 few-shot examples。然后,它使用這些 few-shot examples 格式化 prompt 模板。
我們再看一個例子,需求是根據(jù)用戶輸入,讓模型返回對應的反義詞,我們要通過示例來告訴模型什么是反義詞, 這就是 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
"""
#?創(chuàng)建提示詞模版
example_prompt?=?PromptTemplate(
????input_variables=["word",?"antonym"],
????template=example_template,
)
#?創(chuàng)建小樣本提示詞模版
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="粗")
#?調(diào)用OpenAI
llm?=?OpenAI(temperature=0.9)
print(llm(prompt_text))
2.2.3. Example Selector
如果你有大量的示例,則可以使用 ExampleSelector 來選擇最有信息量的一些示例,以幫助你生成更可能產(chǎn)生良好響應的提示。接下來,我們將使用 LengthBasedExampleSelector,根據(jù)輸入的長度選擇示例。當你擔心構(gòu)造的提示將超過上下文窗口的長度時,此方法非常有用。對于較長的輸入,它會選擇包含較少示例的提示,而對于較短的輸入,它會選擇包含更多示例。
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'創(chuàng)建小樣本提示詞模版
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))
另外官方也提供了根據(jù)最大邊際相關(guān)性、文法重疊、語義相似性來選擇示例。
2.3. Indexes(索引)
索引是指對文檔進行結(jié)構(gòu)化的方法,以便 LLM 能夠更好的與之交互。該組件主要包括:Document Loaders(文檔加載器)、Text Splitters(文本拆分器)、VectorStores(向量存儲器)以及 Retrievers(檢索器)。
2.3.1. Document Loaders
指定源進行加載數(shù)據(jù)的。將特定格式的數(shù)據(jù),轉(zhuǎn)換為文本。如 CSV、File Directory、HTML、
JSON、Markdown、PDF。另外使用相關(guān)接口處理本地知識,或者在線知識。如 AirbyteJSON
Airtable、Alibaba Cloud MaxCompute、wikipedia、BiliBili、GitHub、GitBook 等等。
2.3.2. Text Splitters
由于模型對輸入的字符長度有限制,我們在碰到很長的文本時,需要把文本分割成多個小的文本片段。
文本分割最簡單的方式是按照字符長度進行分割,但是這會帶來很多問題,比如說如果文本是一段代碼,一個函數(shù)被分割到兩段之后就成了沒有意義的字符,所以整體的原則是把語義相關(guān)的文本片段放在一起。
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 還支持多個高級文本分割器,如下:

2.3.3. VectorStores
存儲提取的文本向量,包括 Faiss、Milvus、Pinecone、Chroma 等。如下是 LangChain 集成的向量數(shù)據(jù)庫。

2.3.4. Retrievers
檢索器是一種便于模型查詢的存儲數(shù)據(jù)的方式,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')
#?對加載的內(nèi)容進行索引
index?=?VectorstoreIndexCreator().from_loaders([loader])
query?=?"What?did?the?president?say?about?Ketanji?Brown?Jackson"
#?通過query的方式找到語義檢索的結(jié)果
index.query(query)
2.4. Chains(鏈)
鏈允許我們將多個組件組合在一起以創(chuàng)建一個單一的、連貫的任務(wù)。例如,我們可以創(chuàng)建一個鏈,它接受用戶輸入,使用 PromptTemplate 對其進行格式化,然后將格式化的響應傳遞給 LLM。另外我們也可以通過將多個鏈組合在一起,或者將鏈與其他組件組合來構(gòu)建更復雜的鏈。
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 還提供了一些調(diào)用得方法,如下是不同調(diào)用方法的說明.
●?call方法返回輸入和輸出鍵值。
另外可以通過將 return_only_outputs 設(shè)置為 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 方法允許你對一個輸入列表進行調(diào)用
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 方法,不同之處在于輸入鍵被指定為關(guān)鍵字參數(shù),而不是一個 Python 字典。
#?Single?input?example
llm_chain.predict(product="colorful?socks")
2.4.2. SimpleSequentialChain
順序鏈的最簡單形式,其中每個步驟都有一個單一的輸入/輸出,并且一個步驟的輸出是下一步的輸入。

如下就是將兩個 LLMChain 進行組合成順序鏈進行調(diào)用的案例。
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)
#?執(zhí)行順序鏈
review?=?overall_chain.run("Tragedy?at?sunset?on?the?beach")
2.4.3. SequentialChain
相比 SimpleSequentialChain 只允許有單個輸入輸出,它是一種更通用的順序鏈形式,允許多個輸入/輸出。
特別重要的是: 我們?nèi)绾蚊斎?輸出變量名稱。在上面的示例中,我們不必考慮這一點,因為我們只是將一個鏈的輸出直接作為輸入傳遞給下一個鏈,但在這里我們確實需要擔心這一點,因為我們有多個輸入。
第一個 LLMChain:
#?這是一個?LLMChain,根據(jù)戲劇的標題和設(shè)定的時代,生成一個簡介。
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,根據(jù)劇情簡介撰寫一篇戲劇評論。
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")
執(zhí)行順序鏈:
overall_chain({"title":"Tragedy?at?sunset?on?the?beach",?"era":?"Victorian?England"})
執(zhí)行結(jié)果,可以看到會把每一步的輸出都能打印出來。
????>?Entering?new?SequentialChain?chain...
????>?Finished?chain.
????{'title':?'Tragedy?at?sunset?on?the?beach',
?????'era':?'Victorian?England',
?????'synopsis':?"xxxxxx",
?????'review':?"xxxxxxx"}
2.4.4. TransformChain
轉(zhuǎn)換鏈允許我們創(chuàng)建一個自定義的轉(zhuǎn)換函數(shù)來處理輸入,將處理后的結(jié)果用作下一個鏈的輸入。如下示例我們將創(chuàng)建一個轉(zhuǎn)換函數(shù),它接受超長文本,將文本過濾為僅前 3 段,然后將其傳遞到 LLMChain 中以總結(jié)這些內(nèi)容。
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()
#?定義轉(zhuǎn)換方法,入?yún)⒑统鰠⒍际亲值?,取前三?def?transform_func(inputs:?dict)?->?dict:
????text?=?inputs["text"]
????shortened_text?=?"\n\n".join(text.split("\n\n")[:3])
????return?{"output_text":?shortened_text}
#?轉(zhuǎn)換鏈:輸入變量: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])
#?開始執(zhí)行
sequential_chain.run(state_of_the_union)
#?結(jié)果
"""
????'?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,本身是不具備“記憶的”能力。如果想要使聊天具有記憶功能,則需要我們自行維護聊天記錄,即每次把聊天記錄發(fā)給 gpt。具體過程如下
第一次發(fā)送:
import?openai
openai.ChatCompletion.create(
??model="gpt-3.5-turbo",
??messages=[
????????{"role":?"system",?"content":?"You?are?a?helpful?assistant."},
????????{"role":?"user",?"content":?"Hello"},
????]
)
第二次發(fā)送就要帶上我們第一次的記錄:
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"},
????]
)
那如果我們一直聊天下去,發(fā)送的內(nèi)容也越來越多,那很可能就碰到 token 的限制。聰明的同學會發(fā)現(xiàn),其實我們只保留最近幾次的聊天記錄就可以了。沒錯,其實 LangChain 也是這樣實現(xiàn)的,不過 LangChain 提供了更多的方法。
langchain 提供了不同的 Memory 組件完成內(nèi)容記憶,如下是目前提供的組件。
2.5.1. ConversationBufferMemory
該組件類似我們上面的描述,只不過它會將聊天內(nèi)容記錄在內(nèi)存中,而不需要每次再手動拼接聊天記錄。
2.5.2. ConversationBufferWindowMemory
相比較第一個記憶組件,該組件增加了一個窗口參數(shù),會保存最近看 k 論的聊天內(nèi)容。
2.5.3. ConversationTokenBufferMemory
在內(nèi)存中保留最近交互的緩沖區(qū),并使用 token 長度而不是交互次數(shù)來確定何時刷新交互。
2.5.4. ConversationSummaryMemory
相比第一個記憶組件,該組件只會存儲一個用戶和機器人之間的聊天內(nèi)容的摘要。
2.5.5. ConversationSummaryBufferMemory
結(jié)合了上面兩個思路,存儲一個用戶和機器人之間的聊天內(nèi)容的摘要并使用 token 長度來確定何時刷新交互。
2.5.6. VectorStoreRetrieverMemory
它是將所有之前的對話通過向量的方式存儲到 VectorDB(向量數(shù)據(jù)庫)中,在每一輪新的對話中,會根據(jù)用戶的輸入信息,匹配向量數(shù)據(jù)庫中最相似的 K 組對話。
2.6. Agents(代理)
一些應用程序需要根據(jù)用戶輸入靈活地調(diào)用 LLM 和其他工具的鏈。代理接口為這樣的應用程序提供了靈活性。代理可以訪問一套工具,并根據(jù)用戶輸入確定要使用哪些工具。我們可以簡單的理解為他可以動態(tài)的幫我們選擇和調(diào)用 chain 或者已有的工具。代理主要有兩種類型 Action agents 和 Plan-and-execute agents。
2.6.1. Action agents
行為代理: 在每個時間步,使用所有先前動作的輸出來決定下一個動作。下圖展示了行為代理執(zhí)行的流程。

2.6.2. Plan-and-execute agents
預先決定完整的操作順序,然后執(zhí)行所有操作而不更新計劃,下面是其流程。
● 接收用戶輸入
● 計劃要采取的完整步驟順序
● 按順序執(zhí)行步驟,將過去步驟的輸出作為未來步驟的輸入傳遞
?
3. LangChain 實戰(zhàn)
3.1. 完成一次問答
LangChain 加載 OpenAI 的模型,并且完成一次問答。
先設(shè)置我們的 openai 的 key,然后,我們進行導入和執(zhí)行。
#?導入os,?設(shè)置環(huán)境變量,導入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. 通過谷歌搜索并返回答案
為了實現(xiàn)我們的項目,我們需要使用 Serpapi 提供的 Google 搜索 API 接口。首先,我們需要在 Serpapi 官網(wǎng)上注冊一個用戶,并復制由 Serpapi 生成的 API 密鑰。接下來,我們需要將這個 API 密鑰設(shè)置為環(huán)境變量,就像我們之前設(shè)置 OpenAI API 密鑰一樣。
#?導入os,?設(shè)置環(huán)境變量
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、語言模型的數(shù)學工具
tools?=?load_tools(["serpapi",?"llm-math"],?llm=llm)
#?工具加載后都需要初始化,verbose?參數(shù)為?True,會打印全部的執(zhí)行詳情
agent?=?initialize_agent(tools,?llm,?agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,?verbose=True)
#?執(zhí)行代理
agent.run("今天是幾號?歷史上的今天發(fā)生了什么事情")
可以看到,正確的返回了日期(有時差),并且返回了歷史上的今天。并且通過設(shè)置 verbose 這個參數(shù)為 True,可以看到完整的 chain 執(zhí)行過程。將我們的問題拆分成了幾個步驟,然后一步一步得到最終的答案。
3.3. 對超長文本進行總結(jié)
假如我們想要用 openai api 對一個段文本進行總結(jié),我們通常的做法就是直接發(fā)給 api 讓他總結(jié)。但是如果文本超過了 api 最大的 token 限制就會報錯。這時,我們一般會進行對文章進行分段,比如通過 tiktoken 計算并分割,然后將各段發(fā)送給 api 進行總結(jié),最后將各段的總結(jié)再進行一個全部的總結(jié)。
LangChain 很好的幫我們處理了這個過程,使得我們編寫代碼變的非常簡單。
#?導入os,設(shè)置環(huán)境變量。導入文本加載器、總結(jié)鏈、文本分割器及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__))
#?構(gòu)建doc.txt文件的路徑
doc_path?=?os.path.join(base_dir,?'static',?'open.txt')
#?通過文本加載器加載文本
loader?=?TextLoader(doc_path)
#?將文本轉(zhuǎn)成?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)
#?創(chuàng)建總結(jié)鏈
chain?=?load_summarize_chain(llm,?chain_type="refine",?verbose=True)
#?執(zhí)行總結(jié)鏈
chain.run(split_documents)
這里解釋下文本分割器的 chunk_overlap 參數(shù)和 chain 的 chain_type 參數(shù)。
chunk_overlap 是指切割后的每個 document 里包含幾個上一個 document 結(jié)尾的內(nèi)容,主要作用是為了增加每個 document 的上下文關(guān)聯(lián)。比如,chunk_overlap=0 時, 第一個 document 為 aaaaaa,第二個為 bbbbbb;當 chunk_overlap=2 時,第一個 document 為 aaaaaa,第二個為 aabbbbbb。
chain_type 主要控制了將 document 傳遞給 llm 模型的方式,一共有 4 種方式:
stuff: 這種最簡單粗暴,會把所有的 document 一次全部傳給 llm 模型進行總結(jié)。如果 document 很多的話,勢必會報超出最大 token 限制的錯,所以總結(jié)文本的時候一般不會選中這個。
map_reduce: 這個方式會先將每個 document 進行總結(jié),最后將所有 document 總結(jié)出的結(jié)果再進行一次總結(jié)。

refine: 這種方式會先總結(jié)第一個 document,然后在將第一個 document 總結(jié)出的內(nèi)容和第二個 document 一起發(fā)給 llm 模型在進行總結(jié),以此類推。這種方式的好處就是在總結(jié)后一個 document 的時候,會帶著前一個的 document 進行總結(jié),給需要總結(jié)的 document 添加了上下文,增加了總結(jié)內(nèi)容的連貫性。
map_rerank: 這種一般不會用在總結(jié)的 chain 上,而是會用在問答的 chain 上,他其實是一種搜索答案的匹配方式。首先你要給出一個問題,他會根據(jù)問題給每個 document 計算一個這個 document 能回答這個問題的概率分數(shù),然后找到分數(shù)最高的那個 document ,在通過把這個 document 轉(zhuǎn)化為問題的 prompt 的一部分(問題+document)發(fā)送給 llm 模型,最后 llm 模型返回具體答案。
3.4. 構(gòu)建本地知識庫問答機器人
通過這個可以很方便的做一個可以介紹公司業(yè)務(wù)的機器人,或是介紹一個產(chǎn)品的機器人。這里主要使用了 Embedding(相關(guān)性)的能力。
```
導入os,設(shè)置環(huán)境變量。導入OpenAI嵌入模型、Chroma向量數(shù)據(jù)庫、文本分割器、OpenAI模型、向量數(shù)據(jù)庫數(shù)據(jù)查詢模塊及文件夾文檔加載器
```
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__))
#?構(gòu)建doc.txt文件的路徑
doc_Directory?=?os.path.join(base_dir,?'static')
#?加載文件夾中的所有txt類型的文件
loader?=?DirectoryLoader(doc_Directory,?glob='**/*.txt')
#?將數(shù)據(jù)轉(zhuǎn)成?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?向量數(shù)據(jù)庫,用于后續(xù)匹配查詢
docsearch?=?Chroma.from_documents(split_docs,?embeddings)
#?創(chuàng)建問答對象
qa?=?VectorDBQA.from_chain_type(llm=OpenAI(),?chain_type="stuff",?vectorstore=docsearch,return_source_documents=True)
#?進行問答
result?=?qa({"query":?"一年收入是多少?"})
上圖中成功的從我們的給到的數(shù)據(jù)中獲取了正確的答案。
3.5.構(gòu)建向量索引數(shù)據(jù)庫
?? Home | Chroma
我們上個案例里面有一步是將 document 信息轉(zhuǎn)換成向量信息和 embeddings 的信息并臨時存入 Chroma 數(shù)據(jù)庫。
因為是臨時存入,所以當我們上面的代碼執(zhí)行完成后,上面的向量化后的數(shù)據(jù)將會丟失。如果想下次使用,那么就還需要再計算一次 embeddings,這肯定不是我們想要的。 LangChain 支持的數(shù)據(jù)庫有很多,這個案例介紹下通過 Chroma 個數(shù)據(jù)庫來講一下如何做向量數(shù)據(jù)持久化。
chroma 是個本地的向量數(shù)據(jù)庫,他提供的一個 persist_directory 來設(shè)置持久化目錄進行持久化。讀取時,只需要調(diào)取 from_document 方法加載即可。
from?langchain.vectorstores?import?Chroma
#?持久化數(shù)據(jù)
docsearch?=?Chroma.from_documents(documents,?embeddings,?persist_directory="D:/vector_store")
docsearch.persist()
#?從已有文件中加載數(shù)據(jù)
docsearch?=?Chroma(persist_directory="D:/vector_store",?embedding_function=embeddings)
3.6.基于 LangChain 構(gòu)建的開源應用
基于 LangChain 的優(yōu)秀項目資源庫
基于 LangChain 和 ChatGLM-6B 等系列 LLM 的針對本地知識庫的自動問答
?
4.?總結(jié)
隨著 LangChain 不斷迭代和優(yōu)化,它的功能將變得越來越強大,支持的范圍也將更廣泛。無論是處理復雜的語言模型還是解決各種實際問題,LangChain 都將展現(xiàn)出更高的實力和靈活性。然而,我必須承認,我的理解能力和解釋能力是有限的,可能會出現(xiàn)錯誤或者解釋不夠清晰。因此,懇請讀者們諒解。
5、參考文獻
●?LangChain | LangChain
●?LangChain 中文入門教程 - LangChain 的中文入門教程
?
作者: 騰訊應用寶 MoonWebTeam 團隊? jansezhou文章來源地址http://www.zghlxwxcb.cn/news/detail-710491.html
到了這里,關(guān)于一文入門最熱的LLM應用開發(fā)框架LangChain的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!