@smilintux/skcapstone 0.5.4 → 0.5.6
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.
package/package.json
CHANGED
|
@@ -140,23 +140,14 @@ def _backend_from_model(model_name: str, tier: ModelTier) -> str:
|
|
|
140
140
|
|
|
141
141
|
Returns:
|
|
142
142
|
Backend string: ``"ollama"``, ``"anthropic"``, ``"openai"``, ``"grok"``,
|
|
143
|
-
``"kimi"``, ``"nvidia"``, ``"passthrough"``, or ``"unknown"``.
|
|
143
|
+
``"kimi"``, ``"minimax"``, ``"nvidia"``, ``"passthrough"``, or ``"unknown"``.
|
|
144
144
|
"""
|
|
145
145
|
if tier == ModelTier.LOCAL:
|
|
146
146
|
return "ollama"
|
|
147
147
|
name_base = model_name.lower().split(":")[0]
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
return "openai"
|
|
152
|
-
if "grok" in name_base:
|
|
153
|
-
return "grok"
|
|
154
|
-
if "kimi" in name_base or "moonshot" in name_base:
|
|
155
|
-
return "kimi"
|
|
156
|
-
if "minimax" in name_base:
|
|
157
|
-
return "minimax"
|
|
158
|
-
if "nvidia" in name_base:
|
|
159
|
-
return "nvidia"
|
|
148
|
+
for patterns, backend in LLMBridge._MODEL_PATTERNS:
|
|
149
|
+
if any(p in name_base for p in patterns):
|
|
150
|
+
return backend
|
|
160
151
|
if any(p in name_base for p in _OLLAMA_MODEL_PATTERNS):
|
|
161
152
|
return "ollama"
|
|
162
153
|
return "unknown"
|
|
@@ -253,6 +244,7 @@ class LLMBridge:
|
|
|
253
244
|
adapter: Optional[PromptAdapter] = None,
|
|
254
245
|
cache: Optional[ResponseCache] = None,
|
|
255
246
|
) -> None:
|
|
247
|
+
self._config = config
|
|
256
248
|
self._router = ModelRouter(config=router_config)
|
|
257
249
|
self._adapter = adapter or PromptAdapter()
|
|
258
250
|
self._fallback_chain = config.fallback_chain
|
|
@@ -263,17 +255,29 @@ class LLMBridge:
|
|
|
263
255
|
self._ollama_pool = _OllamaPool(os.environ.get("OLLAMA_HOST", config.ollama_host))
|
|
264
256
|
self._probe_available_backends()
|
|
265
257
|
|
|
258
|
+
# Maps backend name → env var that activates it.
|
|
259
|
+
# Backends with None are probed separately (ollama) or always on (passthrough).
|
|
260
|
+
_BACKEND_ENV_KEYS: dict[str, Optional[str]] = {
|
|
261
|
+
"ollama": None,
|
|
262
|
+
"anthropic": "ANTHROPIC_API_KEY",
|
|
263
|
+
"openai": "OPENAI_API_KEY",
|
|
264
|
+
"grok": "XAI_API_KEY",
|
|
265
|
+
"kimi": "MOONSHOT_API_KEY",
|
|
266
|
+
"minimax": "MINIMAX_API_KEY",
|
|
267
|
+
"nvidia": "NVIDIA_API_KEY",
|
|
268
|
+
"passthrough": None,
|
|
269
|
+
}
|
|
270
|
+
|
|
266
271
|
def _probe_available_backends(self) -> None:
|
|
267
272
|
"""Probe all backends for availability."""
|
|
268
|
-
self._available = {
|
|
269
|
-
|
|
270
|
-
"
|
|
271
|
-
|
|
272
|
-
"
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}
|
|
273
|
+
self._available = {}
|
|
274
|
+
for name, env_key in self._BACKEND_ENV_KEYS.items():
|
|
275
|
+
if name == "ollama":
|
|
276
|
+
self._available[name] = self._probe_ollama()
|
|
277
|
+
elif name == "passthrough":
|
|
278
|
+
self._available[name] = True
|
|
279
|
+
else:
|
|
280
|
+
self._available[name] = bool(os.environ.get(env_key or ""))
|
|
277
281
|
available = [k for k, v in self._available.items() if v]
|
|
278
282
|
logger.info("LLM backends available: %s", available)
|
|
279
283
|
|
|
@@ -289,9 +293,23 @@ class LLMBridge:
|
|
|
289
293
|
self._ollama_pool.invalidate()
|
|
290
294
|
return False
|
|
291
295
|
|
|
296
|
+
# Maps model-name substring → backend name for pattern matching.
|
|
297
|
+
_MODEL_PATTERNS: list[tuple[tuple[str, ...], str]] = [
|
|
298
|
+
(("claude",), "anthropic"),
|
|
299
|
+
(("gpt", "o1", "o3", "o4"), "openai"),
|
|
300
|
+
(("grok",), "grok"),
|
|
301
|
+
(("kimi", "moonshot"), "kimi"),
|
|
302
|
+
(("minimax",), "minimax"),
|
|
303
|
+
(("nvidia",), "nvidia"),
|
|
304
|
+
]
|
|
305
|
+
|
|
292
306
|
def _resolve_callback(self, tier: ModelTier, model_name: str):
|
|
293
307
|
"""Map tier+model to a skseed callback.
|
|
294
308
|
|
|
309
|
+
Uses the configured ollama_model for local inference and
|
|
310
|
+
resolves cloud backends by model-name pattern matching.
|
|
311
|
+
Falls back through the configured fallback_chain.
|
|
312
|
+
|
|
295
313
|
Args:
|
|
296
314
|
tier: The routing tier.
|
|
297
315
|
model_name: The concrete model name.
|
|
@@ -299,74 +317,58 @@ class LLMBridge:
|
|
|
299
317
|
Returns:
|
|
300
318
|
An LLMCallback callable.
|
|
301
319
|
"""
|
|
302
|
-
from skseed.llm import
|
|
303
|
-
anthropic_callback,
|
|
304
|
-
grok_callback,
|
|
305
|
-
kimi_callback,
|
|
306
|
-
minimax_callback,
|
|
307
|
-
nvidia_callback,
|
|
308
|
-
ollama_callback,
|
|
309
|
-
openai_callback,
|
|
310
|
-
passthrough_callback,
|
|
311
|
-
)
|
|
320
|
+
from skseed.llm import ollama_callback
|
|
312
321
|
|
|
313
|
-
|
|
314
|
-
# Strip Ollama :tag suffix for pattern matching (e.g. "deepseek-r1:8b" -> "deepseek-r1")
|
|
315
|
-
name_base = name_lower.split(":")[0]
|
|
322
|
+
name_base = model_name.lower().split(":")[0]
|
|
316
323
|
|
|
317
324
|
# LOCAL tier always goes to Ollama
|
|
318
325
|
if tier == ModelTier.LOCAL:
|
|
319
326
|
return ollama_callback(model=model_name)
|
|
320
327
|
|
|
321
|
-
# Pattern matching on model name
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
return openai_callback(model=model_name)
|
|
326
|
-
if "grok" in name_base:
|
|
327
|
-
return grok_callback(model=model_name)
|
|
328
|
-
if "kimi" in name_base or "moonshot" in name_base:
|
|
329
|
-
return kimi_callback(model=model_name)
|
|
330
|
-
if "minimax" in name_base:
|
|
331
|
-
return minimax_callback(model=model_name)
|
|
332
|
-
if "nvidia" in name_base:
|
|
333
|
-
return nvidia_callback(model=model_name)
|
|
328
|
+
# Pattern matching on model name
|
|
329
|
+
for patterns, backend in self._MODEL_PATTERNS:
|
|
330
|
+
if any(p in name_base for p in patterns):
|
|
331
|
+
return self._callback_for_backend(backend, model=model_name)
|
|
334
332
|
|
|
335
333
|
# Models that run on Ollama (local inference)
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
"mistral",
|
|
339
|
-
"nemotron",
|
|
340
|
-
"devstral",
|
|
341
|
-
"deepseek",
|
|
342
|
-
"qwen",
|
|
343
|
-
"codestral",
|
|
344
|
-
)
|
|
345
|
-
for pattern in ollama_patterns:
|
|
346
|
-
if pattern in name_base:
|
|
347
|
-
return ollama_callback(model=model_name)
|
|
334
|
+
if any(p in name_base for p in _OLLAMA_MODEL_PATTERNS):
|
|
335
|
+
return ollama_callback(model=model_name)
|
|
348
336
|
|
|
349
337
|
# Walk fallback chain for first available backend
|
|
350
338
|
for backend in self._fallback_chain:
|
|
351
|
-
if
|
|
352
|
-
|
|
353
|
-
if backend == "ollama":
|
|
354
|
-
return ollama_callback(model="llama3.2")
|
|
355
|
-
elif backend == "anthropic":
|
|
356
|
-
return anthropic_callback()
|
|
357
|
-
elif backend == "openai":
|
|
358
|
-
return openai_callback()
|
|
359
|
-
elif backend == "grok":
|
|
360
|
-
return grok_callback()
|
|
361
|
-
elif backend == "kimi":
|
|
362
|
-
return kimi_callback()
|
|
363
|
-
elif backend == "nvidia":
|
|
364
|
-
return nvidia_callback()
|
|
365
|
-
elif backend == "passthrough":
|
|
366
|
-
return self._make_passthrough_callback()
|
|
339
|
+
if self._available.get(backend, False):
|
|
340
|
+
return self._callback_for_backend(backend)
|
|
367
341
|
|
|
368
342
|
return self._make_passthrough_callback()
|
|
369
343
|
|
|
344
|
+
def _callback_for_backend(self, backend: str, model: Optional[str] = None):
|
|
345
|
+
"""Return the skseed callback for *backend*, importing only what's needed.
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
backend: Backend name (e.g. "ollama", "anthropic", "openai").
|
|
349
|
+
model: Optional model override. When None, uses each provider's default.
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
An LLMCallback callable.
|
|
353
|
+
"""
|
|
354
|
+
import skseed.llm as _llm
|
|
355
|
+
|
|
356
|
+
if backend == "ollama":
|
|
357
|
+
return _llm.ollama_callback(model=model or self._config.ollama_model)
|
|
358
|
+
if backend == "passthrough":
|
|
359
|
+
return self._make_passthrough_callback()
|
|
360
|
+
|
|
361
|
+
# All other backends follow the same pattern: <backend>_callback(model=…)
|
|
362
|
+
factory = getattr(_llm, f"{backend}_callback", None)
|
|
363
|
+
if factory is None:
|
|
364
|
+
logger.warning("No skseed callback for backend %r — using passthrough", backend)
|
|
365
|
+
return self._make_passthrough_callback()
|
|
366
|
+
|
|
367
|
+
kwargs: dict[str, Any] = {}
|
|
368
|
+
if model:
|
|
369
|
+
kwargs["model"] = model
|
|
370
|
+
return factory(**kwargs)
|
|
371
|
+
|
|
370
372
|
@staticmethod
|
|
371
373
|
def _make_passthrough_callback():
|
|
372
374
|
"""Return a passthrough callback that always produces a plain str.
|
|
@@ -463,16 +465,6 @@ class LLMBridge:
|
|
|
463
465
|
Returns:
|
|
464
466
|
LLM response text, or a fallback error message.
|
|
465
467
|
"""
|
|
466
|
-
from skseed.llm import (
|
|
467
|
-
anthropic_callback,
|
|
468
|
-
grok_callback,
|
|
469
|
-
kimi_callback,
|
|
470
|
-
minimax_callback,
|
|
471
|
-
nvidia_callback,
|
|
472
|
-
ollama_callback,
|
|
473
|
-
openai_callback,
|
|
474
|
-
)
|
|
475
|
-
|
|
476
468
|
decision = self._router.route(signal)
|
|
477
469
|
logger.info(
|
|
478
470
|
"Routed to tier=%s model=%s: %s",
|
|
@@ -612,31 +604,14 @@ class LLMBridge:
|
|
|
612
604
|
)
|
|
613
605
|
)
|
|
614
606
|
|
|
615
|
-
# Cross-provider cascade via fallback chain —
|
|
616
|
-
#
|
|
607
|
+
# Cross-provider cascade via fallback chain — uses _callback_for_backend
|
|
608
|
+
# so adding a new provider only requires updating the registry, not this loop.
|
|
617
609
|
for backend in self._fallback_chain:
|
|
618
610
|
if not self._available.get(backend, False):
|
|
619
611
|
continue
|
|
620
612
|
try:
|
|
621
613
|
logger.info("Fallback cascade: %s", backend)
|
|
622
|
-
|
|
623
|
-
callback = ollama_callback(model="llama3.2")
|
|
624
|
-
elif backend == "anthropic":
|
|
625
|
-
callback = anthropic_callback()
|
|
626
|
-
elif backend == "grok":
|
|
627
|
-
callback = grok_callback()
|
|
628
|
-
elif backend == "kimi":
|
|
629
|
-
callback = kimi_callback()
|
|
630
|
-
elif backend == "minimax":
|
|
631
|
-
callback = minimax_callback()
|
|
632
|
-
elif backend == "nvidia":
|
|
633
|
-
callback = nvidia_callback()
|
|
634
|
-
elif backend == "openai":
|
|
635
|
-
callback = openai_callback()
|
|
636
|
-
elif backend == "passthrough":
|
|
637
|
-
callback = self._make_passthrough_callback()
|
|
638
|
-
else:
|
|
639
|
-
continue
|
|
614
|
+
callback = self._callback_for_backend(backend)
|
|
640
615
|
result = self._timed_call(callback, adapted, ModelTier.FAST)
|
|
641
616
|
if _out_info is not None:
|
|
642
617
|
_out_info["backend"] = backend
|
|
@@ -1283,44 +1283,61 @@ def _step_doctor_check(home_path: Path) -> "object":
|
|
|
1283
1283
|
|
|
1284
1284
|
|
|
1285
1285
|
def _step_test_consciousness(home_path: Path) -> bool:
|
|
1286
|
-
"""Send a test message
|
|
1286
|
+
"""Send a quick test message to the configured LLM backend.
|
|
1287
|
+
|
|
1288
|
+
Reads the consciousness config to determine the default backend
|
|
1289
|
+
(typically the local Ollama model chosen during onboarding) and
|
|
1290
|
+
sends a single prompt to verify the pipeline works end-to-end.
|
|
1287
1291
|
|
|
1288
1292
|
Args:
|
|
1289
1293
|
home_path: Agent home directory.
|
|
1290
1294
|
|
|
1291
1295
|
Returns:
|
|
1292
|
-
True if the
|
|
1296
|
+
True if the LLM responded successfully.
|
|
1293
1297
|
"""
|
|
1294
|
-
if not click.confirm(" Send a test message to verify the
|
|
1298
|
+
if not click.confirm(" Send a test message to verify the LLM backend?", default=False):
|
|
1295
1299
|
click.echo(
|
|
1296
1300
|
click.style(" ↷ ", fg="bright_black")
|
|
1297
1301
|
+ "Skipped — test later: skcapstone consciousness test 'hello'"
|
|
1298
1302
|
)
|
|
1299
1303
|
return False
|
|
1300
1304
|
|
|
1301
|
-
|
|
1305
|
+
# Load config to discover which backend/model was configured
|
|
1302
1306
|
try:
|
|
1303
1307
|
from .consciousness_config import load_consciousness_config
|
|
1304
|
-
from .consciousness_loop import LLMBridge, SystemPromptBuilder, _classify_message
|
|
1305
|
-
|
|
1306
1308
|
config = load_consciousness_config(home_path)
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1309
|
+
except Exception:
|
|
1310
|
+
# Fall back to defaults
|
|
1311
|
+
ollama_model = "llama3.2"
|
|
1312
|
+
ollama_host = "http://localhost:11434"
|
|
1313
|
+
config = None
|
|
1314
|
+
else:
|
|
1315
|
+
ollama_model = config.ollama_model
|
|
1316
|
+
ollama_host = config.ollama_host
|
|
1317
|
+
|
|
1318
|
+
click.echo(
|
|
1319
|
+
click.style(" Testing ", fg="bright_black")
|
|
1320
|
+
+ click.style(f"{ollama_model}", fg="cyan")
|
|
1321
|
+
+ click.style(f" @ {ollama_host}…", fg="bright_black")
|
|
1322
|
+
)
|
|
1323
|
+
|
|
1324
|
+
try:
|
|
1325
|
+
from skseed.llm import ollama_callback
|
|
1326
|
+
|
|
1327
|
+
callback = ollama_callback(model=ollama_model, base_url=ollama_host)
|
|
1328
|
+
response = callback("Respond in one sentence: are you online?")
|
|
1312
1329
|
if response:
|
|
1313
1330
|
preview = response[:80].replace("\n", " ")
|
|
1314
|
-
click.echo(click.style(" ✓ ", fg="green") +
|
|
1331
|
+
click.echo(click.style(" ✓ ", fg="green") + "LLM backend active")
|
|
1315
1332
|
click.echo(click.style(" ", fg="bright_black") + f"Response: {preview!r}")
|
|
1316
1333
|
return True
|
|
1317
1334
|
else:
|
|
1318
|
-
click.echo(click.style(" ⚠ ", fg="yellow") + "Empty response —
|
|
1319
|
-
click.echo(click.style(" ", fg="bright_black") + "
|
|
1335
|
+
click.echo(click.style(" ⚠ ", fg="yellow") + "Empty response — model may still be loading")
|
|
1336
|
+
click.echo(click.style(" ", fg="bright_black") + f"Try: ollama run {ollama_model}")
|
|
1320
1337
|
return False
|
|
1321
1338
|
except Exception as exc:
|
|
1322
1339
|
click.echo(click.style(" ⚠ ", fg="yellow") + f"Test failed: {exc}")
|
|
1323
|
-
click.echo(click.style(" ", fg="bright_black") + "
|
|
1340
|
+
click.echo(click.style(" ", fg="bright_black") + f"Check: ollama serve && ollama run {ollama_model}")
|
|
1324
1341
|
return False
|
|
1325
1342
|
|
|
1326
1343
|
|
|
@@ -80,17 +80,23 @@ def initialize_consciousness(home: Path) -> ConsciousnessState:
|
|
|
80
80
|
except (json.JSONDecodeError, OSError):
|
|
81
81
|
pass
|
|
82
82
|
|
|
83
|
-
# Check if daemon is running (systemd)
|
|
83
|
+
# Check if consciousness daemon is running (systemd)
|
|
84
|
+
# Accept either the legacy skwhisper service or the skcapstone service
|
|
84
85
|
try:
|
|
85
86
|
import subprocess
|
|
86
87
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
88
|
+
for service_name in ("skcapstone", "skwhisper"):
|
|
89
|
+
result = subprocess.run(
|
|
90
|
+
["systemctl", "--user", "is-active", service_name],
|
|
91
|
+
capture_output=True,
|
|
92
|
+
text=True,
|
|
93
|
+
timeout=3,
|
|
94
|
+
)
|
|
95
|
+
if result.stdout.strip() == "active":
|
|
96
|
+
state.whisper_active = True
|
|
97
|
+
break
|
|
98
|
+
else:
|
|
99
|
+
state.whisper_active = False
|
|
94
100
|
except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
|
|
95
101
|
state.whisper_active = False
|
|
96
102
|
|
|
@@ -105,6 +111,9 @@ def initialize_consciousness(home: Path) -> ConsciousnessState:
|
|
|
105
111
|
state.status = PillarStatus.ACTIVE
|
|
106
112
|
else:
|
|
107
113
|
state.status = PillarStatus.DEGRADED
|
|
114
|
+
elif state.whisper_active:
|
|
115
|
+
# Daemon is running but no sessions digested yet — consciousness is live
|
|
116
|
+
state.status = PillarStatus.DEGRADED
|
|
108
117
|
elif state.sessions_digested > 0 or state.whisper_md is not None:
|
|
109
118
|
state.status = PillarStatus.DEGRADED
|
|
110
119
|
else:
|