@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/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 qVec = Float32Array.from(await provider.embed(query));
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
- return createOpenAIProvider({ apiKey, model, baseUrl });
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
- return createDashScopeProvider({ apiKey, model, baseUrl });
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
- const resp = await fetch(`${baseUrl.replace(/\/$/, "")}/embeddings`, {
1185
- method: "POST",
1186
- headers: {
1187
- "content-type": "application/json",
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
- const resp = await fetch(`${baseUrl.replace(/\/$/, "")}/api/v1/services/embeddings/text-embedding/text-embedding`, {
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: authHeader(opts.apiKey)
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(`DashScope embeddings failed: ${resp.status} ${resp.statusText} ${body}`.trim());
1331
+ throw new Error(`Rerank API failed: ${resp.status} ${resp.statusText} ${body}`.trim());
1222
1332
  }
1223
1333
  const data = await resp.json();
1224
- const emb = data?.output?.embeddings?.[0]?.embedding ?? data?.output?.embeddings?.[0]?.vector ?? data?.output?.embedding ?? data?.data?.[0]?.embedding;
1225
- return normalizeEmbedding(emb);
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,