Hybrid Search: A Hands-on Guide to Multimodal Retrieval (Chapter 4)

1. From Corrective RAG to Multimodal Retrieval

In Chapter 3, we studied Corrective RAG (CRAG), which improves the reliability of a RAG system through document scoring and fallback mechanisms. CRAG mainly addresses the quality-validation problem for retrieval results, but at the retrieval step itself, traditional RAG systems still have a fundamental problem: the blind spots of any single retrieval method.

This chapter solves that problem: how to combine multiple retrieval methods through Hybrid Search to improve both recall and precision.

Hybrid Search is a technique that combines three modalities — vector search, sparse search, and full-text search — and improves retrieval effectiveness through weighted score fusion. By letting different retrieval methods complement one another, it overcomes the blind spots of any single method and thereby improves recall and precision.

2.2 The Problems with a Single Retrieval Method

Let’s first look at the problems you run into when relying on just one retrieval method.

Blind spots of vector search:

Vector search is good at understanding semantics and concepts, but it misses precise keywords. For example, if you search for proper nouns like “GAAP” or “Q3 2023,” vector search may return results that are conceptually similar but actually irrelevant. There’s also the problem of over-generalization — it may return documents that are conceptually similar but that don’t actually answer your question.

Blind spots of keyword search:

Keyword search is good at matching exact terms, but it doesn’t understand semantics. For example, if you search for “machine learning,” it won’t find documents containing “AI”; if you search for “revenue,” it won’t find content containing “earnings” or “income.” This is the semantic blind-spot problem.

The crux of the issue is this: vector search misses keywords, and keyword search misses semantics — each method has its own blind spots.

2.3 Hybrid Search: Fusing Three Modalities

The idea behind Hybrid Search is: since every single method has blind spots, why not combine them? Specifically, Hybrid Search combines three complementary retrieval methods:

Hybrid Search: A Hands-on Guide to Multimodal Retrieval (Chapter 4)

The three retrieval methods each have their own emphasis:

  • Vector Search → understands semantic similarity
  • Sparse Search → matches keywords and synonyms
  • Full-text Search → exact phrase matching

2.4 Hybrid RAG vs Corrective RAG

Hybrid Search and corrective mechanisms address problems at different stages:

Dimension Hybrid RAG (this chapter) Corrective RAG (Chapter 3)
Core goal Better Retrieval Better Validation
Approach Combines 3 retrieval modalities Document relevance scoring
Key mechanism Weighted score fusion Query rewriting + fallback
Stage Retrieval stage Post-retrieval validation stage
Agent’s role Selects the retrieval strategy Assesses quality + triggers fallback

These two techniques pair perfectly: use Hybrid Search to improve retrieval quality, then use Corrective RAG for quality validation.

3. A Detailed Look at the Three Retrieval Modalities

Now that we understand why Hybrid Search is necessary, let’s take a deep dive into the three core modalities that make it up.

Vector search converts text into dense embeddings (typically 768–1536 dimensions), then uses cosine similarity to measure the angle between vectors, returning the documents that are most semantically similar.

Its strength lies in understanding concepts and semantic relationships, and it can handle paraphrases and synonymous expressions. But it can’t precisely match specific terms, such as proper nouns like “GAAP” or “SKU-12345.”

Best for: conceptual queries, such as “What causes inflation?”

Sparse search uses TF-IDF (term frequency–inverse document frequency) to extract keywords, can expand synonyms within the vocabulary, and matches based on keyword weights (not exact string matching).

The principle of TF-IDF is: Term Frequency × Inverse Document Frequency — the rarer a word is across the entire document collection, the higher the weight it receives.

The strength of sparse search is its ability to match related terms — synonyms like revenue, earnings, and income — without requiring an embedding model. But it is constrained by vocabulary dimensions and struggles with rare proper nouns.

Typical use case: Tool Selection

Sparse search plays the keyword-matching role within Hybrid Search and performs especially well in tool selection and in term-sensitive queries (such as proper nouns and technical abbreviations).

Full-text search builds a tokenized inverted index, applies the BM25 scoring algorithm (an improved TF-IDF that adds document-length normalization), and returns exact phrase matches.

BM25 is an improved version of TF-IDF that adds document-length normalization to prevent long documents from getting unfairly high scores.

The strength of full-text search is its ability to exactly match phrases (such as “Item 1A Risk Factors”), handle rare proper nouns, and support precise section location. But it can’t handle typos or variants, and it doesn’t understand semantic relationships.

