clm-kernel 0.0.0__py3-none-any.whl
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.
- clm/__init__.py +230 -0
- clm/__main__.py +25 -0
- clm/cross_cutting/__init__.py +50 -0
- clm/cross_cutting/_test_runner.py +36 -0
- clm/cross_cutting/async_utils.py +25 -0
- clm/cross_cutting/cel_builder.py +123 -0
- clm/cross_cutting/cel_converter.py +697 -0
- clm/cross_cutting/cel_engine.py +252 -0
- clm/cross_cutting/cel_ops.py +72 -0
- clm/cross_cutting/cel_ops_db.py +157 -0
- clm/cross_cutting/cel_ops_files.py +99 -0
- clm/cross_cutting/cel_ops_state.py +71 -0
- clm/cross_cutting/cel_ops_strings.py +64 -0
- clm/cross_cutting/cel_resolver.py +63 -0
- clm/cross_cutting/cel_slices.py +56 -0
- clm/cross_cutting/cel_store.py +330 -0
- clm/cross_cutting/clm_logger.py +140 -0
- clm/cross_cutting/config/__init__.py +155 -0
- clm/cross_cutting/config/config_constants.py +392 -0
- clm/cross_cutting/config/env_parameters.py +90 -0
- clm/cross_cutting/config/logging.py +378 -0
- clm/cross_cutting/config/settings.py +504 -0
- clm/cross_cutting/context.py +5 -0
- clm/cross_cutting/domain_models.py +55 -0
- clm/cross_cutting/domain_types.py +61 -0
- clm/cross_cutting/effects.py +67 -0
- clm/cross_cutting/errors.py +153 -0
- clm/cross_cutting/governance.py +331 -0
- clm/cross_cutting/guards.py +41 -0
- clm/cross_cutting/mcard.py +321 -0
- clm/cross_cutting/native.py +185 -0
- clm/cross_cutting/observability.py +289 -0
- clm/cross_cutting/pcard.py +434 -0
- clm/cross_cutting/registry.py +71 -0
- clm/cross_cutting/resources.py +84 -0
- clm/cross_cutting/secrets.py +44 -0
- clm/cross_cutting/subprocess_utils.py +118 -0
- clm/cross_cutting/telemetry.py +164 -0
- clm/cross_cutting/testing/__init__.py +8 -0
- clm/cross_cutting/testing/comparator.py +187 -0
- clm/cross_cutting/testing/datasets.py +87 -0
- clm/cross_cutting/timeutil.py +20 -0
- clm/cross_cutting/types/new_type_check.py +0 -0
- clm/cross_cutting/utils/__init__.py +1 -0
- clm/cross_cutting/utils/url_safety.py +50 -0
- clm/cross_cutting/vcard.py +560 -0
- clm/cross_cutting/writer.py +118 -0
- clm/layer0/README.md +50 -0
- clm/layer0/__init__.py +223 -0
- clm/layer0/algebra.py +60 -0
- clm/layer0/algebra_baldwin.py +133 -0
- clm/layer0/algebra_baldwin_generators.py +36 -0
- clm/layer0/algebra_equivalence.py +262 -0
- clm/layer0/algebra_ingest.py +306 -0
- clm/layer0/algebra_manifest_io.py +156 -0
- clm/layer0/assembler.py +481 -0
- clm/layer0/cel/__init__.py +23 -0
- clm/layer0/cel_eval.py +491 -0
- clm/layer0/codec.py +106 -0
- clm/layer0/context.py +122 -0
- clm/layer0/db.py +160 -0
- clm/layer0/exceptions.py +73 -0
- clm/layer0/fibration.py +106 -0
- clm/layer0/gates.py +50 -0
- clm/layer0/hash.py +80 -0
- clm/layer0/loader.py +826 -0
- clm/layer0/math_utils.py +163 -0
- clm/layer0/mcard.py +96 -0
- clm/layer0/model/__init__.py +0 -0
- clm/layer0/model/action.py +223 -0
- clm/layer0/model/card.py +280 -0
- clm/layer0/model/card_triad.py +427 -0
- clm/layer0/model/compatibility.py +60 -0
- clm/layer0/model/dictionary.py +46 -0
- clm/layer0/model/dots.py +331 -0
- clm/layer0/model/event_producer.py +88 -0
- clm/layer0/model/g_time.py +89 -0
- clm/layer0/model/handle.py +163 -0
- clm/layer0/model/hash/__init__.py +6 -0
- clm/layer0/model/hash/algorithms/__init__.py +3 -0
- clm/layer0/model/hash/algorithms/custom_hash.py +7 -0
- clm/layer0/model/hash/algorithms/local_sha256.py +178 -0
- clm/layer0/model/hash/constants.py +53 -0
- clm/layer0/model/hash/enums.py +60 -0
- clm/layer0/model/hash/validator.py +307 -0
- clm/layer0/model/interpreter.py +275 -0
- clm/layer0/model/pagination.py +45 -0
- clm/layer0/model/pcard.py +716 -0
- clm/layer0/model/schema.py +44 -0
- clm/layer0/model/utils/__init__.py +1 -0
- clm/layer0/model/utils/content_analyzer.py +152 -0
- clm/layer0/model/validators/__init__.py +1 -0
- clm/layer0/model/validators/base_validator.py +44 -0
- clm/layer0/model/validators/binary_validator.py +66 -0
- clm/layer0/model/validators/text_validator.py +82 -0
- clm/layer0/model/validators/validation_registry.py +49 -0
- clm/layer0/model/vcard.py +823 -0
- clm/layer0/model/vcard_ext/__init__.py +39 -0
- clm/layer0/model/vcard_ext/core.py +60 -0
- clm/layer0/model/vcard_ext/network.py +23 -0
- clm/layer0/model/vcard_ext/observability.py +63 -0
- clm/layer0/model/vcard_ext/storage.py +60 -0
- clm/layer0/model/vcard_ext/vendors.py +211 -0
- clm/layer0/model/vcard_sandwich.py +119 -0
- clm/layer0/model/vcard_vocabulary.py +463 -0
- clm/layer0/model/workflow.py +274 -0
- clm/layer0/narrative.py +431 -0
- clm/layer0/ontology/collection.py +125 -0
- clm/layer0/parser.py +429 -0
- clm/layer0/relations.py +73 -0
- clm/layer0/rule_evaluator.py +251 -0
- clm/layer0/schema/__init__.py +38 -0
- clm/layer0/schema/ddl.py +97 -0
- clm/layer0/templates/abstract.yaml +99 -0
- clm/layer0/templates/balanced.yaml +253 -0
- clm/layer0/templates/concrete.yaml +172 -0
- clm/layer0/type_registry.py +223 -0
- clm/layer0/types.py +204 -0
- clm/layer0/utils.py +886 -0
- clm/layer0/verifier_core.py +262 -0
- clm/layer1/__init__.py +31 -0
- clm/layer1/action_dispatcher.py +275 -0
- clm/layer1/adapter_registry.py +233 -0
- clm/layer1/algebra_baldwin_generators.py +192 -0
- clm/layer1/arrow_compiler.py +525 -0
- clm/layer1/batch_processor.py +152 -0
- clm/layer1/cel/__init__.py +27 -0
- clm/layer1/cel/builder.py +173 -0
- clm/layer1/engine.py +759 -0
- clm/layer1/engine_async.py +89 -0
- clm/layer1/eval_callback.py +49 -0
- clm/layer1/gate_composer.py +229 -0
- clm/layer1/hoare_sandwich.py +298 -0
- clm/layer1/identity_guard.py +348 -0
- clm/layer1/matrix.py +138 -0
- clm/layer1/neural_runtime.py +221 -0
- clm/layer1/operations/__init__.py +123 -0
- clm/layer1/operations/augment.py +156 -0
- clm/layer1/operations/base.py +109 -0
- clm/layer1/operations/builtins.py +338 -0
- clm/layer1/operations/dispatch.py +144 -0
- clm/layer1/operations/exclude.py +107 -0
- clm/layer1/operations/handle.py +197 -0
- clm/layer1/operations/identity.py +139 -0
- clm/layer1/operations/invert.py +66 -0
- clm/layer1/operations/loader.py +116 -0
- clm/layer1/operations/manipulators/__init__.py +23 -0
- clm/layer1/operations/manipulators/document.py +211 -0
- clm/layer1/operations/manipulators/media.py +134 -0
- clm/layer1/operations/manipulators/structured.py +159 -0
- clm/layer1/operations/manipulators/tabular.py +334 -0
- clm/layer1/operations/port.py +65 -0
- clm/layer1/operations/services.py +529 -0
- clm/layer1/operations/split.py +117 -0
- clm/layer1/operations/substitute.py +75 -0
- clm/layer1/petri.py +1052 -0
- clm/layer1/pocketflow_orchestrator.py +408 -0
- clm/layer1/pocketflow_scheduler.py +519 -0
- clm/layer1/protocols.py +197 -0
- clm/layer1/runtime_adapter.py +718 -0
- clm/layer1/runtimes/__init__.py +71 -0
- clm/layer1/runtimes/_generated_assertions.py +125 -0
- clm/layer1/runtimes/_generated_types.py +42 -0
- clm/layer1/runtimes/base.py +265 -0
- clm/layer1/runtimes/binary.py +101 -0
- clm/layer1/runtimes/factory.py +203 -0
- clm/layer1/runtimes/javascript.py +321 -0
- clm/layer1/runtimes/javascript_runtime.js +33 -0
- clm/layer1/runtimes/js_scripts/isomorphism.mjs +22 -0
- clm/layer1/runtimes/js_scripts/loader.mjs +52 -0
- clm/layer1/runtimes/lambda_calc.py +126 -0
- clm/layer1/runtimes/python.py +288 -0
- clm/layer1/runtimes/python_runtime.py +41 -0
- clm/layer1/runtimes/script.py +114 -0
- clm/layer1/sandbox.py +334 -0
- clm/layer1/savepoint_guard.py +136 -0
- clm/layer1/sparse_net.py +122 -0
- clm/layer1/spec_tester.py +165 -0
- clm/layer1/store.py +295 -0
- clm/layer1/test_runner.py +498 -0
- clm/layer1/type_inspector.py +166 -0
- clm/layer1/verifier.py +772 -0
- clm/layer1/vm.py +91 -0
- clm/layer2/__init__.py +78 -0
- clm/layer2/card_collection.py +678 -0
- clm/layer2/classifier.py +479 -0
- clm/layer2/engine/__init__.py +1 -0
- clm/layer2/engine/abstract_sql_engine.py +320 -0
- clm/layer2/engine/base.py +193 -0
- clm/layer2/engine/duckdb_engine.py +576 -0
- clm/layer2/engine/sqlite_engine.py +518 -0
- clm/layer2/fiber/__init__.py +31 -0
- clm/layer2/fiber/context.py +89 -0
- clm/layer2/fiber/disposable.py +163 -0
- clm/layer2/fiber/fiber.py +125 -0
- clm/layer2/fiber/hoare.py +191 -0
- clm/layer2/file_io.py +376 -0
- clm/layer2/file_registrar.py +77 -0
- clm/layer2/ingest.py +202 -0
- clm/layer2/lineage.py +322 -0
- clm/layer2/mcard_fs.py +262 -0
- clm/layer2/merkle.py +11 -0
- clm/layer2/mime.py +244 -0
- clm/layer2/mime_detector.py +245 -0
- clm/layer2/private_collection.py +118 -0
- clm/layer2/shared_store.py +11 -0
- clm/layer2/storage/__init__.py +190 -0
- clm/layer2/storage/db_bitemporal.py +141 -0
- clm/layer2/storage/db_driver.py +331 -0
- clm/layer2/storage/db_hyperlinks.py +214 -0
- clm/layer2/storage/db_pool.py +139 -0
- clm/layer2/storage/db_repository.py +229 -0
- clm/layer2/storage/savepoint.py +78 -0
- clm/layer2/storage/sqlite_wal.py +74 -0
- clm/layer2/tridb.py +245 -0
- clm/layer2/vcard_receipt.py +71 -0
- clm/layer2/vfs.py +17 -0
- clm/layer3/__init__.py +141 -0
- clm/layer3/agency/__init__.py +19 -0
- clm/layer3/agency/config.py +220 -0
- clm/layer3/agency/providers/__init__.py +15 -0
- clm/layer3/agency/providers/base.py +91 -0
- clm/layer3/agency/providers/mlc_llm.py +169 -0
- clm/layer3/agency/providers/ollama.py +203 -0
- clm/layer3/agency/router.py +71 -0
- clm/layer3/agency/runtime.py +524 -0
- clm/layer3/gateway.py +353 -0
- clm/layer3/open_interpreter_adapter.py +159 -0
- clm/layer3/rag/__init__.py +70 -0
- clm/layer3/rag/cli.py +228 -0
- clm/layer3/rag/config.py +153 -0
- clm/layer3/rag/embeddings/__init__.py +16 -0
- clm/layer3/rag/embeddings/base.py +79 -0
- clm/layer3/rag/embeddings/ollama.py +211 -0
- clm/layer3/rag/embeddings/vision.py +290 -0
- clm/layer3/rag/engine.py +276 -0
- clm/layer3/rag/graph/__init__.py +22 -0
- clm/layer3/rag/graph/community.py +198 -0
- clm/layer3/rag/graph/engine.py +433 -0
- clm/layer3/rag/graph/extractor.py +255 -0
- clm/layer3/rag/graph/schema.py +115 -0
- clm/layer3/rag/graph/store.py +641 -0
- clm/layer3/rag/indexer.py +333 -0
- clm/layer3/rag/llm_providers.json +52 -0
- clm/layer3/rag/semantic_versioning.py +313 -0
- clm/layer3/rag/vector/__init__.py +28 -0
- clm/layer3/rag/vector/handle_vector_store.py +749 -0
- clm/layer3/rag/vector/schema.py +151 -0
- clm/layer3/rag/vector/store.py +632 -0
- clm/layer3/reticulum/__init__.py +55 -0
- clm/layer3/reticulum/bridge.py +74 -0
- clm/layer3/reticulum/daemon.py +88 -0
- clm/layer3/reticulum/discovery.py +164 -0
- clm/layer3/reticulum/identity.py +107 -0
- clm/layer3/reticulum/media.py +148 -0
- clm/layer3/reticulum/transport.py +133 -0
- clm/layer3/satori/__init__.py +209 -0
- clm/layer3/satori/alpha_conversion.py +168 -0
- clm/layer3/satori/beta_reduction.py +359 -0
- clm/layer3/satori/continuation.py +38 -0
- clm/layer3/satori/eta_conversion.py +205 -0
- clm/layer3/satori/free_variables.py +163 -0
- clm/layer3/satori/io_effects.py +370 -0
- clm/layer3/satori/lambda_runtime.py +726 -0
- clm/layer3/satori/lambda_term.py +230 -0
- clm/layer3/satori/sheaf_audit.py +261 -0
- clm/layer3/satori/speech_act.py +279 -0
- clm/layer4/README.md +9 -0
- clm/layer4/__init__.py +67 -0
- clm/layer4/_sovereign_executor.py +580 -0
- clm/layer4/action_commands.py +719 -0
- clm/layer4/api.py +236 -0
- clm/layer4/bootstrap/__init__.py +13 -0
- clm/layer4/bootstrap/bootstrapper.py +118 -0
- clm/layer4/bootstrap/cleanup.py +44 -0
- clm/layer4/bootstrap/clm_hooks.py +49 -0
- clm/layer4/bootstrap/genesis.py +77 -0
- clm/layer4/bootstrap/injection.py +125 -0
- clm/layer4/cli.py +549 -0
- clm/layer4/collection_manager.py +553 -0
- clm/layer4/commands/__init__.py +81 -0
- clm/layer4/commands/action_cmd.py +42 -0
- clm/layer4/commands/check_cmd.py +82 -0
- clm/layer4/commands/envelope.py +73 -0
- clm/layer4/commands/evaluate_cmd.py +224 -0
- clm/layer4/commands/mcard_cmd.py +24 -0
- clm/layer4/commands/petri_cmd.py +92 -0
- clm/layer4/commands/tridb_cmd.py +319 -0
- clm/layer4/commands/version_cmd.py +27 -0
- clm/layer4/diff_commands.py +167 -0
- clm/layer4/eoa_sponsor.py +232 -0
- clm/layer4/events.py +69 -0
- clm/layer4/flux_gateway.py +133 -0
- clm/layer4/gateway.py +91 -0
- clm/layer4/loader.py +307 -0
- clm/layer4/mcard_commands.py +166 -0
- clm/layer4/membrane.py +273 -0
- clm/layer4/navigation.py +50 -0
- clm/layer4/protocol/protocol_entry_python.py +108 -0
- clm/layer4/protocol/python_sync.py +126 -0
- clm/layer4/runner.py +150 -0
- clm/layer4/satori.py +262 -0
- clm/layer4/server/python_sync_client.py +150 -0
- clm/layer4/server/python_sync_server.py +139 -0
- clm/layer4/server/service_api.py +315 -0
- clm/layer4/server/start_servers.py +107 -0
- clm/layer4/server/stop_servers.py +24 -0
- clm/layer4/simulated_optimization_fix.py +0 -0
- clm/layer4/storage.py +463 -0
- clm/layer4/tridb_admin.py +268 -0
- clm/layer4/websocket/test_ws_client.py +104 -0
- clm/layer4/websocket/test_ws_server.py +138 -0
- clm/layer4/websocket/ws_server_local.py +160 -0
- clm/layer5/__init__.py +51 -0
- clm/layer5/async_batch_writer.py +133 -0
- clm/layer5/learning.py +276 -0
- clm/layer5/telemetry_hook.py +107 -0
- clm/layer5/type_lattice.py +238 -0
- clm/py.typed +0 -0
- clm/schemas/__init__.py +652 -0
- clm/schemas/abstract_face_schema.yaml +77 -0
- clm/schemas/cel_schema.yaml +416 -0
- clm/schemas/lifecycle/__init__.py +45 -0
- clm/schemas/lifecycle/models.py +123 -0
- clm/schemas/lifecycle/service.py +364 -0
- clm/schemas/mcard_schema.sql +192 -0
- clm/schemas/mcard_vector_schema.sql +283 -0
- clm/schemas/mlp-context-v1.json +91 -0
- clm_kernel-0.0.0.dist-info/METADATA +121 -0
- clm_kernel-0.0.0.dist-info/RECORD +333 -0
- clm_kernel-0.0.0.dist-info/WHEEL +4 -0
- clm_kernel-0.0.0.dist-info/entry_points.txt +3 -0
- clm_kernel-0.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Handle-Aware Vector Store for Semantic Versioning
|
|
3
|
+
|
|
4
|
+
Extends MCardVectorStore to enable semantic similarity detection
|
|
5
|
+
across MCard versions linked to handles.
|
|
6
|
+
|
|
7
|
+
Key Features:
|
|
8
|
+
- Link MCards to handles during indexing
|
|
9
|
+
- Compare versions within a handle by semantic similarity
|
|
10
|
+
- Filter searches by handle
|
|
11
|
+
- Sort version history by distance to current or query
|
|
12
|
+
- Automatic upgrade type classification based on semantic delta
|
|
13
|
+
|
|
14
|
+
See: docs/architecture/Handle_Vector_Similarity_Design.md
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from datetime import datetime, timezone
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from clm.layer0.model.card import MCard
|
|
23
|
+
|
|
24
|
+
from .schema import HANDLE_VECTOR_SCHEMAS
|
|
25
|
+
from .store import (
|
|
26
|
+
MCardVectorStore,
|
|
27
|
+
cosine_similarity,
|
|
28
|
+
deserialize_vector,
|
|
29
|
+
euclidean_distance,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
36
|
+
# Data Classes
|
|
37
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class HandleVersion:
|
|
42
|
+
"""A version in a handle's history with embedding info."""
|
|
43
|
+
|
|
44
|
+
hash: str
|
|
45
|
+
version_order: int # 0 = current, 1 = previous, etc.
|
|
46
|
+
is_current: bool
|
|
47
|
+
created_at: str
|
|
48
|
+
parent_hash: str | None = None
|
|
49
|
+
embedding_id: int | None = None
|
|
50
|
+
semantic_delta: float | None = None
|
|
51
|
+
upgrade_type: str | None = None
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def has_embedding(self) -> bool:
|
|
55
|
+
return self.embedding_id is not None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class VersionSimilarityResult:
|
|
60
|
+
"""Result from version similarity comparison."""
|
|
61
|
+
|
|
62
|
+
hash: str
|
|
63
|
+
version_order: int
|
|
64
|
+
similarity_to_current: float # Cosine similarity to current version
|
|
65
|
+
distance_to_current: float # Euclidean distance to current version
|
|
66
|
+
similarity_to_query: float | None = None # If query provided
|
|
67
|
+
parent_hash: str | None = None
|
|
68
|
+
upgrade_type: str | None = None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
72
|
+
# Upgrade Type Classification
|
|
73
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def classify_upgrade_type(semantic_delta: float) -> str:
|
|
77
|
+
"""
|
|
78
|
+
Classify upgrade type based on semantic similarity to parent.
|
|
79
|
+
|
|
80
|
+
Thresholds:
|
|
81
|
+
- >= 0.95: trivial (formatting, typo fixes)
|
|
82
|
+
- 0.85 - 0.94: minor (small edits, clarifications)
|
|
83
|
+
- 0.70 - 0.84: major (significant content changes)
|
|
84
|
+
- < 0.70: breaking (major semantic drift)
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
semantic_delta: Cosine similarity to parent version
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Upgrade type string: 'trivial', 'minor', 'major', or 'breaking'
|
|
91
|
+
"""
|
|
92
|
+
if semantic_delta >= 0.95:
|
|
93
|
+
return "trivial"
|
|
94
|
+
elif semantic_delta >= 0.85:
|
|
95
|
+
return "minor"
|
|
96
|
+
elif semantic_delta >= 0.70:
|
|
97
|
+
return "major"
|
|
98
|
+
else:
|
|
99
|
+
return "breaking"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
103
|
+
# Handle-Aware Vector Store
|
|
104
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class HandleVectorStore(MCardVectorStore):
|
|
108
|
+
"""
|
|
109
|
+
Extended vector store with handle-aware version similarity.
|
|
110
|
+
|
|
111
|
+
This class implements the Handle-Hash Duality pattern with semantic
|
|
112
|
+
awareness, enabling:
|
|
113
|
+
|
|
114
|
+
- Version tracking via handles
|
|
115
|
+
- Semantic similarity detection across versions
|
|
116
|
+
- Intelligent upgrade classification
|
|
117
|
+
- Cross-version semantic search
|
|
118
|
+
|
|
119
|
+
Architecture:
|
|
120
|
+
- Handles remain stable identifiers (Proxy Pattern)
|
|
121
|
+
- Hashes reference immutable content (Content-Addressing)
|
|
122
|
+
- Embeddings enable semantic understanding (Vector Search)
|
|
123
|
+
|
|
124
|
+
Usage:
|
|
125
|
+
store = HandleVectorStore(db_path="vectors.db")
|
|
126
|
+
|
|
127
|
+
# Index with handle association
|
|
128
|
+
store.index_with_handle(mcard, handle="my_document")
|
|
129
|
+
|
|
130
|
+
# Get versions sorted by similarity to current
|
|
131
|
+
versions = store.get_versions_by_similarity("my_document")
|
|
132
|
+
|
|
133
|
+
# Find versions similar to a query
|
|
134
|
+
versions = store.search_handle_versions("my_document", "machine learning")
|
|
135
|
+
|
|
136
|
+
# Compute all pairwise distances
|
|
137
|
+
distances = store.compute_version_distances("my_document")
|
|
138
|
+
|
|
139
|
+
See Also:
|
|
140
|
+
- MCardVectorStore: Base vector store implementation
|
|
141
|
+
- docs/architecture/Handle_Vector_Similarity_Design.md
|
|
142
|
+
- docs/architecture/Handle-Hash_Duality_and_Scale-Free_Architecture.md
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
def __init__(self, *args, **kwargs):
|
|
146
|
+
"""
|
|
147
|
+
Initialize HandleVectorStore.
|
|
148
|
+
|
|
149
|
+
Inherits all arguments from MCardVectorStore and adds
|
|
150
|
+
handle-version tables for semantic versioning.
|
|
151
|
+
"""
|
|
152
|
+
super().__init__(*args, **kwargs)
|
|
153
|
+
self._init_handle_tables()
|
|
154
|
+
|
|
155
|
+
def _init_handle_tables(self):
|
|
156
|
+
"""Create handle-vector bridge tables for semantic versioning."""
|
|
157
|
+
cursor = self.conn.cursor()
|
|
158
|
+
|
|
159
|
+
# Create handle-version-vector bridge table
|
|
160
|
+
cursor.execute(HANDLE_VECTOR_SCHEMAS["handle_version_vectors"])
|
|
161
|
+
|
|
162
|
+
# Create indexes (split by semicolons and execute each)
|
|
163
|
+
for statement in HANDLE_VECTOR_SCHEMAS["handle_version_vectors_indexes"].split(
|
|
164
|
+
";"
|
|
165
|
+
):
|
|
166
|
+
statement = statement.strip()
|
|
167
|
+
if statement:
|
|
168
|
+
cursor.execute(statement)
|
|
169
|
+
|
|
170
|
+
# Create similarity cache table
|
|
171
|
+
cursor.execute(HANDLE_VECTOR_SCHEMAS["similarity_cache"])
|
|
172
|
+
cursor.execute(HANDLE_VECTOR_SCHEMAS["similarity_cache_index"])
|
|
173
|
+
|
|
174
|
+
self.conn.commit()
|
|
175
|
+
logger.debug("Initialized handle-vector bridge tables")
|
|
176
|
+
|
|
177
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
178
|
+
# Indexing with Handle Association
|
|
179
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
def index_with_handle(
|
|
182
|
+
self, mcard: MCard, handle: str, is_current: bool = True, chunk: bool = True
|
|
183
|
+
) -> int:
|
|
184
|
+
"""
|
|
185
|
+
Index an MCard and associate it with a handle.
|
|
186
|
+
|
|
187
|
+
This method:
|
|
188
|
+
1. Indexes the MCard content with embeddings (via base class)
|
|
189
|
+
2. Links the MCard hash to the handle with version tracking
|
|
190
|
+
3. Computes semantic delta from parent version (if exists)
|
|
191
|
+
4. Classifies the upgrade type based on similarity
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
mcard: MCard to index
|
|
195
|
+
handle: Handle name to associate with
|
|
196
|
+
is_current: Whether this is the current version
|
|
197
|
+
chunk: Whether to chunk long content
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
Number of vectors indexed (0 if content couldn't be indexed)
|
|
201
|
+
|
|
202
|
+
Example:
|
|
203
|
+
>>> doc_v1 = create_mcard("Introduction to ML...")
|
|
204
|
+
>>> store.index_with_handle(doc_v1, handle="ml_intro")
|
|
205
|
+
1
|
|
206
|
+
>>> doc_v2 = create_mcard("Introduction to ML with more detail...")
|
|
207
|
+
>>> store.index_with_handle(doc_v2, handle="ml_intro")
|
|
208
|
+
1
|
|
209
|
+
"""
|
|
210
|
+
# Standard indexing (generates embeddings)
|
|
211
|
+
indexed = self.index(mcard, chunk=chunk)
|
|
212
|
+
|
|
213
|
+
if indexed == 0:
|
|
214
|
+
logger.warning(
|
|
215
|
+
f"MCard {mcard.hash[:8]} could not be indexed, skipping handle link"
|
|
216
|
+
)
|
|
217
|
+
return 0
|
|
218
|
+
|
|
219
|
+
cursor = self.conn.cursor()
|
|
220
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
221
|
+
|
|
222
|
+
# Get embedding ID for this hash (chunk 0)
|
|
223
|
+
cursor.execute(
|
|
224
|
+
"""
|
|
225
|
+
SELECT id FROM mcard_vector_metadata
|
|
226
|
+
WHERE hash = ? AND chunk_index = 0 LIMIT 1
|
|
227
|
+
""",
|
|
228
|
+
(mcard.hash,),
|
|
229
|
+
)
|
|
230
|
+
row = cursor.fetchone()
|
|
231
|
+
embedding_id = row[0] if row else None
|
|
232
|
+
|
|
233
|
+
# Get current version info for calculating semantic delta
|
|
234
|
+
parent_hash = None
|
|
235
|
+
semantic_delta = None
|
|
236
|
+
upgrade_type = None
|
|
237
|
+
|
|
238
|
+
if is_current:
|
|
239
|
+
# Get the current version before updating
|
|
240
|
+
cursor.execute(
|
|
241
|
+
"""
|
|
242
|
+
SELECT hash, embedding_id FROM handle_version_vectors
|
|
243
|
+
WHERE handle = ? AND is_current = TRUE LIMIT 1
|
|
244
|
+
""",
|
|
245
|
+
(handle,),
|
|
246
|
+
)
|
|
247
|
+
current_row = cursor.fetchone()
|
|
248
|
+
|
|
249
|
+
if current_row:
|
|
250
|
+
parent_hash = current_row[0]
|
|
251
|
+
parent_embedding_id = current_row[1]
|
|
252
|
+
|
|
253
|
+
# Calculate semantic delta if both have embeddings
|
|
254
|
+
if embedding_id and parent_embedding_id:
|
|
255
|
+
new_embedding = self._get_embedding(mcard.hash)
|
|
256
|
+
parent_embedding = self._get_embedding(parent_hash)
|
|
257
|
+
|
|
258
|
+
if new_embedding and parent_embedding:
|
|
259
|
+
semantic_delta = cosine_similarity(
|
|
260
|
+
new_embedding, parent_embedding
|
|
261
|
+
)
|
|
262
|
+
upgrade_type = classify_upgrade_type(semantic_delta)
|
|
263
|
+
logger.debug(
|
|
264
|
+
f"Semantic delta for {handle}: {semantic_delta:.4f} ({upgrade_type})"
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# Shift existing versions down
|
|
268
|
+
cursor.execute(
|
|
269
|
+
"""
|
|
270
|
+
UPDATE handle_version_vectors
|
|
271
|
+
SET version_order = version_order + 1, is_current = FALSE
|
|
272
|
+
WHERE handle = ?
|
|
273
|
+
""",
|
|
274
|
+
(handle,),
|
|
275
|
+
)
|
|
276
|
+
version_order = 0
|
|
277
|
+
else:
|
|
278
|
+
# Append as non-current version
|
|
279
|
+
cursor.execute(
|
|
280
|
+
"""
|
|
281
|
+
SELECT COALESCE(MAX(version_order), -1) + 1
|
|
282
|
+
FROM handle_version_vectors
|
|
283
|
+
WHERE handle = ?
|
|
284
|
+
""",
|
|
285
|
+
(handle,),
|
|
286
|
+
)
|
|
287
|
+
version_order = cursor.fetchone()[0]
|
|
288
|
+
|
|
289
|
+
# Insert version record
|
|
290
|
+
cursor.execute(
|
|
291
|
+
"""
|
|
292
|
+
INSERT OR REPLACE INTO handle_version_vectors
|
|
293
|
+
(handle, hash, parent_hash, version_order, is_current,
|
|
294
|
+
embedding_id, semantic_delta_from_parent, upgrade_type, created_at)
|
|
295
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
296
|
+
""",
|
|
297
|
+
(
|
|
298
|
+
handle,
|
|
299
|
+
mcard.hash,
|
|
300
|
+
parent_hash,
|
|
301
|
+
version_order,
|
|
302
|
+
is_current,
|
|
303
|
+
embedding_id,
|
|
304
|
+
semantic_delta,
|
|
305
|
+
upgrade_type,
|
|
306
|
+
now,
|
|
307
|
+
),
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
self.conn.commit()
|
|
311
|
+
logger.info(
|
|
312
|
+
f"Indexed MCard {mcard.hash[:8]} for handle '{handle}' "
|
|
313
|
+
f"(v{version_order}, current={is_current}, type={upgrade_type})"
|
|
314
|
+
)
|
|
315
|
+
return indexed
|
|
316
|
+
|
|
317
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
318
|
+
# Version Retrieval
|
|
319
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
320
|
+
|
|
321
|
+
def get_handle_versions(self, handle: str) -> list[HandleVersion]:
|
|
322
|
+
"""
|
|
323
|
+
Get all versions for a handle.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
handle: Handle name to query
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
List of HandleVersion objects, ordered by version_order (0 = current)
|
|
330
|
+
|
|
331
|
+
Example:
|
|
332
|
+
>>> versions = store.get_handle_versions("ml_intro")
|
|
333
|
+
>>> for v in versions:
|
|
334
|
+
... print(f"v{v.version_order}: {v.hash[:8]} ({v.upgrade_type})")
|
|
335
|
+
v0: abc12345 (None) # current
|
|
336
|
+
v1: def67890 (minor)
|
|
337
|
+
v2: xyz11223 (major)
|
|
338
|
+
"""
|
|
339
|
+
cursor = self.conn.cursor()
|
|
340
|
+
cursor.execute(
|
|
341
|
+
"""
|
|
342
|
+
SELECT hash, version_order, is_current, embedding_id, created_at,
|
|
343
|
+
parent_hash, semantic_delta_from_parent, upgrade_type
|
|
344
|
+
FROM handle_version_vectors
|
|
345
|
+
WHERE handle = ?
|
|
346
|
+
ORDER BY version_order
|
|
347
|
+
""",
|
|
348
|
+
(handle,),
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
return [
|
|
352
|
+
HandleVersion(
|
|
353
|
+
hash=row[0],
|
|
354
|
+
version_order=row[1],
|
|
355
|
+
is_current=bool(row[2]),
|
|
356
|
+
embedding_id=row[3],
|
|
357
|
+
created_at=row[4],
|
|
358
|
+
parent_hash=row[5],
|
|
359
|
+
semantic_delta=row[6],
|
|
360
|
+
upgrade_type=row[7],
|
|
361
|
+
)
|
|
362
|
+
for row in cursor.fetchall()
|
|
363
|
+
]
|
|
364
|
+
|
|
365
|
+
def get_handle_version_hashes(self, handle: str) -> list[str]:
|
|
366
|
+
"""
|
|
367
|
+
Get all version hashes for a handle.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
handle: Handle name to query
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
List of hashes, ordered by version_order
|
|
374
|
+
"""
|
|
375
|
+
versions = self.get_handle_versions(handle)
|
|
376
|
+
return [v.hash for v in versions]
|
|
377
|
+
|
|
378
|
+
def get_current_version(self, handle: str) -> HandleVersion | None:
|
|
379
|
+
"""
|
|
380
|
+
Get the current version for a handle.
|
|
381
|
+
|
|
382
|
+
Args:
|
|
383
|
+
handle: Handle name to query
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
HandleVersion for current version, or None if handle not found
|
|
387
|
+
"""
|
|
388
|
+
cursor = self.conn.cursor()
|
|
389
|
+
cursor.execute(
|
|
390
|
+
"""
|
|
391
|
+
SELECT hash, version_order, is_current, embedding_id, created_at,
|
|
392
|
+
parent_hash, semantic_delta_from_parent, upgrade_type
|
|
393
|
+
FROM handle_version_vectors
|
|
394
|
+
WHERE handle = ? AND is_current = TRUE
|
|
395
|
+
LIMIT 1
|
|
396
|
+
""",
|
|
397
|
+
(handle,),
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
row = cursor.fetchone()
|
|
401
|
+
if not row:
|
|
402
|
+
return None
|
|
403
|
+
|
|
404
|
+
return HandleVersion(
|
|
405
|
+
hash=row[0],
|
|
406
|
+
version_order=row[1],
|
|
407
|
+
is_current=bool(row[2]),
|
|
408
|
+
embedding_id=row[3],
|
|
409
|
+
created_at=row[4],
|
|
410
|
+
parent_hash=row[5],
|
|
411
|
+
semantic_delta=row[6],
|
|
412
|
+
upgrade_type=row[7],
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
416
|
+
# Semantic Similarity Operations
|
|
417
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
418
|
+
|
|
419
|
+
def get_versions_by_similarity(
|
|
420
|
+
self, handle: str, reference_hash: str = None, metric: str = "cosine"
|
|
421
|
+
) -> list[VersionSimilarityResult]:
|
|
422
|
+
"""
|
|
423
|
+
Get versions for a handle, sorted by similarity to a reference.
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
handle: Handle name
|
|
427
|
+
reference_hash: Hash to compare against (default: current version)
|
|
428
|
+
metric: 'cosine' (higher = more similar) or 'euclidean' (lower = more similar)
|
|
429
|
+
|
|
430
|
+
Returns:
|
|
431
|
+
List of VersionSimilarityResult, sorted by similarity
|
|
432
|
+
|
|
433
|
+
Example:
|
|
434
|
+
>>> versions = store.get_versions_by_similarity("ml_intro")
|
|
435
|
+
>>> for v in versions:
|
|
436
|
+
... print(f"v{v.version_order}: sim={v.similarity_to_current:.4f}")
|
|
437
|
+
v0: sim=1.0000 (current - self-similarity)
|
|
438
|
+
v1: sim=0.8234 (most similar to current)
|
|
439
|
+
v2: sim=0.6521 (least similar)
|
|
440
|
+
"""
|
|
441
|
+
versions = self.get_handle_versions(handle)
|
|
442
|
+
|
|
443
|
+
if not versions:
|
|
444
|
+
return []
|
|
445
|
+
|
|
446
|
+
# Determine reference hash
|
|
447
|
+
if reference_hash is None:
|
|
448
|
+
for v in versions:
|
|
449
|
+
if v.is_current:
|
|
450
|
+
reference_hash = v.hash
|
|
451
|
+
break
|
|
452
|
+
if reference_hash is None and versions:
|
|
453
|
+
reference_hash = versions[0].hash
|
|
454
|
+
|
|
455
|
+
# Get reference embedding
|
|
456
|
+
ref_embedding = self._get_embedding(reference_hash)
|
|
457
|
+
if ref_embedding is None:
|
|
458
|
+
logger.warning(
|
|
459
|
+
f"No embedding found for reference hash {reference_hash[:8]}"
|
|
460
|
+
)
|
|
461
|
+
return []
|
|
462
|
+
|
|
463
|
+
# Calculate similarities
|
|
464
|
+
results = []
|
|
465
|
+
for v in versions:
|
|
466
|
+
if v.hash == reference_hash:
|
|
467
|
+
# Self-similarity
|
|
468
|
+
similarity = 1.0
|
|
469
|
+
distance = 0.0
|
|
470
|
+
else:
|
|
471
|
+
v_embedding = self._get_embedding(v.hash)
|
|
472
|
+
if v_embedding is None:
|
|
473
|
+
logger.debug(f"Skipping version {v.hash[:8]} - no embedding")
|
|
474
|
+
continue
|
|
475
|
+
|
|
476
|
+
similarity = cosine_similarity(ref_embedding, v_embedding)
|
|
477
|
+
distance = euclidean_distance(ref_embedding, v_embedding)
|
|
478
|
+
|
|
479
|
+
results.append(
|
|
480
|
+
VersionSimilarityResult(
|
|
481
|
+
hash=v.hash,
|
|
482
|
+
version_order=v.version_order,
|
|
483
|
+
similarity_to_current=similarity,
|
|
484
|
+
distance_to_current=distance,
|
|
485
|
+
parent_hash=v.parent_hash,
|
|
486
|
+
upgrade_type=v.upgrade_type,
|
|
487
|
+
)
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
# Sort by similarity (descending) or distance (ascending)
|
|
491
|
+
if metric == "cosine":
|
|
492
|
+
results.sort(key=lambda x: x.similarity_to_current, reverse=True)
|
|
493
|
+
else:
|
|
494
|
+
results.sort(key=lambda x: x.distance_to_current)
|
|
495
|
+
|
|
496
|
+
return results
|
|
497
|
+
|
|
498
|
+
def search_handle_versions(
|
|
499
|
+
self, handle: str, query: str, k: int = None
|
|
500
|
+
) -> list[VersionSimilarityResult]:
|
|
501
|
+
"""
|
|
502
|
+
Search within a handle's versions by semantic query.
|
|
503
|
+
|
|
504
|
+
Useful for finding which version of a document best matches
|
|
505
|
+
a specific concept or topic.
|
|
506
|
+
|
|
507
|
+
Args:
|
|
508
|
+
handle: Handle name to filter by
|
|
509
|
+
query: Search query text
|
|
510
|
+
k: Number of results (default from config)
|
|
511
|
+
|
|
512
|
+
Returns:
|
|
513
|
+
List of VersionSimilarityResult with query similarity scores
|
|
514
|
+
|
|
515
|
+
Example:
|
|
516
|
+
>>> results = store.search_handle_versions("ml_intro", "neural networks")
|
|
517
|
+
>>> for r in results:
|
|
518
|
+
... print(f"v{r.version_order}: query_sim={r.similarity_to_query:.4f}")
|
|
519
|
+
v0: query_sim=0.8923 (current version mentions neural networks)
|
|
520
|
+
v1: query_sim=0.4123 (mentions AI but not directly)
|
|
521
|
+
"""
|
|
522
|
+
k = k or self.config.top_k
|
|
523
|
+
|
|
524
|
+
# Get version hashes for this handle
|
|
525
|
+
version_hashes = self.get_handle_version_hashes(handle)
|
|
526
|
+
if not version_hashes:
|
|
527
|
+
return []
|
|
528
|
+
|
|
529
|
+
# Search with hash filter (uses base class method)
|
|
530
|
+
results = self.search(query, k=k, filter_hashes=version_hashes)
|
|
531
|
+
|
|
532
|
+
# Get version info for enrichment
|
|
533
|
+
versions = {v.hash: v for v in self.get_handle_versions(handle)}
|
|
534
|
+
|
|
535
|
+
# Enrich results with version info
|
|
536
|
+
enriched = []
|
|
537
|
+
for r in results:
|
|
538
|
+
v = versions.get(r.hash)
|
|
539
|
+
if v:
|
|
540
|
+
enriched.append(
|
|
541
|
+
VersionSimilarityResult(
|
|
542
|
+
hash=r.hash,
|
|
543
|
+
version_order=v.version_order,
|
|
544
|
+
similarity_to_current=0.0, # Not computed in this method
|
|
545
|
+
distance_to_current=0.0, # Not computed in this method
|
|
546
|
+
similarity_to_query=r.score,
|
|
547
|
+
parent_hash=v.parent_hash,
|
|
548
|
+
upgrade_type=v.upgrade_type,
|
|
549
|
+
)
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
return enriched
|
|
553
|
+
|
|
554
|
+
def compute_version_distances(
|
|
555
|
+
self, handle: str, cache: bool = True
|
|
556
|
+
) -> dict[tuple[str, str], float]:
|
|
557
|
+
"""
|
|
558
|
+
Compute pairwise cosine similarities between all versions of a handle.
|
|
559
|
+
|
|
560
|
+
Optionally caches results in the version_similarity_cache table.
|
|
561
|
+
|
|
562
|
+
Args:
|
|
563
|
+
handle: Handle name
|
|
564
|
+
cache: Whether to cache computed similarities
|
|
565
|
+
|
|
566
|
+
Returns:
|
|
567
|
+
Dict mapping (hash_a, hash_b) tuple to cosine similarity
|
|
568
|
+
|
|
569
|
+
Example:
|
|
570
|
+
>>> distances = store.compute_version_distances("ml_intro")
|
|
571
|
+
>>> for (h1, h2), sim in sorted(distances.items()):
|
|
572
|
+
... print(f"{h1[:8]} <-> {h2[:8]}: {sim:.4f}")
|
|
573
|
+
"""
|
|
574
|
+
versions = self.get_handle_versions(handle)
|
|
575
|
+
embeddings: dict[str, list[float]] = {}
|
|
576
|
+
|
|
577
|
+
for v in versions:
|
|
578
|
+
emb = self._get_embedding(v.hash)
|
|
579
|
+
if emb:
|
|
580
|
+
embeddings[v.hash] = emb
|
|
581
|
+
|
|
582
|
+
distances: dict[tuple[str, str], float] = {}
|
|
583
|
+
hashes = list(embeddings.keys())
|
|
584
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
585
|
+
|
|
586
|
+
cursor = self.conn.cursor()
|
|
587
|
+
|
|
588
|
+
for i, h1 in enumerate(hashes):
|
|
589
|
+
for h2 in hashes[i:]:
|
|
590
|
+
if h1 == h2:
|
|
591
|
+
sim = 1.0
|
|
592
|
+
else:
|
|
593
|
+
sim = cosine_similarity(embeddings[h1], embeddings[h2])
|
|
594
|
+
dist = euclidean_distance(embeddings[h1], embeddings[h2])
|
|
595
|
+
|
|
596
|
+
# Cache the result
|
|
597
|
+
if cache:
|
|
598
|
+
try:
|
|
599
|
+
cursor.execute(
|
|
600
|
+
"""
|
|
601
|
+
INSERT OR REPLACE INTO version_similarity_cache
|
|
602
|
+
(handle, hash_a, hash_b, similarity_score, distance_euclidean, computed_at)
|
|
603
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
604
|
+
""",
|
|
605
|
+
(handle, h1, h2, sim, dist, now),
|
|
606
|
+
)
|
|
607
|
+
except Exception as e:
|
|
608
|
+
logger.warning(
|
|
609
|
+
f"Failed to cache similarity for {h1[:8]}<->{h2[:8]}: {e}"
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
distances[(h1, h2)] = sim
|
|
613
|
+
distances[(h2, h1)] = sim
|
|
614
|
+
|
|
615
|
+
if cache:
|
|
616
|
+
self.conn.commit()
|
|
617
|
+
|
|
618
|
+
return distances
|
|
619
|
+
|
|
620
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
621
|
+
# Embedding Retrieval (Internal)
|
|
622
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
623
|
+
|
|
624
|
+
def _get_embedding(self, hash: str, chunk_index: int = 0) -> list[float] | None:
|
|
625
|
+
"""
|
|
626
|
+
Get embedding vector for a hash.
|
|
627
|
+
|
|
628
|
+
Args:
|
|
629
|
+
hash: MCard hash
|
|
630
|
+
chunk_index: Chunk index (default 0 for first/whole chunk)
|
|
631
|
+
|
|
632
|
+
Returns:
|
|
633
|
+
Embedding vector as list of floats, or None if not found
|
|
634
|
+
"""
|
|
635
|
+
cursor = self.conn.cursor()
|
|
636
|
+
|
|
637
|
+
# Get metadata ID
|
|
638
|
+
cursor.execute(
|
|
639
|
+
"""
|
|
640
|
+
SELECT id FROM mcard_vector_metadata
|
|
641
|
+
WHERE hash = ? AND chunk_index = ?
|
|
642
|
+
""",
|
|
643
|
+
(hash, chunk_index),
|
|
644
|
+
)
|
|
645
|
+
row = cursor.fetchone()
|
|
646
|
+
|
|
647
|
+
if not row:
|
|
648
|
+
return None
|
|
649
|
+
|
|
650
|
+
metadata_id = row[0]
|
|
651
|
+
|
|
652
|
+
# Get embedding from appropriate table
|
|
653
|
+
if self.has_vec_extension:
|
|
654
|
+
cursor.execute(
|
|
655
|
+
"""
|
|
656
|
+
SELECT embedding FROM mcard_vec WHERE metadata_id = ?
|
|
657
|
+
""",
|
|
658
|
+
(metadata_id,),
|
|
659
|
+
)
|
|
660
|
+
else:
|
|
661
|
+
cursor.execute(
|
|
662
|
+
"""
|
|
663
|
+
SELECT embedding FROM mcard_embeddings WHERE metadata_id = ?
|
|
664
|
+
""",
|
|
665
|
+
(metadata_id,),
|
|
666
|
+
)
|
|
667
|
+
|
|
668
|
+
row = cursor.fetchone()
|
|
669
|
+
if row:
|
|
670
|
+
return deserialize_vector(row[0])
|
|
671
|
+
return None
|
|
672
|
+
|
|
673
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
674
|
+
# Utility Methods
|
|
675
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
676
|
+
|
|
677
|
+
def list_handles(self) -> list[str]:
|
|
678
|
+
"""
|
|
679
|
+
List all handles with indexed versions.
|
|
680
|
+
|
|
681
|
+
Returns:
|
|
682
|
+
List of unique handle names
|
|
683
|
+
"""
|
|
684
|
+
cursor = self.conn.cursor()
|
|
685
|
+
cursor.execute(
|
|
686
|
+
"SELECT DISTINCT handle FROM handle_version_vectors ORDER BY handle"
|
|
687
|
+
)
|
|
688
|
+
return [row[0] for row in cursor.fetchall()]
|
|
689
|
+
|
|
690
|
+
def count_versions(self, handle: str) -> int:
|
|
691
|
+
"""
|
|
692
|
+
Count versions for a handle.
|
|
693
|
+
|
|
694
|
+
Args:
|
|
695
|
+
handle: Handle name
|
|
696
|
+
|
|
697
|
+
Returns:
|
|
698
|
+
Number of versions
|
|
699
|
+
"""
|
|
700
|
+
cursor = self.conn.cursor()
|
|
701
|
+
cursor.execute(
|
|
702
|
+
"SELECT COUNT(*) FROM handle_version_vectors WHERE handle = ?", (handle,)
|
|
703
|
+
)
|
|
704
|
+
return cursor.fetchone()[0]
|
|
705
|
+
|
|
706
|
+
def delete_handle(self, handle: str) -> int:
|
|
707
|
+
"""
|
|
708
|
+
Delete all version records for a handle.
|
|
709
|
+
|
|
710
|
+
Note: This only removes the handle-version associations,
|
|
711
|
+
not the underlying MCard embeddings.
|
|
712
|
+
|
|
713
|
+
Args:
|
|
714
|
+
handle: Handle name
|
|
715
|
+
|
|
716
|
+
Returns:
|
|
717
|
+
Number of version records deleted
|
|
718
|
+
"""
|
|
719
|
+
cursor = self.conn.cursor()
|
|
720
|
+
|
|
721
|
+
# Delete similarity cache
|
|
722
|
+
cursor.execute(
|
|
723
|
+
"DELETE FROM version_similarity_cache WHERE handle = ?", (handle,)
|
|
724
|
+
)
|
|
725
|
+
|
|
726
|
+
# Delete version records
|
|
727
|
+
cursor.execute("DELETE FROM handle_version_vectors WHERE handle = ?", (handle,))
|
|
728
|
+
deleted = cursor.rowcount
|
|
729
|
+
|
|
730
|
+
self.conn.commit()
|
|
731
|
+
logger.info(f"Deleted {deleted} version records for handle '{handle}'")
|
|
732
|
+
return deleted
|
|
733
|
+
|
|
734
|
+
def get_info(self) -> dict[str, Any]:
|
|
735
|
+
"""Get extended vector store information including handle stats."""
|
|
736
|
+
info = super().get_info()
|
|
737
|
+
|
|
738
|
+
# Add handle-specific stats
|
|
739
|
+
cursor = self.conn.cursor()
|
|
740
|
+
cursor.execute("SELECT COUNT(DISTINCT handle) FROM handle_version_vectors")
|
|
741
|
+
info["handle_count"] = cursor.fetchone()[0]
|
|
742
|
+
|
|
743
|
+
cursor.execute("SELECT COUNT(*) FROM handle_version_vectors")
|
|
744
|
+
info["version_count"] = cursor.fetchone()[0]
|
|
745
|
+
|
|
746
|
+
cursor.execute("SELECT COUNT(*) FROM version_similarity_cache")
|
|
747
|
+
info["cached_similarities"] = cursor.fetchone()[0]
|
|
748
|
+
|
|
749
|
+
return info
|