微信扫码
添加专属顾问
我要投稿
上一篇《10行代码!实现本地大模型RAG智能问答(1)》,我们基于LlamaIndex + Ollama + Streamlit,构建了一个可本地部署和运行的RAG智能问答系统。
本篇,我们将进一步对系统进行完善,包括:使用更好的嵌入模型和文本分割器,通过向量数据库将文档索引持久化,定制Prompt模板,以及采用重排(rerank)等技术以提升检索和回答的准确性。
系统技术栈如下表所示:
数据框架 | LlamaIndex |
前端框架 | Streamlit |
本地大模型工具 | Ollama |
大语言模型 | Gemma 2B |
嵌入模型 | BAAI/bge-small-zh-v1.5 |
重排模型 | BAAI/bge-reranker-base |
分词器 | SpacyTextSplitter |
向量数据库 | Chroma |
接下来,我将详细讲解,如何通过6个步骤,100多行代码,更好地实现在笔记本电脑上可运行的,本地大模型RAG智能问答系统。
文末附上全部源代码。
第1步:下载和配置本地模型
首先,下载安装Ollama,并通过Ollama下载大语言模型,这次我们选用速度更快、效果较好的Gemma 2B模型,共1.7GB。
ollama pull gemma:2b
关于如何安装和使用Ollama,请参考此前的文章:《在笔记本电脑上,轻松使用Gemma模型》。
然后,我们下载用到的嵌入模型。
BAAI/bge-small-zh-v1.5 用于Embedding
BAAI/bge-reranker-base 用于Rerank
为了从Hugging Face下载各种模型,我们使用官方提供的命令行工具huggingface-cli,并设置Hugging Face国内镜像网址。
pip install -U huggingface_hubexport HF_ENDPOINT=https://hf-mirror.com
我们创建一个本地项目RAGDemo,然后新建一个目录embed_model,用于保存下载的嵌入模型。
mkdir embed_model && cd embed_modelhuggingface-cli download --resume-download BAAI/bge-small-zh-v1.5 --local-dir bge-small-zh-v1.5huggingface-cli download --resume-download BAAI/bge-reranker-base --local-dir bge-reranker-base
接下来,确保已经安装LlamaIndex和LangChain框架。因为我们通过LlamaIndex与LangChain的集成,来使用Hugging Face上下载的嵌入模型。
pip install llama-indexpip install langchain
然后,创建一个文件app.py,编写如下代码,对用到的各个模型进行配置。
from llama_index.core import Settings
# 配置ollama的LLM模型,这里我们用gemma:2b
from llama_index.llms.ollama import Ollama
llm_ollama = Ollama(model="gemma:2b", request_timeout=600.0)
# 配置HuggingFaceEmbeddings嵌入模型,这里我们用BAAI/bge-small-zh-v1.5
from langchain_community.embeddings import HuggingFaceEmbeddings
embed_model_bge_small = HuggingFaceEmbeddings(
model_name="./embed_model/bge-small-zh-v1.5",
model_kwargs = {"device": "cpu"},
encode_kwargs = {"normalize_embeddings": True}
)
# 配置Rerank模型,这里我们用BAAI/bge-reranker-base
from llama_index.core.postprocessor import SentenceTransformerRerank
rerank_model_bge_base = SentenceTransformerRerank(
model="./embed_model/bge-reranker-base",
top_n=2
)
# 配置使用SpacyTextSplitter
from llama_index.core.node_parser import LangchainNodeParser
from langchain.text_splitter import SpacyTextSplitter
spacy_text_splitter = LangchainNodeParser(SpacyTextSplitter(
pipeline="zh_core_web_sm",
chunk_size = 1024
))
Settings.llm = llm_ollama
Settings.embed_model = embed_model_bge_small
Settings.text_splitter = spacy_text_splitter
当完成各个模型的配置后,将它们挂载到LlamaIndex的Settings上,全局有效。
大语言模型llm:使用Ollama和gemma:2b
嵌入模型embed_model:使用bge-small-zh-v1.5
分词器text_splitter:原本LlamaIndex默认使用SentencSplitter,我们改用对中文支持更好的SpacyTextSplitter.
第2步:配置向量数据库
LlamaIndex支持很多向量数据库,我们选择对开发者友好易用的Chroma。
首先,安装Chroma向量数据库,及相应的LlamaIndex组件。
pip install chromadbpip install llama-index-vector-stores-chroma
然后,继续编写以下代码。其中,索引将保存在storage目录下。后续运行代码后,我们可以看到,storage目录下会生成一个chroma.sqlite3文件。
import chromadb
from llama_index.core import StorageContext
from llama_index.vector_stores.chroma import ChromaVectorStore
STORAGE_DIR = "./storage"# 定义索引保存的目录
db = chromadb.PersistentClient(path=STORAGE_DIR)
chroma_collection = db.get_or_create_collection("think")
chroma_vector_store = ChromaVectorStore(chroma_collection=chroma_collection)
chroma_storage_context = StorageContext.from_defaults(vector_store=chroma_vector_store)
第3步:初始化Streamlit Web应用
首先,确保已安装Streamlit。这是一个基于Python,快速构建数据类Web 应用的框架。
pip install streamlit
然后,编写以下代码完成初始化配置。你可以定制界面上显示的内容。
import streamlit as st
st.set_page_config(page_title="本地大模型知识库RAG应用", page_icon="?", layout="centered", initial_sidebar_state="auto", menu_items=None)
st.title("本地大模型知识库RAG应用")
st.info("By 大卫", icon="?")
if "messages" not in st.session_state.keys(): # 初始化聊天历史记录
st.session_state.messages = [
{"role": "assistant", "content": "关于文档里的内容,请随便问"}
]
第4步:加载文档和建立索引
首先,创建一个目录data,把你的知识库文件放在此目录下。我用的是《大卫说流程》系列文章的DOCX文件。你可以使用自己电脑上的DOCX、PDF等文件。
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
DATA_DIR = "./data"# 知识库文档所在目录
@st.cache_resource(show_spinner=False)
def load_data():
with st.spinner(text="加载文档并建立索引,需要1-2分钟"):
# 将指定目录下的文档建立索引,保存到向量数据库
documents = SimpleDirectoryReader(input_dir=DATA_DIR, recursive=True).load_data()
index = VectorStoreIndex.from_documents(
documents, storage_context=chroma_storage_context)
return index
def load_index():
# 直接从向量数据库读取索引
index = VectorStoreIndex.from_vector_store(chroma_vector_store)
return index
其中,load_data用于第一次运行程序时,通过加载data目录下的文件,建立索引,并保存到Chroma向量数据库中。
后续再次运行程序的话,无需再加载原文档和生成索引,只需要读取向量数据库中保存的索引即可,这时应改为调用load_index。
第5步:定制Prompt模板
LlamaIndex提供了一系列默认模板。但是模板是英文的,很容易导致大模型回答中文问题时,也使用英文回答。
因此,我们需要对模板进行定制,且可以提供更多的信息与约束条件。
from llama_index.core import PromptTemplate
text_qa_template_str = (
"以下为上下文信息\n"
"---------------------\n"
"{context_str}\n"
"---------------------\n"
"请根据上下文信息回答我的问题或回复我的指令。前面的上下文信息可能有用,也可能没用,你需要从我给出的上下文信息中选出与我的问题最相关的那些,来为你的回答提供依据。回答一定要忠于原文,简洁但不丢信息,不要胡乱编造。我的问题或指令是什么语种,你就用什么语种回复。\n"
"问题:{query_str}\n"
"你的回复:"
)
text_qa_template = PromptTemplate(text_qa_template_str)
refine_template_str = (
"这是原本的问题:{query_str}\n"
"我们已经提供了回答: {existing_answer}\n"
"现在我们有机会改进这个回答 "
"使用以下更多上下文(仅当需要用时)\n"
"------------\n"
"{context_msg}\n"
"------------\n"
"根据新的上下文, 请改进原来的回答。"
"如果新的上下文没有用, 直接返回原本的回答。\n"
"改进的回答: "
)
refine_template = PromptTemplate(refine_template_str)
一般来说,我们向大模型提问,为了得到高质量的回答,通常会进行多次交互。
我们定制了两个模板,分别是text_qa_template和refine_template。前者用于第一次与大模型交互。后者用于把第一次大模型给出的回答,加上更多上下文信息,给到大模型,以改进(refine)回答的质量。
第6步:创建检索引擎,并提问
# 仅第一次运行时使用load_data建立索引,再次运行使用load_index读取索引
index = load_data();
#index = load_index();
# 初始化检索引擎
if "query_engine" not in st.session_state.keys():
query_engine = index.as_query_engine(
text_qa_template=text_qa_template,
refine_template=refine_template,
similarity_top_k=6,
node_postprocessors=[rerank_model_bge_base],
response_mode="compact",
verbose=True
)
st.session_state.query_engine = query_engine
# 提示用户输入问题,并将问题添加到消息历史记录
if prompt := st.chat_input("Your question"):
st.session_state.messages.append({"role": "user", "content": prompt})
# 显示此前的问答记录
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.write(message["content"])
# 生成回答
if st.session_state.messages[-1]["role"] != "assistant":
with st.chat_message("assistant"):
with st.spinner("Thinking..."):
response = st.session_state.query_engine.query(prompt)
st.write(response.response)
message = {"role": "assistant", "content": response.response}
st.session_state.messages.append(message)
从代码中可以看到,创建检索引擎(Query Engine)时,我们配置了相应的Prompt模板和Rerank模型。
系统会检索相似度最高的前6条(TopK=6)信息,通过Rerank模型重排之后,选取前2条(TopN=2),发送给大模型进行回答,最终输出refine后的答案。
现在,我们可以通过以下命令,运行app.py程序。
streamlit run app.py
浏览器将自动打开:http://localhost:8501,系统会需要一些时间加载嵌入模型、加载文档并生成索引。
稍等片刻,你就可以提问了。
比如,我提了这个问题:“流程设计有哪些步骤”,系统准确的回答了我的文档中提到的流程设计五步法。
在我的笔记本电脑上(MacBook Pro,CPU 2 GHz 四核Intel Core i5,内存16GB),回答用时大约1分钟。
至此,我们仅通过100多行代码,成功地实现了一个使用本地知识库、本地大模型、本地嵌入模型,对中文文档解析能力更强,答案生成速度更快、效果更好的大模型RAG智能问答系统。
项目最终的目录结构如下:
以下为app.py全部源代码,供你参考。如有问题,欢迎留言讨论。
###################################
#
# 第1步:配置本地模型
#
###################################
from llama_index.core import Settings
# 配置ollama的LLM模型,这里我们用gemma:2b
from llama_index.llms.ollama import Ollama
llm_ollama = Ollama(model="gemma:2b", request_timeout=600.0)
# 配置HuggingFaceEmbeddings嵌入模型,这里我们用BAAI/bge-small-zh-v1.5
from langchain_community.embeddings import HuggingFaceEmbeddings
embed_model_bge_small = HuggingFaceEmbeddings(
model_name="./embed_model/bge-small-zh-v1.5",
model_kwargs = {"device": "cpu"},
encode_kwargs = {"normalize_embeddings": True}
)
# 配置Rerank模型,这里我们用BAAI/bge-reranker-base
from llama_index.core.postprocessor import SentenceTransformerRerank
rerank_model_bge_base = SentenceTransformerRerank(
model="./embed_model/bge-reranker-base",
top_n=2
)
# 配置使用SpacyTextSplitter
from llama_index.core.node_parser import LangchainNodeParser
from langchain.text_splitter import SpacyTextSplitter
spacy_text_splitter = LangchainNodeParser(SpacyTextSplitter(
pipeline="zh_core_web_sm",
chunk_size = 1024
))
Settings.llm = llm_ollama
Settings.embed_model = embed_model_bge_small
Settings.text_splitter = spacy_text_splitter
###################################
#
# 第2步:配置向量数据库
#
###################################
import chromadb
from llama_index.core import StorageContext
from llama_index.vector_stores.chroma import ChromaVectorStore
STORAGE_DIR = "./storage"# 定义索引保存的目录
db = chromadb.PersistentClient(path=STORAGE_DIR)
chroma_collection = db.get_or_create_collection("think")
chroma_vector_store = ChromaVectorStore(chroma_collection=chroma_collection)
chroma_storage_context = StorageContext.from_defaults(vector_store=chroma_vector_store)
###################################
#
# 第3步:初始化Streamlit Web应用
#
###################################
import streamlit as st
st.set_page_config(page_title="本地大模型知识库RAG应用", page_icon="?", layout="centered", initial_sidebar_state="auto", menu_items=None)
st.title("本地大模型知识库RAG应用")
st.info("By 大卫", icon="?")
if "messages" not in st.session_state.keys(): # 初始化聊天历史记录
st.session_state.messages = [
{"role": "assistant", "content": "关于文档里的内容,请随便问"}
]
###################################
#
# 第4步:加载文档建立索引
#
###################################
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
DATA_DIR = "./data"# 知识库文档所在目录
@st.cache_resource(show_spinner=False)
def load_data():
with st.spinner(text="加载文档并建立索引,需要1-2分钟"):
# 将指定目录下的文档建立索引,保存到向量数据库
documents = SimpleDirectoryReader(input_dir=DATA_DIR, recursive=True).load_data()
index = VectorStoreIndex.from_documents(
documents, storage_context=chroma_storage_context)
return index
def load_index():
# 直接从向量数据库读取索引
index = VectorStoreIndex.from_vector_store(chroma_vector_store)
return index
###################################
#
# 第5步:定制Prompt模板
#
###################################
from llama_index.core import PromptTemplate
text_qa_template_str = (
"以下为上下文信息\n"
"---------------------\n"
"{context_str}\n"
"---------------------\n"
"请根据上下文信息回答我的问题或回复我的指令。前面的上下文信息可能有用,也可能没用,你需要从我给出的上下文信息中选出与我的问题最相关的那些,来为你的回答提供依据。回答一定要忠于原文,简洁但不丢信息,不要胡乱编造。我的问题或指令是什么语种,你就用什么语种回复。\n"
"问题:{query_str}\n"
"你的回复: "
)
text_qa_template = PromptTemplate(text_qa_template_str)
refine_template_str = (
"这是原本的问题: {query_str}\n"
"我们已经提供了回答: {existing_answer}\n"
"现在我们有机会改进这个回答 "
"使用以下更多上下文(仅当需要用时)\n"
"------------\n"
"{context_msg}\n"
"------------\n"
"根据新的上下文, 请改进原来的回答。"
"如果新的上下文没有用, 直接返回原本的回答。\n"
"改进的回答: "
)
refine_template = PromptTemplate(refine_template_str)
###################################
#
# 第6步:创建检索引擎,并提问
#
###################################
# 仅第一次运行时使用load_data建立索引,再次运行使用load_index读取索引
index = load_data();
#index = load_index();
# 初始化检索引擎
if "query_engine" not in st.session_state.keys():
query_engine = index.as_query_engine(
text_qa_template=text_qa_template,
refine_template=refine_template,
similarity_top_k=6,
node_postprocessors=[rerank_model_bge_base],
response_mode="compact",
verbose=True
)
st.session_state.query_engine = query_engine
# 提示用户输入问题,并将问题添加到消息历史记录
if prompt := st.chat_input("Your question"):
st.session_state.messages.append({"role": "user", "content": prompt})
# 显示此前的问答记录
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.write(message["content"])
# 生成回答
if st.session_state.messages[-1]["role"] != "assistant":
with st.chat_message("assistant"):
with st.spinner("Thinking..."):
response = st.session_state.query_engine.query(prompt)
st.write(response.response)
message = {"role": "assistant", "content": response.response}
st.session_state.messages.append(message)
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费场景POC验证,效果验证后签署服务协议。零风险落地应用大模型,已交付160+中大型企业
2025-02-01
2025-01-01
2024-07-25
2025-02-04
2024-08-13
2024-04-25
2024-06-13
2024-08-21
2024-09-23
2024-04-26