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.
Files changed (333) hide show
  1. clm/__init__.py +230 -0
  2. clm/__main__.py +25 -0
  3. clm/cross_cutting/__init__.py +50 -0
  4. clm/cross_cutting/_test_runner.py +36 -0
  5. clm/cross_cutting/async_utils.py +25 -0
  6. clm/cross_cutting/cel_builder.py +123 -0
  7. clm/cross_cutting/cel_converter.py +697 -0
  8. clm/cross_cutting/cel_engine.py +252 -0
  9. clm/cross_cutting/cel_ops.py +72 -0
  10. clm/cross_cutting/cel_ops_db.py +157 -0
  11. clm/cross_cutting/cel_ops_files.py +99 -0
  12. clm/cross_cutting/cel_ops_state.py +71 -0
  13. clm/cross_cutting/cel_ops_strings.py +64 -0
  14. clm/cross_cutting/cel_resolver.py +63 -0
  15. clm/cross_cutting/cel_slices.py +56 -0
  16. clm/cross_cutting/cel_store.py +330 -0
  17. clm/cross_cutting/clm_logger.py +140 -0
  18. clm/cross_cutting/config/__init__.py +155 -0
  19. clm/cross_cutting/config/config_constants.py +392 -0
  20. clm/cross_cutting/config/env_parameters.py +90 -0
  21. clm/cross_cutting/config/logging.py +378 -0
  22. clm/cross_cutting/config/settings.py +504 -0
  23. clm/cross_cutting/context.py +5 -0
  24. clm/cross_cutting/domain_models.py +55 -0
  25. clm/cross_cutting/domain_types.py +61 -0
  26. clm/cross_cutting/effects.py +67 -0
  27. clm/cross_cutting/errors.py +153 -0
  28. clm/cross_cutting/governance.py +331 -0
  29. clm/cross_cutting/guards.py +41 -0
  30. clm/cross_cutting/mcard.py +321 -0
  31. clm/cross_cutting/native.py +185 -0
  32. clm/cross_cutting/observability.py +289 -0
  33. clm/cross_cutting/pcard.py +434 -0
  34. clm/cross_cutting/registry.py +71 -0
  35. clm/cross_cutting/resources.py +84 -0
  36. clm/cross_cutting/secrets.py +44 -0
  37. clm/cross_cutting/subprocess_utils.py +118 -0
  38. clm/cross_cutting/telemetry.py +164 -0
  39. clm/cross_cutting/testing/__init__.py +8 -0
  40. clm/cross_cutting/testing/comparator.py +187 -0
  41. clm/cross_cutting/testing/datasets.py +87 -0
  42. clm/cross_cutting/timeutil.py +20 -0
  43. clm/cross_cutting/types/new_type_check.py +0 -0
  44. clm/cross_cutting/utils/__init__.py +1 -0
  45. clm/cross_cutting/utils/url_safety.py +50 -0
  46. clm/cross_cutting/vcard.py +560 -0
  47. clm/cross_cutting/writer.py +118 -0
  48. clm/layer0/README.md +50 -0
  49. clm/layer0/__init__.py +223 -0
  50. clm/layer0/algebra.py +60 -0
  51. clm/layer0/algebra_baldwin.py +133 -0
  52. clm/layer0/algebra_baldwin_generators.py +36 -0
  53. clm/layer0/algebra_equivalence.py +262 -0
  54. clm/layer0/algebra_ingest.py +306 -0
  55. clm/layer0/algebra_manifest_io.py +156 -0
  56. clm/layer0/assembler.py +481 -0
  57. clm/layer0/cel/__init__.py +23 -0
  58. clm/layer0/cel_eval.py +491 -0
  59. clm/layer0/codec.py +106 -0
  60. clm/layer0/context.py +122 -0
  61. clm/layer0/db.py +160 -0
  62. clm/layer0/exceptions.py +73 -0
  63. clm/layer0/fibration.py +106 -0
  64. clm/layer0/gates.py +50 -0
  65. clm/layer0/hash.py +80 -0
  66. clm/layer0/loader.py +826 -0
  67. clm/layer0/math_utils.py +163 -0
  68. clm/layer0/mcard.py +96 -0
  69. clm/layer0/model/__init__.py +0 -0
  70. clm/layer0/model/action.py +223 -0
  71. clm/layer0/model/card.py +280 -0
  72. clm/layer0/model/card_triad.py +427 -0
  73. clm/layer0/model/compatibility.py +60 -0
  74. clm/layer0/model/dictionary.py +46 -0
  75. clm/layer0/model/dots.py +331 -0
  76. clm/layer0/model/event_producer.py +88 -0
  77. clm/layer0/model/g_time.py +89 -0
  78. clm/layer0/model/handle.py +163 -0
  79. clm/layer0/model/hash/__init__.py +6 -0
  80. clm/layer0/model/hash/algorithms/__init__.py +3 -0
  81. clm/layer0/model/hash/algorithms/custom_hash.py +7 -0
  82. clm/layer0/model/hash/algorithms/local_sha256.py +178 -0
  83. clm/layer0/model/hash/constants.py +53 -0
  84. clm/layer0/model/hash/enums.py +60 -0
  85. clm/layer0/model/hash/validator.py +307 -0
  86. clm/layer0/model/interpreter.py +275 -0
  87. clm/layer0/model/pagination.py +45 -0
  88. clm/layer0/model/pcard.py +716 -0
  89. clm/layer0/model/schema.py +44 -0
  90. clm/layer0/model/utils/__init__.py +1 -0
  91. clm/layer0/model/utils/content_analyzer.py +152 -0
  92. clm/layer0/model/validators/__init__.py +1 -0
  93. clm/layer0/model/validators/base_validator.py +44 -0
  94. clm/layer0/model/validators/binary_validator.py +66 -0
  95. clm/layer0/model/validators/text_validator.py +82 -0
  96. clm/layer0/model/validators/validation_registry.py +49 -0
  97. clm/layer0/model/vcard.py +823 -0
  98. clm/layer0/model/vcard_ext/__init__.py +39 -0
  99. clm/layer0/model/vcard_ext/core.py +60 -0
  100. clm/layer0/model/vcard_ext/network.py +23 -0
  101. clm/layer0/model/vcard_ext/observability.py +63 -0
  102. clm/layer0/model/vcard_ext/storage.py +60 -0
  103. clm/layer0/model/vcard_ext/vendors.py +211 -0
  104. clm/layer0/model/vcard_sandwich.py +119 -0
  105. clm/layer0/model/vcard_vocabulary.py +463 -0
  106. clm/layer0/model/workflow.py +274 -0
  107. clm/layer0/narrative.py +431 -0
  108. clm/layer0/ontology/collection.py +125 -0
  109. clm/layer0/parser.py +429 -0
  110. clm/layer0/relations.py +73 -0
  111. clm/layer0/rule_evaluator.py +251 -0
  112. clm/layer0/schema/__init__.py +38 -0
  113. clm/layer0/schema/ddl.py +97 -0
  114. clm/layer0/templates/abstract.yaml +99 -0
  115. clm/layer0/templates/balanced.yaml +253 -0
  116. clm/layer0/templates/concrete.yaml +172 -0
  117. clm/layer0/type_registry.py +223 -0
  118. clm/layer0/types.py +204 -0
  119. clm/layer0/utils.py +886 -0
  120. clm/layer0/verifier_core.py +262 -0
  121. clm/layer1/__init__.py +31 -0
  122. clm/layer1/action_dispatcher.py +275 -0
  123. clm/layer1/adapter_registry.py +233 -0
  124. clm/layer1/algebra_baldwin_generators.py +192 -0
  125. clm/layer1/arrow_compiler.py +525 -0
  126. clm/layer1/batch_processor.py +152 -0
  127. clm/layer1/cel/__init__.py +27 -0
  128. clm/layer1/cel/builder.py +173 -0
  129. clm/layer1/engine.py +759 -0
  130. clm/layer1/engine_async.py +89 -0
  131. clm/layer1/eval_callback.py +49 -0
  132. clm/layer1/gate_composer.py +229 -0
  133. clm/layer1/hoare_sandwich.py +298 -0
  134. clm/layer1/identity_guard.py +348 -0
  135. clm/layer1/matrix.py +138 -0
  136. clm/layer1/neural_runtime.py +221 -0
  137. clm/layer1/operations/__init__.py +123 -0
  138. clm/layer1/operations/augment.py +156 -0
  139. clm/layer1/operations/base.py +109 -0
  140. clm/layer1/operations/builtins.py +338 -0
  141. clm/layer1/operations/dispatch.py +144 -0
  142. clm/layer1/operations/exclude.py +107 -0
  143. clm/layer1/operations/handle.py +197 -0
  144. clm/layer1/operations/identity.py +139 -0
  145. clm/layer1/operations/invert.py +66 -0
  146. clm/layer1/operations/loader.py +116 -0
  147. clm/layer1/operations/manipulators/__init__.py +23 -0
  148. clm/layer1/operations/manipulators/document.py +211 -0
  149. clm/layer1/operations/manipulators/media.py +134 -0
  150. clm/layer1/operations/manipulators/structured.py +159 -0
  151. clm/layer1/operations/manipulators/tabular.py +334 -0
  152. clm/layer1/operations/port.py +65 -0
  153. clm/layer1/operations/services.py +529 -0
  154. clm/layer1/operations/split.py +117 -0
  155. clm/layer1/operations/substitute.py +75 -0
  156. clm/layer1/petri.py +1052 -0
  157. clm/layer1/pocketflow_orchestrator.py +408 -0
  158. clm/layer1/pocketflow_scheduler.py +519 -0
  159. clm/layer1/protocols.py +197 -0
  160. clm/layer1/runtime_adapter.py +718 -0
  161. clm/layer1/runtimes/__init__.py +71 -0
  162. clm/layer1/runtimes/_generated_assertions.py +125 -0
  163. clm/layer1/runtimes/_generated_types.py +42 -0
  164. clm/layer1/runtimes/base.py +265 -0
  165. clm/layer1/runtimes/binary.py +101 -0
  166. clm/layer1/runtimes/factory.py +203 -0
  167. clm/layer1/runtimes/javascript.py +321 -0
  168. clm/layer1/runtimes/javascript_runtime.js +33 -0
  169. clm/layer1/runtimes/js_scripts/isomorphism.mjs +22 -0
  170. clm/layer1/runtimes/js_scripts/loader.mjs +52 -0
  171. clm/layer1/runtimes/lambda_calc.py +126 -0
  172. clm/layer1/runtimes/python.py +288 -0
  173. clm/layer1/runtimes/python_runtime.py +41 -0
  174. clm/layer1/runtimes/script.py +114 -0
  175. clm/layer1/sandbox.py +334 -0
  176. clm/layer1/savepoint_guard.py +136 -0
  177. clm/layer1/sparse_net.py +122 -0
  178. clm/layer1/spec_tester.py +165 -0
  179. clm/layer1/store.py +295 -0
  180. clm/layer1/test_runner.py +498 -0
  181. clm/layer1/type_inspector.py +166 -0
  182. clm/layer1/verifier.py +772 -0
  183. clm/layer1/vm.py +91 -0
  184. clm/layer2/__init__.py +78 -0
  185. clm/layer2/card_collection.py +678 -0
  186. clm/layer2/classifier.py +479 -0
  187. clm/layer2/engine/__init__.py +1 -0
  188. clm/layer2/engine/abstract_sql_engine.py +320 -0
  189. clm/layer2/engine/base.py +193 -0
  190. clm/layer2/engine/duckdb_engine.py +576 -0
  191. clm/layer2/engine/sqlite_engine.py +518 -0
  192. clm/layer2/fiber/__init__.py +31 -0
  193. clm/layer2/fiber/context.py +89 -0
  194. clm/layer2/fiber/disposable.py +163 -0
  195. clm/layer2/fiber/fiber.py +125 -0
  196. clm/layer2/fiber/hoare.py +191 -0
  197. clm/layer2/file_io.py +376 -0
  198. clm/layer2/file_registrar.py +77 -0
  199. clm/layer2/ingest.py +202 -0
  200. clm/layer2/lineage.py +322 -0
  201. clm/layer2/mcard_fs.py +262 -0
  202. clm/layer2/merkle.py +11 -0
  203. clm/layer2/mime.py +244 -0
  204. clm/layer2/mime_detector.py +245 -0
  205. clm/layer2/private_collection.py +118 -0
  206. clm/layer2/shared_store.py +11 -0
  207. clm/layer2/storage/__init__.py +190 -0
  208. clm/layer2/storage/db_bitemporal.py +141 -0
  209. clm/layer2/storage/db_driver.py +331 -0
  210. clm/layer2/storage/db_hyperlinks.py +214 -0
  211. clm/layer2/storage/db_pool.py +139 -0
  212. clm/layer2/storage/db_repository.py +229 -0
  213. clm/layer2/storage/savepoint.py +78 -0
  214. clm/layer2/storage/sqlite_wal.py +74 -0
  215. clm/layer2/tridb.py +245 -0
  216. clm/layer2/vcard_receipt.py +71 -0
  217. clm/layer2/vfs.py +17 -0
  218. clm/layer3/__init__.py +141 -0
  219. clm/layer3/agency/__init__.py +19 -0
  220. clm/layer3/agency/config.py +220 -0
  221. clm/layer3/agency/providers/__init__.py +15 -0
  222. clm/layer3/agency/providers/base.py +91 -0
  223. clm/layer3/agency/providers/mlc_llm.py +169 -0
  224. clm/layer3/agency/providers/ollama.py +203 -0
  225. clm/layer3/agency/router.py +71 -0
  226. clm/layer3/agency/runtime.py +524 -0
  227. clm/layer3/gateway.py +353 -0
  228. clm/layer3/open_interpreter_adapter.py +159 -0
  229. clm/layer3/rag/__init__.py +70 -0
  230. clm/layer3/rag/cli.py +228 -0
  231. clm/layer3/rag/config.py +153 -0
  232. clm/layer3/rag/embeddings/__init__.py +16 -0
  233. clm/layer3/rag/embeddings/base.py +79 -0
  234. clm/layer3/rag/embeddings/ollama.py +211 -0
  235. clm/layer3/rag/embeddings/vision.py +290 -0
  236. clm/layer3/rag/engine.py +276 -0
  237. clm/layer3/rag/graph/__init__.py +22 -0
  238. clm/layer3/rag/graph/community.py +198 -0
  239. clm/layer3/rag/graph/engine.py +433 -0
  240. clm/layer3/rag/graph/extractor.py +255 -0
  241. clm/layer3/rag/graph/schema.py +115 -0
  242. clm/layer3/rag/graph/store.py +641 -0
  243. clm/layer3/rag/indexer.py +333 -0
  244. clm/layer3/rag/llm_providers.json +52 -0
  245. clm/layer3/rag/semantic_versioning.py +313 -0
  246. clm/layer3/rag/vector/__init__.py +28 -0
  247. clm/layer3/rag/vector/handle_vector_store.py +749 -0
  248. clm/layer3/rag/vector/schema.py +151 -0
  249. clm/layer3/rag/vector/store.py +632 -0
  250. clm/layer3/reticulum/__init__.py +55 -0
  251. clm/layer3/reticulum/bridge.py +74 -0
  252. clm/layer3/reticulum/daemon.py +88 -0
  253. clm/layer3/reticulum/discovery.py +164 -0
  254. clm/layer3/reticulum/identity.py +107 -0
  255. clm/layer3/reticulum/media.py +148 -0
  256. clm/layer3/reticulum/transport.py +133 -0
  257. clm/layer3/satori/__init__.py +209 -0
  258. clm/layer3/satori/alpha_conversion.py +168 -0
  259. clm/layer3/satori/beta_reduction.py +359 -0
  260. clm/layer3/satori/continuation.py +38 -0
  261. clm/layer3/satori/eta_conversion.py +205 -0
  262. clm/layer3/satori/free_variables.py +163 -0
  263. clm/layer3/satori/io_effects.py +370 -0
  264. clm/layer3/satori/lambda_runtime.py +726 -0
  265. clm/layer3/satori/lambda_term.py +230 -0
  266. clm/layer3/satori/sheaf_audit.py +261 -0
  267. clm/layer3/satori/speech_act.py +279 -0
  268. clm/layer4/README.md +9 -0
  269. clm/layer4/__init__.py +67 -0
  270. clm/layer4/_sovereign_executor.py +580 -0
  271. clm/layer4/action_commands.py +719 -0
  272. clm/layer4/api.py +236 -0
  273. clm/layer4/bootstrap/__init__.py +13 -0
  274. clm/layer4/bootstrap/bootstrapper.py +118 -0
  275. clm/layer4/bootstrap/cleanup.py +44 -0
  276. clm/layer4/bootstrap/clm_hooks.py +49 -0
  277. clm/layer4/bootstrap/genesis.py +77 -0
  278. clm/layer4/bootstrap/injection.py +125 -0
  279. clm/layer4/cli.py +549 -0
  280. clm/layer4/collection_manager.py +553 -0
  281. clm/layer4/commands/__init__.py +81 -0
  282. clm/layer4/commands/action_cmd.py +42 -0
  283. clm/layer4/commands/check_cmd.py +82 -0
  284. clm/layer4/commands/envelope.py +73 -0
  285. clm/layer4/commands/evaluate_cmd.py +224 -0
  286. clm/layer4/commands/mcard_cmd.py +24 -0
  287. clm/layer4/commands/petri_cmd.py +92 -0
  288. clm/layer4/commands/tridb_cmd.py +319 -0
  289. clm/layer4/commands/version_cmd.py +27 -0
  290. clm/layer4/diff_commands.py +167 -0
  291. clm/layer4/eoa_sponsor.py +232 -0
  292. clm/layer4/events.py +69 -0
  293. clm/layer4/flux_gateway.py +133 -0
  294. clm/layer4/gateway.py +91 -0
  295. clm/layer4/loader.py +307 -0
  296. clm/layer4/mcard_commands.py +166 -0
  297. clm/layer4/membrane.py +273 -0
  298. clm/layer4/navigation.py +50 -0
  299. clm/layer4/protocol/protocol_entry_python.py +108 -0
  300. clm/layer4/protocol/python_sync.py +126 -0
  301. clm/layer4/runner.py +150 -0
  302. clm/layer4/satori.py +262 -0
  303. clm/layer4/server/python_sync_client.py +150 -0
  304. clm/layer4/server/python_sync_server.py +139 -0
  305. clm/layer4/server/service_api.py +315 -0
  306. clm/layer4/server/start_servers.py +107 -0
  307. clm/layer4/server/stop_servers.py +24 -0
  308. clm/layer4/simulated_optimization_fix.py +0 -0
  309. clm/layer4/storage.py +463 -0
  310. clm/layer4/tridb_admin.py +268 -0
  311. clm/layer4/websocket/test_ws_client.py +104 -0
  312. clm/layer4/websocket/test_ws_server.py +138 -0
  313. clm/layer4/websocket/ws_server_local.py +160 -0
  314. clm/layer5/__init__.py +51 -0
  315. clm/layer5/async_batch_writer.py +133 -0
  316. clm/layer5/learning.py +276 -0
  317. clm/layer5/telemetry_hook.py +107 -0
  318. clm/layer5/type_lattice.py +238 -0
  319. clm/py.typed +0 -0
  320. clm/schemas/__init__.py +652 -0
  321. clm/schemas/abstract_face_schema.yaml +77 -0
  322. clm/schemas/cel_schema.yaml +416 -0
  323. clm/schemas/lifecycle/__init__.py +45 -0
  324. clm/schemas/lifecycle/models.py +123 -0
  325. clm/schemas/lifecycle/service.py +364 -0
  326. clm/schemas/mcard_schema.sql +192 -0
  327. clm/schemas/mcard_vector_schema.sql +283 -0
  328. clm/schemas/mlp-context-v1.json +91 -0
  329. clm_kernel-0.0.0.dist-info/METADATA +121 -0
  330. clm_kernel-0.0.0.dist-info/RECORD +333 -0
  331. clm_kernel-0.0.0.dist-info/WHEEL +4 -0
  332. clm_kernel-0.0.0.dist-info/entry_points.txt +3 -0
  333. 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
+ }
@@ -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
+ ]