agentdb 3.0.0 → 3.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/README.md +13 -0
- package/dist/src/backends/ExactSearch.d.ts +22 -0
- package/dist/src/backends/ExactSearch.d.ts.map +1 -0
- package/dist/src/backends/ExactSearch.js +36 -0
- package/dist/src/backends/ExactSearch.js.map +1 -0
- package/dist/src/backends/VectorBackend.d.ts +33 -1
- package/dist/src/backends/VectorBackend.d.ts.map +1 -1
- package/dist/src/backends/VectorBackend.js +58 -4
- package/dist/src/backends/VectorBackend.js.map +1 -1
- package/dist/src/backends/rvf/SqlJsRvfBackend.d.ts.map +1 -1
- package/dist/src/backends/rvf/SqlJsRvfBackend.js +22 -12
- package/dist/src/backends/rvf/SqlJsRvfBackend.js.map +1 -1
- package/dist/src/controllers/EmbeddingService.d.ts +10 -0
- package/dist/src/controllers/EmbeddingService.d.ts.map +1 -1
- package/dist/src/controllers/EmbeddingService.js +12 -0
- package/dist/src/controllers/EmbeddingService.js.map +1 -1
- package/dist/src/controllers/ReasoningBank.d.ts +40 -4
- package/dist/src/controllers/ReasoningBank.d.ts.map +1 -1
- package/dist/src/controllers/ReasoningBank.js +124 -57
- package/dist/src/controllers/ReasoningBank.js.map +1 -1
- package/dist/src/controllers/ReflexionMemory.d.ts +20 -0
- package/dist/src/controllers/ReflexionMemory.d.ts.map +1 -1
- package/dist/src/controllers/ReflexionMemory.js +104 -17
- package/dist/src/controllers/ReflexionMemory.js.map +1 -1
- package/dist/src/controllers/SkillLibrary.d.ts +9 -0
- package/dist/src/controllers/SkillLibrary.d.ts.map +1 -1
- package/dist/src/controllers/SkillLibrary.js +60 -20
- package/dist/src/controllers/SkillLibrary.js.map +1 -1
- package/dist/src/controllers/WASMVectorSearch.d.ts.map +1 -1
- package/dist/src/controllers/WASMVectorSearch.js +48 -17
- package/dist/src/controllers/WASMVectorSearch.js.map +1 -1
- package/dist/src/controllers/episode-hybrid.d.ts +63 -0
- package/dist/src/controllers/episode-hybrid.d.ts.map +1 -0
- package/dist/src/controllers/episode-hybrid.js +117 -0
- package/dist/src/controllers/episode-hybrid.js.map +1 -0
- package/dist/src/mcp/agentdb-mcp-server.js +146 -3
- package/dist/src/mcp/agentdb-mcp-server.js.map +1 -1
- package/dist/src/search/conversation-retrieval.d.ts +56 -0
- package/dist/src/search/conversation-retrieval.d.ts.map +1 -0
- package/dist/src/search/conversation-retrieval.js +161 -0
- package/dist/src/search/conversation-retrieval.js.map +1 -0
- package/dist/src/search/cross-encoder.d.ts +17 -0
- package/dist/src/search/cross-encoder.d.ts.map +1 -0
- package/dist/src/search/cross-encoder.js +52 -0
- package/dist/src/search/cross-encoder.js.map +1 -0
- package/dist/src/security/mcp-policy.d.ts.map +1 -1
- package/dist/src/security/mcp-policy.js +4 -0
- package/dist/src/security/mcp-policy.js.map +1 -1
- package/dist/src/simd/simd-vector-ops.d.ts +11 -0
- package/dist/src/simd/simd-vector-ops.d.ts.map +1 -1
- package/dist/src/simd/simd-vector-ops.js +67 -0
- package/dist/src/simd/simd-vector-ops.js.map +1 -1
- package/dist/src/utils/top-k.d.ts +44 -0
- package/dist/src/utils/top-k.d.ts.map +1 -0
- package/dist/src/utils/top-k.js +111 -0
- package/dist/src/utils/top-k.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hybrid (dense + BM25) episode retrieval for ReflexionMemory (ADR-016).
|
|
3
|
+
*
|
|
4
|
+
* Dense search alone misses episodes whose wording matches the query but whose embedding does not (names, error
|
|
5
|
+
* codes, numbers). This keeps a BM25 index over the most recent episodes, rebuilt only when the episodes table
|
|
6
|
+
* changes, and fuses its ranking with the dense ranking by reciprocal-rank fusion. On LongMemEval session retrieval
|
|
7
|
+
* (benchmarks/longmemeval, which runs these ranking functions over turn chunks, not the episode store), the same
|
|
8
|
+
* fusion lifts recall_all@5 over dense-only ranking of the same chunks from 0.883 to 0.900 (S) and from 0.630 to
|
|
9
|
+
* 0.719 (M), with the default MiniLM embedder. See ADR-016.
|
|
10
|
+
*/
|
|
11
|
+
import type { IDatabaseConnection } from '../types/database.types.js';
|
|
12
|
+
/**
|
|
13
|
+
* Episodes indexed for BM25, newest first; older episodes are still found by dense search. The first search in a
|
|
14
|
+
* process builds the index (about 200 ms at 10k episodes, 1 s at 50k); later writes are appended incrementally.
|
|
15
|
+
*/
|
|
16
|
+
export declare const HYBRID_MAX_INDEXED: number;
|
|
17
|
+
/** The candidate pool each side contributes before fusion. */
|
|
18
|
+
export declare const HYBRID_MAX_POOL = 500;
|
|
19
|
+
/** Weight of the BM25 ranking against the dense ranking. Equal weights won on LongMemEval_M; see ADR-016. */
|
|
20
|
+
export declare const HYBRID_LEXICAL_WEIGHT = 1;
|
|
21
|
+
/**
|
|
22
|
+
* A BM25 index over episode text. New episodes are appended incrementally (the common write-then-read pattern);
|
|
23
|
+
* the index is rebuilt only when rows were deleted or replaced, or it has grown past 1.25x its cap.
|
|
24
|
+
*/
|
|
25
|
+
export declare class EpisodeLexicalIndex {
|
|
26
|
+
private readonly db;
|
|
27
|
+
private readonly maxDocs;
|
|
28
|
+
private count;
|
|
29
|
+
private maxId;
|
|
30
|
+
private ids;
|
|
31
|
+
private index;
|
|
32
|
+
constructor(db: IDatabaseConnection, maxDocs?: number);
|
|
33
|
+
private static textOf;
|
|
34
|
+
private refresh;
|
|
35
|
+
/** Episode ids with a BM25 match, best first, at most `limit`. */
|
|
36
|
+
search(query: string, limit: number): {
|
|
37
|
+
id: number;
|
|
38
|
+
score: number;
|
|
39
|
+
}[];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Weighted RRF scores of a dense ranking (best first) and a lexical ranking (best first) over episode ids.
|
|
43
|
+
* Episodes only in the lexical list are ranked just past the dense pool on the dense side.
|
|
44
|
+
*/
|
|
45
|
+
export declare function fuseEpisodeScores(denseIds: readonly number[], lexical: readonly {
|
|
46
|
+
id: number;
|
|
47
|
+
}[], lexicalWeight?: number): Map<number, number>;
|
|
48
|
+
/** Ids ordered by score, ties broken by dense order, then by newer id. */
|
|
49
|
+
export declare function orderByScore(score: ReadonlyMap<number, number>, denseIds?: readonly number[]): number[];
|
|
50
|
+
/** Fused ranking of episode ids, best first. */
|
|
51
|
+
export declare function fuseEpisodeRankings(denseIds: readonly number[], lexical: readonly {
|
|
52
|
+
id: number;
|
|
53
|
+
}[], lexicalWeight?: number): number[];
|
|
54
|
+
/** Candidates the cross-encoder reads: the top `RERANK_DEPTH` of each ranking (the configuration benchmarked in ADR-016). */
|
|
55
|
+
export declare const RERANK_DEPTH = 60;
|
|
56
|
+
/** Weight of the cross-encoder rank in the fusion; best on LongMemEval_M (ADR-016). */
|
|
57
|
+
export declare const RERANK_WEIGHT = 8;
|
|
58
|
+
/**
|
|
59
|
+
* Adds `RERANK_WEIGHT`/(k + cross-encoder rank) to each candidate's fused score, in place. Candidates without text
|
|
60
|
+
* are skipped. Throws if the model cannot be loaded; the caller keeps the hybrid order.
|
|
61
|
+
*/
|
|
62
|
+
export declare function applyCrossEncoder(query: string, score: Map<number, number>, candidates: readonly number[], textOf: (id: number) => string | undefined, model?: string): Promise<void>;
|
|
63
|
+
//# sourceMappingURL=episode-hybrid.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"episode-hybrid.d.ts","sourceRoot":"","sources":["../../../src/controllers/episode-hybrid.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAItE;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAA0E,CAAC;AAM1G,8DAA8D;AAC9D,eAAO,MAAM,eAAe,MAAM,CAAC;AACnC,6GAA6G;AAC7G,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAWvC;;;GAGG;AACH,qBAAa,mBAAmB;IAO5B,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAP1B,OAAO,CAAC,KAAK,CAAM;IACnB,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,GAAG,CAAgB;IAC3B,OAAO,CAAC,KAAK,CAA0B;gBAGpB,EAAE,EAAE,mBAAmB,EACvB,OAAO,SAAqB;IAG/C,OAAO,CAAC,MAAM,CAAC,MAAM;IAIrB,OAAO,CAAC,OAAO;IA4Bf,kEAAkE;IAClE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE;CAStE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,aAAa,SAAwB,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAS7J;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,GAAE,SAAS,MAAM,EAAO,GAAG,MAAM,EAAE,CAM3G;AAED,gDAAgD;AAChD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,aAAa,SAAwB,GAAG,MAAM,EAAE,CAEpJ;AAED,6HAA6H;AAC7H,eAAO,MAAM,YAAY,KAAK,CAAC;AAC/B,uFAAuF;AACvF,eAAO,MAAM,aAAa,IAAI,CAAC;AAE/B;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAC1B,UAAU,EAAE,SAAS,MAAM,EAAE,EAC7B,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,EAC1C,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAKf"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Bm25Index, tokenize } from '../search/conversation-retrieval.js';
|
|
2
|
+
import { crossEncoderScores } from '../search/cross-encoder.js';
|
|
3
|
+
/**
|
|
4
|
+
* Episodes indexed for BM25, newest first; older episodes are still found by dense search. The first search in a
|
|
5
|
+
* process builds the index (about 200 ms at 10k episodes, 1 s at 50k); later writes are appended incrementally.
|
|
6
|
+
*/
|
|
7
|
+
export const HYBRID_MAX_INDEXED = clampInt(process.env.AGENTDB_HYBRID_MAX_EPISODES, 10_000, 1, 1_000_000);
|
|
8
|
+
function clampInt(raw, fallback, min, max) {
|
|
9
|
+
const n = Math.floor(Number(raw));
|
|
10
|
+
return Number.isFinite(n) && n >= min ? Math.min(n, max) : fallback;
|
|
11
|
+
}
|
|
12
|
+
/** The candidate pool each side contributes before fusion. */
|
|
13
|
+
export const HYBRID_MAX_POOL = 500;
|
|
14
|
+
/** Weight of the BM25 ranking against the dense ranking. Equal weights won on LongMemEval_M; see ADR-016. */
|
|
15
|
+
export const HYBRID_LEXICAL_WEIGHT = 1;
|
|
16
|
+
const RRF_K = 60;
|
|
17
|
+
/**
|
|
18
|
+
* A BM25 index over episode text. New episodes are appended incrementally (the common write-then-read pattern);
|
|
19
|
+
* the index is rebuilt only when rows were deleted or replaced, or it has grown past 1.25x its cap.
|
|
20
|
+
*/
|
|
21
|
+
export class EpisodeLexicalIndex {
|
|
22
|
+
db;
|
|
23
|
+
maxDocs;
|
|
24
|
+
count = -1;
|
|
25
|
+
maxId = 0;
|
|
26
|
+
ids = [];
|
|
27
|
+
index = null;
|
|
28
|
+
constructor(db, maxDocs = HYBRID_MAX_INDEXED) {
|
|
29
|
+
this.db = db;
|
|
30
|
+
this.maxDocs = maxDocs;
|
|
31
|
+
}
|
|
32
|
+
static textOf(r) {
|
|
33
|
+
return tokenize([r.task, r.input, r.output, r.critique].filter(Boolean).join('\n'));
|
|
34
|
+
}
|
|
35
|
+
refresh() {
|
|
36
|
+
const sig = this.db.prepare('SELECT COUNT(*) AS c, MAX(id) AS m FROM episodes').get();
|
|
37
|
+
const count = sig?.c ?? 0;
|
|
38
|
+
const maxId = sig?.m ?? 0;
|
|
39
|
+
if (this.index && count === this.count && maxId === this.maxId)
|
|
40
|
+
return;
|
|
41
|
+
if (this.index && count > this.count && maxId > this.maxId && this.ids.length < this.maxDocs * 1.25) {
|
|
42
|
+
const added = this.db
|
|
43
|
+
.prepare('SELECT id, task, input, output, critique FROM episodes WHERE id > ? ORDER BY id ASC')
|
|
44
|
+
.all(this.maxId);
|
|
45
|
+
if (this.count + added.length === count) {
|
|
46
|
+
for (const r of added) {
|
|
47
|
+
this.index.add(EpisodeLexicalIndex.textOf(r));
|
|
48
|
+
this.ids.push(r.id);
|
|
49
|
+
}
|
|
50
|
+
this.count = count;
|
|
51
|
+
this.maxId = maxId;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const rows = this.db
|
|
56
|
+
.prepare('SELECT id, task, input, output, critique FROM episodes ORDER BY id DESC LIMIT ?')
|
|
57
|
+
.all(this.maxDocs);
|
|
58
|
+
this.index = new Bm25Index(rows.map(r => EpisodeLexicalIndex.textOf(r)));
|
|
59
|
+
this.ids = rows.map(r => r.id);
|
|
60
|
+
this.count = count;
|
|
61
|
+
this.maxId = maxId;
|
|
62
|
+
}
|
|
63
|
+
/** Episode ids with a BM25 match, best first, at most `limit`. */
|
|
64
|
+
search(query, limit) {
|
|
65
|
+
this.refresh();
|
|
66
|
+
if (!this.index || this.ids.length === 0)
|
|
67
|
+
return [];
|
|
68
|
+
const scores = this.index.score(tokenize(query, 256));
|
|
69
|
+
const hits = [];
|
|
70
|
+
for (let i = 0; i < scores.length; i++)
|
|
71
|
+
if (scores[i] > 0)
|
|
72
|
+
hits.push({ id: this.ids[i], score: scores[i] });
|
|
73
|
+
hits.sort((a, z) => z.score - a.score || z.id - a.id);
|
|
74
|
+
return hits.slice(0, limit);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Weighted RRF scores of a dense ranking (best first) and a lexical ranking (best first) over episode ids.
|
|
79
|
+
* Episodes only in the lexical list are ranked just past the dense pool on the dense side.
|
|
80
|
+
*/
|
|
81
|
+
export function fuseEpisodeScores(denseIds, lexical, lexicalWeight = HYBRID_LEXICAL_WEIGHT) {
|
|
82
|
+
const score = new Map();
|
|
83
|
+
const denseMiss = denseIds.length + 1;
|
|
84
|
+
denseIds.forEach((id, r) => score.set(id, 1 / (RRF_K + r + 1)));
|
|
85
|
+
lexical.forEach(({ id }, r) => {
|
|
86
|
+
const base = score.get(id) ?? 1 / (RRF_K + denseMiss);
|
|
87
|
+
score.set(id, base + lexicalWeight / (RRF_K + r + 1));
|
|
88
|
+
});
|
|
89
|
+
return score;
|
|
90
|
+
}
|
|
91
|
+
/** Ids ordered by score, ties broken by dense order, then by newer id. */
|
|
92
|
+
export function orderByScore(score, denseIds = []) {
|
|
93
|
+
const order = new Map();
|
|
94
|
+
denseIds.forEach((id, i) => order.set(id, i));
|
|
95
|
+
return [...score.entries()]
|
|
96
|
+
.sort((a, z) => z[1] - a[1] || (order.get(a[0]) ?? Infinity) - (order.get(z[0]) ?? Infinity) || z[0] - a[0])
|
|
97
|
+
.map(([id]) => id);
|
|
98
|
+
}
|
|
99
|
+
/** Fused ranking of episode ids, best first. */
|
|
100
|
+
export function fuseEpisodeRankings(denseIds, lexical, lexicalWeight = HYBRID_LEXICAL_WEIGHT) {
|
|
101
|
+
return orderByScore(fuseEpisodeScores(denseIds, lexical, lexicalWeight), denseIds);
|
|
102
|
+
}
|
|
103
|
+
/** Candidates the cross-encoder reads: the top `RERANK_DEPTH` of each ranking (the configuration benchmarked in ADR-016). */
|
|
104
|
+
export const RERANK_DEPTH = 60;
|
|
105
|
+
/** Weight of the cross-encoder rank in the fusion; best on LongMemEval_M (ADR-016). */
|
|
106
|
+
export const RERANK_WEIGHT = 8;
|
|
107
|
+
/**
|
|
108
|
+
* Adds `RERANK_WEIGHT`/(k + cross-encoder rank) to each candidate's fused score, in place. Candidates without text
|
|
109
|
+
* are skipped. Throws if the model cannot be loaded; the caller keeps the hybrid order.
|
|
110
|
+
*/
|
|
111
|
+
export async function applyCrossEncoder(query, score, candidates, textOf, model) {
|
|
112
|
+
const ids = candidates.filter(id => score.has(id) && textOf(id));
|
|
113
|
+
const ce = await crossEncoderScores(query, ids.map(id => textOf(id)), model);
|
|
114
|
+
const byCe = ids.map((_, i) => i).sort((a, z) => ce[z] - ce[a] || a - z);
|
|
115
|
+
byCe.forEach((i, r) => score.set(ids[i], score.get(ids[i]) + RERANK_WEIGHT / (RRF_K + r + 1)));
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=episode-hybrid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"episode-hybrid.js","sourceRoot":"","sources":["../../../src/controllers/episode-hybrid.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,7 +7,11 @@
|
|
|
7
7
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
8
8
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
9
9
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
10
|
-
import { createDatabase } from '../db-fallback.js';
|
|
10
|
+
import { createDatabase, getDatabaseInfo } from '../db-fallback.js';
|
|
11
|
+
import { NativeAccelerator } from '../backends/rvf/NativeAccelerator.js';
|
|
12
|
+
import { detectBackends } from '../backends/factory.js';
|
|
13
|
+
import { DEFAULT_EXACT_SEARCH_THRESHOLD } from '../backends/VectorBackend.js';
|
|
14
|
+
import { DEFAULT_RERANK_WEIGHTS } from '../controllers/ReasoningBank.js';
|
|
11
15
|
import { CausalMemoryGraph } from '../controllers/CausalMemoryGraph.js';
|
|
12
16
|
import { CausalRecall } from '../controllers/CausalRecall.js';
|
|
13
17
|
import { ReflexionMemory } from '../controllers/ReflexionMemory.js';
|
|
@@ -32,6 +36,21 @@ const toolPolicy = buildMcpToolPolicy();
|
|
|
32
36
|
// Request-size limits at the MCP boundary
|
|
33
37
|
const MAX_INSERT_BATCH_ITEMS = 1000;
|
|
34
38
|
const MAX_SEARCH_K = 100;
|
|
39
|
+
const MAX_FEEDBACK_LATENCY_MS = 24 * 60 * 60 * 1000;
|
|
40
|
+
/**
|
|
41
|
+
* Outcome feedback at the MCP boundary (ADR-015): a boolean success, a reward
|
|
42
|
+
* in [0, 1], or both. A reward alone counts as a success when >= 0.5.
|
|
43
|
+
*/
|
|
44
|
+
function validateFeedback(args) {
|
|
45
|
+
const hasReward = args?.reward !== undefined && args?.reward !== null;
|
|
46
|
+
const hasSuccess = args?.success !== undefined && args?.success !== null;
|
|
47
|
+
if (!hasReward && !hasSuccess) {
|
|
48
|
+
throw new ValidationError('success or reward is required', 'MISSING_REQUIRED_FIELD', 'success');
|
|
49
|
+
}
|
|
50
|
+
const reward = hasReward ? validateNumericRange(args.reward, 'reward', 0, 1) : undefined;
|
|
51
|
+
const success = hasSuccess ? validateBoolean(args.success, 'success') : reward >= 0.5;
|
|
52
|
+
return { success, reward };
|
|
53
|
+
}
|
|
35
54
|
// stdout belongs exclusively to JSON-RPC when running over stdio. Route every
|
|
36
55
|
// transitive diagnostic (including legacy controller console.log calls) to
|
|
37
56
|
// stderr before database and embedding initialization begins.
|
|
@@ -242,6 +261,40 @@ const learningSystem = new LearningSystem(db, embeddingService);
|
|
|
242
261
|
const batchOps = new BatchOperations(db, embeddingService);
|
|
243
262
|
const reasoningBank = new ReasoningBank(db, embeddingService);
|
|
244
263
|
const caches = new MCPToolCaches();
|
|
264
|
+
// agentdb_capabilities: native probing is done once, on first request
|
|
265
|
+
let acceleratorProbe = null;
|
|
266
|
+
async function describeCapabilities() {
|
|
267
|
+
acceleratorProbe ??= (async () => {
|
|
268
|
+
const accelerator = new NativeAccelerator();
|
|
269
|
+
await accelerator.initialize();
|
|
270
|
+
return accelerator;
|
|
271
|
+
})();
|
|
272
|
+
const [accelerator, detected] = await Promise.all([acceleratorProbe, detectBackends()]);
|
|
273
|
+
const exactThreshold = Number(process.env.AGENTDB_EXACT_SEARCH_THRESHOLD);
|
|
274
|
+
return {
|
|
275
|
+
nativeAccelerator: accelerator.capabilities(),
|
|
276
|
+
vectorBackend: {
|
|
277
|
+
// This server's controllers are built without a vector index: every
|
|
278
|
+
// search here is an exact cosine scan over the SQL embedding tables.
|
|
279
|
+
inUse: 'sql-exact-scan',
|
|
280
|
+
available: detected.available,
|
|
281
|
+
ruvector: detected.ruvector,
|
|
282
|
+
rvf: detected.rvf,
|
|
283
|
+
hnswlib: detected.hnswlib,
|
|
284
|
+
sqljsRvf: detected.sqljsRvf,
|
|
285
|
+
ruvectorBackendEnv: process.env.RUVECTOR_BACKEND ?? null,
|
|
286
|
+
},
|
|
287
|
+
sqlDriver: getDatabaseInfo(),
|
|
288
|
+
embedding: embeddingService.getRuntimeInfo(),
|
|
289
|
+
retrieval: {
|
|
290
|
+
exactSearchThreshold: Number.isFinite(exactThreshold) && exactThreshold >= 0
|
|
291
|
+
? exactThreshold
|
|
292
|
+
: DEFAULT_EXACT_SEARCH_THRESHOLD,
|
|
293
|
+
patternRerankWeights: DEFAULT_RERANK_WEIGHTS,
|
|
294
|
+
},
|
|
295
|
+
mcpProfile: toolPolicy.profile,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
245
298
|
// ============================================================================
|
|
246
299
|
// MCP Server Setup
|
|
247
300
|
// ============================================================================
|
|
@@ -312,13 +365,15 @@ const tools = [
|
|
|
312
365
|
},
|
|
313
366
|
{
|
|
314
367
|
name: 'agentdb_search',
|
|
315
|
-
description: '
|
|
368
|
+
description: 'Hybrid episode search: dense k-NN (cosine) fused with BM25 keyword ranking by reciprocal-rank fusion. Set hybrid=false for pure vector order.',
|
|
316
369
|
inputSchema: {
|
|
317
370
|
type: 'object',
|
|
318
371
|
properties: {
|
|
319
372
|
query: { type: 'string', description: 'Search query text' },
|
|
320
373
|
k: { type: 'number', description: 'Number of results to return (max 100)', default: 10, minimum: 1, maximum: 100 },
|
|
321
374
|
min_similarity: { type: 'number', description: 'Minimum similarity threshold (0-1)', default: 0.0 },
|
|
375
|
+
hybrid: { type: 'boolean', description: 'Fuse BM25 keyword ranking with the vector ranking (ADR-016)', default: true },
|
|
376
|
+
rerank: { type: 'boolean', description: 'Rerank the top candidates with a local cross-encoder (ADR-016; ~200 ms on CPU, 23 MB model on first use)', default: false },
|
|
322
377
|
filters: {
|
|
323
378
|
type: 'object',
|
|
324
379
|
properties: {
|
|
@@ -708,6 +763,41 @@ const tools = [
|
|
|
708
763
|
properties: {},
|
|
709
764
|
},
|
|
710
765
|
},
|
|
766
|
+
{
|
|
767
|
+
name: 'agentdb_pattern_feedback',
|
|
768
|
+
description: 'Record the outcome of using a reasoning pattern. Updates its success rate and average reward, which agentdb_pattern_search ranks by (0.7 similarity + 0.2 success rate + 0.1 reward). Give success, reward, or both.',
|
|
769
|
+
inputSchema: {
|
|
770
|
+
type: 'object',
|
|
771
|
+
properties: {
|
|
772
|
+
pattern_id: { type: 'integer', minimum: 0, description: 'Pattern ID from agentdb_pattern_store or agentdb_pattern_search' },
|
|
773
|
+
success: { type: 'boolean', description: 'Whether applying the pattern succeeded' },
|
|
774
|
+
reward: { type: 'number', minimum: 0, maximum: 1, description: 'Reward in [0, 1]; defaults to 1 for success, 0 for failure. A reward alone counts as success when >= 0.5' },
|
|
775
|
+
},
|
|
776
|
+
required: ['pattern_id'],
|
|
777
|
+
},
|
|
778
|
+
},
|
|
779
|
+
{
|
|
780
|
+
name: 'agentdb_skill_feedback',
|
|
781
|
+
description: 'Record the outcome of using a skill. Updates its uses, success rate, average reward and (optionally) latency, which skill_search ranks by. Give success, reward, or both.',
|
|
782
|
+
inputSchema: {
|
|
783
|
+
type: 'object',
|
|
784
|
+
properties: {
|
|
785
|
+
skill_id: { type: 'integer', minimum: 0, description: 'Skill ID from skill_create or skill_search' },
|
|
786
|
+
success: { type: 'boolean', description: 'Whether the skill succeeded' },
|
|
787
|
+
reward: { type: 'number', minimum: 0, maximum: 1, description: 'Reward in [0, 1]; defaults to 1 for success, 0 for failure. A reward alone counts as success when >= 0.5' },
|
|
788
|
+
latency_ms: { type: 'number', minimum: 0, description: 'Observed latency in milliseconds (optional)' },
|
|
789
|
+
},
|
|
790
|
+
required: ['skill_id'],
|
|
791
|
+
},
|
|
792
|
+
},
|
|
793
|
+
{
|
|
794
|
+
name: 'agentdb_capabilities',
|
|
795
|
+
description: 'Report what this AgentDB server is running on: native accelerator features that loaded, the vector backend in use and the ones available, the SQL driver, the embedding model and dimension (and whether it is the hash fallback), and the ranking defaults. Read-only.',
|
|
796
|
+
inputSchema: {
|
|
797
|
+
type: 'object',
|
|
798
|
+
properties: {},
|
|
799
|
+
},
|
|
800
|
+
},
|
|
711
801
|
{
|
|
712
802
|
name: 'agentdb_clear_cache',
|
|
713
803
|
description: 'Clear query cache to refresh statistics and search results',
|
|
@@ -981,13 +1071,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
981
1071
|
};
|
|
982
1072
|
}
|
|
983
1073
|
case 'agentdb_search': {
|
|
984
|
-
const queryText = args?.query;
|
|
1074
|
+
const queryText = validateTaskString(args?.query, 'query');
|
|
985
1075
|
const k = Math.floor(validateNumericRange(args?.k ?? 10, 'k', 1, MAX_SEARCH_K));
|
|
986
1076
|
const minSimilarity = args?.min_similarity || 0.0;
|
|
987
1077
|
const filters = args?.filters;
|
|
988
1078
|
const query = {
|
|
989
1079
|
task: queryText,
|
|
990
1080
|
k,
|
|
1081
|
+
hybrid: args?.hybrid !== false,
|
|
1082
|
+
rerank: args?.rerank === true,
|
|
991
1083
|
};
|
|
992
1084
|
if (filters) {
|
|
993
1085
|
if (filters.min_reward !== undefined) {
|
|
@@ -1403,6 +1495,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1403
1495
|
`Top Results:\n` +
|
|
1404
1496
|
patterns.slice(0, 5).map((p, i) => `${i + 1}. [ID: ${p.id}] ${p.taskType}\n` +
|
|
1405
1497
|
` Similarity: ${(p.similarity || 0).toFixed(3)}\n` +
|
|
1498
|
+
(p.score !== undefined ? ` Rank score: ${p.score.toFixed(3)} (similarity + outcome)\n` : '') +
|
|
1406
1499
|
` Success Rate: ${(p.successRate * 100).toFixed(1)}%\n` +
|
|
1407
1500
|
` Approach: ${p.approach.substring(0, 80)}${p.approach.length > 80 ? '...' : ''}\n` +
|
|
1408
1501
|
` Uses: ${p.uses || 0}`).join('\n\n') +
|
|
@@ -1447,6 +1540,56 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1447
1540
|
],
|
|
1448
1541
|
};
|
|
1449
1542
|
}
|
|
1543
|
+
case 'agentdb_pattern_feedback': {
|
|
1544
|
+
const patternId = validateId(args?.pattern_id, 'pattern_id');
|
|
1545
|
+
const { success, reward } = validateFeedback(args);
|
|
1546
|
+
if (!(await reasoningBank.recordOutcome(patternId, success, reward))) {
|
|
1547
|
+
throw new ValidationError(`Pattern ${patternId} not found`, 'NOT_FOUND', 'pattern_id');
|
|
1548
|
+
}
|
|
1549
|
+
caches.stats.clear();
|
|
1550
|
+
const pattern = reasoningBank.getPattern(patternId);
|
|
1551
|
+
return {
|
|
1552
|
+
content: [{
|
|
1553
|
+
type: 'text',
|
|
1554
|
+
text: JSON.stringify({
|
|
1555
|
+
patternId,
|
|
1556
|
+
recorded: { success, reward: reward ?? (success ? 1 : 0) },
|
|
1557
|
+
uses: pattern.uses,
|
|
1558
|
+
successRate: pattern.successRate,
|
|
1559
|
+
avgReward: pattern.avgReward,
|
|
1560
|
+
}, null, 2),
|
|
1561
|
+
}],
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
case 'agentdb_skill_feedback': {
|
|
1565
|
+
const skillId = validateId(args?.skill_id, 'skill_id');
|
|
1566
|
+
const { success, reward } = validateFeedback(args);
|
|
1567
|
+
const latencyMs = args?.latency_ms === undefined || args?.latency_ms === null
|
|
1568
|
+
? undefined
|
|
1569
|
+
: validateNumericRange(args.latency_ms, 'latency_ms', 0, MAX_FEEDBACK_LATENCY_MS);
|
|
1570
|
+
if (!skills.recordOutcome(skillId, success, reward, latencyMs)) {
|
|
1571
|
+
throw new ValidationError(`Skill ${skillId} not found`, 'NOT_FOUND', 'skill_id');
|
|
1572
|
+
}
|
|
1573
|
+
const row = db.prepare('SELECT uses, success_rate, avg_reward, avg_latency_ms FROM skills WHERE id = ?').get(skillId);
|
|
1574
|
+
return {
|
|
1575
|
+
content: [{
|
|
1576
|
+
type: 'text',
|
|
1577
|
+
text: JSON.stringify({
|
|
1578
|
+
skillId,
|
|
1579
|
+
recorded: { success, reward: reward ?? (success ? 1 : 0), latencyMs },
|
|
1580
|
+
uses: row.uses,
|
|
1581
|
+
successRate: row.success_rate,
|
|
1582
|
+
avgReward: row.avg_reward,
|
|
1583
|
+
avgLatencyMs: row.avg_latency_ms,
|
|
1584
|
+
}, null, 2),
|
|
1585
|
+
}],
|
|
1586
|
+
};
|
|
1587
|
+
}
|
|
1588
|
+
case 'agentdb_capabilities': {
|
|
1589
|
+
return {
|
|
1590
|
+
content: [{ type: 'text', text: JSON.stringify(await describeCapabilities(), null, 2) }],
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1450
1593
|
case 'agentdb_clear_cache': {
|
|
1451
1594
|
const cacheType = args?.cache_type || 'all';
|
|
1452
1595
|
let cleared = 0;
|