superlocalmemory 3.6.22 → 3.7.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 +52 -0
- package/README.md +275 -72
- package/bin/slm-npm +43 -89
- package/docs/pi-dev-integration.md +43 -0
- package/ide/configs/antigravity-mcp.json +2 -2
- package/ide/configs/chatgpt-desktop-mcp.json +1 -1
- package/ide/configs/claude-desktop-mcp.json +2 -2
- package/ide/configs/windsurf-mcp.json +2 -2
- package/ide/hooks/context-hook.js +6 -2
- package/ide/hooks/post-recall-hook.js +7 -3
- package/ide/hooks/tool-event-hook.sh +2 -1
- package/package.json +19 -10
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/_GENERATED.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/rules/AGENTS.md +1 -1
- package/pyproject.toml +40 -8
- package/scripts/postinstall-interactive.js +17 -94
- package/scripts/postinstall.js +185 -258
- package/scripts/preuninstall.js +9 -50
- package/src/superlocalmemory/__init__.py +2 -2
- package/src/superlocalmemory/attribution/mathematical_dna.py +1 -1
- package/src/superlocalmemory/attribution/signer.py +34 -19
- package/src/superlocalmemory/attribution/watermark.py +1 -1
- package/src/superlocalmemory/cli/_lazy_init.py +3 -5
- package/src/superlocalmemory/cli/commands.py +490 -195
- package/src/superlocalmemory/cli/context_commands.py +5 -4
- package/src/superlocalmemory/cli/daemon.py +282 -187
- package/src/superlocalmemory/cli/db_migrate.py +3 -1
- package/src/superlocalmemory/cli/diagnostics_cmd.py +28 -0
- package/src/superlocalmemory/cli/evidence_cmd.py +103 -0
- package/src/superlocalmemory/cli/ingest_cmd.py +7 -3
- package/src/superlocalmemory/cli/main.py +128 -31
- package/src/superlocalmemory/cli/pending_store.py +54 -38
- package/src/superlocalmemory/cli/scale_engine_cmd.py +37 -0
- package/src/superlocalmemory/cli/service_installer.py +57 -52
- package/src/superlocalmemory/cli/setup_wizard.py +142 -88
- package/src/superlocalmemory/cli/version_banner.py +2 -1
- package/src/superlocalmemory/code_graph/config.py +3 -1
- package/src/superlocalmemory/core/backend_orchestrator.py +81 -21
- package/src/superlocalmemory/core/config.py +65 -20
- package/src/superlocalmemory/core/consolidation_engine.py +9 -7
- package/src/superlocalmemory/core/context_cache.py +56 -8
- package/src/superlocalmemory/core/derivation_lineage.py +246 -0
- package/src/superlocalmemory/core/embedding_worker.py +32 -20
- package/src/superlocalmemory/core/embeddings.py +54 -18
- package/src/superlocalmemory/core/engine.py +150 -104
- package/src/superlocalmemory/core/engine_ingestion.py +513 -0
- package/src/superlocalmemory/core/engine_wiring.py +2 -0
- package/src/superlocalmemory/core/evidence_bundle.py +526 -0
- package/src/superlocalmemory/core/fact_consolidator.py +5 -11
- package/src/superlocalmemory/core/graph_analyzer.py +2 -2
- package/src/superlocalmemory/core/health_monitor.py +4 -2
- package/src/superlocalmemory/core/ingestion_command.py +636 -0
- package/src/superlocalmemory/core/injection.py +69 -18
- package/src/superlocalmemory/core/lifecycle_state.py +153 -0
- package/src/superlocalmemory/core/maintenance.py +23 -22
- package/src/superlocalmemory/core/maintenance_scheduler.py +51 -35
- package/src/superlocalmemory/core/mutations.py +143 -0
- package/src/superlocalmemory/core/platform_utils.py +7 -4
- package/src/superlocalmemory/core/ram_lock.py +16 -5
- package/src/superlocalmemory/core/rate_limit.py +1 -1
- package/src/superlocalmemory/core/recall_pipeline.py +60 -101
- package/src/superlocalmemory/core/recall_worker.py +76 -59
- package/src/superlocalmemory/core/registry.py +1 -1
- package/src/superlocalmemory/core/scale_engine.py +293 -0
- package/src/superlocalmemory/core/score_contract.py +62 -0
- package/src/superlocalmemory/core/security_primitives.py +3 -1
- package/src/superlocalmemory/core/slm_disabled.py +3 -5
- package/src/superlocalmemory/core/store_pipeline.py +172 -40
- package/src/superlocalmemory/core/tier_manager.py +32 -20
- package/src/superlocalmemory/core/worker_pool.py +13 -4
- package/src/superlocalmemory/dynamics/activation_guided_quantization.py +1 -1
- package/src/superlocalmemory/dynamics/eap_scheduler.py +10 -3
- package/src/superlocalmemory/dynamics/ebbinghaus_langevin_coupling.py +1 -1
- package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +1 -1
- package/src/superlocalmemory/encoding/auto_linker.py +1 -1
- package/src/superlocalmemory/encoding/cognitive_consolidator.py +7 -16
- package/src/superlocalmemory/encoding/consolidator.py +22 -5
- package/src/superlocalmemory/encoding/fact_extractor.py +1 -1
- package/src/superlocalmemory/encoding/foresight.py +2 -0
- package/src/superlocalmemory/encoding/graph_builder.py +1 -1
- package/src/superlocalmemory/encoding/temporal_parser.py +2 -0
- package/src/superlocalmemory/evaluation/__init__.py +13 -0
- package/src/superlocalmemory/evaluation/calibration.py +308 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +2 -1
- package/src/superlocalmemory/graph/cozo_backend.py +256 -23
- package/src/superlocalmemory/hooks/_outcome_common.py +21 -11
- package/src/superlocalmemory/hooks/antigravity_adapter.py +10 -31
- package/src/superlocalmemory/hooks/auto_invoker.py +25 -27
- package/src/superlocalmemory/hooks/auto_recall.py +31 -6
- package/src/superlocalmemory/hooks/auto_recall_hook.py +13 -33
- package/src/superlocalmemory/hooks/before_web_hook.py +9 -7
- package/src/superlocalmemory/hooks/claude_code_hooks.py +126 -39
- package/src/superlocalmemory/hooks/codex_assets.py +59 -0
- package/src/superlocalmemory/hooks/codex_hooks.py +186 -0
- package/src/superlocalmemory/hooks/context_payload.py +1 -1
- package/src/superlocalmemory/hooks/copilot_adapter.py +9 -24
- package/src/superlocalmemory/hooks/cursor_adapter.py +10 -32
- package/src/superlocalmemory/hooks/hook_daemon.py +4 -2
- package/src/superlocalmemory/hooks/hook_handlers.py +241 -55
- package/src/superlocalmemory/hooks/memory_protocol.py +5 -3
- package/src/superlocalmemory/hooks/post_tool_async_hook.py +4 -2
- package/src/superlocalmemory/hooks/session_registry.py +15 -8
- package/src/superlocalmemory/hooks/stop_outcome_hook.py +10 -6
- package/src/superlocalmemory/hooks/topic_shift_hook.py +42 -12
- package/src/superlocalmemory/hooks/user_prompt_hook.py +9 -14
- package/src/superlocalmemory/hooks/user_prompt_rehash_hook.py +19 -11
- package/src/superlocalmemory/infra/auth_middleware.py +38 -5
- package/src/superlocalmemory/infra/backup.py +7 -5
- package/src/superlocalmemory/infra/cloud_backup.py +18 -8
- package/src/superlocalmemory/infra/daemon_identity.py +248 -0
- package/src/superlocalmemory/infra/data_root.py +199 -0
- package/src/superlocalmemory/infra/event_bus.py +3 -1
- package/src/superlocalmemory/infra/local_diagnostics.py +327 -0
- package/src/superlocalmemory/infra/process_reaper.py +23 -0
- package/src/superlocalmemory/ingestion/adapter_manager.py +27 -9
- package/src/superlocalmemory/ingestion/base_adapter.py +25 -31
- package/src/superlocalmemory/ingestion/calendar_adapter.py +13 -4
- package/src/superlocalmemory/ingestion/credentials.py +14 -7
- package/src/superlocalmemory/ingestion/gmail_adapter.py +13 -4
- package/src/superlocalmemory/ingestion/transcript_adapter.py +7 -2
- package/src/superlocalmemory/learning/consolidation_quantization_worker.py +1 -1
- package/src/superlocalmemory/learning/ensemble.py +11 -0
- package/src/superlocalmemory/learning/entity_compiler.py +1 -1
- package/src/superlocalmemory/learning/feedback.py +1 -1
- package/src/superlocalmemory/learning/forgetting_scheduler.py +12 -7
- package/src/superlocalmemory/learning/quantization_scheduler.py +1 -1
- package/src/superlocalmemory/learning/ranker.py +4 -1
- package/src/superlocalmemory/learning/source_quality.py +1 -1
- package/src/superlocalmemory/learning/trigram_index.py +3 -2
- package/src/superlocalmemory/llm/backbone.py +13 -8
- package/src/superlocalmemory/math/ebbinghaus.py +1 -1
- package/src/superlocalmemory/math/fisher.py +1 -1
- package/src/superlocalmemory/math/fisher_quantized.py +1 -1
- package/src/superlocalmemory/math/hopfield.py +1 -1
- package/src/superlocalmemory/math/langevin.py +1 -1
- package/src/superlocalmemory/math/polar_quant.py +3 -4
- package/src/superlocalmemory/math/qjl.py +1 -1
- package/src/superlocalmemory/math/sheaf.py +1 -1
- package/src/superlocalmemory/math/turbo_quant.py +3 -2
- package/src/superlocalmemory/mcp/_daemon_proxy.py +12 -11
- package/src/superlocalmemory/mcp/_pool_adapter.py +27 -0
- package/src/superlocalmemory/mcp/http_transport.py +53 -0
- package/src/superlocalmemory/mcp/server.py +39 -13
- package/src/superlocalmemory/mcp/shared.py +69 -3
- package/src/superlocalmemory/mcp/tools_active.py +141 -31
- package/src/superlocalmemory/mcp/tools_core.py +128 -29
- package/src/superlocalmemory/mcp/tools_evolution.py +5 -7
- package/src/superlocalmemory/mcp/tools_learning.py +42 -2
- package/src/superlocalmemory/mcp/tools_mesh.py +7 -23
- package/src/superlocalmemory/mcp/tools_optimize.py +8 -1
- package/src/superlocalmemory/mcp/tools_v28.py +23 -2
- package/src/superlocalmemory/mcp/tools_v3.py +26 -1
- package/src/superlocalmemory/mcp/tools_v33.py +56 -17
- package/src/superlocalmemory/mesh/broker.py +2 -0
- package/src/superlocalmemory/mesh/remote_sync.py +50 -12
- package/src/superlocalmemory/optimize/cache/manager.py +77 -1
- package/src/superlocalmemory/optimize/cache/semantic.py +23 -3
- package/src/superlocalmemory/optimize/compress/ccr.py +4 -0
- package/src/superlocalmemory/optimize/compress/router.py +6 -1
- package/src/superlocalmemory/optimize/config/__init__.py +5 -0
- package/src/superlocalmemory/optimize/config/store.py +6 -4
- package/src/superlocalmemory/optimize/proxy/_helpers.py +15 -5
- package/src/superlocalmemory/optimize/proxy/capture.py +3 -2
- package/src/superlocalmemory/optimize/proxy/server.py +2 -2
- package/src/superlocalmemory/optimize/storage/db.py +14 -13
- package/src/superlocalmemory/retrieval/agentic.py +1 -1
- package/src/superlocalmemory/retrieval/ann_index.py +1 -1
- package/src/superlocalmemory/retrieval/bm25_channel.py +35 -11
- package/src/superlocalmemory/retrieval/bridge_discovery.py +73 -8
- package/src/superlocalmemory/retrieval/engine.py +169 -79
- package/src/superlocalmemory/retrieval/entity_channel.py +289 -67
- package/src/superlocalmemory/retrieval/forgetting_filter.py +1 -1
- package/src/superlocalmemory/retrieval/fusion.py +1 -1
- package/src/superlocalmemory/retrieval/hopfield_channel.py +118 -30
- package/src/superlocalmemory/retrieval/profile_channel.py +1 -1
- package/src/superlocalmemory/retrieval/quantization_aware_search.py +16 -10
- package/src/superlocalmemory/retrieval/reranker.py +56 -20
- package/src/superlocalmemory/retrieval/scope_policy.py +85 -0
- package/src/superlocalmemory/retrieval/semantic_channel.py +122 -14
- package/src/superlocalmemory/retrieval/spreading_activation.py +141 -25
- package/src/superlocalmemory/retrieval/strategy.py +1 -1
- package/src/superlocalmemory/retrieval/temporal_channel.py +30 -15
- package/src/superlocalmemory/retrieval/vector_store.py +1 -1
- package/src/superlocalmemory/server/api.py +10 -7
- package/src/superlocalmemory/server/bandit_loops.py +4 -2
- package/src/superlocalmemory/server/recall_serializer.py +24 -0
- package/src/superlocalmemory/server/route_mutations.py +84 -0
- package/src/superlocalmemory/server/routes/agents.py +8 -6
- package/src/superlocalmemory/server/routes/brain.py +14 -12
- package/src/superlocalmemory/server/routes/chat.py +29 -12
- package/src/superlocalmemory/server/routes/data_io.py +55 -24
- package/src/superlocalmemory/server/routes/helpers.py +29 -4
- package/src/superlocalmemory/server/routes/ingest.py +53 -36
- package/src/superlocalmemory/server/routes/memories.py +104 -43
- package/src/superlocalmemory/server/routes/mesh.py +31 -0
- package/src/superlocalmemory/server/routes/profiles.py +26 -4
- package/src/superlocalmemory/server/routes/tiers.py +43 -11
- package/src/superlocalmemory/server/routes/timeline.py +5 -1
- package/src/superlocalmemory/server/routes/v3_api.py +76 -21
- package/src/superlocalmemory/server/security_middleware.py +1 -1
- package/src/superlocalmemory/server/ui.py +6 -3
- package/src/superlocalmemory/server/unified_daemon.py +680 -293
- package/src/superlocalmemory/server/write_identity.py +147 -0
- package/src/superlocalmemory/storage/access_log.py +4 -3
- package/src/superlocalmemory/storage/database.py +118 -25
- package/src/superlocalmemory/storage/migration_runner.py +84 -1
- package/src/superlocalmemory/storage/migration_v33.py +1 -1
- package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +6 -60
- package/src/superlocalmemory/storage/migrations/M018_ingestion_operations.py +120 -0
- package/src/superlocalmemory/storage/migrations/M019_derivation_lineage.py +54 -0
- package/src/superlocalmemory/storage/migrations/M020_model_state_integrity.py +52 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +5 -0
- package/src/superlocalmemory/storage/models.py +16 -0
- package/src/superlocalmemory/storage/quantized_store.py +20 -3
- package/src/superlocalmemory/storage/v2_migrator.py +5 -3
- package/src/superlocalmemory/ui/favicon.svg +5 -0
- package/src/superlocalmemory/ui/index.html +1 -0
- package/src/superlocalmemory/ui/js/compliance.js +1 -1
- package/src/superlocalmemory/ui/js/core.js +49 -8
- package/src/superlocalmemory/ui/js/dashboard.js +23 -2
- package/src/superlocalmemory/ui/js/feedback.js +1 -1
- package/src/superlocalmemory/ui/js/graph-filters.js +1 -1
- package/src/superlocalmemory/ui/js/graph-ui.js +1 -1
- package/src/superlocalmemory/ui/js/lifecycle.js +1 -1
- package/src/superlocalmemory/ui/js/ng-mesh.js +15 -49
- package/src/superlocalmemory/ui/js/settings.js +4 -2
- package/src/superlocalmemory/vector/lancedb_backend.py +57 -9
- package/bin/slm +0 -59
- package/bin/slm.bat +0 -77
- package/bin/slm.cmd +0 -5
- package/ide/integrations/langchain/README.md +0 -106
- package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +0 -9
- package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +0 -201
- package/ide/integrations/langchain/pyproject.toml +0 -38
- package/ide/integrations/langchain/tests/__init__.py +0 -3
- package/ide/integrations/langchain/tests/test_chat_message_history.py +0 -215
- package/ide/integrations/langchain/tests/test_security.py +0 -117
- package/ide/integrations/llamaindex/README.md +0 -81
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +0 -9
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +0 -316
- package/ide/integrations/llamaindex/pyproject.toml +0 -43
- package/ide/integrations/llamaindex/tests/__init__.py +0 -3
- package/ide/integrations/llamaindex/tests/test_chat_store.py +0 -294
- package/ide/integrations/llamaindex/tests/test_security.py +0 -241
- package/plugin-src/.mcp.json +0 -12
- package/plugin-src/agents/slm-memory-advisor.md +0 -44
- package/plugin-src/agents/slm-optimize-advisor.md +0 -38
- package/plugin-src/hooks/.gitkeep +0 -0
- package/plugin-src/hooks/hooks.json +0 -23
- package/plugin-src/manifest.json +0 -25
- package/plugin-src/requirements.txt +0 -1
- package/plugin-src/rules/CLAUDE.md.fragment +0 -44
- package/plugin-src/scripts/ensure-venv.bat +0 -122
- package/plugin-src/scripts/ensure-venv.sh +0 -105
- package/plugin-src/scripts/slm-launch +0 -15
- package/plugin-src/scripts/slm-launch.bat +0 -17
- package/plugin-src/settings.json +0 -16
- package/plugin-src/skills/slm-cache/SKILL.md +0 -140
- package/plugin-src/skills/slm-compress/SKILL.md +0 -143
- package/plugin-src/skills/slm-graph/SKILL.md +0 -300
- package/plugin-src/skills/slm-recall/SKILL.md +0 -204
- package/plugin-src/skills/slm-remember/SKILL.md +0 -194
- package/plugin-src/skills/slm-session/SKILL.md +0 -207
- package/plugin-src/skills/slm-status/SKILL.md +0 -149
- package/scripts/__tests__/build-plugin.test.mjs +0 -613
- package/scripts/_savings_math.py +0 -270
- package/scripts/build-dmg.sh +0 -417
- package/scripts/build-plugin.js +0 -742
- package/scripts/build-slm-hook.ps1 +0 -40
- package/scripts/build-slm-hook.sh +0 -45
- package/scripts/build_entry.py +0 -452
- package/scripts/ci/stage5b_gate.sh +0 -50
- package/scripts/dogfood_savings.py +0 -490
- package/scripts/generate-thumbnails.py +0 -218
- package/scripts/install-skills.ps1 +0 -4
- package/scripts/install-skills.sh +0 -5
- package/scripts/install.ps1 +0 -701
- package/scripts/install.sh +0 -1015
- package/scripts/postinstall_binary.js +0 -287
- package/scripts/prepack.js +0 -33
- package/scripts/release_manifest.py +0 -273
- package/scripts/slm-hook.spec +0 -56
- package/scripts/start-dashboard.ps1 +0 -52
- package/scripts/start-dashboard.sh +0 -41
- package/scripts/sync-wiki.ps1 +0 -127
- package/scripts/sync-wiki.sh +0 -82
- package/scripts/test-dmg.sh +0 -161
- package/scripts/test-npm-package.ps1 +0 -252
- package/scripts/test-npm-package.sh +0 -207
- package/scripts/verify-install.ps1 +0 -294
- package/scripts/verify-install.sh +0 -266
- package/scripts/verify-v27.ps1 +0 -301
- package/scripts/verify-v27.sh +0 -233
- package/src/superlocalmemory.egg-info/PKG-INFO +0 -513
- package/src/superlocalmemory.egg-info/SOURCES.txt +0 -529
- package/src/superlocalmemory.egg-info/dependency_links.txt +0 -1
- package/src/superlocalmemory.egg-info/entry_points.txt +0 -2
- package/src/superlocalmemory.egg-info/requires.txt +0 -71
- package/src/superlocalmemory.egg-info/top_level.txt +0 -1
|
@@ -12,7 +12,7 @@ Edge types: ENTITY (shared entity, weight 1.0), TEMPORAL (exp-decay,
|
|
|
12
12
|
weight 0.8). CONTRADICTION exposed for external Sheaf module (Wave 4).
|
|
13
13
|
|
|
14
14
|
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
15
|
-
License:
|
|
15
|
+
License: AGPL-3.0-or-later
|
|
16
16
|
"""
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Evaluation artifacts that are isolated from production retrieval paths."""
|
|
2
|
+
|
|
3
|
+
from superlocalmemory.evaluation.calibration import (
|
|
4
|
+
CalibrationArtifactError,
|
|
5
|
+
build_calibration_report,
|
|
6
|
+
compute_calibration_metrics,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"CalibrationArtifactError",
|
|
11
|
+
"build_calibration_report",
|
|
12
|
+
"compute_calibration_metrics",
|
|
13
|
+
]
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
"""Held-out answer-probability calibration report tooling.
|
|
2
|
+
|
|
3
|
+
Raw retrieval scores are ranking utilities. They are retained for distribution
|
|
4
|
+
diagnostics but are never passed to Brier, ECE, reliability, or selective-risk
|
|
5
|
+
calculations. Those metrics require an explicitly supplied probability from a
|
|
6
|
+
separately fitted calibrator and a disjoint held-out evaluation artifact.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import hashlib
|
|
12
|
+
import json
|
|
13
|
+
import math
|
|
14
|
+
from collections.abc import Iterable, Sequence
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
INPUT_SCHEMA = "superlocalmemory.calibration-input/v1"
|
|
18
|
+
REPORT_SCHEMA = "superlocalmemory.calibration-report/v1"
|
|
19
|
+
MIN_HELD_OUT_EXAMPLES = 100
|
|
20
|
+
DEFAULT_THRESHOLDS = (0.0, 0.5, 0.6, 0.7, 0.8, 0.9, 0.95)
|
|
21
|
+
_RAW_AS_PROBABILITY_METHODS = {
|
|
22
|
+
"identity",
|
|
23
|
+
"raw",
|
|
24
|
+
"raw_score",
|
|
25
|
+
"raw_retrieval_score",
|
|
26
|
+
"none",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class CalibrationArtifactError(ValueError):
|
|
31
|
+
"""The input cannot support a truthful held-out calibration report."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _canonical_sha256(value: object) -> str:
|
|
35
|
+
payload = json.dumps(
|
|
36
|
+
value, sort_keys=True, separators=(",", ":"), ensure_ascii=False
|
|
37
|
+
).encode("utf-8")
|
|
38
|
+
return hashlib.sha256(payload).hexdigest()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _finite_number(value: object, *, field: str) -> float:
|
|
42
|
+
if isinstance(value, bool):
|
|
43
|
+
raise CalibrationArtifactError(f"{field} must be a finite number")
|
|
44
|
+
try:
|
|
45
|
+
number = float(value)
|
|
46
|
+
except (TypeError, ValueError) as exc:
|
|
47
|
+
raise CalibrationArtifactError(f"{field} must be a finite number") from exc
|
|
48
|
+
if not math.isfinite(number):
|
|
49
|
+
raise CalibrationArtifactError(f"{field} must be a finite number")
|
|
50
|
+
return number
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _validated_rows(examples: Sequence[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
54
|
+
validated: list[dict[str, Any]] = []
|
|
55
|
+
seen_ids: set[str] = set()
|
|
56
|
+
for index, example in enumerate(examples):
|
|
57
|
+
if not isinstance(example, dict):
|
|
58
|
+
raise CalibrationArtifactError(f"examples[{index}] must be an object")
|
|
59
|
+
example_id = str(example.get("example_id", "")).strip()
|
|
60
|
+
if not example_id:
|
|
61
|
+
raise CalibrationArtifactError(f"examples[{index}].example_id is required")
|
|
62
|
+
if example_id in seen_ids:
|
|
63
|
+
raise CalibrationArtifactError("example_id values must be unique")
|
|
64
|
+
seen_ids.add(example_id)
|
|
65
|
+
|
|
66
|
+
if "predicted_probability" not in example:
|
|
67
|
+
raise CalibrationArtifactError(
|
|
68
|
+
"predicted_probability is required; raw retrieval scores are not probabilities"
|
|
69
|
+
)
|
|
70
|
+
probability = _finite_number(
|
|
71
|
+
example["predicted_probability"], field="predicted_probability"
|
|
72
|
+
)
|
|
73
|
+
if probability < 0.0 or probability > 1.0:
|
|
74
|
+
raise CalibrationArtifactError(
|
|
75
|
+
"predicted_probability must be between 0 and 1"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
label = example.get("label")
|
|
79
|
+
if isinstance(label, bool):
|
|
80
|
+
label = int(label)
|
|
81
|
+
if label not in (0, 1):
|
|
82
|
+
raise CalibrationArtifactError("label must be binary (0 or 1)")
|
|
83
|
+
raw_score = _finite_number(
|
|
84
|
+
example.get("raw_retrieval_score"), field="raw_retrieval_score"
|
|
85
|
+
)
|
|
86
|
+
validated.append(
|
|
87
|
+
{
|
|
88
|
+
"example_id": example_id,
|
|
89
|
+
"raw_retrieval_score": raw_score,
|
|
90
|
+
"predicted_probability": probability,
|
|
91
|
+
"label": int(label),
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
return validated
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _round(value: float) -> float:
|
|
98
|
+
return round(value, 12)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def compute_calibration_metrics(
|
|
102
|
+
examples: Sequence[dict[str, Any]],
|
|
103
|
+
*,
|
|
104
|
+
bin_count: int = 10,
|
|
105
|
+
thresholds: Iterable[float] = DEFAULT_THRESHOLDS,
|
|
106
|
+
) -> dict[str, Any]:
|
|
107
|
+
"""Compute probability metrics while reporting raw scores separately."""
|
|
108
|
+
if not isinstance(bin_count, int) or isinstance(bin_count, bool) or bin_count < 2:
|
|
109
|
+
raise CalibrationArtifactError("bin_count must be an integer >= 2")
|
|
110
|
+
rows = _validated_rows(examples)
|
|
111
|
+
if not rows:
|
|
112
|
+
raise CalibrationArtifactError("at least one example is required")
|
|
113
|
+
total = len(rows)
|
|
114
|
+
|
|
115
|
+
brier = sum(
|
|
116
|
+
(row["predicted_probability"] - row["label"]) ** 2 for row in rows
|
|
117
|
+
) / total
|
|
118
|
+
|
|
119
|
+
reliability: list[dict[str, Any]] = []
|
|
120
|
+
ece = 0.0
|
|
121
|
+
mce = 0.0
|
|
122
|
+
for index in range(bin_count):
|
|
123
|
+
lower = index / bin_count
|
|
124
|
+
upper = (index + 1) / bin_count
|
|
125
|
+
members = [
|
|
126
|
+
row for row in rows
|
|
127
|
+
if lower <= row["predicted_probability"]
|
|
128
|
+
and (row["predicted_probability"] < upper or index == bin_count - 1)
|
|
129
|
+
]
|
|
130
|
+
if members:
|
|
131
|
+
mean_probability = sum(row["predicted_probability"] for row in members) / len(members)
|
|
132
|
+
observed_accuracy = sum(row["label"] for row in members) / len(members)
|
|
133
|
+
gap = abs(mean_probability - observed_accuracy)
|
|
134
|
+
ece += len(members) / total * gap
|
|
135
|
+
mce = max(mce, gap)
|
|
136
|
+
mean_value: float | None = _round(mean_probability)
|
|
137
|
+
accuracy_value: float | None = _round(observed_accuracy)
|
|
138
|
+
gap_value: float | None = _round(gap)
|
|
139
|
+
else:
|
|
140
|
+
mean_value = accuracy_value = gap_value = None
|
|
141
|
+
reliability.append(
|
|
142
|
+
{
|
|
143
|
+
"bin_index": index,
|
|
144
|
+
"lower_inclusive": _round(lower),
|
|
145
|
+
"upper_inclusive": index == bin_count - 1,
|
|
146
|
+
"upper": _round(upper),
|
|
147
|
+
"count": len(members),
|
|
148
|
+
"mean_probability": mean_value,
|
|
149
|
+
"observed_accuracy": accuracy_value,
|
|
150
|
+
"absolute_gap": gap_value,
|
|
151
|
+
}
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
selective: list[dict[str, Any]] = []
|
|
155
|
+
previous = -1.0
|
|
156
|
+
for raw_threshold in thresholds:
|
|
157
|
+
threshold = _finite_number(raw_threshold, field="selective threshold")
|
|
158
|
+
if threshold < 0.0 or threshold > 1.0:
|
|
159
|
+
raise CalibrationArtifactError("selective threshold must be between 0 and 1")
|
|
160
|
+
if threshold <= previous:
|
|
161
|
+
raise CalibrationArtifactError("selective thresholds must be strictly increasing")
|
|
162
|
+
previous = threshold
|
|
163
|
+
retained = [row for row in rows if row["predicted_probability"] >= threshold]
|
|
164
|
+
retained_count = len(retained)
|
|
165
|
+
coverage = retained_count / total
|
|
166
|
+
risk = (
|
|
167
|
+
sum(1 - row["label"] for row in retained) / retained_count
|
|
168
|
+
if retained_count
|
|
169
|
+
else None
|
|
170
|
+
)
|
|
171
|
+
selective.append(
|
|
172
|
+
{
|
|
173
|
+
"threshold": _round(threshold),
|
|
174
|
+
"retained": retained_count,
|
|
175
|
+
"coverage": _round(coverage),
|
|
176
|
+
"abstention_rate": _round(1.0 - coverage),
|
|
177
|
+
"selective_risk": _round(risk) if risk is not None else None,
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
raw_scores = [row["raw_retrieval_score"] for row in rows]
|
|
182
|
+
probabilities = [row["predicted_probability"] for row in rows]
|
|
183
|
+
return {
|
|
184
|
+
"example_count": total,
|
|
185
|
+
"brier_score": _round(brier),
|
|
186
|
+
"expected_calibration_error": _round(ece),
|
|
187
|
+
"maximum_calibration_error": _round(mce),
|
|
188
|
+
"reliability_bins": reliability,
|
|
189
|
+
"selective_risk": selective,
|
|
190
|
+
"probability_summary": {
|
|
191
|
+
"minimum": _round(min(probabilities)),
|
|
192
|
+
"maximum": _round(max(probabilities)),
|
|
193
|
+
"mean": _round(sum(probabilities) / total),
|
|
194
|
+
"unique_count": len(set(probabilities)),
|
|
195
|
+
"boundary_count": sum(value in (0.0, 1.0) for value in probabilities),
|
|
196
|
+
},
|
|
197
|
+
"raw_retrieval_score_summary": {
|
|
198
|
+
"minimum": _round(min(raw_scores)),
|
|
199
|
+
"maximum": _round(max(raw_scores)),
|
|
200
|
+
"mean": _round(sum(raw_scores) / total),
|
|
201
|
+
"unique_count": len(set(raw_scores)),
|
|
202
|
+
"boundary_count": sum(value in (0.0, 1.0) for value in raw_scores),
|
|
203
|
+
"used_in_probability_metrics": False,
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _require_sha256(value: object, *, field: str) -> str:
|
|
209
|
+
text = str(value or "").lower()
|
|
210
|
+
if len(text) != 64 or any(char not in "0123456789abcdef" for char in text):
|
|
211
|
+
raise CalibrationArtifactError(f"{field} must be a 64-character SHA-256")
|
|
212
|
+
return text
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def build_calibration_report(
|
|
216
|
+
artifact: dict[str, Any],
|
|
217
|
+
*,
|
|
218
|
+
bin_count: int = 10,
|
|
219
|
+
thresholds: Iterable[float] = DEFAULT_THRESHOLDS,
|
|
220
|
+
) -> dict[str, Any]:
|
|
221
|
+
"""Validate a disjoint held-out artifact and build a non-activating report."""
|
|
222
|
+
if not isinstance(artifact, dict) or artifact.get("schema") != INPUT_SCHEMA:
|
|
223
|
+
raise CalibrationArtifactError(f"schema must be {INPUT_SCHEMA}")
|
|
224
|
+
calibrator = artifact.get("calibrator")
|
|
225
|
+
evaluation = artifact.get("evaluation")
|
|
226
|
+
examples = artifact.get("examples")
|
|
227
|
+
if not isinstance(calibrator, dict) or not isinstance(evaluation, dict):
|
|
228
|
+
raise CalibrationArtifactError("calibrator and evaluation metadata are required")
|
|
229
|
+
if not isinstance(examples, list):
|
|
230
|
+
raise CalibrationArtifactError("examples must be a list")
|
|
231
|
+
|
|
232
|
+
calibration_id = str(calibrator.get("calibration_id", "")).strip()
|
|
233
|
+
if not calibration_id:
|
|
234
|
+
raise CalibrationArtifactError("calibration_id is required")
|
|
235
|
+
method = str(calibrator.get("method", "")).strip().lower()
|
|
236
|
+
if not method or method in _RAW_AS_PROBABILITY_METHODS:
|
|
237
|
+
raise CalibrationArtifactError(
|
|
238
|
+
"a fitted calibrator is required; a raw score identity mapping is forbidden"
|
|
239
|
+
)
|
|
240
|
+
fit_dataset = str(calibrator.get("fit_dataset_id", "")).strip()
|
|
241
|
+
eval_dataset = str(evaluation.get("dataset_id", "")).strip()
|
|
242
|
+
if not fit_dataset or not eval_dataset or fit_dataset == eval_dataset:
|
|
243
|
+
raise CalibrationArtifactError(
|
|
244
|
+
"calibrator fit and held-out evaluation datasets must be distinct"
|
|
245
|
+
)
|
|
246
|
+
fit_split = str(calibrator.get("fit_split", "")).strip()
|
|
247
|
+
if not fit_split or fit_split == "held_out":
|
|
248
|
+
raise CalibrationArtifactError("fit_split must be distinct from held_out")
|
|
249
|
+
if evaluation.get("split") != "held_out":
|
|
250
|
+
raise CalibrationArtifactError("evaluation split must be held_out")
|
|
251
|
+
fit_artifact_hash = _require_sha256(
|
|
252
|
+
calibrator.get("fit_artifact_sha256"), field="fit_artifact_sha256"
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
validated = _validated_rows(examples)
|
|
256
|
+
if len(validated) < MIN_HELD_OUT_EXAMPLES:
|
|
257
|
+
raise CalibrationArtifactError(
|
|
258
|
+
f"held-out evaluation requires at least {MIN_HELD_OUT_EXAMPLES} examples"
|
|
259
|
+
)
|
|
260
|
+
expected_hash = _require_sha256(
|
|
261
|
+
evaluation.get("examples_sha256"), field="examples_sha256"
|
|
262
|
+
)
|
|
263
|
+
actual_hash = _canonical_sha256(examples)
|
|
264
|
+
if expected_hash != actual_hash:
|
|
265
|
+
raise CalibrationArtifactError("examples hash does not match held-out artifact")
|
|
266
|
+
|
|
267
|
+
metrics = compute_calibration_metrics(
|
|
268
|
+
validated, bin_count=bin_count, thresholds=thresholds
|
|
269
|
+
)
|
|
270
|
+
return {
|
|
271
|
+
"schema": REPORT_SCHEMA,
|
|
272
|
+
"evidence_status": "held_out_report",
|
|
273
|
+
"calibration_id": calibration_id,
|
|
274
|
+
"calibrator": {
|
|
275
|
+
"method": method,
|
|
276
|
+
"fit_dataset_id": fit_dataset,
|
|
277
|
+
"fit_split": fit_split,
|
|
278
|
+
"fit_artifact_sha256": fit_artifact_hash,
|
|
279
|
+
},
|
|
280
|
+
"evaluation": {
|
|
281
|
+
"dataset_id": eval_dataset,
|
|
282
|
+
"split": "held_out",
|
|
283
|
+
"examples_sha256": actual_hash,
|
|
284
|
+
},
|
|
285
|
+
"metrics": metrics,
|
|
286
|
+
"separation": {
|
|
287
|
+
"raw_score_field": "raw_retrieval_score",
|
|
288
|
+
"probability_metric_input": "predicted_probability",
|
|
289
|
+
"raw_retrieval_score_used_as_probability": False,
|
|
290
|
+
},
|
|
291
|
+
"runtime_calibration_status": "uncalibrated",
|
|
292
|
+
"runtime_activation_allowed": False,
|
|
293
|
+
"runtime_activation_reason": (
|
|
294
|
+
"This artifact is evaluation evidence only; production activation "
|
|
295
|
+
"requires a separately reviewed and frozen calibrator contract."
|
|
296
|
+
),
|
|
297
|
+
"input_artifact_sha256": _canonical_sha256(artifact),
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
__all__ = [
|
|
302
|
+
"CalibrationArtifactError",
|
|
303
|
+
"INPUT_SCHEMA",
|
|
304
|
+
"MIN_HELD_OUT_EXAMPLES",
|
|
305
|
+
"REPORT_SCHEMA",
|
|
306
|
+
"build_calibration_report",
|
|
307
|
+
"compute_calibration_metrics",
|
|
308
|
+
]
|
|
@@ -25,6 +25,7 @@ from datetime import datetime, timezone
|
|
|
25
25
|
from pathlib import Path
|
|
26
26
|
from typing import Optional
|
|
27
27
|
|
|
28
|
+
from superlocalmemory.infra.data_root import canonical_data_root
|
|
28
29
|
from superlocalmemory.evolution.types import (
|
|
29
30
|
EvolutionCandidate,
|
|
30
31
|
EvolutionRecord,
|
|
@@ -128,7 +129,7 @@ class SkillEvolver:
|
|
|
128
129
|
# at ~/.superlocalmemory so production callers pick it up
|
|
129
130
|
# automatically. Tests inject their own.
|
|
130
131
|
if budget is None:
|
|
131
|
-
slm_home =
|
|
132
|
+
slm_home = canonical_data_root()
|
|
132
133
|
budget = EvolutionBudget(
|
|
133
134
|
profile_id=profile_id,
|
|
134
135
|
learning_db=Path(self._db_path),
|