1.RAG技术概述
1.1定义
1.2工作原理
2.LlamaIndex概述
- 下面开始实践部分
3.LangChain RAG实践
因为llamaindex使用本地需要冗杂的配置,并且下载了很久都没有反应,尤其是调用(时好时坏,我怀疑是NLTK那里出问题了,但是没有证据),在学习完成RAG原理后我使用langchain实现了这个章节,这里embedding用到了豆包的embedding模型,需要自己去火山方舟引擎申请你自己的id和key,下一个章节我会使用llamaindex实现RAG。
具体流程:
Loading:文档加载器把Documents 加载为以LangChain能够读取的形式。
Splitting:文本分割器把Documents 切分为指定大小的分割,我把它们称为“文档块”或者“文档片”。
Storage:将上一步中分割好的“文档块”以“嵌入”(Embedding)的形式存储到向量数据库(Vector DB)中,形成一个个的“嵌入片”。
Retrieval:应用程序从存储中检索分割后的文档(例如通过比较余弦相似度,找到与输入问题类似的嵌入片)。
Output:把问题和相似的嵌入片传递给语言模型(LLM),使用包含问题和检索到的分割的提示生成答案。
3.1配置环境
- 这里安装环境error不要紧张,如果出现错误可以后期自行修改,因为这个不是严格按照版本要求,如果要求其他的包比如PyPDF或者Doc2txt可以直接使用
pip install
进行下载
pip install --upgrade 'volcengine-python-sdk[ark]'
pip install langchain==0.3.7
pip install langchain_community==0.3.7
pip install langchain_openai==0.2.8
pip install unstructured
pip install unstructured[md]
pip install python-magic
pip install python-magic-bin
#如果您使用linux系统,需要使用apt install libmagic1
#https://github.com/ahupp/python-magic/issues/313
pip install numpy==1.26.3
3.2数据准备
自行准备下载md文档并设置路径:
https://github.com/InternLM/xtuner
3.3文本分割
接下来需要将加载的文本分割成更小的块,以便进行嵌入和向量存储。这个步骤中,我们使用 LangChain中的RecursiveCharacterTextSplitter 来分割文本。
其中chunk_size=200表示分割后的每个文本块大小为200个字符,chunk_overlap=30表示相邻的文本块有30个字符的重复,以确保语义连贯性和上下文检索。
3.4向量数据库存储
紧接着,我们将这些分割后的文本转换成嵌入的形式,并将其存储在一个向量数据库中。在这个例子中,我们会使用豆包Embedding来生成嵌入,然后使用 Qdrant 这个向量数据库来存储嵌入(这里需要pip install qdrant-client)。
同样需要在火山引擎中创建推理接入点,并且需要获取ID作为model(观沧海是本人在稀土掘金的账号,不存在版权问题,如有别人引用请联系本人或者标明引用来源)
- 如果对于火山引擎有不懂的可以查看引用链接中本人对于langchain的其他文章中有介绍。
火山方舟引擎官网:
3.5相关信息的获取
当内部文档存储到向量数据库之后,我们需要根据问题和任务来提取最相关的信息。此时,信息提取的基本方式就是把问题也转换为向量,然后去和向量数据库中的各个向量进行比较,提取最接近的信息。
在这里,我们正在处理的是文本数据,目标是建立一个问答系统,需要从语义上理解和比较问题可能的答案。因此,我建议使用余弦相似度作为度量标准。通过比较问题和答案向量在语义空间中的方向,可以找到与提出的问题最匹配的答案。
在这一步的代码部分,我们会创建一个聊天模型。然后需要创建一个 RetrievalQA 链,它是一个检索式问答模型,用于生成问题的答案。
在RetrievalQA 链中有下面两大重要组成部分。
- LLM是大模型,负责回答问题。
- retriever(vectorstore.as_retriever())负责根据问题检索相关的文档,找到具体的“嵌入片”。这些“嵌入片”对应的“文档块”就会作为知识信息,和问题一起传递进入大模型。
3.6生成回答并且展示
现在我们已经为后续的步骤做好了准备,下一步就是接收来自系统用户的具体问题,并根据问题检索信息,生成回答。
代码:
# 1.Load 导入Document Loaders
import os
from langchain_community.document_loaders import DirectoryLoader
from typing import Dict, List, Any
from langchain.embeddings.base import Embeddings
# from langchain.pydantic_v1 import BaseModel
from pydantic import BaseModel
from volcenginesdkarkruntime import Arkbase_dir = "/data/coding/data" # 文档的存放目录
api_key = "" #你的豆包APIKEY
base_url="https://ark.cn-beijing.volces.com/api/v3"
model = 'ep-20241111110355-2tp82' #YOURMODELID
os.environ["LLM_MODELEND"] = "ep-20241104131149-csxf9" # 你的Doubao-pro-32k模型# 加载Documentsdocuments = []
loader = DirectoryLoader(base_dir,show_progress=True) #默认读取全部非隐藏文件
documents = loader.load()from langchain_core.vectorstores import InMemoryVectorStore
from langchain_text_splitters import RecursiveCharacterTextSplittertext_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)class DoubaoEmbeddings(BaseModel, Embeddings):client: Ark = Noneapi_key: str = api_keymodel: strdef __init__(self, **data: Any):super().__init__(**data)if self.api_key == "":self.api_key = os.environ["OPENAI_API_KEY"]self.client = Ark(base_url=base_url,api_key=self.api_key)def embed_query(self, text: str) -> List[float]:"""生成输入文本的 embedding.Args:texts (str): 要生成 embedding 的文本.Return:embeddings (List[float]): 输入文本的 embedding,一个浮点数值列表."""embeddings = self.client.embeddings.create(model=self.model, input=text)return embeddings.data[0].embeddingdef embed_documents(self, texts: List[str]) -> List[List[float]]:return [self.embed_query(text) for text in texts]class Config:arbitrary_types_allowed = Truesplits = text_splitter.split_documents(documents)
vectorstore = InMemoryVectorStore.from_documents(documents=splits, embedding=DoubaoEmbeddings(model=model)
)retriever = vectorstore.as_retriever()from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplatesystem_prompt = ("You are an assistant for question-answering tasks. ""Use the following pieces of retrieved context to answer ""the question. If you don't know the answer, say that you ""don't know. Use three sentences maximum and keep the ""answer concise.""\n\n""{context}"
)prompt = ChatPromptTemplate.from_messages([("system", system_prompt),("human", "{input}"),]
)from langchain_openai import ChatOpenAI # ChatOpenAI模型
# 实例化一个大模型工具 - Doubao-pro-32k
llm = ChatOpenAI(api_key=api_key,base_url=base_url,model=os.environ["LLM_MODELEND"], temperature=0)question_answer_chain = create_stuff_documents_chain(llm, prompt)
rag_chain = create_retrieval_chain(retriever, question_answer_chain)results = rag_chain.invoke({"input": "xtuner是什么"})print(results["answer"])
- 效果如下:
4.LlamaIndex RAG实践
4.1配置基础环境
- 创建开发机–>30%A100–>
Cuda12.0-conda
conda create -n llamaindex python=3.10
conda env list #查看环境
conda activate llamaindex
pip install einops==0.7.0 protobuf==5.26.1
conda activate llamaindex
pip install llama-index==0.11.20
pip install llama-index-llms-replicate==0.3.0
pip install llama-index-llms-openai-like==0.2.0
pip install llama-index-embeddings-huggingface==0.3.1
pip install llama-index-embeddings-instructor==0.2.1
pip install torch==2.5.0 torchvision==0.20.0 torchaudio==2.5.0 --index-url https://download.pytorch.org/whl/cu121
- 最后一个环境如果不能下载也没有关系,因为我们不跑本地模型,下载那一步如果能够成功则使用本地,如果不行(和我一样)就使用豆包的embedding模型
4.2下载Sentence Transformer
- 注意这些路径要求自己能看懂并且修改,否则很麻烦,你也看不懂代码,最重要是去理解,有什么问题多问
4.2.1常规下载(大概率报错)
cd ~
mkdir llamaindex_demo
mkdir model
cd ~/llamaindex_demo
touch download_hf.py
download_hf.py
import os# 设置环境变量
os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'# 下载模型
os.system('huggingface-cli download --resume-download sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 --local-dir /root/model/sentence-transformer')
cd /root/llamaindex_demo
conda activate llamaindex
python download_hf.py
4.2.2git lfs下载
sudo apt update
sudo apt install git-lfs
git lfs install
cd /root/model/
git clone https://www.modelscope.cn/Ceceliachenen/paraphrase-multilingual-MiniLM-L12-v2.git
mv paraphrase-multilingual-MiniLM-L12-v2 sentence-transformer
4.2.3无需下载
- 在之后我会使用豆包embedding模型,详见4.5节内容
4.3下载NLTK
我们在使用开源词向量模型构建开源词向量的时候,需要用到第三方库 nltk 的一些资源。正常情况下,其会自动从互联网上下载,但可能由于网络原因会导致下载中断,此处我们可以从国内仓库镜像地址下载相关资源,保存到服务器上。 我们用以下命令下载 nltk 资源并解压到服务器上:
cd /root
git clone https://gitee.com/yzy0612/nltk_data.git --branch gh-pages
cd nltk_data
mv packages/* ./
cd tokenizers
unzip punkt.zip
cd ../taggers
unzip averaged_perceptron_tagger.zip
之后使用时服务器即会自动使用已有资源,无需再次下载
4.4是否使用LlamaIndex前后对比
4.4.1不使用LlamaIndex
浦语官网和硅基流动都提供了InternLM的类OpenAI接口格式的免费的 API,可以访问以下两个了解两个 API 的使用方法和 Key。
浦语官方 API:https://internlm.intern-ai.org.cn/api/document
硅基流动:https://cloud.siliconflow.cn/models?mfs=internlm
from openai import OpenAIbase_url = "https://internlm-chat.intern-ai.org.cn/puyu/api/v1/"
api_key = "sk-请填写准确的 token!"
model="internlm2.5-latest"# base_url = "https://api.siliconflow.cn/v1"
# api_key = "sk-请填写准确的 token!"
# model="internlm/internlm2_5-7b-chat"client = OpenAI(api_key=api_key , base_url=base_url,
)chat_rsp = client.chat.completions.create(model=model,messages=[{"role": "user", "content": "xtuner是什么?"}],
)for choice in chat_rsp.choices:print(choice.message.content)
4.4.2使用LlamaIndex
- 这是一个拉取命令,你需要将其移动到合适的文件夹下
git clone https://github.com/InternLM/xtuner.git
import os
os.environ['NLTK_DATA'] = '/root/nltk_data'from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.core.settings import Settings
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.legacy.callbacks import CallbackManager
from llama_index.llms.openai_like import OpenAILike# Create an instance of CallbackManager
callback_manager = CallbackManager()api_base_url = "https://internlm-chat.intern-ai.org.cn/puyu/api/v1/"
model = "internlm2.5-latest"
api_key = "请填写 API Key"# api_base_url = "https://api.siliconflow.cn/v1"
# model = "internlm/internlm2_5-7b-chat"
# api_key = "请填写 API Key"llm =OpenAILike(model=model, api_base=api_base_url, api_key=api_key, is_chat_model=True,callback_manager=callback_manager)#初始化一个HuggingFaceEmbedding对象,用于将文本转换为向量表示
embed_model = HuggingFaceEmbedding(
#指定了一个预训练的sentence-transformer模型的路径model_name="/root/model/sentence-transformer"
)
#将创建的嵌入模型赋值给全局设置的embed_model属性,
#这样在后续的索引构建过程中就会使用这个模型。
Settings.embed_model = embed_model#初始化llm
Settings.llm = llm#从指定目录读取所有文档,并加载数据到内存中
documents = SimpleDirectoryReader("/root/llamaindex_demo/data").load_data()
#创建一个VectorStoreIndex,并使用之前加载的文档来构建索引。
# 此索引将文档转换为向量,并存储这些向量以便于快速检索。
index = VectorStoreIndex.from_documents(documents)
# 创建一个查询引擎,这个引擎可以接收查询并返回相关文档的响应。
query_engine = index.as_query_engine()
response = query_engine.query("xtuner是什么?")print(response)
4.5InternLM+豆包Embedding
- 注意修改代码中你的路径!以及模型id
import os
os.environ['NLTK_DATA'] = '/data/coding/nltk_data'
em_model = 'ep-20241111110355-2tp82' #这里是你的豆包embedding 模型id
doubao_api_key = '' #你的豆包api_key
doubao_api_url = "https://ark.cn-beijing.volces.com/api/v3"
api_base_url = "https://internlm-chat.intern-ai.org.cn/puyu/api/v1/"
model = "internlm2.5-latest"
api_key = "" #你的浦语大模型api_key# api_base_url = "https://api.siliconflow.cn/v1"
# model = "internlm/internlm2_5-7b-chat"
# api_key = "请填写 API Key"from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.core.settings import Settings
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.legacy.callbacks import CallbackManager
from llama_index.llms.openai_like import OpenAILike
from pydantic import BaseModel
from volcenginesdkarkruntime import Ark
# from langchain.embeddings.base import Embeddings
from typing import Dict, List, Any
from llama_index.core.base.embeddings.base import BaseEmbedding # 导入正确的 BaseEmbedding# 定义自定义嵌入类,继承自 BaseEmbedding
class DoubaoEmbeddings(BaseEmbedding,BaseModel):"""自定义豆包嵌入类,继承自 BaseEmbedding,并实现所需的抽象方法。"""client: Ark = Noneapi_key: str = doubao_api_key # 你的 API 密钥model: str = em_modeldef __init__(self, **kwargs: Any):"""初始化豆包嵌入类。参数:- model: 模型 ID"""super().__init__(**kwargs)if self.api_key == "":self.api_key = os.environ["OPENAI_API_KEY"]self.client = Ark(base_url=doubao_api_url,api_key=self.api_key)def _get_query_embedding(self, query: str) -> List[float]:"""实现查询文本的嵌入生成方法。参数:- query: 输入的查询文本返回:- 返回一个浮点数列表,表示文本嵌入"""embeddings = self.client.embeddings.create(model=self.model, input=query)return embeddings.data[0].embeddingdef _get_text_embedding(self, text: str) -> List[float]:"""实现普通文本的嵌入生成方法。参数:- text: 输入的普通文本返回:- 返回一个浮点数列表,表示文本嵌入"""return self._get_query_embedding(text)async def _aget_query_embedding(self, query: str) -> List[float]:"""异步实现查询文本的嵌入生成方法。参数:- query: 输入的查询文本返回:- 返回一个浮点数列表,表示文本嵌入"""return self._get_query_embedding(query)# Create an instance of CallbackManager
callback_manager = CallbackManager()
llm =OpenAILike(model=model, api_base=api_base_url, api_key=api_key, is_chat_model=True,callback_manager=callback_manager)
#初始化一个HuggingFaceEmbedding对象,用于将文本转换为向量表示
# embed_model = HuggingFaceEmbedding(
# #指定了一个预训练的sentence-transformer模型的路径
# model_name="/root/model/sentence-transformer"
# )
#将创建的嵌入模型赋值给全局设置的embed_model属性,
#这样在后续的索引构建过程中就会使用这个模型。
# Settings.embed_model = embed_model
Settings.embed_model = DoubaoEmbeddings()
#初始化llm
Settings.llm = llm#从指定目录读取所有文档,并加载数据到内存中
documents = SimpleDirectoryReader("/data/coding/data").load_data()#创建一个VectorStoreIndex,并使用之前加载的文档来构建索引。
# 此索引将文档转换为向量,并存储这些向量以便于快速检索。
index = VectorStoreIndex.from_documents(documents)
# 创建一个查询引擎,这个引擎可以接收查询并返回相关文档的响应。
query_engine = index.as_query_engine()
response = query_engine.query("xtuner是什么?")print(response)
5.LlamaIndex Web
pip install streamlit==1.39.0
touch app.py
app.py
import streamlit as st
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.legacy.callbacks import CallbackManager
from llama_index.llms.openai_like import OpenAILike# Create an instance of CallbackManager
callback_manager = CallbackManager()api_base_url = "https://internlm-chat.intern-ai.org.cn/puyu/api/v1/"
model = "internlm2.5-latest"
api_key = "请填写 API Key"# api_base_url = "https://api.siliconflow.cn/v1"
# model = "internlm/internlm2_5-7b-chat"
# api_key = "请填写 API Key"llm =OpenAILike(model=model, api_base=api_base_url, api_key=api_key, is_chat_model=True,callback_manager=callback_manager)st.set_page_config(page_title="llama_index_demo", page_icon="🦜🔗")
st.title("llama_index_demo")# 初始化模型
@st.cache_resource
def init_models():embed_model = HuggingFaceEmbedding(model_name="/root/model/sentence-transformer")Settings.embed_model = embed_model#用初始化llmSettings.llm = llmdocuments = SimpleDirectoryReader("/root/llamaindex_demo/data").load_data()index = VectorStoreIndex.from_documents(documents)query_engine = index.as_query_engine()return query_engine# 检查是否需要初始化模型
if 'query_engine' not in st.session_state:st.session_state['query_engine'] = init_models()def greet2(question):response = st.session_state['query_engine'].query(question)return response# Store LLM generated responses
if "messages" not in st.session_state.keys():st.session_state.messages = [{"role": "assistant", "content": "你好,我是你的助手,有什么我可以帮助你的吗?"}] # Display or clear chat messages
for message in st.session_state.messages:with st.chat_message(message["role"]):st.write(message["content"])def clear_chat_history():st.session_state.messages = [{"role": "assistant", "content": "你好,我是你的助手,有什么我可以帮助你的吗?"}]st.sidebar.button('Clear Chat History', on_click=clear_chat_history)# Function for generating LLaMA2 response
def generate_llama_index_response(prompt_input):return greet2(prompt_input)# User-provided prompt
if prompt := st.chat_input():st.session_state.messages.append({"role": "user", "content": prompt})with st.chat_message("user"):st.write(prompt)# Gegenerate_llama_index_response last message is not from assistant
if st.session_state.messages[-1]["role"] != "assistant":with st.chat_message("assistant"):with st.spinner("Thinking..."):response = generate_llama_index_response(prompt)placeholder = st.empty()placeholder.markdown(response)message = {"role": "assistant", "content": response}st.session_state.messages.append(message)
- 启动项目:
streamlit run app.py
6.延伸阅读
1.LangChain实战课-3.用LangChain快速构建基于“易速鲜花”本地知识库的智能问答系统
2.LangChain实战课