作者 | 崔皓
审校 | 重楼
摘要
随着LLM(大语言模型)的发展,最近流行起利用大语言模型对源代码进行分析的潮流。网络博主纷纷针对GitHub Co-Pilot、Code Interpreter、Codium和Codeium上的代码进行分析。我们也来凑个热闹,利用OpenAI 的GPT-3.5-Turbo和LangChain对LangChain的源代码进行分析。
开篇
众所周知,作为程序员经常会和源代码打交道,很多情况下,当程序员遇到新代码库,或者是遗留项目的代码库,都有些手足无措。特别是要在已有的代码库中进行修改,那更是举步维艰,生怕走错一步成千古恨。例如:不清楚类,方法之间的关系,不清楚函数之间的业务逻辑。不过现在不用担心了,有了大语言模型的加持,已让阅读代码不是难事,对代码库的整体分析也是小菜一碟。
总结来说,可以通过大语言模型进行如下操作:
1. 通过对代码库进行问答,以了解其工作原理。
2. 利用LLM提供重构或改进建议。
3. 使用LLM对代码进行文档化。
今天我们就从代码库问答开始,带大家手把手编写代码库问答的程序。
整体介绍
首先,我们来整理一些思路,如图1 所示。我们会先下载LangChain的源代码,将source code的目录以及目录下面的所有源代码文件保存到磁盘上。然后再对其进行加载和转换,也就是图中红色的部分。将这些代码文件切割成小的文件块,用来Embedding操作。也就是将其嵌入到向量数据库中,即图中橙色的部分。接着,图中最右边用户会请求大模型,这里的模型我们使用GPT-3.5-Turbo,请求模型提问与LangChain源代码相关的问题,例如:“在LangChain中如何初始化ReAct agent “。此时,GPT-3.5-Turbo的大语言模型会从向量数据库中获取相关信息,并且返回给用户。
图1 源代码库提问思路整理
具体来说,可以采用一种分割策略,其机制由如下几个步骤组成:
1. 将代码中的每个顶级函数和类加载到单独的文档中。
2. 将剩余部分加载到另一个独立的文档中。
3. 保留关于每个分割来自何处的元数据信息。
不过,这些步骤都是由LangChain内部机制实现的, 我们只需要调用简单的代码就可以完成。
整个代码的构建和处理过程如上面图1 所示,接下来我们就可以编写代码,大概会分如下几个步骤:
- 下载LangChain代码
- 在VS Code导入代码
- 安装相关依赖
- 装载LangChain的源代码文件
- 切割文件
- 嵌入到向量数据库
- 利用大模型进行查询
- 返回查询结果
下面,我们就按照步骤来逐一介绍。
下载LangChain代码
首先,有请我们的主角LangChain源代码登场。 如图2 所示,可以通过访问地址:https://github.com/langchain-ai/langchain,来查看源代码库。
图2 LangChain源代码库
当然可以通过Clone方法下载代码,或者使用如图3所示的方式,直接下载zip包然后解压。
图3 下载LangChain源代码
下载之后进行解压,请记住解压的目录后面会用到。
在VS Code导入代码
在解压LangChain的源代码库之后,将其导入到VS Code中。 如图3 所示,在VS Code中加载,在LANGCHAIN-MASTER目录下面的 /libs/langchain/langchain下面就是我们的目标目录了。里面存放着LangChain的源代码,接下来就需要对这个目录进行扫描读取器中的文件。
图3LangChain代码库所在位置
安装相关依赖
在对代码库进行加载之前,我们先创建对应的Jupyter Notebook文件。如图4 所示,为了方便我们在源代码的根目录下面创建chat_with_code.ipynb文件。
图4 源代码文件结构
在文件中加入一些依赖包如下,分别加载了OpenAI的包,它是用来应用GPT-3.5-Turbo模型的。Tiktoken 是用来处理NLP(自然语言处理)任务的,例如:分词,嵌入,计算文本长度。ChromDB 是向量数据库的包,源代码文件会保存在这里,以便后续查询。另外,LangChain的包是进行一些操作的脚手架,少了它程序玩不转。
#引入依赖包
#openai gpt 模型
#tiktoken NLP 处理
#chromadb 向量数据库
#langchain llm 脚手架
pip install openai tiktoken chromadb langchain
安装完了依赖包之后,需要获取环境变量配置。因为要使用OpenAI的API去调用大模型,所以需要加入如下代码:
#通过环境配置的方式获取openai 访问api的key
import dotenv
dotenv.load_dotenv()
需要说明的是,我们在源代码根目录下面创建了一个”.env”文件,文件中写入如下代码:
OPENAI_API_KEY= openaikey
用来存放OpenAI的 key。
装载LangChain的源代码文件
引入依赖包之后就可以加载LangChain的源代码文件了。 如下代码,我们先引入几个LangChain的Class帮助我们加载代码。
#基于编程语言的字符切割
from langchain.text_splitter import Language
#大文件的装载
from langchain.document_loaders.generic import GenericLoader
#解析编程语言的语法
from langchain.document_loaders.parsers import LanguageParser
- langchain.text_splitter 中的Language可以帮助我们基于编程语言进行文件的切割。
- langchain.document_loaders.generi中的GenericLoader可以进行大文件的加载,因为可能会遇到类文件比较大的情况。
- langchain.document_loaders.parsers中的LanguageParser是用来对类和方法进行解析的。
接着定义源代码所在的路径。
#定义源代码所在的目录
repo_path ="/Users/cuihao/doc/39 - GPT/langchain-master"
然后就可以开始加载Python文件了。
#加载文件(s)多个文件
loader = GenericLoader.from_filesystem(
repo_path+"/libs/langchain/langchain",
#加载所有目录下的所有文件
glob="**/*",
#针对.py的文件进行加载
suffixes=[".py"],
#激活解析所需的最小行数
parser=LanguageParser(language=Language.PYTHON, parser_threshold=500)
)
documents = loader.load()
len(documents)
从上面的代码可以看出通过GenericLoader的from_filesystem方法进行多目录下文件的加载。首先,传入源代码所在的根目录。接着,通过glob 参数定义所有目录下的所有文件是我们的目标文件。再就是定义处理文件的后缀是”.py”。最后,使用了LanguageParser方法针对Python进行解析,并且指定每次激活解析的代码行数是 500。
切割文件
有了加载以后的文件,我们将其给到Documents变量中,接着就是对Documents进行切割。一般而言大模型都有输入限制的要求,如下面代码所示:
#对加载好的py 文件进行切割
#ChatGPT 最大的输入是2048
from langchain.text_splitter import RecursiveCharacterTextSplitter
python_splitter = RecursiveCharacterTextSplitter.from_language(
language=Language.PYTHON,
#每个切割之后的文件的大小
chunk_size=2000,
#文件与文件之间的重合部分是200
chunk_overlap=200)
#将所有源代码文件切割成小的文件块,以便llm 能够进行嵌入
texts = python_splitter.split_documents(documents)
len(texts)
这里利用LangChain.text_splitter包中的RecursiveCharacterTextSplitter函数对源代码进行切割。文件块的大小是2000字节,文件之间重合的部分是200字节。将切割好的文件块赋给texts变量,这里的texts实际上是一个文件块的数组,后面将会将这个数组嵌入到向量数据库chroma中。
这里需要对文件块切割的chunk_size和chunk_overlap两个参数做一下说明。如图5 所示,如果我们对文件按照长度进行切割,切割的文字很有可能丢失上下文。例如:“我们去公园玩好不好,如果天气好的”,这样一句话一定是不完整的,大模型在进行学习或者推理的时候会丢失一部分信息。在自然语言中是这样,在代码解析中也是如此。
图5 自然语言的文本切割
因此,我们在切割的时候会保存一部分文字块的上下文信息。图中“话我们就去”就是这部分信息,我们称之为“overlap”也就是相互覆盖的部分。这样每个文字块都可以保留它相邻文字块的部分信息,最大限度地保证了上下文信息的完整性,在代码解析中我们也会沿用这种做法。
嵌入到向量数据库
文件分块完成以后,接下来将把这些代码形成的文件块嵌入到向量数据库中了。只有嵌入进去以后,才能方便后续用户的查询。如下代码所示,利用OpenAI中的OpenAIEmbeddings函数将texts,也就是切割好的代码文件保存到chroma的向量数据库中。
from langchain.vectorstores import Chroma
from langchain.embeddings.openai import OpenAIEmbeddings
#将切割好的文件块嵌入到向量数据库中, chroma db
db = Chroma.from_documents(texts, OpenAIEmbeddings(disallowed_special=()))
#定义如何查询代码
retriever = db.as_retriever(
#Maximal Marginal Relevance (最大边际相关性)= 相关性 + 多样性
search_type="mmr",# Also test "similarity"
#控制在检索中返回的文档数量
search_kwargs={"k":8},
)
不仅如此,还针对向量数据库创建了Retriever 作为索引器,帮助后续查找。其中有两个参数,第一个search_type定义的是mmr,这个是Maximal Marginal Relevance (最大边际相关性)的缩写。它是一种相关性查询的方式,同时考虑了查询目标的相关性和多样性。还有一个参数search_kwargs 定义了k 为8,这个是匹配相关文档的数量。
利用大模型进行查询
经过上面的步骤离我们的目标已经不远了。创建GPT-3.5-Turbo模型的查询是当务之急。如下代码所示,引入ChatOpenAI函数创建GPT-3.5-Turbo的模型实体。接着使用ConversationSummaryMemory创建有记忆的对话,最重要的是使用ConversationalRetrievalChain,从名字上可以看出来是基于对话的索引器,它以Chain的方式存在。Chain是LangChain的核心组件,用来将其他组件,例如:Model I/O,DataConnection,Agent等组合使用。这里它将大模型(LLM),索引器(Retriever)以及记忆组件(Memory)整合在一起进行问答响应。
#调用llm gpt-3.5-turbo 进行查询
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)
#定义大语言模型 , 查询方式, 记忆方式
qa = ConversationalRetrievalChain.from_llm(llm,retriever=retriever,memory=memory)
返回查询结果
万事俱备只欠东风,我们通过如下代码开始提问。
question ="我如何处初始化 ReAct agent?"
result = qa(question)
result['answer']
GPT-3.5-Turbo的回复如下:
要初始化 ReAct agent,您可以使用以下代码:
from langchain.agents.react_agent import ReActDocstoreAgent
from langchain.tools import LookupTool, SearchTool
# 创建 LookupTool 和 SearchTool
lookup_tool = LookupTool()
search_tool = SearchTool()
# 创建 ReAct agent
react_agent =ReActDocstoreAgent(tools=[lookup_tool, search_tool])
在上述代码中,我们首先导入 ReActDocstoreAgent 类和所需的工具类 LookupTool 和 SearchTool。
然后,我们创建了这些工具的实例,并将它们作为参数传递给 ReActDocstoreAgent 的构造函数,从而初始化了 ReAct agent。
回复中告诉我们要引入哪些类,以及ReAct Agent初始化需要依赖的类以及函数,把类和函数之间的依赖关系说清楚了。
总结
本文介绍了如何利用LangChain和GPT-3.5-Turbo来理解大型代码库。首先,我们下载了LangChain代码库并在VS Code中导入。然后,通过安装必要的依赖包,如OpenAI、Tiktoken、ChromaDB和LangChain,为后续操作做准备。接着,我们加载LangChain的源代码文件,包括使用LanguageParser进行解析。随后,我们将代码文件切割成小块,以满足大模型的输入要求。这些切割后的代码块被嵌入到Chroma向量数据库中,并创建了一个用于查询的Retriever,它使用Maximal Marginal Relevance进行相关性查询,并限制返回的文档数量。最后,我们使用GPT-3.5-Turbo来进行代码库的查询,实现了代码的问答和解释,使代码库的理解变得更加容易。
作者介绍
崔皓,51CTO社区编辑,资深架构师,拥有18年的软件开发和架构经验,10年分布式架构经验。