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,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
|