fix(find): near() searches around the anchor's own vector, and refuses by name without one
The proximity search fetched its anchor through get(), which omits vectors
by default, then handed a zero-length vector to the index — every
find({ near }) refused with a dimension mismatch, for every caller. Found
by the Rust planner's first-contact pins comparing outcomes with and
without the planner on a refused shape. The anchor is now fetched with its
vector, and an anchor that has none refuses by name — a proximity search
around an unvectored row has no meaning and must not fail inside the index.
Pinned in tests/integration/find-near.test.ts.
This commit is contained in:
parent
34f1886f7c
commit
a8c5fbf9dc
2 changed files with 59 additions and 1 deletions
|
|
@ -15892,8 +15892,18 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
)
|
||||
}
|
||||
|
||||
const nearEntity = await this.get(params.near.id)
|
||||
// The anchor's VECTOR is the query; get() omits vectors by default, which
|
||||
// fed a zero-length vector to the index and refused every near() with a
|
||||
// dimension mismatch. Ask for it, and refuse by name when the anchor has
|
||||
// none — a proximity search around an unvectored row has no meaning.
|
||||
const nearEntity = await this.get(params.near.id, { includeVectors: true })
|
||||
if (!nearEntity) return []
|
||||
if (!nearEntity.vector || nearEntity.vector.length === 0) {
|
||||
throw new Error(
|
||||
`find({ near }): entity '${params.near.id}' has no vector to search around — ` +
|
||||
`it was never embedded (or was unvectored). Embed it, or search with a query instead.`
|
||||
)
|
||||
}
|
||||
|
||||
const nearResults: [string, number][] = await this.index.search(nearEntity.vector, params.limit || 10)
|
||||
|
||||
|
|
|
|||
48
tests/integration/find-near.test.ts
Normal file
48
tests/integration/find-near.test.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* @module tests/integration/find-near
|
||||
* @description find({ near }) searches around the anchor's OWN vector (10.4.10).
|
||||
*
|
||||
* The proximity search fetched its anchor without vectors and fed a
|
||||
* zero-length vector to the index — every near() refused with a dimension
|
||||
* mismatch, for every caller. Found by the Rust planner's first-contact pins
|
||||
* (the planner declines `near`; the pin compared outcomes with and without
|
||||
* it). Now the anchor is fetched with its vector, and an anchor without one
|
||||
* refuses by name instead of failing inside the index.
|
||||
*/
|
||||
import { describe, it, expect, beforeAll } from 'vitest'
|
||||
import { Brainy } from '../../src/brainy'
|
||||
import { NounType } from '../../src/types/graphTypes'
|
||||
import { v5 } from '../../src/universal/uuid'
|
||||
import { generateTestVector } from '../helpers/test-factory'
|
||||
|
||||
describe('find({ near }) uses the anchor vector', () => {
|
||||
let brain: Brainy<any>
|
||||
const anchorVector = generateTestVector()
|
||||
|
||||
beforeAll(async () => {
|
||||
brain = new Brainy({ requireSubtype: false, storage: { type: 'memory' } })
|
||||
await brain.init()
|
||||
await brain.add({ id: 'anchor', data: 'anchor row', type: NounType.Thing, vector: anchorVector })
|
||||
// A twin with the identical vector and a far row.
|
||||
await brain.add({ id: 'twin', data: 'twin row', type: NounType.Thing, vector: [...anchorVector] })
|
||||
await brain.add({ id: 'far', data: 'far row', type: NounType.Thing, vector: generateTestVector() })
|
||||
})
|
||||
|
||||
it('returns the anchor\'s neighbours by its own vector', async () => {
|
||||
const results = await brain.find({ near: { id: 'anchor' }, limit: 3 })
|
||||
expect(results.length).toBeGreaterThan(0)
|
||||
const ids = results.map((r) => r.entity.id)
|
||||
expect(ids).toContain(v5('twin'))
|
||||
})
|
||||
|
||||
it('refuses by name when the anchor has no vector', async () => {
|
||||
await brain.add({
|
||||
id: 'unvectored',
|
||||
data: 'no vector here',
|
||||
type: NounType.Thing,
|
||||
deferEmbedding: true
|
||||
})
|
||||
;(brain as any).kickEmbedWorker = () => {}
|
||||
await expect(brain.find({ near: { id: 'unvectored' }, limit: 3 })).rejects.toThrow(/has no vector to search around/)
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue