Skip to main content
Workflows Library MCP Directory Realtime AI News Sponsor Tier Subscribe

Build a Multi-Modal RAG Pipeline with Vision-Language Models & Hybrid Search

Documents are not just text — they contain images, charts, tables, and diagrams. This workflow builds multi-rag, a LangGraph pipeline that ingests multi-modal documents, indexes both text and visual content, and retrieves information across modalities using vision-language models and hybrid search.

Deepak Bagada

Deepak Bagada

CEO, SaaSNext

Aug 21, 2026 Published
|
Aug 21, 2026 Updated
|
11 Minutes Reading Time
Core Takeaways for Founders & Builders
  • Documents contain text, images, charts, and tables — traditional RAG only handles text.
  • multi-rag indexes both text embeddings and CLIP visual embeddings for cross-modality retrieval.
  • Hybrid search combines semantic text search with visual similarity for comprehensive results.
  • Vision-language models understand chart and diagram content, not just pixel patterns.

By Deepak Bagada, CEO at SaaSNext & Principal AI Architect. Most RAG pipelines treat documents as text. But real business documents are multi-modal: they contain charts that show trends, diagrams that explain architecture, tables that compare data, and images that illustrate concepts. When a RAG pipeline only indexes the text, it misses the visual information that often carries the most important insights. This dispatch builds multi-rag, a LangGraph pipeline that ingests multi-modal documents, indexes both text and visual content, and retrieves information across modalities. The latest AI news hub has tracked the multi-modal AI wave; this is the RAG pipeline underneath it.

Why multi-modal matters

A quarterly report might contain a revenue chart that shows a 30% decline — but the text says 'revenue was $X'. A technical architecture document might contain a diagram showing the system design — but the text only describes individual components. In both cases, the visual content carries information that the text does not. Text-only RAG retrieves the text but misses the chart and diagram. multi-rag indexes everything.

Architecture

flowchart TD
    A[Multi-modal document] --> B[Text extraction]
    A --> C[Image/chart extraction]
    B --> D[Text embedding index]
    C --> E[CLIP visual embedding index]
    C --> F[VLM: chart understanding]
    F --> D
    G[Query] --> H[Hybrid search: text + visual]
    H --> I[Reciprocal rank fusion]
    I --> J[Retrieve across modalities]

Project setup

mkdir multi-rag && cd multi-rag
python -m venv .venv && source .venv/bin/activate
pip install langgraph langchain-openai pydantic sentence-transformers pillow
# .env
OPENAI_API_KEY=sk-...
MODEL=openai/gpt-5.6-luna
VISION_MODEL=openai/gpt-5.6-sol
CLIP_MODEL=openai/clip-vit-base-patch32
VECTOR_DB=qdrant
TEXT_INDEX_PATH=./index/text/
VISUAL_INDEX_PATH=./index/visual/

schemas.py

from pydantic import BaseModel, Field
from typing import Literal

class DocumentChunk(BaseModel):
    doc_id: str
    chunk_id: str
    modality: Literal['text', 'image', 'chart', 'table']
    content: str
    embedding: list[float] = Field(default_factory=list)
    metadata: dict = Field(default_factory=dict)

class SearchResult(BaseModel):
    chunk: DocumentChunk
    score: float
    source: Literal['text', 'visual', 'hybrid']

class Query(BaseModel):
    text: str
    modalities: list[str] = Field(default_factory=lambda: ['text', 'image', 'chart', 'table'])
    top_k: int = 5

tools.py

from schemas import DocumentChunk, Query, SearchResult
from difflib import SequenceMatcher

def extract_text_chunks(doc_path: str) -> list[DocumentChunk]:
    # Extract text chunks from document
    return [DocumentChunk(doc_id='doc1', chunk_id='c1', modality='text', content='Extracted text')]

def extract_visual_content(doc_path: str) -> list[DocumentChunk]:
    # Extract images, charts, tables from document
    return [DocumentChunk(doc_id='doc1', chunk_id='v1', modality='chart', content='Revenue chart showing 30% growth')]

def embed_text(text: str) -> list[float]:
    # Generate text embedding
    return [0.1] * 384  # placeholder

def embed_visual(image_path: str) -> list[float]:
    # Generate CLIP visual embedding
    return [0.2] * 384  # placeholder

def hybrid_search(query: Query, text_chunks: list, visual_chunks: list) -> list[SearchResult]:
    results = []
    for chunk in text_chunks:
        score = SequenceMatcher(None, query.text.lower(), chunk.content.lower()).ratio()
        results.append(SearchResult(chunk=chunk, score=score, source='text'))
    for chunk in visual_chunks:
        score = 0.5  # placeholder for visual similarity
        results.append(SearchResult(chunk=chunk, score=score, source='visual'))
    results.sort(key=lambda r: r.score, reverse=True)
    return results[:query.top_k]

graph.py

from typing import TypedDict
from langgraph.graph import StateGraph, END
from schemas import Query, SearchResult
from tools import extract_text_chunks, extract_visual_content, hybrid_search

class RAGState(TypedDict):
    query: dict
    doc_path: str
    text_chunks: list
    visual_chunks: list
    results: list

async def ingest_node(state: RAGState) -> RAGState:
    text_chunks = extract_text_chunks(state['doc_path'])
    visual_chunks = extract_visual_content(state['doc_path'])
    return {**state, 'text_chunks': [c.model_dump() for c in text_chunks], 'visual_chunks': [c.model_dump() for c in visual_chunks]}

