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.
- clm/__init__.py +230 -0
- clm/__main__.py +25 -0
- clm/cross_cutting/__init__.py +50 -0
- clm/cross_cutting/_test_runner.py +36 -0
- clm/cross_cutting/async_utils.py +25 -0
- clm/cross_cutting/cel_builder.py +123 -0
- clm/cross_cutting/cel_converter.py +697 -0
- clm/cross_cutting/cel_engine.py +252 -0
- clm/cross_cutting/cel_ops.py +72 -0
- clm/cross_cutting/cel_ops_db.py +157 -0
- clm/cross_cutting/cel_ops_files.py +99 -0
- clm/cross_cutting/cel_ops_state.py +71 -0
- clm/cross_cutting/cel_ops_strings.py +64 -0
- clm/cross_cutting/cel_resolver.py +63 -0
- clm/cross_cutting/cel_slices.py +56 -0
- clm/cross_cutting/cel_store.py +330 -0
- clm/cross_cutting/clm_logger.py +140 -0
- clm/cross_cutting/config/__init__.py +155 -0
- clm/cross_cutting/config/config_constants.py +392 -0
- clm/cross_cutting/config/env_parameters.py +90 -0
- clm/cross_cutting/config/logging.py +378 -0
- clm/cross_cutting/config/settings.py +504 -0
- clm/cross_cutting/context.py +5 -0
- clm/cross_cutting/domain_models.py +55 -0
- clm/cross_cutting/domain_types.py +61 -0
- clm/cross_cutting/effects.py +67 -0
- clm/cross_cutting/errors.py +153 -0
- clm/cross_cutting/governance.py +331 -0
- clm/cross_cutting/guards.py +41 -0
- clm/cross_cutting/mcard.py +321 -0
- clm/cross_cutting/native.py +185 -0
- clm/cross_cutting/observability.py +289 -0
- clm/cross_cutting/pcard.py +434 -0
- clm/cross_cutting/registry.py +71 -0
- clm/cross_cutting/resources.py +84 -0
- clm/cross_cutting/secrets.py +44 -0
- clm/cross_cutting/subprocess_utils.py +118 -0
- clm/cross_cutting/telemetry.py +164 -0
- clm/cross_cutting/testing/__init__.py +8 -0
- clm/cross_cutting/testing/comparator.py +187 -0
- clm/cross_cutting/testing/datasets.py +87 -0
- clm/cross_cutting/timeutil.py +20 -0
- clm/cross_cutting/types/new_type_check.py +0 -0
- clm/cross_cutting/utils/__init__.py +1 -0
- clm/cross_cutting/utils/url_safety.py +50 -0
- clm/cross_cutting/vcard.py +560 -0
- clm/cross_cutting/writer.py +118 -0
- clm/layer0/README.md +50 -0
- clm/layer0/__init__.py +223 -0
- clm/layer0/algebra.py +60 -0
- clm/layer0/algebra_baldwin.py +133 -0
- clm/layer0/algebra_baldwin_generators.py +36 -0
- clm/layer0/algebra_equivalence.py +262 -0
- clm/layer0/algebra_ingest.py +306 -0
- clm/layer0/algebra_manifest_io.py +156 -0
- clm/layer0/assembler.py +481 -0
- clm/layer0/cel/__init__.py +23 -0
- clm/layer0/cel_eval.py +491 -0
- clm/layer0/codec.py +106 -0
- clm/layer0/context.py +122 -0
- clm/layer0/db.py +160 -0
- clm/layer0/exceptions.py +73 -0
- clm/layer0/fibration.py +106 -0
- clm/layer0/gates.py +50 -0
- clm/layer0/hash.py +80 -0
- clm/layer0/loader.py +826 -0
- clm/layer0/math_utils.py +163 -0
- clm/layer0/mcard.py +96 -0
- clm/layer0/model/__init__.py +0 -0
- clm/layer0/model/action.py +223 -0
- clm/layer0/model/card.py +280 -0
- clm/layer0/model/card_triad.py +427 -0
- clm/layer0/model/compatibility.py +60 -0
- clm/layer0/model/dictionary.py +46 -0
- clm/layer0/model/dots.py +331 -0
- clm/layer0/model/event_producer.py +88 -0
- clm/layer0/model/g_time.py +89 -0
- clm/layer0/model/handle.py +163 -0
- clm/layer0/model/hash/__init__.py +6 -0
- clm/layer0/model/hash/algorithms/__init__.py +3 -0
- clm/layer0/model/hash/algorithms/custom_hash.py +7 -0
- clm/layer0/model/hash/algorithms/local_sha256.py +178 -0
- clm/layer0/model/hash/constants.py +53 -0
- clm/layer0/model/hash/enums.py +60 -0
- clm/layer0/model/hash/validator.py +307 -0
- clm/layer0/model/interpreter.py +275 -0
- clm/layer0/model/pagination.py +45 -0
- clm/layer0/model/pcard.py +716 -0
- clm/layer0/model/schema.py +44 -0
- clm/layer0/model/utils/__init__.py +1 -0
- clm/layer0/model/utils/content_analyzer.py +152 -0
- clm/layer0/model/validators/__init__.py +1 -0
- clm/layer0/model/validators/base_validator.py +44 -0
- clm/layer0/model/validators/binary_validator.py +66 -0
- clm/layer0/model/validators/text_validator.py +82 -0
- clm/layer0/model/validators/validation_registry.py +49 -0
- clm/layer0/model/vcard.py +823 -0
- clm/layer0/model/vcard_ext/__init__.py +39 -0
- clm/layer0/model/vcard_ext/core.py +60 -0
- clm/layer0/model/vcard_ext/network.py +23 -0
- clm/layer0/model/vcard_ext/observability.py +63 -0
- clm/layer0/model/vcard_ext/storage.py +60 -0
- clm/layer0/model/vcard_ext/vendors.py +211 -0
- clm/layer0/model/vcard_sandwich.py +119 -0
- clm/layer0/model/vcard_vocabulary.py +463 -0
- clm/layer0/model/workflow.py +274 -0
- clm/layer0/narrative.py +431 -0
- clm/layer0/ontology/collection.py +125 -0
- clm/layer0/parser.py +429 -0
- clm/layer0/relations.py +73 -0
- clm/layer0/rule_evaluator.py +251 -0
- clm/layer0/schema/__init__.py +38 -0
- clm/layer0/schema/ddl.py +97 -0
- clm/layer0/templates/abstract.yaml +99 -0
- clm/layer0/templates/balanced.yaml +253 -0
- clm/layer0/templates/concrete.yaml +172 -0
- clm/layer0/type_registry.py +223 -0
- clm/layer0/types.py +204 -0
- clm/layer0/utils.py +886 -0
- clm/layer0/verifier_core.py +262 -0
- clm/layer1/__init__.py +31 -0
- clm/layer1/action_dispatcher.py +275 -0
- clm/layer1/adapter_registry.py +233 -0
- clm/layer1/algebra_baldwin_generators.py +192 -0
- clm/layer1/arrow_compiler.py +525 -0
- clm/layer1/batch_processor.py +152 -0
- clm/layer1/cel/__init__.py +27 -0
- clm/layer1/cel/builder.py +173 -0
- clm/layer1/engine.py +759 -0
- clm/layer1/engine_async.py +89 -0
- clm/layer1/eval_callback.py +49 -0
- clm/layer1/gate_composer.py +229 -0
- clm/layer1/hoare_sandwich.py +298 -0
- clm/layer1/identity_guard.py +348 -0
- clm/layer1/matrix.py +138 -0
- clm/layer1/neural_runtime.py +221 -0
- clm/layer1/operations/__init__.py +123 -0
- clm/layer1/operations/augment.py +156 -0
- clm/layer1/operations/base.py +109 -0
- clm/layer1/operations/builtins.py +338 -0
- clm/layer1/operations/dispatch.py +144 -0
- clm/layer1/operations/exclude.py +107 -0
- clm/layer1/operations/handle.py +197 -0
- clm/layer1/operations/identity.py +139 -0
- clm/layer1/operations/invert.py +66 -0
- clm/layer1/operations/loader.py +116 -0
- clm/layer1/operations/manipulators/__init__.py +23 -0
- clm/layer1/operations/manipulators/document.py +211 -0
- clm/layer1/operations/manipulators/media.py +134 -0
- clm/layer1/operations/manipulators/structured.py +159 -0
- clm/layer1/operations/manipulators/tabular.py +334 -0
- clm/layer1/operations/port.py +65 -0
- clm/layer1/operations/services.py +529 -0
- clm/layer1/operations/split.py +117 -0
- clm/layer1/operations/substitute.py +75 -0
- clm/layer1/petri.py +1052 -0
- clm/layer1/pocketflow_orchestrator.py +408 -0
- clm/layer1/pocketflow_scheduler.py +519 -0
- clm/layer1/protocols.py +197 -0
- clm/layer1/runtime_adapter.py +718 -0
- clm/layer1/runtimes/__init__.py +71 -0
- clm/layer1/runtimes/_generated_assertions.py +125 -0
- clm/layer1/runtimes/_generated_types.py +42 -0
- clm/layer1/runtimes/base.py +265 -0
- clm/layer1/runtimes/binary.py +101 -0
- clm/layer1/runtimes/factory.py +203 -0
- clm/layer1/runtimes/javascript.py +321 -0
- clm/layer1/runtimes/javascript_runtime.js +33 -0
- clm/layer1/runtimes/js_scripts/isomorphism.mjs +22 -0
- clm/layer1/runtimes/js_scripts/loader.mjs +52 -0
- clm/layer1/runtimes/lambda_calc.py +126 -0
- clm/layer1/runtimes/python.py +288 -0
- clm/layer1/runtimes/python_runtime.py +41 -0
- clm/layer1/runtimes/script.py +114 -0
- clm/layer1/sandbox.py +334 -0
- clm/layer1/savepoint_guard.py +136 -0
- clm/layer1/sparse_net.py +122 -0
- clm/layer1/spec_tester.py +165 -0
- clm/layer1/store.py +295 -0
- clm/layer1/test_runner.py +498 -0
- clm/layer1/type_inspector.py +166 -0
- clm/layer1/verifier.py +772 -0
- clm/layer1/vm.py +91 -0
- clm/layer2/__init__.py +78 -0
- clm/layer2/card_collection.py +678 -0
- clm/layer2/classifier.py +479 -0
- clm/layer2/engine/__init__.py +1 -0
- clm/layer2/engine/abstract_sql_engine.py +320 -0
- clm/layer2/engine/base.py +193 -0
- clm/layer2/engine/duckdb_engine.py +576 -0
- clm/layer2/engine/sqlite_engine.py +518 -0
- clm/layer2/fiber/__init__.py +31 -0
- clm/layer2/fiber/context.py +89 -0
- clm/layer2/fiber/disposable.py +163 -0
- clm/layer2/fiber/fiber.py +125 -0
- clm/layer2/fiber/hoare.py +191 -0
- clm/layer2/file_io.py +376 -0
- clm/layer2/file_registrar.py +77 -0
- clm/layer2/ingest.py +202 -0
- clm/layer2/lineage.py +322 -0
- clm/layer2/mcard_fs.py +262 -0
- clm/layer2/merkle.py +11 -0
- clm/layer2/mime.py +244 -0
- clm/layer2/mime_detector.py +245 -0
- clm/layer2/private_collection.py +118 -0
- clm/layer2/shared_store.py +11 -0
- clm/layer2/storage/__init__.py +190 -0
- clm/layer2/storage/db_bitemporal.py +141 -0
- clm/layer2/storage/db_driver.py +331 -0
- clm/layer2/storage/db_hyperlinks.py +214 -0
- clm/layer2/storage/db_pool.py +139 -0
- clm/layer2/storage/db_repository.py +229 -0
- clm/layer2/storage/savepoint.py +78 -0
- clm/layer2/storage/sqlite_wal.py +74 -0
- clm/layer2/tridb.py +245 -0
- clm/layer2/vcard_receipt.py +71 -0
- clm/layer2/vfs.py +17 -0
- clm/layer3/__init__.py +141 -0
- clm/layer3/agency/__init__.py +19 -0
- clm/layer3/agency/config.py +220 -0
- clm/layer3/agency/providers/__init__.py +15 -0
- clm/layer3/agency/providers/base.py +91 -0
- clm/layer3/agency/providers/mlc_llm.py +169 -0
- clm/layer3/agency/providers/ollama.py +203 -0
- clm/layer3/agency/router.py +71 -0
- clm/layer3/agency/runtime.py +524 -0
- clm/layer3/gateway.py +353 -0
- clm/layer3/open_interpreter_adapter.py +159 -0
- clm/layer3/rag/__init__.py +70 -0
- clm/layer3/rag/cli.py +228 -0
- clm/layer3/rag/config.py +153 -0
- clm/layer3/rag/embeddings/__init__.py +16 -0
- clm/layer3/rag/embeddings/base.py +79 -0
- clm/layer3/rag/embeddings/ollama.py +211 -0
- clm/layer3/rag/embeddings/vision.py +290 -0
- clm/layer3/rag/engine.py +276 -0
- clm/layer3/rag/graph/__init__.py +22 -0
- clm/layer3/rag/graph/community.py +198 -0
- clm/layer3/rag/graph/engine.py +433 -0
- clm/layer3/rag/graph/extractor.py +255 -0
- clm/layer3/rag/graph/schema.py +115 -0
- clm/layer3/rag/graph/store.py +641 -0
- clm/layer3/rag/indexer.py +333 -0
- clm/layer3/rag/llm_providers.json +52 -0
- clm/layer3/rag/semantic_versioning.py +313 -0
- clm/layer3/rag/vector/__init__.py +28 -0
- clm/layer3/rag/vector/handle_vector_store.py +749 -0
- clm/layer3/rag/vector/schema.py +151 -0
- clm/layer3/rag/vector/store.py +632 -0
- clm/layer3/reticulum/__init__.py +55 -0
- clm/layer3/reticulum/bridge.py +74 -0
- clm/layer3/reticulum/daemon.py +88 -0
- clm/layer3/reticulum/discovery.py +164 -0
- clm/layer3/reticulum/identity.py +107 -0
- clm/layer3/reticulum/media.py +148 -0
- clm/layer3/reticulum/transport.py +133 -0
- clm/layer3/satori/__init__.py +209 -0
- clm/layer3/satori/alpha_conversion.py +168 -0
- clm/layer3/satori/beta_reduction.py +359 -0
- clm/layer3/satori/continuation.py +38 -0
- clm/layer3/satori/eta_conversion.py +205 -0
- clm/layer3/satori/free_variables.py +163 -0
- clm/layer3/satori/io_effects.py +370 -0
- clm/layer3/satori/lambda_runtime.py +726 -0
- clm/layer3/satori/lambda_term.py +230 -0
- clm/layer3/satori/sheaf_audit.py +261 -0
- clm/layer3/satori/speech_act.py +279 -0
- clm/layer4/README.md +9 -0
- clm/layer4/__init__.py +67 -0
- clm/layer4/_sovereign_executor.py +580 -0
- clm/layer4/action_commands.py +719 -0
- clm/layer4/api.py +236 -0
- clm/layer4/bootstrap/__init__.py +13 -0
- clm/layer4/bootstrap/bootstrapper.py +118 -0
- clm/layer4/bootstrap/cleanup.py +44 -0
- clm/layer4/bootstrap/clm_hooks.py +49 -0
- clm/layer4/bootstrap/genesis.py +77 -0
- clm/layer4/bootstrap/injection.py +125 -0
- clm/layer4/cli.py +549 -0
- clm/layer4/collection_manager.py +553 -0
- clm/layer4/commands/__init__.py +81 -0
- clm/layer4/commands/action_cmd.py +42 -0
- clm/layer4/commands/check_cmd.py +82 -0
- clm/layer4/commands/envelope.py +73 -0
- clm/layer4/commands/evaluate_cmd.py +224 -0
- clm/layer4/commands/mcard_cmd.py +24 -0
- clm/layer4/commands/petri_cmd.py +92 -0
- clm/layer4/commands/tridb_cmd.py +319 -0
- clm/layer4/commands/version_cmd.py +27 -0
- clm/layer4/diff_commands.py +167 -0
- clm/layer4/eoa_sponsor.py +232 -0
- clm/layer4/events.py +69 -0
- clm/layer4/flux_gateway.py +133 -0
- clm/layer4/gateway.py +91 -0
- clm/layer4/loader.py +307 -0
- clm/layer4/mcard_commands.py +166 -0
- clm/layer4/membrane.py +273 -0
- clm/layer4/navigation.py +50 -0
- clm/layer4/protocol/protocol_entry_python.py +108 -0
- clm/layer4/protocol/python_sync.py +126 -0
- clm/layer4/runner.py +150 -0
- clm/layer4/satori.py +262 -0
- clm/layer4/server/python_sync_client.py +150 -0
- clm/layer4/server/python_sync_server.py +139 -0
- clm/layer4/server/service_api.py +315 -0
- clm/layer4/server/start_servers.py +107 -0
- clm/layer4/server/stop_servers.py +24 -0
- clm/layer4/simulated_optimization_fix.py +0 -0
- clm/layer4/storage.py +463 -0
- clm/layer4/tridb_admin.py +268 -0
- clm/layer4/websocket/test_ws_client.py +104 -0
- clm/layer4/websocket/test_ws_server.py +138 -0
- clm/layer4/websocket/ws_server_local.py +160 -0
- clm/layer5/__init__.py +51 -0
- clm/layer5/async_batch_writer.py +133 -0
- clm/layer5/learning.py +276 -0
- clm/layer5/telemetry_hook.py +107 -0
- clm/layer5/type_lattice.py +238 -0
- clm/py.typed +0 -0
- clm/schemas/__init__.py +652 -0
- clm/schemas/abstract_face_schema.yaml +77 -0
- clm/schemas/cel_schema.yaml +416 -0
- clm/schemas/lifecycle/__init__.py +45 -0
- clm/schemas/lifecycle/models.py +123 -0
- clm/schemas/lifecycle/service.py +364 -0
- clm/schemas/mcard_schema.sql +192 -0
- clm/schemas/mcard_vector_schema.sql +283 -0
- clm/schemas/mlp-context-v1.json +91 -0
- clm_kernel-0.0.0.dist-info/METADATA +121 -0
- clm_kernel-0.0.0.dist-info/RECORD +333 -0
- clm_kernel-0.0.0.dist-info/WHEEL +4 -0
- clm_kernel-0.0.0.dist-info/entry_points.txt +3 -0
- 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
|