This is part of a series I’m creating with Angela Shi. This article highlights the common issues we’ve both encountered when working with real-world RAG systems. These recurring challenges were exactly why we developed the four-brick framework.
Let me be honest with you. While writing this series, we’ve been pasting entire documents into ChatGPT ourselves. Upload one PDF, ask one question, send it over, read the response. The results are solid, the vendor covers the token costs, and for a quick, one-time query, this approach makes sense.
But this series was written for situations that are nothing like that. In enterprise environments, questions rarely revolve around a single document. A claims handler might need the same query run through every policy in an entire broker archive. A compliance team may have to scan every contract across a full portfolio. At this scale, copying everything into ChatGPT breaks down — and the costs skyrocket almost immediately.
What I’m walking you through here is a rundown of the mistakes we see over again, organized by each brick. The actual solutions are covered in Part II.
1. Parsing: how a document loses its structure
Parsing breaks down when a team treats the document as plain text rather than as a structured object. Three recurring patterns pop up: throwing away tables and layout (Pitfall 1), cramming the entire document into the prompt (Pitfall 2), and splitting content into fixed-size chunks that ignore the document’s logical organization (Pitfall 3). The solution is a structural parser that outputs typed tables instead of raw strings or randomly-created fragments.
A broader version of these three issues echoes throughout the rest of this article. Teams skip verifying how well their parser works and pour hours into tweaking chunk sizes, reranker thresholds, top-K settings, and embedding selections — all while the precision they want stays out of reach. Every dial they’re adjusting sits on the parser’s output: if a parser flattened a crucial table on page 47, no chunker can reconstruct it; if a parser dropped key column headers, no reranker can resolve the confusion. The research literature doesn’t help here. The most widely-cited vendor guide on RAG techniques devotes 194 pages to chunking strategy and zero pages to parsing. Fix your parsing first. Retrieval tuning only makes sense once your parser already preserves the document’s structure.
1.1 Pitfall 1: The PDF contained a table. The parser only returned text.
The instinct most teams start with is pulling the PDF out as one big block of text and trusting the LLM to figure it out. Today’s parsers encourage this habit: one function call, one string returned, done.
The cost becomes apparent the moment a table shows up with grouped row labels. Imagine a claims contract containing a benefits table where identical row names (Premium, Deductible) appear under two different categories (Health, Dental). Once flattened into text, those category labels vanish into the token stream, and the model sees Plan A Plan B Plan C Health Premium 100 200 300 Deductible 5 10 15 Dental Premium 50 80 120 Deductible 2 4 6. Ask “what is the Premium for Plan B?” and two answers are technically valid: 200 (Health) or 80 (Dental). The flat string contains no grouping marker. The model picks one. It guesses wrong part of the time, and there’s no signal telling you it guessed wrong.

The same type of breakdown happens with multi-column layouts (a contract page with a footnote sidebar), headers and footers (page numbers contaminating every retrieved result), and reading order on scanned documents scanned. Each of these is a distinct failure pattern, but they all share one underlying cause: the parser discarded the structure the document originally had.
The fix is a relational parser that generates typed tables (line_df, page_df, toc_df, and so on) instead of a single flat string. Each line retains its bounding box, its page reference, its font, and its section. Tables live as their own distinct layer. Every component downstream reads from structured data, not from blobs.
1.2 Pitfall 2: Paying for 1,200 pages on every single question
A second parsing mistake is one Angela and I both make on smaller projects: skipping parsing entirely and shoving the whole PDF into the chat window. It’s quick to set up, it works fine for a single document, and the vendor picks up the token bill on the free tier.
On a real corpus, that same shortcut becomes expensive in three stages. First, the PDF is no longer 12 pages — it’s 1,200. Second, the question is no longer one-off — it’s 200 queries per day. Third, the team adds five more documents to the chat to “give the model more context,” and the token count per question climbs linearly. The bill jumps from cents to thousands per month, and the answers actually get worse because the model is searching through a bigger haystack for the same needle.
The fix is matching the technique to the document and the question: when the answer fits on three pages, send three pages, not 1,200. The same principle applies here: parse once, scope the retrieval, and generate from the smallest context that contains the answer.
Here’s what the bill looks like on a realistic enterprise scenario: a 1,200-page reinsurance contract that a compliance team queries 200 times a day. Two approaches, same question. The first extracts every line of text from the PDF and stuffs the result into every prompt. The second is the pipeline this series builds: parsing produces structured tables, retrieval returns the three relevant pages, and generation reads only those.

