【可能是全网最丝滑的LangChain教程】十七、LangChain进阶之Retrievers

news2024/9/23 21:21:49

人生不能像做菜,把所有的料都准备好了才下锅。

01 Retrievers介绍

检索器(Retrievers) 是一种接口,用于根据非结构化查询返回文档,它比向量存储更为通用,既可以使用向量存储作为底层,也可以是其他类型。(这里说的这些检索器基本都是和向量存储有关联的)

检索器接受字符串查询作为输入,并输出一系列文档。

检索器顶层接口如下:

class BaseRetriever(RunnableSerializable[RetrieverInput, RetrieverOutput], ABC):
    
    # 根据查询内容获取相关的文档
    @abstractmethod
    def _get_relevant_documents(
        self, query: str, *, run_manager: CallbackManagerForRetrieverRun
    ) -> List[Document]:
        

    # 异步,根据查询内容获取相关的文档
    async def _aget_relevant_documents(
        self, query: str, *, run_manager: AsyncCallbackManagerForRetrieverRun
    ) -> List[Document]:
        return await run_in_executor(
            None,
            self._get_relevant_documents,
            query,
            run_manager=run_manager.get_sync(),
        )

后续如果我们如果有自定义的需求就可以实现这两个接口做具体实现。

02 LangChain种的Retrievers

Vector store-backed retriever

矢量存储检索器

矢量存储检索器是一种利用矢量存储来检索文档的检索器,它是对矢量存储类的封装,使其能够通过相似性搜索和最大边际相关性(MMR)等方法进行文本查询。

示例Demo如下:

from langchain_community.document_loaders import TextLoader
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import CharacterTextSplitter

# 加载文档
loader = TextLoader("index.txt")
documents = loader.load()

# 切割文档
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_documents(documents)

# 创建词嵌入模型
embeddings = OpenAIEmbeddings()

# 基于FAISS,创建向量数据库存储
db = FAISS.from_documents(texts, embeddings)

# 向量数据库转换成检索器
retriever = db.as_retriever()

# 一、执行最基础的检索
docs = retriever.invoke("what did he say about ketanji brown jackson")

# 二、最大边际相关性检索
retriever = db.as_retriever(search_type="mmr")
docs = retriever.invoke("what did he say about ketanji brown jackson")

# 三、相似性分数阈值检索
# 低于0.5分的搜索步返回
retriever = db.as_retriever(
    search_type="similarity_score_threshold", search_kwargs={"score_threshold": 0.5}
)
docs = retriever.invoke("what did he say about ketanji brown jackson")

# 四、指定topk
retriever = db.as_retriever(search_kwargs={"k": 1})
docs = retriever.invoke("what did he say about ketanji brown jackson")
len(docs) # 这里返回1,意思是只返回最相似的那一条搜索结果

这里我们简单分析下源码:

class VectorStoreRetriever(BaseRetriever):
    
    # 具体的实现
    # 本质还是向量搜索
    def _get_relevant_documents(
        self, query: str, *, run_manager: CallbackManagerForRetrieverRun
    ) -> List[Document]:
        if self.search_type == "similarity":
            # 这里本质还是做相似度查询
            docs = self.vectorstore.similarity_search(query, **self.search_kwargs)
        elif self.search_type == "similarity_score_threshold":
            # 返回相关度(分数)的相似度查询
            docs_and_similarities = (
                self.vectorstore.similarity_search_with_relevance_scores(
                    query, **self.search_kwargs
                )
            )
            docs = [doc for doc, _ in docs_and_similarities]
        elif self.search_type == "mmr":
            # 最大边际相关性搜索
            docs = self.vectorstore.max_marginal_relevance_search(
                query, **self.search_kwargs
            )
        else:
            raise ValueError(f"search_type of {self.search_type} not allowed.")
        return docs

    # 异步获取搜索结果,本质没变
    async def _aget_relevant_documents(
        self, query: str, *, run_manager: AsyncCallbackManagerForRetrieverRun
    ) -> List[Document]:
        if self.search_type == "similarity":
            docs = await self.vectorstore.asimilarity_search(
                query, **self.search_kwargs
            )
        elif self.search_type == "similarity_score_threshold":
            docs_and_similarities = (
                await self.vectorstore.asimilarity_search_with_relevance_scores(
                    query, **self.search_kwargs
                )
            )
            docs = [doc for doc, _ in docs_and_similarities]
        elif self.search_type == "mmr":
            docs = await self.vectorstore.amax_marginal_relevance_search(
                query, **self.search_kwargs
            )
        else:
            raise ValueError(f"search_type of {self.search_type} not allowed.")
        return docs

通过上面源码可以看出,本质还是做向量相似度搜索,后续其他的检索器的真正实现也在 _get_relevant_documents_aget_relevant_documents 里面,不再做具体的逻辑分析,感兴趣的可以自己去查看。

MultiQueryRetriever

多查询检索器

MultiQueryRetriever 是一个自动化的查询调优工具,它利用大型语言模型(LLM)为用户输入的查询生成多个不同角度的查询,以克服基于距离的向量数据库检索方法的局限性,并提供更丰富的检索结果。

MultiQueryRetriever 旨在解决基于距离的向量数据库检索时可能出现的问题,如查询表述的微小变化导致的不同检索结果,以及嵌入不足以捕捉数据语义的情况。该工具通过 LLM 自动生成多个查询变体,对于每个变体检索相关文档,并将所有查询的结果取并集,形成一个更全面的相关文档集合。

具体使用如下:

from langchain_community.embeddings import HuggingFaceEmbeddings
from transformers.utils import is_torch_cuda_available, is_torch_mps_available
from langchain_community.vectorstores import FAISS
from langchain_community.document_loaders import WebBaseLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain.retrievers.multi_query import MultiQueryRetriever

# 这里加载的是电影《让子弹飞》百度百科描述
loader = WebBaseLoader("https://baike.baidu.com/item/%E8%AE%A9%E5%AD%90%E5%BC%B9%E9%A3%9E/5358")
data = loader.load()

# 做文档切割
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=0)
splits = text_splitter.split_documents(data)

# VectorDB
# 词嵌入模型
EMBEDDING_DEVICE = "cuda" if is_torch_cuda_available() else "mps" if is_torch_mps_available() else "cpu"
embedding = HuggingFaceEmbeddings(model_name='D:\models\m3e-base', model_kwargs={'device': EMBEDDING_DEVICE})
vectordb = FAISS.from_documents(documents=splits, embedding=embedding)


question = "电影让子弹飞的影评怎么样?"
retriever_from_llm = MultiQueryRetriever.from_llm(
    retriever=vectordb.as_retriever(), llm=chat_model
)

unique_docs = retriever_from_llm.invoke(question)

"""
['1. 对于电影《让子弹飞》的评论,观众们的评价如何?', 
'2. 我想了解关于电影《让子弹飞》的专业影评,请问有哪些值得一看的观点?', 
'3. 有没有人分享他们对《让子弹飞》这部电影的看法?特别是对于其艺术价值和娱乐性的评价如何?']
"""

这里通过LLM本身的泛化能力,在原始问题的基础上生成了3个问题,如下:

