Memory Retrieval Strategy Using BM25 + Vector Retrieval with RRF in TencentDB Agent‑Memory
The TencentDB-Agent-Memory hybrid retrieval pipeline combines BM25 keyword search with dense-vector semantic search, then merges results using Reciprocal Rank Fusion (RRF) to return the most relevant agent memories.
TencentDB-Agent-Memory implements a sophisticated memory retrieval strategy that unifies lexical and semantic search. This approach ensures agents recall facts whether they match exact keywords or capture conceptual meaning. The implementation lives in the open-source TencentCloud/TencentDB-Agent-Memory repository, with core logic in TypeScript and SDKs available for Python and Node.js.
How the Hybrid Retrieval Pipeline Works
The memory search tool executes a four-stage pipeline: capability detection, parallel retrieval, RRF merging, and post‑processing. Each stage is designed to maximize recall quality while maintaining low latency.
Capability Detection: FT25 vs. Embeddings
When a memory_search request arrives, the system first probes which backends are available:
hasFts— true when the store supports BM25 via SQLite FTS5 (line 27 ofmemory-search.ts)hasEmbedding— true when anEmbeddingServiceis configured (lines 26–27)
These flags determine which retrieval paths execute.
Parallel BM25 and Vector Retrieval
When both capabilities are present, the engine launches two independent queries:
BM25 (keyword) search
- Builds an FTS5 query via
buildFtsQuery - Executes
vectorStore.searchL1Fts(lines 87–101 inmemory-search.ts)
Vector (embedding) search
- Generates query embedding via
embeddingService.embed - Executes
vectorStore.searchL1Vector(lines 124–132)
Both searches run concurrently, returning ranked lists ordered by their respective scoring functions.
Reciprocal Rank Fusion (RRF) Implementation
The rrfMergeL1 function combines the two ranked lists without requiring score normalization.
RRF Score Formula
// From MemoryCore/src/core/tools/memory-search.ts
const RRF_K = 60; // lines 52–53 — follows original RRF paper
// For each item across all lists:
score += 1 / (RRF_K + rank + 1); // lines 65–71, rank is 0‑based
The constant RRF_K = 60 is taken directly from the original RRF research, providing a stabilization term that prevents top-ranked items from dominating.
Merging and Re‑ranking
After computing summed RRF scores:
- Items are sorted by accumulated RRF score (descending)
- The original
scorefield is replaced with the RRF score (lines 78–80) - The merged list proceeds to filtering
This rank-based approach inherently upweights items appearing in both lists, since they receive contributions from both BM25 and vector rankings.
Fallback Strategies and Native Hybrid Shortcuts
The implementation includes graceful degradation:
| Scenario | Behavior | Strategy Value |
|---|---|---|
| BM25 only | Return FTS5 results directly | "fts" |
| Embedding only | Return vector results directly | "embedding" |
| Native hybrid endpoint | Short‑circuit to single call (lines 45–52) | Implementation-defined |
The native hybrid shortcut detects when the underlying vector store (e.g., TCVDB) implements dense + sparse + RRF internally, avoiding redundant client-side computation.
Post‑Processing and Result Formatting
After RRF merging, additional filters apply:
- Type filter — restrict to specific memory types (person, org, fact, etc.)
- Scene filter — scope to relevant interaction contexts
- Limit truncation — trim to user‑specified
limit - Response formatting —
formatSearchResponseproduces human‑readable output (lines 88–104)
The final output is a concise string ready for LLM consumption, with RRF scores indicating relative relevance.
Practical Usage Examples
TypeScript SDK
import { skillClient } from "tencentdb-agent-memory/v3";
async function recallFacts() {
const result = await skillClient.searchMemory({
query: "如何在 MySQL 中创建只读用户?",
limit: 5,
filter: { team_id: "team-123" },
});
console.log(result); // RRF‑merged BM25 + vector results
}
recallFacts();
Python SDK
from tencentdb_agent_memory.v3 import SkillClient
client = SkillClient()
resp = client.search_memory(
query="What is the quota for a COS bucket?",
limit=3,
filter={"team_id": "team-xyz"},
)
print(resp) # Human‑readable, RRF‑scored list
Both SDKs invoke the same backend pipeline transparently—you benefit from hybrid retrieval without manual orchestration.
Key Source Files
| Component | File Path |
|---|---|
| Core search logic with RRF | MemoryCore/src/core/tools/memory-search.ts |
RRF merge implementation (rrfMergeL1) |
Lines 62–80 of above |
| BM25 sparse encoder | MemoryCore/src/core/store/bm25-local.ts |
| Vector store interface (native hybrid detection) | MemoryCore/src/core/store/tcvdb.ts |
| Python skill client | sdk/memory-core/python/tencentdb_agent_memory/v3/skill_client.py |
Summary
- BM25 + vector retrieval runs in parallel when both FTS5 and embeddings are available
- RRF with K=60 merges ranks without score calibration, favoring items present in both result sets
- Automatic fallback to single-mode retrieval when only one backend is configured
- Native hybrid shortcut leverages database-native RRF when supported
- Consistent SDK interface across TypeScript and Python hides implementation complexity
Frequently Asked Questions
What is RRF and why use it for memory retrieval?
Reciprocal Rank Fusion is a rank-based combination method that scores items by their position across multiple ordered lists. In TencentDB-Agent-Memory, RRF avoids the problem of incompatible score scales between BM25 (unbounded relevance scores) and vector similarity (typically cosine or dot product). By using rank position with a stabilization constant (K=60), RRF produces a robust unified ranking without requiring training data.
Does the system always use both BM25 and vector search?
No. The pipeline checks hasFts and hasEmbedding flags at runtime. If only BM25 is available, it returns FTS5 results with strategy: "fts". If only embeddings are available, it returns vector results with strategy: "embedding". The hybrid RRF merge executes only when both capabilities are present.
How does RRF handle the same memory appearing in both search results?
The RRF formula 1/(K + rank + 1) is additive across lists. An item ranked 2nd in BM25 and 5th in vector search receives a higher total score than an item ranked 10th in both. This property naturally surfaces memories with strong lexical and semantic relevance without requiring explicit intersection logic.
Can I configure the RRF constant or retrieval weights?
The current implementation hardcodes RRF_K = 60 following the original research paper (lines 52–53 of memory-search.ts). The repository does not expose weighting parameters for the fusion—scores are computed purely from rank positions. For stores with native hybrid support, the RRF computation may occur server-side with implementation-specific constants.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →