天天看點

一文搞懂LangChain

作者:大資料與人工智能分享

在日常生活中,我們通常緻力于建構端到端的應用程式。有許多自動機器學習平台和持續內建/持續傳遞(CI/CD)流水線可用于自動化我們的機器學習流程。我們還有像 Roboflow 和 Andrew N.G. 的 Landing AI 這樣的工具,可以自動化或建立端到端的計算機視覺應用程式。

如果我們想要借助 OpenAI 或 Hugging Face 建立基于大語言模型的應用程式,以前我們可能需要手動完成。現在,為了實作相同的目标,我們有兩個最著名的庫,即 Haystack 和 LangChain,它們可以幫助我們建立基于大語言模型的端到端應用程式或流程。

一文搞懂LangChain

下面讓我們深入地了解一下 LangChain。

一文搞懂LangChain

什麼是 LangChain?

一文搞懂LangChain

LangChain 是一種創新架構,正在徹底改變我們開發由語言模型驅動的應用程式的方式。通過引入先進的原理,LangChain 正在重新定義傳統 API 所能實作的限制。此外,LangChain 應用程式具有智能代理的特性,使語言模型能夠與環境進行互動和自适應。

LangChain 由多個子產品組成。正如其名稱所示,LangChain 的主要目的是将這些子產品進行鍊式連接配接。這意味着我們可以将每個子產品都串聯在一起,并使用這個鍊式結構一次性調用所有子產品。

這些子產品由以下部分組成:

Model

正如介紹中所讨論的那樣,模型主要涵蓋大語言模型(LLM)。大語言模型是指具有大量參數并在大規模無标簽文本上進行訓練的神經網絡模型。科技巨頭們推出了各種各樣的大型語言模型,比如:

  • 谷歌的 BERT
  • OpenAI 的 GPT-3
  • 谷歌 LaMDA
  • 谷歌 PaLM
  • Meta AI 的 LLaMA
  • OpenAI 的 GPT-4
  • ……

借助 LangChain,與大語言模型的互動變得更加便捷。LangChain 提供的接口和功能有助于将 LLM 的強大能力輕松內建到你的工作應用程式中。LangChain 利用 asyncio 庫為 LLM 提供異步支援。

對于需要同時并發調用多個 LLM 的網絡綁定場景,LangChain 還提供了異步支援。通過釋放處理請求的線程,伺服器可以将其配置設定給其他任務,直到響應準備就緒,進而最大限度地提高資源使用率。

目前,LangChain 支援 OpenAI、PromptLayerOpenAI、ChatOpenAI 和 Anthropic 等模型的異步支援,但在未來的計劃中将擴充對其他 LLM 的異步支援。你可以使用 agenerate 方法來異步調用 OpenAI LLM。此外,你還可以編寫自定義的 LLM 包裝器,而不僅限于 LangChain 所支援的模型。

我在我的應用程式中使用了 OpenAI,并主要使用了 Davinci、Babbage、Curie 和 Ada 模型來解決我的問題。每個模型都有其自身的優點、令牌使用量和使用案例。

更多關于這些模型的資訊請閱讀:

https://subscription.packtpub.com/book/data/9781800563193/2/ch02lvl1sec07/introducing-davinci-babbage-curie-and-ada

案例 1:

# Importing modules 
from langchain.llms import OpenAI

#Here we are using text-ada-001 but you can change it 
llm = OpenAI(model_name="text-ada-001", n=2, best_of=2)

#Ask anything
llm("Tell me a joke")
           

輸出 1:

'\n\nWhy did the chicken cross the road?\n\nTo get to the other side.'           

案例 2:

llm_result = llm.generate(["Tell me a poem"]*15)           

輸出 2:

