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,333 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Persistent Vector Indexer
|
|
3
|
+
|
|
4
|
+
Manages automatic indexing of MCards into the vector store,
|
|
5
|
+
with persistent storage alongside the main MCard database.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import threading
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from clm.layer0.model.card import MCard
|
|
15
|
+
from clm.layer2.card_collection import CardCollection
|
|
16
|
+
|
|
17
|
+
from .config import DEFAULT_RAG_CONFIG, RAGConfig
|
|
18
|
+
from .embeddings import OllamaEmbeddingProvider
|
|
19
|
+
from .vector import MCardVectorStore
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class PersistentIndexer:
|
|
25
|
+
"""
|
|
26
|
+
Manages persistent vector indexing for MCard collections.
|
|
27
|
+
|
|
28
|
+
Features:
|
|
29
|
+
- Automatic indexing when MCards are added
|
|
30
|
+
- Persistent vector database alongside MCard database
|
|
31
|
+
- Background indexing for large collections
|
|
32
|
+
- Index status tracking
|
|
33
|
+
|
|
34
|
+
Usage:
|
|
35
|
+
from clm.layer3.rag import PersistentIndexer
|
|
36
|
+
|
|
37
|
+
indexer = PersistentIndexer() # Uses default collection
|
|
38
|
+
|
|
39
|
+
# Index all existing content
|
|
40
|
+
stats = indexer.index_all()
|
|
41
|
+
|
|
42
|
+
# Search
|
|
43
|
+
results = indexer.search("query")
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
_instance = None
|
|
47
|
+
_lock = threading.Lock()
|
|
48
|
+
|
|
49
|
+
def __new__(cls, *args, **kwargs):
|
|
50
|
+
"""Singleton pattern for global indexer."""
|
|
51
|
+
if cls._instance is None:
|
|
52
|
+
with cls._lock:
|
|
53
|
+
if cls._instance is None:
|
|
54
|
+
cls._instance = super().__new__(cls)
|
|
55
|
+
cls._instance._initialized = False
|
|
56
|
+
return cls._instance
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
collection: CardCollection = None,
|
|
61
|
+
config: RAGConfig = None,
|
|
62
|
+
vector_db_path: str = None,
|
|
63
|
+
auto_index: bool = False,
|
|
64
|
+
):
|
|
65
|
+
"""
|
|
66
|
+
Initialize persistent indexer.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
collection: MCard collection (uses default if None)
|
|
70
|
+
config: RAG configuration
|
|
71
|
+
vector_db_path: Path to vector database (auto-derived if None)
|
|
72
|
+
auto_index: Automatically index new MCards
|
|
73
|
+
"""
|
|
74
|
+
if self._initialized:
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
from clm.layer2.card_collection import get_default_collection
|
|
78
|
+
default_collection = get_default_collection()
|
|
79
|
+
|
|
80
|
+
self.collection = collection or default_collection
|
|
81
|
+
self.config = config or DEFAULT_RAG_CONFIG
|
|
82
|
+
self.auto_index = auto_index
|
|
83
|
+
|
|
84
|
+
# Derive vector DB path from MCard DB path
|
|
85
|
+
if vector_db_path is None:
|
|
86
|
+
mcard_db_path = self._get_mcard_db_path()
|
|
87
|
+
if mcard_db_path and mcard_db_path != ":memory:":
|
|
88
|
+
base_path = Path(mcard_db_path)
|
|
89
|
+
vector_db_path = str(base_path.parent / f"{base_path.stem}_vectors.db")
|
|
90
|
+
else:
|
|
91
|
+
vector_db_path = ":memory:"
|
|
92
|
+
|
|
93
|
+
self.vector_db_path = vector_db_path
|
|
94
|
+
|
|
95
|
+
# Initialize embedding provider
|
|
96
|
+
self.embedder = OllamaEmbeddingProvider(
|
|
97
|
+
model=self.config.embedding_model, base_url=self.config.ollama_base_url
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Initialize vector store
|
|
101
|
+
self.vector_store = MCardVectorStore(
|
|
102
|
+
db_path=self.vector_db_path,
|
|
103
|
+
config=self.config,
|
|
104
|
+
embedding_provider=self.embedder,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Track indexing state
|
|
108
|
+
self._indexing_in_progress = False
|
|
109
|
+
self._indexed_hashes: set[str] = set()
|
|
110
|
+
self._load_indexed_hashes()
|
|
111
|
+
|
|
112
|
+
self._initialized = True
|
|
113
|
+
logger.info(f"PersistentIndexer initialized: {self.vector_db_path}")
|
|
114
|
+
|
|
115
|
+
def _get_mcard_db_path(self) -> str | None:
|
|
116
|
+
"""Get the path of the MCard database."""
|
|
117
|
+
try:
|
|
118
|
+
if hasattr(self.collection, "engine"):
|
|
119
|
+
engine = self.collection.engine
|
|
120
|
+
if hasattr(engine, "connection") and hasattr(
|
|
121
|
+
engine.connection, "db_path"
|
|
122
|
+
):
|
|
123
|
+
return engine.connection.db_path
|
|
124
|
+
except Exception:
|
|
125
|
+
pass
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
def _load_indexed_hashes(self):
|
|
129
|
+
"""Load the set of already-indexed hashes."""
|
|
130
|
+
try:
|
|
131
|
+
cursor = self.vector_store.conn.cursor()
|
|
132
|
+
cursor.execute("SELECT DISTINCT hash FROM mcard_vector_metadata")
|
|
133
|
+
self._indexed_hashes = {row[0] for row in cursor.fetchall()}
|
|
134
|
+
logger.debug(f"Loaded {len(self._indexed_hashes)} indexed hashes")
|
|
135
|
+
except Exception as e:
|
|
136
|
+
logger.warning(f"Failed to load indexed hashes: {e}")
|
|
137
|
+
self._indexed_hashes = set()
|
|
138
|
+
|
|
139
|
+
def is_indexed(self, hash: str) -> bool:
|
|
140
|
+
"""Check if an MCard is already indexed."""
|
|
141
|
+
return hash in self._indexed_hashes or self.vector_store.is_indexed(hash)
|
|
142
|
+
|
|
143
|
+
def index_mcard(self, mcard: MCard, force: bool = False) -> bool:
|
|
144
|
+
"""
|
|
145
|
+
Index a single MCard.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
mcard: MCard to index
|
|
149
|
+
force: Re-index even if already indexed
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
True if indexed successfully
|
|
153
|
+
"""
|
|
154
|
+
if not force and self.is_indexed(mcard.hash):
|
|
155
|
+
logger.debug(f"MCard {mcard.hash[:8]} already indexed, skipping")
|
|
156
|
+
return True
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
count = self.vector_store.index(mcard)
|
|
160
|
+
if count > 0:
|
|
161
|
+
self._indexed_hashes.add(mcard.hash)
|
|
162
|
+
logger.debug(f"Indexed MCard {mcard.hash[:8]} ({count} vectors)")
|
|
163
|
+
return True
|
|
164
|
+
return False
|
|
165
|
+
except Exception as e:
|
|
166
|
+
logger.error(f"Failed to index MCard {mcard.hash[:8]}: {e}", exc_info=True)
|
|
167
|
+
return False
|
|
168
|
+
|
|
169
|
+
def index_all(
|
|
170
|
+
self,
|
|
171
|
+
force: bool = False,
|
|
172
|
+
progress_callback: Callable[[int, int], None] = None,
|
|
173
|
+
_batch_size: int = 50,
|
|
174
|
+
) -> dict[str, int]:
|
|
175
|
+
"""
|
|
176
|
+
Index all MCards in the collection.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
force: Re-index even if already indexed
|
|
180
|
+
progress_callback: Optional callback(current, total)
|
|
181
|
+
batch_size: Number of cards to process at once
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Dictionary with 'indexed', 'skipped', 'failed', 'total' counts
|
|
185
|
+
"""
|
|
186
|
+
if self._indexing_in_progress:
|
|
187
|
+
logger.warning("Indexing already in progress")
|
|
188
|
+
return {
|
|
189
|
+
"indexed": 0,
|
|
190
|
+
"skipped": 0,
|
|
191
|
+
"failed": 0,
|
|
192
|
+
"total": 0,
|
|
193
|
+
"status": "busy",
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
self._indexing_in_progress = True
|
|
197
|
+
stats = {"indexed": 0, "skipped": 0, "failed": 0, "total": 0}
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
# Get all cards
|
|
201
|
+
all_cards = self.collection.get_all_mcards_raw()
|
|
202
|
+
stats["total"] = len(all_cards)
|
|
203
|
+
|
|
204
|
+
for i, mcard in enumerate(all_cards):
|
|
205
|
+
if not force and self.is_indexed(mcard.hash):
|
|
206
|
+
stats["skipped"] += 1
|
|
207
|
+
elif self.index_mcard(mcard, force=force):
|
|
208
|
+
stats["indexed"] += 1
|
|
209
|
+
else:
|
|
210
|
+
stats["failed"] += 1
|
|
211
|
+
|
|
212
|
+
# Progress callback
|
|
213
|
+
if progress_callback and (i + 1) % 10 == 0:
|
|
214
|
+
progress_callback(i + 1, stats["total"])
|
|
215
|
+
|
|
216
|
+
logger.info(f"Indexing complete: {stats}")
|
|
217
|
+
|
|
218
|
+
finally:
|
|
219
|
+
self._indexing_in_progress = False
|
|
220
|
+
|
|
221
|
+
return stats
|
|
222
|
+
|
|
223
|
+
def search(self, query: str, k: int = None, hybrid: bool = True) -> list[Any]:
|
|
224
|
+
"""
|
|
225
|
+
Search for similar MCards.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
query: Search query
|
|
229
|
+
k: Number of results
|
|
230
|
+
hybrid: Use hybrid (vector + FTS) search
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
List of VectorSearchResult
|
|
234
|
+
"""
|
|
235
|
+
if hybrid and self.config.enable_hybrid_search:
|
|
236
|
+
return self.vector_store.hybrid_search(query, k)
|
|
237
|
+
else:
|
|
238
|
+
return self.vector_store.search(query, k)
|
|
239
|
+
|
|
240
|
+
def delete(self, hash: str) -> bool:
|
|
241
|
+
"""Delete an MCard from the index."""
|
|
242
|
+
count = self.vector_store.delete(hash)
|
|
243
|
+
if count > 0:
|
|
244
|
+
self._indexed_hashes.discard(hash)
|
|
245
|
+
return True
|
|
246
|
+
return False
|
|
247
|
+
|
|
248
|
+
def clear(self):
|
|
249
|
+
"""Clear the entire vector index."""
|
|
250
|
+
cursor = self.vector_store.conn.cursor()
|
|
251
|
+
cursor.execute("DELETE FROM mcard_vector_metadata")
|
|
252
|
+
cursor.execute("DELETE FROM mcard_embeddings")
|
|
253
|
+
if self.config.enable_hybrid_search:
|
|
254
|
+
cursor.execute("DELETE FROM mcard_fts")
|
|
255
|
+
self.vector_store.conn.commit()
|
|
256
|
+
self._indexed_hashes.clear()
|
|
257
|
+
logger.info("Vector index cleared")
|
|
258
|
+
|
|
259
|
+
def get_stats(self) -> dict[str, Any]:
|
|
260
|
+
"""Get indexer statistics."""
|
|
261
|
+
return {
|
|
262
|
+
"vector_db_path": self.vector_db_path,
|
|
263
|
+
"embedding_model": self.embedder.model_name,
|
|
264
|
+
"dimensions": self.embedder.dimensions,
|
|
265
|
+
"indexed_count": len(self._indexed_hashes),
|
|
266
|
+
"vector_count": self.vector_store.count(),
|
|
267
|
+
"unique_mcards": self.vector_store.count_unique(),
|
|
268
|
+
"has_vec_extension": self.vector_store.has_vec_extension,
|
|
269
|
+
"hybrid_search_enabled": self.config.enable_hybrid_search,
|
|
270
|
+
"indexing_in_progress": self._indexing_in_progress,
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
@classmethod
|
|
274
|
+
def reset_instance(cls):
|
|
275
|
+
"""Reset the singleton instance (for testing)."""
|
|
276
|
+
with cls._lock:
|
|
277
|
+
cls._instance = None
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
281
|
+
# Global Convenience Functions
|
|
282
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
283
|
+
|
|
284
|
+
_default_indexer: PersistentIndexer | None = None
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def get_indexer(
|
|
288
|
+
collection: CardCollection = None, config: RAGConfig = None
|
|
289
|
+
) -> PersistentIndexer:
|
|
290
|
+
"""
|
|
291
|
+
Get or create the default persistent indexer.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
collection: Optional collection override
|
|
295
|
+
config: Optional configuration override
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
PersistentIndexer instance
|
|
299
|
+
"""
|
|
300
|
+
global _default_indexer
|
|
301
|
+
|
|
302
|
+
if _default_indexer is None:
|
|
303
|
+
_default_indexer = PersistentIndexer(collection=collection, config=config)
|
|
304
|
+
|
|
305
|
+
return _default_indexer
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def semantic_search(query: str, k: int = 5) -> list[Any]:
|
|
309
|
+
"""
|
|
310
|
+
Convenience function for semantic search.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
query: Search query
|
|
314
|
+
k: Number of results
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
List of VectorSearchResult
|
|
318
|
+
"""
|
|
319
|
+
return get_indexer().search(query, k)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def index_mcard(mcard: MCard, force: bool = False) -> bool:
|
|
323
|
+
"""
|
|
324
|
+
Convenience function to index an MCard.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
mcard: MCard to index
|
|
328
|
+
force: Re-index if exists
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
True if indexed
|
|
332
|
+
"""
|
|
333
|
+
return get_indexer().index_mcard(mcard, force)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"embedding_models": {
|
|
3
|
+
"nomic-embed-text": {
|
|
4
|
+
"dimensions": 768,
|
|
5
|
+
"provider": "ollama",
|
|
6
|
+
"max_tokens": 8192,
|
|
7
|
+
"description": "High quality, general purpose embeddings"
|
|
8
|
+
},
|
|
9
|
+
"bge-m3": {
|
|
10
|
+
"dimensions": 1024,
|
|
11
|
+
"provider": "ollama",
|
|
12
|
+
"max_tokens": 8192,
|
|
13
|
+
"description": "Multi-lingual, multi-granularity embeddings"
|
|
14
|
+
},
|
|
15
|
+
"mxbai-embed-large": {
|
|
16
|
+
"dimensions": 1024,
|
|
17
|
+
"provider": "ollama",
|
|
18
|
+
"max_tokens": 512,
|
|
19
|
+
"description": "High quality embeddings for retrieval"
|
|
20
|
+
},
|
|
21
|
+
"all-minilm": {
|
|
22
|
+
"dimensions": 384,
|
|
23
|
+
"provider": "ollama",
|
|
24
|
+
"max_tokens": 256,
|
|
25
|
+
"description": "Fast, lightweight embeddings"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"vision_models": {
|
|
29
|
+
"moondream": {
|
|
30
|
+
"size": "1.7GB",
|
|
31
|
+
"description": "Moondream - Tiny, high-performance vision language model",
|
|
32
|
+
"capabilities": ["image_description", "visual_qa"]
|
|
33
|
+
},
|
|
34
|
+
"llama3.2-vision": {
|
|
35
|
+
"size": "7.9GB",
|
|
36
|
+
"description": "Llama 3.2 Vision - 11B multimodal model with strong OCR",
|
|
37
|
+
"capabilities": ["image_description", "ocr", "visual_qa"]
|
|
38
|
+
},
|
|
39
|
+
"llava": {
|
|
40
|
+
"size": "4.7GB",
|
|
41
|
+
"description": "LLaVA - Large Language and Vision Assistant",
|
|
42
|
+
"capabilities": ["image_description", "visual_qa"]
|
|
43
|
+
},
|
|
44
|
+
"minicpm-v": {
|
|
45
|
+
"size": "5.6GB",
|
|
46
|
+
"description": "MiniCPM-V - Efficient vision-language model",
|
|
47
|
+
"capabilities": ["image_description", "ocr", "multi_image"]
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"default_embedding_model": "nomic-embed-text",
|
|
51
|
+
"default_vision_model": "moondream"
|
|
52
|
+
}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Semantic Versioning API for MCards
|
|
3
|
+
|
|
4
|
+
High-level API functions for managing MCard versions linked to handles
|
|
5
|
+
with semantic similarity detection.
|
|
6
|
+
|
|
7
|
+
This module provides a simplified interface for:
|
|
8
|
+
- Linking MCards to handles
|
|
9
|
+
- Retrieving version history with semantic info
|
|
10
|
+
- Comparing versions by semantic similarity
|
|
11
|
+
- Searching within a handle's version history
|
|
12
|
+
|
|
13
|
+
See: docs/architecture/Handle_Vector_Similarity_Design.md
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
|
|
18
|
+
from clm.layer0.model.card import MCard
|
|
19
|
+
from clm.layer3.rag.config import DEFAULT_RAG_CONFIG, RAGConfig
|
|
20
|
+
from clm.layer3.rag.vector import (
|
|
21
|
+
HandleVectorStore,
|
|
22
|
+
VersionSimilarityResult,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
# Default store instance (lazy-initialized)
|
|
28
|
+
_default_store: HandleVectorStore | None = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_store(
|
|
32
|
+
db_path: str = None, config: RAGConfig = None, reinitialize: bool = False
|
|
33
|
+
) -> HandleVectorStore:
|
|
34
|
+
"""
|
|
35
|
+
Get the default HandleVectorStore instance.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
db_path: Path to SQLite database (None = in-memory)
|
|
39
|
+
config: RAG configuration
|
|
40
|
+
reinitialize: Force re-initialization of the store
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
HandleVectorStore instance
|
|
44
|
+
"""
|
|
45
|
+
global _default_store
|
|
46
|
+
|
|
47
|
+
if _default_store is None or reinitialize:
|
|
48
|
+
_default_store = HandleVectorStore(
|
|
49
|
+
db_path=db_path, config=config or DEFAULT_RAG_CONFIG
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
return _default_store
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
56
|
+
# Core API Functions
|
|
57
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def link_mcard_to_handle(
|
|
61
|
+
mcard: MCard, handle: str, is_current: bool = True, store: HandleVectorStore = None
|
|
62
|
+
) -> bool:
|
|
63
|
+
"""
|
|
64
|
+
Link an MCard to a handle with semantic embedding.
|
|
65
|
+
|
|
66
|
+
This function:
|
|
67
|
+
1. Indexes the MCard content with vector embeddings
|
|
68
|
+
2. Associates the MCard with the specified handle
|
|
69
|
+
3. Computes semantic delta from previous version (if exists)
|
|
70
|
+
4. Classifies the upgrade type based on similarity
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
mcard: MCard to link
|
|
74
|
+
handle: Handle name (stable identifier)
|
|
75
|
+
is_current: Whether this becomes the current version
|
|
76
|
+
store: Optional custom HandleVectorStore instance
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
True if successful, False if indexing failed
|
|
80
|
+
|
|
81
|
+
Example:
|
|
82
|
+
>>> from clm.layer0.model.card import MCard
|
|
83
|
+
>>> doc = MCard(content="Introduction to Machine Learning...")
|
|
84
|
+
>>> link_mcard_to_handle(doc, "ml_intro")
|
|
85
|
+
True
|
|
86
|
+
"""
|
|
87
|
+
store = store or get_store()
|
|
88
|
+
indexed = store.index_with_handle(mcard, handle, is_current=is_current)
|
|
89
|
+
return indexed > 0
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def get_handle_version_history(
|
|
93
|
+
handle: str, store: HandleVectorStore = None
|
|
94
|
+
) -> list[dict]:
|
|
95
|
+
"""
|
|
96
|
+
Get version history for a handle with semantic info.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
handle: Handle name to query
|
|
100
|
+
store: Optional custom HandleVectorStore instance
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
List of version dicts with hash, order, timestamps,
|
|
104
|
+
embedding status, and upgrade type
|
|
105
|
+
|
|
106
|
+
Example:
|
|
107
|
+
>>> history = get_handle_version_history("ml_intro")
|
|
108
|
+
>>> for v in history:
|
|
109
|
+
... print(f"v{v['version_order']}: {v['hash'][:8]} ({v['upgrade_type']})")
|
|
110
|
+
"""
|
|
111
|
+
store = store or get_store()
|
|
112
|
+
versions = store.get_handle_versions(handle)
|
|
113
|
+
|
|
114
|
+
return [
|
|
115
|
+
{
|
|
116
|
+
"hash": v.hash,
|
|
117
|
+
"version_order": v.version_order,
|
|
118
|
+
"is_current": v.is_current,
|
|
119
|
+
"created_at": v.created_at,
|
|
120
|
+
"has_embedding": v.has_embedding,
|
|
121
|
+
"parent_hash": v.parent_hash,
|
|
122
|
+
"semantic_delta": v.semantic_delta,
|
|
123
|
+
"upgrade_type": v.upgrade_type,
|
|
124
|
+
}
|
|
125
|
+
for v in versions
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def compare_versions_by_similarity(
|
|
130
|
+
handle: str,
|
|
131
|
+
reference_hash: str = None,
|
|
132
|
+
metric: str = "cosine",
|
|
133
|
+
store: HandleVectorStore = None,
|
|
134
|
+
) -> list[VersionSimilarityResult]:
|
|
135
|
+
"""
|
|
136
|
+
Compare all versions of a handle by semantic similarity.
|
|
137
|
+
|
|
138
|
+
This is useful for:
|
|
139
|
+
- Understanding how content has evolved
|
|
140
|
+
- Finding which version is most similar to current
|
|
141
|
+
- Identifying major semantic drift points
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
handle: Handle name
|
|
145
|
+
reference_hash: Compare to this hash (default: current version)
|
|
146
|
+
metric: 'cosine' (higher = more similar) or 'euclidean' (lower = closer)
|
|
147
|
+
store: Optional custom HandleVectorStore instance
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Versions sorted by similarity to reference
|
|
151
|
+
|
|
152
|
+
Example:
|
|
153
|
+
>>> results = compare_versions_by_similarity("ml_intro")
|
|
154
|
+
>>> for r in results:
|
|
155
|
+
... print(f"v{r.version_order}: sim={r.similarity_to_current:.4f}")
|
|
156
|
+
"""
|
|
157
|
+
store = store or get_store()
|
|
158
|
+
return store.get_versions_by_similarity(
|
|
159
|
+
handle=handle, reference_hash=reference_hash, metric=metric
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def search_within_handle(
|
|
164
|
+
handle: str, query: str, k: int = 10, store: HandleVectorStore = None
|
|
165
|
+
) -> list[VersionSimilarityResult]:
|
|
166
|
+
"""
|
|
167
|
+
Search semantically within a handle's version history.
|
|
168
|
+
|
|
169
|
+
Useful for finding which version of a document best matches
|
|
170
|
+
a specific concept, topic, or query.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
handle: Handle name to filter by
|
|
174
|
+
query: Search query text
|
|
175
|
+
k: Number of results to return
|
|
176
|
+
store: Optional custom HandleVectorStore instance
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Matching versions sorted by query similarity
|
|
180
|
+
|
|
181
|
+
Example:
|
|
182
|
+
>>> results = search_within_handle("ml_intro", "neural networks")
|
|
183
|
+
>>> for r in results:
|
|
184
|
+
... print(f"v{r.version_order}: query_sim={r.similarity_to_query:.4f}")
|
|
185
|
+
"""
|
|
186
|
+
store = store or get_store()
|
|
187
|
+
return store.search_handle_versions(handle=handle, query=query, k=k)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def get_version_distances(
|
|
191
|
+
handle: str, cache: bool = True, store: HandleVectorStore = None
|
|
192
|
+
) -> dict[tuple, float]:
|
|
193
|
+
"""
|
|
194
|
+
Compute pairwise semantic distances between all versions.
|
|
195
|
+
|
|
196
|
+
Returns a dictionary mapping (hash_a, hash_b) tuples to
|
|
197
|
+
their cosine similarity scores.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
handle: Handle name
|
|
201
|
+
cache: Whether to cache computed similarities
|
|
202
|
+
store: Optional custom HandleVectorStore instance
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
Dict mapping (hash_a, hash_b) to cosine similarity [-1, 1]
|
|
206
|
+
|
|
207
|
+
Example:
|
|
208
|
+
>>> distances = get_version_distances("ml_intro")
|
|
209
|
+
>>> for (h1, h2), sim in distances.items():
|
|
210
|
+
... print(f"{h1[:8]} <-> {h2[:8]}: {sim:.4f}")
|
|
211
|
+
"""
|
|
212
|
+
store = store or get_store()
|
|
213
|
+
return store.compute_version_distances(handle=handle, cache=cache)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def find_most_similar_version(
|
|
217
|
+
handle: str, query: str, store: HandleVectorStore = None
|
|
218
|
+
) -> VersionSimilarityResult | None:
|
|
219
|
+
"""
|
|
220
|
+
Find the version most similar to a query.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
handle: Handle name
|
|
224
|
+
query: Search query text
|
|
225
|
+
store: Optional custom HandleVectorStore instance
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Most similar version, or None if no versions exist
|
|
229
|
+
"""
|
|
230
|
+
results = search_within_handle(handle, query, k=1, store=store)
|
|
231
|
+
return results[0] if results else None
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def get_semantic_evolution(handle: str, store: HandleVectorStore = None) -> list[dict]:
|
|
235
|
+
"""
|
|
236
|
+
Get the semantic evolution of a handle's versions.
|
|
237
|
+
|
|
238
|
+
Returns version history annotated with semantic deltas,
|
|
239
|
+
showing how content has evolved over time.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
handle: Handle name
|
|
243
|
+
store: Optional custom HandleVectorStore instance
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
List of version dicts with evolution info
|
|
247
|
+
"""
|
|
248
|
+
store = store or get_store()
|
|
249
|
+
versions = store.get_handle_versions(handle)
|
|
250
|
+
|
|
251
|
+
evolution = []
|
|
252
|
+
for v in versions:
|
|
253
|
+
entry = {
|
|
254
|
+
"hash": v.hash,
|
|
255
|
+
"version": v.version_order,
|
|
256
|
+
"is_current": v.is_current,
|
|
257
|
+
"created_at": v.created_at,
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if v.parent_hash:
|
|
261
|
+
entry["parent_hash"] = v.parent_hash
|
|
262
|
+
entry["semantic_delta"] = v.semantic_delta
|
|
263
|
+
entry["upgrade_type"] = v.upgrade_type
|
|
264
|
+
|
|
265
|
+
# Interpret the delta
|
|
266
|
+
if v.semantic_delta:
|
|
267
|
+
if v.semantic_delta >= 0.95:
|
|
268
|
+
entry["interpretation"] = "Nearly identical to parent"
|
|
269
|
+
elif v.semantic_delta >= 0.85:
|
|
270
|
+
entry["interpretation"] = "Minor changes from parent"
|
|
271
|
+
elif v.semantic_delta >= 0.70:
|
|
272
|
+
entry["interpretation"] = "Significant changes from parent"
|
|
273
|
+
else:
|
|
274
|
+
entry["interpretation"] = "Major semantic shift from parent"
|
|
275
|
+
else:
|
|
276
|
+
entry["interpretation"] = "Initial version (no parent)"
|
|
277
|
+
|
|
278
|
+
evolution.append(entry)
|
|
279
|
+
|
|
280
|
+
return evolution
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
284
|
+
# Utility Functions
|
|
285
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def list_handles(store: HandleVectorStore = None) -> list[str]:
|
|
289
|
+
"""
|
|
290
|
+
List all handles with indexed versions.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
store: Optional custom HandleVectorStore instance
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
List of handle names
|
|
297
|
+
"""
|
|
298
|
+
store = store or get_store()
|
|
299
|
+
return store.list_handles()
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def get_store_info(store: HandleVectorStore = None) -> dict:
|
|
303
|
+
"""
|
|
304
|
+
Get information about the vector store.
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
store: Optional custom HandleVectorStore instance
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
Dict with store statistics
|
|
311
|
+
"""
|
|
312
|
+
store = store or get_store()
|
|
313
|
+
return store.get_info()
|