mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-06-13 18:31:16 +08:00
Merge dev authority protocol fixes
This commit is contained in:
@@ -51,6 +51,10 @@ HTTP 错误(400/401/403/429/502 等)会带状态码和响应体抛出,而
|
||||
|
||||
> BME 在 payload 里发送 `vectorSpaceId` 和 `observedDim`(顶层 + 每项元数据)。DOA 按批校验 vectorSpaceId/observedDim 一致性,拒绝混合维度,返回带类型的校验错误。失败/404/旧 DOA 时回退到旧 Authority Trivium 路径或本地。
|
||||
|
||||
协议身份字段有严格分工:BME 节点 id 只作为 `externalId` / `nodeId` / `payload.nodeId` 发送;`id` 保留给 Trivium 内部 numeric id,BME 不会把字符串 node id 塞进顶层 `id`。边关系使用 Trivium public reference 形状:`src/dst = { externalId, namespace }`,由 Authority 解析到内部 id。
|
||||
|
||||
搜索请求也必须携带 `namespace` / `collectionId` / `chatId`。如果 Authority 返回带 namespace 的命中,BME 会保守过滤掉不属于当前 namespace 的结果;老后端不返回 namespace 时结果仍保留,以避免过度破坏兼容性。
|
||||
|
||||
## 连接测试
|
||||
|
||||
`testVectorConnection()` 测的是**真实批量 embedding 路径**(走 `embedBatch`),而不是单条短文本——因为"测试通过但实际 embedding 失败"的根因就是测试只测了单条短文本而运行时用的是批量长文本。
|
||||
|
||||
@@ -65,6 +65,12 @@ Authority 的后台 job 系统只支持特定 job 类型。ST-BME 不能盲目
|
||||
|
||||
> 该端点按批校验 `vectorSpaceId` / `observedDim` 一致性,拒绝混合维度,返回带类型的校验错误。
|
||||
|
||||
BME → Authority 的向量协议约定:
|
||||
|
||||
- 节点身份使用 `externalId` / `nodeId`;顶层 `id` 是 Trivium 内部 numeric id,BME 不发送字符串 node id 到该字段。
|
||||
- link 使用 `{ src: { externalId, namespace }, dst: { externalId, namespace }, label, weight }`,由 Authority 在服务端解析成 Trivium 内部 id。
|
||||
- search 请求携带 `namespace` / `collectionId` / `chatId`;返回结果若带 namespace,BME 会过滤掉非当前 namespace 的命中,避免多聊天/多集合污染。
|
||||
|
||||
向量空间身份和维度门禁的算法见 [`../algorithms/vector-and-embedding.md`](../algorithms/vector-and-embedding.md)。
|
||||
|
||||
## Authority SQL 图谱存储选择
|
||||
|
||||
@@ -1145,7 +1145,11 @@ function getChatVariables() {
|
||||
}
|
||||
|
||||
function buildEjsContext() {
|
||||
const vars = getChatVariables();
|
||||
return createEnaPlannerEjsContext(getChatVariables());
|
||||
}
|
||||
|
||||
export function createEnaPlannerEjsContext(varsInput = {}) {
|
||||
const vars = varsInput && typeof varsInput === 'object' ? { ...varsInput } : {};
|
||||
|
||||
// getvar: read a chat variable (supports dot-path for nested objects)
|
||||
function getvar(name) {
|
||||
@@ -1195,7 +1199,7 @@ function shouldSkipSyncEjsPreRender(template) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function renderEjsTemplate(template, ctx, templateLabel = '') {
|
||||
export function renderEjsTemplate(template, ctx, templateLabel = '') {
|
||||
const labelSuffix = templateLabel ? ` (${templateLabel})` : '';
|
||||
|
||||
if (shouldSkipSyncEjsPreRender(template)) {
|
||||
@@ -1205,7 +1209,15 @@ function renderEjsTemplate(template, ctx, templateLabel = '') {
|
||||
// Try window.ejs first (ST loads this library)
|
||||
if (window.ejs?.render) {
|
||||
try {
|
||||
return window.ejs.render(template, ctx, { async: false });
|
||||
const renderCtx = ctx && typeof ctx === 'object' ? { ...ctx } : ctx;
|
||||
if (renderCtx && typeof renderCtx === 'object') {
|
||||
delete renderCtx.__append;
|
||||
delete renderCtx.print;
|
||||
}
|
||||
return window.ejs.render(template, renderCtx, {
|
||||
async: false,
|
||||
outputFunctionName: 'print',
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn(`[EnaPlanner] EJS render failed${labelSuffix}, template returned as-is:`, e?.message);
|
||||
return template;
|
||||
@@ -2017,4 +2029,3 @@ export function cleanupEnaPlanner() {
|
||||
delete window.stBmeEnaPlanner;
|
||||
_bmeRuntime = null;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,25 +34,28 @@ function isTavernHelperPromptViewerSyntheticGeneration(runtime) {
|
||||
}
|
||||
|
||||
export function registerBeforeCombinePromptsController(runtime, listener) {
|
||||
const makeFirst = runtime.getEventMakeFirst();
|
||||
const eventName = runtime.eventTypes.GENERATE_BEFORE_COMBINE_PROMPTS;
|
||||
const eventSourceMakeFirst = runtime.eventSource?.makeFirst;
|
||||
const makeFirst =
|
||||
typeof eventSourceMakeFirst === "function"
|
||||
? eventSourceMakeFirst.bind(runtime.eventSource)
|
||||
: runtime.getEventMakeFirst?.();
|
||||
if (typeof makeFirst === "function") {
|
||||
return makeFirst(
|
||||
runtime.eventTypes.GENERATE_BEFORE_COMBINE_PROMPTS,
|
||||
listener,
|
||||
);
|
||||
return makeFirst(eventName, listener);
|
||||
}
|
||||
|
||||
runtime.console.warn("[ST-BME] eventMakeFirst 不可用,回退到普通事件注册");
|
||||
runtime.eventSource.on(
|
||||
runtime.eventTypes.GENERATE_BEFORE_COMBINE_PROMPTS,
|
||||
listener,
|
||||
);
|
||||
runtime.eventSource.on(eventName, listener);
|
||||
return null;
|
||||
}
|
||||
|
||||
export function registerGenerationAfterCommandsController(runtime, listener) {
|
||||
const makeFirst = runtime.getEventMakeFirst();
|
||||
const eventName = runtime.eventTypes.GENERATION_AFTER_COMMANDS;
|
||||
const eventSourceMakeFirst = runtime.eventSource?.makeFirst;
|
||||
const makeFirst =
|
||||
typeof eventSourceMakeFirst === "function"
|
||||
? eventSourceMakeFirst.bind(runtime.eventSource)
|
||||
: runtime.getEventMakeFirst?.();
|
||||
if (typeof makeFirst === "function") {
|
||||
const cleanup = makeFirst(eventName, listener);
|
||||
return cleanup;
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
"js": "index.js",
|
||||
"css": "style.css",
|
||||
"author": "Youzini",
|
||||
"version": "7.5.1",
|
||||
"version": "7.5.4",
|
||||
"homePage": "https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology"
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
],
|
||||
@@ -169,6 +171,10 @@ function createMockTriviumClient({
|
||||
path: "/bme/vector-apply",
|
||||
});
|
||||
}
|
||||
const itemWithTopLevelId = payload.items?.find((item) => item?.id !== undefined);
|
||||
if (itemWithTopLevelId) {
|
||||
throw new Error("bmeVectorApply items must not send top-level Trivium id");
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
database: payload.database || "st_bme_vectors",
|
||||
@@ -261,11 +267,26 @@ 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.deepEqual(applyCall.links[0].src, {
|
||||
externalId: "node-a",
|
||||
namespace: "st-bme::chat-authority-vector",
|
||||
});
|
||||
assert.deepEqual(applyCall.links[0].dst, {
|
||||
externalId: "node-b",
|
||||
namespace: "st-bme::chat-authority-vector",
|
||||
});
|
||||
assert.equal(applyCall.links[0].label, "related");
|
||||
assert.equal(applyCall.links[0].weight, 0.75);
|
||||
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(applyCall.items[0].id, undefined);
|
||||
assert.equal(applyCall.items[0].externalId, "node-a");
|
||||
assert.equal(applyCall.items[0].nodeId, "node-a");
|
||||
assert.equal(applyCall.items[0].payload.nodeId, "node-a");
|
||||
assert.equal(applyCall.items[0].payload.externalId, "node-a");
|
||||
assert.equal(result.timings.authorityDiagnostics.upsert.operation, "bmeVectorApply");
|
||||
}
|
||||
|
||||
@@ -369,6 +390,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");
|
||||
@@ -436,6 +458,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 });
|
||||
|
||||
@@ -158,6 +158,7 @@ await runAuthorityE2eStep("trivium", async () => {
|
||||
headerProvider,
|
||||
});
|
||||
assert.equal(linkResult.linked, graph.edges.length);
|
||||
assert.ok(linkResult.linked >= 1, "Authority Trivium link sync should create at least one edge");
|
||||
|
||||
const searchResults = await searchAuthorityTriviumNodes(graph, "Authority E2E Alpha", config, {
|
||||
namespace,
|
||||
@@ -169,6 +170,10 @@ await runAuthorityE2eStep("trivium", async () => {
|
||||
headerProvider,
|
||||
});
|
||||
assert.ok(Array.isArray(searchResults));
|
||||
assert.ok(
|
||||
searchResults.every((result) => typeof result?.nodeId === "string" && result.nodeId.trim()),
|
||||
"Authority Trivium search results should expose external node ids",
|
||||
);
|
||||
|
||||
const filteredIds = await filterAuthorityTriviumNodes(config, {
|
||||
namespace,
|
||||
@@ -191,6 +196,10 @@ await runAuthorityE2eStep("trivium", async () => {
|
||||
headerProvider,
|
||||
});
|
||||
assert.ok(Array.isArray(neighborIds));
|
||||
assert.ok(
|
||||
neighborIds.includes(graph.nodes[1].id),
|
||||
"Authority Trivium neighbors should resolve links through external ids",
|
||||
);
|
||||
|
||||
return {
|
||||
upserted: upsertResult.upserted,
|
||||
|
||||
70
tests/ena-planner-ejs.mjs
Normal file
70
tests/ena-planner-ejs.mjs
Normal file
@@ -0,0 +1,70 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { createRequire } from "node:module";
|
||||
import {
|
||||
installResolveHooks,
|
||||
toDataModuleUrl,
|
||||
} from "./helpers/register-hooks-compat.mjs";
|
||||
|
||||
const extensionsShimSource = [
|
||||
"export const extension_settings = {};",
|
||||
].join("\n");
|
||||
const scriptShimSource = [
|
||||
"export function getRequestHeaders() { return {}; }",
|
||||
"export function saveSettingsDebounced() {}",
|
||||
"export function substituteParamsExtended(text = '') { return String(text ?? ''); }",
|
||||
].join("\n");
|
||||
|
||||
installResolveHooks([
|
||||
{
|
||||
specifiers: ["../../../../extensions.js"],
|
||||
url: toDataModuleUrl(extensionsShimSource),
|
||||
},
|
||||
{
|
||||
specifiers: ["../../../../../script.js"],
|
||||
url: toDataModuleUrl(scriptShimSource),
|
||||
},
|
||||
]);
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const ejs = require("../vendor/ejs.js");
|
||||
const originalWindow = globalThis.window;
|
||||
const originalWarn = console.warn;
|
||||
const warnings = [];
|
||||
|
||||
try {
|
||||
globalThis.window = { ...(originalWindow || {}), ejs };
|
||||
console.warn = (...args) => warnings.push(args);
|
||||
|
||||
const { createEnaPlannerEjsContext, renderEjsTemplate } = await import(
|
||||
"../ena-planner/ena-planner.js"
|
||||
);
|
||||
|
||||
const ctx = createEnaPlannerEjsContext({ x: "alpha" });
|
||||
assert.equal(renderEjsTemplate("<%= getvar('x') %>", ctx), "alpha");
|
||||
assert.equal(renderEjsTemplate("<% print(getvar('x')) %>", ctx), "alpha");
|
||||
|
||||
const pollutedCtx = {
|
||||
...createEnaPlannerEjsContext({ x: "safe" }),
|
||||
__append() {
|
||||
throw new Error("locals __append should not shadow EJS output");
|
||||
},
|
||||
print() {
|
||||
throw new Error("locals print should not shadow EJS output function");
|
||||
},
|
||||
};
|
||||
assert.equal(renderEjsTemplate("<%= getvar('x') %>", pollutedCtx), "safe");
|
||||
assert.equal(renderEjsTemplate("<% print(getvar('x')) %>", pollutedCtx), "safe");
|
||||
|
||||
const invalidTemplate = "before <% if ( %> after";
|
||||
assert.equal(renderEjsTemplate(invalidTemplate, ctx, "invalid"), invalidTemplate);
|
||||
assert.ok(warnings.some((args) => String(args[0]).includes("EJS render failed")));
|
||||
} finally {
|
||||
console.warn = originalWarn;
|
||||
if (originalWindow === undefined) {
|
||||
delete globalThis.window;
|
||||
} else {
|
||||
globalThis.window = originalWindow;
|
||||
}
|
||||
}
|
||||
|
||||
console.log("ena-planner-ejs tests passed");
|
||||
117
tests/event-binding-priority.mjs
Normal file
117
tests/event-binding-priority.mjs
Normal file
@@ -0,0 +1,117 @@
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
registerBeforeCombinePromptsController,
|
||||
registerGenerationAfterCommandsController,
|
||||
} from "../host/event-binding.js";
|
||||
|
||||
function createRuntime(eventSource, overrides = {}) {
|
||||
return {
|
||||
console: { warn() {} },
|
||||
eventSource,
|
||||
eventTypes: {
|
||||
GENERATE_BEFORE_COMBINE_PROMPTS: "before-combine",
|
||||
GENERATION_AFTER_COMMANDS: "after-commands",
|
||||
},
|
||||
getEventMakeFirst: () => undefined,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function testEventSourceMakeFirstWinsAndIsBound() {
|
||||
const calls = [];
|
||||
const fallbackCalls = [];
|
||||
const eventSource = {
|
||||
marker: "event-source",
|
||||
makeFirst(eventName, listener) {
|
||||
assert.equal(this, eventSource);
|
||||
calls.push({ eventName, listener });
|
||||
return () => calls.push({ cleanup: eventName });
|
||||
},
|
||||
on() {
|
||||
throw new Error("ordinary .on should not be used when makeFirst exists");
|
||||
},
|
||||
};
|
||||
const runtime = createRuntime(eventSource, {
|
||||
getEventMakeFirst: () => (...args) => fallbackCalls.push(args),
|
||||
});
|
||||
const beforeListener = () => {};
|
||||
const afterListener = () => {};
|
||||
|
||||
const beforeCleanup = registerBeforeCombinePromptsController(
|
||||
runtime,
|
||||
beforeListener,
|
||||
);
|
||||
const afterCleanup = registerGenerationAfterCommandsController(
|
||||
runtime,
|
||||
afterListener,
|
||||
);
|
||||
|
||||
assert.equal(typeof beforeCleanup, "function");
|
||||
assert.equal(typeof afterCleanup, "function");
|
||||
assert.deepEqual(calls, [
|
||||
{ eventName: "before-combine", listener: beforeListener },
|
||||
{ eventName: "after-commands", listener: afterListener },
|
||||
]);
|
||||
assert.deepEqual(fallbackCalls, []);
|
||||
}
|
||||
|
||||
function testRuntimeMakeFirstFallback() {
|
||||
const calls = [];
|
||||
const eventSource = {
|
||||
on() {
|
||||
throw new Error("ordinary .on should not be used when fallback exists");
|
||||
},
|
||||
};
|
||||
const runtime = createRuntime(eventSource, {
|
||||
getEventMakeFirst: () => (eventName, listener) => {
|
||||
calls.push({ eventName, listener });
|
||||
return `cleanup:${eventName}`;
|
||||
},
|
||||
});
|
||||
const beforeListener = () => {};
|
||||
const afterListener = () => {};
|
||||
|
||||
assert.equal(
|
||||
registerBeforeCombinePromptsController(runtime, beforeListener),
|
||||
"cleanup:before-combine",
|
||||
);
|
||||
assert.equal(
|
||||
registerGenerationAfterCommandsController(runtime, afterListener),
|
||||
"cleanup:after-commands",
|
||||
);
|
||||
assert.deepEqual(calls, [
|
||||
{ eventName: "before-combine", listener: beforeListener },
|
||||
{ eventName: "after-commands", listener: afterListener },
|
||||
]);
|
||||
}
|
||||
|
||||
function testOrdinaryOnFallback() {
|
||||
const calls = [];
|
||||
const eventSource = {
|
||||
on(eventName, listener) {
|
||||
calls.push({ eventName, listener });
|
||||
},
|
||||
};
|
||||
const runtime = createRuntime(eventSource);
|
||||
const beforeListener = () => {};
|
||||
const afterListener = () => {};
|
||||
|
||||
assert.equal(
|
||||
registerBeforeCombinePromptsController(runtime, beforeListener),
|
||||
null,
|
||||
);
|
||||
assert.equal(
|
||||
registerGenerationAfterCommandsController(runtime, afterListener),
|
||||
null,
|
||||
);
|
||||
assert.deepEqual(calls, [
|
||||
{ eventName: "before-combine", listener: beforeListener },
|
||||
{ eventName: "after-commands", listener: afterListener },
|
||||
]);
|
||||
}
|
||||
|
||||
testEventSourceMakeFirstWinsAndIsBound();
|
||||
testRuntimeMakeFirstFallback();
|
||||
testOrdinaryOnFallback();
|
||||
|
||||
console.log("event-binding-priority tests passed");
|
||||
@@ -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);
|
||||
}
|
||||
@@ -261,6 +279,7 @@ function buildAuthorityNodePayload(node = {}, entry = {}, { chatId = "", modelSc
|
||||
return {
|
||||
chatId,
|
||||
nodeId: normalizeRecordId(node?.id || entry?.nodeId),
|
||||
externalId: normalizeRecordId(node?.id || entry?.nodeId),
|
||||
type: String(node?.type || ""),
|
||||
archived: Boolean(node?.archived),
|
||||
seqStart: Number(seqRange[0] ?? node?.seq ?? 0) || 0,
|
||||
@@ -295,7 +314,6 @@ function buildAuthorityVectorItems(graph, entries = [], options = {}) {
|
||||
if (!node) return null;
|
||||
const payload = buildAuthorityNodePayload(node, entry, options);
|
||||
return {
|
||||
id: nodeId,
|
||||
externalId: nodeId,
|
||||
nodeId,
|
||||
text: String(entry?.text || ""),
|
||||
@@ -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 = {}) {
|
||||
|
||||
Reference in New Issue
Block a user