大模型 LangChain 框架基礎與使用示例
作者:kevine
一圖勝千言,LangChain
已經成爲當前 LLM 應用框架的事實標準,這篇文章就來對 LangChain 基本概念以及其具體使用場景做一個整理。
LangChain 是什麼
LangChain
是一個基於大語言模型的應用開發框架,它主要通過兩種方式規範和簡化了使用LLM
的方式:
-
集成:集成外部數據 (如文件、其他應用、API 數據等) 到
LLM
中; -
Agent:允許
LLM
通過決策與特定的環境交互,並由LLM
協助決定下一步的操作。
LangChain 的優點包括:
-
高度抽象的組件:規範和簡化與語言模型交互所需的各種抽象和組件;
-
高度可自定義的 Chains:提供了大量預置
Chains
的同時,支持自行繼承 BaseChain 並實現相關邏輯以及各個階段的callback handler
等; -
活躍的社區與生態:
Langchain
團隊迭代速度非常快,能快速使用最新的語言模型特性,該團隊也有 langsmith, auto-evaluator 等其它優秀項目,並且開源社區也有相當多的支持。
LangChain 的主要組件
這是一張LangChain
的組件與架構圖(langchain python
和langchain JS/TS
的架構基本一致,本文中以langchain python
來完成相關介紹),基本完整描述了LangChain
的組件與抽象層(callback
不在這張圖中,在下方我們會另外介紹),以及它們之間的相關聯繫。
Model I/O
首先我們從最基本面的部分講起,Model I/O 指的是和 LLM 直接進行交互的過程。
在 Model I/O 這一流程中,LangChain 抽象的組件主要有三個:
下面我們展開介紹一下
⚠️ 注:下面涉及的所有代碼示例中的OPENAI_API_KEY
和OPENAI_BASE_URL
需要提前配置好,OPENAI_API_KEY
指 OpenAI/OpenAI 代理服務的API Key
,OPENAI_BASE_URL
指 OpenAI 代理服務的Base Url
。
Language Model
Language Model
是真正與 LLM / ChatModel 進行交互的組件,它可以直接被當作普通的 openai client 來使用,在LangChain
中,主要使用到的是LLM
,Chat Model
和Embedding
三類 Language Model。
-
LLM: 最基礎的通過 “text in ➡️ text out” 模式來使用的 Language Model,另一方面,LangChain 也收錄了大量的第三方 LLM。
from langchain.llms import OpenAI llm = OpenAI(model_, openai_api_key=OPENAI_API_KEY, openai_api_base=OPENAI_BASE_URL) llm("What day comes after Friday?") # '\n\nSaturday.'
-
Chat Model:
LLM
的變體,抽象了Chat
這一場景下的使用模式,由 “text in ➡️ text out” 變成了 “chat messages in ➡️ chat message out”,chat message
是指 text + message type(System, Human, AI)。from langchain.chat_models import ChatOpenAI from langchain.schema import HumanMessage, SystemMessage chat = ChatOpenAI(model_, temperature=1, openai_api_key=OPENAI_API_KEY, openai_api_base=OPENAI_BASE_URL) chat( [ SystemMessage(content="You are an expert on large language models and can answer any questions related to large language models."), HumanMessage(content="What’s the difference between Generic Language Models, Instruction Tuned Models and Dialog Tuned Models") ] ) # AIMessage(content='Generic Language Models, Instruction-Tuned Models, and Dialog-Tuned Models are all various types of language models that have been trained according to different datasets and methodologies. They are used in different contexts according to their specific strengths. \n\n1. **Generic Language Models**: These models are trained on a broad range of internet text. They are typically not tuned to specific tasks and thus can be used across a wide variety of applications. GPT-3, used by OpenAI, is an example of a general language model.\n\n2. **Instruction-Tuned Models**: These are language models that are fine-tuned specifically to follow instructions given in a prompt. They are trained using a procedure called Reinforcement Learning from Human Feedback (RLHF), following an initial supervised fine-tuning which consists of human AI trainers providing conversations where they play both the user and an AI assistant. This model often includes comparison data - two or more model responses are ranked by quality.\n\n3. **Dialog-Tuned Models**: Like Instruction-Tuned Models, these models are also trained with reinforcement learning from human feedback, and especially shine in multi-turn conversations. However, these are specifically designed for dialog scenarios, resulting in an ability to maintain more coherent and context-aware conversations. \n\nIn essence, the difference among the three revolves around the breadth of their training and their specific use-cases. Generic Language Models are broad but may lack specificity; Instruction-Tuned Models are better at following specific instructions given in prompts; and Dialog-Tuned Models excel in carrying out more coherent and elongated dialogues.', additional_kwargs={}, example=False)
另一方面,LangChain 也收錄了大量的第三方 Chat Model
-
System - 告訴 AI 要做什麼的背景信息上下文;
-
Human - 標識用戶傳入的消息類型;
-
AI - 標識 AI 返回的消息類型。以下是一個簡單的
Chat Model
使用示例: -
Embedding:
Embedding
將一段文字向量化爲一個定長的向量,有了文本的向量化表示我們就可以做一些像語義搜索,聚類選擇等來選擇需要的文本片段,如下是將一個 embed 任意字符串的示例:from langchain.embeddings import OpenAIEmbeddings embeddings = OpenAIEmbeddings(openai_api_key=OPENAI_API_KEY, openai_api_base=OPENAI_BASE_URL) text_embedding = embeddings.embed_query("To embed text(it can have any length)") print (f"Your embedding's length: {len(text_embedding)}") print (f"Here's a sample: {text_embedding[:5]}...") ''' Your embedding's length: 1536 Here's a sample: [-0.03194352, 0.009228715, 0.00807182, 0.0077545005, 0.008256923]... '''
Prompts
Prompt 指用戶的一系列指令和輸入,是決定Language Model
輸出內容的唯一輸入,主要用於幫助模型理解上下文並生成相關和連貫的輸出,如回答問題、拓寫句子和總結問題。在LangChain
中的相關組件主要有Prompt Template
和Example selectors
,以及後面會提到的輔助 / 補充 Prompt 的一些其它組件。
Prompt Template: 預定義的一系列指令和輸入參數的prompt
模版,支持更加靈活的輸入,如支持 output instruction(輸出格式指令), partial input(提前指定部分輸入參數), examples(輸入輸出示例) 等;LangChain
提供了大量方法來創建Prompt Template
,有了這一層組件就可以在不同Language Model
和不同Chain
下大量複用Prompt Template
了,Prompt Template
中也會有下面將提到的Example selectors, Output Parser
的參與。
Example selectors: 在很多場景下,單純的 instruction + input 的prompt
不足以讓LLM
完成高質量的推理回答,這時候我們就還需要爲prompt
補充一些針對具體問題的示例,LangChain 將這一功能抽象爲了Example selectors
這一組件,我們可以基於關鍵字,相似度 (通常使用 MMR/cosine similarity/ngram 來計算相似度, 在後面的向量數據庫章節中會提到)。爲了讓最終的prompt
不超過Language Model
的 token 上限(各個模型的 token 上限見下表),LangChain
還提供了LengthBasedExampleSelector
,根據長度來限制 example 數量,對於較長的輸入,它會選擇包含較少示例的提示,而對於較短的輸入,它會選擇包含更多示例。
Output Parser
通常我們希望Language Model
的輸出是固定的格式,以支持我們解析其輸出爲結構化數據,LangChain
將這一訴求所需的功能抽象成了Output Parser
這一組件,並提供了一系列的預定義Output Parser
,如最常用的 Structured output parser, List parser,以及在LLM
輸出無法解析時發揮作用的 Auto-fixing parser 和 Retry parser。
Output Parser
需要和 Prompt Template, Chain 組合使用:
-
Prompt Template: 在
Prompt Template
中通過指定partial_variables
爲Output Parser
的 format,即可在prompt
中補充讓模型輸出所需格式內容的指令; -
Chain: 在
Chain
中指定Output Parser
,並使用Chain
的predict_and_parse / apply_and_parse
方法啓動Chain
,即可直接輸出解析後的數據。
使用示例
以下是一個完整的組合 Prompt Template, Output Parser 和 Chain 的具體用例:
from langchain.chat_models import ChatOpenAI
llm = ChatOpenAI(temperature=0.5, model_, openai_api_key=OPENAI_API_KEY, openai_api_base=OPENAI_BASE_URL)
template = """
## Input
{text}
## Instruction
Please summarize the piece of text in the input part above.
Respond in a manner that a 5 year old would understand.
{format_instructions}
YOUR RESPONSE:
"""
# 創建一個Output Parser,包含兩個輸出字段,並指定類型和說明
output_parser = StructuredOutputParser.from_response_schemas(
[
ResponseSchema(),
ResponseSchema(),
]
)
# 創建Prompt Template,並將format_instructions通過partial_variables直接指定爲Output Parser的format
prompt = PromptTemplate(
input_variables=["text"],
template=template,
partial_variables={"format_instructions": output_parser.get_format_instructions()},
)
# 創建Chain並綁定Prompt Template和Output Parser(它將自動使用Output Parser解析llm輸出)
summarize_chain = LLMChain(llm=llm, verbose=True, prompt=prompt, output_parser=output_parser)
to_summarize_text = 'Abstract. Text-to-SQL aims at generating SQL queries for the given natural language questions and thus helping users to query databases. Prompt learning with large language models (LLMs) has emerged as a recent approach, which designs prompts to lead LLMs to understand the input question and generate the corresponding SQL. However, it faces challenges with strict SQL syntax requirements. Existing work prompts the LLMs with a list of demonstration examples (i.e. question-SQL pairs) to generate SQL, but the fixed prompts can hardly handle the scenario where the semantic gap between the retrieved demonstration and the input question is large.'
output = summarize_chain.predict(text=to_summarize_text)
import json
print (json.dumps(output, indent=4))
輸出如下:
{
"keywords": [
"Text-to-SQL",
"SQL queries",
"natural language questions",
"databases",
"prompt learning",
"large language models",
"LLMs",
"SQL syntax requirements",
"demonstration examples",
"semantic gap"
],
"summary": "Text-to-SQL is a method that helps users generate SQL queries for their questions about databases. One approach is to use large language models to understand the question and generate the SQL. However, this approach faces challenges with strict SQL syntax rules. Existing methods use examples to teach the language models, but they struggle when the examples are very different from the question."
}
Data connection
正如我在文章開頭的 LangChain 是什麼一節中提到的,集成外部數據到 Language Model 中是 LangChain 提供的核心能力之一,也是市面上很多優秀的大語言模型應用成功的核心之一(Github Copilot Chat,網頁聊天助手,論文總結助手,youtube 視頻總結助手…),在LangChain
中,Data connection
這一層主要包含以下四個抽象組件:
下面我們展開介紹一下
Document loaders
爲了補全 LLM 的上下文信息,給予其足夠的提示,我們需要從各類數據源獲取各類數據,這也就是LangChain
抽象的Document loaders
這一組件的功能。
使用Document loaders
可以將源中的數據加載爲Document
。Document
由一段文本和相關元數據組成。例如,有用於加載簡單. txt 文件的,用於加載相對結構化的 markdown 文件的,用於加載任何網頁文本內容,甚至用於加載解析 YouTube 視頻的腳本。
同時 LangChain 還收錄了海量的第三方 Document loaders,以下是一個使用NotionDBLoader
來加載notion database
中的page
爲Document
的示例:
from langchain.document_loaders import NotionDBLoader
from getpass import getpass
# getpass()指引用戶輸入密鑰
NOTION_TOKEN = getpass()
DATABASE_ID = getpass()
loader = NotionDBLoader(
integration_token=NOTION_TOKEN,
database_id=DATABASE_ID,
request_timeout_sec=30, # optional, defaults to 10
)
# 請求詳情見 https://developers.notion.com/reference/post-database-query
docs = loader.load()
docs[0].page_content[:100]
Document transformers
當我們加載 Document 到內存後,我們通常還會希望將他們儘可能的結構化 / 分塊,以進行更加靈活的操作。
最簡單的例子是,我們很多時候都需要將一個長文檔拆分成更小的塊,以便放入模型的上下文窗口中;LangChain 有許多內置的 Document transformers(大部分都是Text Spliter
),可以輕鬆地拆分、合併、篩選和以其他方式操作文檔,一些常用的 Document transformers 如下:
同時 LangChain 也收錄了很多第三方的 Document transformers(如基於爬蟲中常見的 beautiful soup, 基於 OpenAI 打 metadata tag 的等等)。
Vector stores
在前面的 Prompt 一節中我們提到了 Example selectors,那麼我們要如何找到相關示例呢?通常這個答案就是向量數據庫。
存儲和搜索非結構化數據的最常見方式之一是將其向量化 (embedding) 並存儲所得到的嵌入向量,然後在查詢時向量化非結構化查詢並檢索與嵌入的查詢最相似的向量。Vector stores 負責存儲向量化數據並提供向量搜索的功能,常見的向量數據庫包括 FAISS, Milvus, Pinecone, Weaviate, Chroma 等,日常使用更常用的是 FAISS。
同時 LangChain 也收錄了很多第三方的 Vector Stores,提供更加強大的向量搜索等功能。
Retrievers
Retrievers 是 LangChain 提供的將Document
與Language Model
相結合的組件。
LangChain 中有許多不同類型的 Retrievers,但最廣泛使用的就是 VectoreStoreRetriever,我們可以直接把它當做連接向量數據庫和 Language Model 的中間層,並且 VectoreStoreRetriever 的使用也很簡單,直接retriever = db.as_retriever()
即可。
當然我們還有很多其它的 Retrievers 如 Web search Retrievers 等,LangChain 也收錄了很多第三方的 Retrievers
使用示例 5
以下就是一個使用向量數據庫 + VectoreStoreRetriever + QA Chain 的 QA 應用示例:
from langchain.chat_models import ChatOpenAI
from langchain.vectorstores import FAISS
from langchain.chains import RetrievalQA
from langchain.document_loaders import TextLoader
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 初始化LLM
llm = ChatOpenAI(temperature=0.5, model_, openai_api_key=OPENAI_API_KEY, openai_api_base=OPENAI_PROXY_URL)
# 加載文檔
loader = TextLoader('path/to/related/document')
doc = loader.load()
print (f"You have {len(doc)} document")
print (f"You have {len(doc[0].page_content)} characters in that document")
# 分割字符串
text_splitter = RecursiveCharacterTextSplitter(chunk_size=3000, chunk_overlap=400)
docs = text_splitter.split_documents(doc)
num_total_characters = sum([len(x.page_content) for x in docs])
print (f"Now you have {len(docs)} documents that have an average of {num_total_characters / len(docs):,.0f} characters (smaller pieces)")
# 初始化向量化模型
embeddings = OpenAIEmbeddings(openai_api_key=OPENAI_API_KEY, openai_api_base=OPENAI_PROXY_URL)
# 向量化Document並存入向量數據庫(綁定向量和對應Document元數據),這裏我們選擇本地最常用的FAISS數據庫
# 注意: 這會向OpenAI產生請求併產生費用
doc_search = FAISS.from_documents(docs, embeddings)
qa = RetrievalQA.from_chain_type(llm=llm, chain_type="stuff", retriever=doc_search.as_retriever(), verbose=True)
query = "Specific questions to be asked"
qa.run(query)
執行後 verbose 輸出日誌如下:
You have 1 document
You have 74663 characters in that document
Now you have 29 documents that have an average of 2,930 characters (smaller pieces)**
Entering new chain...
**Prompt after formatting:
**_
System: Use the following pieces of context to answer the users question. If you don't know the answer, just say that you don't know, don't try to make up an answer.
---------------_****_
Human: What does the author describe as good work?
_****_
The author describes working on things that aren't prestigious as a sign of good work. They believe that working on unprestigious types of work can lead to the discovery of something real and that it indicates having the right kind of motives. The author also mentions that working on things that last, such as paintings, is considered good work._**
Chains
接下來就是 LangChain 中的主角——Chains 了,Chains 是 LangChain 中爲連接多次與 Language Model 的交互過程而抽象的重要組件,它可以將多個組件組合在一起以創建一個單一的、連貫的任務,也可以嵌套多個 Chain 組合在一起,或者將 Chain 與其他組件組合來構建更復雜的 Chain。
除了單一 Chain 外,常用的幾個 Chains 如下:
Router Chain
在一些場景下,我們需要根據輸入 / 上下文決定使用哪一條 Chain,甚至哪一個 Prompt,Router Chain 就提供了諸如 MultiPromptChain, LLMRouterChain 等一系列用於決策的 Chain(這與後面我們會提到的 Agent 有類似的地方)。
Router Chain 由兩部分組成:
-
Router Chain 本身:負責決定下一個目標 Chain;
-
目標 Chains:Router Chain 可以路由到的目標 Chain。
Sequential Chain
顧名思義,順序執行的串行 Chain,其中最簡單的 SimpleSequentialChain 非常簡單粗暴,SimpleSequentialChain 的每個子 Chain 都有一個單一的輸入 / 輸出,並且一個步驟的輸出是下一步的輸入。
而高階一些的 SequentialChain 則允許多輸入輸出,並且我們可以通過添加後面會提到的 Memory 等來提高其推理表現。
Map-reduce Chain
Map-reduce Chain 主要用於 summary 的場景,針對那些超長的文檔,首先我們通過前面提到過的 TextSpliter 按一定規則分割文檔爲更小的 Chunks(通常使用 RecursiveCharacterTextSplitter,如果 Document 是結構化的可以考慮使用指定的 TextSpliter), 然後對每個分割的部分執行”map-chain”,收集全部”map-chain” 的輸出後,再執行”reduce-chain”,獲得最終的 summary 輸出。
使用示例
下面就是一個 Router Chain 中的 MultiPromptChain 的具體示例:
from langchain.chains.router import MultiPromptChain
from langchain.llms import OpenAI
from langchain.chains import ConversationChain
from langchain.chains.llm import LLMChain
from langchain.prompts import PromptTemplate
from langchain.chains.router.llm_router import LLMRouterChain, RouterOutputParser
from langchain.chains.router.multi_prompt_prompt import MULTI_PROMPT_ROUTER_TEMPLATE
# 定義要路由的prompts
physics_template = """You are a very smart physics professor. \
You are great at answering questions about physics in a concise and easy to understand manner. \
When you don't know the answer to a question you admit that you don't know.
Here is a question:
{input}"""
math_template = """You are a very good mathematician. You are great at answering math questions. \
You are so good because you are able to break down hard problems into their component parts, \
answer the component parts, and then put them together to answer the broader question.
Here is a question:
{input}"""
# 整理prompt和相關信息
prompt_infos = [
{
"name": "physics",
"description": "Good for answering questions about physics",
"prompt_template": physics_template,
},
{
"name": "math",
"description": "Good for answering math questions",
"prompt_template": math_template,
},
]
llm = OpenAI(temperature=0.5, openai_api_key=OPENAI_API_KEY, openai_api_base=OPENAI_PROXY_URL+"/v1")
destination_chains = {}
for p_info in prompt_infos:
# 以每個prompt爲基礎創建一個destination_chain(開啓verbose)
name = p_info["name"]
prompt_template = p_info["prompt_template"]
prompt = PromptTemplate(template=prompt_template, input_variables=["input"])
chain = LLMChain(llm=llm, prompt=prompt)
destination_chains[name] = chain
# 創建一個缺省chain,如果沒有其他chain滿足路由條件,則使用該chain
default_chain = ConversationChain(llm=llm, output_key="text")
destinations = [f"{p['name']}: {p['description']}" for p in prompt_infos]
destinations_str = "\n".join(destinations)
# 根據prompt_infos中的映射關係創建router_prompt
router_template = MULTI_PROMPT_ROUTER_TEMPLATE.format(destinations=destinations_str)
router_prompt = PromptTemplate(
template=router_template,
input_variables=["input"],
output_parser=RouterOutputParser(),
)
# 創建router_chain(開啓verbose)
router_chain = LLMRouterChain(llm_chain=LLMChain(llm=llm, prompt=router_prompt, verbose=True), verbose=True)
# 將router_chain和destination_chains以及default_chain組合成MultiPromptChain(開啓verbose)
chain = MultiPromptChain(
router_chain=router_chain,
destination_chains=destination_chains,
default_chain=default_chain,
verbose=True,
)
# run
chain.run("What is black body radiation?")
執行後 verbose 輸出日誌如下:
**Entering new chain...**Prompt after formatting: Given a raw text input to a language model select the model prompt best suited for the input. You will be given the names of the available prompts and a description of what the prompt is best suited for. You may also revise the original input if you think that revising it will ultimately lead to a better response from the language model.
<> Return a markdown code snippet with a JSON object formatted to look like:
{ "destination": string \ name of the prompt to use or "DEFAULT" "next_inputs": string \ a potentially modified version of the original input }
REMEMBER: "destination" MUST be one of the candidate prompt names specified below OR it can be "DEFAULT" if the input is not well suited for any of the candidate prompts. REMEMBER: "next_inputs" can just be the original input if you don't think any modifications are needed.
<> physics: Good for answering questions about physics math: Good for answering math questions
<>
What is black body radiation?<>
** Finished chain.
**physics: {'input': 'What is black body radiation?'}
**
Entering new chain...
**Prompt after formatting:
You are a very smart physics professor. You are great at answering questions about physics in a concise and easy to understand manner. When you don't know the answer to a question you admit that you don't know.
Here is a question:
What is black body radiation?**
Finished chain.**
Memory
Memory
可以幫助Language Model
補充歷史信息的上下文,LangChain
中的Memory
是一個有點模糊的術語,它可以像記住你過去聊天過的信息一樣簡單,也可以結合向量數據庫做更加複雜的歷史信息檢索,甚至維護相關實體及其關係的具體信息,這取決於具體的應用。
通常 Memory 用於較長的 Chain,能一定程度上提高模型的推理表現。
常用的 Memory 類型如下:
-
Chat Messages
最簡單 Memory,將歷史 Chat 記錄作爲補充信息放入 prompt 中.
-
Vector store-backed memory
基於向量數據庫的 Memory,將 memory 存儲在向量數據庫中,並在每次調用時查詢 TopK 的最 “重要” 的文檔。
這與大多數其他內存類的不同之處在於,它不顯式跟蹤交互的順序,最多基於部分元數據篩選一下向量數據庫的查詢範圍
-
Conversation buffer(window) memory 保存一段時間內的歷史 Chat 記錄,它只使用最後 K 個記錄(僅保持最近交互的滑動窗口),這樣 buffer 也就不會變得太大,避免超過 token 上限。
-
Conversation summary memory
這種類型的 Memory 會隨着 Chat 的進行創建對話的摘要,並將當前摘要存儲在 Memory 中,用於後續對話的 history 提示;這種 memory 方案對長會話非常有用,但頻繁的總結摘要會耗費大量的 token.
-
Conversation Summary Buffer Memory
結合了
buffer memory
和summary memory
的策略,依舊會在內存中保留最後的一些 Chat 記錄作爲 buffer,並在 buffer 的總 token 數達到預置的上限後,對所有 Chat 記錄總結摘要作爲 SystemMessage 並清理其它歷史 Messages;這種 memory 方案結合了buffer memory
和summary memory
的優點,既不會頻繁地總結摘要消耗 token,也不會讓 buffer 缺失過多信息。
使用示例
下面是一個Conversation Summary Buffer Memory
在ConversationChain
中的使用示例,包含了切換會話時恢復現場 memory 的方法以及自定義 summary prompt 的方法:
from langchain.chains import ConversationChain
from langchain.memory import ConversationSummaryBufferMemory
from langchain.llms import OpenAI
from langchain.schema import SystemMessage, AIMessage, HumanMessage
from langchain.memory.prompt import SUMMARY_PROMPT
from langchain.prompts import PromptTemplate
llm = OpenAI(temperature=0.7, openai_api_key=OPENAI_API_KEY, openai_api_base=OPENAI_PROXY_URL+"/v1")
# ConversationSummaryBufferMemory默認使用langchain.memory.prompt.SUMMARY_PROMPT作爲summary的PromptTemplate
# 如果對它summary的格式/內容有特殊要求,可以自定義PromptTemplate(實測默認的summary有些流水賬)
prompt_template_str = """
## Instruction
Progressively summarize the lines of conversation provided, adding onto the previous summary returning a new concise and detailed summary.
Don't repeat the conversation directly in the summary, extract key information instead.
## EXAMPLE
Current summary:
The human asks what the AI thinks of artificial intelligence. The AI thinks artificial intelligence is a force for good.
New lines of conversation:
Human: Why do you think artificial intelligence is a force for good?
AI: Because artificial intelligence will help humans reach their full potential.
New summary:
The human inquires about the AI's opinion on artificial intelligence. The AI believes that it is a force for good as it can help humans reach their full potential.
## Current summary
{summary}
## New lines of conversation
{new_lines}
## New summary
"""
prompt = PromptTemplate(
input_variables=SUMMARY_PROMPT.input_variables, # input_variables爲SUMMARY_PROMPT中的input_variables不變
template=prompt_template_str, # template替換爲上面重新編寫的prompt_template_str
)
memory = ConversationSummaryBufferMemory(llm=llm, prompt=prompt, max_token_limit=60)
# 添加歷史memory,其中第一條SystemMessage爲歷史對話中Summary的內容,第二條HumanMessage和第三條AIMessage爲歷史對話中最後的對話內容
memory.chat_memory.add_message(SystemMessage(content="The human asks what the AI thinks of artificial intelligence. The AI thinks artificial intelligence is a force for good because it will help humans reach their full potential. The human then asks the difference between python and golang in short. The AI responds that python is a high-level interpreted language with an emphasis on readability and code readability, while golang is a statically typed compiled language with a focus on concurrency and performance. Python is typically used for general-purpose programming, while golang is often used for building distributed systems."))
memory.chat_memory.add_user_message("Then if I want to build a distributed system, which language should I choose?")
memory.chat_memory.add_ai_message("If you want to build a distributed system, I would recommend golang as it is a statically typed compiled language that is designed to facilitate concurrency and performance.")
# 調用memory.prune()確保chat_memory中的對話內容不超過max_token_limit
memory.prune()
conversation_with_summary = ConversationChain(
llm=llm,
# We set a very low max_token_limit for the purposes of testing.
memory=memory,
verbose=True,
)
# memory.prune()會在每次調用predict()後自動執行
conversation_with_summary.predict(input="Is there any well-known distributed system built with golang?")
conversation_with_summary.predict(input="Is there a substitutes for Kubernetes in python?")
執行後 verbose 輸出日誌如下:
> Entering new 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: System: The human asks the AI about its opinion on artificial intelligence and is told that it is a force for good that can help humans reach their full potential. The human then inquires about the differences between python and golang, with the AI explaining that python is a high-level interpreted language for general-purpose programming, while golang is a statically typed compiled language often used for building distributed systems. Human: Then if I want to build a distributed system, which language should I choose? AI: If you want to build a distributed system, I would recommend golang as it is a statically typed compiled language that is designed to facilitate concurrency and performance. Human: Is there any well-known distributed system built with golang?
AI:> Finished chain.
> Entering new 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: System: The human asks the AI about its opinion on artificial intelligence and is told that it is a force for good that can help humans reach their full potential. The human then inquires about the differences between python and golang, with the AI explaining that python is a high-level interpreted language for general-purpose programming, while golang is a statically typed compiled language designed to facilitate concurrency and performance, thus better suited for distributed systems. The AI recommends golang for building distributed systems. Human: Is there any well-known distributed system built with golang? AI: Yes, there are several well-known distributed systems built with golang. These include Kubernetes, Docker, and Consul. Human: Is there a substitutes for Kubernetes in python?
AI:**> Finished chain.
**'Yes, there are several substitutes for Kubernetes in python. These include Dask, Apache Mesos and Marathon, and Apache Aurora.’
Agent
在一些場景下,我們需要根據用戶輸入靈活地調用LLM
和其它工具(LangChain
將工具抽象爲 Tools 這一組件),Agent 爲這樣的應用程序提供了相關的支持。
Agent
可以訪問一套工具,並根據用戶輸入確定要使用Chain
或是Function
,我們可以簡單的理解爲他可以動態的幫我們選擇和調用 Chain 或者已有的工具。
常用的Agent
類型如下:
Conversational Agent
這類 Agent 可以根據 Language Model 的輸出決定是否使用指定的 Tool,以及使用什麼 Tool(這裏的 Tool 也可以是一個 Chain),以及時的爲 Model I/O 的過程補充信息。
OpenAI functions Agent
類似 Conversational Agent,但它能夠讓 Agent 更進一步地幫忙提取指定 Tool 的參數等,甚至使用多個 Tools。
Plan and execute Agent
抽象 Agent“決定做什麼”的過程爲 “planning what to do” 和“executing the sub tasks”(這種方法來自 "Plan-and-Solve" 這一篇論文),其中 “planning what to do” 這一步通常完全由 LLM 完成,而 “executing the sub tasks” 這一任務則通常由更多的 Tools 來完成。
ReAct Agent
結合對 LLM 輸出的歸因和執行,類似 OpenAI functions Agent,提供了一個更加明確的框架以及由論文支撐的方法。
Self ask with search
這類 Agent 會基於 LLM 的輸出,自行調用 Tools 以及 LLM 來進行額外的搜索和自查,以達到拓展和優化輸出的目的。
總結
LangChain 中的 Data Connection 將 LLM 與萬物互聯,給 LangChain 構建的應用帶來了無限可能,而 Agent 又爲應用開發中非常常見的” 事件驅動 “這一開發框架提供了偷懶的途徑,將分支決策的工作交給 LLM,這又進一步簡化了應用開發的工作;結合基於高度抽象的 Model I/O 及 Memory 等組件,LangChain 讓開發者能夠更快,更好更靈活地實現 LLM 助手、對話機器人等應用,極大地降低了 LLM 的使用門檻。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/KrWM3cMywMvYUiawRZ94Gg