微信扫码
添加专属顾问
我要投稿
上一篇《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:2bfrom llama_index.llms.ollama import Ollamallm_ollama = Ollama(model="gemma:2b", request_timeout=600.0)# 配置HuggingFaceEmbeddings嵌入模型,这里我们用BAAI/bge-small-zh-v1.5from langchain_community.embeddings import HuggingFaceEmbeddingsembed_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-basefrom llama_index.core.postprocessor import SentenceTransformerRerankrerank_model_bge_base = SentenceTransformerRerank(model="./embed_model/bge-reranker-base",top_n=2)# 配置使用SpacyTextSplitterfrom llama_index.core.node_parser import LangchainNodeParserfrom langchain.text_splitter import SpacyTextSplitterspacy_text_splitter = LangchainNodeParser(SpacyTextSplitter(pipeline="zh_core_web_sm",chunk_size = 1024))Settings.llm = llm_ollamaSettings.embed_model = embed_model_bge_smallSettings.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 chromadbfrom llama_index.core import StorageContextfrom llama_index.vector_stores.chroma import ChromaVectorStoreSTORAGE_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 stst.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, SimpleDirectoryReaderDATA_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 indexdef 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 PromptTemplatetext_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:2bfrom llama_index.llms.ollama import Ollamallm_ollama = Ollama(model="gemma:2b", request_timeout=600.0)# 配置HuggingFaceEmbeddings嵌入模型,这里我们用BAAI/bge-small-zh-v1.5from langchain_community.embeddings import HuggingFaceEmbeddingsembed_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-basefrom llama_index.core.postprocessor import SentenceTransformerRerankrerank_model_bge_base = SentenceTransformerRerank(model="./embed_model/bge-reranker-base",top_n=2)# 配置使用SpacyTextSplitterfrom llama_index.core.node_parser import LangchainNodeParserfrom langchain.text_splitter import SpacyTextSplitterspacy_text_splitter = LangchainNodeParser(SpacyTextSplitter(pipeline="zh_core_web_sm",chunk_size = 1024))Settings.llm = llm_ollamaSettings.embed_model = embed_model_bge_smallSettings.text_splitter = spacy_text_splitter##################################### 第2步:配置向量数据库####################################import chromadbfrom llama_index.core import StorageContextfrom llama_index.vector_stores.chroma import ChromaVectorStoreSTORAGE_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 stst.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, SimpleDirectoryReaderDATA_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 indexdef load_index():# 直接从向量数据库读取索引index = VectorStoreIndex.from_vector_store(chroma_vector_store)return index##################################### 第5步:定制Prompt模板####################################from llama_index.core import PromptTemplatetext_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-08-21
2025-08-20
2025-09-07
2025-08-21
2025-08-19
2025-08-05
2025-09-16
2025-08-20
2025-10-02
2025-09-08
2025-10-29
2025-10-29
2025-10-29
2025-10-28
2025-10-28
2025-10-28
2025-10-27
2025-10-27