@claude-flow/cli 3.10.27 → 3.10.29
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/src/mcp-tools/agentdb-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/agentdb-tools.js +11 -6
- package/dist/src/mcp-tools/agentdb-tools.js.map +1 -1
- package/dist/src/mcp-tools/memory-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/memory-tools.js +52 -4
- package/dist/src/mcp-tools/memory-tools.js.map +1 -1
- package/dist/src/mcp-tools/neural-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/neural-tools.js +48 -11
- package/dist/src/mcp-tools/neural-tools.js.map +1 -1
- package/dist/src/memory/bge-embedder.d.ts +6 -0
- package/dist/src/memory/bge-embedder.d.ts.map +1 -1
- package/dist/src/memory/bge-embedder.js +14 -0
- package/dist/src/memory/bge-embedder.js.map +1 -1
- package/dist/src/memory/graph-edge-writer.d.ts +7 -1
- package/dist/src/memory/graph-edge-writer.d.ts.map +1 -1
- package/dist/src/memory/graph-edge-writer.js +29 -4
- package/dist/src/memory/graph-edge-writer.js.map +1 -1
- package/dist/src/memory/lucene-bm25.d.ts +19 -0
- package/dist/src/memory/lucene-bm25.d.ts.map +1 -0
- package/dist/src/memory/lucene-bm25.js +308 -0
- package/dist/src/memory/lucene-bm25.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -2
- package/scripts/run-beir-bge.mjs +18 -3
- package/scripts/run-beir-hybrid.mjs +31 -15
- package/scripts/run-beir-lucene-bm25.mjs +154 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.10.
|
|
3
|
+
"version": "3.10.29",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -121,7 +121,8 @@
|
|
|
121
121
|
"@ruvector/rvagent-wasm": "^0.1.0",
|
|
122
122
|
"@ruvector/sona": "^0.1.6",
|
|
123
123
|
"agentdb": "^3.0.0-alpha.16",
|
|
124
|
-
"agentic-flow": "^3.0.0-alpha.1"
|
|
124
|
+
"agentic-flow": "^3.0.0-alpha.1",
|
|
125
|
+
"ruvector": "^0.2.27"
|
|
125
126
|
},
|
|
126
127
|
"publishConfig": {
|
|
127
128
|
"access": "public",
|
package/scripts/run-beir-bge.mjs
CHANGED
|
@@ -62,6 +62,18 @@ const BASELINES_BY_DATASET = {
|
|
|
62
62
|
'BGE-large-v1.5 (pub)': 0.722,
|
|
63
63
|
'SBERT msmarco': 0.555,
|
|
64
64
|
},
|
|
65
|
+
arguana: {
|
|
66
|
+
'BM25 (Lucene)': 0.397,
|
|
67
|
+
'DocT5query': 0.349,
|
|
68
|
+
'TAS-B': 0.429,
|
|
69
|
+
'GenQ': 0.493,
|
|
70
|
+
'ColBERT': 0.233,
|
|
71
|
+
'Contriever': 0.379,
|
|
72
|
+
'GTR-XL': 0.439,
|
|
73
|
+
'SPLADE++': 0.521,
|
|
74
|
+
'BGE-large-v1.5 (pub)': 0.636,
|
|
75
|
+
'SBERT msmarco': 0.371,
|
|
76
|
+
},
|
|
65
77
|
};
|
|
66
78
|
|
|
67
79
|
// Auto-detect dataset from DATA_DIR path; default to nfcorpus baselines.
|
|
@@ -219,7 +231,9 @@ async function main() {
|
|
|
219
231
|
? [...qrels.keys()].slice(0, MAX_QUERIES)
|
|
220
232
|
: [...qrels.keys()];
|
|
221
233
|
|
|
222
|
-
|
|
234
|
+
// ADR-090: BGE_QUERY_PREFIX=1 enables BAAI's recommended query prefix.
|
|
235
|
+
const USE_QUERY_PREFIX = process.env.BGE_QUERY_PREFIX === '1';
|
|
236
|
+
console.log(`\nRunning ${evalQueryIds.length} queries${USE_QUERY_PREFIX ? ' (with BGE query prefix, ADR-090)' : ''}...`);
|
|
223
237
|
let nSum = 0, mSum = 0, r10Sum = 0, r100Sum = 0, n = 0;
|
|
224
238
|
// ADR-086 — save per-query metrics for paired bootstrap significance testing.
|
|
225
239
|
const perQuery = [];
|
|
@@ -227,8 +241,9 @@ async function main() {
|
|
|
227
241
|
for (const qid of evalQueryIds) {
|
|
228
242
|
const qtext = queriesById.get(qid);
|
|
229
243
|
if (!qtext) continue;
|
|
230
|
-
// BGE
|
|
231
|
-
|
|
244
|
+
// ADR-090: opt-in BGE query prefix per BAAI's docs (+0.009 nDCG@10 on
|
|
245
|
+
// NFCorpus dense-alone). Falls back to plain embed() if not enabled.
|
|
246
|
+
const qEmb = USE_QUERY_PREFIX && emb.embedQuery ? await emb.embedQuery(qtext) : await emb.embed(qtext);
|
|
232
247
|
const scores = new Array(docEmbeds.length);
|
|
233
248
|
for (let i = 0; i < docEmbeds.length; i++) scores[i] = { id: docIds[i], score: cosine(qEmb, docEmbeds[i]) };
|
|
234
249
|
scores.sort((a, b) => b.score - a.score);
|
|
@@ -41,6 +41,7 @@ const MAX_QUERIES = Number(process.env.MAX_QUERIES) || 0;
|
|
|
41
41
|
const BASELINES_BY_DATASET = {
|
|
42
42
|
nfcorpus: { 'BM25 (Lucene)': 0.325, 'DocT5query': 0.328, 'TAS-B': 0.319, 'GenQ': 0.319, 'ColBERT': 0.305, 'Contriever': 0.328, 'GTR-XL': 0.343, 'SPLADE++': 0.347, 'BGE-large-v1.5 (pub)': 0.380, 'SBERT msmarco': 0.272 },
|
|
43
43
|
scifact: { 'BM25 (Lucene)': 0.679, 'DocT5query': 0.675, 'TAS-B': 0.643, 'GenQ': 0.644, 'ColBERT': 0.671, 'Contriever': 0.677, 'GTR-XL': 0.662, 'SPLADE++': 0.704, 'BGE-large-v1.5 (pub)': 0.722, 'SBERT msmarco': 0.555 },
|
|
44
|
+
arguana: { 'BM25 (Lucene)': 0.397, 'DocT5query': 0.349, 'TAS-B': 0.429, 'GenQ': 0.493, 'ColBERT': 0.233, 'Contriever': 0.379, 'GTR-XL': 0.439, 'SPLADE++': 0.521, 'BGE-large-v1.5 (pub)': 0.636, 'SBERT msmarco': 0.371 },
|
|
44
45
|
};
|
|
45
46
|
function detectDataset(path) {
|
|
46
47
|
const p = path.toLowerCase();
|
|
@@ -92,15 +93,29 @@ async function main() {
|
|
|
92
93
|
const docEmbeds = cached.embeds;
|
|
93
94
|
console.log(`Loaded ${docIds.length} cached BGE embeddings`);
|
|
94
95
|
|
|
95
|
-
// BM25 setup —
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
96
|
+
// BM25 setup — USE_LUCENE_BM25=1 uses Porter+Lucene-stopword BM25 (matches
|
|
97
|
+
// published BEIR baselines); default is hybrid-retrieval's multi-field BM25.
|
|
98
|
+
const USE_LUCENE_BM25 = process.env.USE_LUCENE_BM25 === '1';
|
|
99
|
+
let bm25Score, tokenizeFn, titleDocs, textDocs, titleStats, textStats, luceneDocs, luceneStats;
|
|
100
|
+
if (USE_LUCENE_BM25) {
|
|
101
|
+
const { luceneTokenize, buildLuceneCorpusStats, luceneBM25 } = await import(join(CLI_ROOT, 'dist/src/memory/lucene-bm25.js'));
|
|
102
|
+
tokenizeFn = luceneTokenize;
|
|
103
|
+
luceneDocs = corpus.map((d) => luceneTokenize(`${d.title || ''} ${d.text || ''}`));
|
|
104
|
+
luceneStats = buildLuceneCorpusStats(luceneDocs);
|
|
105
|
+
bm25Score = (qTokens, idx) => luceneBM25(qTokens, luceneDocs[idx], luceneStats);
|
|
106
|
+
console.log(`BM25: Lucene-style (Porter stem + Lucene stopwords + length norm) — ADR-088`);
|
|
107
|
+
} else {
|
|
108
|
+
const { tokenize, buildCorpusStats, multiFieldBM25 } = await import(join(CLI_ROOT, 'dist/src/memory/hybrid-retrieval.js'));
|
|
109
|
+
tokenizeFn = tokenize;
|
|
110
|
+
titleDocs = corpus.map((d) => tokenize(d.title || ''));
|
|
111
|
+
textDocs = corpus.map((d) => tokenize(d.text || ''));
|
|
112
|
+
titleStats = buildCorpusStats(titleDocs);
|
|
113
|
+
textStats = buildCorpusStats(textDocs);
|
|
114
|
+
const SUBJECT_WEIGHT = Number(process.env.SUBJECT_WEIGHT ?? 1.0);
|
|
115
|
+
const BODY_WEIGHT = Number(process.env.BODY_WEIGHT ?? 1.0);
|
|
116
|
+
bm25Score = (qTokens, idx) => multiFieldBM25(qTokens, titleDocs[idx], textDocs[idx], titleStats, textStats, SUBJECT_WEIGHT, BODY_WEIGHT);
|
|
117
|
+
console.log(`BM25: multi-field (title sw=${SUBJECT_WEIGHT}, text bw=${BODY_WEIGHT})`);
|
|
118
|
+
}
|
|
104
119
|
|
|
105
120
|
// Optional cross-encoder reranker.
|
|
106
121
|
let crossEncoder = null;
|
|
@@ -130,18 +145,19 @@ async function main() {
|
|
|
130
145
|
const qtext = queriesById.get(qid);
|
|
131
146
|
if (!qtext) continue;
|
|
132
147
|
|
|
133
|
-
// §1 — dense BGE retrieval
|
|
134
|
-
const qEmb =
|
|
148
|
+
// §1 — dense BGE retrieval (ADR-090: opt-in query prefix when BGE_QUERY_PREFIX=1)
|
|
149
|
+
const qEmb = (process.env.BGE_QUERY_PREFIX === '1' && emb.embedQuery)
|
|
150
|
+
? await emb.embedQuery(qtext)
|
|
151
|
+
: await emb.embed(qtext);
|
|
135
152
|
const denseScored = new Array(docEmbeds.length);
|
|
136
153
|
for (let i = 0; i < docEmbeds.length; i++) denseScored[i] = { id: docIds[i], score: cosine(qEmb, docEmbeds[i]) };
|
|
137
154
|
denseScored.sort((a, b) => b.score - a.score);
|
|
138
155
|
|
|
139
|
-
// §2 — BM25 retrieval (
|
|
140
|
-
const qTokens =
|
|
156
|
+
// §2 — BM25 retrieval (Lucene or multi-field depending on USE_LUCENE_BM25)
|
|
157
|
+
const qTokens = tokenizeFn(qtext);
|
|
141
158
|
const bm25Scored = new Array(corpus.length);
|
|
142
159
|
for (let i = 0; i < corpus.length; i++) {
|
|
143
|
-
|
|
144
|
-
bm25Scored[i] = { id: corpusIdxToId[i], score: s };
|
|
160
|
+
bm25Scored[i] = { id: corpusIdxToId[i], score: bm25Score(qTokens, i) };
|
|
145
161
|
}
|
|
146
162
|
bm25Scored.sort((a, b) => b.score - a.score);
|
|
147
163
|
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// run-beir-lucene-bm25.mjs — test our Lucene-style BM25 against the
|
|
3
|
+
// published BEIR BM25 baselines. Also runs RRF with Lucene-BM25 + dense
|
|
4
|
+
// to see if the stronger BM25 closes the asymmetric-strength gap that
|
|
5
|
+
// broke RRF in ADR-087.
|
|
6
|
+
//
|
|
7
|
+
// Usage:
|
|
8
|
+
// cd /tmp/beir-nfcorpus
|
|
9
|
+
// node /path/to/scripts/run-beir-lucene-bm25.mjs
|
|
10
|
+
// BEIR_DATA_DIR=/tmp/beir-scifact/scifact node /path/to/scripts/run-beir-lucene-bm25.mjs
|
|
11
|
+
|
|
12
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
import { dirname, join, resolve } from 'node:path';
|
|
15
|
+
import { performance } from 'node:perf_hooks';
|
|
16
|
+
|
|
17
|
+
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const CLI_ROOT = resolve(SCRIPT_DIR, '..');
|
|
19
|
+
const RUFLO_ROOT = resolve(SCRIPT_DIR, '../../../..');
|
|
20
|
+
const RUNS_DIR = join(RUFLO_ROOT, 'docs', 'benchmarks', 'runs');
|
|
21
|
+
|
|
22
|
+
const DATA_DIR = process.env.BEIR_DATA_DIR || '/tmp/beir-nfcorpus/nfcorpus';
|
|
23
|
+
const BGE_MODEL = 'Xenova/bge-base-en-v1.5';
|
|
24
|
+
const CACHE_DIR = join(dirname(DATA_DIR), 'bge-cache');
|
|
25
|
+
|
|
26
|
+
const BASELINES = {
|
|
27
|
+
nfcorpus: { BM25: 0.325, 'BGE-large-pub': 0.380, 'SPLADE++': 0.347 },
|
|
28
|
+
scifact: { BM25: 0.679, 'BGE-large-pub': 0.722, 'SPLADE++': 0.704 },
|
|
29
|
+
};
|
|
30
|
+
function detect(p) { const x = p.toLowerCase(); return Object.keys(BASELINES).find(d => x.includes(d)) || 'nfcorpus'; }
|
|
31
|
+
|
|
32
|
+
function dcg(rels, k) { let s = 0; for (let i = 0; i < Math.min(rels.length, k); i++) s += (Math.pow(2, rels[i]) - 1) / Math.log2(i + 2); return s; }
|
|
33
|
+
function ndcg(retrieved, qrels, k) { const rels = retrieved.slice(0, k).map((id) => qrels.get(id) ?? 0); const ideal = [...qrels.values()].sort((a, b) => b - a).slice(0, k); const idcg = dcg(ideal, k); return idcg > 0 ? dcg(rels, k) / idcg : 0; }
|
|
34
|
+
function recall(retrieved, qrels, k) { const tot = [...qrels.values()].filter((v) => v > 0).length; if (tot === 0) return 0; let h = 0; for (let i = 0; i < Math.min(retrieved.length, k); i++) if ((qrels.get(retrieved[i]) ?? 0) > 0) h++; return h / tot; }
|
|
35
|
+
function cosine(a, b) { let s = 0; for (let i = 0; i < a.length; i++) s += a[i] * b[i]; return s; }
|
|
36
|
+
|
|
37
|
+
function loadJsonl(p) { return readFileSync(p, 'utf-8').split('\n').filter(Boolean).map((l) => JSON.parse(l)); }
|
|
38
|
+
function loadQrels(p) { const q = new Map(); const lines = readFileSync(p, 'utf-8').split('\n'); for (let i = 1; i < lines.length; i++) { if (!lines[i].trim()) continue; const [qid, did, s] = lines[i].split('\t'); if (!q.has(qid)) q.set(qid, new Map()); q.get(qid).set(did, Number(s)); } return q; }
|
|
39
|
+
|
|
40
|
+
function cachePath(s) { const safe = BGE_MODEL.replace(/\//g, '_'); return join(CACHE_DIR, `${safe}.${s}`); }
|
|
41
|
+
function loadEmbeddings(dim) {
|
|
42
|
+
if (!existsSync(cachePath('ids')) || !existsSync(cachePath('f32'))) return null;
|
|
43
|
+
const ids = readFileSync(cachePath('ids'), 'utf-8').split('\n').filter(Boolean);
|
|
44
|
+
const raw = readFileSync(cachePath('f32'));
|
|
45
|
+
const buf = new Float32Array(raw.buffer, raw.byteOffset, raw.length / 4);
|
|
46
|
+
return { ids, embeds: ids.map((_, i) => buf.slice(i * dim, (i + 1) * dim)) };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function rrfFuse(rankings, k = 60, weights) {
|
|
50
|
+
const scores = new Map();
|
|
51
|
+
rankings.forEach((ranking, sIdx) => {
|
|
52
|
+
const w = weights?.[sIdx] ?? 1;
|
|
53
|
+
ranking.forEach((doc, rankIdx) => {
|
|
54
|
+
const rank = rankIdx + 1;
|
|
55
|
+
scores.set(doc.id, (scores.get(doc.id) ?? 0) + w / (k + rank));
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
return [...scores.entries()].map(([id, score]) => ({ id, score })).sort((a, b) => b.score - a.score);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function main() {
|
|
62
|
+
const dataset = detect(DATA_DIR);
|
|
63
|
+
console.log(`# Lucene-style BM25 + RRF on BEIR ${dataset} (ADR-088)`);
|
|
64
|
+
console.log(`Data: ${DATA_DIR}\n`);
|
|
65
|
+
|
|
66
|
+
const corpus = loadJsonl(join(DATA_DIR, 'corpus.jsonl'));
|
|
67
|
+
const queries = loadJsonl(join(DATA_DIR, 'queries.jsonl'));
|
|
68
|
+
const qrels = loadQrels(join(DATA_DIR, 'qrels/test.tsv'));
|
|
69
|
+
console.log(`Corpus: ${corpus.length} · test qrels: ${qrels.size}`);
|
|
70
|
+
|
|
71
|
+
// Lucene BM25
|
|
72
|
+
const { luceneTokenize, buildLuceneCorpusStats, luceneBM25 } = await import(join(CLI_ROOT, 'dist/src/memory/lucene-bm25.js'));
|
|
73
|
+
console.log('\nTokenising corpus (Lucene-style: Porter stem + Lucene stopwords + length norm)...');
|
|
74
|
+
const tIngest = performance.now();
|
|
75
|
+
const docs = corpus.map((d) => luceneTokenize(`${d.title || ''} ${d.text || ''}`));
|
|
76
|
+
const stats = buildLuceneCorpusStats(docs);
|
|
77
|
+
console.log(`Tokenized + stats built in ${((performance.now() - tIngest) / 1000).toFixed(1)}s. avgDocLen=${stats.avgDocLen.toFixed(0)}, vocab=${stats.idf.size}`);
|
|
78
|
+
|
|
79
|
+
// BGE dense (from cache)
|
|
80
|
+
const bge = await import(join(CLI_ROOT, 'dist/src/memory/bge-embedder.js'));
|
|
81
|
+
const emb = await bge.getBgeEmbedder(BGE_MODEL);
|
|
82
|
+
if (!emb) { console.error('BGE failed'); process.exit(1); }
|
|
83
|
+
const cached = loadEmbeddings(emb.dim());
|
|
84
|
+
if (!cached) { console.error(`No cached embeddings. Run run-beir-bge.mjs first.`); process.exit(2); }
|
|
85
|
+
const { ids: docIds, embeds: docEmbeds } = cached;
|
|
86
|
+
const corpusIdxToId = corpus.map((d) => d._id);
|
|
87
|
+
|
|
88
|
+
// Run all configs
|
|
89
|
+
const queriesById = new Map(queries.map((q) => [q._id, q.text]));
|
|
90
|
+
const evalQids = [...qrels.keys()];
|
|
91
|
+
console.log(`\nRunning ${evalQids.length} queries through 4 configs (dense, Lucene-BM25, RRF, RRF-tuned)...\n`);
|
|
92
|
+
|
|
93
|
+
let n = 0;
|
|
94
|
+
const results = {
|
|
95
|
+
dense: { n: 0, ndcg: 0, r10: 0, r100: 0 },
|
|
96
|
+
bm25: { n: 0, ndcg: 0, r10: 0, r100: 0 },
|
|
97
|
+
rrf60: { n: 0, ndcg: 0, r10: 0, r100: 0 },
|
|
98
|
+
rrf30: { n: 0, ndcg: 0, r10: 0, r100: 0 },
|
|
99
|
+
};
|
|
100
|
+
const tQ = performance.now();
|
|
101
|
+
for (const qid of evalQids) {
|
|
102
|
+
const qtext = queriesById.get(qid);
|
|
103
|
+
if (!qtext) continue;
|
|
104
|
+
n++;
|
|
105
|
+
|
|
106
|
+
// Dense
|
|
107
|
+
const qEmb = await emb.embed(qtext);
|
|
108
|
+
const dense = docIds.map((id, i) => ({ id, score: cosine(qEmb, docEmbeds[i]) })).sort((a, b) => b.score - a.score).slice(0, 1000);
|
|
109
|
+
|
|
110
|
+
// BM25
|
|
111
|
+
const qTokens = luceneTokenize(qtext);
|
|
112
|
+
const bm25 = corpus.map((d, i) => ({ id: corpusIdxToId[i], score: luceneBM25(qTokens, docs[i], stats) })).filter((d) => d.score > 0).sort((a, b) => b.score - a.score).slice(0, 1000);
|
|
113
|
+
|
|
114
|
+
// RRF k=60 and k=30
|
|
115
|
+
const rrf60 = rrfFuse([dense, bm25], 60);
|
|
116
|
+
const rrf30 = rrfFuse([dense, bm25], 30);
|
|
117
|
+
|
|
118
|
+
const qmap = qrels.get(qid);
|
|
119
|
+
for (const [key, top] of [['dense', dense], ['bm25', bm25], ['rrf60', rrf60], ['rrf30', rrf30]]) {
|
|
120
|
+
const top100 = top.slice(0, 100).map((s) => s.id);
|
|
121
|
+
results[key].n++;
|
|
122
|
+
results[key].ndcg += ndcg(top100, qmap, 10);
|
|
123
|
+
results[key].r10 += recall(top100, qmap, 10);
|
|
124
|
+
results[key].r100 += recall(top100, qmap, 100);
|
|
125
|
+
}
|
|
126
|
+
if (n % 50 === 0) console.log(` ${n}/${evalQids.length} dense=${(results.dense.ndcg / results.dense.n).toFixed(3)} bm25=${(results.bm25.ndcg / results.bm25.n).toFixed(3)} rrf60=${(results.rrf60.ndcg / results.rrf60.n).toFixed(3)}`);
|
|
127
|
+
}
|
|
128
|
+
const elapsed = (performance.now() - tQ) / 1000;
|
|
129
|
+
|
|
130
|
+
console.log(`\nDone in ${elapsed.toFixed(0)}s.\n`);
|
|
131
|
+
console.log('Config | nDCG@10 R@10 R@100 vs published-BM25');
|
|
132
|
+
console.log('---------------|---------------------------------');
|
|
133
|
+
for (const [key, r] of Object.entries(results)) {
|
|
134
|
+
const score = r.ndcg / r.n;
|
|
135
|
+
const delta = score - BASELINES[dataset].BM25;
|
|
136
|
+
console.log(`${key.padEnd(14)} | ${score.toFixed(4)} ${(r.r10 / r.n).toFixed(3)} ${(r.r100 / r.n).toFixed(3)} ${(delta >= 0 ? '+' : '') + delta.toFixed(4)}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const summary = {
|
|
140
|
+
runAt: new Date().toISOString(),
|
|
141
|
+
benchmark: `beir-${dataset}-lucene-bm25`,
|
|
142
|
+
dataset, model: BGE_MODEL, queries: n,
|
|
143
|
+
results: Object.fromEntries(Object.entries(results).map(([k, v]) => [k, { ndcg10: v.ndcg / v.n, recall10: v.r10 / v.n, recall100: v.r100 / v.n }])),
|
|
144
|
+
baselines: BASELINES[dataset],
|
|
145
|
+
};
|
|
146
|
+
mkdirSync(RUNS_DIR, { recursive: true });
|
|
147
|
+
const stamp = summary.runAt.replace(/[:.]/g, '-');
|
|
148
|
+
writeFileSync(join(RUNS_DIR, `beir-${dataset}-lucene-bm25-${stamp}.json`), JSON.stringify(summary, null, 2));
|
|
149
|
+
writeFileSync(join(RUNS_DIR, `beir-${dataset}-lucene-bm25-latest.json`), JSON.stringify(summary, null, 2));
|
|
150
|
+
console.log(`\nWrote ${join(RUNS_DIR, `beir-${dataset}-lucene-bm25-${stamp}.json`)}`);
|
|
151
|
+
process.exit(0);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
main().catch((err) => { console.error(err); process.exit(1); });
|