Best for: precise section lookup, such as “Find the Risk Factors section of the 10-K report.”

3.4 Choosing Among the Three Modalities

No single modality is the best — the key is to combine them according to the query pattern:

Retrieval Modality Best Query Type Example Query Core Strength
Vector search Conceptual queries requiring semantic understanding “What are Nike’s financial risks?” Semantic understanding
Sparse search Synonym-aware keyword matching “Nike earnings 2023” Keyword generalization
Full-text search Exact phrase queries, section names “Item 1A Risk Factors” Exact matching

4. seekdb: An AI-Native Search Database

4.1 What Is seekdb?

seekdb is the AI-native search database from OceanBase. It integrates vector storage, relational data, and full-text search into a single unified platform. Traditional solutions require a dedicated vector database, which adds extra operational cost and system complexity; seekdb solves this with a unified, multi-model engine.

4.2 The Core Advantages of seekdb

Hybrid Search: A Hands-on Guide to Multimodal Retrieval (Chapter 4)

  • A single query can invoke all 3 modalities, with no need to call external services
  • Native weighted fusion, with built-in RRF and linear combination algorithms
  • Automatic index synchronization — vector, sparse, and BM25 indexes are maintained automatically
  • MySQL protocol, compatible with existing tools and drivers
  • Seamless migration to an OceanBase cluster

5.1 Preparing the Environment

1
2
3
4
5
6
7
8
9
10
11
import os
from dotenv import load_dotenv

# Load environment variables
load_dotenv("../.env")

# Verify configuration
print("✅ Configuration loaded:")
print(f"📍 OceanBase: {os.getenv('OCEANBASE_HOST')}:{os.getenv('OCEANBASE_PORT')}")
print(f"📍 Database: {os.getenv('OCEANBASE_DB')}")
print(f"📍 Embedding Model: {os.getenv('SILICONFLOW_EMBEDDING_MODEL', 'BAAI/bge-m3')}")

Load the embedding model.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from langchain_dev_utils.embeddings import register_embeddings_provider, load_embeddings

# Register SiliconFlow embeddings provider
SILICONFLOW_BASE_URL = os.getenv("SILICONFLOW_BASE_URL", "https://api.siliconflow.cn/v1")

register_embeddings_provider(
provider_name="siliconflow",
embeddings_model="openai-compatible",
base_url=SILICONFLOW_BASE_URL,
)

# Load embedding model
EMBEDDING_MODEL_NAME = os.getenv("SILICONFLOW_EMBEDDING_MODEL", "BAAI/bge-m3")
embeddings = load_embeddings(f"siliconflow:{EMBEDDING_MODEL_NAME}")

print(f"✅ Loaded embedding model: {EMBEDDING_MODEL_NAME}")

Configure the OceanBase connection.

1
2
3
4
5
6
7
8
9
10
# OceanBase connection parameters
connection_args = {
"host": os.getenv("OCEANBASE_HOST", "127.0.0.1"),
"port": int(os.getenv("OCEANBASE_PORT", "2881")),
"user": os.getenv("OCEANBASE_USER", "root@test"),
"password": os.getenv("OCEANBASE_PASSWORD", ""),
"db_name": os.getenv("OCEANBASE_DB", "test"),
}

print("✅ OceanBase connection configured")

5.2 Loading Documents

First, load the source documents to demonstrate hybrid search.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter

# Load Nike 10-K PDF
pdf_path = "./data/nke-10k-2023.pdf"
loader = PyPDFLoader(pdf_path)
documents = loader.load()

# Split into chunks
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=100,
separators=["\n\n", "\n", ". ", " ", ""],
)
splits = text_splitter.split_documents(documents)

print(f"✅ Loaded {len(documents)} pages and split into {len(splits)} chunks")

# Select a subset for demonstration (first 200 chunks)
demo_docs = splits[:200]
print(f"📄 Using {len(demo_docs)} chunks for hybrid search demo")

5.3 Initializing the Hybrid Store

Enable all three search modes:

  1. Dense vectors: semantic similarity via embeddings
  2. Sparse vectors: keyword importance computed via TF-IDF weighting
  3. Full-text search: exact phrase and keyword matching
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from langchain_oceanbase.vectorstores import OceanbaseVectorStore

# Get embedding dimension
embedding_dim = len(embeddings.embed_query("test"))