On a 1,200-page contract, the dump approach costs roughly four hundred times the input cost of the scoped pipeline. The dump grows with the document size and the number of questions; the pipeline grows only with the answer. Per contract, per year, at two hundred questions a day, that’s the difference between burning $131,000 and burning $329.
Prompt caching shifts the math without reversing it. Anthropic’s 90% discount on cache reads and OpenAI’s 50% discount on cached input both apply only on cache hits, expire on a TTL the team doesn’t control, and charge full price on a miss. Even at the 90% rate, the dump still costs $13,140 a year on the same workload — forty times the scoped pipeline — and it still grows with document size, not with the answer.
A hosted RAG solution (OpenAI’s file_search, AWS Knowledge Bases, and similar offerings) sits between the two: cheaper than the dump because the vendor chunks the document and retrieves what looks relevant, but more opaque than the pipeline because the chunking, the embedding model, and the ranking logic are not yours to inspect. It’s the convenient middle ground for prototyping with a single document. It’s rarely the right answer at enterprise scale, where auditability and reproducibility matter just as much as the bill.
For one contract, the absolute
1. The Hidden Cost of a Single Digit
A single percentage point in a contract clause can mean the difference between a manageable expense and a financial disaster. When you’re dealing with ten thousand contracts in a corporate portfolio, that same tiny ratio determines whether your annual costs remain in the tens of thousands or skyrocket into the millions.
1.3 Pitfall 3: Tuning chunk_size. The PDF had structure.
The third common parsing error is treating a PDF like a plain text file. Teams often use tools like pdfplumber, pypdf, or PyMuPDF, run extract_text or get_text, feed the output into a RecursiveCharacterTextSplitter, and then spend weeks adjusting chunk_size and chunk_overlap to marginally improve retrieval accuracy. But the PDF already contained valuable structure, and the convenient API stripped it away. The parameter they’re fine-tuning is downstream of where the real damage occurred.
A 200-page contract typically includes a clickable bookmark table of contents (the outline visible in the sidebar), section headings in varying font sizes (24pt for parts, 18pt for sections, 14pt for sub-sections, the same visual cues readers use to navigate), and tables stored as cell-level bounding boxes that a structural parser can reconstruct. All of this structure exists within the PDF’s object model. The extract_text() function ignores it entirely, delivering a uniform text stream to the splitter. The splitter then divides the text at chunk_size=500 because that’s the only control the team has, even though the PDF’s own layout could have provided natural breakpoints for free.