[Generation(text="\n\nWhat if love neverspeech\n\nWhat if love never ended\n\nWhat if love was only a feeling\n\nI'll never know this love\n\nIt's not a feeling\n\nBut it's what we have for each other\n\nWe just know that love is something strong\n\nAnd we can't help but be happy\n\nWe just feel what love is for us\n\nAnd we love each other with all our heart\n\nWe just don't know how\n\nHow it will go\n\nBut we know that love is something strong\n\nAnd we'll always have each other\n\nIn our lives."),  
 Generation(text='\n\nOnce upon a time\n\nThere was a love so pure and true\n\nIt lasted for centuries\n\nAnd never became stale or dry\n\nIt was moving and alive\n\nAnd the heart of the love-ick\n\nIs still beating strong and true.')]
           

Prompt

衆所周知,提示(prompt)是我們向系統提供的輸入,以便根據我們的使用案例對答案進行精确或特定的調整。許多時候,我們希望得到的不僅僅是文本,還需要更結構化的資訊。基于對比預訓練和零樣本學習的許多新的目标檢測和分類算法都将提示作為有效的輸入來進行結果預測。舉例來說,OpenAI 的 CLIP 和 META 的 Grounding DINO 都使用提示作為預測的輸入。

在 LangChain 中,我們可以根據需要設定提示模闆,并将其與主鍊相連接配接以進行輸出預測。此外,LangChain 還提供了輸出解析器的功能,用于進一步精煉結果。輸出解析器的作用是(1)指導模型輸出的格式化方式,和(2)将輸出解析為所需的格式(包括必要時的重試)。

在 LangChain 中,我們可以提供提示模闆作為輸入。模闆指的是我們希望獲得答案的具體格式或藍圖。LangChain 提供了預先設計好的提示模闆,可以用于生成不同類型任務的提示。然而,在某些情況下,預設的模闆可能無法滿足你的需求。在這種情況下,我們可以使用自定義的提示模闆。

案例:

from langchain import PromptTemplate
# This template will act as a blue print for prompt

template = """
I want you to act as a naming consultant for new companies.
What is a good name for a company that makes {product}?
"""

prompt = PromptTemplate(
    input_variables=["product"],
    template=template,
)
prompt.format(product="colorful socks")
# -> I want you to act as a naming consultant for new companies.
# -> What is a good name for a company that makes colorful socks?
           

Memory

在 LangChain 中,鍊式和代理預設以無狀态模式運作,即它們獨立處理每個傳入的查詢。然而,在某些應用程式(如聊天機器人)中,保留先前的互動記錄對于短期和長期都非常重要。這時就需要引入 “記憶體” 的概念。

LangChain 提供兩種形式的記憶體元件。首先,LangChain 提供了輔助工具,用于管理和操作先前的聊天消息,這些工具設計成子產品化的,無論用例如何,都能很好地使用。其次,LangChain 提供了一種簡單的方法将這些工具內建到鍊式結構中,使其非常靈活和适應各種情況。

案例:

from langchain.memory import  ChatMessageHistory  
  
history  = ChatMessageHistory()  
history.add_user_message("hi!")  
  
history.add_ai_message("whats up?")  
history.messages           

輸出:

[HumanMessage(content='hi!', additional_kwargs={}),  
 AIMessage(content='whats up?', additional_kwargs={})]
           

Chain

鍊式(Chain)提供了将各種元件合并成一個統一應用程式的方式。例如,可以建立一個鍊式,它接收使用者的輸入,并使用 PromptTemplate 對其進行格式化,然後将格式化後的回複傳遞給 LLM(大語言模型)。通過将多個鍊式與其他元件內建,可以生成更複雜的鍊式結構。

LLMChain 被認為是查詢 LLM 對象最常用的方法之一。它根據提示模闆将提供的輸入鍵值和記憶體鍵值(如果存在)進行格式化,然後将格式化後的字元串發送給 LLM,LLM 生成并傳回輸出結果。

在調用語言模型後,可以按照一系列步驟進行操作,可以進行多個模型調用的序列。這種做法特别有價值,當希望将一個調用的輸出作為另一個調用的輸入時。在這個鍊式序列中,每個鍊式都有一個輸入和一個輸出,一個步驟的輸出作為下一個步驟的輸入。

