RAG实操教程,LangChain Llama2 | 发明你的个人LLM

RAG实操教程,LangChain + Llama2 | 发明你的个人LLM
本文将逐渐辅导您创立自己的RAG(检索增强生成)系统,使您能够上传自己的PDF文件并向LLM问询有关PDF的信息。本教程侧重于图中蓝色部分,即暂时不触及Gradio(想了解已接入Gradio的,请参阅官网)。相关技能栈包含以下内容:

  1. LLM: Llama2
  2. LLM API: llama.cpp service
  3. Langchain:
  4. Vector DB: ChromaDB
  5. Embeding: sentence-Tranformers

核心在于 Langchain,它是用于开发由言语模型支持的运用程序的框架。LangChain 就像胶水一样,有各种接口能够衔接LLM模型与其他东西和数据源,不过现在 LangChain 正在繁荣发展中,许多文件或API改版很多。以下我运用最简单的方法演示。

过程1. 环境设置

首要设置 Python 环境,我运用 conda 创立环境,并装置以下库,我在 Jupyter 环境完成示例。

# python=3.9
ipykernel
ipywidgets
langchain
PyMuPDF
chromadb
sentence-transformers
llama-cpp-python

过程2. 读入文件处理并导入数据库。

RAG实操教程,LangChain + Llama2 | 发明你的个人LLM

首要咱们要将外部信息处理后,放到 DB 中,以供之后查询相关常识,这边的过程对应到上图框起来的部分,也便是橘色的 1. 文本拆分器 和 2. embedding。

a). 运用文件加载器

Langchain 供给了很多文件加载器,总共大约有55种,包含word、csv、PDF、GoogleDrive、Youtube等,运用方法也很简单。这儿我创立了一个虚拟人物 Alison Hawk 的 PDF 信息,并运用read in,Alison Hawk 的 PDF 信息。请留意需求装置 PyMuPDFLoader 才干运用。PyMuPDFLoader PyMuPDF

from langchain.document_loaders import PyMuPDFLoader
loader = PyMuPDFLoader("LangChain/Virtual_characters.pdf")
PDF_data = loader.load()

文本切割器会将文档或文字切割成一个个 chunk,用以防备文档的信息超过 LLM 的 tokens,有一些研讨在评论如何将 chunk 优化。咱们后续文章中评论。

这两种常用的东西之间的差异在于,假如块大小超过指定阈值,它们会递归地将文本切割为更小的块。LangChain供给这两种方法,并且主要参数如下:

- RecursiveCharacterTextSplitter
- CharacterTextSplitter
  • chunk size:决议切割文字时每个内存块中的最大字元数。它指定每个内存块的大小或长度。
  • chunk_overlap:决议切割文字时连续内存块之间堆叠的字元数。它指定前一个内存块的多少应包含鄙人一个内存块中。
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=5)
all_splits = text_splitter.split_documents(PDF_data)

在上面的代码中咱们指定chunk_size=100, chunk_overlap=5, 这样的意思便是咱们每块的文档中是 100 个字符,chunk_overlap 表示字符重复的个数,这样能够防止语义被拆分后不完整。

c) 加载嵌入模型

然后运用嵌入将过程(b)切割的块文本转换为向量,LangChain供给了许多嵌入模型的接口,例如OpenAICohereHugging FaceWeaviate等,请参阅LangChain官网。

这边我运用Hugging FaceSentence Transformers,它供给了许多种pretrain模型,能够依据你的需求或运用情境挑选,我挑选,其他model细节能够看到HuggingFace。留意要先装置才干运用。all-MiniLM-L6-v2sentence-Tranformers

from langchain.embeddings import HuggingFaceEmbeddings
model_name = "sentence-transformers/all-MiniLM-L6-v2"
model_kwargs = {'device': 'cpu'}
embedding = HuggingFaceEmbeddings(
 model_name=model_name,
 model_kwargs=model_kwargs
)

d) 将Embedding结果汇入VectorDB

咱们会将嵌入后的结果存储在VectorDB中,常见的VectorDB包含ChromaPineconeFAISS等,这儿我运用Chroma来完成。ChromaLangChain整合得很好,能够直接运用 LangChain的接口进行操作。

