feat: Add start_llama and start_loop CLI commands

Added to rdagent CLI:
- rdagent start_llama: Start llama.cpp server (replaces start_llama.sh)
- rdagent start_loop: Start strategy generator loop (replaces start_strategy_loop.sh)

Features:
- start_llama: --model, --port, --gpu-layers, --ctx-size, --reasoning options
- start_loop: --target, --max-wait options with auto-restart on crash
- Both commands have full help (--help) and examples

Moved .sh scripts to scripts/:
- start_llama.sh → scripts/ (kept as reference)
- start_strategy_loop.sh → scripts/ (kept as reference)

Usage:
  rdagent start_llama                    # Start LLM server
  rdagent start_llama --gpu-layers 40    # Custom GPU layers
  rdagent start_loop                     # Start strategy loop
  rdagent start_loop --target 5          # Generate 5 strategies per run
This commit is contained in:
TPTBusiness
2026-04-09 14:49:00 +02:00
parent a523e62d94
commit 362cff291c
3 changed files with 219 additions and 0 deletions
+219
View File
@@ -1023,3 +1023,222 @@ def _generate_single_strategy_report(strategy_file: Path, output_dir: Path) -> D
if __name__ == "__main__":
app()
@app.command(name="start_llama")
def start_llama_cli(
model: str = typer.Option(
None, "--model", "-m", help="Path to model file"
),
port: int = typer.Option(8081, "--port", "-p", help="Server port"),
gpu_layers: int = typer.Option(30, "--gpu-layers", "-g", help="GPU layers"),
ctx_size: int = typer.Option(80000, "--ctx-size", "-c", help="Context size"),
reasoning: bool = typer.Option(False, "--reasoning", help="Enable reasoning mode"),
):
"""
Start llama.cpp server for local LLM inference.
Options:
--model/-m: Path to model file (default: from .env or ~/models/qwen3.5/)
--port/-p: Server port (default: 8081)
--gpu-layers/-g: GPU layers (default: 30)
--ctx-size/-c: Context size (default: 80000)
--reasoning: Enable reasoning mode (default: off)
Examples:
rdagent start_llama
rdagent start_llama --gpu-layers 40 --ctx-size 4096
rdagent start_llama --reasoning
"""
import subprocess
import sys
import os
model_path = model or os.getenv(
"LLAMA_MODEL_PATH",
str(Path.home() / "models" / "qwen3.5" / "Qwen3.5-35B-A3B-Q3_K_M.gguf"),
)
llama_server = str(Path.home() / "llama.cpp" / "build" / "bin" / "llama-server")
if not Path(llama_server).exists():
print(f"❌ llama.cpp server not found: {llama_server}")
print("\nBuild it first:")
print(" cd ~/llama.cpp && mkdir -p build && cd build && cmake .. && make")
sys.exit(1)
if not Path(model_path).exists():
print(f"❌ Model not found: {model_path}")
sys.exit(1)
cmd = [
llama_server,
"--model", model_path,
"--n-gpu-layers", str(gpu_layers),
"--ctx-size", str(ctx_size),
"--port", str(port),
"--threads", "8",
"--threads-batch", "8",
"--parallel", "1",
"--flash-attn",
"--jinja",
"--host", "0.0.0.0",
]
if not reasoning:
cmd.extend(["--reasoning", "off"])
print(f"🚀 Starting llama.cpp server...")
print(f" Model: {Path(model_path).name}")
print(f" Port: {port}")
print(f" GPU Layers: {gpu_layers}")
print(f" Context: {ctx_size}")
print(f" Reasoning: {'on' if reasoning else 'off'}")
print()
try:
os.execvp(cmd[0], cmd)
except Exception as e:
print(f"❌ Failed to start llama.cpp server: {e}")
sys.exit(1)
@app.command(name="start_loop")
def start_loop_cli(
target_count: int = typer.Option(3, "--target", "-t", help="Strategies per run"),
max_wait: int = typer.Option(1800, "--max-wait", "-w", help="Max wait per run (seconds)"),
):
"""
Start PREDIX strategy generator loop.
Runs continuously, generating strategies with automatic restart on crash.
Options:
--target/-t: Strategies to generate per run (default: 3)
--max-wait/-w: Max wait time per run in seconds (default: 1800 = 30min)
Examples:
rdagent start_loop
rdagent start_loop --target 5 --max-wait 3600
"""
import subprocess
import signal
import sys
import os
from datetime import datetime
import time
script_dir = str(Path(__file__).parent.parent.parent.parent)
generator = f"python {script_dir}/scripts/predix_smart_strategy_gen.py"
logfile = f"{script_dir}/results/logs/generator_loop.log"
pidfile = "/tmp/predix_loop.pid"
os.makedirs(f"{script_dir}/results/logs", exist_ok=True)
def log(msg: str):
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
line = f"{ts} - {msg}"
print(line)
with open(logfile, "a") as f:
f.write(line + "\n")
def cleanup(signum=None, frame=None):
log("Received termination signal. Cleaning up...")
try:
subprocess.run(["pkill", "-f", "predix_smart_strategy_gen.py"], capture_output=True)
except Exception:
pass
try:
os.remove(pidfile)
except FileNotFoundError:
pass
log("Cleanup complete. Exiting.")
sys.exit(0)
signal.signal(signal.SIGTERM, cleanup)
signal.signal(signal.SIGINT, cleanup)
with open(pidfile, "w") as f:
f.write(str(os.getpid()))
log("=========================================")
log("🚀 PREDIX Generator Loop Starting")
log("=========================================")
log(f"Target: {target_count} strategies per run")
log(f"Max wait: {max_wait}s per run")
log(f"Log: {logfile}")
attempt = 0
while True:
attempt += 1
log("")
log(f"=== Attempt #{attempt} ===================================")
# Check disk space
try:
usage = subprocess.run(["df", "-h", script_dir], capture_output=True, text=True)
disk_line = usage.stdout.strip().split("\n")[-1]
pct = int(disk_line.split()[4].replace("%", ""))
if pct > 90:
log(f"⚠️ Disk usage at {pct}%. Pausing...")
time.sleep(300)
continue
except Exception:
pass
# Count existing strategies
from pathlib import Path as P
strat_dir = P(f"{script_dir}/results/strategies_new")
strat_count = len(list(strat_dir.glob("*.json"))) if strat_dir.exists() else 0
log(f"📁 Existing strategies: {strat_count}")
# Kill stale processes
try:
subprocess.run(["pkill", "-9", "-f", "predix_smart_strategy_gen.py"], capture_output=True)
except Exception:
pass
time.sleep(2)
# Start generator
log("🤖 Starting generator...")
proc = subprocess.Popen(
generator.split(),
cwd=script_dir,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
log(f" PID: {proc.pid}")
# Monitor progress
elapsed = 0
while proc.poll() is None:
time.sleep(30)
elapsed += 30
if elapsed % 120 == 0:
log(f" ⏱️ {elapsed}s elapsed")
if elapsed >= max_wait:
log(f" ⏰ Timeout after {elapsed}s. Killing...")
proc.kill()
break
# Check results
exit_code = proc.wait()
if exit_code == 0:
log("✅ Generator completed successfully")
elif exit_code == -9:
log("❌ Generator killed (OOM? Exit 137)")
else:
log(f"⚠️ Generator exited with code {exit_code}")
# Count new strategies
new_strats = sorted(strat_dir.glob("*.json"), key=lambda x: x.stat().st_mtime, reverse=True)[:3]
if new_strats:
log("📊 Latest strategies:")
for s in new_strats:
log(f" - {s.name}")
log("⏳ Waiting 60s before next attempt...")
time.sleep(60)