#Here we are chaining everything
from langchain.chat_models import ChatOpenAI
from langchain.prompts.chat import (
    ChatPromptTemplate,
    HumanMessagePromptTemplate,
)
human_message_prompt = HumanMessagePromptTemplate(
        prompt=PromptTemplate(
            template="What is a good name for a company that makes {product}?",
            input_variables=["product"],
        )
    )
chat_prompt_template = ChatPromptTemplate.from_messages([human_message_prompt])
chat = ChatOpenAI(temperature=0.9)
# Temperature is about randomness in answer more the temp, random the answer
#Final Chain

chain = LLMChain(llm=chat, prompt=chat_prompt_template)
print(chain.run("colorful socks"))
           

Agent

某些應用可能需要不僅預定的 LLM(大型語言模型)/其他工具調用順序,還可能需要根據使用者的輸入确定不确定的調用順序。這種情況下涉及到的序列包括一個 “代理(Agent)”,該代理可以通路多種工具。根據使用者的輸入,代理可能決定是否調用這些工具,并确定調用時的輸入。

根據文檔,代理的進階僞代碼大緻如下:

  1. 接收使用者輸入。
  2. 代理根據輸入決定是否使用某個工具,以及該工具的輸入應該是什麼。
  3. 調用該工具,并記錄觀測結果(即使用該工具和輸入調用後得到的輸出)。
  4. 将工具、工具輸入和觀測結果的曆史傳遞回代理,代理決定下一步應該采取的步驟。
  5. 重複上述步驟,直到代理決定不再需要使用工具,然後直接向使用者作出回應。

這個過程會一直重複,直到代理決定不再需要使用工具,然後直接回應使用者。

案例:

from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.agents import AgentType
from langchain.llms import OpenAI

llm = OpenAI(temperature=0)

tools = load_tools(["serpapi", "llm-math"], llm=llm)

agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True)

agent.run("Who is Leo DiCaprio's girlfriend? What is her current age raised to the 0.43 power?")
           

讓我們将所有内容總結在下面這張圖中。

一文搞懂LangChain

了解所有子產品和鍊式操作對于使用 LangChain 建構大語言模型的管道應用程式非常重要。這隻是對 LangChain 的簡單介紹。

一文搞懂LangChain

LangChain 的實際應用

一文搞懂LangChain

廢話少說,讓我們直接使用 LangChain 建構簡單的應用程式。其中最有趣的應用是在自定義資料上建立一個問答機器人。

免責聲明/警告:這段代碼僅用于展示應用程式的建構方式。我并不保證代碼的優化,并且根據具體的問題陳述,可能需要進行進一步改進。

開始導入子產品

導入 LangChain 和 OpenAI 用于大語言模型部分。如果你還沒有安裝它們,請先安裝。

#    IMPORTS
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.text_splitter import CharacterTextSplitter
from langchain.chains import ConversationalRetrievalChain
from langchain.vectorstores import ElasticVectorSearch, Pinecone, Weaviate, FAISS
from PyPDF2 import PdfReader
from langchain import OpenAI, VectorDBQA
from langchain.vectorstores import Chroma
from langchain.prompts import PromptTemplate
from langchain.chains import ConversationChain

from langchain.document_loaders import TextLoader
# from langchain import ConversationalRetrievalChain
from langchain.chains.question_answering import load_qa_chain
from langchain import LLMChain
# from langchain import retrievers
import langchain
from langchain.chains.conversation.memory import ConversationBufferMemory           

py2PDF 是用于讀取和處理 PDF 檔案的工具。此外,還有不同類型的記憶體,例如 ConversationBufferMemory 和 ConversationBufferWindowMemory,它們具有特定的功能。我将在最後一部分詳細介紹有關記憶體的内容。

設定環境