# Create hybrid search vector store with ALL three modalities enabled
hybrid_store = OceanbaseVectorStore(
embedding_function=embeddings,
table_name="hybrid_search_demo",
connection_args=connection_args,
vidx_metric_type="l2",
include_sparse=True, # Enable sparse vector search (keyword matching)
include_fulltext=True, # Enable full-text search (exact phrase matching)
drop_old=True,
embedding_dim=embedding_dim,
)

print("✅ Hybrid search vector store initialized!")
print(f"📐 Vector dimension: {embedding_dim}")
print(f"🔍 Dense vector: Enabled (L2 distance)")
print(f"🔍 Sparse vector: Enabled (keyword matching)")
print(f"🔍 Full-text search: Enabled (phrase matching)")

5.4 Generating Sparse Vectors

Sparse vectors use TF-IDF (term frequency–inverse document frequency) to represent keyword importance.

Term Frequency (TF): how often a word appears in a document; Inverse Document Frequency (IDF): how rare or important a word is across the corpus.

Vocabulary-based: terms are mapped directly to indexes (no hash collisions).

We’ll build a custom TF-IDF encoder that works within OceanBase’s 500,000-dimension limit.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import re
import math
from collections import Counter

# Stopwords to filter out common words
STOPWORDS = {
'the', 'and', 'for', 'with', 'that', 'this', 'are', 'was', 'were', 'been',
'has', 'have', 'had', 'its', 'our', 'their', 'from', 'which', 'may', 'can',
'will', 'would', 'could', 'should', 'any', 'such', 'than', 'other', 'more',
'also', 'including', 'related', 'into', 'these', 'those', 'each', 'all',
'some', 'them', 'they', 'being', 'about', 'after', 'before', 'between',
'through', 'during', 'under', 'over', 'above', 'below', 'both', 'same',
'but', 'not', 'only', 'own', 'just', 'now', 'then', 'here', 'there', 'when',
'where', 'why', 'how', 'what', 'who', 'whom', 'his', 'her', 'him', 'she',
'you', 'your', 'yours', 'out', 'off', 'down', 'again', 'further', 'once',
}


class TFIDFEncoder:
"""Simple TF-IDF encoder with vocabulary-based indexing."""

def __init__(self, max_vocab_size=100000):
self.max_vocab_size = max_vocab_size
self.vocab = {} # term -> index
self.idf = {} # term -> idf score
self.doc_count = 0

def tokenize(self, text):
"""Tokenize and clean text."""
words = re.findall(r'\b[a-zA-Z][a-zA-Z0-9]*\b', text.lower())
return [w for w in words if w not in STOPWORDS and len(w) >= 2]

def fit(self, documents):
"""Build vocabulary and compute IDF scores."""
self.doc_count = len(documents)
doc_freq = Counter() # term -> number of docs containing term

# Count document frequencies
for doc in documents:
terms = set(self.tokenize(doc))
for term in terms:
doc_freq[term] += 1

# Select top terms by document frequency (most common across docs)
top_terms = doc_freq.most_common(self.max_vocab_size)

# Build vocabulary and compute IDF
for idx, (term, df) in enumerate(top_terms):
self.vocab[term] = idx
# IDF = log(N / df) + 1 (smoothed)
self.idf[term] = math.log(self.doc_count / df) + 1

print(f" Vocabulary size: {len(self.vocab)}")
print(f" Sample high-IDF terms: {[(t, f'{self.idf[t]:.2f}') for t in list(self.vocab.keys())[::len(self.vocab)//5][:5]]}")

def encode(self, text):
"""Encode text to sparse TF-IDF vector."""
terms = self.tokenize(text)
term_freq = Counter(terms)
sparse_vec = {}

for term, tf in term_freq.items():
if term in self.vocab:
idx = self.vocab[term]
# TF-IDF = tf * idf (normalized by max tf)
max_tf = max(term_freq.values()) if term_freq else 1
tfidf = (tf / max_tf) * self.idf[term]
sparse_vec[idx] = tfidf

return sparse_vec


# Initialize and fit TF-IDF encoder
print("⏳ Building TF-IDF vocabulary from document corpus...")
tfidf_encoder = TFIDFEncoder(max_vocab_size=100000)
tfidf_encoder.fit([doc.page_content for doc in demo_docs])
print(f"✅ TF-IDF encoder fitted on {len(demo_docs)} documents")