[
'1. 对于电影《让子弹飞》的评论,观众们的评价如何?',
'2. 我想了解关于电影《让子弹飞》的专业影评,请问有哪些值得一看的观点?',
'3. 有没有人分享他们对《让子弹飞》这部电影的看法?特别是对于其艺术价值和娱乐性的评价如何?'
]

然后将三个问题分别做相似度检索,最后将检索的结果取并集。

Contextual Compression

上下文压缩检索器。

上下文压缩是一种用于提高检索效率的技术,它通过在返回文档之前使用查询的上下文来压缩和过滤文档,确保只有与查询相关的信息被返回。

传统检索方法的局限性,即检索到的文档可能包含大量不相关的信息,这会导致更高昂的语言模型调用成本和较差的响应质量。上下文压缩通过在返回文档之前使用查询的上下文来解决这个问题,它可以减少文档内容,甚至完全过滤掉不相关的文档。

要使用上下文压缩检索器,需要两个基本组件:基础检索器(base retriever)和文档压缩器(Document Compressor)。基础检索器负责从文档集合中检索初始文档,而文档压缩器则对这些文档进行压缩和过滤,以提供更精确的信息。

当我们没有使用上下文压缩检索器时:

from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import HuggingFaceEmbeddings
from transformers.utils import is_torch_cuda_available, is_torch_mps_available
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import CharacterTextSplitter

EMBEDDING_DEVICE = "cuda" if is_torch_cuda_available() else "mps" if is_torch_mps_available() else "cpu"
embeddings = HuggingFaceEmbeddings(model_name='D:\models\m3e-base', model_kwargs={'device': EMBEDDING_DEVICE})

documents = TextLoader("index.txt",encoding='utf-8',autodetect_encoding=True).load()
text_splitter = CharacterTextSplitter(chunk_size=100, chunk_overlap=0)
texts = text_splitter.split_documents(documents)
retriever = FAISS.from_documents(texts, embeddings).as_retriever()

docs = retriever.invoke("狮子王的经典台词")

"""
[Document(page_content='"永远不要小看自己,因为你永远不知道自己有多强大。" ——《狮子王》(The Lion King, 1994)\n\n"我会回来的。" ——《终结者》(The Terminator, 1984)', metadata={'source': 'index.txt'}),
 Document(page_content='这些台词不仅在电影中令人难忘,也在现实生活中激励着许多人。', metadata={'source': 'index.txt'}),
 Document(page_content='电影中的经典台词往往能够深入人心,成为人们记忆中的一部分。以下是一些电影中的经典台词:\n\n"生活就像一盒巧克力,你永远不知道你会得到什么。" ——《阿甘正传》(Forrest Gump, 1994)', metadata={'source': 'index.txt'}),
 Document(page_content='"不要让别人告诉你你能做什么,不能做什么。如果你有一个梦想,就去捍卫它。" ——《当幸福来敲门》(The Pursuit of Happyness, 2006)', metadata={'source': 'index.txt'})]
"""

现在让我们用 ContextualCompressionRetriever 包裹我们的基础检索器。我们将添加一个 LLMChainExtractor,它将遍历最初返回的文档,并仅从每个文档中提取与查询相关的内容。

from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor

compressor = LLMChainExtractor.from_llm(llm_model)
compression_retriever = ContextualCompressionRetriever(
    base_compressor=compressor, base_retriever=retriever
)

compressed_docs = compression_retriever.invoke(
    "狮子王的经典台词"
)

"""
[Document(page_content='"永远不要小看自己,因为你永远不知道自己有多强大。" ——《狮子王》(The Lion King, 1994)', metadata={'source': 'index.txt'})]
"""

很明显,这里只返回了一条结果,并且是对原始Document内容:[Document(page_content=‘“永远不要小看自己,因为你永远不知道自己有多强大。” ——《狮子王》(The Lion King, 1994)\n\n"我会回来的。" ——《终结者》(The Terminator, 1984)’, metadata={‘source’: ‘index.txt’})] 的总结输出!

LLMChainFilter 是一个稍微简单但更强大的压缩器,它使用 LLM 链来决定要过滤掉哪些最初检索的文档以及要返回哪些文档,而无需操作文档内容。

from langchain.retrievers.document_compressors import LLMChainFilter

_filter = LLMChainFilter.from_llm(llm_model)
compression_retriever = ContextualCompressionRetriever(
    base_compressor=_filter, base_retriever=retriever
)

compressed_docs = compression_retriever.invoke(
    "狮子王的经典台词"
)

"""
[Document(page_content='"永远不要小看自己,因为你永远不知道自己有多强大。" ——《狮子王》(The Lion King, 1994)\n\n"我会回来的。" ——《终结者》(The Terminator, 1984)', metadata={'source': 'index.txt'}),
 Document(page_content='这些台词不仅在电影中令人难忘,也在现实生活中激励着许多人。', metadata={'source': 'index.txt'})]
"""

很明显,上面仅仅对文档做了压缩,并没有改变文档的内容!

对每个检索到的文档进行额外的 LLM 调用既昂贵又缓慢。EmbeddingsFilter 通过嵌入文档和查询,并仅返回那些与查询具有足够相似嵌入的文档,提供了一个更便宜、更快捷的选项。

from langchain.retrievers.document_compressors import EmbeddingsFilter

EMBEDDING_DEVICE = "cuda" if is_torch_cuda_available() else "mps" if is_torch_mps_available() else "cpu"
embeddings = HuggingFaceEmbeddings(model_name='D:\models\m3e-base', model_kwargs={'device': EMBEDDING_DEVICE})

embeddings_filter = EmbeddingsFilter(embeddings=embeddings, similarity_threshold=0.76)
compression_retriever = ContextualCompressionRetriever(
    base_compressor=embeddings_filter, base_retriever=retriever
)

compressed_docs = compression_retriever.invoke(
    "狮子王的经典台词"
)

"""
[_DocumentWithState(page_content='"永远不要小看自己,因为你永远不知道自己有多强大。" ——《狮子王》(The Lion King, 1994)\n\n"我会回来的。" ——《终结者》(The Terminator, 1984)', metadata={'source': 'index.txt'}, state={'embedded_doc': [xx,xx,xx...], 'query_similarity_score': 0.7641647574958407})]
"""

这里通过分数过滤,最终获取到1条符合要求的结果!

通过组合不同的压缩器和转换器,可以创建灵活且高效的文档处理流程,以满足不同的检索需求。

下面我们将创建一个压缩器管道,首先将文档拆分为更小的块,然后删除冗余文档,然后根据与查询的相关性进行筛选。

from langchain.retrievers.document_compressors import DocumentCompressorPipeline
from langchain_community.document_transformers import EmbeddingsRedundantFilter
from langchain_text_splitters import CharacterTextSplitter

EMBEDDING_DEVICE = "cuda" if is_torch_cuda_available() else "mps" if is_torch_mps_available() else "cpu"
embeddings = HuggingFaceEmbeddings(model_name='D:\models\m3e-base', model_kwargs={'device': EMBEDDING_DEVICE})

