qa1145 commited on
Commit
4c40bac
·
verified ·
1 Parent(s): c191741

Upload 9 files

Browse files
.env.example ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ OPENROUTER_API_KEYS=your_key1,your_key2,your_key3
2
+ SCAN_INTERVAL_HOURS=1
3
+ MAX_RETRIES=3
4
+ RETRY_DELAY=5
5
+ REQUEST_TIMEOUT=30
6
+ MAX_CONCURRENCY=5
7
+ TEST_PROMPT=hi
app.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from datetime import datetime
3
+
4
+ from src.config import get_api_keys, get_scan_interval_hours
5
+ from src.model_tester import ModelTester
6
+ from src.scheduler import Scheduler
7
+ from src.utils import format_timestamp
8
+
9
+
10
+ class AppState:
11
+ def __init__(self):
12
+ self.model_tester = ModelTester()
13
+ self.scheduler = Scheduler(task_callback=self.run_scan)
14
+ self.scan_result = {
15
+ "available_models": [],
16
+ "total_tested": 0,
17
+ "available_count": 0,
18
+ "timestamp": None
19
+ }
20
+ self.last_update_time = None
21
+
22
+ def run_scan(self):
23
+ result = self.model_tester.test_all_models()
24
+ self.scan_result = result
25
+ self.last_update_time = format_timestamp()
26
+
27
+
28
+ app_state = AppState()
29
+
30
+
31
+ def get_available_models():
32
+ models = app_state.scan_result.get("available_models", [])
33
+ return "\n".join(models) if models else "No models available yet"
34
+
35
+
36
+ def get_scan_status():
37
+ count = app_state.scan_result.get("available_count", 0)
38
+ tested = app_state.scan_result.get("total_tested", 0)
39
+ timestamp = app_state.last_update_time or "Never"
40
+ return f"Available: {count}/{tested} | Last scan: {timestamp}"
41
+
42
+
43
+ def trigger_scan():
44
+ app_state.scheduler.run_now()
45
+ return "Scan triggered! Results will update shortly..."
46
+
47
+
48
+ def get_api_keys_status():
49
+ try:
50
+ keys = get_api_keys()
51
+ return f"{len(keys)} keys configured"
52
+ except ValueError as e:
53
+ return f"Error: {str(e)}"
54
+
55
+
56
+ def get_interval_info():
57
+ return f"Scan interval: every {get_scan_interval_hours()} hour(s)"
58
+
59
+
60
+ with gr.Blocks(title="OpenRouter Free Models Scanner") as demo:
61
+ gr.Markdown("# OpenRouter Free Models Scanner")
62
+ gr.Markdown("Auto-scan available free models from OpenRouter")
63
+
64
+ with gr.Row():
65
+ gr.Markdown(f"### API Keys: {get_api_keys_status()}")
66
+ gr.Markdown(f"### {get_interval_info()}")
67
+
68
+ gr.Markdown("---")
69
+
70
+ with gr.Row():
71
+ gr.Markdown("## Status")
72
+ status_display = gr.Textbox(
73
+ value=get_scan_status(),
74
+ label="Scan Status",
75
+ interactive=False
76
+ )
77
+
78
+ with gr.Row():
79
+ scan_btn = gr.Button("Trigger Scan Now", variant="primary")
80
+ scan_result = gr.Textbox(
81
+ value="Click button to trigger scan",
82
+ label="Scan Result",
83
+ interactive=False
84
+ )
85
+
86
+ scan_btn.click(
87
+ fn=trigger_scan,
88
+ inputs=[],
89
+ outputs=[scan_result]
90
+ )
91
+
92
+ gr.Markdown("---")
93
+ gr.Markdown("## Available Models")
94
+
95
+ models_display = gr.Textbox(
96
+ value=get_available_models(),
97
+ label=f"Available Models ({len(app_state.scan_result.get('available_models', []))})",
98
+ interactive=False,
99
+ lines=20
100
+ )
101
+
102
+ demo.load(
103
+ fn=lambda: (
104
+ get_scan_status(),
105
+ get_available_models()
106
+ ),
107
+ outputs=[status_display, models_display]
108
+ )
109
+
110
+ app_state.scheduler.start()
111
+
112
+
113
+ if __name__ == "__main__":
114
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ gradio
2
+ requests
3
+ python-dotenv
4
+ schedule
src/__init__.py ADDED
File without changes
src/config.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import List
3
+ from dotenv import load_dotenv
4
+
5
+ load_dotenv()
6
+
7
+
8
+ def get_api_keys() -> List[str]:
9
+ keys_str = os.getenv("OPENROUTER_API_KEYS", "")
10
+ if not keys_str:
11
+ raise ValueError("OPENROUTER_API_KEYS environment variable is not set")
12
+ keys = [k.strip() for k in keys_str.split(",") if k.strip()]
13
+ if not keys:
14
+ raise ValueError("No valid API keys found in OPENROUTER_API_KEYS")
15
+ return keys
16
+
17
+
18
+ def get_scan_interval_hours() -> float:
19
+ return float(os.getenv("SCAN_INTERVAL_HOURS", "1"))
20
+
21
+
22
+ def get_max_retries() -> int:
23
+ return int(os.getenv("MAX_RETRIES", "3"))
24
+
25
+
26
+ def get_retry_delay() -> int:
27
+ return int(os.getenv("RETRY_DELAY", "5"))
28
+
29
+
30
+ def get_request_timeout() -> int:
31
+ return int(os.getenv("REQUEST_TIMEOUT", "30"))
32
+
33
+
34
+ def get_max_concurrency() -> int:
35
+ return int(os.getenv("MAX_CONCURRENCY", "5"))
36
+
37
+
38
+ def get_test_prompt() -> str:
39
+ return os.getenv("TEST_PROMPT", "hi")
src/model_tester.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import concurrent.futures
2
+ from typing import List, Dict, Any, Set
3
+ from datetime import datetime
4
+
5
+ from .openrouter_client import OpenRouterClient
6
+ from . import config
7
+ from .utils import clean_model_name
8
+
9
+
10
+ class ModelTester:
11
+ def __init__(self):
12
+ self.client = OpenRouterClient()
13
+ self.max_concurrency = config.get_max_concurrency()
14
+ self.test_prompt = config.get_test_prompt()
15
+
16
+ def get_all_free_models(self) -> List[str]:
17
+ models = self.client.get_models()
18
+ free_models = []
19
+
20
+ for model in models:
21
+ model_id = model.get("id", "")
22
+ if ":free" in model_id:
23
+ free_models.append(model_id)
24
+
25
+ return free_models
26
+
27
+ def test_single_model(self, model_id: str) -> tuple[str, bool]:
28
+ is_available = self.client.test_model(model_id, self.test_prompt)
29
+ cleaned_name = clean_model_name(model_id)
30
+ return cleaned_name, is_available
31
+
32
+ def test_all_models(self) -> Dict[str, Any]:
33
+ print(f"[{datetime.now()}] Starting model scan...")
34
+
35
+ free_models = self.get_all_free_models()
36
+ print(f"Found {len(free_models)} free models to test")
37
+
38
+ available_models: Set[str] = set()
39
+ tested_count = 0
40
+ success_count = 0
41
+
42
+ with concurrent.futures.ThreadPoolExecutor(
43
+ max_workers=self.max_concurrency
44
+ ) as executor:
45
+ future_to_model = {
46
+ executor.submit(self.test_single_model, model_id): model_id
47
+ for model_id in free_models
48
+ }
49
+
50
+ for future in concurrent.futures.as_completed(future_to_model):
51
+ tested_count += 1
52
+ try:
53
+ cleaned_name, is_available = future.result()
54
+ if is_available:
55
+ available_models.add(cleaned_name)
56
+ success_count += 1
57
+ print(f"[{tested_count}/{len(free_models)}] ✓ {cleaned_name}")
58
+ else:
59
+ print(f"[{tested_count}/{len(free_models)}] ✗ {cleaned_name}")
60
+ except Exception as e:
61
+ model_id = future_to_model[future]
62
+ print(f"[{tested_count}/{len(free_models)}] ! {model_id}: {e}")
63
+
64
+ result = {
65
+ "available_models": sorted(list(available_models)),
66
+ "total_tested": tested_count,
67
+ "available_count": success_count,
68
+ "timestamp": datetime.now().isoformat()
69
+ }
70
+
71
+ print(f"Scan complete: {success_count}/{tested_count} models available")
72
+ return result
src/openrouter_client.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import time
3
+ import requests
4
+ from typing import Optional, Dict, Any, List
5
+
6
+ from . import config
7
+
8
+
9
+ class OpenRouterClient:
10
+ def __init__(self):
11
+ self.api_keys = config.get_api_keys()
12
+ self.max_retries = config.get_max_retries()
13
+ self.retry_delay = config.get_retry_delay()
14
+ self.timeout = config.get_request_timeout()
15
+ self.current_key_index = 0
16
+
17
+ def get_random_key(self) -> str:
18
+ return random.choice(self.api_keys)
19
+
20
+ def get_next_key(self) -> str:
21
+ key = self.api_keys[self.current_key_index]
22
+ self.current_key_index = (self.current_key_index + 1) % len(self.api_keys)
23
+ return key
24
+
25
+ def _make_request(
26
+ self,
27
+ method: str,
28
+ url: str,
29
+ api_key: str,
30
+ **kwargs
31
+ ) -> requests.Response:
32
+ headers = kwargs.pop("headers", {})
33
+ headers["Authorization"] = f"Bearer {api_key}"
34
+ headers["Content-Type"] = "application/json"
35
+
36
+ return requests.request(
37
+ method=method,
38
+ url=url,
39
+ headers=headers,
40
+ timeout=self.timeout,
41
+ **kwargs
42
+ )
43
+
44
+ def request_with_retry(
45
+ self,
46
+ method: str,
47
+ url: str,
48
+ **kwargs
49
+ ) -> Optional[requests.Response]:
50
+ last_error = None
51
+
52
+ for attempt in range(self.max_retries):
53
+ api_key = self.get_random_key()
54
+
55
+ try:
56
+ response = self._make_request(method, url, api_key, **kwargs)
57
+
58
+ if response.status_code == 200:
59
+ return response
60
+ elif response.status_code == 429:
61
+ last_error = f"Rate limited (429)"
62
+ time.sleep(self.retry_delay * (attempt + 1))
63
+ elif response.status_code == 401:
64
+ last_error = f"Unauthorized (401)"
65
+ else:
66
+ last_error = f"HTTP {response.status_code}: {response.text[:100]}"
67
+ time.sleep(self.retry_delay)
68
+
69
+ except requests.exceptions.Timeout:
70
+ last_error = "Request timeout"
71
+ time.sleep(self.retry_delay)
72
+ except requests.exceptions.RequestException as e:
73
+ last_error = f"Request error: {str(e)}"
74
+ time.sleep(self.retry_delay)
75
+
76
+ print(f"Request failed after {self.max_retries} attempts: {last_error}")
77
+ return None
78
+
79
+ def get_models(self) -> List[Dict[str, Any]]:
80
+ url = "https://openrouter.ai/api/v1/models"
81
+ response = self.request_with_retry("GET", url)
82
+
83
+ if response and response.status_code == 200:
84
+ data = response.json()
85
+ return data.get("data", [])
86
+ return []
87
+
88
+ def test_model(self, model_id: str, prompt: str = "hi") -> bool:
89
+ url = "https://openrouter.ai/api/v1/chat/completions"
90
+ payload = {
91
+ "model": model_id,
92
+ "messages": [{"role": "user", "content": prompt}],
93
+ "max_tokens": 10
94
+ }
95
+
96
+ response = self.request_with_retry("POST", url, json=payload)
97
+
98
+ if response and response.status_code == 200:
99
+ return True
100
+ return False
src/scheduler.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import threading
2
+ import time
3
+ from datetime import datetime
4
+ from typing import Callable, Optional
5
+
6
+ import schedule
7
+
8
+ from . import config
9
+
10
+
11
+ class Scheduler:
12
+ def __init__(self, task_callback: Callable):
13
+ self.task_callback = task_callback
14
+ self.interval_hours = config.get_scan_interval_hours()
15
+ self._stop_event = threading.Event()
16
+ self._thread: Optional[threading.Thread] = None
17
+ self._running = False
18
+
19
+ def start(self):
20
+ if self._running:
21
+ return
22
+
23
+ schedule.every(self.interval_hours).hours.do(self._run_task)
24
+ self._run_task()
25
+
26
+ self._running = True
27
+ self._stop_event.clear()
28
+ self._thread = threading.Thread(target=self._scheduler_loop, daemon=True)
29
+ self._thread.start()
30
+ print(f"Scheduler started, will run every {self.interval_hours} hour(s)")
31
+
32
+ def stop(self):
33
+ self._running = False
34
+ self._stop_event.set()
35
+ schedule.clear()
36
+ print("Scheduler stopped")
37
+
38
+ def run_now(self):
39
+ self._run_task()
40
+
41
+ def _run_task(self):
42
+ try:
43
+ self.task_callback()
44
+ except Exception as e:
45
+ print(f"Error in scheduled task: {e}")
46
+
47
+ def _scheduler_loop(self):
48
+ while not self._stop_event.is_set():
49
+ schedule.run_pending()
50
+ time.sleep(1)
src/utils.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ from datetime import datetime
3
+
4
+
5
+ def clean_model_name(model_id: str) -> str:
6
+ if "/" in model_id:
7
+ model_id = model_id.split("/")[-1]
8
+ if ":free" in model_id:
9
+ model_id = model_id.replace(":free", "")
10
+ return model_id.strip()
11
+
12
+
13
+ def format_timestamp(dt: datetime = None) -> str:
14
+ if dt is None:
15
+ dt = datetime.now()
16
+ return dt.strftime("%Y-%m-%d %H:%M:%S")