forked from enderofwings/NexusOS
Port Windows installer/ncp fixes and a real nomic-embed-text default
- install-windows.ps1: warn about a leftover profile-based ncp() that would shadow ncp.cmd; the "press any key to close" footer now skips the wait when stdin is redirected (was hanging indefinitely) and exits cleanly instead of Stop-Process when it owns the window; pulls nomic-embed-text alongside the chat/memory models. - management/ncp.py: ncp start / start -b bring Ollama up automatically; longer timeout + real error message on a slow model warm. - synapse/nexus_config.py: DEFAULT_EMBED_MODEL, single source of truth alongside DEFAULT_CHAT_MODEL/DEFAULT_MEMORY_MODEL. - synapse/ollama_manager.py: is_available() cached instead of spawning a process per /status poll; is_running() timeout dropped 2s -> 0.5s so a healthy backend stops reading as dead; embed() reads the new default instead of a hardcoded string; de-duplicated serve-env setup. Ported via bin/publish.sh from NexusOS-jon. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+40
-27
@@ -9,7 +9,7 @@ import signal
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from .nexus_config import settings, DEFAULT_CHAT_MODEL
|
||||
from .nexus_config import settings, DEFAULT_CHAT_MODEL, DEFAULT_EMBED_MODEL
|
||||
from .memory.store import store
|
||||
|
||||
OLLAMA_PORT = 11434
|
||||
@@ -193,6 +193,7 @@ class OllamaManager:
|
||||
def __init__(self, runtime_dir=None):
|
||||
self.process = None
|
||||
self.running = False
|
||||
self._available = None # see is_available()
|
||||
|
||||
self.runtime_dir = Path(runtime_dir) if runtime_dir else Path(__file__).resolve().parent.parent / "runtime"
|
||||
(self.runtime_dir / "logs").mkdir(parents=True, exist_ok=True)
|
||||
@@ -241,17 +242,45 @@ class OllamaManager:
|
||||
except Exception as e:
|
||||
_log.warning("warm: preload failed: %s", e)
|
||||
|
||||
def _serve_env(self, gpu_env: dict) -> dict:
|
||||
"""Environment for a `serve` we spawn. One definition - this was
|
||||
duplicated verbatim in two methods, so the Windows carve-out below had
|
||||
to be fixed in both places or the two paths would disagree."""
|
||||
env = os.environ.copy()
|
||||
env["OLLAMA_HOST"] = self._api_base
|
||||
# Every platform uses the project's own model store. Windows used to be
|
||||
# exempt, because the installer pulled with a bare `ollama pull` into
|
||||
# %USERPROFILE%\.ollama and a NexusOS-spawned serve pointed elsewhere
|
||||
# would not have seen those models. The installer sets OLLAMA_MODELS for
|
||||
# its pulls now (and migrates an existing store), so the exemption just
|
||||
# meant Windows kept models somewhere `ncp models` could not see.
|
||||
env["OLLAMA_MODELS"] = str(settings.models_dir)
|
||||
env.update(gpu_env)
|
||||
return env
|
||||
|
||||
def is_available(self):
|
||||
bin_path = _ollama_bin()
|
||||
try:
|
||||
subprocess.run([bin_path, "--version"], capture_output=True, check=True, timeout=5)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
# ponytail: cached for the life of the process. This spawns a subprocess,
|
||||
# and /status calls it on every poll - the frontend polls continuously,
|
||||
# so it was a process spawn per tick to answer a question whose answer
|
||||
# cannot change without someone installing Ollama. Restart to re-detect.
|
||||
if self._available is None:
|
||||
bin_path = _ollama_bin()
|
||||
try:
|
||||
subprocess.run([bin_path, "--version"], capture_output=True,
|
||||
check=True, timeout=5)
|
||||
self._available = True
|
||||
except Exception:
|
||||
self._available = False
|
||||
return self._available
|
||||
|
||||
def is_running(self):
|
||||
# 0.5s, not 2s: this is a loopback request to a server that is either
|
||||
# listening or is not. Ollama ships OFF (the user presses Start AI), so
|
||||
# the "not running" path is the common one and every /status paid the
|
||||
# full 2s for it - which pushed /status past the 2s client timeout in
|
||||
# bin/nexus_window.py and made a healthy backend look dead.
|
||||
try:
|
||||
r = httpx.get(f"{self._api_base}/api/tags", timeout=2.0)
|
||||
r = httpx.get(f"{self._api_base}/api/tags", timeout=0.5)
|
||||
return r.status_code == 200
|
||||
except httpx.RequestError:
|
||||
return False
|
||||
@@ -269,15 +298,7 @@ class OllamaManager:
|
||||
try:
|
||||
backend, gpu_env = _detect_gpu_backend()
|
||||
_log.info("Starting Ollama service via %s (backend: %s)...", _ollama_bin(), backend)
|
||||
env = os.environ.copy()
|
||||
env["OLLAMA_HOST"] = self._api_base
|
||||
# Linux ships a bundled model store under the project. On Windows,
|
||||
# Ollama is installed system-wide and pulls into its default store,
|
||||
# so don't override OLLAMA_MODELS or a NexusOS-spawned `serve` won't
|
||||
# see the models the installer already pulled.
|
||||
if os.name != "nt":
|
||||
env["OLLAMA_MODELS"] = str(Path(__file__).resolve().parent.parent / "models")
|
||||
env.update(gpu_env)
|
||||
env = self._serve_env(gpu_env)
|
||||
|
||||
with open(self.log_file, "w") as log:
|
||||
self.process = subprocess.Popen(
|
||||
@@ -318,15 +339,7 @@ class OllamaManager:
|
||||
try:
|
||||
backend, gpu_env = _detect_gpu_backend()
|
||||
_log.info("Starting Ollama service via %s (backend: %s)...", _ollama_bin(), backend)
|
||||
env = os.environ.copy()
|
||||
env["OLLAMA_HOST"] = self._api_base
|
||||
# Linux ships a bundled model store under the project. On Windows,
|
||||
# Ollama is installed system-wide and pulls into its default store,
|
||||
# so don't override OLLAMA_MODELS or a NexusOS-spawned `serve` won't
|
||||
# see the models the installer already pulled.
|
||||
if os.name != "nt":
|
||||
env["OLLAMA_MODELS"] = str(Path(__file__).resolve().parent.parent / "models")
|
||||
env.update(gpu_env)
|
||||
env = self._serve_env(gpu_env)
|
||||
|
||||
with open(self.log_file, "w") as log:
|
||||
self.process = subprocess.Popen(
|
||||
@@ -522,7 +535,7 @@ class OllamaManager:
|
||||
_log.exception("chat error after %.3fs: %s", elapsed, e)
|
||||
return None
|
||||
|
||||
async def embed(self, text: str, model: str = "nomic-embed-text") -> list[float] | None:
|
||||
async def embed(self, text: str, model: str = DEFAULT_EMBED_MODEL) -> list[float] | None:
|
||||
"""Return an embedding vector for `text` via /api/embeddings.
|
||||
|
||||
Returns None on any failure so callers can fall back to lexical search —
|
||||
|
||||
Reference in New Issue
Block a user