# Generate sparse vectors for all documents
sparse_embeddings = [tfidf_encoder.encode(doc.page_content) for doc in demo_docs]
print(f"✅ Generated {len(sparse_embeddings)} TF-IDF sparse vectors")
print(f"\n📊 Sample sparse vector (doc 0):")
print(f" Non-zero terms: {len(sparse_embeddings[0])}")
print(f" Sample entries: {list(sparse_embeddings[0].items())[:5]}...")

5.5 Preparing Full-text Content

Full-text search needs its own indexable content. We’ll enrich the page content with metadata.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Create enhanced full-text content
fulltext_content = []

for doc in demo_docs:
# Combine page content with searchable metadata
metadata_text = f"Page {doc.metadata.get('page', 'N/A')} "
metadata_text += f"Title: {doc.metadata.get('title', '')} "

# Full searchable text
full_text = f"{metadata_text}{doc.page_content}"
fulltext_content.append(full_text)

print(f"✅ Prepared {len(fulltext_content)} full-text entries")
print(f"\n📝 Sample full-text content (doc 0):")
print(f" {fulltext_content[0][:200]}...")

5.6 Adding Documents Across All Three Modalities

Store the documents into the vector database and build all three indexes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
print("⏳ Adding documents with hybrid search capabilities...")
print()

# Step 1: Add documents with dense vectors + full-text content
ids = hybrid_store.add_documents_with_fulltext(
documents=demo_docs,
fulltext_content=fulltext_content,
)

# Step 2: Add sparse embeddings to the same documents
hybrid_store.add_sparse_documents(
documents=demo_docs,
sparse_embeddings=sparse_embeddings,
)

print(f"✅ Added {len(ids)} documents with:")
print(f" • Dense vector embeddings (1024-dim BGE-M3)")
print(f" • Sparse vector embeddings (keyword weights)")
print(f" • Full-text searchable content")
print()

print("=" * 80)
print("🎉 Hybrid search store populated!")
print("=" * 80)
print(f"📊 Total documents: {len(demo_docs)}")
print(f" Each document can be searched by:")
print(f" ✓ Semantic similarity (dense vector embeddings)")
print(f" ✓ Keyword matching (sparse vectors)")
print(f" ✓ Exact keywords and phrases (full-text index)")

5.7 Testing Each Modality

Test all three retrieval methods separately and compare the search results.

Each modality returns different results — vector search finds semantically related content, sparse search finds keyword matches, and full-text search finds exact phrases.

1
2
3
4
5
6
7
8
9
10
11
12
13
query = "What were Nike's total revenues and financial performance?"

# Pure vector search (semantic similarity only)
vector_results = hybrid_store.similarity_search(query, k=3)

print(f"🔍 Query: '{query}'")
print(f"\n📊 Vector Search Results (Semantic Similarity Only):\n")

for i, doc in enumerate(vector_results, 1):
print(f"Result {i}:")
print(f" Content: {doc.page_content[:150].replace(chr(10), ' ')}...")
print(f" Page: {doc.metadata.get('page', 'N/A')}")
print()

5.7.2 Sparse Vector Search (Keyword Matching)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Generate TF-IDF sparse query vector for keyword matching
query_text = "Nike total revenues fiscal 2023"
sparse_query = tfidf_encoder.encode(query_text)

# Sparse vector search on the hybrid store
sparse_results = hybrid_store.similarity_search_with_sparse_vector(
sparse_query=sparse_query,
k=3
)

print(f"🔍 Query: '{query_text}'")
print(f"🔢 TF-IDF sparse query: {len(sparse_query)} non-zero terms")
print(f" Matched terms: {[t for t in tfidf_encoder.tokenize(query_text) if t in tfidf_encoder.vocab]}")
print(f"\n📊 Sparse Vector Search Results (TF-IDF Keyword Matching):\n")

for i, doc in enumerate(sparse_results, 1):
print(f"Result {i}:")
print(f" Content: {doc.page_content[:150].replace(chr(10), ' ')}...")
print(f" Page: {doc.metadata.get('page', 'N/A')}")
print()

print("=" * 70)
print("💡 Note: Sparse search may not find the exact revenue tables.")
print(" Compare with Vector Search (6.1) which found pages 31, 34.")
print(" This demonstrates why Hybrid Search (Step 7) is valuable!")
print("=" * 70)

