Skip to content

Commit 60b7f2c

Browse files
fix(memory-core): include 'distances' in query — restore re-ranker semantic score (#13222) (#13223)
query_summaries and query_raw_memories passed a chroma `include` that omitted 'distances', so the response carried no distance values. The Dual-Pass re-ranker (StorageRouter) read an empty distances array -> vectorDist 0 -> semanticScore a constant 1, silently degrading ranking to topology-only and reporting distance:0 / relevanceScore:1 on every result. Add 'distances' to the include in MemoryService.queryMemories and SummaryService.querySummaries. Restores real vector distances end-to-end: Pass-1 distances -> real semanticScore -> composite rank -> relevanceScore. Regression: two substrate-free guards capture the include each service sends and assert it contains 'distances' (the prior relevanceScore > 0 assertion could not catch a constant 1). Split from #12450 (bug-b, the score-reporting half).
1 parent 006c71d commit 60b7f2c

3 files changed

Lines changed: 56 additions & 2 deletions

File tree

ai/services/memory-core/MemoryService.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1102,7 +1102,8 @@ class MemoryService extends Base {
11021102
const queryArgs = {
11031103
queryTexts: [query],
11041104
nResults,
1105-
include : ['metadatas']
1105+
// 'distances' is load-bearing — the Dual-Pass re-ranker's semantic score needs it (else topology-only).
1106+
include : ['metadatas', 'distances']
11061107
};
11071108

11081109
// Tenant-scoped where clause with additive shared-commons access.

ai/services/memory-core/SummaryService.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,8 @@ class SummaryService extends Base {
371371
const queryArgs = {
372372
queryTexts: [query],
373373
nResults,
374-
include : ['metadatas', 'documents']
374+
// 'distances' is load-bearing — the Dual-Pass re-ranker's semantic score needs it (else topology-only).
375+
include : ['metadatas', 'documents', 'distances']
375376
};
376377

377378
// Tenant-scoped where clause with additive shared-commons access.

test/playwright/unit/ai/services/memory-core/QueryReRanker.spec.mjs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,58 @@ test.describe('StorageRouter Query Re-Ranker Defensive Handling', () => {
148148
expect(result.results[0].relevanceScore).toBeGreaterThan(0);
149149
});
150150

151+
test('#13222: queryMemories requests distances in the chroma include (re-ranker semantic-score input)', async () => {
152+
// Regression guard for the include that omitted 'distances'. The Dual-Pass re-ranker reads the
153+
// vector distance as its Pass-1 semantic score; without 'distances' in the include, chroma
154+
// returns none, the score collapses to a constant 1, and ranking silently degrades to
155+
// topology-only (the pre-existing `relevanceScore > 0` assertion cannot catch a constant 1).
156+
// Substrate-free: stub the collection to capture the include the service actually sends, so the
157+
// guard runs without the seeded chroma data the behavioral path needs.
158+
const StorageRouter = (await import('../../../../../../ai/services/memory-core/managers/StorageRouter.mjs')).default;
159+
await StorageRouter.ready();
160+
161+
const originalGetCollection = StorageRouter.getMemoryCollection;
162+
let capturedInclude;
163+
164+
StorageRouter.getMemoryCollection = async () => ({
165+
query: async (args) => {
166+
capturedInclude = args.include;
167+
return {ids: [[]], distances: [[]], metadatas: [[]]};
168+
}
169+
});
170+
171+
try {
172+
await SDK.Memory_Service.queryMemories({query: 'include probe', nResults: 3, sessionId: 'include-probe'});
173+
expect(capturedInclude).toContain('distances');
174+
} finally {
175+
StorageRouter.getMemoryCollection = originalGetCollection;
176+
}
177+
});
178+
179+
test('#13222: querySummaries requests distances in the chroma include (summary-path parity)', async () => {
180+
const SummaryService = (await import('../../../../../../ai/services/memory-core/SummaryService.mjs')).default;
181+
await SummaryService.ready();
182+
const StorageRouter = (await import('../../../../../../ai/services/memory-core/managers/StorageRouter.mjs')).default;
183+
await StorageRouter.ready();
184+
185+
const originalGetCollection = StorageRouter.getSummaryCollection;
186+
let capturedInclude;
187+
188+
StorageRouter.getSummaryCollection = async () => ({
189+
query: async (args) => {
190+
capturedInclude = args.include;
191+
return {ids: [[]], distances: [[]], metadatas: [[]], documents: [[]]};
192+
}
193+
});
194+
195+
try {
196+
await SummaryService.querySummaries({query: 'include probe', nResults: 3});
197+
expect(capturedInclude).toContain('distances');
198+
} finally {
199+
StorageRouter.getSummaryCollection = originalGetCollection;
200+
}
201+
});
202+
151203
test('SummaryService.querySummaries should not crash on empty collections', async () => {
152204
// The summary collection is empty (no summarization run)
153205
// This should return gracefully, not crash

0 commit comments

Comments
 (0)