MicroFish/scripts/setup_agent_deps.sh

311 lines
8.1 KiB
Bash
Executable File

#!/usr/bin/env bash
set -u
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BACKEND_DIR="$ROOT_DIR/backend"
NEO4J_MODE="existing"
START_DOCKER="prompt"
FAILURES=0
if [ -f "$ROOT_DIR/.env" ]; then
set -a
# shellcheck disable=SC1091
. "$ROOT_DIR/.env"
set +a
fi
usage() {
cat <<'EOF'
Usage: scripts/setup_agent_deps.sh [options]
Options:
--neo4j desktop|native|docker|existing
Choose the Neo4j setup path to explain/check. Default: existing.
desktop = Neo4j Desktop-managed local database.
native = Homebrew or other local host installation.
docker = optional Docker Compose path.
existing = already-running local or remote Neo4j instance.
--start-docker
Start the optional Docker Compose Neo4j service if Docker is available.
--skip-services
Do not try to start Docker services. Still checks Neo4j connectivity.
--start-services
Backward-compatible alias for --start-docker.
Installs MiroFish agent-mode Python dependencies and checks required local
services. Graphiti is installed as a Python dependency; its source is not
vendored into this repository. Docker is optional.
EOF
}
ok() {
printf '[ok] %s\n' "$1"
}
warn() {
printf '[warn] %s\n' "$1"
}
fail() {
printf '[fail] %s\n' "$1"
FAILURES=$((FAILURES + 1))
}
while [ "$#" -gt 0 ]; do
case "$1" in
--neo4j)
shift
if [ "$#" -eq 0 ]; then
fail "--neo4j requires one of: desktop, native, docker, existing"
usage
exit 2
fi
case "$1" in
desktop|native|docker|existing)
NEO4J_MODE="$1"
;;
*)
fail "unsupported --neo4j mode: $1"
usage
exit 2
;;
esac
;;
--start-docker|--start-services)
START_DOCKER="yes"
;;
--skip-services)
START_DOCKER="no"
;;
-h|--help)
usage
exit 0
;;
*)
fail "unknown argument: $1"
usage
exit 2
;;
esac
shift
done
install_agent_deps() {
if command -v uv >/dev/null 2>&1; then
ok "installing backend agent extras with uv"
if (cd "$BACKEND_DIR" && uv sync --extra agent --group dev); then
ok "Python agent dependencies installed"
else
fail "uv sync --extra agent --group dev failed"
fi
else
warn "uv is not installed; install dependencies manually from backend:"
warn "python -m pip install -e '.[agent]'"
fi
}
explain_neo4j_mode() {
case "$NEO4J_MODE" in
desktop)
warn "Neo4j Desktop path selected"
warn "Create/start a Neo4j 5.26+ DBMS in Neo4j Desktop, then export NEO4J_URI/NEO4J_USER/NEO4J_PASSWORD/NEO4J_DATABASE."
;;
native)
warn "Native Neo4j path selected"
if command -v brew >/dev/null 2>&1; then
warn "Homebrew detected. Typical install/start: brew install neo4j && brew services start neo4j"
else
warn "Homebrew not detected. Install Neo4j 5.26+ with your local package manager and start it on NEO4J_URI."
fi
;;
docker)
warn "Optional Docker Compose Neo4j path selected"
warn "Compose file: docker-compose.agent.yml"
;;
existing)
warn "Existing Neo4j path selected"
warn "Set NEO4J_URI/NEO4J_USER/NEO4J_PASSWORD/NEO4J_DATABASE for a reachable Neo4j 5.26+ instance."
;;
esac
}
check_docker_optional() {
if command -v docker >/dev/null 2>&1; then
ok "$(docker --version)"
else
warn "Docker optional, skipped"
return 1
fi
if docker compose version >/dev/null 2>&1; then
ok "$(docker compose version)"
else
warn "Docker Compose optional, skipped"
return 1
fi
return 0
}
maybe_start_docker_neo4j() {
if [ "$NEO4J_MODE" != "docker" ]; then
return
fi
if ! check_docker_optional; then
return
fi
if [ "$START_DOCKER" = "prompt" ]; then
if [ -t 0 ]; then
printf 'Start optional Neo4j Docker Compose service now? [y/N] '
read -r answer
case "$answer" in
y|Y|yes|YES)
START_DOCKER="yes"
;;
*)
START_DOCKER="no"
;;
esac
else
START_DOCKER="no"
warn "non-interactive shell; skipping optional Docker startup prompt"
fi
fi
if [ "$START_DOCKER" = "yes" ]; then
if docker compose -f "$ROOT_DIR/docker-compose.agent.yml" up -d neo4j; then
ok "optional Neo4j compose service requested"
else
warn "optional Docker Compose startup failed; Neo4j connectivity check will report actual readiness"
fi
else
warn "optional Docker startup skipped"
warn "manual Docker command: docker compose -f docker-compose.agent.yml up -d neo4j"
fi
}
run_backend_python() {
if command -v uv >/dev/null 2>&1; then
(cd "$BACKEND_DIR" && uv run python "$@")
else
python3 "$@"
fi
}
check_neo4j() {
run_backend_python - <<'PY'
import os
import sys
uri = os.environ.get("NEO4J_URI", "bolt://localhost:7687")
user = os.environ.get("NEO4J_USER", "neo4j")
password = os.environ.get("NEO4J_PASSWORD", "password")
database = os.environ.get("NEO4J_DATABASE", "neo4j")
try:
from neo4j import GraphDatabase
except Exception as exc:
print(f"[fail] neo4j Python driver import failed: {exc}")
sys.exit(1)
def parse_version(value):
cleaned = value.split("-", 1)[0]
parts = cleaned.split(".")
major = int(parts[0]) if len(parts) > 0 and parts[0].isdigit() else 0
minor = int(parts[1]) if len(parts) > 1 and parts[1].isdigit() else 0
return major, minor
try:
driver = GraphDatabase.driver(uri, auth=(user, password))
with driver.session(database=database) as session:
record = session.run(
"CALL dbms.components() YIELD name, versions RETURN name, versions LIMIT 1"
).single()
driver.close()
except Exception as exc:
print(f"[fail] Neo4j connection/version check failed for {uri}: {exc}")
sys.exit(1)
versions = record["versions"] if record else []
version = versions[0] if versions else "unknown"
major, minor = parse_version(version)
if not (major > 5 or (major == 5 and minor >= 26)):
print(f"[fail] Neo4j version {version} is unsupported; use Neo4j 5.26+")
sys.exit(1)
print(f"[ok] Neo4j version {version}")
PY
status=$?
if [ "$status" -ne 0 ]; then
FAILURES=$((FAILURES + 1))
fi
}
check_ollama_if_configured() {
provider="${MIROFISH_EMBEDDING_PROVIDER:-none}"
search_mode="${MIROFISH_GRAPH_SEARCH_MODE:-fulltext}"
if [ "$search_mode" != "semantic" ] && [ "$search_mode" != "hybrid" ]; then
warn "Ollama optional, skipped because MIROFISH_GRAPH_SEARCH_MODE=$search_mode uses no semantic embedding"
return
fi
if [ "$provider" != "ollama" ]; then
warn "Ollama optional, skipped because MIROFISH_EMBEDDING_PROVIDER=$provider; semantic retrieval may be degraded"
return
fi
run_backend_python - <<'PY'
import json
import os
import sys
import urllib.error
import urllib.request
base_url = os.environ.get("OLLAMA_BASE_URL", "http://localhost:11434").rstrip("/")
model = os.environ.get("OLLAMA_EMBEDDING_MODEL", "nomic-embed-text")
url = f"{base_url}/api/tags"
try:
with urllib.request.urlopen(url, timeout=5) as response:
payload = json.loads(response.read().decode("utf-8"))
except (OSError, urllib.error.URLError, json.JSONDecodeError) as exc:
print(f"[fail] Ollama tags check failed for {url}: {exc}")
print(f"[hint] start Ollama and run: ollama pull {model}")
sys.exit(1)
names = [item.get("name", "") for item in payload.get("models", [])]
found = any(name == model or name.startswith(model + ":") for name in names)
if not found:
print(f"[fail] Ollama embedding model '{model}' not found")
print(f"[hint] run: ollama pull {model}")
sys.exit(1)
print(f"[ok] Ollama embedding model available: {model}")
PY
status=$?
if [ "$status" -ne 0 ]; then
FAILURES=$((FAILURES + 1))
fi
}
install_agent_deps
explain_neo4j_mode
if [ "$NEO4J_MODE" != "docker" ]; then
check_docker_optional || true
fi
maybe_start_docker_neo4j
check_neo4j
check_ollama_if_configured
if [ "$FAILURES" -gt 0 ]; then
printf '[fail] setup completed with %s required failure(s)\n' "$FAILURES"
exit 1
fi
ok "agent dependency and required service checks completed"