@jungjaehoon/mama-core 1.9.0 → 2.0.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/dist/agent-graph/alias-write.js +3 -0
- package/dist/agent-graph/entity-resolve.js +3 -7
- package/dist/agent-graph/graph-query.js +5 -4
- package/dist/agent-graph/types.d.ts +30 -0
- package/dist/cases/store.d.ts +0 -4
- package/dist/cases/store.js +0 -66
- package/dist/cases/wiki-page-index.d.ts +0 -13
- package/dist/cases/wiki-page-index.js +0 -153
- package/dist/connectors/event-index.d.ts +1 -22
- package/dist/connectors/event-index.js +0 -157
- package/dist/context-compile/channel-grant.d.ts +46 -0
- package/dist/context-compile/channel-grant.js +66 -0
- package/dist/context-compile/compiler.js +5 -2
- package/dist/context-compile/index.d.ts +1 -0
- package/dist/context-compile/index.js +1 -0
- package/dist/context-compile/packet-store.d.ts +0 -1
- package/dist/context-compile/packet-store.js +0 -16
- package/dist/context-compile/source-readers.d.ts +1 -0
- package/dist/context-compile/source-readers.js +156 -23
- package/dist/context-compile/types.d.ts +17 -0
- package/dist/context-compile/visibility.js +28 -4
- package/dist/db-adapter/node-sqlite-adapter.js +4 -0
- package/dist/db-manager.d.ts +0 -13
- package/dist/db-manager.js +0 -66
- package/dist/decision-formatter.d.ts +0 -11
- package/dist/decision-formatter.js +0 -122
- package/dist/decision-tracker.d.ts +0 -12
- package/dist/decision-tracker.js +0 -50
- package/dist/edges/ref-validation.d.ts +1 -2
- package/dist/edges/ref-validation.js +33 -13
- package/dist/edges/store.d.ts +1 -3
- package/dist/edges/store.js +0 -214
- package/dist/edges/types.d.ts +10 -0
- package/dist/embedding-client.d.ts +0 -17
- package/dist/embedding-client.js +0 -80
- package/dist/embedding-server/mobile/auth.d.ts +1 -10
- package/dist/embedding-server/mobile/auth.js +0 -15
- package/dist/embeddings.d.ts +0 -12
- package/dist/embeddings.js +0 -68
- package/dist/entities/audit-metrics.d.ts +0 -5
- package/dist/entities/audit-metrics.js +0 -4
- package/dist/entities/candidate-generator.d.ts +1 -2
- package/dist/entities/candidate-generator.js +0 -412
- package/dist/entities/entity-list.d.ts +0 -1
- package/dist/entities/entity-list.js +0 -96
- package/dist/entities/entity-orphan-list.d.ts +0 -9
- package/dist/entities/entity-orphan-list.js +0 -88
- package/dist/entities/entity-search.d.ts +0 -1
- package/dist/entities/entity-search.js +0 -147
- package/dist/entities/errors.d.ts +0 -10
- package/dist/entities/errors.js +1 -25
- package/dist/entities/lineage-store.d.ts +0 -23
- package/dist/entities/lineage-store.js +0 -104
- package/dist/entities/policy-store.d.ts +0 -9
- package/dist/entities/policy-store.js +0 -188
- package/dist/entities/provenance-query.d.ts +0 -1
- package/dist/entities/provenance-query.js +0 -164
- package/dist/entities/resolution-engine.d.ts +1 -8
- package/dist/entities/resolution-engine.js +0 -66
- package/dist/entities/rollback-preview.d.ts +0 -4
- package/dist/entities/rollback-preview.js +0 -232
- package/dist/entities/store.d.ts +1 -10
- package/dist/entities/store.js +0 -49
- package/dist/index.d.ts +15 -31
- package/dist/index.js +4 -46
- package/dist/mama-api.d.ts +19 -1
- package/dist/mama-api.js +97 -2
- package/dist/memory/api.d.ts +8 -0
- package/dist/memory/api.js +55 -2
- package/dist/memory/event-store.d.ts +0 -1
- package/dist/memory/event-store.js +0 -15
- package/dist/memory/finding-store.d.ts +0 -1
- package/dist/memory/finding-store.js +0 -12
- package/dist/memory/scope-backfill.d.ts +0 -3
- package/dist/memory/scope-backfill.js +0 -74
- package/dist/memory/scope-store.d.ts +0 -1
- package/dist/memory/scope-store.js +0 -4
- package/dist/memory/truth-store.d.ts +0 -3
- package/dist/memory/truth-store.js +0 -22
- package/dist/memory/types.d.ts +0 -1
- package/dist/memory/types.js +0 -13
- package/dist/memory-store.d.ts +0 -16
- package/dist/memory-store.js +0 -51
- package/dist/ollama-client.d.ts +0 -9
- package/dist/ollama-client.js +0 -125
- package/dist/outcome-tracker.d.ts +0 -14
- package/dist/outcome-tracker.js +0 -44
- package/dist/progress-indicator.d.ts +0 -4
- package/dist/progress-indicator.js +0 -7
- package/dist/provenance/source-ref.d.ts +0 -9
- package/dist/provenance/source-ref.js +0 -145
- package/dist/query-intent.d.ts +0 -4
- package/dist/query-intent.js +0 -30
- package/dist/relevance-scorer.d.ts +0 -9
- package/dist/relevance-scorer.js +0 -81
- package/dist/search/feedback-store.d.ts +0 -1
- package/dist/search/feedback-store.js +0 -4
- package/dist/search/ranker-rescore.d.ts +0 -3
- package/dist/search/ranker-rescore.js +0 -27
- package/dist/search/ranker-trainer.d.ts +0 -8
- package/dist/search/ranker-trainer.js +0 -376
- package/dist/tier-validator.d.ts +0 -12
- package/dist/tier-validator.js +0 -56
- package/package.json +1 -10
- package/dist/cases/case-links.d.ts +0 -116
- package/dist/cases/case-links.js +0 -794
- package/dist/cases/composition-overrides.d.ts +0 -90
- package/dist/cases/composition-overrides.js +0 -724
- package/dist/cases/corrections.d.ts +0 -156
- package/dist/cases/corrections.js +0 -1045
- package/dist/cases/freshness.d.ts +0 -57
- package/dist/cases/freshness.js +0 -283
- package/dist/cases/live-state.d.ts +0 -48
- package/dist/cases/live-state.js +0 -256
- package/dist/cases/membership-explain.d.ts +0 -56
- package/dist/cases/membership-explain.js +0 -140
- package/dist/cases/membership-matcher.d.ts +0 -23
- package/dist/cases/membership-matcher.js +0 -193
- package/dist/cases/merge-split.d.ts +0 -61
- package/dist/cases/merge-split.js +0 -368
- package/dist/cases/role-inference.d.ts +0 -10
- package/dist/cases/role-inference.js +0 -85
- package/dist/cases/sqlite-transaction.d.ts +0 -10
- package/dist/cases/sqlite-transaction.js +0 -62
- package/dist/cases/target-ref.d.ts +0 -25
- package/dist/cases/target-ref.js +0 -22
- package/dist/cases/tombstone-sweeper.d.ts +0 -13
- package/dist/cases/tombstone-sweeper.js +0 -128
- package/dist/entities/entity-impact.d.ts +0 -41
- package/dist/entities/entity-impact.js +0 -140
- package/dist/entities/exact-merge-backfill.d.ts +0 -11
- package/dist/entities/exact-merge-backfill.js +0 -161
- package/dist/entities/lineage-backfill.d.ts +0 -12
- package/dist/entities/lineage-backfill.js +0 -101
- package/dist/memory-inject.d.ts +0 -24
- package/dist/memory-inject.js +0 -116
|
@@ -1,29 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.featureSetVersion = exports.SEARCH_RANKER_FEATURE_SET_VERSION = exports.RANKER_QUALITY_FIXTURES = void 0;
|
|
4
|
-
exports.trainOfflineRanker = trainOfflineRanker;
|
|
5
4
|
exports.scoreWithRankerModel = scoreWithRankerModel;
|
|
6
|
-
exports.evaluateAgainstBaselines = evaluateAgainstBaselines;
|
|
7
|
-
exports.insertRankerModelVersion = insertRankerModelVersion;
|
|
8
|
-
exports.activateRankerModel = activateRankerModel;
|
|
9
5
|
exports.ndcgAtK = ndcgAtK;
|
|
10
6
|
exports.mrr = mrr;
|
|
11
|
-
exports.seedQualityFixtureFeedback = seedQualityFixtureFeedback;
|
|
12
|
-
const node_crypto_1 = require("node:crypto");
|
|
13
|
-
const feedback_store_js_1 = require("./feedback-store.js");
|
|
14
7
|
const ranker_features_js_1 = require("./ranker-features.js");
|
|
15
8
|
Object.defineProperty(exports, "featureSetVersion", { enumerable: true, get: function () { return ranker_features_js_1.featureSetVersion; } });
|
|
16
9
|
Object.defineProperty(exports, "SEARCH_RANKER_FEATURE_SET_VERSION", { enumerable: true, get: function () { return ranker_features_js_1.SEARCH_RANKER_FEATURE_SET_VERSION; } });
|
|
17
|
-
const question_type_js_1 = require("./question-type.js");
|
|
18
|
-
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
19
|
-
const LEARNING_RATE = 0.05;
|
|
20
|
-
const MINI_BATCH_SIZE = 32;
|
|
21
|
-
const MAX_EPOCHS = 500;
|
|
22
|
-
const EARLY_STOP_DELTA = 1e-5;
|
|
23
|
-
const MIN_NEUTRAL_PER_QUERY = 3;
|
|
24
10
|
// 0.01 is a small but meaningful margin across 8 fixture queries: it blocks
|
|
25
11
|
// tie/noise wins without demanding a large jump on a tiny evaluation set.
|
|
26
|
-
const RANKER_QUALITY_MARGIN = 0.01;
|
|
27
12
|
function seededShuffle(items, seed) {
|
|
28
13
|
const shuffled = [...items];
|
|
29
14
|
let state = seed >>> 0;
|
|
@@ -457,105 +442,6 @@ exports.RANKER_QUALITY_FIXTURES = RAW_RANKER_QUALITY_FIXTURES.map((fixture) => (
|
|
|
457
442
|
...fixture,
|
|
458
443
|
candidates: reorderFixtureCandidates(fixture.query, fixture.candidates),
|
|
459
444
|
}));
|
|
460
|
-
function asSourceForFeedback(sourceType) {
|
|
461
|
-
switch (sourceType) {
|
|
462
|
-
case 'wiki_page':
|
|
463
|
-
return 'wiki_page';
|
|
464
|
-
case 'case':
|
|
465
|
-
return 'case';
|
|
466
|
-
case 'connector_event':
|
|
467
|
-
return 'connector_event';
|
|
468
|
-
case 'checkpoint':
|
|
469
|
-
return 'checkpoint';
|
|
470
|
-
default:
|
|
471
|
-
return 'decision';
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
function parseDate(input, field) {
|
|
475
|
-
const date = new Date(input);
|
|
476
|
-
if (Number.isNaN(date.getTime())) {
|
|
477
|
-
throw new Error(`${field} must be a valid ISO timestamp`);
|
|
478
|
-
}
|
|
479
|
-
return date;
|
|
480
|
-
}
|
|
481
|
-
function iso(date) {
|
|
482
|
-
return date.toISOString();
|
|
483
|
-
}
|
|
484
|
-
function effectiveWindow(input) {
|
|
485
|
-
const retentionDays = (0, feedback_store_js_1.getFeedbackRetentionDays)(input.adapter);
|
|
486
|
-
const now = input.now ?? new Date();
|
|
487
|
-
const retentionCutoff = new Date(now.getTime() - retentionDays * MS_PER_DAY);
|
|
488
|
-
const until = input.until ? parseDate(input.until, 'until') : now;
|
|
489
|
-
const since = input.since ? parseDate(input.since, 'since') : retentionCutoff;
|
|
490
|
-
if (since.getTime() > until.getTime()) {
|
|
491
|
-
throw new Error('since must be before until');
|
|
492
|
-
}
|
|
493
|
-
const retentionWarning = Boolean(input.since && since.getTime() < retentionCutoff.getTime());
|
|
494
|
-
if (retentionWarning) {
|
|
495
|
-
console.warn(JSON.stringify({
|
|
496
|
-
event: 'training_window_exceeds_retention',
|
|
497
|
-
since: iso(since),
|
|
498
|
-
retention_cutoff_at: iso(retentionCutoff),
|
|
499
|
-
}));
|
|
500
|
-
}
|
|
501
|
-
return {
|
|
502
|
-
since: iso(since),
|
|
503
|
-
until: iso(until),
|
|
504
|
-
retention_cutoff_at: iso(retentionCutoff),
|
|
505
|
-
retention_days_at_train_time: retentionDays,
|
|
506
|
-
retention_warning: retentionWarning,
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
function labelForKind(kind) {
|
|
510
|
-
if (kind === 'accept' || kind === 'click') {
|
|
511
|
-
return 1;
|
|
512
|
-
}
|
|
513
|
-
if (kind === 'reject' || kind === 'hide' || kind === 'shown') {
|
|
514
|
-
return 0;
|
|
515
|
-
}
|
|
516
|
-
return null;
|
|
517
|
-
}
|
|
518
|
-
function rowToExample(row) {
|
|
519
|
-
const label = labelForKind(row.feedback_kind);
|
|
520
|
-
if (label === null) {
|
|
521
|
-
return null;
|
|
522
|
-
}
|
|
523
|
-
const features = (0, ranker_features_js_1.serializeFeatures)((0, ranker_features_js_1.extractFeatures)({
|
|
524
|
-
...row,
|
|
525
|
-
source_type: row.result_source_type,
|
|
526
|
-
rank_position: row.rank_position,
|
|
527
|
-
}, row.query, { question_type: row.question_type }));
|
|
528
|
-
return {
|
|
529
|
-
query: row.query,
|
|
530
|
-
question_type: row.question_type,
|
|
531
|
-
features,
|
|
532
|
-
label,
|
|
533
|
-
feedback_kind: row.feedback_kind,
|
|
534
|
-
created_at: row.created_at,
|
|
535
|
-
feedback_id: row.feedback_id,
|
|
536
|
-
};
|
|
537
|
-
}
|
|
538
|
-
function capNeutralRows(rows) {
|
|
539
|
-
const byQuery = new Map();
|
|
540
|
-
for (const row of rows) {
|
|
541
|
-
const key = row.query_hash_hex || row.query;
|
|
542
|
-
const current = byQuery.get(key) ?? [];
|
|
543
|
-
current.push(row);
|
|
544
|
-
byQuery.set(key, current);
|
|
545
|
-
}
|
|
546
|
-
const capped = [];
|
|
547
|
-
for (const groupRows of byQuery.values()) {
|
|
548
|
-
const explicit = groupRows.filter((row) => row.feedback_kind !== 'shown');
|
|
549
|
-
const shown = groupRows.filter((row) => row.feedback_kind === 'shown');
|
|
550
|
-
const neutralLimit = Math.max(explicit.length * 3, MIN_NEUTRAL_PER_QUERY);
|
|
551
|
-
capped.push(...explicit, ...shown.slice(0, neutralLimit));
|
|
552
|
-
}
|
|
553
|
-
capped.sort((left, right) => {
|
|
554
|
-
const byCreatedAt = left.created_at.localeCompare(right.created_at);
|
|
555
|
-
return byCreatedAt !== 0 ? byCreatedAt : left.feedback_id.localeCompare(right.feedback_id);
|
|
556
|
-
});
|
|
557
|
-
return capped;
|
|
558
|
-
}
|
|
559
445
|
function sigmoid(value) {
|
|
560
446
|
if (value >= 35) {
|
|
561
447
|
return 1;
|
|
@@ -573,254 +459,11 @@ function dot(left, right) {
|
|
|
573
459
|
}
|
|
574
460
|
return total;
|
|
575
461
|
}
|
|
576
|
-
function hasBothLabels(examples) {
|
|
577
|
-
const labels = new Set(examples.map((example) => example.label));
|
|
578
|
-
return labels.has(0) && labels.has(1);
|
|
579
|
-
}
|
|
580
|
-
function trainWeights(examples, featureCount) {
|
|
581
|
-
const coefficients = new Array(featureCount).fill(0);
|
|
582
|
-
let intercept = 0;
|
|
583
|
-
if (examples.length === 0 || !hasBothLabels(examples)) {
|
|
584
|
-
return { coefficients, intercept };
|
|
585
|
-
}
|
|
586
|
-
for (let epoch = 0; epoch < MAX_EPOCHS; epoch += 1) {
|
|
587
|
-
const shuffledExamples = seededShuffle(examples, epoch + 1);
|
|
588
|
-
let epochDelta = 0;
|
|
589
|
-
let batchCount = 0;
|
|
590
|
-
for (let batchStart = 0; batchStart < shuffledExamples.length; batchStart += MINI_BATCH_SIZE) {
|
|
591
|
-
const batch = shuffledExamples.slice(batchStart, batchStart + MINI_BATCH_SIZE);
|
|
592
|
-
const gradients = new Array(featureCount).fill(0);
|
|
593
|
-
let interceptGradient = 0;
|
|
594
|
-
for (const example of batch) {
|
|
595
|
-
const prediction = sigmoid(dot(coefficients, example.features) + intercept);
|
|
596
|
-
const error = prediction - example.label;
|
|
597
|
-
for (let index = 0; index < featureCount; index += 1) {
|
|
598
|
-
gradients[index] += error * example.features[index];
|
|
599
|
-
}
|
|
600
|
-
interceptGradient += error;
|
|
601
|
-
}
|
|
602
|
-
let delta = 0;
|
|
603
|
-
for (let index = 0; index < featureCount; index += 1) {
|
|
604
|
-
const update = (LEARNING_RATE * gradients[index]) / batch.length;
|
|
605
|
-
coefficients[index] -= update;
|
|
606
|
-
delta += Math.abs(update);
|
|
607
|
-
}
|
|
608
|
-
const interceptUpdate = (LEARNING_RATE * interceptGradient) / batch.length;
|
|
609
|
-
intercept -= interceptUpdate;
|
|
610
|
-
delta += Math.abs(interceptUpdate);
|
|
611
|
-
epochDelta += delta;
|
|
612
|
-
batchCount += 1;
|
|
613
|
-
}
|
|
614
|
-
if (batchCount > 0 && epochDelta / batchCount < EARLY_STOP_DELTA) {
|
|
615
|
-
break;
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
return { coefficients, intercept };
|
|
619
|
-
}
|
|
620
|
-
function questionWeights(examples, featureCount, fallback) {
|
|
621
|
-
const output = {};
|
|
622
|
-
for (const questionType of question_type_js_1.QUESTION_TYPES) {
|
|
623
|
-
const subset = examples.filter((example) => example.question_type === questionType);
|
|
624
|
-
output[questionType] = hasBothLabels(subset)
|
|
625
|
-
? trainWeights(subset, featureCount).coefficients
|
|
626
|
-
: [...fallback];
|
|
627
|
-
}
|
|
628
|
-
return output;
|
|
629
|
-
}
|
|
630
|
-
function modelIdFor(input) {
|
|
631
|
-
const compactTime = input.trainedAt.replace(/[^0-9]/g, '').slice(0, 17);
|
|
632
|
-
const uniqueSuffix = (0, node_crypto_1.randomBytes)(4).toString('hex');
|
|
633
|
-
return `ranker_${compactTime}_${input.rows}_${input.distinctQueries}_${uniqueSuffix}`;
|
|
634
|
-
}
|
|
635
|
-
async function trainOfflineRanker(input) {
|
|
636
|
-
const minFeedbackRows = input.minFeedbackRows ?? 1000;
|
|
637
|
-
const minDistinctQueries = input.minDistinctQueries ?? 10;
|
|
638
|
-
const window = effectiveWindow(input);
|
|
639
|
-
const rows = (0, feedback_store_js_1.listSearchFeedback)(input.adapter, {
|
|
640
|
-
since: window.since,
|
|
641
|
-
until: window.until,
|
|
642
|
-
});
|
|
643
|
-
const cappedRows = capNeutralRows(rows);
|
|
644
|
-
const examples = cappedRows
|
|
645
|
-
.map(rowToExample)
|
|
646
|
-
.filter((example) => Boolean(example));
|
|
647
|
-
const distinctQueries = new Set(examples.map((example) => example.query.trim().toLowerCase()))
|
|
648
|
-
.size;
|
|
649
|
-
const counts = {
|
|
650
|
-
feedbackRows: examples.length,
|
|
651
|
-
distinctQueries,
|
|
652
|
-
};
|
|
653
|
-
const examplesHaveBothLabels = hasBothLabels(examples);
|
|
654
|
-
if (counts.feedbackRows < minFeedbackRows ||
|
|
655
|
-
counts.distinctQueries < minDistinctQueries ||
|
|
656
|
-
!examplesHaveBothLabels) {
|
|
657
|
-
return {
|
|
658
|
-
status: 'insufficient_data',
|
|
659
|
-
effectiveWindow: window,
|
|
660
|
-
counts,
|
|
661
|
-
};
|
|
662
|
-
}
|
|
663
|
-
const featureCount = examples[0].features.length;
|
|
664
|
-
const trained = trainWeights(examples, featureCount);
|
|
665
|
-
const trainedAt = input.now ? input.now.toISOString() : new Date().toISOString();
|
|
666
|
-
const model = {
|
|
667
|
-
model_id: modelIdFor({
|
|
668
|
-
trainedAt,
|
|
669
|
-
rows: counts.feedbackRows,
|
|
670
|
-
distinctQueries: counts.distinctQueries,
|
|
671
|
-
}),
|
|
672
|
-
feature_set_version: input.featureSetVersion ?? (0, ranker_features_js_1.featureSetVersion)(),
|
|
673
|
-
trained_at: trainedAt,
|
|
674
|
-
training_rows_count: examples.length,
|
|
675
|
-
coefficients: trained.coefficients,
|
|
676
|
-
intercept: trained.intercept,
|
|
677
|
-
question_type_weights: questionWeights(examples, featureCount, trained.coefficients),
|
|
678
|
-
training_window: window,
|
|
679
|
-
};
|
|
680
|
-
return {
|
|
681
|
-
status: 'trained',
|
|
682
|
-
model,
|
|
683
|
-
effectiveWindow: window,
|
|
684
|
-
counts,
|
|
685
|
-
};
|
|
686
|
-
}
|
|
687
|
-
function rankByScore(items, score) {
|
|
688
|
-
return items
|
|
689
|
-
.map((item) => ({ item, score: score(item) }))
|
|
690
|
-
.sort((left, right) => {
|
|
691
|
-
const diff = right.score - left.score;
|
|
692
|
-
if (Math.abs(diff) > 1e-12) {
|
|
693
|
-
return diff;
|
|
694
|
-
}
|
|
695
|
-
return left.item.id.localeCompare(right.item.id);
|
|
696
|
-
})
|
|
697
|
-
.map((entry) => entry.item);
|
|
698
|
-
}
|
|
699
|
-
function reciprocalRankRanks(items, leftScore, rightScore) {
|
|
700
|
-
const left = rankByScore(items, leftScore);
|
|
701
|
-
const right = rankByScore(items, rightScore);
|
|
702
|
-
const leftRanks = new Map(left.map((item, index) => [item.id, index + 1]));
|
|
703
|
-
const rightRanks = new Map(right.map((item, index) => [item.id, index + 1]));
|
|
704
|
-
return rankByScore(items, (item) => {
|
|
705
|
-
const leftRank = leftRanks.get(item.id) ?? items.length;
|
|
706
|
-
const rightRank = rightRanks.get(item.id) ?? items.length;
|
|
707
|
-
return 1 / (60 + leftRank) + 1 / (60 + rightRank);
|
|
708
|
-
});
|
|
709
|
-
}
|
|
710
462
|
function scoreWithRankerModel(model, row, query, questionType) {
|
|
711
463
|
const features = (0, ranker_features_js_1.serializeFeatures)((0, ranker_features_js_1.extractFeatures)(row, query, { question_type: questionType }));
|
|
712
464
|
const coefficients = model.question_type_weights[questionType] ?? model.coefficients;
|
|
713
465
|
return sigmoid(dot(coefficients, features) + model.intercept);
|
|
714
466
|
}
|
|
715
|
-
function baselineMetrics(fixtures, ranker) {
|
|
716
|
-
const perQuery = {};
|
|
717
|
-
let ndcgTotal = 0;
|
|
718
|
-
let mrrTotal = 0;
|
|
719
|
-
for (const fixture of fixtures) {
|
|
720
|
-
const ranked = ranker(fixture);
|
|
721
|
-
const relevance = new Map(fixture.candidates.map((candidate) => [candidate.id, candidate.relevance]));
|
|
722
|
-
const relevantSet = new Set(fixture.candidates
|
|
723
|
-
.filter((candidate) => candidate.relevance > 0)
|
|
724
|
-
.map((candidate) => candidate.id));
|
|
725
|
-
const ndcg = ndcgAtK(ranked, relevance, 5);
|
|
726
|
-
const mrrScore = mrr(ranked, relevantSet);
|
|
727
|
-
perQuery[fixture.query] = { ndcg, mrr: mrrScore };
|
|
728
|
-
ndcgTotal += ndcg;
|
|
729
|
-
mrrTotal += mrrScore;
|
|
730
|
-
}
|
|
731
|
-
return {
|
|
732
|
-
ndcg: ndcgTotal / fixtures.length,
|
|
733
|
-
mrr: mrrTotal / fixtures.length,
|
|
734
|
-
per_query: perQuery,
|
|
735
|
-
};
|
|
736
|
-
}
|
|
737
|
-
async function evaluateAgainstBaselines(_input, model) {
|
|
738
|
-
const bm25 = baselineMetrics(exports.RANKER_QUALITY_FIXTURES, (fixture) => rankByScore(fixture.candidates, (candidate) => candidate.bm25_score));
|
|
739
|
-
const vector = baselineMetrics(exports.RANKER_QUALITY_FIXTURES, (fixture) => rankByScore(fixture.candidates, (candidate) => candidate.vector_score));
|
|
740
|
-
const rrf = baselineMetrics(exports.RANKER_QUALITY_FIXTURES, (fixture) => reciprocalRankRanks(fixture.candidates, (candidate) => candidate.bm25_score, (candidate) => candidate.vector_score));
|
|
741
|
-
const logistic = baselineMetrics(exports.RANKER_QUALITY_FIXTURES, (fixture) => rankByScore(fixture.candidates, (candidate) => 0.5 * candidate.bm25_score + 0.5 * candidate.vector_score));
|
|
742
|
-
const learned = baselineMetrics(exports.RANKER_QUALITY_FIXTURES, (fixture) => rankByScore(fixture.candidates, (candidate) => scoreWithRankerModel(model, candidate, fixture.query, fixture.question_type)));
|
|
743
|
-
const bestBaselineNdcg = Math.max(bm25.ndcg, vector.ndcg, rrf.ndcg, logistic.ndcg);
|
|
744
|
-
const bestBaselineMrr = Math.max(bm25.mrr, vector.mrr, rrf.mrr, logistic.mrr);
|
|
745
|
-
return {
|
|
746
|
-
bm25,
|
|
747
|
-
vector,
|
|
748
|
-
rrf,
|
|
749
|
-
logistic,
|
|
750
|
-
learned,
|
|
751
|
-
passes: learned.ndcg >= bestBaselineNdcg + RANKER_QUALITY_MARGIN &&
|
|
752
|
-
learned.mrr >= bestBaselineMrr + RANKER_QUALITY_MARGIN,
|
|
753
|
-
};
|
|
754
|
-
}
|
|
755
|
-
function runTransaction(adapter, fn) {
|
|
756
|
-
if (!adapter.transaction) {
|
|
757
|
-
return fn();
|
|
758
|
-
}
|
|
759
|
-
const result = adapter.transaction(fn);
|
|
760
|
-
if (typeof result === 'function') {
|
|
761
|
-
return result();
|
|
762
|
-
}
|
|
763
|
-
return result;
|
|
764
|
-
}
|
|
765
|
-
async function insertRankerModelVersion(adapter, model, evaluation) {
|
|
766
|
-
const coefficientsPayload = JSON.stringify({
|
|
767
|
-
coefficients: model.coefficients,
|
|
768
|
-
intercept: model.intercept,
|
|
769
|
-
question_type_weights: model.question_type_weights,
|
|
770
|
-
training_rows_count: model.training_rows_count,
|
|
771
|
-
});
|
|
772
|
-
const metricsPayload = JSON.stringify({
|
|
773
|
-
learned: evaluation.learned,
|
|
774
|
-
passes: evaluation.passes,
|
|
775
|
-
});
|
|
776
|
-
const baselinePayload = JSON.stringify({
|
|
777
|
-
bm25: evaluation.bm25,
|
|
778
|
-
vector: evaluation.vector,
|
|
779
|
-
rrf: evaluation.rrf,
|
|
780
|
-
logistic: evaluation.logistic,
|
|
781
|
-
});
|
|
782
|
-
const trainingWindowPayload = JSON.stringify(model.training_window ?? {
|
|
783
|
-
since: null,
|
|
784
|
-
until: null,
|
|
785
|
-
retention_cutoff_at: null,
|
|
786
|
-
retention_days_at_train_time: null,
|
|
787
|
-
retention_warning: false,
|
|
788
|
-
});
|
|
789
|
-
adapter
|
|
790
|
-
.prepare(`
|
|
791
|
-
INSERT INTO ranker_model_versions (
|
|
792
|
-
model_id, model_version, feature_set_version, coefficients_json, metrics_json,
|
|
793
|
-
training_window_json, baseline_metrics_json, quality_gate_status, trained_at,
|
|
794
|
-
trained_by, active
|
|
795
|
-
)
|
|
796
|
-
VALUES (?, 'offline-logistic-v1', ?, ?, ?, ?, ?, ?, ?, 'mama train-ranker', 0)
|
|
797
|
-
`)
|
|
798
|
-
.run(model.model_id, model.feature_set_version, coefficientsPayload, metricsPayload, trainingWindowPayload, baselinePayload, evaluation.passes ? 'passed' : 'failed', model.trained_at);
|
|
799
|
-
return { model_id: model.model_id, active: false };
|
|
800
|
-
}
|
|
801
|
-
async function activateRankerModel(adapter, model_id) {
|
|
802
|
-
const row = adapter
|
|
803
|
-
.prepare(`
|
|
804
|
-
SELECT model_id, feature_set_version, quality_gate_status
|
|
805
|
-
FROM ranker_model_versions
|
|
806
|
-
WHERE model_id = ?
|
|
807
|
-
`)
|
|
808
|
-
.get(model_id);
|
|
809
|
-
if (!row) {
|
|
810
|
-
return 'not_found';
|
|
811
|
-
}
|
|
812
|
-
if (row.feature_set_version !== ranker_features_js_1.SEARCH_RANKER_FEATURE_SET_VERSION) {
|
|
813
|
-
return 'feature_set_mismatch';
|
|
814
|
-
}
|
|
815
|
-
if (row.quality_gate_status !== 'passed') {
|
|
816
|
-
return 'quality_gate_failed';
|
|
817
|
-
}
|
|
818
|
-
runTransaction(adapter, () => {
|
|
819
|
-
adapter.prepare('UPDATE ranker_model_versions SET active = 0 WHERE active = 1').run();
|
|
820
|
-
adapter.prepare('UPDATE ranker_model_versions SET active = 1 WHERE model_id = ?').run(model_id);
|
|
821
|
-
});
|
|
822
|
-
return 'activated';
|
|
823
|
-
}
|
|
824
467
|
function ndcgAtK(results, relevanceMap, k) {
|
|
825
468
|
const gains = results.slice(0, k).map((result, index) => {
|
|
826
469
|
const relevance = relevanceMap.get(result.id) ?? 0;
|
|
@@ -838,23 +481,4 @@ function mrr(results, relevantSet) {
|
|
|
838
481
|
const index = results.findIndex((result) => relevantSet.has(result.id));
|
|
839
482
|
return index === -1 ? 0 : 1 / (index + 1);
|
|
840
483
|
}
|
|
841
|
-
function seedQualityFixtureFeedback(adapter, createdAt = '2026-04-18T00:00:00.000Z') {
|
|
842
|
-
let counter = 0;
|
|
843
|
-
for (const fixture of exports.RANKER_QUALITY_FIXTURES) {
|
|
844
|
-
for (const candidate of fixture.candidates) {
|
|
845
|
-
counter += 1;
|
|
846
|
-
const kind = candidate.relevance >= 3 ? 'accept' : 'reject';
|
|
847
|
-
adapter
|
|
848
|
-
.prepare(`
|
|
849
|
-
INSERT INTO search_feedback (
|
|
850
|
-
feedback_id, query, query_hash, question_type, result_source_type, result_source_id,
|
|
851
|
-
case_id, feedback_kind, rank_position, score_before, score_after, session_id,
|
|
852
|
-
actor, metadata_json, created_at, updated_at
|
|
853
|
-
)
|
|
854
|
-
VALUES (?, ?, zeroblob(32), ?, ?, ?, NULL, ?, ?, NULL, NULL, ?, 'test:ranker-fixture', '{}', ?, NULL)
|
|
855
|
-
`)
|
|
856
|
-
.run(`quality-${counter}`, fixture.query, fixture.question_type, asSourceForFeedback(candidate.source_type), candidate.id, kind, counter % 5, `fixture:${fixture.question_type}`, createdAt);
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
484
|
//# sourceMappingURL=ranker-trainer.js.map
|
package/dist/tier-validator.d.ts
CHANGED
|
@@ -36,20 +36,8 @@ export declare function checkEmbeddings(): CheckResult;
|
|
|
36
36
|
* Validates database file accessibility
|
|
37
37
|
*/
|
|
38
38
|
export declare function checkDatabase(): CheckResult;
|
|
39
|
-
/**
|
|
40
|
-
* Validates MAMA tier status
|
|
41
|
-
*
|
|
42
|
-
* Performs all system checks and determines tier:
|
|
43
|
-
* - Tier 1: All checks pass (full features)
|
|
44
|
-
* - Tier 2: One or more checks fail (degraded mode)
|
|
45
|
-
*/
|
|
46
|
-
export declare function validateTier(): Promise<TierValidation>;
|
|
47
39
|
/**
|
|
48
40
|
* Get user-friendly tier description
|
|
49
41
|
*/
|
|
50
42
|
export declare function getTierDescription(tier: number): string;
|
|
51
|
-
/**
|
|
52
|
-
* Get tier status banner
|
|
53
|
-
*/
|
|
54
|
-
export declare function getTierBanner(validation: TierValidation): string;
|
|
55
43
|
//# sourceMappingURL=tier-validator.d.ts.map
|
package/dist/tier-validator.js
CHANGED
|
@@ -18,9 +18,7 @@ exports.checkNodeVersion = checkNodeVersion;
|
|
|
18
18
|
exports.checkSQLite = checkSQLite;
|
|
19
19
|
exports.checkEmbeddings = checkEmbeddings;
|
|
20
20
|
exports.checkDatabase = checkDatabase;
|
|
21
|
-
exports.validateTier = validateTier;
|
|
22
21
|
exports.getTierDescription = getTierDescription;
|
|
23
|
-
exports.getTierBanner = getTierBanner;
|
|
24
22
|
const fs_1 = __importDefault(require("fs"));
|
|
25
23
|
const path_1 = __importDefault(require("path"));
|
|
26
24
|
const os_1 = __importDefault(require("os"));
|
|
@@ -148,39 +146,6 @@ function checkDatabase() {
|
|
|
148
146
|
};
|
|
149
147
|
}
|
|
150
148
|
}
|
|
151
|
-
/**
|
|
152
|
-
* Validates MAMA tier status
|
|
153
|
-
*
|
|
154
|
-
* Performs all system checks and determines tier:
|
|
155
|
-
* - Tier 1: All checks pass (full features)
|
|
156
|
-
* - Tier 2: One or more checks fail (degraded mode)
|
|
157
|
-
*/
|
|
158
|
-
async function validateTier() {
|
|
159
|
-
const checks = [
|
|
160
|
-
{
|
|
161
|
-
name: 'Node.js',
|
|
162
|
-
...checkNodeVersion(),
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
name: 'SQLite',
|
|
166
|
-
...checkSQLite(),
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
name: 'Embeddings',
|
|
170
|
-
...checkEmbeddings(),
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
name: 'Database',
|
|
174
|
-
...checkDatabase(),
|
|
175
|
-
},
|
|
176
|
-
];
|
|
177
|
-
// Determine tier: all pass = tier 1, any fail = tier 2
|
|
178
|
-
const tier = checks.every((c) => c.status === 'pass') ? 1 : 2;
|
|
179
|
-
return {
|
|
180
|
-
tier,
|
|
181
|
-
checks,
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
149
|
/**
|
|
185
150
|
* Get user-friendly tier description
|
|
186
151
|
*/
|
|
@@ -191,25 +156,4 @@ function getTierDescription(tier) {
|
|
|
191
156
|
};
|
|
192
157
|
return descriptions[tier] || 'Unknown Tier';
|
|
193
158
|
}
|
|
194
|
-
/**
|
|
195
|
-
* Get tier status banner
|
|
196
|
-
*/
|
|
197
|
-
function getTierBanner(validation) {
|
|
198
|
-
const { tier, checks } = validation;
|
|
199
|
-
const failedChecks = checks.filter((c) => c.status === 'fail');
|
|
200
|
-
let banner = `\n┌─────────────────────────────────────────┐\n`;
|
|
201
|
-
banner += `│ MAMA Tier ${tier}: ${getTierDescription(tier).split(' - ')[0]}\n`;
|
|
202
|
-
if (failedChecks.length > 0) {
|
|
203
|
-
banner += `│\n`;
|
|
204
|
-
banner += `│ ⚠️ Issues detected:\n`;
|
|
205
|
-
failedChecks.forEach((check) => {
|
|
206
|
-
banner += `│ • ${check.name}: ${check.details}\n`;
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
else {
|
|
210
|
-
banner += `│ ✅ All systems operational\n`;
|
|
211
|
-
}
|
|
212
|
-
banner += `└─────────────────────────────────────────┘\n`;
|
|
213
|
-
return banner;
|
|
214
|
-
}
|
|
215
159
|
//# sourceMappingURL=tier-validator.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jungjaehoon/mama-core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "MAMA Core - Shared modules for Memory-Augmented MCP Assistant",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
"./decision-tracker": "./dist/decision-tracker.js",
|
|
21
21
|
"./debug-logger": "./dist/debug-logger.js",
|
|
22
22
|
"./decision-formatter": "./dist/decision-formatter.js",
|
|
23
|
-
"./memory-inject": "./dist/memory-inject.js",
|
|
24
23
|
"./ollama-client": "./dist/ollama-client.js",
|
|
25
24
|
"./errors": "./dist/errors.js",
|
|
26
25
|
"./time-formatter": "./dist/time-formatter.js",
|
|
@@ -32,14 +31,6 @@
|
|
|
32
31
|
"./canonicalize": "./dist/canonicalize.js",
|
|
33
32
|
"./cases/types": "./dist/cases/types.js",
|
|
34
33
|
"./cases/store": "./dist/cases/store.js",
|
|
35
|
-
"./cases/role-inference": "./dist/cases/role-inference.js",
|
|
36
|
-
"./cases/target-ref": "./dist/cases/target-ref.js",
|
|
37
|
-
"./cases/corrections": "./dist/cases/corrections.js",
|
|
38
|
-
"./cases/sqlite-transaction": "./dist/cases/sqlite-transaction.js",
|
|
39
|
-
"./cases/live-state": "./dist/cases/live-state.js",
|
|
40
|
-
"./cases/tombstone-sweeper": "./dist/cases/tombstone-sweeper.js",
|
|
41
|
-
"./cases/merge-split": "./dist/cases/merge-split.js",
|
|
42
|
-
"./cases/membership-matcher": "./dist/cases/membership-matcher.js",
|
|
43
34
|
"./cases/search-rollup": "./dist/cases/search-rollup.js",
|
|
44
35
|
"./cases/timeline-range": "./dist/cases/timeline-range.js",
|
|
45
36
|
"./cases/wiki-page-index": "./dist/cases/wiki-page-index.js",
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import type { DatabaseAdapter } from '../db-manager.js';
|
|
2
|
-
import { type ImmediateTransactionAdapter } from './sqlite-transaction.js';
|
|
3
|
-
export type CaseLinkType = 'related' | 'supersedes-case' | 'subcase-of' | 'blocked-by' | 'duplicate-of';
|
|
4
|
-
export type CaseLinkSourceKind = 'manual' | 'wiki_compiler' | 'hitl_correction' | 'system_backfill';
|
|
5
|
-
export interface CaseLinkRecord {
|
|
6
|
-
link_id: string;
|
|
7
|
-
case_id_from: string;
|
|
8
|
-
case_id_to: string;
|
|
9
|
-
link_type: CaseLinkType;
|
|
10
|
-
created_at: string;
|
|
11
|
-
created_by: string;
|
|
12
|
-
confidence: number | null;
|
|
13
|
-
reason_json: string | null;
|
|
14
|
-
source_kind: CaseLinkSourceKind;
|
|
15
|
-
source_ref: string | null;
|
|
16
|
-
source_ref_fingerprint: Buffer | null;
|
|
17
|
-
source_ref_fingerprint_hex: string | null;
|
|
18
|
-
revoked_at: string | null;
|
|
19
|
-
revoked_by: string | null;
|
|
20
|
-
revoke_reason: string | null;
|
|
21
|
-
}
|
|
22
|
-
export interface CaseLinkCreateInput {
|
|
23
|
-
link_id?: string;
|
|
24
|
-
case_id_from: string;
|
|
25
|
-
case_id_to: string;
|
|
26
|
-
link_type: CaseLinkType;
|
|
27
|
-
created_by: string;
|
|
28
|
-
confidence?: number | null;
|
|
29
|
-
reason_json?: string | Record<string, unknown> | null;
|
|
30
|
-
source_kind?: CaseLinkSourceKind;
|
|
31
|
-
source_ref?: string | null;
|
|
32
|
-
source_ref_fingerprint?: Buffer | Uint8Array | null;
|
|
33
|
-
unsuppress_wiki_tombstone?: boolean;
|
|
34
|
-
expected_current_value_json?: string | null;
|
|
35
|
-
reconfirm_token?: string | null;
|
|
36
|
-
session_id?: string | null;
|
|
37
|
-
now?: string;
|
|
38
|
-
}
|
|
39
|
-
export interface CaseLinkRevokeInput {
|
|
40
|
-
link_id: string;
|
|
41
|
-
revoked_by: string;
|
|
42
|
-
revoke_reason: string;
|
|
43
|
-
expected_current_value_json?: string | null;
|
|
44
|
-
reconfirm_token?: string | null;
|
|
45
|
-
session_id?: string | null;
|
|
46
|
-
now?: string;
|
|
47
|
-
}
|
|
48
|
-
export interface CaseLinkRequiresReconfirmResult {
|
|
49
|
-
kind: 'requires_reconfirm';
|
|
50
|
-
code: 'case.correction_requires_reconfirm';
|
|
51
|
-
case_id: string;
|
|
52
|
-
target_kind: 'case_link';
|
|
53
|
-
target_ref_json: string;
|
|
54
|
-
current_value_json: string;
|
|
55
|
-
old_value_json: string | null;
|
|
56
|
-
proposed_new_value_json: string;
|
|
57
|
-
reconfirm_token: string;
|
|
58
|
-
reconfirm_token_expires_at: string;
|
|
59
|
-
message: string;
|
|
60
|
-
}
|
|
61
|
-
export type CaseLinkCreateResult = {
|
|
62
|
-
kind: 'created';
|
|
63
|
-
link_id: string;
|
|
64
|
-
source_ref_fingerprint_hex: string;
|
|
65
|
-
} | CaseLinkRequiresReconfirmResult | {
|
|
66
|
-
kind: 'rejected';
|
|
67
|
-
code: 'case.precompile_gap' | 'case.terminal_status' | 'case.self_link' | 'case.wiki_tombstone_conflict' | 'case.correction_active_conflict' | 'case.reconfirm_token_replayed';
|
|
68
|
-
message: string;
|
|
69
|
-
case_id?: string;
|
|
70
|
-
};
|
|
71
|
-
export type CaseLinkRevokeResult = {
|
|
72
|
-
kind: 'revoked';
|
|
73
|
-
link_id: string;
|
|
74
|
-
} | CaseLinkRequiresReconfirmResult | {
|
|
75
|
-
kind: 'rejected';
|
|
76
|
-
code: 'case.case_link_not_found' | 'case.wiki_tombstone_fingerprint_missing' | 'case.reconfirm_token_replayed';
|
|
77
|
-
message: string;
|
|
78
|
-
link_id?: string;
|
|
79
|
-
};
|
|
80
|
-
export interface ListActiveCaseLinksResult {
|
|
81
|
-
terminal_case_id: string;
|
|
82
|
-
resolved_via_case_id: string | null;
|
|
83
|
-
chain: string[];
|
|
84
|
-
links: CaseLinkRecord[];
|
|
85
|
-
}
|
|
86
|
-
export interface BackfillStructuralLinksResult {
|
|
87
|
-
duplicate_of_inserted: number;
|
|
88
|
-
subcase_of_inserted: number;
|
|
89
|
-
}
|
|
90
|
-
type CaseLinkAdapter = Pick<DatabaseAdapter, 'prepare' | 'transaction'> & Partial<Pick<ImmediateTransactionAdapter, 'exec'>>;
|
|
91
|
-
export declare function sha256TombstoneFingerprint(input: {
|
|
92
|
-
source_case_id: string;
|
|
93
|
-
target_case_id: string;
|
|
94
|
-
link_type: CaseLinkType;
|
|
95
|
-
}): Buffer;
|
|
96
|
-
export declare function createCaseLink(adapter: DatabaseAdapter, input: CaseLinkCreateInput): CaseLinkCreateResult;
|
|
97
|
-
export declare function revokeCaseLink(adapter: DatabaseAdapter, input: CaseLinkRevokeInput): CaseLinkRevokeResult;
|
|
98
|
-
export declare function listActiveCaseLinks(adapter: DatabaseAdapter, caseId: string): ListActiveCaseLinksResult;
|
|
99
|
-
export declare function isWikiTombstoneActive(adapter: DatabaseAdapter | CaseLinkAdapter, input: {
|
|
100
|
-
source_case_id: string;
|
|
101
|
-
target_case_id: string;
|
|
102
|
-
link_type: CaseLinkType;
|
|
103
|
-
}): boolean;
|
|
104
|
-
export declare function unsuppressWikiTombstone(adapter: DatabaseAdapter, input: {
|
|
105
|
-
source_case_id: string;
|
|
106
|
-
target_case_id: string;
|
|
107
|
-
link_type: CaseLinkType;
|
|
108
|
-
unsuppressed_by: string;
|
|
109
|
-
now?: string;
|
|
110
|
-
}): {
|
|
111
|
-
kind: 'unsuppressed';
|
|
112
|
-
unsuppressed_count: number;
|
|
113
|
-
};
|
|
114
|
-
export declare function backfillStructuralLinks(adapter: DatabaseAdapter): BackfillStructuralLinksResult;
|
|
115
|
-
export {};
|
|
116
|
-
//# sourceMappingURL=case-links.d.ts.map
|