作者 | 崔皓
審校 | 重樓
摘要
隨著LLM(大語(yǔ)言模型)的發(fā)展,最近流行起利用大語(yǔ)言模型對(duì)源代碼進(jìn)行分析的潮流。網(wǎng)絡(luò)博主紛紛針對(duì)GitHub Co-Pilot、Code Interpreter、Codium和Codeium上的代碼進(jìn)行分析。我們也來(lái)湊個(gè)熱鬧,利用OpenAI 的GPT-3.5-Turbo和LangChain對(duì)LangChain的源代碼進(jìn)行分析。
開(kāi)篇
眾所周知,作為程序員經(jīng)常會(huì)和源代碼打交道,很多情況下,當(dāng)程序員遇到新代碼庫(kù),或者是遺留項(xiàng)目的代碼庫(kù),都有些手足無(wú)措。特別是要在已有的代碼庫(kù)中進(jìn)行修改,那更是舉步維艱,生怕走錯(cuò)一步成千古恨。例如:不清楚類,方法之間的關(guān)系,不清楚函數(shù)之間的業(yè)務(wù)邏輯。不過(guò)現(xiàn)在不用擔(dān)心了,有了大語(yǔ)言模型的加持,已讓閱讀代碼不是難事,對(duì)代碼庫(kù)的整體分析也是小菜一碟。
總結(jié)來(lái)說(shuō),可以通過(guò)大語(yǔ)言模型進(jìn)行如下操作:
1. 通過(guò)對(duì)代碼庫(kù)進(jìn)行問(wèn)答,以了解其工作原理。
2. 利用LLM提供重構(gòu)或改進(jìn)建議。
3. 使用LLM對(duì)代碼進(jìn)行文檔化。
今天我們就從代碼庫(kù)問(wèn)答開(kāi)始,帶大家手把手編寫代碼庫(kù)問(wèn)答的程序。
整體介紹
首先,我們來(lái)整理一些思路,如圖1 所示。我們會(huì)先下載LangChain的源代碼,將source code的目錄以及目錄下面的所有源代碼文件保存到磁盤上。然后再對(duì)其進(jìn)行加載和轉(zhuǎn)換,也就是圖中紅色的部分。將這些代碼文件切割成小的文件塊,用來(lái)Embedding操作。也就是將其嵌入到向量數(shù)據(jù)庫(kù)中,即圖中橙色的部分。接著,圖中最右邊用戶會(huì)請(qǐng)求大模型,這里的模型我們使用GPT-3.5-Turbo,請(qǐng)求模型提問(wèn)與LangChain源代碼相關(guān)的問(wèn)題,例如:“在LangChain中如何初始化ReAct agent “。此時(shí),GPT-3.5-Turbo的大語(yǔ)言模型會(huì)從向量數(shù)據(jù)庫(kù)中獲取相關(guān)信息,并且返回給用戶。
圖1 源代碼庫(kù)提問(wèn)思路整理
具體來(lái)說(shuō),可以采用一種分割策略,其機(jī)制由如下幾個(gè)步驟組成:
1. 將代碼中的每個(gè)頂級(jí)函數(shù)和類加載到單獨(dú)的文檔中。
2. 將剩余部分加載到另一個(gè)獨(dú)立的文檔中。
3. 保留關(guān)于每個(gè)分割來(lái)自何處的元數(shù)據(jù)信息。
不過(guò),這些步驟都是由LangChain內(nèi)部機(jī)制實(shí)現(xiàn)的, 我們只需要調(diào)用簡(jiǎn)單的代碼就可以完成。
整個(gè)代碼的構(gòu)建和處理過(guò)程如上面圖1 所示,接下來(lái)我們就可以編寫代碼,大概會(huì)分如下幾個(gè)步驟:
- 下載LangChain代碼
- 在VS Code導(dǎo)入代碼
- 安裝相關(guān)依賴
- 裝載LangChain的源代碼文件
- 切割文件
- 嵌入到向量數(shù)據(jù)庫(kù)
- 利用大模型進(jìn)行查詢
- 返回查詢結(jié)果
下面,我們就按照步驟來(lái)逐一介紹。
下載LangChain代碼
首先,有請(qǐng)我們的主角LangChain源代碼登場(chǎng)。 如圖2 所示,可以通過(guò)訪問(wèn)地址:https://github.com/langchain-ai/langchain,來(lái)查看源代碼庫(kù)。
圖2 LangChain源代碼庫(kù)
當(dāng)然可以通過(guò)Clone方法下載代碼,或者使用如圖3所示的方式,直接下載zip包然后解壓。
圖3 下載LangChain源代碼
下載之后進(jìn)行解壓,請(qǐng)記住解壓的目錄后面會(huì)用到。
在VS Code導(dǎo)入代碼
在解壓LangChain的源代碼庫(kù)之后,將其導(dǎo)入到VS Code中。 如圖3 所示,在VS Code中加載,在LANGCHAIN-MASTER目錄下面的 /libs/langchain/langchain下面就是我們的目標(biāo)目錄了。里面存放著LangChain的源代碼,接下來(lái)就需要對(duì)這個(gè)目錄進(jìn)行掃描讀取器中的文件。
圖3LangChain代碼庫(kù)所在位置
安裝相關(guān)依賴
在對(duì)代碼庫(kù)進(jìn)行加載之前,我們先創(chuàng)建對(duì)應(yīng)的Jupyter Notebook文件。如圖4 所示,為了方便我們?cè)谠创a的根目錄下面創(chuàng)建chat_with_code.ipynb文件。
圖4 源代碼文件結(jié)構(gòu)
在文件中加入一些依賴包如下,分別加載了OpenAI的包,它是用來(lái)應(yīng)用GPT-3.5-Turbo模型的。Tiktoken 是用來(lái)處理NLP(自然語(yǔ)言處理)任務(wù)的,例如:分詞,嵌入,計(jì)算文本長(zhǎng)度。ChromDB 是向量數(shù)據(jù)庫(kù)的包,源代碼文件會(huì)保存在這里,以便后續(xù)查詢。另外,LangChain的包是進(jìn)行一些操作的腳手架,少了它程序玩不轉(zhuǎn)。
#引入依賴包
#openai gpt 模型
#tiktoken NLP 處理
#chromadb 向量數(shù)據(jù)庫(kù)
#langchain llm 腳手架
pip install openai tiktoken chromadb langchain
安裝完了依賴包之后,需要獲取環(huán)境變量配置。因?yàn)橐褂肙penAI的API去調(diào)用大模型,所以需要加入如下代碼:
#通過(guò)環(huán)境配置的方式獲取openai 訪問(wèn)api的key
import dotenv
dotenv.load_dotenv()
需要說(shuō)明的是,我們?cè)谠创a根目錄下面創(chuàng)建了一個(gè)”.env”文件,文件中寫入如下代碼:
OPENAI_API_KEY= openaikey
用來(lái)存放OpenAI的 key。
裝載LangChain的源代碼文件
引入依賴包之后就可以加載LangChain的源代碼文件了。 如下代碼,我們先引入幾個(gè)LangChain的Class幫助我們加載代碼。
#基于編程語(yǔ)言的字符切割
from langchain.text_splitter import Language
#大文件的裝載
from langchain.document_loaders.generic import GenericLoader
#解析編程語(yǔ)言的語(yǔ)法
from langchain.document_loaders.parsers import LanguageParser
- langchain.text_splitter 中的Language可以幫助我們基于編程語(yǔ)言進(jìn)行文件的切割。
- langchain.document_loaders.generi中的GenericLoader可以進(jìn)行大文件的加載,因?yàn)榭赡軙?huì)遇到類文件比較大的情況。
- langchain.document_loaders.parsers中的LanguageParser是用來(lái)對(duì)類和方法進(jìn)行解析的。
接著定義源代碼所在的路徑。
#定義源代碼所在的目錄
repo_path ="/Users/cuihao/doc/39 - GPT/langchain-master"
然后就可以開(kāi)始加載Python文件了。
#加載文件(s)多個(gè)文件
loader = GenericLoader.from_filesystem(
repo_path+"/libs/langchain/langchain",
#加載所有目錄下的所有文件
glob="**/*",
#針對(duì).py的文件進(jìn)行加載
suffixes=[".py"],
#激活解析所需的最小行數(shù)
parser=LanguageParser(language=Language.PYTHON, parser_threshold=500)
)
documents = loader.load()
len(documents)
從上面的代碼可以看出通過(guò)GenericLoader的from_filesystem方法進(jìn)行多目錄下文件的加載。首先,傳入源代碼所在的根目錄。接著,通過(guò)glob 參數(shù)定義所有目錄下的所有文件是我們的目標(biāo)文件。再就是定義處理文件的后綴是”.py”。最后,使用了LanguageParser方法針對(duì)Python進(jìn)行解析,并且指定每次激活解析的代碼行數(shù)是 500。
切割文件
有了加載以后的文件,我們將其給到Documents變量中,接著就是對(duì)Documents進(jìn)行切割。一般而言大模型都有輸入限制的要求,如下面代碼所示:
#對(duì)加載好的py 文件進(jìn)行切割
#ChatGPT 最大的輸入是2048
from langchain.text_splitter import RecursiveCharacterTextSplitter
python_splitter = RecursiveCharacterTextSplitter.from_language(
language=Language.PYTHON,
#每個(gè)切割之后的文件的大小
chunk_size=2000,
#文件與文件之間的重合部分是200
chunk_overlap=200)
#將所有源代碼文件切割成小的文件塊,以便llm 能夠進(jìn)行嵌入
texts = python_splitter.split_documents(documents)
len(texts)
這里利用LangChain.text_splitter包中的RecursiveCharacterTextSplitter函數(shù)對(duì)源代碼進(jìn)行切割。文件塊的大小是2000字節(jié),文件之間重合的部分是200字節(jié)。將切割好的文件塊賦給texts變量,這里的texts實(shí)際上是一個(gè)文件塊的數(shù)組,后面將會(huì)將這個(gè)數(shù)組嵌入到向量數(shù)據(jù)庫(kù)chroma中。
這里需要對(duì)文件塊切割的chunk_size和chunk_overlap兩個(gè)參數(shù)做一下說(shuō)明。如圖5 所示,如果我們對(duì)文件按照長(zhǎng)度進(jìn)行切割,切割的文字很有可能丟失上下文。例如:“我們?nèi)ス珗@玩好不好,如果天氣好的”,這樣一句話一定是不完整的,大模型在進(jìn)行學(xué)習(xí)或者推理的時(shí)候會(huì)丟失一部分信息。在自然語(yǔ)言中是這樣,在代碼解析中也是如此。
圖5 自然語(yǔ)言的文本切割
因此,我們?cè)谇懈畹臅r(shí)候會(huì)保存一部分文字塊的上下文信息。圖中“話我們就去”就是這部分信息,我們稱之為“overlap”也就是相互覆蓋的部分。這樣每個(gè)文字塊都可以保留它相鄰文字塊的部分信息,最大限度地保證了上下文信息的完整性,在代碼解析中我們也會(huì)沿用這種做法。
嵌入到向量數(shù)據(jù)庫(kù)
文件分塊完成以后,接下來(lái)將把這些代碼形成的文件塊嵌入到向量數(shù)據(jù)庫(kù)中了。只有嵌入進(jìn)去以后,才能方便后續(xù)用戶的查詢。如下代碼所示,利用OpenAI中的OpenAIEmbeddings函數(shù)將texts,也就是切割好的代碼文件保存到chroma的向量數(shù)據(jù)庫(kù)中。
from langchain.vectorstores import Chroma
from langchain.embeddings.openai import OpenAIEmbeddings
#將切割好的文件塊嵌入到向量數(shù)據(jù)庫(kù)中, chroma db
db = Chroma.from_documents(texts, OpenAIEmbeddings(disallowed_special=()))
#定義如何查詢代碼
retriever = db.as_retriever(
#Maximal Marginal Relevance (最大邊際相關(guān)性)= 相關(guān)性 + 多樣性
search_type="mmr",# Also test "similarity"
#控制在檢索中返回的文檔數(shù)量
search_kwargs={"k":8},
)
不僅如此,還針對(duì)向量數(shù)據(jù)庫(kù)創(chuàng)建了Retriever 作為索引器,幫助后續(xù)查找。其中有兩個(gè)參數(shù),第一個(gè)search_type定義的是mmr,這個(gè)是Maximal Marginal Relevance (最大邊際相關(guān)性)的縮寫。它是一種相關(guān)性查詢的方式,同時(shí)考慮了查詢目標(biāo)的相關(guān)性和多樣性。還有一個(gè)參數(shù)search_kwargs 定義了k 為8,這個(gè)是匹配相關(guān)文檔的數(shù)量。
利用大模型進(jìn)行查詢
經(jīng)過(guò)上面的步驟離我們的目標(biāo)已經(jīng)不遠(yuǎn)了。創(chuàng)建GPT-3.5-Turbo模型的查詢是當(dāng)務(wù)之急。如下代碼所示,引入ChatOpenAI函數(shù)創(chuàng)建GPT-3.5-Turbo的模型實(shí)體。接著使用ConversationSummaryMemory創(chuàng)建有記憶的對(duì)話,最重要的是使用ConversationalRetrievalChain,從名字上可以看出來(lái)是基于對(duì)話的索引器,它以Chain的方式存在。Chain是LangChain的核心組件,用來(lái)將其他組件,例如:Model I/O,DataConnection,Agent等組合使用。這里它將大模型(LLM),索引器(Retriever)以及記憶組件(Memory)整合在一起進(jìn)行問(wèn)答響應(yīng)。
#調(diào)用llm gpt-3.5-turbo 進(jìn)行查詢
from langchain.chat_models import ChatOpenAI
from langchain.memory import ConversationSummaryMemory
from langchain.chains import ConversationalRetrievalChain
llm = ChatOpenAI(model_name="gpt-3.5-turbo")
memory = ConversationSummaryMemory(llm=llm,memory_key="chat_history",return_messages=True)
#定義大語(yǔ)言模型 , 查詢方式, 記憶方式
qa = ConversationalRetrievalChain.from_llm(llm,retriever=retriever,memory=memory)
返回查詢結(jié)果
萬(wàn)事俱備只欠東風(fēng),我們通過(guò)如下代碼開(kāi)始提問(wèn)。
question ="我如何處初始化 ReAct agent?"
result = qa(question)
result['answer']
GPT-3.5-Turbo的回復(fù)如下:
要初始化 ReAct agent,您可以使用以下代碼:
from langchain.agents.react_agent import ReActDocstoreAgent
from langchain.tools import LookupTool, SearchTool
# 創(chuàng)建 LookupTool 和 SearchTool
lookup_tool = LookupTool()
search_tool = SearchTool()
# 創(chuàng)建 ReAct agent
react_agent =ReActDocstoreAgent(tools=[lookup_tool, search_tool])
在上述代碼中,我們首先導(dǎo)入 ReActDocstoreAgent 類和所需的工具類 LookupTool 和 SearchTool。
然后,我們創(chuàng)建了這些工具的實(shí)例,并將它們作為參數(shù)傳遞給 ReActDocstoreAgent 的構(gòu)造函數(shù),從而初始化了 ReAct agent。
回復(fù)中告訴我們要引入哪些類,以及ReAct Agent初始化需要依賴的類以及函數(shù),把類和函數(shù)之間的依賴關(guān)系說(shuō)清楚了。
總結(jié)
本文介紹了如何利用LangChain和GPT-3.5-Turbo來(lái)理解大型代碼庫(kù)。首先,我們下載了LangChain代碼庫(kù)并在VS Code中導(dǎo)入。然后,通過(guò)安裝必要的依賴包,如OpenAI、Tiktoken、ChromaDB和LangChain,為后續(xù)操作做準(zhǔn)備。接著,我們加載LangChain的源代碼文件,包括使用LanguageParser進(jìn)行解析。隨后,我們將代碼文件切割成小塊,以滿足大模型的輸入要求。這些切割后的代碼塊被嵌入到Chroma向量數(shù)據(jù)庫(kù)中,并創(chuàng)建了一個(gè)用于查詢的Retriever,它使用Maximal Marginal Relevance進(jìn)行相關(guān)性查詢,并限制返回的文檔數(shù)量。最后,我們使用GPT-3.5-Turbo來(lái)進(jìn)行代碼庫(kù)的查詢,實(shí)現(xiàn)了代碼的問(wèn)答和解釋,使代碼庫(kù)的理解變得更加容易。
作者介紹
崔皓,51CTO社區(qū)編輯,資深架構(gòu)師,擁有18年的軟件開(kāi)發(fā)和架構(gòu)經(jīng)驗(yàn),10年分布式架構(gòu)經(jīng)驗(yàn)。