@amemhq/core 1.0.0 → 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/chunk-B2NS7WAM.js +2182 -0
- package/dist/chunk-B2NS7WAM.js.map +1 -0
- package/dist/cli-migrate.cjs +932 -0
- package/dist/cli-migrate.cjs.map +1 -0
- package/dist/cli-migrate.d.cts +16 -0
- package/dist/cli-migrate.d.ts +16 -0
- package/dist/cli-migrate.js +86 -0
- package/dist/cli-migrate.js.map +1 -0
- package/dist/index.cjs +105 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +52 -2
- package/dist/index.d.ts +52 -2
- package/dist/index.js +73 -2096
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
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,8 +48,11 @@ __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,
|
|
55
|
+
getEmbeddingPooling: () => getEmbeddingPooling,
|
|
52
56
|
getNote: () => getNote,
|
|
53
57
|
invalidateNote: () => invalidateNote,
|
|
54
58
|
isModelLoaded: () => isModelLoaded,
|
|
@@ -82,47 +86,85 @@ function getDataDir() {
|
|
|
82
86
|
// src/embedding.ts
|
|
83
87
|
var pipeline = null;
|
|
84
88
|
var extractor = null;
|
|
85
|
-
var
|
|
89
|
+
var loadedKey = null;
|
|
86
90
|
var cachedDim = null;
|
|
87
91
|
var DEFAULT_EMBEDDING_MODEL = "Xenova/paraphrase-multilingual-MiniLM-L12-v2";
|
|
88
92
|
function getEmbeddingModel() {
|
|
89
93
|
return process.env.AMEM_EMBED_MODEL?.trim() || DEFAULT_EMBEDDING_MODEL;
|
|
90
94
|
}
|
|
95
|
+
var CLS_POOLED_MODELS = /* @__PURE__ */ new Set([
|
|
96
|
+
"bge-m3",
|
|
97
|
+
"bge-base-zh-v1.5",
|
|
98
|
+
"bge-small-zh-v1.5",
|
|
99
|
+
"bge-base-en-v1.5",
|
|
100
|
+
"bge-small-en-v1.5",
|
|
101
|
+
"bge-large-en-v1.5",
|
|
102
|
+
"gte-multilingual-base",
|
|
103
|
+
"gte-modernbert-base",
|
|
104
|
+
"gte-large-en-v1.5",
|
|
105
|
+
"snowflake-arctic-embed-m",
|
|
106
|
+
"snowflake-arctic-embed-l"
|
|
107
|
+
]);
|
|
108
|
+
function getEmbeddingPooling() {
|
|
109
|
+
const explicit = process.env.AMEM_EMBED_POOLING?.trim().toLowerCase();
|
|
110
|
+
if (explicit === "mean" || explicit === "cls") return explicit;
|
|
111
|
+
const basename2 = getEmbeddingModel().split("/").pop()?.toLowerCase() ?? "";
|
|
112
|
+
return CLS_POOLED_MODELS.has(basename2) ? "cls" : "mean";
|
|
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
|
+
}
|
|
91
123
|
async function getExtractor() {
|
|
92
|
-
const wanted =
|
|
93
|
-
if (extractor &&
|
|
124
|
+
const wanted = extractorKey();
|
|
125
|
+
if (extractor && loadedKey === wanted) return extractor;
|
|
94
126
|
if (!pipeline) {
|
|
95
127
|
const mod = await import("@huggingface/transformers");
|
|
96
128
|
pipeline = mod.pipeline;
|
|
97
129
|
}
|
|
98
|
-
|
|
99
|
-
|
|
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 } : {}
|
|
100
138
|
});
|
|
101
|
-
|
|
139
|
+
loadedKey = wanted;
|
|
102
140
|
cachedDim = null;
|
|
103
141
|
return extractor;
|
|
104
142
|
}
|
|
105
143
|
async function getEmbeddingDim() {
|
|
106
|
-
if (cachedDim !== null &&
|
|
144
|
+
if (cachedDim !== null && loadedKey === extractorKey()) return cachedDim;
|
|
107
145
|
const probe = await encode("dimension probe");
|
|
108
146
|
cachedDim = probe.length;
|
|
109
147
|
return cachedDim;
|
|
110
148
|
}
|
|
111
|
-
function
|
|
149
|
+
function poolNormalize(output, attentionMask, mode) {
|
|
112
150
|
const seqLen = output.length;
|
|
113
151
|
const dim = output[0].length;
|
|
114
152
|
const pooled = new Array(dim).fill(0);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
maskSum
|
|
153
|
+
if (mode === "cls") {
|
|
154
|
+
for (let j = 0; j < dim; j++) pooled[j] = output[0][j];
|
|
155
|
+
} else {
|
|
156
|
+
let maskSum = 0;
|
|
157
|
+
for (let i = 0; i < seqLen; i++) {
|
|
158
|
+
const m = attentionMask[i];
|
|
159
|
+
maskSum += m;
|
|
160
|
+
for (let j = 0; j < dim; j++) {
|
|
161
|
+
pooled[j] += output[i][j] * m;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
119
164
|
for (let j = 0; j < dim; j++) {
|
|
120
|
-
pooled[j]
|
|
165
|
+
pooled[j] /= Math.max(maskSum, 1e-9);
|
|
121
166
|
}
|
|
122
167
|
}
|
|
123
|
-
for (let j = 0; j < dim; j++) {
|
|
124
|
-
pooled[j] /= Math.max(maskSum, 1e-9);
|
|
125
|
-
}
|
|
126
168
|
let norm = 0;
|
|
127
169
|
for (const v of pooled) norm += v * v;
|
|
128
170
|
norm = Math.sqrt(norm);
|
|
@@ -130,7 +172,8 @@ function meanPoolingNormalize(output, attentionMask) {
|
|
|
130
172
|
}
|
|
131
173
|
async function encode(text) {
|
|
132
174
|
const ext = await getExtractor();
|
|
133
|
-
const
|
|
175
|
+
const pooling = getEmbeddingPooling();
|
|
176
|
+
const result = await ext(text, { pooling, normalize: true });
|
|
134
177
|
if (result && result.data) {
|
|
135
178
|
return Array.from(result.data);
|
|
136
179
|
}
|
|
@@ -146,7 +189,7 @@ async function encode(text) {
|
|
|
146
189
|
}
|
|
147
190
|
raw.push(row);
|
|
148
191
|
}
|
|
149
|
-
return
|
|
192
|
+
return poolNormalize(raw, new Array(seqLen).fill(1), pooling);
|
|
150
193
|
}
|
|
151
194
|
throw new Error("Unexpected embedding output shape");
|
|
152
195
|
}
|
|
@@ -183,7 +226,7 @@ var EmbeddingDimensionMismatchError = class extends Error {
|
|
|
183
226
|
constructor(collection, collectionDim, modelDim, model) {
|
|
184
227
|
super(
|
|
185
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.
|
|
186
|
-
Either set AMEM_EMBED_MODEL back to the model this collection was built with, or
|
|
229
|
+
Either set AMEM_EMBED_MODEL back to the model this collection was built with, or ${migrationHint(collection, model)}`
|
|
187
230
|
);
|
|
188
231
|
this.collection = collection;
|
|
189
232
|
this.collectionDim = collectionDim;
|
|
@@ -196,6 +239,35 @@ Either set AMEM_EMBED_MODEL back to the model this collection was built with, or
|
|
|
196
239
|
modelDim;
|
|
197
240
|
model;
|
|
198
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
|
+
}
|
|
199
271
|
async function qdrant(method, path5, body) {
|
|
200
272
|
const res = await fetch(`${QDRANT_URL}${path5}`, {
|
|
201
273
|
method,
|
|
@@ -238,6 +310,14 @@ async function ensureCollection(collectionName) {
|
|
|
238
310
|
throw new EmbeddingDimensionMismatchError(col, collectionDim, modelDim, getEmbeddingModel());
|
|
239
311
|
}
|
|
240
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
|
+
}
|
|
241
321
|
markReady();
|
|
242
322
|
return;
|
|
243
323
|
}
|
|
@@ -249,6 +329,7 @@ async function ensureCollection(collectionName) {
|
|
|
249
329
|
} catch (err) {
|
|
250
330
|
if (!(err instanceof Error) || !err.message.includes("already exists")) throw err;
|
|
251
331
|
}
|
|
332
|
+
await recordCollectionModel(col, getEmbeddingModel());
|
|
252
333
|
await qdrant("PUT", `/collections/${col}/index`, {
|
|
253
334
|
field_name: "agent_id",
|
|
254
335
|
field_schema: "keyword"
|
|
@@ -294,6 +375,7 @@ async function collectionDimRaw(collection) {
|
|
|
294
375
|
}
|
|
295
376
|
async function createCollectionRaw(collection, size) {
|
|
296
377
|
await qdrant("PUT", `/collections/${collection}`, { vectors: { size, distance: "Cosine" } });
|
|
378
|
+
await recordCollectionModel(collection, getEmbeddingModel());
|
|
297
379
|
for (const field_name of ["agent_id", "hash", "topics", "subjects"]) {
|
|
298
380
|
await qdrant("PUT", `/collections/${collection}/index`, { field_name, field_schema: "keyword" });
|
|
299
381
|
}
|
|
@@ -2333,6 +2415,7 @@ function isPlausibleUpdateTarget(newEmbedding, targetEmbedding, minSimilarity) {
|
|
|
2333
2415
|
DEFAULT_CRUD_UPDATE_MIN_SIM,
|
|
2334
2416
|
DEFAULT_EMBEDDING_MODEL,
|
|
2335
2417
|
EmbeddingDimensionMismatchError,
|
|
2418
|
+
EmbeddingModelMismatchError,
|
|
2336
2419
|
addEpisodic,
|
|
2337
2420
|
addMemory,
|
|
2338
2421
|
canRead,
|
|
@@ -2347,8 +2430,11 @@ function isPlausibleUpdateTarget(newEmbedding, targetEmbedding, minSimilarity) {
|
|
|
2347
2430
|
encode,
|
|
2348
2431
|
ensureCollection,
|
|
2349
2432
|
generateReviewBatch,
|
|
2433
|
+
getEmbeddingDevice,
|
|
2350
2434
|
getEmbeddingDim,
|
|
2435
|
+
getEmbeddingDtype,
|
|
2351
2436
|
getEmbeddingModel,
|
|
2437
|
+
getEmbeddingPooling,
|
|
2352
2438
|
getNote,
|
|
2353
2439
|
invalidateNote,
|
|
2354
2440
|
isModelLoaded,
|