開始使用LangChain:掌握LLM流程的關鍵🔗

Runa's story
AI 精選指南
Published in
18 min readJun 17, 2023
圖片來源:LangChain

❓ 你聽過LangChain嗎?

隨著OpenAI發布GPT-3.5,LangChain迅速崛起,成為處理新的LLM Pipeline的最佳方式,其系統化的方法對Generative AI工作流程中的不同流程進行分類。它的受歡迎程度還源於將其他知名的大型語言模型(LLMs)整合進來,如Anthropic和Cohere等公司的模型。

在本篇內容中,我們將重點介紹流行的LangChain Pipeline的簡明但深入的分析,並在一個小而簡單的請求示例中應用LangChain。在未來的文章中,我們將探討LangChain的隱藏功能以及更複雜的服務集成。

LangChain是什麼?

在文檔的第一頁上,LangChain展示了框架的目的和目標:

  1. 數據感知:將語言模型與其他數據源連接起來
  2. 主動性:允許語言模型與其環境進行交互

基本上,LangChain提供了對各種不同類型的LLM服務的抽象,將它們與其他現有工具結合在一起,並提供一種一致的語言來處理LLM作為服務的各個方面。一個直觀的例子是OpenAI的”playground”功能。

圖片來源:OpenAI

如上所示,playground接受一個輸入並根據需要進行補充。這個平台有很多用途,以下舉幾個例子:

  1. 一般續寫:提供一些文本,要求模型繼續完成,比如故事的繼續。
  2. 基於提示的零樣本完工內容:提供一個指示給模型,要求它根據輸入和指示返回輸出。
  3. 基於提示的少樣本完工內容:提供帶有幾個例子的指示;然後,要求模型根據輸入、指示和例子返回輸出。
  4. 思路鏈接:為模型提供一個問題,要求模型回答問題,並解釋或說明到達答案的過程。

從測試的角度來看,playground是一個有用的工具,可以根據使用者的指示快速獲得有用的回應。然而,從應用的角度來看,將所有功能歸結為一個單一的「提交」會限制應用的精確性和可讀性。同時,這個平台與其他現有工具(例如其他公共API)的連接也變得困難。

這就是LangChain發揮作用的地方。基於LLM服務的眾多可能性,他們將整個流程/服務分解為以下模塊(從最簡單到最複雜):

  1. 模型:支持的模型類型和集成。
  2. 提示:提示管理、優化和序列化。
  3. 記憶:記憶是在鏈/代理的調用之間持續存在的狀態。
  4. 索引:當語言模型與特定應用數據相結合時,其功能變得更加強大 — 這個模塊包含了用於加載、查詢和更新外部數據的接口和集成。
  5. 鏈:鏈是結構化的調用序列(對LLM或不同工具的調用)。
  6. 代理:代理是一個鏈,其中LLM根據高層指示和一組工具重複決定動作,執行動作並觀察結果,直到高層指示完成。
  7. 回調:回調允許你記錄和流式傳輸任何鏈的中間步驟,輕鬆觀察、調試和評估應用的內部運作情況。

它是如何運作的呢?

這是一個非常龐大的內容…讓我們通過一個例子來談談所有這些模塊。

# llm
from langchain.llms import OpenAI
llm = OpenAI(temperature=0.7)

# prompt
from langchain.prompts import PromptTemplate
location_extractor_prompt = PromptTemplate(
input_variables=["travel_request"],
template="""
You a travel agent AI that uses the chat_history to obtain the
theme to break down a {travel_request} into the start, pitstops,
and end location of the trip. The pitstops should follow the theme
of the trip if specified.
The output is a Python dictionary that contains the keys of 'start'
(a string), 'pitstops' (a list of strings), and 'end' (a string).
"""
)

# chain
from langchain.chains import LLMChain
location_extractor_chain = LLMChain(
llm=llm,
prompt=location_extractor_prompt
)

# execution
location_extractor_chain.run("I want to go to Seattle from New York City.")

# output
# \\n trip = {\\n "start": "New York City",
# "pitstops": [],\\n "end": "Seattle"\\n }'

以上是一個簡單的LangChain代碼示例,用作位置提取器。它包含五個部分,即LLM、提示、鏈、執行和輸出。從ChatGPT的角度來看,我們可以將上述代碼庫分解為以下部分:

通過將ChatGPT的結果與LangChain進行映射,我們可以看到LangChain如何系統地將ChatGPT中的功能重組為一個更具編程驅動的框架。既然你已經理解了LangChain的一般概念,現在讓我們逐一談談每個部分:

LLM

LLM指的是從LangChain中選擇模型。這與LangChain中最簡單的功能相關,即從各個平台選擇模型。最常見的模型是OpenAI GPT-3模型(顯示為OpenAI(temperature=0.7))和OpenAI ChatGPT模型(顯示為ChatOpenAI(temperature=0))。

由於通用完成模型(GPT-3)和聊天模型(ChatGPT)之間結構的差異,聊天模型在輸入方面需要更多的信息,從定義人類輸入到在列表中創建對話流程(例如,chat([HumanMessage(content=”Translate this sentence from English to French. I love programming.”)]))