# embed 并存储文本
# 指定 persist_directory 将会把嵌入存储到磁盘上。
from langchain.vectorstores import Chroma
persist_directory = 'db'
vectordb = Chroma.from_documents(documents=all_splits, embedding=embedding, persist_directory=persist_directory)

过程3. 启用LLM服务

RAG实操教程,LangChain + Llama2 | 发明你的个人LLM
你能够通过两种方法发动LLM模型并衔接到LangChain。一种是运用LangChain的LlamaCpp接口来完成,这时由LangChain协助你发动llama2服务;另一种方法是用其他方法搭建Llama2的API服务,例如运用llama.cpp的服务器发动API服务等。

a).运用LangChain的LlamaCpp

运用LlamaCpp接口加载model,它会帮你发动Llama的服务,这方法较简单,直接运用下面code就能够执行,model_path指定到你的模型中,比如中我运用量化往后的Llama2 Chat。留意这边要装置llama-cpp-python

from langchain.callbacks.manager import CallbackManager
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
from langchain_community.llms import LlamaCpp
model_path = "llama.cpp/models/llama-2-7b-chat/llama-2_q4.gguf"
​
llm = LlamaCpp(
  model_path=model_path,
  n_gpu_layers=100,
  n_batch=512,
  n_ctx=2048,
  f16_kv=True,
  callback_manager=CallbackManager([StreamingStdOutCallbackHandler()]),
  verbose=True,
)

RAG实操教程,LangChain + Llama2 | 发明你的个人LLM

您能够尝试进行测试,看看 llm 服务是否已发动:

llm("What is Chain known for?")

RAG实操教程,LangChain + Llama2 | 发明你的个人LLM

b). 运用 API 服务

假如你现已运用其他方法架起 LLMAPI 服务,或者是运用 openai 的 API 的话,你需求运用 LangChainChatOpenAI 接口。我这边演示是 llama.cppserver 服务,它供给了类别 OpenAI 的API,因此咱们能直接用同个接口来操作,以下是该接口的一些相关参数:

open_ai_key:由于并没有运用真实的 OpenAI API,因此能够随意填写。 openai_api_base:为模型API的Base URL max_tokens:规范模型回答的长度

from langchain.chat_models import ChatOpenAI
llm = ChatOpenAI(openai_api_key='None', openai_api_base='http://127.0.0.1:8080/v1')

过程4.设定你的Prompt

一些LLM能够运用特定的Prompt。例如,Llama可运用特殊token,细节能够参阅twitter。咱们能够运用ConditionalPromptSelector依据模型类型设定Prompt,如以下:

from langchain.chains import LLMChain
from langchain.chains.prompt_selector import ConditionalPromptSelector
from langchain.prompts import PromptTemplate
​
DEFAULT_LLAMA_SEARCH_PROMPT = PromptTemplate(
  input_variables=["question"],
  template="""<<SYS>> n You are an assistant tasked with improving Google search 
results. n <</SYS>> nn [INST] Generate THREE Google search queries that 
are similar to this question. The output should be a numbered list of questions 
and each should have a question mark at the end: nn {question} [/INST]""",
)
​
DEFAULT_SEARCH_PROMPT = PromptTemplate(
  input_variables=["question"],
  template="""You are an assistant tasked with improving Google search 
results. Generate THREE Google search queries that are similar to 
this question. The output should be a numbered list of questions and each 
should have a question mark at the end: {question}""",
)
​
QUESTION_PROMPT_SELECTOR = ConditionalPromptSelector(
  default_prompt=DEFAULT_SEARCH_PROMPT,
  conditionals=[(lambda llm: isinstance(llm, LlamaCpp), DEFAULT_LLAMA_SEARCH_PROMPT)],
)
​
prompt = QUESTION_PROMPT_SELECTOR.get_prompt(llm)

运用LLMChain将提示与llm衔接在一起,别的LangChain最近的更新采用了“替代”,当您看到其他文章中运用时请留意。invoke run run

llm_chain = LLMChain(prompt=prompt, llm=llm)
question = "What is china known for?"
llm_chain.invoke({"question": question})

过程5. 文本检索 查询LLM

RAG实操教程,LangChain + Llama2 | 发明你的个人LLM

