Agentic Research

Read these first

This article assumes the following earlier in its learning path.

RAG In-Depth Principles and Practice: A Complete Guide from Chunking to Rerank

2026/05/1011 min readBryan Chan閱讀中文原文
TopicsRAGLLMDeep Learning

What is RAG?

RAG (Retrieval-Augmented Generation) lets an LLM retrieve relevant documents from an external knowledge base before answering, then generate an answer based on the retrieved results. This solves two core problems of LLMs:

  1. Knowledge cutoff: LLMs only know information from their training data
  2. Hallucination: LLMs may fabricate facts that do not exist
User question → Embedding → Vector search → Top-K documents → LLM generation → Answer with sources

Core Component Breakdown

1. Embedding Model Selection

ModelDimensionsLanguageCostApplicable Scenarios
text-embedding-3-small1536Multilingual$0.02/1M tokensGeneral purpose, cost-sensitive
text-embedding-3-large3072Multilingual$0.13/1M tokensHigh-precision requirements
bge-large-zh-v1.51024ChineseFree (local)Preferred for Chinese scenarios
bge-m31024MultilingualFree (local)Mixed multilingual scenarios
jina-embeddings-v31024MultilingualFree (API)Long documents (8K tokens)

Recommendation: For Chinese-first scenarios, use bge-large-zh-v1.5; for multilingual scenarios, use bge-m3; for API solutions, use text-embedding-3-small.

# Ollama Local Embedding
ollama pull nomic-embed-text
# or bge-m3
ollama pull bge-m3

2. Chunking Strategies

This is the most easily overlooked yet most critical step. Incorrect chunking directly leads to retrieval failure.

StrategySuitable ScenariosProsCons
Fixed size (500 tokens)General purposeSimple, predictableMay truncate mid-sentence
Semantic splitting (by paragraph/section)Structured documentsPreserves semantic integrityInconsistent chunk sizes
Recursive splitting (by \n\n → \n → 。)Mixed documentsAdaptiveComplex to implement
Sentence windowPrecise retrievalEach sentence is independentLoses context

Best practices:

  • Chunk size: 256-512 tokens (too small loses context, too large dilutes semantics)
  • Overlap: 10-20% (ensures key information is not truncated)
  • Use LangChain's RecursiveCharacterTextSplitter
from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=50,
    separators=["\n\n", "\n", "。", ",", " ", ""]
)
chunks = splitter.split_text(document)

3. Vector Database Comparison

OptionDeploymentScaleSuitable Scenarios
ChromaLocal/Embedded<100K docsPrototyping/Personal projects
QdrantLocal/DockerMillion-scaleProduction
MilvusDistributedHundred-million scaleEnterprise-grade
PineconeCloud SaaSUnlimitedNo infrastructure management
pgvectorPostgreSQL extensionMillion-scaleProjects already using PG

Recommended path: Use Chroma for prototyping → use Qdrant Docker for production → use Milvus for very large scale.

# Qdrant Quick Start
docker run -p 6333:6333 qdrant/qdrant

4. Rerank (Reranking)

After initial vector retrieval, use a more precise Cross-encoder to rerank the Top-K results, significantly improving accuracy.

ModelLanguageDescription
bge-reranker-v2-m3MultilingualBest open source
Cohere RerankMultilingualAPI, best performance
bge-reranker-largeChinese/EnglishRecommended for Chinese scenarios

Why is Rerank needed? Vector similarity ≠ semantic relevance. Rerank uses a Cross-encoder to compare query and document pairwise, making it more precise.

# Typical workflow
# 1. Vector retrieval → 20 candidates
candidates = vector_db.search(query, top_k=20)

# 2. Rerank → take top 5
from FlagEmbedding import FlagReranker
reranker = FlagReranker('BAAI/bge-reranker-v2-m3')
scores = reranker.compute_score([[query, doc] for doc in candidates])
top_5 = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)[:5]

# 3. LLM generation
answer = llm.generate(query, context=top_5)

Complete RAG Pipeline Code

The RAG pipeline: how an AI knows about your documentsRAG has two phases, and explaining them together is why most accounts of it confuse people. The upper half is one-time index building: your documents are cut into chunks, each chunk becomes a list of numbers (a vector), and they are stored in a vector database. The lower half runs on every question: your question is also vectorised, the nearest chunks by meaning are found (not by matching words), and the question plus those chunks are stuffed into one prompt for the model, which answers citing what it saw. The key point: RAG does not teach the model anything — it shows the model the right page at the moment you ask. That distinction changes four practical judgements: the same question can be answered differently on different days (what changed was the retrieved chunks); it can be confidently wrong (from inside the prompt a wrong chunk looks identical to a right one); updating a document changes nothing until the index is rebuilt (stale answers are a pipeline problem, not a model problem); and chunk size is a real tradeoff (too small loses context, too large dilutes the relevant part).RAG does not teach the model anything. It shows it the right page, at the moment you ask.ONCE — BUILDING THE INDEXYour documentsPDFs, notes,wikis, ticketsCut into chunksBlock size decideswhat can be foundTurn into vectorsEach chunk becomesa list of numbersVector DBStored, waitingEVERY QUESTION — RETRIEVALYour questionAlso turned intoa vectorFind nearest chunksNearest in meaning,not matching wordsStuff into the promptQuestion + chunksshare one windowAnswerCites what it sawWhy this distinction changes what you do· The same question can be answered differently on different days — the retrieved chunks differed, not the model.· It can be confidently wrong: a wrong chunk looks exactly like a right one from inside the prompt.· Updating a document changes nothing until the index is rebuilt. Stale answers are a pipeline problem, not a model problem.· Chunk size is a real tradeoff: too small loses context, too large dilutes the relevant part.1Documents go in
1/6Documents go in
PDFs, notes, wikis, tickets
Step 1 of 6 Documents go in
Diagram: the full RAG pipeline. Chunking, embedding, retrieval, reranking and prompt assembly — the input and output of each stage.
from langchain_community.embeddings import OllamaEmbeddings
from langchain_community.vectorstores import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.llms import Ollama

# 1. Load document
with open("knowledge_base.txt") as f:
    text = f.read()

# 2. Chunking
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = splitter.split_text(text)

# 3. Embedding + store in vector store
embeddings = OllamaEmbeddings(model="bge-m3")
vectorstore = Chroma.from_texts(chunks, embeddings)

# 4. Retrieval
retriever = vectorstore.as_retriever(search_kwargs={"k": 5})
docs = retriever.get_relevant_documents("What is RAG?")

# 5. Generation
llm = Ollama(model="qwen2.5:7b")
context = "\n".join([d.page_content for d in docs])
answer = llm.invoke(f"Answer the question based on the following documents:\n{context}\n\nQuestion: What is RAG?")

Common Pitfalls

PitfallCauseSolution
Irrelevant retrieval resultsChunk size too large or too smallTune to 256-512, add overlap
Slow retrievalNo index builtBuild an HNSW index with Qdrant/Milvus
High embedding costCalls the API on every queryDeploy bge-m3 locally
Answer hallucinationLLM ignores retrieval resultsForce source citations in the prompt
Mixed Chinese-English queries failEmbedding model does not support themUse the bge-m3 multilingual model

Recommended Reading

Next on this path