# 首先将文档拆分为更小的块,然后删除冗余文档,然后根据与查询的相关性进行筛选。
splitter = CharacterTextSplitter(chunk_size=100, chunk_overlap=0)
redundant_filter = EmbeddingsRedundantFilter(embeddings=embeddings)
relevant_filter = EmbeddingsFilter(embeddings=embeddings, similarity_threshold=0.76)
pipeline_compressor = DocumentCompressorPipeline(
    transformers=[splitter, redundant_filter, relevant_filter]
)

compression_retriever = ContextualCompressionRetriever(
    base_compressor=pipeline_compressor, base_retriever=retriever
)

compressed_docs = compression_retriever.invoke(
    "狮子王的经典台词"
)

"""
[_DocumentWithState(
    page_content='"永远不要小看自己,因为你永远不知道自己有多强大。" ——《狮子王》(The Lion King, 1994)\n\n"我会回来的。" ——《终结者》(The Terminator, 1984)', 
    metadata={'source': 'index.txt'}, 
    state={
        'embedded_doc': [xx,xx,xx...], 
        'query_similarity_score': 0.7641647574958407
        }
    )
]
"""

Ensemble Retriever

集成检索器Ensemble Retriever 是一个组合检索器,它通过结合多个检索器的结果并使用互惠秩融合算法重新排序,以提高检索性能。

Ensemble Retriever 是一个组合检索器,它可以同时使用多个检索器,如 BM25 和 FAISS,将它们的检索结果整合起来,并通过 Reciprocal Rank Fusion 算法对结果进行重新排序。这种方法结合了稀疏检索器和密集检索器的优势,前者优于关键词查找,后者优于语义相似度查找,从而实现所谓的 “混合搜索”。

示例代码如下:

from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
from langchain_community.vectorstores import FAISS

EMBEDDING_DEVICE = "cuda" if is_torch_cuda_available() else "mps" if is_torch_mps_available() else "cpu"
embedding = HuggingFaceEmbeddings(model_name='D:\models\m3e-base', model_kwargs={'device': EMBEDDING_DEVICE})

doc_list_1 = [
    "I like apples",
    "I like oranges",
    "Apples and oranges are fruits",
]

# initialize the bm25 retriever and faiss retriever
bm25_retriever = BM25Retriever.from_texts(
    doc_list_1, metadatas=[{"source": 1}] * len(doc_list_1)
)
bm25_retriever.k = 2

doc_list_2 = [
    "You like apples",
    "You like oranges",
]

faiss_vectorstore = FAISS.from_texts(
    doc_list_2, embedding, metadatas=[{"source": 2}] * len(doc_list_2)
)
faiss_retriever = faiss_vectorstore.as_retriever(search_kwargs={"k": 2})

# initialize the ensemble retriever
ensemble_retriever = EnsembleRetriever(
    retrievers=[bm25_retriever, faiss_retriever], weights=[0.5, 0.5]
)

docs = ensemble_retriever.invoke("apples")

"""
[Document(page_content='I like apples', metadata={'source': 1}),
 Document(page_content='You like apples', metadata={'source': 2}),
 Document(page_content='Apples and oranges are fruits', metadata={'source': 1}),
 Document(page_content='You like oranges', metadata={'source': 2})]
"""

Long-Context Reorder

长上下文重新排序检索器当语言模型需要在长上下文中访问相关信息时,性能可能会下

降的问题。当包含 10 个以上的检索文档时,模型往往会忽略提供的文档。为了解决这

个问题,可以在检索文档后对它们进行重新排序,以提高模型的性能。将最相关的文档

放置在上下文的开始和结束位置,可以帮助模型更好地利用这些信息来回答查询。

from langchain.chains import LLMChain, StuffDocumentsChain
from langchain_community.document_transformers import (
    LongContextReorder,
)
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_core.prompts import PromptTemplate

# Get embeddings.
EMBEDDING_DEVICE = "cuda" if is_torch_cuda_available() else "mps" if is_torch_mps_available() else "cpu"
embedding = HuggingFaceEmbeddings(model_name='D:\models\m3e-base', model_kwargs={'device': EMBEDDING_DEVICE})

texts = [
    "Basquetball is a great sport.",
    "Fly me to the moon is one of my favourite songs.",
    "The Celtics are my favourite team.",
    "This is a document about the Boston Celtics",
    "I simply love going to the movies",
    "The Boston Celtics won the game by 20 points",
    "This is just a random text.",
    "Elden Ring is one of the best games in the last 15 years.",
    "L. Kornet is one of the best Celtics players.",
    "Larry Bird was an iconic NBA player.",
]

# Create a retriever
retriever = FAISS.from_texts(texts, embedding=embeddings).as_retriever(
    search_kwargs={"k": 10}
)
query = "What can you tell me about the Celtics?"

# Get relevant documents ordered by relevance score
docs = retriever.invoke(query)

# 按照原始相关性输出的结果
"""
[Document(page_content='The Celtics are my favourite team.'),
 Document(page_content='This is a document about the Boston Celtics'),
 Document(page_content='The Boston Celtics won the game by 20 points'),
 Document(page_content='L. Kornet is one of the best Celtics players.'),
 Document(page_content='This is just a random text.'),
 Document(page_content='I simply love going to the movies'),
 Document(page_content='Basquetball is a great sport.'),
 Document(page_content='Fly me to the moon is one of my favourite songs.'),
 Document(page_content='Elden Ring is one of the best games in the last 15 years.'),
 Document(page_content='Larry Bird was an iconic NBA player.')]
"""

# ================= 做重排序 ====================
# Reorder the documents:
# Less relevant document will be at the middle of the list and more
# relevant elements at beginning / end.
reordering = LongContextReorder()
reordered_docs = reordering.transform_documents(docs)

# Confirm that the 4 relevant documents are at beginning and end.
# 对结果进行重排序后,强相关的会出现在开头和结尾
"""
[Document(page_content='This is a document about the Boston Celtics'),
 Document(page_content='L. Kornet is one of the best Celtics players.'),
 Document(page_content='I simply love going to the movies'),
 Document(page_content='Fly me to the moon is one of my favourite songs.'),
 Document(page_content='Larry Bird was an iconic NBA player.'),
 Document(page_content='Elden Ring is one of the best games in the last 15 years.'),
 Document(page_content='Basquetball is a great sport.'),
 Document(page_content='This is just a random text.'),
 Document(page_content='The Boston Celtics won the game by 20 points'),
 Document(page_content='The Celtics are my favourite team.')]
"""

MultiVector Retriever

多向量检索器

LangChain 提供了一个名为 MultiVectorRetriever 的工具,它允许用户为单个文档存储多个向量,以便更准确地进行文档检索。这个工具支持多种创建向量的方法,例如将文档分割成较小的块(ParentDocumentRetriever),为每个文档生成摘要并嵌入这些摘要,或者创建适合文档回答的假设性问题并将这些问题嵌入。此外,用户还可以手动添加嵌入,以便更精确地控制文档检索过程。

首先,我们需要正常加载文档,这里的chunk_size需要设置的大一些。

from langchain_community.document_loaders import TextLoader

loader = TextLoader("index.txt",encoding='utf-8',autodetect_encoding=True)
docs = loader.load()
text_splitter = RecursiveCharacterTextSplitter(chunk_size=300)
docs = text_splitter.split_documents(docs)