The impact is on accuracy. A chunk that breaks mid-table contains only half a row. A chunk that begins mid-section lacks any heading to provide context for the answer. The LLM’s response, while technically based on the source material, relies on truncated fragments rather than coherent units. Retrieval has no way to prioritize, since all chunks appear similar. Generation can’t produce clean citations, since references point to arbitrary segments. The audit trail displays entries like “chunk 1142 of 10,000” with no meaningful context.
Markdown-aware and section-aware splitters address the symptom but not the root cause. They attempt to identify headings from the flat text, but they can’t recover the bounding boxes, font hierarchy, or table structure that extract_text() already discarded. The chunker is working with degraded input.
The solution is the structural parser referenced throughout this article. It preserves the PDF’s typography (line_df includes bounding box, font, page, and section path), maintains the table structure (the table extractor outputs typed cells, not plain strings), and retains the TOC (toc_df derived from PDF bookmarks combined with font-size analysis). Downstream components leverage this structure. No 500-character segment ever crosses a section boundary, because segments don’t exist. There is structure.
2. Question parsing: how you ignore the user
Question parsing fails when teams treat the user’s natural-language input as a simple search query. Two recurring mistakes stand out: feeding the raw string directly into retrieval (Pitfall 4), and limiting analysis to keyword extraction when the question also contains answer format, scope, and structural requirements (Pitfall 5). The solution is a typed ParsedQuestion object that captures all these elements.
2.1 Pitfall 4: “Just embed the question.”
The simplest approach in any RAG system is to take the user’s input, generate an embedding, and pass it to retrieval. Type the question, call the API, done. But the question contains multiple dimensions: scope, expected answer format, structural constraints, sometimes conditions, sometimes negations, sometimes references to prior conversation, and often implicit assumptions about which document the user means. The embedding compresses all of this into a single vector that primarily captures the key terms. Downstream components work with whatever survived this compression, which often misses the user’s actual intent.
Real-world questions come in many forms. Here are examples from production systems, with explanations of why each one breaks naive embedding:
- A concise, structured request. “Cancellation period plan B in days.” Five words, three constraints: a scope filter (plan B), an expected answer type (a duration), and a format requirement (in days). The embedding merges all three into a single vector for retrieval to match against the corpus.
- A negation. “What is NOT covered by this policy?” The embedding barely registers NOT as a function word, so retrieval returns chunks most similar to the rest of the sentence, which describe what IS covered. Generation then paraphrases the opposite of what was asked.
- Combined conditions and question. “For plan B, assuming a one-year contract, what is the cancellation period if I cancel after six months?” The conditions define retrieval scope, while the question shapes generation framing. The embedding blends them together; the wrong component processes the wrong element.
- A multi-part comparison. “Exclusions or deductible, which one matters more?” Retrieval returns chunks about both topics; generation receives no indication that the user wants a comparison rather than a list.
- An abbreviated reference. “And what about Plan C?” Five words, no context. The embedding has nothing to anchor to.
The variations are endless. Each new dataset, audience, and product introduces its own question patterns. Some are brief, some are detailed, some include operators, some depend on prior context. The question parser is the component that handles this diversity so downstream elements receive a structured object they can act on. Without it, every new pattern becomes a new silent failure.
The underlying issue is the same in every case. The question contains structure (constraints, operators, scope, intent, references). The embedding flattens that structure into a single vector. Retrieval works with what remains, generation processes what retrieval returns, and the user receives a response that may or may not address their actual question.

