Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 13 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,41 +24,34 @@ Subnet 1 is the most intelligent inference model on Bittensor. As the first agen

---

## Installation
## Run Validator

1. **Clone the repository:**
```bash
git clone https://github.com/macrocosm-os/apex.git
cd apex
```

2. **Install `uv`:**
Follow the instructions at [https://github.com/astral-sh/uv](https://github.com/astral-sh/uv) to install `uv`. For example:

2. **Prepare config file:**
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
cp config/mainnet.yaml.example config/mainnet.yaml
# Fill in the required values in config/mainnet.yaml
```

3. **Install the project and its development dependencies:**
3. **[Recommended] Run validator with auto-updater:**
```bash
uv venv --python=3.11 && uv pip install '.[dev]'
python scripts/autoupdater.py -c config/mainnet.yaml
```

4. **Activate python environment:**
```bash
. .venv/bin/activate
```

## Run Mainnet Validator

1. Prepare config file:
4. **[Alternative #1] Run validator with pm2 and auto-updater:**
```bash
cp config/mainnet.yaml.example config/mainnet.yaml
# Fill in the required values in config/mainnet.yaml
bash scripts/autoupdater_pm2.sh
```

2. **Run the validator:**
5. **[Alternative #2] Install dependencies and run validator without auto-updater:**
```bash
python validator.py -c config/mainnet.yaml
uv venv --python 3.11 && uv pip install '.[dev]' && python validator.py -c config/mainnet.yaml
```

## Run Testnet Validator
Expand All @@ -69,9 +62,9 @@ Subnet 1 is the most intelligent inference model on Bittensor. As the first agen
# Fill in the required values in config/testnet.yaml
```

2. **Run the validator:**
2. Install dependencies and run validator:
```bash
python validator.py -c config/testnet.yaml
uv venv --python 3.11 && uv pip install '.[dev]' && python validator.py -c config/testnet.yaml
```

## Base Miner (for showcase purposes only)
Expand Down
4 changes: 2 additions & 2 deletions apex/validator/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ def __init__(
deep_research: DeepResearchBase,
logger_apex: LoggerApex | None = None,
num_consumers: int = 5,
timeout_consumer: float = 180,
timeout_producer: float = 36,
timeout_consumer: float = 1200,
timeout_producer: float = 240,
queue_size: int = 10_000,
redundancy_rate: float = 0.05, # The rate that references are generated in addition to generator steps
reference_rate: float = 0.5, # The rate that references are generated as opposed to generator steps
Expand Down
7 changes: 1 addition & 6 deletions apex/validator/weight_syncer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import asyncio
import os
import time
from typing import cast

Expand Down Expand Up @@ -71,7 +70,7 @@ async def start(self) -> None:
)
self.server = uvicorn.Server(config)
self.server_task = asyncio.create_task(self.server.serve())
logger.info(f"Started weight synchronization API on port {self.port}. pid={os.getpid()} self_id={id(self)}")
logger.info(f"Started weight synchronization API on port {self.port}")

# Announce the axon on the network.
external_ip = requests.get("https://checkip.amazonaws.com").text.strip()
Expand Down Expand Up @@ -124,10 +123,6 @@ async def compute_weighted_rewards(self, hotkey_rewards: dict[str, float]) -> di
"""Computes weighted rewards by fetching rewards from other validators and averaging them by stake."""
self.hotkey_rewards = hotkey_rewards
self.last_update_time = time.time()
# logger.debug(f"Updating rewards at: {self.last_update_time}")
import os

logger.debug(f"Updating rewards at: {self.last_update_time} pid={os.getpid()} self_id={id(self)}")
if not self.receive_enabled:
logger.warning("Rewards weight averaging is disable, using raw rewards")
return hotkey_rewards
Expand Down
15 changes: 7 additions & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ dependencies = [
"aiohttp>=3.8.3",
"beautifulsoup4>=4.13.3",
"langchain>=0.3.26",
"langchain-core>=0.3.68",
"langchain-community>=0.0.59",
"faiss-cpu>=1.8.0",
"langchain-openai>=0.3.28",
"langchain-sandbox>=0.0.6",
"faiss-cpu>=1.8.0",
"dotenv>=0.9.9",
"rich>=14.0.0",
"loguru>=0.7.3",
Expand All @@ -29,20 +30,17 @@ dependencies = [
"rouge>=1.0.1",
"substrate-interface>=1.7.11",
"types-netaddr>=1.3.0.20240530",
"types-pyyaml>=6.0.12.20250516",
"types-cachetools>=6.0.0.20250525",
"dotenv>=0.9.9",
"pytest-mock>=3.14.1",
]


[project.optional-dependencies]
dev = [
"mypy==1.17.0",
"ruff==0.12.5",
"types-pyyaml>=6.0.12.20250516",
"types-cachetools>=6.0.0.20250525",
"langchain>=0.3.26",
"dotenv>=0.9.9",
"langchain-openai>=0.3.28",
"langchain-core>=0.3.68",
"langchain-sandbox>=0.0.6",
"pytest>=8.4.1",
"pytest-asyncio>=1.0.0",
"pytest-cov>=5.0.0",
Expand Down Expand Up @@ -203,5 +201,6 @@ dev = [
"pydantic>=2.11.7",
"pytest>=8.4.1",
"pytest-asyncio>=1.0.0",
"pytest-mock>=3.14.1",
"types-pyyaml>=6.0.12.20250516",
]
111 changes: 111 additions & 0 deletions scripts/autoupdater.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#!/usr/bin/env python3
import argparse
import os
import signal
import subprocess
import sys
import time
from pathlib import Path

CHECK_INTERVAL = 15 * 60


def venv_python() -> str:
return os.path.join(".venv", "bin", "python")


def read_python_version() -> str | None:
try:
with open(".python-version", encoding="utf-8") as f:
# Take first non-empty token (pyenv format e.g. "3.11.9").
return f.read().strip().split()[0]
except FileNotFoundError:
return None


def start_proc(config: Path) -> subprocess.Popen:
py_ver = read_python_version()
if py_ver:
subprocess.run(["uv", "venv", "--python", py_ver], check=True)
else:
subprocess.run(["uv", "venv"], check=True)

# Install project in dev mode into the venv.
subprocess.run(["uv", "pip", "install", ".[dev]"], check=True)

# Run validator.
return subprocess.Popen([venv_python(), "validator.py", "-c", str(config)])


def stop_proc(process: subprocess.Popen) -> None:
if process and process.poll() is None:
process.terminate()
try:
process.wait(timeout=10)
except subprocess.TimeoutExpired:
process.kill()


def remote_has_updates() -> bool:
try:
subprocess.run(["git", "fetch", "--quiet"], check=True)
out = subprocess.check_output(
["git", "rev-list", "--left-right", "--count", "@{u}...HEAD"], stderr=subprocess.STDOUT, text=True
).strip()
left, right = map(int, out.split())
# Remote is ahead.
return left > 0
except subprocess.CalledProcessError:
# No upstream or git issue; treat as no updates.
return False


def git_pull_ff_only() -> None:
try:
subprocess.run(["git", "pull", "--ff-only"], check=True)
except subprocess.CalledProcessError as e:
print(f"Error: Git pull failed due to conflicts or other issues: {e}", file=sys.stderr)
print("Staying on the current version.", file=sys.stderr)


def read_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Apex validator")
parser.add_argument(
"-c",
"--config",
# default="config/testnet.yaml",
default="config/mainnet.yaml",
help="Config file path (e.g. config/mainnet.yaml).",
type=Path,
)
args = parser.parse_args()
return args


def main() -> None:
args = read_args()
proc = start_proc(config=args.config)

def handle_sigint(sig, frame):
stop_proc(proc)
sys.exit(0)

signal.signal(signal.SIGINT, handle_sigint)

while True:
time.sleep(CHECK_INTERVAL)
print("Checking for updates...")

# If child exited, propagate its code.
if proc.poll() is not None:
sys.exit(proc.returncode)

if remote_has_updates():
print("Updates detected, restarting validator")
stop_proc(proc)
git_pull_ff_only()
proc = start_proc(config=args.config)


if __name__ == "__main__":
main()
46 changes: 46 additions & 0 deletions scripts/autoupdater_pm2.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
APP_NAME="sn1"
CONFIG="config/mainnet.yaml"
# CONFIG="config/testnet.yaml"

UV_INSTALL_URL="https://astral.sh/uv/install.sh"

# Ensure common user bin dirs are in PATH.
export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$HOME/bin:$HOME/.npm-global/bin:$PATH"

# 1) Ensure uv exists.
if ! command -v uv >/dev/null 2>&1; then
echo "[info] uv not found; installing..."
if ! command -v curl >/dev/null 2>&1; then
echo "[error] curl is required to install uv." >&2
exit 1
fi
curl -LsSf "$UV_INSTALL_URL" | sh
hash -r
if ! command -v uv >/dev/null 2>&1; then
echo "[error] uv installation completed but 'uv' not found in PATH." >&2
exit 1
fi
else
echo "[info] uv found: $(command -v uv)"
fi

# 2) Ensure pm2 exists.
if ! command -v pm2 >/dev/null 2>&1; then
echo "[info] pm2 not found; installing globally with npm..."
if ! command -v npm >/dev/null 2>&1; then
echo "[error] npm is required to install pm2. Please install Node.js first." >&2
exit 1
fi
npm install -g pm2
hash -r
if ! command -v pm2 >/dev/null 2>&1; then
echo "[error] pm2 installation completed but 'pm2' not found in PATH." >&2
exit 1
fi
else
echo "[info] pm2 found: $(command -v pm2)"
fi

pm2 start scripts/autoupdater.py --interpreter .venv/bin/python --name sn1 -- -c $CONFIG
pm2 logs sn1
Loading