AI知识库 AI知识库

53AI知识库

学习大模型的前沿技术与行业应用场景


在笔记本电脑上,10行代码!实现本地大模型RAG智能问答(2)
发布日期:2024-04-23 07:21:44 浏览次数: 2171

上一篇《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


接下来,确保已经安装LlamaIndexLangChain框架。因为我们通过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:使用Ollamagemma: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 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_templaterefine_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 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+中大型企业

年轻人!来一起搞AI吗?

如果你看见AI对商业世界的变革,欢迎来和我们一起探索~

岗位:销售经理

查看详情

岗位:项目经理

查看详情

岗位:产品经理

查看详情

岗位:测试工程师

查看详情

联系我们

售前咨询
186 6662 7370
产品演示
185 8882 0121

微信扫码

与创始人交个朋友

回到顶部

 
扫码咨询