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,290 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Vision Embedding Provider
|
|
3
|
+
|
|
4
|
+
Multimodal embedding provider that uses vision models to describe images,
|
|
5
|
+
then embeds the descriptions for vector search.
|
|
6
|
+
|
|
7
|
+
This approach is recommended for image RAG since Ollama's embed API
|
|
8
|
+
doesn't directly support image inputs.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import base64
|
|
12
|
+
import json
|
|
13
|
+
import logging
|
|
14
|
+
import urllib.request
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from clm.cross_cutting.config.config_constants import (
|
|
19
|
+
RAG_VISION_DESCRIBE_TIMEOUT_SECS,
|
|
20
|
+
RAG_VISION_VALIDATE_TIMEOUT_SECS,
|
|
21
|
+
)
|
|
22
|
+
from clm.cross_cutting.utils.url_safety import safe_urlopen
|
|
23
|
+
|
|
24
|
+
from .base import EmbeddingProvider
|
|
25
|
+
from .ollama import OllamaEmbeddingProvider
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
31
|
+
# Vision Models Configuration
|
|
32
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
VISION_MODELS = {
|
|
35
|
+
"moondream": {
|
|
36
|
+
"description": "Moondream - Tiny, high-performance vision language model",
|
|
37
|
+
"size": "1.7GB",
|
|
38
|
+
},
|
|
39
|
+
"llama3.2-vision": {
|
|
40
|
+
"description": "Llama 3.2 Vision - 11B multimodal model",
|
|
41
|
+
"size": "7.9GB",
|
|
42
|
+
},
|
|
43
|
+
"llava": {
|
|
44
|
+
"description": "LLaVA - Large Language and Vision Assistant",
|
|
45
|
+
"size": "4.7GB",
|
|
46
|
+
},
|
|
47
|
+
"minicpm-v": {
|
|
48
|
+
"description": "MiniCPM-V - Efficient vision-language model",
|
|
49
|
+
"size": "5.6GB",
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
DEFAULT_VISION_MODEL = "moondream"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
57
|
+
# Vision Embedding Provider
|
|
58
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class VisionEmbeddingProvider(EmbeddingProvider):
|
|
62
|
+
"""
|
|
63
|
+
Multimodal embedding provider for images.
|
|
64
|
+
|
|
65
|
+
Uses a two-stage approach:
|
|
66
|
+
1. Vision model generates a text description of the image
|
|
67
|
+
2. Text embedding model converts description to vector
|
|
68
|
+
|
|
69
|
+
This enables semantic search over images using existing vector infrastructure.
|
|
70
|
+
|
|
71
|
+
Usage:
|
|
72
|
+
from clm.layer3.rag.embeddings import VisionEmbeddingProvider
|
|
73
|
+
|
|
74
|
+
provider = VisionEmbeddingProvider()
|
|
75
|
+
|
|
76
|
+
# Embed an image
|
|
77
|
+
embedding = provider.embed_image("/path/to/image.jpg")
|
|
78
|
+
|
|
79
|
+
# Or with bytes
|
|
80
|
+
with open("image.png", "rb") as f:
|
|
81
|
+
embedding = provider.embed_image_bytes(f.read())
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def __init__(
|
|
85
|
+
self,
|
|
86
|
+
vision_model: str = DEFAULT_VISION_MODEL,
|
|
87
|
+
embedding_model: str = "nomic-embed-text",
|
|
88
|
+
base_url: str = "http://localhost:11434",
|
|
89
|
+
description_prompt: str = None,
|
|
90
|
+
):
|
|
91
|
+
"""
|
|
92
|
+
Initialize vision embedding provider.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
vision_model: Ollama vision model for describing images
|
|
96
|
+
embedding_model: Text embedding model
|
|
97
|
+
base_url: Ollama API base URL
|
|
98
|
+
description_prompt: Custom prompt for image description
|
|
99
|
+
"""
|
|
100
|
+
self.vision_model = vision_model
|
|
101
|
+
self.base_url = base_url.rstrip("/")
|
|
102
|
+
|
|
103
|
+
# Text embedder for converting descriptions to vectors
|
|
104
|
+
self.text_embedder = OllamaEmbeddingProvider(
|
|
105
|
+
model=embedding_model, base_url=base_url
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Prompt for image description
|
|
109
|
+
self.description_prompt = (
|
|
110
|
+
description_prompt
|
|
111
|
+
or """Describe this image in detail for semantic search.
|
|
112
|
+
Include:
|
|
113
|
+
- Main subject and objects visible
|
|
114
|
+
- Colors, textures, and visual elements
|
|
115
|
+
- Any text visible in the image
|
|
116
|
+
- Context, setting, or environment
|
|
117
|
+
- Actions or relationships between elements
|
|
118
|
+
|
|
119
|
+
Be comprehensive but concise. Focus on searchable details."""
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def model_name(self) -> str:
|
|
124
|
+
"""Return combined model identifier."""
|
|
125
|
+
return f"vision:{self.vision_model}+{self.text_embedder.model_name}"
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def dimensions(self) -> int:
|
|
129
|
+
"""Return embedding dimensions (from text embedder)."""
|
|
130
|
+
return self.text_embedder.dimensions
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def provider_name(self) -> str:
|
|
134
|
+
return "ollama-vision"
|
|
135
|
+
|
|
136
|
+
def describe_image(self, image_data: str | bytes | Path, prompt: str = None) -> str:
|
|
137
|
+
"""
|
|
138
|
+
Generate text description of an image.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
image_data: Image as file path, bytes, or base64 string
|
|
142
|
+
prompt: Optional custom prompt
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Text description of the image
|
|
146
|
+
"""
|
|
147
|
+
# Convert image to base64
|
|
148
|
+
if isinstance(image_data, str | Path):
|
|
149
|
+
path = Path(image_data)
|
|
150
|
+
if path.exists():
|
|
151
|
+
with open(path, "rb") as f:
|
|
152
|
+
image_bytes = f.read()
|
|
153
|
+
image_b64 = base64.b64encode(image_bytes).decode("utf-8")
|
|
154
|
+
else:
|
|
155
|
+
# Assume it's already base64
|
|
156
|
+
image_b64 = str(image_data)
|
|
157
|
+
elif isinstance(image_data, bytes):
|
|
158
|
+
image_b64 = base64.b64encode(image_data).decode("utf-8")
|
|
159
|
+
else:
|
|
160
|
+
raise ValueError(f"Unsupported image data type: {type(image_data)}")
|
|
161
|
+
|
|
162
|
+
# Call vision model
|
|
163
|
+
url = f"{self.base_url}/api/generate"
|
|
164
|
+
|
|
165
|
+
payload = {
|
|
166
|
+
"model": self.vision_model,
|
|
167
|
+
"prompt": prompt or self.description_prompt,
|
|
168
|
+
"images": [image_b64],
|
|
169
|
+
"stream": False,
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
data = json.dumps(payload).encode("utf-8")
|
|
173
|
+
req = urllib.request.Request(
|
|
174
|
+
url, data=data, headers={"Content-Type": "application/json"}, method="POST"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
with safe_urlopen(
|
|
179
|
+
req, timeout=RAG_VISION_DESCRIBE_TIMEOUT_SECS
|
|
180
|
+
) as response:
|
|
181
|
+
result = json.loads(response.read().decode("utf-8"))
|
|
182
|
+
return result.get("response", "")
|
|
183
|
+
except Exception as e:
|
|
184
|
+
logger.error(f"Vision model call failed: {e}", exc_info=True)
|
|
185
|
+
raise RuntimeError(f"Failed to describe image: {e}") from e
|
|
186
|
+
|
|
187
|
+
def embed_image(
|
|
188
|
+
self, image_data: str | bytes | Path, prompt: str = None
|
|
189
|
+
) -> list[float]:
|
|
190
|
+
"""
|
|
191
|
+
Generate embedding for an image.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
image_data: Image as file path, bytes, or base64 string
|
|
195
|
+
prompt: Optional custom prompt for description
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Embedding vector
|
|
199
|
+
"""
|
|
200
|
+
# Get description
|
|
201
|
+
description = self.describe_image(image_data, prompt)
|
|
202
|
+
|
|
203
|
+
if not description:
|
|
204
|
+
raise ValueError("Vision model returned empty description")
|
|
205
|
+
|
|
206
|
+
logger.debug(f"Image description: {description[:100]}...")
|
|
207
|
+
|
|
208
|
+
# Embed description
|
|
209
|
+
return self.text_embedder.embed(description)
|
|
210
|
+
|
|
211
|
+
def embed_image_with_description(
|
|
212
|
+
self, image_data: str | bytes | Path, prompt: str = None
|
|
213
|
+
) -> tuple[list[float], str]:
|
|
214
|
+
"""
|
|
215
|
+
Generate embedding and return description.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
image_data: Image as file path, bytes, or base64
|
|
219
|
+
prompt: Optional custom prompt
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
Tuple of (embedding, description)
|
|
223
|
+
"""
|
|
224
|
+
description = self.describe_image(image_data, prompt)
|
|
225
|
+
embedding = self.text_embedder.embed(description)
|
|
226
|
+
return embedding, description
|
|
227
|
+
|
|
228
|
+
# Required EmbeddingProvider methods
|
|
229
|
+
|
|
230
|
+
def embed(self, text: str) -> list[float]:
|
|
231
|
+
"""Embed text (delegates to text embedder)."""
|
|
232
|
+
return self.text_embedder.embed(text)
|
|
233
|
+
|
|
234
|
+
def embed_batch(self, texts: list[str]) -> list[list[float]]:
|
|
235
|
+
"""Embed multiple texts."""
|
|
236
|
+
return self.text_embedder.embed_batch(texts)
|
|
237
|
+
|
|
238
|
+
def embed_images(self, images: list[str | bytes | Path]) -> list[list[float]]:
|
|
239
|
+
"""
|
|
240
|
+
Embed multiple images.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
images: List of image paths, bytes, or base64 strings
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
List of embedding vectors
|
|
247
|
+
"""
|
|
248
|
+
embeddings = []
|
|
249
|
+
for img in images:
|
|
250
|
+
try:
|
|
251
|
+
embeddings.append(self.embed_image(img))
|
|
252
|
+
except Exception as e:
|
|
253
|
+
logger.error(f"Failed to embed image: {e}", exc_info=True)
|
|
254
|
+
# Return zero vector on failure
|
|
255
|
+
embeddings.append([0.0] * self.dimensions)
|
|
256
|
+
return embeddings
|
|
257
|
+
|
|
258
|
+
def validate_connection(self) -> bool:
|
|
259
|
+
"""Validate both vision and embedding models are available."""
|
|
260
|
+
# Check text embedder
|
|
261
|
+
if not self.text_embedder.validate_connection():
|
|
262
|
+
return False
|
|
263
|
+
|
|
264
|
+
# Check vision model
|
|
265
|
+
try:
|
|
266
|
+
url = f"{self.base_url}/api/show"
|
|
267
|
+
payload = json.dumps({"name": self.vision_model}).encode("utf-8")
|
|
268
|
+
req = urllib.request.Request(
|
|
269
|
+
url,
|
|
270
|
+
data=payload,
|
|
271
|
+
headers={"Content-Type": "application/json"},
|
|
272
|
+
method="POST",
|
|
273
|
+
)
|
|
274
|
+
with safe_urlopen(
|
|
275
|
+
req, timeout=RAG_VISION_VALIDATE_TIMEOUT_SECS
|
|
276
|
+
) as response:
|
|
277
|
+
return response.status == 200
|
|
278
|
+
except Exception as e:
|
|
279
|
+
logger.warning(f"Vision model check failed: {e}")
|
|
280
|
+
return False
|
|
281
|
+
|
|
282
|
+
def get_info(self) -> dict[str, Any]:
|
|
283
|
+
"""Get provider information."""
|
|
284
|
+
return {
|
|
285
|
+
"provider": self.provider_name,
|
|
286
|
+
"vision_model": self.vision_model,
|
|
287
|
+
"embedding_model": self.text_embedder.model_name,
|
|
288
|
+
"dimensions": self.dimensions,
|
|
289
|
+
"vision_models_available": list(VISION_MODELS.keys()),
|
|
290
|
+
}
|
clm/layer3/rag/engine.py
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCard RAG Engine
|
|
3
|
+
|
|
4
|
+
Main interface for Retrieval-Augmented Generation over MCard collections.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from clm.layer0.model.card import MCard
|
|
12
|
+
from clm.layer2.card_collection import CardCollection
|
|
13
|
+
|
|
14
|
+
from .config import DEFAULT_RAG_CONFIG, RAGConfig
|
|
15
|
+
from .embeddings import OllamaEmbeddingProvider
|
|
16
|
+
from .vector import MCardVectorStore, VectorSearchResult
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
+
# RAG Response
|
|
23
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class RAGResponse:
|
|
28
|
+
"""Response from a RAG query."""
|
|
29
|
+
|
|
30
|
+
answer: str
|
|
31
|
+
sources: list[str] # Source MCard hashes
|
|
32
|
+
source_chunks: list[str] # Relevant text chunks
|
|
33
|
+
confidence: float
|
|
34
|
+
metadata: dict[str, Any] | None = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
38
|
+
# RAG Engine
|
|
39
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class MCardRAGEngine:
|
|
43
|
+
"""
|
|
44
|
+
Retrieval-Augmented Generation engine for MCard collections.
|
|
45
|
+
|
|
46
|
+
Combines semantic vector search with LLM generation to provide
|
|
47
|
+
context-aware answers grounded in MCard content.
|
|
48
|
+
|
|
49
|
+
Usage:
|
|
50
|
+
from clm.layer2.card_collection import get_default_collection
|
|
51
|
+
default_collection = get_default_collection()
|
|
52
|
+
from clm.layer3.rag import MCardRAGEngine
|
|
53
|
+
|
|
54
|
+
# Initialize
|
|
55
|
+
rag = MCardRAGEngine(default_collection)
|
|
56
|
+
|
|
57
|
+
# Index content
|
|
58
|
+
rag.index_all()
|
|
59
|
+
|
|
60
|
+
# Query
|
|
61
|
+
response = rag.query("What is the Cubical Logic Model?")
|
|
62
|
+
print(response.answer)
|
|
63
|
+
print(f"Sources: {response.sources}")
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
collection: CardCollection = None,
|
|
69
|
+
config: RAGConfig = None,
|
|
70
|
+
vector_db_path: str = None,
|
|
71
|
+
):
|
|
72
|
+
"""
|
|
73
|
+
Initialize RAG engine.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
collection: MCard collection for storage (uses default if None)
|
|
77
|
+
config: RAG configuration
|
|
78
|
+
vector_db_path: Path to vector database (None = in-memory)
|
|
79
|
+
"""
|
|
80
|
+
from clm.layer2.card_collection import get_default_collection
|
|
81
|
+
_default_collection = get_default_collection()
|
|
82
|
+
|
|
83
|
+
self.collection = collection or _default_collection
|
|
84
|
+
self.config = config or DEFAULT_RAG_CONFIG
|
|
85
|
+
|
|
86
|
+
# Initialize embedding provider
|
|
87
|
+
self.embedder = OllamaEmbeddingProvider(
|
|
88
|
+
model=self.config.embedding_model, base_url=self.config.ollama_base_url
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Initialize vector store
|
|
92
|
+
self.vector_store = MCardVectorStore(
|
|
93
|
+
db_path=vector_db_path or self.config.vector_db_path,
|
|
94
|
+
config=self.config,
|
|
95
|
+
embedding_provider=self.embedder,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# LLM for generation (lazy loaded)
|
|
99
|
+
self._llm = None
|
|
100
|
+
|
|
101
|
+
logger.info(f"Initialized RAG engine with model {self.config.embedding_model}")
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def llm(self):
|
|
105
|
+
"""Lazy-load LLM runtime."""
|
|
106
|
+
if self._llm is None:
|
|
107
|
+
from clm.layer3.agency import LLMRuntime
|
|
108
|
+
|
|
109
|
+
self._llm = LLMRuntime()
|
|
110
|
+
return self._llm
|
|
111
|
+
|
|
112
|
+
def index(self, mcard: MCard) -> bool:
|
|
113
|
+
"""
|
|
114
|
+
Index a single MCard for semantic search.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
mcard: MCard to index
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
True if indexed successfully
|
|
121
|
+
"""
|
|
122
|
+
try:
|
|
123
|
+
count = self.vector_store.index(mcard)
|
|
124
|
+
return count > 0
|
|
125
|
+
except Exception as e:
|
|
126
|
+
logger.error(f"Failed to index MCard {mcard.hash[:8]}: {e}", exc_info=True)
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
def index_all(self, force: bool = False) -> dict[str, int]:
|
|
130
|
+
"""
|
|
131
|
+
Index all MCards in the collection.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
force: Re-index even if already indexed
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Dictionary with 'indexed', 'skipped', 'failed' counts
|
|
138
|
+
"""
|
|
139
|
+
stats = {"indexed": 0, "skipped": 0, "failed": 0}
|
|
140
|
+
|
|
141
|
+
# Get all cards from collection
|
|
142
|
+
page = self.collection.get_all_cards()
|
|
143
|
+
|
|
144
|
+
for mcard in page.items:
|
|
145
|
+
if not force and self.vector_store.is_indexed(mcard.hash):
|
|
146
|
+
stats["skipped"] += 1
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
if self.index(mcard):
|
|
150
|
+
stats["indexed"] += 1
|
|
151
|
+
else:
|
|
152
|
+
stats["failed"] += 1
|
|
153
|
+
|
|
154
|
+
logger.info(f"Indexing complete: {stats}")
|
|
155
|
+
return stats
|
|
156
|
+
|
|
157
|
+
def search(
|
|
158
|
+
self, query: str, k: int = None, hybrid: bool = True
|
|
159
|
+
) -> list[VectorSearchResult]:
|
|
160
|
+
"""
|
|
161
|
+
Search for relevant MCards.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
query: Search query
|
|
165
|
+
k: Number of results
|
|
166
|
+
hybrid: Use hybrid (vector + FTS) search
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
List of search results with scores
|
|
170
|
+
"""
|
|
171
|
+
k = k or self.config.top_k
|
|
172
|
+
|
|
173
|
+
if hybrid and self.config.enable_hybrid_search:
|
|
174
|
+
return self.vector_store.hybrid_search(query, k)
|
|
175
|
+
else:
|
|
176
|
+
return self.vector_store.search(query, k)
|
|
177
|
+
|
|
178
|
+
def query(
|
|
179
|
+
self, question: str, k: int = None, system_prompt: str = None, model: str = None
|
|
180
|
+
) -> RAGResponse:
|
|
181
|
+
"""
|
|
182
|
+
Query with RAG: search for context and generate answer.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
question: User question
|
|
186
|
+
k: Number of context chunks
|
|
187
|
+
system_prompt: Custom system prompt
|
|
188
|
+
model: LLM model to use
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
RAGResponse with answer and sources
|
|
192
|
+
"""
|
|
193
|
+
k = k or self.config.top_k
|
|
194
|
+
|
|
195
|
+
# Search for relevant context
|
|
196
|
+
results = self.search(question, k)
|
|
197
|
+
|
|
198
|
+
if not results:
|
|
199
|
+
return RAGResponse(
|
|
200
|
+
answer="I couldn't find relevant information in the knowledge base.",
|
|
201
|
+
sources=[],
|
|
202
|
+
source_chunks=[],
|
|
203
|
+
confidence=0.0,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Assemble context
|
|
207
|
+
context_parts = []
|
|
208
|
+
for i, result in enumerate(results):
|
|
209
|
+
chunk = result.chunk_text or f"[Content from {result.hash[:8]}]"
|
|
210
|
+
context_parts.append(f"[{i + 1}] {chunk}")
|
|
211
|
+
|
|
212
|
+
context = "\n\n".join(context_parts)
|
|
213
|
+
|
|
214
|
+
# Generate answer
|
|
215
|
+
default_system = """You are a helpful assistant that answers questions based on the provided context.
|
|
216
|
+
Use the context to provide accurate, relevant answers.
|
|
217
|
+
If the context doesn't contain enough information to fully answer the question, say so.
|
|
218
|
+
Always cite the source numbers [1], [2], etc. when using information from the context."""
|
|
219
|
+
|
|
220
|
+
prompt = f"""Context:
|
|
221
|
+
{context}
|
|
222
|
+
|
|
223
|
+
Question: {question}
|
|
224
|
+
|
|
225
|
+
Please provide a comprehensive answer based on the context above."""
|
|
226
|
+
|
|
227
|
+
try:
|
|
228
|
+
from clm.cross_cutting.config.config_constants import DEFAULT_FALLBACK_MODEL
|
|
229
|
+
from clm.layer3.agency import chat_monad
|
|
230
|
+
|
|
231
|
+
response = chat_monad(
|
|
232
|
+
prompt=prompt,
|
|
233
|
+
system_prompt=system_prompt or default_system,
|
|
234
|
+
model=model or DEFAULT_FALLBACK_MODEL,
|
|
235
|
+
temperature=0.3,
|
|
236
|
+
max_tokens=1000,
|
|
237
|
+
).unsafe_run()
|
|
238
|
+
|
|
239
|
+
if response.is_right():
|
|
240
|
+
answer = response.value.get("content", str(response.value))
|
|
241
|
+
else:
|
|
242
|
+
answer = f"Error generating response: {response.value}"
|
|
243
|
+
|
|
244
|
+
except Exception as e:
|
|
245
|
+
logger.error(f"LLM generation failed: {e}", exc_info=True)
|
|
246
|
+
answer = f"Error: {e}"
|
|
247
|
+
|
|
248
|
+
# Calculate confidence based on top scores
|
|
249
|
+
avg_score = sum(r.score for r in results) / len(results) if results else 0
|
|
250
|
+
|
|
251
|
+
from clm.cross_cutting.config.config_constants import DEFAULT_FALLBACK_MODEL
|
|
252
|
+
|
|
253
|
+
return RAGResponse(
|
|
254
|
+
answer=answer,
|
|
255
|
+
sources=[r.hash for r in results],
|
|
256
|
+
source_chunks=[r.chunk_text or "" for r in results],
|
|
257
|
+
confidence=avg_score,
|
|
258
|
+
metadata={
|
|
259
|
+
"model": model or DEFAULT_FALLBACK_MODEL,
|
|
260
|
+
"num_sources": len(results),
|
|
261
|
+
"scores": [r.score for r in results],
|
|
262
|
+
},
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
def get_info(self) -> dict[str, Any]:
|
|
266
|
+
"""Get RAG engine information."""
|
|
267
|
+
return {
|
|
268
|
+
"config": {
|
|
269
|
+
"embedding_model": self.config.embedding_model,
|
|
270
|
+
"top_k": self.config.top_k,
|
|
271
|
+
"chunk_size": self.config.chunk_size,
|
|
272
|
+
"hybrid_search": self.config.enable_hybrid_search,
|
|
273
|
+
},
|
|
274
|
+
"vector_store": self.vector_store.get_info(),
|
|
275
|
+
"embedder_available": self.embedder.validate_connection(),
|
|
276
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Graph Package
|
|
3
|
+
|
|
4
|
+
Knowledge graph storage and extraction for GraphRAG.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .engine import GraphRAGEngine, GraphRAGResponse
|
|
8
|
+
from .extractor import Entity, ExtractionResult, GraphExtractor, Relationship
|
|
9
|
+
from .schema import GRAPH_SCHEMAS
|
|
10
|
+
from .store import GraphStore, store_extraction_result
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"GRAPH_SCHEMAS",
|
|
14
|
+
"GraphExtractor",
|
|
15
|
+
"Entity",
|
|
16
|
+
"Relationship",
|
|
17
|
+
"ExtractionResult",
|
|
18
|
+
"GraphStore",
|
|
19
|
+
"store_extraction_result",
|
|
20
|
+
"GraphRAGEngine",
|
|
21
|
+
"GraphRAGResponse",
|
|
22
|
+
]
|