Langchain使用 | 模型、提示和解析器、存儲(chǔ)
共 30090字,需瀏覽 61分鐘
·
2024-04-24 08:17
零、LangChain介紹
-
為各種不同基礎(chǔ)模型提供統(tǒng)一接口- 幫助管理提示的框架- 一套中心化接口,用于處理長期記憶(參見Memory)、外部數(shù)據(jù)(參見Indexes)、其他 LLM(參見Chains)以及 LLM 無法處理的任務(wù)的其他代理(例如,計(jì)算或搜索)。
總的來說,有六大核心模塊:
-
Models:從不同的 LLM 和嵌入模型中進(jìn)行選擇
-
Prompts:管理 LLM 輸入
-
Chains:將 LLM 與其他組件相結(jié)合
-
Indexes:訪問外部數(shù)據(jù)
-
Memory:記住以前的對話
-
Agents:代理涉及 LLM 做出行動(dòng)決策、執(zhí)行該行動(dòng)、查看一個(gè)觀察結(jié)果,并重復(fù)該過程直到完成。LangChain 提供了一個(gè)標(biāo)準(zhǔn)的代理接口,
一系列可供選擇的代理,以及端到端代理的示例。
一、模型、提示和解析器
1. 提示
# 讓模型用指定語氣進(jìn)行回答
def get_completion(prompt, model="gpt-3.5-turbo"):
messages = [{<!-- -->"role": "user", "content": prompt}]
response = openai.ChatCompletion.create(
model=model,
messages=messages,
temperature=0,
)
return response.choices[0].message["content"]
customer_email = """
Arrr, I be fuming that me blender lid \
flew off and splattered me kitchen walls \
with smoothie! And to make matters worse,\
the warranty don't cover the cost of \
cleaning up me kitchen. I need yer help \
right now, matey!
"""
# 美式英語 + 平靜、尊敬的語調(diào)
style = """American English \
in a calm and respectful tone
"""
response = get_completion(prompt)
# 如果用英文,要求模型根據(jù)給出的語調(diào)進(jìn)行轉(zhuǎn)化
prompt = f"""Translate the text \
that is delimited by triple backticks
into a style that is {<!-- -->style}.
text: ```{<!-- -->customer_email}```
"""
print(prompt)
# 如果用中文, 要求模型根據(jù)給出的語調(diào)進(jìn)行轉(zhuǎn)化
prompt = f"""把由三個(gè)反引號(hào)分隔的文本text\
翻譯成一種{<!-- -->style}風(fēng)格。
text: ```{<!-- -->customer_email}```
"""
print(prompt)
2. 使用langchain寫提示
-
構(gòu)造langchain提示模板- 使用模板利用 prompt_template.format_messages得到提示消息- 調(diào)用實(shí)例化的ChatOpenAI對象提取信息
!pip install -q --upgrade langchain
from langchain.chat_models import ChatOpenAI
api_key = "..."
chat = ChatOpenAI(temperature=0.0, openai_api_key = api_key)
# 構(gòu)造模板
template_string = """Translate the text \
that is delimited by triple backticks \
into a style that is {style}. \
text: ```{text}```
"""
# 中文
template_string = """把由三個(gè)反引號(hào)分隔的文本text\
翻譯成一種{style}風(fēng)格。\
text: ```{text}```
"""
# 需要安裝最新版的 LangChain
from langchain.prompts import ChatPromptTemplate
prompt_template = ChatPromptTemplate.from_template(template_string)
# prompt_template.messages[0].prompt
# prompt_template.messages[0].prompt.input_variables
# ['style', 'text']
langchain提示模版prompt_template需要兩個(gè)輸入變量: style 和 text。 這里分別對應(yīng)
-
customer_style: 我們想要的顧客郵件風(fēng)格-customer_email: 顧客的原始郵件文本。- 對于給定的customer_style和customer_email, 我們可以使用提示模版prompt_template的format_messages方法生成想要的客戶消息customer_messages。
customer_messages = prompt_template.format_messages(
style=customer_style,
text=customer_email)
customer_response = chat(customer_messages)
customer_response.content # str類型
其他提示模板舉例:
prompt = """ 你的任務(wù)是判斷學(xué)生的解決方案是正確的還是不正確的
要解決該問題,請執(zhí)行以下操作:
- 首先,制定自己的問題解決方案
- 然后將您的解決方案與學(xué)生的解決方案進(jìn)行比較
并評估學(xué)生的解決方案是否正確。
...
使用下面的格式:
問題:
問題文本
學(xué)生的解決方案:
學(xué)生的解決方案文本
實(shí)際解決方案:
... 制定解決方案的步驟以及您的解決方案請參見此處
學(xué)生的解決方案和實(shí)際解決方案是否相同 \
只計(jì)算:
是或者不是
學(xué)生的成績
正確或者不正確
在建立大模型應(yīng)用時(shí),通常希望模型的輸出為給定的格式,比如在輸出使用特定的關(guān)鍵詞來讓輸出結(jié)構(gòu)化。 下面為一個(gè)使用大模型進(jìn)行鏈?zhǔn)剿伎纪评砝?,對于問題:What is the elevation range for the area that the eastern sector of the Colorado orogeny extends into?, 通過使用LangChain庫函數(shù),輸出采用"Thought"(思考)、“Action”(行動(dòng))、“Observation”(觀察)作為鏈?zhǔn)剿伎纪评淼年P(guān)鍵詞,讓輸出結(jié)構(gòu)化。在中,可以查看使用LangChain和OpenAI進(jìn)行鏈?zhǔn)剿伎纪评淼牧硪粋€(gè)代碼實(shí)例。
"""
Thought: I need to search Colorado orogeny, find the area that the eastern sector of the Colorado orogeny extends into, then find the elevation range of the area.
Action: Search[Colorado orogeny]
Observation: The Colorado orogeny was an episode of mountain building (an orogeny) in Colorado and surrounding areas.
Thought: It does not mention the eastern sector. So I need to look up eastern sector.
Action: Lookup[eastern sector]
Observation: (Result 1 / 1) The eastern sector extends into the High Plains and is called the Central Plains orogeny.
Thought: The eastern sector of Colorado orogeny extends into the High Plains. So I need to search High Plains and find its elevation range.
Action: Search[High Plains]
Observation: High Plains refers to one of two distinct land regions
Thought: I need to instead search High Plains (United States).
Action: Search[High Plains (United States)]
Observation: The High Plains are a subregion of the Great Plains. From east to west, the High Plains rise in elevation from around 1,800 to 7,000 ft (550 to 2,130 m).[3]
Thought: High Plains rise in elevation from around 1,800 to 7,000 ft, so the answer is 1,800 to 7,000 ft.
Action: Finish[1,800 to 7,000 ft]
"""
"""
想法:我需要搜索科羅拉多造山帶,找到科羅拉多造山帶東段延伸到的區(qū)域,然后找到該區(qū)域的高程范圍。
行動(dòng):搜索[科羅拉多造山運(yùn)動(dòng)]
觀察:科羅拉多造山運(yùn)動(dòng)是科羅拉多州及周邊地區(qū)造山運(yùn)動(dòng)(造山運(yùn)動(dòng))的一次事件。
想法:它沒有提到東區(qū)。 所以我需要查找東區(qū)。
行動(dòng):查找[東區(qū)]
觀察:(結(jié)果1 / 1)東段延伸至高原,稱為中原造山運(yùn)動(dòng)。
想法:科羅拉多造山運(yùn)動(dòng)的東段延伸至高原。 所以我需要搜索高原并找到它的海拔范圍。
行動(dòng):搜索[高地平原]
觀察:高原是指兩個(gè)不同的陸地區(qū)域之一
想法:我需要搜索高地平原(美國)。
行動(dòng):搜索[高地平原(美國)]
觀察:高地平原是大平原的一個(gè)分區(qū)。 從東到西,高原的海拔從 1,800 英尺左右上升到 7,000 英尺(550 到 2,130 米)。[3]
想法:高原的海拔從大約 1,800 英尺上升到 7,000 英尺,所以答案是 1,800 到 7,000 英尺。
動(dòng)作:完成[1,800 至 7,000 英尺]
"""
3. 輸出解析器
review_template = """\
For the following text, extract the following information:
gift: Was the item purchased as a gift for someone else? \
Answer True if yes, False if not or unknown.
delivery_days: How many days did it take for the product \
to arrive? If this information is not found, output -1.
price_value: Extract any sentences about the value or price,\
and output them as a comma separated Python list.
Format the output as JSON with the following keys:
gift
delivery_days
price_value
text: {text}
"""
使用Langchain輸出解析器:
review_template_2 = """\
For the following text, extract the following information:
gift: Was the item purchased as a gift for someone else? \
Answer True if yes, False if not or unknown.
delivery_days: How many days did it take for the product\
to arrive? If this information is not found, output -1.
price_value: Extract any sentences about the value or price,\
and output them as a comma separated Python list.
text: {text}
{format_instructions}
"""
prompt = ChatPromptTemplate.from_template(template=review_template_2)
# 構(gòu)造輸出解析器
from langchain.output_parsers import ResponseSchema
from langchain.output_parsers import StructuredOutputParser
gift_schema = ResponseSchema(name="gift",
description="Was the item purchased\
as a gift for someone else? \
Answer True if yes,\
False if not or unknown.")
delivery_days_schema = ResponseSchema(name="delivery_days",
description="How many days\
did it take for the product\
to arrive? If this \
information is not found,\
output -1.")
price_value_schema = ResponseSchema(name="price_value",
description="Extract any\
sentences about the value or \
price, and output them as a \
comma separated Python list.")
response_schemas = [gift_schema,
delivery_days_schema,
price_value_schema]
output_parser = StructuredOutputParser.from_response_schemas(response_schemas)
format_instructions = output_parser.get_format_instructions()
print(format_instructions)
# 利用模板得到提示消息
messages = prompt.format_messages(text=customer_review, format_instructions=format_instructions)
# 調(diào)用chat模型得到結(jié)果
response = chat(messages)
print(response.content)
# 使用輸出解析器解析輸出
output_dict = output_parser.parse(response.content)
output_dict
# {'gift': False, 'delivery_days': '2', 'price_value': '它比其他吹葉機(jī)稍微貴一點(diǎn)'}
# 這時(shí)就是dict,可以用get(key)
output_dict.get('delivery_days')
中文版本:
# 中文
review_template = """\
對于以下文本,請從中提取以下信息:
禮物:該商品是作為禮物送給別人的嗎? \
如果是,則回答 是的;如果否或未知,則回答 不是。
交貨天數(shù):產(chǎn)品需要多少天\
到達(dá)? 如果沒有找到該信息,則輸出-1。
價(jià)錢:提取有關(guān)價(jià)值或價(jià)格的任何句子,\
并將它們輸出為逗號(hào)分隔的 Python 列表。
使用以下鍵將輸出格式化為 JSON:
禮物
交貨天數(shù)
價(jià)錢
文本: {text}
"""
# 中文
review_template_2 = """\
對于以下文本,請從中提取以下信息::
禮物:該商品是作為禮物送給別人的嗎?
如果是,則回答 是的;如果否或未知,則回答 不是。
交貨天數(shù):產(chǎn)品到達(dá)需要多少天? 如果沒有找到該信息,則輸出-1。
價(jià)錢:提取有關(guān)價(jià)值或價(jià)格的任何句子,并將它們輸出為逗號(hào)分隔的 Python 列表。
文本: {text}
{format_instructions}
"""
# 中文
from langchain.output_parsers import ResponseSchema
from langchain.output_parsers import StructuredOutputParser
gift_schema = ResponseSchema(name="禮物",
description="這件物品是作為禮物送給別人的嗎?\
如果是,則回答 是的,\
如果否或未知,則回答 不是。")
delivery_days_schema = ResponseSchema(name="交貨天數(shù)",
description="產(chǎn)品需要多少天才能到達(dá)?\
如果沒有找到該信息,則輸出-1。")
price_value_schema = ResponseSchema(name="價(jià)錢",
description="提取有關(guān)價(jià)值或價(jià)格的任何句子,\
并將它們輸出為逗號(hào)分隔的 Python 列表")
response_schemas = [gift_schema,
delivery_days_schema,
price_value_schema]
output_parser = StructuredOutputParser.from_response_schemas(response_schemas)
format_instructions = output_parser.get_format_instructions()
print(format_instructions)
# 結(jié)果如下
The output should be a markdown code snippet formatted in the following schema, including the leading and trailing "\`\`\`json" and "\`\`\`":
```json
{<!-- -->
"禮物": string // 這件物品是作為禮物送給別人的嗎? 如果是,則回答 是的, 如果否或未知,則回答 不是。
"交貨天數(shù)": string // 產(chǎn)品需要多少天才能到達(dá)? 如果沒有找到該信息,則輸出-1。
"價(jià)錢": string // 提取有關(guān)價(jià)值或價(jià)格的任何句子, 并將它們輸出為逗號(hào)分隔的 Python 列表
}
4. 鏈?zhǔn)剿伎纪评?ReAct)
!pip install -q wikipedia
from langchain.docstore.wikipedia import Wikipedia
from langchain.llms import OpenAI
from langchain.agents import initialize_agent, Tool, AgentExecutor
from langchain.agents.react.base import DocstoreExplorer
docstore=DocstoreExplorer(Wikipedia())
tools = [
Tool(
name="Search",
func=docstore.search,
description="Search for a term in the docstore.",
),
Tool(
name="Lookup",
func=docstore.lookup,
description="Lookup a term in the docstore.",
)
]
# 使用大語言模型
llm = OpenAI(
model_name="gpt-3.5-turbo",
temperature=0,
openai_api_key = api_key
)
# 初始化ReAct代理
react = initialize_agent(tools, llm, agent="react-docstore", verbose=True)
agent_executor = AgentExecutor.from_agent_and_tools(
agent=react.agent,
tools=tools,
verbose=True,
)
question = "Author David Chanoff has collaborated with a U.S. Navy admiral who served as the ambassador to the United Kingdom under which President?"
agent_executor.run(question)
二、存儲(chǔ)
1. 對話緩存存儲(chǔ)
dotenv模塊使用解析:
-
安裝方式:pip install python-dotenv- load_dotenv()函數(shù)用于加載環(huán)境變量,- find_dotenv()函數(shù)用于尋找并定位.env文件的路徑- 接下來的代碼 _ = load_dotenv(find_dotenv()) ,通過find_dotenv()函數(shù)找到.env文件的路徑,并將其作為參數(shù)傳遞給load_dotenv()函數(shù)。load_dotenv()函數(shù)會(huì)讀取該.env文件,并將其中的環(huán)境變量加載到當(dāng)前的運(yùn)行環(huán)境中
import os
import warnings
warnings.filterwarnings('ignore')
# 讀取本地的.env文件,并將其中的環(huán)境變量加載到代碼的運(yùn)行環(huán)境中,以便在代碼中可以直接使用這些環(huán)境變量
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())
from langchain.chat_models import ChatOpenAI
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
OPENAI_API_KEY = "..."
llm = ChatOpenAI(temperature=0.0,openai_api_key=OPENAI_API_KEY)
# memory.buffer存儲(chǔ)所有的對話內(nèi)容, memory.load_memory_variables({})也可以
memory = ConversationBufferMemory()
# 新建一個(gè)對話鏈(關(guān)于鏈后面會(huì)提到更多的細(xì)節(jié))
conversation = ConversationChain(
llm=llm,
memory = memory,
verbose=True #查看Langchain實(shí)際上在做什么,設(shè)為FALSE的話只給出回答,看到不到下面綠色的內(nèi)容
)
conversation.predict(input="你好, 我叫山頂夕景")
conversation.predict(input="What is 1+1?")
conversation.predict(input="What is my name?") # 還記得名字
注意:
-
ConversationChain的verbose參數(shù),是查看Langchain實(shí)際上在做什么,設(shè)為FALSE的話只給出回答,看到不到下面綠色的內(nèi)容(prompt format + 完整對話內(nèi)容)。- 上面的memory.buffer存儲(chǔ)所有的對話內(nèi)容,memory.load_memory_variables({})也可以- 添加指定的輸入輸出內(nèi)容到記憶緩存區(qū)
memory = ConversationBufferMemory() #新建一個(gè)空的對話緩存記憶
memory.save_context({<!-- -->"input": "Hi"}, #向緩存區(qū)添加指定對話的輸入輸出
{<!-- -->"output": "What's up"})
memory.load_memory_variables({<!-- -->}) #再次加載記憶變量, 內(nèi)容不變
2. 對話緩存窗口存儲(chǔ)
from langchain.memory import ConversationBufferWindowMemory
memory = ConversationBufferWindowMemory(k=1)
# k=1表明只保留一個(gè)對話記憶, 即上一輪信息
3. 對話token緩存存儲(chǔ)
!pip install tiktoken
# 限制token數(shù)量, 用到之前定義的llm對象
memory = ConversationTokenBufferMemory(llm=llm, max_token_limit=30)
memory.save_context({<!-- -->"input": "AI is what?!"},
{<!-- -->"output": "Amazing!"})
memory.save_context({<!-- -->"input": "Backpropagation is what?"},
{<!-- -->"output": "Beautiful!"})
memory.save_context({<!-- -->"input": "Chatbots are what?"},
{<!-- -->"output": "Charming!"})
ChatGPT使用一種基于字節(jié)對編碼(Byte Pair Encoding,BPE)的方法來進(jìn)行tokenization(將輸入文本拆分為token)。 BPE是一種常見的tokenization技術(shù),它將輸入文本分割成較小的子詞單元。
OpenAI在其官方GitHub上公開了一個(gè)最新的開源Python庫:tiktoken,這個(gè)庫主要是用來計(jì)算tokens數(shù)量的。相比較Hugging Face的tokenizer,其速度提升了好幾倍
具體token計(jì)算方式,特別是漢字和英文單詞的token區(qū)別,參考
4. 對話摘要緩存存儲(chǔ)
-
這里不用前面兩種的窗口輪次存儲(chǔ)或token限制緩存,而是讓大模型先對過去的歷史信息做個(gè)概述,不同的是用 ConversationSummaryBufferMemory實(shí)例化memory對象
from langchain.memory import ConversationSummaryBufferMemory
from langchain.chat_models import ChatOpenAI
from langchain.chains import ConversationChain
memory = ConversationSummaryBufferMemory(llm=llm, max_token_limit=100)
三、利用Langchain導(dǎo)入ChatGLM6b推理
-
在 CPU 上運(yùn)行時(shí),會(huì)根據(jù)硬件自動(dòng)編譯 CPU Kernel ,請確保已安裝 GCC 和 OpenMP (Linux一般已安裝,對于Windows則需手動(dòng)安裝),以獲得最佳并行計(jì)算能力,所以最好用cuda GPU加速推理。
-
如下栗子,繼承
langchain.llms.base.LLM的模型ChatGLM2類,重寫_call類方法進(jìn)行模型推理:首先對user輸入的prompt進(jìn)行編碼,將編碼結(jié)果和self.history歷史對話內(nèi)容一起傳參給模型響應(yīng),將響應(yīng)結(jié)果加入歷史記錄self.history中。 -
@property裝飾器將_llm_type方法轉(zhuǎn)為【只讀屬性】,即可以被類訪問,而不需要實(shí)例的方法來訪問(比如model._llm_type == ChatGLM2的結(jié)果為true)。對其他具體感興趣的讀者可以參考父類LLM代碼。
from langchain.llms.base import LLM
from langchain.llms.utils import enforce_stop_tokens
from transformers import AutoTokenizer, AutoModel
from typing import List, Optional
class ChatGLM2(LLM):
max_token: int = 4096
temperature: float = 0.8
top_p = 0.9
tokenizer: object = None
model: object = None
history = []
def __init__(self):
super().__init__()
@property
def _llm_type(self) -> str:
return "ChatGLM2"
# 定義load_model方法,進(jìn)行模型的加載
def load_model(self, model_path = None):
self.tokenizer = AutoTokenizer.from_pretrained(model_path,trust_remote_code=True)
self.model = AutoModel.from_pretrained(model_path, trust_remote_code=True).float()
# 實(shí)現(xiàn)_call方法,進(jìn)行模型的推理
def _call(self,prompt:str, stop: Optional[List[str]] = None) -> str:
response, _ = self.model.chat(
self.tokenizer,
prompt,
history=self.history,
max_length=self.max_token,
temperature=self.temperature,
top_p=self.top_p)
if stop is not None:
response = enforce_stop_tokens(response, stop)
self.history = self.history + [[None, response]]
return response
if __name__ == "__main__":
llm=ChatGLM2()
model_path = "/Users/andy/Desktop/LLM/model/chatglm-6b-int4"
llm.load_model(model_path)
print(llm._call("如何打好羽毛球?"))
可以用以下一些prompt測試模型回答質(zhì)量:
非洲土地肥沃,為什么很多非洲人寧可挨餓也不種地?
水一百度會(huì)開,下一句是什么?
四、其他類似工具:guidance
同時(shí)也包含使用 {<!-- -->{#select}}...{<!-- -->{or}}...{<!-- -->{/select}} 命令進(jìn)行控制流的選擇:
import guidance
# set the default language model used to execute guidance programs
guidance.llm = guidance.llms.OpenAI("text-davinci-003")
# define the few shot examples
examples = [
{<!-- -->'input': 'I wrote about shakespeare',
'entities': [{<!-- -->'entity': 'I', 'time': 'present'}, {<!-- -->'entity': 'Shakespeare', 'time': '16th century'}],
'reasoning': 'I can write about Shakespeare because he lived in the past with respect to me.',
'answer': 'No'},
{<!-- -->'input': 'Shakespeare wrote about me',
'entities': [{<!-- -->'entity': 'Shakespeare', 'time': '16th century'}, {<!-- -->'entity': 'I', 'time': 'present'}],
'reasoning': 'Shakespeare cannot have written about me, because he died before I was born',
'answer': 'Yes'}
]
# define the guidance program
structure_program = guidance(
'''Given a sentence tell me whether it contains an anachronism (i.e. whether it could have happened or not based on the time periods associated with the entities).
----
{<!-- -->{~! display the few-shot examples ~}}
{<!-- -->{~#each examples}}
Sentence: {<!-- -->{this.input}}
Entities and dates:{<!-- -->{#each this.entities}}
{<!-- -->{this.entity}}: {<!-- -->{this.time}}{<!-- -->{/each}}
Reasoning: {<!-- -->{this.reasoning}}
Anachronism: {<!-- -->{this.answer}}
---
{<!-- -->{~/each}}
{<!-- -->{~! place the real question at the end }}
Sentence: {<!-- -->{input}}
Entities and dates:
{<!-- -->{gen "entities"}}
Reasoning:{<!-- -->{gen "reasoning"}}
Anachronism:{<!-- -->{#select "answer"}} Yes{<!-- -->{or}} No{<!-- -->{/select}}''')
# execute the program
out = structure_program(
examples=examples,
input='The T-rex bit my dog'
)
