@smilintux/skcapstone 0.5.4 → 0.5.5

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smilintux/skcapstone",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
4
4
  "description": "SKCapstone - The sovereign agent framework. CapAuth identity, Cloud 9 trust, SKMemory persistence.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -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
- if "claude" in name_base:
149
- return "anthropic"
150
- if any(x in name_base for x in ("gpt", "o1", "o3", "o4")):
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
- "ollama": self._probe_ollama(),
270
- "anthropic": bool(os.environ.get("ANTHROPIC_API_KEY")),
271
- "openai": bool(os.environ.get("OPENAI_API_KEY")),
272
- "grok": bool(os.environ.get("XAI_API_KEY")),
273
- "kimi": bool(os.environ.get("MOONSHOT_API_KEY")),
274
- "nvidia": bool(os.environ.get("NVIDIA_API_KEY")),
275
- "passthrough": True,
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
- name_lower = model_name.lower()
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 (use name_base to handle :tag suffixes)
322
- if "claude" in name_base:
323
- return anthropic_callback(model=model_name)
324
- if "gpt" in name_base or "o1" in name_base or "o3" in name_base or "o4" in name_base:
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
- ollama_patterns = (
337
- "llama",
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 not self._available.get(backend, False):
352
- continue
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 — direct backend mapping,
616
- # no _resolve_callback, to avoid infinite regression on unknown names.
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
- if backend == "ollama":
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 through the consciousness loop (optional).
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 loop responded successfully.
1296
+ True if the LLM responded successfully.
1293
1297
  """
1294
- if not click.confirm(" Send a test message to verify the consciousness loop?", default=False):
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
- click.echo(click.style(" Sending test message…", fg="bright_black"))
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
- bridge = LLMBridge(config)
1308
- builder = SystemPromptBuilder(home_path, config.max_context_tokens)
1309
- signal = _classify_message("Onboard wizard test — please confirm you are running.")
1310
- system_prompt = builder.build()
1311
- response = bridge.generate(system_prompt, "Onboard wizard test — please confirm you are running.", signal)
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") + f"Consciousness loop active")
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 — loop may not be fully configured")
1319
- click.echo(click.style(" ", fg="bright_black") + "Start daemon: skcapstone daemon start")
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") + "Start daemon: skcapstone daemon start --foreground")
1340
+ click.echo(click.style(" ", fg="bright_black") + f"Check: ollama serve && ollama run {ollama_model}")
1324
1341
  return False
1325
1342
 
1326
1343