memorix 1.7.2 → 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 +27 -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 +396 -64
- package/dist/index.js +368 -62
- package/dist/maintenance-runner.js +365 -58
- package/dist/media-audio-runner.js +365 -58
- package/dist/media-video-runner.js +365 -58
- package/dist/memcode-runtime/CHANGELOG.md +27 -1
- package/dist/sdk.js +367 -61
- package/dist/vector-backfill-runner.js +365 -58
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/CONFIGURATION.md +57 -1
- package/docs/PERFORMANCE.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 +79 -53
|
@@ -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)}` : ""}`
|
|
@@ -6266,46 +6540,78 @@ async function searchObservations(options) {
|
|
|
6266
6540
|
}
|
|
6267
6541
|
}
|
|
6268
6542
|
}
|
|
6269
|
-
const
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
})
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
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
|
+
}
|
|
6305
6590
|
}
|
|
6591
|
+
} catch {
|
|
6592
|
+
console.error("[memorix] neural rerank failed or timed out; keeping original order");
|
|
6306
6593
|
}
|
|
6307
|
-
}
|
|
6308
|
-
|
|
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})`);
|
|
6309
6615
|
}
|
|
6310
6616
|
} else {
|
|
6311
6617
|
mark(`rerank(skipped,tier=${tier})`);
|
|
@@ -6486,6 +6792,7 @@ var init_orama_store = __esm({
|
|
|
6486
6792
|
init_project_affinity();
|
|
6487
6793
|
init_intent_detector();
|
|
6488
6794
|
init_query_expansion();
|
|
6795
|
+
init_policy();
|
|
6489
6796
|
init_timeout();
|
|
6490
6797
|
db = null;
|
|
6491
6798
|
dbInitPromise = null;
|
|
@@ -4,6 +4,33 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [1.7.3] - 2026-08-22
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **Optional HTTP rerank** -- Thorough search with at least 3 candidates can
|
|
11
|
+
reorder top hits through a configurable Cohere-compatible
|
|
12
|
+
`POST {base_url}/rerank` endpoint (`{model, query, documents}`). Works with
|
|
13
|
+
any compatible `/rerank` API. Configure `[rerank]` or `MEMORIX_RERANK_*`;
|
|
14
|
+
base URL inherits the memory LLM lane when unset. Bearer inherits only
|
|
15
|
+
when the effective rerank URL is that same trusted endpoint; a different
|
|
16
|
+
host requires `MEMORIX_RERANK_API_KEY` / `rerank.api_key`. The model id
|
|
17
|
+
is user-configured. When HTTP rerank is configured, a miss or timeout
|
|
18
|
+
keeps the original order (no LLM fallback). LLM rerank remains available
|
|
19
|
+
when HTTP rerank is off.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- **OpenAI-compatible provider base URLs** -- preserve explicit versioned paths
|
|
23
|
+
such as Volcano Ark's `/api/v3` for ordinary chat, tool calling, streaming,
|
|
24
|
+
and vision requests instead of appending an incorrect `/v1` segment.
|
|
25
|
+
- HTTP rerank honors `MEMORIX_RERANK_TIMEOUT_MS` on the inner fetch
|
|
26
|
+
(default 30s) instead of aborting at 5s.
|
|
27
|
+
- HTTP rerank no longer inherits or transmits the memory LLM credential
|
|
28
|
+
when `rerank.base_url` points at a different endpoint. If a Memory LLM
|
|
29
|
+
key is present, that foreign host is not called until
|
|
30
|
+
`MEMORIX_RERANK_API_KEY` / `rerank.api_key` is set.
|
|
31
|
+
- Codegraph CLI tests close git/sqlite sandboxes with retries so Windows
|
|
32
|
+
`afterEach` cannot hang past vitest's 10s hookTimeout.
|
|
33
|
+
|
|
7
34
|
## [1.7.2] - 2026-08-19
|
|
8
35
|
|
|
9
36
|
### Changed
|
|
@@ -29,7 +56,6 @@ All notable changes to this project will be documented in this file.
|
|
|
29
56
|
- **Centered maintenance preview** -- the Cleanup preview dialog is centered
|
|
30
57
|
on desktop and narrow screens, including after the Dashboard global reset
|
|
31
58
|
styles are applied.
|
|
32
|
-
|
|
33
59
|
## [1.7.0] - 2026-08-18
|
|
34
60
|
|
|
35
61
|
### Added
|