Skip to content

Instantly share code, notes, and snippets.

@wsuff
Last active July 4, 2025 01:48
Show Gist options
  • Save wsuff/55f347caf88b8155e5ba1b10910ce0f3 to your computer and use it in GitHub Desktop.
Save wsuff/55f347caf88b8155e5ba1b10910ce0f3 to your computer and use it in GitHub Desktop.
amucfg.py: Automating AI Model Metadata for Aider with OpenRouter
import requests
import json
import os
import time
import yaml
import argparse
import re
from datetime import datetime
OPENROUTER_API_BASE = "https://openrouter.ai/api/v1"
MODEL_CACHE_FILE = "openrouter_models.json"
ENDPOINT_CACHE_DIR = "endpoints_cache"
MY_MODELS_FILE = "my_models.json"
AIDER_MODEL_SETTINGS_FILE = ".aider.model.settings.yml"
AIDER_METADATA_FILE = "aider_model_metadata.json"
MY_MODEL_SLUGS = {
"google/gemini-2.0-flash-001": ["coding"],
"google/gemini-flash-1.5": ["bulk"],
"anthropic/claude-3.5-sonnet": ["coding"],
"anthropic/claude-3.5-haiku": ["bulk"],
}
MODEL_SERIES = ["GPT", "Claude", "Gemini", "DeepSeek", "Mistral", "Llama3"]
MODEL_VENDORS = ["meta-llama", "openai", "google", "anthropic", "mistral", "deepseek"]
DEFAULT_PROVIDERS = ["DeepSeek", "OpenAI", "Anthropic", "Google", "Mistral"]
DEFAULT_CONFIG_DIR = os.path.expanduser("~/.ai-model-utils")
DEFAULT_AIDER_CONFIG_DIR = os.path.expanduser("~/")
def fetch_openrouter_models(config_dir):
try:
response = requests.get(f"{OPENROUTER_API_BASE}/models")
response.raise_for_status()
models = response.json().get("data", [])
save_json_data(models, MODEL_CACHE_FILE, config_dir) # Cache the fetched models
return models
except requests.exceptions.RequestException as e:
print(f"Error fetching models: {e}")
return None
def load_cached_models(config_dir):
filepath = os.path.join(config_dir, MODEL_CACHE_FILE)
if os.path.exists(filepath):
with open(filepath, "r") as f:
return json.load(f)
return None
def update_model_cache(config_dir, cache_time=86400):
filepath = os.path.join(config_dir, MODEL_CACHE_FILE)
if os.path.exists(filepath):
modified_time = os.path.getmtime(filepath)
if time.time() - modified_time < cache_time:
return load_cached_models(config_dir)
return fetch_openrouter_models(config_dir)
def fetch_model_endpoints(author, slug, config_dir):
cache_file = os.path.join(config_dir, ENDPOINT_CACHE_DIR, f"{author}_{slug}.json")
if os.path.exists(cache_file):
with open(cache_file, "r") as f:
return json.load(f)
try:
response = requests.get(f"{OPENROUTER_API_BASE}/models/{author}/{slug}/endpoints")
response.raise_for_status()
endpoints = response.json()
if not os.path.exists(os.path.join(config_dir, ENDPOINT_CACHE_DIR)):
os.makedirs(os.path.join(config_dir, ENDPOINT_CACHE_DIR))
with open(cache_file, "w") as f:
json.dump(endpoints, f, indent=4)
return endpoints
except requests.exceptions.RequestException as e:
print(f"Error fetching endpoints for {author}/{slug}: {e}")
return None
def create_my_models_json(models):
filtered_models = []
for model in models:
# Split vendor / model_slug from id
vendor, module_slug = model["id"].split("/", 1)
# Filter by Model Vendor
if vendor in MODEL_VENDORS and model["architecture"]["tokenizer"] in MODEL_SERIES:
# 1. General Timestamp Filtering (Keep recent models)
if model["created"] > 1700000000: # Adjust this timestamp as needed
model_slug = model["id"]
vendor, model_name = model_slug.split("/", 1) #Split on the / for vendor/model_name
match = re.match(r"^(.*?)(?:-([^-:]*))?(?::(.*))?$", model_name) #Regex on model_name
if match:
series = match.group(1)
version = match.group(2)
tags = match.group(3) if match.group(3) else None
else:
series = model["name"]
version = None
tags = None
print(f"Processing model: {model['name']}")
print(f"Series: {series}, Version: {version}, Tags: {tags}")
exclude = False
# Simplified Exclusion Logic (Exclude if ANY tags are present)
if tags:
exclude = True
if not exclude:
filtered_model = {
"id": model["id"],
"name": model["name"],
"created": model["created"],
"created_date": datetime.fromtimestamp(model["created"]).strftime("%Y-%m-%d"),
"architecture": model["architecture"],
"providers": [],
"series": series,
"version": version,
"tags": tags,
}
filtered_models.append(filtered_model)
filtered_models.sort(key=lambda x: x["id"])
return filtered_models
def create_aider_metadata_json(my_models, config_dir):
aider_metadata = {}
for model in my_models:
model_slug = model["id"]
# Check if the model slug is a key in MY_MODEL_SLUGS
if model_slug not in MY_MODEL_SLUGS.keys():
continue
author, slug = model_slug.split("/", 1)
model_endpoints = fetch_model_endpoints(author, slug, config_dir)
# Retrieve the endpoints from the model_endpoints dictionary,
# accessing the 'data' key and then the 'endpoints' key within the 'data' dictionary.
# If either of these keys do not exist, an empty dictionary will be returned by default.
endpoints = model_endpoints.get("data", {}).get("endpoints", {})
# The retrieved endpoints are stored in the 'endpoints' variable for further use.
# The following line is commented out, but it would assign the endpoints to the 'endpoints' key within the 'model' dictionary.
# model["endpoints"] = endpoints
# Initialize default values
max_tokens = 1000000
max_input_tokens = 1048576
max_output_tokens = 8192
input_cost_per_token = 0
output_cost_per_token = 0
litellm_provider = "openrouter"
mode = "chat"
model_key = f"{litellm_provider}/{model_slug}"
print(f"Processing model: {model_key}")
# Process endpoints to extract relevant information
if endpoints:
# Assuming you want to use the first endpoint for now
endpoint = endpoints[0]
max_tokens = endpoint.get("context_length", max_tokens)
max_completion_tokens = endpoint.get("max_completion_tokens", max_output_tokens)
max_prompt_tokens = endpoint.get("max_prompt_tokens", max_input_tokens)
pricing = endpoint.get("pricing", {})
input_cost_per_token = pricing.get("prompt")
output_cost_per_token = pricing.get("completion")
aider_metadata[model_key] = {
"max_tokens": max_tokens,
"max_input_tokens": max_prompt_tokens,
"max_output_tokens": max_completion_tokens,
"input_cost_per_token": input_cost_per_token,
"output_cost_per_token": output_cost_per_token,
"litellm_provider": "openrouter",
"mode": mode
}
filepath = os.path.join(config_dir, AIDER_METADATA_FILE)
with open(filepath, "w") as f:
json.dump(aider_metadata, f, indent=4)
print("Aider metadata JSON created.")
def get_config_dir(args):
if args.config_dir:
return args.config_dir
else:
return DEFAULT_CONFIG_DIR
def ensure_config_dir(config_dir):
if not os.path.exists(config_dir):
os.makedirs(config_dir)
print(f"Created configuration directory: {config_dir}")
def save_json_data(data, filename, config_dir):
filepath = os.path.join(config_dir, filename)
with open(filepath, "w") as f:
json.dump(data, f, indent=4)
print(f"{filename} saved to {filepath}")
def load_json_data(filename, config_dir):
filepath = os.path.join(config_dir, filename)
if os.path.exists(filepath):
with open(filepath, "r") as f:
return json.load(f)
return None
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="AI Model Utilities")
parser.add_argument("--config-dir", help="Path to the configuration directory")
args = parser.parse_args()
config_dir = get_config_dir(args)
ensure_config_dir(config_dir)
print(f"Using config directory: {config_dir}")
# Load cached models or fetch if not available
models = load_cached_models(config_dir)
if not models:
models = fetch_openrouter_models(config_dir)
# Now with OpenRouter models, we need to create My Models JSON
if models:
my_models = create_my_models_json(models)
if my_models:
save_json_data(my_models, MY_MODELS_FILE, config_dir)
create_aider_metadata_json(my_models, config_dir)
@wsuff
Copy link
Author