咱们现已将 PDF 信息导入 DB,并发动了 LLM 服务,接下来要衔接整个 RAG 过程:

  • 用户发送 QA
  • 从 DB 中检索文本
  • 将 QA 与检索的文本结合发给 LLM
  • LLM 根据信息进行回答

首要要创立 Retriever,它能够依据非结构化的 QA 返回相应文件,LangChain 供给了多种方法,并整合第三方东西,现在有许多研讨评论如何根据 QA 查找对应文件。

请留意,已弃用的功能是运用 RetrievalQA 结合 Retriever、QA 和 llm。现在应该运用 ,假如看到其他文章中说到,请留意。VectorDBQA``RetrievalQA

retriever = vectordb.as_retriever()
​
qa = RetrievalQA.from_chain_type(
  llm=llm, 
  chain_type="stuff", 
  retriever=retriever, 
  verbose=True
)

过程6.运用你的RAG

到这儿咱们就串好整个RAG的流程,接下来咱们来问问Alison Hawk的信息(PDF纪录的虚拟人物称号)

query = "Tell me about Alison Hawk's career and age"
qa.invoke(query)

RAG实操教程,LangChain + Llama2 | 发明你的个人LLM

LLM现已获取了从数据库中取得的Alison Hawk上传的PDF文件,并且知道她是一位28岁的研讨员。

Jupyter 代码

from langchain.document_loaders import PyMuPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.llms import LlamaCpp
from langchain.chains import RetrievalQA
loader = PyMuPDFLoader("Virtual_characters.pdf")
PDF_data = loader.load()
text_splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=5)
all_splits = text_splitter.split_documents(PDF_data)
# Embed and store the texts
# Supplying a persist_directory will store the embeddings on disk
persist_directory = 'db'
model_name = "sentence-transformers/all-MiniLM-L6-v2"
model_kwargs = {'device': 'cpu'}
embedding = HuggingFaceEmbeddings(model_name=model_name,
                 model_kwargs=model_kwargs)
​
vectordb = Chroma.from_documents(documents=all_splits, embedding=embedding, persist_directory=persist_directory)
from langchain.callbacks.manager import CallbackManager
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
from langchain_community.llms import LlamaCpp
​
llm = LlamaCpp(
  model_path="llama-2_q4.gguf",
  n_gpu_layers=100,
  n_batch=512,
  n_ctx=2048,
  f16_kv=True,
  callback_manager=CallbackManager([StreamingStdOutCallbackHandler()]),
  verbose=True,
)
from langchain.chains import LLMChain
from langchain.chains.prompt_selector import ConditionalPromptSelector
from langchain.prompts import PromptTemplate
​
DEFAULT_LLAMA_SEARCH_PROMPT = PromptTemplate(
  input_variables=["question"],
  template="""<<SYS>> 
   You are a helpful assistant eager to assist with providing better Google search results.
   <</SYS>> 
  
   [INST] Provide an answer to the following question in 150 words. Ensure that the answer is informative, 
       relevant, and concise:
       {question} 
   [/INST]""",
)
​
DEFAULT_SEARCH_PROMPT = PromptTemplate(
  input_variables=["question"],
  template="""You are a helpful assistant eager to assist with providing better Google search results. 
     Provide an answer to the following question in about 150 words. Ensure that the answer is informative, 
     relevant, and concise: 
     {question}""",
)
​
QUESTION_PROMPT_SELECTOR = ConditionalPromptSelector(
  default_prompt=DEFAULT_SEARCH_PROMPT,
  conditionals=[(lambda llm: isinstance(llm, LlamaCpp), DEFAULT_LLAMA_SEARCH_PROMPT)],
)
​
prompt = QUESTION_PROMPT_SELECTOR.get_prompt(llm)
prompt
llm_chain = LLMChain(prompt=prompt, llm=llm)
question = "What is China known for?"
llm_chain.invoke({"question": question})
retriever = vectordb.as_retriever()
​
qa = RetrievalQA.from_chain_type(
  llm=llm, 
  chain_type="stuff", 
  retriever=retriever, 
  verbose=True
)
query = "Tell me about Alison Hawk's career and age"
qa.invoke(query)

相关文章