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,198 @@
1
+ """
2
+ Community Detection and Summarization
3
+
4
+ Label Propagation Algorithm (LPA) for community detection
5
+ and LLM-based hierarchical summarization.
6
+ """
7
+
8
+ import json
9
+ import logging
10
+ import random
11
+ import re
12
+ from collections import Counter, defaultdict
13
+
14
+ from .store import GraphStore
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ # ─────────────────────────────────────────────────────────────────────────────
20
+ # Community Detection
21
+ # ─────────────────────────────────────────────────────────────────────────────
22
+
23
+
24
+ def detect_communities(store: GraphStore, max_iter: int = 20) -> list[list[int]]:
25
+ """
26
+ Detect communities using asynchronous Label Propagation.
27
+
28
+ Args:
29
+ store: GraphStore instance
30
+ max_iter: Maximum iterations
31
+
32
+ Returns:
33
+ List of communities, where each community is a list of entity IDs
34
+ """
35
+ logger.info("Starting community detection (LPA)...")
36
+
37
+ # 1. Build Adjacency List
38
+ adj = defaultdict(list)
39
+ nodes = set()
40
+
41
+ cursor = store.conn.cursor()
42
+ cursor.execute("SELECT source_entity_id, target_entity_id FROM graph_relationships")
43
+
44
+ rows = cursor.fetchall()
45
+ for src, tgt in rows:
46
+ adj[src].append(tgt)
47
+ adj[tgt].append(src) # Undirected for community detection
48
+ nodes.add(src)
49
+ nodes.add(tgt)
50
+
51
+ node_list = list(nodes)
52
+ if not node_list:
53
+ logger.warning("No nodes found for community detection")
54
+ return []
55
+
56
+ logger.debug(f"Graph size: {len(nodes)} nodes, {len(rows)} edges")
57
+
58
+ # 2. Initialize Labels
59
+ labels = {node: node for node in nodes}
60
+
61
+ # 3. Propagate Labels
62
+ for i in range(max_iter):
63
+ changes = 0
64
+ random.shuffle(node_list)
65
+
66
+ for node in node_list:
67
+ if not adj[node]:
68
+ continue
69
+
70
+ neighbor_labels = [labels[neighbor] for neighbor in adj[node]]
71
+ if not neighbor_labels:
72
+ continue
73
+
74
+ # Find most frequent label (ties broken randomly)
75
+ counts = Counter(neighbor_labels)
76
+ max_freq = counts.most_common(1)[0][1]
77
+ best_labels = [lbl for lbl, count in counts.items() if count == max_freq]
78
+ new_label = random.choice(best_labels)
79
+
80
+ if labels[node] != new_label:
81
+ labels[node] = new_label
82
+ changes += 1
83
+
84
+ logger.debug(f"LPA Iteration {i + 1}: {changes} changes")
85
+ if changes == 0:
86
+ logger.info(f"LPA converged after {i + 1} iterations")
87
+ break
88
+
89
+ # 4. Group Communities
90
+ communities = defaultdict(list)
91
+ for node, label in labels.items():
92
+ communities[label].append(node)
93
+
94
+ result = list(communities.values())
95
+ logger.info(f"Detected {len(result)} communities")
96
+
97
+ # Sort for deterministic output (by size desc, then first ID)
98
+ result.sort(key=lambda c: (-len(c), min(c)))
99
+
100
+ return result
101
+
102
+
103
+ # ─────────────────────────────────────────────────────────────────────────────
104
+ # Community Summarization
105
+ # ─────────────────────────────────────────────────────────────────────────────
106
+
107
+ SUMMARIZE_SYSTEM_PROMPT = """You are an expert graph analyst.
108
+ Your task is to summarize a "community" of related entities from a knowledge graph.
109
+ Focus on the common themes, purposes, or technologies that connect these entities.
110
+ Synthesize the descriptions into a cohesive whole."""
111
+
112
+ SUMMARIZE_USER_PROMPT = """Analyze the following list of entities and their descriptions which form a community in a knowledge graph.
113
+ Create a Title and valid JSON Summary.
114
+
115
+ --- BEGIN ENTITY LIST ---
116
+ {entity_text}
117
+ --- END ENTITY LIST ---
118
+
119
+ Requirement:
120
+ - Provide a short Title.
121
+ - Provide a detailed Summary of common themes.
122
+ - Output MUST be valid JSON in the format:
123
+ {{
124
+ "title": "Community Title",
125
+ "summary": "Detailed summary..."
126
+ }}
127
+ """
128
+
129
+
130
+ class CommunitySummarizer:
131
+ """Summarizes graph communities using LLM."""
132
+
133
+ def __init__(self, store: GraphStore, model: str = "qwen3:0.6b"):
134
+ self.store = store
135
+ self.model = model
136
+
137
+ def summarize_and_store(self, communities: list[list[int]]) -> int:
138
+ """
139
+ Summarize communities and store them in the DB.
140
+
141
+ Returns:
142
+ Count of summaries generated
143
+ """
144
+ count = 0
145
+
146
+ for comm_ids in communities:
147
+ # Prepare context
148
+ entity_text = self._prepare_context(comm_ids)
149
+ if not entity_text:
150
+ continue
151
+
152
+ try:
153
+ title, summary = self._generate_summary(entity_text)
154
+ self.store.add_community(title, summary, comm_ids)
155
+ count += 1
156
+ logger.info(f"Generated community: {title}")
157
+ except Exception as e:
158
+ logger.error(f"Failed to summarize community: {e}", exc_info=True)
159
+
160
+ return count
161
+
162
+ def _prepare_context(self, ids: list[int]) -> str:
163
+ lines = []
164
+ for eid in ids[:30]: # Limit context size
165
+ ent = self.store.get_entity_by_id(eid)
166
+ if ent:
167
+ lines.append(f"- {ent['name']} ({ent['type']}): {ent['description']}")
168
+ return "\n".join(lines)
169
+
170
+ def _generate_summary(self, entity_text: str) -> tuple[str, str]:
171
+ from clm.layer3.agency import chat_monad
172
+
173
+ prompt = SUMMARIZE_USER_PROMPT.format(entity_text=entity_text)
174
+
175
+ result = chat_monad(
176
+ prompt=prompt,
177
+ system_prompt=SUMMARIZE_SYSTEM_PROMPT,
178
+ model=self.model,
179
+ temperature=0.3,
180
+ max_tokens=1000,
181
+ ).unsafe_run()
182
+
183
+ if result.is_left():
184
+ raise RuntimeError(f"LLM failure: {result.value}")
185
+
186
+ content = result.value.get("content", str(result.value))
187
+
188
+ # Parse JSON
189
+ json_match = re.search(r"\{[\s\S]*\}", content)
190
+ if json_match:
191
+ try:
192
+ data = json.loads(json_match.group())
193
+ return data.get("title", "Unknown Community"), data.get("summary", "")
194
+ except json.JSONDecodeError:
195
+ pass
196
+
197
+ # Fallback if no JSON
198
+ return "Community Summary", content[:500]
@@ -0,0 +1,433 @@
1
+ """
2
+ GraphRAG Engine
3
+
4
+ Combines vector search with knowledge graph for enhanced RAG.
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
+
13
+ from ..config import DEFAULT_RAG_CONFIG, RAGConfig
14
+ from ..embeddings import OllamaEmbeddingProvider
15
+ from ..vector import MCardVectorStore
16
+ from .extractor import GraphExtractor
17
+ from .store import GraphStore, store_extraction_result
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ # ─────────────────────────────────────────────────────────────────────────────
23
+ # GraphRAG Response
24
+ # ─────────────────────────────────────────────────────────────────────────────
25
+
26
+
27
+ @dataclass
28
+ class GraphRAGResponse:
29
+ """Response from GraphRAG query."""
30
+
31
+ answer: str
32
+ sources: list[str] # Source MCard hashes
33
+ entities: list[dict[str, Any]] # Relevant entities
34
+ relationships: list[dict[str, Any]] # Relevant relationships
35
+ graph_context: str # Graph traversal context
36
+ vector_context: str # Vector search context
37
+ confidence: float
38
+ metadata: dict[str, Any] | None = None
39
+
40
+
41
+ # ─────────────────────────────────────────────────────────────────────────────
42
+ # System Prompts
43
+ # ─────────────────────────────────────────────────────────────────────────────
44
+
45
+ GRAPHRAG_SYSTEM_PROMPT = """You are a helpful assistant that answers questions using both document context and a knowledge graph.
46
+
47
+ You have access to:
48
+ 1. DOCUMENT CONTEXT: Relevant text passages from the knowledge base
49
+ 2. GRAPH CONTEXT: Entities and their relationships from the knowledge graph
50
+
51
+ Use both sources to provide comprehensive, accurate answers.
52
+ - Cite sources [1], [2], etc. for document context
53
+ - Reference entities by name when using graph information
54
+ - If the graph shows relationships that help answer the question, describe them
55
+
56
+ Be thorough but concise. Acknowledge if information is incomplete."""
57
+
58
+
59
+ GRAPH_QUERY_PROMPT = """Question: {question}
60
+
61
+ === DOCUMENT CONTEXT ===
62
+ {vector_context}
63
+
64
+ === KNOWLEDGE GRAPH ===
65
+ Entities: {entities}
66
+ Relationships: {relationships}
67
+
68
+ Please provide a comprehensive answer using both the document context and knowledge graph information."""
69
+
70
+
71
+ # ─────────────────────────────────────────────────────────────────────────────
72
+ # GraphRAG Engine
73
+ # ─────────────────────────────────────────────────────────────────────────────
74
+
75
+
76
+ class GraphRAGEngine:
77
+ """
78
+ GraphRAG Engine combining vector search with knowledge graphs.
79
+
80
+ Features:
81
+ - Entity extraction from MCard content
82
+ - Knowledge graph storage and traversal
83
+ - Multi-hop reasoning across entities
84
+ - Combined vector + graph context for LLM
85
+
86
+ Usage:
87
+ from clm.layer3.rag.graph import GraphRAGEngine
88
+
89
+ engine = GraphRAGEngine(vector_db_path=':memory:')
90
+
91
+ # Index with graph extraction
92
+ engine.index(mcard)
93
+
94
+ # Query with graph context
95
+ response = engine.query("How does MCard relate to PTR?")
96
+ """
97
+
98
+ def __init__(
99
+ self,
100
+ config: RAGConfig = None,
101
+ vector_db_path: str = None,
102
+ graph_db_path: str = None,
103
+ llm_model: str = "qwen3:0.6b",
104
+ ):
105
+ """
106
+ Initialize GraphRAG engine.
107
+
108
+ Args:
109
+ config: RAG configuration
110
+ vector_db_path: Path to vector database
111
+ graph_db_path: Path to graph database (default: same as vector)
112
+ llm_model: LLM model for generation
113
+ """
114
+ self.config = config or DEFAULT_RAG_CONFIG
115
+ self.llm_model = llm_model
116
+
117
+ # Initialize embedding provider
118
+ self.embedder = OllamaEmbeddingProvider(
119
+ model=self.config.embedding_model, base_url=self.config.ollama_base_url
120
+ )
121
+
122
+ # Initialize vector store
123
+ self.vector_store = MCardVectorStore(
124
+ db_path=vector_db_path, config=self.config, embedding_provider=self.embedder
125
+ )
126
+
127
+ # Initialize graph store (same DB or separate)
128
+ graph_path = graph_db_path or vector_db_path or ":memory:"
129
+ self.graph_store = GraphStore(db_path=graph_path)
130
+
131
+ # Initialize extractor
132
+ self.extractor = GraphExtractor(model=llm_model)
133
+
134
+ logger.info("Initialized GraphRAG engine")
135
+
136
+ def index(
137
+ self, mcard: MCard, extract_graph: bool = True, force: bool = False
138
+ ) -> dict[str, int]:
139
+ """
140
+ Index an MCard with optional graph extraction.
141
+
142
+ Args:
143
+ mcard: MCard to index
144
+ extract_graph: Extract entities and relationships
145
+ force: Re-index even if exists
146
+
147
+ Returns:
148
+ Dict with indexing stats
149
+ """
150
+ stats = {"vectors": 0, "entities": 0, "relationships": 0}
151
+
152
+ # Get content
153
+ content = mcard.get_content()
154
+ if isinstance(content, bytes):
155
+ try:
156
+ content = content.decode("utf-8")
157
+ except UnicodeDecodeError:
158
+ logger.warning(f"Cannot decode MCard {mcard.hash[:8]}")
159
+ return stats
160
+
161
+ # Vector indexing
162
+ if force or not self.vector_store.is_indexed(mcard.hash):
163
+ stats["vectors"] = self.vector_store.index(mcard)
164
+
165
+ # Graph extraction
166
+ if extract_graph and (force or not self.graph_store.is_extracted(mcard.hash)):
167
+ logger.debug(f"Extracting graph from {mcard.hash[:8]}...")
168
+
169
+ result = self.extractor.extract(content)
170
+
171
+ if result.success:
172
+ entity_count, rel_count = store_extraction_result(
173
+ self.graph_store, result, mcard.hash
174
+ )
175
+ stats["entities"] = entity_count
176
+ stats["relationships"] = rel_count
177
+ logger.debug(
178
+ f"Extracted {entity_count} entities, {rel_count} relationships"
179
+ )
180
+ else:
181
+ logger.warning(f"Graph extraction failed: {result.error}")
182
+
183
+ return stats
184
+
185
+ def detect_and_summarize_communities(self) -> int:
186
+ """
187
+ Detect communities and generate summaries.
188
+
189
+ Returns:
190
+ Number of communities summarized
191
+ """
192
+ from .community import CommunitySummarizer, detect_communities
193
+
194
+ logger.info("Detecting communities...")
195
+ communities = detect_communities(self.graph_store)
196
+
197
+ if not communities:
198
+ logger.info("No communities detected.")
199
+ return 0
200
+
201
+ logger.info(f"Summarizing {len(communities)} communities...")
202
+ summarizer = CommunitySummarizer(self.graph_store, model=self.llm_model)
203
+ count = summarizer.summarize_and_store(communities)
204
+
205
+ logger.info(f"Generated {count} community summaries.")
206
+ return count
207
+
208
+ def _get_vector_context(self, query: str, k: int) -> tuple[str, list[str]]:
209
+ """Get vector search context."""
210
+ results = self.vector_store.hybrid_search(query, k)
211
+
212
+ context_parts = []
213
+ sources = []
214
+
215
+ for i, result in enumerate(results):
216
+ chunk = result.chunk_text or f"[Content from {result.hash[:8]}]"
217
+ context_parts.append(f"[{i + 1}] {chunk}")
218
+ sources.append(result.hash)
219
+
220
+ return "\n\n".join(context_parts), sources
221
+
222
+ def _get_graph_context(
223
+ self, query: str, source_hashes: list[str] = None
224
+ ) -> tuple[list[dict], list[dict]]:
225
+ """
226
+ Get relevant graph context.
227
+
228
+ Searches for entities mentioned in the query and finds related entities.
229
+ """
230
+ entities = []
231
+ relationships = []
232
+ seen_entity_ids: set[int] = set()
233
+
234
+ # Search for entities matching query terms
235
+ for word in query.split():
236
+ if len(word) > 3:
237
+ found = self.graph_store.search_entities(word, limit=5)
238
+ for entity in found:
239
+ if entity["id"] not in seen_entity_ids:
240
+ seen_entity_ids.add(entity["id"])
241
+ entities.append(entity)
242
+
243
+ # Get relationships
244
+ rels_out = self.graph_store.get_relationships_from(entity["id"])
245
+ rels_in = self.graph_store.get_relationships_to(entity["id"])
246
+
247
+ for rel in rels_out:
248
+ relationships.append(
249
+ {
250
+ "source": entity["name"],
251
+ "relationship": rel["relationship"],
252
+ "target": rel["target_name"],
253
+ }
254
+ )
255
+
256
+ for rel in rels_in:
257
+ relationships.append(
258
+ {
259
+ "source": rel["source_name"],
260
+ "relationship": rel["relationship"],
261
+ "target": entity["name"],
262
+ }
263
+ )
264
+
265
+ # Also get entities from source documents
266
+ if source_hashes:
267
+ for hash_val in source_hashes[:3]: # Limit
268
+ source_entities = self.graph_store.get_entities_by_source(hash_val)
269
+ for entity in source_entities:
270
+ if entity["id"] not in seen_entity_ids:
271
+ seen_entity_ids.add(entity["id"])
272
+ entities.append(entity)
273
+
274
+ return entities, relationships
275
+
276
+ def query(
277
+ self, question: str, k: int = 5, use_graph: bool = True
278
+ ) -> GraphRAGResponse:
279
+ """
280
+ Query with GraphRAG: vector search + graph context.
281
+
282
+ Args:
283
+ question: User question
284
+ k: Number of vector results
285
+ use_graph: Include graph context
286
+
287
+ Returns:
288
+ GraphRAGResponse with answer and sources
289
+ """
290
+ # Get vector context
291
+ vector_context, sources = self._get_vector_context(question, k)
292
+
293
+ # Get graph context
294
+ entities = []
295
+ relationships = []
296
+ graph_context = ""
297
+
298
+ if use_graph:
299
+ entities, relationships = self._get_graph_context(question, sources)
300
+
301
+ if entities:
302
+ entity_strs = [f"{e['name']} ({e['type']})" for e in entities[:10]]
303
+ rel_strs = [
304
+ f"{r['source']} --{r['relationship']}-> {r['target']}"
305
+ for r in relationships[:15]
306
+ ]
307
+
308
+ graph_context = f"Entities: {', '.join(entity_strs)}\n"
309
+ graph_context += "Relationships:\n" + "\n".join(rel_strs)
310
+
311
+ # Generate answer
312
+ if not vector_context and not entities:
313
+ return GraphRAGResponse(
314
+ answer="I couldn't find relevant information.",
315
+ sources=[],
316
+ entities=[],
317
+ relationships=[],
318
+ graph_context="",
319
+ vector_context="",
320
+ confidence=0.0,
321
+ )
322
+
323
+ # Build prompt
324
+ entity_str = (
325
+ ", ".join(f"{e['name']} ({e['type']})" for e in entities)
326
+ if entities
327
+ else "None found"
328
+ )
329
+ rel_str = (
330
+ "\n".join(
331
+ f" - {r['source']} --{r['relationship']}-> {r['target']}"
332
+ for r in relationships
333
+ )
334
+ if relationships
335
+ else "None found"
336
+ )
337
+
338
+ prompt = GRAPH_QUERY_PROMPT.format(
339
+ question=question,
340
+ vector_context=vector_context or "No document context available.",
341
+ entities=entity_str,
342
+ relationships=rel_str,
343
+ )
344
+
345
+ # Call LLM
346
+ try:
347
+ from clm.layer3.agency import chat_monad
348
+
349
+ response = chat_monad(
350
+ prompt=prompt,
351
+ system_prompt=GRAPHRAG_SYSTEM_PROMPT,
352
+ model=self.llm_model,
353
+ temperature=0.3,
354
+ max_tokens=1500,
355
+ ).unsafe_run()
356
+
357
+ if response.is_right():
358
+ answer = response.value.get("content", str(response.value))
359
+ else:
360
+ answer = f"Error generating response: {response.value}"
361
+
362
+ except Exception as e:
363
+ logger.error(f"LLM generation failed: {e}", exc_info=True)
364
+ answer = f"Error: {e}"
365
+
366
+ # Calculate confidence
367
+ vec_results = self.vector_store.search(question, k)
368
+ avg_score = (
369
+ sum(r.score for r in vec_results) / len(vec_results) if vec_results else 0
370
+ )
371
+ graph_boost = 0.1 if entities else 0
372
+
373
+ return GraphRAGResponse(
374
+ answer=answer,
375
+ sources=sources,
376
+ entities=entities,
377
+ relationships=relationships,
378
+ graph_context=graph_context,
379
+ vector_context=vector_context,
380
+ confidence=min(avg_score + graph_boost, 1.0),
381
+ metadata={
382
+ "model": self.llm_model,
383
+ "entity_count": len(entities),
384
+ "relationship_count": len(relationships),
385
+ },
386
+ )
387
+
388
+ def explore_entity(self, entity_name: str, hops: int = 2) -> dict[str, Any]:
389
+ """
390
+ Explore an entity and its neighborhood.
391
+
392
+ Args:
393
+ entity_name: Entity to explore
394
+ hops: Traversal depth
395
+
396
+ Returns:
397
+ Dict with entity info and related entities
398
+ """
399
+ # Try exact match first
400
+ entity = self.graph_store.get_entity_by_name(entity_name)
401
+
402
+ # If not found, try fuzzy search
403
+ if not entity:
404
+ candidates = self.graph_store.search_entities(entity_name, limit=1)
405
+ if candidates:
406
+ entity = candidates[0]
407
+ # Update outgoing/incoming/related to use the found entity name/id
408
+
409
+ if not entity:
410
+ return {"error": f"Entity '{entity_name}' not found"}
411
+
412
+ related = self.graph_store.find_related(entity["name"], hops=hops)
413
+
414
+ return {
415
+ "entity": entity,
416
+ "related": related,
417
+ "outgoing": self.graph_store.get_relationships_from(entity["id"]),
418
+ "incoming": self.graph_store.get_relationships_to(entity["id"]),
419
+ }
420
+
421
+ def find_connection(self, source: str, target: str) -> list[str] | None:
422
+ """Find path between two entities."""
423
+ return self.graph_store.find_path(source, target)
424
+
425
+ def get_stats(self) -> dict[str, Any]:
426
+ """Get engine statistics."""
427
+ return {
428
+ "vector_count": self.vector_store.count(),
429
+ "unique_mcards": self.vector_store.count_unique(),
430
+ "graph": self.graph_store.get_stats(),
431
+ "llm_model": self.llm_model,
432
+ "embedding_model": self.config.embedding_model,
433
+ }