{
  "source_file": "0930",
  "source_platform": "Medium",
  "author": "Angela Shi & Kezhan Shi",
  "title_listed": "Multi-Document RAG: A Folder of Unrelated PDFs Is One Long Document with a Nested Outline",
  "keywords": "",
  "domain": "大模型算法",
  "content_type": "深度技术解析",
  "is_paid_flag": "否",
  "url": "https://towardsdatascience.com/multi-document-rag-a-folder-of-unrelated-pdfs-is-one-long-document-with-a-nested-outline/",
  "publish_date": "2026-08-22",
  "corpus_stage": "",
  "notes": "",
  "id": "491af05b0d4d",
  "collected_at": "2026-10-02T01:02:30",
  "title": "Multi-Document RAG: A Folder of Unrelated PDFs Is One Long Document with a Nested Outline | Towards Data Science",
  "body_markdown": "![Revisit consent button](../images/body-df29beacd7496220cc94.svg)\nWe value your privacy\nWe use cookies to enhance your browsing experience, serve personalised ads or content, and analyse our traffic. By clicking \"Accept All\", you consent to our use of cookies.\nCustomise Reject All Accept All\nCustomise Consent Preferences ![](../images/body-2159bb67f3de8ad870eb.svg)\nWe use cookies to help you navigate efficiently and perform certain functions. You will find detailed information about all cookies under each consent category below.\nThe cookies that are categorised as \"Necessary\" are stored on your browser as they are essential for enabling the basic functionalities of the site. ... Show more\nNecessaryAlways Active\nNecessary cookies are required to enable the basic features of this site, such as providing secure log-in or adjusting your consent preferences. These cookies do not store any personally identifiable data.\n  * Cookie\nBCTempID\n  * Duration\n10 minutes\n  * Description\nNo description available.\n\n\n  * Cookie\n__cf_bm\n  * Duration\n1 hour\n  * Description\nThis cookie, set by Cloudflare, is used to support Cloudflare Bot Management. \n\n\n  * Cookie\nAWSALBCORS\n  * Duration\n7 days\n  * Description\nAmazon Web Services set this cookie for load balancing.\n\n\n  * Cookie\n_cfuvid\n  * Duration\nsession\n  * Description\nCloudflare sets this cookie to track users across sessions to optimize user experience by maintaining session consistency and providing personalized services\n\n\n  * Cookie\nli_gc\n  * Duration\n6 months\n  * Description\nLinkedin set this cookie for storing visitor's consent regarding using cookies for non-essential purposes.\n\n\n  * Cookie\n__hssrc\n  * Duration\nsession\n  * Description\nThis cookie is set by Hubspot whenever it changes the session cookie. The __hssrc cookie set to 1 indicates that the user has restarted the browser, and if the cookie does not exist, it is assumed to be a new session.\n\n\n  * Cookie\n__hssc\n  * Duration\n1 hour\n  * Description\nHubSpot sets this cookie to keep track of sessions and to determine if HubSpot should increment the session number and timestamps in the __hstc cookie.\n\n\n  * Cookie\nwpEmojiSettingsSupports\n  * Duration\nsession\n  * Description\nWordPress sets this cookie when a user interacts with emojis on a WordPress site. It helps determine if the user's browser can display emojis properly.\n\n\n  * Cookie\nBCSessionID\n  * Duration\n1 year 1 month 4 days\n  * Description\nBlueconic sets this cookie as a unique identifier for the BlueConic profile.\n\n\n  * Cookie\n_octo\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\nlogged_in\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\n__Secure-YEC\n  * Duration\npast\n  * Description\nYouTube sets this cookie to stores the user's video player preferences using embedded YouTube video\n\n\n  * Cookie\n__eoi\n  * Duration\n6 months\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nAWSALBTGCORS\n  * Duration\n7 days\n  * Description\nNo description available.\n\n\n  * Cookie\nlogin-status-p\n  * Duration\npast\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nAWSALBTG\n  * Duration\n7 days\n  * Description\nNo description available.\n\n\n  * Cookie\ncsrf_token\n  * Duration\nsession\n  * Description\nNo description available.\n\n\n  * Cookie\ntoken_v2\n  * Duration\n1 day\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nD\n  * Duration\n1 year\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nPHPSESSID\n  * Duration\nsession\n  * Description\nThis cookie is native to PHP applications. The cookie stores and identifies a user's unique session ID to manage user sessions on the website. The cookie is a session cookie and will be deleted when all the browser windows are closed.\n\n\n  * Cookie\nVISITOR_PRIVACY_METADATA\n  * Duration\n6 months\n  * Description\nYouTube sets this cookie to store the user's cookie consent state for the current domain. \n\n\n  * Cookie\ncookietest\n  * Duration\nsession\n  * Description\nThe cookietest cookie is typically used to determine whether the user's browser accepts cookies, essential for website functionality and user experience.\n\n\n  * Cookie\n__Host-airtable-session\n  * Duration\n1 year\n  * Description\nThis cookie is used to enable us to integrate the services of Airtable.\n\n\n  * Cookie\n__Host-airtable-session.sig\n  * Duration\n1 year\n  * Description\nThis cookie is used to enable us to integrate the services of Airtable.\n\n\n  * Cookie\nm\n  * Duration\n1 year 1 month 4 days\n  * Description\nStripe sets this cookie for fraud prevention purposes. It identifies the device used to access the website, allowing the website to be formatted accordingly.\n\n\n  * Cookie\nBIGipServer*\n  * Duration\nsession\n  * Description\nMarketo sets this cookie to collect information about the user's online activity and build a profile about their interests to provide advertisements relevant to the user. \n\n\n  * Cookie\n__cfruid\n  * Duration\nsession\n  * Description\nCloudflare sets this cookie to identify trusted web traffic.\n\n\n  * Cookie\n_GRECAPTCHA\n  * Duration\n6 months\n  * Description\nGoogle Recaptcha service sets this cookie to identify bots to protect the website against malicious spam attacks.\n\n\n  * Cookie\n__Secure-YNID\n  * Duration\n6 months\n  * Description\nGoogle cookie used to protect user security and prevent fraud, especially during the login process.\n\n\n  * Cookie\ncookieyes-consent\n  * Duration\n1 year\n  * Description\nCookieYes sets this cookie to remember users' consent preferences so that their preferences are respected on subsequent visits to this site. It does not collect or store any personal information about the site visitors.\n\n\nFunctional\nFunctional cookies help perform certain functionalities like sharing the content of the website on social media platforms, collecting feedback, and other third-party features.\n  * Cookie\nlidc\n  * Duration\n1 day\n  * Description\nLinkedIn sets the lidc cookie to facilitate data center selection.\n\n\n  * Cookie\nbrw\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\nbrwConsent\n  * Duration\n5 minutes\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nWMF-Uniq\n  * Duration\n1 year\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nloom_anon_comment\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\nloom_referral_video\n  * Duration\nsession\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nVISITOR_INFO1_LIVE\n  * Duration\n6 months\n  * Description\nA cookie set by YouTube to measure bandwidth that determines whether the user gets the new or old player interface.\n\n\n  * Cookie\nyt-remote-connected-devices\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to store the user's video preferences using embedded YouTube videos.\n\n\n  * Cookie\nytidb::LAST_RESULT_ENTRY_KEY\n  * Duration\nNever Expires\n  * Description\nThe cookie ytidb::LAST_RESULT_ENTRY_KEY is used by YouTube to store the last search result entry that was clicked by the user. This information is used to improve the user experience by providing more relevant search results in the future.\n\n\n  * Cookie\nyt-remote-device-id\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to store the user's video preferences using embedded YouTube videos.\n\n\n  * Cookie\nyt-remote-session-name\n  * Duration\nsession\n  * Description\nThe yt-remote-session-name cookie is used by YouTube to store the user's video player preferences using embedded YouTube video.\n\n\n  * Cookie\nyt-remote-fast-check-period\n  * Duration\nsession\n  * Description\nThe yt-remote-fast-check-period cookie is used by YouTube to store the user's video player preferences for embedded YouTube videos.\n\n\n  * Cookie\nyt-remote-session-app\n  * Duration\nsession\n  * Description\nThe yt-remote-session-app cookie is used by YouTube to store user preferences and information about the interface of the embedded YouTube video player.\n\n\n  * Cookie\nyt-remote-cast-available\n  * Duration\nsession\n  * Description\nThe yt-remote-cast-available cookie is used to store the user's preferences regarding whether casting is available on their YouTube video player.\n\n\n  * Cookie\nyt-remote-cast-installed\n  * Duration\nsession\n  * Description\nThe yt-remote-cast-installed cookie is used to store the user's video player preferences using embedded YouTube video.\n\n\n  * Cookie\ncp_session\n  * Duration\n3 months\n  * Description\nCodepen sets this cookie for Help systems found in the website.\n\n\n  * Cookie\nloid\n  * Duration\n1 year 1 month 4 days\n  * Description\nThis cookie is set by the Reddit. The cookie enables the sharing of content from the website onto the social media platform.\n\n\nAnalytics\nAnalytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics such as the number of visitors, bounce rate, traffic source, etc.\n  * Cookie\n__hstc\n  * Duration\n6 months\n  * Description\nHubspot set this main cookie for tracking visitors. It contains the domain, initial timestamp (first visit), last timestamp (last visit), current timestamp (this visit), and session number (increments for each subsequent session).\n\n\n  * Cookie\nhubspotutk\n  * Duration\n6 months\n  * Description\nHubSpot sets this cookie to keep track of the visitors to the website. This cookie is passed to HubSpot on form submission and used when deduplicating contacts.\n\n\n  * Cookie\n_ga\n  * Duration\n1 year 1 month 4 days\n  * Description\nGoogle Analytics sets this cookie to calculate visitor, session and campaign data and track site usage for the site's analytics report. The cookie stores information anonymously and assigns a randomly generated number to recognise unique visitors.\n\n\n  * Cookie\n_ga_*\n  * Duration\n1 year 1 month 4 days\n  * Description\nGoogle Analytics sets this cookie to store and count page views.\n\n\n  * Cookie\n__Host-psifi.analyticsTrace\n  * Duration\n6 hours\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\n__Host-psifi.analyticsTraceV2\n  * Duration\n6 hours\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\n_gh_sess\n  * Duration\nsession\n  * Description\nGitHub sets this cookie for temporary application and framework state between pages like what step the user is on in a multiple step form.\n\n\n  * Cookie\nYSC\n  * Duration\nsession\n  * Description\nYSC cookie is set by Youtube and is used to track the views of embedded videos on Youtube pages.\n\n\n  * Cookie\najs_anonymous_id\n  * Duration\n1 year\n  * Description\nThis cookie is set by Segment to count the number of people who visit a certain site by tracking if they have visited before.\n\n\n  * Cookie\nvuid\n  * Duration\n1 year 1 month 4 days\n  * Description\nVimeo installs this cookie to collect tracking information by setting a unique ID to embed videos on the website. \n\n\nPerformance\nPerformance cookies are used to understand and analyse the key performance indexes of the website which helps in delivering a better user experience for the visitors.\n  * Cookie\nAWSALB\n  * Duration\n7 days\n  * Description\nAWSALB is an application load balancer cookie set by Amazon Web Services to map the session to the target.\n\n\n  * Cookie\nacq\n  * Duration\npast\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nacq.sig\n  * Duration\npast\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nptc\n  * Duration\n2 years\n  * Description\nNo description available.\n\n\nAdvertisement\nAdvertisement cookies are used to provide visitors with customised advertisements based on the pages you visited previously and to analyse the effectiveness of the ad campaigns.\n  * Cookie\nmuc_ads\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to collect user behaviour and interaction data to optimize the website.\n\n\n  * Cookie\nguest_id_marketing\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to identify and track the website visitor.\n\n\n  * Cookie\nguest_id_ads\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to identify and track the website visitor.\n\n\n  * Cookie\npersonalization_id\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to integrate and share features for social media and also store information about how the user uses the website, for tracking and targeting.\n\n\n  * Cookie\nguest_id\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to identify and track the website visitor. It registers if a user is signed in to the Twitter platform and collects information about ad preferences.\n\n\n  * Cookie\nbcookie\n  * Duration\n1 year\n  * Description\nLinkedIn sets this cookie from LinkedIn share buttons and ad tags to recognize browser IDs.\n\n\n  * Cookie\n__Secure-ROLLOUT_TOKEN\n  * Duration\n6 months\n  * Description\nYouTube sets this cookie to manage feature rollout and experimentation. It helps Google control which new features or interface changes are shown to users as part of testing and staged rollouts, ensuring consistent experience for a given user during an experiment.\n\n\n  * Cookie\nyt.innertube::nextId\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.\n\n\n  * Cookie\nyt.innertube::requests\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.\n\n\n  * Cookie\nsession_tracker\n  * Duration\nsession\n  * Description\nThis cookie is set by the Reddit. This cookie is used to identify trusted web traffic. It also helps in adverstising on the website.\n\n\n  * Cookie\nedgebucket\n  * Duration\nsession\n  * Description\nReddit sets this cookie to save the information about a log-on Reddit user, for the purpose of advertisement recommendations and updating the content.\n\n\n  * Cookie\ndid\n  * Duration\n1 year\n  * Description\nArbor sets this cookie to show targeted ads to site visitors.This cookie expires after 2 months or 1 year.\n\n\nUncategorised\nOther uncategorised cookies are those that are being analysed and have not been classified into a category as yet.\nNo cookies to display.\nReject All Save My Preferences Accept All\nToggle Search[](https://towardsdatascience.com/)Toggle Mobile Navigation\n[](https://towardsdatascience.com/)\nPublish AI, ML & data-science insights to a global community of data professionals.\nSign in[Submit an Article](https://write.insightmediagroup.io)\n[Latest](https://towardsdatascience.com/latest/)[Editor's Picks](https://towardsdatascience.com/tag/editors-pick/)[Newsletter](https://towardsdatascience.com/tag/the-variable/)[ShipAI](https://towardsdatascience.com/shipai/)\n* * *\n[Write For TDS](https://towardsdatascience.com/submissions/)\n[LinkedIn](https://www.linkedin.com/company/towards-data-science/)[X](https://x.com/TDataScience)Toggle Search\n[Large Language Model](https://towardsdatascience.com/category/large-language-model/)\n# Multi-Document RAG: A Folder of Unrelated PDFs Is One Long Document with a Nested Outline\nEnterprise Document Intelligence [Vol.1 #14B] - No shared fields means no index to build. One summary line per file plus each file’s own table of contents, and retrieval routes down two levels\n[Angela and Kezhan Shi](https://towardsdatascience.com/author/angela.shi/)\nAugust 22, 202616 min read\n![](../images/000.jpg)\n[Photo](https://www.pexels.com/photo/11952176/) by Beyza Kaplan, via Pexels.\nThe standard advice for taking a RAG system past one document is to build an index: one row per document, one column per field you may want to filter on, then a filter in front of the search.\nThat advice assumes the documents have fields in common. Plenty of folders do not.\nTake a research folder: a 492-page catalogue of security controls, a zero-trust architecture spec, an AI risk framework, thirty-one machine-learning papers, and seven commodity market reports. Ask what column they share. There is no client, no amount, no effective date that would mean the same thing across all of them, and no other field a business user could name either.\nWhen there is no shared field, there is no table to build. That reads like a missing piece. It is the opposite. It means the preparation step collapses into two artefacts, and one of them the parser already returns for free.\nLearn this step by step with the interactive [AI Engineer](https://roadmap.sh/ai-engineer) roadmap.\nThis article works through that case:\n  * the two questions that tell it apart from the case that does need an index\n  * the preparation, which is one summary line per file plus each file’s own table of contents, and nothing else\n  * why the summary line has to be written for a router rather than for a reader\n  * the two-level routing that answers a question, and why the nested outline never enters a prompt whole\n  * four ways it breaks, including the point where the flat file list stops scaling\n\n\nThis article is part of Part IV of [Enterprise Document Intelligence](https://towardsdatascience.com/prompt-context-loop-the-three-engineering-layers-every-rag-system-is-built-on/), a series that builds an enterprise RAG system from four bricks. Part IV asks what changes when the input is a folder instead of a file, and the answer depends on which kind of folder you have. This one is the kind that needs the least new machinery.\n🧭 **New to the series? Start with the map** : [Prompt, Context, Loop](https://towardsdatascience.com/prompt-context-loop-the-three-engineering-layers-every-rag-system-is-built-on/) sets out the three engineering layers every RAG system is built on, the prompt (the call itself), the context (what fills the model’s window), the loop (when the next call fires and when it stops), and walks the whole series through that lens, article by article. It is the shortest way to see what is covered and where this one sits.\n![](../images/001.png)\n_where this article sits: Part IV, the folder that needs no index - Image by author_\n📓 **Build the two-level index over your own folder in the companion notebook** : write one summary line per file, print the level-0 list, then run the routing call and watch it keep one file out of sixty-three before a single page is read. Repo → [doc-intel/notebooks-vol1](https://github.com/doc-intel/notebooks-vol1).\n![](../images/002.png)\n_The public companion-code repo at doc-intel/notebooks-vol1 - Image by author_\nThe folder used throughout is 63 public PDFs, 4,211 pages in total: 25 NIST files, which are 24 publications plus a three-page scan of one of them (US Government work, public domain in the US, see the [NIST copyright statement](https://www.nist.gov/director/copyright-fair-use-and-licensing-statements-srd-data-and-software)), 31 arXiv papers (each under the arXiv non-exclusive distribution license declared on its own [abstract page](https://arxiv.org/)), and 7 issues of the World Bank [_Commodity Markets Outlook_](https://www.worldbank.org/en/research/commodity-markets) ([CC BY 3.0 IGO, as declared on the OKR publication page for April 2026](https://openknowledge.worldbank.org/entities/publication/3ede6f8c-ba03-4176-867c-40e604698570)).\n## 1. Which kind of folder do you have\nArticle 14A (three kinds of corpus, and what building for the wrong one costs) sets out three shapes a collection can take, and says that each one wants a different architecture. This article takes the one where the documents have nothing in common. Before building anything, it is worth being sure that is the case you are in, because the wrong guess costs a lot in either direction.\n### 1.1. Two questions that decide it\nThe first question: **do two documents in the folder ever refer to each other?** An amendment refers to its master agreement. A renewal refers to last year’s policy. A certificate belongs to a contract. If that happens, the folder has a structure you must model, and this article is not the one you want.\nThe second question: **can a business user name a field that every document has, and that means the same thing in each?** Client, effective date, premium, policy number. If the answer comes back fast and confidently, the folder is a database nobody has typed yet, and typing it is the job.\nIf both answers are no, there is nothing to put in the columns. You could still extract metadata (file name, page count, creation date), but none of it is a field anyone will filter on. A column that is empty for most rows and means something different in the rest is not a column.\n![](../images/001.png)\n_the same four questions, two folders, two architectures - Image by author_\nMixed folders exist, and they are common. A shared drive can hold 5,000 look-alike contracts next to 300 miscellaneous files that belong to nobody. That is not a contradiction, it is two corpora sharing a disk. Split them and run the right architecture on each.\n### 1.2. What the other case gets, and this one does not\nThe homogeneous case (Article 14C, where the business already knows the columns) gets three things this one cannot have.\nIt gets a **filter**. A question that says _retailer X, currently in force_ becomes a SQL clause, and 18,000 rows become three before anything is read.\nIt gets **aggregation**. _How many policies expire this quarter_ is answered by the index itself, with no document opened at all.\nIt gets a **shared vocabulary**. Every row has a client and a date, so the ontology that maps user words to column values pays off across the whole corpus.\nNone of that transfers here. There is no clause to write, nothing to count, and a vocabulary shared by a control catalogue and a commodity report would be a vocabulary about nothing. Articles 15 (preparing the corpus), 16 (the ontology behind the columns), and 17 (querying the corpus with SQL first) build all three, and they are the right articles when the second question above came back yes.\nWhat is left when you take those away is smaller than it looks, and it is the whole architecture for this case: **a folder whose documents have nothing in common is one long document.** The files are its chapters. Their sections are its subsections. You already know how to read a long document. You read its table of contents.\n## 2. The preparation: one line per file, plus the outline the parser returns\nTwo artefacts, built once at ingestion.\n**Level 0** is the list of files, one line each, saying what that file is about. **Level 1** is each file’s own table of contents. Together they are a nested outline: the folder’s chapters, then each chapter’s sections.\nThat is the entire preparation. No classification pass, no field extraction, no schema, no ontology.\n### 2.1. The summary line is the index, so write it for the router\nHere is where most of the quality lives, and it is easy to underestimate because the artefact is one sentence.\nThe summary line is not documentation. Nobody reads it. Its only consumer is the routing call that has to decide, from 63 lines, which files could hold the answer. It should be written the way you would write a column definition, not the way you would write a blurb.\n![](../images/body-ff559ec93d44256e94d6.png)\n_the same file, described two ways, and what each version lets the router do - Image by author_\nThree things make the difference between the two versions.\n**The subject in both vocabularies.** The words the user will type, and the words the document itself uses. A question about _account management_ has to land on a file whose line says _account management_ and _AC-2_ , because the router matches on text, not on intent.\n**The shape of what the file answers.** One numbered control per section is a different promise from a narrative report. It tells the router whether the answer is a section or a whole chapter, which is what the second level then acts on.\n**What the file is not about.** This is the part people skip, and it is the one that makes routing precise. _Not an implementation guide_ removes the file from every how-to question in one clause. Negative information is what lets a router discard sixty-two files without opening any of them.\nProducing the lines is cheap. One call per document at ingestion, over the first pages plus the table of contents, gives a usable draft; on a folder this size you can also write them by hand in an afternoon. Either way, the cost is paid once per document and re-paid only when the file changes. What matters is that the lines get reviewed like schema, not like copy.\n### 2.2. The second level costs nothing new\nThe parsing brick already returns a `toc_df`, one row per heading with its page range. Article 5B (the relational tables a parser returns) builds it from the PDF’s native outline. Nothing in this article extends it. Level 1 is that table, kept per file.\nFor the folder here, 47 of the 63 PDFs ship a native outline, and those 47 cost one parse each. The other 16 ship none: a scanned memo, a slide export, a two-page note.\nTwo answers work for those 16, and length is what picks between them. A short file becomes a leaf: when the router keeps it, you read it whole, and level 1 never runs. A long one gets its outline reconstructed first. Article 5septies (rebuilding the table of contents from the page that prints one) covers the case where the PDF shows a contents page but never encodes it as an outline. [Building Document Structure with Loop Engineering](https://towardsdatascience.com/building-document-structure-with-loop-engineering-recovering-a-pdfs-outline-from-body-typography-for-rag/) covers the harder one, where there is no contents page either and the headings have to be recovered from the body typography.\n![](../images/body-bc10adfec5e8f52dfb65.png)\n_63 files, 4,211 pages, and an index that is 63 sentences long - Image by author_\nAcross the folder the two levels come to 63 lines and 2,422 outline rows. The 63 is what the model reads on every question. The 2,422 is what it can reach, one file at a time, and almost never touches.\n## 3. Query time: routing down two levels\nTake one question through the folder: _“what does the account management control require?”_ The answer is five pages of [NIST SP 800-53 Rev. 5](https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final) (_Security and Privacy Controls for Information Systems and Organizations_ , US Government work, public domain in the US), control AC-2, pages 46 to 50. Sixty-two other files cannot hold it, and none of them will be opened.\n![](../images/body-c9b4969d96c30526ef99.png)\n_pick the files from 63 lines, then descend inside the ones that survive - Image by author_\n### 3.1. Level 0, picking the files\nOne call. The model reads 63 lines, each one a file name, its routing summary, and a keyword tally. It returns the file ids that could hold the answer, with a reason for each. Usually one to three files come back.\nThe keyword tally runs alongside, not instead. Counting how many of the question’s terms appear in a file’s summary line and in its outline titles costs nothing, and it catches what a summary sentence rounds off: an exact code like `AC-2` or `GV.OC-01` that the sentence never spelled out. The two signals go into the same prompt and the model decides, which is the pattern Article 7B (parallel detectors, then one call at the end) established inside a single document. Here it runs one level up, unchanged.\nTwo properties of this call are worth naming. It is bounded: 63 short lines, whatever the folder weighs on disk. And it is auditable: the reason string says why a file was kept, so a wrong answer can be traced to the routing decision rather than to a similarity score.\n### 3.2. Level 1, descending inside the survivors\nFrom here it is the single-document case, with nothing added. The file’s own outline becomes the current level, the model picks a branch, and if that branch has finer children the loop opens them and repeats. It stops at a leaf, or at a section short enough to read whole. Article 7quater (hierarchical retrieval, reading a long document by its table of contents) works that loop out in full on this same catalogue: eleven chapters, then twenty control families, then twenty-five controls, then AC-2.\nThe two levels are the same call.\npython\n\n```\n# The folder's file list is the top level of ONE table of contents:\n# level 0 rows are files, deeper rows are those files' own sections.\nlevel = corpus_toc[corpus_toc.level == 0]           # 63 rows, one per file\nfiles = reason_on_toc(question, level,              # the SAME router call\n                      section_signals=keyword_hits(question, level))\n\nsections = []\nfor file_id in files.section_ids:                   # typically 1 to 3\n    level = corpus_toc[(corpus_toc.file_id == file_id)\n                       & (corpus_toc.level == 1)]\n    while True:                                     # the one-document loop\n        pick    = reason_on_toc(question, level)\n        section = level[level.section_id.isin(pick.section_ids)]\n        kids    = immediate_children(corpus_toc, section)\n        if kids.empty or section.n_pages.max() <= SMALL:\n            break                                   # a leaf, or short enough\n        level = kids                                # open it, descend\n    sections.append(section)\n\n# 63 files -> 1 file -> AC-2 ACCOUNT MANAGEMENT -> 5 pages out of 4,211\n```\n\nEvery line that comes out keeps its file id, so a citation names the file it came from. On a folder whose documents share no vocabulary, that matters more than usual: an answer stitched from a control catalogue and a market report reads fluently and is worth nothing.\n### 3.3. Why the whole nested outline never goes in one prompt\nThe nested outline is one object, 63 lines plus 2,422 rows. Nothing stops you from serialising all of it into a single call. Three reasons not to.\n**It stops fitting.** Multiply this folder by twenty and the outline alone is a long prompt with no room left to reason in. The folders that need this architecture are the ones that keep growing.\n**Precision falls before the window does.** At level 0 the model compares 63 whole-document descriptions, which is a decision it makes well. Handed 2,485 mixed rows, the section it needs competes with 357 siblings from a document that is not even relevant, and a section title read out of its document is a weak signal.\n**It costs on every question.** Level 0 is 63 short lines, paid every time. Level 1 is only the outlines of the files that survived, so it is paid on one file rather than sixty-three.\nThe descent is also optional. On a folder of eight short files with shallow outlines, level 0 picks a file and there is nothing to descend into: the loop runs once and behaves like flat routing. The model decides at each level whether going deeper buys anything, which is the same termination rule the single-document loop uses.\n## 4. Where it breaks\nFour things go wrong with a two-level folder index, and they are not equally serious. The first is a writing problem you fix in an afternoon. The next two are parsing problems that show up as retrieval problems. The last one is a real ceiling.\n![](../images/body-2bc52e7dd81e624915df.png)\n_the four ways a folder index goes wrong, and what each one needs - Image by author_\n### 4.1. The summary line that says nothing\n_“A technical report on cybersecurity.”_ Twelve files match that description, so level 0 returns all twelve, or picks three of them for no reason you can reconstruct. The tell is specific: the level-0 call keeps too many files, while level 1 looks perfectly healthy on each one it is given.\nThe fix is to rewrite the line, and the test is cheap. Hold out ten real questions, run the level-0 call on each, and check that the file you would have picked by hand comes back. Ten questions catch most of it, because a bad summary line is usually bad for a whole class of question rather than for one.\nTreat the lines as an artefact under review. They are the index; a sloppy line is a sloppy column definition, and it will not announce itself.\n### 4.2. The document with no structure\nLevel 1 needs something to descend into. A file with no headings gives the loop nothing to pick from, so the router either reads it whole or reads nothing.\nReading it whole is fine most of the time. A three-page memo is a leaf, and the two-level scheme handles leaves naturally. The failure case is narrow and specific: long **and** structureless. A 200-page scanned bundle with no headings cannot be routed at any level, because there is nothing to route over.\nThat one is not a retrieval problem. It is a parsing problem, and it gets solved before this article’s machinery runs, by reconstructing an outline from whatever signal the document does carry.\n### 4.3. The flat file list stops scaling\nLevel 0 is a flat list. It works while the list stays readable in one call.\nA few hundred files is comfortable. A few thousand is not, and the failure is not the one people expect. The context window is not what gives out first; routing accuracy is. At 3,000 lines the level-0 call is the same top-k problem this whole architecture was built to avoid, moved up one floor.\nThe fix is one more level, not a different architecture. Group the files, by folder, by source, by year, by whatever grouping the shelf already has, and give each group its own summary line. Level 0 becomes 40 group lines, level 1 the files inside the groups the router kept, level 2 their outlines. Same routing call, one more turn of the same loop.\nThat is also the point where this case starts asking for what the homogeneous case has from day one. If the natural grouping turns out to be a field that every file has, the second question from section 1.1 has quietly changed its answer, and the folder wants an index after all.\n## 5. Conclusion\nA folder whose documents have nothing in common is one long document, and it wants the retrieval you already have rather than the corpus machinery of the articles around it.\nWhat it does not need is worth listing, because each item is a project someone will otherwise propose. No relational index: there are no columns to fill. No ontology: a vocabulary shared by a control catalogue and a commodity report describes nothing. No SQL agent: there is nothing to query. No entity extraction pass over the corpus: the entities do not recur across files, so there is nothing to link.\nWhat it does need is a sentence per file, written for the router, and the table of contents the parser already returns. Then routing runs twice: once over the file list, once inside the files that survived. Sixty-three files and 4,211 pages, and the model reads 63 lines plus one outline before landing on five pages.\nThe two follow-ups in this group take the other two shapes. Article 14C (the homogeneous corpus, where the business already knows the columns) covers the folder where the second question comes back yes. Article 14D (case files, one bundle about a single entity) covers the folder that is heterogeneous inside and repetitive across, which needs a bit of both.\n## 6. Further reading and sources\nEarlier in the series, on the pieces this article reuses:\n  * [Building Document Structure with Loop Engineering: Recovering a PDF’s Outline from Body Typography for RAG](https://towardsdatascience.com/building-document-structure-with-loop-engineering-recovering-a-pdfs-outline-from-body-typography-for-rag/). What to do about the 16 files in this folder that ship no native outline.\n  * Most RAG Hallucinations Are Retrieval Failures: Fix Retrieval, Not the Prompt. Why a routing mistake at level 0 shows up later as a generation problem.\n  * Context engineering for RAG question parsing: from a raw question to typed fields that steer retrieval and generation. Where the keywords the level-0 tally counts come from.\n  * [Prompt Engineering Isn’t Enough: How Four Bricks of Context Engineering Stop RAG Hallucinations](https://towardsdatascience.com/prompt-engineering-isnt-enough-how-four-bricks-of-context-engineering-stop-rag-hallucinations/). The four typed pieces that fill the call the router feeds.\n  * [Most RAG Hallucinations Are Extraction Errors: Seven Patterns for a Typed Generation Contract](https://towardsdatascience.com/most-rag-hallucinations-are-extraction-errors-seven-patterns-for-a-typed-generation-contract/). What generation does with the lines the descent returns, and why each one carries its file id.\n\n\nAlso referenced above, listed here without links: Article 5B (the relational tables a parser returns), Article 5septies (rebuilding a table of contents from the page that prints one), Article 7B (parallel detectors, then one call at the end), Article 7quater (hierarchical retrieval inside one long document), Article 14A (three kinds of corpus, and what building for the wrong one costs), Article 14C (the homogeneous corpus), Article 14D (case files), and Articles 15 to 17 (the index, the ontology, and the SQL-first query path for the other kind of folder).\nExternal sources:\n  * _Document Summary Index_ , LlamaIndex documentation ([docs.llamaindex.ai](https://docs.llamaindex.ai/en/stable/examples/index_structs/doc_summary/DocSummary/)). The same level-0 idea as a shipped open-source component: a summary per document, and retrieval that picks documents by summary before it picks passages. Worth reading for the prompt it uses to generate the summaries.\n  * Parth Sarthi et al., _RAPTOR: Recursive Abstractive Processing for Tree-Organized Retrieval_ , 2024 ([arXiv 2401.18059](https://arxiv.org/abs/2401.18059)). Builds the tree by clustering and summarising recursively when no structure exists. The contrast is the point: a folder of documents already ships a tree, so there is nothing to infer.\n  * Anthropic, _Introducing Contextual Retrieval_ , 2024 ([anthropic.com](https://www.anthropic.com/news/contextual-retrieval)). Prepends a short document-level context to each chunk so a passage stops being orphaned from its source. The summary line here is the same move at file granularity.\n  * Yunfan Gao et al., _Retrieval-Augmented Generation for Large Language Models: A Survey_ , 2023 ([arXiv 2312.10997](https://arxiv.org/abs/2312.10997)). The naive, advanced, modular vocabulary this article’s baseline sits against.\n  * Scott Barnett et al., _Seven Failure Points When Engineering a Retrieval Augmented Generation System_ , 2024 ([arXiv 2401.05856](https://arxiv.org/abs/2401.05856)). Field-measured failure modes; the missing-document and wrong-document points are exactly what level-0 routing is built to remove.\n\n\n## Register Now to Continue Reading\n### Unlock Towards Data Science with a Free Account\nAll articles on Towards Data Science are available to registered users. Create a free account today to access this article and thousands more.\nRegister for freeSign in to your account\n* * *\n![](../images/body-249d024535392fb76067.jpeg)\nWritten By\n## [Angela and Kezhan Shi](https://towardsdatascience.com/author/angela.shi/)\n[See all from Angela and Kezhan Shi](https://towardsdatascience.com/author/angela.shi/)\n[Document Processing](https://towardsdatascience.com/tag/document-processing/)[Llm](https://towardsdatascience.com/tag/llm/)[Llm Applications](https://towardsdatascience.com/tag/llm-applications/)[Rag](https://towardsdatascience.com/tag/rag/)[Rag Architecture](https://towardsdatascience.com/tag/rag-architecture/)\nShare This Article\n  * [](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Ftowardsdatascience.com%2Fmulti-document-rag-a-folder-of-unrelated-pdfs-is-one-long-document-with-a-nested-outline%2F&title=Multi-Document%20RAG%3A%20A%20Folder%20of%20Unrelated%20PDFs%20Is%20One%20Long%20Document%20with%20a%20Nested%20Outline)\n  * [](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Ftowardsdatascience.com%2Fmulti-document-rag-a-folder-of-unrelated-pdfs-is-one-long-document-with-a-nested-outline%2F&title=Multi-Document%20RAG%3A%20A%20Folder%20of%20Unrelated%20PDFs%20Is%20One%20Long%20Document%20with%20a%20Nested%20Outline)\n  * [](https://x.com/share?url=https%3A%2F%2Ftowardsdatascience.com%2Fmulti-document-rag-a-folder-of-unrelated-pdfs-is-one-long-document-with-a-nested-outline%2F&text=Multi-Document%20RAG%3A%20A%20Folder%20of%20Unrelated%20PDFs%20Is%20One%20Long%20Document%20with%20a%20Nested%20Outline)\n\n\nTowards Data Science is a community publication. Submit your insights to reach our global audience and earn through the TDS Author Payment Program.\n[Write for TDS](https://towardsdatascience.com/submissions/)\n## Related Articles\n  * [![](../images/003.jpg)](https://towardsdatascience.com/building-trustworthy-production-rag-systems-through-continuous-evaluation/)\n## [Building Trustworthy Production RAG Systems Through Continuous Evaluation](https://towardsdatascience.com/building-trustworthy-production-rag-systems-through-continuous-evaluation/)\n[Large Language Model](https://towardsdatascience.com/category/large-language-model/)\nA practical guide to building an evaluation workflow that catches retrieval failures, hallucinations, and performance drift before they reach users\n[Priyansh Bhardwaj](https://towardsdatascience.com/author/priyansh.bhardwaj/)\nJuly 15, 20269 min read\n  * [![](../images/004.jpeg)](https://towardsdatascience.com/learn-to-build-agentic-ai-systems-9e552d841525/)\n## [On a Time Crunch but Still Want to Learn to Develop Multi-Agent AI?](https://towardsdatascience.com/learn-to-build-agentic-ai-systems-9e552d841525/)\n[Data Science](https://towardsdatascience.com/category/data-science/)\nThese 3 starter projects only take a weekend (and a few cups of coffee, maybe)\n[Thuwarakesh Murallie](https://towardsdatascience.com/author/thuwarakesh/)\nJanuary 23, 202513 min read\n  * [![](../images/005.webp)](https://towardsdatascience.com/5-ai-skills-that-will-keep-data-scientists-relevant-in-2027/)\n## [5 AI Skills That Will Keep Data Scientists Relevant in 2027](https://towardsdatascience.com/5-ai-skills-that-will-keep-data-scientists-relevant-in-2027/)\n[Data Science](https://towardsdatascience.com/category/data-science/)\nWhat each one solves, and runnable code you can paste into a notebook.\n[Sara Nobrega](https://towardsdatascience.com/author/saranobregafn/)\nSeptember 1, 202616 min read\n  * [![](../images/006.jpg)](https://towardsdatascience.com/llm-evals-are-based-on-vibes-i-built-the-missing-layer-that-decides-what-ships/)\n## [LLM Evals Are Based on Vibes — I Built the Missing Layer That Decides What Ships](https://towardsdatascience.com/llm-evals-are-based-on-vibes-i-built-the-missing-layer-that-decides-what-ships/)\n[Large Language Model](https://towardsdatascience.com/category/large-language-model/)\nHow I built a lightweight evaluation system that measures faithfulness, detects hallucinations, and turns subjective LLM outputs into reproducible metrics — all in pure Python\n[Emmimal P Alexander](https://towardsdatascience.com/author/emmimalp.alexander/)\nMay 17, 202622 min read\n  * [![](../images/007.jpg)](https://towardsdatascience.com/rag-is-burning-money-i-built-a-cost-control-layer-to-fix-it/)\n## [RAG Is Burning Money — I Built a Cost Control Layer to Fix It](https://towardsdatascience.com/rag-is-burning-money-i-built-a-cost-control-layer-to-fix-it/)\n[Large Language Model](https://towardsdatascience.com/category/large-language-model/)\nMost RAG systems optimize for relevance, not cost. I built a production-ready cost control layer combining semantic caching, query routing, and budget enforcement that reduces LLM costs by 85% without sacrificing answer quality.\n[Emmimal P Alexander](https://towardsdatascience.com/author/emmimalp.alexander/)\nMay 29, 202620 min read\n  * [![](../images/008.jpg)](https://towardsdatascience.com/tds-newsletter-what-happens-when-ai-reaches-its-limits/)\n## [TDS Newsletter: What Happens When AI Reaches Its Limits?](https://towardsdatascience.com/tds-newsletter-what-happens-when-ai-reaches-its-limits/)\n[The Variable](https://towardsdatascience.com/category/the-variable/)\nIf you're interested in insightful takes on AI's current blockers and limitations — and the ways we might be able to overcome them — read on.\n[TDS Editors](https://towardsdatascience.com/author/towardsdatascience/)\nOctober 23, 20254 min read\n  * [![](../images/009.jpg)](https://towardsdatascience.com/context-engineering-for-rag-the-four-typed-inputs-behind-every-rag-answer/)\n## [Context Engineering for RAG: The Four Typed Inputs Behind Every Answer](https://towardsdatascience.com/context-engineering-for-rag-the-four-typed-inputs-behind-every-rag-answer/)\n[Large Language Model](https://towardsdatascience.com/category/large-language-model/)\nEnterprise Document Intelligence [Vol.1 #7bis] - Tobi Lütke and Andrej Karpathy named the practice in 2025. For a single document, each brick emits typed pieces that converge on one LLM call. Corpus, conversation, and tool extensions are follow-up work\n[Kezhan Shi](https://towardsdatascience.com/author/kezhanshi/)\nJune 30, 202617 min read\n  * [![](../images/010.png)](https://towardsdatascience.com/gpt-vs-bert-which-is-better-2f1cf92af21a/)\n## [GPT vs BERT: Which is Better?](https://towardsdatascience.com/gpt-vs-bert-which-is-better-2f1cf92af21a/)\n[Artificial Intelligence](https://towardsdatascience.com/category/artificial-intelligence/)\nComparing two large-language models: Approach and example\n[Pranay Dave](https://towardsdatascience.com/author/pranay-dave9/)\nJune 23, 20235 min read\n  * [![](../images/011.jpg)](https://towardsdatascience.com/how-to-build-tools-for-ai-agents/)\n## [How to Build Tools for AI Agents](https://towardsdatascience.com/how-to-build-tools-for-ai-agents/)\n[Agentic AI](https://towardsdatascience.com/category/artificial-intelligence/agentic-ai/)\nLearn how to design and build effective tools to be used by AI Agents\n[Eivind Kjosbakken](https://towardsdatascience.com/author/oieivind/)\nOctober 15, 20259 min read\n\n\n  * [YouTube](https://www.youtube.com/c/TowardsDataScience)\n  * [X](https://x.com/TDataScience)\n  * [LinkedIn](https://www.linkedin.com/company/towards-data-science/)\n  * [Threads](https://www.threads.net/@towardsdatascience)\n  * [Bluesky](https://bsky.app/profile/towardsdatascience.com)\n\n\n[](https://towardsdatascience.com/)\nYour home for data science and AI. The world's leading publication for data science, data analytics, data engineering, machine learning, and artificial intelligence professionals.\nSubscribe to Our Newsletter[Write For TDS](https://towardsdatascience.com/submissions/)•[About](https://towardsdatascience.com/about-towards-data-science-d691af11cc2f/)•[Advertise](https://contact.towardsdatascience.com/advertise-with-towards-data-science)•[Privacy Policy](https://towardsdatascience.com/privacy-policy/)•[Terms of Use](https://towardsdatascience.com/website-terms-of-use/)\n© Insight Media Group, LLC 2026•[The New Stack](https://thenewstack.io)•[roadmap.sh](https://roadmap.sh)•[Creator Network](https://insightmediagroup.io/creator-network)\n####  Recommended Articles \nClose\n  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607631/image?etag=1781543525510&width=500&height=500) Critical Mistakes Companies Make When Integrating AI/ML into Their Processes ](https://towardsdatascience.com/critical-mistakes-companies-make-when-integrating-ai-ml-into-their-processes/ \"Critical Mistakes Companies Make When Integrating AI/ML into Their Processes\")\n  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607686/image?etag=1782161464448&width=500&height=500) How Relevance Models Foreshadowed Transformers for NLP ](https://towardsdatascience.com/how-relevance-models-foreshadowed-transformers-for-nlp/ \"How Relevance Models Foreshadowed Transformers for NLP\")\n  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607639/image?etag=1781250125848&width=500&height=500) Javascript Fatigue: HTMX Is All You Need to Build ChatGPT — Part 1 ](https://towardsdatascience.com/javascript-fatigue-you-dont-need-js-to-build-chatgpt/ \"Javascript Fatigue: HTMX Is All You Need to Build ChatGPT — Part 1\")\n  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607930/image?etag=1782124566054&width=500&height=500) The Subset Sum Problem Solved in Linear Time for Dense Enough Inputs ](https://towardsdatascience.com/subset-sum-problem-solved-in-linear-time-for-dense-enough-inputs/ \"The Subset Sum Problem Solved in Linear Time for Dense Enough Inputs\")\n\n\n",
  "metadata": {
    "title": "Multi-Document RAG: A Folder of Unrelated PDFs Is One Long Document with a Nested Outline | Towards Data Science",
    "description": "Enterprise Document Intelligence [Vol.1 #14B] - No shared fields means no index to build. One summary line per file plus each file’s own table of contents, and retrieval routes down two levels",
    "keywords": null,
    "author": "Angela and Kezhan Shi",
    "og:locale": "en_US",
    "og:type": "article",
    "og:title": "Multi-Document RAG: A Folder of Unrelated PDFs Is One Long Document with a Nested Outline",
    "og:description": "Enterprise Document Intelligence [Vol.1 #14B] - No shared fields means no index to build. One summary line per file plus each file’s own table of contents, and retrieval routes down two levels",
    "og:url": "https://towardsdatascience.com/multi-document-rag-a-folder-of-unrelated-pdfs-is-one-long-document-with-a-nested-outline/",
    "og:site_name": "Towards Data Science",
    "og:image": "https://assets.insightmediagroup.io/media/wp-content/uploads/2026/08/mixed_archive_pile_11952176_v3_card.jpg",
    "og:image:width": "1200",
    "og:image:height": "630",
    "og:image:type": "image/jpeg",
    "twitter:card": "summary_large_image",
    "twitter:site": "@TDataScience",
    "twitter:creator": "@TDataScience",
    "twitter:label1": "Written by",
    "twitter:data1": "Angela and Kezhan Shi",
    "twitter:label2": "Est. reading time",
    "twitter:data2": "16 min read",
    "article:published_time": "2026-08-22T09:00:04.000Z",
    "article:modified_time": "2026-08-22T09:00:04.000Z",
    "article:author": "https://towardsdatascience.com/author/angela.shi/"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 200,
    "error": ""
  },
  "code_blocks": [
    {
      "language": "txt",
      "file": "attachments/491af05b0d4d/code/block_000.txt",
      "chars": 1104
    }
  ],
  "images": [
    {
      "status": "http_404",
      "url": "https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607631/image?etag=1781543525510&width=500&height=500",
      "src": "https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607631/image?etag=1781543525510&width=500&height=500",
      "alt": "",
      "downloaded": false,
      "discovery": "saved_markdown",
      "retried_at": "2026-10-20T17:46:44Z"
    },
    {
      "status": "http_404",
      "url": "https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607686/image?etag=1782161464448&width=500&height=500",
      "src": "https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607686/image?etag=1782161464448&width=500&height=500",
      "alt": "",
      "downloaded": false,
      "discovery": "saved_markdown",
      "retried_at": "2026-10-20T17:46:44Z"
    },
    {
      "status": "http_404",
      "url": "https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607639/image?etag=1781250125848&width=500&height=500",
      "src": "https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607639/image?etag=1781250125848&width=500&height=500",
      "alt": "",
      "downloaded": false,
      "discovery": "saved_markdown",
      "retried_at": "2026-10-20T17:46:44Z"
    },
    {
      "status": "http_404",
      "url": "https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607930/image?etag=1782124566054&width=500&height=500",
      "src": "https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607930/image?etag=1782124566054&width=500&height=500",
      "alt": "",
      "downloaded": false,
      "discovery": "saved_markdown",
      "retried_at": "2026-10-20T17:46:44Z"
    }
  ],
  "audio": [],
  "videos": [
    {
      "url": "https://www.youtube.com/c/TowardsDataScience",
      "kind": "channel_reference",
      "status": "reference_only"
    }
  ],
  "links": [
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/latest/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/editors-pick/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/the-variable/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/shipai/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/submissions/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/category/large-language-model/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/author/angela.shi/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/prompt-context-loop-the-three-engineering-layers-every-rag-system-is-built-on/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/building-document-structure-with-loop-engineering-recovering-a-pdfs-outline-from-body-typography-for-rag/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/prompt-engineering-isnt-enough-how-four-bricks-of-context-engineering-stop-rag-hallucinations/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/most-rag-hallucinations-are-extraction-errors-seven-patterns-for-a-typed-generation-contract/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/document-processing/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/llm/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/llm-applications/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/rag/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/rag-architecture/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/building-trustworthy-production-rag-systems-through-continuous-evaluation/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/author/priyansh.bhardwaj/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/learn-to-build-agentic-ai-systems-9e552d841525/"
    },
    {
      "type": "external",
      "href": "https://write.insightmediagroup.io"
    },
    {
      "type": "external",
      "href": "https://www.linkedin.com/company/towards-data-science/"
    },
    {
      "type": "external",
      "href": "https://x.com/TDataScience"
    },
    {
      "type": "external",
      "href": "https://www.pexels.com/photo/11952176/"
    },
    {
      "type": "external",
      "href": "https://roadmap.sh/ai-engineer"
    },
    {
      "type": "external",
      "href": "https://github.com/doc-intel/notebooks-vol1"
    },
    {
      "type": "external",
      "href": "https://www.nist.gov/director/copyright-fair-use-and-licensing-statements-srd-data-and-software"
    },
    {
      "type": "external",
      "href": "https://arxiv.org/"
    },
    {
      "type": "external",
      "href": "https://www.worldbank.org/en/research/commodity-markets"
    },
    {
      "type": "external",
      "href": "https://openknowledge.worldbank.org/entities/publication/3ede6f8c-ba03-4176-867c-40e604698570"
    },
    {
      "type": "external",
      "href": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final"
    },
    {
      "type": "external",
      "href": "https://docs.llamaindex.ai/en/stable/examples/index_structs/doc_summary/DocSummary/"
    },
    {
      "type": "external",
      "href": "https://arxiv.org/abs/2401.18059"
    },
    {
      "type": "external",
      "href": "https://www.anthropic.com/news/contextual-retrieval"
    },
    {
      "type": "external",
      "href": "https://arxiv.org/abs/2312.10997"
    },
    {
      "type": "external",
      "href": "https://arxiv.org/abs/2401.05856"
    },
    {
      "type": "external",
      "href": "https://www.facebook.com/sharer/sharer.php?title=Multi-Document+RAG%3A+A+Folder+of+Unrelated+PDFs+Is+One+Long+Document+with+a+Nested+Outline&u=https%3A%2F%2Ftowardsdatascience.com%2Fmulti-document-rag-a-folder-of-unrelated-pdfs-is-one-long-document-with-a-nested-outline%2F"
    },
    {
      "type": "external",
      "href": "https://www.linkedin.com/shareArticle?mini=true&title=Multi-Document+RAG%3A+A+Folder+of+Unrelated+PDFs+Is+One+Long+Document+with+a+Nested+Outline&url=https%3A%2F%2Ftowardsdatascience.com%2Fmulti-document-rag-a-folder-of-unrelated-pdfs-is-one-long-document-with-a-nested-outline%2F"
    },
    {
      "type": "external",
      "href": "https://x.com/share?text=Multi-Document+RAG%3A+A+Folder+of+Unrelated+PDFs+Is+One+Long+Document+with+a+Nested+Outline&url=https%3A%2F%2Ftowardsdatascience.com%2Fmulti-document-rag-a-folder-of-unrelated-pdfs-is-one-long-document-with-a-nested-outline%2F"
    },
    {
      "type": "external",
      "href": "https://www.youtube.com/c/TowardsDataScience"
    }
  ],
  "repair_version": 2,
  "repaired_at": "2026-10-20T17:26:13Z",
  "source_url": "https://towardsdatascience.com/multi-document-rag-a-folder-of-unrelated-pdfs-is-one-long-document-with-a-nested-outline/",
  "delivery_status": "unverified",
  "discovery_status": "http_403",
  "missing_reason": "源页面未重新核验:http_403；已按保留正文补采图片",
  "repair_error": "",
  "actual_video_files": [],
  "expected_saved_body_images": 4,
  "image_discovery": "saved_markdown",
  "source_body_markdown": "![Revisit consent button](https://cdn-cookieyes.com/assets/images/revisit.svg)\nWe value your privacy\nWe use cookies to enhance your browsing experience, serve personalised ads or content, and analyse our traffic. By clicking \"Accept All\", you consent to our use of cookies.\nCustomise Reject All Accept All\nCustomise Consent Preferences ![](https://cdn-cookieyes.com/assets/images/close.svg)\nWe use cookies to help you navigate efficiently and perform certain functions. You will find detailed information about all cookies under each consent category below.\nThe cookies that are categorised as \"Necessary\" are stored on your browser as they are essential for enabling the basic functionalities of the site. ... Show more\nNecessaryAlways Active\nNecessary cookies are required to enable the basic features of this site, such as providing secure log-in or adjusting your consent preferences. These cookies do not store any personally identifiable data.\n  * Cookie\nBCTempID\n  * Duration\n10 minutes\n  * Description\nNo description available.\n\n\n  * Cookie\n__cf_bm\n  * Duration\n1 hour\n  * Description\nThis cookie, set by Cloudflare, is used to support Cloudflare Bot Management. \n\n\n  * Cookie\nAWSALBCORS\n  * Duration\n7 days\n  * Description\nAmazon Web Services set this cookie for load balancing.\n\n\n  * Cookie\n_cfuvid\n  * Duration\nsession\n  * Description\nCloudflare sets this cookie to track users across sessions to optimize user experience by maintaining session consistency and providing personalized services\n\n\n  * Cookie\nli_gc\n  * Duration\n6 months\n  * Description\nLinkedin set this cookie for storing visitor's consent regarding using cookies for non-essential purposes.\n\n\n  * Cookie\n__hssrc\n  * Duration\nsession\n  * Description\nThis cookie is set by Hubspot whenever it changes the session cookie. The __hssrc cookie set to 1 indicates that the user has restarted the browser, and if the cookie does not exist, it is assumed to be a new session.\n\n\n  * Cookie\n__hssc\n  * Duration\n1 hour\n  * Description\nHubSpot sets this cookie to keep track of sessions and to determine if HubSpot should increment the session number and timestamps in the __hstc cookie.\n\n\n  * Cookie\nwpEmojiSettingsSupports\n  * Duration\nsession\n  * Description\nWordPress sets this cookie when a user interacts with emojis on a WordPress site. It helps determine if the user's browser can display emojis properly.\n\n\n  * Cookie\nBCSessionID\n  * Duration\n1 year 1 month 4 days\n  * Description\nBlueconic sets this cookie as a unique identifier for the BlueConic profile.\n\n\n  * Cookie\n_octo\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\nlogged_in\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\n__Secure-YEC\n  * Duration\npast\n  * Description\nYouTube sets this cookie to stores the user's video player preferences using embedded YouTube video\n\n\n  * Cookie\n__eoi\n  * Duration\n6 months\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nAWSALBTGCORS\n  * Duration\n7 days\n  * Description\nNo description available.\n\n\n  * Cookie\nlogin-status-p\n  * Duration\npast\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nAWSALBTG\n  * Duration\n7 days\n  * Description\nNo description available.\n\n\n  * Cookie\ncsrf_token\n  * Duration\nsession\n  * Description\nNo description available.\n\n\n  * Cookie\ntoken_v2\n  * Duration\n1 day\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nD\n  * Duration\n1 year\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nPHPSESSID\n  * Duration\nsession\n  * Description\nThis cookie is native to PHP applications. The cookie stores and identifies a user's unique session ID to manage user sessions on the website. The cookie is a session cookie and will be deleted when all the browser windows are closed.\n\n\n  * Cookie\nVISITOR_PRIVACY_METADATA\n  * Duration\n6 months\n  * Description\nYouTube sets this cookie to store the user's cookie consent state for the current domain. \n\n\n  * Cookie\ncookietest\n  * Duration\nsession\n  * Description\nThe cookietest cookie is typically used to determine whether the user's browser accepts cookies, essential for website functionality and user experience.\n\n\n  * Cookie\n__Host-airtable-session\n  * Duration\n1 year\n  * Description\nThis cookie is used to enable us to integrate the services of Airtable.\n\n\n  * Cookie\n__Host-airtable-session.sig\n  * Duration\n1 year\n  * Description\nThis cookie is used to enable us to integrate the services of Airtable.\n\n\n  * Cookie\nm\n  * Duration\n1 year 1 month 4 days\n  * Description\nStripe sets this cookie for fraud prevention purposes. It identifies the device used to access the website, allowing the website to be formatted accordingly.\n\n\n  * Cookie\nBIGipServer*\n  * Duration\nsession\n  * Description\nMarketo sets this cookie to collect information about the user's online activity and build a profile about their interests to provide advertisements relevant to the user. \n\n\n  * Cookie\n__cfruid\n  * Duration\nsession\n  * Description\nCloudflare sets this cookie to identify trusted web traffic.\n\n\n  * Cookie\n_GRECAPTCHA\n  * Duration\n6 months\n  * Description\nGoogle Recaptcha service sets this cookie to identify bots to protect the website against malicious spam attacks.\n\n\n  * Cookie\n__Secure-YNID\n  * Duration\n6 months\n  * Description\nGoogle cookie used to protect user security and prevent fraud, especially during the login process.\n\n\n  * Cookie\ncookieyes-consent\n  * Duration\n1 year\n  * Description\nCookieYes sets this cookie to remember users' consent preferences so that their preferences are respected on subsequent visits to this site. It does not collect or store any personal information about the site visitors.\n\n\nFunctional\nFunctional cookies help perform certain functionalities like sharing the content of the website on social media platforms, collecting feedback, and other third-party features.\n  * Cookie\nlidc\n  * Duration\n1 day\n  * Description\nLinkedIn sets the lidc cookie to facilitate data center selection.\n\n\n  * Cookie\nbrw\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\nbrwConsent\n  * Duration\n5 minutes\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nWMF-Uniq\n  * Duration\n1 year\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nloom_anon_comment\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\nloom_referral_video\n  * Duration\nsession\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nVISITOR_INFO1_LIVE\n  * Duration\n6 months\n  * Description\nA cookie set by YouTube to measure bandwidth that determines whether the user gets the new or old player interface.\n\n\n  * Cookie\nyt-remote-connected-devices\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to store the user's video preferences using embedded YouTube videos.\n\n\n  * Cookie\nytidb::LAST_RESULT_ENTRY_KEY\n  * Duration\nNever Expires\n  * Description\nThe cookie ytidb::LAST_RESULT_ENTRY_KEY is used by YouTube to store the last search result entry that was clicked by the user. This information is used to improve the user experience by providing more relevant search results in the future.\n\n\n  * Cookie\nyt-remote-device-id\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to store the user's video preferences using embedded YouTube videos.\n\n\n  * Cookie\nyt-remote-session-name\n  * Duration\nsession\n  * Description\nThe yt-remote-session-name cookie is used by YouTube to store the user's video player preferences using embedded YouTube video.\n\n\n  * Cookie\nyt-remote-fast-check-period\n  * Duration\nsession\n  * Description\nThe yt-remote-fast-check-period cookie is used by YouTube to store the user's video player preferences for embedded YouTube videos.\n\n\n  * Cookie\nyt-remote-session-app\n  * Duration\nsession\n  * Description\nThe yt-remote-session-app cookie is used by YouTube to store user preferences and information about the interface of the embedded YouTube video player.\n\n\n  * Cookie\nyt-remote-cast-available\n  * Duration\nsession\n  * Description\nThe yt-remote-cast-available cookie is used to store the user's preferences regarding whether casting is available on their YouTube video player.\n\n\n  * Cookie\nyt-remote-cast-installed\n  * Duration\nsession\n  * Description\nThe yt-remote-cast-installed cookie is used to store the user's video player preferences using embedded YouTube video.\n\n\n  * Cookie\ncp_session\n  * Duration\n3 months\n  * Description\nCodepen sets this cookie for Help systems found in the website.\n\n\n  * Cookie\nloid\n  * Duration\n1 year 1 month 4 days\n  * Description\nThis cookie is set by the Reddit. The cookie enables the sharing of content from the website onto the social media platform.\n\n\nAnalytics\nAnalytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics such as the number of visitors, bounce rate, traffic source, etc.\n  * Cookie\n__hstc\n  * Duration\n6 months\n  * Description\nHubspot set this main cookie for tracking visitors. It contains the domain, initial timestamp (first visit), last timestamp (last visit), current timestamp (this visit), and session number (increments for each subsequent session).\n\n\n  * Cookie\nhubspotutk\n  * Duration\n6 months\n  * Description\nHubSpot sets this cookie to keep track of the visitors to the website. This cookie is passed to HubSpot on form submission and used when deduplicating contacts.\n\n\n  * Cookie\n_ga\n  * Duration\n1 year 1 month 4 days\n  * Description\nGoogle Analytics sets this cookie to calculate visitor, session and campaign data and track site usage for the site's analytics report. The cookie stores information anonymously and assigns a randomly generated number to recognise unique visitors.\n\n\n  * Cookie\n_ga_*\n  * Duration\n1 year 1 month 4 days\n  * Description\nGoogle Analytics sets this cookie to store and count page views.\n\n\n  * Cookie\n__Host-psifi.analyticsTrace\n  * Duration\n6 hours\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\n__Host-psifi.analyticsTraceV2\n  * Duration\n6 hours\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\n_gh_sess\n  * Duration\nsession\n  * Description\nGitHub sets this cookie for temporary application and framework state between pages like what step the user is on in a multiple step form.\n\n\n  * Cookie\nYSC\n  * Duration\nsession\n  * Description\nYSC cookie is set by Youtube and is used to track the views of embedded videos on Youtube pages.\n\n\n  * Cookie\najs_anonymous_id\n  * Duration\n1 year\n  * Description\nThis cookie is set by Segment to count the number of people who visit a certain site by tracking if they have visited before.\n\n\n  * Cookie\nvuid\n  * Duration\n1 year 1 month 4 days\n  * Description\nVimeo installs this cookie to collect tracking information by setting a unique ID to embed videos on the website. \n\n\nPerformance\nPerformance cookies are used to understand and analyse the key performance indexes of the website which helps in delivering a better user experience for the visitors.\n  * Cookie\nAWSALB\n  * Duration\n7 days\n  * Description\nAWSALB is an application load balancer cookie set by Amazon Web Services to map the session to the target.\n\n\n  * Cookie\nacq\n  * Duration\npast\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nacq.sig\n  * Duration\npast\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nptc\n  * Duration\n2 years\n  * Description\nNo description available.\n\n\nAdvertisement\nAdvertisement cookies are used to provide visitors with customised advertisements based on the pages you visited previously and to analyse the effectiveness of the ad campaigns.\n  * Cookie\nmuc_ads\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to collect user behaviour and interaction data to optimize the website.\n\n\n  * Cookie\nguest_id_marketing\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to identify and track the website visitor.\n\n\n  * Cookie\nguest_id_ads\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to identify and track the website visitor.\n\n\n  * Cookie\npersonalization_id\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to integrate and share features for social media and also store information about how the user uses the website, for tracking and targeting.\n\n\n  * Cookie\nguest_id\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to identify and track the website visitor. It registers if a user is signed in to the Twitter platform and collects information about ad preferences.\n\n\n  * Cookie\nbcookie\n  * Duration\n1 year\n  * Description\nLinkedIn sets this cookie from LinkedIn share buttons and ad tags to recognize browser IDs.\n\n\n  * Cookie\n__Secure-ROLLOUT_TOKEN\n  * Duration\n6 months\n  * Description\nYouTube sets this cookie to manage feature rollout and experimentation. It helps Google control which new features or interface changes are shown to users as part of testing and staged rollouts, ensuring consistent experience for a given user during an experiment.\n\n\n  * Cookie\nyt.innertube::nextId\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.\n\n\n  * Cookie\nyt.innertube::requests\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.\n\n\n  * Cookie\nsession_tracker\n  * Duration\nsession\n  * Description\nThis cookie is set by the Reddit. This cookie is used to identify trusted web traffic. It also helps in adverstising on the website.\n\n\n  * Cookie\nedgebucket\n  * Duration\nsession\n  * Description\nReddit sets this cookie to save the information about a log-on Reddit user, for the purpose of advertisement recommendations and updating the content.\n\n\n  * Cookie\ndid\n  * Duration\n1 year\n  * Description\nArbor sets this cookie to show targeted ads to site visitors.This cookie expires after 2 months or 1 year.\n\n\nUncategorised\nOther uncategorised cookies are those that are being analysed and have not been classified into a category as yet.\nNo cookies to display.\nReject All Save My Preferences Accept All\nToggle Search[](https://towardsdatascience.com/)Toggle Mobile Navigation\n[](https://towardsdatascience.com/)\nPublish AI, ML & data-science insights to a global community of data professionals.\nSign in[Submit an Article](https://write.insightmediagroup.io)\n[Latest](https://towardsdatascience.com/latest/)[Editor's Picks](https://towardsdatascience.com/tag/editors-pick/)[Newsletter](https://towardsdatascience.com/tag/the-variable/)[ShipAI](https://towardsdatascience.com/shipai/)\n* * *\n[Write For TDS](https://towardsdatascience.com/submissions/)\n[LinkedIn](https://www.linkedin.com/company/towards-data-science/)[X](https://x.com/TDataScience)Toggle Search\n[Large Language Model](https://towardsdatascience.com/category/large-language-model/)\n# Multi-Document RAG: A Folder of Unrelated PDFs Is One Long Document with a Nested Outline\nEnterprise Document Intelligence [Vol.1 #14B] - No shared fields means no index to build. One summary line per file plus each file’s own table of contents, and retrieval routes down two levels\n[Angela and Kezhan Shi](https://towardsdatascience.com/author/angela.shi/)\nAugust 22, 202616 min read\n![](https://assets.insightmediagroup.io/media/wp-content/uploads/2026/08/mixed_archive_pile_11952176_v3_card.jpg)\n[Photo](https://www.pexels.com/photo/11952176/) by Beyza Kaplan, via Pexels.\nThe standard advice for taking a RAG system past one document is to build an index: one row per document, one column per field you may want to filter on, then a filter in front of the search.\nThat advice assumes the documents have fields in common. Plenty of folders do not.\nTake a research folder: a 492-page catalogue of security controls, a zero-trust architecture spec, an AI risk framework, thirty-one machine-learning papers, and seven commodity market reports. Ask what column they share. There is no client, no amount, no effective date that would mean the same thing across all of them, and no other field a business user could name either.\nWhen there is no shared field, there is no table to build. That reads like a missing piece. It is the opposite. It means the preparation step collapses into two artefacts, and one of them the parser already returns for free.\nLearn this step by step with the interactive [AI Engineer](https://roadmap.sh/ai-engineer) roadmap.\nThis article works through that case:\n  * the two questions that tell it apart from the case that does need an index\n  * the preparation, which is one summary line per file plus each file’s own table of contents, and nothing else\n  * why the summary line has to be written for a router rather than for a reader\n  * the two-level routing that answers a question, and why the nested outline never enters a prompt whole\n  * four ways it breaks, including the point where the flat file list stops scaling\n\n\nThis article is part of Part IV of [Enterprise Document Intelligence](https://towardsdatascience.com/prompt-context-loop-the-three-engineering-layers-every-rag-system-is-built-on/), a series that builds an enterprise RAG system from four bricks. Part IV asks what changes when the input is a folder instead of a file, and the answer depends on which kind of folder you have. This one is the kind that needs the least new machinery.\n🧭 **New to the series? Start with the map** : [Prompt, Context, Loop](https://towardsdatascience.com/prompt-context-loop-the-three-engineering-layers-every-rag-system-is-built-on/) sets out the three engineering layers every RAG system is built on, the prompt (the call itself), the context (what fills the model’s window), the loop (when the next call fires and when it stops), and walks the whole series through that lens, article by article. It is the shortest way to see what is covered and where this one sits.\n![](https://assets.insightmediagroup.io/media/wp-content/uploads/2026/08/image-109-1024x572.png)\n_where this article sits: Part IV, the folder that needs no index - Image by author_\n📓 **Build the two-level index over your own folder in the companion notebook** : write one summary line per file, print the level-0 list, then run the routing call and watch it keep one file out of sixty-three before a single page is read. Repo → [doc-intel/notebooks-vol1](https://github.com/doc-intel/notebooks-vol1).\n![](https://assets.insightmediagroup.io/media/wp-content/uploads/2026/08/image-108-1024x658.png)\n_The public companion-code repo at doc-intel/notebooks-vol1 - Image by author_\nThe folder used throughout is 63 public PDFs, 4,211 pages in total: 25 NIST files, which are 24 publications plus a three-page scan of one of them (US Government work, public domain in the US, see the [NIST copyright statement](https://www.nist.gov/director/copyright-fair-use-and-licensing-statements-srd-data-and-software)), 31 arXiv papers (each under the arXiv non-exclusive distribution license declared on its own [abstract page](https://arxiv.org/)), and 7 issues of the World Bank [_Commodity Markets Outlook_](https://www.worldbank.org/en/research/commodity-markets) ([CC BY 3.0 IGO, as declared on the OKR publication page for April 2026](https://openknowledge.worldbank.org/entities/publication/3ede6f8c-ba03-4176-867c-40e604698570)).\n## 1. Which kind of folder do you have\nArticle 14A (three kinds of corpus, and what building for the wrong one costs) sets out three shapes a collection can take, and says that each one wants a different architecture. This article takes the one where the documents have nothing in common. Before building anything, it is worth being sure that is the case you are in, because the wrong guess costs a lot in either direction.\n### 1.1. Two questions that decide it\nThe first question: **do two documents in the folder ever refer to each other?** An amendment refers to its master agreement. A renewal refers to last year’s policy. A certificate belongs to a contract. If that happens, the folder has a structure you must model, and this article is not the one you want.\nThe second question: **can a business user name a field that every document has, and that means the same thing in each?** Client, effective date, premium, policy number. If the answer comes back fast and confidently, the folder is a database nobody has typed yet, and typing it is the job.\nIf both answers are no, there is nothing to put in the columns. You could still extract metadata (file name, page count, creation date), but none of it is a field anyone will filter on. A column that is empty for most rows and means something different in the rest is not a column.\n![](https://assets.insightmediagroup.io/media/wp-content/uploads/2026/08/image-109-1024x572.png)\n_the same four questions, two folders, two architectures - Image by author_\nMixed folders exist, and they are common. A shared drive can hold 5,000 look-alike contracts next to 300 miscellaneous files that belong to nobody. That is not a contradiction, it is two corpora sharing a disk. Split them and run the right architecture on each.\n### 1.2. What the other case gets, and this one does not\nThe homogeneous case (Article 14C, where the business already knows the columns) gets three things this one cannot have.\nIt gets a **filter**. A question that says _retailer X, currently in force_ becomes a SQL clause, and 18,000 rows become three before anything is read.\nIt gets **aggregation**. _How many policies expire this quarter_ is answered by the index itself, with no document opened at all.\nIt gets a **shared vocabulary**. Every row has a client and a date, so the ontology that maps user words to column values pays off across the whole corpus.\nNone of that transfers here. There is no clause to write, nothing to count, and a vocabulary shared by a control catalogue and a commodity report would be a vocabulary about nothing. Articles 15 (preparing the corpus), 16 (the ontology behind the columns), and 17 (querying the corpus with SQL first) build all three, and they are the right articles when the second question above came back yes.\nWhat is left when you take those away is smaller than it looks, and it is the whole architecture for this case: **a folder whose documents have nothing in common is one long document.** The files are its chapters. Their sections are its subsections. You already know how to read a long document. You read its table of contents.\n## 2. The preparation: one line per file, plus the outline the parser returns\nTwo artefacts, built once at ingestion.\n**Level 0** is the list of files, one line each, saying what that file is about. **Level 1** is each file’s own table of contents. Together they are a nested outline: the folder’s chapters, then each chapter’s sections.\nThat is the entire preparation. No classification pass, no field extraction, no schema, no ontology.\n### 2.1. The summary line is the index, so write it for the router\nHere is where most of the quality lives, and it is easy to underestimate because the artefact is one sentence.\nThe summary line is not documentation. Nobody reads it. Its only consumer is the routing call that has to decide, from 63 lines, which files could hold the answer. It should be written the way you would write a column definition, not the way you would write a blurb.\n![](https://assets.insightmediagroup.io/media/wp-content/uploads/2026/08/image-105-1024x541.png)\n_the same file, described two ways, and what each version lets the router do - Image by author_\nThree things make the difference between the two versions.\n**The subject in both vocabularies.** The words the user will type, and the words the document itself uses. A question about _account management_ has to land on a file whose line says _account management_ and _AC-2_ , because the router matches on text, not on intent.\n**The shape of what the file answers.** One numbered control per section is a different promise from a narrative report. It tells the router whether the answer is a section or a whole chapter, which is what the second level then acts on.\n**What the file is not about.** This is the part people skip, and it is the one that makes routing precise. _Not an implementation guide_ removes the file from every how-to question in one clause. Negative information is what lets a router discard sixty-two files without opening any of them.\nProducing the lines is cheap. One call per document at ingestion, over the first pages plus the table of contents, gives a usable draft; on a folder this size you can also write them by hand in an afternoon. Either way, the cost is paid once per document and re-paid only when the file changes. What matters is that the lines get reviewed like schema, not like copy.\n### 2.2. The second level costs nothing new\nThe parsing brick already returns a `toc_df`, one row per heading with its page range. Article 5B (the relational tables a parser returns) builds it from the PDF’s native outline. Nothing in this article extends it. Level 1 is that table, kept per file.\nFor the folder here, 47 of the 63 PDFs ship a native outline, and those 47 cost one parse each. The other 16 ship none: a scanned memo, a slide export, a two-page note.\nTwo answers work for those 16, and length is what picks between them. A short file becomes a leaf: when the router keeps it, you read it whole, and level 1 never runs. A long one gets its outline reconstructed first. Article 5septies (rebuilding the table of contents from the page that prints one) covers the case where the PDF shows a contents page but never encodes it as an outline. [Building Document Structure with Loop Engineering](https://towardsdatascience.com/building-document-structure-with-loop-engineering-recovering-a-pdfs-outline-from-body-typography-for-rag/) covers the harder one, where there is no contents page either and the headings have to be recovered from the body typography.\n![](https://assets.insightmediagroup.io/media/wp-content/uploads/2026/08/image-106-1024x576.png)\n_63 files, 4,211 pages, and an index that is 63 sentences long - Image by author_\nAcross the folder the two levels come to 63 lines and 2,422 outline rows. The 63 is what the model reads on every question. The 2,422 is what it can reach, one file at a time, and almost never touches.\n## 3. Query time: routing down two levels\nTake one question through the folder: _“what does the account management control require?”_ The answer is five pages of [NIST SP 800-53 Rev. 5](https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final) (_Security and Privacy Controls for Information Systems and Organizations_ , US Government work, public domain in the US), control AC-2, pages 46 to 50. Sixty-two other files cannot hold it, and none of them will be opened.\n![](https://assets.insightmediagroup.io/media/wp-content/uploads/2026/08/image-107-1024x575.png)\n_pick the files from 63 lines, then descend inside the ones that survive - Image by author_\n### 3.1. Level 0, picking the files\nOne call. The model reads 63 lines, each one a file name, its routing summary, and a keyword tally. It returns the file ids that could hold the answer, with a reason for each. Usually one to three files come back.\nThe keyword tally runs alongside, not instead. Counting how many of the question’s terms appear in a file’s summary line and in its outline titles costs nothing, and it catches what a summary sentence rounds off: an exact code like `AC-2` or `GV.OC-01` that the sentence never spelled out. The two signals go into the same prompt and the model decides, which is the pattern Article 7B (parallel detectors, then one call at the end) established inside a single document. Here it runs one level up, unchanged.\nTwo properties of this call are worth naming. It is bounded: 63 short lines, whatever the folder weighs on disk. And it is auditable: the reason string says why a file was kept, so a wrong answer can be traced to the routing decision rather than to a similarity score.\n### 3.2. Level 1, descending inside the survivors\nFrom here it is the single-document case, with nothing added. The file’s own outline becomes the current level, the model picks a branch, and if that branch has finer children the loop opens them and repeats. It stops at a leaf, or at a section short enough to read whole. Article 7quater (hierarchical retrieval, reading a long document by its table of contents) works that loop out in full on this same catalogue: eleven chapters, then twenty control families, then twenty-five controls, then AC-2.\nThe two levels are the same call.\npython\n\n```\n# The folder's file list is the top level of ONE table of contents:\n# level 0 rows are files, deeper rows are those files' own sections.\nlevel = corpus_toc[corpus_toc.level == 0]           # 63 rows, one per file\nfiles = reason_on_toc(question, level,              # the SAME router call\n                      section_signals=keyword_hits(question, level))\n\nsections = []\nfor file_id in files.section_ids:                   # typically 1 to 3\n    level = corpus_toc[(corpus_toc.file_id == file_id)\n                       & (corpus_toc.level == 1)]\n    while True:                                     # the one-document loop\n        pick    = reason_on_toc(question, level)\n        section = level[level.section_id.isin(pick.section_ids)]\n        kids    = immediate_children(corpus_toc, section)\n        if kids.empty or section.n_pages.max() <= SMALL:\n            break                                   # a leaf, or short enough\n        level = kids                                # open it, descend\n    sections.append(section)\n\n# 63 files -> 1 file -> AC-2 ACCOUNT MANAGEMENT -> 5 pages out of 4,211\n```\n\nEvery line that comes out keeps its file id, so a citation names the file it came from. On a folder whose documents share no vocabulary, that matters more than usual: an answer stitched from a control catalogue and a market report reads fluently and is worth nothing.\n### 3.3. Why the whole nested outline never goes in one prompt\nThe nested outline is one object, 63 lines plus 2,422 rows. Nothing stops you from serialising all of it into a single call. Three reasons not to.\n**It stops fitting.** Multiply this folder by twenty and the outline alone is a long prompt with no room left to reason in. The folders that need this architecture are the ones that keep growing.\n**Precision falls before the window does.** At level 0 the model compares 63 whole-document descriptions, which is a decision it makes well. Handed 2,485 mixed rows, the section it needs competes with 357 siblings from a document that is not even relevant, and a section title read out of its document is a weak signal.\n**It costs on every question.** Level 0 is 63 short lines, paid every time. Level 1 is only the outlines of the files that survived, so it is paid on one file rather than sixty-three.\nThe descent is also optional. On a folder of eight short files with shallow outlines, level 0 picks a file and there is nothing to descend into: the loop runs once and behaves like flat routing. The model decides at each level whether going deeper buys anything, which is the same termination rule the single-document loop uses.\n## 4. Where it breaks\nFour things go wrong with a two-level folder index, and they are not equally serious. The first is a writing problem you fix in an afternoon. The next two are parsing problems that show up as retrieval problems. The last one is a real ceiling.\n![](https://assets.insightmediagroup.io/media/wp-content/uploads/2026/08/image-110-1024x276.png)\n_the four ways a folder index goes wrong, and what each one needs - Image by author_\n### 4.1. The summary line that says nothing\n_“A technical report on cybersecurity.”_ Twelve files match that description, so level 0 returns all twelve, or picks three of them for no reason you can reconstruct. The tell is specific: the level-0 call keeps too many files, while level 1 looks perfectly healthy on each one it is given.\nThe fix is to rewrite the line, and the test is cheap. Hold out ten real questions, run the level-0 call on each, and check that the file you would have picked by hand comes back. Ten questions catch most of it, because a bad summary line is usually bad for a whole class of question rather than for one.\nTreat the lines as an artefact under review. They are the index; a sloppy line is a sloppy column definition, and it will not announce itself.\n### 4.2. The document with no structure\nLevel 1 needs something to descend into. A file with no headings gives the loop nothing to pick from, so the router either reads it whole or reads nothing.\nReading it whole is fine most of the time. A three-page memo is a leaf, and the two-level scheme handles leaves naturally. The failure case is narrow and specific: long **and** structureless. A 200-page scanned bundle with no headings cannot be routed at any level, because there is nothing to route over.\nThat one is not a retrieval problem. It is a parsing problem, and it gets solved before this article’s machinery runs, by reconstructing an outline from whatever signal the document does carry.\n### 4.3. The flat file list stops scaling\nLevel 0 is a flat list. It works while the list stays readable in one call.\nA few hundred files is comfortable. A few thousand is not, and the failure is not the one people expect. The context window is not what gives out first; routing accuracy is. At 3,000 lines the level-0 call is the same top-k problem this whole architecture was built to avoid, moved up one floor.\nThe fix is one more level, not a different architecture. Group the files, by folder, by source, by year, by whatever grouping the shelf already has, and give each group its own summary line. Level 0 becomes 40 group lines, level 1 the files inside the groups the router kept, level 2 their outlines. Same routing call, one more turn of the same loop.\nThat is also the point where this case starts asking for what the homogeneous case has from day one. If the natural grouping turns out to be a field that every file has, the second question from section 1.1 has quietly changed its answer, and the folder wants an index after all.\n## 5. Conclusion\nA folder whose documents have nothing in common is one long document, and it wants the retrieval you already have rather than the corpus machinery of the articles around it.\nWhat it does not need is worth listing, because each item is a project someone will otherwise propose. No relational index: there are no columns to fill. No ontology: a vocabulary shared by a control catalogue and a commodity report describes nothing. No SQL agent: there is nothing to query. No entity extraction pass over the corpus: the entities do not recur across files, so there is nothing to link.\nWhat it does need is a sentence per file, written for the router, and the table of contents the parser already returns. Then routing runs twice: once over the file list, once inside the files that survived. Sixty-three files and 4,211 pages, and the model reads 63 lines plus one outline before landing on five pages.\nThe two follow-ups in this group take the other two shapes. Article 14C (the homogeneous corpus, where the business already knows the columns) covers the folder where the second question comes back yes. Article 14D (case files, one bundle about a single entity) covers the folder that is heterogeneous inside and repetitive across, which needs a bit of both.\n## 6. Further reading and sources\nEarlier in the series, on the pieces this article reuses:\n  * [Building Document Structure with Loop Engineering: Recovering a PDF’s Outline from Body Typography for RAG](https://towardsdatascience.com/building-document-structure-with-loop-engineering-recovering-a-pdfs-outline-from-body-typography-for-rag/). What to do about the 16 files in this folder that ship no native outline.\n  * Most RAG Hallucinations Are Retrieval Failures: Fix Retrieval, Not the Prompt. Why a routing mistake at level 0 shows up later as a generation problem.\n  * Context engineering for RAG question parsing: from a raw question to typed fields that steer retrieval and generation. Where the keywords the level-0 tally counts come from.\n  * [Prompt Engineering Isn’t Enough: How Four Bricks of Context Engineering Stop RAG Hallucinations](https://towardsdatascience.com/prompt-engineering-isnt-enough-how-four-bricks-of-context-engineering-stop-rag-hallucinations/). The four typed pieces that fill the call the router feeds.\n  * [Most RAG Hallucinations Are Extraction Errors: Seven Patterns for a Typed Generation Contract](https://towardsdatascience.com/most-rag-hallucinations-are-extraction-errors-seven-patterns-for-a-typed-generation-contract/). What generation does with the lines the descent returns, and why each one carries its file id.\n\n\nAlso referenced above, listed here without links: Article 5B (the relational tables a parser returns), Article 5septies (rebuilding a table of contents from the page that prints one), Article 7B (parallel detectors, then one call at the end), Article 7quater (hierarchical retrieval inside one long document), Article 14A (three kinds of corpus, and what building for the wrong one costs), Article 14C (the homogeneous corpus), Article 14D (case files), and Articles 15 to 17 (the index, the ontology, and the SQL-first query path for the other kind of folder).\nExternal sources:\n  * _Document Summary Index_ , LlamaIndex documentation ([docs.llamaindex.ai](https://docs.llamaindex.ai/en/stable/examples/index_structs/doc_summary/DocSummary/)). The same level-0 idea as a shipped open-source component: a summary per document, and retrieval that picks documents by summary before it picks passages. Worth reading for the prompt it uses to generate the summaries.\n  * Parth Sarthi et al., _RAPTOR: Recursive Abstractive Processing for Tree-Organized Retrieval_ , 2024 ([arXiv 2401.18059](https://arxiv.org/abs/2401.18059)). Builds the tree by clustering and summarising recursively when no structure exists. The contrast is the point: a folder of documents already ships a tree, so there is nothing to infer.\n  * Anthropic, _Introducing Contextual Retrieval_ , 2024 ([anthropic.com](https://www.anthropic.com/news/contextual-retrieval)). Prepends a short document-level context to each chunk so a passage stops being orphaned from its source. The summary line here is the same move at file granularity.\n  * Yunfan Gao et al., _Retrieval-Augmented Generation for Large Language Models: A Survey_ , 2023 ([arXiv 2312.10997](https://arxiv.org/abs/2312.10997)). The naive, advanced, modular vocabulary this article’s baseline sits against.\n  * Scott Barnett et al., _Seven Failure Points When Engineering a Retrieval Augmented Generation System_ , 2024 ([arXiv 2401.05856](https://arxiv.org/abs/2401.05856)). Field-measured failure modes; the missing-document and wrong-document points are exactly what level-0 routing is built to remove.\n\n\n## Register Now to Continue Reading\n### Unlock Towards Data Science with a Free Account\nAll articles on Towards Data Science are available to registered users. Create a free account today to access this article and thousands more.\nRegister for freeSign in to your account\n* * *\n![](https://assets.insightmediagroup.io/media/1787657699750_yw6uqg.jpeg)\nWritten By\n## [Angela and Kezhan Shi](https://towardsdatascience.com/author/angela.shi/)\n[See all from Angela and Kezhan Shi](https://towardsdatascience.com/author/angela.shi/)\n[Document Processing](https://towardsdatascience.com/tag/document-processing/)[Llm](https://towardsdatascience.com/tag/llm/)[Llm Applications](https://towardsdatascience.com/tag/llm-applications/)[Rag](https://towardsdatascience.com/tag/rag/)[Rag Architecture](https://towardsdatascience.com/tag/rag-architecture/)\nShare This Article\n  * [](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Ftowardsdatascience.com%2Fmulti-document-rag-a-folder-of-unrelated-pdfs-is-one-long-document-with-a-nested-outline%2F&title=Multi-Document%20RAG%3A%20A%20Folder%20of%20Unrelated%20PDFs%20Is%20One%20Long%20Document%20with%20a%20Nested%20Outline)\n  * [](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Ftowardsdatascience.com%2Fmulti-document-rag-a-folder-of-unrelated-pdfs-is-one-long-document-with-a-nested-outline%2F&title=Multi-Document%20RAG%3A%20A%20Folder%20of%20Unrelated%20PDFs%20Is%20One%20Long%20Document%20with%20a%20Nested%20Outline)\n  * [](https://x.com/share?url=https%3A%2F%2Ftowardsdatascience.com%2Fmulti-document-rag-a-folder-of-unrelated-pdfs-is-one-long-document-with-a-nested-outline%2F&text=Multi-Document%20RAG%3A%20A%20Folder%20of%20Unrelated%20PDFs%20Is%20One%20Long%20Document%20with%20a%20Nested%20Outline)\n\n\nTowards Data Science is a community publication. Submit your insights to reach our global audience and earn through the TDS Author Payment Program.\n[Write for TDS](https://towardsdatascience.com/submissions/)\n## Related Articles\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2026/07/rag_4.jpg)](https://towardsdatascience.com/building-trustworthy-production-rag-systems-through-continuous-evaluation/)\n## [Building Trustworthy Production RAG Systems Through Continuous Evaluation](https://towardsdatascience.com/building-trustworthy-production-rag-systems-through-continuous-evaluation/)\n[Large Language Model](https://towardsdatascience.com/category/large-language-model/)\nA practical guide to building an evaluation workflow that catches retrieval failures, hallucinations, and performance drift before they reach users\n[Priyansh Bhardwaj](https://towardsdatascience.com/author/priyansh.bhardwaj/)\nJuly 15, 20269 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/01/1f0PkshYadnjBu0el57GX7Q.jpeg)](https://towardsdatascience.com/learn-to-build-agentic-ai-systems-9e552d841525/)\n## [On a Time Crunch but Still Want to Learn to Develop Multi-Agent AI?](https://towardsdatascience.com/learn-to-build-agentic-ai-systems-9e552d841525/)\n[Data Science](https://towardsdatascience.com/category/data-science/)\nThese 3 starter projects only take a weekend (and a few cups of coffee, maybe)\n[Thuwarakesh Murallie](https://towardsdatascience.com/author/thuwarakesh/)\nJanuary 23, 202513 min read\n  * [![](https://assets.insightmediagroup.io/media/1787909177964_0h8syh.webp)](https://towardsdatascience.com/5-ai-skills-that-will-keep-data-scientists-relevant-in-2027/)\n## [5 AI Skills That Will Keep Data Scientists Relevant in 2027](https://towardsdatascience.com/5-ai-skills-that-will-keep-data-scientists-relevant-in-2027/)\n[Data Science](https://towardsdatascience.com/category/data-science/)\nWhat each one solves, and runnable code you can paste into a notebook.\n[Sara Nobrega](https://towardsdatascience.com/author/saranobregafn/)\nSeptember 1, 202616 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2026/05/Missing-Scoring-Layer.jpg)](https://towardsdatascience.com/llm-evals-are-based-on-vibes-i-built-the-missing-layer-that-decides-what-ships/)\n## [LLM Evals Are Based on Vibes — I Built the Missing Layer That Decides What Ships](https://towardsdatascience.com/llm-evals-are-based-on-vibes-i-built-the-missing-layer-that-decides-what-ships/)\n[Large Language Model](https://towardsdatascience.com/category/large-language-model/)\nHow I built a lightweight evaluation system that measures faithfulness, detects hallucinations, and turns subjective LLM outputs into reproducible metrics — all in pure Python\n[Emmimal P Alexander](https://towardsdatascience.com/author/emmimalp.alexander/)\nMay 17, 202622 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2026/05/RAG-Is-Burning-Money.jpg)](https://towardsdatascience.com/rag-is-burning-money-i-built-a-cost-control-layer-to-fix-it/)\n## [RAG Is Burning Money — I Built a Cost Control Layer to Fix It](https://towardsdatascience.com/rag-is-burning-money-i-built-a-cost-control-layer-to-fix-it/)\n[Large Language Model](https://towardsdatascience.com/category/large-language-model/)\nMost RAG systems optimize for relevance, not cost. I built a production-ready cost control layer combining semantic caching, query routing, and budget enforcement that reduces LLM costs by 85% without sacrificing answer quality.\n[Emmimal P Alexander](https://towardsdatascience.com/author/emmimalp.alexander/)\nMay 29, 202620 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/10/daniele-franchi-kKaKVCpkdUU-unsplash.jpg)](https://towardsdatascience.com/tds-newsletter-what-happens-when-ai-reaches-its-limits/)\n## [TDS Newsletter: What Happens When AI Reaches Its Limits?](https://towardsdatascience.com/tds-newsletter-what-happens-when-ai-reaches-its-limits/)\n[The Variable](https://towardsdatascience.com/category/the-variable/)\nIf you're interested in insightful takes on AI's current blockers and limitations — and the ways we might be able to overcome them — read on.\n[TDS Editors](https://towardsdatascience.com/author/towardsdatascience/)\nOctober 23, 20254 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2026/06/compare_pasta_bowls_2720445_v3_card.jpg)](https://towardsdatascience.com/context-engineering-for-rag-the-four-typed-inputs-behind-every-rag-answer/)\n## [Context Engineering for RAG: The Four Typed Inputs Behind Every Answer](https://towardsdatascience.com/context-engineering-for-rag-the-four-typed-inputs-behind-every-rag-answer/)\n[Large Language Model](https://towardsdatascience.com/category/large-language-model/)\nEnterprise Document Intelligence [Vol.1 #7bis] - Tobi Lütke and Andrej Karpathy named the practice in 2025. For a single document, each brick emits typed pieces that converge on one LLM call. Corpus, conversation, and tool extensions are follow-up work\n[Kezhan Shi](https://towardsdatascience.com/author/kezhanshi/)\nJune 30, 202617 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2023/06/17J_eUxDlScAizmAq9I2lzg.png)](https://towardsdatascience.com/gpt-vs-bert-which-is-better-2f1cf92af21a/)\n## [GPT vs BERT: Which is Better?](https://towardsdatascience.com/gpt-vs-bert-which-is-better-2f1cf92af21a/)\n[Artificial Intelligence](https://towardsdatascience.com/category/artificial-intelligence/)\nComparing two large-language models: Approach and example\n[Pranay Dave](https://towardsdatascience.com/author/pranay-dave9/)\nJune 23, 20235 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/10/image-134.jpg)](https://towardsdatascience.com/how-to-build-tools-for-ai-agents/)\n## [How to Build Tools for AI Agents](https://towardsdatascience.com/how-to-build-tools-for-ai-agents/)\n[Agentic AI](https://towardsdatascience.com/category/artificial-intelligence/agentic-ai/)\nLearn how to design and build effective tools to be used by AI Agents\n[Eivind Kjosbakken](https://towardsdatascience.com/author/oieivind/)\nOctober 15, 20259 min read\n\n\n  * [YouTube](https://www.youtube.com/c/TowardsDataScience)\n  * [X](https://x.com/TDataScience)\n  * [LinkedIn](https://www.linkedin.com/company/towards-data-science/)\n  * [Threads](https://www.threads.net/@towardsdatascience)\n  * [Bluesky](https://bsky.app/profile/towardsdatascience.com)\n\n\n[](https://towardsdatascience.com/)\nYour home for data science and AI. The world's leading publication for data science, data analytics, data engineering, machine learning, and artificial intelligence professionals.\nSubscribe to Our Newsletter[Write For TDS](https://towardsdatascience.com/submissions/)•[About](https://towardsdatascience.com/about-towards-data-science-d691af11cc2f/)•[Advertise](https://contact.towardsdatascience.com/advertise-with-towards-data-science)•[Privacy Policy](https://towardsdatascience.com/privacy-policy/)•[Terms of Use](https://towardsdatascience.com/website-terms-of-use/)\n© Insight Media Group, LLC 2026•[The New Stack](https://thenewstack.io)•[roadmap.sh](https://roadmap.sh)•[Creator Network](https://insightmediagroup.io/creator-network)\n####  Recommended Articles \nClose\n  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607631/image?etag=1781543525510&width=500&height=500) Critical Mistakes Companies Make When Integrating AI/ML into Their Processes ](https://towardsdatascience.com/critical-mistakes-companies-make-when-integrating-ai-ml-into-their-processes/ \"Critical Mistakes Companies Make When Integrating AI/ML into Their Processes\")\n  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607686/image?etag=1782161464448&width=500&height=500) How Relevance Models Foreshadowed Transformers for NLP ](https://towardsdatascience.com/how-relevance-models-foreshadowed-transformers-for-nlp/ \"How Relevance Models Foreshadowed Transformers for NLP\")\n  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607639/image?etag=1781250125848&width=500&height=500) Javascript Fatigue: HTMX Is All You Need to Build ChatGPT — Part 1 ](https://towardsdatascience.com/javascript-fatigue-you-dont-need-js-to-build-chatgpt/ \"Javascript Fatigue: HTMX Is All You Need to Build ChatGPT — Part 1\")\n  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607930/image?etag=1782124566054&width=500&height=500) The Subset Sum Problem Solved in Linear Time for Dense Enough Inputs ](https://towardsdatascience.com/subset-sum-problem-solved-in-linear-time-for-dense-enough-inputs/ \"The Subset Sum Problem Solved in Linear Time for Dense Enough Inputs\")\n\n\n",
  "body_base_path": "attachments/491af05b0d4d/pages/",
  "body_markdown_path": "attachments/491af05b0d4d/pages/archived-body.md",
  "local_html": "attachments/491af05b0d4d/pages/archived-body.html",
  "body_source": "prior_capture"
}