memorix 1.7.1 → 1.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -1
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/assets/star-history-dark.svg +4 -4
- package/assets/star-history-light.svg +4 -4
- package/assets/star-history.json +47 -3
- package/dist/cli/index.js +423 -74
- package/dist/index.js +396 -73
- package/dist/maintenance-runner.js +393 -69
- package/dist/media-audio-runner.js +393 -69
- package/dist/media-video-runner.js +393 -69
- package/dist/memcode-runtime/CHANGELOG.md +43 -1
- package/dist/sdk.js +395 -72
- package/dist/vector-backfill-runner.js +393 -69
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/CONFIGURATION.md +57 -1
- package/docs/DEVELOPMENT.md +6 -2
- package/docs/PERFORMANCE.md +13 -4
- package/docs/README.md +3 -1
- package/docs/SETUP.md +2 -1
- package/package.json +1 -1
- package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
- package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/copilot/memorix/plugin.json +1 -1
- package/plugins/gemini/memorix/gemini-extension.json +1 -1
- package/plugins/omp/memorix/package.json +1 -1
- package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/pi/memorix/package.json +1 -1
- package/server.json +2 -2
- package/src/cli/commands/config-migrate.ts +7 -0
- package/src/cli/commands/doctor.ts +5 -1
- package/src/cli/commands/init.ts +17 -0
- package/src/cli/commands/status.ts +2 -0
- package/src/config/resolved-config.ts +110 -12
- package/src/config/toml-loader.ts +8 -0
- package/src/config/yaml-loader.ts +9 -0
- package/src/llm/provider.ts +33 -7
- package/src/multimodal/image-loader.ts +6 -3
- package/src/rerank/http-provider.ts +125 -0
- package/src/rerank/index.ts +81 -0
- package/src/rerank/policy.ts +88 -0
- package/src/store/orama-store.ts +111 -65
|
@@ -176,6 +176,7 @@ function loadYamlConfig(projectRoot) {
|
|
|
176
176
|
llm: { ...userConfig.llm, ...projectConfig.llm },
|
|
177
177
|
agent: { ...userConfig.agent, ...projectConfig.agent },
|
|
178
178
|
embedding: { ...userConfig.embedding, ...projectConfig.embedding },
|
|
179
|
+
rerank: { ...userConfig.rerank, ...projectConfig.rerank },
|
|
179
180
|
git: { ...userConfig.git, ...projectConfig.git },
|
|
180
181
|
codegraph: { ...userConfig.codegraph, ...projectConfig.codegraph },
|
|
181
182
|
behavior: { ...userConfig.behavior, ...projectConfig.behavior },
|
|
@@ -1836,6 +1837,7 @@ function mergeTomlConfig(base, override) {
|
|
|
1836
1837
|
llm: { ...base.memory?.llm, ...override.memory?.llm }
|
|
1837
1838
|
},
|
|
1838
1839
|
embedding: { ...base.embedding, ...override.embedding },
|
|
1840
|
+
rerank: { ...base.rerank, ...override.rerank },
|
|
1839
1841
|
hooks: { ...base.hooks, ...override.hooks },
|
|
1840
1842
|
git: { ...base.git, ...override.git },
|
|
1841
1843
|
codegraph: { ...base.codegraph, ...override.codegraph },
|
|
@@ -1932,6 +1934,7 @@ __export(resolved_config_exports, {
|
|
|
1932
1934
|
getResolvedConfigForCwd: () => getResolvedConfigForCwd,
|
|
1933
1935
|
getResolvedEmbeddingLane: () => getResolvedEmbeddingLane,
|
|
1934
1936
|
getResolvedMemoryLane: () => getResolvedMemoryLane,
|
|
1937
|
+
getResolvedRerankLane: () => getResolvedRerankLane,
|
|
1935
1938
|
resetResolvedConfigCache: () => resetResolvedConfigCache
|
|
1936
1939
|
});
|
|
1937
1940
|
import { homedir as homedir5 } from "os";
|
|
@@ -1964,6 +1967,16 @@ function getResolvedConfig(options = {}) {
|
|
|
1964
1967
|
legacy.llm?.baseUrl
|
|
1965
1968
|
);
|
|
1966
1969
|
const openRouterMemoryLlmApiKey = isOpenRouterMemoryLane(memoryLlmProvider, memoryLlmBaseUrl) ? process.env.OPENROUTER_API_KEY : void 0;
|
|
1970
|
+
const memoryLlmApiKey = first(
|
|
1971
|
+
process.env.MEMORIX_LLM_API_KEY,
|
|
1972
|
+
process.env.MEMORIX_API_KEY,
|
|
1973
|
+
toml.memory?.llm?.api_key,
|
|
1974
|
+
yaml.llm?.apiKey,
|
|
1975
|
+
legacy.llm?.apiKey,
|
|
1976
|
+
process.env.OPENAI_API_KEY,
|
|
1977
|
+
process.env.ANTHROPIC_API_KEY,
|
|
1978
|
+
openRouterMemoryLlmApiKey
|
|
1979
|
+
);
|
|
1967
1980
|
const resolved = {
|
|
1968
1981
|
agent: {
|
|
1969
1982
|
provider: first(
|
|
@@ -2004,16 +2017,7 @@ function getResolvedConfig(options = {}) {
|
|
|
2004
2017
|
provider: memoryLlmProvider,
|
|
2005
2018
|
model: memoryLlmModel,
|
|
2006
2019
|
baseUrl: memoryLlmBaseUrl,
|
|
2007
|
-
apiKey:
|
|
2008
|
-
process.env.MEMORIX_LLM_API_KEY,
|
|
2009
|
-
process.env.MEMORIX_API_KEY,
|
|
2010
|
-
toml.memory?.llm?.api_key,
|
|
2011
|
-
yaml.llm?.apiKey,
|
|
2012
|
-
legacy.llm?.apiKey,
|
|
2013
|
-
process.env.OPENAI_API_KEY,
|
|
2014
|
-
process.env.ANTHROPIC_API_KEY,
|
|
2015
|
-
openRouterMemoryLlmApiKey
|
|
2016
|
-
)
|
|
2020
|
+
apiKey: memoryLlmApiKey
|
|
2017
2021
|
}
|
|
2018
2022
|
},
|
|
2019
2023
|
embedding: {
|
|
@@ -2023,6 +2027,12 @@ function getResolvedConfig(options = {}) {
|
|
|
2023
2027
|
apiKey: first(process.env.MEMORIX_EMBEDDING_API_KEY, toml.embedding?.api_key, yaml.embedding?.apiKey, legacy.embeddingApi?.apiKey, openRouterEmbeddingApiKey),
|
|
2024
2028
|
dimensions: firstNumber(parseNumber(process.env.MEMORIX_EMBEDDING_DIMENSIONS), toml.embedding?.dimensions, yaml.embedding?.dimensions, legacy.embeddingApi?.dimensions)
|
|
2025
2029
|
},
|
|
2030
|
+
rerank: resolveRerankLane({
|
|
2031
|
+
toml,
|
|
2032
|
+
yaml,
|
|
2033
|
+
memoryLlmApiKey,
|
|
2034
|
+
memoryLlmBaseUrl
|
|
2035
|
+
}),
|
|
2026
2036
|
git: {
|
|
2027
2037
|
autoHook: firstBool(toml.git?.auto_hook, yaml.git?.autoHook),
|
|
2028
2038
|
ingestOnCommit: firstBool(toml.git?.ingest_on_commit, yaml.git?.ingestOnCommit),
|
|
@@ -2092,6 +2102,9 @@ function getResolvedMemoryLane(options = {}) {
|
|
|
2092
2102
|
function getResolvedEmbeddingLane(options = {}) {
|
|
2093
2103
|
return getResolvedConfig(options).embedding;
|
|
2094
2104
|
}
|
|
2105
|
+
function getResolvedRerankLane(options = {}) {
|
|
2106
|
+
return getResolvedConfig(options).rerank;
|
|
2107
|
+
}
|
|
2095
2108
|
function resetResolvedConfigCache() {
|
|
2096
2109
|
}
|
|
2097
2110
|
function first(...values) {
|
|
@@ -2134,6 +2147,10 @@ function getEnvSourceNames() {
|
|
|
2134
2147
|
"MEMORIX_EMBEDDING_BASE_URL",
|
|
2135
2148
|
"MEMORIX_EMBEDDING_MODEL",
|
|
2136
2149
|
"MEMORIX_EMBEDDING_DIMENSIONS",
|
|
2150
|
+
"MEMORIX_RERANK_PROVIDER",
|
|
2151
|
+
"MEMORIX_RERANK_MODEL",
|
|
2152
|
+
"MEMORIX_RERANK_BASE_URL",
|
|
2153
|
+
"MEMORIX_RERANK_API_KEY",
|
|
2137
2154
|
"MEMORIX_CODEGRAPH_EXTERNAL_CONTEXT",
|
|
2138
2155
|
"MEMORIX_CODEGRAPH_EXTERNAL_COMMAND",
|
|
2139
2156
|
"MEMORIX_CODEGRAPH_EXTERNAL_TIMEOUT_MS",
|
|
@@ -2157,6 +2174,63 @@ function normalizeExternalContext(value) {
|
|
|
2157
2174
|
if (normalized === "auto" || normalized === "off") return normalized;
|
|
2158
2175
|
return void 0;
|
|
2159
2176
|
}
|
|
2177
|
+
function normalizeRerankProvider(value) {
|
|
2178
|
+
const normalized = value?.trim().toLowerCase();
|
|
2179
|
+
if (normalized === "http") return "http";
|
|
2180
|
+
return "off";
|
|
2181
|
+
}
|
|
2182
|
+
function resolveRerankLane(args) {
|
|
2183
|
+
const provider2 = normalizeRerankProvider(first(
|
|
2184
|
+
process.env.MEMORIX_RERANK_PROVIDER,
|
|
2185
|
+
args.toml.rerank?.provider,
|
|
2186
|
+
args.yaml.rerank?.provider,
|
|
2187
|
+
"off"
|
|
2188
|
+
));
|
|
2189
|
+
const model = first(
|
|
2190
|
+
process.env.MEMORIX_RERANK_MODEL,
|
|
2191
|
+
args.toml.rerank?.model,
|
|
2192
|
+
args.yaml.rerank?.model
|
|
2193
|
+
);
|
|
2194
|
+
const explicitBaseUrl = first(
|
|
2195
|
+
process.env.MEMORIX_RERANK_BASE_URL,
|
|
2196
|
+
args.toml.rerank?.base_url,
|
|
2197
|
+
args.yaml.rerank?.baseUrl
|
|
2198
|
+
);
|
|
2199
|
+
const baseUrl = provider2 === "http" ? first(explicitBaseUrl, args.memoryLlmBaseUrl) : explicitBaseUrl;
|
|
2200
|
+
const dedicatedApiKey = first(
|
|
2201
|
+
process.env.MEMORIX_RERANK_API_KEY,
|
|
2202
|
+
args.toml.rerank?.api_key,
|
|
2203
|
+
args.yaml.rerank?.apiKey
|
|
2204
|
+
);
|
|
2205
|
+
const sameEndpoint = provider2 === "http" && sameTrustedEndpoint(baseUrl, args.memoryLlmBaseUrl);
|
|
2206
|
+
const inheritedApiKey = sameEndpoint ? args.memoryLlmApiKey : void 0;
|
|
2207
|
+
const apiKey = first(dedicatedApiKey, inheritedApiKey);
|
|
2208
|
+
const canSendRequest = provider2 === "http" && Boolean(baseUrl) && (Boolean(apiKey) || !args.memoryLlmApiKey || sameEndpoint);
|
|
2209
|
+
return { provider: provider2, model, baseUrl, apiKey, canSendRequest };
|
|
2210
|
+
}
|
|
2211
|
+
function sameTrustedEndpoint(left, right) {
|
|
2212
|
+
const a = normalizeTrustedEndpoint(left);
|
|
2213
|
+
const b = normalizeTrustedEndpoint(right);
|
|
2214
|
+
return Boolean(a && b && a === b);
|
|
2215
|
+
}
|
|
2216
|
+
function normalizeTrustedEndpoint(value) {
|
|
2217
|
+
const trimmed = value?.trim();
|
|
2218
|
+
if (!trimmed) return void 0;
|
|
2219
|
+
try {
|
|
2220
|
+
const url = new URL(trimmed);
|
|
2221
|
+
const protocol = url.protocol.toLowerCase();
|
|
2222
|
+
const hostname = url.hostname.toLowerCase();
|
|
2223
|
+
const port = url.port;
|
|
2224
|
+
let pathname = url.pathname.replace(/\/+$/, "");
|
|
2225
|
+
if (pathname.toLowerCase().endsWith("/rerank")) {
|
|
2226
|
+
pathname = pathname.slice(0, -"/rerank".length).replace(/\/+$/, "");
|
|
2227
|
+
}
|
|
2228
|
+
return `${protocol}//${hostname}${port ? `:${port}` : ""}${pathname}`;
|
|
2229
|
+
} catch {
|
|
2230
|
+
const fallback = trimmed.replace(/\/+$/, "").replace(/\/rerank$/i, "").replace(/\/+$/, "");
|
|
2231
|
+
return fallback.toLowerCase() || void 0;
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2160
2234
|
var init_resolved_config = __esm({
|
|
2161
2235
|
"src/config/resolved-config.ts"() {
|
|
2162
2236
|
"use strict";
|
|
@@ -4392,9 +4466,27 @@ __export(provider_exports2, {
|
|
|
4392
4466
|
getLLMConfig: () => getLLMConfig,
|
|
4393
4467
|
initLLM: () => initLLM,
|
|
4394
4468
|
isLLMEnabled: () => isLLMEnabled,
|
|
4469
|
+
normalizeOpenAICompatibleBaseUrl: () => normalizeOpenAICompatibleBaseUrl,
|
|
4395
4470
|
parseLLMTimeoutMs: () => parseLLMTimeoutMs,
|
|
4396
4471
|
setLLMConfig: () => setLLMConfig
|
|
4397
4472
|
});
|
|
4473
|
+
function normalizeOpenAICompatibleBaseUrl(baseUrl) {
|
|
4474
|
+
const base = baseUrl.trim().replace(/\/+$/, "");
|
|
4475
|
+
if (!base) return base;
|
|
4476
|
+
let pathname = "";
|
|
4477
|
+
try {
|
|
4478
|
+
pathname = new URL(base).pathname.replace(/\/+$/, "");
|
|
4479
|
+
} catch {
|
|
4480
|
+
pathname = base.split(/[?#]/, 1)[0].replace(/\/+$/, "");
|
|
4481
|
+
}
|
|
4482
|
+
const lastSegment = pathname.split("/").filter(Boolean).at(-1);
|
|
4483
|
+
if (lastSegment && OPENAI_API_VERSION_SEGMENT.test(lastSegment)) {
|
|
4484
|
+
return base;
|
|
4485
|
+
}
|
|
4486
|
+
const queryOrHash = base.match(/[?#].*$/)?.[0] ?? "";
|
|
4487
|
+
const pathWithoutQuery = base.slice(0, base.length - queryOrHash.length).replace(/\/+$/, "");
|
|
4488
|
+
return `${pathWithoutQuery}/v1${queryOrHash}`;
|
|
4489
|
+
}
|
|
4398
4490
|
function parseLLMTimeoutMs(raw) {
|
|
4399
4491
|
if (raw === void 0 || raw.trim() === "") return LLM_TIMEOUT_DEFAULT_MS;
|
|
4400
4492
|
const parsed = Number(raw);
|
|
@@ -4498,8 +4590,7 @@ async function callLLM(systemPrompt, userMessage, signal) {
|
|
|
4498
4590
|
}
|
|
4499
4591
|
async function callOpenAICompatible(systemPrompt, userMessage, signal) {
|
|
4500
4592
|
const config = currentConfig;
|
|
4501
|
-
|
|
4502
|
-
if (!base.endsWith("/v1")) base += "/v1";
|
|
4593
|
+
const base = normalizeOpenAICompatibleBaseUrl(config.baseUrl);
|
|
4503
4594
|
const url = `${base}/chat/completions`;
|
|
4504
4595
|
const fetchSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(LLM_CALL_TIMEOUT_MS)]) : AbortSignal.timeout(LLM_CALL_TIMEOUT_MS);
|
|
4505
4596
|
const response = await fetch(url, {
|
|
@@ -4578,8 +4669,7 @@ async function callLLMWithTools(messages, tools, signal) {
|
|
|
4578
4669
|
}
|
|
4579
4670
|
async function callOpenAIWithTools(messages, tools, signal) {
|
|
4580
4671
|
const config = currentConfig;
|
|
4581
|
-
|
|
4582
|
-
if (!base.endsWith("/v1")) base += "/v1";
|
|
4672
|
+
const base = normalizeOpenAICompatibleBaseUrl(config.baseUrl);
|
|
4583
4673
|
const url = `${base}/chat/completions`;
|
|
4584
4674
|
const openaiMessages = messages.map((msg) => {
|
|
4585
4675
|
if (msg.role === "tool") {
|
|
@@ -4729,8 +4819,7 @@ async function callAnthropicWithTools(messages, tools, signal) {
|
|
|
4729
4819
|
}
|
|
4730
4820
|
async function* callOpenAIWithToolsStream(messages, tools) {
|
|
4731
4821
|
const config = currentConfig;
|
|
4732
|
-
|
|
4733
|
-
if (!base.endsWith("/v1")) base += "/v1";
|
|
4822
|
+
const base = normalizeOpenAICompatibleBaseUrl(config.baseUrl);
|
|
4734
4823
|
const url = `${base}/chat/completions`;
|
|
4735
4824
|
const openaiMessages = messages.map((msg) => {
|
|
4736
4825
|
if (msg.role === "tool") {
|
|
@@ -4978,7 +5067,7 @@ async function* callAnthropicWithToolsStream(messages, tools) {
|
|
|
4978
5067
|
}
|
|
4979
5068
|
};
|
|
4980
5069
|
}
|
|
4981
|
-
var LLM_TIMEOUT_DEFAULT_MS, LLM_TIMEOUT_MIN_MS, LLM_TIMEOUT_MAX_MS, MAX_LLM_RESPONSE_BYTES, LLM_CALL_TIMEOUT_MS, PROVIDER_DEFAULTS, currentConfig;
|
|
5070
|
+
var LLM_TIMEOUT_DEFAULT_MS, LLM_TIMEOUT_MIN_MS, LLM_TIMEOUT_MAX_MS, MAX_LLM_RESPONSE_BYTES, OPENAI_API_VERSION_SEGMENT, LLM_CALL_TIMEOUT_MS, PROVIDER_DEFAULTS, currentConfig;
|
|
4982
5071
|
var init_provider2 = __esm({
|
|
4983
5072
|
"src/llm/provider.ts"() {
|
|
4984
5073
|
"use strict";
|
|
@@ -4988,6 +5077,7 @@ var init_provider2 = __esm({
|
|
|
4988
5077
|
LLM_TIMEOUT_MIN_MS = 1e3;
|
|
4989
5078
|
LLM_TIMEOUT_MAX_MS = 3e5;
|
|
4990
5079
|
MAX_LLM_RESPONSE_BYTES = 2 * 1024 * 1024;
|
|
5080
|
+
OPENAI_API_VERSION_SEGMENT = /^v\d+(?:[a-z][a-z0-9-]*)?$/i;
|
|
4991
5081
|
LLM_CALL_TIMEOUT_MS = parseLLMTimeoutMs(process.env.MEMORIX_LLM_TIMEOUT_MS);
|
|
4992
5082
|
PROVIDER_DEFAULTS = {
|
|
4993
5083
|
openai: { baseUrl: "https://api.openai.com/v1", model: "gpt-4.1-nano" },
|
|
@@ -5047,6 +5137,48 @@ Rules:
|
|
|
5047
5137
|
}
|
|
5048
5138
|
});
|
|
5049
5139
|
|
|
5140
|
+
// src/rerank/policy.ts
|
|
5141
|
+
function parseRerankTimeoutMs(raw) {
|
|
5142
|
+
const value = raw?.trim();
|
|
5143
|
+
if (!value) return DEFAULT_RERANK_TIMEOUT_MS;
|
|
5144
|
+
const parsed = Number(value);
|
|
5145
|
+
if (!Number.isInteger(parsed) || Number.isNaN(parsed)) {
|
|
5146
|
+
console.warn(
|
|
5147
|
+
`[memorix] MEMORIX_RERANK_TIMEOUT_MS="${raw}" is invalid (must be a positive integer between ${RERANK_TIMEOUT_MIN_MS}-${RERANK_TIMEOUT_MAX_MS}ms). Using default ${DEFAULT_RERANK_TIMEOUT_MS}ms.`
|
|
5148
|
+
);
|
|
5149
|
+
return DEFAULT_RERANK_TIMEOUT_MS;
|
|
5150
|
+
}
|
|
5151
|
+
if (parsed < RERANK_TIMEOUT_MIN_MS) return RERANK_TIMEOUT_MIN_MS;
|
|
5152
|
+
if (parsed > RERANK_TIMEOUT_MAX_MS) return RERANK_TIMEOUT_MAX_MS;
|
|
5153
|
+
return parsed;
|
|
5154
|
+
}
|
|
5155
|
+
function resolveRerankTimeoutMs(explicitTimeoutMs) {
|
|
5156
|
+
if (typeof explicitTimeoutMs === "number" && Number.isFinite(explicitTimeoutMs) && explicitTimeoutMs > 0) {
|
|
5157
|
+
return explicitTimeoutMs;
|
|
5158
|
+
}
|
|
5159
|
+
return parseRerankTimeoutMs(process.env.MEMORIX_RERANK_TIMEOUT_MS);
|
|
5160
|
+
}
|
|
5161
|
+
function shouldAttemptHttpRerank(input) {
|
|
5162
|
+
return input.quality === "thorough" && input.hasQuery && input.candidateCount >= MIN_RERANK_CANDIDATES;
|
|
5163
|
+
}
|
|
5164
|
+
function shouldAttemptLlmRerank(input) {
|
|
5165
|
+
if (input.httpRerankConfigured) return false;
|
|
5166
|
+
if (input.quality !== "thorough" || input.tier !== "heavy" || !input.hasQuery) return false;
|
|
5167
|
+
if (input.candidateCount < MIN_RERANK_CANDIDATES) return false;
|
|
5168
|
+
return input.topScore > 0 && input.secondScore / input.topScore > 0.7;
|
|
5169
|
+
}
|
|
5170
|
+
var DEFAULT_RERANK_TIMEOUT_MS, RERANK_TIMEOUT_MIN_MS, RERANK_TIMEOUT_MAX_MS, MIN_RERANK_CANDIDATES;
|
|
5171
|
+
var init_policy = __esm({
|
|
5172
|
+
"src/rerank/policy.ts"() {
|
|
5173
|
+
"use strict";
|
|
5174
|
+
init_esm_shims();
|
|
5175
|
+
DEFAULT_RERANK_TIMEOUT_MS = 3e4;
|
|
5176
|
+
RERANK_TIMEOUT_MIN_MS = 1e3;
|
|
5177
|
+
RERANK_TIMEOUT_MAX_MS = 3e5;
|
|
5178
|
+
MIN_RERANK_CANDIDATES = 3;
|
|
5179
|
+
}
|
|
5180
|
+
});
|
|
5181
|
+
|
|
5050
5182
|
// src/timeout.ts
|
|
5051
5183
|
function withTimeout(promise, ms, label) {
|
|
5052
5184
|
return new Promise((resolve, reject) => {
|
|
@@ -5332,6 +5464,148 @@ var init_aliases = __esm({
|
|
|
5332
5464
|
}
|
|
5333
5465
|
});
|
|
5334
5466
|
|
|
5467
|
+
// src/rerank/http-provider.ts
|
|
5468
|
+
function buildRerankUrl(baseUrl) {
|
|
5469
|
+
const trimmed = baseUrl.trim().replace(/\/+$/, "");
|
|
5470
|
+
if (trimmed.endsWith("/rerank")) return trimmed;
|
|
5471
|
+
return `${trimmed}/rerank`;
|
|
5472
|
+
}
|
|
5473
|
+
function parseRerankResponse(payload) {
|
|
5474
|
+
const rows = extractRows(payload);
|
|
5475
|
+
if (rows.length === 0) {
|
|
5476
|
+
throw new Error("Neural rerank response contained no ranked results");
|
|
5477
|
+
}
|
|
5478
|
+
return rows.sort((a, b) => b.score - a.score);
|
|
5479
|
+
}
|
|
5480
|
+
function extractRows(payload) {
|
|
5481
|
+
if (Array.isArray(payload)) {
|
|
5482
|
+
return payload.map(rowFromUnknown).filter((row) => row !== null);
|
|
5483
|
+
}
|
|
5484
|
+
if (payload && typeof payload === "object" && "results" in payload) {
|
|
5485
|
+
const results = payload.results;
|
|
5486
|
+
if (Array.isArray(results)) {
|
|
5487
|
+
return results.map(rowFromUnknown).filter((row) => row !== null);
|
|
5488
|
+
}
|
|
5489
|
+
}
|
|
5490
|
+
throw new Error("Neural rerank response was not Cohere or TEI shaped");
|
|
5491
|
+
}
|
|
5492
|
+
function rowFromUnknown(value) {
|
|
5493
|
+
if (!value || typeof value !== "object") return null;
|
|
5494
|
+
const row = value;
|
|
5495
|
+
if (typeof row.index !== "number" || !Number.isFinite(row.index)) return null;
|
|
5496
|
+
const score = typeof row.relevance_score === "number" ? row.relevance_score : typeof row.score === "number" ? row.score : null;
|
|
5497
|
+
if (score === null || !Number.isFinite(score)) return null;
|
|
5498
|
+
return { index: row.index, score };
|
|
5499
|
+
}
|
|
5500
|
+
function buildRerankBody(query, documents, model) {
|
|
5501
|
+
const body = {
|
|
5502
|
+
query,
|
|
5503
|
+
documents,
|
|
5504
|
+
top_n: documents.length,
|
|
5505
|
+
return_documents: false
|
|
5506
|
+
};
|
|
5507
|
+
const trimmed = model?.trim();
|
|
5508
|
+
if (trimmed) body.model = trimmed;
|
|
5509
|
+
return body;
|
|
5510
|
+
}
|
|
5511
|
+
async function rerankViaHttp(query, documents, config) {
|
|
5512
|
+
if (documents.length === 0) return [];
|
|
5513
|
+
const url = buildRerankUrl(config.baseUrl);
|
|
5514
|
+
const headers = {
|
|
5515
|
+
"content-type": "application/json"
|
|
5516
|
+
};
|
|
5517
|
+
if (config.apiKey) {
|
|
5518
|
+
headers.authorization = `Bearer ${config.apiKey}`;
|
|
5519
|
+
}
|
|
5520
|
+
const controller = new AbortController();
|
|
5521
|
+
const timeoutMs = resolveRerankTimeoutMs(config.timeoutMs);
|
|
5522
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
5523
|
+
try {
|
|
5524
|
+
const fetchImpl = config.fetchImpl ?? fetch;
|
|
5525
|
+
const response = await fetchImpl(url, {
|
|
5526
|
+
method: "POST",
|
|
5527
|
+
headers,
|
|
5528
|
+
signal: controller.signal,
|
|
5529
|
+
body: JSON.stringify(buildRerankBody(query, documents, config.model))
|
|
5530
|
+
});
|
|
5531
|
+
if (!response.ok) {
|
|
5532
|
+
throw new Error(`Neural rerank HTTP ${response.status} from ${url}`);
|
|
5533
|
+
}
|
|
5534
|
+
return parseRerankResponse(await response.json());
|
|
5535
|
+
} finally {
|
|
5536
|
+
clearTimeout(timer);
|
|
5537
|
+
}
|
|
5538
|
+
}
|
|
5539
|
+
var init_http_provider = __esm({
|
|
5540
|
+
"src/rerank/http-provider.ts"() {
|
|
5541
|
+
"use strict";
|
|
5542
|
+
init_esm_shims();
|
|
5543
|
+
init_policy();
|
|
5544
|
+
}
|
|
5545
|
+
});
|
|
5546
|
+
|
|
5547
|
+
// src/rerank/index.ts
|
|
5548
|
+
var rerank_exports = {};
|
|
5549
|
+
__export(rerank_exports, {
|
|
5550
|
+
DEFAULT_RERANK_TIMEOUT_MS: () => DEFAULT_RERANK_TIMEOUT_MS,
|
|
5551
|
+
MIN_RERANK_CANDIDATES: () => MIN_RERANK_CANDIDATES,
|
|
5552
|
+
isNeuralRerankEnabled: () => isNeuralRerankEnabled,
|
|
5553
|
+
neuralRerankCandidates: () => neuralRerankCandidates,
|
|
5554
|
+
parseRerankTimeoutMs: () => parseRerankTimeoutMs,
|
|
5555
|
+
resolveRerankTimeoutMs: () => resolveRerankTimeoutMs,
|
|
5556
|
+
shouldAttemptHttpRerank: () => shouldAttemptHttpRerank,
|
|
5557
|
+
shouldAttemptLlmRerank: () => shouldAttemptLlmRerank
|
|
5558
|
+
});
|
|
5559
|
+
function isNeuralRerankEnabled(options = {}) {
|
|
5560
|
+
const lane = getResolvedRerankLane(options);
|
|
5561
|
+
return lane.provider === "http" && Boolean(lane.baseUrl);
|
|
5562
|
+
}
|
|
5563
|
+
async function neuralRerankCandidates(query, candidates, options = {}) {
|
|
5564
|
+
if (!isNeuralRerankEnabled(options) || candidates.length === 0) return null;
|
|
5565
|
+
const lane = getResolvedRerankLane(options);
|
|
5566
|
+
if (!lane.canSendRequest || !lane.baseUrl) return null;
|
|
5567
|
+
const toRerank = candidates.slice(0, MAX_RERANK);
|
|
5568
|
+
const rest = candidates.slice(MAX_RERANK);
|
|
5569
|
+
const documents = toRerank.map((candidate) => candidateText(candidate));
|
|
5570
|
+
const ranked = await rerankViaHttp(query, documents, {
|
|
5571
|
+
provider: "http",
|
|
5572
|
+
model: lane.model,
|
|
5573
|
+
baseUrl: lane.baseUrl,
|
|
5574
|
+
apiKey: lane.apiKey,
|
|
5575
|
+
timeoutMs: resolveRerankTimeoutMs()
|
|
5576
|
+
});
|
|
5577
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5578
|
+
const reranked = [];
|
|
5579
|
+
for (const row of ranked) {
|
|
5580
|
+
const candidate = toRerank[row.index];
|
|
5581
|
+
if (!candidate || seen.has(row.index)) continue;
|
|
5582
|
+
reranked.push(candidate);
|
|
5583
|
+
seen.add(row.index);
|
|
5584
|
+
}
|
|
5585
|
+
for (const [index, candidate] of toRerank.entries()) {
|
|
5586
|
+
if (!seen.has(index)) reranked.push(candidate);
|
|
5587
|
+
}
|
|
5588
|
+
reranked.push(...rest);
|
|
5589
|
+
return reranked;
|
|
5590
|
+
}
|
|
5591
|
+
function candidateText(candidate) {
|
|
5592
|
+
const narrative = candidate.narrative?.trim();
|
|
5593
|
+
if (narrative) return `${candidate.title} \u2014 ${narrative.slice(0, 200)}`;
|
|
5594
|
+
return candidate.title;
|
|
5595
|
+
}
|
|
5596
|
+
var MAX_RERANK;
|
|
5597
|
+
var init_rerank = __esm({
|
|
5598
|
+
"src/rerank/index.ts"() {
|
|
5599
|
+
"use strict";
|
|
5600
|
+
init_esm_shims();
|
|
5601
|
+
init_resolved_config();
|
|
5602
|
+
init_http_provider();
|
|
5603
|
+
init_policy();
|
|
5604
|
+
init_policy();
|
|
5605
|
+
MAX_RERANK = 10;
|
|
5606
|
+
}
|
|
5607
|
+
});
|
|
5608
|
+
|
|
5335
5609
|
// src/llm/quality.ts
|
|
5336
5610
|
var quality_exports = {};
|
|
5337
5611
|
__export(quality_exports, {
|
|
@@ -5371,9 +5645,9 @@ async function rerankResults(query, candidates) {
|
|
|
5371
5645
|
if (!isLLMEnabled() || candidates.length <= 2) {
|
|
5372
5646
|
return { reranked: candidates, usedLLM: false };
|
|
5373
5647
|
}
|
|
5374
|
-
const
|
|
5375
|
-
const toRerank = candidates.slice(0,
|
|
5376
|
-
const rest = candidates.slice(
|
|
5648
|
+
const MAX_RERANK2 = 10;
|
|
5649
|
+
const toRerank = candidates.slice(0, MAX_RERANK2);
|
|
5650
|
+
const rest = candidates.slice(MAX_RERANK2);
|
|
5377
5651
|
try {
|
|
5378
5652
|
const candidateList = toRerank.map(
|
|
5379
5653
|
(c) => `[ID: ${c.id}] (${c.type}) ${c.title}${c.narrative ? ` \u2014 ${c.narrative.substring(0, 100)}` : ""}`
|
|
@@ -5597,7 +5871,7 @@ __export(orama_store_exports, {
|
|
|
5597
5871
|
searchObservations: () => searchObservations,
|
|
5598
5872
|
updateObservationMetadata: () => updateObservationMetadata
|
|
5599
5873
|
});
|
|
5600
|
-
import { create, insert as insert2, search, remove as remove2, update, count, getByID } from "@orama/orama";
|
|
5874
|
+
import { create, insert as insert2, insertMultiple, search, remove as remove2, update, count, getByID } from "@orama/orama";
|
|
5601
5875
|
function getLastSearchMode(projectId) {
|
|
5602
5876
|
return lastSearchModeByProject.get(projectId ?? SEARCH_MODE_DEFAULT_KEY) ?? "fulltext";
|
|
5603
5877
|
}
|
|
@@ -5809,7 +6083,7 @@ async function hydrateIndex(observations2, options = {}) {
|
|
|
5809
6083
|
}
|
|
5810
6084
|
const deferCachedVectors = options.deferCachedVectors === true && Boolean(indexEmbeddingProvider?.getCachedEmbeddings);
|
|
5811
6085
|
const cachedVectors = deferCachedVectors ? candidates.map(() => null) : await getCachedEmbeddings(candidates.map(({ observation }) => observationEmbeddingText(observation)));
|
|
5812
|
-
|
|
6086
|
+
const documents = [];
|
|
5813
6087
|
for (let index = 0; index < candidates.length; index++) {
|
|
5814
6088
|
const { observation: obs, id } = candidates[index];
|
|
5815
6089
|
try {
|
|
@@ -5843,18 +6117,35 @@ async function hydrateIndex(observations2, options = {}) {
|
|
|
5843
6117
|
knowledgeLayer: resolveKnowledgeLayer("observation", obs.sourceDetail, obs.source),
|
|
5844
6118
|
...compatibleVector ? { embedding: compatibleVector } : {}
|
|
5845
6119
|
};
|
|
5846
|
-
|
|
5847
|
-
rememberObservationDoc(doc);
|
|
5848
|
-
inserted++;
|
|
5849
|
-
if (inserted % 50 === 0) {
|
|
5850
|
-
await new Promise((resolve) => setImmediate(resolve));
|
|
5851
|
-
}
|
|
5852
|
-
if (inserted % 200 === 0) {
|
|
5853
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
5854
|
-
}
|
|
6120
|
+
documents.push(doc);
|
|
5855
6121
|
} catch {
|
|
5856
6122
|
}
|
|
5857
6123
|
}
|
|
6124
|
+
let inserted = 0;
|
|
6125
|
+
const hydrationBatchSize = 200;
|
|
6126
|
+
for (let start = 0; start < documents.length; start += hydrationBatchSize) {
|
|
6127
|
+
const batch = documents.slice(start, start + hydrationBatchSize);
|
|
6128
|
+
try {
|
|
6129
|
+
await insertMultiple(database, batch, batch.length);
|
|
6130
|
+
for (const doc of batch) rememberObservationDoc(doc);
|
|
6131
|
+
inserted += batch.length;
|
|
6132
|
+
} catch {
|
|
6133
|
+
for (const doc of batch) {
|
|
6134
|
+
if (getByID(database, doc.id)) {
|
|
6135
|
+
rememberObservationDoc(doc);
|
|
6136
|
+
inserted++;
|
|
6137
|
+
continue;
|
|
6138
|
+
}
|
|
6139
|
+
try {
|
|
6140
|
+
await insert2(database, doc);
|
|
6141
|
+
rememberObservationDoc(doc);
|
|
6142
|
+
inserted++;
|
|
6143
|
+
} catch {
|
|
6144
|
+
}
|
|
6145
|
+
}
|
|
6146
|
+
}
|
|
6147
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
6148
|
+
}
|
|
5858
6149
|
deferredCachedVectorHydration = options.skipCachedVectors ? null : deferCachedVectors ? attachCachedVectors(database, candidates).catch(() => {
|
|
5859
6150
|
}) : null;
|
|
5860
6151
|
return inserted;
|
|
@@ -6249,46 +6540,78 @@ async function searchObservations(options) {
|
|
|
6249
6540
|
}
|
|
6250
6541
|
}
|
|
6251
6542
|
}
|
|
6252
|
-
const
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
})
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6543
|
+
const httpGate = shouldAttemptHttpRerank({
|
|
6544
|
+
quality,
|
|
6545
|
+
hasQuery: Boolean(hasQuery),
|
|
6546
|
+
candidateCount: intermediate.length
|
|
6547
|
+
});
|
|
6548
|
+
const llmGateWithoutHttp = shouldAttemptLlmRerank({
|
|
6549
|
+
quality,
|
|
6550
|
+
tier,
|
|
6551
|
+
hasQuery: Boolean(hasQuery),
|
|
6552
|
+
candidateCount: intermediate.length,
|
|
6553
|
+
topScore: intermediate[0]?.score ?? 0,
|
|
6554
|
+
secondScore: intermediate[1]?.score ?? 0,
|
|
6555
|
+
httpRerankConfigured: false
|
|
6556
|
+
});
|
|
6557
|
+
if (httpGate || llmGateWithoutHttp) {
|
|
6558
|
+
const narrativeMap = /* @__PURE__ */ new Map();
|
|
6559
|
+
for (const hit of results.hits) {
|
|
6560
|
+
const doc = hit.document;
|
|
6561
|
+
narrativeMap.set(makeEntryKey(doc.projectId, doc.observationId), doc.narrative);
|
|
6562
|
+
}
|
|
6563
|
+
const RERANK_TOP_K = 5;
|
|
6564
|
+
const toRerank = intermediate.slice(0, RERANK_TOP_K);
|
|
6565
|
+
const candidates = toRerank.map((e2, index) => ({
|
|
6566
|
+
id: `r${index + 1}`,
|
|
6567
|
+
title: e2.title,
|
|
6568
|
+
type: e2.type,
|
|
6569
|
+
score: e2.score,
|
|
6570
|
+
narrative: narrativeMap.get(makeEntryKey(e2.projectId, e2.id))
|
|
6571
|
+
}));
|
|
6572
|
+
const RERANK_TIMEOUT_MS = parseRerankTimeoutMs(process.env.MEMORIX_RERANK_TIMEOUT_MS);
|
|
6573
|
+
const candidateMap = new Map(candidates.map((candidate, index) => [candidate.id, toRerank[index]]));
|
|
6574
|
+
const { isNeuralRerankEnabled: isNeuralRerankEnabled2, neuralRerankCandidates: neuralRerankCandidates2 } = await Promise.resolve().then(() => (init_rerank(), rerank_exports));
|
|
6575
|
+
const httpConfigured = isNeuralRerankEnabled2();
|
|
6576
|
+
if (httpConfigured && httpGate) {
|
|
6577
|
+
try {
|
|
6578
|
+
const neural = await withTimeout(
|
|
6579
|
+
neuralRerankCandidates2(originalQuery, candidates),
|
|
6580
|
+
RERANK_TIMEOUT_MS,
|
|
6581
|
+
"neural rerank"
|
|
6582
|
+
);
|
|
6583
|
+
if (neural && neural.length > 0) {
|
|
6584
|
+
const rerankedTop = neural.map((row) => candidateMap.get(row.id)).filter((entry) => entry != null);
|
|
6585
|
+
if (rerankedTop.length > 0) {
|
|
6586
|
+
intermediate = [...rerankedTop, ...intermediate.slice(RERANK_TOP_K)];
|
|
6587
|
+
lastSearchModeByProject.set(modeKey, (lastSearchModeByProject.get(modeKey) ?? "fulltext") + " + neural rerank");
|
|
6588
|
+
mark("rerank(usedNeural=true)");
|
|
6589
|
+
}
|
|
6288
6590
|
}
|
|
6591
|
+
} catch {
|
|
6592
|
+
console.error("[memorix] neural rerank failed or timed out; keeping original order");
|
|
6289
6593
|
}
|
|
6290
|
-
}
|
|
6291
|
-
|
|
6594
|
+
} else if (!httpConfigured && llmGateWithoutHttp) {
|
|
6595
|
+
try {
|
|
6596
|
+
const { rerankResults: rerankResults2 } = await Promise.resolve().then(() => (init_quality(), quality_exports));
|
|
6597
|
+
const { reranked, usedLLM } = await withTimeout(
|
|
6598
|
+
rerankResults2(originalQuery, candidates),
|
|
6599
|
+
RERANK_TIMEOUT_MS,
|
|
6600
|
+
"LLM rerank"
|
|
6601
|
+
);
|
|
6602
|
+
mark(`rerank(usedLLM=${usedLLM})`);
|
|
6603
|
+
if (usedLLM) {
|
|
6604
|
+
lastSearchModeByProject.set(modeKey, (lastSearchModeByProject.get(modeKey) ?? "fulltext") + " + LLM rerank");
|
|
6605
|
+
const rerankedTop = reranked.map((row) => candidateMap.get(row.id)).filter((entry) => entry != null);
|
|
6606
|
+
if (rerankedTop.length > 0) {
|
|
6607
|
+
intermediate = [...rerankedTop, ...intermediate.slice(RERANK_TOP_K)];
|
|
6608
|
+
}
|
|
6609
|
+
}
|
|
6610
|
+
} catch {
|
|
6611
|
+
console.error("[memorix] LLM rerank failed or timed out, using original order");
|
|
6612
|
+
}
|
|
6613
|
+
} else {
|
|
6614
|
+
mark(`rerank(skipped,tier=${tier})`);
|
|
6292
6615
|
}
|
|
6293
6616
|
} else {
|
|
6294
6617
|
mark(`rerank(skipped,tier=${tier})`);
|
|
@@ -6469,6 +6792,7 @@ var init_orama_store = __esm({
|
|
|
6469
6792
|
init_project_affinity();
|
|
6470
6793
|
init_intent_detector();
|
|
6471
6794
|
init_query_expansion();
|
|
6795
|
+
init_policy();
|
|
6472
6796
|
init_timeout();
|
|
6473
6797
|
db = null;
|
|
6474
6798
|
dbInitPromise = null;
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -391,7 +391,7 @@ Resolution order:
|
|
|
391
391
|
2. project `.env`
|
|
392
392
|
3. user `~/.memorix/.env`
|
|
393
393
|
|
|
394
|
-
The primary lanes are `[agent]` for memcode, `[memory.llm]` for formation/rerank
|
|
394
|
+
The primary lanes are `[agent]` for memcode, `[memory.llm]` for formation/summaries/LLM-rerank fallback, `[rerank]` for optional HTTP rerank against a compatible `/rerank` API, and `[embedding]` for semantic search. The dashboard and `memorix status` expose config provenance so the active value source is visible.
|
|
395
395
|
|
|
396
396
|
---
|
|
397
397
|
|