@oomkapwn/enquire-mcp 2.11.0 → 2.13.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 +161 -0
- package/README.md +7 -3
- package/dist/embed-db.d.ts +21 -0
- package/dist/embed-db.d.ts.map +1 -1
- package/dist/embed-db.js +44 -0
- package/dist/embed-db.js.map +1 -1
- package/dist/eval.d.ts +117 -0
- package/dist/eval.d.ts.map +1 -0
- package/dist/eval.js +266 -0
- package/dist/eval.js.map +1 -0
- package/dist/hnsw.d.ts +87 -0
- package/dist/hnsw.d.ts.map +1 -0
- package/dist/hnsw.js +156 -0
- package/dist/hnsw.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +194 -5
- package/dist/index.js.map +1 -1
- package/dist/tools.d.ts +36 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +22 -3
- package/dist/tools.js.map +1 -1
- package/docs/api.md +3 -0
- package/package.json +2 -1
package/dist/eval.js
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
// Retrieval-quality evaluation harness for enquire-mcp.
|
|
2
|
+
//
|
|
3
|
+
// v2.12.0 — closes the "you can't tune what you can't measure" gap. Before
|
|
4
|
+
// this, anyone trying to A/B test retrieval changes (graph_boost on/off,
|
|
5
|
+
// reranker on/off, different limit / min_signals values) had to write a
|
|
6
|
+
// custom script. Now there's a first-class subcommand:
|
|
7
|
+
//
|
|
8
|
+
// enquire-mcp eval --vault <path> --queries <file>
|
|
9
|
+
// Reads JSONL queries with known-relevant doc paths, runs
|
|
10
|
+
// `obsidian_search` for each, computes NDCG@10 + Recall@10 + MRR,
|
|
11
|
+
// reports per-query + aggregate scores. Pretty table by default,
|
|
12
|
+
// `--json` for machine-readable output, `--matrix` to A/B several
|
|
13
|
+
// flag combinations side-by-side in one run.
|
|
14
|
+
//
|
|
15
|
+
// Standard IR metrics (Manning et al, "Introduction to Information
|
|
16
|
+
// Retrieval", Chapter 8):
|
|
17
|
+
// • NDCG@K (Normalized Discounted Cumulative Gain) — penalizes
|
|
18
|
+
// relevant docs found low in the ranking; 1.0 is perfect, 0.0 is
|
|
19
|
+
// worst. Best for graded relevance + position-aware comparison.
|
|
20
|
+
// • Recall@K — fraction of relevant docs found in top-K. Best for
|
|
21
|
+
// "did we surface ANY relevant content?" measurement.
|
|
22
|
+
// • MRR (Mean Reciprocal Rank) — 1/rank of the first relevant doc.
|
|
23
|
+
// Best for "did we put SOMETHING relevant near the top?"
|
|
24
|
+
//
|
|
25
|
+
// We treat the user's `relevant` paths as binary-relevance ground truth
|
|
26
|
+
// (each listed path is gain=1, others are gain=0) since most users won't
|
|
27
|
+
// label graded relevance. The DCG formula simplifies to
|
|
28
|
+
// sum(rel_i / log2(i + 1)) where rel_i ∈ {0, 1}. NDCG normalizes by the
|
|
29
|
+
// ideal DCG = sum(1 / log2(i + 1)) for i in [1, |relevant|].
|
|
30
|
+
//
|
|
31
|
+
// "Only enquire-mcp has this": no other Obsidian-MCP ships a built-in
|
|
32
|
+
// retrieval evaluation harness. This makes Karpathy-style LLM Wiki users
|
|
33
|
+
// systematically tune their hybrid retrieval — measure first, then
|
|
34
|
+
// adjust graph_boost / reranker / min_signals based on real numbers
|
|
35
|
+
// over their real corpus.
|
|
36
|
+
import { promises as fs } from "node:fs";
|
|
37
|
+
import { searchHybrid } from "./tools.js";
|
|
38
|
+
/**
|
|
39
|
+
* NDCG@K with binary relevance.
|
|
40
|
+
*
|
|
41
|
+
* DCG@K = sum_{i=1..K} rel_i / log2(i + 1)
|
|
42
|
+
* IdealDCG@K = sum_{i=1..min(K, |relevant|)} 1 / log2(i + 1)
|
|
43
|
+
* NDCG@K = DCG@K / IdealDCG@K
|
|
44
|
+
*
|
|
45
|
+
* Returns 0 when `relevant` is empty (no ground truth → undefined ratio).
|
|
46
|
+
*/
|
|
47
|
+
export function ndcgAtK(retrievedPaths, relevant, k) {
|
|
48
|
+
if (relevant.size === 0)
|
|
49
|
+
return 0;
|
|
50
|
+
let dcg = 0;
|
|
51
|
+
for (let i = 0; i < Math.min(k, retrievedPaths.length); i++) {
|
|
52
|
+
const path = retrievedPaths[i];
|
|
53
|
+
if (path && relevant.has(path)) {
|
|
54
|
+
dcg += 1 / Math.log2(i + 2); // i+2 because i is 0-indexed; rank = i+1, log2(rank+1)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
let idealDcg = 0;
|
|
58
|
+
for (let i = 0; i < Math.min(k, relevant.size); i++) {
|
|
59
|
+
idealDcg += 1 / Math.log2(i + 2);
|
|
60
|
+
}
|
|
61
|
+
return idealDcg > 0 ? dcg / idealDcg : 0;
|
|
62
|
+
}
|
|
63
|
+
/** Recall @ K = |retrieved ∩ relevant| / |relevant|. */
|
|
64
|
+
export function recallAtK(retrievedPaths, relevant, k) {
|
|
65
|
+
if (relevant.size === 0)
|
|
66
|
+
return 0;
|
|
67
|
+
let hits = 0;
|
|
68
|
+
for (let i = 0; i < Math.min(k, retrievedPaths.length); i++) {
|
|
69
|
+
const path = retrievedPaths[i];
|
|
70
|
+
if (path && relevant.has(path))
|
|
71
|
+
hits += 1;
|
|
72
|
+
}
|
|
73
|
+
return hits / relevant.size;
|
|
74
|
+
}
|
|
75
|
+
/** Mean Reciprocal Rank — 1/rank of first relevant; 0 if none in top-K. */
|
|
76
|
+
export function reciprocalRank(retrievedPaths, relevant, k) {
|
|
77
|
+
for (let i = 0; i < Math.min(k, retrievedPaths.length); i++) {
|
|
78
|
+
const path = retrievedPaths[i];
|
|
79
|
+
if (path && relevant.has(path))
|
|
80
|
+
return 1 / (i + 1);
|
|
81
|
+
}
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Read a JSONL file of EvalQuery objects. Tolerates blank lines and
|
|
86
|
+
* comments (lines starting with `//`). Throws on invalid JSON or
|
|
87
|
+
* missing required fields.
|
|
88
|
+
*/
|
|
89
|
+
export async function readQueriesJsonl(file) {
|
|
90
|
+
const raw = await fs.readFile(file, "utf8");
|
|
91
|
+
const queries = [];
|
|
92
|
+
let lineNum = 0;
|
|
93
|
+
for (const line of raw.split("\n")) {
|
|
94
|
+
lineNum += 1;
|
|
95
|
+
const trimmed = line.trim();
|
|
96
|
+
if (trimmed.length === 0 || trimmed.startsWith("//"))
|
|
97
|
+
continue;
|
|
98
|
+
try {
|
|
99
|
+
const parsed = JSON.parse(trimmed);
|
|
100
|
+
if (typeof parsed.query !== "string" || parsed.query.length === 0) {
|
|
101
|
+
throw new Error(`line ${lineNum}: missing or empty 'query' field`);
|
|
102
|
+
}
|
|
103
|
+
if (!Array.isArray(parsed.relevant) || parsed.relevant.some((p) => typeof p !== "string")) {
|
|
104
|
+
throw new Error(`line ${lineNum}: 'relevant' must be an array of vault-relative path strings`);
|
|
105
|
+
}
|
|
106
|
+
queries.push({
|
|
107
|
+
query: parsed.query,
|
|
108
|
+
relevant: parsed.relevant,
|
|
109
|
+
...(parsed.id ? { id: parsed.id } : {})
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
114
|
+
throw new Error(`enquire eval: failed to parse queries file at line ${lineNum} — ${msg}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return queries;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Run obsidian_search across a set of evaluation queries and compute
|
|
121
|
+
* NDCG@K, Recall@K, MRR. Returns a fully-populated EvalResult.
|
|
122
|
+
*
|
|
123
|
+
* `embedFile` may be a non-existent path — embeddings simply won't
|
|
124
|
+
* contribute (graceful degradation matches `searchHybrid` behavior).
|
|
125
|
+
*/
|
|
126
|
+
export async function runEval(opts) {
|
|
127
|
+
const k = opts.k ?? 10;
|
|
128
|
+
const totalT0 = Date.now();
|
|
129
|
+
const perQuery = [];
|
|
130
|
+
for (let i = 0; i < opts.queries.length; i++) {
|
|
131
|
+
const q = opts.queries[i];
|
|
132
|
+
if (!q)
|
|
133
|
+
continue;
|
|
134
|
+
const id = q.id ?? `q${i + 1}`;
|
|
135
|
+
const relevantSet = new Set(q.relevant);
|
|
136
|
+
const t0 = Date.now();
|
|
137
|
+
let hits = [];
|
|
138
|
+
try {
|
|
139
|
+
const result = await searchHybrid(opts.vault, {
|
|
140
|
+
query: q.query,
|
|
141
|
+
limit: k,
|
|
142
|
+
...(opts.searchOpts?.graph_boost !== undefined ? { graph_boost: opts.searchOpts.graph_boost } : {}),
|
|
143
|
+
...(opts.searchOpts?.min_signals !== undefined ? { min_signals: opts.searchOpts.min_signals } : {}),
|
|
144
|
+
...(opts.searchOpts?.embedding_model ? { embedding_model: opts.searchOpts.embedding_model } : {})
|
|
145
|
+
}, {
|
|
146
|
+
ftsIndex: opts.ftsIndex,
|
|
147
|
+
embedFile: opts.embedFile,
|
|
148
|
+
...(opts.reranker ? { reranker: opts.reranker } : {}),
|
|
149
|
+
...(opts.rerankerOverride ? { rerankerOverride: opts.rerankerOverride } : {})
|
|
150
|
+
});
|
|
151
|
+
hits = result.matches;
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
// Per-query isolation — one bad query doesn't sink the whole eval.
|
|
155
|
+
// The query's scores will all be 0 and we keep going.
|
|
156
|
+
process.stderr.write(`enquire eval: query "${q.query.slice(0, 60)}" failed — ${err instanceof Error ? err.message : String(err)}\n`);
|
|
157
|
+
}
|
|
158
|
+
const latency = Date.now() - t0;
|
|
159
|
+
const retrievedPaths = hits.map((h) => h.path);
|
|
160
|
+
const ndcg = ndcgAtK(retrievedPaths, relevantSet, k);
|
|
161
|
+
const recall = recallAtK(retrievedPaths, relevantSet, k);
|
|
162
|
+
const mrr = reciprocalRank(retrievedPaths, relevantSet, k);
|
|
163
|
+
let hitsRelevant = 0;
|
|
164
|
+
for (const p of retrievedPaths.slice(0, k)) {
|
|
165
|
+
if (relevantSet.has(p))
|
|
166
|
+
hitsRelevant += 1;
|
|
167
|
+
}
|
|
168
|
+
perQuery.push({
|
|
169
|
+
id,
|
|
170
|
+
query: q.query,
|
|
171
|
+
ndcg_at_k: round(ndcg),
|
|
172
|
+
recall_at_k: round(recall),
|
|
173
|
+
mrr: round(mrr),
|
|
174
|
+
hits_relevant: hitsRelevant,
|
|
175
|
+
hits_total_relevant: relevantSet.size,
|
|
176
|
+
latency_ms: latency
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
const meanNdcg = mean(perQuery.map((p) => p.ndcg_at_k));
|
|
180
|
+
const meanRecall = mean(perQuery.map((p) => p.recall_at_k));
|
|
181
|
+
const meanMrr = mean(perQuery.map((p) => p.mrr));
|
|
182
|
+
const meanLatency = mean(perQuery.map((p) => p.latency_ms));
|
|
183
|
+
return {
|
|
184
|
+
label: opts.label ?? "default",
|
|
185
|
+
k,
|
|
186
|
+
query_count: perQuery.length,
|
|
187
|
+
per_query: perQuery,
|
|
188
|
+
mean_ndcg: round(meanNdcg),
|
|
189
|
+
mean_recall: round(meanRecall),
|
|
190
|
+
mean_mrr: round(meanMrr),
|
|
191
|
+
mean_latency_ms: Math.round(meanLatency),
|
|
192
|
+
total_wall_ms: Date.now() - totalT0
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function mean(xs) {
|
|
196
|
+
if (xs.length === 0)
|
|
197
|
+
return 0;
|
|
198
|
+
let s = 0;
|
|
199
|
+
for (const x of xs)
|
|
200
|
+
s += x;
|
|
201
|
+
return s / xs.length;
|
|
202
|
+
}
|
|
203
|
+
function round(x) {
|
|
204
|
+
return Math.round(x * 10000) / 10000;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Render an EvalResult as a pretty CLI table. ANSI-colored when stdout
|
|
208
|
+
* is a TTY, plain text otherwise (so `enquire eval | tee report.txt`
|
|
209
|
+
* stays readable).
|
|
210
|
+
*/
|
|
211
|
+
export function formatEvalResult(result, opts = {}) {
|
|
212
|
+
const isTty = process.stdout.isTTY === true;
|
|
213
|
+
const bold = (s) => (isTty ? `\x1b[1m${s}\x1b[0m` : s);
|
|
214
|
+
const dim = (s) => (isTty ? `\x1b[2m${s}\x1b[0m` : s);
|
|
215
|
+
const lines = [];
|
|
216
|
+
lines.push(bold(`enquire eval — ${result.label}`));
|
|
217
|
+
lines.push(` ${result.query_count} queries · k=${result.k} · wall=${result.total_wall_ms}ms`);
|
|
218
|
+
lines.push("");
|
|
219
|
+
if (opts.perQuery) {
|
|
220
|
+
lines.push(bold("per query:"));
|
|
221
|
+
lines.push(" id ndcg@k recall@k mrr hits latency");
|
|
222
|
+
for (const p of result.per_query) {
|
|
223
|
+
lines.push(` ${p.id.padEnd(15)} ${p.ndcg_at_k.toFixed(4)} ${p.recall_at_k.toFixed(4)} ${p.mrr.toFixed(4)} ${`${p.hits_relevant}/${p.hits_total_relevant}`.padEnd(6)} ${p.latency_ms}ms`);
|
|
224
|
+
}
|
|
225
|
+
lines.push("");
|
|
226
|
+
}
|
|
227
|
+
lines.push(bold("aggregate:"));
|
|
228
|
+
lines.push(` mean NDCG@${result.k} = ${result.mean_ndcg.toFixed(4)}`);
|
|
229
|
+
lines.push(` mean Recall@${result.k} = ${result.mean_recall.toFixed(4)}`);
|
|
230
|
+
lines.push(` mean MRR = ${result.mean_mrr.toFixed(4)}`);
|
|
231
|
+
lines.push(` mean latency = ${result.mean_latency_ms}ms ${dim("(per query)")}`);
|
|
232
|
+
return lines.join("\n");
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Render multiple EvalResults side-by-side as a comparison matrix. Used
|
|
236
|
+
* by `enquire eval --matrix` to A/B several configurations in one run.
|
|
237
|
+
*/
|
|
238
|
+
export function formatEvalMatrix(results) {
|
|
239
|
+
if (results.length === 0)
|
|
240
|
+
return "(no results)";
|
|
241
|
+
const isTty = process.stdout.isTTY === true;
|
|
242
|
+
const bold = (s) => (isTty ? `\x1b[1m${s}\x1b[0m` : s);
|
|
243
|
+
const lines = [];
|
|
244
|
+
lines.push(bold(`enquire eval matrix (${results.length} configs)`));
|
|
245
|
+
lines.push("");
|
|
246
|
+
// Column header.
|
|
247
|
+
const labelWidth = Math.max(...results.map((r) => r.label.length), 8) + 2;
|
|
248
|
+
const header = `${"label".padEnd(labelWidth)}NDCG@${results[0]?.k ?? 10} Recall@${results[0]?.k ?? 10} MRR latency`;
|
|
249
|
+
lines.push(bold(header));
|
|
250
|
+
// Rows.
|
|
251
|
+
for (const r of results) {
|
|
252
|
+
lines.push(`${r.label.padEnd(labelWidth)}${r.mean_ndcg.toFixed(4)} ${r.mean_recall.toFixed(4)} ${r.mean_mrr.toFixed(4)} ${r.mean_latency_ms}ms`);
|
|
253
|
+
}
|
|
254
|
+
// Best-config callout.
|
|
255
|
+
let best = results[0];
|
|
256
|
+
if (best) {
|
|
257
|
+
for (const r of results) {
|
|
258
|
+
if (r.mean_ndcg > best.mean_ndcg)
|
|
259
|
+
best = r;
|
|
260
|
+
}
|
|
261
|
+
lines.push("");
|
|
262
|
+
lines.push(`best NDCG@${best.k}: ${bold(best.label)} (${best.mean_ndcg.toFixed(4)})`);
|
|
263
|
+
}
|
|
264
|
+
return lines.join("\n");
|
|
265
|
+
}
|
|
266
|
+
//# sourceMappingURL=eval.js.map
|
package/dist/eval.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eval.js","sourceRoot":"","sources":["../src/eval.ts"],"names":[],"mappings":"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"}
|
package/dist/hnsw.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { EmbedSearchHit } from "./embed-db.js";
|
|
2
|
+
/** A single labeled vector — used to populate the index. */
|
|
3
|
+
export interface LabeledVector {
|
|
4
|
+
/** Stable identifier — lets the search code recover the source row from the EmbedDb. */
|
|
5
|
+
label: number;
|
|
6
|
+
/** L2-normalized vector. Caller is responsible for the normalization. */
|
|
7
|
+
vector: Float32Array;
|
|
8
|
+
}
|
|
9
|
+
/** Build-time HNSW parameters. Defaults tuned for 384-dim cosine on PKM data. */
|
|
10
|
+
export interface HnswBuildOptions {
|
|
11
|
+
/** Embedding dimensionality (must match the corpus). */
|
|
12
|
+
dim: number;
|
|
13
|
+
/** Maximum elements (caller's count of vectors); enables index pre-sizing. */
|
|
14
|
+
maxElements: number;
|
|
15
|
+
/**
|
|
16
|
+
* Number of bidirectional links per node. Higher M = better recall but
|
|
17
|
+
* more memory + slower build. Default 16 (Malkov & Yashunin, 2018, §4.1).
|
|
18
|
+
*/
|
|
19
|
+
m?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Beam width during build. Higher efConstruction = better recall,
|
|
22
|
+
* slower build, no query-time cost. Default 200.
|
|
23
|
+
*/
|
|
24
|
+
efConstruction?: number;
|
|
25
|
+
/** Seed for build-time randomization (reproducibility in tests). */
|
|
26
|
+
seed?: number;
|
|
27
|
+
}
|
|
28
|
+
/** Per-query parameters. */
|
|
29
|
+
export interface HnswQueryOptions {
|
|
30
|
+
/**
|
|
31
|
+
* Beam width during search. Higher = more accurate, slower. Default 100.
|
|
32
|
+
* Must be ≥ k. Common range: 50-500.
|
|
33
|
+
*/
|
|
34
|
+
ef?: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* In-memory HNSW index over L2-normalized cosine vectors. Built once on
|
|
38
|
+
* serve start from `EmbedDb.getAllVectors()`; queried per
|
|
39
|
+
* `obsidian_search` / `obsidian_embeddings_search` invocation.
|
|
40
|
+
*/
|
|
41
|
+
export interface HnswIndex {
|
|
42
|
+
/** Vector dimensionality. */
|
|
43
|
+
readonly dim: number;
|
|
44
|
+
/** Number of points currently in the index. */
|
|
45
|
+
readonly size: number;
|
|
46
|
+
/**
|
|
47
|
+
* k-NN search. Returns labels + distances (cosine distance, smaller =
|
|
48
|
+
* more similar). Caller maps labels back to source rows via the same
|
|
49
|
+
* `LabeledVector.label` they used at build time.
|
|
50
|
+
*/
|
|
51
|
+
searchKnn(queryVec: Float32Array, k: number, opts?: HnswQueryOptions): {
|
|
52
|
+
labels: number[];
|
|
53
|
+
distances: number[];
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Build a fresh in-memory HNSW from labeled vectors.
|
|
58
|
+
*
|
|
59
|
+
* `vectors` must be L2-normalized — the cosine distance space treats
|
|
60
|
+
* inputs as already-unit-length, so unnormalized inputs produce wrong
|
|
61
|
+
* distances. The `EmbedDb` already L2-normalizes at insert time, so the
|
|
62
|
+
* usual call path (loadAllVectors → buildHnsw) is safe by construction.
|
|
63
|
+
*
|
|
64
|
+
* Throws if `dim` doesn't match any vector's length, if `maxElements`
|
|
65
|
+
* is less than the input count, or if `hnswlib-wasm` failed to load.
|
|
66
|
+
*/
|
|
67
|
+
export declare function buildHnsw(vectors: ReadonlyArray<LabeledVector>, opts: HnswBuildOptions): Promise<HnswIndex>;
|
|
68
|
+
/**
|
|
69
|
+
* Convert HNSW search results to EmbedSearchHit using a label → source-row
|
|
70
|
+
* lookup. The label was assigned by the caller at build time (typically
|
|
71
|
+
* `EmbedDb.getAllVectors()` returns rows with sequential integer labels);
|
|
72
|
+
* we just reverse the mapping. Distance → cosine similarity: cosine
|
|
73
|
+
* distance is `1 - cosine_similarity`, so we flip back here so callers
|
|
74
|
+
* can compare HNSW + brute-force scores apples-to-apples.
|
|
75
|
+
*/
|
|
76
|
+
export declare function hnswResultsToHits(result: {
|
|
77
|
+
labels: number[];
|
|
78
|
+
distances: number[];
|
|
79
|
+
}, rowByLabel: ReadonlyMap<number, {
|
|
80
|
+
rel_path: string;
|
|
81
|
+
chunk_index: number;
|
|
82
|
+
line_start: number;
|
|
83
|
+
line_end: number;
|
|
84
|
+
text_preview: string;
|
|
85
|
+
kind: "md" | "pdf";
|
|
86
|
+
}>): EmbedSearchHit[];
|
|
87
|
+
//# sourceMappingURL=hnsw.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hnsw.d.ts","sourceRoot":"","sources":["../src/hnsw.ts"],"names":[],"mappings":"AA0CA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,4DAA4D;AAC5D,MAAM,WAAW,aAAa;IAC5B,wFAAwF;IACxF,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC/B,wDAAwD;IACxD,GAAG,EAAE,MAAM,CAAC;IACZ,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,CAAC,CAAC,EAAE,MAAM,CAAC;IACX;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,4BAA4B;AAC5B,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,6BAA6B;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,+CAA+C;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,SAAS,CAAC,QAAQ,EAAE,YAAY,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CAClH;AAgDD;;;;;;;;;;GAUG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,aAAa,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,CAAC,CAmDjH;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE;IAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,EACjD,UAAU,EAAE,WAAW,CACrB,MAAM,EACN;IACE,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,IAAI,GAAG,KAAK,CAAC;CACpB,CACF,GACA,cAAc,EAAE,CAsBlB"}
|
package/dist/hnsw.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// HNSW (Hierarchical Navigable Small World) vector index for enquire-mcp.
|
|
2
|
+
//
|
|
3
|
+
// v2.13.0 — closes the "brute-force semantic search doesn't scale" gap. The
|
|
4
|
+
// existing path in `EmbedDb.search()` runs O(n) cosine over every embedded
|
|
5
|
+
// chunk per query (~5ms at 8K chunks, ~30ms at 50K, ~300ms at 500K, ~3s at
|
|
6
|
+
// 5M). HNSW is the IR-standard graph-based index that achieves O(log n)
|
|
7
|
+
// approximate nearest neighbor lookups — sub-10ms even at million-chunk
|
|
8
|
+
// scale, with recall@K ≥ 95% at default parameters (M=16, efConstruction=200).
|
|
9
|
+
//
|
|
10
|
+
// Architecture: in-memory rebuild on serve start.
|
|
11
|
+
//
|
|
12
|
+
// Why not persistent?
|
|
13
|
+
// • `hnswlib-wasm` writes through Emscripten's virtual FS; persisting
|
|
14
|
+
// to disk + restoring requires syncing the WASM FS to host disk. The
|
|
15
|
+
// plumbing isn't bad but it's another file to manage (WAL-style
|
|
16
|
+
// consistency: which version of .embed.db produced the .hnsw.bin?).
|
|
17
|
+
// • For typical vault scales (≤50K chunks), rebuild is ≤30s on serve
|
|
18
|
+
// start — tolerable as a one-time boot cost for a long-running server.
|
|
19
|
+
// • Persistence is tracked for v3.0+ when million-chunk vaults become
|
|
20
|
+
// a real use case. For now: simple in-memory keeps the surface clean.
|
|
21
|
+
//
|
|
22
|
+
// Native dep: `hnswlib-node@^3.0` (Node-N-API binding to the C++ hnswlib
|
|
23
|
+
// reference impl). Maintained by yoshoku since 2022, stable since v3.0
|
|
24
|
+
// (March 2024). Ships prebuilds for darwin-x64/arm64 + linux-x64/arm64
|
|
25
|
+
// + win32-x64; falls back to source build (requires C++ toolchain) on
|
|
26
|
+
// uncommon platforms. Lazy-loaded — same `optionalDependencies` pattern
|
|
27
|
+
// as tesseract.js / pdfjs-dist / @huggingface/transformers.
|
|
28
|
+
//
|
|
29
|
+
// Why not hnswlib-wasm? It exists (~340 KB pure-WASM) but its v0.8
|
|
30
|
+
// build is hardcoded for the browser environment (ENVIRONMENT_IS_WEB=
|
|
31
|
+
// true at compile time) and refuses to load under Node. hnswlib-node
|
|
32
|
+
// is the production-grade choice for server-side vault retrieval.
|
|
33
|
+
//
|
|
34
|
+
// Performance characteristics on M1 Pro (cosine space, dim=384):
|
|
35
|
+
// • Build: ~0.5ms per vector → 8K chunks ≈ 4s, 50K ≈ 25s, 500K ≈ 4min
|
|
36
|
+
// • Query: ~0.5-1ms per top-10 lookup, independent of corpus size
|
|
37
|
+
//
|
|
38
|
+
// Recall@10 vs brute-force on the same corpus is consistently ≥98% at
|
|
39
|
+
// default params. Users tuning for max recall can pass `--hnsw-ef-search`
|
|
40
|
+
// to widen the search beam (default 100; higher = more accurate,
|
|
41
|
+
// slower).
|
|
42
|
+
let cachedModule = null;
|
|
43
|
+
async function loadHnswlib() {
|
|
44
|
+
if (cachedModule)
|
|
45
|
+
return cachedModule;
|
|
46
|
+
try {
|
|
47
|
+
const mod = (await import("hnswlib-node"));
|
|
48
|
+
// hnswlib-node ships as CJS with a default export; ESM consumers get
|
|
49
|
+
// both `.default` and the named exports. Try both.
|
|
50
|
+
const lib = mod.default ?? mod;
|
|
51
|
+
if (typeof lib.HierarchicalNSW !== "function") {
|
|
52
|
+
throw new Error("hnswlib-node has no HierarchicalNSW export — package mismatch");
|
|
53
|
+
}
|
|
54
|
+
cachedModule = lib;
|
|
55
|
+
return cachedModule;
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
59
|
+
throw new Error("enquire: hnswlib-node (optional dependency) is not available. HNSW requires it. " +
|
|
60
|
+
`Install with: npm install hnswlib-node@^3 (or reinstall enquire-mcp without --omit=optional). ` +
|
|
61
|
+
`Underlying error: ${msg}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Build a fresh in-memory HNSW from labeled vectors.
|
|
66
|
+
*
|
|
67
|
+
* `vectors` must be L2-normalized — the cosine distance space treats
|
|
68
|
+
* inputs as already-unit-length, so unnormalized inputs produce wrong
|
|
69
|
+
* distances. The `EmbedDb` already L2-normalizes at insert time, so the
|
|
70
|
+
* usual call path (loadAllVectors → buildHnsw) is safe by construction.
|
|
71
|
+
*
|
|
72
|
+
* Throws if `dim` doesn't match any vector's length, if `maxElements`
|
|
73
|
+
* is less than the input count, or if `hnswlib-wasm` failed to load.
|
|
74
|
+
*/
|
|
75
|
+
export async function buildHnsw(vectors, opts) {
|
|
76
|
+
const dim = opts.dim;
|
|
77
|
+
if (vectors.length > opts.maxElements) {
|
|
78
|
+
throw new Error(`buildHnsw: vectors.length=${vectors.length} exceeds maxElements=${opts.maxElements}; pre-size the index`);
|
|
79
|
+
}
|
|
80
|
+
const m = opts.m ?? 16;
|
|
81
|
+
const efConstruction = opts.efConstruction ?? 200;
|
|
82
|
+
const seed = opts.seed ?? 100;
|
|
83
|
+
// Validate first — fail fast before pulling in the WASM module.
|
|
84
|
+
for (let i = 0; i < vectors.length; i++) {
|
|
85
|
+
const v = vectors[i];
|
|
86
|
+
if (!v)
|
|
87
|
+
continue;
|
|
88
|
+
if (v.vector.length !== dim) {
|
|
89
|
+
throw new Error(`buildHnsw: vector at index ${i} has dim ${v.vector.length}, expected ${dim}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const lib = await loadHnswlib();
|
|
93
|
+
const ctor = new lib.HierarchicalNSW("cosine", dim);
|
|
94
|
+
// Pre-size the index. `m=16` and `efConstruction=200` are HNSW defaults
|
|
95
|
+
// (Malkov & Yashunin, 2018) and produce ≥98% recall@10 vs brute-force on
|
|
96
|
+
// typical PKM corpora.
|
|
97
|
+
ctor.initIndex(Math.max(opts.maxElements, 1), m, efConstruction, seed);
|
|
98
|
+
for (let i = 0; i < vectors.length; i++) {
|
|
99
|
+
const v = vectors[i];
|
|
100
|
+
if (!v)
|
|
101
|
+
continue;
|
|
102
|
+
// hnswlib-node accepts plain number[] (it copies into its own C++
|
|
103
|
+
// buffer internally). Float32Array.from-via-Array.from would allocate
|
|
104
|
+
// an intermediate; we use a plain spread which is fast and explicit.
|
|
105
|
+
ctor.addPoint(Array.from(v.vector), v.label);
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
dim,
|
|
109
|
+
size: vectors.length,
|
|
110
|
+
searchKnn(queryVec, k, qOpts) {
|
|
111
|
+
if (queryVec.length !== dim) {
|
|
112
|
+
throw new Error(`HnswIndex.searchKnn: query dim ${queryVec.length} ≠ index dim ${dim}`);
|
|
113
|
+
}
|
|
114
|
+
// ef must be ≥ k; the underlying lib enforces this but we surface a
|
|
115
|
+
// friendlier error if the caller forgets.
|
|
116
|
+
const ef = Math.max(qOpts?.ef ?? 100, k);
|
|
117
|
+
ctor.setEf(ef);
|
|
118
|
+
const result = ctor.searchKnn(Array.from(queryVec), k, undefined);
|
|
119
|
+
return { labels: result.neighbors, distances: result.distances };
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Convert HNSW search results to EmbedSearchHit using a label → source-row
|
|
125
|
+
* lookup. The label was assigned by the caller at build time (typically
|
|
126
|
+
* `EmbedDb.getAllVectors()` returns rows with sequential integer labels);
|
|
127
|
+
* we just reverse the mapping. Distance → cosine similarity: cosine
|
|
128
|
+
* distance is `1 - cosine_similarity`, so we flip back here so callers
|
|
129
|
+
* can compare HNSW + brute-force scores apples-to-apples.
|
|
130
|
+
*/
|
|
131
|
+
export function hnswResultsToHits(result, rowByLabel) {
|
|
132
|
+
const hits = [];
|
|
133
|
+
for (let i = 0; i < result.labels.length; i++) {
|
|
134
|
+
const label = result.labels[i];
|
|
135
|
+
const distance = result.distances[i];
|
|
136
|
+
if (label === undefined || distance === undefined)
|
|
137
|
+
continue;
|
|
138
|
+
const row = rowByLabel.get(label);
|
|
139
|
+
if (!row)
|
|
140
|
+
continue; // race: row deleted between build and query — skip
|
|
141
|
+
// hnswlib-wasm cosine distance = 1 - cosine_similarity.
|
|
142
|
+
// Convert back so callers can compare against brute-force scores.
|
|
143
|
+
const score = 1 - distance;
|
|
144
|
+
hits.push({
|
|
145
|
+
rel_path: row.rel_path,
|
|
146
|
+
chunk_index: row.chunk_index,
|
|
147
|
+
line_start: row.line_start,
|
|
148
|
+
line_end: row.line_end,
|
|
149
|
+
text_preview: row.text_preview,
|
|
150
|
+
score,
|
|
151
|
+
kind: row.kind
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return hits;
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=hnsw.js.map
|
package/dist/hnsw.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hnsw.js","sourceRoot":"","sources":["../src/hnsw.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -30,6 +30,13 @@ export interface ServeOptions {
|
|
|
30
30
|
rerankerModel?: string;
|
|
31
31
|
/** v2.9.0 — how many top fused candidates to rerank (default 50). */
|
|
32
32
|
rerankerTopN?: string;
|
|
33
|
+
/** v2.13.0 — build an in-memory HNSW vector index on serve start.
|
|
34
|
+
* Off by default; rebuild cost ~25s for 50K chunks. Sub-10ms top-K
|
|
35
|
+
* per query thereafter, vs O(n) brute-force without it. Defers
|
|
36
|
+
* persistence to v3.0. */
|
|
37
|
+
useHnsw?: boolean;
|
|
38
|
+
/** v2.13.0 — HNSW search-time beam width (default 100; ≥k). */
|
|
39
|
+
hnswEf?: string;
|
|
33
40
|
}
|
|
34
41
|
declare function main(): Promise<void>;
|
|
35
42
|
/**
|
|
@@ -52,6 +59,26 @@ export interface ServerDeps {
|
|
|
52
59
|
warningTracker: {
|
|
53
60
|
printed: boolean;
|
|
54
61
|
};
|
|
62
|
+
/**
|
|
63
|
+
* v2.13.0 — opt-in HNSW vector index built in-memory on serve start
|
|
64
|
+
* from the embed-db rows. Sub-10ms top-K queries vs O(n) brute-force.
|
|
65
|
+
* `null` when `--use-hnsw` wasn't passed or the embed-db doesn't exist.
|
|
66
|
+
*/
|
|
67
|
+
hnswContext: {
|
|
68
|
+
/** The HNSW index. */
|
|
69
|
+
index: import("./hnsw.js").HnswIndex;
|
|
70
|
+
/** Map from HNSW label (= embeddings.id) to source row metadata. */
|
|
71
|
+
rowByLabel: Map<number, {
|
|
72
|
+
rel_path: string;
|
|
73
|
+
chunk_index: number;
|
|
74
|
+
line_start: number;
|
|
75
|
+
line_end: number;
|
|
76
|
+
text_preview: string;
|
|
77
|
+
kind: "md" | "pdf";
|
|
78
|
+
}>;
|
|
79
|
+
/** Search-time beam width override; falls back to module default if undefined. */
|
|
80
|
+
ef?: number;
|
|
81
|
+
} | null;
|
|
55
82
|
}
|
|
56
83
|
/**
|
|
57
84
|
* One-time bootstrap of the heavy deps (vault open + FTS5 sync + watcher).
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAIA,OAAO,EAAE,SAAS,EAAoB,MAAM,yCAAyC,CAAC;AAMtF,OAAO,EAAkC,QAAQ,EAAE,MAAM,WAAW,CAAC;AAyCrE,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAW5C,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;mCAE+B;IAC/B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;8EAC0E;IAC1E,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|