superlocalmemory 2.8.5 → 3.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/CHANGELOG.md +11 -0
- package/LICENSE +9 -1
- package/NOTICE +63 -0
- package/README.md +165 -480
- package/bin/slm +17 -449
- package/bin/slm-npm +2 -2
- package/bin/slm.bat +4 -2
- package/conftest.py +5 -0
- package/docs/api-reference.md +284 -0
- package/docs/architecture.md +149 -0
- package/docs/auto-memory.md +150 -0
- package/docs/cli-reference.md +276 -0
- package/docs/compliance.md +191 -0
- package/docs/configuration.md +182 -0
- package/docs/getting-started.md +102 -0
- package/docs/ide-setup.md +261 -0
- package/docs/mcp-tools.md +220 -0
- package/docs/migration-from-v2.md +170 -0
- package/docs/profiles.md +173 -0
- package/docs/troubleshooting.md +310 -0
- package/{configs → ide/configs}/antigravity-mcp.json +3 -3
- package/ide/configs/chatgpt-desktop-mcp.json +16 -0
- package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
- package/{configs → ide/configs}/codex-mcp.toml +4 -4
- package/{configs → ide/configs}/continue-mcp.yaml +4 -3
- package/{configs → ide/configs}/continue-skills.yaml +6 -6
- package/ide/configs/cursor-mcp.json +15 -0
- package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
- package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
- package/{configs → ide/configs}/opencode-mcp.json +2 -2
- package/{configs → ide/configs}/perplexity-mcp.json +2 -2
- package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
- package/{configs → ide/configs}/windsurf-mcp.json +3 -3
- package/{configs → ide/configs}/zed-mcp.json +2 -2
- package/{hooks → ide/hooks}/context-hook.js +9 -20
- package/ide/hooks/memory-list-skill.js +70 -0
- package/ide/hooks/memory-profile-skill.js +101 -0
- package/ide/hooks/memory-recall-skill.js +62 -0
- package/ide/hooks/memory-remember-skill.js +68 -0
- package/ide/hooks/memory-reset-skill.js +160 -0
- package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
- package/ide/integrations/langchain/README.md +106 -0
- package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
- package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
- package/ide/integrations/langchain/pyproject.toml +38 -0
- package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
- package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
- package/ide/integrations/langchain/tests/test_security.py +117 -0
- package/ide/integrations/llamaindex/README.md +81 -0
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
- package/ide/integrations/llamaindex/pyproject.toml +43 -0
- package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
- package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
- package/ide/integrations/llamaindex/tests/test_security.py +241 -0
- package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
- package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
- package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
- package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
- package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
- package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
- package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
- package/package.json +13 -22
- package/pyproject.toml +85 -0
- package/scripts/build-dmg.sh +417 -0
- package/scripts/install-skills.ps1 +334 -0
- package/{install.ps1 → scripts/install.ps1} +36 -4
- package/{install.sh → scripts/install.sh} +14 -13
- package/scripts/postinstall.js +2 -2
- package/scripts/start-dashboard.ps1 +52 -0
- package/scripts/start-dashboard.sh +41 -0
- package/scripts/sync-wiki.ps1 +127 -0
- package/scripts/sync-wiki.sh +82 -0
- package/scripts/test-dmg.sh +161 -0
- package/scripts/test-npm-package.ps1 +252 -0
- package/scripts/test-npm-package.sh +207 -0
- package/scripts/verify-install.ps1 +294 -0
- package/scripts/verify-install.sh +266 -0
- package/src/superlocalmemory/__init__.py +0 -0
- package/src/superlocalmemory/attribution/__init__.py +9 -0
- package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
- package/src/superlocalmemory/attribution/signer.py +153 -0
- package/src/superlocalmemory/attribution/watermark.py +189 -0
- package/src/superlocalmemory/cli/__init__.py +5 -0
- package/src/superlocalmemory/cli/commands.py +245 -0
- package/src/superlocalmemory/cli/main.py +89 -0
- package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
- package/src/superlocalmemory/cli/post_install.py +99 -0
- package/src/superlocalmemory/cli/setup_wizard.py +129 -0
- package/src/superlocalmemory/compliance/__init__.py +0 -0
- package/src/superlocalmemory/compliance/abac.py +204 -0
- package/src/superlocalmemory/compliance/audit.py +314 -0
- package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
- package/src/superlocalmemory/compliance/gdpr.py +294 -0
- package/src/superlocalmemory/compliance/lifecycle.py +158 -0
- package/src/superlocalmemory/compliance/retention.py +232 -0
- package/src/superlocalmemory/compliance/scheduler.py +148 -0
- package/src/superlocalmemory/core/__init__.py +0 -0
- package/src/superlocalmemory/core/config.py +391 -0
- package/src/superlocalmemory/core/embeddings.py +293 -0
- package/src/superlocalmemory/core/engine.py +701 -0
- package/src/superlocalmemory/core/hooks.py +65 -0
- package/src/superlocalmemory/core/maintenance.py +172 -0
- package/src/superlocalmemory/core/modes.py +140 -0
- package/src/superlocalmemory/core/profiles.py +234 -0
- package/src/superlocalmemory/core/registry.py +117 -0
- package/src/superlocalmemory/dynamics/__init__.py +0 -0
- package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
- package/src/superlocalmemory/encoding/__init__.py +0 -0
- package/src/superlocalmemory/encoding/consolidator.py +485 -0
- package/src/superlocalmemory/encoding/emotional.py +125 -0
- package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
- package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
- package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
- package/src/superlocalmemory/encoding/foresight.py +91 -0
- package/src/superlocalmemory/encoding/graph_builder.py +302 -0
- package/src/superlocalmemory/encoding/observation_builder.py +160 -0
- package/src/superlocalmemory/encoding/scene_builder.py +183 -0
- package/src/superlocalmemory/encoding/signal_inference.py +90 -0
- package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
- package/src/superlocalmemory/encoding/type_router.py +235 -0
- package/src/superlocalmemory/hooks/__init__.py +3 -0
- package/src/superlocalmemory/hooks/auto_capture.py +111 -0
- package/src/superlocalmemory/hooks/auto_recall.py +93 -0
- package/src/superlocalmemory/hooks/ide_connector.py +204 -0
- package/src/superlocalmemory/hooks/rules_engine.py +99 -0
- package/src/superlocalmemory/infra/__init__.py +3 -0
- package/src/superlocalmemory/infra/auth_middleware.py +82 -0
- package/src/superlocalmemory/infra/backup.py +317 -0
- package/src/superlocalmemory/infra/cache_manager.py +267 -0
- package/src/superlocalmemory/infra/event_bus.py +381 -0
- package/src/superlocalmemory/infra/rate_limiter.py +135 -0
- package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
- package/src/superlocalmemory/learning/__init__.py +0 -0
- package/src/superlocalmemory/learning/adaptive.py +172 -0
- package/src/superlocalmemory/learning/behavioral.py +490 -0
- package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
- package/src/superlocalmemory/learning/bootstrap.py +298 -0
- package/src/superlocalmemory/learning/cross_project.py +399 -0
- package/src/superlocalmemory/learning/database.py +376 -0
- package/src/superlocalmemory/learning/engagement.py +323 -0
- package/src/superlocalmemory/learning/features.py +138 -0
- package/src/superlocalmemory/learning/feedback.py +316 -0
- package/src/superlocalmemory/learning/outcomes.py +255 -0
- package/src/superlocalmemory/learning/project_context.py +366 -0
- package/src/superlocalmemory/learning/ranker.py +155 -0
- package/src/superlocalmemory/learning/source_quality.py +303 -0
- package/src/superlocalmemory/learning/workflows.py +309 -0
- package/src/superlocalmemory/llm/__init__.py +0 -0
- package/src/superlocalmemory/llm/backbone.py +316 -0
- package/src/superlocalmemory/math/__init__.py +0 -0
- package/src/superlocalmemory/math/fisher.py +356 -0
- package/src/superlocalmemory/math/langevin.py +398 -0
- package/src/superlocalmemory/math/sheaf.py +257 -0
- package/src/superlocalmemory/mcp/__init__.py +0 -0
- package/src/superlocalmemory/mcp/resources.py +245 -0
- package/src/superlocalmemory/mcp/server.py +61 -0
- package/src/superlocalmemory/mcp/tools.py +18 -0
- package/src/superlocalmemory/mcp/tools_core.py +305 -0
- package/src/superlocalmemory/mcp/tools_v28.py +223 -0
- package/src/superlocalmemory/mcp/tools_v3.py +286 -0
- package/src/superlocalmemory/retrieval/__init__.py +0 -0
- package/src/superlocalmemory/retrieval/agentic.py +295 -0
- package/src/superlocalmemory/retrieval/ann_index.py +223 -0
- package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
- package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
- package/src/superlocalmemory/retrieval/engine.py +390 -0
- package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
- package/src/superlocalmemory/retrieval/fusion.py +78 -0
- package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
- package/src/superlocalmemory/retrieval/reranker.py +154 -0
- package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
- package/src/superlocalmemory/retrieval/strategy.py +96 -0
- package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
- package/src/superlocalmemory/server/__init__.py +1 -0
- package/src/superlocalmemory/server/api.py +248 -0
- package/src/superlocalmemory/server/routes/__init__.py +4 -0
- package/src/superlocalmemory/server/routes/agents.py +107 -0
- package/src/superlocalmemory/server/routes/backup.py +91 -0
- package/src/superlocalmemory/server/routes/behavioral.py +127 -0
- package/src/superlocalmemory/server/routes/compliance.py +160 -0
- package/src/superlocalmemory/server/routes/data_io.py +188 -0
- package/src/superlocalmemory/server/routes/events.py +183 -0
- package/src/superlocalmemory/server/routes/helpers.py +85 -0
- package/src/superlocalmemory/server/routes/learning.py +273 -0
- package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
- package/src/superlocalmemory/server/routes/memories.py +399 -0
- package/src/superlocalmemory/server/routes/profiles.py +219 -0
- package/src/superlocalmemory/server/routes/stats.py +346 -0
- package/src/superlocalmemory/server/routes/v3_api.py +365 -0
- package/src/superlocalmemory/server/routes/ws.py +82 -0
- package/src/superlocalmemory/server/security_middleware.py +57 -0
- package/src/superlocalmemory/server/ui.py +245 -0
- package/src/superlocalmemory/storage/__init__.py +0 -0
- package/src/superlocalmemory/storage/access_control.py +182 -0
- package/src/superlocalmemory/storage/database.py +594 -0
- package/src/superlocalmemory/storage/migrations.py +303 -0
- package/src/superlocalmemory/storage/models.py +406 -0
- package/src/superlocalmemory/storage/schema.py +726 -0
- package/src/superlocalmemory/storage/v2_migrator.py +317 -0
- package/src/superlocalmemory/trust/__init__.py +0 -0
- package/src/superlocalmemory/trust/gate.py +130 -0
- package/src/superlocalmemory/trust/provenance.py +124 -0
- package/src/superlocalmemory/trust/scorer.py +347 -0
- package/src/superlocalmemory/trust/signals.py +153 -0
- package/ui/index.html +278 -5
- package/ui/js/auto-settings.js +70 -0
- package/ui/js/dashboard.js +90 -0
- package/ui/js/fact-detail.js +92 -0
- package/ui/js/feedback.js +2 -2
- package/ui/js/ide-status.js +102 -0
- package/ui/js/math-health.js +98 -0
- package/ui/js/recall-lab.js +127 -0
- package/ui/js/settings.js +2 -2
- package/ui/js/trust-dashboard.js +73 -0
- package/api_server.py +0 -724
- package/bin/aider-smart +0 -72
- package/bin/superlocalmemoryv2-learning +0 -4
- package/bin/superlocalmemoryv2-list +0 -3
- package/bin/superlocalmemoryv2-patterns +0 -4
- package/bin/superlocalmemoryv2-profile +0 -3
- package/bin/superlocalmemoryv2-recall +0 -3
- package/bin/superlocalmemoryv2-remember +0 -3
- package/bin/superlocalmemoryv2-reset +0 -3
- package/bin/superlocalmemoryv2-status +0 -3
- package/configs/chatgpt-desktop-mcp.json +0 -16
- package/configs/cursor-mcp.json +0 -15
- package/docs/SECURITY-QUICK-REFERENCE.md +0 -214
- package/hooks/memory-list-skill.js +0 -139
- package/hooks/memory-profile-skill.js +0 -273
- package/hooks/memory-recall-skill.js +0 -114
- package/hooks/memory-remember-skill.js +0 -127
- package/hooks/memory-reset-skill.js +0 -274
- package/mcp_server.py +0 -1800
- package/requirements-core.txt +0 -22
- package/requirements-learning.txt +0 -12
- package/requirements.txt +0 -12
- package/src/agent_registry.py +0 -411
- package/src/auth_middleware.py +0 -61
- package/src/auto_backup.py +0 -459
- package/src/behavioral/__init__.py +0 -49
- package/src/behavioral/behavioral_listener.py +0 -203
- package/src/behavioral/behavioral_patterns.py +0 -275
- package/src/behavioral/cross_project_transfer.py +0 -206
- package/src/behavioral/outcome_inference.py +0 -194
- package/src/behavioral/outcome_tracker.py +0 -193
- package/src/behavioral/tests/__init__.py +0 -4
- package/src/behavioral/tests/test_behavioral_integration.py +0 -108
- package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
- package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
- package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
- package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
- package/src/behavioral/tests/test_outcome_inference.py +0 -107
- package/src/behavioral/tests/test_outcome_tracker.py +0 -96
- package/src/cache_manager.py +0 -518
- package/src/compliance/__init__.py +0 -48
- package/src/compliance/abac_engine.py +0 -149
- package/src/compliance/abac_middleware.py +0 -116
- package/src/compliance/audit_db.py +0 -215
- package/src/compliance/audit_logger.py +0 -148
- package/src/compliance/retention_manager.py +0 -289
- package/src/compliance/retention_scheduler.py +0 -186
- package/src/compliance/tests/__init__.py +0 -4
- package/src/compliance/tests/test_abac_enforcement.py +0 -95
- package/src/compliance/tests/test_abac_engine.py +0 -124
- package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
- package/src/compliance/tests/test_audit_db.py +0 -123
- package/src/compliance/tests/test_audit_logger.py +0 -98
- package/src/compliance/tests/test_mcp_audit.py +0 -128
- package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
- package/src/compliance/tests/test_retention_manager.py +0 -131
- package/src/compliance/tests/test_retention_scheduler.py +0 -99
- package/src/compression/__init__.py +0 -25
- package/src/compression/cli.py +0 -150
- package/src/compression/cold_storage.py +0 -217
- package/src/compression/config.py +0 -72
- package/src/compression/orchestrator.py +0 -133
- package/src/compression/tier2_compressor.py +0 -228
- package/src/compression/tier3_compressor.py +0 -153
- package/src/compression/tier_classifier.py +0 -148
- package/src/db_connection_manager.py +0 -536
- package/src/embedding_engine.py +0 -63
- package/src/embeddings/__init__.py +0 -47
- package/src/embeddings/cache.py +0 -70
- package/src/embeddings/cli.py +0 -113
- package/src/embeddings/constants.py +0 -47
- package/src/embeddings/database.py +0 -91
- package/src/embeddings/engine.py +0 -247
- package/src/embeddings/model_loader.py +0 -145
- package/src/event_bus.py +0 -562
- package/src/graph/__init__.py +0 -36
- package/src/graph/build_helpers.py +0 -74
- package/src/graph/cli.py +0 -87
- package/src/graph/cluster_builder.py +0 -188
- package/src/graph/cluster_summary.py +0 -148
- package/src/graph/constants.py +0 -47
- package/src/graph/edge_builder.py +0 -162
- package/src/graph/entity_extractor.py +0 -95
- package/src/graph/graph_core.py +0 -226
- package/src/graph/graph_search.py +0 -231
- package/src/graph/hierarchical.py +0 -207
- package/src/graph/schema.py +0 -99
- package/src/graph_engine.py +0 -52
- package/src/hnsw_index.py +0 -628
- package/src/hybrid_search.py +0 -46
- package/src/learning/__init__.py +0 -217
- package/src/learning/adaptive_ranker.py +0 -682
- package/src/learning/bootstrap/__init__.py +0 -69
- package/src/learning/bootstrap/constants.py +0 -93
- package/src/learning/bootstrap/db_queries.py +0 -316
- package/src/learning/bootstrap/sampling.py +0 -82
- package/src/learning/bootstrap/text_utils.py +0 -71
- package/src/learning/cross_project_aggregator.py +0 -857
- package/src/learning/db/__init__.py +0 -40
- package/src/learning/db/constants.py +0 -44
- package/src/learning/db/schema.py +0 -279
- package/src/learning/engagement_tracker.py +0 -628
- package/src/learning/feature_extractor.py +0 -708
- package/src/learning/feedback_collector.py +0 -806
- package/src/learning/learning_db.py +0 -915
- package/src/learning/project_context_manager.py +0 -572
- package/src/learning/ranking/__init__.py +0 -33
- package/src/learning/ranking/constants.py +0 -84
- package/src/learning/ranking/helpers.py +0 -278
- package/src/learning/source_quality_scorer.py +0 -676
- package/src/learning/synthetic_bootstrap.py +0 -755
- package/src/learning/tests/test_adaptive_ranker.py +0 -325
- package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
- package/src/learning/tests/test_aggregator.py +0 -306
- package/src/learning/tests/test_auto_retrain_v28.py +0 -35
- package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
- package/src/learning/tests/test_feature_extractor_v28.py +0 -93
- package/src/learning/tests/test_feedback_collector.py +0 -294
- package/src/learning/tests/test_learning_db.py +0 -602
- package/src/learning/tests/test_learning_db_v28.py +0 -110
- package/src/learning/tests/test_learning_init_v28.py +0 -48
- package/src/learning/tests/test_outcome_signals.py +0 -48
- package/src/learning/tests/test_project_context.py +0 -292
- package/src/learning/tests/test_schema_migration.py +0 -319
- package/src/learning/tests/test_signal_inference.py +0 -397
- package/src/learning/tests/test_source_quality.py +0 -351
- package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
- package/src/learning/tests/test_workflow_miner.py +0 -318
- package/src/learning/workflow_pattern_miner.py +0 -655
- package/src/lifecycle/__init__.py +0 -54
- package/src/lifecycle/bounded_growth.py +0 -239
- package/src/lifecycle/compaction_engine.py +0 -226
- package/src/lifecycle/lifecycle_engine.py +0 -355
- package/src/lifecycle/lifecycle_evaluator.py +0 -257
- package/src/lifecycle/lifecycle_scheduler.py +0 -130
- package/src/lifecycle/retention_policy.py +0 -285
- package/src/lifecycle/tests/test_bounded_growth.py +0 -193
- package/src/lifecycle/tests/test_compaction.py +0 -179
- package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
- package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
- package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
- package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
- package/src/lifecycle/tests/test_mcp_compact.py +0 -149
- package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
- package/src/lifecycle/tests/test_retention_policy.py +0 -162
- package/src/mcp_tools_v28.py +0 -281
- package/src/memory/__init__.py +0 -36
- package/src/memory/cli.py +0 -205
- package/src/memory/constants.py +0 -39
- package/src/memory/helpers.py +0 -28
- package/src/memory/schema.py +0 -166
- package/src/memory-profiles.py +0 -595
- package/src/memory-reset.py +0 -491
- package/src/memory_compression.py +0 -989
- package/src/memory_store_v2.py +0 -1155
- package/src/migrate_v1_to_v2.py +0 -629
- package/src/pattern_learner.py +0 -34
- package/src/patterns/__init__.py +0 -24
- package/src/patterns/analyzers.py +0 -251
- package/src/patterns/learner.py +0 -271
- package/src/patterns/scoring.py +0 -171
- package/src/patterns/store.py +0 -225
- package/src/patterns/terminology.py +0 -140
- package/src/provenance_tracker.py +0 -312
- package/src/qualixar_attribution.py +0 -139
- package/src/qualixar_watermark.py +0 -78
- package/src/query_optimizer.py +0 -511
- package/src/rate_limiter.py +0 -83
- package/src/search/__init__.py +0 -20
- package/src/search/cli.py +0 -77
- package/src/search/constants.py +0 -26
- package/src/search/engine.py +0 -241
- package/src/search/fusion.py +0 -122
- package/src/search/index_loader.py +0 -114
- package/src/search/methods.py +0 -162
- package/src/search_engine_v2.py +0 -401
- package/src/setup_validator.py +0 -482
- package/src/subscription_manager.py +0 -391
- package/src/tree/__init__.py +0 -59
- package/src/tree/builder.py +0 -185
- package/src/tree/nodes.py +0 -202
- package/src/tree/queries.py +0 -257
- package/src/tree/schema.py +0 -80
- package/src/tree_manager.py +0 -19
- package/src/trust/__init__.py +0 -45
- package/src/trust/constants.py +0 -66
- package/src/trust/queries.py +0 -157
- package/src/trust/schema.py +0 -95
- package/src/trust/scorer.py +0 -299
- package/src/trust/signals.py +0 -95
- package/src/trust_scorer.py +0 -44
- package/ui/app.js +0 -1588
- package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
- package/ui/js/graph-cytoscape.js +0 -1168
- package/ui/js/graph-d3-backup.js +0 -32
- package/ui/js/graph.js +0 -32
- package/ui_server.py +0 -266
- /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
- /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
- /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
- /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
- /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
- /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
- /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
- /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
- /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
- /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
- /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
- /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
- /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
- /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
- /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
- /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
- /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
- /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
- /package/{completions → ide/completions}/slm.bash +0 -0
- /package/{completions → ide/completions}/slm.zsh +0 -0
- /package/{configs → ide/configs}/cody-commands.json +0 -0
- /package/{install-skills.sh → scripts/install-skills.sh} +0 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under the MIT License - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
|
|
5
|
+
"""SuperLocalMemory V3 — Configuration.
|
|
6
|
+
|
|
7
|
+
Unified configuration with Mode A/B/C capability matrix.
|
|
8
|
+
Clean — zero dead options, every config has a consumer.
|
|
9
|
+
|
|
10
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from superlocalmemory.storage.models import Mode
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
# Default Paths
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
DEFAULT_BASE_DIR = Path.home() / ".superlocalmemory"
|
|
26
|
+
DEFAULT_DB_NAME = "memory.db"
|
|
27
|
+
DEFAULT_PROFILES_FILE = "profiles.json"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# Embedding Config
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class EmbeddingConfig:
|
|
36
|
+
"""Embedding model configuration per mode."""
|
|
37
|
+
|
|
38
|
+
model_name: str = "nomic-ai/nomic-embed-text-v1.5"
|
|
39
|
+
dimension: int = 768
|
|
40
|
+
# Azure / cloud settings (Mode C only)
|
|
41
|
+
api_endpoint: str = ""
|
|
42
|
+
api_key: str = ""
|
|
43
|
+
api_version: str = "2024-02-01"
|
|
44
|
+
deployment_name: str = ""
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def is_cloud(self) -> bool:
|
|
48
|
+
return bool(self.api_endpoint)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# ---------------------------------------------------------------------------
|
|
52
|
+
# LLM Config
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
@dataclass(frozen=True)
|
|
56
|
+
class LLMConfig:
|
|
57
|
+
"""LLM provider configuration per mode."""
|
|
58
|
+
|
|
59
|
+
provider: str = "" # "" = no LLM, "ollama", "azure", "openai", "anthropic"
|
|
60
|
+
model: str = "" # Model name/deployment
|
|
61
|
+
api_key: str = ""
|
|
62
|
+
api_base: str = ""
|
|
63
|
+
temperature: float = 0.0 # Deterministic by default
|
|
64
|
+
max_tokens: int = 4096
|
|
65
|
+
timeout_seconds: float = 60.0
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def is_available(self) -> bool:
|
|
69
|
+
return bool(self.provider)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
# Channel Weights
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class ChannelWeights:
|
|
78
|
+
"""Retrieval channel weights — 4 channels, query-adaptive."""
|
|
79
|
+
|
|
80
|
+
# Entity-linked facts are high-precision matches that rank above BM25.
|
|
81
|
+
semantic: float = 1.2
|
|
82
|
+
bm25: float = 1.0
|
|
83
|
+
entity_graph: float = 1.3
|
|
84
|
+
temporal: float = 1.0
|
|
85
|
+
|
|
86
|
+
def as_dict(self) -> dict[str, float]:
|
|
87
|
+
return {
|
|
88
|
+
"semantic": self.semantic,
|
|
89
|
+
"bm25": self.bm25,
|
|
90
|
+
"entity_graph": self.entity_graph,
|
|
91
|
+
"temporal": self.temporal,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# ---------------------------------------------------------------------------
|
|
96
|
+
# Encoding Config
|
|
97
|
+
# ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
@dataclass
|
|
100
|
+
class EncodingConfig:
|
|
101
|
+
"""Configuration for the encoding (memory creation) pipeline."""
|
|
102
|
+
|
|
103
|
+
# Fact extraction
|
|
104
|
+
chunk_size: int = 10 # Conversation turns per extraction chunk
|
|
105
|
+
max_facts_per_chunk: int = 5 # Max facts extracted per chunk
|
|
106
|
+
min_fact_confidence: float = 0.3
|
|
107
|
+
|
|
108
|
+
# Entity resolution
|
|
109
|
+
entity_similarity_threshold: float = 0.85
|
|
110
|
+
max_entity_candidates: int = 10
|
|
111
|
+
|
|
112
|
+
# Graph construction
|
|
113
|
+
semantic_edge_top_k: int = 5 # Top-K semantic edges per new fact
|
|
114
|
+
temporal_edge_window_hours: int = 168 # 1 week
|
|
115
|
+
|
|
116
|
+
# Consolidation
|
|
117
|
+
consolidation_similarity_threshold: float = 0.85
|
|
118
|
+
max_consolidation_candidates: int = 5
|
|
119
|
+
|
|
120
|
+
# Entropy gate
|
|
121
|
+
entropy_threshold: float = 0.95
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# ---------------------------------------------------------------------------
|
|
125
|
+
# Retrieval Config
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
@dataclass
|
|
129
|
+
class RetrievalConfig:
|
|
130
|
+
"""Configuration for the retrieval (recall) pipeline."""
|
|
131
|
+
|
|
132
|
+
# Fusion
|
|
133
|
+
rrf_k: int = 60 # RRF smoothing constant (D116: k=60 for diversity)
|
|
134
|
+
top_k: int = 20 # Final results to return
|
|
135
|
+
|
|
136
|
+
# Per-channel
|
|
137
|
+
semantic_top_k: int = 50 # ANN pre-filter candidates
|
|
138
|
+
bm25_top_k: int = 50
|
|
139
|
+
entity_graph_max_hops: int = 3
|
|
140
|
+
temporal_proximity_days: int = 30
|
|
141
|
+
|
|
142
|
+
# Reranking
|
|
143
|
+
use_cross_encoder: bool = True
|
|
144
|
+
cross_encoder_model: str = "BAAI/bge-reranker-v2-m3"
|
|
145
|
+
|
|
146
|
+
# Agentic (Mode C only)
|
|
147
|
+
agentic_max_rounds: int = 3
|
|
148
|
+
agentic_confidence_threshold: float = 0.3
|
|
149
|
+
|
|
150
|
+
# Spreading activation
|
|
151
|
+
spreading_activation_decay: float = 0.7
|
|
152
|
+
spreading_activation_threshold: float = 0.1
|
|
153
|
+
|
|
154
|
+
# Trust weighting — apply Bayesian trust scores to retrieval ranking.
|
|
155
|
+
# When enabled, each fact's score is multiplied by a trust weight in [0.5, 1.5].
|
|
156
|
+
# Low-trust facts are demoted; high-trust facts are promoted.
|
|
157
|
+
# Default trust = 1.0 (no effect when no trust data exists).
|
|
158
|
+
use_trust_weighting: bool = True
|
|
159
|
+
|
|
160
|
+
# Ablation channel control for experiments.
|
|
161
|
+
# List of channel names to SKIP during retrieval (e.g., ["bm25", "entity_graph"]).
|
|
162
|
+
# Used by s19_runner for ablation experiments. Empty = all channels active.
|
|
163
|
+
disabled_channels: list[str] = field(default_factory=list)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# ---------------------------------------------------------------------------
|
|
167
|
+
# Math Config
|
|
168
|
+
# ---------------------------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
@dataclass
|
|
171
|
+
class MathConfig:
|
|
172
|
+
"""Configuration for mathematical layers."""
|
|
173
|
+
|
|
174
|
+
# Fisher-Rao
|
|
175
|
+
fisher_temperature: float = 15.0
|
|
176
|
+
fisher_bayesian_update: bool = True
|
|
177
|
+
# "simplified" = local Mahalanobis-like (fast, existing behaviour)
|
|
178
|
+
# "full" = Atkinson-Mitchell geodesic from FisherRaoMetric class
|
|
179
|
+
fisher_mode: str = "simplified"
|
|
180
|
+
|
|
181
|
+
# Langevin
|
|
182
|
+
langevin_dt: float = 0.005
|
|
183
|
+
langevin_temperature: float = 0.3
|
|
184
|
+
langevin_persist_positions: bool = True
|
|
185
|
+
langevin_weight_range: tuple[float, float] = (0.0, 1.0)
|
|
186
|
+
|
|
187
|
+
# Hopfield
|
|
188
|
+
|
|
189
|
+
# Sheaf (at encoding time, NOT retrieval)
|
|
190
|
+
sheaf_at_encoding: bool = True
|
|
191
|
+
sheaf_contradiction_threshold: float = 0.45
|
|
192
|
+
# Max edges to check per fact during sheaf consistency.
|
|
193
|
+
# At 18K+ edges, coboundary computation becomes O(N*dim^2) and hangs.
|
|
194
|
+
# Facts with more edges than this skip sheaf check (still get contradiction
|
|
195
|
+
# detection via consolidator UPDATE/SUPERSEDE path).
|
|
196
|
+
sheaf_max_edges_per_check: int = 200
|
|
197
|
+
|
|
198
|
+
# Rate-Distortion (production only, disabled for benchmarks)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
# ---------------------------------------------------------------------------
|
|
202
|
+
# Master Config
|
|
203
|
+
# ---------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
@dataclass
|
|
206
|
+
class SLMConfig:
|
|
207
|
+
"""Master configuration for SuperLocalMemory V3.
|
|
208
|
+
|
|
209
|
+
Create via SLMConfig.for_mode(Mode.A) for mode-specific defaults.
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
mode: Mode = Mode.A
|
|
213
|
+
base_dir: Path = DEFAULT_BASE_DIR
|
|
214
|
+
db_path: Path | None = None # Computed from base_dir if None
|
|
215
|
+
active_profile: str = "default"
|
|
216
|
+
|
|
217
|
+
embedding: EmbeddingConfig = field(default_factory=EmbeddingConfig)
|
|
218
|
+
llm: LLMConfig = field(default_factory=LLMConfig)
|
|
219
|
+
channel_weights: ChannelWeights = field(default_factory=ChannelWeights)
|
|
220
|
+
encoding: EncodingConfig = field(default_factory=EncodingConfig)
|
|
221
|
+
retrieval: RetrievalConfig = field(default_factory=RetrievalConfig)
|
|
222
|
+
math: MathConfig = field(default_factory=MathConfig)
|
|
223
|
+
|
|
224
|
+
def __post_init__(self) -> None:
|
|
225
|
+
if self.db_path is None:
|
|
226
|
+
self.db_path = self.base_dir / DEFAULT_DB_NAME
|
|
227
|
+
|
|
228
|
+
@classmethod
|
|
229
|
+
def load(cls, config_path: Path | None = None) -> SLMConfig:
|
|
230
|
+
"""Load config from JSON file. Returns default Mode A if file doesn't exist."""
|
|
231
|
+
path = config_path or (DEFAULT_BASE_DIR / "config.json")
|
|
232
|
+
if not path.exists():
|
|
233
|
+
return cls.for_mode(Mode.A)
|
|
234
|
+
import json
|
|
235
|
+
data = json.loads(path.read_text())
|
|
236
|
+
mode = Mode(data.get("mode", "a"))
|
|
237
|
+
llm_data = data.get("llm", {})
|
|
238
|
+
config = cls.for_mode(
|
|
239
|
+
mode,
|
|
240
|
+
llm_provider=llm_data.get("provider", ""),
|
|
241
|
+
llm_model=llm_data.get("model", ""),
|
|
242
|
+
llm_api_key=llm_data.get("api_key", ""),
|
|
243
|
+
llm_api_base=llm_data.get("base_url", ""),
|
|
244
|
+
embedding_endpoint=data.get("embedding", {}).get("api_endpoint", ""),
|
|
245
|
+
embedding_key=data.get("embedding", {}).get("api_key", ""),
|
|
246
|
+
embedding_deployment=data.get("embedding", {}).get("deployment_name", ""),
|
|
247
|
+
)
|
|
248
|
+
config.active_profile = data.get("active_profile", "default")
|
|
249
|
+
return config
|
|
250
|
+
|
|
251
|
+
def save(self, config_path: Path | None = None) -> None:
|
|
252
|
+
"""Save config to JSON file."""
|
|
253
|
+
import json
|
|
254
|
+
path = config_path or (self.base_dir / "config.json")
|
|
255
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
256
|
+
data = {
|
|
257
|
+
"mode": self.mode.value,
|
|
258
|
+
"active_profile": self.active_profile,
|
|
259
|
+
"llm": {
|
|
260
|
+
"provider": self.llm.provider,
|
|
261
|
+
"model": self.llm.model,
|
|
262
|
+
"api_key": self.llm.api_key,
|
|
263
|
+
"base_url": self.llm.api_base,
|
|
264
|
+
},
|
|
265
|
+
"embedding": {
|
|
266
|
+
"model_name": self.embedding.model_name,
|
|
267
|
+
"dimension": self.embedding.dimension,
|
|
268
|
+
"api_endpoint": self.embedding.api_endpoint,
|
|
269
|
+
"api_key": self.embedding.api_key,
|
|
270
|
+
"deployment_name": self.embedding.deployment_name,
|
|
271
|
+
},
|
|
272
|
+
}
|
|
273
|
+
path.write_text(json.dumps(data, indent=2))
|
|
274
|
+
|
|
275
|
+
@staticmethod
|
|
276
|
+
def provider_presets() -> dict[str, dict[str, str]]:
|
|
277
|
+
"""Provider presets for setup wizard."""
|
|
278
|
+
return {
|
|
279
|
+
"openai": {
|
|
280
|
+
"base_url": "https://api.openai.com/v1",
|
|
281
|
+
"model": "gpt-4.1-mini",
|
|
282
|
+
"embedding_model": "text-embedding-3-large",
|
|
283
|
+
"env_key": "OPENAI_API_KEY",
|
|
284
|
+
},
|
|
285
|
+
"anthropic": {
|
|
286
|
+
"base_url": "https://api.anthropic.com",
|
|
287
|
+
"model": "claude-sonnet-4-6",
|
|
288
|
+
"embedding_model": "",
|
|
289
|
+
"env_key": "ANTHROPIC_API_KEY",
|
|
290
|
+
},
|
|
291
|
+
"ollama": {
|
|
292
|
+
"base_url": "http://localhost:11434",
|
|
293
|
+
"model": "llama3.2",
|
|
294
|
+
"embedding_model": "nomic-embed-text",
|
|
295
|
+
"env_key": "",
|
|
296
|
+
},
|
|
297
|
+
"openrouter": {
|
|
298
|
+
"base_url": "https://openrouter.ai/api/v1",
|
|
299
|
+
"model": "openai/gpt-4.1-mini",
|
|
300
|
+
"embedding_model": "",
|
|
301
|
+
"env_key": "OPENROUTER_API_KEY",
|
|
302
|
+
},
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
@classmethod
|
|
306
|
+
def default(cls) -> SLMConfig:
|
|
307
|
+
"""Create default Mode A configuration."""
|
|
308
|
+
return cls.for_mode(Mode.A)
|
|
309
|
+
|
|
310
|
+
@classmethod
|
|
311
|
+
def for_mode(
|
|
312
|
+
cls,
|
|
313
|
+
mode: Mode,
|
|
314
|
+
base_dir: Path | None = None,
|
|
315
|
+
*,
|
|
316
|
+
llm_provider: str = "",
|
|
317
|
+
llm_model: str = "",
|
|
318
|
+
llm_api_key: str = "",
|
|
319
|
+
llm_api_base: str = "",
|
|
320
|
+
embedding_endpoint: str = "",
|
|
321
|
+
embedding_key: str = "",
|
|
322
|
+
embedding_deployment: str = "",
|
|
323
|
+
) -> SLMConfig:
|
|
324
|
+
"""Create config with mode-appropriate defaults."""
|
|
325
|
+
_base = base_dir or DEFAULT_BASE_DIR
|
|
326
|
+
|
|
327
|
+
if mode == Mode.A:
|
|
328
|
+
return cls(
|
|
329
|
+
mode=mode,
|
|
330
|
+
base_dir=_base,
|
|
331
|
+
embedding=EmbeddingConfig(
|
|
332
|
+
model_name="nomic-ai/nomic-embed-text-v1.5",
|
|
333
|
+
dimension=768,
|
|
334
|
+
),
|
|
335
|
+
llm=LLMConfig(), # No LLM
|
|
336
|
+
retrieval=RetrievalConfig(
|
|
337
|
+
use_cross_encoder=True, # Hotpatch: CE ON for Mode A with bge-reranker
|
|
338
|
+
),
|
|
339
|
+
math=MathConfig(
|
|
340
|
+
sheaf_contradiction_threshold=0.45, # 768d threshold
|
|
341
|
+
),
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
if mode == Mode.B:
|
|
345
|
+
return cls(
|
|
346
|
+
mode=mode,
|
|
347
|
+
base_dir=_base,
|
|
348
|
+
embedding=EmbeddingConfig(
|
|
349
|
+
model_name="nomic-ai/nomic-embed-text-v1.5",
|
|
350
|
+
dimension=768,
|
|
351
|
+
),
|
|
352
|
+
llm=LLMConfig(
|
|
353
|
+
provider=llm_provider or "ollama",
|
|
354
|
+
model=llm_model or "phi3:mini",
|
|
355
|
+
api_base=llm_api_base or "http://localhost:11434",
|
|
356
|
+
),
|
|
357
|
+
retrieval=RetrievalConfig(use_cross_encoder=True),
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
# Mode C — FULL POWER, UNRESTRICTED
|
|
361
|
+
return cls(
|
|
362
|
+
mode=mode,
|
|
363
|
+
base_dir=_base,
|
|
364
|
+
embedding=EmbeddingConfig(
|
|
365
|
+
model_name="text-embedding-3-large",
|
|
366
|
+
dimension=3072,
|
|
367
|
+
api_endpoint=embedding_endpoint,
|
|
368
|
+
api_key=embedding_key,
|
|
369
|
+
deployment_name=embedding_deployment,
|
|
370
|
+
),
|
|
371
|
+
llm=LLMConfig(
|
|
372
|
+
provider=llm_provider or "azure",
|
|
373
|
+
model=llm_model or "gpt-4.1-mini",
|
|
374
|
+
api_key=llm_api_key,
|
|
375
|
+
api_base=llm_api_base,
|
|
376
|
+
),
|
|
377
|
+
channel_weights=ChannelWeights(
|
|
378
|
+
semantic=1.5,
|
|
379
|
+
bm25=1.2,
|
|
380
|
+
entity_graph=1.3,
|
|
381
|
+
temporal=1.0,
|
|
382
|
+
),
|
|
383
|
+
retrieval=RetrievalConfig(
|
|
384
|
+
use_cross_encoder=True,
|
|
385
|
+
semantic_top_k=80,
|
|
386
|
+
agentic_max_rounds=2, # EverMemOS 2-round
|
|
387
|
+
),
|
|
388
|
+
math=MathConfig(
|
|
389
|
+
sheaf_contradiction_threshold=0.65, # Higher for 3072d embeddings
|
|
390
|
+
),
|
|
391
|
+
)
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under the MIT License - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
|
|
5
|
+
"""SuperLocalMemory V3 — Embedding Service.
|
|
6
|
+
|
|
7
|
+
Thread-safe, dimension-validated embedding with Fisher variance computation.
|
|
8
|
+
Supports local (768-dim nomic) and cloud (3072-dim) models with EXPLICIT errors
|
|
9
|
+
on dimension mismatch — NEVER silently falls back to a different dimension.
|
|
10
|
+
|
|
11
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
import threading
|
|
18
|
+
import time
|
|
19
|
+
from typing import TYPE_CHECKING
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from numpy.typing import NDArray
|
|
25
|
+
|
|
26
|
+
from superlocalmemory.core.config import EmbeddingConfig
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# Fisher variance constants
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
_FISHER_VAR_MIN = 0.05
|
|
34
|
+
_FISHER_VAR_MAX = 2.0
|
|
35
|
+
_FISHER_VAR_RANGE = _FISHER_VAR_MAX - _FISHER_VAR_MIN # 1.95
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class DimensionMismatchError(RuntimeError):
|
|
39
|
+
"""Raised when the actual embedding dimension differs from config.
|
|
40
|
+
|
|
41
|
+
This is a HARD failure — V1 silently fell back to local embeddings
|
|
42
|
+
when Azure failed, changing dimension from 3072 to 768 mid-run.
|
|
43
|
+
We crash loudly instead.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class EmbeddingService:
|
|
48
|
+
"""Thread-safe embedding service with strict dimension validation.
|
|
49
|
+
|
|
50
|
+
Lazy-loads the underlying model on first embed call.
|
|
51
|
+
Validates every output dimension against the configured expectation.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self, config: EmbeddingConfig) -> None:
|
|
55
|
+
self._config = config
|
|
56
|
+
self._model: object | None = None
|
|
57
|
+
self._lock = threading.Lock()
|
|
58
|
+
self._loaded = False
|
|
59
|
+
|
|
60
|
+
# ------------------------------------------------------------------
|
|
61
|
+
# Public API
|
|
62
|
+
# ------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def dimension(self) -> int:
|
|
66
|
+
"""Expected embedding dimension (from config)."""
|
|
67
|
+
return self._config.dimension
|
|
68
|
+
|
|
69
|
+
def embed(self, text: str) -> list[float]:
|
|
70
|
+
"""Embed a single text string.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
L2-normalized embedding of exactly ``self.dimension`` floats.
|
|
74
|
+
|
|
75
|
+
Raises:
|
|
76
|
+
ValueError: If text is empty.
|
|
77
|
+
DimensionMismatchError: If output dimension != config.
|
|
78
|
+
"""
|
|
79
|
+
if not text or not text.strip():
|
|
80
|
+
raise ValueError("Cannot embed empty text")
|
|
81
|
+
vec = self._encode_single(text)
|
|
82
|
+
self._validate_dimension(vec)
|
|
83
|
+
return vec.tolist()
|
|
84
|
+
|
|
85
|
+
def embed_batch(self, texts: list[str]) -> list[list[float]]:
|
|
86
|
+
"""Embed a batch of texts.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
List of L2-normalized embeddings, each ``self.dimension`` floats.
|
|
90
|
+
|
|
91
|
+
Raises:
|
|
92
|
+
ValueError: If any text is empty or list is empty.
|
|
93
|
+
DimensionMismatchError: If any output dimension != config.
|
|
94
|
+
"""
|
|
95
|
+
if not texts:
|
|
96
|
+
raise ValueError("Cannot embed empty batch")
|
|
97
|
+
for i, t in enumerate(texts):
|
|
98
|
+
if not t or not t.strip():
|
|
99
|
+
raise ValueError(f"Text at index {i} is empty")
|
|
100
|
+
|
|
101
|
+
vectors = self._encode_batch(texts)
|
|
102
|
+
for vec in vectors:
|
|
103
|
+
self._validate_dimension(vec)
|
|
104
|
+
return [v.tolist() for v in vectors]
|
|
105
|
+
|
|
106
|
+
def compute_fisher_params(
|
|
107
|
+
self,
|
|
108
|
+
embedding: list[float],
|
|
109
|
+
) -> tuple[list[float], list[float]]:
|
|
110
|
+
"""Compute Fisher-Rao parameters from a raw embedding.
|
|
111
|
+
|
|
112
|
+
Variance is content-derived (NOT uniform). Dimensions with strong
|
|
113
|
+
signal (high absolute value) get LOW variance (high confidence).
|
|
114
|
+
Weak-signal dimensions get HIGH variance (uncertainty).
|
|
115
|
+
|
|
116
|
+
This heterogeneous variance is what gives Fisher-Rao metric
|
|
117
|
+
discriminative power beyond simple cosine similarity.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
embedding: Raw embedding vector (already L2-normalized).
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
(mean, variance) — both lists of ``self.dimension`` floats.
|
|
124
|
+
Variance values are clamped to [0.3, 2.0].
|
|
125
|
+
"""
|
|
126
|
+
arr = np.asarray(embedding, dtype=np.float64)
|
|
127
|
+
norm = float(np.linalg.norm(arr))
|
|
128
|
+
|
|
129
|
+
if norm < 1e-10:
|
|
130
|
+
mean = np.zeros(len(arr), dtype=np.float64)
|
|
131
|
+
variance = np.full(len(arr), _FISHER_VAR_MAX, dtype=np.float64)
|
|
132
|
+
return mean.tolist(), variance.tolist()
|
|
133
|
+
|
|
134
|
+
mean = arr / norm
|
|
135
|
+
|
|
136
|
+
# Content-derived heterogeneous variance
|
|
137
|
+
abs_mean = np.abs(mean)
|
|
138
|
+
max_val = float(np.max(abs_mean)) + 1e-10
|
|
139
|
+
signal_strength = abs_mean / max_val # [0, 1]
|
|
140
|
+
|
|
141
|
+
# Inverse: strong signal -> low variance, weak -> high
|
|
142
|
+
variance = _FISHER_VAR_MAX - _FISHER_VAR_RANGE * signal_strength
|
|
143
|
+
variance = np.clip(variance, _FISHER_VAR_MIN, _FISHER_VAR_MAX)
|
|
144
|
+
|
|
145
|
+
return mean.tolist(), variance.tolist()
|
|
146
|
+
|
|
147
|
+
# ------------------------------------------------------------------
|
|
148
|
+
# Internals — model loading
|
|
149
|
+
# ------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
def _ensure_loaded(self) -> None:
|
|
152
|
+
"""Lazy-load the model on first use (thread-safe)."""
|
|
153
|
+
if self._loaded:
|
|
154
|
+
return
|
|
155
|
+
with self._lock:
|
|
156
|
+
if self._loaded:
|
|
157
|
+
return
|
|
158
|
+
if self._config.is_cloud:
|
|
159
|
+
# Cloud mode: no local model needed, validate config
|
|
160
|
+
if not self._config.api_endpoint or not self._config.api_key:
|
|
161
|
+
raise RuntimeError(
|
|
162
|
+
"Cloud embedding requires api_endpoint and api_key"
|
|
163
|
+
)
|
|
164
|
+
logger.info(
|
|
165
|
+
"EmbeddingService: cloud mode (%s, %d-dim)",
|
|
166
|
+
self._config.deployment_name,
|
|
167
|
+
self._config.dimension,
|
|
168
|
+
)
|
|
169
|
+
else:
|
|
170
|
+
self._load_local_model()
|
|
171
|
+
self._loaded = True
|
|
172
|
+
|
|
173
|
+
def _load_local_model(self) -> None:
|
|
174
|
+
"""Load sentence-transformers model for local embedding."""
|
|
175
|
+
try:
|
|
176
|
+
from sentence_transformers import SentenceTransformer
|
|
177
|
+
except ImportError:
|
|
178
|
+
raise ImportError(
|
|
179
|
+
"sentence-transformers required: "
|
|
180
|
+
"pip install sentence-transformers"
|
|
181
|
+
)
|
|
182
|
+
model = SentenceTransformer(
|
|
183
|
+
self._config.model_name, trust_remote_code=False,
|
|
184
|
+
)
|
|
185
|
+
actual_dim = model.get_sentence_embedding_dimension()
|
|
186
|
+
if actual_dim != self._config.dimension:
|
|
187
|
+
raise DimensionMismatchError(
|
|
188
|
+
f"Model '{self._config.model_name}' produces {actual_dim}-dim "
|
|
189
|
+
f"embeddings but config expects {self._config.dimension}-dim"
|
|
190
|
+
)
|
|
191
|
+
self._model = model
|
|
192
|
+
logger.info(
|
|
193
|
+
"EmbeddingService: local model loaded (%s, %d-dim)",
|
|
194
|
+
self._config.model_name,
|
|
195
|
+
actual_dim,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# ------------------------------------------------------------------
|
|
199
|
+
# Internals — encoding
|
|
200
|
+
# ------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
def _encode_single(self, text: str) -> NDArray[np.float32]:
|
|
203
|
+
"""Encode one text. Dispatches to local or cloud."""
|
|
204
|
+
self._ensure_loaded()
|
|
205
|
+
if self._config.is_cloud:
|
|
206
|
+
return self._cloud_embed([text])[0]
|
|
207
|
+
return self._local_embed_batch([text])[0]
|
|
208
|
+
|
|
209
|
+
def _encode_batch(self, texts: list[str]) -> list[NDArray[np.float32]]:
|
|
210
|
+
"""Encode a batch. Dispatches to local or cloud."""
|
|
211
|
+
self._ensure_loaded()
|
|
212
|
+
if self._config.is_cloud:
|
|
213
|
+
return self._cloud_embed(texts)
|
|
214
|
+
return self._local_embed_batch(texts)
|
|
215
|
+
|
|
216
|
+
def _local_embed_batch(
|
|
217
|
+
self,
|
|
218
|
+
texts: list[str],
|
|
219
|
+
) -> list[NDArray[np.float32]]:
|
|
220
|
+
"""Encode via local sentence-transformers (L2-normalized)."""
|
|
221
|
+
if self._model is None:
|
|
222
|
+
raise RuntimeError("Local model not loaded")
|
|
223
|
+
vecs = self._model.encode(texts, normalize_embeddings=True)
|
|
224
|
+
if isinstance(vecs, np.ndarray) and vecs.ndim == 2:
|
|
225
|
+
return [vecs[i] for i in range(vecs.shape[0])]
|
|
226
|
+
return [np.asarray(v, dtype=np.float32) for v in vecs]
|
|
227
|
+
|
|
228
|
+
def _cloud_embed(
|
|
229
|
+
self,
|
|
230
|
+
texts: list[str],
|
|
231
|
+
*,
|
|
232
|
+
max_retries: int = 3,
|
|
233
|
+
) -> list[NDArray[np.float32]]:
|
|
234
|
+
"""Encode via Azure OpenAI embedding API with retry logic.
|
|
235
|
+
|
|
236
|
+
Raises on failure — NEVER falls back to local model.
|
|
237
|
+
"""
|
|
238
|
+
import httpx
|
|
239
|
+
|
|
240
|
+
url = (
|
|
241
|
+
f"{self._config.api_endpoint.rstrip('/')}/openai/deployments/"
|
|
242
|
+
f"{self._config.deployment_name}/embeddings"
|
|
243
|
+
f"?api-version={self._config.api_version}"
|
|
244
|
+
)
|
|
245
|
+
headers = {
|
|
246
|
+
"Content-Type": "application/json",
|
|
247
|
+
"api-key": self._config.api_key,
|
|
248
|
+
}
|
|
249
|
+
body = {"input": texts, "model": self._config.deployment_name}
|
|
250
|
+
|
|
251
|
+
last_error: Exception | None = None
|
|
252
|
+
for attempt in range(max_retries):
|
|
253
|
+
try:
|
|
254
|
+
with httpx.Client(timeout=httpx.Timeout(30.0)) as client:
|
|
255
|
+
resp = client.post(url, headers=headers, json=body)
|
|
256
|
+
resp.raise_for_status()
|
|
257
|
+
data = resp.json()
|
|
258
|
+
results: list[NDArray[np.float32]] = []
|
|
259
|
+
for item in sorted(data["data"], key=lambda d: d["index"]):
|
|
260
|
+
vec = np.asarray(item["embedding"], dtype=np.float32)
|
|
261
|
+
results.append(vec)
|
|
262
|
+
return results
|
|
263
|
+
except Exception as exc:
|
|
264
|
+
last_error = exc
|
|
265
|
+
wait = 2 ** attempt # 1s, 2s, 4s
|
|
266
|
+
logger.warning(
|
|
267
|
+
"Cloud embed attempt %d/%d failed: %s (retry in %ds)",
|
|
268
|
+
attempt + 1,
|
|
269
|
+
max_retries,
|
|
270
|
+
exc,
|
|
271
|
+
wait,
|
|
272
|
+
)
|
|
273
|
+
if attempt < max_retries - 1:
|
|
274
|
+
time.sleep(wait)
|
|
275
|
+
|
|
276
|
+
raise RuntimeError(
|
|
277
|
+
f"Cloud embedding failed after {max_retries} attempts: "
|
|
278
|
+
f"{last_error}"
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# ------------------------------------------------------------------
|
|
282
|
+
# Validation
|
|
283
|
+
# ------------------------------------------------------------------
|
|
284
|
+
|
|
285
|
+
def _validate_dimension(self, vec: NDArray) -> None:
|
|
286
|
+
"""Hard validation — crash on mismatch, never silently fall back."""
|
|
287
|
+
actual = len(vec)
|
|
288
|
+
if actual != self._config.dimension:
|
|
289
|
+
raise DimensionMismatchError(
|
|
290
|
+
f"Embedding dimension {actual} != "
|
|
291
|
+
f"expected {self._config.dimension}. "
|
|
292
|
+
f"This is a HARD failure — check your model/API config."
|
|
293
|
+
)
|