@amemhq/core 1.0.1 → 1.1.0

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.cjs CHANGED
@@ -33,6 +33,7 @@ __export(index_exports, {
33
33
  DEFAULT_CRUD_UPDATE_MIN_SIM: () => DEFAULT_CRUD_UPDATE_MIN_SIM,
34
34
  DEFAULT_EMBEDDING_MODEL: () => DEFAULT_EMBEDDING_MODEL,
35
35
  EmbeddingDimensionMismatchError: () => EmbeddingDimensionMismatchError,
36
+ EmbeddingModelMismatchError: () => EmbeddingModelMismatchError,
36
37
  addEpisodic: () => addEpisodic,
37
38
  addMemory: () => addMemory,
38
39
  canRead: () => canRead,
@@ -47,7 +48,9 @@ __export(index_exports, {
47
48
  encode: () => encode,
48
49
  ensureCollection: () => ensureCollection,
49
50
  generateReviewBatch: () => generateReviewBatch,
51
+ getEmbeddingDevice: () => getEmbeddingDevice,
50
52
  getEmbeddingDim: () => getEmbeddingDim,
53
+ getEmbeddingDtype: () => getEmbeddingDtype,
51
54
  getEmbeddingModel: () => getEmbeddingModel,
52
55
  getEmbeddingPooling: () => getEmbeddingPooling,
53
56
  getNote: () => getNote,
@@ -83,7 +86,7 @@ function getDataDir() {
83
86
  // src/embedding.ts
84
87
  var pipeline = null;
85
88
  var extractor = null;
86
- var loadedModelName = null;
89
+ var loadedKey = null;
87
90
  var cachedDim = null;
88
91
  var DEFAULT_EMBEDDING_MODEL = "Xenova/paraphrase-multilingual-MiniLM-L12-v2";
89
92
  function getEmbeddingModel() {
@@ -108,22 +111,37 @@ function getEmbeddingPooling() {
108
111
  const basename2 = getEmbeddingModel().split("/").pop()?.toLowerCase() ?? "";
109
112
  return CLS_POOLED_MODELS.has(basename2) ? "cls" : "mean";
110
113
  }
114
+ function getEmbeddingDevice() {
115
+ return process.env.AMEM_EMBED_DEVICE?.trim() || void 0;
116
+ }
117
+ function getEmbeddingDtype() {
118
+ return process.env.AMEM_EMBED_DTYPE?.trim() || void 0;
119
+ }
120
+ function extractorKey() {
121
+ return `${getEmbeddingModel()}|${getEmbeddingDevice() ?? ""}|${getEmbeddingDtype() ?? ""}`;
122
+ }
111
123
  async function getExtractor() {
112
- const wanted = getEmbeddingModel();
113
- if (extractor && loadedModelName === wanted) return extractor;
124
+ const wanted = extractorKey();
125
+ if (extractor && loadedKey === wanted) return extractor;
114
126
  if (!pipeline) {
115
127
  const mod = await import("@huggingface/transformers");
116
128
  pipeline = mod.pipeline;
117
129
  }
118
- extractor = await pipeline("feature-extraction", wanted, {
119
- revision: "main"
130
+ const device = getEmbeddingDevice();
131
+ const dtype = getEmbeddingDtype();
132
+ extractor = await pipeline("feature-extraction", getEmbeddingModel(), {
133
+ revision: "main",
134
+ // Omitted entirely when unset, so an unconfigured install gets exactly the
135
+ // library defaults it got before these existed.
136
+ ...device ? { device } : {},
137
+ ...dtype ? { dtype } : {}
120
138
  });
121
- loadedModelName = wanted;
139
+ loadedKey = wanted;
122
140
  cachedDim = null;
123
141
  return extractor;
124
142
  }
125
143
  async function getEmbeddingDim() {
126
- if (cachedDim !== null && loadedModelName === getEmbeddingModel()) return cachedDim;
144
+ if (cachedDim !== null && loadedKey === extractorKey()) return cachedDim;
127
145
  const probe = await encode("dimension probe");
128
146
  cachedDim = probe.length;
129
147
  return cachedDim;
@@ -208,7 +226,7 @@ var EmbeddingDimensionMismatchError = class extends Error {
208
226
  constructor(collection, collectionDim, modelDim, model) {
209
227
  super(
210
228
  `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.
211
- 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.`
229
+ Either set AMEM_EMBED_MODEL back to the model this collection was built with, or ${migrationHint(collection, model)}`
212
230
  );
213
231
  this.collection = collection;
214
232
  this.collectionDim = collectionDim;
@@ -221,6 +239,35 @@ Either set AMEM_EMBED_MODEL back to the model this collection was built with, or
221
239
  modelDim;
222
240
  model;
223
241
  };
242
+ function migrationHint(collection, targetModel) {
243
+ return `migrate to a new collection:
244
+
245
+ AMEM_EMBED_MODEL=${targetModel} \\
246
+ npx --package=@amemhq/core amem-migrate --to ${collection}_v2
247
+
248
+ 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.`;
249
+ }
250
+ var EmbeddingModelMismatchError = class extends Error {
251
+ constructor(collection, collectionModel, configuredModel) {
252
+ super(
253
+ `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.
254
+ Either set AMEM_EMBED_MODEL back to "${collectionModel}", or ` + migrationHint(collection, configuredModel)
255
+ );
256
+ this.collection = collection;
257
+ this.collectionModel = collectionModel;
258
+ this.configuredModel = configuredModel;
259
+ this.name = "EmbeddingModelMismatchError";
260
+ }
261
+ collection;
262
+ collectionModel;
263
+ configuredModel;
264
+ };
265
+ async function recordCollectionModel(collection, model) {
266
+ try {
267
+ await qdrant("PATCH", `/collections/${collection}`, { metadata: { embedding_model: model } });
268
+ } catch {
269
+ }
270
+ }
224
271
  async function qdrant(method, path5, body) {
225
272
  const res = await fetch(`${QDRANT_URL}${path5}`, {
226
273
  method,
@@ -263,6 +310,14 @@ async function ensureCollection(collectionName) {
263
310
  throw new EmbeddingDimensionMismatchError(col, collectionDim, modelDim, getEmbeddingModel());
264
311
  }
265
312
  }
313
+ const recorded = existing.config?.metadata?.embedding_model;
314
+ const current = getEmbeddingModel();
315
+ if (typeof recorded === "string" && recorded !== current) {
316
+ throw new EmbeddingModelMismatchError(col, recorded, current);
317
+ }
318
+ if (recorded === void 0) {
319
+ await recordCollectionModel(col, current);
320
+ }
266
321
  markReady();
267
322
  return;
268
323
  }
@@ -274,6 +329,7 @@ async function ensureCollection(collectionName) {
274
329
  } catch (err) {
275
330
  if (!(err instanceof Error) || !err.message.includes("already exists")) throw err;
276
331
  }
332
+ await recordCollectionModel(col, getEmbeddingModel());
277
333
  await qdrant("PUT", `/collections/${col}/index`, {
278
334
  field_name: "agent_id",
279
335
  field_schema: "keyword"
@@ -319,6 +375,7 @@ async function collectionDimRaw(collection) {
319
375
  }
320
376
  async function createCollectionRaw(collection, size) {
321
377
  await qdrant("PUT", `/collections/${collection}`, { vectors: { size, distance: "Cosine" } });
378
+ await recordCollectionModel(collection, getEmbeddingModel());
322
379
  for (const field_name of ["agent_id", "hash", "topics", "subjects"]) {
323
380
  await qdrant("PUT", `/collections/${collection}/index`, { field_name, field_schema: "keyword" });
324
381
  }
@@ -2358,6 +2415,7 @@ function isPlausibleUpdateTarget(newEmbedding, targetEmbedding, minSimilarity) {
2358
2415
  DEFAULT_CRUD_UPDATE_MIN_SIM,
2359
2416
  DEFAULT_EMBEDDING_MODEL,
2360
2417
  EmbeddingDimensionMismatchError,
2418
+ EmbeddingModelMismatchError,
2361
2419
  addEpisodic,
2362
2420
  addMemory,
2363
2421
  canRead,
@@ -2372,7 +2430,9 @@ function isPlausibleUpdateTarget(newEmbedding, targetEmbedding, minSimilarity) {
2372
2430
  encode,
2373
2431
  ensureCollection,
2374
2432
  generateReviewBatch,
2433
+ getEmbeddingDevice,
2375
2434
  getEmbeddingDim,
2435
+ getEmbeddingDtype,
2376
2436
  getEmbeddingModel,
2377
2437
  getEmbeddingPooling,
2378
2438
  getNote,