openclaw-amem 1.4.2 → 1.4.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/dist/index.js +68 -11
- package/openclaw.plugin.json +3 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -62,22 +62,37 @@ function getEmbeddingPooling() {
|
|
|
62
62
|
const basename2 = getEmbeddingModel().split("/").pop()?.toLowerCase() ?? "";
|
|
63
63
|
return CLS_POOLED_MODELS.has(basename2) ? "cls" : "mean";
|
|
64
64
|
}
|
|
65
|
+
function getEmbeddingDevice() {
|
|
66
|
+
return process.env.AMEM_EMBED_DEVICE?.trim() || void 0;
|
|
67
|
+
}
|
|
68
|
+
function getEmbeddingDtype() {
|
|
69
|
+
return process.env.AMEM_EMBED_DTYPE?.trim() || void 0;
|
|
70
|
+
}
|
|
71
|
+
function extractorKey() {
|
|
72
|
+
return `${getEmbeddingModel()}|${getEmbeddingDevice() ?? ""}|${getEmbeddingDtype() ?? ""}`;
|
|
73
|
+
}
|
|
65
74
|
async function getExtractor() {
|
|
66
|
-
const wanted =
|
|
67
|
-
if (extractor &&
|
|
75
|
+
const wanted = extractorKey();
|
|
76
|
+
if (extractor && loadedKey === wanted) return extractor;
|
|
68
77
|
if (!pipeline) {
|
|
69
78
|
const mod = await import("@huggingface/transformers");
|
|
70
79
|
pipeline = mod.pipeline;
|
|
71
80
|
}
|
|
72
|
-
|
|
73
|
-
|
|
81
|
+
const device = getEmbeddingDevice();
|
|
82
|
+
const dtype = getEmbeddingDtype();
|
|
83
|
+
extractor = await pipeline("feature-extraction", getEmbeddingModel(), {
|
|
84
|
+
revision: "main",
|
|
85
|
+
// Omitted entirely when unset, so an unconfigured install gets exactly the
|
|
86
|
+
// library defaults it got before these existed.
|
|
87
|
+
...device ? { device } : {},
|
|
88
|
+
...dtype ? { dtype } : {}
|
|
74
89
|
});
|
|
75
|
-
|
|
90
|
+
loadedKey = wanted;
|
|
76
91
|
cachedDim = null;
|
|
77
92
|
return extractor;
|
|
78
93
|
}
|
|
79
94
|
async function getEmbeddingDim() {
|
|
80
|
-
if (cachedDim !== null &&
|
|
95
|
+
if (cachedDim !== null && loadedKey === extractorKey()) return cachedDim;
|
|
81
96
|
const probe = await encode("dimension probe");
|
|
82
97
|
cachedDim = probe.length;
|
|
83
98
|
return cachedDim;
|
|
@@ -140,13 +155,13 @@ function cosineSimilarity(a, b) {
|
|
|
140
155
|
for (let i = 0; i < a.length; i++) dot += a[i] * b[i];
|
|
141
156
|
return dot;
|
|
142
157
|
}
|
|
143
|
-
var pipeline, extractor,
|
|
158
|
+
var pipeline, extractor, loadedKey, cachedDim, DEFAULT_EMBEDDING_MODEL, CLS_POOLED_MODELS;
|
|
144
159
|
var init_embedding = __esm({
|
|
145
160
|
"../amem-core/src/embedding.ts"() {
|
|
146
161
|
"use strict";
|
|
147
162
|
pipeline = null;
|
|
148
163
|
extractor = null;
|
|
149
|
-
|
|
164
|
+
loadedKey = null;
|
|
150
165
|
cachedDim = null;
|
|
151
166
|
DEFAULT_EMBEDDING_MODEL = "Xenova/paraphrase-multilingual-MiniLM-L12-v2";
|
|
152
167
|
CLS_POOLED_MODELS = /* @__PURE__ */ new Set([
|
|
@@ -179,6 +194,20 @@ var init_auth = __esm({
|
|
|
179
194
|
});
|
|
180
195
|
|
|
181
196
|
// ../amem-core/src/storage.ts
|
|
197
|
+
function migrationHint(collection, targetModel) {
|
|
198
|
+
return `migrate to a new collection:
|
|
199
|
+
|
|
200
|
+
AMEM_EMBED_MODEL=${targetModel} \\
|
|
201
|
+
npx --package=@amemhq/core amem-migrate --to ${collection}_v2
|
|
202
|
+
|
|
203
|
+
That is a dry run; add --apply to write. "${collection}" is only read, so nothing is lost either way. When it looks right, point whatever names this collection at the new one \u2014 AMEM_COLLECTION, or the plugin's "collection" setting if this agent has its own. See https://amem.owo.lc/reference/embedding-models.`;
|
|
204
|
+
}
|
|
205
|
+
async function recordCollectionModel(collection, model) {
|
|
206
|
+
try {
|
|
207
|
+
await qdrant("PATCH", `/collections/${collection}`, { metadata: { embedding_model: model } });
|
|
208
|
+
} catch {
|
|
209
|
+
}
|
|
210
|
+
}
|
|
182
211
|
async function qdrant(method, path6, body) {
|
|
183
212
|
const res = await fetch(`${QDRANT_URL}${path6}`, {
|
|
184
213
|
method,
|
|
@@ -219,6 +248,14 @@ async function ensureCollection(collectionName) {
|
|
|
219
248
|
throw new EmbeddingDimensionMismatchError(col, collectionDim, modelDim, getEmbeddingModel());
|
|
220
249
|
}
|
|
221
250
|
}
|
|
251
|
+
const recorded = existing.config?.metadata?.embedding_model;
|
|
252
|
+
const current = getEmbeddingModel();
|
|
253
|
+
if (typeof recorded === "string" && recorded !== current) {
|
|
254
|
+
throw new EmbeddingModelMismatchError(col, recorded, current);
|
|
255
|
+
}
|
|
256
|
+
if (recorded === void 0) {
|
|
257
|
+
await recordCollectionModel(col, current);
|
|
258
|
+
}
|
|
222
259
|
markReady();
|
|
223
260
|
return;
|
|
224
261
|
}
|
|
@@ -230,6 +267,7 @@ async function ensureCollection(collectionName) {
|
|
|
230
267
|
} catch (err) {
|
|
231
268
|
if (!(err instanceof Error) || !err.message.includes("already exists")) throw err;
|
|
232
269
|
}
|
|
270
|
+
await recordCollectionModel(col, getEmbeddingModel());
|
|
233
271
|
await qdrant("PUT", `/collections/${col}/index`, {
|
|
234
272
|
field_name: "agent_id",
|
|
235
273
|
field_schema: "keyword"
|
|
@@ -275,6 +313,7 @@ async function collectionDimRaw(collection) {
|
|
|
275
313
|
}
|
|
276
314
|
async function createCollectionRaw(collection, size) {
|
|
277
315
|
await qdrant("PUT", `/collections/${collection}`, { vectors: { size, distance: "Cosine" } });
|
|
316
|
+
await recordCollectionModel(collection, getEmbeddingModel());
|
|
278
317
|
for (const field_name of ["agent_id", "hash", "topics", "subjects"]) {
|
|
279
318
|
await qdrant("PUT", `/collections/${collection}/index`, { field_name, field_schema: "keyword" });
|
|
280
319
|
}
|
|
@@ -667,7 +706,7 @@ async function invalidateNote(id, callerAgentId) {
|
|
|
667
706
|
async function patchNotePayload(id, fields) {
|
|
668
707
|
return makeCrud(getCollection()).patchNotePayload(id, fields);
|
|
669
708
|
}
|
|
670
|
-
var QDRANT_URL, getCollection, EmbeddingDimensionMismatchError, _collectionReady, _collectionReadyMap;
|
|
709
|
+
var QDRANT_URL, getCollection, EmbeddingDimensionMismatchError, EmbeddingModelMismatchError, _collectionReady, _collectionReadyMap;
|
|
671
710
|
var init_storage = __esm({
|
|
672
711
|
"../amem-core/src/storage.ts"() {
|
|
673
712
|
"use strict";
|
|
@@ -679,7 +718,7 @@ var init_storage = __esm({
|
|
|
679
718
|
constructor(collection, collectionDim, modelDim, model) {
|
|
680
719
|
super(
|
|
681
720
|
`Collection "${collection}" stores ${collectionDim}-dimension vectors, but the embedding model "${model}" produces ${modelDim}. Qdrant fixes a collection's vector size at creation and cannot change it, so writes and searches would both fail.
|
|
682
|
-
Either set AMEM_EMBED_MODEL back to the model this collection was built with, or
|
|
721
|
+
Either set AMEM_EMBED_MODEL back to the model this collection was built with, or ${migrationHint(collection, model)}`
|
|
683
722
|
);
|
|
684
723
|
this.collection = collection;
|
|
685
724
|
this.collectionDim = collectionDim;
|
|
@@ -692,6 +731,21 @@ Either set AMEM_EMBED_MODEL back to the model this collection was built with, or
|
|
|
692
731
|
modelDim;
|
|
693
732
|
model;
|
|
694
733
|
};
|
|
734
|
+
EmbeddingModelMismatchError = class extends Error {
|
|
735
|
+
constructor(collection, collectionModel, configuredModel) {
|
|
736
|
+
super(
|
|
737
|
+
`Collection "${collection}" was built with the embedding model "${collectionModel}", but this process is configured for "${configuredModel}". Both produce vectors of the same width, so nothing would fail \u2014 searches would just quietly compare vectors from two different models.
|
|
738
|
+
Either set AMEM_EMBED_MODEL back to "${collectionModel}", or ` + migrationHint(collection, configuredModel)
|
|
739
|
+
);
|
|
740
|
+
this.collection = collection;
|
|
741
|
+
this.collectionModel = collectionModel;
|
|
742
|
+
this.configuredModel = configuredModel;
|
|
743
|
+
this.name = "EmbeddingModelMismatchError";
|
|
744
|
+
}
|
|
745
|
+
collection;
|
|
746
|
+
collectionModel;
|
|
747
|
+
configuredModel;
|
|
748
|
+
};
|
|
695
749
|
_collectionReady = false;
|
|
696
750
|
_collectionReadyMap = /* @__PURE__ */ new Map();
|
|
697
751
|
}
|
|
@@ -2403,6 +2457,7 @@ __export(src_exports, {
|
|
|
2403
2457
|
DEFAULT_CRUD_UPDATE_MIN_SIM: () => DEFAULT_CRUD_UPDATE_MIN_SIM,
|
|
2404
2458
|
DEFAULT_EMBEDDING_MODEL: () => DEFAULT_EMBEDDING_MODEL,
|
|
2405
2459
|
EmbeddingDimensionMismatchError: () => EmbeddingDimensionMismatchError,
|
|
2460
|
+
EmbeddingModelMismatchError: () => EmbeddingModelMismatchError,
|
|
2406
2461
|
addEpisodic: () => addEpisodic,
|
|
2407
2462
|
addMemory: () => addMemory,
|
|
2408
2463
|
canRead: () => canRead,
|
|
@@ -2417,7 +2472,9 @@ __export(src_exports, {
|
|
|
2417
2472
|
encode: () => encode,
|
|
2418
2473
|
ensureCollection: () => ensureCollection,
|
|
2419
2474
|
generateReviewBatch: () => generateReviewBatch,
|
|
2475
|
+
getEmbeddingDevice: () => getEmbeddingDevice,
|
|
2420
2476
|
getEmbeddingDim: () => getEmbeddingDim,
|
|
2477
|
+
getEmbeddingDtype: () => getEmbeddingDtype,
|
|
2421
2478
|
getEmbeddingModel: () => getEmbeddingModel,
|
|
2422
2479
|
getEmbeddingPooling: () => getEmbeddingPooling,
|
|
2423
2480
|
getNote: () => getNote,
|
|
@@ -2552,7 +2609,7 @@ function register(api) {
|
|
|
2552
2609
|
`openclaw-amem: registered (native TS, Qdrant, default agent_id=${defaultScope.agentId}, default collection=${pluginConfig.collection ?? "amem_notes (default)"}, per-agent scope resolved per call)`
|
|
2553
2610
|
);
|
|
2554
2611
|
ensureCollection(pluginConfig.collection).catch((e) => {
|
|
2555
|
-
if (e instanceof EmbeddingDimensionMismatchError) {
|
|
2612
|
+
if (e instanceof EmbeddingDimensionMismatchError || e instanceof EmbeddingModelMismatchError) {
|
|
2556
2613
|
logger.error(`openclaw-amem: memory is UNUSABLE \u2014 ${e.message}`);
|
|
2557
2614
|
} else {
|
|
2558
2615
|
logger.warn(`openclaw-amem: ensureCollection failed \u2014 ${e.message}`);
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "openclaw-amem",
|
|
3
3
|
"name": "amem",
|
|
4
4
|
"description": "Catches memories that contradict each other. Notes rewrite themselves as new ones arrive, link into a graph, and stay separated per agent and per person. Runs on a small model. Requires a local Qdrant. 中文走 jieba 分词,提示词有中文版。",
|
|
5
|
-
"version": "1.4.
|
|
5
|
+
"version": "1.4.3",
|
|
6
6
|
"kind": "memory",
|
|
7
7
|
"openclaw": {
|
|
8
8
|
"compat": {
|
|
@@ -40,6 +40,8 @@
|
|
|
40
40
|
"AMEM_CRUD_UPDATE_MIN_SIM",
|
|
41
41
|
"AMEM_EMBED_MODEL",
|
|
42
42
|
"AMEM_EMBED_POOLING",
|
|
43
|
+
"AMEM_EMBED_DEVICE",
|
|
44
|
+
"AMEM_EMBED_DTYPE",
|
|
43
45
|
"AMEM_COLLECTION",
|
|
44
46
|
"AMEM_DATA_DIR",
|
|
45
47
|
"AMEM_EVO_COUNTER_PATH",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-amem",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.3",
|
|
4
4
|
"description": "Catches memories that contradict each other. Notes rewrite themselves as new ones arrive, link into a graph, and stay separated per agent and per person. Runs on a small model. Requires a local Qdrant. 中文走 jieba 分词,提示词有中文版。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|