相关实战课程理论概括
课程主要介绍的是本地知识库构建与本地检索,关于RAG的介绍比较片面。
Reference:
3. 用LangChain快速构建基于“易速鲜花”本地知识库的智能问答系统
1. langchain基于文档的问答功能实现
具体流程分为下面5步。
- Loading:文档加载器把Documents 加载为以LangChain能够读取的形式。
- Splitting:文本分割器把Documents 切分为指定大小的分割,我把它们称为“文档块”或者“文档片”。
- Storage:将上一步中分割好的“文档块”以“嵌入”(Embedding)的形式存储到向量数据库(Vector DB)中,形成一个个的“嵌入片”。
- Retrieval:应用程序从存储中检索分割后的文档(例如通过比较余弦相似度,找到与输入问题类似的嵌入片)。
- Output:把问题和相似的嵌入片传递给语言模型(LLM),使用包含问题和检索到的分割的提示生成答案。
2. RAG 的工作原理
RAG 的工作原理可以概括为几个步骤。
- 检索:对于给定的输入(问题),模型首先使用检索系统从大型文档集合中查找相关的文档或段落。这个检索系统通常基于密集向量搜索,例如ChromaDB、Faiss这样的向量数据库。
- 上下文编码:找到相关的文档或段落后,模型将它们与原始输入(问题)一起编码。
- 生成:使用编码的上下文信息,模型生成输出(答案)。这通常当然是通过大模型完成的。
本地知识库QA实现
0. 导入包
import os
from langchain_community.document_loaders import PyPDFLoader
from langchain_community.document_loaders import Docx2txtLoader
from langchain_community.document_loaders import TextLoader
from typing import Dict, List, Any
from langchain.embeddings.base import Embeddings
from pydantic.v1 import BaseModel
from volcenginesdkarkruntime import Ark
from langchain.text_splitter import RecursiveCharacterTextSplitter
import numpy as np
from langchain_community.vectorstores import Qdrant
1. 加载数据:导入文档
# 加载Documents
base_dir = "./OneFlower" # 文档的存放目录
documents = []
for file in os.listdir(base_dir):
# 构建完整的文件路径
file_path = os.path.join(base_dir, file)
if file.endswith(".pdf"):
loader = PyPDFLoader(file_path)
documents.extend(loader.load())
elif file.endswith(".docx"):
loader = Docx2txtLoader(file_path)
documents.extend(loader.load())
elif file.endswith(".txt"):
loader = TextLoader(file_path)
documents.extend(loader.load())
2. 分割文档
# 2.Split 将Documents切分成块以便后续进行嵌入和向量存储
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=10)
chunked_documents = text_splitter.split_documents(documents)
3. 向量数据库存储
-
文本嵌入
- 豆包嵌入模型
class DoubaoEmbeddings(BaseModel, Embeddings): client: Ark = None api_key: str = "" model: str def __init__(self, **data: Any): super().__init__(**data) if self.api_key == "": self.api_key = os.environ["OPENAI_API_KEY"] self.client = Ark( base_url=os.environ["OPENAI_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].embedding def embed_documents(self, texts: List[str]) -> List[List[float]]: return [self.embed_query(text) for text in texts] class Config: arbitrary_types_allowed = True
- 模型初始化
'''嵌入模型初始化''' embeddingModel=DoubaoEmbeddings( model=os.environ["EMBEDDING_MODELEND"], )
- (调用测试)测试文本嵌入向量的相似度
'''测试embedding文本相似度''' # 生成文本嵌入 text1 = "Hello, world! This is an example sentence." text2 = "Hello, world! This are some example sentences." embedding_1 = embeddingModel.embed_query(text1) embedding_2 = embeddingModel.embed_query(text2) print(f"Embedding dimension: {len(embedding_2)}") print(f"First few values: {embedding_2[:5]}") import numpy as np embedding_1 = np.array(embedding_1) embedding_2 = np.array(embedding_2) similarity = np.dot(embedding_1, embedding_2) print(similarity)
- (进阶)缓存嵌入
# 导入内存存储库,该库允许我们在RAM中临时存储数据 from langchain.storage import InMemoryStore # 创建一个InMemoryStore的实例 store = InMemoryStore() # 导入与嵌入相关的库。OpenAIEmbeddings是用于生成嵌入的工具,而CacheBackedEmbeddings允许我们缓存这些嵌入 from langchain.embeddings import CacheBackedEmbeddings # 创建一个OpenAIEmbeddings的实例,这将用于实际计算文档的嵌入 underlying_embeddings = DoubaoEmbeddings( model=os.environ["EMBEDDING_MODELEND"], ) # 创建一个CacheBackedEmbeddings的实例。 # 这将为underlying_embeddings提供缓存功能,嵌入会被存储在上面创建的InMemoryStore中。 # 我们还为缓存指定了一个命名空间,以确保不同的嵌入模型之间不会出现冲突。 embedder = CacheBackedEmbeddings.from_bytes_store( underlying_embeddings, # 实际生成嵌入的工具 store, # 嵌入的缓存位置 namespace=underlying_embeddings.model, # 嵌入缓存的命名空间 ) # 使用embedder为两段文本生成嵌入。 # 结果,即嵌入向量,将被存储在上面定义的内存存储中。 embeddings = embedder.embed_documents(["你好", "智能鲜花客服"]) print(len(embeddings[1]))
-
存储在向量数据库中
location=":memory:"
与前面缓存嵌入原理类似vectorstore = Qdrant.from_documents( documents=chunked_documents, # 以分块的文档 embedding=DoubaoEmbeddings( model=os.environ["EMBEDDING_MODELEND"], ), # 用OpenAI的Embedding Model做嵌入 location=":memory:", # in-memory 存储 collection_name="my_documents", ) # 指定collection_name
4. 相关信息的获取:Retrieval
使用多查询检索器MultiQueryRetriever
MultiQueryRetriever通过使用 LLM 从不同角度为给定的用户输入查询生成多个查询,可以自动执行提示调整过程
# 4. Retrieval 准备模型和Retrieval链
import logging # 导入Logging工具
from langchain_openai import ChatOpenAI # ChatOpenAI模型
from langchain.retrievers.multi_query import (
MultiQueryRetriever,
) # MultiQueryRetriever工具
from langchain.chains import RetrievalQA # RetrievalQA链
# 设置Logging
logging.basicConfig()
logging.getLogger("langchain.retrievers.multi_query").setLevel(logging.INFO)
# 实例化一个大模型工具 - OpenAI的GPT-3.5
llm = ChatOpenAI(model=os.environ["LLM_MODELEND"], temperature=0)
# 实例化一个MultiQueryRetriever
retriever_from_llm = MultiQueryRetriever.from_llm(
retriever=vectorstore.as_retriever(), llm=llm
)
5. 生成问答:构建QA链调用retriever,检索提问
# 实例化一个RetrievalQA链
qa_chain = RetrievalQA.from_chain_type(llm, retriever=retriever_from_llm)
# RetrievalQA链 - 读入问题,生成答案
result = qa_chain({"query": question})
print(result)
最终结果示例
INFO:langchain.retrievers.multi_query:Generated queries: ['1. What are the characteristics of a rose?', '2. How is a rose defined?', '3. What constitutes a rose?']
{'query': 'What is a rose?', 'result': "I don't know."}
INFO:langchain.retrievers.multi_query:Generated queries: ['1. What is a rose?', '2. Different types of roses.', '3. Characteristics of roses.']
{'query': 'rose', 'result': "I don't know."}
结论:并不理想。
解决方案:
-
减少载入数据库的文档,缩小检索范围后能回答到。
-
修改前面文档载入代码,只载入“花语大全.txt”
-
快速载入txt完成整个检索的demo
embeddings = DoubaoEmbeddings( model=os.environ["EMBEDDING_MODELEND"], ) # 导入文档加载器模块,并使用TextLoader来加载文本文件 from langchain_community.document_loaders import TextLoader from langchain_openai import ChatOpenAI # ChatOpenAI模型 loader = TextLoader("./OneFlower/花语大全.txt", encoding="utf8") # 使用VectorstoreIndexCreator来从加载器创建索引 from langchain.indexes import VectorstoreIndexCreator index = VectorstoreIndexCreator(embedding=embeddings).from_loaders([loader]) llm = ChatOpenAI(model=os.environ["LLM_MODELEND"], temperature=0) # 定义查询字符串, 使用创建的索引执行查询 query = "玫瑰花的花语是什么?" result = index.query(llm=llm, question=query) print(result) # 打印查询结果 ##显示调用,使用不同的text splitter ##text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0) ##from langchain_community.vectorstores import Qdrant ##index_creator = VectorstoreIndexCreator( ## # vectorstore_cls=Qdrant, ## embedding=embeddings, ## text_splitter=CharacterTextSplitter(chunk_size=1000, chunk_overlap=0), ##)
-
INFO:langchain.retrievers.multi_query:Generated queries: ['1. What is a rose?', '2. Different types of roses.', '3. Characteristics of roses.']
{'query': 'rose', 'result': 'Love, Passion, Beauty'}
- 更换嵌入模型
使用开源嵌入模型完成本地知识库QA
BAAI的嵌入模型性能较优,有多种版本的模型:不同体量、支持中英文
尝试使用bge-large在当前2core cpu设备不是很好运行
-
下载并使用示例
from langchain_community.embeddings import HuggingFaceBgeEmbeddings # 选择模型 model_name = "BAAI/bge-small-zh-v1.5" model_kwargs = {"device": "cpu"} encode_kwargs = {"normalize_embeddings": True} # 初始化嵌入模型 embeddingModel = HuggingFaceBgeEmbeddings( model_name=model_name, model_kwargs=model_kwargs, encode_kwargs=encode_kwargs, query_instruction="为这个句子生成表示以用于检索相关文章:" ) # 生成文本嵌入 text1 = "Hello, world! This is an example sentence." text2 = "Hello, world! This are some example sentences." embedding_1 = embeddingModel.embed_query(text1) embedding_2 = embeddingModel.embed_query(text2) print(f"Embedding dimension: {len(embedding_2)}") print(f"First few values: {embedding_2[:5]}") import numpy as np embedding_1 = np.array(embedding_1) embedding_2 = np.array(embedding_2) similarity = np.dot(embedding_1, embedding_2) print(similarity)
-
修改前文整体QA流程embbeding载入代码
print("start embedding to vetcorstore")
# 3.Store 将分割嵌入并存储在矢量数据库Qdrant中
vectorstore = Qdrant.from_documents(
documents=chunked_documents, # 以分块的文档
embedding=HuggingFaceBgeEmbeddings(
model_name="BAAI/bge-small-zh-v1.5",
# model_kwargs=model_kwargs,
encode_kwargs={"normalize_embeddings": True},
# query_instruction="为这个句子生成表示以用于检索相关文章:"
),
location=":memory:", # in-memory 存储
collection_name="my_documents",
) # 指定collection_name
print("finish embedding to vetcorstore")
- 结果展示
在完整包含不同文件的知识库中,能够正确检索。
INFO:langchain.retrievers.multi_query:Generated queries: ['What is a rose?', 'Describe a rose.', 'How is a rose different from other flowers?']
{'query': 'Rose', 'result': 'Love, Passion, Beauty'}