TechByteByByte

Multimodal RAG

Closing Level 7: extending retrieval beyond generic text to PDFs, web content, and code repositories — each with really distinct handling requirements, unified through Module 5's loader pattern.

#RAG#AI#Multimodal RAG#Code RAG#Level 7

Begin with the problem

Useful evidence may live in images, scanned pages, diagrams, audio, or code—not only paragraphs. Multimodal RAG must preserve the meaning and location of each modality.

question → choose specialized retrieval path → collect multimodal/structured evidence → answer

What you will learn

  • Explain Multimodal RAG in simple language before using its technical details.
  • Follow the mechanism step by step through a small RAG example.
  • Connect this topic to the modules before and after it.
  • Decide when to use it, when not to use it, and what to measure in production.

Current real-system grounding: Google’s current File Search documentation includes file-based grounding and multimodal retrieval capabilities, with model, file-type, and tool-combination limitations.

The product example proves that the pattern is used in a real system. It does not mean every provider uses the same hidden algorithm, defaults, limits, or pricing.

1. The problem this module solves

Level 7 has covered advanced retrieval reasoning (Modules 28-29) and alternative data sources (Module 30). This module closes the level by addressing really different content types — PDFs, web pages, and code — each with real, source-specific handling needs, unified through the same loader pattern Module 5 established.


2. PDF RAG — The Complete, End-to-End Pipeline

This module brings together nearly everything covered so far into one complete, worked pipeline for the single most common real-world RAG source:

PDF

PARSING (Module 6) -- text/table/image extraction, structure
                     preservation, handling multi-column layouts and
                     OCR for scanned pages

CHUNKING (Modules 7-9) -- ideally structure-aware, respecting
                         headings and sections

EMBEDDING (Module 10)

VECTOR STORE (Module 12)

RETRIEVAL (Modules 13-18)

LLM (Module 22)

Answer + Citation (Module 23, referencing the ORIGINAL PDF's page
                   number -- Module 5's metadata, carried all the
                   way through)

Every single stage in this diagram is a module you’ve already completed — PDF RAG isn’t a new technique, it’s this course’s entire pipeline, applied to the single most common real document format.


3. Web RAG — Real, Distinct Considerations

Web pages