"""
[Document(page_content='电影中的经典台词往往能够深入人心,成为人们记忆中的一部分。以下是一些电影中的经典台词:\n\n"生活就像一盒巧克力,你永远不知道你会得到什么。" ——《阿甘正传》(Forrest Gump, 1994)\n\n"永远不要小看自己,因为你永远不知道自己有多强大。" ——《狮子王》(The Lion King, 1994)\n\n"我会回来的。" ——《终结者》(The Terminator, 1984)\n\n"你不能改变过去,但你可以改变未来。" ——《回到未来》(Back to the Future, 1985)\n\n"即使世界末日来临,我也要和你一起度过。" ——《泰坦尼克号》(Titanic, 1997)', metadata={'source': 'index.txt'}),
 Document(page_content='"我会回来的。" ——《终结者》(The Terminator, 1984)\n\n"你不能改变过去,但你可以改变未来。" ——《回到未来》(Back to the Future, 1985)\n\n"即使世界末日来临,我也要和你一起度过。" ——《泰坦尼克号》(Titanic, 1997)\n\n"不要让别人告诉你你能做什么,不能做什么。如果你有一个梦想,就去捍卫它。" ——《当幸福来敲门》(The Pursuit of Happyness, 2006)\n\n"我将永远爱你。" ——《保镖》(The Bodyguard, 1992)', metadata={'source': 'index.txt'}),
 Document(page_content='"即使世界末日来临,我也要和你一起度过。" ——《泰坦尼克号》(Titanic, 1997)\n\n"不要让别人告诉你你能做什么,不能做什么。如果你有一个梦想,就去捍卫它。" ——《当幸福来敲门》(The Pursuit of Happyness, 2006)\n\n"我将永远爱你。" ——《保镖》(The Bodyguard, 1992)\n\n"这就是生活,小家伙。" ——《美丽人生》(Life is Beautiful, 1997)\n\n"我有一个梦想。" ——《我有一个梦想》(I Have a Dream, 1963)', metadata={'source': 'index.txt'}),
 Document(page_content='"我将永远爱你。" ——《保镖》(The Bodyguard, 1992)\n\n"这就是生活,小家伙。" ——《美丽人生》(Life is Beautiful, 1997)\n\n"我有一个梦想。" ——《我有一个梦想》(I Have a Dream, 1963)\n\n"你只需要跟随你的黄砖路。" ——《绿野仙踪》(The Wizard of Oz, 1939)\n\n这些台词不仅在电影中令人难忘,也在现实生活中激励着许多人。', metadata={'source': 'index.txt'})]
"""

Smaller chunks示例:

from langchain.retrievers import MultiVectorRetriever
from langchain_core.stores import InMemoryByteStore
from langchain_community.embeddings import HuggingFaceEmbeddings
from transformers.utils import is_torch_cuda_available, is_torch_mps_available
from langchain_community.vectorstores import FAISS

EMBEDDING_DEVICE = "cuda" if is_torch_cuda_available() else "mps" if is_torch_mps_available() else "cpu"
embedding = HuggingFaceEmbeddings(model_name='D:\models\m3e-base', model_kwargs={'device': EMBEDDING_DEVICE})

# The vectorstore to use to index the child chunks
vectorstore = FAISS.from_documents(docs,embedding)

# The storage layer for the parent documents
store = InMemoryByteStore()
id_key = "doc_id"

# The retriever (empty to start)
retriever = MultiVectorRetriever(
    vectorstore=vectorstore,
    byte_store=store,
    id_key=id_key,
)

import uuid

doc_ids = [str(uuid.uuid4()) for _ in docs]

# 这里生成了4个父文档id
"""
['acff96e4-e86d-4305-8446-ddb45de574b3',
 '333dfffd-f245-46cd-b1f1-ebe7bffe25c1',
 'ad7e4e35-ba91-4add-b03b-733d7805e9d2',
 'd1ed38d3-a301-45fc-be4c-380e0a2480cf']
"""

from langchain_text_splitters import RecursiveCharacterTextSplitter

# The splitter to use to create smaller chunks
child_text_splitter = RecursiveCharacterTextSplitter(chunk_size=100,chunk_overlap=10)
sub_docs = []
for i, doc in enumerate(docs):
    _id = doc_ids[i]
    # 对父文档再切割,形成子文档
    _sub_docs = child_text_splitter.split_documents([doc])
    for _doc in _sub_docs:
        _doc.metadata[id_key] = _id
    sub_docs.extend(_sub_docs)

# 子文档入向量数据库
retriever.vectorstore.add_documents(sub_docs)
# 子父文档建立映射,方便查询
retriever.docstore.mset(list(zip(doc_ids, docs)))

# 查询
documents = retriever.vectorstore.similarity_search("狮子王")

"""
[Document(page_content='"永远不要小看自己,因为你永远不知道自己有多强大。" ——《狮子王》(The Lion King, 1994)\n\n"我会回来的。" ——《终结者》(The Terminator, 1984)', metadata={'source': 'index.txt', 'doc_id': 'acff96e4-e86d-4305-8446-ddb45de574b3'}),
 Document(page_content='"不要让别人告诉你你能做什么,不能做什么。如果你有一个梦想,就去捍卫它。" ——《当幸福来敲门》(The Pursuit of Happyness, 2006)', metadata={'source': 'index.txt', 'doc_id': '333dfffd-f245-46cd-b1f1-ebe7bffe25c1'}),
 Document(page_content='"不要让别人告诉你你能做什么,不能做什么。如果你有一个梦想,就去捍卫它。" ——《当幸福来敲门》(The Pursuit of Happyness, 2006)', metadata={'source': 'index.txt', 'doc_id': 'ad7e4e35-ba91-4add-b03b-733d7805e9d2'}),
 Document(page_content='电影中的经典台词往往能够深入人心,成为人们记忆中的一部分。以下是一些电影中的经典台词:\n\n"生活就像一盒巧克力,你永远不知道你会得到什么。" ——《阿甘正传》(Forrest Gump, 1994)\n\n"永远不要小看自己,因为你永远不知道自己有多强大。" ——《狮子王》(The Lion King, 1994)\n\n"我会回来的。" ——《终结者》(The Terminator, 1984)\n\n"你不能改变过去,但你可以改变未来。" ——《回到未来》(Back to the Future, 1985)\n\n"即使世界末日来临,我也要和你一起度过。" ——《泰坦尼克号》(Titanic, 1997)', metadata={'source': 'index.txt'})]
"""

Summary示例:

import uuid

from langchain_core.documents import Document
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate

# LCEL表达式,构建执行链
chain = (
    {"doc": lambda x: x.page_content}
    | ChatPromptTemplate.from_template("总结下面的一段文本:\n\n{doc}")
    | chat_model
    | StrOutputParser()
)

# 对原始文档做总结
summaries = chain.batch(docs, {"max_concurrency": 5})

# 词嵌入模型
EMBEDDING_DEVICE = "cuda" if is_torch_cuda_available() else "mps" if is_torch_mps_available() else "cpu"
embedding = HuggingFaceEmbeddings(model_name='D:\models\m3e-base', model_kwargs={'device': EMBEDDING_DEVICE})

