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,179 @@
|
|
|
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 — Entity Graph Channel with Spreading Activation.
|
|
6
|
+
|
|
7
|
+
SA-RAG pattern: entities from query -> canonical lookup -> graph traversal
|
|
8
|
+
with decay. Handles BOTH uppercase and lowercase entity mentions.
|
|
9
|
+
|
|
10
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
11
|
+
License: MIT
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import logging
|
|
17
|
+
import re
|
|
18
|
+
from collections import defaultdict
|
|
19
|
+
from typing import TYPE_CHECKING
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from superlocalmemory.encoding.entity_resolver import EntityResolver
|
|
23
|
+
from superlocalmemory.storage.database import DatabaseManager
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
_PROPER_NOUN_RE = re.compile(r"\b[A-Z][a-z]{1,}\b")
|
|
28
|
+
|
|
29
|
+
_ENTITY_STOP: frozenset[str] = frozenset({
|
|
30
|
+
# Expanded stop list for query entity extraction
|
|
31
|
+
"what", "when", "where", "who", "which", "how", "does", "did",
|
|
32
|
+
"the", "that", "this", "there", "then", "than", "they", "them",
|
|
33
|
+
"have", "has", "had", "been", "being", "about", "after", "before",
|
|
34
|
+
"from", "into", "with", "some", "other", "would", "could", "should",
|
|
35
|
+
"will", "because", "also", "just", "like", "know", "think",
|
|
36
|
+
"feel", "want", "need", "make", "take", "give", "tell", "said",
|
|
37
|
+
"wow", "gonna", "got", "by", "thanks", "thank", "hey", "hi",
|
|
38
|
+
"hello", "bye", "good", "great", "nice", "cool", "right",
|
|
39
|
+
"let", "can", "might", "much", "many", "more", "most",
|
|
40
|
+
"something", "anything", "everything", "nothing", "someone",
|
|
41
|
+
"it", "my", "your", "our", "their", "me", "you", "we", "us",
|
|
42
|
+
"do", "if", "or", "no", "to", "at", "on", "in", "so",
|
|
43
|
+
"go", "come", "see", "look", "say", "ask", "try", "keep",
|
|
44
|
+
"yes", "yeah", "sure", "okay", "ok", "really", "actually",
|
|
45
|
+
"maybe", "well", "still", "even", "very",
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def extract_query_entities(query: str) -> list[str]:
|
|
50
|
+
"""Extract entity candidates from query (handles both cases).
|
|
51
|
+
|
|
52
|
+
Strategy: find proper nouns in original + title-cased text,
|
|
53
|
+
plus quoted phrases. Deduplicates case-insensitively.
|
|
54
|
+
"""
|
|
55
|
+
candidates: list[str] = []
|
|
56
|
+
seen: set[str] = set()
|
|
57
|
+
|
|
58
|
+
def _add(name: str) -> None:
|
|
59
|
+
lo = name.lower()
|
|
60
|
+
if lo not in seen and lo not in _ENTITY_STOP and len(name) >= 2:
|
|
61
|
+
seen.add(lo)
|
|
62
|
+
candidates.append(name)
|
|
63
|
+
|
|
64
|
+
for m in _PROPER_NOUN_RE.finditer(query):
|
|
65
|
+
_add(m.group(0))
|
|
66
|
+
for m in _PROPER_NOUN_RE.finditer(query.title()):
|
|
67
|
+
_add(m.group(0))
|
|
68
|
+
for m in re.finditer(r'"([^"]+)"', query):
|
|
69
|
+
_add(m.group(1).strip())
|
|
70
|
+
|
|
71
|
+
return candidates
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class EntityGraphChannel:
|
|
75
|
+
"""Entity-based retrieval with spreading activation (SA-RAG)."""
|
|
76
|
+
|
|
77
|
+
def __init__(
|
|
78
|
+
self, db: DatabaseManager,
|
|
79
|
+
entity_resolver: EntityResolver | None = None,
|
|
80
|
+
decay: float = 0.7, activation_threshold: float = 0.1,
|
|
81
|
+
max_hops: int = 3,
|
|
82
|
+
) -> None:
|
|
83
|
+
self._db = db
|
|
84
|
+
self._resolver = entity_resolver
|
|
85
|
+
self._decay = decay
|
|
86
|
+
self._threshold = activation_threshold
|
|
87
|
+
self._max_hops = max_hops
|
|
88
|
+
|
|
89
|
+
def search(self, query: str, profile_id: str, top_k: int = 50) -> list[tuple[str, float]]:
|
|
90
|
+
"""Search via entity graph with spreading activation."""
|
|
91
|
+
raw_entities = extract_query_entities(query)
|
|
92
|
+
if not raw_entities:
|
|
93
|
+
return []
|
|
94
|
+
|
|
95
|
+
canonical_ids = self._resolve_entities(raw_entities, profile_id)
|
|
96
|
+
if not canonical_ids:
|
|
97
|
+
return []
|
|
98
|
+
|
|
99
|
+
# Seed activation from direct entity-linked facts
|
|
100
|
+
activation: dict[str, float] = defaultdict(float)
|
|
101
|
+
visited_entities: set[str] = set(canonical_ids)
|
|
102
|
+
|
|
103
|
+
for eid in canonical_ids:
|
|
104
|
+
for fact in self._db.get_facts_by_entity(eid, profile_id):
|
|
105
|
+
activation[fact.fact_id] = max(activation[fact.fact_id], 1.0)
|
|
106
|
+
|
|
107
|
+
# Spreading activation through graph edges
|
|
108
|
+
frontier = set(activation.keys())
|
|
109
|
+
for hop in range(1, self._max_hops):
|
|
110
|
+
hop_decay = self._decay ** hop
|
|
111
|
+
if hop_decay < self._threshold:
|
|
112
|
+
break
|
|
113
|
+
next_frontier: set[str] = set()
|
|
114
|
+
|
|
115
|
+
for fid in frontier:
|
|
116
|
+
for edge in self._db.get_edges_for_node(fid, profile_id):
|
|
117
|
+
neighbor = edge.target_id if edge.source_id == fid else edge.source_id
|
|
118
|
+
propagated = activation[fid] * self._decay
|
|
119
|
+
if propagated >= self._threshold and propagated > activation.get(neighbor, 0.0):
|
|
120
|
+
activation[neighbor] = propagated
|
|
121
|
+
next_frontier.add(neighbor)
|
|
122
|
+
|
|
123
|
+
# Discover new entities from activated facts -> get their facts
|
|
124
|
+
new_eids = self._discover_entities(frontier, profile_id, visited_entities)
|
|
125
|
+
for eid in new_eids:
|
|
126
|
+
visited_entities.add(eid)
|
|
127
|
+
for fact in self._db.get_facts_by_entity(eid, profile_id):
|
|
128
|
+
if hop_decay > activation.get(fact.fact_id, 0.0):
|
|
129
|
+
activation[fact.fact_id] = hop_decay
|
|
130
|
+
next_frontier.add(fact.fact_id)
|
|
131
|
+
|
|
132
|
+
frontier = next_frontier
|
|
133
|
+
if not frontier:
|
|
134
|
+
break
|
|
135
|
+
|
|
136
|
+
results = [(fid, sc) for fid, sc in activation.items() if sc >= self._threshold]
|
|
137
|
+
results.sort(key=lambda x: x[1], reverse=True)
|
|
138
|
+
return results[:top_k]
|
|
139
|
+
|
|
140
|
+
def _resolve_entities(self, raw: list[str], profile_id: str) -> list[str]:
|
|
141
|
+
"""Resolve raw names to canonical entity IDs."""
|
|
142
|
+
ids: list[str] = []
|
|
143
|
+
seen: set[str] = set()
|
|
144
|
+
if self._resolver is not None:
|
|
145
|
+
for eid in self._resolver.resolve(raw, profile_id).values():
|
|
146
|
+
if eid not in seen:
|
|
147
|
+
seen.add(eid)
|
|
148
|
+
ids.append(eid)
|
|
149
|
+
else:
|
|
150
|
+
for name in raw:
|
|
151
|
+
ent = self._db.get_entity_by_name(name, profile_id)
|
|
152
|
+
if ent and ent.entity_id not in seen:
|
|
153
|
+
seen.add(ent.entity_id)
|
|
154
|
+
ids.append(ent.entity_id)
|
|
155
|
+
return ids
|
|
156
|
+
|
|
157
|
+
def _discover_entities(
|
|
158
|
+
self, fact_ids: set[str], profile_id: str, visited: set[str],
|
|
159
|
+
) -> list[str]:
|
|
160
|
+
"""Find new canonical entity IDs referenced by a set of facts."""
|
|
161
|
+
new: list[str] = []
|
|
162
|
+
seen = set(visited)
|
|
163
|
+
for fid in fact_ids:
|
|
164
|
+
rows = self._db.execute(
|
|
165
|
+
"SELECT canonical_entities_json FROM atomic_facts WHERE fact_id = ?", (fid,),
|
|
166
|
+
)
|
|
167
|
+
if not rows:
|
|
168
|
+
continue
|
|
169
|
+
raw = dict(rows[0]).get("canonical_entities_json")
|
|
170
|
+
if not raw:
|
|
171
|
+
continue
|
|
172
|
+
try:
|
|
173
|
+
for eid in json.loads(raw):
|
|
174
|
+
if eid not in seen:
|
|
175
|
+
seen.add(eid)
|
|
176
|
+
new.append(eid)
|
|
177
|
+
except (ValueError, TypeError):
|
|
178
|
+
continue
|
|
179
|
+
return new
|
|
@@ -0,0 +1,78 @@
|
|
|
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 — Weighted Reciprocal Rank Fusion.
|
|
6
|
+
|
|
7
|
+
Single-pass RRF with k=60 for diverse retrieval (D116).
|
|
8
|
+
V1 had triple re-fusion which destroyed rankings — fixed in V2.
|
|
9
|
+
|
|
10
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
11
|
+
License: MIT
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class FusionResult:
|
|
20
|
+
"""Single fused result with per-channel provenance."""
|
|
21
|
+
fact_id: str
|
|
22
|
+
fused_score: float
|
|
23
|
+
channel_ranks: dict[str, int] = field(default_factory=dict)
|
|
24
|
+
channel_scores: dict[str, float] = field(default_factory=dict)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def weighted_rrf(
|
|
28
|
+
channels: dict[str, list[tuple[str, float]]],
|
|
29
|
+
weights: dict[str, float],
|
|
30
|
+
k: int = 60,
|
|
31
|
+
max_rank_penalty: int = 1000,
|
|
32
|
+
) -> list[FusionResult]:
|
|
33
|
+
"""Fuse ranked lists via Weighted Reciprocal Rank Fusion.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
channels: channel_name -> [(fact_id, score)] sorted desc.
|
|
37
|
+
weights: channel_name -> weight multiplier.
|
|
38
|
+
k: RRF smoothing constant (60 for diverse retrieval, D116).
|
|
39
|
+
max_rank_penalty: Rank assigned to absent documents.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
FusionResult list sorted by fused_score descending.
|
|
43
|
+
"""
|
|
44
|
+
if k <= 0:
|
|
45
|
+
raise ValueError(f"k must be positive, got {k}")
|
|
46
|
+
|
|
47
|
+
rank_maps: dict[str, dict[str, int]] = {}
|
|
48
|
+
score_maps: dict[str, dict[str, float]] = {}
|
|
49
|
+
|
|
50
|
+
for ch, ranked in channels.items():
|
|
51
|
+
ranks: dict[str, int] = {}
|
|
52
|
+
scores: dict[str, float] = {}
|
|
53
|
+
for i, (fid, sc) in enumerate(ranked):
|
|
54
|
+
ranks[fid] = i + 1
|
|
55
|
+
scores[fid] = sc
|
|
56
|
+
rank_maps[ch] = ranks
|
|
57
|
+
score_maps[ch] = scores
|
|
58
|
+
|
|
59
|
+
all_ids: set[str] = set()
|
|
60
|
+
for ranked in channels.values():
|
|
61
|
+
for fid, _ in ranked:
|
|
62
|
+
all_ids.add(fid)
|
|
63
|
+
|
|
64
|
+
results: list[FusionResult] = []
|
|
65
|
+
for fid in all_ids:
|
|
66
|
+
fused = 0.0
|
|
67
|
+
ch_ranks: dict[str, int] = {}
|
|
68
|
+
ch_scores: dict[str, float] = {}
|
|
69
|
+
for ch in channels:
|
|
70
|
+
w = weights.get(ch, 1.0)
|
|
71
|
+
rank = rank_maps[ch].get(fid, max_rank_penalty)
|
|
72
|
+
ch_ranks[ch] = rank
|
|
73
|
+
ch_scores[ch] = score_maps[ch].get(fid, 0.0)
|
|
74
|
+
fused += w / (k + rank)
|
|
75
|
+
results.append(FusionResult(fid, fused, ch_ranks, ch_scores))
|
|
76
|
+
|
|
77
|
+
results.sort(key=lambda r: r.fused_score, reverse=True)
|
|
78
|
+
return results
|
|
@@ -0,0 +1,105 @@
|
|
|
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 — Profile Channel (Entity-Profile Retrieval).
|
|
6
|
+
|
|
7
|
+
Returns fact IDs from entity profiles — enables direct answers for
|
|
8
|
+
"What does Alice do?" style queries without full embedding search.
|
|
9
|
+
|
|
10
|
+
This is a SHORTCUT channel: if the query mentions a known entity,
|
|
11
|
+
the profile's accumulated fact IDs are injected directly into the
|
|
12
|
+
retrieval pool with high scores.
|
|
13
|
+
|
|
14
|
+
Competitor reference: EverMemOS profile synthesis (~+15-20% SH).
|
|
15
|
+
|
|
16
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
17
|
+
License: MIT
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import logging
|
|
22
|
+
import re
|
|
23
|
+
from typing import TYPE_CHECKING
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from superlocalmemory.storage.database import DatabaseManager
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
# Pattern for extracting potential entity names (capitalized multi-word)
|
|
31
|
+
_ENTITY_PATTERN = re.compile(r"\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*\b")
|
|
32
|
+
|
|
33
|
+
# Common sentence starters to exclude from entity extraction
|
|
34
|
+
_SENTENCE_STARTERS = frozenset({
|
|
35
|
+
"What", "Where", "Who", "Which", "How", "When", "Does", "Did",
|
|
36
|
+
"Can", "Could", "Would", "Should", "Are", "Is", "Was", "Were",
|
|
37
|
+
"Has", "Have", "The", "Tell", "Please", "Do", "Why",
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ProfileChannel:
|
|
42
|
+
"""Entity-profile-based retrieval for direct entity queries.
|
|
43
|
+
|
|
44
|
+
If the query mentions a known entity (by canonical name or alias),
|
|
45
|
+
the entity's profile fact IDs are returned with high base score.
|
|
46
|
+
|
|
47
|
+
Usage::
|
|
48
|
+
|
|
49
|
+
channel = ProfileChannel(db)
|
|
50
|
+
results = channel.search("What is Alice's job?", "default")
|
|
51
|
+
# returns [(fact_id_1, 0.95), (fact_id_2, 0.95), ...]
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self, db: DatabaseManager) -> None:
|
|
55
|
+
self._db = db
|
|
56
|
+
|
|
57
|
+
def search(
|
|
58
|
+
self,
|
|
59
|
+
query: str,
|
|
60
|
+
profile_id: str,
|
|
61
|
+
top_k: int = 10,
|
|
62
|
+
) -> list[tuple[str, float]]:
|
|
63
|
+
"""Search entity profiles for matching facts.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
query: User query text.
|
|
67
|
+
profile_id: Scope to this profile.
|
|
68
|
+
top_k: Maximum results to return.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
List of (fact_id, score) sorted by score descending.
|
|
72
|
+
"""
|
|
73
|
+
entities = self._extract_entity_names(query)
|
|
74
|
+
if not entities:
|
|
75
|
+
return []
|
|
76
|
+
|
|
77
|
+
results: list[tuple[str, float]] = []
|
|
78
|
+
seen: set[str] = set()
|
|
79
|
+
|
|
80
|
+
for name in entities:
|
|
81
|
+
entity = self._db.get_entity_by_name(name, profile_id)
|
|
82
|
+
if not entity:
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
profiles = self._db.get_entity_profiles_by_entity(
|
|
86
|
+
entity.entity_id, profile_id,
|
|
87
|
+
)
|
|
88
|
+
for p in profiles:
|
|
89
|
+
for fid in p.fact_ids:
|
|
90
|
+
if fid not in seen:
|
|
91
|
+
seen.add(fid)
|
|
92
|
+
results.append((fid, 0.95))
|
|
93
|
+
|
|
94
|
+
results.sort(key=lambda x: x[1], reverse=True)
|
|
95
|
+
return results[:top_k]
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def _extract_entity_names(query: str) -> list[str]:
|
|
99
|
+
"""Extract potential entity names from query text.
|
|
100
|
+
|
|
101
|
+
Returns capitalized words/phrases that aren't common
|
|
102
|
+
sentence starters.
|
|
103
|
+
"""
|
|
104
|
+
matches = _ENTITY_PATTERN.findall(query)
|
|
105
|
+
return [m for m in matches if m not in _SENTENCE_STARTERS]
|
|
@@ -0,0 +1,154 @@
|
|
|
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 — Cross-Encoder Reranker.
|
|
6
|
+
|
|
7
|
+
Scores (query, fact) pairs through a cross-encoder in a single forward
|
|
8
|
+
pass. Lazy model loading, thread-safe via lock.
|
|
9
|
+
|
|
10
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
11
|
+
License: MIT
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
import threading
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from superlocalmemory.storage.models import AtomicFact
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CrossEncoderReranker:
|
|
26
|
+
"""Rerank candidate facts using a local cross-encoder model.
|
|
27
|
+
|
|
28
|
+
When the model is unavailable (missing package, download failure,
|
|
29
|
+
offline environment), falls back to returning candidates in their
|
|
30
|
+
original score order — never crashes.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
model_name: HuggingFace cross-encoder model identifier.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
model_name: str = "BAAI/bge-reranker-v2-m3",
|
|
39
|
+
) -> None:
|
|
40
|
+
self._model_name = model_name
|
|
41
|
+
self._model: Any = None
|
|
42
|
+
self._loaded = False
|
|
43
|
+
self._lock = threading.Lock()
|
|
44
|
+
|
|
45
|
+
# ------------------------------------------------------------------
|
|
46
|
+
# Lazy loading
|
|
47
|
+
# ------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
def _ensure_model(self) -> None:
|
|
50
|
+
"""Load cross-encoder on first use (thread-safe)."""
|
|
51
|
+
if self._loaded:
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
with self._lock:
|
|
55
|
+
if self._loaded:
|
|
56
|
+
return # Double-check after acquiring lock
|
|
57
|
+
try:
|
|
58
|
+
from sentence_transformers import CrossEncoder
|
|
59
|
+
|
|
60
|
+
self._model = CrossEncoder(self._model_name)
|
|
61
|
+
logger.info("Cross-encoder loaded: %s", self._model_name)
|
|
62
|
+
except ImportError:
|
|
63
|
+
logger.warning(
|
|
64
|
+
"sentence-transformers not installed; "
|
|
65
|
+
"cross-encoder reranking disabled"
|
|
66
|
+
)
|
|
67
|
+
except OSError as exc:
|
|
68
|
+
logger.warning(
|
|
69
|
+
"Failed to load cross-encoder %s: %s",
|
|
70
|
+
self._model_name,
|
|
71
|
+
exc,
|
|
72
|
+
)
|
|
73
|
+
finally:
|
|
74
|
+
self._loaded = True
|
|
75
|
+
|
|
76
|
+
# ------------------------------------------------------------------
|
|
77
|
+
# Public API
|
|
78
|
+
# ------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
def rerank(
|
|
81
|
+
self,
|
|
82
|
+
query: str,
|
|
83
|
+
candidates: list[tuple[AtomicFact, float]],
|
|
84
|
+
top_k: int = 10,
|
|
85
|
+
) -> list[tuple[AtomicFact, float]]:
|
|
86
|
+
"""Rerank candidates by cross-encoder relevance.
|
|
87
|
+
|
|
88
|
+
Each (query, fact.content) pair is scored in a single forward
|
|
89
|
+
pass. Results are returned sorted by cross-encoder score.
|
|
90
|
+
|
|
91
|
+
When the model is unavailable, returns candidates sorted by
|
|
92
|
+
their existing score (graceful fallback).
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
query: User query text.
|
|
96
|
+
candidates: List of (AtomicFact, score) tuples from the
|
|
97
|
+
fusion stage.
|
|
98
|
+
top_k: Maximum results to return.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Top-k (AtomicFact, cross_encoder_score) tuples, sorted
|
|
102
|
+
descending by cross-encoder score.
|
|
103
|
+
"""
|
|
104
|
+
if not candidates:
|
|
105
|
+
return []
|
|
106
|
+
|
|
107
|
+
self._ensure_model()
|
|
108
|
+
|
|
109
|
+
if self._model is None:
|
|
110
|
+
# Fallback: keep existing score order
|
|
111
|
+
sorted_cands = sorted(
|
|
112
|
+
candidates, key=lambda x: x[1], reverse=True
|
|
113
|
+
)
|
|
114
|
+
return sorted_cands[:top_k]
|
|
115
|
+
|
|
116
|
+
# Build (query, document) pairs for batch scoring
|
|
117
|
+
pairs: list[tuple[str, str]] = [
|
|
118
|
+
(query, fact.content) for fact, _ in candidates
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
scores = self._model.predict(pairs)
|
|
122
|
+
|
|
123
|
+
scored: list[tuple[AtomicFact, float]] = [
|
|
124
|
+
(fact, float(score))
|
|
125
|
+
for (fact, _), score in zip(candidates, scores)
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
scored.sort(key=lambda x: x[1], reverse=True)
|
|
129
|
+
return scored[:top_k]
|
|
130
|
+
|
|
131
|
+
def score_pair(self, query: str, document: str) -> float:
|
|
132
|
+
"""Score a single (query, document) pair.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
query: Query text.
|
|
136
|
+
document: Document text.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Relevance score (higher = more relevant). 0.0 if model
|
|
140
|
+
is unavailable.
|
|
141
|
+
"""
|
|
142
|
+
self._ensure_model()
|
|
143
|
+
|
|
144
|
+
if self._model is None:
|
|
145
|
+
return 0.0
|
|
146
|
+
|
|
147
|
+
scores = self._model.predict([(query, document)])
|
|
148
|
+
return float(scores[0])
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def is_available(self) -> bool:
|
|
152
|
+
"""Whether the cross-encoder model is loaded and ready."""
|
|
153
|
+
self._ensure_model()
|
|
154
|
+
return self._model is not None
|