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 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 = getEmbeddingModel();
67
- if (extractor && loadedModelName === wanted) return 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
- extractor = await pipeline("feature-extraction", wanted, {
73
- revision: "main"
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
- loadedModelName = wanted;
90
+ loadedKey = wanted;
76
91
  cachedDim = null;
77
92
  return extractor;
78
93
  }
79
94
  async function getEmbeddingDim() {
80
- if (cachedDim !== null && loadedModelName === getEmbeddingModel()) return cachedDim;
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, loadedModelName, cachedDim, DEFAULT_EMBEDDING_MODEL, CLS_POOLED_MODELS;
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
- loadedModelName = null;
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 migrate: build a new collection with the new model, backfill it, then point AMEM_COLLECTION at it. See docs/reference/embedding-models.md.`
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}`);
@@ -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.2",
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.2",
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",