在AI應用開發領域,將大模型語言(LLM)整合到實際應用中已成為一項關鍵技能。LangChain作為一個輕量級框架,提供了一套完整的工具和元件,讓開發者能夠輕鬆地將LLM功能融入到應用程式中。這篇文章將探討LangChain的核心元件,並透過實際範例展示如何有效利用這個強大的框架。

LangChain核心概念與發展現況

LangChain是一個專為簡化LLM整合與協調而設計的框架。雖然最初主要支援Python,但近期已擴充套件到JavaScript和TypeScript。在使用LangChain之前,有幾個重要的注意事項:

  • 不同的LangChain套件版本略有差異,但所有版本都由維護者高頻率發布,並對重大變更保持清晰的溝通策略
  • 某些元件已移至experimental套件,表示它們更適合實驗性質的使用
  • 部分第三方整合已移至community套件

值得一提的是,LangChain提供了一個名為LangChain Expression Language (LCEL)的介面,可以加速開發並利用與LangChain開發堆積積疊的原生整合,不過在這篇文章中我們不會探討LCEL。

LangChain的核心架構與元件

LangChain框架主要由以下幾個關鍵元件構成:

  1. 模型與提示範本
  2. 資料連線
  3. 記憶機制
  4. 鏈(Chains)
  5. 代理(Agents)

這些元件共同構成了一個完整的生態系統,使開發者能夠建立複雜的LLM應用。接下來,讓我們逐一深入瞭解這些元件。

模型與提示(Models and Prompts)

LangChain提供了50多種與第三方供應商和平台的整合,包括OpenAI、Azure OpenAI、Databricks和MosaicML,以及與Hugging Face Hub和開放原始碼LLM的整合。

使用OpenAI模型範例

以下是如何使用OpenAI GPT-3模型的簡單範例:

from langchain.llms import OpenAI

llm = OpenAI(openai_api_key="your-api-key")
print(llm('tell me a joke'))

這段程式碼展示了LangChain與OpenAI模型的基本整合。首先從langchain.llms模組匯入OpenAI類別,然後建立一個OpenAI例項並傳入API金鑰。最後,直接呼叫這個例項並傳入提示文字「tell me a joke」,它會回傳模型生成的回應。預設情況下,這裡使用的是gpt-3.5-turbo-instruct模型,但你可以透過引數指定其他模型。

執行上述程式碼會得到類別似這樣的輸出:

Q: What did one plate say to the other plate? A: Dinner's on me!

需要注意的是,每次執行LLM範例時,由於模型本身的隨機性,輸出都會有所不同。如果想減少輸出的變異性,可以透過調整temperature超引數使模型更具「確定性」。這個引數範圍從0(確定性)到1(隨機性)。

提示範本與範例選擇器

在LLM應用開發中,提示工程(prompt engineering)是一個關鍵環節。LangChain提供了兩個主要元件來支援提示設計:

提示範本(Prompt Templates)

提示範本定義瞭如何為語言模型生成提示。它可以包含變數、佔位符、字首、字尾和其他可根據資料和任務自定義的元素。

例如,假設你想使用語言模型進行語言翻譯,可以使用如下範本:

from langchain import PromptTemplate

template = """Sentence: {sentence}
Translation in {language}:"""

prompt = PromptTemplate(template=template, input_variables=["sentence", "language"])
print(prompt.format(sentence="the cat is on the table", language="spanish"))

這段程式碼展示瞭如何建立和使用提示範本。我們首先定義一個字元串範本,其中包含兩個變數:{sentence}{language}。然後建立一個PromptTemplate例項,並指定範本和輸入變數。最後,透過format方法填充這些變數並列印結果。這種方法的優勢在於可以輕鬆地重複使用相同的範本結構,只需更改輸入變數即可。

輸出結果為:

Sentence: the cat is on the table Translation in spanish:

值得注意的是,提示範本通常對LLM型別不敏感,能適用於完成模型(completion model)和聊天模型(chat model)。

完成模型與聊天模型的區別

  • 完成模型:接收文字輸入並生成文字輸出(完成)。它嘗試以連貫與相關的方式繼續提示,根據其訓練的任務和資料。例如,完成模型可以生成摘要、翻譯、故事、程式碼等。

  • 聊天模型:是一種特殊的完成模型,設計用於生成對話回應。聊天模型接收訊息列表作為輸入,每條訊息都有角色(系統、使用者或助手)和內容。聊天模型嘗試根據前面的訊息和系統指令為助手角色生成新訊息。