我想你知道如何擷取 OpenAI API 密鑰,但是我還是想說明一下:

  1. 前往 OpenAI API 頁面,
  2. 點選 “Create new secret key(建立新的密鑰) ”
  3. 那将是你的 API 密鑰。請将其粘貼在下方
import os  
os.environ["OPENAI_API_KEY"] = "sk-YOUR API KEY"
           

要使用哪個模型?Davinci、Babbage、Curie 還是 Ada?基于 GPT-3、基于 GPT-3.5、還是基于 GPT-4?關于模型有很多問題,所有模型都适用于不同的任務。有些模型價格較低,有些模型更準确。

為了簡單起見,我們将使用最經濟實惠的模型 “gpt-3.5-turbo”。溫度是一個參數,它影響答案的随機性。溫度值越高,我們得到的答案就越随機。

llm = ChatOpenAI(temperature=0,model_name="gpt-3.5-turbo")           

在這裡,你可以添加自己的資料。你可以使用任何格式,如 PDF、文本、文檔或 CSV。根據你的資料格式,你可以取消/注釋以下代碼。

# Custom data
from langchain.document_loaders import DirectoryLoader
pdf_loader = PdfReader(r'Your PDF location')

# excel_loader = DirectoryLoader('./Reports/', glob="**/*.txt")
# word_loader = DirectoryLoader('./Reports/', glob="**/*.docx")           

我們無法一次性添加所有資料。我們将資料分成塊并将其發送以建立資料 Embedding。

Embedding 是以數字向量或數組的形式表示的,它們捕捉了模型處理和生成的标記的實質和上下文資訊。這些嵌入是通過模型的參數或權重派生出來的,用于編碼和解碼輸入和輸出文本。

一文搞懂LangChain

這就是 Embedding 的建立方式。

簡單來說,在 LLM 中,Embedding 是将文本表示為數字向量的一種方式。這使得語言模型能夠了解單詞和短語的含義,并執行文本分類、摘要和翻譯等任務。

通俗地說,Embedding 是将單詞轉化為數字的一種方法。這是通過在大量文本語料庫上訓練機器學習模型來實作的。模型學會将每個單詞與一個唯一的數字向量相關聯。該向量代表單詞的含義,以及與其他單詞的關系。

一文搞懂LangChain

讓我們做與上圖所示完全相同的事情。

#Preprocessing of file

raw_text = ''
for i, page in enumerate(pdf_loader.pages):
    text = page.extract_text()
    if text:
        raw_text += text

# print(raw_text[:100])


text_splitter = CharacterTextSplitter(        
    separator = "\n",
    chunk_size = 1000,
    chunk_overlap  = 200,
    length_function = len,
)
texts = text_splitter.split_text(raw_text)           

在實際情況中,當使用者發起查詢時,将在向量存儲中進行搜尋,并檢索出最合适的索引,然後将其傳遞給 LLM。然後,LLM 會重新建構索引中的内容,以向使用者提供格式化的響應。

我建議進一步深入研究向量存儲和 Embedding 的概念,以增強你的了解。

embeddings = OpenAIEmbeddings()
# vectorstore = Chroma.from_documents(documents, embeddings)
vectorstore = FAISS.from_texts(texts, embeddings)           

嵌入向量直接存儲在一個向量資料庫中。有許多可用的向量資料庫,如 Pinecone、FAISS 等。在這裡,我們将使用 FAISS。

prompt_template = """Use the following pieces of context to answer the question at the end. If you don't know the answer, just say GTGTGTGTGTGTGTGTGTG, don't try to make up an answer.
{context}
Question: {question}
Helpful Answer:"""
QA_PROMPT = PromptTemplate(
    template=prompt_template, input_variables=['context',"question"]
)           

你可以使用自己的提示來優化查詢和答案。在編寫提示後,讓我們将其與最終的鍊條進行連結。

