Skip to content

Part 04

RAG and Knowledge Systems

A citation-backed reference for ten topics behind production RAG: chunking, vector databases, hybrid search, query transformation, multi-hop and agentic retrieval, GraphRAG, reranking, metadata filtering, document parsing, and multimodal retrieval. Each section ships in three voices: the engineer's version (diagrams, code, research findings), a layman's version (plain English with analogies), and a Bangla explanation. প্রতিটি টপিকের শেষে বাংলা ব্যাখ্যাও আছে।

10 sections · 68 sources · three voices

01

Chunking strategies: fixed, semantic, structural, late chunking

Retrieval works on pieces of documents, not whole documents, so how you cut the text decides what the retriever can ever return. A chunk that is too coarse buries the answer in noise; one that is too fine strips away the context that made the sentence meaningful. This section covers four families: fixed-size (with overlap and recursive separators), structural (headers and sentences), semantic (embedding-driven breakpoints) and late chunking (split after the transformer), plus Anthropic's contextual retrieval, which repairs lost context by prepending an explanation to every chunk.[1][2]

Fixed-size and recursive splitting

The baseline is a window of chunk_size units with chunk_overlap units shared between neighbours so a fact straddling a boundary still lands whole in at least one chunk. In LangChain's RecursiveCharacterTextSplitter the size is measured by length_function, which defaults to Python len(), i.e. characters. The recursive variant tries separators in order ("\n\n", "\n", " ", "": paragraph, line, word, character) so coherent units stay together for as long as possible.[3]

Fixed-size window with overlap document text, left to right chunk 1 (chunk_size) chunk 2 chunk 3 shared text (chunk_overlap) survives in both overlap with chunk 3 Recursive splitter: fall back to a finer separator only when a piece is still too big 1 · paragraph "\n\n" 2 · line "\n" 3 · word " " 4 · character "" too big too big too big Separator order is the LangChain default list; CJK/Thai text needs custom punctuation separators.
Top: overlapping windows keep boundary text in two chunks. Bottom: the recursive splitter prefers paragraph breaks and only descends to words or characters when it must.
উপরে overlap সহ sliding window দেখানো হয়েছে, যাতে দুই chunk-এর সীমানার লেখা দুই জায়গাতেই থাকে। নিচে recursive splitter আগে paragraph, তারপর line, word এবং শেষে character দিয়ে কাটে, যতক্ষণ না টুকরো ছোট হয়।

Structural and sentence-aware splitting

Structure-aware splitters respect boundaries the author already drew. LlamaIndex's SentenceSplitter breaks at sentence boundaries (the docs example uses chunk_size=1024 plus a chunk_overlap), and MarkdownNodeParser parses raw markdown into nodes.[4] LangChain's MarkdownHeaderTextSplitter groups text by header level and attaches the headers as metadata (for example {'Header 1': ..., 'Header 2': ...}); headers are stripped from the text by default (strip_headers=False keeps them). Chain it with a recursive splitter so overlap applies inside a section and never crosses a header boundary.[5]

from langchain_text_splitters import (
    MarkdownHeaderTextSplitter, RecursiveCharacterTextSplitter)

md = MarkdownHeaderTextSplitter(
    headers_to_split_on=[("#", "Header 1"), ("##", "Header 2")],
    strip_headers=False)          # keep headings inside the chunk text
sections = md.split_text(markdown_text)  # each Document carries header metadata

rc = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)  # illustrative values
chunks = rc.split_documents(sections)   # overlap stays inside each section

Units matter: the LangChain default measures characters, not tokens, so a "1000" chunk is not 1000 tokens. The values above are illustrative; none of the sources I checked states a recommended chunk size or overlap percentage, so tune them against your own retrieval evals.

Semantic chunking

Instead of a fixed size, semantic chunking embeds sentences and cuts where adjacent-sentence similarity drops. LlamaIndex's SemanticSplitterNodeParser chooses breakpoints from embedding similarity; its docs example uses breakpoint_percentile_threshold=95 and buffer_size=1, and credits Greg Kamradt's "5 levels of embedding chunking".[4]

Similarity of neighbouring sentences (schematic, not measured data) hi lo breakpoint breakpoint chunk 1 chunk 2 chunk 3 A cut is made where the distance jumps past a percentile threshold (docs example: 95).
Semantic chunking cuts at the biggest topic shifts between adjacent sentences. The curve is illustrative only.
পাশাপাশি বাক্যের embedding similarity হঠাৎ কমে গেলে সেখানে breakpoint বসে এবং নতুন chunk শুরু হয়। বক্ররেখাটি শুধু ধারণা বোঝানোর জন্য আঁকা, বাস্তব ডেটা নয়।

Late chunking

Conventional pipelines chunk first and embed each chunk alone, so a chunk saying "the company" cannot know which company. Late chunking (Günther et al., 2024) runs a long-context embedding model over the whole text and splits "after the transformer model and just before mean pooling", so every chunk vector is pooled from token embeddings that already attended to the surrounding document. The authors state it needs no extra training, and fine-tuning can improve it further.[1] The limit is practical: it requires an embedding model whose context window can hold the document.

Naive: chunk, then embed 1 · Split first chunk A | B | C 2 · Encode chunks alone no view of other chunks 3 · Mean pooling per chunk's tokens Chunk vectors context-blind Late chunking: embed, then chunk 1 · Encode whole doc long-context model 2 · Token embeddings every token saw the doc 3 · Split, then pool mean pool per span Chunk vectors carry surrounding context The split moves to "after the transformer, just before mean pooling". No extra training required; fine-tuning can improve further (Günther et al., 2024).
Only the position of the split changes: late chunking pools chunk vectors from token embeddings that were computed over the whole document.
Naive পদ্ধতিতে আগে কাটা হয়, তারপর প্রতিটি chunk আলাদাভাবে encode হয়। Late chunking-এ পুরো document একবারে encode করে, তারপর mean pooling-এর ঠিক আগে কাটা হয়, তাই প্রতিটি chunk vector আশপাশের context বহন করে।

Contextual retrieval (Anthropic)

A different repair: have an LLM write a short, chunk-specific explanation of where the chunk sits in its document, prepend it to the chunk, and use the result for both the embedding and the BM25 index. Anthropic reports the context is roughly 50 to 100 tokens per chunk and that one-time generation costs $1.02 per million document tokens. For knowledge bases under about 200,000 tokens (around 500 pages) the post says simpler approaches, such as putting everything in the prompt, are enough.[2]

Prepend generated context, then index twice Chunk raw slice LLM-written context ~50-100 tokens each Contextualized chunk context + original Embeddings BM25 index Fuse + rerank top results Reported retrieval failure rate (lower is better; bar length = rate) baseline 5.7% contextual embeddings 3.7% (-35%) + contextual BM25 2.9% (-49%) + reranking 1.9% (-67%) Figures as reported by Anthropic on its own evaluation sets; percentages are relative reductions vs the 5.7% baseline.
Contextual retrieval: generated context is prepended before both embedding and BM25 indexing; reported failure rate falls from 5.7% to 1.9% once reranking is added.
প্রতিটি chunk-এর আগে LLM-এর লেখা ছোট context জুড়ে দিয়ে embedding ও BM25 দুটোই বানানো হয়। Anthropic-এর রিপোর্ট অনুযায়ী retrieval failure rate 5.7% থেকে কমে reranking-সহ 1.9% হয়।

Choosing a strategy

The comparison below is my own editorial synthesis from how each method works, not a benchmark result.

StrategyCuts atStrength (editorial)Watch out for (editorial)
Fixed + overlapA size limitTrivial, predictable, cheapCan slice through sentences and facts
RecursiveParagraph, line, word, character, in that orderKeeps coherent units whenever they fitSize is in characters by default
Structural (headers, sentences)Author-drawn boundariesHeader metadata for filtering and citationsNeeds clean, consistent markup
SemanticEmbedding-similarity dropsTopic-coherent chunksExtra embedding pass; threshold to tune
Late chunkingAfter the transformer, before poolingChunk vectors carry document contextNeeds a long-context embedding model
Contextual retrievalAny chunker, plus LLM contextRepairs lost context for dense and BM25One-time LLM cost per chunk
Baseline failure rate
5.7%
Contextual embeddings
-35% (3.7%)
+ contextual BM25
-49% (2.9%)
+ reranking
-67% (1.9%)
Context per chunk
~50-100 tokens
Generation cost (one-time)
$1.02 / M doc tokens

Research frontier: Late chunking (Günther et al., 2024) moves the split to after the transformer and before mean pooling so chunk embeddings carry whole-document context, with no extra training required.[1] Anthropic's contextual retrieval reports cutting failed retrievals by 35% (contextual embeddings), 49% (adding contextual BM25) and 67% (adding reranking) from a 5.7% baseline.[2] Limits: the Anthropic figures are the vendor's own evaluation, and late chunking depends on a long-context embedding model. I did not verify head-to-head comparisons between the two.
গবেষণার সারকথা: Late chunking পুরো document-এর context রেখে chunk embedding বানায়; contextual retrieval প্রতিটি chunk-এ ব্যাখ্যা জুড়ে failure rate 5.7% থেকে 1.9% করে।

Practice · 1 task

Open any free AI chat: ChatGPT · Claude · Gemini

How practice works
  • A new chat is a fresh conversation with no history. In ChatGPT, Claude, or Gemini, click New chat. It is usually at the top left.
  • Same chat means: keep typing in the chat that is already open. The AI remembers what you sent before.
  • To use a prompt: click Copy, click in the message box of the chat, paste it (Ctrl+V, or press and hold on a phone), and press Enter.
  • AI answers are a little different every time. Your answer does not need to match the example word for word. Look for the same idea.
  • Use made-up data only. Do not paste private information into a chat.

Task 1

See how a bad cut breaks an answer

Why this matters

When you build a support chatbot, your documents are cut into small pieces called chunks. If a cut lands in the middle of a fact, the bot can give a wrong answer, or no answer.

  1. Step 1
    New chat

    Copy this prompt, paste it, and press Enter. It gives the AI a chunk that was cut in the middle of a sentence.

    Prompt
    You are a support bot. Answer the question using ONLY the chunk below. If the chunk does not contain the answer, say "Not enough information".
    
    Chunk (cut every 90 characters, this is chunk 2 of 2):
    "days of purchase. Pro plan customers may request a refund within 30 days. Refunds are paid to the original payment method in 5 business days."
    
    Question: How many days do Basic plan customers have to ask for a refund?

    You should see: Most chats reply Not enough information, because the number for the Basic plan is missing. A few wrongly say 30.

    Example answer
    Not enough information

    Your answer will be worded differently.

  2. Step 2
    New chat

    Copy this prompt, paste it, and press Enter. This chunk was cut at the end of a sentence.

    Prompt
    You are a support bot. Answer the question using ONLY the chunk below. If the chunk does not contain the answer, say "Not enough information".
    
    Chunk (cut at the end of a sentence):
    "Zentro Cloud Refund Policy. Basic plan customers may request a full refund within 14 days of purchase. Pro plan customers may request a refund within 30 days."
    
    Question: How many days do Basic plan customers have to ask for a refund?

    You should see: 14 days. The whole fact is inside one chunk.

    Example answer
    Basic plan customers have 14 days to request a refund.

    Your answer will be worded differently.

  3. Step 3
    Same chat

    Look at the chunk in step 1 again. Write down the words that are missing there.

    You should see: The chunk starts with days of purchase. The number 14 and the words Basic plan are gone.

What just happened

A chatbot does not read your whole document. It searches small pieces called chunks and sends only the best ones to the AI. A fixed-size cutter cuts after a set number of characters, even in the middle of a fact. Here the cut removed 14, so the AI could not answer. A cutter that stops at sentence ends keeps the fact whole.

You are done when You have two answers: one without the number 14, and one with 14 days.

Check yourselfWhy did step 1 fail, when the full policy exists in the document?Show answer

The cutter split the sentence, so the number and the plan name went into different chunks. The search sent only one chunk to the AI. A cutter that respects sentence ends keeps the fact in one piece.

Try a harder versionIn the chunk from step 2, replace the first sentence with The company raised prices. and ask Which company is this about? Then add the line From the Zentro Cloud refund policy. at the top and ask again. What changed?

Watch · 3 videos

02

Vector databases: Qdrant, Pinecone, Weaviate, pgvector, Chroma, Milvus

A vector database stores embeddings and answers "which stored vectors are closest to this one?" without comparing against every row. Exact search over millions of vectors is too slow, so these systems build an approximate nearest neighbour (ANN) index, most often the HNSW graph.[6] Six popular options differ less in the core idea than in deployment model, index choices, hybrid-search support and how they isolate tenants. The feature table below is built only from vendor documentation I fetched; the "when to choose" guidance is my own synthesis and is labelled that way.

ANN basics: flat, IVF and HNSW

Flat search is a linear scan: exact, low memory, sensible for small collections (Weaviate suggests it for small per-tenant data).[7] IVF partitions vectors into lists and searches only some of them; in pgvector that is lists at build time and ivfflat.probes (default 1) at query time.[8] HNSW (Malkov and Yashunin) builds multi-layer proximity graphs, similar in spirit to a skip list, with search cost that scales logarithmically.[6]

layer 2 few nodes layer 1 layer 0 all vectors entry point nearest to query Greedy descent: hop to the closest neighbour on a layer, drop down a layer, repeat; layer 0 holds every vector.
HNSW search: start at a sparse top layer, take long hops to get near the target, then refine on denser layers down to layer 0.
HNSW-তে উপরের স্তর ফাঁকা, তাই লম্বা লাফে দ্রুত লক্ষ্যের কাছে পৌঁছানো যায়। তারপর প্রতিটি নিচের স্তরে আরও ঘন নোডের মধ্যে খুঁজে layer 0-তে সবচেয়ে কাছের vector পাওয়া যায়।
Flat (exact) IVF (lists) HNSW (graph) compare to every vector exact, low memory probe a few lists only walk the graph greedily layers cut the hops Red dot = query. Schematic only: cell and graph layouts are not to scale.
Three ways to find neighbours: scan everything (flat), scan only the nearest few lists (IVF), or walk a proximity graph (HNSW).
Flat পদ্ধতিতে সব vector-এর সাথে তুলনা হয়, IVF-এ শুধু query-র কাছের কয়েকটি list দেখা হয়, আর HNSW-তে graph ধরে হেঁটে নিকটতম প্রতিবেশী খোঁজা হয়। লাল বিন্দু হলো query।

Distance metrics

pgvector exposes <-> (L2), <#> (negative inner product), <=> (cosine), <+> (L1), <~> (Hamming) and <%> (Jaccard).[8] Milvus lists L2 and IP for float vectors, Jaccard and Hamming for binary vectors, and IP or BM25 for sparse vectors.[9] Weaviate's HFresh index supports only cosine and l2-squared.[7] Match the metric to the one your embedding model was trained for; for unit-length vectors cosine and dot product rank identically.

