scenarist / run.py
github-actions[bot]
Sync backend to Hugging Face Space (commit: 39b5c807918249fa80049d49f4b6a74d6a0ed1fc)
6d86412
Raw
History Blame Contribute Delete
10.8 kB
"""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()