主要區別在於完成模型期望單一文字輸入作為提示,而聊天模型期望一個訊息列表作為輸入。

範例選擇器(Example Selector)

範例選擇器是LangChain中允許你選擇在提示中包含哪些範例的元件。這個概念與我們之前討論的少樣本學習(few-shot learning)相關。範例是輸入和輸出的配對,用於展示任務和輸出格式,如下所示:

{"prompt": "<prompt text>", "completion": "<ideal generated text>"}

LangChain提供了一個名為BaseExampleSelector的範例選擇器類別,你可以匯入並根據需要修改。

資料連線(Data Connections)

資料連線是指將使用者特定資料整合到LLM應用中所需的構建塊。這個過程通常包括五個主要步驟:

  1. 載入資料
  2. 拆分資料
  3. 儲存資料
  4. 檢索資料
  5. 傳遞結果給LLM

LangChain提供了多種工具來支援這個流程:

檔案載入器(Document Loaders)

檔案載入器負責從不同來源載入檔案,如CSV、檔案目錄、HTML、JSON、Markdown和PDF。它們提供一個.load方法,用於從設定的來源載入資料作為檔案。輸出是一個Document物件,包含一段文字和相關的中繼資料。

以下是載入CSV檔案的範例:

from langchain.document_loaders.csv_loader import CSVLoader

loader = CSVLoader(file_path='sample.csv')
data = loader.load()
print(data)

這段程式碼演示瞭如何使用CSVLoader載入CSV檔案。首先匯入CSVLoader類別,然後建立一個例項並指定CSV檔案路徑。呼叫load方法後,它會回傳一個Document物件列表,每個物件包含從CSV行提取的內容以及中繼資料(如源檔案和行號)。

輸出結果類別似於:

[Document(page_content='Name: John\nAge: 25\nCity: New York', metadata={'source': 'sample.csv', 'row': 0}), Document(page_content='Name: Emily\nAge: 28\nCity: Los Angeles', metadata={'source': 'sample.csv', 'row': 1}), Document(page_content='Name: Michael\nAge: 22\nCity: Chicago', metadata={'source': 'sample.csv', 'row': 2})]

檔案轉換器(Document Transformers)

匯入檔案後,通常需要修改它們以更好地滿足需求。一個基本例子是將長檔案分解成更小的塊,以適應模型的上下文視窗。LangChain提供了各種預建的檔案轉換器,稱為文字分割器(text splitters)。

文字分割器的目標是將檔案分成語義相關的塊,避免在不相關的地方截斷,這樣我們就不會丟失重要的上下文訊息。分割文字的常見方法是根據固定大小的塊或根據特定分隔符(如段落或句子)。

LangChain的文字分割策略主要考慮以下因素:

  • 塊大小(通常以字元數或標記數測量)
  • 塊重疊(確保上下文連續性)
  • 分割方法(根據字元、標記、句子等)

記憶機制與對話管理

在構建對話式應用時,維護對話歷史是至關重要的。LangChain提供了多種記憶型別來處理這一需求:

  1. 對話記憶:儲存對話的來回交流
  2. 實體記憶:記住特定實體的訊息
  3. 摘要記憶:儲存對話的摘要版本

這些記憶機制使LLM能夠理解對話上下文,並生成連貫的回應。

鏈(Chains)與組合能力

LangChain的一個核心優勢是能夠將不同元件組合成「鏈」。鏈允許你將多個操作序列化,例如:

  1. 接收使用者輸入
  2. 格式化為提示
  3. 傳遞給LLM
  4. 解析輸出
  5. 回傳結果

這種組合能力使開發者能夠建立複雜的工作流程,同時保持程式碼的模組化和可維護性。

代理(Agents):自主決策的LLM

LangChain的代理功能允許LLM做出決策,選擇使用哪些工具,並根據結果採取行動。這使LLM能夠處理更複雜的任務,如:

  • 搜尋訊息
  • 執行計算
  • 與API互動
  • 進行推理

代理本質上是一個決策引擎,能夠根據使用者請求選擇最合適的工具和行動順序。

實際應用與最佳實踐

