Skip to content

Commit 7a5c701

Browse files
committed
merge: resolve conflict with master, keep both test entries
2 parents 18f4ece + 619d703 commit 7a5c701

11 files changed

+1007
-181
lines changed

commit_msg.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
fix(embedder): address PR review comments (Issue #629)
2+
3+
- Add embedder-ollama-batch-routing.test.mjs to CI manifest
4+
- Add comments explaining why provider options are omitted for Ollama batch
5+
- Add note about /v1/embeddings no-fallback assumption
6+
7+
Reviewed by: rwmjhb

index.ts

Lines changed: 241 additions & 161 deletions
Large diffs are not rendered by default.

openclaw.plugin.json

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
"description": "Enhanced LanceDB-backed long-term memory with hybrid retrieval, multi-scope isolation, long-context chunking, and management CLI",
55
"version": "1.1.0-beta.10",
66
"kind": "memory",
7-
"skills": ["./skills"],
7+
"skills": [
8+
"./skills"
9+
],
810
"configSchema": {
911
"type": "object",
1012
"additionalProperties": false,
@@ -165,6 +167,18 @@
165167
"default": "full",
166168
"description": "Auto-recall depth mode. 'full': inject with configured per-item budget. 'summary': L0 abstracts only (compact). 'adaptive': analyze query intent to auto-select category and depth. 'off': disable auto-recall injection."
167169
},
170+
"autoRecallExcludeAgents": {
171+
"type": "array",
172+
"items": { "type": "string" },
173+
"default": [],
174+
"description": "Blacklist mode for auto-recall injection. Agents in this list are skipped. Agent resolution falls back to 'main' when no explicit agentId is available. If autoRecallIncludeAgents is also set, include wins."
175+
},
176+
"autoRecallIncludeAgents": {
177+
"type": "array",
178+
"items": { "type": "string" },
179+
"default": [],
180+
"description": "Whitelist mode for auto-recall injection. Only agents in this list receive auto-recall. Agent resolution falls back to 'main' when no explicit agentId is available. If both include and exclude are set, autoRecallIncludeAgents takes precedence (whitelist wins)."
181+
},
168182
"captureAssistant": {
169183
"type": "boolean"
170184
},
@@ -854,7 +868,10 @@
854868
}
855869
}
856870
}
857-
}
871+
},
872+
"required": [
873+
"embedding"
874+
]
858875
},
859876
"uiHints": {
860877
"embedding.apiKey": {
@@ -1376,6 +1393,16 @@
13761393
"label": "Max Extractions Per Hour",
13771394
"help": "Rate limit for auto-capture extractions. Prevents excessive LLM calls during rapid-fire sessions.",
13781395
"advanced": true
1396+
},
1397+
"autoRecallExcludeAgents": {
1398+
"label": "Auto-Recall Excluded Agents",
1399+
"help": "Blacklist mode. Agents here are skipped for auto-recall. If agentId is unavailable it falls back to 'main'. If autoRecallIncludeAgents is set, include wins.",
1400+
"advanced": true
1401+
},
1402+
"autoRecallIncludeAgents": {
1403+
"label": "Auto-Recall Included Agents",
1404+
"help": "Whitelist mode. Only these agents receive auto-recall. If agentId is unavailable it falls back to 'main'. Includes take precedence over excludes.",
1405+
"advanced": true
13791406
}
13801407
}
13811408
}

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"author": "win4r",
2626
"license": "MIT",
2727
"scripts": {
28-
"test": "node scripts/verify-ci-test-manifest.mjs && npm run test:cli-smoke && npm run test:core-regression && npm run test:storage-and-schema && npm run test:llm-clients-and-auth && npm run test:packaging-and-workflow",
28+
"test": "node test/embedder-error-hints.test.mjs && node test/cjk-recursion-regression.test.mjs && node test/migrate-legacy-schema.test.mjs && node --test test/config-session-strategy-migration.test.mjs && node --test test/scope-access-undefined.test.mjs && node --test test/reflection-bypass-hook.test.mjs && node --test test/smart-extractor-scope-filter.test.mjs && node --test test/store-empty-scope-filter.test.mjs && node --test test/recall-text-cleanup.test.mjs && node test/update-consistency-lancedb.test.mjs && node --test test/strip-envelope-metadata.test.mjs && node test/cli-smoke.mjs && node test/functional-e2e.mjs && node --test test/per-agent-auto-recall.test.mjs && node test/retriever-rerank-regression.mjs && node test/smart-memory-lifecycle.mjs && node test/smart-extractor-branches.mjs && node test/plugin-manifest-regression.mjs && node --test test/session-summary-before-reset.test.mjs && node --test test/sync-plugin-version.test.mjs && node test/smart-metadata-v2.mjs && node test/vector-search-cosine.test.mjs && node test/context-support-e2e.mjs && node test/temporal-facts.test.mjs && node test/memory-update-supersede.test.mjs && node test/memory-upgrader-diagnostics.test.mjs && node --test test/llm-api-key-client.test.mjs && node --test test/llm-oauth-client.test.mjs && node --test test/cli-oauth-login.test.mjs && node --test test/workflow-fork-guards.test.mjs && node --test test/clawteam-scope.test.mjs && node --test test/cross-process-lock.test.mjs && node --test test/preference-slots.test.mjs && node test/is-latest-auto-supersede.test.mjs && node --test test/temporal-awareness.test.mjs",
2929
"test:cli-smoke": "node scripts/run-ci-tests.mjs --group cli-smoke",
3030
"test:core-regression": "node scripts/run-ci-tests.mjs --group core-regression",
3131
"test:storage-and-schema": "node scripts/run-ci-tests.mjs --group storage-and-schema",
@@ -62,4 +62,4 @@
6262
"jiti": "^2.6.0",
6363
"typescript": "^5.9.3"
6464
}
65-
}
65+
}