The result is contradictions the pipeline can’t catch. The retrieved chunks appear relevant. The model produces a fluent response. The user reads a confident answer about coverage when they asked about exclusions, with no alert, no warning, no indication that the question’s operator was lost along the way.
A common workaround is to insert a small LLM call that returns a JSON object with intent, scope, and keywords. This addresses the no structure issue but not the no contract issue. The dictionary keys vary between prompts (one callHere is the paraphrased version of the HTML content:
The first call returns scope, the next returns scope_filter. Retrieval reads one key, generation reads the other, and the silent miss reaches the user. A typed ParsedQuestion Pydantic schema turns the drift into a parse-time error that the audit log catches. The real win isn’t the JSON itself—it’s the validation.
It also blocks every downstream improvement. You can’t route a question to a specialized pipeline if you don’t know what type of question it is. You can’t ask the user to confirm an ambiguous term if you haven’t flagged the ambiguity. You can’t break down a multi-part question if you haven’t recognized it contains multiple parts.
The solution is a typed ParsedQuestion object: the question parsing component converts the raw string into a structured object containing keywords, answer shape, scope filters, and an execution plan. The string is the input; everything downstream works with the typed object.
2.2 Pitfall 5: “Just use HyDE.” Or trust the embedding.
The second question-parsing mistake is assuming the component doesn’t need to exist. The user types “what’s the cancellation period for plan B?”. The pipeline sends the string to an embedding model, retrieves the top-K chunks by cosine similarity, and passes them to generation. There’s no question parser. Modern developers are skeptical of hand-crafted keyword extraction (and rightly so: brittle lists, drift, language-specific edge cases) and instead rely on the embedding, which seems to capture the question’s meaning for free.

Embeddings capture something. They produce a dense vector close to passages that resemble the question. But they don’t produce the answer shape, the scope, the format constraint, or the implicit “in this document” clause. Those elements carry no embedding signal until the pipeline records them somewhere in a typed form.
A common workaround the field turns to at this point is HyDE (Hypothetical Document Embeddings): the LLM generates a hypothetical answer to the question, the pipeline embeds that hypothetical answer, and retrieval scores corpus chunks against it instead of against the question. It works on benchmarks, and developers adopt it as the clever escape from the embedding-only trap. The reason it works is rarely stated plainly: the hypothetical answer contains the keywords a real answer would contain, and those latent keywords are what the embedding picks up. HyDE is essentially LLM-driven keyword extraction in disguise—one extra generation per query, no expert validation, no audit trail. When it underperforms, the instinct is to switch to a stronger model. The deterministic version of the same insight is to ask the domain expert for the concept vocabulary and store it once. In enterprise, the answer worth shipping is the one a domain expert would validate, not the one a more capable model happens to imagine.
The format constraint “in days” is the clearest example. Encoded into the question vector, the “days” signal biases the top-k toward chunks about “response time within 30 days” or “Day 1 of the policy”, both pure noise for a question about cancellation. The constraint belongs in the generation brief, not in the retrieval query. Pipelines that skip question parsing send the same encoded vector to retrieval and the same raw string to generation, and the wrong component consumes the wrong field.
The fix isn’t a smarter embedding. The fix is a question parser that produces a typed object with answer shape, scope, format, and decomposition as separate fields, each routed to the component that uses it. The keyword case becomes one field of that object, validated against an expert dictionary so the term premium maps to prime, cotisation, price without the developer maintaining the list by hand. Article 6 develops the parser and the two typed briefs that come out the other side—one for retrieval and one for generation.
3. Retrieval: the vector DB reflex and its blind spots
Retrieval fails when “just embed it and rank by cosine” becomes the only tool available. Three habits cause this: treating RAG as a synonym for vector DB (Pitfall 6), treating the chunk as the only granularity when the answer is a single line within a larger passage (Pitfall 7), and stopping at references to other parts of the document (Pitfall 8). The fixes are hybrid retrieval, two granularities returned together, and a reference-resolution loop.
3.1 Pitfall 6: “Just use a vector DB”
This is the biggest mistake we see, and the most expensive to undo because it dictates the entire infrastructure stack. The pattern is fixed: chunk the corpus, embed every chunk, embed the question, return the top-k chunks by cosine similarity. Done.

The cost shows up on every question where a keyword would have helped more than a vector. Acronyms (“RC” in insurance, “SCR” in solvency), product codes, numeric ranges, rare names, legal references like Section 4.2(a)(iii). Embeddings flatten these into a dense vector and lose the discreteness. The retrieval component returns a passage about something similar instead of the passage that contains the term.
More broadly, embeddings work when the question is paraphrased prose matched against paraphrased prose. They struggle when the question is a token: a code, a number, a regex-shaped pattern, a precise reference. A small anecdote that stuck with me: a few months ago I used a chat assistant inside a copywriting tool to find a specific phrase in a long document I had pasted in. At some point the assistant tried to find the phrase with a regular expression. The regex came back empty. I went looking: the original PDF had a typographic character (a curly quote, I think) that my copy-paste had replaced with a straight quote. The model had been right to reach for a regex. The token match was the fundamental operation. The pipeline around it just couldn’t handle a one-character difference.
Anthropic’s tooling pushes this further: when an agent needs to find a span, it reaches for grep-like primitives before it reaches for an embedding. That’s the direction the field is moving in, slowly, because conversations are made of words, and words match best on tokens, not on vectors.
The deeper issue is cultural. The name Retrieval Augmented Generation says nothing about vectors. It says retrieval, which is a fifty-year-old field with many techniques. Yet when we talk to developers building RAG systems, almost every conversation goes the same way: “yes, we use a vector database for retrieval.” It’s treated as the default, not as one choice among many.
Angela and I even argued
about coining a different name for what we build. ROG, for Retrieval Only Generation, because in enterprise the retrieval is the work and the generation is the wrapper around it. The historical RAG definition pointed the other way: a parametric model generates, retrieval augments it. We kept “RAG” in the end because that is how the work is searched and known, and we did not want to invent another acronym just to make a point. But it is worth saying plainly: there is no pure vector search anywhere in this series. Embeddings appear, but as a fallback.
The fix is hybrid retrieval by default: keyword detectors (exact, free, deterministic) running in parallel with embedding detectors, with an LLM arbiter at the end that ranks the aggregated candidates with reasons. The popular shortcut “RAG equals vector DB” is the single biggest source of expensive failures we have seen at scale.
3.2 Pitfall 7: The chunk is right. The pipeline stopped there.
The second retrieval mistake is subtler and shows up only when you try to ground an answer in the source. The pipeline retrieves a chunk, hands it to the LLM, the LLM returns an answer. Where in the chunk was the answer? Nobody asks, because the chunk was the unit.

This breaks every downstream feature that depends on knowing where the answer is. Highlighting on the source PDF. Citations with line numbers. A compliance trail. The system can say “the cancellation period is 30 days” but cannot point to the line it read.
The instinct is to recover the location after the fact, by string-matching the LLM’s quote against the source. It fails the moment the model paraphrases (which it does whenever the quote runs more than a few tokens) and the citation points at a near-miss line. The location has to be computed on the way in, not retrofitted from the output.
The chunk is also the wrong unit on the other side of the fix: the amount of surrounding text the LLM needs depends on the question. Take “what is the date of the events?” on a 200-page incident report. The keyword date of events hits one line; that line carries the date. Two lines around it are enough to ground the answer. Returning the chunk that contains the line, let alone the whole chapter, buries the date in noise the model has to wade through. A question about the contract’s cancellation policy asks for a different size: a paragraph or two, because the policy is built from several conditions that interact. Same chunker, same document, different right answer for how much surrounding text to keep.
The fix is not a better chunker. The fix is to retrieve at two granularities at once: one precise enough to highlight on the source (the line where the keyword hit), one sized to what the question needs (two lines for a date, a paragraph for a policy). Article 7 builds the retrieval brick around this split, and gives the two scopes the names Angela and I argued about for weeks before settling on.
3.3 Pitfall 8: “See Section 4.2” and never look
The third retrieval mistake shows up the first time the document refers to itself. The retrieved chunk reads “the exclusions are listed in Section 4.2”, and the pipeline stops. Retrieval found the chunk that mentions the exclusions; it did not follow the pointer. Generation gets the chunk, sees the reference, and has two equally bad options: invent the contents of Section 4.2 from its pre-training priors, or refuse with “the document does not specify”. The document does specify. The pipeline just did not look.

The cost is a silent breach of the audit chain. The user is told the system grounds in the corpus, but when references are unresolved the answer is reasoning from priors. That is exactly what the four-brick contract was meant to prevent. Worse, this failure is uncatchable from the outside: the answer reads fluent either way, and the cited Span covers the chunk that mentioned Section 4.2, not the section itself. A reviewer who clicks the citation hits a sentence that says “see Section 4.2” and a confident answer next to it. The chain of evidence stops one hop short.
Agentic RAG handles this the agentic way: the LLM calls a fetch_section tool when it sees a reference. It works, at a cost the team often does not see. Every reference resolution becomes a non-deterministic loop, the audit trail forks per agent step, the per-question cost grows with the depth of the reference chain.
The deterministic alternative is a two-pass loop with a typed trigger. The first pass produces a structured answer that flags the pending reference instead of fabricating around it. The orchestrator follows the reference to the cited section, runs retrieval on the right pages, and the second pass comes back grounded on Section 4.2 itself. Article 11 develops the trigger field on the answer schema, the resolver that maps a reference to the right pages, and the orchestrator pass that wires them together.
4. Generation: where the audit chain dies
Generation fails when the brick is treated as the API call that returns a string. Two patterns repeat: shipping the raw LLM string with no flag, no schema, no audit (Pitfall 9), and trusting the LLM’s “not found” claim without an external proof of absence (Pitfall 10). The fix is a typed answer wired to programmatic checks the model has no access to.
4.1 Pitfall 9: No flag, no schema, no audit. Just text.
The retrieved passage goes into a prompt, the LLM returns a string, the system passes the string to the user. Production RAGs ship like this every day. The brick is the API call.

The cost is that you have no signal the answer is reliable. The model returns a fluent sentence whether the passage contained the answer or not. There is no answer_found flag, no quote of the supporting span. When the model invents a number, the system has no signal to catch it before it reaches the user.
Structured outputs (OpenAI’s response_format, Anthropic’s tool use, Pydantic AI) close part of this. A typed response with answer_found and quote fields says what the model thinks it grounded on. What they do not close is the model rating itself. “confidence”: 0.95
The same conviction applies whether the quote is genuine or fabricated. The same brick that reads the passage also evaluates the response.
The escalation involves programmatic verification that the model cannot access. A regex check confirms the cited quote appears exactly as written in the referenced span. A set-coverage check validates enumeration answers (the question requests four exclusions, the schema provides four, each item maps to a distinct passage segment). A type check validates the answer format (the answer should be a Duration, but the model returned “around a month”). Each check produces a verdict that the dispatcher acts upon. The model populates the schema; the verifier determines whether to proceed.
A second cost emerges from this: downstream tools cannot respond to the model’s state. The dispatcher cannot initiate a refetch because it was never informed the retrieval was incomplete. The audit log cannot reconstruct the reasoning because the raw text lacks provenance. The pipeline becomes a single attempt: either the answer is correct, or you restart the entire process.
The solution is the typed answer schema combined with the verifier that completes the cycle. Article 8 develops the schema, the dispatcher that selects the appropriate structure per answer type, and the verifier that completes the cycle.
4.2 Pitfall 10: “Not in the chunks” is not “not in the corpus.”
The second-generation mistake is trusting the LLM when it claims “not found.” Retrieval is seldom empty: with embeddings, cosine top-k always produces results, so the LLM receives several chunks and determines whether the answer exists within them. When it says no, the pipeline delivers answer_found=False to the user. The system has just assigned verification responsibility to the same brick that read the chunks.

The LLM’s “not found” means “not in these chunks.” It does not mean “not in this corpus.” The model examined the top-k passages, not the document or the remainder of the archive. Two failure modes lurk behind a confident denial: the answer existed in the top-k and the model overlooked it (LLM error), or the answer was elsewhere in the corpus and retrieval failed to locate it (retrieval miss). The user reads “the document does not specify” and assumes the corpus was thoroughly examined. It was not.
The solution is to support the “not found” with a deterministic absence proof. The expert keyword dictionary, containing every term and curated synonym for the question’s concept, performs a literal substring search across the entire corpus, not just the retrieved chunks. Zero matches means the system can state “not in this corpus” with a defensible audit trail. At least one match while the LLM still said no indicates retrieval failed, and the orchestrator initiates a second pass on the pages where keywords appeared. Keywords prove absence; embeddings cannot. The solution uses bricks the article has already identified: the expert dictionary from Pitfall 5 and the keyword retrieval from Pitfall 6.
5. What you should expect from Part II
Each of the ten mistakes above represents a structural decision the team made early, before establishing a contract that defined the brick. The contracts that prevent these failures are developed throughout the series: a relational parser that preserves the document’s structure, a typed question that carries every constraint downstream, hybrid retrieval at two granularities, a reference-resolution loop, and a typed answer connected to programmatic checks the model cannot access. Each one is the brick that the four-brick architecture demanded.
The same vector-reflex problem manifests differently in agentic systems. When an agent must select a tool from a catalog of hundreds, the default reflex is again to embed the tool descriptions and rank by similarity. The outcome is identical: imprecise on codes, unable to distinguish between “reads” and “writes”, and opaque to audit. The solution follows the same pattern: words first, embeddings as fallback, audit on every selection.
If you find yourself nodding along because your own pipeline commits most of these errors, that is the most valuable kind of recognition. The solutions are developed in the articles that follow.
6. Sources and further reading
Other articles in the series:
External references:
- Gao et al., Precise Zero-Shot Dense Retrieval without Relevance Labels, ACL 2023. The original HyDE paper. Pitfall 5 explains why the technique works (the LLM-generated hypothetical contains the keywords a real answer would) and argues the deterministic equivalent is the expert dictionary.
- Anthropic, Introducing Contextual Retrieval, September 2024. The LLM-generated context-prepending approach to chunking, adjacent to Pitfall 3. The series solves the decontextualization problem with structured metadata rather than LLM-generated blurbs.
- Anthropic, Prompt caching with Claude. The cost lever that tilts Pitfall 2’s math at the 90%-off cache-read rate without flipping it.
- Pinecone Learn, Chunking Strategies for LLM Applications. The field’s reference chunking survey with the precision-vs-richness matrix Pitfall 3 argues sits inside a frame that
extract_text()already corrupted. - LlamaIndex, Building Performant RAG Applications for Production. Names the decoupled chunks for retrieval vs synthesis pattern, the same insight Pitfall 7 frames as anchor vs context.
- Liu, Instructor: Structured outputs for LLMs. The Pydantic-typed-output library and the schema-as-contract argument. Direct support for the Pydantic-vs-dict pushback in Pitfall 4 and the typed answer in Pitfall 9.
- Zaharia, Khattab et al., The Shift from Models to Compound AI Systems, BAIR 2024. The academic frame for the four-bricks architecture this article assumes.