讓我們調用最後的鍊條,其中包括之前連結的所有内容。我們在這裡使用 ConversationalRetrievalChain。它可以幫助我們以人類的方式進行對話,并記住之前的聊天記錄。

qa = ConversationalRetrievalChain.from_llm(ChatOpenAI(temperature=0.8), vectorstore.as_retriever(),qa_prompt=QA_PROMPT)           

我們将使用簡單的 Gradio 來建立一個 Web 應用。你可以選擇使用 Streamlit 或其他前端技術。此外,還有許多免費的部署選項可供選擇,例如部署到 Hugging Face 或本地主機上,我們可以在稍後進行。

# Front end web app
import gradio as gr
with gr.Blocks() as demo:
    gr.Markdown("## Grounding DINO ChatBot")
    chatbot = gr.Chatbot()
    msg = gr.Textbox()
    clear = gr.Button("Clear")
    chat_history = []
           
def user(user_message, history)
        print("Type of use msg:",type(user_message))
        # Get response from QA chain
        response = qa({"question": user_message, "chat_history": history})
        # Append user message and response to chat history
        history.append((user_message, response["answer"]))
        print(history)
        return gr.update(value=""), history
    msg.submit(user, [msg, chatbot], [msg, chatbot], queue=False)
    clear.click(lambda: None, None, chatbot, queue=False)
    ############################################

if __name__ == "__main__":
    demo.launch(debug=True)           

這段代碼将在本地建立一個連結,你可以直接提出問題并檢視回答。同時,在你的內建開發環境(IDE)中,你将看到聊天曆史記錄的維護情況。

一文搞懂LangChain

LangChain 快照

這是一個簡單的介紹,展示了如何通過連接配接不同的子產品來建立最終的鍊條。通過對子產品和代碼進行調整,你可以實作許多不同的功能。我想說,玩耍是研究的最高形式!

一文搞懂LangChain

LangChain 的 Token 和模型

一文搞懂LangChain

Token

Token 可以被視為單詞的組成部分。在處理提示資訊之前,API 會将輸入拆分成 Token。Token 的切分位置不一定與單詞的開始或結束位置完全對應,還可能包括尾随的空格甚至子詞。

在自然語言進行中,我們通常會進行 Tokenizer 的操作,将段落拆分為句子或單詞。在這裡,我們也将句子和段落切分成由單詞組成的小塊。

一文搞懂LangChain

上圖顯示了如何将文本分割為 Token。不同顔色表示不同的 Token。一個經驗法則是,一個 Token 大約相當于常見英國文本中的 4 個字元。這意味着 100 個 Token 大約相當于 75 個單詞。

如果你想檢查特定文本的 Token 數量,可以直接在 OpenAI 的 Tokenizer 上進行檢查。

另一種計算 Token 數量的方法是使用 tiktoken 庫。

import tiktoken
#Write function to take string input and return number of tokens 
def num_tokens_from_string(string: str, encoding_name: str) -> int:
    """Returns the number of tokens in a text string."""
    encoding = tiktoken.encoding_for_model(encoding_name)
    num_tokens = len(encoding.encode(string))
    return num_tokens           

最後,使用上述函數:

prompt = []
for i in data:
    prompt.append((num_tokens_from_string(i['prompt'], "davinci")))
    
completion = []
for j in data:
    completion.append((num_tokens_from_string(j['completion'], "davinci")))
    
res_list = []
for i in range(0, len(prompt)):
    res_list.append(prompt[i] + completion[i])
    
no_of_final_token = 0
for i in res_list:
    no_of_final_token+=i
print("Number of final token",no_of_final_token)
           

輸出:

Number of final token 2094           

不同模型的選擇受到 Token 數量的影響。

首先,讓我們了解 OpenAI 提供的不同模型。在本部落格中,我專注于 OpenAI 模型。我們也可以使用 hugging faces 和 cohere AI 模型。

讓我們先了解基本模型。

Model

