设置一个LLM来讨论你的代码-使用LangChain和本地硬件
在当前的大型语言模型(LLMs)可以执行的各种任务中,如果你是软件开发人员或数据科学家,并且使用源代码工作,那么代码理解可能对你来说特别有兴趣。有没有一个聊天机器人可以问关于你的代码的问题?数据预处理是在哪里实现的?是否已经有一个用于验证用户身份的函数?calculate_vector_dim和calculate_vector_dimension函数之间有什么区别?不要自己搜索正确的文件,只需问问机器人,它会给你答案,并指向包含相关代码片段的文件。这个机制被称为语义搜索,你可以想象它有多么有用。
在本教程中,我将向你展示如何实现一个能够做到这一点的LangChain机器人。此外,我将重点关注与数据隐私相关的问题,即不将你的代码随意交给别人。你或你的公司所产生的代码是私有财产,可能包含敏感信息或有价值的知识。你可能不希望或者你的公司的政策可能不允许你将其发送到由其他公司托管的LLM,而这些公司可能位于国外。因此,在本教程中,我将向你展示如何设置一个在你的本地硬件上运行的代码理解机器人,这样你的代码永远不会离开你的基础设施。
让我们开始吧!首先,我将给你一个简要介绍语义搜索的一般过程,然后我们将实现一个用于代码理解的机器人。
语义搜索简介
首先,让我简要解释一下语义搜索的一般思想。这种方法包括两个主要步骤,即检索和LLM自身的答案生成。在检索步骤中,选择包含相关信息的文档,并将其输入到LLM中以生成自然语言答案。例如,如果你问一个关于名为transform_vectors的函数的问题,检索步骤将选择那些与回答该问题相关的文件。这可能包括实现transform_vectors函数的文件,但也可能包括使用它的文件或提到它的文档的部分。在第二步中,将这些文件的内容以一种类似于以下形式的提示提供给LLM:
"""在给定上下文的情况下回答以下问题。...问题:答案:"""
LLM使用提供给它的文档的信息对问题创建自然语言答案。
这就是语义搜索的主要思想。现在让我们开始实现!首先,我们必须安装所需的软件包并读取我们的数据。
安装要求
在我们开始之前,请确保你已经设置了一个运行Python的环境并安装了以下软件包:
pip install langchain==0.0.191pip install transformers
读取文档
现在我们需要读取数据并将其转换成LangChain可以处理的格式。为了演示,我将下载LangChain本身的代码,但你当然也可以使用自己的代码库:
import osfolder_name = "sample_code"os.system(f"git clone https://github.com/hwchase17/langchain {folder_name}")
我们加载所有文件并将它们转换为一个文档,即每个文档将包含代码库中的一个文件。
from langchain.docstore.document import Document
documents = []
for root, dirs, files in os.walk(folder_name):
for file in files:
try:
with open(os.path.join(root, file), "r", encoding="utf-8") as o:
code = o.readlines()
d = Document(page_content="\n".join(code), metadata={"source": os.path.join(root, file)})
documents.append(d)
except UnicodeDecodeError:
# 一些文件不是utf-8编码的; 现在先忽略它们。
pass
检索
现在我们已经创建了我们的文档,我们需要对它们进行索引以使其可以搜索。索引一个文档意味着计算一个数值向量,该向量捕捉文档的最相关信息。与纯文本本身不同,数字向量可以用于进行数值计算,这意味着我们可以轻松地在其上计算相似性,然后用于确定哪些文档与回答给定问题相关。
在技术层面上,我们将使用嵌入和VectorStore的帮助来创建此索引。有一些可用作服务的VectorStore(例如DeepLake),它们具有一些便利的优势,但在我们的场景中,我们不想将代码交给其他人,因此我们在本地机器上创建一个VectorStore。最简单的方法是使用Chroma,在内存中创建一个VectorStore并允许我们对其进行持久化。
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.vectorstores import Chroma
hfemb = HuggingFaceEmbeddings(model_name="krlvi/sentence-t5-base-nlpl-code-x-glue")
persist_directory = "db"
db = Chroma.from_documents(documents, hfemb, persist_directory=persist_directory)
db.persist()
在from_documents函数中,计算并存储了Chroma数据库中的索引。下次,我们可以直接加载持久化的Chroma数据库,而无需再次调用from_documents函数:
db = Chroma(persist_directory=persist_directory, embedding_function=hfemb)
如上所述,我使用了krlvi/sentence-t5-base-nlpl-code-x-glue作为嵌入,它是在开源GitHub库的代码上训练的嵌入。正如你可以想象的,我们使用的嵌入必须是在代码上进行过训练的(除其他数据外),以便能够利用我们提供给它的数据。只经过自然语言训练的嵌入性能可能较差。
现在我们有了VectorStore和嵌入,我们可以直接从Chroma数据库创建检索器:
retriever = db.as_retriever()
LLM
我们需要的最后一个组件是LLM。最简单的解决方案是使用托管的LLM,例如使用OpenAI接口。然而,我们不想将我们的代码发送到这样的托管服务中。相反,我们将在自己的硬件上运行LLM。为此,我们使用HuggingFacePipeline,它允许我们在LangChain框架中使用HuggingFace的模型。
from langchain import HuggingFacePipeline
import transformers
model_id = "mosaicml/mpt-7b-instruct"
config = transformers.AutoConfig.from_pretrained(model_id, trust_remote_code=True)
tokenizer = transformers.AutoTokenizer.from_pretrained(model_id)
model = transformers.AutoModelForCausalLM.from_pretrained(model_id, config=config, trust_remote_code=True)
pipe = transformers.pipeline("text-generation", model=model, tokenizer=tokenizer, max_new_tokens=100)
llm = HuggingFacePipeline(pipeline=pipe)
如你所见,我使用了mosaic mpt-7b模型,它只需要在GPU上约16GB的内存。我创建了一个AutoModelForCausalLM,将其传递给transformers.pipeline,最终转换为HuggingFacePipeline。HuggingFacePipeline实现了LangChain中典型LLM对象相同的接口。也就是说,你可以像使用OpenAI LLM接口一样使用它。
如果你的机器上有多个GPU,你需要指定要使用哪一个。在这种情况下,我想使用索引为0的GPU:
config.init_device="cuda:0"model.to(device='cuda:0')pipe = transformers.pipeline("text-generation", model=model, tokenizer=tokenizer, max_new_tokens=100, device=0)
上述设置的一些额外参数可以解释如下:
- trust_remote_code:必须设置为true以允许运行来自LangChain外部的模型。
- max_new_tokens:定义模型在其回答中可以生成的最大标记数量。如果此值过低,则可能在模型能够完全回答问题之前截断其响应。
将所有组件连接在一起
现在我们拥有了所需的所有组件,可以将它们组合在一个ConversationalRetrievalChain中。
from langchain.chains import ConversationalRetrievalChainqa_chain = ConversationalRetrievalChain.from_llm(llm=llm, retriever=retriever, return_source_documents=True)
最后,我们可以查询链条来回答我们的问题。结果对象将包含一个自然语言答案和一个被查询以得出答案的源文档列表。
result = qa_chain({"question":"KNNRetriever中create_index函数的返回类型是什么?", "chat_history":[]})print(f"答案:{result['answer']}")print(f"来源:{[x.metadata['source'] for x in result['source_documents']]}")
这是答案:
答案:KNNRetriever中create_index函数的返回类型是np.ndarray.来源:['sample_code/langchain/retrievers/knn.py', 'sample_code/langchain/vectorstores/elastic_vector_search.py', 'sample_code/langchain/vectorstores/elastic_vector_search.py', 'sample_code/langchain/vectorstores/opensearch_vector_search.py']
总结
我们完成了!嗯,有点。通过上面的代码,我们现在能够就源代码提出问题。然而,根据你的需求,有一些步骤可能需要进行修改:
- 使用自己的源代码作为文档,而不是LangChain的代码。
- 尝试不同的嵌入。如果嵌入不匹配,检索器无法找到正确的文档,最终问题可能无法准确回答。
- 尝试不同的模型。有更大、更强大的模型,但有些可能太大无法在你的硬件上运行。你必须找到一个性能良好但仍能够以令人满意的方式运行模型的平衡点。
- 尝试不同的文档预处理方式以便于检索步骤。一个常见的例子是将它们分成相等长度的块。
我相信还有很多可以尝试以获得更好性能的方法。只需玩弄并根据自己的需求调整机器人。
进一步阅读
关于LangChain的代码理解的更多示例,请参阅他们的文档:
- https://python.langchain.com/docs/use_cases/code/
在HuggingFace上,你可以找到可以在LangChain中轻松使用的模型和嵌入:
- https://huggingface.co/models
喜欢这篇文章吗?关注我以接收我未来的帖子通知。