wsuff commented Feb 17, 2025

This script, amucfg.py, automates the process of generating model metadata for use with Aider, the AI pair programming tool. It leverages the OpenRouter API to fetch information about available AI models and their endpoints, then filters and formats this data into a my_models.json
file and an aider_model_metadata.json file.

Key Features:

• OpenRouter Integration: Fetches model information directly from the OpenRouter API.
• Model Filtering: Filters models based on vendor, architecture, and recency.
• Metadata Generation: Creates my_models.json (a list of models) and aider_model_metadata.json (Aider-specific metadata including token limits and pricing).
• Caching: Caches model data and endpoint information to reduce API calls.
• Customizable: Uses a configuration directory to store cached data and generated files.

Usage:

1 Clone this Gist.
2 Install the required Python packages: requests, PyYAML.
3 Run the script: python amucfg.py [--config-dir ]. The --config-dir argument lets you specify a directory to store the generated files. If not specified, it defaults to ~/.ai-model-utils. (I didn't want to overwrite anything while iterating)
4 The my_models.json and aider_model_metadata.json files will be created in the specified configuration directory.
5 Copy aider_model_metadata.json to where aider can read it if --config-dir wasn't set to a folder Aider can read already.

Why this is useful for Aider users:

Aider relies on accurate model metadata for optimal performance. This script simplifies the process of obtaining and formatting this metadata, ensuring that Aider users can easily access and utilize the latest AI models available through OpenRouter. This is especially helpful for
managing token limits and cost estimations.

Yes, Aider/Gemini Flash was used on this project as well as this comment Just thought I'd share what I put together even tho it doesn't do what I wanted completely.

@wsuff
Copy link
Author

wsuff commented Feb 19, 2025

BUG Fix: Aider model config now uses model_key instead of model_slug so openrouter/ is added to model_slug

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment