⚙️ RRF Fusion Parameters
k = 60
70% Vector / 30% Lexical
0.0 (Lexical)0.5 (Balanced)1.0 (Vector)
Corpus Status
Total Notebooks:5
Total Chunks:8
Active Scope:ALL
Local demo: search runs in your browser over the sample corpus below. It is not yet connected to a live Vectorize index or D1 database — see the Retriever API tab and Deployment Guide.md in this repo for the backend version.
Hybrid Results (0)
Notebook RRF Ranking
➕ Add Chunk to Notebook
Adds to the in-browser corpus for this session.
📋 Active Notebook Corpus Chunks
All registered chunks across all notebook files
🖥️ Cloudflare Hybrid Search Implementation
Global Multi-Notebook Hybrid Search Query Implementation — the backend retriever described in Deployment Guide.md. This page's search tab is a browser simulator of the same RRF logic (k = 60, alpha-weighted).
// Cloudflare Global Multi-Notebook Retriever Implementation
export interface HybridSearchQuery {
query: string;
notebookId?: string; // Optional: Pass specific notebook ID or omit for global search
topK?: number;
alpha?: number;
}
export class CloudflareHybridRetriever {
constructor(private env: Env) {}
async search(params: HybridSearchQuery) {
const topK = params.topK ?? 5;
const alpha = params.alpha ?? 0.7;
const ftsQuery = params.notebookId && params.notebookId !== 'ALL'
? this.env.DB.prepare(
`SELECT id, content, rank FROM chunks_fts
WHERE chunks_fts MATCH ? AND notebook_id = ?
ORDER BY rank LIMIT ?`
).bind(params.query, params.notebookId, topK * 2)
: this.env.DB.prepare(
`SELECT id, content, rank FROM chunks_fts
WHERE chunks_fts MATCH ?
ORDER BY rank LIMIT ?`
).bind(params.query, topK * 2);
const [aiResponse, ftsResults] = await Promise.all([
this.env.AI.run('@cf/baai/bge-large-en-v1.5', { text: [params.query] }),
ftsQuery.all()
]);
const queryVector = aiResponse.data[0];
const filter = params.notebookId && params.notebookId !== 'ALL'
? { notebook_id: params.notebookId }
: undefined;
const vectorResults = await this.env.VECTOR_INDEX.query(queryVector, {
topK: topK * 2,
filter,
returnMetadata: 'all'
});
const rrfScores = new Map();
const k = 60;
vectorResults.matches.forEach((match, index) => {
const score = alpha * (1 / (k + index + 1));
rrfScores.set(match.id, (rrfScores.get(match.id) || 0) + score);
});
const ftsRows = ftsResults.results;
ftsRows.forEach((row, index) => {
const score = (1 - alpha) * (1 / (k + index + 1));
rrfScores.set(row.id, (rrfScores.get(row.id) || 0) + score);
});
const sortedChunkIds = Array.from(rrfScores.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, topK)
.map(([id]) => id);
if (sortedChunkIds.length === 0) return [];
const placeholders = sortedChunkIds.map(() => '?').join(',');
const hydrationQuery = `SELECT id, notebook_id, doc_id, content, section_path FROM chunks WHERE id IN (${placeholders})`;
const { results: finalChunks } = await this.env.DB.prepare(hydrationQuery)
.bind(...sortedChunkIds)
.all();
const chunkMap = new Map(finalChunks.map((c) => [c.id, c]));
return sortedChunkIds.map(id => chunkMap.get(id)).filter(Boolean);
}
}