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,203 @@
1
+ """
2
+ Ollama Provider Implementation
3
+
4
+ Provider for the Ollama local LLM service.
5
+ https://ollama.ai/
6
+ """
7
+
8
+ import json
9
+ import logging
10
+ import urllib.error
11
+ import urllib.request
12
+ from typing import Any
13
+
14
+ from clm.cross_cutting.config.config_constants import LLM_DEFAULT_TIMEOUT_SECS
15
+ from clm.cross_cutting.utils.url_safety import safe_urlopen
16
+ from clm.layer0.narrative import Either, Left, Right
17
+
18
+ from ..config import LLM_PROVIDERS
19
+ from .base import LLMProvider
20
+
21
+
22
+ class OllamaProvider(LLMProvider):
23
+ """
24
+ Ollama LLM provider for local model execution.
25
+
26
+ Ollama provides a simple API for running LLMs locally:
27
+ - /api/generate - Text completion
28
+ - /api/chat - Chat completion
29
+ - /api/tags - List available models
30
+ """
31
+
32
+ provider_name = "ollama"
33
+
34
+ def __init__(self, base_url: str = None, timeout: int = LLM_DEFAULT_TIMEOUT_SECS):
35
+ self.logger = logging.getLogger(__name__)
36
+ self.config = LLM_PROVIDERS["ollama"]
37
+ self.base_url = (base_url or self.config["base_url"]).rstrip("/")
38
+ self.timeout = timeout
39
+
40
+ def _make_request(
41
+ self, endpoint: str, data: dict = None, method: str = "POST"
42
+ ) -> Either[str, dict]:
43
+ """Make HTTP request to Ollama API."""
44
+ url = f"{self.base_url}{endpoint}"
45
+
46
+ try:
47
+ if data:
48
+ request = urllib.request.Request(
49
+ url,
50
+ data=json.dumps(data).encode("utf-8"),
51
+ headers={"Content-Type": "application/json"},
52
+ method=method,
53
+ )
54
+ else:
55
+ request = urllib.request.Request(url, method=method)
56
+
57
+ with safe_urlopen(request, timeout=self.timeout) as response:
58
+ response_text = response.read().decode("utf-8")
59
+
60
+ # Handle streaming response (multiple JSON objects)
61
+ if "\n" in response_text and not response_text.strip().startswith("{"):
62
+ # Parse last complete response for final result
63
+ lines = [line for line in response_text.strip().split("\n") if line]
64
+ if lines:
65
+ return Right(json.loads(lines[-1]))
66
+
67
+ return Right(json.loads(response_text))
68
+
69
+ except urllib.error.HTTPError as e:
70
+ error_body = e.read().decode("utf-8") if e.fp else str(e)
71
+ return Left(f"Ollama HTTP error {e.code}: {error_body}")
72
+ except urllib.error.URLError as e:
73
+ return Left(f"Ollama connection error: {e.reason}")
74
+ except json.JSONDecodeError as e:
75
+ return Left(f"Ollama response parse error: {e}")
76
+ except (OSError, ConnectionError, ValueError) as e:
77
+ return Left(f"Ollama request failed: {e}")
78
+
79
+ def complete(self, prompt: str, params: dict[str, Any]) -> Either[str, str]:
80
+ """
81
+ Generate text completion using Ollama's /api/generate endpoint.
82
+
83
+ Args:
84
+ prompt: The input prompt
85
+ params: Generation parameters (model, options, etc.)
86
+
87
+ Returns:
88
+ Either[Error, Completion]
89
+ """
90
+ data = {
91
+ "model": params.get("model", self.config["default_model"]),
92
+ "prompt": prompt,
93
+ "stream": False, # Disable streaming for simpler handling
94
+ }
95
+
96
+ # Add options if present
97
+ if "options" in params:
98
+ data["options"] = params["options"]
99
+
100
+ self.logger.debug(f"Ollama generate request: model={data['model']}")
101
+
102
+ result = self._make_request(self.config["api_path"], data)
103
+
104
+ if result.is_left():
105
+ return result
106
+
107
+ response = result.value
108
+ if "response" in response:
109
+ return Right(response["response"])
110
+ elif "error" in response:
111
+ return Left(f"Ollama error: {response['error']}")
112
+ else:
113
+ return Left(f"Unexpected Ollama response format: {response}")
114
+
115
+ def chat(
116
+ self, messages: list[dict[str, str]], params: dict[str, Any]
117
+ ) -> Either[str, dict[str, Any]]:
118
+ """
119
+ Generate chat completion using Ollama's /api/chat endpoint.
120
+
121
+ Args:
122
+ messages: List of message dicts with 'role' and 'content'
123
+ params: Generation parameters
124
+
125
+ Returns:
126
+ Either[Error, ResponseDict]
127
+ """
128
+ data = {
129
+ "model": params.get("model", self.config["default_model"]),
130
+ "messages": messages,
131
+ "stream": False,
132
+ }
133
+
134
+ # Add options if present
135
+ if "options" in params:
136
+ data["options"] = params["options"]
137
+
138
+ self.logger.debug(
139
+ f"Ollama chat request: model={data['model']}, messages={len(messages)}"
140
+ )
141
+
142
+ result = self._make_request(self.config["chat_path"], data)
143
+
144
+ if result.is_left():
145
+ return result
146
+
147
+ response = result.value
148
+ if "message" in response:
149
+ chat_result = {
150
+ "content": response["message"].get("content", ""),
151
+ "role": response["message"].get("role", "assistant"),
152
+ "model": response.get("model", data["model"]),
153
+ "done": response.get("done", True),
154
+ "done_reason": response.get("done_reason"),
155
+ "total_duration": response.get("total_duration"),
156
+ "eval_count": response.get("eval_count"),
157
+ }
158
+
159
+ # Surface thinking field from thinking models (e.g. qwen3:0.6b)
160
+ # Ollama returns thinking as a separate field in the message,
161
+ # distinct from the <think> blocks in content.
162
+ if "thinking" in response["message"]:
163
+ chat_result["thinking"] = response["message"]["thinking"]
164
+
165
+ return Right(chat_result)
166
+ elif "error" in response:
167
+ return Left(f"Ollama error: {response['error']}")
168
+ else:
169
+ return Left(f"Unexpected Ollama chat response format: {response}")
170
+
171
+ def validate_connection(self) -> bool:
172
+ """Check if Ollama service is running."""
173
+ try:
174
+ result = self._make_request(self.config["models_path"], method="GET")
175
+ return result.is_right()
176
+ except (OSError, ConnectionError):
177
+ return False
178
+
179
+ def list_models(self) -> Either[str, list[str]]:
180
+ """List available models in Ollama."""
181
+ result = self._make_request(self.config["models_path"], method="GET")
182
+
183
+ if result.is_left():
184
+ return result
185
+
186
+ response = result.value
187
+ if "models" in response:
188
+ models = [
189
+ m.get("name", m.get("model", "unknown")) for m in response["models"]
190
+ ]
191
+ return Right(models)
192
+ else:
193
+ return Left(f"Unexpected models response: {response}")
194
+
195
+ def pull_model(self, model_name: str) -> Either[str, str]:
196
+ """Pull a model from Ollama library."""
197
+ data = {"name": model_name, "stream": False}
198
+ result = self._make_request("/api/pull", data)
199
+
200
+ if result.is_left():
201
+ return result
202
+
203
+ return Right(f"Model {model_name} pulled successfully")
@@ -0,0 +1,71 @@
1
+ import logging
2
+
3
+ from clm.cross_cutting.config.config_constants import (
4
+ DEFAULT_FALLBACK_MODEL,
5
+ DEFAULT_OLLAMA_BASE_URL,
6
+ )
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class IntelligenceRouter:
12
+ """
13
+ IntelligenceRouter handles Adaptive Intelligence Routing across LLM providers.
14
+
15
+ Complies with the 'Local-First Sovereign AI' mandate by connection probing
16
+ and automatically determining the most appropriate fallback when primary
17
+ cloud pipelines are incapacitated.
18
+ """
19
+
20
+ def __init__(self):
21
+ # By default we configure local ollama for failover
22
+ self.local_url = DEFAULT_OLLAMA_BASE_URL
23
+ self.local_model = DEFAULT_FALLBACK_MODEL
24
+
25
+ def probe_connection(self, provider_url: str, timeout: float = 2.0) -> bool:
26
+ """
27
+ Probe a remote provider URL recursively.
28
+ Returns True if reachable within the timeout.
29
+ """
30
+ import httpx
31
+
32
+ try:
33
+ # We use a lightweight GET over the base URL.
34
+ # While some APIs require deeper paths, an error 404 or 401 is still
35
+ # a network connection success, while a Timeout isn't.
36
+ httpx.get(provider_url, timeout=timeout)
37
+ return True
38
+ except httpx.RequestError as e:
39
+ logger.warning(
40
+ f"[IntelligenceRouter] Connection Probe Failed for {provider_url}: {str(e)}"
41
+ )
42
+ return False
43
+
44
+ def get_optimal_provider(
45
+ self,
46
+ requested_provider: str,
47
+ requested_model: str,
48
+ requested_url: str | None = None,
49
+ ) -> tuple[str, str, str | None]:
50
+ """
51
+ Returns the (provider, model, base_url) that is optimal.
52
+ For now, this simply favors the requested configuration unless
53
+ a failure explicitly overrides it.
54
+ """
55
+ return requested_provider, requested_model, requested_url
56
+
57
+ def handle_failure(
58
+ self, failed_provider: str, error: Exception
59
+ ) -> tuple[str, str, str | None, str]:
60
+ """
61
+ Returns the fallback configuration tuple and a warning message to store.
62
+ (fallback_provider, fallback_model, fallback_url, warning_message)
63
+ """
64
+ msg = (
65
+ f"Adaptive Fallback Triggered: Provider '{failed_provider}' "
66
+ f"failed with {type(error).__name__}. Defaulting to Edge Fallback "
67
+ f"[{self.local_model}] via Ollama."
68
+ )
69
+ logger.warning(f"[IntelligenceRouter] {msg}")
70
+
71
+ return "ollama", self.local_model, self.local_url, msg