此外,模型接受不同的超參數來定義模型的行為。例如,temperature控制結果的多樣性,較高的temperature(接近1)表示更多的多樣性,反之亦然。而model則允許你從LLM提供者中選擇特定的模型(例如ChatGPT模型的gpt-turbo-3.5)。以下是其他常見參數的列表:

  1. presence penalty/frequency penalty:根據頻率對重複的token進行懲罰
  2. max tokens/min tokens:限制完成中token的最大/最小數量
  3. num results:生成的完成數量
  4. top P/top K:在每個步驟中考慮的token的總概率質量/數量,以確定如何回應
  5. streaming:與新輸入同步返回輸出。

值得注意的一點是,不同的公司可能具有略有不同的參數名稱,並且可能支持的功能比其他公司少- LangChain受到LLM提供者的限制。

Prompt(提示)

Prompt指的是使用者對模型下達的指令。我們通常可以將Prompt分為兩種類型:零樣本或少樣本。

  1. 零樣本Prompt是指不包含任何範例,只有指令的Prompt。例如,上面的位置提取Prompt。
  2. 少樣本Prompt是指包含範例的Prompt。這可以幫助模型了解使用者想要的回應類型,並提供更相關且多樣性較少的結果。

每種類型的Prompt都有其優缺點。一般而言,提供給Prompt的範例越多,結果的多樣性就越少,獨立於模型的超參數(例如模型的temperature)。因此,如果你希望輸出具有嚴格的格式,請確保在Prompt中提供一些範例。

為了滿足範例需求,LangChain提供了ExampleSelector工具,以幫助智能選擇範例。

❓ 為什麼我們需要範例選擇器?範例越多越好嗎?

儘管提供更多範例確實可以獲得更一致的結果,但由於模型的輸入token限制,你給模型的範例數量是有限制的。簡單來說,你可以將多少單詞輸入模型中是有限制的。這是因為在LLM訓練過程中使用了處理能力的限制。因此,ExampleSelector提供了幾種不同的變體,以幫助你自動選擇最佳範例:

  1. 基於長度:根據長度選擇要使用的範例。
  2. 最大邊界相似性:根據範例與輸入的相似性組合選擇範例,同時優化多樣性。
  3. N-Gram重疊:根據n-gram重疊分數選擇和排序與輸入最相似的範例。
  4. 相似性:根據與輸入的餘弦相似性分數選擇範例,基於用戶提供的嵌入。

每個選擇器的構建方式相同,以下是LangChain文檔中的總結範例:

步驟1:你以下表字典格式提供你的範例:

examples = [
{"input": "happy", "output": "sad"},
{"input": "tall", "output": "short"},
{"input": "energetic", "output": "lethargic"},
{"input": "sunny", "output": "gloomy"},
{"input": "windy", "output": "calm"},
]

步驟2:接下來,你將範例放入範例選擇器類中,並提供Prompt和其他參數:

# Prompt format of your examples
example_prompt = PromptTemplate(
input_variables=["input", "output"],
template="Input: {input}\\nOutput: {output}",
)
# Example selector
example_selector = LengthBasedExampleSelector(
# These are the examples it has available to choose from.
examples=examples,
# This is the PromptTemplate being used to format the examples.
example_prompt=example_prompt,
# Max length of the examples
max_length=25, # (only applicable to length-based selector)
)

步驟3:最後,將這個範例選擇器添加到特殊的Prompt模板類中,以創建鏈條的Prompt。

prompt = FewShotPromptTemplate(
# We provide an ExampleSelector instead of examples.
example_selector=example_selector,
example_prompt=example_prompt,
prefix="Give the antonym of every input",
suffix="Input: {adjective}\\nOutput:",
input_variables=["adjective"],
)

最後,將你的Prompt作為其他Prompt一樣放入你的鏈條中。範例選擇器將根據你選擇的評估標準動態選擇最有用或相關的範例。

此外,由於聊天型模型的結構差異,它們需要不同的Prompt結構,如下所示:

  1. 你首先需要為系統和人類分別確定Prompt模板。
    # Prompt template for the system
    template="""You are a helpful assistant that translates {input_language} to {output_language}."""
    system_message_prompt = SystemMessagePromptTemplate.from_template(template)
    # Prompt template for the human
    human_template="{text}"
    human_message_prompt = HumanMessagePromptTemplate.from_template(human_template)
  2. 然後,使用ChatPromptTemplate類結合這些Prompt並格式化聊天輸出
    chat_prompt = ChatPromptTemplate.from_messages([system_message_prompt, human_message_prompt])

然後,這可以作為聊天模型的Prompt進一步使用。

Chain(鏈)

我會說,鏈條是任何LangChain應用的基本單位。從技術上講,它是封裝函數,用於將Prompt和模型包裹起來,以創建一個函數,該函數接收輸入文本並返回輸出文本。換句話說,它是對複雜的Prompt管道的抽象,使用戶能夠創建一個函數,該函數接收一些文本並輸出一些與OpenAI playground類似的文本。

