feat(vector): use Authority BME apply when available

This commit is contained in:
opencode
2026-05-15 10:59:49 +00:00
parent 6e547bcdaf
commit 3c69b7d4fb
5 changed files with 233 additions and 36 deletions

View File

@@ -7086,8 +7086,12 @@ async function resolveAuthorityCapabilityForStoreSelection(settings = getSetting
function buildAuthorityGraphStoreOptions(settings = getSettings()) {
const normalizedSettings = normalizeAuthoritySettings(settings);
const capability = normalizeAuthorityCapabilityState(authorityCapabilityState, settings);
return {
baseUrl: normalizedSettings.baseUrl,
bmeVectorManifestReady: Boolean(capability.bmeVectorManifestReady),
bmeVectorApplyReady: Boolean(capability.bmeVectorApplyReady),
bmeProtocolVersion: Math.max(0, Number(capability.bmeProtocolVersion) || 0),
headerProvider:
typeof getRequestHeaders === "function" ? () => getRequestHeaders() : null,
};

View File

@@ -15,7 +15,7 @@ const capability = normalizeAuthorityProbeResponse({
bme: {
protocolVersion: 1,
vectorManifest: true,
vectorApply: false,
vectorApply: true,
vectorApplyJobs: false,
serverEmbeddingProbe: false,
candidateSearch: false,
@@ -25,7 +25,7 @@ const capability = normalizeAuthorityProbeResponse({
assert.equal(capability.bmeProtocolVersion, 1);
assert.equal(capability.bmeVectorManifestReady, true);
assert.equal(capability.bmeVectorApplyReady, false);
assert.equal(capability.bmeVectorApplyReady, true);
assert.equal(capability.bmeServerEmbeddingProbeReady, false);
assert.ok(capability.features.includes("bme.vectormanifest"));
assert.ok(capability.features.includes("bme.protocolversion"));

View File

@@ -70,7 +70,7 @@ function createAuthorityVectorGraph() {
return { graph, first, second };
}
function createMockTriviumClient({ failBulkUpsert = false, failSearch = false } = {}) {
function createMockTriviumClient({ failBulkUpsert = false, failSearch = false, failBmeVectorApply = false } = {}) {
const calls = [];
return {
calls,
@@ -146,6 +146,23 @@ function createMockTriviumClient({ failBulkUpsert = false, failSearch = false }
calls.push(["stat", payload]);
return { ok: true };
},
async bmeVectorApply(payload) {
calls.push(["bmeVectorApply", payload]);
if (failBmeVectorApply) {
throw new AuthorityHttpError("bme apply missing", {
status: 404,
category: "validation",
path: "/bme/vector-apply",
});
}
return {
ok: true,
database: payload.database || "st_bme_vectors",
manifest: { database: payload.database || "st_bme_vectors", exists: true },
upsert: { successCount: payload.items?.length || 0, failureCount: 0 },
links: { successCount: payload.links?.length || 0, failureCount: 0 },
};
},
};
}
@@ -209,6 +226,46 @@ assert.equal(isAuthorityVectorConfig(config), true);
assert.equal(result.timings.authorityDiagnostics.link.totalItems, 1);
}
{
const { graph } = createAuthorityVectorGraph();
const triviumClient = createMockTriviumClient();
const applyConfig = { ...config, bmeVectorApplyReady: true };
const result = await syncGraphVectorIndexFromIndex(graph, applyConfig, {
chatId: "chat-authority-vector",
purge: true,
triviumClient,
});
assert.equal(result.stats.indexed, 2);
assert.equal(graph.vectorIndexState.dirty, false);
assert.equal(triviumClient.calls.filter(([name]) => name === "bmeVectorApply").length, 1);
assert.equal(triviumClient.calls.some(([name]) => name === "purge"), false);
assert.equal(triviumClient.calls.some(([name]) => name === "bulkUpsert"), false);
const applyCall = triviumClient.calls.find(([name]) => name === "bmeVectorApply")?.[1];
assert.equal(applyCall.items.length, 2);
assert.equal(applyCall.links.length, 1);
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({ failBmeVectorApply: true });
const applyConfig = { ...config, bmeVectorApplyReady: true };
const result = await syncGraphVectorIndexFromIndex(graph, applyConfig, {
chatId: "chat-authority-vector",
purge: true,
triviumClient,
});
assert.equal(result.stats.indexed, 2);
assert.equal(graph.vectorIndexState.dirty, false);
assert.equal(triviumClient.calls.filter(([name]) => name === "bmeVectorApply").length, 1);
assert.equal(triviumClient.calls.filter(([name]) => name === "purge").length, 1);
assert.ok(triviumClient.calls.some(([name]) => name === "bulkUpsert"));
assert.ok(triviumClient.calls.some(([name]) => name === "linkMany"));
}
{
const { graph, first, second } = createAuthorityVectorGraph();
const triviumClient = createMockTriviumClient();

View File

@@ -397,6 +397,9 @@ export function normalizeAuthorityVectorConfig(settings = {}, overrides = {}) {
),
timeoutMs: Math.max(0, Number(source.timeoutMs || 0) || 0),
failOpen: source.authorityVectorFailOpen !== false && source.failOpen !== false,
bmeVectorApplyReady: Boolean(source.bmeVectorApplyReady ?? source.authorityBmeVectorApplyReady),
bmeVectorManifestReady: Boolean(source.bmeVectorManifestReady ?? source.authorityBmeVectorManifestReady),
bmeProtocolVersion: Math.max(0, Number(source.bmeProtocolVersion ?? source.authorityBmeProtocolVersion) || 0),
...overrides,
};
}
@@ -676,6 +679,10 @@ export class AuthorityTriviumHttpClient {
...(payload.includeMappingIntegrity ? { includeMappingIntegrity: true } : {}),
});
}
async bmeVectorApply(payload = {}) {
return await this.requestV06("/bme/vector-apply", payload);
}
}
export function createAuthorityTriviumClient(config = {}, options = {}) {
@@ -864,6 +871,79 @@ export async function upsertAuthorityTriviumEntries(graph, config = {}, entries
};
}
export async function applyAuthorityBmeVectorManifest(graph, config = {}, entries = [], options = {}) {
const items = buildAuthorityVectorItems(graph, entries, options);
const links = buildAuthorityLinkItems(graph, options).map((link) => ({
src: buildNodeReference(link.fromId, options.namespace),
dst: buildNodeReference(link.toId, options.namespace),
label: link.relation,
weight: link.weight,
}));
const missingVector = items.find((item) => !normalizeVector(item?.vector || item?.embedding).length);
if (missingVector) {
throw new Error("BME vector apply requires vector for every item");
}
throwIfAborted(options.signal);
const client = createAuthorityTriviumClient(config, options);
const startedAt = nowMs();
const estimatedBytes = estimateJsonBytes({ items, links });
const result = await callClient(
client,
["bmeVectorApply", "applyBmeVectorManifest", "vectorApply"],
"bmeVectorApply",
{
...buildOpenOptions(config, options),
database: config.database,
namespace: options.namespace,
collectionId: options.collectionId,
chatId: options.chatId,
graphRevision: Math.max(0, Math.floor(Number(options.revision) || 0)),
modelScope: String(options.modelScope || ""),
embeddingMode: config.embeddingMode || "client",
items,
links,
idempotencyKey: [
options.chatId || "chat",
options.collectionId || options.namespace || "collection",
options.revision || 0,
options.modelScope || "model",
items.length,
links.length,
].join(":"),
},
);
const upserted = Number(result?.upsert?.successCount ?? result?.upserted ?? items.length) || 0;
const linked = Number(result?.links?.successCount ?? result?.linked ?? links.length) || 0;
const ok = result?.ok !== false && Number(result?.upsert?.failureCount || 0) === 0 && Number(result?.links?.failureCount || 0) === 0;
if (!ok) {
const error = new Error("BME vector apply returned failures");
error.authorityDiagnostics = {
operation: "bmeVectorApply",
totalItems: items.length,
linkItems: links.length,
upsertFailures: Number(result?.upsert?.failureCount || 0),
linkFailures: Number(result?.links?.failureCount || 0),
result,
};
throw error;
}
return {
...result,
upserted,
linked,
diagnostics: {
operation: "bmeVectorApply",
totalItems: items.length,
linkItems: links.length,
upserted,
linked,
estimatedBytes,
manifest: result?.manifest || null,
totalMs: roundMs(nowMs() - startedAt),
},
};
}
export async function syncAuthorityTriviumLinks(graph, config = {}, options = {}) {
const links = buildAuthorityLinkItems(graph, options);
if (!links.length) {

View File

@@ -9,6 +9,7 @@ import { buildVectorCollectionId, stableHashString } from "../runtime/runtime-st
import {
AUTHORITY_VECTOR_MODE,
AUTHORITY_VECTOR_SOURCE,
applyAuthorityBmeVectorManifest,
deleteAuthorityTriviumNodes,
isAuthorityVectorConfig,
normalizeAuthorityVectorConfig,
@@ -840,23 +841,50 @@ export async function syncGraphVectorIndex(
if (embeddingResult.failures > 0) {
throw createEmbeddingProviderError(embeddingResult.failures);
}
const purgeStartedAt = nowMs();
const purgeResult = await purgeAuthorityTriviumNamespace(config, authorityOptions);
authorityPurgeMs += nowMs() - purgeStartedAt;
authorityPurgeDiagnostics = purgeResult?.diagnostics || null;
if (purgeResult?.truncated) {
throw new Error(`Authority Trivium purge truncated after ${purgeResult.pages || 0} page(s)`);
let appliedViaBme = false;
if (config.bmeVectorApplyReady === true) {
try {
const applyStartedAt = nowMs();
const applyResult = await applyAuthorityBmeVectorManifest(
graph,
config,
desiredEntries,
authorityOptions,
);
authorityUpsertMs += nowMs() - applyStartedAt;
authorityUpsertDiagnostics = applyResult?.diagnostics || null;
authorityLinkDiagnostics = {
operation: "bmeVectorApply:links",
totalItems: Number(applyResult?.diagnostics?.linkItems || 0),
linked: Number(applyResult?.diagnostics?.linked || 0),
totalMs: 0,
};
resetVectorMappings(graph, config, effectiveChatId);
appliedViaBme = true;
} catch (applyError) {
if (isAbortError(applyError)) throw applyError;
console.warn("[ST-BME] BME 服务端向量 apply 失败,回退 Authority Trivium 旧路径:", applyError);
}
}
if (!appliedViaBme) {
const purgeStartedAt = nowMs();
const purgeResult = await purgeAuthorityTriviumNamespace(config, authorityOptions);
authorityPurgeMs += nowMs() - purgeStartedAt;
authorityPurgeDiagnostics = purgeResult?.diagnostics || null;
if (purgeResult?.truncated) {
throw new Error(`Authority Trivium purge truncated after ${purgeResult.pages || 0} page(s)`);
}
resetVectorMappings(graph, config, effectiveChatId);
const upsertStartedAt = nowMs();
const upsertResult = await upsertAuthorityTriviumEntries(
graph,
config,
desiredEntries,
authorityOptions,
);
authorityUpsertMs += nowMs() - upsertStartedAt;
authorityUpsertDiagnostics = upsertResult?.diagnostics || null;
}
resetVectorMappings(graph, config, effectiveChatId);
const upsertStartedAt = nowMs();
const upsertResult = await upsertAuthorityTriviumEntries(
graph,
config,
desiredEntries,
authorityOptions,
);
authorityUpsertMs += nowMs() - upsertStartedAt;
authorityUpsertDiagnostics = upsertResult?.diagnostics || null;
for (const entry of desiredEntries) {
state.hashToNodeId[entry.hash] = entry.nodeId;
state.nodeToHash[entry.nodeId] = entry.hash;
@@ -905,19 +933,45 @@ export async function syncGraphVectorIndex(
throw createEmbeddingProviderError(embeddingResult.failures);
}
deletedNodeCount = nodeIdsToDelete.length;
const deleteStartedAt = nowMs();
const deleteResult = await deleteAuthorityTriviumNodes(config, nodeIdsToDelete, authorityOptions);
authorityDeleteMs += nowMs() - deleteStartedAt;
authorityDeleteDiagnostics = deleteResult?.diagnostics || null;
const upsertStartedAt = nowMs();
const upsertResult = await upsertAuthorityTriviumEntries(
graph,
config,
entriesToUpsert,
authorityOptions,
);
authorityUpsertMs += nowMs() - upsertStartedAt;
authorityUpsertDiagnostics = upsertResult?.diagnostics || null;
let appliedViaBme = false;
if (config.bmeVectorApplyReady === true && nodeIdsToDelete.length === 0) {
try {
const applyStartedAt = nowMs();
const applyResult = await applyAuthorityBmeVectorManifest(
graph,
config,
entriesToUpsert,
authorityOptions,
);
authorityUpsertMs += nowMs() - applyStartedAt;
authorityUpsertDiagnostics = applyResult?.diagnostics || null;
authorityLinkDiagnostics = {
operation: "bmeVectorApply:links",
totalItems: Number(applyResult?.diagnostics?.linkItems || 0),
linked: Number(applyResult?.diagnostics?.linked || 0),
totalMs: 0,
};
appliedViaBme = true;
} catch (applyError) {
if (isAbortError(applyError)) throw applyError;
console.warn("[ST-BME] BME 服务端向量 apply 失败,回退 Authority Trivium 旧路径:", applyError);
}
}
if (!appliedViaBme) {
const deleteStartedAt = nowMs();
const deleteResult = await deleteAuthorityTriviumNodes(config, nodeIdsToDelete, authorityOptions);
authorityDeleteMs += nowMs() - deleteStartedAt;
authorityDeleteDiagnostics = deleteResult?.diagnostics || null;
const upsertStartedAt = nowMs();
const upsertResult = await upsertAuthorityTriviumEntries(
graph,
config,
entriesToUpsert,
authorityOptions,
);
authorityUpsertMs += nowMs() - upsertStartedAt;
authorityUpsertDiagnostics = upsertResult?.diagnostics || null;
}
for (const entry of entriesToUpsert) {
state.hashToNodeId[entry.hash] = entry.nodeId;
@@ -926,10 +980,12 @@ export async function syncGraphVectorIndex(
}
}
const linkStartedAt = nowMs();
const linkResult = await syncAuthorityTriviumLinks(graph, config, authorityOptions);
authorityLinkMs += nowMs() - linkStartedAt;
authorityLinkDiagnostics = linkResult?.diagnostics || null;
if (!authorityLinkDiagnostics || authorityLinkDiagnostics.operation !== "bmeVectorApply:links") {
const linkStartedAt = nowMs();
const linkResult = await syncAuthorityTriviumLinks(graph, config, authorityOptions);
authorityLinkMs += nowMs() - linkStartedAt;
authorityLinkDiagnostics = linkResult?.diagnostics || null;
}
for (const node of graph.nodes || []) {
if (Array.isArray(node.embedding) && node.embedding.length > 0) {