5.7.3 Full-text Search (Exact Matching)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Full-text search with exact phrase matching
fulltext_results = hybrid_store.similarity_search_with_fulltext(
query="revenue financial performance",
fulltext_query="revenues billion fiscal 2023", # Exact keywords
k=3
)

print(f"🔍 Vector query: 'revenue financial performance'")
print(f"🔍 Full-text query: 'revenues billion fiscal 2023'")
print(f"\n📊 Full-Text Search Results (Exact Matching):\n")

for i, doc in enumerate(fulltext_results, 1):
print(f"Result {i}:")
print(f" Content: {doc.page_content[:150].replace(chr(10), ' ')}...")
print(f" Page: {doc.metadata.get('page', 'N/A')}")
print()

In the previous steps, we enabled all three retrieval modalities and tested each one separately. The question now is: how do we effectively combine these three retrieval methods?

That’s the core problem advanced hybrid search solves: through weighted score fusion, automatically combining multiple retrieval modalities to achieve better retrieval results than any single modality.

6.1 The Built-in Score Fusion Mechanism

OceanBase provides the advanced_hybrid_search() method, which automatically combines the retrieval results of all three modalities.

How it works:

  1. Run the three searches in parallel — execute vector search, sparse search, and full-text search simultaneously
  2. Score normalization — normalize each modality’s scores to the 0–1 range
  3. Weighted fusion — apply the weighting formula: final_score = w₁×vector + w₂×sparse + w₃×fulltext
  4. Sort and return — sort by the fused score and return the Top-K results

All of the score normalization and fusion logic is handled automatically inside seekdb; developers only need to focus on configuring the weights.

6.1.1 Search Mode Presets

Different types of queries need different weight configurations. We can define several commonly used search modes:

Balanced

Suited to general queries, such as “Nike business in 2023.” Weight configuration: Vector 40%, Sparse 30%, Fulltext 30%.

Semantic

Suited to conceptual understanding, such as “What is Nike’s strategy?” Weight configuration: Vector 70%, Sparse 20%, Fulltext 10%.

Keyword

Suited to specific terms and numeric queries, such as “Nike earnings 2023.” Weight configuration: Vector 20%, Sparse 60%, Fulltext 20%.

Exact

Suited to legal text and section lookup, such as “Item 1A Risk Factors.” Weight configuration: Vector 10%, Sparse 20%, Fulltext 70%.

Preset V/S/F Use Case Example Query
Balanced 40/30/30 Unknown or mixed query types “Nike business in 2023”
Semantic 70/20/10 Research, exploratory questions “What is Nike’s strategy?”
Keyword 20/60/20 Specific terms, numeric queries “Nike earnings 2023”
Exact 10/20/70 Legal text, section lookup “Item 1A Risk Factors”

6.2 Weight-Tuning Recommendations

  1. Start with Balanced — when in doubt, use 40/30/30 as a baseline
  2. Adjust to your business scenario — analyze your actual query logs to identify the dominant query types
  3. Validate with A/B testing — compare the retrieval effectiveness of different weight configurations
  4. Allow dynamic adjustment — different queries can use different weight configurations

6.3 Choosing a Fusion Algorithm

Besides linear weighted combination, seekdb also supports other fusion algorithms:

  • Linear Combination — a weighted average, suitable for most scenarios
  • RRF (Reciprocal Rank Fusion) — rank-based fusion, insensitive to score scale
  • Max fusion — takes the highest score across modalities, suitable for “OR” logic

Recommended practice: start with linear combination, and if the results aren’t ideal, try RRF.

💡 Extended knowledge: The RRF and max fusion mentioned in this section are common fusion algorithms; seekdb supports multiple fusion strategies. For the specific API, please refer to the official documentation.

7. Agentic Hybrid RAG: Letting the Agent Choose the Optimal Strategy

Combining intelligent decision-making with multimodal retrieval gives you the best of both worlds.

Hybrid Search alone: multimodal (V+S+F), better recall — but fixed weights and always performs retrieval.

Agentic + Hybrid Search: dynamic search modes, multi-step reasoning, skipping retrieval when it isn’t needed, and synthesizing results from multiple searches.

The core value: Agentic RAG + Hybrid Search = intelligent decision-making + multimodal retrieval.

7.2 Defining a Tool with Dynamic Search Modes

Create a tool that lets the Agent choose the best search strategy.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from langchain.tools import tool
from typing import Literal


