@smyslenny/agent-memory 2.1.0 → 2.2.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/CHANGELOG.md +20 -0
- package/README.md +40 -0
- package/dist/bin/agent-memory.js +86 -37
- package/dist/bin/agent-memory.js.map +1 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.js +146 -28
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +147 -29
- package/dist/mcp/server.js.map +1 -1
- package/docs/design/0004-agent-memory-integration.md +316 -0
- package/docs/design/0005-reranker-api-integration.md +276 -0
- package/docs/design/0006-multi-provider-embedding.md +196 -0
- package/docs/roadmap/integration-plan-v1.md +139 -0
- package/docs/roadmap/memory-architecture.md +168 -0
- package/docs/roadmap/warm-boot.md +135 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -197,6 +197,27 @@ declare function getStrategy(intent: SearchIntent): {
|
|
|
197
197
|
limit: number;
|
|
198
198
|
};
|
|
199
199
|
|
|
200
|
+
interface RerankResult {
|
|
201
|
+
index: number;
|
|
202
|
+
relevance_score: number;
|
|
203
|
+
}
|
|
204
|
+
interface RerankProvider {
|
|
205
|
+
id: string;
|
|
206
|
+
model: string;
|
|
207
|
+
rerank(query: string, documents: string[]): Promise<RerankResult[]>;
|
|
208
|
+
}
|
|
209
|
+
declare function getRerankerProviderFromEnv(): RerankProvider | null;
|
|
210
|
+
declare function createOpenAIRerankProvider(opts: {
|
|
211
|
+
apiKey: string;
|
|
212
|
+
model: string;
|
|
213
|
+
baseUrl?: string;
|
|
214
|
+
}): RerankProvider;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Optionally rerank results using an external semantic reranker provider.
|
|
218
|
+
* Best-effort: on failure, returns original results unchanged.
|
|
219
|
+
*/
|
|
220
|
+
declare function rerankWithProvider(results: SearchResult[], query: string, provider: RerankProvider): Promise<SearchResult[]>;
|
|
200
221
|
/**
|
|
201
222
|
* Rerank search results based on intent strategy and priority weighting.
|
|
202
223
|
*/
|
|
@@ -211,18 +232,24 @@ interface EmbeddingProvider {
|
|
|
211
232
|
id: string;
|
|
212
233
|
model: string;
|
|
213
234
|
dimension?: number;
|
|
235
|
+
instructionPrefix?: string | null;
|
|
214
236
|
embed(text: string): Promise<number[]>;
|
|
237
|
+
embedQuery?(query: string): Promise<number[]>;
|
|
215
238
|
}
|
|
239
|
+
declare function getDefaultInstruction(model: string): string | null;
|
|
216
240
|
declare function getEmbeddingProviderFromEnv(): EmbeddingProvider | null;
|
|
217
241
|
declare function createOpenAIProvider(opts: {
|
|
242
|
+
id?: string;
|
|
218
243
|
apiKey: string;
|
|
219
244
|
model: string;
|
|
220
245
|
baseUrl?: string;
|
|
246
|
+
instruction?: string | null;
|
|
221
247
|
}): EmbeddingProvider;
|
|
222
248
|
declare function createDashScopeProvider(opts: {
|
|
223
249
|
apiKey: string;
|
|
224
250
|
model: string;
|
|
225
251
|
baseUrl?: string;
|
|
252
|
+
instruction?: string | null;
|
|
226
253
|
}): EmbeddingProvider;
|
|
227
254
|
|
|
228
255
|
interface HybridSearchOptions {
|
|
@@ -375,4 +402,4 @@ declare function boot(db: Database.Database, opts?: {
|
|
|
375
402
|
corePaths?: string[];
|
|
376
403
|
}): BootResult;
|
|
377
404
|
|
|
378
|
-
export { type BootResult, type CreateMemoryInput, type EmbeddingProvider, type ExportResult, type GovernResult, type GuardAction, type GuardResult, type HybridSearchOptions, type IntentResult, type Link, type Memory, type MemoryType, type Path, type Priority, type RelationType, type SearchIntent, type SearchResult, type Snapshot, type SnapshotAction, type StoredEmbedding, type SyncInput, type SyncResult, type TidyResult, type UpdateMemoryInput, boot, calculateVitality, classifyIntent, contentHash, countMemories, createDashScopeProvider, createLink, createMemory, createOpenAIProvider, createPath, createSnapshot, decodeEmbedding, deleteLink, deleteMemory, deletePath, embedMemory, embedMissingForAgent, encodeEmbedding, exportMemories, getDecayedMemories, getEmbedding, getEmbeddingProviderFromEnv, getLinks, getMemory, getOutgoingLinks, getPath, getPathByUri, getPathsByDomain, getPathsByMemory, getPathsByPrefix, getSnapshot, getSnapshots, getStrategy, guard, listEmbeddings, listMemories, parseUri, recordAccess, rerank, rollback, runDecay, runGovern, runTidy, searchBM25, searchHybrid, syncBatch, syncOne, tokenize, traverse, updateMemory, upsertEmbedding };
|
|
405
|
+
export { type BootResult, type CreateMemoryInput, type EmbeddingProvider, type ExportResult, type GovernResult, type GuardAction, type GuardResult, type HybridSearchOptions, type IntentResult, type Link, type Memory, type MemoryType, type Path, type Priority, type RelationType, type RerankProvider, type RerankResult, type SearchIntent, type SearchResult, type Snapshot, type SnapshotAction, type StoredEmbedding, type SyncInput, type SyncResult, type TidyResult, type UpdateMemoryInput, boot, calculateVitality, classifyIntent, contentHash, countMemories, createDashScopeProvider, createLink, createMemory, createOpenAIProvider, createOpenAIRerankProvider, createPath, createSnapshot, decodeEmbedding, deleteLink, deleteMemory, deletePath, embedMemory, embedMissingForAgent, encodeEmbedding, exportMemories, getDecayedMemories, getDefaultInstruction, getEmbedding, getEmbeddingProviderFromEnv, getLinks, getMemory, getOutgoingLinks, getPath, getPathByUri, getPathsByDomain, getPathsByMemory, getPathsByPrefix, getRerankerProviderFromEnv, getSnapshot, getSnapshots, getStrategy, guard, listEmbeddings, listMemories, parseUri, recordAccess, rerank, rerankWithProvider, rollback, runDecay, runGovern, runTidy, searchBM25, searchHybrid, syncBatch, syncOne, tokenize, traverse, updateMemory, upsertEmbedding };
|
package/dist/index.js
CHANGED
|
@@ -997,6 +997,26 @@ function getStrategy(intent) {
|
|
|
997
997
|
}
|
|
998
998
|
|
|
999
999
|
// src/search/rerank.ts
|
|
1000
|
+
async function rerankWithProvider(results, query, provider) {
|
|
1001
|
+
if (results.length === 0) return results;
|
|
1002
|
+
const documents = results.map((r) => r.memory.content);
|
|
1003
|
+
try {
|
|
1004
|
+
const apiResults = await provider.rerank(query, documents);
|
|
1005
|
+
const scoreMap = new Map(apiResults.map((r) => [r.index, r.relevance_score]));
|
|
1006
|
+
return results.map((r, i) => {
|
|
1007
|
+
const score = scoreMap.get(i);
|
|
1008
|
+
if (score === void 0) return r;
|
|
1009
|
+
return {
|
|
1010
|
+
...r,
|
|
1011
|
+
score,
|
|
1012
|
+
matchReason: `${r.matchReason}+rerank`
|
|
1013
|
+
};
|
|
1014
|
+
});
|
|
1015
|
+
} catch (err) {
|
|
1016
|
+
console.warn("[agent-memory] External rerank failed, falling back:", err);
|
|
1017
|
+
return results;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1000
1020
|
function rerank(results, opts) {
|
|
1001
1021
|
const now2 = Date.now();
|
|
1002
1022
|
const scored = results.map((r) => {
|
|
@@ -1112,7 +1132,8 @@ async function searchHybrid(db, query, opts) {
|
|
|
1112
1132
|
if (!provider || !model) {
|
|
1113
1133
|
return bm25.slice(0, limit);
|
|
1114
1134
|
}
|
|
1115
|
-
const
|
|
1135
|
+
const embedFn = provider.embedQuery ?? provider.embed;
|
|
1136
|
+
const qVec = Float32Array.from(await embedFn.call(provider, query));
|
|
1116
1137
|
const embeddings = listEmbeddings(db, agentId, model);
|
|
1117
1138
|
const scored = [];
|
|
1118
1139
|
for (const e of embeddings) {
|
|
@@ -1145,6 +1166,29 @@ async function searchHybrid(db, query, opts) {
|
|
|
1145
1166
|
}
|
|
1146
1167
|
|
|
1147
1168
|
// src/search/providers.ts
|
|
1169
|
+
var QWEN_DEFAULT_INSTRUCTION = "Given a query, retrieve the most semantically relevant document";
|
|
1170
|
+
function getDefaultInstruction(model) {
|
|
1171
|
+
const m = model.toLowerCase();
|
|
1172
|
+
if (m.includes("qwen")) return QWEN_DEFAULT_INSTRUCTION;
|
|
1173
|
+
if (m.includes("gemini")) return null;
|
|
1174
|
+
return null;
|
|
1175
|
+
}
|
|
1176
|
+
function resolveInstruction(model) {
|
|
1177
|
+
const override = process.env.AGENT_MEMORY_EMBEDDINGS_INSTRUCTION;
|
|
1178
|
+
if (override !== void 0) {
|
|
1179
|
+
const normalized = override.trim();
|
|
1180
|
+
if (!normalized) return null;
|
|
1181
|
+
const lowered = normalized.toLowerCase();
|
|
1182
|
+
if (lowered === "none" || lowered === "off" || lowered === "false" || lowered === "null") return null;
|
|
1183
|
+
return normalized;
|
|
1184
|
+
}
|
|
1185
|
+
return getDefaultInstruction(model);
|
|
1186
|
+
}
|
|
1187
|
+
function buildQueryInput(query, instructionPrefix) {
|
|
1188
|
+
if (!instructionPrefix) return query;
|
|
1189
|
+
return `Instruct: ${instructionPrefix}
|
|
1190
|
+
Query: ${query}`;
|
|
1191
|
+
}
|
|
1148
1192
|
function getEmbeddingProviderFromEnv() {
|
|
1149
1193
|
const provider = (process.env.AGENT_MEMORY_EMBEDDINGS_PROVIDER ?? "none").toLowerCase();
|
|
1150
1194
|
if (provider === "none" || provider === "off" || provider === "false") return null;
|
|
@@ -1153,14 +1197,24 @@ function getEmbeddingProviderFromEnv() {
|
|
|
1153
1197
|
const model = process.env.AGENT_MEMORY_EMBEDDINGS_MODEL ?? "text-embedding-3-small";
|
|
1154
1198
|
const baseUrl = process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1";
|
|
1155
1199
|
if (!apiKey) return null;
|
|
1156
|
-
|
|
1200
|
+
const instruction = resolveInstruction(model);
|
|
1201
|
+
return createOpenAIProvider({ apiKey, model, baseUrl, instruction });
|
|
1202
|
+
}
|
|
1203
|
+
if (provider === "gemini" || provider === "google") {
|
|
1204
|
+
const apiKey = process.env.GEMINI_API_KEY ?? process.env.OPENAI_API_KEY;
|
|
1205
|
+
const model = process.env.AGENT_MEMORY_EMBEDDINGS_MODEL ?? "gemini-embedding-001";
|
|
1206
|
+
const baseUrl = process.env.GEMINI_BASE_URL ?? process.env.OPENAI_BASE_URL ?? "https://generativelanguage.googleapis.com/v1beta";
|
|
1207
|
+
if (!apiKey) return null;
|
|
1208
|
+
const instruction = resolveInstruction(model);
|
|
1209
|
+
return createOpenAIProvider({ id: "gemini", apiKey, model, baseUrl, instruction });
|
|
1157
1210
|
}
|
|
1158
1211
|
if (provider === "qwen" || provider === "dashscope" || provider === "tongyi") {
|
|
1159
1212
|
const apiKey = process.env.DASHSCOPE_API_KEY;
|
|
1160
1213
|
const model = process.env.AGENT_MEMORY_EMBEDDINGS_MODEL ?? "text-embedding-v3";
|
|
1161
1214
|
const baseUrl = process.env.DASHSCOPE_BASE_URL ?? "https://dashscope.aliyuncs.com";
|
|
1162
1215
|
if (!apiKey) return null;
|
|
1163
|
-
|
|
1216
|
+
const instruction = resolveInstruction(model);
|
|
1217
|
+
return createDashScopeProvider({ apiKey, model, baseUrl, instruction });
|
|
1164
1218
|
}
|
|
1165
1219
|
return null;
|
|
1166
1220
|
}
|
|
@@ -1177,52 +1231,112 @@ function normalizeEmbedding(e) {
|
|
|
1177
1231
|
}
|
|
1178
1232
|
function createOpenAIProvider(opts) {
|
|
1179
1233
|
const baseUrl = opts.baseUrl ?? "https://api.openai.com/v1";
|
|
1234
|
+
const instructionPrefix = opts.instruction ?? null;
|
|
1235
|
+
async function requestEmbedding(input) {
|
|
1236
|
+
const resp = await fetch(`${baseUrl.replace(/\/$/, "")}/embeddings`, {
|
|
1237
|
+
method: "POST",
|
|
1238
|
+
headers: {
|
|
1239
|
+
"content-type": "application/json",
|
|
1240
|
+
authorization: authHeader(opts.apiKey)
|
|
1241
|
+
},
|
|
1242
|
+
body: JSON.stringify({ model: opts.model, input })
|
|
1243
|
+
});
|
|
1244
|
+
if (!resp.ok) {
|
|
1245
|
+
const body = await resp.text().catch(() => "");
|
|
1246
|
+
throw new Error(`OpenAI embeddings failed: ${resp.status} ${resp.statusText} ${body}`.trim());
|
|
1247
|
+
}
|
|
1248
|
+
const data = await resp.json();
|
|
1249
|
+
return normalizeEmbedding(data.data?.[0]?.embedding);
|
|
1250
|
+
}
|
|
1180
1251
|
return {
|
|
1181
|
-
id: "openai",
|
|
1252
|
+
id: opts.id ?? "openai",
|
|
1182
1253
|
model: opts.model,
|
|
1254
|
+
instructionPrefix,
|
|
1183
1255
|
async embed(text) {
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
authorization: authHeader(opts.apiKey)
|
|
1189
|
-
},
|
|
1190
|
-
body: JSON.stringify({ model: opts.model, input: text })
|
|
1191
|
-
});
|
|
1192
|
-
if (!resp.ok) {
|
|
1193
|
-
const body = await resp.text().catch(() => "");
|
|
1194
|
-
throw new Error(`OpenAI embeddings failed: ${resp.status} ${resp.statusText} ${body}`.trim());
|
|
1195
|
-
}
|
|
1196
|
-
const data = await resp.json();
|
|
1197
|
-
const emb = data.data?.[0]?.embedding;
|
|
1198
|
-
return normalizeEmbedding(emb);
|
|
1256
|
+
return requestEmbedding(text);
|
|
1257
|
+
},
|
|
1258
|
+
async embedQuery(query) {
|
|
1259
|
+
return requestEmbedding(buildQueryInput(query, instructionPrefix));
|
|
1199
1260
|
}
|
|
1200
1261
|
};
|
|
1201
1262
|
}
|
|
1202
1263
|
function createDashScopeProvider(opts) {
|
|
1203
1264
|
const baseUrl = opts.baseUrl ?? "https://dashscope.aliyuncs.com";
|
|
1265
|
+
const instructionPrefix = opts.instruction ?? null;
|
|
1266
|
+
async function requestEmbedding(text) {
|
|
1267
|
+
const resp = await fetch(`${baseUrl.replace(/\/$/, "")}/api/v1/services/embeddings/text-embedding/text-embedding`, {
|
|
1268
|
+
method: "POST",
|
|
1269
|
+
headers: {
|
|
1270
|
+
"content-type": "application/json",
|
|
1271
|
+
authorization: authHeader(opts.apiKey)
|
|
1272
|
+
},
|
|
1273
|
+
body: JSON.stringify({
|
|
1274
|
+
model: opts.model,
|
|
1275
|
+
input: { texts: [text] }
|
|
1276
|
+
})
|
|
1277
|
+
});
|
|
1278
|
+
if (!resp.ok) {
|
|
1279
|
+
const body = await resp.text().catch(() => "");
|
|
1280
|
+
throw new Error(`DashScope embeddings failed: ${resp.status} ${resp.statusText} ${body}`.trim());
|
|
1281
|
+
}
|
|
1282
|
+
const data = await resp.json();
|
|
1283
|
+
const emb = data.output?.embeddings?.[0]?.embedding ?? data.output?.embeddings?.[0]?.vector ?? data.output?.embedding ?? data.data?.[0]?.embedding;
|
|
1284
|
+
return normalizeEmbedding(emb);
|
|
1285
|
+
}
|
|
1204
1286
|
return {
|
|
1205
1287
|
id: "dashscope",
|
|
1206
1288
|
model: opts.model,
|
|
1289
|
+
instructionPrefix,
|
|
1207
1290
|
async embed(text) {
|
|
1208
|
-
|
|
1291
|
+
return requestEmbedding(text);
|
|
1292
|
+
},
|
|
1293
|
+
async embedQuery(query) {
|
|
1294
|
+
return requestEmbedding(buildQueryInput(query, instructionPrefix));
|
|
1295
|
+
}
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
// src/search/rerank-provider.ts
|
|
1300
|
+
function authHeader2(apiKey) {
|
|
1301
|
+
return apiKey.startsWith("Bearer ") ? apiKey : `Bearer ${apiKey}`;
|
|
1302
|
+
}
|
|
1303
|
+
function getRerankerProviderFromEnv() {
|
|
1304
|
+
const provider = (process.env.AGENT_MEMORY_RERANK_PROVIDER ?? "none").toLowerCase();
|
|
1305
|
+
if (provider === "none" || provider === "off") return null;
|
|
1306
|
+
if (provider === "openai" || provider === "jina" || provider === "cohere") {
|
|
1307
|
+
const apiKey = process.env.AGENT_MEMORY_RERANK_API_KEY ?? process.env.OPENAI_API_KEY;
|
|
1308
|
+
const model = process.env.AGENT_MEMORY_RERANK_MODEL ?? "Qwen/Qwen3-Reranker-8B";
|
|
1309
|
+
const baseUrl = process.env.AGENT_MEMORY_RERANK_BASE_URL ?? process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1";
|
|
1310
|
+
if (!apiKey) return null;
|
|
1311
|
+
return createOpenAIRerankProvider({ apiKey, model, baseUrl });
|
|
1312
|
+
}
|
|
1313
|
+
return null;
|
|
1314
|
+
}
|
|
1315
|
+
function createOpenAIRerankProvider(opts) {
|
|
1316
|
+
const baseUrl = opts.baseUrl ?? "https://api.openai.com/v1";
|
|
1317
|
+
return {
|
|
1318
|
+
id: "openai-rerank",
|
|
1319
|
+
model: opts.model,
|
|
1320
|
+
async rerank(query, documents) {
|
|
1321
|
+
const resp = await fetch(`${baseUrl.replace(/\/$/, "")}/rerank`, {
|
|
1209
1322
|
method: "POST",
|
|
1210
1323
|
headers: {
|
|
1211
1324
|
"content-type": "application/json",
|
|
1212
|
-
authorization:
|
|
1325
|
+
authorization: authHeader2(opts.apiKey)
|
|
1213
1326
|
},
|
|
1214
|
-
body: JSON.stringify({
|
|
1215
|
-
model: opts.model,
|
|
1216
|
-
input: { texts: [text] }
|
|
1217
|
-
})
|
|
1327
|
+
body: JSON.stringify({ model: opts.model, query, documents })
|
|
1218
1328
|
});
|
|
1219
1329
|
if (!resp.ok) {
|
|
1220
1330
|
const body = await resp.text().catch(() => "");
|
|
1221
|
-
throw new Error(`
|
|
1331
|
+
throw new Error(`Rerank API failed: ${resp.status} ${resp.statusText} ${body}`.trim());
|
|
1222
1332
|
}
|
|
1223
1333
|
const data = await resp.json();
|
|
1224
|
-
const
|
|
1225
|
-
return
|
|
1334
|
+
const results = data.results ?? [];
|
|
1335
|
+
return results.map((r) => {
|
|
1336
|
+
const index = typeof r.index === "number" ? r.index : Number.NaN;
|
|
1337
|
+
const relevance = typeof r.relevance_score === "number" ? r.relevance_score : Number.NaN;
|
|
1338
|
+
return { index, relevance_score: relevance };
|
|
1339
|
+
}).filter((r) => Number.isInteger(r.index) && Number.isFinite(r.relevance_score));
|
|
1226
1340
|
}
|
|
1227
1341
|
};
|
|
1228
1342
|
}
|
|
@@ -1521,6 +1635,7 @@ export {
|
|
|
1521
1635
|
createLink,
|
|
1522
1636
|
createMemory,
|
|
1523
1637
|
createOpenAIProvider,
|
|
1638
|
+
createOpenAIRerankProvider,
|
|
1524
1639
|
createPath,
|
|
1525
1640
|
createSnapshot,
|
|
1526
1641
|
decodeEmbedding,
|
|
@@ -1532,6 +1647,7 @@ export {
|
|
|
1532
1647
|
encodeEmbedding,
|
|
1533
1648
|
exportMemories,
|
|
1534
1649
|
getDecayedMemories,
|
|
1650
|
+
getDefaultInstruction,
|
|
1535
1651
|
getEmbedding,
|
|
1536
1652
|
getEmbeddingProviderFromEnv,
|
|
1537
1653
|
getLinks,
|
|
@@ -1542,6 +1658,7 @@ export {
|
|
|
1542
1658
|
getPathsByDomain,
|
|
1543
1659
|
getPathsByMemory,
|
|
1544
1660
|
getPathsByPrefix,
|
|
1661
|
+
getRerankerProviderFromEnv,
|
|
1545
1662
|
getSnapshot,
|
|
1546
1663
|
getSnapshots,
|
|
1547
1664
|
getStrategy,
|
|
@@ -1553,6 +1670,7 @@ export {
|
|
|
1553
1670
|
parseUri,
|
|
1554
1671
|
recordAccess,
|
|
1555
1672
|
rerank,
|
|
1673
|
+
rerankWithProvider,
|
|
1556
1674
|
rollback,
|
|
1557
1675
|
runDecay,
|
|
1558
1676
|
runGovern,
|