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:
@@ -38,7 +38,7 @@ manually from the app (**Start AI** in the sidebar), not at boot.
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# 0. Allow scripts to run (PowerShell blocks unsigned scripts by default, which
|
# 0. Allow scripts to run (PowerShell blocks unsigned scripts by default, which
|
||||||
# stops Nexus's CLI from working). Process scope covers only one single window;
|
# stops Nexus's CLI from working). Process scope covers only this window;
|
||||||
# LocalMachine makes it permanent so ncp works from every future shell (run
|
# LocalMachine makes it permanent so ncp works from every future shell (run
|
||||||
# PowerShell as Administrator for the LocalMachine line).
|
# PowerShell as Administrator for the LocalMachine line).
|
||||||
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force
|
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force
|
||||||
|
|||||||
+59
-13
@@ -178,6 +178,30 @@ try {
|
|||||||
Write-Warn " $RepoRoot\management"
|
Write-Warn " $RepoRoot\management"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# -- Legacy profile shim check --------------------------------------------------
|
||||||
|
# An earlier version of this installer registered ncp as a `function ncp` in the
|
||||||
|
# PowerShell profile instead of management\ncp.cmd (see the "why" above). Profile
|
||||||
|
# functions resolve before PATH executables, so a leftover one silently shadows
|
||||||
|
# the real ncp.cmd - `ncp` still runs, just the wrong code, with no error. We do
|
||||||
|
# not rewrite the user's profile automatically: it is their file, and a blind
|
||||||
|
# strip risks mangling whatever else lives in it alongside it. Just flag it.
|
||||||
|
Write-Step "Checking for a stale ncp() in your PowerShell profile"
|
||||||
|
$DocsRoot = [Environment]::GetFolderPath("MyDocuments")
|
||||||
|
$ProfileCandidates = @(
|
||||||
|
(Join-Path $DocsRoot "WindowsPowerShell\Microsoft.PowerShell_profile.ps1"),
|
||||||
|
(Join-Path $DocsRoot "PowerShell\Microsoft.PowerShell_profile.ps1")
|
||||||
|
)
|
||||||
|
$Shadowed = $ProfileCandidates | Where-Object {
|
||||||
|
(Test-Path $_) -and (Select-String -Path $_ -Pattern '^\s*function\s+ncp\b' -Quiet)
|
||||||
|
}
|
||||||
|
if ($Shadowed) {
|
||||||
|
Write-Warn "Found an old 'function ncp' that will shadow the real ncp.cmd:"
|
||||||
|
foreach ($p in $Shadowed) { Write-Warn " $p" }
|
||||||
|
Write-Warn " Remove that function from the file(s) above so ncp resolves to ncp.cmd."
|
||||||
|
} else {
|
||||||
|
Write-OK "No stale ncp() in your PowerShell profile"
|
||||||
|
}
|
||||||
|
|
||||||
# -- Desktop shortcut ----------------------------------------------------------
|
# -- Desktop shortcut ----------------------------------------------------------
|
||||||
Write-Step "Creating desktop shortcut"
|
Write-Step "Creating desktop shortcut"
|
||||||
try {
|
try {
|
||||||
@@ -213,8 +237,9 @@ try {
|
|||||||
Push-Location $RepoRoot
|
Push-Location $RepoRoot
|
||||||
$ChatModel = (& $VenvPy -c "from synapse.nexus_config import DEFAULT_CHAT_MODEL as m; print(m)")
|
$ChatModel = (& $VenvPy -c "from synapse.nexus_config import DEFAULT_CHAT_MODEL as m; print(m)")
|
||||||
$MemModel = (& $VenvPy -c "from synapse.nexus_config import DEFAULT_MEMORY_MODEL as m; print(m)")
|
$MemModel = (& $VenvPy -c "from synapse.nexus_config import DEFAULT_MEMORY_MODEL as m; print(m)")
|
||||||
|
$EmbedModel = (& $VenvPy -c "from synapse.nexus_config import DEFAULT_EMBED_MODEL as m; print(m)")
|
||||||
Pop-Location
|
Pop-Location
|
||||||
if ($LASTEXITCODE -ne 0 -or -not $ChatModel -or -not $MemModel) {
|
if ($LASTEXITCODE -ne 0 -or -not $ChatModel -or -not $MemModel -or -not $EmbedModel) {
|
||||||
Write-Fail "Could not read the default models from synapse\nexus_config.py - the venv install is broken"
|
Write-Fail "Could not read the default models from synapse\nexus_config.py - the venv install is broken"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,7 +280,7 @@ Pop-Location
|
|||||||
if ($seedOk) { Write-OK "Default model set to $ChatModel" }
|
if ($seedOk) { Write-OK "Default model set to $ChatModel" }
|
||||||
else { Write-Warn "Could not persist default model - pick it at the top of the chat instead." }
|
else { Write-Warn "Could not persist default model - pick it at the top of the chat instead." }
|
||||||
|
|
||||||
Write-Step "Pulling models ($ChatModel for chat, $MemModel for memory)"
|
Write-Step "Pulling models ($ChatModel for chat, $MemModel for memory, $EmbedModel for recall)"
|
||||||
# A prompt, not "press Ctrl+C to skip": Ctrl+C in PowerShell 5.1 terminates the
|
# A prompt, not "press Ctrl+C to skip": Ctrl+C in PowerShell 5.1 terminates the
|
||||||
# whole script, so the escape hatch the installer advertised was also the one
|
# whole script, so the escape hatch the installer advertised was also the one
|
||||||
# thing that stopped it finishing - no Ollama cleanup, no summary, no window
|
# thing that stopped it finishing - no Ollama cleanup, no summary, no window
|
||||||
@@ -277,6 +302,10 @@ if ($pullAnswer -match '^\s*(n|no)\s*$') {
|
|||||||
ollama pull $MemModel
|
ollama pull $MemModel
|
||||||
if ($LASTEXITCODE -eq 0) { Write-OK "$MemModel ready (memory curator)" }
|
if ($LASTEXITCODE -eq 0) { Write-OK "$MemModel ready (memory curator)" }
|
||||||
else { Write-Warn "$MemModel pull skipped/failed - the memory service will fall back to the chat model." }
|
else { Write-Warn "$MemModel pull skipped/failed - the memory service will fall back to the chat model." }
|
||||||
|
|
||||||
|
ollama pull $EmbedModel
|
||||||
|
if ($LASTEXITCODE -eq 0) { Write-OK "$EmbedModel ready (conversation recall)" }
|
||||||
|
else { Write-Warn "$EmbedModel pull skipped/failed - recall will fall back to lexical search." }
|
||||||
}
|
}
|
||||||
|
|
||||||
# -- Make Ollama manual-start (NexusOS owns the lifecycle) ----------------------
|
# -- Make Ollama manual-start (NexusOS owns the lifecycle) ----------------------
|
||||||
@@ -326,15 +355,32 @@ Write-Host " A process reads PATH once, when it starts. This shell started befo
|
|||||||
Write-Host " the installer put management\ on PATH, so it will never see ncp no" -ForegroundColor DarkGray
|
Write-Host " the installer put management\ on PATH, so it will never see ncp no" -ForegroundColor DarkGray
|
||||||
Write-Host " matter what you run here. The next terminal you open will." -ForegroundColor DarkGray
|
Write-Host " matter what you run here. The next terminal you open will." -ForegroundColor DarkGray
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Read-Host " Press Enter to close this window"
|
|
||||||
|
|
||||||
# Stop-Process on our own PID, not `exit`: `exit` only ends the window when the
|
# Skip the wait entirely when stdin is redirected: nobody is at the keyboard to
|
||||||
# host was started with -File. Run interactively - `& .\install-windows.ps1` from
|
# press anything, and both ReadKey and Read-Host were confirmed (empirically,
|
||||||
# a shell already open, which is the common case - it would just return to the
|
# not just in theory) to hang indefinitely in that case instead of failing
|
||||||
# prompt in a session whose PATH is permanently stale. Killing the host closes
|
# fast - Read-Host only fails fast under an explicit -NonInteractive flag,
|
||||||
# the window either way, which is the point: the user cannot accidentally keep
|
# which is not how the self-elevation Start-Process above launches this script.
|
||||||
# using a shell where ncp will never resolve.
|
if (-not [Console]::IsInputRedirected) {
|
||||||
# Caveat: a Start-Transcript in this window never reaches Stop-Transcript. The
|
Write-Host " Press any key to close this window..." -ForegroundColor Yellow
|
||||||
# transcript is flushed as it is written, so the content survives without the
|
try { $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") } catch { Read-Host | Out-Null }
|
||||||
# closing footer.
|
}
|
||||||
Stop-Process -Id $PID
|
|
||||||
|
# $OwnsWindow means this is the elevated instance the self-elevation block above
|
||||||
|
# launched with `-File`, so a clean `exit` closes it - and does so with exit code
|
||||||
|
# 0, which is what lets Windows Terminal's closeOnExit actually close the tab
|
||||||
|
# instead of treating an abrupt kill as a crash and falling back to a fresh
|
||||||
|
# shell in the pane. Without $OwnsWindow we are in a shell the user already had
|
||||||
|
# open (they ran an admin PowerShell and invoked the script directly, so it was
|
||||||
|
# never re-launched with -File) - `exit` there would just return to the prompt
|
||||||
|
# in a session whose PATH is permanently stale, so kill the host instead; the
|
||||||
|
# point is the user cannot accidentally keep using a shell where ncp never
|
||||||
|
# resolves.
|
||||||
|
# Caveat: a Start-Transcript in this window never reaches Stop-Transcript in the
|
||||||
|
# Stop-Process path. The transcript is flushed as it is written, so the content
|
||||||
|
# survives without the closing footer.
|
||||||
|
if ($OwnsWindow) {
|
||||||
|
exit 0
|
||||||
|
} else {
|
||||||
|
Stop-Process -Id $PID
|
||||||
|
}
|
||||||
|
|||||||
+12
-5
@@ -304,14 +304,19 @@ def stop_service(svc: Service) -> bool:
|
|||||||
def start_ollama() -> None:
|
def start_ollama() -> None:
|
||||||
"""Driven through the backend endpoint (the path the control panel uses)
|
"""Driven through the backend endpoint (the path the control panel uses)
|
||||||
rather than launching the binary, because OllamaManager owns model and GPU
|
rather than launching the binary, because OllamaManager owns model and GPU
|
||||||
selection. Requires the backend to be up."""
|
selection. Requires the backend to be up.
|
||||||
|
|
||||||
|
Long timeout: the endpoint blocks until the model is warmed - weights read
|
||||||
|
off disk into RAM/VRAM - which the web UI's own "Loading model..." button
|
||||||
|
state calls out as routinely taking about a minute, not just the Ollama
|
||||||
|
process launching."""
|
||||||
print("Starting OLLAMA...")
|
print("Starting OLLAMA...")
|
||||||
req = urllib.request.Request("http://localhost:8000/ollama/start", method="POST")
|
req = urllib.request.Request("http://localhost:8000/ollama/start", method="POST")
|
||||||
try:
|
try:
|
||||||
urllib.request.urlopen(req, timeout=30).read(1)
|
urllib.request.urlopen(req, timeout=180).read(1)
|
||||||
print("NEXUS OLLAMA STARTED")
|
print("NEXUS OLLAMA STARTED")
|
||||||
except Exception:
|
except Exception as e:
|
||||||
print(" OLLAMA start request failed (backend not reachable on :8000)")
|
print(f" OLLAMA start failed: {e}")
|
||||||
|
|
||||||
|
|
||||||
def stop_ollama() -> None:
|
def stop_ollama() -> None:
|
||||||
@@ -339,6 +344,7 @@ def cmd_start(target) -> None:
|
|||||||
launch(SERVICES["memory"]); wait_for_port(SERVICES["memory"])
|
launch(SERVICES["memory"]); wait_for_port(SERVICES["memory"])
|
||||||
elif target in ("--backend", "-b"):
|
elif target in ("--backend", "-b"):
|
||||||
launch(SERVICES["backend"]); wait_for_port(SERVICES["backend"])
|
launch(SERVICES["backend"]); wait_for_port(SERVICES["backend"])
|
||||||
|
start_ollama()
|
||||||
elif target in ("--frontend", "-f"):
|
elif target in ("--frontend", "-f"):
|
||||||
launch(SERVICES["frontend"]); wait_for_port(SERVICES["frontend"])
|
launch(SERVICES["frontend"]); wait_for_port(SERVICES["frontend"])
|
||||||
elif target in ("--ai", "-a"):
|
elif target in ("--ai", "-a"):
|
||||||
@@ -349,6 +355,7 @@ def cmd_start(target) -> None:
|
|||||||
launch(SERVICES["backend"])
|
launch(SERVICES["backend"])
|
||||||
wait_for_port(SERVICES["memory"])
|
wait_for_port(SERVICES["memory"])
|
||||||
wait_for_port(SERVICES["backend"])
|
wait_for_port(SERVICES["backend"])
|
||||||
|
start_ollama()
|
||||||
launch(SERVICES["frontend"])
|
launch(SERVICES["frontend"])
|
||||||
wait_for_port(SERVICES["frontend"])
|
wait_for_port(SERVICES["frontend"])
|
||||||
else:
|
else:
|
||||||
@@ -656,7 +663,7 @@ Commands:
|
|||||||
--memory, -m Start only the memory service
|
--memory, -m Start only the memory service
|
||||||
--frontend,-f Start only the frontend
|
--frontend,-f Start only the frontend
|
||||||
--backend, -b Start only the backend
|
--backend, -b Start only the backend
|
||||||
--ai, -a Start the AI (Ollama) — manual; not started by default
|
--ai, -a Start only the AI (Ollama); `start`/`start -b` already include it
|
||||||
|
|
||||||
stop Stop ALL Nexus services
|
stop Stop ALL Nexus services
|
||||||
--memory, -m Stop only the memory service
|
--memory, -m Stop only the memory service
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ except Exception:
|
|||||||
VERSION = "0.0.0"
|
VERSION = "0.0.0"
|
||||||
|
|
||||||
# --- MODEL DEFAULTS ---
|
# --- MODEL DEFAULTS ---
|
||||||
# Single source of truth for the two models NexusOS ships with. The installers
|
# Single source of truth for the three models NexusOS ships with. The installers
|
||||||
# pull and pin these, the runtime falls back to them; keeping them in one place
|
# pull and pin these, the runtime falls back to them; keeping them in one place
|
||||||
# is what stops installer and backend from drifting apart.
|
# is what stops installer and backend from drifting apart.
|
||||||
#
|
#
|
||||||
@@ -30,8 +30,12 @@ except Exception:
|
|||||||
# 4 GB GPU into CPU/RAM). Chosen over Qwen3 because Qwen3 is a reasoning model:
|
# 4 GB GPU into CPU/RAM). Chosen over Qwen3 because Qwen3 is a reasoning model:
|
||||||
# smart only with its slow <think> step, weak without it.
|
# smart only with its slow <think> step, weak without it.
|
||||||
# Memory: mistral - the curator that extracts facts and titles conversations.
|
# Memory: mistral - the curator that extracts facts and titles conversations.
|
||||||
|
# Embed: nomic-embed-text - powers semantic recall of past conversations
|
||||||
|
# (OllamaManager.embed / store.semantic_search_conversations). Without it,
|
||||||
|
# recall silently degrades to lexical substring matching.
|
||||||
DEFAULT_CHAT_MODEL = "llama3.1:8b"
|
DEFAULT_CHAT_MODEL = "llama3.1:8b"
|
||||||
DEFAULT_MEMORY_MODEL = "mistral:latest"
|
DEFAULT_MEMORY_MODEL = "mistral:latest"
|
||||||
|
DEFAULT_EMBED_MODEL = "nomic-embed-text"
|
||||||
|
|
||||||
# --- CORE DIRECTORIES ---
|
# --- CORE DIRECTORIES ---
|
||||||
DATA_DIR = PROJECT_ROOT / "data"
|
DATA_DIR = PROJECT_ROOT / "data"
|
||||||
@@ -144,7 +148,7 @@ settings = Settings()
|
|||||||
|
|
||||||
# explicit exports for static checkers and IDEs
|
# explicit exports for static checkers and IDEs
|
||||||
__all__ = ["Settings", "settings", "path", "VERSION",
|
__all__ = ["Settings", "settings", "path", "VERSION",
|
||||||
"DEFAULT_CHAT_MODEL", "DEFAULT_MEMORY_MODEL",
|
"DEFAULT_CHAT_MODEL", "DEFAULT_MEMORY_MODEL", "DEFAULT_EMBED_MODEL",
|
||||||
"PROJECT_ROOT", "DATA_DIR", "MODELS_DIR", "RUNTIME_DIR",
|
"PROJECT_ROOT", "DATA_DIR", "MODELS_DIR", "RUNTIME_DIR",
|
||||||
"MEMORY_DIR", "LOGS_DIR", "PLAYBOOK_DIR", "UPLOADS_DIR",
|
"MEMORY_DIR", "LOGS_DIR", "PLAYBOOK_DIR", "UPLOADS_DIR",
|
||||||
"EXPORTS_DIR", "MEMORY_DB",
|
"EXPORTS_DIR", "MEMORY_DB",
|
||||||
|
|||||||
+37
-24
@@ -9,7 +9,7 @@ import signal
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
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
|
from .memory.store import store
|
||||||
|
|
||||||
OLLAMA_PORT = 11434
|
OLLAMA_PORT = 11434
|
||||||
@@ -193,6 +193,7 @@ class OllamaManager:
|
|||||||
def __init__(self, runtime_dir=None):
|
def __init__(self, runtime_dir=None):
|
||||||
self.process = None
|
self.process = None
|
||||||
self.running = False
|
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 = Path(runtime_dir) if runtime_dir else Path(__file__).resolve().parent.parent / "runtime"
|
||||||
(self.runtime_dir / "logs").mkdir(parents=True, exist_ok=True)
|
(self.runtime_dir / "logs").mkdir(parents=True, exist_ok=True)
|
||||||
@@ -241,17 +242,45 @@ class OllamaManager:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
_log.warning("warm: preload failed: %s", 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):
|
def is_available(self):
|
||||||
|
# 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()
|
bin_path = _ollama_bin()
|
||||||
try:
|
try:
|
||||||
subprocess.run([bin_path, "--version"], capture_output=True, check=True, timeout=5)
|
subprocess.run([bin_path, "--version"], capture_output=True,
|
||||||
return True
|
check=True, timeout=5)
|
||||||
|
self._available = True
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
self._available = False
|
||||||
|
return self._available
|
||||||
|
|
||||||
def is_running(self):
|
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:
|
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
|
return r.status_code == 200
|
||||||
except httpx.RequestError:
|
except httpx.RequestError:
|
||||||
return False
|
return False
|
||||||
@@ -269,15 +298,7 @@ class OllamaManager:
|
|||||||
try:
|
try:
|
||||||
backend, gpu_env = _detect_gpu_backend()
|
backend, gpu_env = _detect_gpu_backend()
|
||||||
_log.info("Starting Ollama service via %s (backend: %s)...", _ollama_bin(), backend)
|
_log.info("Starting Ollama service via %s (backend: %s)...", _ollama_bin(), backend)
|
||||||
env = os.environ.copy()
|
env = self._serve_env(gpu_env)
|
||||||
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)
|
|
||||||
|
|
||||||
with open(self.log_file, "w") as log:
|
with open(self.log_file, "w") as log:
|
||||||
self.process = subprocess.Popen(
|
self.process = subprocess.Popen(
|
||||||
@@ -318,15 +339,7 @@ class OllamaManager:
|
|||||||
try:
|
try:
|
||||||
backend, gpu_env = _detect_gpu_backend()
|
backend, gpu_env = _detect_gpu_backend()
|
||||||
_log.info("Starting Ollama service via %s (backend: %s)...", _ollama_bin(), backend)
|
_log.info("Starting Ollama service via %s (backend: %s)...", _ollama_bin(), backend)
|
||||||
env = os.environ.copy()
|
env = self._serve_env(gpu_env)
|
||||||
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)
|
|
||||||
|
|
||||||
with open(self.log_file, "w") as log:
|
with open(self.log_file, "w") as log:
|
||||||
self.process = subprocess.Popen(
|
self.process = subprocess.Popen(
|
||||||
@@ -522,7 +535,7 @@ class OllamaManager:
|
|||||||
_log.exception("chat error after %.3fs: %s", elapsed, e)
|
_log.exception("chat error after %.3fs: %s", elapsed, e)
|
||||||
return None
|
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.
|
"""Return an embedding vector for `text` via /api/embeddings.
|
||||||
|
|
||||||
Returns None on any failure so callers can fall back to lexical search —
|
Returns None on any failure so callers can fall back to lexical search —
|
||||||
|
|||||||
Reference in New Issue
Block a user