在實際應用LangChain時,以下是一些最佳實踐:

  1. 適當選擇模型:根據任務需求選擇合適的LLM,同時考慮成本和效能的平衡
  2. 最佳化提示範本:花時間設計有效的提示範本,這常是提高輸出品質的關鍵
  3. 合理分割文字:確保文字分割策略保留語義連貫性
  4. 實施有效的記憶策略:特別是對長對話,選擇合適的記憶機制至關重要
  5. 逐步構建鏈:從簡單鏈開始,逐步增加複雜性,確保每個環節都按預期工作

透過LangChain,開發者能夠將強大的LLM功能無縫整合到應用程式中,同時保持架構的靈活性和可擴充套件性。隨著LLM技術的不斷發展,LangChain等框架將繼續提供更多工具和抽象,使AI應用開發更加高效和直觀。

在構建LLM驅動的應用時,理解這些核心元件及其互動方式是成功的關鍵。無論是建立聊天機器人、知識函式庫查詢系統還是內容生成工具,LangChain都提供了必要的構建塊,使開發過程更加流暢和高效。

在應用程式中嵌入大模型語言的實用技術

將大模型語言(LLM)整合到應用程式中已成為現代AI應用開發的核心技術。這項技術允許我們建立能夠理解、處理和生成人類語言的人工智慧系統。在這篇文章中,玄貓將帶領大家深入瞭解如何有效地在應用程式中嵌入LLM,從文書處理到向量搜尋的完整實作流程。

文書處理與分割:RAG系統的基礎

在構建檢索增強生成(RAG)系統時,第一步是將長文字分割成較小的片段。文字分割器(Text Splitter)是實作這一目標的關鍵工具,它允許我們根據特定需求將檔案拆分為適當大小的區塊。

LangChain提供了多種文字分割器,讓我們可以靈活選擇如何分割文字(例如按字元、標題、token等)以及如何測量區塊長度(例如字元數)。

實作文字分割

讓我們使用RecursiveCharacterTextSplitter模組來分割檔案,這個模組在字元層面上操作。以下是一個實際的例子,我們將處理一個關於山脈的文字檔案:

with open('mountain.txt') as f:
    mountain = f.read()

from langchain.text_splitter import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size = 100,  # 每個區塊的字元數
    chunk_overlap = 20,  # 相鄰區塊間重疊的字元數
    length_function = len  # 用於測量字元數的函式
)

texts = text_splitter.create_documents([mountain])
print(texts[0])
print(texts[1])
print(texts[2])

這段程式碼展示瞭如何使用LangChain的RecursiveCharacterTextSplitter來處理文字檔案。關鍵引數有:

  • chunk_size:定義每個文字區塊的大小(這裡設為100個字元)
  • chunk_overlap:指定相鄰區塊間的重疊部分(20個字元),這有助於保持上下文連貫性
  • length_function:用來計算文字長度的函式,這裡使用Python內建的len

執行後,程式會輸出前三個生成的文字區塊,每個區塊包含原文的一小部分,並且相鄰區塊之間有20個字元的重疊。輸出結果顯示:

page_content=" Amidst the serene landscape, towering mountains stand as majestic guardians of nature's beauty." metadata={}
page_content='The crisp mountain air carries whispers of tranquility, while the rustling leaves compose a' metadata={}

文字分割是RAG系統的基礎步驟,合理的分割策略能夠保證檢索結果的相關性和準確性。在實際應用中,需要根據文字特性和應用需求調整分割引數。

文字嵌入模型:將文字轉換為向量

嵌入(Embedding)是將文字轉換為數值向量的過程,這是將非引數知識整合到LLM中的關鍵步驟。一旦適當地儲存在向量資料函式庫中,這些嵌入向量就成為我們可以測量使用者查詢距離的非引數知識基礎。

LangChain提供了Embedding類別,主要包含兩個模組,分別用於嵌入非引數知識(多個輸入文字)和使用者查詢(單個輸入文字)。

使用OpenAI嵌入模型

以下是使用OpenAI的text-embedding-ada-002模型生成文字嵌入的例子:

from langchain.embeddings import OpenAIEmbeddings
from dotenv import load_dotenv
import os

load_dotenv()
# 確保環境變數中有API金鑰
os.environ["OPENAI_API_KEY"]

embeddings_model = OpenAIEmbeddings(model='text-embedding-ada-002')

# 嵌入多個檔案
embeddings = embeddings_model.embed_documents(
    [
        "Good morning!",
        "Oh, hello!",
        "I want to report an accident",
        "Sorry to hear that. May I ask your name?",
        "Sure, Mario Rossi."
    ]
)

print("Embed documents:")
print(f"Number of vector: {len(embeddings)}; Dimension of each vector: {len(embeddings[0])}")

