You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* feat(rag): add MMR (Maximal Marginal Relevance) diversity strategy
- new MmrStrategy (post-retrieval), greedily selects chunks balancing
relevance (context.scores) against similarity to already-selected
chunks, so near-duplicates (e.g. overlapping parent/child chunks)
don't crowd out distinct context
- lambda (relevance/diversity balance, default 0.5) and topK configurable
- extract cosineSimilarity as a shared utils/cosine-similarity.ts,
used by both MmrStrategy and HybridVectorStore (previously inline)
Closes#133
* fix(rag): address review feedback on PR #163
- MmrStrategy: validate topK (non-negative integer) and lambda (finite,
in [0, 1]) at construction instead of accepting arbitrary values
- MmrStrategy: initialize maxSimToSelected to -Infinity instead of 0, so
a genuinely negative cosine similarity (anti-correlated embeddings)
is not clamped away, matching the documented MMR formula
- cosineSimilarity: add @PARAM a/@PARAM b JSDoc
- docs: clarify that chunks without an embedding still participate in
MMR selection via their relevance score, they only skip the diversity
comparison; only an entirely embedding-free set passes through unranked
Not changed: Njent's suggestion to prune context.scores to only the
selected chunk IDs. RerankerStrategy already returns the full scores map
after slicing chunks to topK; changing MmrStrategy alone would be an
inconsistent, one-off deviation from that existing convention.
Add `MmrStrategy`, a post-retrieval Maximal Marginal Relevance diversity strategy that reduces near-duplicate chunks in retrieved context. Extract `cosineSimilarity` as a shared utility, used by both `MmrStrategy` and `HybridVectorStore`. Closes #133.
Copy file name to clipboardExpand all lines: apps/landing/content/docs/rag/retrieval-strategies.mdx
+14Lines changed: 14 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -16,6 +16,20 @@ Blends a document-level global embedding into each chunk vector to preserve full
16
16
### 3. `ParentChildHydrationStrategy`
17
17
Works with chunks produced by `ParentChildSplitter`: indexes small sub-chunks (methods, paragraphs) for high vector search precision, but hydrates the full parent text (carried in `chunk.metadata.parentText`) when constructing the final context prompt.
18
18
19
+
### 4. `MmrStrategy`
20
+
A post-retrieval diversity strategy implementing Maximal Marginal Relevance: greedily selects chunks that are relevant to the query but dissimilar to chunks already selected, so near-duplicate results (e.g. overlapping parent/child chunks) don't crowd out distinct context.
21
+
22
+
```typescript
23
+
import { MmrStrategy } from'@nestjs-agentic/rag';
24
+
25
+
const mmr =newMmrStrategy({
26
+
topK: 5,
27
+
lambda: 0.5, // 1 = pure relevance, 0 = pure diversity
28
+
});
29
+
```
30
+
31
+
Relevance comes from `context.scores` (populated by `RAGPipeline` during retrieval). Chunks without an `embedding` still participate in MMR selection using their relevance score, they just skip the diversity comparison against other chunks. If no chunk in the set has an embedding at all, MMR has no diversity signal to work with and passes the incoming order through, capped at `topK`.
32
+
19
33
Strategies aren't passed to `KnowledgeBase` directly — they're registered on a `RAGPipeline`, which runs pre-retrieval strategies, retrieves via the `KnowledgeBase`, then runs post-retrieval strategies:
Copy file name to clipboardExpand all lines: docs/ARCHITECTURE.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -177,6 +177,7 @@ All stores implement the shared `AgentMemoryStore` interface (`save(record)` / `
177
177
***Hybrid Retrieval (`HybridVectorStore`)**: Combines dense cosine-similarity vector search with a real BM25 sparse keyword score (IDF-weighted, `k1`/`b` length-normalization, incremental corpus statistics maintained on `addChunks`/`deleteChunk`). Fusion strategy is configurable via `fusionMethod`: `'weighted'` (default) blends max-normalized raw scores by `vectorWeight`; `'rrf'` combines the two rankers' rank positions via Reciprocal Rank Fusion (`reciprocalRankFusion`, smoothing constant `rrfK`, default `60`), avoiding cross-scale score normalization.
178
178
***Relational Traversal (`GraphRAGStrategy` + `InMemoryKnowledgeGraphProvider`)**: Traverses a manually or programmatically populated entity-relationship graph (imports, callers, inheritance) via BFS sub-graph queries, boosting chunks that mention matched entities.
179
179
***Pluggable Reranking (`RerankerStrategy`)**: A post-retrieval hook accepting any custom `rerankFn`, with built-in `createCohereRerankProvider`/`createVoyageRerankProvider` factories, a `minScore` cutoff to drop low-relevance chunks outright, and an observable `onRerankFailure`/`onRerankFailureMode` (`'fallback'` | `'throw'`) for when `rerankFn` fails, instead of silently degrading to term-overlap scoring.
180
+
***Diversity Selection (`MmrStrategy`)**: A post-retrieval Maximal Marginal Relevance strategy that greedily balances relevance (`context.scores`) against similarity to already-selected chunks (via a shared `cosineSimilarity` utility), reducing near-duplicate chunks (e.g. overlapping parent/child sections) crowding out distinct context. Tunable via `lambda` (relevance/diversity balance, default `0.5`) and `topK`.
Copy file name to clipboardExpand all lines: docs/ROADMAP.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -153,7 +153,7 @@ Goal: bring `@nestjs-agentic/rag`'s retrieval and reranking up to what its own d
153
153
-[x] Implement real Reciprocal Rank Fusion (RRF) for combining dense and sparse result lists ([#130](https://github.com/irzix/nestjs-agentic/issues/130)).
154
154
-[x] Replace `HybridVectorStore`'s term-frequency-only sparse score with real BM25 (IDF, k1/b saturation) ([#131](https://github.com/irzix/nestjs-agentic/issues/131)).
155
155
-[x] Ship built-in reranker provider adapters (Cohere, Voyage) and a `minScore` cutoff ([#132](https://github.com/irzix/nestjs-agentic/issues/132)).
156
-
-[] Add an MMR (Maximal Marginal Relevance) diversity strategy to reduce near-duplicate chunks in context ([#133](https://github.com/irzix/nestjs-agentic/issues/133)).
156
+
-[x] Add an MMR (Maximal Marginal Relevance) diversity strategy to reduce near-duplicate chunks in context ([#133](https://github.com/irzix/nestjs-agentic/issues/133)).
157
157
-[x] Fix `HybridVectorStore.addChunks` to embed via batched `embedDocuments` instead of one `embedQuery` call per chunk ([#134](https://github.com/irzix/nestjs-agentic/issues/134)).
158
158
-[ ] Add an embedding cache (in-memory LRU, pluggable Redis backend) wrapping any `EmbeddingProvider` ([#134](https://github.com/irzix/nestjs-agentic/issues/134)).
assert(mmrIds.join(',')==='a,c','Test 17c: MMR selects the relevant chunk plus a distinct one (A, C), surfacing more unique context than plain top-K (A, B)');
1068
+
1069
+
// 17d. lambda close to 1 behaves like pure relevance ranking (ignores diversity)
constchunkR={id: 'r1',parentId: 'p',content: 'r',metadata: {},embedding: [1,0]};// identical to P
1122
+
constantiResult=pureDiversity.process({
1123
+
query: 'q',
1124
+
chunks: [chunkP,chunkQ,chunkR],
1125
+
scores: newMap([...antiScores,['r1',0.98]]),
1126
+
});
1127
+
assert(
1128
+
antiResult.chunks![1].id==='q1',
1129
+
'Test 17k: an anti-correlated (negative cosine similarity) chunk is preferred over a duplicate under pure diversity, proving negative similarity is not clamped to 0',
1130
+
);
1131
+
1132
+
// 17l. Mixed embedded/non-embedded chunks: the non-embedded chunk still participates via its score
0 commit comments