Spaces:
Sleeping
Sleeping
github-actions[bot]
Sync backend to Hugging Face Space (commit: 39b5c807918249fa80049d49f4b6a74d6a0ed1fc)
6d86412 | """Orsync Scenarist — Local Run Script. | |
| Prerequisites: | |
| Run ``python setup.py`` first to install dependencies and configure .env. | |
| Usage: | |
| python run.py # Start API server on port 7860 | |
| python run.py --port 8000 # Custom port | |
| python run.py --reload # Auto-reload on code changes (dev mode) | |
| python run.py --no-redis # Skip embedded Redis startup | |
| python run.py --no-neo4j # Skip embedded Neo4j startup | |
| python run.py --no-chroma # Skip embedded ChromaDB startup | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import errno | |
| import os | |
| import shutil | |
| import socket | |
| import subprocess | |
| import sys | |
| import time | |
| ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) | |
| PROJECT_PARENT = os.path.dirname(ROOT_DIR) | |
| if PROJECT_PARENT not in sys.path: | |
| sys.path.insert(0, PROJECT_PARENT) | |
| SETUP_MARKER = os.path.join(ROOT_DIR, ".setup_done") | |
| _redis_proc: subprocess.Popen | None = None | |
| _neo4j_proc: subprocess.Popen | None = None | |
| _chroma_proc: subprocess.Popen | None = None | |
| def _is_neo4j_ready(uri: str, username: str, password: str) -> bool: | |
| try: | |
| from neo4j import GraphDatabase | |
| driver = GraphDatabase.driver(uri, auth=(username, password)) | |
| try: | |
| driver.verify_connectivity() | |
| return True | |
| finally: | |
| driver.close() | |
| except Exception: | |
| return False | |
| def _is_chroma_ready(host: str, port: int) -> bool: | |
| try: | |
| import chromadb | |
| client = chromadb.HttpClient(host=host, port=port) | |
| client.heartbeat() | |
| return True | |
| except Exception: | |
| return False | |
| def _is_port_available(host: str, port: int) -> bool: | |
| """Return True when the requested bind address can accept a TCP listener.""" | |
| family = socket.AF_INET6 if ":" in host else socket.AF_INET | |
| probe = socket.socket(family, socket.SOCK_STREAM) | |
| try: | |
| probe.bind((host, port)) | |
| except OSError as exc: | |
| if exc.errno in {errno.EADDRINUSE, errno.EACCES, 10013, 10048}: | |
| return False | |
| raise | |
| finally: | |
| probe.close() | |
| return True | |
| def _display_host(host: str) -> str: | |
| if host in {"0.0.0.0", "::"}: | |
| return "127.0.0.1" | |
| return host | |
| def _ensure_setup() -> None: | |
| """Run setup.py automatically if it hasn't been run yet.""" | |
| if os.path.isfile(SETUP_MARKER): | |
| return | |
| print("[run] First run detected — launching setup ...\n") | |
| result = subprocess.run([sys.executable, os.path.join(ROOT_DIR, "setup.py")]) | |
| if result.returncode != 0: | |
| print("\n[run] Setup failed. Fix the issues above and try again.") | |
| sys.exit(1) | |
| print() | |
| def _start_redis() -> bool: | |
| """Start an embedded Redis server if redis-server is available locally.""" | |
| global _redis_proc | |
| # First check if Redis is already running | |
| try: | |
| import redis as _redis | |
| r = _redis.from_url("redis://localhost:6379/0", socket_connect_timeout=1) | |
| r.ping() | |
| print("[run] Redis: already running on localhost:6379") | |
| return True | |
| except Exception: | |
| pass | |
| # Try to start redis-server | |
| redis_bin = shutil.which("redis-server") | |
| if not redis_bin: | |
| print("[run] Redis: redis-server not found — using in-memory fallback") | |
| print("[run] Install Redis: https://redis.io/download") | |
| return False | |
| try: | |
| _redis_proc = subprocess.Popen( | |
| [redis_bin, "--daemonize", "no", "--save", "", "--appendonly", "no", | |
| "--maxmemory", "256mb", "--maxmemory-policy", "allkeys-lru"], | |
| stdout=subprocess.DEVNULL, | |
| stderr=subprocess.DEVNULL, | |
| ) | |
| # Wait for it to be ready | |
| for _ in range(20): | |
| try: | |
| import redis as _redis | |
| r = _redis.from_url("redis://localhost:6379/0", socket_connect_timeout=1) | |
| r.ping() | |
| print("[run] Redis: started embedded server on localhost:6379") | |
| return True | |
| except Exception: | |
| time.sleep(0.25) | |
| print("[run] Redis: server started but not responding — using in-memory fallback") | |
| return False | |
| except Exception as exc: | |
| print(f"[run] Redis: failed to start — {exc}") | |
| return False | |
| def _start_neo4j() -> bool: | |
| """Start an embedded Neo4j server if neo4j is available locally.""" | |
| global _neo4j_proc | |
| from backend.app.core.config import settings | |
| neo4j_uri = settings.neo4j_uri | |
| neo4j_username = settings.neo4j_username | |
| neo4j_password = settings.neo4j_password | |
| # First check if Neo4j is already running | |
| if _is_neo4j_ready(neo4j_uri, neo4j_username, neo4j_password): | |
| print(f"[run] Neo4j: already running on {neo4j_uri}") | |
| return True | |
| # Find neo4j binary — check common locations | |
| neo4j_bin = shutil.which("neo4j") | |
| if not neo4j_bin: | |
| # Check common install paths | |
| for candidate in [ | |
| "/opt/neo4j/bin/neo4j", | |
| os.path.expanduser("~/neo4j/bin/neo4j"), | |
| r"C:\Program Files\Neo4j\bin\neo4j.bat", | |
| ]: | |
| if os.path.isfile(candidate): | |
| neo4j_bin = candidate | |
| break | |
| if not neo4j_bin: | |
| print("[run] Neo4j: not found — graph queries will use no-op stub") | |
| print("[run] Install: https://neo4j.com/download/") | |
| return False | |
| try: | |
| # Use 'neo4j console' which runs in foreground (suitable for subprocess) | |
| _neo4j_proc = subprocess.Popen( | |
| [neo4j_bin, "console"], | |
| stdout=subprocess.DEVNULL, | |
| stderr=subprocess.DEVNULL, | |
| ) | |
| # Wait for bolt port to be ready | |
| print("[run] Neo4j: starting ...") | |
| for _ in range(60): | |
| if _is_neo4j_ready(neo4j_uri, neo4j_username, neo4j_password): | |
| print(f"[run] Neo4j: started on {neo4j_uri}") | |
| return True | |
| time.sleep(0.5) | |
| print("[run] Neo4j: started but not responding — using no-op stub") | |
| return False | |
| except Exception as exc: | |
| print(f"[run] Neo4j: failed to start — {exc}") | |
| return False | |
| def _start_chroma() -> bool: | |
| """Start an embedded ChromaDB server if the chroma CLI is available.""" | |
| global _chroma_proc | |
| from backend.app.core.config import settings | |
| chroma_bind_host = settings.chroma_host | |
| chroma_probe_host = _display_host(chroma_bind_host) | |
| chroma_port = settings.chroma_port | |
| # First check if ChromaDB is already running | |
| if _is_chroma_ready(chroma_probe_host, chroma_port): | |
| print(f"[run] ChromaDB: already running on {chroma_probe_host}:{chroma_port}") | |
| return True | |
| # Find chroma binary | |
| chroma_bin = shutil.which("chroma") | |
| if not chroma_bin: | |
| print("[run] ChromaDB: chroma CLI not found — using no-op stub") | |
| print("[run] Install: pip install chromadb") | |
| return False | |
| try: | |
| data_dir = os.path.join(ROOT_DIR, ".chroma_data") | |
| os.makedirs(data_dir, exist_ok=True) | |
| _chroma_proc = subprocess.Popen( | |
| [chroma_bin, "run", "--path", data_dir, "--host", chroma_bind_host, "--port", str(chroma_port)], | |
| stdout=subprocess.DEVNULL, | |
| stderr=subprocess.DEVNULL, | |
| ) | |
| print("[run] ChromaDB: starting ...") | |
| for _ in range(10): | |
| if _is_chroma_ready(chroma_probe_host, chroma_port): | |
| print(f"[run] ChromaDB: started on {chroma_probe_host}:{chroma_port}") | |
| return True | |
| if _chroma_proc.poll() is not None: | |
| print("[run] ChromaDB: process exited before becoming ready — using no-op stub") | |
| return False | |
| time.sleep(0.5) | |
| if _chroma_proc.poll() is None: | |
| print( | |
| "[run] ChromaDB: still warming up in background — " | |
| "vector search will connect automatically once ready" | |
| ) | |
| return True | |
| print("[run] ChromaDB: process exited before becoming ready — using no-op stub") | |
| return False | |
| except Exception as exc: | |
| print(f"[run] ChromaDB: failed to start — {exc}") | |
| return False | |
| def main() -> None: | |
| parser = argparse.ArgumentParser(description="Run Orsync Scenarist backend locally") | |
| default_host = "127.0.0.1" if os.name == "nt" else "0.0.0.0" | |
| parser.add_argument("--host", default=default_host, help=f"Bind address (default: {default_host})") | |
| parser.add_argument("--port", type=int, default=None, help="Port (default: from .env or 7860)") | |
| parser.add_argument("--reload", action="store_true", help="Enable auto-reload for development") | |
| parser.add_argument("--no-redis", action="store_true", help="Skip embedded Redis startup") | |
| parser.add_argument("--no-neo4j", action="store_true", help="Skip embedded Neo4j startup") | |
| parser.add_argument("--no-chroma", action="store_true", help="Skip embedded ChromaDB startup") | |
| args = parser.parse_args() | |
| os.chdir(ROOT_DIR) | |
| _ensure_setup() | |
| from backend.app.core.config import settings | |
| port = args.port or settings.port | |
| if not _is_port_available(args.host, port): | |
| display_host = _display_host(args.host) | |
| print(f"[run] Port {port} is already in use on {args.host}.") | |
| print(f"[run] Check the existing server: http://{display_host}:{port}/healthz") | |
| print(f"[run] Or start another instance on a different port: python run.py --port {port + 1}") | |
| sys.exit(1) | |
| # Start embedded services unless --no-* or running inside Docker | |
| is_docker = os.path.isfile("/.dockerenv") | |
| if not args.no_redis and not is_docker: | |
| _start_redis() | |
| if not args.no_neo4j and not is_docker: | |
| _start_neo4j() | |
| if not args.no_chroma and not is_docker: | |
| _start_chroma() | |
| print(f"[run] Starting Orsync Scenarist on http://{args.host}:{port}") | |
| print(f"[run] LLM model : {settings.ollama_model}") | |
| print(f"[run] Embeddings: {settings.embedding_model}") | |
| print(f"[run] Ollama host: {settings.ollama_host}") | |
| import uvicorn | |
| try: | |
| uvicorn.run( | |
| "backend.app.main:app", | |
| host=args.host, | |
| port=port, | |
| reload=args.reload, | |
| log_level="info", | |
| log_config=None, | |
| access_log=False, | |
| timeout_keep_alive=0, | |
| ) | |
| finally: | |
| if _redis_proc is not None: | |
| _redis_proc.terminate() | |
| _redis_proc.wait(timeout=5) | |
| if _neo4j_proc is not None: | |
| _neo4j_proc.terminate() | |
| _neo4j_proc.wait(timeout=10) | |
| if _chroma_proc is not None: | |
| _chroma_proc.terminate() | |
| _chroma_proc.wait(timeout=5) | |
| if __name__ == "__main__": | |
| main() | |