material_forge_env / benchmark.py
ArshPathan's picture
Upload folder using huggingface_hub
0483756
Raw History Blame Contribute Delete
6.83 kB
import asyncio
import os
import random
import csv
import json
import statistics
import time
import subprocess
import socket
from datetime import datetime
from typing import List, Dict
# Standardize environment variables BEFORE importing inference
# This handles the case where users set variables with quotes in CMD
for key in ["API_BASE_URL", "API_KEY", "MODEL_NAME"]:
val = os.environ.get(key)
if val:
# Strip literal quotes that CMD sometimes includes
os.environ[key] = val.strip('"').strip("'")
# Set defaults if not provided
if "API_BASE_URL" not in os.environ:
os.environ["API_BASE_URL"] = "http://127.0.0.1:11434/v1"
if "API_KEY" not in os.environ:
os.environ["API_KEY"] = "ollama"
# (MODEL_NAME is handled inside inference.py default, but we can set it here too)
if "MODEL_NAME" not in os.environ:
os.environ["MODEL_NAME"] = "qwen3.5:latest"
# Import the core logic from inference.py
from inference import (
MaterialForgeEnv,
run_episode,
start_environment_server,
TASKS as ORIGINAL_TASKS,
MODEL_NAME,
SUCCESS_THRESHOLD
)
from openai import OpenAI
def is_port_open(port):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
return s.connect_ex(('127.0.0.1', port)) == 0
def start_ollama_server():
if is_port_open(11434):
print("[INFO] Ollama server is already running on port 11434.")
return None
print("[INFO] Ollama server not detected. Starting 'ollama serve'...")
try:
proc = subprocess.Popen(
["ollama", "serve"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.DETACHED_PROCESS if os.name == 'nt' else 0
)
# Wait up to 30 seconds for Ollama to become ready
start_time = time.time()
while time.time() - start_time < 30:
if is_port_open(11434):
print("[INFO] Ollama server started successfully.")
# Give it an extra second to initialize routes
time.sleep(2)
return proc
time.sleep(1)
print("[WARNING] Ollama server start timed out. It may still be warming up.")
return proc
except Exception as e:
print(f"[ERROR] Failed to start Ollama server: {e}")
return None
async def run_analytical_suite(num_trials: int = 100, mode: str = "llm"):
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
report_file = f"benchmark_{mode}_{timestamp}.csv"
summary_file = f"summary_{mode}_{timestamp}.txt"
ollama_proc = None
if mode == "llm":
ollama_proc = start_ollama_server()
print(f"πŸ“Š Starting Analytical Benchmark Task ({mode.upper()} mode)")
print(f"πŸ•’ Timestamp: {timestamp}")
print(f"πŸ“ Logging results to: {report_file}")
api_url = os.environ.get("API_BASE_URL", "http://localhost:11434/v1")
api_key = os.environ.get("API_KEY", "ollama")
# In heuristic mode, we don't need a real client
client = OpenAI(base_url=api_url, api_key=api_key) if mode == "llm" else None
if mode == "heuristic":
print("πŸ›  Mode: Pure Heuristic (LLM Bypassed)")
else:
print(f"πŸ€– Model: {os.getenv('MODEL_NAME', MODEL_NAME)}")
# Start the environment server once
server_proc = start_environment_server(port=7860)
env = MaterialForgeEnv(base_url="http://127.0.0.1:7860")
try:
await env.connect()
except Exception as e:
print(f"❌ Connection failed: {e}. Is Ollama or the server running?")
if server_proc: server_proc.terminate()
return
results = []
successes = 0
print("\n" + "="*50)
print(f"{'Trial':<8} | {'Seed':<8} | {'Score':<8} | {'Status':<10}")
print("-" * 50)
with open(report_file, mode='w', newline='') as csvfile:
fieldnames = ['trial', 'seed', 'score', 'success', 'timestamp']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
for i in range(1, num_trials + 1):
seed = random.randint(1000, 9999)
# Select a random task type from the original list as a template
template = random.choice(ORIGINAL_TASKS)
task = {
"name": f"analytical-{i}",
"difficulty": template["difficulty"],
"seed": seed
}
try:
# Run the episode without printing every single [STEP] to keep log clean
# We wrap it to suppress standard prints if we wanted to, but let's keep it simple
score = await run_episode(env, client, task)
is_success = score >= SUCCESS_THRESHOLD
results.append(score)
if is_success:
successes += 1
status = "βœ… PASS" if is_success else "❌ FAIL"
print(f"{i:<8} | {seed:<8} | {score:<8.3f} | {status:<10}")
writer.writerow({
'trial': i,
'seed': seed,
'score': f"{score:.3f}",
'success': is_success,
'timestamp': datetime.now().isoformat()
})
except Exception as e:
print(f"Trial {i} Error: {e}")
# Final Analysis
summary_data = [
"="*50,
"πŸ“ˆ FINAL ANALYTICAL REPORT",
"="*50,
f"Mode: {mode.upper()}",
f"Model: {os.getenv('MODEL_NAME', MODEL_NAME) if mode == 'llm' else 'Heuristic-Only'}",
f"Total Trials: {num_trials}",
f"Success Rate: {(successes/num_trials)*100:.1f}%",
f"Average Score: {statistics.mean(results):.3f}",
f"Max Score: {max(results):.3f}",
f"Min Score: {min(results):.3f}",
"="*50
]
report_text = "\n".join(summary_data)
print("\n" + report_text)
with open(summary_file, 'w') as f:
f.write(report_text)
# Cleanup
await env.close()
if server_proc:
server_proc.terminate()
server_proc.wait()
if ollama_proc:
print("[INFO] Terminating local Ollama server...")
ollama_proc.terminate()
try:
ollama_proc.wait(timeout=5)
except subprocess.TimeoutExpired:
ollama_proc.kill()
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--trials", type=int, default=100)
parser.add_argument("--mode", type=str, choices=["llm", "heuristic"], default="llm")
args = parser.parse_args()
asyncio.run(run_analytical_suite(args.trials, mode=args.mode))