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]
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 sectionUnits 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]
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.
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]
Choosing a strategy
The comparison below is my own editorial synthesis from how each method works, not a benchmark result.
| Strategy | Cuts at | Strength (editorial) | Watch out for (editorial) |
|---|---|---|---|
| Fixed + overlap | A size limit | Trivial, predictable, cheap | Can slice through sentences and facts |
| Recursive | Paragraph, line, word, character, in that order | Keeps coherent units whenever they fit | Size is in characters by default |
| Structural (headers, sentences) | Author-drawn boundaries | Header metadata for filtering and citations | Needs clean, consistent markup |
| Semantic | Embedding-similarity drops | Topic-coherent chunks | Extra embedding pass; threshold to tune |
| Late chunking | After the transformer, before pooling | Chunk vectors carry document context | Needs a long-context embedding model |
| Contextual retrieval | Any chunker, plus LLM context | Repairs lost context for dense and BM25 | One-time LLM cost per chunk |
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.
Step 1
New chatCopy 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.
Step 2
New chatCopy 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.
Step 3
Same chatLook 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.
Watch · 3 videos
The 5 Levels Of Text Splitting For Retrieval Greg Kamradt
Semantic Chunking - 3 Methods for Better RAG James Briggs
Late chunking improves context recall in RAG pipelines Weaviate vector database
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]
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.
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.
| Database | Deployment and licence | Index types (as stated) | Sparse / hybrid | Filtering and isolation |
|---|---|---|---|---|
| Qdrant | Rust, Apache 2.0; self-managed, Managed Cloud, Hybrid Cloud, Private Cloud[10][11] | HNSW | Dense, sparse and multivector; hybrid via Query API prefetch | JSON payload filters; payload indexes filter during a single graph traversal; multitenancy |
| Pinecone | Managed, serverless; BYOC option[12] | Not stated | Dense, sparse, full-text and hybrid in one index | Namespaces for tenant isolation; metadata filters |
| Weaviate | Licence 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 |
| pgvector | Open-source Postgres extension; ACID, point-in-time recovery[8] | HNSW, IVFFlat | Types sparsevec, halfvec, bit; hybrid not covered in my notes | WHERE filters with partial indexes or partitioning; iterative index scans for filtered queries |
| Chroma | Apache 2.0; local, client-server, Chroma Cloud (managed serverless)[14] | Not verified | Dense, sparse and hybrid; keyword and regex search | Metadata filtering; multi-modal |
| Milvus | Apache 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, reranking | Multi-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;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 situation | Lean toward | Reasoning (synthesis) |
|---|---|---|
| Data already in Postgres; want transactions and joins | pgvector | Vectors live beside relational rows with ACID and PITR |
| Prototype or laptop-scale experiment | Chroma (local mode) | Runs locally; can later move to client-server or Cloud |
| Minimal operations, managed service | Pinecone, or a managed tier of Qdrant, Chroma or Milvus | Pinecone is serverless; others offer managed cloud options |
| Self-hosted, filter-heavy search | Qdrant | Payload filtering built into graph traversal; Apache 2.0 |
| Very large, distributed deployment on Kubernetes | Milvus | Decoupled query, data and index nodes; Distributed mode |
| Built-in hybrid fusion with tunable alpha, or many tenants | Weaviate | BM25 + 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.
Step 1
New chatCopy 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.
Step 2
Same chatCopy 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.
Watch · 3 videos
Vector databases are so hot right now. WTF are they? Fireship
Qdrant Essentials | Fast Vector Search with Qdrant HNSW Indexing Qdrant Vector Search
The Weaviate Vector Database — Bring AI-native applications to life. CMU Database Group
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]
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.
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]
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]
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.
Step 1
New chatCopy 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.
Step 2
Same chatCopy 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.
Watch · 3 videos
Hybrid Search Finds What Vectors Miss (RAG) Shane | LLM Implementation
Understanding Reciprocal Rank Fusion in Hybrid Search [Advanced RAG] TechViz - The Data Science Guy
Build your own Hybrid Search Abhishek Thakur
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]
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]
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.
# 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]| Technique | What it changes | Reported in the paper | Watch out |
|---|---|---|---|
| HyDE[23] | Embeds a generated pseudo-answer | Beats Contriever baseline on web search, QA, fact verification | Generated text can be wrong; encoder is expected to filter it |
| Step-back[24] | Adds a broader, abstract question | PaLM-2L: +7% MMLU physics, +11% chemistry, +27% TimeQA, +7% MuSiQue | Prompting method, not retrieval-only |
| Query2doc[25] | Query + LLM pseudo-document | BM25 +3-15% in-domain; dense +0.4-4% | Little help for dense retrievers; bigger LLM needed |
| RAG-Fusion[26] | N query variants + RRF | Manual assessment only | Answers can drift off-topic if variants are irrelevant |
| Rewrite-Retrieve-Read[27] | Rewrites query before retrieval | Tested on HotpotQA, AmbigNQ, PopQA, MMLU | Extra 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.
Step 1
New chatCopy 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.
Step 2
New chatCopy 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.
Step 3
New chatCopy 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.
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]
# 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), evidenceSelf-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]
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]
| Pattern | Who decides what to retrieve | Reported result | Cost to watch |
|---|---|---|---|
| IRCoT[29] | CoT sentences become queries | +11-21 retrieval points, up to +15 F1 | Several retrieve + generate rounds |
| Self-RAG[31] | Model, via reflection tokens | 7B/13B beat ChatGPT on listed tasks | Needs a trained model |
| CRAG[32] | Evaluator confidence | Plug-and-play over existing RAG | Evaluator pass; web search fallback |
| Adaptive-RAG[33] | Small-LM complexity classifier | Better efficiency and accuracy vs adaptive baselines | Classifier 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.
Step 1
New chatCopy 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 sayFINAL:yet, because it knows no facts.Example answer
SEARCH: Which team built the Kestrel payment API?
Your answer will be worded differently.
Step 2
Same chatYou 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.
Step 3
Same chatCopy 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.
Step 4
Same chatCopy 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.
Watch · 3 videos
Agentic RAG: An End-to-End Open Source Framework Weaviate vector database
Agentic RAG Explained DataStax Developers
Self-Corrective RAG with LangGraph - Agentic RAG Tutorial Coding Crash Courses
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 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]
The two-stage funnel
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
| Option | Access | Documented limits and parameters | Notes |
|---|---|---|---|
| Cohere Rerank | v2 rerank API; model, query, documents required | Up 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 rerankers | POST https://api.voyageai.com/v1/rerank | query, 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-m3 | Open weights on Hugging Face, run locally | Outputs 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.
Step 1
New chatCopy 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.
Step 2
New chatCopy 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.
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
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]
What each engine does
| Engine | Behaviour with filters | Notes |
|---|---|---|
| Weaviate | Pre-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] |
| Qdrant | Filterable 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] |
| Elasticsearch | A 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] |
| Pinecone | Metadata 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'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.
Step 1
New chatCopy 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.
Step 2
Same chatCopy 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.
Step 3
Same chatCopy 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.
Watch · 2 videos
Qdrant Essentials | Combine Search & Filtering with Qdrant Filterable HNSW Qdrant Vector Search
Qdrant Payload Filtering in Python: Search Vectors with Metadata Conditions Professor Py: AI Foundations
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.
Microsoft GraphRAG: index, then local or global search
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]
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-graphragPython package is Neo4j's official GraphRAG library (successor to neo4j-genai): a knowledge-graph builder, retrievers (VectorRetrieverand others), and aGraphRAGclass. 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.
| Vector / hybrid RAG | GraphRAG (Microsoft) | LightRAG / HippoRAG | |
|---|---|---|---|
| Strength | Point lookups, simple pipeline | Global questions via community summaries[37] | Incremental updates[38]; cheap single-step multi-hop[39] |
| Index step | Chunk + embed | Extract entities/relations, Leiden, summarize[41] | Graph from LLM extraction (see papers) |
| Main cost | Embedding 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.
Step 1
New chatCopy 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.
Step 2
Same chatCopy 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.
Step 3
Same chatCopy 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.
Watch · 2 videos
From Local to Global: A Graph RAG Approach to Query-Focused Summarization Natural Language Processing Interest Group
RAG with a Neo4j Knowledge Graph: How it Works and How to Set It Up Neo4j
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
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.
| Docling | Unstructured (open source) | LlamaParse | |
|---|---|---|---|
| Origin | IBM Research; MIT-licensed; LF AI & Data project[58][61] | Open-source partitioning library[59] | LlamaIndex parsing service[60] |
| Inputs | PDF, DOCX, PPTX, XLSX, HTML, EPUB, audio, email[61] | Strategy-based PDF partitioning (documented here)[59] | PDFs, scans, tables, charts; 130+ file types[60] |
| Layout / tables | DocLayNet 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] |
| OCR | OCR for scans; VLM support (GraniteDocling)[61] | ocr_only strategy for scans[59] | Built into the layout-aware OCR[60] |
| Output | Unified DoclingDocument; export to Markdown, HTML, JSON[61] | Typed elements: Title, NarrativeText, ListItem, Table, Header, Footer, PageBreak, Image[59] | Markdown, text or JSON[60] |
| Control knobs | Runs 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
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
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.
Step 1
New chatCopy 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.
Step 2
New chatCopy 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.
Step 3
New chatCopy 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.
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
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 LLMMultimodal 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]
| Model | Inputs | Stated 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, figures | 32k context; dims 256/512/1024/2048 |
| Cohere Embed v4[66] | Text and images, including mixed PDFs; multilingual | 128k context; dims 256/512/1024/1536 (default) |
| jina-embeddings-v5-omni[67] | Text, images, audio, video, PDFs | small: 1.74B, 1024d, 32K; nano: 1.04B, 768d, 8K |
| jina-clip-v2[67] | Text + image | 865M 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]
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.
Step 1
New chatCopy 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.
Step 2
New chatCopy 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.
Step 3
New chatCopy 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.
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]Günther, Mohr, Williams, Wang, Xiao, "Late Chunking: Contextual Chunk Embeddings Using Long-Context Embedding Models" — arXiv, 2024
- [2]Anthropic, "Introducing Contextual Retrieval" — Anthropic, 2024
- [3]LangChain, "RecursiveCharacterTextSplitter" — LangChain documentation
- [4]LlamaIndex, "Node Parser Modules" — LlamaIndex documentation
- [5]LangChain, "MarkdownHeaderTextSplitter" — LangChain documentation
- [6]Malkov, Yashunin, "Efficient and robust approximate nearest neighbor search using Hierarchical Navigable Small World graphs" — arXiv, 2016 (final 2018)
- [7]Weaviate, "Vector index" — Weaviate documentation
- [8]Andrew Kane, "pgvector" — GitHub README
- [9]Milvus, "Index types" — Milvus documentation
- [10]Qdrant, "qdrant/qdrant" — GitHub repository
- [11]Qdrant, "Overview" — Qdrant documentation
- [12]Pinecone, "Get started: overview" — Pinecone documentation
- [13]Weaviate, "Hybrid search" — Weaviate documentation
- [14]Chroma, "Introduction" — Chroma documentation
- [15]Milvus, "Overview" — Milvus documentation
- [16]Cormack, Clarke, Buettcher, "Reciprocal rank fusion outperforms Condorcet and individual rank learning methods" — SIGIR 2009, pp. 758-759
- [17]Robertson, Zaragoza, "The Probabilistic Relevance Framework: BM25 and Beyond" — Foundations and Trends in Information Retrieval, 2009
- [18]SPLADE (sparse neural retrieval with term expansion) — paper summary, Hugging Face Papers (arXiv 2107.05720)
- [19]Elastic, "Reciprocal rank fusion" — Elasticsearch Reference 8.17
- [20]OpenSearch, "Introducing reciprocal rank fusion for hybrid search" — OpenSearch blog, 2025
- [21]Qdrant, "Hybrid queries" — Qdrant documentation
- [22]Pinecone, "Hybrid search" — Pinecone documentation
- [23]Gao, Ma, Lin, Callan, "Precise Zero-Shot Dense Retrieval without Relevance Labels" (HyDE) — arXiv 2212.10496, 2022
- [24]Zheng, Mishra, Chen et al., "Take a Step Back: Evoking Reasoning via Abstraction in Large Language Models" — Google DeepMind, ICLR 2024
- [25]Wang, Yang, Wei, "Query2doc: Query Expansion with Large Language Models" — Microsoft Research, EMNLP 2023
- [26]Rackauckas, "RAG-Fusion: a New Take on Retrieval-Augmented Generation" — arXiv 2402.03367, 2024
- [27]Ma, Gong, He, Zhao, Duan, "Query Rewriting for Retrieval-Augmented Large Language Models" — EMNLP 2023
- [28]Yang et al., "HotpotQA: A Dataset for Diverse, Explainable Multi-hop Question Answering" — EMNLP 2018
- [29]Khot, Sabharwal, Trivedi, Balasubramanian, "Interleaving Retrieval with Chain-of-Thought Reasoning for Knowledge-Intensive Multi-Step Questions" — 2022/2023
- [30]Yao et al., "ReAct: Synergizing Reasoning and Acting in Language Models" — 2022
- [31]Asai, Wu, Wang, Sil, Hajishirzi, "Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection" — 2023
- [32]Yan, Gu, Zhu, Ling, "Corrective Retrieval Augmented Generation" — 2024
- [33]Jeong, Baek, Cho, Hwang, Park, "Adaptive-RAG: Learning to Adapt Retrieval-Augmented LLMs through Question Complexity" — NAACL 2024
- [34]Anthropic, "Building effective agents" — Anthropic Engineering, 2024
- [35]OpenAI, "File search" — OpenAI API guides
- [36]Anthropic, "How we built our multi-agent research system" — Anthropic Engineering, 2025
- [37]Edge et al., "From Local to Global: A Graph RAG Approach to Query-Focused Summarization" — Microsoft, 2024
- [38]Guo, Xia, Yu, Ao, Huang, "LightRAG: Simple and Fast Retrieval-Augmented Generation" — 2024
- [39]Gutierrez, Shu, Gu, Yasunaga, Su, "HippoRAG: Neurobiologically Inspired Long-Term Memory for Large Language Models" — 2024
- [40]Neo4j, "neo4j-graphrag Python package" — Neo4j documentation
- [41]Microsoft, "GraphRAG" — documentation
- [42]Microsoft, "graphrag" — GitHub repository README
- [43]Sentence-Transformers, "Cross-Encoder Applications" — SBERT documentation
- [44]Cohere, "Rerank API reference (v2)" — Cohere documentation
- [45]Voyage AI, "Rerankers" — Voyage AI documentation
- [46]BAAI, "bge-reranker-v2-m3" — Hugging Face model card
- [47]BAAI, "Reranking" tutorial — BGE documentation
- [48]Khattab & Zaharia, "ColBERT: Efficient and Effective Passage Search via Contextualized Late Interaction over BERT" — SIGIR, 2020 (arXiv 2004.12832)
- [49]Cohere, "Rerank v3.5" — Cohere changelog
- [50]Qdrant, "Vector search filtering" — Qdrant documentation
- [51]Weaviate, "Filtering" — Weaviate documentation (concepts)
- [52]Qdrant, "Pre-filtering vs post-filtering" — Qdrant blog
- [53]Elastic, "Filtered kNN search" — Elasticsearch documentation
- [54]Pinecone, "Filter by metadata" — Pinecone documentation
- [55]Elastic Search Labs, "Self-querying retrievers with Elasticsearch" — Elastic blog
- [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]Patel et al., "ACORN: Performant and Predicate-Agnostic Search Over Vector Embeddings and Structured Data" — arXiv 2403.04871, 2024
- [58]Auer, Lysak, Nassar, Dolfi et al., "Docling Technical Report" — arXiv, 2024
- [59]Unstructured, "Partitioning" (open-source core functionality) — Unstructured docs
- [60]LlamaIndex, "LlamaParse: Parse" — LlamaIndex developer docs
- [61]IBM Research / LF AI & Data, "Docling documentation" — docling-project, 2025
- [62]Faysse et al., "ColPali: Efficient Document Retrieval with Vision Language Models" — ICLR 2025 (arXiv 2024)
- [63]LangChain, "Multi-modal RAG (semi-structured)" — LangChain blog
- [64]Radford et al., "Learning Transferable Visual Models From Natural Language Supervision (CLIP)" — arXiv, 2021
- [65]Voyage AI, "Multimodal embeddings" — Voyage AI docs
- [66]Cohere, "Embed" — Cohere docs
- [67]Jina AI, "Embeddings" — Jina AI
- [68]Guo, Ren, Xu, Zhang, Huang, "RAG-Anything" — arXiv, 2025