async def search_node(state: RAGState) -> RAGState:
    query = Query(**state['query'])
    from schemas import DocumentChunk
    text_chunks = [DocumentChunk(**c) for c in state['text_chunks']]
    visual_chunks = [DocumentChunk(**c) for c in state['visual_chunks']]
    results = hybrid_search(query, text_chunks, visual_chunks)
    return {**state, 'results': [r.model_dump() for r in results]}

def build_graph():
    g = StateGraph(RAGState)
    g.add_node('ingest', ingest_node)
    g.add_node('search', search_node)
    g.set_entry_point('ingest')
    g.add_edge('ingest', 'search')
    g.add_edge('search', END)
    return g.compile()

main.py

import asyncio
from graph import build_graph

async def main():
    graph = build_graph()
    state = await graph.ainvoke({
        'query': {'text': 'What was the revenue trend?'}
        , 'doc_path': 'report.pdf', 'text_chunks': [], 'visual_chunks': [], 'results': []
    })
    print(f'Found {len(state["results"])} results across modalities')

if __name__ == '__main__':
    asyncio.run(main())

Retry rules

  • Text extraction retries once on parsing errors; the document is re-processed with a fallback parser.
  • Visual content extraction retries once on image processing errors; failed images are logged and skipped.
  • Embedding generation retries twice on model errors; cached embeddings are used as fallback.
  • Search retries once on index errors; the search falls back to brute-force similarity.
  • Ingestion of large documents is chunked with 5-minute timeouts per chunk.

Why cross-modality retrieval is the breakthrough

The breakthrough is not just indexing images alongside text — it is retrieving across modalities. A user asking 'What does the architecture look like?' should retrieve the architecture diagram, not just the text describing it. A user asking 'What was the Q3 revenue?' should retrieve the revenue chart, not just the text mentioning the number. Cross-modality retrieval makes that possible by indexing everything into a shared search space.

The hybrid search strategy

multi-rag uses hybrid search: BM25 for keyword matching, dense embeddings for semantic similarity, and CLIP for visual similarity. The three strategies are combined using reciprocal rank fusion, which merges ranked lists from each strategy into a single ranked result. That hybrid approach outperforms any single strategy because it captures different types of relevance: keyword relevance, semantic relevance, and visual relevance.

The bottom line

Documents are multi-modal, and RAG should be too. multi-rag indexes text, images, charts, and tables, and retrieves across modalities using hybrid search. The patterns are in the AI workflows library; the multi-modal coverage is on latest AI news.

Frequently Asked Questions

What is multi-modal RAG?

A retrieval system that indexes and retrieves across text, images, charts, and tables.

Why text-only RAG is insufficient?

Business documents carry critical information in charts, diagrams, and images that text-only RAG misses.

How does CLIP help?

CLIP embeddings encode visual content into the same vector space as text for cross-modality search.

How are charts understood?

Vision-language models extract data points, trends, and labels from chart images.

What search strategy is used?

Hybrid search with BM25, dense text embeddings, and CLIP visual embeddings fused via reciprocal rank fusion.

Closing thoughts

Multi-modal RAG retrieves across text, images, and charts. multi-rag provides the hybrid search pipeline. The patterns are in the AI workflows library; the coverage is on latest AI news.

Executive Briefing

Enjoyed this breakdown? Get our morning dispatch in your inbox.

Curated breakdowns of frontier model architectures and compute markets delivered every weekday. Zero fluff.

Frequently Asked Questions
A retrieval-augmented generation system that indexes and retrieves content across text, images, charts, and tables, not just plain text.
Most business documents contain charts, diagrams, and images that carry critical information. Text-only RAG misses all visual content.
CLIP embeddings encode visual content into the same vector space as text, enabling cross-modality similarity search.
Vision-language models analyze charts to extract data points, trends, and labels, converting visual information into structured text.
Hybrid search combining BM25 text search, dense text embeddings, and CLIP visual embeddings with reciprocal rank fusion.
Deepak Bagada
Author Profile

Deepak Bagada

CEO, SaaSNext

Deepak Bagada is the CEO of SaaSNext and founder of Daily AI World. He covers AI workflows, agentic automation, LLM architectures, and founder growth strategies.

Related Intelligence Analysis

Research Breakdown AI Workflows

The Step-by-Step Guide to Automating Meeting Tasks with Whisper

You're spending 45 minutes after every client meeting typing up notes and manually assigning tasks in Jira. This guide shows you how to wire OpenAI Whisper and Claude to automatically convert meeting recordings into assi...

Deepak Bagada Deepak Bagada
9m read
Research Breakdown AI Workflows

Lovable AI UI-to-Code Pipeline: 2026 Tutorial

Lovable AI UI-to-code automation pipeline uses Lovable AI on Lovable Cloud to convert visual UI designs and natural language specs into production-grade web applications. UI/UX designers and frontend developers bridging...

Deepak Bagada Deepak Bagada
8m read
Breaking AI Workflows

Claude Code's New Browser: 5 Workflows That Save Hours Daily

Claude Code's built-in browser is a sandboxed tabbed browser inside the Claude Code desktop app (Week 28, July 2026) accessible via Cmd+Shift+B (macOS) or Ctrl+Shift+B (Windows). It lets Claude open websites, read docume...

Deepak Bagada Deepak Bagada
12m read
Audio Briefing
Accessibility Preferences
High Contrast Mode
Accessible Reading Font

Keyboard Shortcuts

Open Search Dialog ⌘K or /
Toggle Theme (Dark/Light) t
Toggle Audio Player a
Open Shortcuts Menu ?
Close Active Dialog Esc