This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
// The vector contract suite against the store shape this module binds in tests: a conforming
|
||||
// in-memory VectorStore that keeps the tenant wall the real adapters guarantee (org filter on
|
||||
// every search and delete). The suite is the kit's own two-tenant contract; running it here pins
|
||||
// the module's metadata conventions (collection filters, subject deletion) to what every adapter
|
||||
// must provide, so a convention drift fails in this module's unit suite.
|
||||
|
||||
import { newId, type TenantScope } from "@conusai-kit/core"
|
||||
import type { VectorMatch, VectorQuery, VectorRecord, VectorStore, VectorSubject } from "@conusai-kit/data"
|
||||
import { runVectorStoreContract } from "@conusai-kit/testing"
|
||||
import { describe, expect, it } from "vitest"
|
||||
|
||||
/** The in-memory stand-in with the tenant wall the adapters implement. */
|
||||
function fakeVectorStore(): { store: VectorStore; failures: () => unknown[] } {
|
||||
const rows = new Map<string, { scope: string; record: VectorRecord }>()
|
||||
const keyOf = (orgId: string, id: string) => `${orgId}:${id}`
|
||||
const failures: unknown[] = []
|
||||
const cosine = (a: readonly number[], b: readonly number[]) => {
|
||||
let dot = 0
|
||||
let na = 0
|
||||
let nb = 0
|
||||
for (const [index, value] of a.entries()) {
|
||||
dot += value * (b[index] ?? 0)
|
||||
na += value * value
|
||||
nb += (b[index] ?? 0) ** 2
|
||||
}
|
||||
return na === 0 || nb === 0 ? 0 : dot / Math.sqrt(na * nb)
|
||||
}
|
||||
return {
|
||||
failures: () => failures,
|
||||
store: {
|
||||
async upsert(scope: TenantScope, records: readonly VectorRecord[]) {
|
||||
for (const record of records) rows.set(keyOf(scope.orgId, record.id), { scope: scope.orgId, record })
|
||||
},
|
||||
async search(scope: TenantScope, query: VectorQuery): Promise<readonly VectorMatch[]> {
|
||||
const out: VectorMatch[] = []
|
||||
for (const row of rows.values()) {
|
||||
if (row.scope !== scope.orgId) continue
|
||||
const metadata = row.record.metadata ?? {}
|
||||
const filter = query.filter ?? {}
|
||||
if (!Object.entries(filter).every(([key, value]) => metadata[key] === value)) continue
|
||||
out.push({
|
||||
id: row.record.id,
|
||||
score: cosine(query.vector, row.record.vector),
|
||||
subject: row.record.subject,
|
||||
metadata,
|
||||
})
|
||||
}
|
||||
return out.sort((a, b) => b.score - a.score).slice(0, query.limit ?? 10)
|
||||
},
|
||||
async deleteBySubject(scope: TenantScope, subject: VectorSubject) {
|
||||
for (const [key, row] of rows.entries()) {
|
||||
if (
|
||||
row.scope === scope.orgId &&
|
||||
row.record.subject.type === subject.type &&
|
||||
row.record.subject.id === subject.id
|
||||
) {
|
||||
rows.delete(key)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const dimensions = 4
|
||||
|
||||
describe("the vector store the knowledge module relies on", () => {
|
||||
it("passes the kit's two-tenant vector contract", async () => {
|
||||
const { store } = fakeVectorStore()
|
||||
const result = await runVectorStoreContract(async () => ({ store, dimensions }))
|
||||
expect(result.failures.map((failure) => `${failure.check}: ${failure.error}`)).toEqual([])
|
||||
expect(result.passed.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
// The collection-filter convention this module writes into every chunk's metadata: a search names
|
||||
// the collection and the wall holds. Pinning it here keeps the adapters' payload indexes useful.
|
||||
describe("the module's metadata conventions", () => {
|
||||
it("stores collection, filename, chunk and snippet so a cited result can be rendered", () => {
|
||||
const record: VectorRecord = {
|
||||
id: newId(),
|
||||
vector: [1, 0, 0, 0],
|
||||
subject: { type: "knowledge_document", id: newId() },
|
||||
metadata: { collection: "contracts", filename: "a.txt", chunk: 2, snippet: "passage" },
|
||||
}
|
||||
expect(Object.keys(record.metadata ?? {}).sort()).toEqual(["chunk", "collection", "filename", "snippet"])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user