# 嵌入單個查詢
embedded_query = embeddings_model.embed_query("What was the name mentioned in the conversation?")

print("Embed query:")
print(f"Dimension of the vector: {len(embedded_query)}")
print(f"Sample of the first 5 elements of the vector: {embedded_query[:5]}")

這段程式碼示範瞭如何使用OpenAI的嵌入模型將文字轉換為向量表示:

  1. 首先匯入必要的函式庫並載入環境變數(API金鑰)
  2. 建立一個OpenAI嵌入模型例項,使用text-embedding-ada-002模型
  3. 使用embed_documents方法將多個對話文字轉換為向量
  4. 使用embed_query方法將使用者查詢轉換為向量
  5. 輸出向量的維度和部分內容以驗證結果

輸出結果顯示:

  • 檔案嵌入生成了5個向量(對應5個輸入文字),每個向量的維度為1536
  • 查詢嵌入生成了一個1536維的向量,並展示了前五個元素的值

這些嵌入向量捕捉了文字的語義訊息,使我們能夠計算不同文字之間的相似度。1536維是OpenAI的text-embedding-ada-002模型的標準輸出維度,每個維度代表文字的某種語義特徵。

向量儲存:管理和檢索嵌入向量

向量儲存(Vector Store或VectorDB)是一種能夠儲存並搜尋非結構化資料(如文字、影像、音訊或影片)的資料函式庫,它透過使用嵌入向量實作這一功能。向量儲存能夠執行快速與準確的相似度搜尋,找到與給定查詢最相關的資料。

相似度測量

相似度是衡量兩個向量在向量空間中的接近或相關程度的指標。在LLM的背景下,向量是捕捉句子、單詞或檔案語義含義的數值表示,而向量之間的距離應該代表它們的語義相似性。

在使用LLM時,餘弦相似度(Cosine Similarity)是最流行的相似度測量方法之一。它是多維空間中兩個向量之間角度的餘弦值,計算為向量的點積除以它們長度的乘積。餘弦相似度對尺度和位置不敏感,範圍從-1到1,其中1表示相同,0表示正交,-1表示相反。

向量儲存的典型流程

使用向量儲存的典型流程包括:

  1. 將檔案轉換為嵌入向量並儲存
  2. 將使用者查詢轉換為嵌入向量
  3. 使用相似度計算找到最相關的檔案
  4. 將相關檔案作為上下文提供給LLM

LangChain提供了40多種與第三方向量儲存的整合,例如Facebook AI Similarity Search (FAISS)、Elasticsearch、MongoDB Atlas和Azure Search等。

使用FAISS向量儲存的例項

以下是一個使用FAISS向量儲存的例子,FAISS是由Meta AI研究開發的,用於高效的相似度搜尋和密集向量的聚類別:

from langchain.document_loaders import TextLoader
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import FAISS
from dotenv import load_dotenv
import os

load_dotenv()
os.environ["OPENAI_API_KEY"]

# 載入檔案,將其分割成區塊,嵌入每個區塊並載入到向量儲存中
raw_documents = TextLoader('dialogue.txt').load()
text_splitter = CharacterTextSplitter(
    chunk_size=50, 
    chunk_overlap=0,
    separator="\n",
)
documents = text_splitter.split_documents(raw_documents)
db = FAISS.from_documents(documents, OpenAIEmbeddings())

# 嵌入使用者查詢並搜尋最相似的文字區塊
query = "What is the reason for calling?"
docs = db.similarity_search(query)
print(docs[0].page_content)

這段程式碼展示了一個完整的向量搜尋流程:

  1. 首先,使用TextLoader載入文字檔案(對話內容)
  2. 使用CharacterTextSplitter將文字分割成較小的區塊,每個區塊50個字元,不設重疊
  3. 使用OpenAI的嵌入模型將每個區塊轉換為向量表示
  4. 將這些向量儲存到FAISS向量資料函式庫中
  5. 嵌入使用者的查詢「What is the reason for calling?」(打電話的原因是什麼?)
  6. 使用similarity_search方法找到與查詢最相似的文字區塊
  7. 輸出最相似的文字區塊內容

執行結果輸出:I want to report an accident(我想報告一個事故)

這個結果表明系統成功地從對話文字中找到了與使用者查詢最相關的部分。在實際應用中,這個結果會作為上下文提供給LLM,以生成一個對話式的回應。

