目錄
- 前言
- 一、模型、提示和解析器(model、prompt、parsers)
- 二、儲存
- 三、模型鏈
- 四、基于文檔的問答
- 1.使用向量存儲查詢
- 2. 結合表征模型和向量存儲
- 使用檢索問答鏈回答問題
前言
在前面兩部分,我們分別學習了大語言模型的基礎使用準則(Prompt Engineering)與如何基于 ChatGPT 搭建一個完整的問答系統,對基于 LLM 開發應用程序有了一定了解。但是,雖然 LLM 提供了強大的能力,極大便利了應用程序的開發,個人開發者要基于 LLM 快速、便捷地開發一個完整的應用程序依然是一個具有較大工作量的任務。針對 LLM 開發,LangChain 應運而生。LangChain 是一套專為 LLM 開發打造的開源框架,實現了 LLM 多種強大能力的利用,提供了 Chain、Agent、Tool 等多種封裝工具,基于 LangChain 可以便捷開發應用程序,極大化發揮 LLM 潛能。目前,使用 LangChain 已經成為 LLM 開發的必備能力之一。
langchain官網文檔langchainchina
一、模型、提示和解析器(model、prompt、parsers)
langchain的主要功能:
- 提供了更結構化的模型調用方式,包括模型(Models)、提示(Prompts)和輸出解析器(Output Parsers)三個主要組件
- 提供了模板化的提示管理系統
- 提供了輸出格式的規范化處理
相比直接調用的優勢:
- 提示模板化:通過ChatPromptTemplate可以更好地管理和重用提示模板
- 結構化輸出:通過OutputParser可以將模型返回的字符串解析成Python對象(如dict),方便后續處理
- 統一接口:支持多種模型,便于切換和管理
- 內置常用場景:提供了許多預設的提示模板,如摘要、問答等
使用方法:
模型調用:
from langchain.chat_models import ChatOpenAI
chat = ChatOpenAI(temperature=0.0)
提示模板:
from langchain.prompts import ChatPromptTemplate
template = ChatPromptTemplate.from_template(template_string)
messages = template.format_messages(variables)
輸出解析:
from langchain.output_parsers import ResponseSchema, StructuredOutputParser
# 定義輸出schema
schemas = [ResponseSchema(...)]
# 創建解析器
parser = StructuredOutputParser.from_response_schemas(schemas)
# 解析輸出
result = parser.parse(response.content)
總的來說,langchain提供了一個更加工程化和結構化的方式來使用大語言模型,特別適合構建生產級別的應用。
二、儲存
在與語言模型交互時,你可能已經注意到一個關鍵問題:它們并不記憶你之前的交流內容,這在我們構建一些應用程序(如聊天機器人)的時候,帶來了很大的挑戰,使得對話似乎缺乏真正的連續性。因此,在本節中我們將介紹 LangChain 中的儲存模塊,即如何將先前的對話嵌入到語言模型中的,使其具有連續對話的能力。
當使用 LangChain 中的儲存(Memory)模塊時,它旨在保存、組織和跟蹤整個對話的歷史,從而為用戶和模型之間的交互提供連續的上下文。
LangChain 提供了多種儲存類型。其中,緩沖區儲存允許保留最近的聊天消息,摘要儲存則提供了對整個對話的摘要。實體儲存則允許在多輪對話中保留有關特定實體的信息。這些記憶組件都是模塊化的,可與其他組件組合使用,從而增強機器人的對話管理能力。儲存模塊可以通過簡單的 API 調用來訪問和更新,允許開發人員更輕松地實現對話歷史記錄的管理和維護。
langchain中四種主要的儲存模塊及其特點:
1. 對話緩存儲存 (ConversationBufferMemory)
- 功能:完整保存所有歷史對話內容
- 特點:
- 能記住完整對話歷史
- 可以通過save_context直接添加內容
- 隨著對話增加會占用越來越多內存
- 適用場景:需要完整對話歷史的短期對話
2. 對話緩存窗口儲存 (ConversationBufferWindowMemory)
- 功能:只保留最近k輪對話
- 特點:
- 通過設置k值控制保留對話輪數
- 節省內存空間
- 只能訪問最近k輪對話
- 適用場景:只需要最近幾輪對話上下文的場合
3. 對話令牌緩存儲存 (ConversationTokenBufferMemory)
- 功能:限制保存的token數量
- 特點:
- 可以設置最大token限制
- 超出限制時會裁剪早期對話
- 基于tiktoken計算token
- 適用場景:需要控制token使用量的場合
4. 對話摘要緩存儲存 (ConversationSummaryBufferMemory)
- 功能:自動總結歷史對話為摘要
- 特點:
- 使用LLM自動生成對話摘要
- 可以保留關鍵信息同時節省空間
- 摘要會隨新對話更新
- 適用場景:長對話場景,需要記住重要信息但不需要完整細節
例子:
# 首先需要安裝這些包:
# pip install langchain langchain-community openaifrom langchain_community.chat_models import ChatOpenAI
from langchain.chains import ConversationChain
from langchain.memory import (ConversationBufferMemory,ConversationBufferWindowMemory,ConversationTokenBufferMemory,ConversationSummaryBufferMemory
)# 設置OpenAI API密鑰
import os
os.environ["OPENAI_API_KEY"] = "你的API密鑰"# 初始化模型
llm = ChatOpenAI(temperature=0.0)# 1. 完整對話儲存
memory1 = ConversationBufferMemory()
chain1 = ConversationChain(llm=llm, memory=memory1)
chain1.predict(input="你好,我叫小明")
chain1.predict(input="我的名字是什么?")
print("\n完整儲存記憶:", memory1.load_memory_variables({}))# 2. 窗口儲存(只保留最后1輪對話)
memory2 = ConversationBufferWindowMemory(k=1)
chain2 = ConversationChain(llm=llm, memory=memory2)
chain2.predict(input="你好,我叫小紅")
chain2.predict(input="我的名字是什么?")
print("\n窗口儲存記憶:", memory2.load_memory_variables({}))# 3. Token儲存(限制token數量)
memory3 = ConversationTokenBufferMemory(llm=llm, max_token_limit=30)
chain3 = ConversationChain(llm=llm, memory=memory3)
chain3.predict(input="你好,我叫小華")
chain3.predict(input="我的名字是什么?")
print("\nToken儲存記憶:", memory3.load_memory_variables({}))# 4. 摘要儲存
memory4 = ConversationSummaryBufferMemory(llm=llm, max_token_limit=30)
chain4 = ConversationChain(llm=llm, memory=memory4)
chain4.predict(input="你好,我叫小李")
chain4.predict(input="我的名字是什么?")
print("\n摘要儲存記憶:", memory4.load_memory_variables({}))
這些儲存模塊的主要優勢是:
- 讓無狀態的LLM能夠"記住"歷史對話
- 提供不同的記憶管理策略
- 靈活控制內存使用和token消耗
- 支持更自然的多輪對話
使用時可以根據具體需求選擇合適的儲存模塊。
三、模型鏈
鏈(Chains)通常將大語言模型(LLM)與提示(Prompt)結合在一起,基于此,我們可以對文本或數據進行一系列操作。鏈(Chains)可以一次性接受多個輸入。例如,我們可以創建一個鏈,該鏈接受用戶輸入,使用提示模板對其進行格式化,然后將格式化的響應傳遞給 LLM 。我們可以通過將多個鏈組合在一起,或者通過將鏈與其他組件組合在一起來構建更復雜的鏈。
- LLMChain (大語言模型鏈)
from langchain_community.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.chains import LLMChain# 初始化
llm = ChatOpenAI(temperature=0.0)
prompt = ChatPromptTemplate.from_template("描述制造{product}的公司的最佳名稱是什么?")
chain = LLMChain(llm=llm, prompt=prompt)# 運行
result = chain.run("床單")
- SimpleSequentialChain (簡單順序鏈)
from langchain.chains import SimpleSequentialChain# 創建兩個子鏈
prompt1 = ChatPromptTemplate.from_template("為{product}公司起名")
chain1 = LLMChain(llm=llm, prompt=prompt1)prompt2 = ChatPromptTemplate.from_template("描述{company_name}公司")
chain2 = LLMChain(llm=llm, prompt=prompt2)# 組合成順序鏈
overall_chain = SimpleSequentialChain(chains=[chain1, chain2],verbose=True
)# 運行
result = overall_chain.run("床單")
- SequentialChain (順序鏈)
from langchain.chains import SequentialChain# 創建多個子鏈
chain1 = LLMChain(llm=llm, prompt=prompt1, output_key="company_name")
chain2 = LLMChain(llm=llm, prompt=prompt2, output_key="description")
chain3 = LLMChain(llm=llm, prompt=prompt3, output_key="slogan")# 組合成順序鏈
overall_chain = SequentialChain(chains=[chain1, chain2, chain3],input_variables=["product"],output_variables=["company_name", "description", "slogan"],verbose=True
)# 運行
result = overall_chain({"product": "床單"})
- MultiPromptChain (路由鏈)
from langchain.chains.router import MultiPromptChain
from langchain.chains.router.llm_router import LLMRouterChain# 創建目標鏈
destination_chains = {"數學": LLMChain(llm=llm, prompt=math_prompt),"物理": LLMChain(llm=llm, prompt=physics_prompt)
}# 創建默認鏈
default_chain = LLMChain(llm=llm, prompt=default_prompt)# 創建路由鏈
router_chain = LLMRouterChain.from_llm(llm, router_prompt)# 組合成路由鏈
chain = MultiPromptChain(router_chain=router_chain,destination_chains=destination_chains,default_chain=default_chain,verbose=True
)# 運行
result = chain.run("2+2等于多少?")
主要特點:
- LLMChain: 最基礎的鏈,將prompt和LLM組合
- SimpleSequentialChain: 單輸入單輸出的順序鏈
- SequentialChain: 多輸入多輸出的順序鏈
- MultiPromptChain: 根據輸入內容路由到不同的專門鏈
使用建議:
- 簡單任務用LLMChain
- 需要步驟處理用Sequential Chain
- 需要分類處理用MultiPromptChain
- 注意設置verbose=True可以查看鏈的執行過程
四、基于文檔的問答
使用大語言模型構建一個能夠回答關于給定文檔和文檔集合的問答系統是一種非常實用和有效的應用場景。與僅依賴模型預訓練知識不同,這種方法可以進一步整合用戶自有數據,實現更加個性化和專業的問答服務。例如,我們可以收集某公司的內部文檔、產品說明書等文字資料,導入問答系統中。然后用戶針對這些文檔提出問題時,系統可以先在文檔中檢索相關信息,再提供給語言模型生成答案。
這樣,語言模型不僅利用了自己的通用知識,還可以充分運用外部輸入文檔的專業信息來回答用戶問題,顯著提升答案的質量和適用性。構建這類基于外部文檔的問答系統,可以讓語言模型更好地服務于具體場景,而不是停留在通用層面。這種靈活應用語言模型的方法值得在實際使用中推廣。
基于文檔問答的這個過程,我們會涉及 LangChain 中的其他組件,比如:嵌入模型(Embedding Models)和向量儲存(Vector Stores)。
1.使用向量存儲查詢
from langchain.chains import RetrievalQA
from langchain.document_loaders import CSVLoader
from langchain.vectorstores import DocArrayInMemorySearch
from langchain.indexes import VectorstoreIndexCreator# 1. 加載數據
loader = CSVLoader(file_path='clothing_catalog.csv')# 2. 創建向量存儲索引
index = VectorstoreIndexCreator(vectorstore_cls=DocArrayInMemorySearch
).from_loaders([loader])# 3. 查詢
response = index.query("請列出防曬襯衫")
2. 結合表征模型和向量存儲
由于語言模型的上下文長度限制,直接處理長文檔具有困難。為實現對長文檔的問答,我們可以引入向量嵌入(Embeddings)和向量存儲(Vector Store)等技術:
首先,使用文本嵌入(Embeddings)算法對文檔進行向量化,使語義相似的文本片段具有接近的向量表示。其次,將向量化的文檔切分為小塊,存入向量數據庫,這個流程正是創建索引(index)的過程。向量數據庫對各文檔片段進行索引,支持快速檢索。這樣,當用戶提出問題時,可以先將問題轉換為向量,在數據庫中快速找到語義最相關的文檔片段。然后將這些文檔片段與問題一起傳遞給語言模型,生成回答。
通過嵌入向量化和索引技術,我們實現了對長文檔的切片檢索和問答。這種流程克服了語言模型的上下文限制,可以構建處理大規模文檔的問答系統。
from langchain.embeddings import OpenAIEmbeddings
from langchain.chat_models import ChatOpenAI# 1. 加載數據
loader = CSVLoader(file_path='clothing_catalog.csv')
docs = loader.load()# 2. 創建文本向量表征
embeddings = OpenAIEmbeddings()# 3. 創建向量存儲
db = DocArrayInMemorySearch.from_documents(docs, embeddings)# 4. 相似度搜索
query = "防曬襯衫"
similar_docs = db.similarity_search(query)# 5. 使用LLM處理搜索結果
llm = ChatOpenAI(temperature=0.0)
combined_docs = "".join([doc.page_content for doc in similar_docs])
response = llm.call_as_llm(f"{combined_docs}\n問題:{query}")
使用檢索問答鏈回答問題
通過LangChain創建一個檢索問答鏈,對檢索到的文檔進行問題回答。檢索問答鏈的輸入包含以下
llm: 語言模型,進行文本生成
chain_type: 傳入鏈類型
- stuff:將所有查詢得到的文檔組合成一個文檔傳入下一步。
- Map Reduce: 將所有塊與問題一起傳遞給語言模型,獲取回復,使用另一個語言模型調用將所有單獨的回復總結成最終答案,它可以在任意數量的文檔上運行。可以并行處理單個問題,同時也需要更多的調用。它將所有文檔視為獨立的
- Refine: 用于循環許多文檔,實際上它是用迭代實現的,它建立在先前文檔的答案之上,非常適合用于合并信息并隨時間逐步構建答案,由于依賴于先前調用的結果,因此它通常需要更長的時間,并且基本上需要與Map Reduce一樣多的調用
- Map Re-rank: 對每個文檔進行單個語言模型調用,要求它返回一個分數,選擇最高分,這依賴于語言模型知道分數應該是什么,需要告訴它,如果它與文檔相關,則應該是高分,并在那里精細調整說明,可以批量處理它們相對較快,但是更加昂貴
實際代碼示例:
from langchain.chains import RetrievalQA
from langchain.chat_models import ChatOpenAI
from langchain.document_loaders import CSVLoader
from langchain.vectorstores import DocArrayInMemorySearch
from langchain.embeddings import OpenAIEmbeddings
from langchain.chains.question_answering import load_qa_chain# 1. 準備數據和向量存儲
loader = CSVLoader('clothing_catalog.csv')
docs = loader.load()
embeddings = OpenAIEmbeddings()
db = DocArrayInMemorySearch.from_documents(docs, embeddings)# 初始化LLM
llm = ChatOpenAI(temperature=0)
- Stuff Chain (直接組合)
# 適合處理少量文檔,將所有文檔直接組合
stuff_chain = RetrievalQA.from_chain_type(llm=llm,chain_type="stuff",retriever=db.as_retriever(search_kwargs={"k": 3}),verbose=True
)# 使用示例
query = "列出所有防曬襯衫的特點"
result = stuff_chain.run(query)
print(f"Stuff Chain 結果:\n{result}")
- Map Reduce Chain (并行處理)
# 適合處理大量文檔,將文檔分塊并行處理
map_reduce_chain = RetrievalQA.from_chain_type(llm=llm,chain_type="map_reduce",retriever=db.as_retriever(search_kwargs={"k": 5}),verbose=True
)# 使用示例 - 適合需要匯總的查詢
query = "總結所有防曬服裝的共同特點"
result = map_reduce_chain.run(query)
print(f"Map Reduce Chain 結果:\n{result}")# Map Reduce 處理過程
"""
1. Map階段:每個文檔單獨處理
Doc1 -> LLM -> 結果1
Doc2 -> LLM -> 結果2
Doc3 -> LLM -> 結果32. Reduce階段:合并所有結果
[結果1, 結果2, 結果3] -> LLM -> 最終結果
"""
- Refine Chain (迭代優化)
# 適合需要漸進式構建答案的場景
refine_chain = RetrievalQA.from_chain_type(llm=llm,chain_type="refine",retriever=db.as_retriever(search_kwargs={"k": 4}),verbose=True
)# 使用示例 - 適合需要詳細分析的查詢
query = "分析防曬襯衫的材質和功能特點,并給出穿著建議"
result = refine_chain.run(query)
print(f"Refine Chain 結果:\n{result}")# Refine 處理過程
"""
初始答案 = LLM(Doc1)
改進答案1 = LLM(初始答案 + Doc2)
改進答案2 = LLM(改進答案1 + Doc3)
最終答案 = LLM(改進答案2 + Doc4)
"""
- Map Rerank Chain (評分排序)
# 適合需要根據相關性排序的場景
map_rerank_chain = RetrievalQA.from_chain_type(llm=llm,chain_type="map_rerank",retriever=db.as_retriever(search_kwargs={"k": 5}),verbose=True
)# 使用示例 - 適合需要最相關答案的查詢
query = "推薦最適合夏季穿著的防曬襯衫"
result = map_rerank_chain.run(query)
print(f"Map Rerank Chain 結果:\n{result}")# Map Rerank 處理過程
"""
1. 對每個文檔生成答案和相關性分數
Doc1 -> LLM -> (答案1, 分數1)
Doc2 -> LLM -> (答案2, 分數2)
Doc3 -> LLM -> (答案3, 分數3)2. 選擇分數最高的答案
返回 max(分數1, 分數2, 分數3) 對應的答案
"""
比較和使用建議:
def compare_chain_types(query):"""比較不同鏈類型的結果"""chains = {"stuff": stuff_chain,"map_reduce": map_reduce_chain,"refine": refine_chain,"map_rerank": map_rerank_chain}results = {}for name, chain in chains.items():print(f"\n使用 {name} chain 處理查詢...")results[name] = chain.run(query)return results# 測試不同類型的查詢
queries = {"簡單查詢": "有防曬襯衫嗎?","匯總查詢": "總結所有防曬服裝的特點","詳細分析": "分析不同防曬襯衫的優缺點","推薦查詢": "推薦最適合戶外活動的防曬衣物"
}for query_type, query in queries.items():print(f"\n\n處理 {query_type}: {query}")results = compare_chain_types(query)for chain_type, result in results.items():print(f"\n{chain_type} chain 結果:")print(result)
使用建議:
-
Stuff Chain:
- 適用于文檔數量少( < 3-4個)
- 需要快速響應
- 文檔內容簡單
-
Map Reduce Chain:
- 適用于大量文檔
- 需要綜合信息
- 可以并行處理
- 適合統計和匯總類查詢
-
Refine Chain:
- 適用于需要深入分析的查詢
- 需要考慮上下文的連續性
- 適合生成詳細報告或分析
-
Map Rerank Chain:
- 適用于需要精確匹配的查詢
- 需要按相關性排序的結果
- 適合推薦類查詢
這些鏈類型可以根據具體需求組合使用,比如先用Map Rerank找到最相關的文檔,然后用Refine Chain生成詳細分析。