mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-31 03:12:16 +00:00
- Engine discovery (#73): get_engine() now falls back to any healthy engine when the explicitly-requested key fails, instead of returning None. Fixes LM Studio (and other non-default engines) not being found when deploying agents. - FTS5 case sensitivity & scoring (#67): Add explicit unicode61 tokenizer to the FTS5 virtual table for case-insensitive search. Replace ambiguous `rank` column with explicit `bm25()` call with column weights (id=0, content=1, source=0.5) to produce correct positive scores. Includes auto-migration for existing databases. - Interactive engine selection (#72): `jarvis init` now detects running engines and presents an interactive picker. Also accepts `--engine` flag to skip the prompt. Engine choice flows through to config generation. - Windows desktop support (#68): Add RAM detection via wmic, Windows binary paths (LOCALAPPDATA, ProgramFiles, cargo), port cleanup via netstat+taskkill, and USERPROFILE fallback for HOME env var. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
112 lines
3.4 KiB
Python
112 lines
3.4 KiB
Python
"""Engine discovery — probe running engines and aggregate available models."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any, Dict, List, Tuple
|
|
|
|
from openjarvis.core.config import JarvisConfig
|
|
from openjarvis.core.registry import EngineRegistry
|
|
from openjarvis.engine._base import InferenceEngine
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Map registry keys to config host attribute (None = no host arg)
|
|
_HOST_MAP: Dict[str, str | None] = {
|
|
"ollama": "ollama_host",
|
|
"vllm": "vllm_host",
|
|
"llamacpp": "llamacpp_host",
|
|
"sglang": "sglang_host",
|
|
"mlx": "mlx_host",
|
|
"lmstudio": "lmstudio_host",
|
|
"exo": "exo_host",
|
|
"nexa": "nexa_host",
|
|
"uzu": "uzu_host",
|
|
"apple_fm": "apple_fm_host",
|
|
"cloud": None,
|
|
"litellm": None,
|
|
}
|
|
|
|
|
|
def _make_engine(key: str, config: JarvisConfig) -> InferenceEngine:
|
|
"""Instantiate a registered engine with the appropriate config host."""
|
|
cls = EngineRegistry.get(key)
|
|
host_attr = _HOST_MAP.get(key)
|
|
if host_attr is not None:
|
|
host = getattr(config.engine, host_attr, None)
|
|
if host:
|
|
return cls(host=host)
|
|
return cls()
|
|
|
|
|
|
def discover_engines(config: JarvisConfig) -> List[Tuple[str, InferenceEngine]]:
|
|
"""Probe registered engines and return ``[(key, instance)]`` for healthy ones.
|
|
|
|
Results are sorted with the config default engine first.
|
|
"""
|
|
healthy: List[Tuple[str, InferenceEngine]] = []
|
|
for key in EngineRegistry.keys():
|
|
try:
|
|
engine = _make_engine(key, config)
|
|
if engine.health():
|
|
healthy.append((key, engine))
|
|
except Exception as exc:
|
|
logger.debug("Engine %r failed during discovery: %s", key, exc)
|
|
continue
|
|
|
|
default_key = config.engine.default
|
|
|
|
def sort_key(item: Tuple[str, Any]) -> Tuple[int, str]:
|
|
return (0 if item[0] == default_key else 1, item[0])
|
|
|
|
healthy.sort(key=sort_key)
|
|
return healthy
|
|
|
|
|
|
def discover_models(
|
|
engines: List[Tuple[str, InferenceEngine]],
|
|
) -> Dict[str, List[str]]:
|
|
"""Call ``list_models()`` on each engine and return a dict."""
|
|
result: Dict[str, List[str]] = {}
|
|
for key, engine in engines:
|
|
try:
|
|
result[key] = engine.list_models()
|
|
except Exception as exc:
|
|
logger.debug("Failed to list models for engine %r: %s", key, exc)
|
|
result[key] = []
|
|
return result
|
|
|
|
|
|
def get_engine(
|
|
config: JarvisConfig, engine_key: str | None = None
|
|
) -> Tuple[str, InferenceEngine] | None:
|
|
"""Get a specific engine by key, or the default with fallback.
|
|
|
|
Returns ``(key, engine_instance)`` or ``None`` if no engine is available.
|
|
"""
|
|
# Build an ordered list of keys to try, then fall back to full discovery.
|
|
keys_to_try: list[str] = []
|
|
if engine_key:
|
|
keys_to_try.append(engine_key)
|
|
|
|
default_key = config.engine.default
|
|
if default_key and default_key not in keys_to_try:
|
|
keys_to_try.append(default_key)
|
|
|
|
for key in keys_to_try:
|
|
if not EngineRegistry.contains(key):
|
|
continue
|
|
try:
|
|
engine = _make_engine(key, config)
|
|
if engine.health():
|
|
return (key, engine)
|
|
except Exception as exc:
|
|
logger.debug("Engine %r health check failed: %s", key, exc)
|
|
|
|
# Fallback to any healthy engine
|
|
healthy = discover_engines(config)
|
|
return healthy[0] if healthy else None
|
|
|
|
|
|
__all__ = ["discover_engines", "discover_models", "get_engine"]
|