{
  "source_file": "0930",
  "source_platform": "Medium",
  "author": "Angela Shi & Kezhan Shi",
  "title_listed": "Loop Engineering for RAG: The Small Loops Inside Each Step, the Big Loops Across the Pipeline",
  "keywords": "",
  "domain": "大模型算法",
  "content_type": "深度技术解析",
  "is_paid_flag": "否",
  "url": "https://towardsdatascience.com/loop-engineering-for-rag-the-small-loops-inside-each-step-the-big-loops-across-the-pipeline/",
  "publish_date": "2026-08-17",
  "corpus_stage": "",
  "notes": "",
  "id": "ff566a69f452",
  "collected_at": "2026-10-02T01:07:16",
  "title": "Loop Engineering for RAG: The Small Loops Inside Each Step, the Big Loops Across the Pipeline | 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# Loop Engineering for RAG: The Small Loops Inside Each Step, the Big Loops Across the Pipeline\nEnterprise Document Intelligence [Vol.1 #13bis] - The four bricks return useful results most of the time. Loop engineering is what the system does the rest of the time: when retrieval misses, when generation fails the schema, when the listing comes back incomplete, when an API call times out. Three control surfaces (trigger, termination, recovery) and one rule that separates a useful loop from a spinning one\n[Angela and Kezhan Shi](https://towardsdatascience.com/author/angela.shi/)\nAugust 17, 202614 min read\n![](../images/000.jpg)\n[Photo](https://www.pexels.com/photo/11598090/) by Nina Simková, via Pexels.\nAI engineers today talk a lot about loop engineering. The phrase took off after Boris Cherny, the Anthropic engineer behind Claude Code, said he had mostly stopped prompting: he writes loops that prompt the model until a condition is met. For a coding agent with a test suite to check against, that works. For an enterprise RAG pipeline, the loop itself is the part that needs engineering, because the failures it has to absorb are ordinary: the parser flattens the one table the answer lived in, retrieval returns the page next to the right one, the model sends back JSON that fails the schema, the API times out in the middle of a batch. The four bricks handle the clean path; loop engineering is what the system does on every other path, and doing it well is what lets the pipeline recover instead of spin.\nA one-shot pipeline commits to its first try: parse once, retrieve once, generate once, return whatever comes out. When retrieval comes back empty or the answer is half-formed, there is no second chance. A loop gives the pipeline one: notice the miss, adjust, and run the weak step again before the user ever sees it.\nThis article is a standalone companion to [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. It sits next to [Article 7bis (context engineering for single-document RAG)](https://towardsdatascience.com/context-engineering-for-rag-the-four-typed-inputs-behind-every-rag-answer/) and treats the layer just above it.\nArticle 13 (the composite pipeline) glued the bricks together and named the dispatcher. This article zooms into what the dispatcher does when a single call is not enough. Volume 4 develops the agentic version of the same discipline. This piece stays at single-document scope. The rest is the map: what loop engineering is, the anatomy of one loop (trigger, termination, recovery), the two scales it runs at, the failure modes to guard against, and the outer loop the human holds.\nLearn this step by step with the interactive [AI Engineer](https://roadmap.sh/ai-engineer) roadmap.\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 in the series: Article 13bis (loop engineering), a companion to the composite pipeline - Image by author_\n📓 The runnable companion shows the loops firing: you trip `llm_parse` on a simulated timeout and watch the backoff schedule count down, then flip `complete_answer_found` to false on a listing answer and watch the dispatcher widen the retrieval scope and regenerate. On GitHub: [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_\n## 1. What loop engineering is\n### 1.1 The third layer of the agent stack\nThree disciplines stack on top of an LLM call. Prompt engineering writes the call: system message, user message, schema. Context engineering chooses what enters and exits the model’s context window between calls (see Article 7bis). Loop engineering decides _when the next call happens_ , _what triggers it_ , _when the loop stops_ , and _how the system recovers when something goes wrong_.\nA polished prompt with a clean retrieval upstream produces a correct answer most of the time. The rest of the time the call fails in one of the ways the opening listed: an invalid JSON, a self-flagged incomplete listing, a 429 at the deployment’s rate cap. Loop engineering is the discipline of designing what happens next.\nThe shortest critique of bad loop engineering, paraphrasing the practitioner literature, is this: a loop that retries the same action on the same error is not learning, it is spinning. The difference between a loop that helps and a loop that wastes tokens is whether each iteration changes something the previous iteration did not address.\n### 1.2 The lineage in one paragraph\nBounded retry is the oldest pattern in the catalogue: Erlang’s _let it crash_ model has carried some version of it since the 1980s. ReAct (Princeton and Google, October 2022) put it inside an LLM call for the first time: reason about the result, decide to act again or stop. AutoGPT (March 2023) made the autonomous version public. Reflexion (NeurIPS 2023) added self-evaluation. Plan-and-Execute separated planning from execution. Geoffrey Huntley’s Ralph Loop (July 2025) put the goal on disk so a context reset never lost track. Anthropic released the `/goal` command in Claude Code (May 2026) and packaged the whole stack two weeks later as Dynamic Workflows. The single-document case in this article uses a much smaller subset: retry-with-backoff, schema-fail retry, completeness check, dispatcher branching. None of the heavier orchestration primitives bite at single-document scope. They will, in V1 Part IV (corpus) and V4 (agentic).\n## 2. The anatomy of a loop\nBefore cataloguing the loops in a RAG pipeline, it helps to fix the parts of a single one. Every loop, small or big, has the same three control surfaces and answers to the same one rule.\n### 2.1 Three control surfaces: trigger, termination, recovery\nA loop has three control surfaces that are easier to design separately than together.\n**Trigger.** The condition under which a _new_ call fires after the first one returned. Three triggers show up in the single-document case. _Schema validation failed_ : the model produced JSON that the Pydantic schema rejected. _The answer carries a self-flag_ : the listing schema’s `complete_answer_found` field is `false`, the answer schema’s `confidence` is below 0.6, the model wrote `needs_clarification`. _Transient API failure_ : an `APITimeoutError`, an `APIConnectionError`, a 429, a 5xx. Each trigger is a single named predicate. The trace store logs which one fired; the dispatcher (Article 13) decides which one to handle and how.\n**Termination.** The condition under which the loop stops. Three classes show up in the single-document case. _Loop-until-done_ terminates when the predicate is finally satisfied (the schema validates, `complete_answer_found` is true, the confidence is above threshold). _Loop-until-budget_ terminates when a quota is exhausted (six retries reached, sixty seconds of wall-clock burned). _Hard cap_ terminates unconditionally after N attempts (the `llm_parse` wrapper hard-stops at six retries with `RuntimeError`). A loop without an explicit termination is the canonical failure mode the Anthropic Cookbook flags first.\n**Recovery.** What happens when a call fails. The library this series provides does the simplest answer in its LLM wrapper: retry-with-backoff. Six attempts, delays of 2 / 4 / 8 / 16 / 32 / 60 seconds plus jitter, on a fixed list of retriable exceptions (`APITimeoutError`, `APIConnectionError`, `RateLimitError`, 429, 500, 502, 503, 504). Three richer answers fit different cases: fallback to a different model (the 3B failed schema, the 7B might), escalate to a human reviewer (any answer below 0.6 confidence on a high-stakes question), or skip the item and return what was computed (the batch result carries the list of successes and the list of failures, the consumer decides whether to retry the failures later).\nThese three controls describe most of what a loop _does_ between LLM calls.\n![](../images/body-c47e68c98ade5aace361.png)\n_One LLM call, three control surfaces around it: trigger, termination, recovery. - Image by author_\nThe one control surface the library provides as a ready-made primitive is recovery, in the shape of retry-with-backoff. It lives in `llm_parse` and wraps every parse call: bounded attempts, an exponential delay with jitter, a hard cap that raises rather than spins.\npython\n\n```\n# the library's LLM wrapper - retry-with-backoff, built into every parse call\n\ndef llm_parse(*, input, text_format, max_retries=6, cache=True, **opts):\n    for attempt in range(max_retries):\n        try:\n            return client.responses.parse(input=input, text_format=text_format)\n        except Exception as err:\n            if not is_retriable(err):              # timeout/429/5xx retry ;\n                raise                              # context-length never does\n            if attempt == max_retries - 1:\n                raise                              # hard cap -> propagate\n            delay = _compute_delay(attempt, base=2, cap=60, jitter=0.3,\n                                   retry_after=extract_retry_after(err))  # 2,4,..,60s\n            time.sleep(delay)                      # honour Retry-After floor\n```\n\n### 2.2 The anti-spinning rule\nA loop that fires its trigger, retries with the same payload, gets the same failure, and retries again is not a loop. It is a denial-of-service against the company’s API budget. The rule that prevents this is the simplest one in the discipline.\nA loop should only retry when _something has changed_ between the previous attempt and the next. Three legitimate things to change.\nThe _payload_ : the dispatcher widened the retrieval scope (50 lines became 200 lines), the system prompt got an extra constraint (_“the previous answer did not validate; produce ONLY a JSON matching this schema with no prose around it”_), the question was rephrased (the user clarified what they meant by “in Q3”).\nThe _model_ : the small model returned garbage, the large model gets the second shot. [Article 8quater (adaptive model selection)](https://towardsdatascience.com/loop-engineering-for-rag-generation-an-llm-cascade-from-a-cheap-local-model-up-to-a-hosted-flagship/) builds this loop: start with the cheapest model the job allows, escalate only when validation rejects.\nThe _strategy_ : the keyword retrieval missed, the second attempt uses dense retrieval. This is the dispatcher’s job (Article 13) and the V4 catalogue calls it _classify-and-act_.\nRetry that does not change one of the three is spinning. The implementation rule is operational: every retry should carry a trace block that names what changed. If the trace block is empty, the retry should not have fired. The fix is to bound retries at a small N (the library uses six) and to log the _changed-since-last_ field on every iteration. The day a user complains that _“the same question now costs four times as much”_ , the trace tells you whether the loop was working or spinning.\n## 3. Two scales of loop\nA loop is not one magic mechanism bolted onto the pipeline. Each one is a named, bounded response to a specific way a brick can fall short, and they sort into two scales: small loops that run inside a single brick, and big loops that cross bricks when generation distrusts its own input.\n### 3.1 Small loops: one inside each brick\n**Small loops live inside a single brick.** Document parsing runs the image cascade: filter the page’s images cheaply, classify what each one is, and pay a vision model to describe only the ones worth reading (Article 5). It also rebuilds a missing outline from body typography, looping until the outline stabilises (Article 5octies), and dispatches each page to the parser its signals call for (Article 5nonies). Retrieval walks the table of contents down to the right subsection (Article 7quater). Question parsing asks one clarifying question and learns the default (Article 6bis). Generation retries when the JSON fails the schema, and reruns retrieval and generation when the listing comes back incomplete (Articles 8 and 12).\nEach of these has the same three control surfaces as any loop, a trigger, a termination, and a bounded depth; it just iterates over its own brick’s material rather than reacting to a downstream result. Retry-with-backoff wraps every API call underneath all of them, the one loop that is pure plumbing rather than a brick’s own reasoning.\n![](../images/body-e4dae080c43e196c671e.png)\n_Small loops sit inside a brick, big loops cross bricks when generation distrusts its input. - Image by author_\n### 3.2 Big loops: across bricks\n**Big loops cross bricks.** Generation reads its own input, decides it was not good enough to answer from, and sends the pipeline back to an earlier brick. The single-document case has three: adaptive parsing (generation’s `context_structured=false` re-parses the offending page with a deeper parser, Articles 10A and 10B), reference resolution (generation reports `pending_references`, the orchestrator resolves the pointer against the parsing tables, re-retrieves the target region, regenerates, Article 11), and scope feedback (generation’s `complete_answer_found=false` widens retrieval, Article 13). Same shape each time: a typed flag on the answer, a bounded trip back upstream, a second generation pass that re-checks itself.\nThe check is a typed flag, the escalation is bounded to the flagged page, and the loop stops the moment the re-checked answer trusts its context.\n![](../images/body-a7ea2b086f4ebf6c3125.png)\n_The adaptive-parsing big loop: verify the parse, escalate the flagged page, regenerate, re-verify. - Image by author_\n### 3.3 The named patterns the series already has\nLooking at the existing V1 code with the 2026 loop-engineering vocabulary on hand: most of the named primitives have a counterpart already in the code, under a less catchy name.\n![](../images/body-e4dae080c43e196c671e.png)\n_The five primitives the single-document case uses already exist in the series under quieter names. - Image by author_\n**Loop-until-done** : the V1 Article 12 listing completeness loop. The generator answers, the schema carries the `complete_answer_found` flag, the loop reruns retrieval and generation if the flag is false. The pattern came with the listing schema and predates the name.\n**Retry-with-backoff** : the library’s `llm_parse` wrapper. Six attempts with the exponential schedule. The cost analysis in Article 21 Section 4.6 details when this triggers and what the operational impact looks like.\n**Generate-and-filter** : V1 Article 8’s structured-output schema. The LLM returns JSON, the Pydantic validator rejects anything that does not match, the loop retries if validation fails. The 2026 vocabulary makes the loop layer explicit; the discipline has been in the series since the schema-first generation article.\n**Classify-and-act** : V1 Article 13’s composite dispatcher. The parsed question carries a shape (single-value, listing, aggregation, comparison), the dispatcher routes to the matching handler. Anthropic’s classify-and-act applies the same shape to free-form classification rather than to a parsed-question taxonomy; the underlying machinery is the same.\n**Feedback-loop-between-bricks** : V1 Article 13’s between-bricks feedback. Generation flags the retrieval scope as insufficient, the dispatcher widens the scope, generation reruns. The 2026 vocabulary calls this _evaluator-optimiser_ ; the series called it the dispatcher’s feedback loop and built it in 2024.\nWhat is _not_ yet in V1: adversarial verification (a separate agent that tries to refute the answer), the tournament pattern (N candidate answers, judges pick the winner), explicit _loop-until-dry_ termination (K consecutive empty rounds). Those are V4-scope; the single-document case rarely needs them.\n### 3.4 The loops at a glance\nEvery loop in the single-document pipeline, small or big, wears the same three control surfaces from Section 2. Read down the trigger column and the shape is the same each time: the loop fires on one specific, named signal, terminates on a bound, and changes exactly one thing before it retries.\n![](../images/body-9abc75b626988dd8d317.png)\n_Every loop as one row against the three control surfaces of Section 2. - Image by author_\n## 4. Failure modes the discipline guards against\nThree failure modes are routine in production single-document RAG and each one maps to a specific loop-engineering rule.\n**Infinite-loop on transient failure.** The retry layer has no hard cap. The deployment hits a 429, the loop retries, the next 429 arrives within the rate-limit window, the loop retries again, the API budget burns through the night. _Fix_ : a hard cap on retries (the library uses six), an explicit `RuntimeError` after the cap, and an alert on the trace store when the cap is hit.\n**Spinning on a deterministic failure.** The Pydantic schema rejects the output because the model wrote a field name with a typo. The loop retries with the same prompt. The model produces the same typo. Six retries, six rejections, the user sees a `RuntimeError`. _Fix_ : on schema-fail retry, modify the prompt to include the validator error message verbatim (“ _your previous answer failed validation with:_`_field_name_`_should be_` _field_name_actual_`_. Produce a JSON matching the schema exactly._ ”). The next attempt has new information.\n**Confidence-flag ignored.** The answer comes back with `confidence = 0.3` and the dispatcher returns it to the user without flagging anything. _Fix_ : the dispatcher applies a confidence threshold, low-confidence answers either escalate to a richer retrieval pass (widen scope, switch from keyword to dense) or carry an explicit `low_confidence` warning to the consumer.\n## 5. Loops within loops: the human holds the outer one\nLoop engineering as covered so far is one loop: the dispatcher fires a call, reads the result, decides to retry, terminates when satisfied. Zoom out and there are three loops running at different cadences around the same system, borrowing the frame Andrew Ng laid out in [his “Three Key Loops for Building Great Software” letter](https://www.deeplearning.ai/the-batch/three-key-loops-for-building-great-software):\n  * **The inner loop, seconds to a minute.** The dispatcher itself. Retry-with-backoff on a 502, regenerate on a schema fail, expand the anchor set on `complete_answer_found: false`. No human, bounded budget, code decides. Sections 2 to 4 of this article.\n  * **The dev loop, minutes to hours.** An engineer reads the audit trail, spots a class of failures, edits `concept_keywords_df`, adds a synonym pair, tightens a Pydantic schema, deploys. The dispatcher then runs the corrected version on the next call. Article 6’s expert dictionary and Article 20’s evaluation dashboard are the surfaces this loop reads from.\n  * **The outer loop, hours to weeks.** A user edits an extracted field the system got wrong. The correction lands in the storage layer with a citation, becomes ground truth for the next evaluation pass, feeds into the concept catalog through a review queue. Or a claims handler pushes back on a question template the system was answering literally, and the template gets rephrased for everyone downstream. The refresh-field pattern from Article 19 and the question-template review from Article 20 sit here.\n\n\nNg’s point is that as long as the human knows something the AI does not, that outer loop cannot be closed by more automation, and treating it as noise to be optimised away is the mistake. _“As long as the human knows something the AI does not, human-in-the-loop is needed to inject that knowledge into the system.”_ This is the series’ amplify-the-expert thesis in 2026 vocabulary. The dispatcher owns the inner loop, the engineer owns the dev loop, the expert owns the outer loop, and none of them can substitute for the others.\nThe corollary for design: keep the outer loop cheap for the expert. If correcting a wrong field takes ten clicks, the outer loop breaks and the system’s error rate stops improving. The refresh-field UX, the review queue, and the audit trail keep the outer loop fast enough to compound.\n## 6. Out of scope, with pointers\nThree siblings stay open here.\n  * **The corpus case.** Single-document loops are bounded by the size of one document. Corpus loops add cascade-with-partial-recovery (twelve documents in a batch, three fail, keep the nine successes and let the consumer retry the failures later) and refresh-field on user edit. V1 Article 17quater treats the corpus loop engineering.\n  * **The agentic case.** Single-document loops are bounded in turns (the dispatcher fires at most a handful of retries before terminating). Agentic loops keep the budget open across dozens of turns, with sub-agents, memory tools, and the `/compact` lifecycle. V4 Article 9bis covers context engineering for the agentic loop; V4 Article 9ter covers loop engineering for the same.\n  * **Adversarial verification.** This article focuses on bounded retry and self-flagged completeness checks. The richer discipline of _independent agents whose job is to refute the answer_ is V4-scope. V1 single-document RAG rarely needs it; the V4 article develops it.\n\n\n## 7. Conclusion\nLoop engineering is the third discipline after prompt engineering and context engineering. At single-document scope, it owns four primitives: retry-with-backoff for transient API failures, generate-and-filter for schema-fail retries, loop-until-done for completeness checks, classify-and-act for dispatcher branching. The single rule that separates a useful loop from a spinning one is _every retry changes something_. The library this series provides implements the first primitive directly; the other three are in the dispatcher (Article 13) and the listing schema (Article 12). The 2026 vocabulary puts a name on a discipline the series has been practising since 2024.\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/)[Rag](https://towardsdatascience.com/tag/rag/)[Rag Architecture](https://towardsdatascience.com/tag/rag-architecture/)[loop engineering](https://towardsdatascience.com/tag/loop-engineering/)\nShare This Article\n  * [](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Ftowardsdatascience.com%2Floop-engineering-for-rag-the-small-loops-inside-each-step-the-big-loops-across-the-pipeline%2F&title=Loop%20Engineering%20for%20RAG%3A%20The%20Small%20Loops%20Inside%20Each%20Step%2C%20the%20Big%20Loops%20Across%20the%20Pipeline)\n  * [](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Ftowardsdatascience.com%2Floop-engineering-for-rag-the-small-loops-inside-each-step-the-big-loops-across-the-pipeline%2F&title=Loop%20Engineering%20for%20RAG%3A%20The%20Small%20Loops%20Inside%20Each%20Step%2C%20the%20Big%20Loops%20Across%20the%20Pipeline)\n  * [](https://x.com/share?url=https%3A%2F%2Ftowardsdatascience.com%2Floop-engineering-for-rag-the-small-loops-inside-each-step-the-big-loops-across-the-pipeline%2F&text=Loop%20Engineering%20for%20RAG%3A%20The%20Small%20Loops%20Inside%20Each%20Step%2C%20the%20Big%20Loops%20Across%20the%20Pipeline)\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/code-understanding-on-your-own-hardware-dd38c4f266d6/)\n## [Code understanding on your own hardware](https://towardsdatascience.com/code-understanding-on-your-own-hardware-dd38c4f266d6/)\n[](https://towardsdatascience.com/loop-engineering-for-rag-the-small-loops-inside-each-step-the-big-loops-across-the-pipeline/)\nSetting up an LLM to talk about your code - with LangChain and local hardware\n[Dorian Drost](https://towardsdatascience.com/author/doriandrost/)\nJuly 5, 20237 min read\n  * [![](../images/004.jpg)](https://towardsdatascience.com/how-to-increase-coding-iteration-speed/)\n## [How to Increase Coding Iteration Speed](https://towardsdatascience.com/how-to-increase-coding-iteration-speed/)\n[LLM Applications](https://towardsdatascience.com/category/artificial-intelligence/llm-applications/)\nLearn how to become a more efficient programmer with local testing\n[Eivind Kjosbakken](https://towardsdatascience.com/author/oieivind/)\nDecember 13, 20257 min read\n  * [![](../images/005.jpg)](https://towardsdatascience.com/prompt-engineering-isnt-enough-i-built-a-control-layer-that-works-in-production/)\n## [Prompt Engineering Isn’t Enough — I Built a Control Layer That Works in Production](https://towardsdatascience.com/prompt-engineering-isnt-enough-i-built-a-control-layer-that-works-in-production/)\n[Large Language Model](https://towardsdatascience.com/category/large-language-model/)\nMost production LLM integrations treat prompts like the final layer. That gets you 0% reliability on structured output. I built an 8-component system that brought it to 100% — without changing a single prompt.\n[Emmimal P Alexander](https://towardsdatascience.com/author/emmimalp.alexander/)\nMay 21, 202620 min read\n  * [![](../images/006.jpg)](https://towardsdatascience.com/llm-fallbacks-break-agent-pipelines-i-built-the-missing-recovery-layer/)\n## [LLM Fallbacks Break Agent Pipelines — I Built the Missing Recovery Layer](https://towardsdatascience.com/llm-fallbacks-break-agent-pipelines-i-built-the-missing-recovery-layer/)\n[Large Language Model](https://towardsdatascience.com/category/large-language-model/)\nStandard model swapping forces your production AI to accept corrupted data. Here is the zero-dependency, state-aware recovery layer I built to preserve schema integrity during failovers.\n[Emmimal P Alexander](https://towardsdatascience.com/author/emmimalp.alexander/)\nJune 16, 202614 min read\n  * [![](../images/007.jpg)](https://towardsdatascience.com/stop-returning-text-from-rag-the-typed-answer-contract-that-prevents-hallucination/)\n## [Stop Returning Text from RAG: The Typed Answer Contract That Prevents Hallucination](https://towardsdatascience.com/stop-returning-text-from-rag-the-typed-answer-contract-that-prevents-hallucination/)\n[Large Language Model](https://towardsdatascience.com/category/large-language-model/)\nEnterprise Document Intelligence [Vol.1 #8A] - The schema is the contract: every field is a question the pipeline asks the model, and every answer is checkable\n[Kezhan Shi](https://towardsdatascience.com/author/kezhanshi/)\nJuly 4, 202626 min read\n  * [![](../images/008.png)](https://towardsdatascience.com/building-a-knowledge-graph-from-scratch-using-llms-f6f677a17f07/)\n## [Building a Knowledge Graph From Scratch Using LLMs](https://towardsdatascience.com/building-a-knowledge-graph-from-scratch-using-llms-f6f677a17f07/)\n[Artificial Intelligence](https://towardsdatascience.com/category/artificial-intelligence/)\nTurn your Pandas Data Frame into a Knowledge Graph using LLMs. Build your own LLM graph-builder and QA your KG.\n[Cristian Leo](https://towardsdatascience.com/author/cristianleo120/)\nNovember 25, 202439 min read\n  * [![](../images/009.jpg)](https://towardsdatascience.com/llms-pandas-how-i-use-generative-ai-to-generate-pandas-dataframe-summaries-2/)\n## [LLMs + Pandas: How I Use Generative AI to Generate Pandas DataFrame Summaries](https://towardsdatascience.com/llms-pandas-how-i-use-generative-ai-to-generate-pandas-dataframe-summaries-2/)\n[Large Language Models](https://towardsdatascience.com/category/artificial-intelligence/large-language-models/)\nLocal Large Language Models can convert massive DataFrames to presentable Markdown reports — here's how.\n[Dario Radečić](https://towardsdatascience.com/author/radecicdario/)\nJune 2, 202510 min read\n  * [![](../images/010.jpg)](https://towardsdatascience.com/one-rag-pipeline-four-very-different-pdfs-same-four-bricks-every-answer-typed-and-cited/)\n## [A Production RAG Pipeline in Action: Every Answer Typed and Cited](https://towardsdatascience.com/one-rag-pipeline-four-very-different-pdfs-same-four-bricks-every-answer-typed-and-cited/)\n[Large Language Model](https://towardsdatascience.com/category/large-language-model/)\nEnterprise Document Intelligence [Vol.1 #9B] - One call wires the four upgraded bricks together, run on a paper, a NIST standard, and a report with a broken TOC\n[Angela and Kezhan Shi](https://towardsdatascience.com/author/angela.shi/)\nJuly 17, 202625 min read\n  * [![](../images/011.jpg)](https://towardsdatascience.com/cut-an-enterprise-rag-pipelines-latency-and-cost-by-calling-the-llm-less-not-by-buying-a-faster-model/)\n## [Cut an Enterprise RAG Pipeline’s Latency and Cost by Calling the LLM Less, Not by Buying a Faster Model](https://towardsdatascience.com/cut-an-enterprise-rag-pipelines-latency-and-cost-by-calling-the-llm-less-not-by-buying-a-faster-model/)\n[Large Language Model](https://towardsdatascience.com/category/large-language-model/)\nEnterprise Document Intelligence [Vol.1 #9ter] - The pipeline from Article 9 calls a model at several steps to be sure it is right. On easy questions that is needless latency. A per-question signal routes them past the model, about two seconds saved for a keyword match.\n[Angela and Kezhan Shi](https://towardsdatascience.com/author/angela.shi/)\nAugust 13, 202611 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": "Loop Engineering for RAG: The Small Loops Inside Each Step, the Big Loops Across the Pipeline | Towards Data Science",
    "description": "Enterprise Document Intelligence [Vol.1 #13bis] - The four bricks return useful results most of the time. Loop engineering is what the system does the rest of the time: when retrieval misses, when generation fails the schema, when the listing comes back incomplete, when an API call times out. Three control surfaces (trigger, termination, recovery) and one rule that separates a useful loop from a spinning one",
    "keywords": null,
    "author": "Angela and Kezhan Shi",
    "og:locale": "en_US",
    "og:type": "article",
    "og:title": "Loop Engineering for RAG: The Small Loops Inside Each Step, the Big Loops Across the Pipeline",
    "og:description": "Enterprise Document Intelligence [Vol.1 #13bis] - The four bricks return useful results most of the time. Loop engineering is what the system does the rest of the time: when retrieval misses, when generation fails the schema, when the listing comes back incomplete, when an API call times out. Three control surfaces (trigger, termination, recovery) and one rule that separates a useful loop from a spinning one",
    "og:url": "https://towardsdatascience.com/loop-engineering-for-rag-the-small-loops-inside-each-step-the-big-loops-across-the-pipeline/",
    "og:site_name": "Towards Data Science",
    "og:image": "https://assets.insightmediagroup.io/media/wp-content/uploads/2026/08/vintage_gauges_11598090_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": "14 min read",
    "article:published_time": "2026-08-17T06:00:05.000Z",
    "article:modified_time": "2026-08-17T06:00:05.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/ff566a69f452/code/block_000.txt",
      "chars": 828
    }
  ],
  "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-20T18:11:35Z"
    },
    {
      "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-20T18:11:35Z"
    },
    {
      "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-20T18:11:35Z"
    },
    {
      "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-20T18:11:35Z"
    }
  ],
  "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/context-engineering-for-rag-the-four-typed-inputs-behind-every-rag-answer/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/loop-engineering-for-rag-generation-an-llm-cascade-from-a-cheap-local-model-up-to-a-hosted-flagship/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/document-processing/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/llm/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/rag/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/rag-architecture/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/loop-engineering/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/code-understanding-on-your-own-hardware-dd38c4f266d6/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/loop-engineering-for-rag-the-small-loops-inside-each-step-the-big-loops-across-the-pipeline/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/author/doriandrost/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/how-to-increase-coding-iteration-speed/"
    },
    {
      "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/11598090/"
    },
    {
      "type": "external",
      "href": "https://roadmap.sh/ai-engineer"
    },
    {
      "type": "external",
      "href": "https://github.com/doc-intel/notebooks-vol1"
    },
    {
      "type": "external",
      "href": "https://www.deeplearning.ai/the-batch/three-key-loops-for-building-great-software"
    },
    {
      "type": "external",
      "href": "https://www.facebook.com/sharer/sharer.php?title=Loop+Engineering+for+RAG%3A+The+Small+Loops+Inside+Each+Step%2C+the+Big+Loops+Across+the+Pipeline&u=https%3A%2F%2Ftowardsdatascience.com%2Floop-engineering-for-rag-the-small-loops-inside-each-step-the-big-loops-across-the-pipeline%2F"
    },
    {
      "type": "external",
      "href": "https://www.linkedin.com/shareArticle?mini=true&title=Loop+Engineering+for+RAG%3A+The+Small+Loops+Inside+Each+Step%2C+the+Big+Loops+Across+the+Pipeline&url=https%3A%2F%2Ftowardsdatascience.com%2Floop-engineering-for-rag-the-small-loops-inside-each-step-the-big-loops-across-the-pipeline%2F"
    },
    {
      "type": "external",
      "href": "https://x.com/share?text=Loop+Engineering+for+RAG%3A+The+Small+Loops+Inside+Each+Step%2C+the+Big+Loops+Across+the+Pipeline&url=https%3A%2F%2Ftowardsdatascience.com%2Floop-engineering-for-rag-the-small-loops-inside-each-step-the-big-loops-across-the-pipeline%2F"
    },
    {
      "type": "external",
      "href": "https://www.youtube.com/c/TowardsDataScience"
    },
    {
      "type": "external",
      "href": "https://www.threads.net/@towardsdatascience"
    },
    {
      "type": "external",
      "href": "https://bsky.app/profile/towardsdatascience.com"
    },
    {
      "type": "external",
      "href": "https://thenewstack.io"
    },
    {
      "type": "external",
      "href": "https://roadmap.sh"
    },
    {
      "type": "external",
      "href": "https://insightmediagroup.io/creator-network"
    }
  ],
  "repair_version": 2,
  "repaired_at": "2026-10-20T17:35:29Z",
  "source_url": "https://towardsdatascience.com/loop-engineering-for-rag-the-small-loops-inside-each-step-the-big-loops-across-the-pipeline/",
  "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# Loop Engineering for RAG: The Small Loops Inside Each Step, the Big Loops Across the Pipeline\nEnterprise Document Intelligence [Vol.1 #13bis] - The four bricks return useful results most of the time. Loop engineering is what the system does the rest of the time: when retrieval misses, when generation fails the schema, when the listing comes back incomplete, when an API call times out. Three control surfaces (trigger, termination, recovery) and one rule that separates a useful loop from a spinning one\n[Angela and Kezhan Shi](https://towardsdatascience.com/author/angela.shi/)\nAugust 17, 202614 min read\n![](https://assets.insightmediagroup.io/media/wp-content/uploads/2026/08/vintage_gauges_11598090_v3_card.jpg)\n[Photo](https://www.pexels.com/photo/11598090/) by Nina Simková, via Pexels.\nAI engineers today talk a lot about loop engineering. The phrase took off after Boris Cherny, the Anthropic engineer behind Claude Code, said he had mostly stopped prompting: he writes loops that prompt the model until a condition is met. For a coding agent with a test suite to check against, that works. For an enterprise RAG pipeline, the loop itself is the part that needs engineering, because the failures it has to absorb are ordinary: the parser flattens the one table the answer lived in, retrieval returns the page next to the right one, the model sends back JSON that fails the schema, the API times out in the middle of a batch. The four bricks handle the clean path; loop engineering is what the system does on every other path, and doing it well is what lets the pipeline recover instead of spin.\nA one-shot pipeline commits to its first try: parse once, retrieve once, generate once, return whatever comes out. When retrieval comes back empty or the answer is half-formed, there is no second chance. A loop gives the pipeline one: notice the miss, adjust, and run the weak step again before the user ever sees it.\nThis article is a standalone companion to [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. It sits next to [Article 7bis (context engineering for single-document RAG)](https://towardsdatascience.com/context-engineering-for-rag-the-four-typed-inputs-behind-every-rag-answer/) and treats the layer just above it.\nArticle 13 (the composite pipeline) glued the bricks together and named the dispatcher. This article zooms into what the dispatcher does when a single call is not enough. Volume 4 develops the agentic version of the same discipline. This piece stays at single-document scope. The rest is the map: what loop engineering is, the anatomy of one loop (trigger, termination, recovery), the two scales it runs at, the failure modes to guard against, and the outer loop the human holds.\nLearn this step by step with the interactive [AI Engineer](https://roadmap.sh/ai-engineer) roadmap.\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-52-1024x572.png)\n_where this article sits in the series: Article 13bis (loop engineering), a companion to the composite pipeline - Image by author_\n📓 The runnable companion shows the loops firing: you trip `llm_parse` on a simulated timeout and watch the backoff schedule count down, then flip `complete_answer_found` to false on a listing answer and watch the dispatcher widen the retrieval scope and regenerate. On GitHub: [doc-intel/notebooks-vol1](https://github.com/doc-intel/notebooks-vol1).\n![](https://assets.insightmediagroup.io/media/wp-content/uploads/2026/08/image-53-1024x658.png)\n_The public companion-code repo at doc-intel/notebooks-vol1 - Image by author_\n## 1. What loop engineering is\n### 1.1 The third layer of the agent stack\nThree disciplines stack on top of an LLM call. Prompt engineering writes the call: system message, user message, schema. Context engineering chooses what enters and exits the model’s context window between calls (see Article 7bis). Loop engineering decides _when the next call happens_ , _what triggers it_ , _when the loop stops_ , and _how the system recovers when something goes wrong_.\nA polished prompt with a clean retrieval upstream produces a correct answer most of the time. The rest of the time the call fails in one of the ways the opening listed: an invalid JSON, a self-flagged incomplete listing, a 429 at the deployment’s rate cap. Loop engineering is the discipline of designing what happens next.\nThe shortest critique of bad loop engineering, paraphrasing the practitioner literature, is this: a loop that retries the same action on the same error is not learning, it is spinning. The difference between a loop that helps and a loop that wastes tokens is whether each iteration changes something the previous iteration did not address.\n### 1.2 The lineage in one paragraph\nBounded retry is the oldest pattern in the catalogue: Erlang’s _let it crash_ model has carried some version of it since the 1980s. ReAct (Princeton and Google, October 2022) put it inside an LLM call for the first time: reason about the result, decide to act again or stop. AutoGPT (March 2023) made the autonomous version public. Reflexion (NeurIPS 2023) added self-evaluation. Plan-and-Execute separated planning from execution. Geoffrey Huntley’s Ralph Loop (July 2025) put the goal on disk so a context reset never lost track. Anthropic released the `/goal` command in Claude Code (May 2026) and packaged the whole stack two weeks later as Dynamic Workflows. The single-document case in this article uses a much smaller subset: retry-with-backoff, schema-fail retry, completeness check, dispatcher branching. None of the heavier orchestration primitives bite at single-document scope. They will, in V1 Part IV (corpus) and V4 (agentic).\n## 2. The anatomy of a loop\nBefore cataloguing the loops in a RAG pipeline, it helps to fix the parts of a single one. Every loop, small or big, has the same three control surfaces and answers to the same one rule.\n### 2.1 Three control surfaces: trigger, termination, recovery\nA loop has three control surfaces that are easier to design separately than together.\n**Trigger.** The condition under which a _new_ call fires after the first one returned. Three triggers show up in the single-document case. _Schema validation failed_ : the model produced JSON that the Pydantic schema rejected. _The answer carries a self-flag_ : the listing schema’s `complete_answer_found` field is `false`, the answer schema’s `confidence` is below 0.6, the model wrote `needs_clarification`. _Transient API failure_ : an `APITimeoutError`, an `APIConnectionError`, a 429, a 5xx. Each trigger is a single named predicate. The trace store logs which one fired; the dispatcher (Article 13) decides which one to handle and how.\n**Termination.** The condition under which the loop stops. Three classes show up in the single-document case. _Loop-until-done_ terminates when the predicate is finally satisfied (the schema validates, `complete_answer_found` is true, the confidence is above threshold). _Loop-until-budget_ terminates when a quota is exhausted (six retries reached, sixty seconds of wall-clock burned). _Hard cap_ terminates unconditionally after N attempts (the `llm_parse` wrapper hard-stops at six retries with `RuntimeError`). A loop without an explicit termination is the canonical failure mode the Anthropic Cookbook flags first.\n**Recovery.** What happens when a call fails. The library this series provides does the simplest answer in its LLM wrapper: retry-with-backoff. Six attempts, delays of 2 / 4 / 8 / 16 / 32 / 60 seconds plus jitter, on a fixed list of retriable exceptions (`APITimeoutError`, `APIConnectionError`, `RateLimitError`, 429, 500, 502, 503, 504). Three richer answers fit different cases: fallback to a different model (the 3B failed schema, the 7B might), escalate to a human reviewer (any answer below 0.6 confidence on a high-stakes question), or skip the item and return what was computed (the batch result carries the list of successes and the list of failures, the consumer decides whether to retry the failures later).\nThese three controls describe most of what a loop _does_ between LLM calls.\n![](https://assets.insightmediagroup.io/media/wp-content/uploads/2026/08/image-54-1024x887.png)\n_One LLM call, three control surfaces around it: trigger, termination, recovery. - Image by author_\nThe one control surface the library provides as a ready-made primitive is recovery, in the shape of retry-with-backoff. It lives in `llm_parse` and wraps every parse call: bounded attempts, an exponential delay with jitter, a hard cap that raises rather than spins.\npython\n\n```\n# the library's LLM wrapper - retry-with-backoff, built into every parse call\n\ndef llm_parse(*, input, text_format, max_retries=6, cache=True, **opts):\n    for attempt in range(max_retries):\n        try:\n            return client.responses.parse(input=input, text_format=text_format)\n        except Exception as err:\n            if not is_retriable(err):              # timeout/429/5xx retry ;\n                raise                              # context-length never does\n            if attempt == max_retries - 1:\n                raise                              # hard cap -> propagate\n            delay = _compute_delay(attempt, base=2, cap=60, jitter=0.3,\n                                   retry_after=extract_retry_after(err))  # 2,4,..,60s\n            time.sleep(delay)                      # honour Retry-After floor\n```\n\n### 2.2 The anti-spinning rule\nA loop that fires its trigger, retries with the same payload, gets the same failure, and retries again is not a loop. It is a denial-of-service against the company’s API budget. The rule that prevents this is the simplest one in the discipline.\nA loop should only retry when _something has changed_ between the previous attempt and the next. Three legitimate things to change.\nThe _payload_ : the dispatcher widened the retrieval scope (50 lines became 200 lines), the system prompt got an extra constraint (_“the previous answer did not validate; produce ONLY a JSON matching this schema with no prose around it”_), the question was rephrased (the user clarified what they meant by “in Q3”).\nThe _model_ : the small model returned garbage, the large model gets the second shot. [Article 8quater (adaptive model selection)](https://towardsdatascience.com/loop-engineering-for-rag-generation-an-llm-cascade-from-a-cheap-local-model-up-to-a-hosted-flagship/) builds this loop: start with the cheapest model the job allows, escalate only when validation rejects.\nThe _strategy_ : the keyword retrieval missed, the second attempt uses dense retrieval. This is the dispatcher’s job (Article 13) and the V4 catalogue calls it _classify-and-act_.\nRetry that does not change one of the three is spinning. The implementation rule is operational: every retry should carry a trace block that names what changed. If the trace block is empty, the retry should not have fired. The fix is to bound retries at a small N (the library uses six) and to log the _changed-since-last_ field on every iteration. The day a user complains that _“the same question now costs four times as much”_ , the trace tells you whether the loop was working or spinning.\n## 3. Two scales of loop\nA loop is not one magic mechanism bolted onto the pipeline. Each one is a named, bounded response to a specific way a brick can fall short, and they sort into two scales: small loops that run inside a single brick, and big loops that cross bricks when generation distrusts its own input.\n### 3.1 Small loops: one inside each brick\n**Small loops live inside a single brick.** Document parsing runs the image cascade: filter the page’s images cheaply, classify what each one is, and pay a vision model to describe only the ones worth reading (Article 5). It also rebuilds a missing outline from body typography, looping until the outline stabilises (Article 5octies), and dispatches each page to the parser its signals call for (Article 5nonies). Retrieval walks the table of contents down to the right subsection (Article 7quater). Question parsing asks one clarifying question and learns the default (Article 6bis). Generation retries when the JSON fails the schema, and reruns retrieval and generation when the listing comes back incomplete (Articles 8 and 12).\nEach of these has the same three control surfaces as any loop, a trigger, a termination, and a bounded depth; it just iterates over its own brick’s material rather than reacting to a downstream result. Retry-with-backoff wraps every API call underneath all of them, the one loop that is pure plumbing rather than a brick’s own reasoning.\n![](https://assets.insightmediagroup.io/media/wp-content/uploads/2026/08/image-56-1024x700.png)\n_Small loops sit inside a brick, big loops cross bricks when generation distrusts its input. - Image by author_\n### 3.2 Big loops: across bricks\n**Big loops cross bricks.** Generation reads its own input, decides it was not good enough to answer from, and sends the pipeline back to an earlier brick. The single-document case has three: adaptive parsing (generation’s `context_structured=false` re-parses the offending page with a deeper parser, Articles 10A and 10B), reference resolution (generation reports `pending_references`, the orchestrator resolves the pointer against the parsing tables, re-retrieves the target region, regenerates, Article 11), and scope feedback (generation’s `complete_answer_found=false` widens retrieval, Article 13). Same shape each time: a typed flag on the answer, a bounded trip back upstream, a second generation pass that re-checks itself.\nThe check is a typed flag, the escalation is bounded to the flagged page, and the loop stops the moment the re-checked answer trusts its context.\n![](https://assets.insightmediagroup.io/media/wp-content/uploads/2026/08/image-55-1024x478.png)\n_The adaptive-parsing big loop: verify the parse, escalate the flagged page, regenerate, re-verify. - Image by author_\n### 3.3 The named patterns the series already has\nLooking at the existing V1 code with the 2026 loop-engineering vocabulary on hand: most of the named primitives have a counterpart already in the code, under a less catchy name.\n![](https://assets.insightmediagroup.io/media/wp-content/uploads/2026/08/image-56-1024x700.png)\n_The five primitives the single-document case uses already exist in the series under quieter names. - Image by author_\n**Loop-until-done** : the V1 Article 12 listing completeness loop. The generator answers, the schema carries the `complete_answer_found` flag, the loop reruns retrieval and generation if the flag is false. The pattern came with the listing schema and predates the name.\n**Retry-with-backoff** : the library’s `llm_parse` wrapper. Six attempts with the exponential schedule. The cost analysis in Article 21 Section 4.6 details when this triggers and what the operational impact looks like.\n**Generate-and-filter** : V1 Article 8’s structured-output schema. The LLM returns JSON, the Pydantic validator rejects anything that does not match, the loop retries if validation fails. The 2026 vocabulary makes the loop layer explicit; the discipline has been in the series since the schema-first generation article.\n**Classify-and-act** : V1 Article 13’s composite dispatcher. The parsed question carries a shape (single-value, listing, aggregation, comparison), the dispatcher routes to the matching handler. Anthropic’s classify-and-act applies the same shape to free-form classification rather than to a parsed-question taxonomy; the underlying machinery is the same.\n**Feedback-loop-between-bricks** : V1 Article 13’s between-bricks feedback. Generation flags the retrieval scope as insufficient, the dispatcher widens the scope, generation reruns. The 2026 vocabulary calls this _evaluator-optimiser_ ; the series called it the dispatcher’s feedback loop and built it in 2024.\nWhat is _not_ yet in V1: adversarial verification (a separate agent that tries to refute the answer), the tournament pattern (N candidate answers, judges pick the winner), explicit _loop-until-dry_ termination (K consecutive empty rounds). Those are V4-scope; the single-document case rarely needs them.\n### 3.4 The loops at a glance\nEvery loop in the single-document pipeline, small or big, wears the same three control surfaces from Section 2. Read down the trigger column and the shape is the same each time: the loop fires on one specific, named signal, terminates on a bound, and changes exactly one thing before it retries.\n![](https://assets.insightmediagroup.io/media/wp-content/uploads/2026/08/image-57-1024x635.png)\n_Every loop as one row against the three control surfaces of Section 2. - Image by author_\n## 4. Failure modes the discipline guards against\nThree failure modes are routine in production single-document RAG and each one maps to a specific loop-engineering rule.\n**Infinite-loop on transient failure.** The retry layer has no hard cap. The deployment hits a 429, the loop retries, the next 429 arrives within the rate-limit window, the loop retries again, the API budget burns through the night. _Fix_ : a hard cap on retries (the library uses six), an explicit `RuntimeError` after the cap, and an alert on the trace store when the cap is hit.\n**Spinning on a deterministic failure.** The Pydantic schema rejects the output because the model wrote a field name with a typo. The loop retries with the same prompt. The model produces the same typo. Six retries, six rejections, the user sees a `RuntimeError`. _Fix_ : on schema-fail retry, modify the prompt to include the validator error message verbatim (“ _your previous answer failed validation with:_`_field_name_`_should be_` _field_name_actual_`_. Produce a JSON matching the schema exactly._ ”). The next attempt has new information.\n**Confidence-flag ignored.** The answer comes back with `confidence = 0.3` and the dispatcher returns it to the user without flagging anything. _Fix_ : the dispatcher applies a confidence threshold, low-confidence answers either escalate to a richer retrieval pass (widen scope, switch from keyword to dense) or carry an explicit `low_confidence` warning to the consumer.\n## 5. Loops within loops: the human holds the outer one\nLoop engineering as covered so far is one loop: the dispatcher fires a call, reads the result, decides to retry, terminates when satisfied. Zoom out and there are three loops running at different cadences around the same system, borrowing the frame Andrew Ng laid out in [his “Three Key Loops for Building Great Software” letter](https://www.deeplearning.ai/the-batch/three-key-loops-for-building-great-software):\n  * **The inner loop, seconds to a minute.** The dispatcher itself. Retry-with-backoff on a 502, regenerate on a schema fail, expand the anchor set on `complete_answer_found: false`. No human, bounded budget, code decides. Sections 2 to 4 of this article.\n  * **The dev loop, minutes to hours.** An engineer reads the audit trail, spots a class of failures, edits `concept_keywords_df`, adds a synonym pair, tightens a Pydantic schema, deploys. The dispatcher then runs the corrected version on the next call. Article 6’s expert dictionary and Article 20’s evaluation dashboard are the surfaces this loop reads from.\n  * **The outer loop, hours to weeks.** A user edits an extracted field the system got wrong. The correction lands in the storage layer with a citation, becomes ground truth for the next evaluation pass, feeds into the concept catalog through a review queue. Or a claims handler pushes back on a question template the system was answering literally, and the template gets rephrased for everyone downstream. The refresh-field pattern from Article 19 and the question-template review from Article 20 sit here.\n\n\nNg’s point is that as long as the human knows something the AI does not, that outer loop cannot be closed by more automation, and treating it as noise to be optimised away is the mistake. _“As long as the human knows something the AI does not, human-in-the-loop is needed to inject that knowledge into the system.”_ This is the series’ amplify-the-expert thesis in 2026 vocabulary. The dispatcher owns the inner loop, the engineer owns the dev loop, the expert owns the outer loop, and none of them can substitute for the others.\nThe corollary for design: keep the outer loop cheap for the expert. If correcting a wrong field takes ten clicks, the outer loop breaks and the system’s error rate stops improving. The refresh-field UX, the review queue, and the audit trail keep the outer loop fast enough to compound.\n## 6. Out of scope, with pointers\nThree siblings stay open here.\n  * **The corpus case.** Single-document loops are bounded by the size of one document. Corpus loops add cascade-with-partial-recovery (twelve documents in a batch, three fail, keep the nine successes and let the consumer retry the failures later) and refresh-field on user edit. V1 Article 17quater treats the corpus loop engineering.\n  * **The agentic case.** Single-document loops are bounded in turns (the dispatcher fires at most a handful of retries before terminating). Agentic loops keep the budget open across dozens of turns, with sub-agents, memory tools, and the `/compact` lifecycle. V4 Article 9bis covers context engineering for the agentic loop; V4 Article 9ter covers loop engineering for the same.\n  * **Adversarial verification.** This article focuses on bounded retry and self-flagged completeness checks. The richer discipline of _independent agents whose job is to refute the answer_ is V4-scope. V1 single-document RAG rarely needs it; the V4 article develops it.\n\n\n## 7. Conclusion\nLoop engineering is the third discipline after prompt engineering and context engineering. At single-document scope, it owns four primitives: retry-with-backoff for transient API failures, generate-and-filter for schema-fail retries, loop-until-done for completeness checks, classify-and-act for dispatcher branching. The single rule that separates a useful loop from a spinning one is _every retry changes something_. The library this series provides implements the first primitive directly; the other three are in the dispatcher (Article 13) and the listing schema (Article 12). The 2026 vocabulary puts a name on a discipline the series has been practising since 2024.\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/)[Rag](https://towardsdatascience.com/tag/rag/)[Rag Architecture](https://towardsdatascience.com/tag/rag-architecture/)[loop engineering](https://towardsdatascience.com/tag/loop-engineering/)\nShare This Article\n  * [](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Ftowardsdatascience.com%2Floop-engineering-for-rag-the-small-loops-inside-each-step-the-big-loops-across-the-pipeline%2F&title=Loop%20Engineering%20for%20RAG%3A%20The%20Small%20Loops%20Inside%20Each%20Step%2C%20the%20Big%20Loops%20Across%20the%20Pipeline)\n  * [](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Ftowardsdatascience.com%2Floop-engineering-for-rag-the-small-loops-inside-each-step-the-big-loops-across-the-pipeline%2F&title=Loop%20Engineering%20for%20RAG%3A%20The%20Small%20Loops%20Inside%20Each%20Step%2C%20the%20Big%20Loops%20Across%20the%20Pipeline)\n  * [](https://x.com/share?url=https%3A%2F%2Ftowardsdatascience.com%2Floop-engineering-for-rag-the-small-loops-inside-each-step-the-big-loops-across-the-pipeline%2F&text=Loop%20Engineering%20for%20RAG%3A%20The%20Small%20Loops%20Inside%20Each%20Step%2C%20the%20Big%20Loops%20Across%20the%20Pipeline)\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/2023/07/0A_aoDRKoNYn9sC4N-scaled.jpg)](https://towardsdatascience.com/code-understanding-on-your-own-hardware-dd38c4f266d6/)\n## [Code understanding on your own hardware](https://towardsdatascience.com/code-understanding-on-your-own-hardware-dd38c4f266d6/)\n[](https://towardsdatascience.com/loop-engineering-for-rag-the-small-loops-inside-each-step-the-big-loops-across-the-pipeline/)\nSetting up an LLM to talk about your code - with LangChain and local hardware\n[Dorian Drost](https://towardsdatascience.com/author/doriandrost/)\nJuly 5, 20237 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/12/image-146.jpg)](https://towardsdatascience.com/how-to-increase-coding-iteration-speed/)\n## [How to Increase Coding Iteration Speed](https://towardsdatascience.com/how-to-increase-coding-iteration-speed/)\n[LLM Applications](https://towardsdatascience.com/category/artificial-intelligence/llm-applications/)\nLearn how to become a more efficient programmer with local testing\n[Eivind Kjosbakken](https://towardsdatascience.com/author/oieivind/)\nDecember 13, 20257 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2026/05/Prompt-Engineering-Failed-in-Production.jpg)](https://towardsdatascience.com/prompt-engineering-isnt-enough-i-built-a-control-layer-that-works-in-production/)\n## [Prompt Engineering Isn’t Enough — I Built a Control Layer That Works in Production](https://towardsdatascience.com/prompt-engineering-isnt-enough-i-built-a-control-layer-that-works-in-production/)\n[Large Language Model](https://towardsdatascience.com/category/large-language-model/)\nMost production LLM integrations treat prompts like the final layer. That gets you 0% reliability on structured output. I built an 8-component system that brought it to 100% — without changing a single prompt.\n[Emmimal P Alexander](https://towardsdatascience.com/author/emmimalp.alexander/)\nMay 21, 202620 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2026/06/LLM-Rate-Limit.jpg)](https://towardsdatascience.com/llm-fallbacks-break-agent-pipelines-i-built-the-missing-recovery-layer/)\n## [LLM Fallbacks Break Agent Pipelines — I Built the Missing Recovery Layer](https://towardsdatascience.com/llm-fallbacks-break-agent-pipelines-i-built-the-missing-recovery-layer/)\n[Large Language Model](https://towardsdatascience.com/category/large-language-model/)\nStandard model swapping forces your production AI to accept corrupted data. Here is the zero-dependency, state-aware recovery layer I built to preserve schema integrity during failovers.\n[Emmimal P Alexander](https://towardsdatascience.com/author/emmimalp.alexander/)\nJune 16, 202614 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2026/07/compare_wax_seal_5425649_v3_card.jpg)](https://towardsdatascience.com/stop-returning-text-from-rag-the-typed-answer-contract-that-prevents-hallucination/)\n## [Stop Returning Text from RAG: The Typed Answer Contract That Prevents Hallucination](https://towardsdatascience.com/stop-returning-text-from-rag-the-typed-answer-contract-that-prevents-hallucination/)\n[Large Language Model](https://towardsdatascience.com/category/large-language-model/)\nEnterprise Document Intelligence [Vol.1 #8A] - The schema is the contract: every field is a question the pipeline asks the model, and every answer is checkable\n[Kezhan Shi](https://towardsdatascience.com/author/kezhanshi/)\nJuly 4, 202626 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2024/11/169AP1WpV7NfZZi8ptYeiGA.png)](https://towardsdatascience.com/building-a-knowledge-graph-from-scratch-using-llms-f6f677a17f07/)\n## [Building a Knowledge Graph From Scratch Using LLMs](https://towardsdatascience.com/building-a-knowledge-graph-from-scratch-using-llms-f6f677a17f07/)\n[Artificial Intelligence](https://towardsdatascience.com/category/artificial-intelligence/)\nTurn your Pandas Data Frame into a Knowledge Graph using LLMs. Build your own LLM graph-builder and QA your KG.\n[Cristian Leo](https://towardsdatascience.com/author/cristianleo120/)\nNovember 25, 202439 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/06/headway-5QgIuuBxKwM-unsplash-scaled-1.jpg)](https://towardsdatascience.com/llms-pandas-how-i-use-generative-ai-to-generate-pandas-dataframe-summaries-2/)\n## [LLMs + Pandas: How I Use Generative AI to Generate Pandas DataFrame Summaries](https://towardsdatascience.com/llms-pandas-how-i-use-generative-ai-to-generate-pandas-dataframe-summaries-2/)\n[Large Language Models](https://towardsdatascience.com/category/artificial-intelligence/large-language-models/)\nLocal Large Language Models can convert massive DataFrames to presentable Markdown reports — here's how.\n[Dario Radečić](https://towardsdatascience.com/author/radecicdario/)\nJune 2, 202510 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2026/07/pegboard_tools_909256_card.jpg)](https://towardsdatascience.com/one-rag-pipeline-four-very-different-pdfs-same-four-bricks-every-answer-typed-and-cited/)\n## [A Production RAG Pipeline in Action: Every Answer Typed and Cited](https://towardsdatascience.com/one-rag-pipeline-four-very-different-pdfs-same-four-bricks-every-answer-typed-and-cited/)\n[Large Language Model](https://towardsdatascience.com/category/large-language-model/)\nEnterprise Document Intelligence [Vol.1 #9B] - One call wires the four upgraded bricks together, run on a paper, a NIST standard, and a report with a broken TOC\n[Angela and Kezhan Shi](https://towardsdatascience.com/author/angela.shi/)\nJuly 17, 202625 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2026/08/toll_plaza_aerial_34282750_card.jpg)](https://towardsdatascience.com/cut-an-enterprise-rag-pipelines-latency-and-cost-by-calling-the-llm-less-not-by-buying-a-faster-model/)\n## [Cut an Enterprise RAG Pipeline’s Latency and Cost by Calling the LLM Less, Not by Buying a Faster Model](https://towardsdatascience.com/cut-an-enterprise-rag-pipelines-latency-and-cost-by-calling-the-llm-less-not-by-buying-a-faster-model/)\n[Large Language Model](https://towardsdatascience.com/category/large-language-model/)\nEnterprise Document Intelligence [Vol.1 #9ter] - The pipeline from Article 9 calls a model at several steps to be sure it is right. On easy questions that is needless latency. A per-question signal routes them past the model, about two seconds saved for a keyword match.\n[Angela and Kezhan Shi](https://towardsdatascience.com/author/angela.shi/)\nAugust 13, 202611 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/ff566a69f452/pages/",
  "body_markdown_path": "attachments/ff566a69f452/pages/archived-body.md",
  "local_html": "attachments/ff566a69f452/pages/archived-body.html",
  "body_source": "prior_capture"
}