@amemhq/core 1.1.0 → 2.0.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/README.md +2 -2
- package/dist/{chunk-B2NS7WAM.js → chunk-K6WZTDM7.js} +229 -58
- package/dist/chunk-K6WZTDM7.js.map +1 -0
- package/dist/cli-migrate.cjs +206 -52
- package/dist/cli-migrate.cjs.map +1 -1
- package/dist/cli-migrate.d.cts +21 -7
- package/dist/cli-migrate.d.ts +21 -7
- package/dist/cli-migrate.js +103 -42
- package/dist/cli-migrate.js.map +1 -1
- package/dist/index.cjs +228 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +145 -29
- package/dist/index.d.ts +145 -29
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-B2NS7WAM.js.map +0 -1
package/dist/cli-migrate.cjs
CHANGED
|
@@ -31,6 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
// src/cli-migrate.ts
|
|
32
32
|
var cli_migrate_exports = {};
|
|
33
33
|
__export(cli_migrate_exports, {
|
|
34
|
+
carried: () => carried,
|
|
35
|
+
deriveTarget: () => deriveTarget,
|
|
34
36
|
parseArgs: () => parseArgs
|
|
35
37
|
});
|
|
36
38
|
module.exports = __toCommonJS(cli_migrate_exports);
|
|
@@ -40,9 +42,11 @@ var pipeline = null;
|
|
|
40
42
|
var extractor = null;
|
|
41
43
|
var loadedKey = null;
|
|
42
44
|
var cachedDim = null;
|
|
43
|
-
var DEFAULT_EMBEDDING_MODEL = "Xenova/
|
|
45
|
+
var DEFAULT_EMBEDDING_MODEL = "Xenova/bge-m3";
|
|
46
|
+
var DEFAULT_MODEL_DTYPE = "fp16";
|
|
47
|
+
var pinnedModel = null;
|
|
44
48
|
function getEmbeddingModel() {
|
|
45
|
-
return process.env.AMEM_EMBED_MODEL?.trim() || DEFAULT_EMBEDDING_MODEL;
|
|
49
|
+
return process.env.AMEM_EMBED_MODEL?.trim() || pinnedModel || DEFAULT_EMBEDDING_MODEL;
|
|
46
50
|
}
|
|
47
51
|
var CLS_POOLED_MODELS = /* @__PURE__ */ new Set([
|
|
48
52
|
"bge-m3",
|
|
@@ -67,7 +71,9 @@ function getEmbeddingDevice() {
|
|
|
67
71
|
return process.env.AMEM_EMBED_DEVICE?.trim() || void 0;
|
|
68
72
|
}
|
|
69
73
|
function getEmbeddingDtype() {
|
|
70
|
-
|
|
74
|
+
const explicit = process.env.AMEM_EMBED_DTYPE?.trim();
|
|
75
|
+
if (explicit) return explicit;
|
|
76
|
+
return getEmbeddingModel() === DEFAULT_EMBEDDING_MODEL ? DEFAULT_MODEL_DTYPE : void 0;
|
|
71
77
|
}
|
|
72
78
|
function extractorKey() {
|
|
73
79
|
return `${getEmbeddingModel()}|${getEmbeddingDevice() ?? ""}|${getEmbeddingDtype() ?? ""}`;
|
|
@@ -202,6 +208,43 @@ async function createCollectionRaw(collection, size) {
|
|
|
202
208
|
async function upsertPointsRaw(collection, points) {
|
|
203
209
|
await qdrant("PUT", `/collections/${collection}/points?wait=true`, { points });
|
|
204
210
|
}
|
|
211
|
+
async function scrollIdsRaw(collection, limit = 1e4) {
|
|
212
|
+
const ids = /* @__PURE__ */ new Set();
|
|
213
|
+
let offset = void 0;
|
|
214
|
+
for (; ; ) {
|
|
215
|
+
const body = { with_payload: false, with_vector: false, limit };
|
|
216
|
+
if (offset !== void 0 && offset !== null) body.offset = offset;
|
|
217
|
+
const res = await qdrant("POST", `/collections/${collection}/points/scroll`, body);
|
|
218
|
+
for (const p of res.points) ids.add(String(p.id));
|
|
219
|
+
offset = res.next_page_offset;
|
|
220
|
+
if (offset === void 0 || offset === null || res.points.length === 0) break;
|
|
221
|
+
}
|
|
222
|
+
return ids;
|
|
223
|
+
}
|
|
224
|
+
async function deleteCollectionRaw(collection) {
|
|
225
|
+
await qdrant("DELETE", `/collections/${collection}`);
|
|
226
|
+
}
|
|
227
|
+
async function resolveAliasRaw(alias) {
|
|
228
|
+
try {
|
|
229
|
+
const res = await qdrant("GET", `/aliases`);
|
|
230
|
+
return res.aliases.find((a) => a.alias_name === alias)?.collection_name ?? null;
|
|
231
|
+
} catch {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async function createAliasRaw(alias, collection) {
|
|
236
|
+
await qdrant("POST", `/collections/aliases`, {
|
|
237
|
+
actions: [{ create_alias: { collection_name: collection, alias_name: alias } }]
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
async function setAliasRaw(alias, collection) {
|
|
241
|
+
await qdrant("POST", `/collections/aliases`, {
|
|
242
|
+
actions: [
|
|
243
|
+
{ delete_alias: { alias_name: alias } },
|
|
244
|
+
{ create_alias: { collection_name: collection, alias_name: alias } }
|
|
245
|
+
]
|
|
246
|
+
});
|
|
247
|
+
}
|
|
205
248
|
function noteToPoint(note) {
|
|
206
249
|
return {
|
|
207
250
|
id: note.id,
|
|
@@ -795,8 +838,19 @@ async function migrateCollection(opts) {
|
|
|
795
838
|
);
|
|
796
839
|
if (dryRun) {
|
|
797
840
|
log("[migrate] dry run \u2014 nothing written. Pass dryRun: false to apply.");
|
|
798
|
-
return {
|
|
841
|
+
return {
|
|
842
|
+
total: notes.length,
|
|
843
|
+
missingDerived,
|
|
844
|
+
refreshed: 0,
|
|
845
|
+
migrated: 0,
|
|
846
|
+
skipped: 0,
|
|
847
|
+
sourceDim,
|
|
848
|
+
targetDim,
|
|
849
|
+
model,
|
|
850
|
+
dryRun: true
|
|
851
|
+
};
|
|
799
852
|
}
|
|
853
|
+
let alreadyDone = /* @__PURE__ */ new Set();
|
|
800
854
|
const existingTargetDim = await collectionDimRaw(to);
|
|
801
855
|
if (existingTargetDim === null) {
|
|
802
856
|
await createCollectionRaw(to, targetDim);
|
|
@@ -805,9 +859,17 @@ async function migrateCollection(opts) {
|
|
|
805
859
|
if (existingTargetDim !== targetDim) {
|
|
806
860
|
throw new Error(`migrate: target "${to}" exists at ${existingTargetDim}d but the model produces ${targetDim}d`);
|
|
807
861
|
}
|
|
808
|
-
const
|
|
809
|
-
if (
|
|
810
|
-
|
|
862
|
+
const present = await scrollIdsRaw(to);
|
|
863
|
+
if (present.size > 0) {
|
|
864
|
+
const sourceIds = new Set(notes.map((n) => n.id));
|
|
865
|
+
const foreign = [...present].filter((id) => !sourceIds.has(id));
|
|
866
|
+
if (foreign.length > 0) {
|
|
867
|
+
throw new Error(
|
|
868
|
+
`migrate: target "${to}" holds ${foreign.length} point(s) that are not in "${from}" (e.g. ${foreign[0]}). That is not an interrupted migration \u2014 use a different target.`
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
alreadyDone = present;
|
|
872
|
+
log(`[migrate] resuming: ${present.size} of ${notes.length} already in ${to}`);
|
|
811
873
|
}
|
|
812
874
|
}
|
|
813
875
|
let refreshed = 0;
|
|
@@ -821,6 +883,7 @@ async function migrateCollection(opts) {
|
|
|
821
883
|
buffer = [];
|
|
822
884
|
};
|
|
823
885
|
for (const note of notes) {
|
|
886
|
+
if (alreadyDone.has(note.id)) continue;
|
|
824
887
|
if (refreshFields && missingDerivedFields(note)) {
|
|
825
888
|
try {
|
|
826
889
|
const built = await llmConstructNote(note.content);
|
|
@@ -836,7 +899,7 @@ async function migrateCollection(opts) {
|
|
|
836
899
|
buffer.push(point);
|
|
837
900
|
if (buffer.length >= BATCH) {
|
|
838
901
|
await flush();
|
|
839
|
-
log(`[migrate] ${migrated}/${notes.length}`);
|
|
902
|
+
log(`[migrate] ${alreadyDone.size + migrated}/${notes.length}`);
|
|
840
903
|
}
|
|
841
904
|
}
|
|
842
905
|
await flush();
|
|
@@ -844,32 +907,68 @@ async function migrateCollection(opts) {
|
|
|
844
907
|
if (finalCount !== notes.length) {
|
|
845
908
|
warn(`[migrate] target holds ${finalCount} point(s) but the source had ${notes.length} \u2014 check before switching`);
|
|
846
909
|
}
|
|
847
|
-
log(
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
910
|
+
log(`[migrate] done: ${migrated} written, ${alreadyDone.size} already present, ${refreshed} re-extracted.`);
|
|
911
|
+
return {
|
|
912
|
+
total: notes.length,
|
|
913
|
+
missingDerived,
|
|
914
|
+
refreshed,
|
|
915
|
+
migrated,
|
|
916
|
+
skipped: alreadyDone.size,
|
|
917
|
+
sourceDim,
|
|
918
|
+
targetDim,
|
|
919
|
+
model,
|
|
920
|
+
dryRun: false
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
async function switchToMigrated(opts) {
|
|
924
|
+
const { name, to } = opts;
|
|
925
|
+
const log = opts.logger?.info ?? ((m) => console.log(m));
|
|
926
|
+
if (name === to) throw new Error(`switch: "${name}" and "${to}" are the same collection`);
|
|
927
|
+
const already = await resolveAliasRaw(name);
|
|
928
|
+
if (already === to) {
|
|
929
|
+
log(`[switch] "${name}" already points at "${to}" \u2014 nothing to do`);
|
|
930
|
+
return { name, to, moved: await countPointsRaw(to) };
|
|
931
|
+
}
|
|
932
|
+
const targetCount = await countPointsRaw(to);
|
|
933
|
+
if (targetCount === 0) throw new Error(`switch: "${to}" is empty \u2014 migrate into it first`);
|
|
934
|
+
if (already === null) {
|
|
935
|
+
const sourceCount = await countPointsRaw(name);
|
|
936
|
+
if (targetCount < sourceCount) {
|
|
937
|
+
throw new Error(
|
|
938
|
+
`switch: "${to}" holds ${targetCount} point(s) but "${name}" still holds ${sourceCount}. The migration is not finished \u2014 run it again before switching.`
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
log(`[switch] verified ${targetCount} in "${to}" against ${sourceCount} in "${name}"`);
|
|
942
|
+
await deleteCollectionRaw(name);
|
|
943
|
+
log(`[switch] dropped "${name}"`);
|
|
944
|
+
await createAliasRaw(name, to);
|
|
945
|
+
} else {
|
|
946
|
+
await setAliasRaw(name, to);
|
|
947
|
+
}
|
|
948
|
+
log(`[switch] "${name}" now resolves to "${to}"`);
|
|
949
|
+
return { name, to, moved: targetCount };
|
|
851
950
|
}
|
|
852
951
|
|
|
853
952
|
// src/cli-migrate.ts
|
|
854
|
-
var USAGE = `amem-migrate \u2014
|
|
953
|
+
var USAGE = `amem-migrate \u2014 move a memory store onto a different embedding model
|
|
855
954
|
|
|
856
|
-
|
|
955
|
+
amem-migrate what state the store is in, and what comes next
|
|
956
|
+
amem-migrate --apply do the next step; safe to interrupt and re-run
|
|
957
|
+
amem-migrate --switch put the new store behind the old name (irreversible)
|
|
857
958
|
|
|
858
959
|
Options
|
|
859
|
-
--
|
|
860
|
-
--
|
|
861
|
-
--
|
|
862
|
-
|
|
863
|
-
had them. Faster, and makes no LLM calls at all.
|
|
960
|
+
--from-collection <name> the store to migrate. Defaults to AMEM_COLLECTION.
|
|
961
|
+
--to-collection <name> where to build it. Derived from the source if omitted.
|
|
962
|
+
--no-refresh-fields skip re-extracting keywords for notes that never had
|
|
963
|
+
them. Makes the run completely offline.
|
|
864
964
|
-h, --help
|
|
865
965
|
|
|
866
|
-
|
|
966
|
+
Migrates onto amem's current default unless AMEM_EMBED_MODEL says otherwise:
|
|
867
967
|
|
|
868
|
-
AMEM_EMBED_MODEL=
|
|
869
|
-
npx --package=@amemhq/core amem-migrate --to amem_notes_v2 --apply
|
|
968
|
+
AMEM_EMBED_MODEL=Alibaba-NLP/gte-multilingual-base amem-migrate --apply
|
|
870
969
|
|
|
871
|
-
|
|
872
|
-
|
|
970
|
+
Nothing before --switch touches the original. If a run looks wrong, delete the
|
|
971
|
+
target and start again.`;
|
|
873
972
|
function parseArgs(argv) {
|
|
874
973
|
const value = (flag) => {
|
|
875
974
|
const i = argv.indexOf(flag);
|
|
@@ -877,49 +976,102 @@ function parseArgs(argv) {
|
|
|
877
976
|
};
|
|
878
977
|
return {
|
|
879
978
|
help: argv.includes("-h") || argv.includes("--help"),
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
979
|
+
apply: argv.includes("--apply"),
|
|
980
|
+
switchOver: argv.includes("--switch"),
|
|
981
|
+
from: value("--from-collection"),
|
|
982
|
+
to: value("--to-collection"),
|
|
883
983
|
refreshFields: !argv.includes("--no-refresh-fields")
|
|
884
984
|
};
|
|
885
985
|
}
|
|
986
|
+
function deriveTarget(source) {
|
|
987
|
+
const m = source.match(/^(.*)_v(\d+)$/);
|
|
988
|
+
return m ? `${m[1]}_v${Number(m[2]) + 1}` : `${source}_v2`;
|
|
989
|
+
}
|
|
990
|
+
function carried(args) {
|
|
991
|
+
return (args.from ? ` --from-collection ${args.from}` : "") + (args.to ? ` --to-collection ${args.to}` : "");
|
|
992
|
+
}
|
|
993
|
+
async function detect(from, to) {
|
|
994
|
+
const alias = await resolveAliasRaw(from);
|
|
995
|
+
if (alias !== null) return { kind: "switched", points: await countPointsRaw(from) };
|
|
996
|
+
const sourceDim = await collectionDimRaw(from);
|
|
997
|
+
if (sourceDim === null) return { kind: "no-source" };
|
|
998
|
+
const modelDim = await getEmbeddingDim();
|
|
999
|
+
if (sourceDim === modelDim) return { kind: "already-current", model: getEmbeddingModel() };
|
|
1000
|
+
const notes = await countPointsRaw(from);
|
|
1001
|
+
const targetDim = await collectionDimRaw(to);
|
|
1002
|
+
if (targetDim === null) return { kind: "not-started", notes };
|
|
1003
|
+
const done = (await scrollIdsRaw(to)).size;
|
|
1004
|
+
return done >= notes ? { kind: "ready-to-switch", notes } : { kind: "partial", done, notes };
|
|
1005
|
+
}
|
|
886
1006
|
async function main() {
|
|
887
1007
|
const args = parseArgs(process.argv.slice(2));
|
|
888
1008
|
if (args.help) {
|
|
889
1009
|
console.log(USAGE);
|
|
890
1010
|
return;
|
|
891
1011
|
}
|
|
892
|
-
const to = args.to;
|
|
893
|
-
if (!to) {
|
|
894
|
-
console.error("amem-migrate: --to <collection> is required\n");
|
|
895
|
-
console.error(USAGE);
|
|
896
|
-
process.exitCode = 1;
|
|
897
|
-
return;
|
|
898
|
-
}
|
|
899
1012
|
const from = args.from ?? getCollection();
|
|
900
|
-
const
|
|
1013
|
+
const to = args.to ?? deriveTarget(from);
|
|
1014
|
+
const flags = carried(args);
|
|
1015
|
+
const phase = await detect(from, to);
|
|
1016
|
+
console.log(`store: ${from}`);
|
|
901
1017
|
console.log(`model: ${getEmbeddingModel()}`);
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
1018
|
+
switch (phase.kind) {
|
|
1019
|
+
case "no-source":
|
|
1020
|
+
console.error(`
|
|
1021
|
+
No collection named "${from}". Nothing to migrate.`);
|
|
1022
|
+
process.exitCode = 1;
|
|
1023
|
+
return;
|
|
1024
|
+
case "switched":
|
|
1025
|
+
console.log(`
|
|
1026
|
+
"${from}" is already an alias \u2014 ${phase.points} notes, nothing to do.`);
|
|
1027
|
+
return;
|
|
1028
|
+
case "already-current":
|
|
1029
|
+
console.log(`
|
|
1030
|
+
Already on ${phase.model}. Nothing to migrate.`);
|
|
1031
|
+
return;
|
|
1032
|
+
case "not-started":
|
|
1033
|
+
if (!args.apply) {
|
|
1034
|
+
console.log(`
|
|
1035
|
+
${phase.notes} notes to rebuild into "${to}".`);
|
|
1036
|
+
console.log(`Run "amem-migrate${flags} --apply" to start. "${from}" is only read.`);
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
break;
|
|
1040
|
+
case "partial":
|
|
1041
|
+
if (!args.apply) {
|
|
1042
|
+
console.log(`
|
|
1043
|
+
${phase.done} of ${phase.notes} rebuilt into "${to}".`);
|
|
1044
|
+
console.log(`Run "amem-migrate${flags} --apply" to carry on from there.`);
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
break;
|
|
1048
|
+
case "ready-to-switch":
|
|
1049
|
+
if (!args.switchOver) {
|
|
1050
|
+
console.log(`
|
|
1051
|
+
All ${phase.notes} notes are in "${to}". "${from}" is untouched.`);
|
|
1052
|
+
console.log(`Check it, then run "amem-migrate${flags} --switch" to put "${to}" behind the name "${from}".`);
|
|
1053
|
+
console.log(`That drops "${from}" and cannot be undone.`);
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
await switchToMigrated({ name: from, to });
|
|
1057
|
+
console.log(`
|
|
1058
|
+
Done. Nothing to change in your config \u2014 "${from}" now resolves to "${to}".`);
|
|
1059
|
+
return;
|
|
914
1060
|
}
|
|
915
|
-
if (
|
|
916
|
-
console.
|
|
917
|
-
|
|
1061
|
+
if (args.switchOver) {
|
|
1062
|
+
console.error(`
|
|
1063
|
+
Not finished yet \u2014 run "amem-migrate${flags} --apply" until it is before switching.`);
|
|
1064
|
+
process.exitCode = 1;
|
|
918
1065
|
return;
|
|
919
1066
|
}
|
|
1067
|
+
const result = await migrateCollection({ from, to, dryRun: false, refreshFields: args.refreshFields });
|
|
920
1068
|
console.log(`
|
|
921
|
-
${result.migrated
|
|
922
|
-
|
|
1069
|
+
${result.migrated + result.skipped} of ${result.total} rebuilt.`);
|
|
1070
|
+
if (result.migrated + result.skipped >= result.total) {
|
|
1071
|
+
console.log(`Check "${to}", then run "amem-migrate${flags} --switch".`);
|
|
1072
|
+
} else {
|
|
1073
|
+
console.log(`Run "amem-migrate${flags} --apply" again to carry on.`);
|
|
1074
|
+
}
|
|
923
1075
|
}
|
|
924
1076
|
main().catch((err) => {
|
|
925
1077
|
console.error(`amem-migrate: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -927,6 +1079,8 @@ main().catch((err) => {
|
|
|
927
1079
|
});
|
|
928
1080
|
// Annotate the CommonJS export names for ESM import in node:
|
|
929
1081
|
0 && (module.exports = {
|
|
1082
|
+
carried,
|
|
1083
|
+
deriveTarget,
|
|
930
1084
|
parseArgs
|
|
931
1085
|
});
|
|
932
1086
|
//# sourceMappingURL=cli-migrate.cjs.map
|