@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.
@@ -14580,6 +14580,26 @@ function getStrategy(intent) {
14580
14580
  }
14581
14581
 
14582
14582
  // src/search/rerank.ts
14583
+ async function rerankWithProvider(results, query, provider) {
14584
+ if (results.length === 0) return results;
14585
+ const documents = results.map((r) => r.memory.content);
14586
+ try {
14587
+ const apiResults = await provider.rerank(query, documents);
14588
+ const scoreMap = new Map(apiResults.map((r) => [r.index, r.relevance_score]));
14589
+ return results.map((r, i) => {
14590
+ const score = scoreMap.get(i);
14591
+ if (score === void 0) return r;
14592
+ return {
14593
+ ...r,
14594
+ score,
14595
+ matchReason: `${r.matchReason}+rerank`
14596
+ };
14597
+ });
14598
+ } catch (err) {
14599
+ console.warn("[agent-memory] External rerank failed, falling back:", err);
14600
+ return results;
14601
+ }
14602
+ }
14583
14603
  function rerank(results, opts) {
14584
14604
  const now2 = Date.now();
14585
14605
  const scored = results.map((r) => {
@@ -14741,7 +14761,8 @@ async function searchHybrid(db, query, opts) {
14741
14761
  if (!provider || !model) {
14742
14762
  return bm25.slice(0, limit);
14743
14763
  }
14744
- const qVec = Float32Array.from(await provider.embed(query));
14764
+ const embedFn = provider.embedQuery ?? provider.embed;
14765
+ const qVec = Float32Array.from(await embedFn.call(provider, query));
14745
14766
  const embeddings = listEmbeddings(db, agentId, model);
14746
14767
  const scored = [];
14747
14768
  for (const e of embeddings) {
@@ -14774,6 +14795,29 @@ async function searchHybrid(db, query, opts) {
14774
14795
  }
14775
14796
 
14776
14797
  // src/search/providers.ts
14798
+ var QWEN_DEFAULT_INSTRUCTION = "Given a query, retrieve the most semantically relevant document";
14799
+ function getDefaultInstruction(model) {
14800
+ const m = model.toLowerCase();
14801
+ if (m.includes("qwen")) return QWEN_DEFAULT_INSTRUCTION;
14802
+ if (m.includes("gemini")) return null;
14803
+ return null;
14804
+ }
14805
+ function resolveInstruction(model) {
14806
+ const override = process.env.AGENT_MEMORY_EMBEDDINGS_INSTRUCTION;
14807
+ if (override !== void 0) {
14808
+ const normalized = override.trim();
14809
+ if (!normalized) return null;
14810
+ const lowered = normalized.toLowerCase();
14811
+ if (lowered === "none" || lowered === "off" || lowered === "false" || lowered === "null") return null;
14812
+ return normalized;
14813
+ }
14814
+ return getDefaultInstruction(model);
14815
+ }
14816
+ function buildQueryInput(query, instructionPrefix) {
14817
+ if (!instructionPrefix) return query;
14818
+ return `Instruct: ${instructionPrefix}
14819
+ Query: ${query}`;
14820
+ }
14777
14821
  function getEmbeddingProviderFromEnv() {
14778
14822
  const provider = (process.env.AGENT_MEMORY_EMBEDDINGS_PROVIDER ?? "none").toLowerCase();
14779
14823
  if (provider === "none" || provider === "off" || provider === "false") return null;
@@ -14782,14 +14826,24 @@ function getEmbeddingProviderFromEnv() {
14782
14826
  const model = process.env.AGENT_MEMORY_EMBEDDINGS_MODEL ?? "text-embedding-3-small";
14783
14827
  const baseUrl = process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1";
14784
14828
  if (!apiKey) return null;
14785
- return createOpenAIProvider({ apiKey, model, baseUrl });
14829
+ const instruction = resolveInstruction(model);
14830
+ return createOpenAIProvider({ apiKey, model, baseUrl, instruction });
14831
+ }
14832
+ if (provider === "gemini" || provider === "google") {
14833
+ const apiKey = process.env.GEMINI_API_KEY ?? process.env.OPENAI_API_KEY;
14834
+ const model = process.env.AGENT_MEMORY_EMBEDDINGS_MODEL ?? "gemini-embedding-001";
14835
+ const baseUrl = process.env.GEMINI_BASE_URL ?? process.env.OPENAI_BASE_URL ?? "https://generativelanguage.googleapis.com/v1beta";
14836
+ if (!apiKey) return null;
14837
+ const instruction = resolveInstruction(model);
14838
+ return createOpenAIProvider({ id: "gemini", apiKey, model, baseUrl, instruction });
14786
14839
  }
14787
14840
  if (provider === "qwen" || provider === "dashscope" || provider === "tongyi") {
14788
14841
  const apiKey = process.env.DASHSCOPE_API_KEY;
14789
14842
  const model = process.env.AGENT_MEMORY_EMBEDDINGS_MODEL ?? "text-embedding-v3";
14790
14843
  const baseUrl = process.env.DASHSCOPE_BASE_URL ?? "https://dashscope.aliyuncs.com";
14791
14844
  if (!apiKey) return null;
14792
- return createDashScopeProvider({ apiKey, model, baseUrl });
14845
+ const instruction = resolveInstruction(model);
14846
+ return createDashScopeProvider({ apiKey, model, baseUrl, instruction });
14793
14847
  }
14794
14848
  return null;
14795
14849
  }
@@ -14806,52 +14860,112 @@ function normalizeEmbedding(e) {
14806
14860
  }
14807
14861
  function createOpenAIProvider(opts) {
14808
14862
  const baseUrl = opts.baseUrl ?? "https://api.openai.com/v1";
14863
+ const instructionPrefix = opts.instruction ?? null;
14864
+ async function requestEmbedding(input) {
14865
+ const resp = await fetch(`${baseUrl.replace(/\/$/, "")}/embeddings`, {
14866
+ method: "POST",
14867
+ headers: {
14868
+ "content-type": "application/json",
14869
+ authorization: authHeader(opts.apiKey)
14870
+ },
14871
+ body: JSON.stringify({ model: opts.model, input })
14872
+ });
14873
+ if (!resp.ok) {
14874
+ const body = await resp.text().catch(() => "");
14875
+ throw new Error(`OpenAI embeddings failed: ${resp.status} ${resp.statusText} ${body}`.trim());
14876
+ }
14877
+ const data = await resp.json();
14878
+ return normalizeEmbedding(data.data?.[0]?.embedding);
14879
+ }
14809
14880
  return {
14810
- id: "openai",
14881
+ id: opts.id ?? "openai",
14811
14882
  model: opts.model,
14883
+ instructionPrefix,
14812
14884
  async embed(text) {
14813
- const resp = await fetch(`${baseUrl.replace(/\/$/, "")}/embeddings`, {
14814
- method: "POST",
14815
- headers: {
14816
- "content-type": "application/json",
14817
- authorization: authHeader(opts.apiKey)
14818
- },
14819
- body: JSON.stringify({ model: opts.model, input: text })
14820
- });
14821
- if (!resp.ok) {
14822
- const body = await resp.text().catch(() => "");
14823
- throw new Error(`OpenAI embeddings failed: ${resp.status} ${resp.statusText} ${body}`.trim());
14824
- }
14825
- const data = await resp.json();
14826
- const emb = data.data?.[0]?.embedding;
14827
- return normalizeEmbedding(emb);
14885
+ return requestEmbedding(text);
14886
+ },
14887
+ async embedQuery(query) {
14888
+ return requestEmbedding(buildQueryInput(query, instructionPrefix));
14828
14889
  }
14829
14890
  };
14830
14891
  }
14831
14892
  function createDashScopeProvider(opts) {
14832
14893
  const baseUrl = opts.baseUrl ?? "https://dashscope.aliyuncs.com";
14894
+ const instructionPrefix = opts.instruction ?? null;
14895
+ async function requestEmbedding(text) {
14896
+ const resp = await fetch(`${baseUrl.replace(/\/$/, "")}/api/v1/services/embeddings/text-embedding/text-embedding`, {
14897
+ method: "POST",
14898
+ headers: {
14899
+ "content-type": "application/json",
14900
+ authorization: authHeader(opts.apiKey)
14901
+ },
14902
+ body: JSON.stringify({
14903
+ model: opts.model,
14904
+ input: { texts: [text] }
14905
+ })
14906
+ });
14907
+ if (!resp.ok) {
14908
+ const body = await resp.text().catch(() => "");
14909
+ throw new Error(`DashScope embeddings failed: ${resp.status} ${resp.statusText} ${body}`.trim());
14910
+ }
14911
+ const data = await resp.json();
14912
+ const emb = data.output?.embeddings?.[0]?.embedding ?? data.output?.embeddings?.[0]?.vector ?? data.output?.embedding ?? data.data?.[0]?.embedding;
14913
+ return normalizeEmbedding(emb);
14914
+ }
14833
14915
  return {
14834
14916
  id: "dashscope",
14835
14917
  model: opts.model,
14918
+ instructionPrefix,
14836
14919
  async embed(text) {
14837
- const resp = await fetch(`${baseUrl.replace(/\/$/, "")}/api/v1/services/embeddings/text-embedding/text-embedding`, {
14920
+ return requestEmbedding(text);
14921
+ },
14922
+ async embedQuery(query) {
14923
+ return requestEmbedding(buildQueryInput(query, instructionPrefix));
14924
+ }
14925
+ };
14926
+ }
14927
+
14928
+ // src/search/rerank-provider.ts
14929
+ function authHeader2(apiKey) {
14930
+ return apiKey.startsWith("Bearer ") ? apiKey : `Bearer ${apiKey}`;
14931
+ }
14932
+ function getRerankerProviderFromEnv() {
14933
+ const provider = (process.env.AGENT_MEMORY_RERANK_PROVIDER ?? "none").toLowerCase();
14934
+ if (provider === "none" || provider === "off") return null;
14935
+ if (provider === "openai" || provider === "jina" || provider === "cohere") {
14936
+ const apiKey = process.env.AGENT_MEMORY_RERANK_API_KEY ?? process.env.OPENAI_API_KEY;
14937
+ const model = process.env.AGENT_MEMORY_RERANK_MODEL ?? "Qwen/Qwen3-Reranker-8B";
14938
+ const baseUrl = process.env.AGENT_MEMORY_RERANK_BASE_URL ?? process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1";
14939
+ if (!apiKey) return null;
14940
+ return createOpenAIRerankProvider({ apiKey, model, baseUrl });
14941
+ }
14942
+ return null;
14943
+ }
14944
+ function createOpenAIRerankProvider(opts) {
14945
+ const baseUrl = opts.baseUrl ?? "https://api.openai.com/v1";
14946
+ return {
14947
+ id: "openai-rerank",
14948
+ model: opts.model,
14949
+ async rerank(query, documents) {
14950
+ const resp = await fetch(`${baseUrl.replace(/\/$/, "")}/rerank`, {
14838
14951
  method: "POST",
14839
14952
  headers: {
14840
14953
  "content-type": "application/json",
14841
- authorization: authHeader(opts.apiKey)
14954
+ authorization: authHeader2(opts.apiKey)
14842
14955
  },
14843
- body: JSON.stringify({
14844
- model: opts.model,
14845
- input: { texts: [text] }
14846
- })
14956
+ body: JSON.stringify({ model: opts.model, query, documents })
14847
14957
  });
14848
14958
  if (!resp.ok) {
14849
14959
  const body = await resp.text().catch(() => "");
14850
- throw new Error(`DashScope embeddings failed: ${resp.status} ${resp.statusText} ${body}`.trim());
14960
+ throw new Error(`Rerank API failed: ${resp.status} ${resp.statusText} ${body}`.trim());
14851
14961
  }
14852
14962
  const data = await resp.json();
14853
- const emb = data?.output?.embeddings?.[0]?.embedding ?? data?.output?.embeddings?.[0]?.vector ?? data?.output?.embedding ?? data?.data?.[0]?.embedding;
14854
- return normalizeEmbedding(emb);
14963
+ const results = data.results ?? [];
14964
+ return results.map((r) => {
14965
+ const index = typeof r.index === "number" ? r.index : Number.NaN;
14966
+ const relevance = typeof r.relevance_score === "number" ? r.relevance_score : Number.NaN;
14967
+ return { index, relevance_score: relevance };
14968
+ }).filter((r) => Number.isInteger(r.index) && Number.isFinite(r.relevance_score));
14855
14969
  }
14856
14970
  };
14857
14971
  }
@@ -15226,6 +15340,7 @@ function createMcpServer(dbPath, agentId) {
15226
15340
  const db = openDatabase({ path: dbPath ?? DB_PATH });
15227
15341
  const aid = agentId ?? AGENT_ID;
15228
15342
  const embeddingProvider = getEmbeddingProviderFromEnv();
15343
+ const rerankerProvider = getRerankerProviderFromEnv();
15229
15344
  const server = new McpServer({
15230
15345
  name: "agent-memory",
15231
15346
  version: "2.1.0"
@@ -15263,7 +15378,10 @@ function createMcpServer(dbPath, agentId) {
15263
15378
  async ({ query, limit }) => {
15264
15379
  const { intent, confidence } = classifyIntent(query);
15265
15380
  const strategy = getStrategy(intent);
15266
- const raw = await searchHybrid(db, query, { agent_id: aid, embeddingProvider, limit: limit * 2 });
15381
+ let raw = await searchHybrid(db, query, { agent_id: aid, embeddingProvider, limit: limit * 2 });
15382
+ if (rerankerProvider) {
15383
+ raw = await rerankWithProvider(raw, query, rerankerProvider);
15384
+ }
15267
15385
  const results = rerank(raw, { ...strategy, limit });
15268
15386
  const output = {
15269
15387
  intent,