這種方法的優勢在於它能夠從大量文字中精確找到與使用者問題最相關的訊息,而不需要LLM記住所有內容,大提高了回應的準確性和相關性。

檢索器:靈活的檔案檢索方法

檢索器(Retriever)是LangChain中的一個元件,可以根據非結構化查詢(如自然語言問題或關鍵字)回傳相關檔案。檢索器本身不需要儲存檔案,只需要能從某個來源檢索檔案。檢索器可以使用不同的方法來查詢相關檔案,例如關鍵字比對、語義搜尋或排序演算法。

檢索器與向量儲存的區別

檢索器比向量儲存更通用和靈活:

  1. 方法多樣性:檢索器可以使用任何方法查詢相關檔案,而向量儲存依賴於嵌入和相似度量
  2. 資料來源:檢索器可以使用不同的檔案來源(如網頁、資料函式庫或檔案),而向量儲存需要自己儲存資料
  3. 功能整合:向量儲存可以作為檢索器的後端,檢索器可以利用向量儲存執行相似度搜尋

在LangChain中,根據向量儲存的檢索器是主要型別之一,它使用向量儲存對嵌入資料執行相似度搜尋,並回傳最相關的檔案。

實際應用場景與最佳實踐

將LLM嵌入應用程式中有許多實際應用場景,以下是一些常見的使用案例和最佳實踐:

客戶服務人工智慧助手

透過將公司的FAQ、產品手冊和服務檔案轉換為向量儲存,可以構建一個能夠準確回答客戶問題的人工智慧助手。系統會找到與使用者問題最相關的檔案部分,然後使用LLM生成自然與有幫助的回應。

知識管理系統

對於擁有大量內部檔案的組織,可以實作一個人工智慧知識管理系統,使員工能夠用自然語言查詢公司知識函式庫。系統會回傳最相關的訊息,大提高訊息檢索效率。

內容推薦引擎

透過嵌入使用者閱讀的內容和可用的文章函式庫,可以構建一個根據語義相似度的內容推薦引擎,為使用者提供真正相關的推薦,而不僅是根據標籤或類別。

最佳實踐

  1. 適當的文字分割策略:根據內容型別選擇合適的分割方法和

LangChain進階應用:從檢索到記憶的完整實作

在構建根據大模型語言(LLM)的應用時,僅呼叫模型API是不夠的。真正強大的應用需要具備檢索資料、保持對話記憶、處理複雜邏輯等能力。LangChain框架提供了豐富的模組來實作這些功能,讓開發者能夠構建出更人工智慧、更實用的AI應用。

檢索器:人工智慧查詢的核心引擎

在前面我們已經瞭解了向量儲存的基本概念,現在讓我們看如何在向量儲存之上建立檢索系統。檢索器(Retriever)是LangChain中連線資料與模型的重要橋樑,它能根據使用者的查詢從資料函式庫中找出最相關的內容。

以下是一個使用FAISS向量儲存實作問答功能的例子:

from langchain.chains import RetrievalQA
from langchain.llms import OpenAI

# 將向量資料函式庫轉換為檢索器
retriever = db.as_retriever()

# 建立問答鏈
qa = RetrievalQA.from_chain_type(
    llm=OpenAI(), 
    chain_type="stuff",  # 使用stuff方法將檢索到的檔案合併
    retriever=retriever
)

# 執行查詢
query = "What was the reason of the call?"
result = qa.run(query)

上面的程式碼展示了LangChain的核心優勢 - 元件化設計。首先,我們將之前初始化的FAISS向量資料函式庫轉換為檢索器(db.as_retriever())。這個檢索器負責根據查詢從向量儲存中找出最相關的檔案片段。

接著,我們建立了一個RetrievalQA鏈,它將檢索器與LLM(這裡使用OpenAI)組合起來。chain_type="stuff"引數指定了如何處理檢索到的多個檔案 - 在這種情況下,系統會將所有檢索到的檔案合併後一次性傳送給LLM處理。

執行查詢後,我們得到了答案:“The reason for the call was to report an accident."(通話的原因是報告一起事故)。整個過程中,LangChain自動處理了檔案檢索、上下文組合和LLM呼叫,大簡化了開發流程。

資料連線模組為LLM應用提供了豐富的整合選項和預建範本,讓資料流管理變得更加簡單。在接下來的章節中,我們將探索這些構建塊的具體應用,但現在,讓我們深入瞭解LangChain的另一個核心元件 - 記憶系統。