diff --git a/tests/authority-vector-primary.mjs b/tests/authority-vector-primary.mjs index b6b4f67..68dc8d8 100644 --- a/tests/authority-vector-primary.mjs +++ b/tests/authority-vector-primary.mjs @@ -33,6 +33,7 @@ const { isAuthorityVectorConfig, normalizeAuthorityVectorConfig, queryAuthorityTriviumNeighbors, + searchAuthorityTriviumNodes, applyAuthorityBmeVectorManifest, } = await import("../vector/authority-vector-primary-adapter.js"); const { @@ -125,6 +126,7 @@ function createMockTriviumClient({ } return { results: [ + { nodeId: "node-a", namespace: "other-chat", score: 0.95 }, { nodeId: "node-b", score: 0.91 }, { nodeId: "node-outside", score: 0.88 }, ], @@ -378,6 +380,7 @@ assert.equal(isAuthorityVectorConfig(config), true); assert.deepEqual(results, [{ nodeId: "node-b", score: 0.91 }]); const searchCall = triviumClient.calls.find(([name]) => name === "search"); assert.deepEqual(searchCall?.[1]?.candidateIds.sort(), ["node-a", "node-b"]); + assert.equal(searchCall?.[1]?.namespace, "st-bme::chat-authority-vector"); assert.equal(Array.isArray(searchCall?.[1]?.queryVector), true); assert.ok(searchCall?.[1]?.queryVector.length > 0); assert.equal(graph.vectorIndexState.lastSearchTimings.mode, "authority"); @@ -445,6 +448,50 @@ assert.equal(isAuthorityVectorConfig(config), true); } } +{ + const { graph } = createAuthorityVectorGraph(); + const fetchCalls = []; + const fetchImpl = async (url, options = {}) => { + const body = JSON.parse(String(options.body || "{}")); + fetchCalls.push({ url: String(url), body }); + if (String(url).endsWith("/session/init")) { + return { + ok: true, + status: 200, + json: async () => ({ sessionToken: "test-session" }), + }; + } + return { + ok: true, + status: 200, + json: async () => ({ + results: [ + { externalId: "node-a", namespace: "other-chat", score: 0.99 }, + { externalId: "node-b", namespace: "st-bme::chat-authority-vector", score: 0.93 }, + { externalId: "node-c", score: 0.72 }, + ], + }), + }; + }; + + const results = await searchAuthorityTriviumNodes(graph, "archive door", config, { + namespace: "st-bme::chat-authority-vector", + collectionId: "st-bme::chat-authority-vector", + chatId: "chat-authority-vector", + queryVector: [1, 0, 0], + topK: 5, + fetchImpl, + }); + const searchCall = fetchCalls.find((call) => call.url.endsWith("/trivium/search-hybrid")); + assert.equal(searchCall?.body?.namespace, "st-bme::chat-authority-vector"); + assert.equal(searchCall?.body?.collectionId, "st-bme::chat-authority-vector"); + assert.equal(searchCall?.body?.chatId, "chat-authority-vector"); + assert.deepEqual( + results.map((entry) => entry.nodeId), + ["node-b", "node-c"], + ); +} + { const { graph, first, second } = createAuthorityVectorGraph(); const triviumClient = createMockTriviumClient({ failSearch: true }); diff --git a/vector/authority-vector-primary-adapter.js b/vector/authority-vector-primary-adapter.js index 7f0ec6d..782c804 100644 --- a/vector/authority-vector-primary-adapter.js +++ b/vector/authority-vector-primary-adapter.js @@ -140,6 +140,15 @@ function normalizeNodeResultId(item = null) { ); } +function normalizeSearchResultNamespace(item = null) { + return normalizeRecordId( + item?.namespace || + item?.collectionId || + readNestedValue(item, ["payload", "namespace"]) || + readNestedValue(item, ["payload", "collectionId"]), + ); +} + function readResultRows(payload = null) { if (Array.isArray(payload)) return payload; if (!payload || typeof payload !== "object") return []; @@ -211,12 +220,17 @@ function getNodeFieldText(node = {}, keys = []) { return ""; } -function normalizeSearchResults(payload = null) { +function normalizeSearchResults(payload = null, { namespace = "" } = {}) { const rows = readResultRows(payload); + const expectedNamespace = normalizeRecordId(namespace); return rows .map((item, index) => { const nodeId = normalizeNodeResultId(item); if (!nodeId) return null; + const resultNamespace = normalizeSearchResultNamespace(item); + if (expectedNamespace && resultNamespace && resultNamespace !== expectedNamespace) { + return null; + } const rawScore = Number(item?.score ?? item?.similarity ?? item?.rankScore); const distance = Number(item?.distance); const score = Number.isFinite(rawScore) @@ -224,7 +238,11 @@ function normalizeSearchResults(payload = null) { : Number.isFinite(distance) ? 1 / (1 + Math.max(0, distance)) : Math.max(0.01, 1 - index / Math.max(1, rows.length)); - return { nodeId, score }; + return { + nodeId, + score, + ...(resultNamespace ? { namespace: resultNamespace } : {}), + }; }) .filter(Boolean); } @@ -597,8 +615,12 @@ export class AuthorityTriviumHttpClient { throw new Error("Authority Trivium v0.6 search requires vector"); } const queryText = String(payload.queryText || payload.text || payload.searchText || payload.query || ""); + const namespace = getNamespace(payload); const body = { ...this.buildOpenOptions(payload), + ...(namespace ? { namespace } : {}), + ...(payload.collectionId ? { collectionId: String(payload.collectionId) } : {}), + ...(payload.chatId ? { chatId: String(payload.chatId) } : {}), vector, topK: Number(payload.topK || payload.limit || 0) || undefined, expandDepth: Number(payload.expandDepth || payload.depth || 0) || undefined, @@ -1045,7 +1067,7 @@ export async function searchAuthorityTriviumNodes(graph, text, config = {}, opti topK: Math.max(1, Math.floor(Number(options.topK) || 1)), candidateIds: toArray(options.candidateIds).map(normalizeRecordId).filter(Boolean), }); - return normalizeSearchResults(payload); + return normalizeSearchResults(payload, { namespace: options.namespace }); } export async function testAuthorityTriviumConnection(config = {}, options = {}) {