@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/mcp/server.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
14814
|
-
|
|
14815
|
-
|
|
14816
|
-
|
|
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
|
-
|
|
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:
|
|
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(`
|
|
14960
|
+
throw new Error(`Rerank API failed: ${resp.status} ${resp.statusText} ${body}`.trim());
|
|
14851
14961
|
}
|
|
14852
14962
|
const data = await resp.json();
|
|
14853
|
-
const
|
|
14854
|
-
return
|
|
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
|
-
|
|
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,
|