#!/usr/bin/env python3
"""
OpenRouter Model Picker
Fetches available models from OpenRouter and helps select cost-effective model pairs
for RLM root + recursion usage patterns.
Usage:
python scripts/openrouter_model_picker.py [--root-hint PATTERN] [--other-hint PATTERN]
Examples:
# Show all coding models
python scripts/openrouter_model_picker.py --root-hint coder --other-hint coder
# Find Qwen models
python scripts/openrouter_model_picker.py --root-hint qwen --other-hint qwen
# Show cheapest models
python scripts/openrouter_model_picker.py
"""
import argparse
import json
import sys
from typing import Dict, List, Optional
try:
import requests
except ImportError:
print("Error: requests library required. Install with: pip install requests")
sys.exit(1)
class OpenRouterModelPicker:
"""Fetch and filter OpenRouter models for RLM usage."""
BASE_URL = "https://openrouter.ai/api/v1"
def __init__(self, api_key: Optional[str] = None):
self.api_key = api_key or self._get_api_key()
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
})
def _get_api_key(self) -> str:
"""Get API key from environment."""
import os
key = os.getenv("OPENROUTER_API_KEY")
if not key:
raise ValueError("OPENROUTER_API_KEY environment variable required")
return key
def fetch_models(self) -> List[Dict]:
"""Fetch all available models from OpenRouter."""
try:
response = self.session.get(f"{self.BASE_URL}/models")
response.raise_for_status()
data = response.json()
return data.get("data", [])
except requests.exceptions.RequestException as e:
print(f"Error fetching models: {e}")
return []
def filter_models(self, models: List[Dict], pattern: str) -> List[Dict]:
"""Filter models by pattern in model ID."""
if not pattern:
return models
return [m for m in models if pattern.lower() in m.get("id", "").lower()]
def sort_by_cost(self, models: List[Dict], input_priority: bool = True) -> List[Dict]:
"""Sort models by cost (input tokens prioritized by default)."""
def cost_key(model):
pricing = model.get("pricing", {})
input_cost = float(pricing.get("prompt", "0").replace("$", "") or 0)
output_cost = float(pricing.get("completion", "0").replace("$", "") or 0)
# Prioritize input cost for root models, output cost for recursion
return input_cost if input_priority else (input_cost + output_cost) / 2
return sorted(models, key=cost_key)
def format_model_info(self, model: Dict) -> str:
"""Format model information for display."""
model_id = model.get("id", "unknown")
pricing = model.get("pricing", {})
context = model.get("context_length", 0)
input_cost = pricing.get("prompt", "$0")
output_cost = pricing.get("completion", "$0")
return f"{model_id:<40} | In: {input_cost:>8} | Out: {output_cost:>8} | Ctx: {context}"
def suggest_pairs(self, root_hint: str = "", other_hint: str = "") -> None:
"""Suggest root + other model pairs."""
models = self.fetch_models()
if not models:
print("No models fetched. Check your API key and internet connection.")
return
# Filter candidates
root_candidates = self.filter_models(models, root_hint)
other_candidates = self.filter_models(models, other_hint)
# Sort by cost
root_candidates = self.sort_by_cost(root_candidates, input_priority=True)[:10]
other_candidates = self.sort_by_cost(other_candidates, input_priority=False)[:10]
print("=== RLM Model Pair Suggestions ===")
print(f"Root models ({len(root_candidates)} candidates):")
for i, model in enumerate(root_candidates[:5], 1):
print(f"{i}. {self.format_model_info(model)}")
print(f"\nOther models ({len(other_candidates)} candidates):")
for i, model in enumerate(other_candidates[:5], 1):
print(f"{i}. {self.format_model_info(model)}")
# Suggest optimal pairs
print("\n=== Recommended Pairs (Strong Root + Cheap Other) ===")
if root_candidates and other_candidates:
# Best root (not necessarily cheapest, but good quality/cost)
best_root = root_candidates[min(2, len(root_candidates)-1)] # 3rd cheapest for quality
best_other = other_candidates[0] # Cheapest
print(f"Root: {self.format_model_info(best_root)}")
print(f"Other: {self.format_model_info(best_other)}")
# Calculate estimated cost savings
root_input = float(best_root.get("pricing", {}).get("prompt", "0").replace("$", "") or 0)
other_input = float(best_other.get("pricing", {}).get("prompt", "0").replace("$", "") or 0)
root_output = float(best_root.get("pricing", {}).get("completion", "0").replace("$", "") or 0)
other_output = float(best_other.get("pricing", {}).get("completion", "0").replace("$", "") or 0)
print(f"Combined Input Cost: ${root_input + other_input:.6f}")
print(f"Combined Output Cost: ${root_output + other_output:.6f}")
else:
print("Insufficient model data for cost calculation.")
def main():
parser = argparse.ArgumentParser(description="OpenRouter model picker for RLM")
parser.add_argument("--root-hint", default="",
help="Pattern to filter root models (e.g., 'qwen', 'gpt')")
parser.add_argument("--other-hint", default="",
help="Pattern to filter other/recursion models")
parser.add_argument("--api-key", help="OpenRouter API key (or set OPENROUTER_API_KEY)")
args = parser.parse_args()
try:
picker = OpenRouterModelPicker(args.api_key)
picker.suggest_pairs(args.root_hint, args.other_hint)
except ValueError as e:
print(f"Error: {e}")
sys.exit(1)
except KeyboardInterrupt:
print("\nCancelled.")
sys.exit(0)
if __name__ == "__main__":
main()