# The vectorstore to use to index the child chunks
vectorstore = FAISS.from_documents(docs,embedding)
# The storage layer for the parent documents
store = InMemoryByteStore()
id_key = "doc_id"
# The retriever (empty to start)
retriever = MultiVectorRetriever(
    vectorstore=vectorstore,
    byte_store=store,
    id_key=id_key,
)
doc_ids = [str(uuid.uuid4()) for _ in docs]

# 构建总结文档的document列表
summary_docs = [
    Document(page_content=s, metadata={id_key: doc_ids[i]})
    for i, s in enumerate(summaries)
]

# 添加总结文档到向量数据库
retriever.vectorstore.add_documents(summary_docs)
# 总结文档和原始文档建立映射
retriever.docstore.mset(list(zip(doc_ids, docs)))

# 执行向量搜索
sub_docs = vectorstore.similarity_search("泰坦尼克号")

"""
[Document(page_content='"我会回来的。" ——《终结者》(The Terminator, 1984)\n\n"你不能改变过去,但你可以改变未来。" ——《回到未来》(Back to the Future, 1985)\n\n"即使世界末日来临,我也要和你一起度过。" ——《泰坦尼克号》(Titanic, 1997)\n\n"不要让别人告诉你你能做什么,不能做什么。如果你有一个梦想,就去捍卫它。" ——《当幸福来敲门》(The Pursuit of Happyness, 2006)\n\n"我将永远爱你。" ——《保镖》(The Bodyguard, 1992)', metadata={'source': 'index.txt'}),
 Document(page_content='"即使世界末日来临,我也要和你一起度过。" ——《泰坦尼克号》(Titanic, 1997)\n\n"不要让别人告诉你你能做什么,不能做什么。如果你有一个梦想,就去捍卫它。" ——《当幸福来敲门》(The Pursuit of Happyness, 2006)\n\n"我将永远爱你。" ——《保镖》(The Bodyguard, 1992)\n\n"这就是生活,小家伙。" ——《美丽人生》(Life is Beautiful, 1997)\n\n"我有一个梦想。" ——《我有一个梦想》(I Have a Dream, 1963)', metadata={'source': 'index.txt'}),
 Document(page_content='这些引述表达了不同的情感和生活理念:\n\n1. 《泰坦尼克号》中的台词体现了坚定不移的爱情承诺,即使在极端困境中也不放弃对彼此的陪伴。\n\n2. 《当幸福来敲门》传递的是鼓舞人心的信息,鼓励人们追求自己的梦想,不受他人的限制或质疑。\n\n3. 《保镖》中的经典表白表达了永恒不变的爱情誓言,让人感受到深情与承诺的力量。\n\n4. 《美丽人生》则以一种乐观和智慧的方式揭示生活的真谛,提醒我们无论处境如何,都要以积极的态度面对。\n\n5. 最后,《我有一个梦想》是马丁·路德·金的著名演讲,体现了对平等、自由和平等权利的强烈渴望,激励人们为理想而奋斗。', metadata={'doc_id': 'b40d73bb-6def-4418-8137-49edaaf6131f'}),
 Document(page_content='这段文本讲述了电影中的经典台词对人们的影响,列举了几个深入人心的例句。这些台词包括《阿甘正传》中关于生活不确定性的哲理,“永远不要小看自己”的鼓舞人心语句,《终结者》中的承诺,《回到未来》关于把握未来的警醒,以及《泰坦尼克号》中表达的爱情誓言。这些台词不仅在电影中起着重要的剧情推动作用,也在观众心中留下了深刻的印象。', metadata={'doc_id': 'eeb6a418-5cd0-4218-9f53-b739713c7c51'})]
"""

Hypothetical Queries示例:

# 定义函数,openai专用function_call
functions = [
    {
        "name": "hypothetical_questions",
        "description": "Generate hypothetical questions",
        "parameters": {
            "type": "object",
            "properties": {
                "questions": {
                    "type": "array",
                    "items": {"type": "string"},
                },
            },
            "required": ["questions"],
        },
    }
]

from langchain.output_parsers.openai_functions import JsonKeyOutputFunctionsParser

# LCEL表达式构建执行链,这里要求模型执行预定义的函数
chain = (
    {"doc": lambda x: x.page_content}
    # Only asking for 3 hypothetical questions, but this could be adjusted
    | ChatPromptTemplate.from_template(
        "Generate a list of exactly 3 hypothetical questions that the below document could be used to answer:\n\n{doc}"
    )
    | chat_model.bind(
        functions=functions, function_call={"name": "hypothetical_questions"}
    )
    | JsonKeyOutputFunctionsParser(key_name="questions")
)

# 执行函数调用
hypothetical_questions = chain.batch(docs, {"max_concurrency": 5})

# 词嵌入模型
EMBEDDING_DEVICE = "cuda" if is_torch_cuda_available() else "mps" if is_torch_mps_available() else "cpu"
embedding = HuggingFaceEmbeddings(model_name='D:\models\m3e-base', model_kwargs={'device': EMBEDDING_DEVICE})

# 向量存储
vectorstore = FAISS.from_documents(docs,embedding)

# 父文档存储
store = InMemoryByteStore()
id_key = "doc_id"

# 定义检索器
retriever = MultiVectorRetriever(
    vectorstore=vectorstore,
    byte_store=store,
    id_key=id_key,
)
doc_ids = [str(uuid.uuid4()) for _ in docs]

# 构建提问文档列表
question_docs = []
for i, question_list in enumerate(hypothetical_questions):
    question_docs.extend(
        [Document(page_content=s, metadata={id_key: doc_ids[i]}) for s in question_list]
    )

# 添加提问文档到向量数据库    
retriever.vectorstore.add_documents(question_docs)
# 将原始文档和提问文档建立映射
retriever.docstore.mset(list(zip(doc_ids, docs)))

# 执行向量检索
sub_docs = vectorstore.similarity_search("狮子王")

Parent Document Retriever

父文档检索器

ParentDocumentRetriever 是一个用于在精确性和上下文保留之间取得平衡的文档检索工具,它通过拆分和存储数据的小块,在检索时既能返回精确的小块,也能通过查找这些块的父 ID 返回更大的文档。

该工具旨在解决在分割文档以进行检索时,既需要小文档以便其嵌入能够准确反映其含义,又需要足够长的文档以保留每个块的上下文信息的问题。ParentDocumentRetriever 首先将数据拆分成小块并存储,在检索时,会先获取这些小块,然后根据小块的父 ID 查找并返回更大的文档。

示例代码如下:

from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter

loader = TextLoader("index.txt",encoding='utf-8',autodetect_encoding=True)
docs = loader.load()

from langchain.retrievers import ParentDocumentRetriever
from langchain_core.stores import InMemoryByteStore, InMemoryStore
from langchain_community.embeddings import HuggingFaceEmbeddings
from transformers.utils import is_torch_cuda_available, is_torch_mps_available
from langchain_community.vectorstores import FAISS

EMBEDDING_DEVICE = "cuda" if is_torch_cuda_available() else "mps" if is_torch_mps_available() else "cpu"
embedding = HuggingFaceEmbeddings(model_name='D:\models\m3e-base', model_kwargs={'device': EMBEDDING_DEVICE})

