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,524 @@
1
+ """
2
+ LLM Runtime Module
3
+
4
+ Provides LLMRuntime (RuntimeExecutor subclass) for executing LLM prompts
5
+ as part of the PTR polyglot runtime system.
6
+ """
7
+
8
+ import json
9
+ from typing import Any
10
+
11
+ from clm.cross_cutting.config.config_constants import LLM_DEFAULT_TIMEOUT_SECS
12
+ from clm.layer0.model.card import MCard
13
+ from clm.layer0.narrative import IO, Either, Left
14
+ from clm.layer1.runtimes.base import RuntimeExecutor
15
+
16
+ from .config import DEFAULT_PROVIDER, LLMConfig
17
+ from .providers import LLMProvider, MLCLLMProvider, OllamaProvider
18
+
19
+ # ─────────────────────────────────────────────────────────────────────────────
20
+ # Provider Factory
21
+ # ─────────────────────────────────────────────────────────────────────────────
22
+
23
+
24
+ def get_provider(
25
+ provider_name: str = DEFAULT_PROVIDER,
26
+ base_url: str = None,
27
+ timeout: int = LLM_DEFAULT_TIMEOUT_SECS,
28
+ ) -> LLMProvider:
29
+ """
30
+ Get an LLM provider instance by name.
31
+
32
+ Args:
33
+ provider_name: Provider identifier ('ollama', 'lmstudio', 'openai', etc.)
34
+ base_url: Optional override for the provider's base URL
35
+ timeout: Request timeout in seconds
36
+
37
+ Returns:
38
+ LLMProvider instance
39
+ """
40
+ providers = {
41
+ "ollama": lambda: OllamaProvider(base_url, timeout),
42
+ "mlc-llm": lambda: MLCLLMProvider(base_url, timeout),
43
+ # Future: 'lmstudio': lambda: LMStudioProvider(base_url, timeout),
44
+ # Future: 'openai': lambda: OpenAIProvider(base_url, timeout, api_key),
45
+ }
46
+
47
+ factory = providers.get(provider_name)
48
+ if not factory:
49
+ raise ValueError(
50
+ f"Unknown provider: {provider_name}. Available: {list(providers.keys())}"
51
+ )
52
+
53
+ return factory()
54
+
55
+
56
+ # ─────────────────────────────────────────────────────────────────────────────
57
+ # LLM Runtime Executor
58
+ # ─────────────────────────────────────────────────────────────────────────────
59
+
60
+
61
+ class LLMRuntime(RuntimeExecutor):
62
+ """
63
+ LLM runtime executor for the PTR polyglot runtime system.
64
+
65
+ Executes LLM prompts via configurable providers (Ollama, LMStudio, OpenAI, etc.)
66
+ and integrates with the standard RuntimeFactory pattern.
67
+
68
+ Usage in PCard concrete section:
69
+ concrete:
70
+ runtime: llm
71
+ provider: ollama
72
+ model: qwen3:0.6b:27b
73
+ llm_config:
74
+ system_prompt: "You are a helpful assistant."
75
+ temperature: 0.7
76
+ max_tokens: 1024
77
+ """
78
+
79
+ runtime_name = "llm"
80
+
81
+ def __init__(self, provider_name: str = DEFAULT_PROVIDER):
82
+ super().__init__()
83
+ self.provider_name = provider_name
84
+ self._provider: LLMProvider | None = None
85
+
86
+ @property
87
+ def provider(self) -> LLMProvider:
88
+ """Lazy-load provider on first access."""
89
+ if self._provider is None:
90
+ self._provider = get_provider(self.provider_name)
91
+ return self._provider
92
+
93
+ def execute(
94
+ self, concrete_impl: dict[str, Any], target: MCard, context: dict[str, Any]
95
+ ) -> Any:
96
+ """
97
+ Execute LLM prompt based on concrete implementation.
98
+
99
+ Args:
100
+ concrete_impl: Concrete section from PCard containing llm_config
101
+ target: Target MCard containing the prompt/input
102
+ context: Execution context with optional overrides
103
+
104
+ Returns:
105
+ LLM response (string or structured data based on response_format)
106
+ """
107
+ # Build configuration from concrete and context
108
+ config = LLMConfig.from_concrete(concrete_impl, context)
109
+
110
+ # Update provider if different from init
111
+ if config.provider != self.provider_name:
112
+ self._provider = get_provider(
113
+ config.provider, config.endpoint_url, config.timeout
114
+ )
115
+
116
+ # Get prompt from target
117
+ prompt = self._extract_prompt(target, context)
118
+
119
+ from .router import IntelligenceRouter
120
+
121
+ router = IntelligenceRouter()
122
+
123
+ def attempt_execution(cfg: LLMConfig) -> Either[str, Any]:
124
+ if cfg.provider != self.provider_name:
125
+ self._provider = get_provider(
126
+ cfg.provider, cfg.endpoint_url, cfg.timeout
127
+ )
128
+ if cfg.system_prompt:
129
+ return self._execute_chat(prompt, cfg)
130
+ else:
131
+ return self._execute_completion(prompt, cfg)
132
+
133
+ # Attempt primary execution
134
+ result = attempt_execution(config)
135
+
136
+ # Intelligence Router Intercept / Fallback
137
+ if result.is_left():
138
+ # If it's a generic connection/timeout/failed error, trigger fallback
139
+ err_msg = str(result.value).lower()
140
+ if "timeout" in err_msg or "connection" in err_msg or "failed" in err_msg:
141
+ # Ask router for failover setup
142
+ fb_provider, fb_model, fb_url, warning = router.handle_failure(
143
+ config.provider, Exception(result.value)
144
+ )
145
+
146
+ # Push warning to context for the _post phase to map into VCard
147
+ if "execution_warnings" not in context:
148
+ context["execution_warnings"] = []
149
+ context["execution_warnings"].append(warning)
150
+
151
+ # Reconfigure and retry silently
152
+ config.provider = fb_provider
153
+ config.model = fb_model
154
+ config.endpoint_url = fb_url
155
+ self.provider_name = fb_provider
156
+ self._provider = None # Invalidate cached provider
157
+ result = attempt_execution(config)
158
+
159
+ # Handle result
160
+ if result.is_left():
161
+ return f"Error: {result.value}"
162
+
163
+ return self._format_response(result.value, config, context)
164
+
165
+ def _extract_prompt(self, target: MCard, context: dict) -> str:
166
+ """Extract prompt from target MCard, enriched with context variables.
167
+
168
+ When test cases provide context variables (e.g. a=5, b=10), they are
169
+ serialised as JSON and appended to the prompt so the LLM can see
170
+ the actual input values.
171
+ """
172
+ content = target.get_content()
173
+ if isinstance(content, bytes):
174
+ content = content.decode("utf-8")
175
+ else:
176
+ content = str(content)
177
+
178
+ # Enrich prompt with context variables (filter out internal/structural keys)
179
+ ctx_vars = {}
180
+ excluded_keys = {
181
+ # LLM config keys
182
+ "system_prompt",
183
+ "temperature",
184
+ "max_tokens",
185
+ "model",
186
+ "provider",
187
+ "response_format",
188
+ "endpoint_url",
189
+ "timeout",
190
+ "assistant_instruction",
191
+ "operation",
192
+ # Structural CLM / PCard metadata
193
+ "runtime",
194
+ "llm_config",
195
+ "input_type",
196
+ "output_type",
197
+ "process_type",
198
+ "boundary",
199
+ "builtin",
200
+ "config",
201
+ # Execution infrastructure
202
+ "balanced",
203
+ "abstract",
204
+ "concrete",
205
+ "db_dir",
206
+ "pcard_dir",
207
+ "identities_db",
208
+ "content_db",
209
+ "log_db",
210
+ "execute_cleanup",
211
+ }
212
+ for k, v in context.items():
213
+ if k.startswith("_") or k in excluded_keys:
214
+ continue
215
+ try:
216
+ # Test serializability before inclusion
217
+ json.dumps(v)
218
+ ctx_vars[k] = v
219
+ except (TypeError, ValueError):
220
+ continue
221
+
222
+ if ctx_vars:
223
+ content = f"{content}\n\nInput: {json.dumps(ctx_vars)}"
224
+
225
+ return content
226
+
227
+ def _execute_completion(self, prompt: str, config: LLMConfig) -> Either[str, str]:
228
+ """Execute text completion."""
229
+ params = config.to_provider_params()
230
+ return self.provider.complete(prompt, params)
231
+
232
+ def _execute_chat(self, prompt: str, config: LLMConfig) -> Either[str, dict]:
233
+ """Execute chat completion with system prompt."""
234
+ messages = []
235
+
236
+ # Add system prompt
237
+ if config.system_prompt:
238
+ messages.append({"role": "system", "content": config.system_prompt})
239
+
240
+ # Add user message
241
+ messages.append({"role": "user", "content": prompt})
242
+
243
+ # Add assistant instruction (prefill) if provided
244
+ if config.assistant_instruction:
245
+ messages.append(
246
+ {"role": "assistant", "content": config.assistant_instruction}
247
+ )
248
+
249
+ params = config.to_provider_params()
250
+ return self.provider.chat(messages, params)
251
+
252
+ def _format_response(
253
+ self, response: Any, config: LLMConfig, context: dict = None
254
+ ) -> Any:
255
+ """Format response based on response_format setting and extract thinking."""
256
+ # Extract content from chat response
257
+ if isinstance(response, dict):
258
+ content = response.get("content", response)
259
+ else:
260
+ content = response
261
+
262
+ # Surface native thinking field from thinking models (e.g. qwen3:0.6b)
263
+ # Ollama returns thinking as a separate field, distinct from <think> blocks in content
264
+ if isinstance(response, dict) and "thinking" in response:
265
+ thinking_native = response["thinking"]
266
+ if isinstance(thinking_native, str) and thinking_native:
267
+ if context is not None:
268
+ context["thinking_process"] = thinking_native
269
+
270
+ # Isolate thinking process for thinking models (e.g. qwen3:0.6b/deepseek)
271
+ thinking_process = None
272
+ if isinstance(content, str):
273
+ think_start = content.find("<think>")
274
+ think_end = content.find("</think>")
275
+ if think_start >= 0 and think_end > think_start:
276
+ thinking_process = content[think_start + 7 : think_end].strip()
277
+ content = content[:think_start] + content[think_end + 8 :].strip()
278
+
279
+ # Expose it in the context dictionary for VCard evaluation/logging
280
+ if context is not None:
281
+ context["thinking_process"] = thinking_process
282
+
283
+ # If we were given a response dict, update it incase it is returned unmodified
284
+ if isinstance(response, dict):
285
+ response["thinking_process"] = thinking_process
286
+ response["content"] = content
287
+
288
+ # Format based on config
289
+ if config.response_format == "json":
290
+ try:
291
+ # Try to parse as JSON
292
+ if isinstance(content, str):
293
+ # Find JSON in response
294
+ start = content.find("{")
295
+ end = content.rfind("}") + 1
296
+ if start >= 0 and end > start:
297
+ return json.loads(content[start:end])
298
+ return content
299
+ except json.JSONDecodeError:
300
+ return content
301
+
302
+ return content
303
+
304
+ def validate_environment(self) -> bool:
305
+ """Check if the LLM provider is available."""
306
+ try:
307
+ return self.provider.validate_connection()
308
+ except (OSError, ConnectionError, ImportError):
309
+ return False
310
+
311
+ def get_runtime_status(self) -> dict[str, Any]:
312
+ """Get LLM runtime status with provider info."""
313
+ try:
314
+ available = self.validate_environment()
315
+ models = []
316
+
317
+ if available:
318
+ models_result = self.provider.list_models()
319
+ if models_result.is_right():
320
+ models = models_result.value
321
+
322
+ return {
323
+ "available": available,
324
+ "version": (
325
+ f"{self.provider_name} ({len(models)} models)"
326
+ if available
327
+ else None
328
+ ),
329
+ "provider": self.provider_name,
330
+ "models": models,
331
+ "command": (
332
+ "ollama" if self.provider_name == "ollama" else self.provider_name
333
+ ),
334
+ "details": f"LLM Runtime via {self.provider_name}",
335
+ }
336
+ except (OSError, ConnectionError, ImportError, ValueError) as e:
337
+ return {
338
+ "available": False,
339
+ "version": None,
340
+ "provider": self.provider_name,
341
+ "models": [],
342
+ "command": self.provider_name,
343
+ "details": f"Error: {e}",
344
+ }
345
+
346
+
347
+ # ─────────────────────────────────────────────────────────────────────────────
348
+ # Monadic Interface Functions
349
+ # ─────────────────────────────────────────────────────────────────────────────
350
+
351
+
352
+ def prompt_monad(
353
+ prompt: str, config: LLMConfig = None, **kwargs
354
+ ) -> IO[Either[str, str]]:
355
+ """
356
+ Create a monadic LLM completion execution.
357
+
358
+ Returns IO[Either[Error, Response]] for functional composition.
359
+
360
+ Usage:
361
+ result = prompt_monad("Explain monads", temperature=0.5).unsafe_run()
362
+ if result.is_right():
363
+ print(result.value)
364
+
365
+ Args:
366
+ prompt: The input prompt
367
+ config: Optional LLMConfig (uses defaults if not provided)
368
+ **kwargs: Override config parameters
369
+
370
+ Returns:
371
+ IO[Either[str, str]]: Monadic wrapper around the LLM call
372
+ """
373
+ if config is None:
374
+ config = LLMConfig(**kwargs) if kwargs else LLMConfig()
375
+ elif kwargs:
376
+ # Merge kwargs into config
377
+ config_dict = config.to_dict()
378
+ config_dict.update(kwargs)
379
+ config = LLMConfig.from_dict(config_dict)
380
+
381
+ def run() -> Either[str, str]:
382
+ try:
383
+ runtime = LLMRuntime(config.provider)
384
+
385
+ # Simple wrapper context to reuse the `execute` retry loop if needed natively,
386
+ # or we manually handle IntelligenceRouter here as well.
387
+ from .router import IntelligenceRouter
388
+
389
+ router = IntelligenceRouter()
390
+
391
+ def attempt_execution(cfg):
392
+ return runtime._execute_completion(prompt, cfg)
393
+
394
+ result = attempt_execution(config)
395
+
396
+ if result.is_left():
397
+ err_msg = str(result.value).lower()
398
+ if (
399
+ "timeout" in err_msg
400
+ or "connection" in err_msg
401
+ or "failed" in err_msg
402
+ ):
403
+ fb_provider, fb_model, fb_url, warning = router.handle_failure(
404
+ config.provider, Exception(result.value)
405
+ )
406
+ config.provider = fb_provider
407
+ config.model = fb_model
408
+ config.endpoint_url = fb_url
409
+ runtime = LLMRuntime(fb_provider)
410
+ result = attempt_execution(config)
411
+
412
+ return result
413
+ except (ValueError, TypeError, RuntimeError, ConnectionError, OSError) as e:
414
+ return Left(f"LLM execution failed: {e}")
415
+
416
+ return IO(run)
417
+
418
+
419
+ def chat_monad(
420
+ messages: list[dict[str, str]] = None,
421
+ prompt: str = None,
422
+ system_prompt: str = "",
423
+ config: LLMConfig = None,
424
+ **kwargs,
425
+ ) -> IO[Either[str, dict]]:
426
+ """
427
+ Create a monadic LLM chat execution.
428
+
429
+ Returns IO[Either[Error, Response]] for functional composition.
430
+
431
+ Usage:
432
+ result = chat_monad(
433
+ prompt="What is Python?",
434
+ system_prompt="You are a programming tutor.",
435
+ model="llama3:latest"
436
+ ).unsafe_run()
437
+
438
+ if result.is_right():
439
+ print(result.value['content'])
440
+
441
+ Args:
442
+ messages: Optional list of message dicts (overrides prompt/system_prompt)
443
+ prompt: User prompt (used if messages not provided)
444
+ system_prompt: System prompt (used if messages not provided)
445
+ config: Optional LLMConfig
446
+ **kwargs: Override config parameters
447
+
448
+ Returns:
449
+ IO[Either[str, Dict]]: Monadic wrapper around the chat call
450
+ """
451
+ # Build config
452
+ kwargs.setdefault("system_prompt", system_prompt)
453
+
454
+ if config is None:
455
+ config = LLMConfig(**kwargs) if kwargs else LLMConfig()
456
+ elif kwargs:
457
+ config_dict = config.to_dict()
458
+ config_dict.update(kwargs)
459
+ config = LLMConfig.from_dict(config_dict)
460
+
461
+ # Build messages
462
+ if messages is None:
463
+ messages = []
464
+ if config.system_prompt:
465
+ messages.append({"role": "system", "content": config.system_prompt})
466
+ if prompt:
467
+ messages.append({"role": "user", "content": prompt})
468
+
469
+ def run() -> Either[str, dict]:
470
+ try:
471
+ from .router import IntelligenceRouter
472
+
473
+ router = IntelligenceRouter()
474
+
475
+ def attempt_execution(cfg):
476
+ provider = get_provider(cfg.provider, cfg.endpoint_url, cfg.timeout)
477
+ params = cfg.to_provider_params()
478
+ return provider.chat(messages, params)
479
+
480
+ result = attempt_execution(config)
481
+
482
+ if result.is_left():
483
+ err_msg = str(result.value).lower()
484
+ if (
485
+ "timeout" in err_msg
486
+ or "connection" in err_msg
487
+ or "failed" in err_msg
488
+ ):
489
+ fb_provider, fb_model, fb_url, warning = router.handle_failure(
490
+ config.provider, Exception(result.value)
491
+ )
492
+ config.provider = fb_provider
493
+ config.model = fb_model
494
+ config.endpoint_url = fb_url
495
+ result = attempt_execution(config)
496
+
497
+ if result.is_right():
498
+ response = result.value
499
+ content = response.get("content", "")
500
+ if isinstance(content, str):
501
+ think_start = content.find("<think>")
502
+ think_end = content.find("</think>")
503
+ if think_start >= 0 and think_end > think_start:
504
+ response["thinking_process"] = content[
505
+ think_start + 7 : think_end
506
+ ].strip()
507
+ response["content"] = (
508
+ content[:think_start] + content[think_end + 8 :].strip()
509
+ )
510
+
511
+ return result
512
+ except (ValueError, TypeError, RuntimeError, ConnectionError, OSError) as e:
513
+ return Left(f"LLM chat failed: {e}")
514
+
515
+ return IO(run)
516
+
517
+
518
+ # Register with Layer 1 runtime factory (INV-383-01 / DEL-383-04)
519
+ try:
520
+ from clm.layer1.runtimes.factory import register_external_executor
521
+
522
+ register_external_executor("llm", LLMRuntime)
523
+ except ImportError:
524
+ pass