我喜歡這樣想,我們可以將鏈條想像成汽車。無論汽車的複雜結構如何,汽車的目標都是根據輸入(例如方向盤、當前速度、檔位類型等)進行一些輸出(例如左轉和右轉),這是基於對輸入參數進行複雜計算。

當然,我們可以將汽車拆分為相互作用的較小系統。這與LangChain的鏈條相同。

這些鏈條的基本單位被稱為LLMChain。對於給定的Prompt、LLM和輸入,它將預測輸出:

from langchain import PromptTemplate, OpenAI, LLMChain

# prompt
prompt_template = "What is a good name for a company that makes {product}?"

# llm
llm = OpenAI(temperature=0)

# chain
llm_chain = LLMChain(
llm=llm,
prompt=PromptTemplate.from_template(prompt_template)
)

# predict
llm_chain("colorful socks")

# output
# {'product': 'colorful socks', 'text': '\\n\\nSocktastic!'}

然而,LangChain讓你可以在這個簡單的設置上做更多事情。例如,你可以鏈接這些鏈條!就像汽車中的各種系統一樣,你可以創建一個包含不同鏈條的管道,以完成更大的任務。這可以通過SimpleSequentialChain類來實現:

# This is the overall chain where we run these two chains in sequence.
from langchain.chains import SimpleSequentialChain

overall_chain = SimpleSequentialChain(chains=[synopsis_chain, review_chain], verbose=True)

你將兩個鏈條鏈接在一起,將第一個輸出作為下一個輸入。在這個例子中,我們同時鏈接了一個劇本的簡介和生成評論,輸入是劇本的標題。這樣做的好處是,通過給評論鏈條提供更多關於劇本的上下文信息,而不僅僅是知道劇本標題,可以更好地理解劇本。

此外,你還可以使用SequentialChain類同時輸出劇本簡介和評論,該類支持多個輸入:

# This is the overall chain where we run these two chains in sequence.
from langchain.chains import SequentialChain

overall_chain = SequentialChain(
chains=[synopsis_chain, review_chain],
# supports multiple inputs
input_variables=["era", "title"],
# Here we return multiple variables
output_variables=["synopsis", "review"],
verbose=True)

要執行這個操作,你需要在第一個類的提示中指定輸入:

# This is an LLMChain to write a synopsis given a title of a play and the era it is set in.
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: {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")

需要注意的一點是,只有第一個提示接受多個輸入,因為每個LLM鏈條的結果只是一個字符串,後續鏈條的輸入只會是一個變量。

由於鏈條的廣泛應用,我們將在未來的另一篇部落格文章中專門介紹除了最簡單的順序鏈條以外的其他鏈條應用。

Output(輸出)

我們流程的最後一個部分是輸出。有時候,我們想要將LLM的輸出轉換成特定的格式,以便未來使用。例如,在我們的位置提取器示例中,我在提示中指定了以下的輸出格式:

# prompt
"""
The output is a Python dictionary that contains the keys of
'start' (a string), 'pitstops' (a list of strings), and 'end' (a string).
"""
# output
# trip = '{
# "start": "New York City",
# "pitstops": [],
# "end": "Seattle"
#}'

這樣可以讓LLM以正確的格式返回回應。然而,如果溫度較高,回應可能是不正確的。例如,它使用了單引號而不是雙引號:

# output
# trip = "{
# 'start': "New York City",
# 'pitstops': [],
# 'end': "Seattle"
#}"

如果我們使用JSON解碼器對這個輸出進行解碼,這將是個問題,因為JSON解碼器只接受雙引號作為鍵。為了解決這個問題,我們可以重新撰寫提示,或者使用LangChain中的OutputFixingParser類。

OutputFixingParser使用具有特定指示的另一個LLM和一個簡單的輸出解析器來指定解析器的結構。

# OutputFixingParser
from langchain.output_parsers import OutputFixingParser

new_parser = OutputFixingParser.from_llm(parser=parser, llm=ChatOpenAI())

有關輸出解析器的更多信息,請自行參閱LangChain的文檔(近期LangChain更新中,因此文檔有另外新增,請有興趣讀者自行參閱)。

結論

透過對ChatGPT的單一請求,我們介紹了LangChain的基本功能以及它如何以更具程式設計風格的方式處理相同的請求。此外,我們還可以看到LangChain的高度自定義性和廣泛的應用潛力,僅僅從一個簡單的請求就能體現出來。不過,LangChain還提供了更多功能!

這只是LangChain的基礎知識,它還具有更強大的功能,例如AutoGPT和問答系統。然而,深入內部,所有這些功能都嵌入在本部落格文章介紹的”chain”結構中,所以這篇部落格文章將為我們未來的部落格文章做好準備。

希望在閱讀完這篇文章後,如果有人問你LangChain是如何工作的,你可以自信地告訴他們:”LangChain就像一輛’汽車’,能夠實現你的目標。”

👋 深入瞭解學習資源、討論和最新消息,加入我們的Line群:https://supr.link/ijpL7

--

--

Runa's story
AI 精選指南

Here’re my life stories. Including startup, exercise, and daily record.