# This text splitter is used to create the child documents
child_splitter = RecursiveCharacterTextSplitter(chunk_size=100,chunk_overlap=10)

# The vectorstore to use to index the child chunks
vectorstore = FAISS.from_documents(docs,embedding)

# The storage layer for the parent documents
store = InMemoryStore()

# The retriever (empty to start)
retriever = ParentDocumentRetriever(
    vectorstore=vectorstore,
    docstore=store,
    child_splitter=child_splitter,
)

retriever.add_documents(docs, ids=None)

"""
list(store.yield_keys())输出如下,只有一个id,因为整个文档都作为一个Document处理了

['0d15e752-fb35-4190-a15c-54aadad82e47']
"""

# 检索切分成小块后的文档
sub_docs = vectorstore.similarity_search("狮子王")
"""
[Document(page_content='"永远不要小看自己,因为你永远不知道自己有多强大。" ——《狮子王》(The Lion King, 1994)\n\n"我会回来的。" ——《终结者》(The Terminator, 1984)', metadata={'source': 'index.txt', 'doc_id': '6dbe9646-9544-4fa6-94d9-325242d9c443'}),
 Document(page_content='"不要让别人告诉你你能做什么,不能做什么。如果你有一个梦想,就去捍卫它。" ——《当幸福来敲门》(The Pursuit of Happyness, 2006)', metadata={'source': 'index.txt', 'doc_id': '6dbe9646-9544-4fa6-94d9-325242d9c443'}),
 Document(page_content='这些台词不仅在电影中令人难忘,也在现实生活中激励着许多人。', metadata={'source': 'index.txt', 'doc_id': '6dbe9646-9544-4fa6-94d9-325242d9c443'}),
 Document(page_content='"你不能改变过去,但你可以改变未来。" ——《回到未来》(Back to the Future, 1985)', metadata={'source': 'index.txt', 'doc_id': '6dbe9646-9544-4fa6-94d9-325242d9c443'})]
"""

# 检索整个文档
retrieved_docs = retriever.invoke("狮子王")
"""
[Document(page_content='电影中的经典台词往往能够深入人心,成为人们记忆中的一部分。以下是一些电影中的经典台词:\n\n"生活就像一盒巧克力,你永远不知道你会得到什么。" ——《阿甘正传》(Forrest Gump, 1994)\n\n"永远不要小看自己,因为你永远不知道自己有多强大。" ——《狮子王》(The Lion King, 1994)\n\n"我会回来的。" ——《终结者》(The Terminator, 1984)\n\n"你不能改变过去,但你可以改变未来。" ——《回到未来》(Back to the Future, 1985)\n\n"即使世界末日来临,我也要和你一起度过。" ——《泰坦尼克号》(Titanic, 1997)\n\n"不要让别人告诉你你能做什么,不能做什么。如果你有一个梦想,就去捍卫它。" ——《当幸福来敲门》(The Pursuit of Happyness, 2006)\n\n"我将永远爱你。" ——《保镖》(The Bodyguard, 1992)\n\n"这就是生活,小家伙。" ——《美丽人生》(Life is Beautiful, 1997)\n\n"我有一个梦想。" ——《我有一个梦想》(I Have a Dream, 1963)\n\n"你只需要跟随你的黄砖路。" ——《绿野仙踪》(The Wizard of Oz, 1939)\n\n这些台词不仅在电影中令人难忘,也在现实生活中激励着许多人。', metadata={'source': 'index.txt'})]
"""

有时,完整的文档可能太大,无法按原样检索它们。在这种情况下,我们真正想做的是首先将原始文档拆分为较大的块,然后将其拆分为较小的块。然后,我们对较小的块进行索引,但在检索时,我们会检索较大的块(但仍然不是完整的文档)。

# This text splitter is used to create the parent documents
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=300)
# This text splitter is used to create the child documents
# It should create documents smaller than the parent
child_splitter = RecursiveCharacterTextSplitter(chunk_size=100,chunk_overlap=10)
# The vectorstore to use to index the child chunks
EMBEDDING_DEVICE = "cuda" if is_torch_cuda_available() else "mps" if is_torch_mps_available() else "cpu"
embedding = HuggingFaceEmbeddings(model_name='D:\models\m3e-base', model_kwargs={'device': EMBEDDING_DEVICE})
vectorstore = FAISS.from_documents(docs,embedding)
# The storage layer for the parent documents
store = InMemoryStore()

retriever = ParentDocumentRetriever(
    vectorstore=vectorstore,
    docstore=store,
    child_splitter=child_splitter,
    parent_splitter=parent_splitter,
)

retriever.add_documents(docs)

"""
这里len(list(store.yield_keys()))返回结果是4,说明有4个父文档id
"""

sub_docs = vectorstore.similarity_search("狮子王")
"""
[Document(page_content='"永远不要小看自己,因为你永远不知道自己有多强大。" ——《狮子王》(The Lion King, 1994)\n\n"我会回来的。" ——《终结者》(The Terminator, 1984)', metadata={'source': 'index.txt', 'doc_id': '8f4c2878-0cb8-4a86-94e1-f80bc977de6e'}),
 Document(page_content='"不要让别人告诉你你能做什么,不能做什么。如果你有一个梦想,就去捍卫它。" ——《当幸福来敲门》(The Pursuit of Happyness, 2006)', metadata={'source': 'index.txt', 'doc_id': 'bd4631ac-4b97-47af-b4c6-58f87d10c79e'}),
 Document(page_content='"不要让别人告诉你你能做什么,不能做什么。如果你有一个梦想,就去捍卫它。" ——《当幸福来敲门》(The Pursuit of Happyness, 2006)', metadata={'source': 'index.txt', 'doc_id': 'c81dbd21-cff4-4208-aab1-9bf499ad1b0c'}),
 Document(page_content='这些台词不仅在电影中令人难忘,也在现实生活中激励着许多人。', metadata={'source': 'index.txt', 'doc_id': '3d4c986c-c259-4005-8d39-f99f886d267a'})]
"""

