!function(){function t(t){document.documentElement.setAttribute("data-theme",t)}var e=function(){var t=null;try{t=new URLSearchParams(window.location.search).get("docusaurus-theme")}catch(t){}return t}()||function(){var t=null;try{t=localStorage.getItem("theme")}catch(t){}return t}();t(null!==e?e:"light")}(),document.documentElement.setAttribute("data-announcement-bar-initially-dismissed",function(){try{return"true"===localStorage.getItem("docusaurus.announcement.dismiss")}catch(t){}return!1}())

[Skip to main content](#__docusaurus_skipToContent_fallback)

LangChain v0.2 is coming soon! Preview the new docs [here](/v0.2/docs/introduction/).

[

![🦜️🔗 Langchain](/v0.1/img/brand/wordmark.png)![🦜️🔗 Langchain](/v0.1/img/brand/wordmark-dark.png)

](/v0.1/)[Docs](/v0.1/docs/get_started/introduction/)[Use cases](/v0.1/docs/use_cases/)[Integrations](/v0.1/docs/integrations/platforms/)[API Reference](https://api.js.langchain.com)

[More](#)

*   [People](/v0.1/docs/people/)
*   [Community](/v0.1/docs/community/)
*   [Tutorials](/v0.1/docs/additional_resources/tutorials/)
*   [Contributing](/v0.1/docs/contributing/)

[v0.1](#)

*   [v0.2](https://js.langchain.com/v0.2/docs/introduction)
*   [v0.1](/v0.1/docs/get_started/introduction/)

[🦜🔗](#)

*   [LangSmith](https://smith.langchain.com)
*   [LangSmith Docs](https://docs.smith.langchain.com)
*   [LangChain Hub](https://smith.langchain.com/hub)
*   [LangServe](https://github.com/langchain-ai/langserve)
*   [Python Docs](https://python.langchain.com/)

[Chat](https://chatjs.langchain.com)[](https://github.com/langchain-ai/langchainjs)

Search

*   [Get started](/v0.1/docs/get_started/)
    
    *   [Introduction](/v0.1/docs/get_started/introduction/)
    *   [Installation](/v0.1/docs/get_started/installation/)
    *   [Quickstart](/v0.1/docs/get_started/quickstart/)
*   [LangChain Expression Language](/v0.1/docs/expression_language/)
    
    *   [Get started](/v0.1/docs/expression_language/get_started/)
    *   [Why use LCEL?](/v0.1/docs/expression_language/why/)
    *   [Interface](/v0.1/docs/expression_language/interface/)
    *   [Streaming](/v0.1/docs/expression_language/streaming/)
    *   [How to](/v0.1/docs/expression_language/how_to/routing/)
        
    *   [Cookbook](/v0.1/docs/expression_language/cookbook/)
        
    *   [LangChain Expression Language (LCEL)](/v0.1/docs/expression_language/)
*   [Modules](/v0.1/docs/modules/)
    
    *   [Model I/O](/v0.1/docs/modules/model_io/)
        
    *   [Retrieval](/v0.1/docs/modules/data_connection/)
        
        *   [Document loaders](/v0.1/docs/modules/data_connection/document_loaders/)
            
        *   [Text Splitters](/v0.1/docs/modules/data_connection/document_transformers/)
            
        *   [Retrievers](/v0.1/docs/modules/data_connection/retrievers/)
            
            *   [Custom retrievers](/v0.1/docs/modules/data_connection/retrievers/custom/)
            *   [Contextual compression](/v0.1/docs/modules/data_connection/retrievers/contextual_compression/)
            *   [Matryoshka Retriever](/v0.1/docs/modules/data_connection/retrievers/matryoshka_retriever/)
            *   [MultiQuery Retriever](/v0.1/docs/modules/data_connection/retrievers/multi-query-retriever/)
            *   [MultiVector Retriever](/v0.1/docs/modules/data_connection/retrievers/multi-vector-retriever/)
            *   [Parent Document Retriever](/v0.1/docs/modules/data_connection/retrievers/parent-document-retriever/)
            *   [Self-querying](/v0.1/docs/modules/data_connection/retrievers/self_query/)
                
            *   [Similarity Score Threshold](/v0.1/docs/modules/data_connection/retrievers/similarity-score-threshold-retriever/)
            *   [Time-weighted vector store retriever](/v0.1/docs/modules/data_connection/retrievers/time_weighted_vectorstore/)
            *   [Vector store-backed retriever](/v0.1/docs/modules/data_connection/retrievers/vectorstore/)
        *   [Retrieval](/v0.1/docs/modules/data_connection/)
        *   [Text embedding models](/v0.1/docs/modules/data_connection/text_embedding/)
            
        *   [Vector stores](/v0.1/docs/modules/data_connection/vectorstores/)
            
        *   [Indexing](/v0.1/docs/modules/data_connection/indexing/)
        *   [Experimental](/v0.1/docs/modules/data_connection/experimental/multimodal_embeddings/google_vertex_ai/)
            
    *   [Chains](/v0.1/docs/modules/chains/)
    *   [Agents](/v0.1/docs/modules/agents/)
        
    *   [More](/v0.1/docs/modules/memory/)
        
*   [Security](/v0.1/docs/security/)
*   [Guides](/v0.1/docs/guides/)
    
*   [Ecosystem](/v0.1/docs/ecosystem/)
    
*   [LangGraph](/v0.1/docs/langgraph/)
*   * * *
    

*   [](/v0.1/)
*   [Modules](/v0.1/docs/modules/)
*   [Retrieval](/v0.1/docs/modules/data_connection/)
*   [Retrievers](/v0.1/docs/modules/data_connection/retrievers/)
*   Parent Document Retriever

Parent Document Retriever
=========================

When splitting documents for retrieval, there are often conflicting desires:

1.  You may want to have small documents, so that their embeddings can most accurately reflect their meaning. If too long, then the embeddings can lose meaning.
2.  You want to have long enough documents that the context of each chunk is retained.

The ParentDocumentRetriever strikes that balance by splitting and storing small chunks of data. During retrieval, it first fetches the small chunks but then looks up the parent ids for those chunks and returns those larger documents.

Note that "parent document" refers to the document that a small chunk originated from. This can either be the whole raw document OR a larger chunk.

Usage[​](#usage "Direct link to Usage")
---------------------------------------

tip

See [this section for general instructions on installing integration packages](/v0.1/docs/get_started/installation/#installing-integration-packages).

*   npm
*   Yarn
*   pnpm

    npm install @langchain/openai

    yarn add @langchain/openai

    pnpm add @langchain/openai

    import { OpenAIEmbeddings } from "@langchain/openai";import { MemoryVectorStore } from "langchain/vectorstores/memory";import { InMemoryStore } from "langchain/storage/in_memory";import { ParentDocumentRetriever } from "langchain/retrievers/parent_document";import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";import { TextLoader } from "langchain/document_loaders/fs/text";const vectorstore = new MemoryVectorStore(new OpenAIEmbeddings());const docstore = new InMemoryStore();const retriever = new ParentDocumentRetriever({  vectorstore,  docstore,  // Optional, not required if you're already passing in split documents  parentSplitter: new RecursiveCharacterTextSplitter({    chunkOverlap: 0,    chunkSize: 500,  }),  childSplitter: new RecursiveCharacterTextSplitter({    chunkOverlap: 0,    chunkSize: 50,  }),  // Optional `k` parameter to search for more child documents in VectorStore.  // Note that this does not exactly correspond to the number of final (parent) documents  // retrieved, as multiple child documents can point to the same parent.  childK: 20,  // Optional `k` parameter to limit number of final, parent documents returned from this  // retriever and sent to LLM. This is an upper-bound, and the final count may be lower than this.  parentK: 5,});const textLoader = new TextLoader("../examples/state_of_the_union.txt");const parentDocuments = await textLoader.load();// We must add the parent documents via the retriever's addDocuments methodawait retriever.addDocuments(parentDocuments);const retrievedDocs = await retriever.invoke("justice breyer");// Retrieved chunks are the larger parent chunksconsole.log(retrievedDocs);/*  [    Document {      pageContent: 'Tonight, I call on the Senate to pass — pass the Freedom to Vote Act. Pass the John Lewis Act — Voting Rights Act. And while you’re at it, pass the DISCLOSE Act so Americans know who is funding our elections.\n' +        '\n' +        'Look, tonight, I’d — I’d like to honor someone who has dedicated his life to serve this country: Justice Breyer — an Army veteran, Constitutional scholar, retiring Justice of the United States Supreme Court.',      metadata: { source: '../examples/state_of_the_union.txt', loc: [Object] }    },    Document {      pageContent: 'As I did four days ago, I’ve nominated a Circuit Court of Appeals — Ketanji Brown Jackson. One of our nation’s top legal minds who will continue in just Brey- — Justice Breyer’s legacy of excellence. A former top litigator in private practice, a former federal public defender from a family of public-school educators and police officers — she’s a consensus builder.',      metadata: { source: '../examples/state_of_the_union.txt', loc: [Object] }    },    Document {      pageContent: 'Justice Breyer, thank you for your service. Thank you, thank you, thank you. I mean it. Get up. Stand — let me see you. Thank you.\n' +        '\n' +        'And we all know — no matter what your ideology, we all know one of the most serious constitutional responsibilities a President has is nominating someone to serve on the United States Supreme Court.',      metadata: { source: '../examples/state_of_the_union.txt', loc: [Object] }    }  ]*/

#### API Reference:

*   [OpenAIEmbeddings](https://api.js.langchain.com/classes/langchain_openai.OpenAIEmbeddings.html) from `@langchain/openai`
*   [MemoryVectorStore](https://api.js.langchain.com/classes/langchain_vectorstores_memory.MemoryVectorStore.html) from `langchain/vectorstores/memory`
*   [InMemoryStore](https://api.js.langchain.com/classes/langchain_core_stores.InMemoryStore.html) from `langchain/storage/in_memory`
*   [ParentDocumentRetriever](https://api.js.langchain.com/classes/langchain_retrievers_parent_document.ParentDocumentRetriever.html) from `langchain/retrievers/parent_document`
*   [RecursiveCharacterTextSplitter](https://api.js.langchain.com/classes/langchain_textsplitters.RecursiveCharacterTextSplitter.html) from `langchain/text_splitter`
*   [TextLoader](https://api.js.langchain.com/classes/langchain_document_loaders_fs_text.TextLoader.html) from `langchain/document_loaders/fs/text`

With Score Threshold[​](#with-score-threshold "Direct link to With Score Threshold")
------------------------------------------------------------------------------------

By setting the options in `scoreThresholdOptions` we can force the `ParentDocumentRetriever` to use the `ScoreThresholdRetriever` under the hood. This sets the vector store inside `ScoreThresholdRetriever` as the one we passed when initializing `ParentDocumentRetriever`, while also allowing us to also set a score threshold for the retriever.

This can be helpful when you're not sure how many documents you want (or if you are sure, just set the `maxK` option), but you want to make sure that the documents you do get are within a certain relevancy threshold.

Note: if a retriever is passed, `ParentDocumentRetriever` will default to use it for retrieving small chunks, as well as adding documents via the `addDocuments` method.

    import { OpenAIEmbeddings } from "@langchain/openai";import { MemoryVectorStore } from "langchain/vectorstores/memory";import { InMemoryStore } from "langchain/storage/in_memory";import { ParentDocumentRetriever } from "langchain/retrievers/parent_document";import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";import { TextLoader } from "langchain/document_loaders/fs/text";import { ScoreThresholdRetriever } from "langchain/retrievers/score_threshold";const vectorstore = new MemoryVectorStore(new OpenAIEmbeddings());const docstore = new InMemoryStore();const childDocumentRetriever = ScoreThresholdRetriever.fromVectorStore(  vectorstore,  {    minSimilarityScore: 0.01, // Essentially no threshold    maxK: 1, // Only return the top result  });const retriever = new ParentDocumentRetriever({  vectorstore,  docstore,  childDocumentRetriever,  // Optional, not required if you're already passing in split documents  parentSplitter: new RecursiveCharacterTextSplitter({    chunkOverlap: 0,    chunkSize: 500,  }),  childSplitter: new RecursiveCharacterTextSplitter({    chunkOverlap: 0,    chunkSize: 50,  }),});const textLoader = new TextLoader("../examples/state_of_the_union.txt");const parentDocuments = await textLoader.load();// We must add the parent documents via the retriever's addDocuments methodawait retriever.addDocuments(parentDocuments);const retrievedDocs = await retriever.invoke("justice breyer");// Retrieved chunk is the larger parent chunkconsole.log(retrievedDocs);/*  [    Document {      pageContent: 'Tonight, I call on the Senate to pass — pass the Freedom to Vote Act. Pass the John Lewis Act — Voting Rights Act. And while you’re at it, pass the DISCLOSE Act so Americans know who is funding our elections.\n' +        '\n' +        'Look, tonight, I’d — I’d like to honor someone who has dedicated his life to serve this country: Justice Breyer — an Army veteran, Constitutional scholar, retiring Justice of the United States Supreme Court.',      metadata: { source: '../examples/state_of_the_union.txt', loc: [Object] }    },  ]*/

#### API Reference:

*   [OpenAIEmbeddings](https://api.js.langchain.com/classes/langchain_openai.OpenAIEmbeddings.html) from `@langchain/openai`
*   [MemoryVectorStore](https://api.js.langchain.com/classes/langchain_vectorstores_memory.MemoryVectorStore.html) from `langchain/vectorstores/memory`
*   [InMemoryStore](https://api.js.langchain.com/classes/langchain_core_stores.InMemoryStore.html) from `langchain/storage/in_memory`
*   [ParentDocumentRetriever](https://api.js.langchain.com/classes/langchain_retrievers_parent_document.ParentDocumentRetriever.html) from `langchain/retrievers/parent_document`
*   [RecursiveCharacterTextSplitter](https://api.js.langchain.com/classes/langchain_textsplitters.RecursiveCharacterTextSplitter.html) from `langchain/text_splitter`
*   [TextLoader](https://api.js.langchain.com/classes/langchain_document_loaders_fs_text.TextLoader.html) from `langchain/document_loaders/fs/text`
*   [ScoreThresholdRetriever](https://api.js.langchain.com/classes/langchain_retrievers_score_threshold.ScoreThresholdRetriever.html) from `langchain/retrievers/score_threshold`

With Contextual chunk headers[​](#with-contextual-chunk-headers "Direct link to With Contextual chunk headers")
---------------------------------------------------------------------------------------------------------------

Consider a scenario where you want to store collection of documents in a vector store and perform Q&A tasks on them. Simply splitting documents with overlapping text may not provide sufficient context for LLMs to determine if multiple chunks are referencing the same information, or how to resolve information from contradictory sources.

Tagging each document with metadata is a solution if you know what to filter against, but you may not know ahead of time exactly what kind of queries your vector store will be expected to handle. Including additional contextual information directly in each chunk in the form of headers can help deal with arbitrary queries.

This is particularly important if you have several fine-grained child chunks that need to be correctly retrieved from the vector store.

    import { OpenAIEmbeddings } from "@langchain/openai";import { HNSWLib } from "@langchain/community/vectorstores/hnswlib";import { InMemoryStore } from "langchain/storage/in_memory";import { ParentDocumentRetriever } from "langchain/retrievers/parent_document";import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";const splitter = new RecursiveCharacterTextSplitter({  chunkSize: 1500,  chunkOverlap: 0,});const jimDocs = await splitter.createDocuments([`My favorite color is blue.`]);const jimChunkHeaderOptions = {  chunkHeader: "DOC NAME: Jim Interview\n---\n",  appendChunkOverlapHeader: true,};const pamDocs = await splitter.createDocuments([`My favorite color is red.`]);const pamChunkHeaderOptions = {  chunkHeader: "DOC NAME: Pam Interview\n---\n",  appendChunkOverlapHeader: true,};const vectorstore = await HNSWLib.fromDocuments([], new OpenAIEmbeddings());const docstore = new InMemoryStore();const retriever = new ParentDocumentRetriever({  vectorstore,  docstore,  // Very small chunks for demo purposes.  // Use a bigger chunk size for serious use-cases.  childSplitter: new RecursiveCharacterTextSplitter({    chunkSize: 10,    chunkOverlap: 0,  }),  childK: 50,  parentK: 5,});// We pass additional option `childDocChunkHeaderOptions`// that will add the chunk header to child documentsawait retriever.addDocuments(jimDocs, {  childDocChunkHeaderOptions: jimChunkHeaderOptions,});await retriever.addDocuments(pamDocs, {  childDocChunkHeaderOptions: pamChunkHeaderOptions,});// This will search child documents in vector store with the help of chunk header,// returning the unmodified parent documentsconst retrievedDocs = await retriever.invoke("What is Pam's favorite color?");// Pam's favorite color is returned first!console.log(JSON.stringify(retrievedDocs, null, 2));/*  [    {      "pageContent": "My favorite color is red.",      "metadata": {        "loc": {          "lines": {            "from": 1,            "to": 1          }        }      }    },    {      "pageContent": "My favorite color is blue.",      "metadata": {        "loc": {          "lines": {            "from": 1,            "to": 1          }        }      }    }  ]*/const rawDocs = await vectorstore.similaritySearch(  "What is Pam's favorite color?");// Raw docs in vectorstore are short but have chunk headersconsole.log(JSON.stringify(rawDocs, null, 2));/*  [    {      "pageContent": "DOC NAME: Pam Interview\n---\n(cont'd) color is",      "metadata": {        "loc": {          "lines": {            "from": 1,            "to": 1          }        },        "doc_id": "affdcbeb-6bfb-42e9-afe5-80f4f2e9f6aa"      }    },    {      "pageContent": "DOC NAME: Pam Interview\n---\n(cont'd) favorite",      "metadata": {        "loc": {          "lines": {            "from": 1,            "to": 1          }        },        "doc_id": "affdcbeb-6bfb-42e9-afe5-80f4f2e9f6aa"      }    },    {      "pageContent": "DOC NAME: Pam Interview\n---\n(cont'd) red.",      "metadata": {        "loc": {          "lines": {            "from": 1,            "to": 1          }        },        "doc_id": "affdcbeb-6bfb-42e9-afe5-80f4f2e9f6aa"      }    },    {      "pageContent": "DOC NAME: Pam Interview\n---\nMy",      "metadata": {        "loc": {          "lines": {            "from": 1,            "to": 1          }        },        "doc_id": "affdcbeb-6bfb-42e9-afe5-80f4f2e9f6aa"      }    }  ]*/

#### API Reference:

*   [OpenAIEmbeddings](https://api.js.langchain.com/classes/langchain_openai.OpenAIEmbeddings.html) from `@langchain/openai`
*   [HNSWLib](https://api.js.langchain.com/classes/langchain_community_vectorstores_hnswlib.HNSWLib.html) from `@langchain/community/vectorstores/hnswlib`
*   [InMemoryStore](https://api.js.langchain.com/classes/langchain_core_stores.InMemoryStore.html) from `langchain/storage/in_memory`
*   [ParentDocumentRetriever](https://api.js.langchain.com/classes/langchain_retrievers_parent_document.ParentDocumentRetriever.html) from `langchain/retrievers/parent_document`
*   [RecursiveCharacterTextSplitter](https://api.js.langchain.com/classes/langchain_textsplitters.RecursiveCharacterTextSplitter.html) from `langchain/text_splitter`

With Reranking[​](#with-reranking "Direct link to With Reranking")
------------------------------------------------------------------

With many documents from the vector store that are passed to LLM, final answers sometimes consist of information from irrelevant chunks, making it less precise and sometimes incorrect. Also, passing multiple irrelevant documents makes it more expensive. So there are two reasons to use rerank - precision and costs.

    import { OpenAIEmbeddings } from "@langchain/openai";import { CohereRerank } from "@langchain/cohere";import { HNSWLib } from "@langchain/community/vectorstores/hnswlib";import { InMemoryStore } from "langchain/storage/in_memory";import {  ParentDocumentRetriever,  type SubDocs,} from "langchain/retrievers/parent_document";import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";// init Cohere Rerank. Remember to add COHERE_API_KEY to your .envconst reranker = new CohereRerank({  topN: 50,  model: "rerank-multilingual-v2.0",});export function documentCompressorFiltering({  relevanceScore,}: { relevanceScore?: number } = {}) {  return (docs: SubDocs) => {    let outputDocs = docs;    if (relevanceScore) {      const docsRelevanceScoreValues = docs.map(        (doc) => doc?.metadata?.relevanceScore      );      outputDocs = docs.filter(        (_doc, index) =>          (docsRelevanceScoreValues?.[index] || 1) >= relevanceScore      );    }    return outputDocs;  };}const splitter = new RecursiveCharacterTextSplitter({  chunkSize: 500,  chunkOverlap: 0,});const jimDocs = await splitter.createDocuments([`Jim favorite color is blue.`]);const pamDocs = await splitter.createDocuments([`Pam favorite color is red.`]);const vectorstore = await HNSWLib.fromDocuments([], new OpenAIEmbeddings());const docstore = new InMemoryStore();const retriever = new ParentDocumentRetriever({  vectorstore,  docstore,  // Very small chunks for demo purposes.  // Use a bigger chunk size for serious use-cases.  childSplitter: new RecursiveCharacterTextSplitter({    chunkSize: 10,    chunkOverlap: 0,  }),  childK: 50,  parentK: 5,  // We add Reranker  documentCompressor: reranker,  documentCompressorFilteringFn: documentCompressorFiltering({    relevanceScore: 0.3,  }),});const docs = jimDocs.concat(pamDocs);await retriever.addDocuments(docs);// This will search for documents in vector store and return for LLM already reranked and sorted document// with appropriate minimum relevance scoreconst retrievedDocs = await retriever.getRelevantDocuments(  "What is Pam's favorite color?");// Pam's favorite color is returned first!console.log(JSON.stringify(retrievedDocs, null, 2));/*  [    {      "pageContent": "My favorite color is red.",      "metadata": {        "relevanceScore": 0.9        "loc": {          "lines": {            "from": 1,            "to": 1          }        }      }    }  ]*/

#### API Reference:

*   [OpenAIEmbeddings](https://api.js.langchain.com/classes/langchain_openai.OpenAIEmbeddings.html) from `@langchain/openai`
*   [CohereRerank](https://api.js.langchain.com/classes/langchain_cohere.CohereRerank.html) from `@langchain/cohere`
*   [HNSWLib](https://api.js.langchain.com/classes/langchain_community_vectorstores_hnswlib.HNSWLib.html) from `@langchain/community/vectorstores/hnswlib`
*   [InMemoryStore](https://api.js.langchain.com/classes/langchain_core_stores.InMemoryStore.html) from `langchain/storage/in_memory`
*   [ParentDocumentRetriever](https://api.js.langchain.com/classes/langchain_retrievers_parent_document.ParentDocumentRetriever.html) from `langchain/retrievers/parent_document`
*   [SubDocs](https://api.js.langchain.com/types/langchain_retrievers_parent_document.SubDocs.html) from `langchain/retrievers/parent_document`
*   [RecursiveCharacterTextSplitter](https://api.js.langchain.com/classes/langchain_textsplitters.RecursiveCharacterTextSplitter.html) from `langchain/text_splitter`

* * *

#### Help us out by providing feedback on this documentation page:

[

Previous

MultiVector Retriever

](/v0.1/docs/modules/data_connection/retrievers/multi-vector-retriever/)[

Next

Self-querying

](/v0.1/docs/modules/data_connection/retrievers/self_query/)

Community

*   [Discord](https://discord.gg/cU2adEyC7w)
*   [Twitter](https://twitter.com/LangChainAI)

GitHub

*   [Python](https://github.com/langchain-ai/langchain)
*   [JS/TS](https://github.com/langchain-ai/langchainjs)

More

*   [Homepage](https://langchain.com)
*   [Blog](https://blog.langchain.dev)

Copyright © 2024 LangChain, Inc.