L2 (Euclidean) Cosine Dot / inner product a b |a - b| θ a b a b projection straight-line gap pgvector <-> | Milvus L2 angle only, ignores length pgvector <=> angle and length together pgvector <#> (negated) | Milvus IP Unit-length vectors: cosine = dot product, and L2 squared = 2 - 2*cos(θ), so all three give the same ranking.
L2 measures the straight-line gap, cosine measures only the angle, and the dot product mixes angle with vector length.
L2 দুই বিন্দুর সরলরেখার দূরত্ব মাপে, cosine শুধু কোণ মাপে, আর dot product কোণ ও দৈর্ঘ্য দুটোই ধরে। unit-length vector হলে তিনটিই একই ranking দেয়।

Feature comparison (vendor docs only)

Every cell comes from the documentation pages I fetched. "Not stated" means the page I read does not say, not that the feature is absent.

DatabaseDeployment and licenceIndex types (as stated)Sparse / hybridFiltering and isolation
QdrantRust, Apache 2.0; self-managed, Managed Cloud, Hybrid Cloud, Private Cloud[10][11]HNSWDense, sparse and multivector; hybrid via Query API prefetchJSON payload filters; payload indexes filter during a single graph traversal; multitenancy
PineconeManaged, serverless; BYOC option[12]Not statedDense, sparse, full-text and hybrid in one indexNamespaces for tenant isolation; metadata filters
WeaviateLicence not covered by my notes[7]HNSW (default), flat, dynamic (experimental, v1.25+), HFresh (v1.36)Hybrid = vector + BM25, fused by relativeScoreFusion (default) or rankedFusion[13]Docs suggest flat index for multi-tenant SaaS with small per-tenant data
pgvectorOpen-source Postgres extension; ACID, point-in-time recovery[8]HNSW, IVFFlatTypes sparsevec, halfvec, bit; hybrid not covered in my notesWHERE filters with partial indexes or partitioning; iterative index scans for filtered queries
ChromaApache 2.0; local, client-server, Chroma Cloud (managed serverless)[14]Not verifiedDense, sparse and hybrid; keyword and regex searchMetadata filtering; multi-modal
MilvusApache 2.0 (Zilliz); Milvus Lite, Standalone (Docker), Distributed (Kubernetes); C++[15]FLAT, IVF_FLAT, IVF_SQ8, IVF_PQ, HNSW, HNSW_SQ, HNSW_PQ, HNSW_PRQ, SCANN, plus binary and sparse inverted indexes; DiskANN mentioned[9]Multi-vector hybrid, BM25 full-text, rerankingMulti-tenancy at database, collection, partition or partition-key level
-- pgvector: HNSW index with the documented defaults, cosine distance
CREATE EXTENSION vector;
CREATE TABLE items (id bigserial PRIMARY KEY, body text, embedding vector(768));
CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops)
  WITH (m = 16, ef_construction = 64);

SET hnsw.ef_search = 40;                 -- query-time recall/speed knob (default 40)
SELECT id, body FROM items
ORDER BY embedding <=> '[0.12, -0.03, ...]'  -- <=> is cosine distance
LIMIT 5;
pgvector HNSW defaults
m 16, ef_construction 64
hnsw.ef_search default
40
ivfflat.probes default
1
HNSW index dim limit
2,000 vector / 4,000 halfvec
Weaviate dynamic switch
flat to HNSW past 10,000 objects
Qdrant quantization claim
up to 97% RAM reduction

Filtered search: combining a metadata filter with ANN is where engines differ. Qdrant's docs say payload indexes let it filter during a single graph traversal, while pgvector offers partial indexes, partitioning and iterative index scans for filtered queries. Test your real filters at your real selectivity; a recall number measured without filters says little about filtered queries.[11][8]

When to choose what (my own synthesis)

Editorial, not vendor guidance. None of the vendors state "choose us when" rules in the pages I read. The mapping below is my inference from the feature table.

Your situationLean towardReasoning (synthesis)
Data already in Postgres; want transactions and joinspgvectorVectors live beside relational rows with ACID and PITR
Prototype or laptop-scale experimentChroma (local mode)Runs locally; can later move to client-server or Cloud
Minimal operations, managed servicePinecone, or a managed tier of Qdrant, Chroma or MilvusPinecone is serverless; others offer managed cloud options
Self-hosted, filter-heavy searchQdrantPayload filtering built into graph traversal; Apache 2.0
Very large, distributed deployment on KubernetesMilvusDecoupled query, data and index nodes; Distributed mode
Built-in hybrid fusion with tunable alpha, or many tenantsWeaviateBM25 + vector fusion options and a flat-index tenancy pattern

Research frontier: The HNSW paper (Malkov and Yashunin, 2016, final 2018) introduced multi-layer proximity graphs with logarithmic search scaling, plus a neighbour-selection heuristic that helps at high recall and on clustered data.[6] Limits: HNSW is an approximate index, so recall depends on parameters such as ef_search, and I did not verify any cross-vendor benchmark numbers, so none are given here.
গবেষণার সারকথা: HNSW বহুস্তরীয় graph দিয়ে লগারিদমিক সময়ে প্রায়-নিকটতম প্রতিবেশী খোঁজে; তবে ফল approximate, তাই recall নির্ভর করে ef_search-এর মতো প্যারামিটারের ওপর।

Practice · 1 task

Open any free AI chat: ChatGPT · Claude · Gemini

How practice works
  • A new chat is a fresh conversation with no history. In ChatGPT, Claude, or Gemini, click New chat. It is usually at the top left.
  • Same chat means: keep typing in the chat that is already open. The AI remembers what you sent before.
  • To use a prompt: click Copy, click in the message box of the chat, paste it (Ctrl+V, or press and hold on a phone), and press Enter.
  • AI answers are a little different every time. Your answer does not need to match the example word for word. Look for the same idea.
  • Use made-up data only. Do not paste private information into a chat.

Task 1

Find the nearest point three different ways

Why this matters

When you build document search, each text becomes a list of numbers (a vector), and a vector database finds the closest ones. The measure of closest that you pick can change which document comes back.

  1. Step 1
    New chat

    Copy this prompt, paste it, and press Enter. Write down the nearest point for each of the three measures.

    Prompt
    Here are 5 points, written as (x, y):
    P1 = (2, 2)
    P2 = (10, 9)
    P3 = (1, 0.5)
    P4 = (0, 3)
    P5 = (-1, -1)
    
    The query point is q = (1, 1).
    
    For every point, calculate three numbers:
    (a) L2 distance to q (the straight-line gap; smaller means nearer)
    (b) cosine similarity with q (how close the direction is; bigger means nearer)
    (c) dot product with q (direction mixed with length; bigger means nearer)
    
    Show one table with 3 decimals. Then name the nearest point for each of the three.

    You should see: A table with 5 rows. The nearest point is P3 by L2, P1 by cosine, and P2 by dot product. That is three different winners.

    Example answer
    Point | L2 | Cosine | Dot
    P1 | 1.414 | 1.000 | 4.000
    P2 | 12.042 | 0.999 | 19.000
    P3 | 0.500 | 0.949 | 1.500
    P4 | 2.236 | 0.707 | 3.000
    P5 | 2.828 | -1.000 | -2.000
    
    Nearest by L2: P3. Nearest by cosine: P1. Nearest by dot product: P2.

    Your answer will be worded differently.

  2. Step 2
    Same chat

    Copy this prompt, paste it, and press Enter. Write down the nearest point for each measure again.

    Prompt
    Now divide every point by its own length, so every point has length 1. Do the same for q. Then repeat the table with L2 distance, cosine similarity, and dot product, with 3 decimals. Name the nearest point for each of the three.

    You should see: All three measures now pick the same point, P1. The numbers in the table are close for P1 and P2.

    Example answer
    Point | L2 | Cosine | Dot
    P1 | 0.000 | 1.000 | 1.000
    P2 | 0.053 | 0.999 | 0.999
    P3 | 0.320 | 0.949 | 0.949
    P4 | 0.765 | 0.707 | 0.707
    P5 | 2.000 | -1.000 | -1.000
    
    Now all three measures pick the same point: P1.

    Your answer will be worded differently.

What just happened

Each measure has its own idea of near. L2 is the straight-line gap, cosine looks only at direction, and the dot product also rewards length. P2 is far away but long, so only the dot product picks it. After you scale every point to length 1, the three measures agree.

You are done when You have 3 different winners from step 1, and 1 shared winner from step 2.

Check yourselfYour embedding model (the model that turns text into vectors) was trained with cosine similarity. Which measure should the vector database use?Show answer

Use cosine, or the dot product on vectors scaled to length 1, which ranks the same. L2 or a raw dot product can pick by length, so the database would return different documents.

Try a harder versionGo back to step 1, change q to (3, 3), and run it again. Which winner changes, and which stay the same? Why?

Watch · 3 videos

03

Hybrid search (BM25 + vector) & reciprocal rank fusion

Keyword search (BM25) and embedding search fail in opposite ways: BM25 needs the query's exact words to appear, while dense vectors can blur rare identifiers, names and codes. Hybrid search runs both and merges the results. The most common merge is reciprocal rank fusion (RRF), which ignores raw scores and combines only each document's rank in each list.[16] The catch is that the two retrievers' scores live on different scales, which is exactly the problem rank-based fusion sidesteps.

The two retrievers and the parallel pipeline

BM25 comes from the Probabilistic Relevance Framework (grounded in 1970s-80s work); BM25F extends it to document fields such as metadata.[17] It scores exact term matches via an inverted index. Learned sparse models such as SPLADE keep that exact-match, inverted-index efficiency and interpretability but add learned term expansion to handle vocabulary mismatch.[18] A dense retriever embeds the query and finds nearest vectors with an ANN index. Weaviate states that its hybrid search runs the vector and BM25 searches in parallel and then fuses them.[13]

Query user text BM25 (sparse) inverted index, exact terms Dense vector embedding + ANN index ranked list 1 scores: BM25 scale ranked list 2 scores: vector scale RRF uses ranks only fused top-k to reranker / LLM Both searches run on the same query; only the ranks (not the incompatible scores) cross into the fusion step.
Hybrid search: one query goes to a keyword index and a vector index in parallel, and a fusion step merges the two ranked lists.
একই query একসাথে BM25 (keyword) ও dense vector search-এ যায়। দুটি আলাদা ranked list পাওয়া যায়, তারপর RRF শুধু rank দেখে সেগুলোকে মিলিয়ে একটি fused top-k বানায়।

Reciprocal rank fusion, worked by hand

Cormack, Clarke and Buettcher define the score as the sum, over every ranking that contains the document, of 1 / (k + r(d)), where r(d) is the document's rank. The paper states that "k = 60 was fixed during a pilot investigation and not altered during subsequent validation."[16] Elasticsearch uses score += 1.0 / (k + rank) with rank starting at 1, rank_constant defaulting to 60, and rank_window_size defaulting to the size parameter.[19] OpenSearch added RRF in 2.19 (12 Feb 2025) with a default rank constant of 60.[20] The ranks in the diagram are invented for the arithmetic.

BM25 ranks Dense ranks RRF with k = 60, sorted by fused score rank 1 · doc A rank 2 · doc B rank 3 · doc E rank 4 · doc C rank 5 · doc G rank 1 · doc C rank 2 · doc B rank 3 · doc D rank 4 · doc F rank 5 · doc A #1 B 1/(60+2) + 1/(60+2) = 0.016129 + 0.016129 = 0.032258 #2 C 1/(60+4) + 1/(60+1) = 0.015625 + 0.016393 = 0.032018 #3 A 1/(60+1) + 1/(60+5) = 0.016393 + 0.015385 = 0.031778 #4 D 1/(60+3) (dense only) = 0.015873 #5 E 1/(60+3) (BM25 only) = 0.015873 (tie with D) #6 F 1/(60+4) (dense only) = 0.015625 #7 G 1/(60+5) (BM25 only) = 0.015385 Doc A is first in BM25 yet third overall: documents both lists agree on beat a single strong rank. RRFscore(d) = sum over rankings r of 1 / (k + r(d)); ranks start at 1; a document missing from a list adds nothing. Ranks are invented for illustration; the arithmetic is exact.
Worked RRF example: doc A leads BM25 but sits at rank 5 in the dense list, so documents ranked well by both retrievers (B, C) overtake it.
এখানে doc A BM25-তে প্রথম কিন্তু dense তালিকায় পঞ্চম, তাই fused স্কোরে B ও C তার চেয়ে এগিয়ে যায়। দুই তালিকাতেই ভালো rank পাওয়া document-ই RRF-এ জেতে। ranks কাল্পনিক, অঙ্কটি নিখুঁত।

Vendors disagree on k: the paper fixed k = 60, and Elasticsearch and OpenSearch default to 60. Qdrant's docs, however, state that RRF's k is "set to 2 by default". Weaviate's rankedFusion uses 1/(RANK+60).[21][13] The same two lists can therefore fuse differently across engines; check the default before comparing results, and set k explicitly where the API allows it.

Rank-based versus score-based fusion

Weaviate's default hybrid method is relativeScoreFusion (default since v1.24), which normalises each search's scores to 0-1 (min-max style) and weights them with alpha (0 is keyword only, 1 is vector only, server default 0.75). It notes there is no max-distance equivalent for BM25 because BM25 scores are unbounded. rankedFusion, the default through v1.23, is the rank-based alternative.[13] Qdrant's Query API supports RRF and DBSF (distribution-based score fusion), added weighted RRF in v1.17.0, and advises RRF as the default when you have no evaluation set.[21]

