libs/langchain/langchain_classic/retrievers/multi_vector.py PYTHON 156 lines View on github.com → Search inside
1from enum import Enum2from typing import Any34from langchain_core.callbacks import (5    AsyncCallbackManagerForRetrieverRun,6    CallbackManagerForRetrieverRun,7)8from langchain_core.documents import Document9from langchain_core.retrievers import BaseRetriever10from langchain_core.stores import BaseStore, ByteStore11from langchain_core.vectorstores import VectorStore12from pydantic import Field, model_validator13from typing_extensions import override1415from langchain_classic.storage._lc_store import create_kv_docstore161718class SearchType(str, Enum):19    """Enumerator of the types of search to perform."""2021    similarity = "similarity"22    """Similarity search."""23    similarity_score_threshold = "similarity_score_threshold"24    """Similarity search with a score threshold."""25    mmr = "mmr"26    """Maximal Marginal Relevance reranking of similarity search."""272829class MultiVectorRetriever(BaseRetriever):30    """Retriever that supports multiple embeddings per parent document.3132    This retriever is designed for scenarios where documents are split into33    smaller chunks for embedding and vector search, but retrieval returns34    the original parent documents rather than individual chunks.3536    It works by:37    - Performing similarity (or MMR) search over embedded child chunks38    - Collecting unique parent document IDs from chunk metadata39    - Fetching and returning the corresponding parent documents from the docstore4041    This pattern is commonly used in RAG pipelines to improve answer grounding42    while preserving full document context.43    """4445    vectorstore: VectorStore46    """The underlying `VectorStore` to use to store small chunks47    and their embedding vectors"""4849    byte_store: ByteStore | None = None50    """The lower-level backing storage layer for the parent documents"""5152    docstore: BaseStore[str, Document]53    """The storage interface for the parent documents"""5455    id_key: str = "doc_id"5657    search_kwargs: dict = Field(default_factory=dict)58    """Keyword arguments to pass to the search function."""5960    search_type: SearchType = SearchType.similarity61    """Type of search to perform (similarity / mmr)"""6263    @model_validator(mode="before")64    @classmethod65    def _shim_docstore(cls, values: dict) -> Any:66        byte_store = values.get("byte_store")67        docstore = values.get("docstore")68        if byte_store is not None:69            docstore = create_kv_docstore(byte_store)70        elif docstore is None:71            msg = "You must pass a `byte_store` parameter."72            raise ValueError(msg)73        values["docstore"] = docstore74        return values7576    @override77    def _get_relevant_documents(78        self,79        query: str,80        *,81        run_manager: CallbackManagerForRetrieverRun,82    ) -> list[Document]:83        """Get documents relevant to a query.8485        Args:86            query: String to find relevant documents for87            run_manager: The callbacks handler to use88        Returns:89            List of relevant documents.90        """91        if self.search_type == SearchType.mmr:92            sub_docs = self.vectorstore.max_marginal_relevance_search(93                query,94                **self.search_kwargs,95            )96        elif self.search_type == SearchType.similarity_score_threshold:97            sub_docs_and_similarities = (98                self.vectorstore.similarity_search_with_relevance_scores(99                    query,100                    **self.search_kwargs,101                )102            )103            sub_docs = [sub_doc for sub_doc, _ in sub_docs_and_similarities]104        else:105            sub_docs = self.vectorstore.similarity_search(query, **self.search_kwargs)106107        # We do this to maintain the order of the IDs that are returned108        ids = []109        for d in sub_docs:110            if self.id_key in d.metadata and d.metadata[self.id_key] not in ids:111                ids.append(d.metadata[self.id_key])112        docs = self.docstore.mget(ids)113        return [d for d in docs if d is not None]114115    @override116    async def _aget_relevant_documents(117        self,118        query: str,119        *,120        run_manager: AsyncCallbackManagerForRetrieverRun,121    ) -> list[Document]:122        """Asynchronously get documents relevant to a query.123124        Args:125            query: String to find relevant documents for126            run_manager: The callbacks handler to use127        Returns:128            List of relevant documents.129        """130        if self.search_type == SearchType.mmr:131            sub_docs = await self.vectorstore.amax_marginal_relevance_search(132                query,133                **self.search_kwargs,134            )135        elif self.search_type == SearchType.similarity_score_threshold:136            sub_docs_and_similarities = (137                await self.vectorstore.asimilarity_search_with_relevance_scores(138                    query,139                    **self.search_kwargs,140                )141            )142            sub_docs = [sub_doc for sub_doc, _ in sub_docs_and_similarities]143        else:144            sub_docs = await self.vectorstore.asimilarity_search(145                query,146                **self.search_kwargs,147            )148149        # We do this to maintain the order of the IDs that are returned150        ids = []151        for d in sub_docs:152            if self.id_key in d.metadata and d.metadata[self.id_key] not in ids:153                ids.append(d.metadata[self.id_key])154        docs = await self.docstore.amget(ids)155        return [d for d in docs if d is not None]

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.