retrieved_docs = retriever.invoke("狮子王")
"""
[Document(page_content='电影中的经典台词往往能够深入人心,成为人们记忆中的一部分。以下是一些电影中的经典台词:\n\n"生活就像一盒巧克力,你永远不知道你会得到什么。" ——《阿甘正传》(Forrest Gump, 1994)\n\n"永远不要小看自己,因为你永远不知道自己有多强大。" ——《狮子王》(The Lion King, 1994)\n\n"我会回来的。" ——《终结者》(The Terminator, 1984)\n\n"你不能改变过去,但你可以改变未来。" ——《回到未来》(Back to the Future, 1985)\n\n"即使世界末日来临,我也要和你一起度过。" ——《泰坦尼克号》(Titanic, 1997)', metadata={'source': 'index.txt'}),
 Document(page_content='"我会回来的。" ——《终结者》(The Terminator, 1984)\n\n"你不能改变过去,但你可以改变未来。" ——《回到未来》(Back to the Future, 1985)\n\n"即使世界末日来临,我也要和你一起度过。" ——《泰坦尼克号》(Titanic, 1997)\n\n"不要让别人告诉你你能做什么,不能做什么。如果你有一个梦想,就去捍卫它。" ——《当幸福来敲门》(The Pursuit of Happyness, 2006)\n\n"我将永远爱你。" ——《保镖》(The Bodyguard, 1992)', metadata={'source': 'index.txt'}),
 Document(page_content='"即使世界末日来临,我也要和你一起度过。" ——《泰坦尼克号》(Titanic, 1997)\n\n"不要让别人告诉你你能做什么,不能做什么。如果你有一个梦想,就去捍卫它。" ——《当幸福来敲门》(The Pursuit of Happyness, 2006)\n\n"我将永远爱你。" ——《保镖》(The Bodyguard, 1992)\n\n"这就是生活,小家伙。" ——《美丽人生》(Life is Beautiful, 1997)\n\n"我有一个梦想。" ——《我有一个梦想》(I Have a Dream, 1963)', metadata={'source': 'index.txt'}),
 Document(page_content='"我将永远爱你。" ——《保镖》(The Bodyguard, 1992)\n\n"这就是生活,小家伙。" ——《美丽人生》(Life is Beautiful, 1997)\n\n"我有一个梦想。" ——《我有一个梦想》(I Have a Dream, 1963)\n\n"你只需要跟随你的黄砖路。" ——《绿野仙踪》(The Wizard of Oz, 1939)\n\n这些台词不仅在电影中令人难忘,也在现实生活中激励着许多人。', metadata={'source': 'index.txt'})]
"""

Self Query

自查询检索器

图片

自查询检索器是一种能够自动构建结构化查询并应用于矢量存储的检索器,用于提取用户查询的筛选器,并对存储文档的元数据进行过滤和执行。

自查询检索器能够将自然语言查询转换为结构化查询,并将其应用于基础的矢量存储(VectorStore),如Chroma。这种检索器不仅能够根据用户输入的查询进行语义相似性匹配,还能从用户查询中提取出对文档元数据的筛选条件,并执行这些条件。

自查询检索器对向量数据库有要求,部分向量数据库不支持(FAISS不支持)

from langchain_core.documents import Document
from langchain.chains.query_constructor.base import AttributeInfo
from langchain.retrievers.self_query.base import SelfQueryRetriever

# 创建示例文档列表
docs = [
    Document(
        page_content="A bunch of scientists bring back dinosaurs and mayhem breaks loose",
        metadata={"year": 1993, "rating": 7.7, "genre": "science fiction"},
    ),
    Document(
        page_content="Leo DiCaprio gets lost in a dream within a dream within a dream within a ...",
        metadata={"year": 2010, "director": "Christopher Nolan", "rating": 8.2},
    ),
    Document(
        page_content="A psychologist / detective gets lost in a series of dreams within dreams within dreams and Inception reused the idea",
        metadata={"year": 2006, "director": "Satoshi Kon", "rating": 8.6},
    ),
    Document(
        page_content="A bunch of normal-sized women are supremely wholesome and some men pine after them",
        metadata={"year": 2019, "director": "Greta Gerwig", "rating": 8.3},
    ),
    Document(
        page_content="Toys come alive and have a blast doing so",
        metadata={"year": 1995, "genre": "animated"},
    ),
    Document(
        page_content="Three men walk into the Zone, three men walk out of the Zone",
        metadata={
            "year": 1979,
            "director": "Andrei Tarkovsky",
            "genre": "thriller",
            "rating": 9.9,
        },
    ),
]

# 定义词嵌入模型
EMBEDDING_DEVICE = "cuda" if is_torch_cuda_available() else "mps" if is_torch_mps_available() else "cpu"
embedding = HuggingFaceEmbeddings(model_name='D:\models\m3e-base', model_kwargs={'device': EMBEDDING_DEVICE})
vectorstore = FAISS.from_documents(docs, embedding)

# 定义元数据信息
metadata_field_info = [
    AttributeInfo(
        name="genre",
        description="The genre of the movie. One of ['science fiction', 'comedy', 'drama', 'thriller', 'romance', 'action', 'animated']",
        type="string",
    ),
    AttributeInfo(
        name="year",
        description="The year the movie was released",
        type="integer",
    ),
    AttributeInfo(
        name="director",
        description="The name of the movie director",
        type="string",
    ),
    AttributeInfo(
        name="rating", description="A 1-10 rating for the movie", type="float"
    ),
]
document_content_description = "Brief summary of a movie"

# 创建子查询检索器
retriever = SelfQueryRetriever.from_llm(
    llm_model,
    vectorstore,
    document_content_description,
    metadata_field_info,
)

# 检索
retriever.invoke("I want to watch a movie rated higher than 8.5")

"""
[Document(page_content='Three men walk into the Zone, three men walk out of the Zone', metadata={'director': 'Andrei Tarkovsky', 'genre': 'thriller', 'rating': 9.9, 'year': 1979}),
 Document(page_content='A psychologist / detective gets lost in a series of dreams within dreams within dreams and Inception reused the idea', metadata={'director': 'Satoshi Kon', 'rating': 8.6, 'year': 2006})]
"""

Time-weighted vector store retriever

时间加权矢量存储检索器是一种考虑了对象上次访问时间和语义相似性的检索方法。其评分算法为 semantic_similarity + (1.0 - decay_rate) ^ hours_passed,其中 hours_passed 是指自上次访问检索器中的对象以来经过的小时数。这种方法使得经常访问的对象保持 “新鲜” 状态。

  • 时间加权检索器的核心优势在于能够根据对象的最后访问时间调整其相关性评分,从而使得频繁访问的信息保持优先级。
  • 衰减率(decay_rate)的设置直接影响记忆保持的持久性。低衰减率意味着信息会被 “记忆” 更长时间,而高衰减率则会导致旧信息迅速被 “遗忘”。
  • 通过模拟时间(virtual time),可以更容易地测试和验证检索器在不同时间点下的表现。

这个实际项目种很大概率是用不到的(个人觉得),这里就不贴示例代码了,有需要的可以在官网看看。

03 总结

检索器是对向量存储的进一步封装,本质还是利用了向量存储的相似度搜索,但是功能更强更完善。

以上就是本次 Retrievers 的全部内容,希望能给到你们一些小小的帮助。

如果能帮我点个免费的关注,那就是对我个人的最大的肯定。如果觉得写的还行,分享一下也是我生活的小确幸~

在这里插入图片描述

以上内容依据官方文档编写,官方地址:https://python.langchain.com/docs/modules/data_connection/retrievers

Peace Guys~

图片

  • 【LangChain进阶教程】九、LangChain进阶之Vector Stores
  • 【LangChain进阶教程】八、LangChain进阶之Embedding Models
  • 【LangChain进阶教程】七、LangChain进阶之Text Splitters
  • 【LangChain进阶教程】六、LangChain进阶之Document loaders

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1906374.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++11右值引用及移动构造

区分左值和右值 在学习c11的右值引用前,大家肯定会有点陌生什么是右值?什么是左值?现在我先来带大家熟悉一下概念。 左值 可以被取地址,也可被修改(const修饰的除外) 可以出现在等号左边,也可…

