终于学习完了Langchain框架的核心内容,最后基于langchain技术实现一个个人知识库助手的小项目,将这些内容串联起来,在实际中进行应用。
工具清单:
1、langchain框架
2、chroma向量数据库
3、embedding模型(bge-large-zh-v1.5、bge-large-en-v1.5)
4、rerank模型(bge-reranker-v2-m3)
5、gradio前端框架
6、LLM(GLM4系列模型)
1、数据读取与数据库构建
我们使用langchain提供的文本加载器来进行各种类型数据的加载;
def file_loader(file, loaders):
if isinstance(file, tempfile._TemporaryFileWrapper):
file = file.name
if not os.path.isfile(file):
[file_loader(os.path.join(file, f), loaders) for f in os.listdir(file)]
return
file_type = file.split('.')[-1]
if file_type == 'pdf':
loaders.append(PyMuPDFLoader(file))
elif file_type == 'md':
pattern = r"不存在|风控"
match = re.search(pattern, file)
if not match:
loaders.append(UnstructuredMarkdownLoader(file))
elif file_type == "txt":
loaders.append(TextLoader(file))
elif file_type == "docx":
loaders.append(Docx2txtLoader(file))
return
得到数据后,由于Token上下文的限制,LLM不能一次将整个文档全都读入,我们需要将文档进行切分得到chunk;langchain为我们提供了多种文本分割器,每个文本分割器都根据 chunk_size
(块大小)和 chunk_overlap
(块与块之间的重叠大小)进行分割;
- chunk_size 指每个块包含的字符或 Token的数量;
- chunk_overlap 指两个块之间共享的字符数量,用于保持上下文的连贯性,避免分割丢失上下文信息;
这里我们选择使用RecursiveCharacterTextSplitter,按字符串分割文本,递归地尝试按不同的分隔符进行分割文本;
我们得到分割后的文本后,我们就可以将数据存储到数据库中了,所有我们这里就需要一个embedding模型,embedding可以将文本映射到一个高维的向量空间,实现一个文本到数据的映射,而相似的文本编码后得到的向量就会相近;
最后我们初始化数据库,并将数据可持久化到硬盘,方便我们后续直接加载;
def create_db(files=DEFAULT_DB_PATH, persist_directory=DEFAULT_PERSIST_PATH, embeddings="bge-large-zh-v1.5"):
if files == None:
return "can't load empty file"
if type(files) != list:
files = [files]
loaders = []
[file_loader(file, loaders) for file in files]
docs = []
for loader in loaders:
if loader is not None:
docs.extend(loader.load())
# 切分文档
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=150)
split_docs = text_splitter.split_documents(docs)
print(len(split_docs))
if type(embeddings) == str:
embeddings = get_embedding(embedding=embeddings)
# 加载数据库
vectordb = Chroma.from_documents(
documents=split_docs,
embedding=embeddings,
persist_directory=persist_directory # 允许我们将persist_directory目录保存到磁盘上
)
vectordb.persist()
return vectordb
2、 构建RAG问答链
由了刚刚构建的数据库,我们这里直接将其接入到问答流程;
这里我们需要传入一些必要的参数,然后加载一下我们的向量数据库,并根据该向量数据库构建一个检索器,作为我们的基本检索器,我们使用rerank模型将检索到的chunk进行进一步的精排,从而提升查询结果相关性;
def __init__(self, model: str, temperature: float = 0.0, top_k: int = 4, api_key: str = None, file_path: str = None,
persist_path: str = None, embedding=None, template=default_template_rq):
print(persist_path)
self.model = model
self.temperature = temperature
self.top_k = top_k
self.file_path = file_path
self.persist_path = persist_path
self.api_key = api_key
self.embedding = embedding
self.template = template
self.vectordb = get_vectordb(self.file_path, self.persist_path, self.embedding)
self.prompt = PromptTemplate(input_variables=["context", "question"], template=self.template)
self.retriever = self.vectordb.as_retriever(search_type="similarity",
search_kwargs={'k': 10})
self.reranker = HuggingFaceCrossEncoder(model_name="../embedding/BAAI/bge-reranker-v2-m3",
model_kwargs={'device': 'cuda'})
print(f"向量库中存储的数量:{self.vectordb._collection.count()}")
接下来就是我们的问答环节,用户发出一个问题,我们根据用户指定的一些参数(temperature、top-k等)来初始化我们的大语言模型和重排器,我们使用langchain框架提供的RetrievalQA链来构建我们的问答链;
def answer(self, question: str = None, temperature=None, top_k=4):
""""
核心方法,调用问答链
arguments:
- question:用户提问
"""
if len(question) == 0:
return ""
if temperature is None:
temperature = self.temperature
if top_k is None:
top_k = self.top_k
llm = model_to_llm(self.model, temperature, self.api_key)
compressor = CrossEncoderReranker(model=self.reranker, top_n=top_k)
compression_retriever = ContextualCompressionRetriever(base_compressor=compressor,
base_retriever=self.retriever)
# 自定义 QA 链
qa_chain = RetrievalQA.from_chain_type(llm=llm,
retriever=compression_retriever,
return_source_documents=True,
chain_type_kwargs={"prompt": self.prompt})
result = qa_chain({"query": question})
answer = result["result"]
source_documents = result["source_documents"]
answer = re.sub(r"\\n", '<br/>', answer)
return answer, source_documents
以上就是我们问答助手的整个流程,接下来我们可以使用gradio框架快速的搭建起我们的交互界面;
我们问一个问题:
如何保持持续行动(刻意学习中的一个内容)
这是我们在数据库中检索到的内容:
[Document(page_content='些工作量。平时要更加严格地要求自己了。我有时候时间充裕,一天会多\n写两篇文章,即使某天真的安排不出时间,也全然不会有“完蛋,我中断\n了”的感觉。树挪死,人挪活,持续行动的核心在于持续稳定。\n成长会里有位朋友在某知名通信企业任职,有一次谈到加班,她\n说:“很多公司的加班是今天做昨天的事情,或者今天做今天还没完成的事\n情,反正加班是因为做不完事情,而我们的加班是因为今天要把明天的事\n情做完,这个月把下个月的事情做完,所以能够永远赶在竞争对手前面。”\n另外一个例子是,在成长会的一些练习小组里每天都会有任务。往往\n出现这样一种情况:有一些成员从周一到周五,没有按时完成对应的任\n务,于是拖到周六,用一天的时间把所有作业都补完。很多人会觉得这好\n像挺正常,平常都做不完,周末再补,其实这是大部分人碌碌无为的原\n因。\n换一个场景你可能就会觉得这很荒谬,我们平常吃饭,每天每餐都要\n吃,到了点要吃,饿了就要吃。你会不会说我从周一到周五不吃饭,我周\n六一天把前面五天的饭全吃了?你没法五天不吃饭,你也无法一天补吃原\n来五天的饭。做事情就像吃饭一样,每天都要吃饭,于是这会变成一个常\n规项目。持续行动也应该如此。', metadata={'author': 'Scalers', 'creationDate': "D:20201006233519+00'00'", 'creator': 'calibre 3.39.1 [https://calibre-ebook.com]', 'file_path': '../knowledge_db/刻意学习.pdf', 'format': 'PDF 1.4', 'keywords': '', 'modDate': '', 'page': 91, 'producer': 'calibre 3.39.1 [https://calibre-ebook.com]', 'source': '../knowledge_db/刻意学习.pdf', 'subject': '', 'title': '刻意学习', 'total_pages': 235, 'trapped': ''}), Document(page_content='持续的事情,或者你也就走出了两三步,就迫不及待地想分享一些“牛×的\n方法”“秘籍成果”。\n这就像传销一样。一个人做了一点点事情,先把自己感动了,然后开\n始把这种感动转换成文字,营造了一个“感动场”,营造因为自己感动而产\n生的幻象。然后这个文字传播出去,立即引发一群人的感动和对牛×的憧\n憬,于是这群人在什么都没有做的情况下,便开始了新一轮的情绪高潮,\n高潮过后继续传播……于是这种氛围从公众号蔓延到社群……但是一轮一\n轮下来,你还是什么都没有做,那只是一群人产生了一些精神垃圾,然后\n找到另一群人接盘而已。这就跟开着印钞机印钞票,增发货币,是一个道\n理。\n当然,在持续行动中,我们需要正向反馈。每到一个关键节点,我们\n停下来整理整理思绪,甚至给自己一个小小的庆贺,这的确是有必要的。\n因为本质上这是一种正向反馈,有利于形成正循环。所以你看我过去每逢\n一百天都会有那么一篇总结文章,但是你也能看出越到后面的文章,我的\n心态越平和。\n如果我们在持续行动和持续进步,如果我们是在持续成长,那我们现\n在所取得的成绩,在未来都不值一提。如果我们每天都是在怀念自己以前', metadata={'author': 'Scalers', 'creationDate': "D:20201006233519+00'00'", 'creator': 'calibre 3.39.1 [https://calibre-ebook.com]', 'file_path': '../knowledge_db/刻意学习.pdf', 'format': 'PDF 1.4', 'keywords': '', 'modDate': '', 'page': 53, 'producer': 'calibre 3.39.1 [https://calibre-ebook.com]', 'source': '../knowledge_db/刻意学习.pdf', 'subject': '', 'title': '刻意学习', 'total_pages': 235, 'trapped': ''}), Document(page_content='因。\n换一个场景你可能就会觉得这很荒谬,我们平常吃饭,每天每餐都要\n吃,到了点要吃,饿了就要吃。你会不会说我从周一到周五不吃饭,我周\n六一天把前面五天的饭全吃了?你没法五天不吃饭,你也无法一天补吃原\n来五天的饭。做事情就像吃饭一样,每天都要吃饭,于是这会变成一个常\n规项目。持续行动也应该如此。\n在我刚开始写作的时候,总会担心万一有什么不可抗拒的事情,让我\n中断了写作,于是纠结不已。后面我发现这只是无谓的担心。生活中有很\n多人总是在为没有发生的事情担心,但也仅仅是担心却不做预案。以前我\n也是如此,后来才发现,不管发生什么,总是可以想办法在一天的繁忙中\n找出时间来做事情。\n白天再忙,总要起床,起床后的一段时间不会有人打扰你,于是你可\n以再起早一些,就有了属于自己的时间了;睡觉之前也有不被打扰的时\n间,这个时间也可以用来做事,只是你需要有足够的体力让自己不会感到\n困乏,于是体育锻炼的重要性就显现出来了。\n这么一来,我总能找到一些时间干自己的事情,于是就更加安心地持\n续行动了。在我持续写作的时间里,有人和我说自己想多做一些事情,却', metadata={'author': 'Scalers', 'creationDate': "D:20201006233519+00'00'", 'creator': 'calibre 3.39.1 [https://calibre-ebook.com]', 'file_path': '../knowledge_db/刻意学习.pdf', 'format': 'PDF 1.4', 'keywords': '', 'modDate': '', 'page': 91, 'producer': 'calibre 3.39.1 [https://calibre-ebook.com]', 'source': '../knowledge_db/刻意学习.pdf', 'subject': '', 'title': '刻意学习', 'total_pages': 235, 'trapped': ''})]
这是我们调用大语言模型后的答案:
保持持续行动需要:1. 严格自我要求,确保每天稳定完成任务,避免堆积工作。2. 提前规划,像吃饭一样将行动变成日常习惯,不断前行。3. 正向反馈,适时庆祝关键节点,形成正循环。谢谢你的提问!