当前位置:   article > 正文

【Langchain大语言模型开发教程】基于Langchain的私人助手

【Langchain大语言模型开发教程】基于Langchain的私人助手

终于学习完了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提供的文本加载器来进行各种类型数据的加载;

  1. def file_loader(file, loaders):
  2. if isinstance(file, tempfile._TemporaryFileWrapper):
  3. file = file.name
  4. if not os.path.isfile(file):
  5. [file_loader(os.path.join(file, f), loaders) for f in os.listdir(file)]
  6. return
  7. file_type = file.split('.')[-1]
  8. if file_type == 'pdf':
  9. loaders.append(PyMuPDFLoader(file))
  10. elif file_type == 'md':
  11. pattern = r"不存在|风控"
  12. match = re.search(pattern, file)
  13. if not match:
  14. loaders.append(UnstructuredMarkdownLoader(file))
  15. elif file_type == "txt":
  16. loaders.append(TextLoader(file))
  17. elif file_type == "docx":
  18. loaders.append(Docx2txtLoader(file))
  19. return

得到数据后,由于Token上下文的限制,LLM不能一次将整个文档全都读入,我们需要将文档进行切分得到chunk;langchain为我们提供了多种文本分割器,每个文本分割器都根据 chunk_size (块大小)和 chunk_overlap (块与块之间的重叠大小)进行分割;

  • chunk_size 指每个块包含的字符或 Token的数量;
  • chunk_overlap 指两个块之间共享的字符数量,用于保持上下文的连贯性,避免分割丢失上下文信息;

 这里我们选择使用RecursiveCharacterTextSplitter,按字符串分割文本,递归地尝试按不同的分隔符进行分割文本;

我们得到分割后的文本后,我们就可以将数据存储到数据库中了,所有我们这里就需要一个embedding模型,embedding可以将文本映射到一个高维的向量空间,实现一个文本到数据的映射,而相似的文本编码后得到的向量就会相近;

最后我们初始化数据库,并将数据可持久化到硬盘,方便我们后续直接加载;

  1. def create_db(files=DEFAULT_DB_PATH, persist_directory=DEFAULT_PERSIST_PATH, embeddings="bge-large-zh-v1.5"):
  2. if files == None:
  3. return "can't load empty file"
  4. if type(files) != list:
  5. files = [files]
  6. loaders = []
  7. [file_loader(file, loaders) for file in files]
  8. docs = []
  9. for loader in loaders:
  10. if loader is not None:
  11. docs.extend(loader.load())
  12. # 切分文档
  13. text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=150)
  14. split_docs = text_splitter.split_documents(docs)
  15. print(len(split_docs))
  16. if type(embeddings) == str:
  17. embeddings = get_embedding(embedding=embeddings)
  18. # 加载数据库
  19. vectordb = Chroma.from_documents(
  20. documents=split_docs,
  21. embedding=embeddings,
  22. persist_directory=persist_directory # 允许我们将persist_directory目录保存到磁盘上
  23. )
  24. vectordb.persist()
  25. return vectordb

2、 构建RAG问答链

由了刚刚构建的数据库,我们这里直接将其接入到问答流程;

这里我们需要传入一些必要的参数,然后加载一下我们的向量数据库,并根据该向量数据库构建一个检索器,作为我们的基本检索器,我们使用rerank模型将检索到的chunk进行进一步的精排,从而提升查询结果相关性;

  1. def __init__(self, model: str, temperature: float = 0.0, top_k: int = 4, api_key: str = None, file_path: str = None,
  2. persist_path: str = None, embedding=None, template=default_template_rq):
  3. print(persist_path)
  4. self.model = model
  5. self.temperature = temperature
  6. self.top_k = top_k
  7. self.file_path = file_path
  8. self.persist_path = persist_path
  9. self.api_key = api_key
  10. self.embedding = embedding
  11. self.template = template
  12. self.vectordb = get_vectordb(self.file_path, self.persist_path, self.embedding)
  13. self.prompt = PromptTemplate(input_variables=["context", "question"], template=self.template)
  14. self.retriever = self.vectordb.as_retriever(search_type="similarity",
  15. search_kwargs={'k': 10})
  16. self.reranker = HuggingFaceCrossEncoder(model_name="../embedding/BAAI/bge-reranker-v2-m3",
  17. model_kwargs={'device': 'cuda'})
  18. print(f"向量库中存储的数量:{self.vectordb._collection.count()}")

接下来就是我们的问答环节,用户发出一个问题,我们根据用户指定的一些参数(temperature、top-k等)来初始化我们的大语言模型和重排器,我们使用langchain框架提供的RetrievalQA链来构建我们的问答链;

  1. def answer(self, question: str = None, temperature=None, top_k=4):
  2. """"
  3. 核心方法,调用问答链
  4. arguments:
  5. - question:用户提问
  6. """
  7. if len(question) == 0:
  8. return ""
  9. if temperature is None:
  10. temperature = self.temperature
  11. if top_k is None:
  12. top_k = self.top_k
  13. llm = model_to_llm(self.model, temperature, self.api_key)
  14. compressor = CrossEncoderReranker(model=self.reranker, top_n=top_k)
  15. compression_retriever = ContextualCompressionRetriever(base_compressor=compressor,
  16. base_retriever=self.retriever)
  17. # 自定义 QA 链
  18. qa_chain = RetrievalQA.from_chain_type(llm=llm,
  19. retriever=compression_retriever,
  20. return_source_documents=True,
  21. chain_type_kwargs={"prompt": self.prompt})
  22. result = qa_chain({"query": question})
  23. answer = result["result"]
  24. source_documents = result["source_documents"]
  25. answer = re.sub(r"\\n", '<br/>', answer)
  26. 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. 正向反馈,适时庆祝关键节点,形成正循环。谢谢你的提问! 

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号