Skip to content

Commit 7acae58

Browse files
fix(knowledge): share compact vector retrieval across access scopes (#7944)
* fix(knowledge): share compact vector retrieval across access scopes * fix(knowledge): align search fixtures with shared retrieval * fix(knowledge): preserve recall within bounded reranking * fix(knowledge): validate bounded compact scan plans * fix(knowledge): batch workspace search refills
1 parent cbdb864 commit 7acae58

7 files changed

Lines changed: 682 additions & 484 deletions

File tree

‎apps/sim/app/api/knowledge/search/utils.test.ts‎

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ describe('Knowledge Search Utils', () => {
217217
Array.from({ length: 201 }, (_, index) => ({ id: `candidate-${index}` }))
218218
)
219219
queueTableRows(schemaMock.embedding, [makeResult('second', 0.2), makeResult('first', 0.1)])
220+
queueTableRows(schemaMock.embedding, [makeResult('second', 0.2), makeResult('first', 0.1)])
220221

221222
const results = await handleTagAndVectorSearch({
222223
knowledgeBaseIds: ['kb-1', 'kb-2'],
@@ -231,11 +232,10 @@ describe('Knowledge Search Utils', () => {
231232

232233
expect(results.map((row) => row.id)).toEqual(['first', 'second'])
233234
expect(dbChainMockFns.select).toHaveBeenCalledTimes(3)
234-
expect(dbChainMockFns.as).toHaveBeenCalledWith('ranked_embeddings')
235235
expect(Object.keys(dbChainMockFns.select.mock.calls[0][0])).toEqual(['id'])
236-
expect(dbChainMockFns.limit).toHaveBeenNthCalledWith(1, 201)
236+
expect(dbChainMockFns.limit).toHaveBeenNthCalledWith(1, 400)
237237
expect(dbChainMockFns.select.mock.calls[1][0]).toHaveProperty('distance')
238-
expect(dbChainMockFns.limit).toHaveBeenCalledWith(2)
238+
expect(dbChainMockFns.limit).toHaveBeenCalledWith(20)
239239
})
240240

241241
it('should throw error when no filters provided', async () => {
@@ -463,11 +463,7 @@ describe('Knowledge Search Utils', () => {
463463
queryVector: JSON.stringify([0.1, 0.2, 0.3]),
464464
})
465465

466-
/**
467-
* A single global LIMIT would let the lexically strongest base consume
468-
* every slot, so an exact-token hit in a smaller base never reaches
469-
* fusion. The vector leg already fans out here; both legs must match.
470-
*/
466+
/** Keyword retrieval preserves its existing per-base lexical candidate selection. */
471467
expect(dbChainMockFns.select).toHaveBeenCalledTimes(knowledgeBaseIds.length)
472468
})
473469

@@ -542,6 +538,7 @@ describe('Knowledge Search Utils', () => {
542538
})
543539

544540
it('runs a single retrieval leg in vector mode', async () => {
541+
dbChainMockFns.execute.mockResolvedValue([{ id: 'vector-hit' }])
545542
queueTableRows(schemaMock.embedding, [{ id: 'vector-hit' }])
546543
queueTableRows(schemaMock.embedding, [makeResult('vector-hit')])
547544

@@ -551,22 +548,22 @@ describe('Knowledge Search Utils', () => {
551548
topK: 10,
552549
searchMode: 'vector',
553550
query: 'PROJ-1234',
554-
queryVector: JSON.stringify([0.1, 0.2, 0.3]),
551+
queryVector: { vector: JSON.stringify(TEST_EMBEDDING), dimensions: 1536 },
555552
})
556553

557554
expect(results.map((r) => r.id)).toEqual(['vector-hit'])
558-
expect(dbChainMockFns.select).toHaveBeenCalledTimes(3)
559-
expect(dbChainMockFns.as).toHaveBeenCalledWith('ranked_embeddings')
555+
expect(dbChainMockFns.select).toHaveBeenCalledTimes(2)
560556
})
561557

562558
it('runs both legs and fuses them in hybrid mode', async () => {
563559
/**
564-
* Chains dequeue in creation order: keyword ranking, the budgeted vector
565-
* probe, keyword hydration, then vector ranking and hydration in one query.
560+
* The raw vector probe does not consume a table chain. Keyword ranking and
561+
* hydration complete before vector exact ranking and content hydration.
566562
*/
563+
dbChainMockFns.execute.mockResolvedValue([{ id: 'vector-hit' }])
567564
queueTableRows(schemaMock.embedding, [{ id: 'keyword-hit', keywordRank: 0.9 }])
568-
queueTableRows(schemaMock.embedding, [{ id: 'vector-hit' }])
569565
queueTableRows(schemaMock.embedding, [makeResult('keyword-hit')])
566+
queueTableRows(schemaMock.embedding, [{ id: 'vector-hit' }])
570567
queueTableRows(schemaMock.embedding, [makeResult('vector-hit')])
571568

572569
const results = await executeKnowledgeSearch({
@@ -575,15 +572,16 @@ describe('Knowledge Search Utils', () => {
575572
topK: 10,
576573
searchMode: 'hybrid',
577574
query: 'PROJ-1234',
578-
queryVector: JSON.stringify([0.1, 0.2, 0.3]),
575+
queryVector: { vector: JSON.stringify(TEST_EMBEDDING), dimensions: 1536 },
579576
})
580577

581578
expect(results.map((r) => r.id).sort()).toEqual(['keyword-hit', 'vector-hit'])
582-
expect(dbChainMockFns.select).toHaveBeenCalledTimes(5)
579+
expect(dbChainMockFns.select).toHaveBeenCalledTimes(4)
583580
})
584581

585582
it('propagates unexpected keyword errors after the vector leg finishes', async () => {
586583
/** The failing ranking chain is still built first and takes the first queued set. */
584+
dbChainMockFns.execute.mockResolvedValue([{ id: 'vector-hit' }])
587585
queueTableRows(schemaMock.embedding, [{ id: 'never-ranked', keywordRank: 0 }])
588586
queueTableRows(schemaMock.embedding, [{ id: 'vector-hit' }])
589587
queueTableRows(schemaMock.embedding, [makeResult('vector-hit')])
@@ -600,10 +598,10 @@ describe('Knowledge Search Utils', () => {
600598
topK: 10,
601599
searchMode: 'hybrid',
602600
query: 'PROJ-1234',
603-
queryVector: JSON.stringify([0.1, 0.2, 0.3]),
601+
queryVector: { vector: JSON.stringify(TEST_EMBEDDING), dimensions: 1536 },
604602
})
605603
).rejects.toBe(failure)
606-
expect(dbChainMockFns.as).toHaveBeenCalledWith('ranked_embeddings')
604+
expect(dbChainMockFns.select).toHaveBeenCalledTimes(3)
607605
})
608606

609607
it('skips both query legs when only tag filters are provided', async () => {

‎apps/sim/lib/knowledge/__integration__/kb-block-search.integration.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ describe('API-key KB block fan-out', () => {
124124
expect(result.rows[0].knowledgeBaseId).toBe(bases[index].id)
125125
expect(result.rows[0].distance).toBeCloseTo(0)
126126
}
127-
expect(statements.filter((query) => query.includes('statement_timeout'))).toHaveLength(36)
127+
expect(statements.filter((query) => query.includes('statement_timeout'))).toHaveLength(54)
128128
expect(statements.filter((query) => query.includes('+ 0'))).toHaveLength(18)
129129
expect(statements.some((query) => query.includes('hnsw.iterative_scan'))).toBe(false)
130130
} finally {

0 commit comments

Comments
 (0)