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
clm/layer3/rag/cli.py ADDED
@@ -0,0 +1,228 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ MCard RAG CLI
4
+
5
+ Command-line interface for RAG operations.
6
+
7
+ Usage:
8
+ mcard-rag index # Index all MCards
9
+ mcard-rag search "query" # Semantic search
10
+ mcard-rag query "question" # RAG query with LLM
11
+ mcard-rag status # Show indexing status
12
+ mcard-rag clear # Clear vector index
13
+ """
14
+
15
+ import argparse
16
+ import logging
17
+ import sys
18
+
19
+ logging.basicConfig(level=logging.INFO, format="%(message)s")
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ def cmd_status(args):
24
+ """Show RAG system status."""
25
+ from clm.layer3.rag import get_indexer
26
+ from clm.layer3.rag.embeddings import OllamaEmbeddingProvider
27
+
28
+ logger.info("=" * 60)
29
+ logger.info("MCard RAG System Status")
30
+ logger.info("=" * 60)
31
+
32
+ # Check embedding provider
33
+ provider = OllamaEmbeddingProvider()
34
+ available = provider.validate_connection()
35
+ logger.info(f"\n📦 Embedding Model: {provider.model_name}")
36
+ logger.info(f" Status: {'✅ Available' if available else '❌ Not available'}")
37
+ logger.info(f" Dimensions: {provider.dimensions}")
38
+
39
+ # Get indexer stats
40
+ indexer = get_indexer()
41
+ stats = indexer.get_stats()
42
+
43
+ logger.info("\n📊 Vector Index:")
44
+ logger.info(f" Database: {stats['vector_db_path']}")
45
+ logger.info(f" Indexed MCards: {stats['unique_mcards']}")
46
+ logger.info(f" Total Vectors: {stats['vector_count']}")
47
+ logger.info(
48
+ f" sqlite-vec: {'✅' if stats['has_vec_extension'] else '❌ (fallback mode)'}"
49
+ )
50
+ logger.info(f" Hybrid Search: {'✅' if stats['hybrid_search_enabled'] else '❌'}")
51
+
52
+ # Show MCard collection count
53
+ from clm.layer2.card_collection import get_default_collection
54
+ default_collection = get_default_collection()
55
+
56
+ total_mcards = default_collection.count()
57
+ indexed = stats["unique_mcards"]
58
+ pending = total_mcards - indexed
59
+
60
+ logger.info("\n📁 Collection:")
61
+ logger.info(f" Total MCards: {total_mcards}")
62
+ logger.info(f" Indexed: {indexed}")
63
+ logger.info(f" Pending: {pending}")
64
+
65
+ if pending > 0:
66
+ logger.info(f"\n💡 Run 'mcard-rag index' to index {pending} pending cards")
67
+
68
+
69
+ def cmd_index(args):
70
+ """Index all MCards."""
71
+ from clm.layer3.rag import get_indexer
72
+
73
+ logger.info("=" * 60)
74
+ logger.info("Indexing MCards")
75
+ logger.info("=" * 60)
76
+
77
+ indexer = get_indexer()
78
+
79
+ def progress(current, total):
80
+ pct = (current / total) * 100 if total > 0 else 0
81
+ print(f"\r Progress: {current}/{total} ({pct:.1f}%)", end="", flush=True)
82
+
83
+ logger.info("\n📊 Starting index...")
84
+ stats = indexer.index_all(force=args.force, progress_callback=progress)
85
+
86
+ logger.info("\n\n✅ Indexing Complete:")
87
+ logger.info(f" Indexed: {stats['indexed']}")
88
+ logger.info(f" Skipped: {stats['skipped']}")
89
+ logger.info(f" Failed: {stats['failed']}")
90
+ logger.info(f" Total: {stats['total']}")
91
+
92
+
93
+ def cmd_search(args):
94
+ """Semantic search."""
95
+ from clm.layer3.rag import semantic_search
96
+
97
+ logger.info("=" * 60)
98
+ logger.info(f"Semantic Search: {args.query}")
99
+ logger.info("=" * 60)
100
+
101
+ results = semantic_search(args.query, k=args.k)
102
+
103
+ if not results:
104
+ logger.info("\n❌ No results found")
105
+ return
106
+
107
+ logger.info(f"\n📋 Results ({len(results)} found):\n")
108
+
109
+ for i, result in enumerate(results):
110
+ logger.info(f"[{i + 1}] Score: {result.score:.4f}")
111
+ logger.info(f" Hash: {result.hash}")
112
+ if result.chunk_text:
113
+ preview = result.chunk_text[:200].replace("\n", " ")
114
+ logger.info(f" Preview: {preview}...")
115
+ logger.info("")
116
+
117
+
118
+ def cmd_query(args):
119
+ """RAG query with LLM."""
120
+ from clm.layer3.rag import MCardRAGEngine, get_indexer
121
+
122
+ logger.info("=" * 60)
123
+ logger.info(f"RAG Query: {args.question}")
124
+ logger.info("=" * 60)
125
+
126
+ # Use persistent indexer's vector store
127
+ indexer = get_indexer()
128
+
129
+ rag = MCardRAGEngine(vector_db_path=indexer.vector_db_path)
130
+ rag.vector_store = indexer.vector_store
131
+
132
+ logger.info("\n🔍 Searching for context...")
133
+ response = rag.query(args.question, k=args.k, model=args.model)
134
+
135
+ logger.info(f"\n💬 Answer:\n{response.answer}")
136
+ logger.info(f"\n📊 Confidence: {response.confidence:.2%}")
137
+ logger.info(f"📚 Sources: {[h[:8] for h in response.sources]}")
138
+
139
+ if args.verbose:
140
+ logger.info("\n📝 Source Chunks:")
141
+ for i, chunk in enumerate(response.source_chunks):
142
+ if chunk:
143
+ logger.info(f" [{i + 1}] {chunk[:150]}...")
144
+
145
+
146
+ def cmd_clear(args):
147
+ """Clear vector index."""
148
+ from clm.layer3.rag import get_indexer
149
+
150
+ if not args.yes:
151
+ confirm = input("Are you sure you want to clear the vector index? [y/N]: ")
152
+ if confirm.lower() != "y":
153
+ logger.info("Cancelled")
154
+ return
155
+
156
+ indexer = get_indexer()
157
+ indexer.clear()
158
+ logger.info("✅ Vector index cleared")
159
+
160
+
161
+ def main():
162
+ parser = argparse.ArgumentParser(
163
+ description="MCard RAG CLI",
164
+ formatter_class=argparse.RawDescriptionHelpFormatter,
165
+ )
166
+
167
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
168
+
169
+ # Status command
170
+ status_parser = subparsers.add_parser("status", help="Show RAG system status")
171
+ status_parser.set_defaults(func=cmd_status)
172
+
173
+ # Index command
174
+ index_parser = subparsers.add_parser("index", help="Index all MCards")
175
+ index_parser.add_argument(
176
+ "--force", "-f", action="store_true", help="Re-index existing cards"
177
+ )
178
+ index_parser.set_defaults(func=cmd_index)
179
+
180
+ # Search command
181
+ search_parser = subparsers.add_parser("search", help="Semantic search")
182
+ search_parser.add_argument("query", help="Search query")
183
+ search_parser.add_argument(
184
+ "-k", type=int, default=5, help="Number of results (default: 5)"
185
+ )
186
+ search_parser.set_defaults(func=cmd_search)
187
+
188
+ # Query command
189
+ query_parser = subparsers.add_parser("query", help="RAG query with LLM")
190
+ query_parser.add_argument("question", help="Question to answer")
191
+ query_parser.add_argument(
192
+ "-k", type=int, default=5, help="Number of context chunks (default: 5)"
193
+ )
194
+ query_parser.add_argument(
195
+ "--model",
196
+ "-m",
197
+ default="qwen3:0.6b",
198
+ help="LLM model (default: qwen3:0.6b)",
199
+ )
200
+ query_parser.add_argument(
201
+ "--verbose", "-v", action="store_true", help="Show source chunks"
202
+ )
203
+ query_parser.set_defaults(func=cmd_query)
204
+
205
+ # Clear command
206
+ clear_parser = subparsers.add_parser("clear", help="Clear vector index")
207
+ clear_parser.add_argument(
208
+ "--yes", "-y", action="store_true", help="Skip confirmation"
209
+ )
210
+ clear_parser.set_defaults(func=cmd_clear)
211
+
212
+ args = parser.parse_args()
213
+
214
+ if args.command is None:
215
+ parser.print_help()
216
+ return
217
+
218
+ try:
219
+ args.func(args)
220
+ except Exception as e:
221
+ logger.error(f"Error: {e}", exc_info=True)
222
+ if "--debug" in sys.argv:
223
+ raise
224
+ sys.exit(1)
225
+
226
+
227
+ if __name__ == "__main__":
228
+ main()
@@ -0,0 +1,153 @@
1
+ import json
2
+ from dataclasses import dataclass
3
+
4
+ # ─────────────────────────────────────────────────────────────────────────────
5
+ # Load Centralized LLM Provider Configuration
6
+ # ─────────────────────────────────────────────────────────────────────────────
7
+
8
+
9
+ def _load_llm_config():
10
+ """Load models from centralized JSON file."""
11
+ from pathlib import Path
12
+
13
+ local_path = Path(__file__).resolve().parent / "llm_providers.json"
14
+ if local_path.exists():
15
+ try:
16
+ with open(local_path, encoding="utf-8") as f:
17
+ return json.load(f)
18
+ except Exception as e:
19
+ print(f"Warning: Failed to load {local_path}: {e}")
20
+
21
+ try:
22
+ from clm.cross_cutting.config.config_constants import _find_project_root
23
+ root_dir = _find_project_root()
24
+ config_path = root_dir / "llm_providers.json"
25
+ if config_path.exists():
26
+ with open(config_path, encoding="utf-8") as f:
27
+ return json.load(f)
28
+ except Exception:
29
+ pass
30
+
31
+ curr = Path(__file__).resolve()
32
+ for parent in list(curr.parents):
33
+ cand = parent / "llm_providers.json"
34
+ if cand.exists():
35
+ try:
36
+ with open(cand, encoding="utf-8") as f:
37
+ return json.load(f)
38
+ except Exception:
39
+ pass
40
+
41
+ # Fallback if file missing or error
42
+ return {
43
+ "embedding_models": {
44
+ "nomic-embed-text": {
45
+ "dimensions": 768,
46
+ "provider": "ollama",
47
+ "max_tokens": 8192,
48
+ }
49
+ },
50
+ "vision_models": {"moondream": {"description": "Fallback Moondream"}},
51
+ "default_embedding_model": "nomic-embed-text",
52
+ "default_vision_model": "moondream",
53
+ }
54
+
55
+
56
+ _RAW_CONFIG = _load_llm_config()
57
+
58
+ EMBEDDING_MODELS = _RAW_CONFIG.get("embedding_models", {})
59
+ DEFAULT_EMBEDDING_MODEL = _RAW_CONFIG.get("default_embedding_model", "nomic-embed-text")
60
+
61
+ VISION_MODELS = _RAW_CONFIG.get("vision_models", {})
62
+ DEFAULT_VISION_MODEL = _RAW_CONFIG.get("default_vision_model", "moondream")
63
+
64
+ # ─────────────────────────────────────────────────────────────────────────────
65
+ # Vector Search Configuration
66
+ # ─────────────────────────────────────────────────────────────────────────────
67
+
68
+ # Distance metrics supported by sqlite-vec
69
+ DISTANCE_METRICS = ("L2", "cosine", "L1")
70
+ DEFAULT_DISTANCE_METRIC = "cosine"
71
+
72
+ # Default search parameters
73
+ DEFAULT_TOP_K = 5
74
+ MAX_TOP_K = 100
75
+
76
+ # Chunking configuration
77
+ DEFAULT_CHUNK_SIZE = 1000 # characters
78
+ DEFAULT_CHUNK_OVERLAP = 200
79
+
80
+ # ─────────────────────────────────────────────────────────────────────────────
81
+ # RAG Configuration Dataclass
82
+ # ─────────────────────────────────────────────────────────────────────────────
83
+
84
+
85
+ @dataclass
86
+ class RAGConfig:
87
+ """
88
+ Configuration for MCard RAG system.
89
+
90
+ Attributes:
91
+ embedding_model: Name of the embedding model to use
92
+ embedding_provider: Provider for embeddings (ollama, openai, etc.)
93
+ ollama_base_url: Base URL for Ollama API
94
+ vector_db_path: Path to vector database (None = use main MCard db)
95
+ distance_metric: Distance metric for similarity search
96
+ top_k: Default number of results to return
97
+ chunk_size: Maximum characters per chunk
98
+ chunk_overlap: Overlap between chunks
99
+ enable_hybrid_search: Enable FTS + vector hybrid search
100
+ rerank_results: Re-rank results using cross-encoder
101
+ """
102
+
103
+ # Embedding configuration
104
+ embedding_model: str = DEFAULT_EMBEDDING_MODEL
105
+ embedding_provider: str = "ollama"
106
+ ollama_base_url: str = "http://localhost:11434"
107
+
108
+ # Vector storage
109
+ vector_db_path: str | None = None # None = same as MCard db
110
+ distance_metric: str = DEFAULT_DISTANCE_METRIC
111
+
112
+ # Search parameters
113
+ top_k: int = DEFAULT_TOP_K
114
+ similarity_threshold: float = 0.0 # Minimum similarity (0-1)
115
+
116
+ # Chunking
117
+ chunk_size: int = DEFAULT_CHUNK_SIZE
118
+ chunk_overlap: int = DEFAULT_CHUNK_OVERLAP
119
+
120
+ # Advanced features
121
+ enable_hybrid_search: bool = True
122
+ rerank_results: bool = False
123
+
124
+ def __post_init__(self):
125
+ """Validate configuration after initialization."""
126
+ if self.embedding_model not in EMBEDDING_MODELS:
127
+ raise ValueError(
128
+ f"Unknown embedding model: {self.embedding_model}. "
129
+ f"Available: {list(EMBEDDING_MODELS.keys())}"
130
+ )
131
+
132
+ if self.distance_metric not in DISTANCE_METRICS:
133
+ raise ValueError(
134
+ f"Unknown distance metric: {self.distance_metric}. "
135
+ f"Available: {DISTANCE_METRICS}"
136
+ )
137
+
138
+ if self.top_k < 1 or self.top_k > MAX_TOP_K:
139
+ raise ValueError(f"top_k must be between 1 and {MAX_TOP_K}")
140
+
141
+ @property
142
+ def dimensions(self) -> int:
143
+ """Get embedding dimensions for the configured model."""
144
+ return EMBEDDING_MODELS[self.embedding_model]["dimensions"]
145
+
146
+ @property
147
+ def max_tokens(self) -> int:
148
+ """Get max tokens for the configured model."""
149
+ return EMBEDDING_MODELS[self.embedding_model]["max_tokens"]
150
+
151
+
152
+ # Default configuration
153
+ DEFAULT_RAG_CONFIG = RAGConfig()
@@ -0,0 +1,16 @@
1
+ """
2
+ Embedding Providers Package
3
+
4
+ Provides embedding generation capabilities for RAG.
5
+ """
6
+
7
+ from .base import EmbeddingProvider
8
+ from .ollama import OllamaEmbeddingProvider
9
+ from .vision import VISION_MODELS, VisionEmbeddingProvider
10
+
11
+ __all__ = [
12
+ "EmbeddingProvider",
13
+ "OllamaEmbeddingProvider",
14
+ "VisionEmbeddingProvider",
15
+ "VISION_MODELS",
16
+ ]
@@ -0,0 +1,79 @@
1
+ """
2
+ Embedding Provider Base Class
3
+
4
+ Abstract interface for embedding providers.
5
+ """
6
+
7
+ from abc import ABC, abstractmethod
8
+ from dataclasses import dataclass
9
+
10
+
11
+ @dataclass
12
+ class EmbeddingResult:
13
+ """Result from an embedding operation."""
14
+
15
+ vector: list[float]
16
+ model: str
17
+ dimensions: int
18
+ token_count: int | None = None
19
+
20
+
21
+ class EmbeddingProvider(ABC):
22
+ """
23
+ Abstract base class for embedding providers.
24
+
25
+ Implementations should handle:
26
+ - Connection to the embedding service
27
+ - Text preprocessing (truncation, etc.)
28
+ - Batch processing for efficiency
29
+ - Error handling and retries
30
+ """
31
+
32
+ provider_name: str = "base"
33
+ model_name: str
34
+ dimensions: int
35
+
36
+ @abstractmethod
37
+ def embed(self, text: str) -> list[float]:
38
+ """
39
+ Generate embedding vector for a single text.
40
+
41
+ Args:
42
+ text: Input text to embed
43
+
44
+ Returns:
45
+ List of floats representing the embedding vector
46
+ """
47
+ pass
48
+
49
+ @abstractmethod
50
+ def embed_batch(self, texts: list[str]) -> list[list[float]]:
51
+ """
52
+ Generate embeddings for multiple texts.
53
+
54
+ Args:
55
+ texts: List of input texts
56
+
57
+ Returns:
58
+ List of embedding vectors
59
+ """
60
+ pass
61
+
62
+ @abstractmethod
63
+ def validate_connection(self) -> bool:
64
+ """
65
+ Check if the embedding service is available.
66
+
67
+ Returns:
68
+ True if connection is valid
69
+ """
70
+ pass
71
+
72
+ def get_info(self) -> dict:
73
+ """Get provider information."""
74
+ return {
75
+ "provider": self.provider_name,
76
+ "model": self.model_name,
77
+ "dimensions": self.dimensions,
78
+ "available": self.validate_connection(),
79
+ }
@@ -0,0 +1,211 @@
1
+ """
2
+ Ollama Embedding Provider
3
+
4
+ Generates embeddings using Ollama's local embedding models.
5
+ """
6
+
7
+ import json
8
+ import logging
9
+ import urllib.error
10
+ import urllib.request
11
+
12
+ from clm.cross_cutting.config.config_constants import (
13
+ RAG_OLLAMA_TIMEOUT_SECS,
14
+ RAG_OLLAMA_VALIDATE_TIMEOUT_SECS,
15
+ )
16
+ from clm.cross_cutting.utils.url_safety import safe_urlopen
17
+
18
+ from ..config import DEFAULT_EMBEDDING_MODEL, EMBEDDING_MODELS
19
+ from .base import EmbeddingProvider
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class OllamaEmbeddingProvider(EmbeddingProvider):
25
+ """
26
+ Embedding provider using Ollama's embedding API.
27
+
28
+ Supports models like:
29
+ - nomic-embed-text (768 dimensions)
30
+ - bge-m3 (1024 dimensions)
31
+ - mxbai-embed-large (1024 dimensions)
32
+ - all-minilm (384 dimensions)
33
+
34
+ Usage:
35
+ provider = OllamaEmbeddingProvider('nomic-embed-text')
36
+ vector = provider.embed("Hello world")
37
+ """
38
+
39
+ provider_name = "ollama"
40
+
41
+ def __init__(
42
+ self,
43
+ model: str = DEFAULT_EMBEDDING_MODEL,
44
+ base_url: str = "http://localhost:11434",
45
+ timeout: int = RAG_OLLAMA_TIMEOUT_SECS,
46
+ ):
47
+ """
48
+ Initialize Ollama embedding provider.
49
+
50
+ Args:
51
+ model: Ollama embedding model name
52
+ base_url: Ollama API base URL
53
+ timeout: Request timeout in seconds
54
+ """
55
+ self.model_name = model
56
+ self.base_url = base_url.rstrip("/")
57
+ self.timeout = timeout
58
+
59
+ # Get dimensions from config or default
60
+ if model in EMBEDDING_MODELS:
61
+ self.dimensions = EMBEDDING_MODELS[model]["dimensions"]
62
+ else:
63
+ # Unknown model, will determine dimensions on first embed
64
+ self.dimensions = 0
65
+ logger.warning(
66
+ f"Unknown model '{model}', dimensions will be determined on first embed"
67
+ )
68
+
69
+ def _make_request(self, endpoint: str, data: dict) -> dict:
70
+ """Make HTTP request to Ollama API."""
71
+ url = f"{self.base_url}{endpoint}"
72
+
73
+ try:
74
+ request = urllib.request.Request(
75
+ url,
76
+ data=json.dumps(data).encode("utf-8"),
77
+ headers={"Content-Type": "application/json"},
78
+ method="POST",
79
+ )
80
+
81
+ with safe_urlopen(request, timeout=self.timeout) as response:
82
+ return json.loads(response.read().decode("utf-8"))
83
+
84
+ except urllib.error.HTTPError as e:
85
+ error_body = e.read().decode("utf-8") if e.fp else str(e)
86
+ raise RuntimeError(f"Ollama HTTP error {e.code}: {error_body}") from e
87
+ except urllib.error.URLError as e:
88
+ raise RuntimeError(f"Ollama connection error: {e.reason}") from e
89
+ except Exception as e:
90
+ raise RuntimeError(f"Ollama request failed: {e}") from e
91
+
92
+ def embed(self, text: str) -> list[float]:
93
+ """
94
+ Generate embedding for a single text.
95
+
96
+ Args:
97
+ text: Input text to embed
98
+
99
+ Returns:
100
+ Embedding vector as list of floats
101
+ """
102
+ if not text or not text.strip():
103
+ raise ValueError("Cannot embed empty text")
104
+
105
+ response = self._make_request(
106
+ "/api/embed",
107
+ {
108
+ "model": self.model_name,
109
+ "input": text,
110
+ },
111
+ )
112
+
113
+ if "embeddings" in response and response["embeddings"]:
114
+ embedding = response["embeddings"][0]
115
+
116
+ # Update dimensions if unknown
117
+ if self.dimensions == 0:
118
+ self.dimensions = len(embedding)
119
+ logger.info(
120
+ f"Determined {self.model_name} dimensions: {self.dimensions}"
121
+ )
122
+
123
+ return embedding
124
+ else:
125
+ raise RuntimeError(f"Unexpected Ollama response: {response}")
126
+
127
+ def embed_batch(self, texts: list[str]) -> list[list[float]]:
128
+ """
129
+ Generate embeddings for multiple texts.
130
+
131
+ Ollama supports batch embedding with the 'input' parameter as a list.
132
+
133
+ Args:
134
+ texts: List of input texts
135
+
136
+ Returns:
137
+ List of embedding vectors
138
+ """
139
+ if not texts:
140
+ return []
141
+
142
+ # Filter empty texts
143
+ valid_texts = [t for t in texts if t and t.strip()]
144
+ if not valid_texts:
145
+ raise ValueError("All texts are empty")
146
+
147
+ response = self._make_request(
148
+ "/api/embed",
149
+ {
150
+ "model": self.model_name,
151
+ "input": valid_texts,
152
+ },
153
+ )
154
+
155
+ if "embeddings" in response:
156
+ embeddings = response["embeddings"]
157
+
158
+ # Update dimensions if unknown
159
+ if self.dimensions == 0 and embeddings:
160
+ self.dimensions = len(embeddings[0])
161
+
162
+ return embeddings
163
+ else:
164
+ raise RuntimeError(f"Unexpected Ollama batch response: {response}")
165
+
166
+ def validate_connection(self) -> bool:
167
+ """Check if Ollama is running and model is available."""
168
+ try:
169
+ # Check if server is running
170
+ request = urllib.request.Request(f"{self.base_url}/api/tags", method="GET")
171
+
172
+ with safe_urlopen(
173
+ request, timeout=RAG_OLLAMA_VALIDATE_TIMEOUT_SECS
174
+ ) as response:
175
+ data = json.loads(response.read().decode("utf-8"))
176
+
177
+ # Check if model is available
178
+ if "models" in data:
179
+ available_models = [m.get("name", "") for m in data["models"]]
180
+
181
+ # Check exact match or partial match
182
+ for model in available_models:
183
+ if self.model_name in model or model in self.model_name:
184
+ return True
185
+
186
+ logger.warning(
187
+ f"Model '{self.model_name}' not found. "
188
+ f"Available: {available_models[:5]}..."
189
+ )
190
+ return False
191
+
192
+ return True
193
+
194
+ except Exception as e:
195
+ logger.debug(f"Ollama validation failed: {e}")
196
+ return False
197
+
198
+ def pull_model(self) -> bool:
199
+ """Pull the embedding model from Ollama."""
200
+ try:
201
+ self._make_request(
202
+ "/api/pull",
203
+ {
204
+ "name": self.model_name,
205
+ "stream": False,
206
+ },
207
+ )
208
+ return True
209
+ except Exception as e:
210
+ logger.error(f"Failed to pull model {self.model_name}: {e}", exc_info=True)
211
+ return False