scripts/ci-test-manifest.mjs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export const CI_TEST_MANIFEST = [
2020
{ group: "core-regression", runner: "node", file: "test/strip-envelope-metadata.test.mjs", args: ["--test"] },
2121
{ group: "cli-smoke", runner: "node", file: "test/cli-smoke.mjs" },
2222
{ group: "cli-smoke", runner: "node", file: "test/functional-e2e.mjs" },
23+
{ group: "storage-and-schema", runner: "node", file: "test/per-agent-auto-recall.test.mjs", args: ["--test"] },
2324
{ group: "core-regression", runner: "node", file: "test/retriever-rerank-regression.mjs" },
2425
{ group: "core-regression", runner: "node", file: "test/smart-memory-lifecycle.mjs" },
2526
{ group: "core-regression", runner: "node", file: "test/smart-extractor-branches.mjs" },
@@ -41,7 +42,6 @@ export const CI_TEST_MANIFEST = [
4142
{ group: "storage-and-schema", runner: "node", file: "test/cross-process-lock.test.mjs", args: ["--test"] },
4243
{ group: "core-regression", runner: "node", file: "test/preference-slots.test.mjs", args: ["--test"] },
4344
{ group: "core-regression", runner: "node", file: "test/is-latest-auto-supersede.test.mjs" },
44-
{ group: "core-regression", runner: "node", file: "test/hook-dedup-phase1.test.mjs", args: ["--test"] },
4545
{ group: "core-regression", runner: "node", file: "test/temporal-awareness.test.mjs", args: ["--test"] },
4646
// Issue #598 regression tests
4747
{ group: "core-regression", runner: "node", file: "test/store-serialization.test.mjs" },
@@ -50,6 +50,8 @@ export const CI_TEST_MANIFEST = [
5050
// Issue #632 / PR #639 lock contention fix
5151
{ group: "core-regression", runner: "node", file: "test/upgrader-phase2-lock.test.mjs" },
5252
{ group: "core-regression", runner: "node", file: "test/upgrader-phase2-extreme.test.mjs" },
53+
// Issue #629 batch embedding fix
54+
{ group: "llm-clients-and-auth", runner: "node", file: "test/embedder-ollama-batch-routing.test.mjs" },
5355
];
5456

5557
export function getEntriesForGroup(group) {

scripts/verify-ci-test-manifest.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const EXPECTED_BASELINE = [
2121
{ group: "core-regression", runner: "node", file: "test/strip-envelope-metadata.test.mjs", args: ["--test"] },
2222
{ group: "cli-smoke", runner: "node", file: "test/cli-smoke.mjs" },
2323
{ group: "cli-smoke", runner: "node", file: "test/functional-e2e.mjs" },
24+
{ group: "storage-and-schema", runner: "node", file: "test/per-agent-auto-recall.test.mjs", args: ["--test"] },
2425
{ group: "core-regression", runner: "node", file: "test/retriever-rerank-regression.mjs" },
2526
{ group: "core-regression", runner: "node", file: "test/smart-memory-lifecycle.mjs" },
2627
{ group: "core-regression", runner: "node", file: "test/smart-extractor-branches.mjs" },

src/embedder.ts

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -569,39 +569,89 @@ export class Embedder {
569569
* Call embeddings.create using native fetch (bypasses OpenAI SDK).
570570
* Used exclusively for Ollama endpoints where AbortController must work
571571
* correctly to avoid long-lived stalled sockets.
572+
*
573+
* For Ollama 0.20.5+: /v1/embeddings may return empty arrays for some models,
574+
* so we use /api/embeddings with "prompt" field for single requests (PR #621).
575+
* For batch requests, we use /v1/embeddings with "input" array as it's more
576+
* efficient and confirmed working in local testing.
577+
*
578+
* See: https://github.com/CortexReach/memory-lancedb-pro/issues/620
579+
* Fix: https://github.com/CortexReach/memory-lancedb-pro/issues/629
572580
*/
573581
private async embedWithNativeFetch(payload: any, signal?: AbortSignal): Promise<any> {
574582
if (!this._baseURL) {
575583
throw new Error("embedWithNativeFetch requires a baseURL");
576584
}
577585

578-
// Fix for Ollama 0.20.5+: /v1/embeddings returns empty arrays for both `input` and `prompt`.
579-
// Only /api/embeddings + `prompt` parameter works correctly.
580-
// See: https://github.com/CortexReach/memory-lancedb-pro/issues/620
581586
const base = this._baseURL.replace(/\/$/, "").replace(/\/v1$/, "");
582-
const endpoint = base + "/api/embeddings";
583-
584587
const apiKey = this.clients[0]?.apiKey ?? "ollama";
585588

586-
// Ollama's /api/embeddings requires "prompt" field, not "input"
587-
const ollamaPayload = {
588-
model: payload.model,
589-
prompt: payload.input,
590-
};
589+
// Handle batch requests with /v1/embeddings + input array
590+
// NOTE: /v1/embeddings is used unconditionally for batch with no fallback.
591+
// If a model doesn't support that endpoint, failure will be silent from the user's perspective.
592+
// This is acceptable because most Ollama embedding models support /v1/embeddings.
593+
if (Array.isArray(payload.input)) {
594+
const response = await fetch(base + "/v1/embeddings", {
595+
method: "POST",
596+
headers: {
597+
"Content-Type": "application/json",
598+
"Authorization": `Bearer ${apiKey}`,
599+
},
600+
body: JSON.stringify({
601+
model: payload.model,
602+
input: payload.input,
603+
// NOTE: Other provider options (encoding_format, normalized, dimensions, etc.)
604+
// from buildPayload() are intentionally not included. Ollama embedding models
605+
// do not support these parameters, so omitting them is correct.
606+
}),
607+
signal,
608+
});
591609

592-
const response = await fetch(endpoint, {
610+
if (!response.ok) {
611+
const body = await response.text().catch(() => "");
612+
throw new Error(
613+
`Ollama batch embedding failed: ${response.status} ${response.statusText} ??${body.slice(0, 200)}`
614+
);
615+
}
616+
617+
const data = await response.json();
618+
619+
// Validate response count and non-empty embeddings
620+
if (
621+
!Array.isArray(data?.data) ||
622+
data.data.length !== payload.input.length ||
623+
data.data.some((item: any) => {
624+
const embedding = item?.embedding;
625+
return !Array.isArray(embedding) || embedding.length === 0;
626+
})
627+
) {
628+
throw new Error(
629+
`Ollama batch embedding returned invalid response for ${payload.input.length} inputs`
630+
);
631+
}
632+
633+
return data;
634+
}
635+
636+
// Single request: use /api/embeddings + prompt (PR #621 fix)
637+
const response = await fetch(base + "/api/embeddings", {
593638
method: "POST",
594639
headers: {
595640
"Content-Type": "application/json",
596641
"Authorization": `Bearer ${apiKey}`,
597642
},
598-
body: JSON.stringify(ollamaPayload),
599-
signal: signal,
643+
body: JSON.stringify({
644+
model: payload.model,
645+
prompt: payload.input,
646+
}),
647+
signal,
600648
});
601649

602650
if (!response.ok) {
603651
const body = await response.text().catch(() => "");
604-
throw new Error(`Ollama embedding failed: ${response.status} ${response.statusText} ??${body.slice(0, 200)}`);
652+
throw new Error(
653+
`Ollama embedding failed: ${response.status} ${response.statusText} ??${body.slice(0, 200)}`
654+
);
605655
}
606656

607657
const data = await response.json();

src/extraction-prompts.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ Each memory contains three levels:
103103
\`\`\`json
104104
{
105105
"category": "cases",
106-
"abstract": "LanceDB BigInt error -> Use Number() coercion before arithmetic",
106+
"abstract": "LanceDB BigInt numeric handling issue",
107107
"overview": "## Problem\\nLanceDB 0.26+ returns BigInt for numeric columns\\n\\n## Solution\\nCoerce values with Number(...) before arithmetic",
108108
"content": "When LanceDB returns BigInt values, wrap them with Number() before doing arithmetic operations."
109109
}

0 commit comments

Comments
 (0)