华为HCIP Datacom H12-821 卷29

1.多选题 下面关于LSA age字段,描述正确的是∶ A、LSA age的单位为秒,在LSDB中的LSA的LS age随时间增长而增长 B、LSA age的单位为秒,在LSDB中的LSA的LS age随时间增长而减少 C、如果一条LSA的LS age达到了LS RefreshTime&#xff08…

【C++】AVL树(旋转、平衡因子)

🌈个人主页:秦jh_-CSDN博客🔥 系列专栏:https://blog.csdn.net/qinjh_/category_12575764.html?spm1001.2014.3001.5482 ​ 目录 前言 AVL树的概念 节点 插入 AVL树的旋转 新节点插入较高左子树的左侧---左左:…

Spring的AOP基础以及AOP的核心概念

2. AOP基础 学习完spring的事务管理之后,接下来我们进入到AOP的学习。 AOP也是spring框架的第二大核心,我们先来学习AOP的基础。 在AOP基础这个阶段,我们首先介绍一下什么是AOP,再通过一个快速入门程序,让大家快速体…

高级RAG检索中的五种查询重写策略_用于检索增强的大型语言模型的查询重写

一、前言 检索增强生成 (RAG) 作为人工智能 (AI) 领域的一项重要技术,近年来得到了飞速发展。它将基于检索模型和基于生成的模型相结合,利用海量外部数据,生成更具信息量、更准确、更具语境相关性的回复。检索策略是 RAG 系统的关键组成部分…

2024年最适合高级网工的11款Linux

号主:老杨丨11年资深网络工程师,更多网工提升干货,请关注公众号:网络工程师俱乐部 你们好,我的网工朋友。 Linux作为一个免费且开源的操作系统,随着时间的推移催生了多个发行版,并且得到了庞大…

golang验证Etherscan上的智能合约

文章目录 golang验证Etherscan上的智能合约为什么要验证智能合约如何使用golang去验证合约获取EtherscanAPI密钥Verify Source Code接口Check Source Code Verification Status接口演示示例及注意事项网络问题无法调用Etherscan接口(最重要的步骤) golan…

应用层协议原理——因特网提供的运输服务

我们已经考虑了计算机网络能够一般性地提供的运输服务。现在我们要更为具体地考察由因特网提供的运输服务类型。因特网(更一般的是TCP/IP网络)为应用程序提供两个运输层协议,即UDP和TCP。当软件开发者为因特网创建一个新的应用时,首先要做出的决定是&…

js逆向案例 | 加速乐反爬逆向

前言 加速乐作为一种常见的反爬虫技术,在网络上已有大量详尽深入的教程可供参考。然而,对于那些初次接触的人来说,直接面对它可能仍会感到困惑。 声明 本文仅用于学习交流,学习探讨逆向知识,欢迎私信共享学习心得。如…

收银系统源码-商品报损管理

千呼新零售2.0系统是零售行业连锁店一体化收银系统,包括线下收银线上商城连锁店管理ERP管理商品管理供应商管理会员营销等功能为一体,线上线下数据全部打通。 适用于商超、便利店、水果、生鲜、母婴、服装、零食、百货、宠物等连锁店使用。 详细介绍请…

ESXi6.7 update 3主机实现新硬件运行老环境

server 2003 SQL server 2000 SQL SP4 vmware tools 一、适用场景 1、运行多年的老企业,积累的数据量庞大,其中的数据库并不一定都是现在开发的平台或系统,而是已经正在运行,不能停业务的状态。 2、老系统老应用平台&#xf…

day01:项目概述,环境搭建

文章目录 软件开发整体介绍软件开发流程角色分工软件环境 外卖平台项目介绍项目介绍定位功能架构 产品原型技术选型 开发环境搭建整体结构:前后端分离开发前后端混合开发缺点前后端分离开发 前端环境搭建Nginx 后端环境搭建熟悉项目结构使用Git进行版本控制数据库环…

Day06-01-lvs

Day06-01-lvs 0. 核心内容1.负载均衡项目 选择故障: 2.lvs 预备姿势-arp3.lvs 概述4. lvs工作模式4.1 预备姿势4.2 lvs-dr模式4.3 lvs-nat模式4.4 小结 5. lvs-dr模式5.1 环境准备5.2 lvs-dr模式配置流程1) lvs服务端配置2) web服务器 RS服务端配置3) 小结4) 调试 5.3 抓包查看…

电脑清理软件用哪个好?这款工具有些饱受争议

电脑清理软件用哪个好?电脑作为我们工作和娱乐的重要工具,其运行速度和安全性直接影响着我们的生活质量。然而,随着时间的推移,电脑系统会积累大量垃圾文件、注册表错误、恶意软件等,导致电脑运行缓慢甚至崩溃。 这时&#xff0c…

el-tree 获取当前勾选节点的选中状态以及选中值对象 触发check-change多次事件问题原因

1.需求 现在需要一个树状结构的资产树 但是现在需求是 获取当前选中的值的状态是选中还是取消选中 然后再用当前选中 or 取消选中的值 进行 选中 or 取消选中的操作 一开始使用的是 check-change 方法 接收参数如图 但是我勾选父节点 或者 子节点后 他会打印一堆数据 是因…

基于Java+SpringMvc+Vue技术的智慧校园系统设计与实现

博主介绍:硕士研究生,专注于信息化技术领域开发与管理,会使用java、标准c/c等开发语言,以及毕业项目实战✌ 从事基于java BS架构、CS架构、c/c 编程工作近16年,拥有近12年的管理工作经验,拥有较丰富的技术架…

昇思25天学习打卡营第11天 | LLM原理和实践:基于MindSpore实现BERT对话情绪识别

1. 基于MindSpore实现BERT对话情绪识别 1.1 环境配置 # 实验环境已经预装了mindspore2.2.14,如需更换mindspore版本,可更改下面mindspore的版本号 !pip uninstall mindspore -y !pip install -i https://pypi.mirrors.ustc.edu.cn/simple mindspore2.2…

geospy.AI 根据图片定位地理位置

文章目录 简介场景标志性建筑平凡的商店名标志性的物品标志性的地理位置标志性的街道难以分辨的古建筑不出名的山水 简介 GeoSpy.ai 作为一个地理空间情报平台,可以在以下场景中使用: 农业:监控农作物生长情况,预测产量&#xf…

课设:二手车交易管理系统(Java+MySQL)

简易数据库课程设计~分享 技术栈 本项目使用以下技术栈构建: Java: 作为主要编程语言,负责业务逻辑的实现。MySQL: 用于数据存储,管理用户、车辆和订单信息。JDBC: 用于Java与MySQL数据库之间的连接和操作。Swing GUI: 提供用户图形界面&am…

数据库课设---酒店管理系统(MySQL、VBNet)

目录 一. 知识技术 二. 需求分析 2.1 功能需求 2.2 数据需求 三. 数据流图与数据字典 3.1 数据流图 3.1.1 业务流图 3.1.2 数据流图 3.1.3 关系图 3.2 数据字典 四. 数据库设计 4.1 概念模型设计 4.2 逻辑模型设计 4.3 数据库实现 …