RAG实操教程,LangChain Llama2 | 发明你的个人LLM
本文将逐渐辅导您创立自己的RAG(检索增强生成)系统,使您能够上传自己的PDF文件并向LLM问询有关PDF的信息。本教程侧重于图中蓝色部分,即暂时不触及Gradio(想了解已接入Gradio
的,请参阅官网)。相关技能栈包含以下内容:
-
LLM:
Llama2
- LLM API:
llama.cpp service
- Langchain:
-
Vector DB:
ChromaDB
-
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. 读入文件处理并导入数据库。
首要咱们要将外部信息处理后,放到 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
供给了许多嵌入模型的接口,例如OpenAI
、Cohere
、Hugging Face
、Weaviate
等,请参阅LangChain
官网。
这边我运用Hugging Face
的Sentence 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
包含Chroma
、Pinecone
和FAISS
等,这儿我运用Chroma
来完成。Chroma
与LangChain
整合得很好,能够直接运用 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服务
你能够通过两种方法发动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,
)
您能够尝试进行测试,看看 llm 服务是否已发动:
llm("What is Chain known for?")
b). 运用 API 服务
假如你现已运用其他方法架起 LLM
的 API
服务,或者是运用 openai
的 API 的话,你需求运用 LangChain
的 ChatOpenAI
接口。我这边演示是 llama.cpp
的 server
服务,它供给了类别 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
咱们现已将 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)
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)