diff --git a/tests/authority-vector-primary.mjs b/tests/authority-vector-primary.mjs index 46c70d6..a5bf085 100644 --- a/tests/authority-vector-primary.mjs +++ b/tests/authority-vector-primary.mjs @@ -33,6 +33,7 @@ const { isAuthorityVectorConfig, normalizeAuthorityVectorConfig, queryAuthorityTriviumNeighbors, + applyAuthorityBmeVectorManifest, } = await import("../vector/authority-vector-primary-adapter.js"); const { findSimilarNodesByText: findSimilarNodesByTextFromIndex, @@ -244,10 +245,36 @@ assert.equal(isAuthorityVectorConfig(config), true); const applyCall = triviumClient.calls.find(([name]) => name === "bmeVectorApply")?.[1]; assert.equal(applyCall.items.length, 2); assert.equal(applyCall.links.length, 1); + assert.equal(applyCall.observedDim, 2); + assert.equal(String(applyCall.vectorSpaceId || "").startsWith("vs_"), true); + assert.equal(applyCall.items.every((item) => item.payload?.vectorSpaceId === applyCall.vectorSpaceId), true); + assert.equal(applyCall.items.every((item) => item.payload?.observedDim === 2), true); assert.equal(applyCall.items.every((item) => Array.isArray(item.vector) && item.vector.length > 0), true); assert.equal(result.timings.authorityDiagnostics.upsert.operation, "bmeVectorApply"); } +{ + const { graph } = createAuthorityVectorGraph(); + const triviumClient = createMockTriviumClient(); + const entries = [ + { nodeId: "node-a", text: "a", hash: "hash-a", index: 0 }, + { nodeId: "node-b", text: "b", hash: "hash-b", index: 1 }, + ]; + graph.nodes[0].embedding = [1, 0, 0]; + graph.nodes[1].embedding = [1, 0]; + await assert.rejects( + () => applyAuthorityBmeVectorManifest(graph, { ...config, bmeVectorApplyReady: true }, entries, { + namespace: "st-bme::chat-authority-vector", + collectionId: "st-bme::chat-authority-vector", + chatId: "chat-authority-vector", + modelScope: "scope", + triviumClient, + }), + /single vector dimension/, + ); + assert.equal(triviumClient.calls.some(([name]) => name === "bmeVectorApply"), false); +} + { const { graph } = createAuthorityVectorGraph(); const triviumClient = createMockTriviumClient({ failBmeVectorApply: true }); diff --git a/vector/authority-vector-primary-adapter.js b/vector/authority-vector-primary-adapter.js index a8c6bf2..a997e5e 100644 --- a/vector/authority-vector-primary-adapter.js +++ b/vector/authority-vector-primary-adapter.js @@ -5,6 +5,7 @@ import { AuthorityHttpError, } from "../runtime/authority-http-client.js"; import { embedBatch } from "./embedding.js"; +import { deriveVectorSpace } from "./vector-space.js"; export const AUTHORITY_VECTOR_MODE = "authority"; export const AUTHORITY_VECTOR_SOURCE = "authority-trivium"; @@ -872,7 +873,7 @@ export async function upsertAuthorityTriviumEntries(graph, config = {}, entries } export async function applyAuthorityBmeVectorManifest(graph, config = {}, entries = [], options = {}) { - const items = buildAuthorityVectorItems(graph, entries, options); + let items = buildAuthorityVectorItems(graph, entries, options); const links = buildAuthorityLinkItems(graph, options).map((link) => ({ src: buildNodeReference(link.fromId, options.namespace), dst: buildNodeReference(link.toId, options.namespace), @@ -883,6 +884,29 @@ export async function applyAuthorityBmeVectorManifest(graph, config = {}, entrie if (missingVector) { throw new Error("BME vector apply requires vector for every item"); } + const observedDim = items.reduce((dim, item) => { + const vectorDim = normalizeVector(item?.vector || item?.embedding).length; + if (!vectorDim) return dim; + if (!dim) return vectorDim; + return dim === vectorDim ? dim : -1; + }, 0); + if (observedDim < 0) { + const error = new Error("BME vector apply requires a single vector dimension per batch"); + error.errorCategory = "vector-dimension-mismatch"; + error.errorDomain = "embedding"; + throw error; + } + const vectorSpace = observedDim > 0 ? deriveVectorSpace(config, observedDim) : null; + if (vectorSpace?.vectorSpaceId) { + items = items.map((item) => ({ + ...item, + payload: { + ...(item.payload || {}), + vectorSpaceId: vectorSpace.vectorSpaceId, + observedDim: vectorSpace.observedDim, + }, + })); + } throwIfAborted(options.signal); const client = createAuthorityTriviumClient(config, options); const startedAt = nowMs(); @@ -900,6 +924,8 @@ export async function applyAuthorityBmeVectorManifest(graph, config = {}, entrie graphRevision: Math.max(0, Math.floor(Number(options.revision) || 0)), modelScope: String(options.modelScope || ""), embeddingMode: config.embeddingMode || "client", + ...(vectorSpace?.vectorSpaceId ? { vectorSpaceId: vectorSpace.vectorSpaceId } : {}), + ...(observedDim > 0 ? { observedDim } : {}), items, links, idempotencyKey: [