GPT 之是以強大,是因為它是在大規模資料集上進行訓練的。然而,強大的功能也伴随着一定的代價,是以 OpenAI 提供了多個可供選擇的模型,也被稱為引擎。

Davinci 是最大、功能最強大的引擎。它可以執行其他引擎可以執行的所有任務。Babbage 是次強大的引擎,它可以執行 Curie 和 Ada 能夠執行的任務。Ada 是功能最弱的引擎,但它性能最佳且價格最低。

随着 GPT 的不斷發展,還有許多不同版本的模型可供選擇。GPT 系列中大約有 50 多個模型可供使用。

一文搞懂LangChain

截圖自 OpenAI 官方模型頁面

是以,針對不同的用途,有不同的模型可供選擇,包括生成和編輯圖像、處理音頻和編碼等。對于文本處理和自然語言處理,我們希望選擇能夠準确執行任務的模型。在上圖中,我們可以看到三個可用的模型:

  • GPT-3
  • GPT-3.5
  • GPT-4

然而,目前我們無法直接使用 GPT-4,因為 GPT-4 目前僅限于有限的測試階段,隻有特定授權使用者才能使用。我們需要加入等待清單并等待授權。是以,現在我們隻剩下兩個選擇,即 GPT-3 和 GPT-3.5。

一文搞懂LangChain
一文搞懂LangChain

截圖自 OpenAI 官方模型頁面

上圖展示了 GPT-3 和 GPT-3.5 可用的模型。你可以看到這些模型都是基于 Davinci、Babbage、Curie 和 Ada 的不同版本。

如果你觀察上面的圖表,會發現有一個名為 “Max tokens” 的列。“Max tokens” 是 OpenAI 模型的一個參數,用于限制單個請求中可以生成的 Token 數量。該限制包括提示和完成的 Token 數量。

換句話說,如果你的提示占用了 1,000 個 Token,那麼你隻能生成最多 3,000 個 Token 的完成文本。此外,“Max tokens” 限制由 OpenAI 伺服器執行。如果你嘗試生成超過限制的文本,你的請求将被拒絕。

基于 GPT-3 的模型具有較低的 “Max tokens” 數值(2049),而基于 GPT-3.5 的模型具有較高的數值(4096)。是以,使用 GPT-3。5 模型可以處理更多的資料量。

接下來,讓我們來了解不同模型的定價。

一文搞懂LangChain
一文搞懂LangChain
一文搞懂LangChain

我們可以選擇基于 GPT-3.5 的 “gpt-3.5-turbo” 模型。

假設我有 5000 個單詞,并且我使用 “gpt-3.5-turbo” 模型,那麼:

5000 個單詞約等于 6667 個 Token。

現在,對于 1000 個 Token,我們需要 0.002 美元。

是以,對于 6667 個 Token,我們大約需要 0.0133 美元。

我們可以粗略計算需要多少使用量來進行處理。同時,疊代次數是會改變 Token 數量的一個參數,是以在計算中需要考慮這一點。

現在,你可以了解 Token 的重要性了吧。這就是為什麼我們必須進行非常幹淨和适當的預處理,以減少文檔中的噪聲,同時也減少處理 Token 的成本。是以,正确清理文本非常重要,例如消除噪聲。甚至删除多餘的空格也可以為你的 API 密鑰節省費用。

讓我們在一個記憶體圖中檢視所有模型。

一文搞懂LangChain
一文搞懂LangChain

總結

一文搞懂LangChain

Token 對于問題回答或其他 LLM 相關任務至關重要。如何以一種能夠使用更便宜的模型的方式預處理資料是真正的變革因素。模型的選擇取決于你希望做出的權衡。Davinci 系列将以更高的速度和準确性提供服務,但成本較高。而基于 GPT-3.5 Turbo 的模型将節省費用,但速度較慢。

作者:Chinmay Bhalerao

來源:分布式實驗室

原文:https://pub.towardsai.net/tokens-and-models-understanding-langchain-%EF%B8%8F-part-3-e471aececf19