本文将逐步指导您创建自己的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
环境完成示例。
arduino复制代码 # 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
py复制代码 from langchain.document_loaders import PyMuPDFLoader
loader = PyMuPDFLoader("LangChain/Virtual_characters.pdf")
PDF_data = loader.load()
文本分割器会将文档或文字分割成一个个 chunk,用以预防文档的信息超过 LLM 的 tokens,有一些研究在探讨如何将 chunk 优化。我们后续文章中讨论。
这两种常用的工具之间的区别在于,如果块大小超过指定阈值,它们会递归地将文本分割为更小的块。LangChain提供这两种方式,并且主要参数如下:
py复制代码 - RecursiveCharacterTextSplitter
- CharacterTextSplitter
- chunk size:决定分割文字时每个内存块中的最大字元数。它指定每个内存块的大小或长度。
- chunk_overlap:决定分割文字时连续内存块之间重叠的字元数。它指定前一个内存块的多少应包含在下一个内存块中。
py复制代码 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
py复制代码 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
的接口进行操作。
py复制代码 # 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
py复制代码 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
:规范模型回答的长度
ini复制代码 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
,如以下:
ini复制代码 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>> \n\n [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: \n\n {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
ini复制代码 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
ini复制代码 retriever = vectordb.as_retriever()
qa = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
verbose=True
)
步骤6.使用你的RAG
到这里我们就串好整个RAG的流程,接下来我们来问问Alison Hawk的信息(PDF纪录的虚拟人物名称)
ini复制代码 query = "Tell me about Alison Hawk's career and age"
qa.invoke(query)
LLM已经获取了从数据库中取得的Alison Hawk上传的PDF文件,并且知道她是一位28岁的研究员。
Jupyter 代码
python复制代码 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)
如何学习大模型 AI ?
由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。
但是具体到个人,只能说是:
“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。
这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费
】
第一阶段(10天):初阶应用
该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。
- 大模型 AI 能干什么?
- 大模型是怎样获得「智能」的?
- 用好 AI 的核心心法
- 大模型应用业务架构
- 大模型应用技术架构
- 代码示例:向 GPT-3.5 灌入新知识
- 提示工程的意义和核心思想
- Prompt 典型构成
- 指令调优方法论
- 思维链和思维树
- Prompt 攻击和防范
- …
第二阶段(30天):高阶应用
该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。
- 为什么要做 RAG
- 搭建一个简单的 ChatPDF
- 检索的基础概念
- 什么是向量表示(Embeddings)
- 向量数据库与向量检索
- 基于向量检索的 RAG
- 搭建 RAG 系统的扩展知识
- 混合检索与 RAG-Fusion 简介
- 向量模型本地部署
- …
第三阶段(30天):模型训练
恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。
到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?
- 为什么要做 RAG
- 什么是模型
- 什么是模型训练
- 求解器 & 损失函数简介
- 小实验2:手写一个简单的神经网络并训练它
- 什么是训练/预训练/微调/轻量化微调
- Transformer结构简介
- 轻量化微调
- 实验数据集的构建
- …
第四阶段(20天):商业闭环
对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。
- 硬件选型
- 带你了解全球大模型
- 使用国产大模型服务
- 搭建 OpenAI 代理
- 热身:基于阿里云 PAI 部署 Stable Diffusion
- 在本地计算机运行大模型
- 大模型的私有化部署
- 基于 vLLM 部署大模型
- 案例:如何优雅地在阿里云私有部署开源大模型
- 部署一套开源 LLM 项目
- 内容安全
- 互联网信息服务算法备案
- …
学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。
如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。