fix(vector): scope authority search by namespace

This commit is contained in:
youzini
2026-06-09 11:36:20 +00:00
parent 74ee28a365
commit b56103c6fd
2 changed files with 72 additions and 3 deletions

View File

@@ -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 });

View File

@@ -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 = {}) {