CRAWLING / LOADING (Module 5's loader pattern, web-specific)

CLEANING (stripping navigation menus, ads, footers -- Module 6's
         boilerplate-removal principle, applied to HTML specifically)

CHUNKING -> EMBEDDING -> INDEX (unchanged from standard RAG)
Really DISTINCT considerations for web content:

- FRESHNESS: web content changes MORE frequently than most internal
  documents -- Module 26's reindexing strategy matters even more
  here
- DUPLICATE CONTENT: the same information often appears on MULTIPLE
  pages (mirrors, syndicated content) -- real deduplication (Module
  21) is important
- SOURCE TRUST: not all web sources are EQUALLY reliable -- metadata
  (Module 9) should really capture source credibility signals
  where possible

4. Code RAG — A Really Different Chunking Unit

User: "Where is authentication handled in our codebase?"
Repository

CODE PARSING (really different from Module 6's document parsing --
             understanding functions, classes, imports)

SYMBOL-AWARE CHUNKING -- chunk by FUNCTION or CLASS, not by
                        arbitrary character count (directly extending
                        Module 8's structure-aware chunking
                        principle: code has its OWN natural
                        structural units)

EMBEDDING -> RETRIEVAL -> LLM -> Answer

The real insight: code has its own natural “paragraphs” — functions and classes — exactly analogous to Module 8’s heading-based structure-aware chunking, but for a really different kind of document. Splitting a function’s definition from its implementation (arbitrary character-based chunking) would be exactly as destructive as splitting Module 8’s heading from its content.


5. A Real Developer Example — Unifying All Three Under One Loader

Pattern

TechCorp builds ONE internal search tool covering THREE really
different source types, all through Module 5's UNIFORM loader
pattern:

load_pdf("travel_policy.pdf", ...)          -> LoadedSource(type="pdf",
                                              metadata={page_count,
                                              filename})

load_web_page("wiki.../oncall", ...)            -> LoadedSource(
                                                 type="web",
                                                 metadata={url,
                                                 crawl_date})

load_code_file("auth/login.py", ...)                -> LoadedSource(
                                                     type="code",
                                                     metadata={
                                                     filepath,
                                                     language})

ALL THREE produce the SAME uniform structure (Module 5's Section 3
principle) -- downstream chunking, embedding, and retrieval code
doesn't need to know or care which original SOURCE TYPE a chunk
came from, even though each loader handled really
SOURCE-SPECIFIC extraction challenges internally.

6. A Simple Agentic AI Connection

An agent with access to PDF search, web search, and code search tools simultaneously can really choose the right source type based on a question’s nature — recognizing that “what does our policy say” points toward PDF/document search, while “where is this function defined” points toward code search, directly extending Module 30’s structured-vs-unstructured routing to this module’s multiple CONTENT-TYPE routing.


7. How Is This Used in AI?

🤖 How Is This Used in AI?

Production RAG systems really integrate multiple content types under one unified retrieval interface — coding assistants search across code, documentation, and web resources simultaneously; enterprise knowledge assistants combine PDFs, wikis, and internal web tools — all built on the same loader-and-pipeline pattern established back in Module 5, extended to each source’s specific handling needs.


8. Real-World Applications

  • Coding assistants (searching codebases, documentation, and Stack Overflow-style content together)
  • Enterprise knowledge assistants spanning documents, wikis, and internal tools
  • Research assistants combining academic PDFs with current web content

9. Common Mistakes

Incorrect idea: Treating code as if it were prose text for chunking purposes.

Why it is incorrect: As shown directly in Section 4, code has its own real structural units — functions and classes — that arbitrary character-based chunking would destructively split.

Incorrect idea: Not accounting for web content’s really higher change frequency.

Why it is incorrect: As shown directly in Section 3, this directly connects to Module 26’s freshness/reindexing strategy, which matters even more for web sources.

Incorrect idea: Building really separate, incompatible pipelines for each content type.

Why it is incorrect: As shown directly in Section 5, a unified loader pattern (Module 5) lets downstream stages remain source-agnostic.


10. Limitations

  • Code-aware chunking really requires language-specific parsing logic (understanding Python’s syntax differs from understanding JavaScript’s) — a real, additional implementation requirement beyond generic text chunking
  • Web content’s freshness and trust considerations require real, ongoing operational effort — not a one-time setup concern

11. Quick Reference — The Whole Idea in One Diagram

PDF:      parse (Module 6) -> chunk (structure-aware, Module 8) ->
        standard pipeline

WEB:         load + CLEAN (strip boilerplate) -> chunk -> standard
           pipeline, with REALLY higher freshness/dedup needs

CODE:            SYMBOL-AWARE chunking (by function/class, NOT
               character count) -> standard pipeline

ALL THREE unified through Module 5's LOADER PATTERN -- downstream
stages remain source-agnostic

12. Code — Implementing Unified Multi-Source Loading

🎯 Target of this example: implement Section 5’s real developer example directly — three really different loader functions producing a uniform LoadedSource structure, exactly demonstrating Module 5’s pattern extended to PDF, web, and code sources together.

Example 1 — Simple

from dataclasses import dataclass

@dataclass
class LoadedSource:
    """The SAME uniform structure from Module 5, Section 3 --
    regardless of which of the three really different source
    types produced it."""
    source_type: str
    content: str
    metadata: dict

def load_pdf(filename: str, extracted_text: str, page_count: int) -> LoadedSource:
    return LoadedSource("pdf", extracted_text, {"filename": filename, "page_count": page_count})

def load_web_page(url: str, extracted_text: str, crawl_date: str) -> LoadedSource:
    return LoadedSource("web", extracted_text, {"url": url, "crawl_date": crawl_date})

def load_code_file(filepath: str, code_content: str, language: str) -> LoadedSource:
    return LoadedSource("code", code_content, {"filepath": filepath, "language": language})

sources = [
    load_pdf("travel_policy.pdf", "International hotel reimbursement is limited to $200/night.", 12),
    load_web_page("https://wiki.techcorp.com/oncall", "The on-call rotation follows a weekly schedule.", "2026-08-01"),
    load_code_file("auth/login.py", "def authenticate_user(username, password):\n    ...", "python"),
]

for source in sources:
    print(f"[{source.source_type}] {source.metadata}")
    print(f"  Content preview: {source.content[:50]}...")

Expected Output:

[pdf] {'filename': 'travel_policy.pdf', 'page_count': 12}
  Content preview: International hotel reimbursement is limited to
$2...
[web] {'url': 'https://wiki.techcorp.com/oncall', 'crawl_date':
'2026-08-01'}
  Content preview: The on-call rotation follows a weekly schedule....
[code] {'filepath': 'auth/login.py', 'language': 'python'}
  Content preview: def authenticate_user(username, password):
    ......

What we conclude from this example: all three really different loaders produce the exact same LoadedSource structure — exactly Section 5’s real developer example, demonstrating that downstream code never needs to know or branch on which specific source type produced any given piece of content.

Example 2 — Intermediate

import re

def chunk_code_by_function(code: str) -> list:
    """Directly implements Section 4's SYMBOL-AWARE chunking --
    splits Python code by FUNCTION definitions, never splitting a
    function's signature from its own implementation."""
    lines = code.split("\n")
    chunks = []
    current_chunk = []

    for line in lines:
        if re.match(r"^def \w+\(", line) and current_chunk:
            chunks.append("\n".join(current_chunk))
            current_chunk = [line]
        else:
            current_chunk.append(line)

    if current_chunk:
        chunks.append("\n".join(current_chunk))
    return chunks

code = (
    "def authenticate_user(username, password):\n"
    "    return check_credentials(username, password)\n"
    "\n"
    "def check_credentials(username, password):\n"
    "    return db.verify(username, password)\n"
    "\n"
    "def logout_user(session_id):\n"
    "    session_store.remove(session_id)"
)

chunks = chunk_code_by_function(code)
for i, chunk in enumerate(chunks, 1):
    print(f"Chunk {i}:\n{chunk}\n---")

Expected Output:

Chunk 1:
def authenticate_user(username, password):
    return check_credentials(username, password)

---
Chunk 2:
def check_credentials(username, password):
    return db.verify(username, password)

---
Chunk 3:
def logout_user(session_id):
    session_store.remove(session_id)
---

What we conclude from this example: each chunk contains exactly one complete function, never splitting a function signature from its implementation — exactly Section 4’s symbol-aware chunking principle, directly analogous to Module 8’s heading-preserving structure-aware chunking, but applied to code’s own natural structural units.

Example 3 — Production Grade

from dataclasses import dataclass, field
from enum import Enum

class SourceType(Enum):
    PDF = "pdf"
    WEB = "web"
    CODE = "code"

@dataclass
class UnifiedSource:
    source_type: SourceType
    content: str
    metadata: dict = field(default_factory=dict)
    requires_frequent_refresh: bool = False  # Section 3's freshness point

class MultiSourceLoader:
    """A production-style unified loader COMBINING all three source
    types under Module 5's pattern, while ALSO capturing Section 3's
    real freshness distinction -- web content is explicitly
    flagged as needing MORE frequent reindexing than PDFs or code."""

    def load_pdf(self, filename: str, text: str, page_count: int) -> UnifiedSource:
        return UnifiedSource(SourceType.PDF, text, {"filename": filename, "page_count": page_count},
                              requires_frequent_refresh=False)

    def load_web_page(self, url: str, text: str, crawl_date: str) -> UnifiedSource:
        return UnifiedSource(SourceType.WEB, text, {"url": url, "crawl_date": crawl_date},
                              requires_frequent_refresh=True)  # Section 3's real distinction

    def load_code_file(self, filepath: str, code: str, language: str) -> UnifiedSource:
        return UnifiedSource(SourceType.CODE, code, {"filepath": filepath, "language": language},
                              requires_frequent_refresh=True)  # code changes frequently too

loader = MultiSourceLoader()

sources = [
    loader.load_pdf("travel_policy.pdf", "Hotel reimbursement policy text.", 12),
    loader.load_web_page("https://wiki.techcorp.com/oncall", "On-call schedule.", "2026-08-01"),
    loader.load_code_file("auth/login.py", "def authenticate_user(): ...", "python"),
]

print("Reindexing priority (frequent refresh needed):")
for source in sources:
    if source.requires_frequent_refresh:
        print(f"  [{source.source_type.value}] {source.metadata}")

Expected Output:

Reindexing priority (frequent refresh needed):
  [web] {'url': 'https://wiki.techcorp.com/oncall', 'crawl_date':
'2026-08-01'}
  [code] {'filepath': 'auth/login.py', 'language': 'python'}

What we conclude from this example: the requires_frequent_refresh flag correctly identifies web and code sources as needing more frequent reindexing than the PDF, directly connecting Section 3’s freshness observation to Module 26’s reindexing strategy — a real production system could use this exact flag to prioritize which sources get checked for staleness most often, rather than treating all source types identically.


13. Interview Questions

Q: Why is PDF RAG described as “not a new technique” despite being covered in its own dedicated module?

Ans: PDF RAG is really the complete pipeline this entire course has built — parsing (Module 6), chunking (Modules 7-9), embedding (Module 10), retrieval (Modules 11-18), and citation (Module 23) — applied to the single most common real-world document format. There’s no new underlying mechanism specific to PDFs; the module exists to demonstrate the complete, already-learned pipeline working end-to-end on this really common source type.

Q: What really distinct considerations does web content introduce compared to static internal documents?

Ans: Web content typically changes more frequently, making freshness and reindexing strategy (Module 26) really more important. The same information often appears duplicated across multiple pages, making deduplication (Module 21) more relevant. And not all web sources are equally reliable, so capturing source credibility as metadata (Module 9) matters more for web content than for internally-authored, presumably-trusted documents.

Q: Explain why code requires “symbol-aware” chunking rather than standard character or sentence-based chunking.

Ans: Code has its own natural structural units — functions and classes — that carry real, complete meaning as a whole. Arbitrary character-based chunking risks splitting a function’s signature from its implementation, exactly as destructive as separating a document heading from its content (Module 8’s structure-aware chunking principle). Chunking by function or class boundary instead ensures each chunk represents a complete, independently meaningful unit of code.

Q: How does Module 5’s unified loader pattern benefit a system that needs to search across PDFs, web pages, and code simultaneously?

Ans: Each source type has really different extraction challenges — PDF parsing, web content cleaning, code symbol parsing — but if every loader produces the same uniform output structure (content plus metadata), then everything downstream — chunking, embedding, indexing, retrieval — can remain completely source-agnostic. This means adding support for a new source type only requires writing a new source-specific loader, without needing to modify any of the shared downstream pipeline logic.


14. What You Should Remember

  • PDF RAG is the complete pipeline this course has built, applied to the most common real document format — no really new mechanism required.
  • Web content has really distinct freshness, deduplication, and trust considerations compared to static documents.
  • Code requires symbol-aware chunking (by function/class) — verified directly by correctly chunking code without ever splitting a function’s signature from its implementation.
  • Module 5’s unified loader pattern lets really different source types share the same downstream pipeline — verified directly through a production loader that also tracks source-specific refresh needs.

15. Quick Practice

Design a chunking strategy for a source type not explicitly covered in this module (like a spreadsheet, an email thread, or a chat log) — what would its own “natural structural unit” (analogous to code’s functions or documents’ headings) really be?

16. Next Step

Next: Module 32 — RAG Evaluation — Level 8 begins here: how to systematically measure retrieval quality and generation quality separately, closing in on the final production-readiness modules.

Author
TechByteByByte Editorial Team
Reviewed by
TechByteByByte Admin
Published
Last reviewed