Score-based fusion (Weaviate relativeScoreFusion) Rank-based fusion: 1/(k + rank) 1 · Run both searches BM25 scores are unbounded, so raw scores cannot be added 2 · Normalise to 0-1 min-max style; default since v1.24 3 · Weight with alpha 0 = keyword only, 1 = vector only, default 0.75 Uses score magnitudes: normalisation choice matters k = 60 (paper; Elasticsearch and OpenSearch default) rank 10.01639100% rank 50.0153894% rank 100.0142987% k = 2 (stated as Qdrant's default) rank 10.33333100% rank 50.1428643% rank 100.0833325% Bar = share of the rank-1 contribution; small k favours top ranks
Left: score-based fusion normalises and weights raw scores. Right: in RRF a rank-10 hit keeps 87% of a rank-1 hit's weight at k = 60 but only 25% at k = 2 (arithmetic from the formula).
বাঁয়ে score-ভিত্তিক fusion: score normalise করে alpha দিয়ে ওজন দেওয়া হয়। ডানে RRF: k = 60 হলে rank-10 এর ওজন rank-1-এর 87%, কিন্তু k = 2 হলে মাত্র 25%, অর্থাৎ ছোট k উপরের rank-কে অনেক বেশি গুরুত্ব দেয়।

In code

def rrf(rankings, k=60):
    """rankings: list of ranked doc-id lists, best first. Returns fused ids."""
    scores = {}
    for ranking in rankings:
        for rank, doc in enumerate(ranking, start=1):   # ranks start at 1
            scores[doc] = scores.get(doc, 0.0) + 1.0 / (k + rank)
    return sorted(scores, key=scores.get, reverse=True)

bm25  = ["A", "B", "E", "C", "G"]
dense = ["C", "B", "D", "F", "A"]
print(rrf([bm25, dense]))   # ['B', 'C', 'A', 'E', 'D', 'F', 'G'] (D and E tie; order is insertion order)

Pinecone documents three hybrid patterns: filter-then-rank (Documents API), client-side RRF over separate keyword and dense searches, and a single index storing a dense and a sparse vector per record with alpha weighting (Vectors API). Its docs add a useful caution: "RRF is a fusion method, not a synonym for hybrid search."[22]

RRF constant in the paper
k = 60
Elasticsearch rank_constant
60 (default)
OpenSearch RRF default
60 (since 2.19)
Qdrant docs: RRF k
2 (stated default)
Weaviate alpha default
0.75 (favours vector)
Qdrant weighted RRF
since v1.17.0

Research frontier: Cormack et al. (SIGIR 2009) showed that RRF outperforms Condorcet fusion and individual rank-learning methods, using only ranks and a single constant fixed in a pilot study.[16] SPLADE shows sparse retrieval can add learned term expansion while keeping inverted-index efficiency.[18] Elasticsearch's docs say RRF is also shown to give improved relevance over either query individually.[19] Limits: the optimal k is not universal (Qdrant documents 2, the paper used 60), and I did not verify head-to-head benchmark numbers across engines, so none are claimed.
গবেষণার সারকথা: RRF শুধু rank ব্যবহার করে বলে আলাদা scale-এর স্কোর মেলাতে হয় না; তবে k-এর মান বিভিন্ন সিস্টেমে ভিন্ন (paper-এ 60, Qdrant docs-এ 2)।

Practice · 1 task

Open any free AI chat: ChatGPT · Claude · Gemini

How practice works
  • A new chat is a fresh conversation with no history. In ChatGPT, Claude, or Gemini, click New chat. It is usually at the top left.
  • Same chat means: keep typing in the chat that is already open. The AI remembers what you sent before.
  • To use a prompt: click Copy, click in the message box of the chat, paste it (Ctrl+V, or press and hold on a phone), and press Enter.
  • AI answers are a little different every time. Your answer does not need to match the example word for word. Look for the same idea.
  • Use made-up data only. Do not paste private information into a chat.

Task 1

Merge two search lists with RRF

Why this matters

When you build search for a help centre, keyword search and meaning search each give their own ranked list. You must merge them into one list, and the merge rule changes what the user sees first.

  1. Step 1
    New chat

    Copy this prompt, paste it, and press Enter. Write down the top document for k = 60 and for k = 2.

    Prompt
    Reciprocal rank fusion (RRF) merges ranked lists. Each document gets this score: add up 1 / (k + rank) for every list that contains it. Rank 1 is the best place.
    
    List 1, keyword search (best first): A, B, E, C, G
    List 2, meaning search (best first): D, G, B, F, A
    
    Calculate the score of every document (A to G) twice: once with k = 60 and once with k = 2. Show one table with 4 decimals. Then give the final ranking for each k. If two documents tie, say so.

    You should see: A table with 7 rows. With k = 60 the top document is B. With k = 2 the top document is A. C and F tie at the bottom.

    Example answer
    Doc | k=60 | k=2
    A | 0.0318 | 0.4762
    B | 0.0320 | 0.4500
    C | 0.0156 | 0.1667
    D | 0.0164 | 0.3333
    E | 0.0159 | 0.2000
    F | 0.0156 | 0.1667
    G | 0.0315 | 0.3929
    
    k=60 ranking: B, A, G, D, E, then C and F tie.
    k=2 ranking: A, B, G, D, E, then C and F tie.

    Your answer will be worded differently.

  2. Step 2
    Same chat

    Copy this prompt, paste it, and press Enter. Write down which search decided the winner.

    Prompt
    A search system gives these scores to 3 documents.
    
    Keyword score (BM25): X = 14.2, Y = 9.8, Z = 2.1
    Meaning score (cosine): X = 0.61, Y = 0.83, Z = 0.88
    
    Add the two scores of each document, and rank the documents by the sum. Which of the two searches decided the winner? Why is adding these two scores not fair? Answer in 3 short sentences after the ranking.

    You should see: The sums are X = 14.81, Y = 10.63, Z = 2.98. X wins, because the keyword scores are far bigger than the cosine scores.

    Example answer
    Ranking by sum: X = 14.81, Y = 10.63, Z = 2.98.
    The keyword search decided the winner. Its scores are large, so the cosine scores hardly matter. Cosine says Z is best, but Z comes last. The two scores are on different scales, so adding them is not fair.

    Your answer will be worded differently.

What just happened

RRF scores a document by its place in each list, not by its raw score: 1 / (k + place). With k = 60, a document that is high in both lists (B) wins. With k = 2, a first place counts much more, so A wins. Search tools use different k (Elasticsearch uses 60, Qdrant uses 2), so the same lists can give a different order.

You are done when You have two rankings: B first with k = 60, and A first with k = 2.

Check yourselfWhy does RRF use the place in each list and not the raw scores?Show answer

Keyword scores and meaning scores use different scales, so adding them is not fair: the bigger numbers win. Places have the same scale in every list.

Try a harder versionAsk the AI to add a third list, Newest first: B, E, A, C, D, F, G, and compute again with k = 60. Which document wins now?

Watch · 3 videos

04

Query rewriting, expansion & HyDE

User queries are short, ambiguous and phrased differently from the documents that answer them. Query transformation puts a step before retrieval that rewrites, broadens, multiplies or even "answers" the query so the retriever has a better string (or vector) to match. Established variants include HyDE[23], step-back prompting[24], Query2doc[25], RAG-Fusion[26] and Rewrite-Retrieve-Read[27]. Each trades one extra LLM call (latency and cost) for a better chance that the right chunk appears in the candidate set.

HyDE: retrieve with a hypothetical answer

HyDE (Hypothetical Document Embeddings) asks an instruction-following LM to write a document that answers the query. The text "captures relevance patterns but is unreal and may contain false details"; an unsupervised contrastive encoder (Contriever) embeds it, and the resulting vector is used for nearest-neighbour search over the real corpus. The authors describe the encoder as a lossy compressor that filters out the hallucinated details, and report gains over the Contriever baseline across web search, QA and fact verification, all without relevance labels.[23]

User query short, terse LLM writes fake answer instruction-following LM Dense encoder Contriever (unsupervised) ANN search over real corpus Real docs top-k to reader the fake answer may contain false details; encoding compresses it to relevance patterns
HyDE: the query is replaced by a generated pseudo-answer; embedding it discards most invented specifics and keeps the "shape" of a relevant document.
HyDE-তে query-র বদলে LLM-এর লেখা একটি কাল্পনিক উত্তর embed করা হয়। encoder সেই লেখা থেকে ভুল খুঁটিনাটি ঝেড়ে ফেলে শুধু প্রাসঙ্গিকতার ধরনটা রাখে, তারপর সেই vector দিয়ে আসল documents খোঁজা হয়।

Step-back, rewriting and expansion

  • Rewrite-Retrieve-Read inserts a rewriting step before retrieval. The rewriter can be a prompted LLM or a small trainable T5 tuned with reinforcement learning, and the reader can stay a frozen black-box LLM. It was tested on HotpotQA, AmbigNQ, PopQA and MMLU.[27]
  • Step-back prompting has two stages: abstraction (derive a higher-level concept or question) and reasoning. With PaLM-2L the authors report +7% on MMLU physics, +11% on chemistry, +27% on TimeQA and +7% on MuSiQue. It is a reasoning method that also covers retrieval-augmented use (TimeQA), not a retrieval trick alone.[24]
  • Query2doc few-shot prompts an LLM for a pseudo-document and concatenates it with the query; for BM25 the original query is repeated several times to balance the weights. Reported: 3-15% BM25 gains on in-domain sets, only 0.4-4% for dense retrievers, and larger LLMs help more.[25]
  • RAG-Fusion generates several query variants, retrieves for each, and fuses the lists with Reciprocal Rank Fusion before the LLM answers.[26]
Original query as typed Rewrite clean up / disambiguate Step-back ask a broader question Multi-query N paraphrases (RAG-Fusion) HyDE / Query2doc generate a pseudo-document Retrievers one search per query variant Fuse (+ rerank) RRF over ranked lists
Query transformations fan one query out into several retrieval strings. You can use one technique or combine several, then fuse the resulting lists.
একটি query থেকে rewrite, step-back, multi-query ও HyDE-এর মতো একাধিক সার্চ স্ট্রিং তৈরি হয়। প্রতিটির জন্য আলাদা retrieval চলে, তারপর ফলাফলের তালিকাগুলো fuse করা হয়।

Multi-query fusion with RRF, worked through

RAG-Fusion scores each document by summing 1/(k + rank) across the lists produced by the query variants.[26] The original paper defining RRF fixed k = 60 in a pilot and did not alter it afterwards[16], and Elasticsearch defaults rank_constant to 60[19]. Vendors differ, though: Qdrant's docs state RRF's k is "set to 2 by default"[21], so check your engine's default before comparing rankings.

variant 1 list variant 2 list fused score, k = 60 rank 1: Doc A rank 2: Doc B rank 3: Doc C rank 1: Doc B rank 2: Doc D rank 3: Doc A RRF sum of 1/(k+rank) 1. Doc B 1/62 + 1/61 = 0.03252 2. Doc A 1/61 + 1/63 = 0.03227 3. Doc D 1/62 = 0.01613 4. Doc C 1/63 = 0.01587 Doc B wins: high in both lists beats rank 1 in only one list. Illustrative numbers computed from the RRF formula. Qdrant docs state a default k of 2, the 2009 paper used 60.
Worked RRF: ranks from two query-variant lists are turned into 1/(60 + rank) contributions and summed; agreement across lists decides the order.
দুটি query variant-এর ranking থেকে প্রতিটি document-এর 1/(60 + rank) যোগ করা হয়েছে। Doc B দুই তালিকাতেই ওপরের দিকে থাকায় একটিতে এক নম্বরে থাকা Doc A-কে সামান্য ব্যবধানে হারায়।
# Query transformation sketch: multi-query + HyDE, fused with RRF.
# llm(), embed(), dense_search() and bm25_search() are placeholders for your stack.
def rrf(lists, k=60):
    scores = {}
    for ranked in lists:
        for rank, doc_id in enumerate(ranked, start=1):
            scores[doc_id] = scores.get(doc_id, 0.0) + 1.0 / (k + rank)
    return sorted(scores, key=scores.get, reverse=True)

def retrieve(question, n_variants=3, top_k=20):
    variants = llm(f"Write {n_variants} search queries for: {question}").splitlines()
    fake_doc = llm(f"Write a short passage answering: {question}")   # HyDE
    lists = [bm25_search(q, top_k) for q in [question, *variants]]
    lists.append(dense_search(embed(fake_doc), top_k))
    return rrf(lists)[:top_k]
TechniqueWhat it changesReported in the paperWatch out
HyDE[23]Embeds a generated pseudo-answerBeats Contriever baseline on web search, QA, fact verificationGenerated text can be wrong; encoder is expected to filter it
Step-back[24]Adds a broader, abstract questionPaLM-2L: +7% MMLU physics, +11% chemistry, +27% TimeQA, +7% MuSiQuePrompting method, not retrieval-only
Query2doc[25]Query + LLM pseudo-documentBM25 +3-15% in-domain; dense +0.4-4%Little help for dense retrievers; bigger LLM needed
RAG-Fusion[26]N query variants + RRFManual assessment onlyAnswers can drift off-topic if variants are irrelevant
Rewrite-Retrieve-Read[27]Rewrites query before retrievalTested on HotpotQA, AmbigNQ, PopQA, MMLUExtra model call; rewriter may need training

Pitfalls: every transformation adds latency and token cost before the first search. Generated variants can drift from the user's intent (the RAG-Fusion author notes off-topic answers)[26], and Query2doc's gains shrink for dense retrievers[25]. Measure on your own queries, and keep the original query among the retrieval strings.

Research frontier: HyDE showed that a dense retriever can be steered without relevance labels by retrieving with a generated pseudo-document[23]; Query2doc found the same idea helps sparse BM25 much more than dense models[25]. Rewrite-Retrieve-Read made the rewriter itself trainable with reinforcement learning against a frozen reader[27], and step-back prompting showed that abstraction before reasoning improves multi-hop and knowledge-heavy QA[24]. Limits: RAG-Fusion's evidence is manual assessment and it can drift off-topic[26], and HyDE's hypothetical text may contain false details.[23]
গবেষণার সারকথা: LLM দিয়ে query বদলে বা কাল্পনিক উত্তর বানিয়ে retrieval ভালো করা যায়, তবে লাভ retriever-এর ধরনের ওপর নির্ভর করে এবং বাড়তি খরচ ও বিষয়-থেকে-সরে-যাওয়ার ঝুঁকি আছে।

Practice · 1 task

Open any free AI chat: ChatGPT · Claude · Gemini

How practice works
  • A new chat is a fresh conversation with no history. In ChatGPT, Claude, or Gemini, click New chat. It is usually at the top left.
  • Same chat means: keep typing in the chat that is already open. The AI remembers what you sent before.
  • To use a prompt: click Copy, click in the message box of the chat, paste it (Ctrl+V, or press and hold on a phone), and press Enter.
  • AI answers are a little different every time. Your answer does not need to match the example word for word. Look for the same idea.
  • Use made-up data only. Do not paste private information into a chat.

Task 1

Search with a made-up answer

Why this matters

When you build a help-centre chatbot, users type vague questions like it keeps saying not allowed. The help articles use other words, like permission denied, so a keyword search misses them.

  1. Step 1
    New chat

    Copy this prompt, paste it, and press Enter. Write down which document wins.

    Prompt
    You are a keyword search engine. Score each document by the number of different words it shares with the query. Ignore these small words: a, an, the, it, I, you, your, is, are, was, to, on, in, of, for, with, and, or, if, when, that, this, there, no. Show the shared words and the count for each document. Then name the best document.
    
    Query: "it keeps saying not allowed when I push"
    
    D1: Fixing HTTP 403 Forbidden on git push. Your access token lacks write permission. Create a new personal access token with the repo scope.
    D2: How to push a new branch. Run git push origin branch-name. If the terminal keeps saying there is no upstream, use --set-upstream.
    D3: Branch protection rules block direct pushes to main. Permission is denied for everyone except admins. Open a pull request instead.
    D4: Cleaning up old branches. Delete merged branches with git branch -d.

    You should see: D2 wins with 3 shared words (push, keeps, saying). D1 has 1, and D3 and D4 have 0. D2 only shows how to push. It does not fix the error.

    Example answer
    Shared words:
    D1: push = 1
    D2: keeps, push, saying = 3
    D3: none = 0
    D4: none = 0
    Best document: D2

    Your answer will be worded differently.

  2. Step 2
    New chat

    Copy this prompt, paste it, and press Enter. Then copy the passage that the AI writes.

    Prompt
    Write a short passage (3 sentences) that could appear in a help-centre article answering this question: "it keeps saying not allowed when I push". The passage may contain guesses. Do not add a title or a comment.

    You should see: A passage of 3 sentences with words like permission, denied, forbidden, or token.

    Example answer
    If you see 'permission denied' or '403 Forbidden' when you push, your access token may not have write permission. The branch may also be protected, so direct pushes are blocked. Create a new token with the repo scope, or open a pull request instead.

    Your answer will be worded differently.

  3. Step 3
    New chat

    Copy this prompt. Replace the words in brackets with your passage from step 2. Paste it and press Enter.

    Prompt
    You are a keyword search engine. Score each document by the number of different words it shares with the query. Ignore these small words: a, an, the, it, I, you, your, is, are, was, to, on, in, of, for, with, and, or, if, when, that, this, there, no. Show the shared words and the count for each document. Then name the best document.
    
    Query: "[PASTE YOUR PASSAGE HERE]"
    
    D1: Fixing HTTP 403 Forbidden on git push. Your access token lacks write permission. Create a new personal access token with the repo scope.
    D2: How to push a new branch. Run git push origin branch-name. If the terminal keeps saying there is no upstream, use --set-upstream.
    D3: Branch protection rules block direct pushes to main. Permission is denied for everyone except admins. Open a pull request instead.
    D4: Cleaning up old branches. Delete merged branches with git branch -d.

    You should see: D1 or D3 wins now, and D2 falls behind. For the example passage below, D1 has 11 shared words, D3 has 9, and D2 has 3.

    Example answer
    Shared words:
    D1: 403, access, create, forbidden, new, permission, push, repo, scope, token, write = 11
    D2: branch, new, push = 3
    D3: branch, denied, direct, instead, open, permission, pull, pushes, request = 9
    D4: branch = 1
    Best document: D1

    Your answer will be worded differently.

What just happened

A short, vague question and the real answer often use different words. In HyDE (search with a made-up answer), the AI first writes a fake answer, and you search with that text instead of the question. The fake answer can have wrong details. But it uses the words real answers use, like permission and denied, so it finds D1 and D3 instead of D2.

You are done when You have two winners: D2 for the raw question, and D1 or D3 for the made-up passage.

Check yourselfWhy can a made-up answer find better documents than the original question?Show answer

The made-up answer is written in the style and words of a real answer, so it matches the right documents better than a short, vague question. It can contain false details, because the search only needs its words, not its facts.

Try a harder versionIn a new chat, ask the AI for three different rewrites of the query from step 1. Run the keyword search with each rewrite. Do the top documents agree?

Watch · 3 videos

05

Multi-hop retrieval & agentic retrieval

Some questions cannot be answered from one retrieval: the second search depends on what the first one found. Multi-hop benchmarks such as HotpotQA (113k Wikipedia question-answer pairs that require reasoning over multiple documents, with sentence-level supporting facts)[28] exist to test exactly this. Agentic retrieval lets the model itself decide whether to retrieve, what to search for, whether the results are good enough, and when to stop. The price is more LLM calls, more latency and a harder system to debug.

Interleaving retrieval and reasoning (IRCoT, ReAct)

IRCoT alternates retrieval with chain-of-thought steps: the CoT so far guides what to retrieve next, and the retrieved passages improve the following CoT step. The paper reports retrieval gains of 11-21 points and QA gains of up to 15 F1 across HotpotQA, 2WikiMultihopQA, MuSiQue and IIRC, working from 0.2B to 175B models, and up to 50% fewer factual errors in the CoT.[29] ReAct interleaves reasoning traces with actions such as Wikipedia lookups; it reduced hallucination in QA and fact verification and gave +34% / +10% absolute success on ALFWorld / WebShop.[30]

Question multi-hop Retrieve search the corpus Next CoT sentence reason over passages Enough to answer? Final answer with evidence yes no: last CoT sentence becomes the next query CoT guides retrieval; retrieved passages improve the next CoT step.
IRCoT-style loop: each reasoning sentence becomes the query for the next retrieval, until the model has enough evidence to answer.
IRCoT-তে প্রতিটি যুক্তির বাক্য পরের retrieval-এর query হয়ে যায়। যথেষ্ট প্রমাণ না পাওয়া পর্যন্ত retrieve করা আর যুক্তি দেওয়ার এই চক্র চলতে থাকে, তারপর চূড়ান্ত উত্তর আসে।
# IRCoT-style loop (sketch). retrieve() and llm() are placeholders.
def ircot(question, max_steps=5, k=4):
    evidence, cot = [], []
    query = question
    for _ in range(max_steps):
        evidence += retrieve(query, k)                    # 1. retrieve with the current query
        step = llm(question, evidence, cot)               # 2. write ONE next reasoning sentence
        cot.append(step)
        if "answer is" in step:                         # 3. stop when the model commits
            return step, evidence
        query = step                                    # 4. last sentence drives the next search
    return llm(question, evidence, cot), evidence

Self-reflection and routing (Self-RAG, CRAG, Adaptive-RAG)

  • Self-RAG: the LM retrieves on demand and critiques both the passages and its own output using special "reflection tokens". The 7B and 13B models are reported to outperform ChatGPT and retrieval-augmented Llama2-chat on open-domain QA, reasoning and fact verification, with better citation accuracy.[31]
  • CRAG (Corrective RAG): a lightweight retrieval evaluator returns a confidence; if the static corpus retrieval is inadequate, it falls back to large-scale web search, and a decompose-then-recompose step filters noise. It is plug-and-play over an existing RAG pipeline.[32]
  • Adaptive-RAG: a smaller LM classifier predicts query complexity and routes among no retrieval, single-step retrieval and iterative retrieval, improving efficiency and accuracy against adaptive baselines (NAACL 2024).[33]
Adaptive-RAG: route by complexity Query Classifier small-LM complexity No retrieval LLM only Single-step one retrieval Iterative multi-step loop CRAG: evaluate, then correct Retrieve Evaluator returns confidence Adequate: refine decompose-recompose Inadequate: web fall back: web search Both are controllers wrapped around an ordinary retriever + LLM.
Two control patterns: Adaptive-RAG spends retrieval effort according to predicted query complexity; CRAG checks retrieval quality and repairs it with web search when the corpus falls short.
বাঁ দিকে Adaptive-RAG প্রশ্নের জটিলতা অনুযায়ী retrieval ছাড়া, এক ধাপ বা বহু ধাপের পথ বেছে নেয়। ডান দিকে CRAG retrieval-এর মান যাচাই করে, অপর্যাপ্ত হলে web search-এ যায়।

Agentic retrieval and its cost

Anthropic distinguishes workflows (LLMs and tools orchestrated through predefined code paths) from agents (the LLM directs its own process and tool use), and recommends starting simple because a single optimized LLM call with retrieval is often enough; it also stresses investing in tool definitions.[34] OpenAI's file_search tool in the Responses API is a hosted retrieval tool the model chooses to invoke, doing semantic and keyword search over vector stores, with max_num_results and metadata filters; its docs do not detail internal query rewriting or ranking.[35]

In Anthropic's multi-agent research system a lead agent spawns parallel subagents that search iteratively. They report agents using about 4x the tokens of chat and multi-agent systems about 15x, token usage explaining 80% of performance variance on a browsing evaluation, and an Opus 4 lead with Sonnet 4 subagents beating single-agent Opus 4 by 90.2% on their internal evaluation.[36]

Lead agent plans, spawns, merges Subagent 1 search + read Subagent 2 search + read Subagent 3 search + read Relative token use (Anthropic report) chat 1x agent ~4x multi-agent ~15x Token use explained 80% of variance on a browsing evaluation (same report). Lead agent fans out to parallel subagents, each iterating on its own searches.
Orchestrator-worker agentic search and its cost: parallel subagents explore more, but token use climbs roughly 4x for an agent and 15x for a multi-agent system relative to chat.
একটি lead agent কয়েকটি subagent-কে সমান্তরালে খুঁজতে পাঠায়। Anthropic-এর রিপোর্ট অনুযায়ী chat-এর তুলনায় agent প্রায় 4 গুণ এবং multi-agent প্রায় 15 গুণ token খরচ করে।
PatternWho decides what to retrieveReported resultCost to watch
IRCoT[29]CoT sentences become queries+11-21 retrieval points, up to +15 F1Several retrieve + generate rounds
Self-RAG[31]Model, via reflection tokens7B/13B beat ChatGPT on listed tasksNeeds a trained model
CRAG[32]Evaluator confidencePlug-and-play over existing RAGEvaluator pass; web search fallback
Adaptive-RAG[33]Small-LM complexity classifierBetter efficiency and accuracy vs adaptive baselinesClassifier errors misroute queries
Multi-agent search[36]Lead agent + subagents+90.2% over single-agent Opus 4 (internal eval)~15x chat tokens

Pitfalls: loops need a hard step limit and a stopping rule, or a confused model keeps searching. Start with single-step retrieval and add iteration only for questions that fail it (a routing idea in the spirit of Adaptive-RAG)[33], in line with Anthropic's advice to start simple[34]. Agentic systems multiply tokens and latency[36], so track cost per answered question.

Research frontier: IRCoT showed that using each reasoning step as the next query lifts retrieval and QA on four multi-hop datasets[29]; Self-RAG trains the model to decide when to retrieve and to critique itself[31]; CRAG adds an evaluator with a web-search fallback[32]; Adaptive-RAG routes by complexity[33]; ReAct interleaves reasoning with actions[30]. Limits: these papers report on specific benchmarks and models, and the Anthropic system numbers come from the vendor's own internal evaluation, not an independent benchmark.[36]
গবেষণার সারকথা: মডেলকে কখন, কী খুঁজতে হবে তা নিজে ঠিক করতে দিলে multi-hop প্রশ্নে ফল ভালো হয়, কিন্তু খরচ ও জটিলতা বাড়ে।

Practice · 1 task

Open any free AI chat: ChatGPT · Claude · Gemini

How practice works
  • A new chat is a fresh conversation with no history. In ChatGPT, Claude, or Gemini, click New chat. It is usually at the top left.
  • Same chat means: keep typing in the chat that is already open. The AI remembers what you sent before.
  • To use a prompt: click Copy, click in the message box of the chat, paste it (Ctrl+V, or press and hold on a phone), and press Enter.
  • AI answers are a little different every time. Your answer does not need to match the example word for word. Look for the same idea.
  • Use made-up data only. Do not paste private information into a chat.

Task 1

Play the search tool for an AI agent

Why this matters

When you build a research agent, some questions need several searches, and each search depends on the last result. The agent must also know when to stop.

  1. Step 1
    New chat

    Copy this prompt, paste it, and press Enter. Do not answer the search yet.

    Prompt
    You are a research agent. You can use one tool.
    
    Tool: search(query) returns a short text from the company wiki.
    
    Rules:
    - Reply with ONLY one line: either SEARCH: <your query> or FINAL: <your answer>.
    - Use at most 4 searches.
    - Use only facts from search results. Do not guess.
    
    Question: In which city does the manager of the team that built the Kestrel payment API work?

    You should see: One line that starts with SEARCH:, for example SEARCH: Kestrel payment API team. It should not say FINAL: yet, because it knows no facts.

    Example answer
    SEARCH: Which team built the Kestrel payment API?

    Your answer will be worded differently.

  2. Step 2
    Same chat

    You are the search tool now. Copy this line, paste it, and press Enter.

    Prompt
    Search result: The Kestrel payment API was built by the Ledger team in 2023.

    You should see: A new search that uses the team name, for example SEARCH: Who manages the Ledger team?

    Example answer
    SEARCH: Who manages the Ledger team?

    Your answer will be worded differently.

  3. Step 3
    Same chat

    Copy this line, paste it, and press Enter.

    Prompt
    Search result: The Ledger team is managed by Amina Rahman.

    You should see: A third search that uses the manager's name, for example SEARCH: In which city does Amina Rahman work?

    Example answer
    SEARCH: In which city does Amina Rahman work?

    Your answer will be worded differently.

  4. Step 4
    Same chat

    Copy this line, paste it, and press Enter. Count the searches the AI made.

    Prompt
    Search result: Amina Rahman works in the Chattogram office.

    You should see: FINAL: with the city Chattogram. The AI made 3 searches.

    Example answer
    FINAL: Chattogram

    Your answer will be worded differently.

What just happened

No single search could answer this question. Each search needed a fact from the search before it: the team, then the manager, then the city. A question like this is called multi-hop. In agentic retrieval, the AI chooses the next search itself and decides when to stop. The price is more AI calls.

You are done when You have a FINAL: answer, Chattogram, after 3 searches.

Check yourselfWhy can one single search not answer this question well?Show answer

The second and third searches need facts that only the earlier searches found, so no single query holds everything. The AI has to choose each next search, and that costs more AI calls.

Try a harder versionStart a new chat with the prompt from step 1, but change the rule to Use at most 2 searches. Play the tool again. What does the AI do when it cannot finish in time? Is that what you want?

Watch · 3 videos

06

Reranking: Cohere, BGE, Voyage

First-stage retrieval (BM25, dense vectors, or both) is built to be fast over a huge corpus, so it scores each document without ever looking at the query and document together. A reranker is a second-stage model that does look at them together: it takes the short candidate list and re-orders it by a more accurate relevance score. Sentence-Transformers documents this as the recommended pattern: a bi-encoder retrieves top-k candidates, then a cross-encoder re-ranks them.[43] Hosted options include Cohere Rerank and Voyage rerankers; open weights include BAAI's BGE rerankers.[44][45][46]

Three ways to score a (query, document) pair

Bi-encoder interact only at the very end query document encoder encoder vec(q) vec(d) cosine(vec q, vec d) doc vectors precomputed offline Cross-encoder full joint attention over the pair query document one transformer reads [q; d] query tokens attend to doc tokens relevance score no reusable vector; run per pair ColBERT (late interaction) token vectors, MaxSim at the end query document encoder encoder MaxSim: sum of row maxima doc token vectors precomputed offline
Where the query and document meet: never until the final cosine (bi-encoder), inside every attention layer (cross-encoder), or at token level via MaxSim after independent encoding (ColBERT).
তিন ধরনের মডেলে query ও document কোথায় মিলিত হয় তা দেখানো হয়েছে: bi-encoder-এ একদম শেষে cosine-এ, cross-encoder-এ পুরো attention-এর ভেতরে একসাথে, আর ColBERT-এ আলাদা encode করে token স্তরে MaxSim দিয়ে।

Bi-encoder vs cross-encoder vs late interaction

Per the Sentence-Transformers docs, a bi-encoder embeds each text independently and compares vectors with cosine similarity, while a cross-encoder processes the pair jointly and outputs a score; it produces no reusable embedding, and it is more accurate but slower.[43] The same docs give a concrete cost example: clustering 10,000 sentences with a cross-encoder needs about 50 million pairwise comparisons, roughly 65 hours.[43] BGE describes its reranker the same way: it takes the question and document as input and directly outputs a similarity, not an embedding, and a full system is a first-stage retriever followed by the reranker.[47]

ColBERT sits in between. Query and document are encoded independently by BERT, so document representations can be computed offline, and the score is MaxSim: for each query token take the maximum cosine against the document's token vectors, then sum.[48] The paper reports about 170x faster and 14,000x fewer FLOPs per query than BERT rerankers at competitive quality on MS MARCO and TREC CAR, and shows it used either as a reranker over BM25 top-1000 or end-to-end with FAISS.[48]

MaxSim worked example (illustrative cosine values, not measured) query / doc flight fares to dhaka cheap row max cheap tickets dhaka 0.10 0.20 0.00 0.10 0.80 0.80 0.60 0.70 0.10 0.10 0.20 0.70 0.00 0.10 0.10 0.90 0.10 0.90 sum = 2.40 How the score is built 1. each query token scans all doc tokens 2. keep only the best match per row (green) 3. add the maxima: 0.80 + 0.70 + 0.90 = 2.40 doc token vectors can be stored ahead of time, so no joint transformer pass per pair
MaxSim on a toy grid: every query token keeps only its best-matching document token, and the per-token maxima are summed into the document score.
MaxSim-এর একটি ছোট উদাহরণ: প্রতিটি query token শুধু document-এর সবচেয়ে মিলে যাওয়া token-টির মান রাখে, তারপর সেই সর্বোচ্চ মানগুলো যোগ করে document-এর score হয়। সংখ্যাগুলো উদাহরণের জন্য বানানো।

The two-stage funnel

stage 1: recall stage 2: precision generation whole corpus ANN / BM25 / hybrid top-N candidates cross-encoder re-orders top-k chunks to LLM cheap per doc: precomputed index scores the whole corpus tune for recall one forward pass per (query, doc) pair cost grows linearly with N tune for precision LLM sees only what survives fewer, better chunks less noise in the prompt Sourced upper bounds: Cohere recommends max 1,000 docs per request; ColBERT reranks BM25 top-1000. Typical N of 50-100 and k of 5-10 are common practice (unsourced), not vendor guidance.
Two-stage funnel: a cheap first stage maximises recall over the whole corpus, then a reranker spends its per-pair cost only on the N survivors before the top-k go to the LLM.
দুই ধাপের funnel: প্রথম ধাপ সস্তা উপায়ে পুরো corpus থেকে প্রার্থী আনে (recall), দ্বিতীয় ধাপে reranker শুধু সেই N-টি প্রার্থীর ওপর বেশি খরচ করে নিখুঁতভাবে সাজায় (precision), এবং সেরা top-k LLM-এর কাছে যায়।

The sizing of N and k is an engineering choice you tune against your own evaluation set. The vendor and paper limits above are hard ceilings; the commonly quoted "retrieve 50 to 100, keep 5 to 10" is practitioner folklore that none of the pages cited here state, so treat it as a starting point, not a rule. Because cross-encoder cost is linear in N, doubling N roughly doubles reranking latency and cost (this follows from one forward pass per pair).

Hosted and open rerankers: API facts

OptionAccessDocumented limits and parametersNotes
Cohere Rerankv2 rerank API; model, query, documents requiredUp to 1,000 documents recommended; optional top_n; max_tokens_per_doc default 4,096[44]Rerank v3.5: 4096-token context, multilingual; v2 API requires model and replaced max_chunks_per_doc with max_tokens_per_doc[49]
Voyage rerankersPOST https://api.voyageai.com/v1/rerankquery, documents (up to 1,000; 600K total tokens), top_k, truncation[45]Described as cross-encoders; docs list rerank-3 / rerank-3-lite at 32K tokens. That page may lag newer releases, so check model names before use[45]
BAAI bge-reranker-v2-m3Open weights on Hugging Face, run locallyOutputs a relevance score you can map to [0, 1] with a sigmoid; use_fp16 speeds inference with slight degradation[46]Multilingual, built on bge-m3[46]
# Hosted: Cohere Rerank (v2 API)
import cohere
co = cohere.ClientV2()          # reads the API key from the environment
docs = ["Refund policy: 30 days.", "Shipping takes 3-5 days.", "Refunds need a receipt."]
resp = co.rerank(
    model="rerank-v4.0-pro",       # model id from the API reference example; check the current list
    query="how do I get my money back?",
    documents=docs,                  # docs recommends at most 1,000
    top_n=2,
)
for r in resp.results:
    print(r.index, r.relevance_score, docs[r.index])

# Local: BGE reranker (FlagEmbedding package)
from FlagEmbedding import FlagReranker
reranker = FlagReranker("BAAI/bge-reranker-v2-m3", use_fp16=True)
pairs = [["how do I get my money back?", d] for d in docs]
scores = reranker.compute_score(pairs, normalize=True)   # sigmoid -> [0, 1]
ranked = sorted(zip(scores, docs), reverse=True)[:2]

Pitfalls. Scores from different rerankers are not on a common scale, so pick any score threshold per model and per corpus. Long documents get truncated or cut by token limits (for example Cohere's max_tokens_per_doc, Voyage's truncation), so rerank chunks, not whole files. A reranker can only re-order what stage 1 returned: if the right passage is not in the top-N, no reranker can recover it.

Research frontier: ColBERT showed that you can keep cross-encoder-like token-level matching while precomputing document representations: about 170x faster and 14,000x fewer FLOPs per query than BERT rerankers, at competitive quality on MS MARCO and TREC CAR.[48] Qdrant exposes the same idea operationally: prefetch cheap dense candidates, then rescore the top candidates with a multivector (ColBERT-style) model.[21] Limits: late interaction stores one vector per token, which costs index space, and the speed figures are from the paper's own setup, not your hardware.
গবেষণার সারকথা: ColBERT প্রমাণ করেছে document-এর token vector আগে থেকে হিসাব করে রাখলে cross-encoder-এর কাছাকাছি মান অনেক কম খরচে পাওয়া যায়; তবে প্রতি token-এ একটি vector রাখতে index-এর জায়গা বেশি লাগে।

Practice · 1 task

Open any free AI chat: ChatGPT · Claude · Gemini

How practice works
  • A new chat is a fresh conversation with no history. In ChatGPT, Claude, or Gemini, click New chat. It is usually at the top left.
  • Same chat means: keep typing in the chat that is already open. The AI remembers what you sent before.
  • To use a prompt: click Copy, click in the message box of the chat, paste it (Ctrl+V, or press and hold on a phone), and press Enter.
  • AI answers are a little different every time. Your answer does not need to match the example word for word. Look for the same idea.
  • Use made-up data only. Do not paste private information into a chat.

Task 1

Let an AI reorder search results

Why this matters

When you build a help-centre search, the first search is fast but often puts the wrong passage on top. A reranker is a second AI step that reads the question with each passage and reorders them.

  1. Step 1
    New chat

    Copy this prompt, paste it, and press Enter. Compare the new order with the order in the prompt.

    Prompt
    You are a reranker. Read the query and the 6 passages below. They are in the order a keyword search gave them. Give each passage a relevance score from 0 to 10 for the query, with a 1-sentence reason. Then give the new order, best first.
    
    Query: Can I get my money back if the course is bad?
    
    1. Our course gets very good reviews, and the money you spend on learning is well spent.
    2. Please get back to us within 24 hours if the course website is down.
    3. Refund policy: if you are not satisfied, request a refund within 30 days. We return the full amount to your card.
    4. Course prices include tax. Money transfers from other banks take 2 days.
    5. Refunds need a receipt or an order number.
    6. Bad weather may delay in-person classes.

    You should see: Passage 3 (the refund policy) moves to the top. Passage 5 gets a middle score. Passages 1, 2, 4, and 6 get low scores.

    Example answer
    1. Score 2. It praises the course but says nothing about refunds.
    2. Score 1. It is about a website problem.
    3. Score 10. It says you can get a full refund in 30 days.
    4. Score 2. It is about prices and bank transfers.
    5. Score 6. It is about refunds, but gives only what you need to ask.
    6. Score 0. It is about weather.
    
    New order: 3, 5, 1, 4, 2, 6

    Your answer will be worded differently.

  2. Step 2
    New chat

    Copy this prompt, paste it, and press Enter. It has only the top 2 passages of the same list.

    Prompt
    You are a reranker. Read the query and the 2 passages below. They are the top 2 results of a first search. Give each passage a relevance score from 0 to 10 for the query, with a 1-sentence reason. Then say if these 2 passages are enough to answer the query.
    
    Query: Can I get my money back if the course is bad?
    
    1. Our course gets very good reviews, and the money you spend on learning is well spent.
    2. Please get back to us within 24 hours if the course website is down.

    You should see: Both scores are low, and the AI says these passages are not enough. The refund policy is not in the list.

    Example answer
    1. Score 2. It praises the course but does not say how to get money back.
    2. Score 1. It is about a website problem.
    These 2 passages are not enough. Neither one talks about refunds.

    Your answer will be worded differently.

What just happened

The first search matched single words like money and course. The reranker reads the question and each passage together, so it sees that passage 3 really answers it. But a reranker can only reorder the passages it gets. In step 2 the right passage was not in the list, so no reranker could find it.

You are done when You have a new order with passage 3 on top, and a low-score answer for the list of 2.

Check yourselfWhat can a reranker do, and what can it not do?Show answer

It can reorder the passages it receives and put the best one on top, because it reads the question and the passage together. It cannot find a passage that the first search did not return.

Try a harder versionIn a new chat, add a seventh passage at the end of the step 1 prompt: 7. Money-back requests are handled by billing. Where does the AI put it? Is its score fair?

Watch · 3 videos

07

Metadata filtering & structured retrieval

Real questions mix meaning with constraints: "refund policy for enterprise customers in 2025" or "science-fiction movies after 2000 with rating above 8". Vector similarity alone cannot enforce year > 2000, so production retrieval attaches metadata to each chunk and filters on it, and for questions about counts or totals it hands the job to a database. The hard part is that a filter interacts badly with approximate nearest-neighbour (ANN) indexes, and each vector database handles that differently.[50][51]

Pre-filter, post-filter, and filtering inside the graph

Pre-filter restrict the index first 1. keep matching points drop the rest of the graph 2. ANN on what is left links to others are gone k results, but accuracy drops broken HNSW links at scale Post-filter search first, filter after 1. ANN top-k, no filter k = 10 nearest overall 2. discard non-matches a strict filter kills most fewer than k, maybe zero large part of results discarded In-graph filter check the filter while walking 1. allow-list from index metadata / payload index 2. walk the full graph keep only allowed ids k matching results cost: more nodes visited Terms vary by vendor: Weaviate calls its allow-list approach "pre-filtering", which behaves like the in-graph lane here.
Three strategies for combining an ANN search with a metadata filter and how each one fails or costs extra.
ANN search-এর সাথে metadata filter মেলানোর তিনটি পদ্ধতি: আগে filter করলে graph-এর link ভেঙে accuracy কমে, পরে filter করলে k-এর চেয়ে কম (এমনকি শূন্য) ফল আসতে পারে, আর graph চলার সময় filter দেখলে k-টি ঠিক ফল মেলে কিন্তু বেশি node ঘুরতে হয়।

Qdrant's filtering guide states the trade-off plainly: at scale, pre-filtering "breaks too many links in the HNSW graph, causing lower accuracy", while post-filtering with a low-cardinality filter means you often end up discarding a large portion of the results.[50] Its answer is filterable HNSW, which adds links between the leftover points, plus a payload index that lets it switch from HNSW to a payload-index search when the filter's cardinality is low.[50] A newer Qdrant post describes in-place filtering during HNSW traversal, filterable HNSW with extra index-time edges, and ACORN as a query-time repair, with a query planner choosing the path from estimated selectivity.[52]

Filter applied to a plain graph Extra links reconnect matches every neighbour of a match is filtered out, so the search cannot hop between matches links between matching points (filterable HNSW) or query-time expansion (ACORN) keep it connected matches filter filtered out extra link (simplified sketch, not exact algorithm)
Why a strict filter hurts HNSW: matching points are only reachable through filtered-out ones. Extra links between matches, or query-time expansion, restore connectivity.
কড়া filter দিলে HNSW graph-এ মিলে যাওয়া node-গুলো শুধু বাদ পড়া node-এর মধ্য দিয়ে পৌঁছানো যায়, তাই খোঁজ আটকে যায়। মিলে যাওয়া node-দের মধ্যে বাড়তি link (filterable HNSW) বা query-time expansion (ACORN) যোগ করলে graph আবার সংযুক্ত হয়। ছবিটি সরলীকৃত স্কেচ।

What each engine does

EngineBehaviour with filtersNotes
WeaviatePre-filtering: the inverted index builds an allow-list handed to HNSW, which only adds allowed ids to results; post-filtering would give unpredictable result counts, possibly zero[51]ACORN is the default from v1.34+; a flat-search cutoff switches to brute force on very restrictive filters (a ~15% threshold is mentioned)[51]
QdrantFilterable HNSW (extra links between leftover points), payload index, in-place filtering during traversal[50][52]Query planner picks the path by estimated selectivity; ACORN as a query-time repair[52]
ElasticsearchA filter inside the knn clause applies during approximate kNN, so k matching docs come back; a filter outside it is a post-filter and can return fewer than k[53]Filters can slow HNSW (more nodes explored); mitigated by a per-segment brute-force fallback and the acorn heuristic[53]
PineconeMetadata filter on queries with operators $eq $ne $gt $gte $lt $lte $in $nin $exists $and $or $not[54]$in/$nin accept at most 10,000 values; text-match operators ($match_phrase, $match_all, $match_any) work on document schemas only[54]

Two silent failures. (1) Post-filtering returns fewer than k results, or none, and a downstream LLM then answers from nothing; check result counts, not just scores. (2) Filtering on a field with no index forces scans or fallbacks, so index the fields you filter on (Qdrant's payload index, for example, is what enables its low-cardinality switch).[50]

Self-querying: let the LLM write the filter

Users type natural language, not filter JSON. A self-querying retriever has an LLM parse the request into a semantic query plus a structured metadata filter, then runs both against embeddings and metadata; Elastic's example is "science fiction movies released after 2000 with rating above 8".[55] The LLM must be told the available fields, their types, and the allowed operators, and its output should be validated against that schema before it reaches the database.

# 1) LLM turns text into {query, filter}  (llm_json = your structured-output call)
parsed = llm_json(
    "Fields: genre(str), year(int), rating(float). "
    "Return JSON {query, filter} for: sci-fi movies after 2000 rated above 8"
)
# parsed = {"query": "science fiction",
#           "filter": {"$and": [{"genre": {"$eq": "sci-fi"}},
#                               {"year": {"$gt": 2000}}, {"rating": {"$gt": 8}}]}}

# 2a) Pinecone: filter goes straight into the query
res = index.query(vector=embed(parsed["query"]), top_k=5,
                  filter=parsed["filter"], include_metadata=True)

# 2b) Elasticsearch: put the filter INSIDE knn so k matching docs return
body = {"knn": {
    "field": "embedding", "query_vector": embed(parsed["query"]),
    "k": 5, "num_candidates": 50,
    "filter": {"range": {"year": {"gt": 2000}}},
}}

When the question is really a database query

Counts, sums, joins and "top customer by revenue" are exact computations, not similarity problems; the right tool is text-to-SQL, where an LLM writes SQL against a schema. The standard benchmark is Spider (Yu et al., EMNLP 2018), built so that test databases have schemas the model never saw.[56]

Spider questions
10,181
Unique SQL queries
5,693
Databases
200
Domains
138
Best model in 2018
9.7%
user question router LLM prompt or rules Exact or aggregate count, sum, join, group by text-to-SQL run on the relational DB Fuzzy meaning only no hard constraints vector or hybrid search no filter needed Meaning plus attributes sci-fi after 2000, rating > 8 self-query LLM emits {query, filter} Needs facts and documents numbers plus policy text run both, then merge SQL rows + passages routing tree = design synthesis, not a sourced standard
A routing decision tree: exact or aggregate questions go to text-to-SQL, fuzzy ones to vector search, meaning plus attributes to self-query filtered search, and mixed ones to both.
প্রশ্নের ধরন দেখে পথ বেছে নেওয়ার গাছ: গণনা বা যোগফলের মতো নিখুঁত প্রশ্ন যায় text-to-SQL-এ, অস্পষ্ট অর্থের প্রশ্ন vector search-এ, অর্থ ও শর্ত মেশানো প্রশ্ন self-query filtered search-এ, আর মিশ্র প্রশ্নে দুটোই চলে। এই গাছটি ডিজাইনের সারসংক্ষেপ, কোনো উৎস থেকে নেওয়া মানদণ্ড নয়।

Spider's 2018 figure of 9.7% for the best model is a historical baseline showing how hard cross-domain SQL generation was then, not a statement about current models.[56] In practice, give the model the schema, validate generated SQL (read-only credentials, allow-listed tables, row limits) and log every query.

Research frontier: The ACORN paper (arXiv 2403.04871) frames the problem: pre-filtering scans the filtered set exhaustively and scales poorly, post-filtering has to expand the search when the predicate is uncorrelated with vector similarity. ACORN modifies HNSW to traverse the predicate subgraph, and claims 2 to 1,000 times higher throughput at fixed recall.[57] Elasticsearch and Weaviate have since exposed ACORN-style handling in their docs.[53][51] Limits: the throughput range is the paper's own claim across its workloads, and how well any engine copes still depends on filter selectivity and correlation with the query.
গবেষণার সারকথা: ACORN HNSW-কে এমনভাবে বদলায় যাতে filter-এ মিলে যাওয়া অংশের graph ধরে চলা যায়; paper-এর দাবি একই recall-এ ২ থেকে ১,০০০ গুণ বেশি throughput, তবে ফল নির্ভর করে filter কতটা কড়া তার ওপর।

Practice · 1 task

Open any free AI chat: ChatGPT · Claude · Gemini

How practice works
  • A new chat is a fresh conversation with no history. In ChatGPT, Claude, or Gemini, click New chat. It is usually at the top left.
  • Same chat means: keep typing in the chat that is already open. The AI remembers what you sent before.
  • To use a prompt: click Copy, click in the message box of the chat, paste it (Ctrl+V, or press and hold on a phone), and press Enter.
  • AI answers are a little different every time. Your answer does not need to match the example word for word. Look for the same idea.
  • Use made-up data only. Do not paste private information into a chat.

Task 1

Filter before or after the search

Why this matters

When you build a movie or product search, users add rules like after 2000 or under 500 taka. If your system applies the rule at the wrong time, it can return nothing, and the AI behind it may start to guess.

  1. Step 1
    New chat

    Copy this prompt, paste it, and press Enter. Write down how many results each method gives.

    Prompt
    A movie search has a similarity score for the query "space survival". The user wants the top 3 movies with year greater than 2000.
    
    Catalog (title, year, rating, similarity):
    Orbit Lost, 1998, 8.4, 0.92
    Red Dust, 1995, 7.9, 0.90
    Moon Base Zero, 1999, 8.1, 0.88
    Silent Ship, 2004, 8.6, 0.80
    Star Harbor, 2012, 7.2, 0.78
    Lunar Mail, 2016, 8.3, 0.75
    Deep Orbit, 2019, 8.8, 0.70
    Dust Planet, 2001, 6.9, 0.66
    
    Method 1 (post-filter): take the top 3 by similarity first, then remove the movies with year 2000 or earlier.
    Method 2 (pre-filter): remove the movies with year 2000 or earlier first, then take the top 3 by similarity.
    
    Show the result of each method. Say how many results each method returns.

    You should see: Method 1 gives 0 results. Method 2 gives 3 results: Silent Ship, Star Harbor, and Lunar Mail.

    Example answer
    Method 1 (post-filter): The top 3 by similarity are Orbit Lost (1998), Red Dust (1995), and Moon Base Zero (1999). All three are 2000 or earlier, so all are removed. Result: 0 movies.
    
    Method 2 (pre-filter): Movies after 2000: Silent Ship (0.80), Star Harbor (0.78), Lunar Mail (0.75), Deep Orbit (0.70), Dust Planet (0.66). The top 3 are Silent Ship, Star Harbor, and Lunar Mail. Result: 3 movies.

    Your answer will be worded differently.

  2. Step 2
    Same chat

    Copy this prompt, paste it, and press Enter. See what the bot says when the search result is empty.

    Prompt
    Now you are the support bot. Answer the user using ONLY the results of Method 1. If there are no results, tell the user what happened.
    
    User: Recommend one space survival movie made after 2000.

    You should see: A good bot says it found no matching movie. A weaker bot may name a movie from memory, which is a made-up answer.

    Example answer
    Sorry, the search found no space survival movies made after 2000, so I cannot recommend one right now.

    Your answer will be worded differently.

  3. Step 3
    Same chat

    Copy this prompt, paste it, and press Enter. Compare the answer with step 2.

    Prompt
    Answer the same user again, now using ONLY the results of Method 2.

    You should see: A real recommendation, most likely Silent Ship (2004), because it has the highest similarity.

    Example answer
    I recommend Silent Ship (2004). It is the closest match to space survival, and it has a rating of 8.6.

    Your answer will be worded differently.

What just happened

A post-filter searches first and removes items after. If the best matches fail the rule, nothing is left. A pre-filter removes the items first and then takes the top 3, so you get 3 results when 3 exist. An empty list is dangerous, because the AI behind it may guess. Check how many results come back, not only how good they look.

You are done when You have 0 results for Method 1, 3 results for Method 2, and two different bot answers.

Check yourselfWhy is an empty result list from a post-filter dangerous in a RAG system?Show answer

The search took the top items before the filter, so the matching items were never in the list. The AI then has nothing to use and may guess. A pre-filter returns the items that match the rule, so check the number of results, not only the scores.

Try a harder versionAsk the AI: Turn this request into JSON with the keys query and filter: sci-fi movies after 2000 with rating above 8. Which parts go into the query, and which into the filter?

Watch · 2 videos

08

GraphRAG & knowledge-graph augmentation

Vector search returns chunks that look like the query. It struggles with "global" questions such as "what are the main themes in this dataset?", which no single chunk answers. Microsoft's GraphRAG reframes this as query-focused summarization: an LLM builds an entity knowledge graph, pre-generates summaries for communities of related entities, and merges partial answers from those summaries at query time.[37] Lighter variants (LightRAG[38], HippoRAG[39]) and graph-database tooling (Neo4j[40]) target the same gap with different cost profiles.

The documented indexing pipeline segments documents into TextUnits, extracts entities, relationships and claims, runs Leiden hierarchical clustering, and summarizes each community. Query modes include Global search (reasoning over community summaries), Local search (expanding from matched entities to their neighbours) and DRIFT (local search enriched with community context). The docs recommend prompt tuning.[41] The original paper tested on corpora of about 1M tokens and reported gains in answer comprehensiveness and diversity over standard RAG.[37]

Documents raw corpus TextUnits segmented text LLM extract entities, relationships, claims Entity graph nodes + edges Leiden hierarchical communities Community summaries LLM-written LLM calls: index-time cost LLM calls: index-time cost Microsoft's README: indexing can be expensive, so start small.
GraphRAG indexing: the documented pipeline turns raw text into a graph, clusters it with Leiden, and has an LLM summarize every community. The LLM-heavy steps dominate cost.
GraphRAG indexing-এ text ছোট ছোট TextUnit হয়, LLM entity ও সম্পর্ক বের করে graph বানায়, Leiden দিয়ে community ভাগ হয় এবং প্রতিটির summary লেখা হয়। LLM-নির্ভর ধাপগুলোতেই বেশি খরচ।
Local search: entity to neighbours start at matched entities, expand to neighbours + text Global search: community summaries community 1 summary community 2 summary community N summary Merge partial answers into one reply answer per summary, then merge the partial answers
Same graph, two query paths: local search walks outward from specific entities; global search answers from pre-written community summaries and merges the partial answers.
একই graph-এ দুই ধরনের খোঁজ: local search নির্দিষ্ট entity থেকে প্রতিবেশীদের দিকে যায়; global search আগে থেকে লেখা community summary থেকে আংশিক উত্তর তৈরি করে তা মিলিয়ে একটি উত্তর দেয়।

Lighter alternatives: LightRAG, HippoRAG, Neo4j

  • LightRAG builds graph structures into both indexing and retrieval, with dual-level (low-level and high-level) retrieval, combines graph and vector retrieval, supports incremental updates, and is open source.[38]
  • HippoRAG is hippocampus-inspired: an LLM, a knowledge graph and Personalized PageRank. It reports up to 20% better multi-hop QA, and single-step retrieval comparable to or better than iterative retrieval while 10-30x cheaper and 6-13x faster.[39]
  • Neo4j's neo4j-graphrag Python package is Neo4j's official GraphRAG library (successor to neo4j-genai): a knowledge-graph builder, retrievers (VectorRetriever and others), and a GraphRAG class. It needs Neo4j 5.18.1 or later and supports LLM providers including OpenAI, Anthropic, Cohere, Mistral, Vertex AI and Ollama, plus external vector stores such as Weaviate, Pinecone and Qdrant.[40]
# neo4j-graphrag: vector retrieval wrapped by a RAG class (sketch; check the docs for exact setup)
from neo4j import GraphDatabase
from neo4j_graphrag.retrievers import VectorRetriever
from neo4j_graphrag.generation import GraphRAG

driver = GraphDatabase.driver("neo4j://localhost:7687", auth=("neo4j", "password"))
retriever = VectorRetriever(driver, index_name="chunk_embeddings", embedder=my_embedder)
rag = GraphRAG(retriever=retriever, llm=my_llm)       # my_embedder / my_llm: provider wrappers
answer = rag.search(query_text="Which suppliers connect to both plants?")

When graphs help versus cost (synthesis)

The following is my synthesis from the sources above, not a measured result. Global, whole-corpus sensemaking questions and multi-hop questions about connected entities are where the graph papers report their gains[37][39]. The cost is LLM extraction and summarization at index time, which Microsoft itself warns can be expensive[42]. LightRAG and HippoRAG emphasize incremental updates and cheaper retrieval respectively[38][39]. For simple point lookups, a plain vector or hybrid pipeline is the natural default.

Question type Suggested approach (synthesis) Point lookup (one fact) Vector / hybrid RAG Multi-hop entity question Local search / HippoRAG-style Global theme over whole corpus Community summaries (global) Index-time cost (qualitative) vector RAG LightRAG / HippoRAG GraphRAG (full) bar lengths: my sketch, not measured Synthesis from the cited sources; validate on your own corpus before committing to a graph index.
Synthesis, not a benchmark: match the retrieval structure to the question type, and expect index-time cost to rise as you add graph extraction and summarization.
এটি উৎসগুলো থেকে নেওয়া আমার সংশ্লেষণ, মাপা ফল নয়। সরল প্রশ্নে vector RAG, সংযুক্ত entity-র প্রশ্নে local বা HippoRAG-ধাঁচ, আর পুরো corpus-এর থিমে community summary মানায়; graph যত জটিল, index-এর খরচও তত বেশি।
Vector / hybrid RAGGraphRAG (Microsoft)LightRAG / HippoRAG
StrengthPoint lookups, simple pipelineGlobal questions via community summaries[37]Incremental updates[38]; cheap single-step multi-hop[39]
Index stepChunk + embedExtract entities/relations, Leiden, summarize[41]Graph from LLM extraction (see papers)
Main costEmbedding calls"Can be an expensive operation"[42]Lower per the papers' claims; verify

Pitfalls: do not build a graph before you know your failing question types. Start on a small slice of the corpus, as Microsoft advises[42], tune the extraction prompts[41], and compare against a plain hybrid baseline. Extraction quality (missed or merged entities) bounds everything downstream, and community summaries go stale when documents change unless you re-index.

Research frontier: Edge et al. recast RAG over a whole corpus as query-focused summarization and showed gains in comprehensiveness and diversity on roughly 1M-token datasets[37]. LightRAG added dual-level retrieval and incremental updates[38], and HippoRAG used Personalized PageRank over an LLM-built graph to match or beat iterative retrieval at far lower cost[39]. Limits: GraphRAG's own authors tested at about 1M tokens, indexing is costly[42], and the lighter methods' cost claims come from their own papers.
গবেষণার সারকথা: graph যোগ করলে পুরো corpus-এর থিম ও সংযুক্ত entity-র প্রশ্নে সুবিধা মেলে, তবে index তৈরির LLM খরচ বড় বিবেচ্য।

Practice · 1 task

Open any free AI chat: ChatGPT · Claude · Gemini

How practice works
  • A new chat is a fresh conversation with no history. In ChatGPT, Claude, or Gemini, click New chat. It is usually at the top left.
  • Same chat means: keep typing in the chat that is already open. The AI remembers what you sent before.
  • To use a prompt: click Copy, click in the message box of the chat, paste it (Ctrl+V, or press and hold on a phone), and press Enter.
  • AI answers are a little different every time. Your answer does not need to match the example word for word. Look for the same idea.
  • Use made-up data only. Do not paste private information into a chat.

Task 1

Build a tiny knowledge graph with AI

Why this matters

When you build a chatbot over many documents, users ask big questions like what are the main risks? No single piece of text answers that. GraphRAG first builds a map of the facts, then answers from the map.

  1. Step 1
    New chat

    Copy this prompt, paste it, and press Enter. Read the lines. Check if one thing appears under two names.

    Prompt
    Read the text and list the things (entities) and how they are linked (relations). Output only lines in this format: Entity A -> relation -> Entity B. Write nothing else.
    
    Text: Dhaka Plant makes motor housings. Chattogram Plant makes gearboxes. Delta Steel supplies steel to Dhaka Plant. Delta Steel Ltd. also supplies steel to Chattogram Plant. Orion Bolts supplies bolts to Dhaka Plant only. Nova Paints supplies paint to Chattogram Plant. Both plants ship to Rahim Motors, a bus maker. A strike at Delta Steel in June delayed deliveries to both plants. Orion Bolts is owned by Karim Holdings, which also owns Nova Paints.

    You should see: About 10 to 12 lines like Delta Steel -> supplies steel to -> Dhaka Plant. Some models keep Delta Steel and Delta Steel Ltd. as two things. That is a mistake.

    Example answer
    Dhaka Plant -> makes -> motor housings
    Chattogram Plant -> makes -> gearboxes
    Delta Steel -> supplies steel to -> Dhaka Plant
    Delta Steel -> supplies steel to -> Chattogram Plant
    Orion Bolts -> supplies bolts to -> Dhaka Plant
    Nova Paints -> supplies paint to -> Chattogram Plant
    Dhaka Plant -> ships to -> Rahim Motors
    Chattogram Plant -> ships to -> Rahim Motors
    Delta Steel -> had a strike in -> June
    Karim Holdings -> owns -> Orion Bolts
    Karim Holdings -> owns -> Nova Paints

    Your answer will be worded differently.

  2. Step 2
    Same chat

    Copy this prompt, paste it, and press Enter. Write down how many communities you get.

    Prompt
    Now group the entities into communities of closely linked entities. Give each community a name and a one-sentence summary. Output only the communities.

    You should see: 2 to 4 groups, each with a name and one sentence, for example Delta Steel supply and Karim Holdings group.

    Example answer
    1. Delta Steel supply: Delta Steel supplies steel to both plants, and a June strike delayed both.
    2. Karim Holdings group: Karim Holdings owns Orion Bolts and Nova Paints, which supply bolts and paint.
    3. Plants and customer: Dhaka Plant and Chattogram Plant ship to Rahim Motors.

    Your answer will be worded differently.

  3. Step 3
    Same chat

    Copy this prompt, paste it, and press Enter. Compare the answer with the text from step 1.

    Prompt
    Using ONLY the community summaries above, answer this question in 3 sentences: What are the main risks and themes across this whole supplier network?

    You should see: 3 sentences about themes, for example one steel supplier for both plants, one owner for two suppliers, and one customer. No single sentence of the text says this.

    Example answer
    The network depends on Delta Steel, which supplies both plants, and its June strike delayed both. Karim Holdings owns two other suppliers, so one problem there could hit both bolts and paint. Both plants ship to one customer, Rahim Motors, so a delay reaches it.

    Your answer will be worded differently.

What just happened

You built the two first parts of GraphRAG by hand. First the AI turned text into a knowledge graph (things and the links between them). Then it grouped the graph into communities and wrote a summary of each. The last answer came from these summaries, so it could name themes that no single sentence holds.

You are done when You have a list of links, 2 to 4 community summaries, and a 3-sentence answer about the whole network.

Check yourselfWhy does GraphRAG write community summaries before the user asks, and what is the cost?Show answer

A big question like what are the main themes? has no single matching piece of text, so GraphRAG writes summaries of connected groups in advance and answers from them. The cost is many AI calls, and a mistake in the graph, like a missed or merged name, harms every later answer.

Try a harder versionOpen a new chat. Send only the first two sentences of the text from step 1, and ask the question from step 3. How much worse is the answer, and why?

Watch · 2 videos

09

Document parsing pipelines: Unstructured, LlamaParse, Docling

A RAG system is only as good as the text it indexes, and most enterprise knowledge sits in PDFs, scans, slide decks and spreadsheets, not clean strings. A parsing pipeline turns those files into structured elements (headings, paragraphs, tables, figures) and then into Markdown or JSON that a chunker can split sensibly. Three widely used options are Docling (IBM Research, MIT-licensed)[58], the Unstructured open-source partitioner[59] and LlamaParse from LlamaIndex[60].

The canonical pipeline

generic flow; Docling's stage names shown underneath PDF / scan born-digital or image Layout analysis regions, reading order OCR + tables text, cell structure Markdown / JSON typed elements Chunks then embed + index Docling: DocLayNet Docling: TableFormer DoclingDocument export The quality of every downstream step (embedding, retrieval, citation) is capped by this stage.
A parser detects page layout, recovers text (with OCR for scans) and table structure, emits structured Markdown or JSON, and only then is the result chunked.
PDF থেকে প্রথমে layout বোঝা হয়, তারপর OCR ও table structure বের করা হয়, শেষে structured Markdown/JSON হয়ে chunk-এ ভাগ হয়। এই ধাপ দুর্বল হলে পরের সব ধাপের মান নেমে যায়।

Docling, Unstructured, LlamaParse side by side

The table below contains only what each project's own documentation or paper states; it is not a benchmark.

DoclingUnstructured (open source)LlamaParse
OriginIBM Research; MIT-licensed; LF AI & Data project[58][61]Open-source partitioning library[59]LlamaIndex parsing service[60]
InputsPDF, DOCX, PPTX, XLSX, HTML, EPUB, audio, email[61]Strategy-based PDF partitioning (documented here)[59]PDFs, scans, tables, charts; 130+ file types[60]
Layout / tablesDocLayNet layout model, TableFormer table structure[58]hi_res uses a layout model; yields Table elements[59]Layout-aware OCR; table extraction preserving cell structure; chart parsing[60]
OCROCR for scans; VLM support (GraniteDocling)[61]ocr_only strategy for scans[59]Built into the layout-aware OCR[60]
OutputUnified DoclingDocument; export to Markdown, HTML, JSON[61]Typed elements: Title, NarrativeText, ListItem, Table, Header, Footer, PageBreak, Image[59]Markdown, text or JSON[60]
Control knobsRuns locally / on-prem; LangChain and LlamaIndex integrations[61]Strategy choice: auto, fast, hi_res, ocr_only[59]Tiers (Fast, Cost Effective, Agentic, Agentic Plus); natural-language instructions and schemas[60]

Choosing a strategy in Unstructured

strategy="auto" default; picks for you else: text layer tables needed else: no text fast pdfminer text extraction; needs selectable text hi_res layout-model based; best when element classification matters; struggles with multi-column ocr_only scanned documents or files with no text layer Elements out Title, NarrativeText, ListItem, Table, Header, Footer, PageBreak, Image
Per the Unstructured docs, auto picks hi_res when tables are needed, otherwise fast or ocr_only; each strategy returns the same family of typed elements.
auto strategy টেবিল দরকার হলে hi_res বাছে, নইলে fast বা ocr_only। স্ক্যান করা ফাইলে text layer না থাকলে fast কাজ করে না, তখন ocr_only লাগে।
# Docling: convert, then export structured Markdown
from docling.document_converter import DocumentConverter
doc = DocumentConverter().convert("report.pdf").document
markdown = doc.export_to_markdown()

# Unstructured: choose a strategy explicitly, get typed elements
from unstructured.partition.pdf import partition_pdf
elements = partition_pdf(filename="report.pdf", strategy="hi_res")
tables = [e for e in elements if e.category == "Table"]

Where parsing fails

1. Text extraction (fast) 2. Layout (hi_res) 3. Table structure 4. Text-only pipelines Scanned PDF, no text layer: fast fails; needs an OCR fallback (ocr_only) Multi-column pages: hi_res struggles, per the Unstructured docs Not verified in sources: reading-order errors, merged cells (experience) Text extraction misses figures, layout, tables, fonts (ColPali motivation)
Documented failure modes mapped to the stage where they arise. The dashed box marks problems common in practice but not confirmed by the sources used here.
কোন ধাপে কোন সমস্যা হয় তার মানচিত্র: text layer ছাড়া scan, multi-column পাতা, আর শুধু text-ভিত্তিক extraction-এ figure ও table হারানো। ড্যাশ করা বাক্সের সমস্যাগুলো এখানকার সূত্রে যাচাই করা নয়।

Always plan an OCR fallback. The fast strategy relies on a selectable text layer, so a scanned PDF silently produces nothing useful unless you detect the empty result and re-run with ocr_only or a layout-aware parser.[59] Spot-check multi-column documents by eye before trusting the output.

Research frontier: The Docling technical report describes an open-source toolkit that converts PDFs using a layout-analysis model (DocLayNet) and a table-structure model (TableFormer), runs on commodity hardware and is extensible.[58] A different line of work questions the pipeline itself: ColPali argues that text-extraction pipelines lose figures, layout, tables and fonts, and retrieves from page images directly (covered in the next section).[62] Limit: neither source provides a head-to-head parser benchmark in the material used here, so choose by testing on your own documents.
গবেষণার সারকথা: Docling layout ও table-এর জন্য আলাদা মডেল ব্যবহার করে। ColPali বলে text extraction-এ ছবি, layout ও table হারায়, তাই পাতার ছবি থেকেই retrieval করা যায়।

Practice · 1 task

Open any free AI chat: ChatGPT · Claude · Gemini

How practice works
  • A new chat is a fresh conversation with no history. In ChatGPT, Claude, or Gemini, click New chat. It is usually at the top left.
  • Same chat means: keep typing in the chat that is already open. The AI remembers what you sent before.
  • To use a prompt: click Copy, click in the message box of the chat, paste it (Ctrl+V, or press and hold on a phone), and press Enter.
  • AI answers are a little different every time. Your answer does not need to match the example word for word. Look for the same idea.
  • Use made-up data only. Do not paste private information into a chat.

Task 1

Feed the AI broken page text

Why this matters

When you build a chatbot over PDFs, a parser first turns each page into text. If the parser mixes two columns, or finds no text in a scan, the chatbot works with broken text.

  1. Step 1
    New chat

    Copy this prompt, paste it, and press Enter. The text came from a two-column page that was read line by line. Write down the answer.

    Prompt
    Answer the question using ONLY the extracted page text below. If you are not sure, say what is unclear.
    
    Extracted text:
    "Basic costs 8 USD a Basic allows 3 month. Pro costs 25 users. Pro allows USD a month. The free 20 users. Extra users trial lasts 14 days. cost 2 USD each."
    
    Question: How many users does the Pro plan allow, and how much does an extra user cost?

    You should see: A good model works out 20 users and 2 USD, or says the text is confusing. A weaker one may say 25 users, which is wrong.

    Example answer
    The text looks mixed up. It seems to say Pro allows 20 users, and extra users cost 2 USD each. I am not fully sure, because the words are out of order.

    Your answer will be worded differently.

  2. Step 2
    New chat

    Copy this prompt, paste it, and press Enter. It is the same page, with the columns read in the right order.

    Prompt
    Answer the question using ONLY the extracted page text below. If you are not sure, say what is unclear.
    
    Extracted text (Markdown):
    "## Pricing
    Basic costs 8 USD a month. Pro costs 25 USD a month. The free trial lasts 14 days.
    
    ## Limits
    Basic allows 3 users. Pro allows 20 users. Extra users cost 2 USD each."
    
    Question: How many users does the Pro plan allow, and how much does an extra user cost?

    You should see: A clear answer: 20 users and 2 USD. The headings make the structure easy.

    Example answer
    The Pro plan allows 20 users. An extra user costs 2 USD.

    Your answer will be worded differently.

  3. Step 3
    New chat

    Copy this prompt, paste it, and press Enter. It is a scanned page where the parser found no text.

    Prompt
    Answer the question using ONLY the extracted page text below. If you are not sure, say what is unclear.
    
    Extracted text:
    ""
    
    Question: How many users does the Pro plan allow, and how much does an extra user cost?

    You should see: A good model says the text is empty and it cannot answer.

    Example answer
    The extracted text is empty, so I cannot answer. Please give me the page text.

    Your answer will be worded differently.

What just happened

The chatbot can only use the text the parser (the tool that reads the file) gives it. With mixed columns the words come out in the wrong order, so the AI may answer with a wrong number. With a scan the parser finds nothing, unless it uses OCR (a tool that reads text from a picture). Always check the parser output before you use it.

You are done when You have 3 answers: a careful or wrong one, a correct one (20 users, 2 USD), and a no text one.

Check yourselfWhy should a RAG system check for empty or odd parser output before it cuts the text into chunks?Show answer

The system can only store what the parser found. A scan gives empty text, and a simple reader can mix columns, so a bad parse gives bad answers later. Check for empty output, re-run with OCR or a layout-aware parser, and look at multi-column pages by eye.

Try a harder versionIn a new chat, take the broken text from step 1 and write it yourself as a Markdown table with 3 rows (Basic, Pro, Free trial). Ask the same question. How much did the structure help?

Watch · 3 videos

10

Multimodal RAG: images, tables, charts

Text-only RAG goes blind the moment the answer lives in a chart, a scanned table or a slide. Multimodal RAG keeps those objects searchable. There are three broad designs: turn everything into text, embed images directly into a shared vector space, or retrieve page images with late interaction and let a vision-capable model read them.[63][62]

Three architectures

1. Multimodal embeddings Raw text + images no conversion step Multimodal embedder e.g. CLIP, one vector space Vector index nearest-neighbour search Multimodal LLM answers from raw images 2. Summarize to text Image / table parsed from the document LLM summary describes the content in text Embed the summary ordinary text embeddings Text LLM answers from summaries 3. Summary + raw reference Image / table kept as the raw object LLM summary used as retrieval key only Multi-vector retriever summary hit, fetch raw doc Multimodal LLM synthesis sees the original
The three options described by LangChain: embed raw images with a multimodal embedder, embed LLM-written summaries, or embed summaries while keeping a pointer to the raw object for the answer step.
তিনটি পদ্ধতি: (১) CLIP-ধরনের embedder দিয়ে সরাসরি ছবি embed করা, (২) ছবি বা table-এর LLM summary টেক্সট হিসেবে embed করা, (৩) summary দিয়ে খোঁজা কিন্তু উত্তর দেওয়ার সময় মূল ছবি multimodal LLM-কে দেওয়া।

LangChain's write-up lists these as: (1) multimodal embeddings such as CLIP, passing raw images to a multimodal LLM; (2) LLM summaries of images and tables embedded as text; (3) summaries embedded with a reference to the raw image, which is handed to a multimodal LLM at synthesis time. Option 3 relies on a multi-vector retriever, which decouples the retrieval key from the document used for synthesis.[63]

# Option 3 in plain Python: summary is the key, raw object is the value
docstore = {}          # id -> raw table / image (kept as-is)
index = []             # (vector, id) pairs built from summaries
for obj_id, raw in parsed_objects:
    summary = summarize_with_llm(raw)       # placeholder: your LLM call
    docstore[obj_id] = raw
    index.append((embed_text(summary), obj_id))   # placeholder: your embedder

hits = nearest(index, embed_text(question), k=3)
context = [docstore[obj_id] for obj_id in hits]   # raw objects go to a multimodal LLM

Multimodal embedding models

CLIP was trained on 400M image-text pairs and transfers zero-shot to 30+ vision datasets.[64] Newer hosted models take mixed inputs through one backbone. Voyage notes that a single backbone avoids a CLIP-style problem where text and image vectors align with same-modality items in mixed search.[65]

CLIP training data
400M image-text pairs
voyage-multimodal-3.5 context
32k tokens
Cohere Embed v4 context
128k tokens
jina-embeddings-v5-omni small
1.74B params, 1024d
ModelInputsStated specs
CLIP[64]Image + text (dual encoder)400M pairs; zero-shot on 30+ datasets
voyage-multimodal-3.5[65]Interleaved text, images, video; PDF screenshots, slides, tables, figures32k context; dims 256/512/1024/2048
Cohere Embed v4[66]Text and images, including mixed PDFs; multilingual128k context; dims 256/512/1024/1536 (default)
jina-embeddings-v5-omni[67]Text, images, audio, video, PDFssmall: 1.74B, 1024d, 32K; nano: 1.04B, 768d, 8K
jina-clip-v2[67]Text + image865M params

Jina's vendor page also lists approximate image token cost per 600x600 image: 363 (v5-omni), 4,840 (v4) and 16,000 (clip-v2), which matters when you embed millions of pages.[67]

Page-image retrieval with late interaction

ColPali introduces the ViDoRe benchmark for visually rich document retrieval and a vision-language model that produces multi-vector embeddings directly from page images, skipping text extraction. The authors report that it largely outperforms modern document retrieval pipelines; it appeared at ICLR 2025.[62]

Text pipeline (parse first) Page image PDF page rendered OCR + layout parse text extraction step Text chunks figures, layout may be lost Text embedding dense retrieval ColPali (skip text extraction) Page image no text extraction Vision-language model ColPali backbone Patch embeddings multi-vector per page MaxSim scoring late interaction ColPali's motivation: text pipelines miss figures, layout, tables and fonts.
Top: the classic route extracts text first, so anything the extractor drops never reaches the index. Bottom: ColPali embeds the page image itself as many patch vectors.
উপরের লাইনে আগে text বের করা হয়, তাই extractor যা বাদ দেয় তা index-এ পৌঁছায় না। নিচের লাইনে ColPali পাতার ছবি সরাসরি অনেকগুলো patch vector-এ রূপ দেয়।
Late interaction (MaxSim), toy numbers patch 1 patch 2 patch 3 patch 4 q1 "revenue" 0.2 0.8 0.1 0.3 max = 0.8 q2 "2023" 0.6 0.1 0.2 0.1 max = 0.6 q3 "chart" 0.1 0.2 0.9 0.4 max = 0.9 Score one page per query token: best patch similarity then sum over all query tokens score = 0.8 + 0.6 + 0.9 = 2.3 illustrative numbers, not from a benchmark
Each query token is compared to every patch vector of a page, the best match per token is kept, and the maxima are summed into the page score.
প্রতিটি query token পাতার সব patch-এর সাথে মেলানো হয়, প্রতি token-এর সেরা মিলটি রাখা হয়, তারপর সেগুলো যোগ করে পাতার score হয়। এখানের সংখ্যাগুলো কেবল বোঝানোর জন্য।

Tables and unified frameworks

Tables are the hardest object: a plain-text flattening loses row and column relationships. Three practical routes follow from the architectures above: parse the table into Markdown or HTML (previous section), summarise it for retrieval while keeping the raw table for the answer step (option 3), or retrieve the page image (ColPali). RAG-Anything takes a broader view: a unified framework over text, images, tables and equations with dual-graph construction (cross-modal plus textual) and cross-modal hybrid retrieval; code is open source at HKUDS/RAG-Anything.[68]

Summaries can hide the detail you need. In options 2 and 3 the retriever only sees what the summariser wrote. If the summary omits a number in a chart, no query can find it. Prefer option 3 so the generator still sees the original, and evaluate retrieval on questions that depend on specific cell values (my synthesis, not a sourced finding).

Research frontier: ColPali (Faysse et al.) builds the ViDoRe benchmark and shows that embedding page images as multi-vector representations with late interaction can replace the parse-then-embed pipeline for visually rich documents.[62] RAG-Anything (Guo et al., 2025) instead unifies modalities through a dual-graph design and cross-modal hybrid retrieval.[68] Limits: multi-vector page embeddings store many vectors per page (index size is a cost to measure), and the reported gains are on the authors' benchmark, so validate on your own documents.
গবেষণার সারকথা: ColPali পাতার ছবি থেকেই multi-vector embedding বানায়, text extraction লাগে না। RAG-Anything text, ছবি, table ও equation এক কাঠামোয় আনে।

Practice · 1 task

Open any free AI chat: ChatGPT · Claude · Gemini

How practice works
  • A new chat is a fresh conversation with no history. In ChatGPT, Claude, or Gemini, click New chat. It is usually at the top left.
  • Same chat means: keep typing in the chat that is already open. The AI remembers what you sent before.
  • To use a prompt: click Copy, click in the message box of the chat, paste it (Ctrl+V, or press and hold on a phone), and press Enter.
  • AI answers are a little different every time. Your answer does not need to match the example word for word. Look for the same idea.
  • Use made-up data only. Do not paste private information into a chat.

Task 1

Search by summary, answer from the raw table

Why this matters

When you build a chatbot over reports with tables and charts, you often store a short AI summary of each table for search. A summary can leave out the one detail a user asks about.

  1. Step 1
    New chat

    Copy this prompt, paste it, and press Enter. Then copy the one sentence the AI writes.

    Prompt
    Write ONE sentence that summarises this table, so that it can be stored in a search index. Reply with the sentence only.
    
    Table: Quarterly revenue by branch (lakh BDT)
    Branch | Q1 | Q2 | Q3 | Q4
    Dhaka | 42 | 45 | 51 | 60
    Chattogram | 30 | 28 | 35 | 41
    Sylhet | 18 | 22 | 19 | 27
    Rajshahi | 15 | 16 | 21 | 24

    You should see: One sentence about the general trend, for example that Dhaka leads and revenue grows. It probably does not mention any drop.

    Example answer
    Dhaka had the highest quarterly revenue in every quarter, rising from 42 to 60 lakh BDT, and all four branches grew over the year.

    Your answer will be worded differently.

  2. Step 2
    New chat

    Copy this prompt. Replace the words in brackets with your summary from step 1. Paste it and press Enter.

    Prompt
    Answer the question using ONLY the text below. If the text does not contain the answer, say "The text does not contain the answer".
    
    Text: "[PASTE YOUR SUMMARY HERE]"
    
    Question: Which branch had a revenue drop from Q2 to Q3, and by how many lakh BDT?

    You should see: Most chats reply The text does not contain the answer. A few guess a branch, which is worse.

    Example answer
    The text does not contain the answer

    Your answer will be worded differently.

  3. Step 3
    New chat

    Copy this prompt, paste it, and press Enter. This time the AI gets the raw table, not the summary.

    Prompt
    Answer the question using ONLY the table below. If the table does not contain the answer, say "The table does not contain the answer".
    
    Table: Quarterly revenue by branch (lakh BDT)
    Branch | Q1 | Q2 | Q3 | Q4
    Dhaka | 42 | 45 | 51 | 60
    Chattogram | 30 | 28 | 35 | 41
    Sylhet | 18 | 22 | 19 | 27
    Rajshahi | 15 | 16 | 21 | 24
    
    Question: Which branch had a revenue drop from Q2 to Q3, and by how many lakh BDT?

    You should see: Sylhet, down 3 lakh BDT (from 22 to 19). It is the only branch with a drop.

    Example answer
    Sylhet. Its revenue fell from 22 lakh BDT in Q2 to 19 lakh BDT in Q3, a drop of 3 lakh BDT.

    Your answer will be worded differently.

What just happened

The search only sees what the summary says. The summary talked about the general trend, so the small Sylhet drop was lost, and the AI had nothing to answer from. The fix is to search with the summary but give the AI the raw table when it writes the answer. Then it can read every number.

You are done when You have two answers: the text does not contain the answer from the summary, and Sylhet, 3 lakh BDT from the table.

Check yourselfWhy keep a link to the raw table or image when you search with a summary?Show answer

The search can only find what the summary says, so a missing detail cannot be found or answered. If the raw table goes to the answer step, the AI can still read the exact numbers.

Try a harder versionIf your chat allows image upload, take a screenshot of the table and ask the same question with the image. Compare the answer with step 3. If you cannot upload images, skip this.

Watch · 3 videos

References

Sources

All citations resolve to primary papers (arXiv), vendor docs (OpenAI, Anthropic, Google), or official team blogs. IDs were verified by fetching each page and extracting the citation metadata.

  1. [1]Günther, Mohr, Williams, Wang, Xiao, "Late Chunking: Contextual Chunk Embeddings Using Long-Context Embedding Models" — arXiv, 2024
  2. [2]Anthropic, "Introducing Contextual Retrieval" — Anthropic, 2024
  3. [3]LangChain, "RecursiveCharacterTextSplitter" — LangChain documentation
  4. [4]LlamaIndex, "Node Parser Modules" — LlamaIndex documentation
  5. [5]LangChain, "MarkdownHeaderTextSplitter" — LangChain documentation
  6. [6]Malkov, Yashunin, "Efficient and robust approximate nearest neighbor search using Hierarchical Navigable Small World graphs" — arXiv, 2016 (final 2018)
  7. [7]Weaviate, "Vector index" — Weaviate documentation
  8. [8]Andrew Kane, "pgvector" — GitHub README
  9. [9]Milvus, "Index types" — Milvus documentation
  10. [10]Qdrant, "qdrant/qdrant" — GitHub repository
  11. [11]Qdrant, "Overview" — Qdrant documentation
  12. [12]Pinecone, "Get started: overview" — Pinecone documentation
  13. [13]Weaviate, "Hybrid search" — Weaviate documentation
  14. [14]Chroma, "Introduction" — Chroma documentation
  15. [15]Milvus, "Overview" — Milvus documentation
  16. [16]Cormack, Clarke, Buettcher, "Reciprocal rank fusion outperforms Condorcet and individual rank learning methods" — SIGIR 2009, pp. 758-759
  17. [17]Robertson, Zaragoza, "The Probabilistic Relevance Framework: BM25 and Beyond" — Foundations and Trends in Information Retrieval, 2009
  18. [18]SPLADE (sparse neural retrieval with term expansion) — paper summary, Hugging Face Papers (arXiv 2107.05720)
  19. [19]Elastic, "Reciprocal rank fusion" — Elasticsearch Reference 8.17
  20. [20]OpenSearch, "Introducing reciprocal rank fusion for hybrid search" — OpenSearch blog, 2025
  21. [21]Qdrant, "Hybrid queries" — Qdrant documentation
  22. [22]Pinecone, "Hybrid search" — Pinecone documentation
  23. [23]Gao, Ma, Lin, Callan, "Precise Zero-Shot Dense Retrieval without Relevance Labels" (HyDE) — arXiv 2212.10496, 2022
  24. [24]Zheng, Mishra, Chen et al., "Take a Step Back: Evoking Reasoning via Abstraction in Large Language Models" — Google DeepMind, ICLR 2024
  25. [25]Wang, Yang, Wei, "Query2doc: Query Expansion with Large Language Models" — Microsoft Research, EMNLP 2023
  26. [26]Rackauckas, "RAG-Fusion: a New Take on Retrieval-Augmented Generation" — arXiv 2402.03367, 2024
  27. [27]Ma, Gong, He, Zhao, Duan, "Query Rewriting for Retrieval-Augmented Large Language Models" — EMNLP 2023
  28. [28]Yang et al., "HotpotQA: A Dataset for Diverse, Explainable Multi-hop Question Answering" — EMNLP 2018
  29. [29]Khot, Sabharwal, Trivedi, Balasubramanian, "Interleaving Retrieval with Chain-of-Thought Reasoning for Knowledge-Intensive Multi-Step Questions" — 2022/2023
  30. [30]Yao et al., "ReAct: Synergizing Reasoning and Acting in Language Models" — 2022
  31. [31]Asai, Wu, Wang, Sil, Hajishirzi, "Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection" — 2023
  32. [32]Yan, Gu, Zhu, Ling, "Corrective Retrieval Augmented Generation" — 2024
  33. [33]Jeong, Baek, Cho, Hwang, Park, "Adaptive-RAG: Learning to Adapt Retrieval-Augmented LLMs through Question Complexity" — NAACL 2024
  34. [34]Anthropic, "Building effective agents" — Anthropic Engineering, 2024
  35. [35]OpenAI, "File search" — OpenAI API guides
  36. [36]Anthropic, "How we built our multi-agent research system" — Anthropic Engineering, 2025
  37. [37]Edge et al., "From Local to Global: A Graph RAG Approach to Query-Focused Summarization" — Microsoft, 2024
  38. [38]Guo, Xia, Yu, Ao, Huang, "LightRAG: Simple and Fast Retrieval-Augmented Generation" — 2024
  39. [39]Gutierrez, Shu, Gu, Yasunaga, Su, "HippoRAG: Neurobiologically Inspired Long-Term Memory for Large Language Models" — 2024
  40. [40]Neo4j, "neo4j-graphrag Python package" — Neo4j documentation
  41. [41]Microsoft, "GraphRAG" — documentation
  42. [42]Microsoft, "graphrag" — GitHub repository README
  43. [43]Sentence-Transformers, "Cross-Encoder Applications" — SBERT documentation
  44. [44]Cohere, "Rerank API reference (v2)" — Cohere documentation
  45. [45]Voyage AI, "Rerankers" — Voyage AI documentation
  46. [46]BAAI, "bge-reranker-v2-m3" — Hugging Face model card
  47. [47]BAAI, "Reranking" tutorial — BGE documentation
  48. [48]Khattab & Zaharia, "ColBERT: Efficient and Effective Passage Search via Contextualized Late Interaction over BERT" — SIGIR, 2020 (arXiv 2004.12832)
  49. [49]Cohere, "Rerank v3.5" — Cohere changelog
  50. [50]Qdrant, "Vector search filtering" — Qdrant documentation
  51. [51]Weaviate, "Filtering" — Weaviate documentation (concepts)
  52. [52]Qdrant, "Pre-filtering vs post-filtering" — Qdrant blog
  53. [53]Elastic, "Filtered kNN search" — Elasticsearch documentation
  54. [54]Pinecone, "Filter by metadata" — Pinecone documentation
  55. [55]Elastic Search Labs, "Self-querying retrievers with Elasticsearch" — Elastic blog
  56. [56]Yu et al., "Spider: A Large-Scale Human-Labeled Dataset for Complex and Cross-Domain Semantic Parsing and Text-to-SQL Task" — EMNLP, 2018
  57. [57]Patel et al., "ACORN: Performant and Predicate-Agnostic Search Over Vector Embeddings and Structured Data" — arXiv 2403.04871, 2024
  58. [58]Auer, Lysak, Nassar, Dolfi et al., "Docling Technical Report" — arXiv, 2024
  59. [59]Unstructured, "Partitioning" (open-source core functionality) — Unstructured docs
  60. [60]LlamaIndex, "LlamaParse: Parse" — LlamaIndex developer docs
  61. [61]IBM Research / LF AI & Data, "Docling documentation" — docling-project, 2025
  62. [62]Faysse et al., "ColPali: Efficient Document Retrieval with Vision Language Models" — ICLR 2025 (arXiv 2024)
  63. [63]LangChain, "Multi-modal RAG (semi-structured)" — LangChain blog
  64. [64]Radford et al., "Learning Transferable Visual Models From Natural Language Supervision (CLIP)" — arXiv, 2021
  65. [65]Voyage AI, "Multimodal embeddings" — Voyage AI docs
  66. [66]Cohere, "Embed" — Cohere docs
  67. [67]Jina AI, "Embeddings" — Jina AI
  68. [68]Guo, Ren, Xu, Zhang, Huang, "RAG-Anything" — arXiv, 2025