@tool
def hybrid_search_knowledge_base(
query: str,
top_k: int = 3,
search_mode: Literal[
"balanced", # 40/30/30
"semantic", # 70/20/10
"keyword", # 20/60/20
"exact" # 10/20/70
] = "balanced"
) -> str:
"""Search Nike's 10-K with hybrid search.

Args:
query: What to search for
search_mode: Strategy based on query type
"""
# Weight presets
weight_presets = {
"balanced": {"vector": 0.4, "sparse": 0.3, "fulltext": 0.3},
"semantic": {"vector": 0.7, "sparse": 0.2, "fulltext": 0.1},
"keyword": {"vector": 0.2, "sparse": 0.6, "fulltext": 0.2},
"exact": {"vector": 0.1, "sparse": 0.2, "fulltext": 0.7},
}

weights = weight_presets[search_mode]

# Generate the sparse vector
sparse_vec = tfidf_encoder.encode(query)

# Run hybrid search
results = hybrid_store.advanced_hybrid_search(
vector_query=query,
sparse_query=sparse_vec,
fulltext_query=query,
modality_weights=weights,
k=top_k
)

return results

Available search modes:

  • balanced (general scenarios, 40/30/30)
  • semantic (concepts and semantics, 70/20/10)
  • keyword (keyword queries, 20/60/20)
  • exact (exact phrases, 10/20/70)

The Agent analyzes the query and automatically selects the best mode.

7.3 Creating an Agent with LangChain

Build an intelligent Agent that can dynamically use hybrid search.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from langchain.agents import create_agent

agent = create_agent(
model=chat_model,
tools=[hybrid_search_knowledge_base],
system_prompt="""You are a helpful AI with access to Nike's 10-K report.

Choose search_mode based on query type:
- "semantic": concepts, strategy, high-level understanding
- "keyword": specific terms, numbers, technical abbreviations
- "exact": legal text, section names, precise phrases
- "balanced": unknown or mixed query types

For complex questions, search multiple times with different modes."""
)

# Invoke the Agent
result = agent.invoke({
"messages": [{"role": "user", "content": "What are Nike's financial risks?"}]
})

Agent capabilities: analyzing the query type before searching, selecting the optimal search mode, performing multi-step searches for complex questions, and synthesizing the results into a coherent answer. The system prompt guides the Agent on when to use each search mode.

Advanced tip: have the Agent output custom weights (such as 0.5/0.3/0.2) instead of preset names, for finer-grained control.

7.4 Agent in Action: Examples

Watch how the Agent dynamically selects its search strategy.

Financial data query:

User: “Nike revenue fiscal 2023?” → Agent analysis → search_mode=”keyword”, reasoning: use keyword mode to find a specific number.

Strategy question:

User: “What is Nike’s innovation approach?” → Agent analysis → search_mode=”semantic”, reasoning: use semantic mode to understand concepts.

Section lookup:

User: “Find Item 1A Risk Factors” → Agent analysis → search_mode=”exact”, reasoning: use exact mode for precise matching.

Key advantage: the Agent intelligently selects its search strategy based on its analysis of the query — no manual tuning required.

8. Key Takeaways

8.1 Three Modalities

  • Vector search → semantic understanding
  • Sparse search → keywords + synonym expansion
  • Full-text search → exact phrases

8.2 Weighted Fusion

  • Four preset modes: balanced / semantic / keyword / exact
  • Supports custom weights for combining modalities
  • Tune the weights to your domain

8.3 The Agentic Approach

  • Let the Agent choose the search strategy
  • Dynamic mode selection per query
  • Multi-step search for complex Q&A

8.4 seekdb by OceanBase

  • Native hybrid search support
  • A single query → 3 modalities
  • Easy migration to an OceanBase cluster

8.5 Practical Recommendations

  • Start with Balanced: use 40/30/30 weights when the query type is uncertain
  • Let the Agent decide: guide the Agent’s search-mode selection through the system prompt
  • Tune iteratively: adjust the weight presets based on your actual query logs
  • Combine techniques: Hybrid Search + Corrective RAG = a more powerful system

9. Next Steps

In this chapter, we developed a deep understanding of the multimodal fusion mechanism behind Hybrid Search, learned how to combine the three modalities of vector search, sparse search, and full-text search, and used seekdb to build a complete Agentic Hybrid RAG system.

Follow the WeChat account “Lao Ji’s Tech Talk” to keep up with this series of courses! Let’s explore more of what Agentic RAG and Hybrid Search can do together! 🎯