qa1145 commited on
Commit
2a464ca
·
verified ·
1 Parent(s): 3b8a72f

Upload 9 files

Browse files
Files changed (2) hide show
  1. app.py +34 -13
  2. src/model_tester.py +140 -71
app.py CHANGED
@@ -1,4 +1,5 @@
1
  import gradio as gr
 
2
  from datetime import datetime
3
 
4
  from src.config import get_api_keys, get_scan_interval_hours
@@ -13,16 +14,19 @@ class AppState:
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()
@@ -33,8 +37,15 @@ def get_available_models():
33
  return "\n".join(models) if models else "No models available yet"
34
 
35
 
36
- def get_models_api():
 
 
 
 
 
37
  """API endpoint to get available models as JSON"""
 
 
38
  return app_state.scan_result.get("available_models", [])
39
 
40
 
@@ -66,10 +77,10 @@ def chat(prompt: str, model: str = None):
66
 
67
 
68
  def get_scan_status():
69
- count = app_state.scan_result.get("available_count", 0)
70
- tested = app_state.scan_result.get("total_tested", 0)
71
  timestamp = app_state.last_update_time or "Never"
72
- return f"Available: {count}/{tested} | Last scan: {timestamp}"
73
 
74
 
75
  def trigger_scan():
@@ -91,7 +102,7 @@ def get_interval_info():
91
 
92
  with gr.Blocks(title="OpenRouter Free Models Scanner") as demo:
93
  gr.Markdown("# OpenRouter Free Models Scanner")
94
- gr.Markdown("Auto-scan available free models from OpenRouter")
95
 
96
  with gr.Row():
97
  gr.Markdown(f"### API Keys: {get_api_keys_status()}")
@@ -122,26 +133,36 @@ with gr.Blocks(title="OpenRouter Free Models Scanner") as demo:
122
  )
123
 
124
  gr.Markdown("---")
125
- gr.Markdown("## Available Models")
 
 
 
 
 
 
 
 
 
126
 
127
  models_display = gr.Textbox(
128
  value=get_available_models(),
129
- label=f"Available Models ({len(app_state.scan_result.get('available_models', []))})",
130
  interactive=False,
131
- lines=20
132
  )
133
 
134
  demo.load(
135
  fn=lambda: (
136
  get_scan_status(),
 
137
  get_available_models()
138
  ),
139
- outputs=[status_display, models_display]
140
  )
141
 
142
  app_state.scheduler.start()
143
 
144
- gr.API(documentation=None)(get_models_api)
145
  gr.API(documentation=None)(chat)
146
 
147
 
 
1
  import gradio as gr
2
+ import threading
3
  from datetime import datetime
4
 
5
  from src.config import get_api_keys, get_scan_interval_hours
 
14
  self.scheduler = Scheduler(task_callback=self.run_scan)
15
  self.scan_result = {
16
  "available_models": [],
17
+ "available_free_models": [],
18
+ "total_available": 0,
19
+ "free_available": 0,
20
  "timestamp": None
21
  }
22
  self.last_update_time = None
23
+ self._initial_scan_done = False
24
 
25
  def run_scan(self):
26
+ result = self.model_tester.scan_all_models()
27
  self.scan_result = result
28
  self.last_update_time = format_timestamp()
29
+ self._initial_scan_done = True
30
 
31
 
32
  app_state = AppState()
 
37
  return "\n".join(models) if models else "No models available yet"
38
 
39
 
40
+ def get_available_free_models():
41
+ models = app_state.scan_result.get("available_free_models", [])
42
+ return "\n".join(models) if models else "No free models available yet"
43
+
44
+
45
+ def get_models_api(free_only: bool = False):
46
  """API endpoint to get available models as JSON"""
47
+ if free_only:
48
+ return app_state.scan_result.get("available_free_models", [])
49
  return app_state.scan_result.get("available_models", [])
50
 
51
 
 
77
 
78
 
79
  def get_scan_status():
80
+ total = app_state.scan_result.get("total_available", 0)
81
+ free = app_state.scan_result.get("free_available", 0)
82
  timestamp = app_state.last_update_time or "Never"
83
+ return f"Free: {free} | Total: {total} | Last scan: {timestamp}"
84
 
85
 
86
  def trigger_scan():
 
102
 
103
  with gr.Blocks(title="OpenRouter Free Models Scanner") as demo:
104
  gr.Markdown("# OpenRouter Free Models Scanner")
105
+ gr.Markdown("Auto-scan all available models from OpenRouter (free priority)")
106
 
107
  with gr.Row():
108
  gr.Markdown(f"### API Keys: {get_api_keys_status()}")
 
133
  )
134
 
135
  gr.Markdown("---")
136
+ gr.Markdown("## Available Free Models (Priority)")
137
+
138
+ free_models_display = gr.Textbox(
139
+ value=get_available_free_models(),
140
+ label=f"Free Models ({len(app_state.scan_result.get('available_free_models', []))})",
141
+ interactive=False,
142
+ lines=15
143
+ )
144
+
145
+ gr.Markdown("## All Available Models")
146
 
147
  models_display = gr.Textbox(
148
  value=get_available_models(),
149
+ label=f"All Models ({len(app_state.scan_result.get('available_models', []))})",
150
  interactive=False,
151
+ lines=15
152
  )
153
 
154
  demo.load(
155
  fn=lambda: (
156
  get_scan_status(),
157
+ get_available_free_models(),
158
  get_available_models()
159
  ),
160
+ outputs=[status_display, free_models_display, models_display]
161
  )
162
 
163
  app_state.scheduler.start()
164
 
165
+ gr.API(documentation=None)(lambda free_only=False: get_models_api(free_only))
166
  gr.API(documentation=None)(chat)
167
 
168
 
src/model_tester.py CHANGED
@@ -14,25 +14,117 @@ class ModelTester:
14
  self.client = OpenRouterClient()
15
  self.max_concurrency = config.get_max_concurrency()
16
  self.test_prompt = config.get_test_prompt()
17
- self._cached_models: Optional[List[str]] = None
18
- self._cache_time: Optional[datetime] = None
 
 
 
 
 
19
 
20
- def get_all_free_models(self) -> List[str]:
 
21
  models = self.client.get_models()
22
- free_models = []
 
23
 
24
  for model in models:
25
  model_id = model.get("id", "")
26
- if ":free" in model_id:
27
- free_models.append(model_id)
 
 
 
 
 
 
28
 
29
- return free_models
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
- def get_cached_free_models(self, force_refresh: bool = False) -> List[str]:
32
- if self._cached_models is None or force_refresh:
33
- self._cached_models = self.get_all_free_models()
34
- self._cache_time = datetime.now()
35
- return self._cached_models
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
  async def try_model_direct(
38
  self,
@@ -74,38 +166,54 @@ class ModelTester:
74
  except Exception as e:
75
  return {"success": False, "model": model_id, "error": str(e), "method": "direct"}
76
 
77
- async def try_models_from_list(
78
  self,
79
  session: aiohttp.ClientSession,
80
  keyword: str,
81
  api_key: str
82
  ) -> Optional[Dict[str, Any]]:
83
- free_models = self.get_cached_free_models()
 
84
 
85
- matched = [m for m in free_models if keyword.lower() in m.lower()]
86
 
87
- if not matched:
88
- matched = free_models[:10]
89
-
90
- for model_id in matched:
91
- result = await self.try_model_direct(session, model_id, api_key)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  if result and result.get("success"):
93
- result["method"] = "list_match"
94
  return result
95
-
96
  return {
97
  "success": False,
98
- "model": matched[0] if matched else None,
99
  "error": "No available model found",
100
- "method": "list_match"
101
  }
102
 
103
  async def chat_completion(self, prompt: str, model_hint: Optional[str] = None) -> Dict[str, Any]:
104
  api_keys = config.get_api_keys()
105
  api_key = random.choice(api_keys)
106
 
107
- timeout = aiohttp.ClientTimeout(total=config.get_request_timeout())
108
-
109
  async with aiohttp.ClientSession() as session:
110
  tasks = []
111
 
@@ -116,7 +224,7 @@ class ModelTester:
116
  ))
117
 
118
  tasks.append(asyncio.create_task(
119
- self.try_models_from_list(session, model_hint or "", api_key)
120
  ))
121
 
122
  done, pending = await asyncio.wait(
@@ -133,7 +241,7 @@ class ModelTester:
133
  return {
134
  "success": True,
135
  "response": result.get("response"),
136
- "method": "direct",
137
  "model": result.get("model")
138
  }
139
 
@@ -143,14 +251,14 @@ class ModelTester:
143
  return {
144
  "success": True,
145
  "response": result.get("response"),
146
- "method": "list_match",
147
  "model": result.get("model")
148
  }
149
  else:
150
  return {
151
  "success": False,
152
  "error": result.get("error", "Unknown error"),
153
- "method": "list_match"
154
  }
155
 
156
  return {
@@ -168,44 +276,5 @@ class ModelTester:
168
  return cleaned_name, is_available
169
 
170
  def test_all_models(self) -> Dict[str, Any]:
171
- print(f"[{datetime.now()}] Starting model scan...")
172
-
173
- free_models = self.get_cached_free_models(force_refresh=True)
174
- print(f"Found {len(free_models)} free models to test")
175
-
176
- available_models: Set[str] = set()
177
- tested_count = 0
178
- success_count = 0
179
-
180
- import concurrent.futures
181
- with concurrent.futures.ThreadPoolExecutor(
182
- max_workers=self.max_concurrency
183
- ) as executor:
184
- future_to_model = {
185
- executor.submit(self.test_single_model, model_id): model_id
186
- for model_id in free_models
187
- }
188
-
189
- for future in concurrent.futures.as_completed(future_to_model):
190
- tested_count += 1
191
- try:
192
- cleaned_name, is_available = future.result()
193
- if is_available:
194
- available_models.add(cleaned_name)
195
- success_count += 1
196
- print(f"[{tested_count}/{len(free_models)}] ✓ {cleaned_name}")
197
- else:
198
- print(f"[{tested_count}/{len(free_models)}] ✗ {cleaned_name}")
199
- except Exception as e:
200
- model_id = future_to_model[future]
201
- print(f"[{tested_count}/{len(free_models)}] ! {model_id}: {e}")
202
-
203
- result = {
204
- "available_models": sorted(list(available_models)),
205
- "total_tested": tested_count,
206
- "available_count": success_count,
207
- "timestamp": datetime.now().isoformat()
208
- }
209
-
210
- print(f"Scan complete: {success_count}/{tested_count} models available")
211
- return result
 
14
  self.client = OpenRouterClient()
15
  self.max_concurrency = config.get_max_concurrency()
16
  self.test_prompt = config.get_test_prompt()
17
+
18
+ self._all_models: List[str] = []
19
+ self._free_models: List[str] = []
20
+ self._available_models: List[str] = []
21
+ self._available_free_models: List[str] = []
22
+ self._scan_in_progress = False
23
+ self._last_scan_time: Optional[datetime] = None
24
 
25
+ def refresh_model_list(self):
26
+ """Get latest model list from API"""
27
  models = self.client.get_models()
28
+ all_ids = []
29
+ free_ids = []
30
 
31
  for model in models:
32
  model_id = model.get("id", "")
33
+ if model_id:
34
+ all_ids.append(model_id)
35
+ if ":free" in model_id:
36
+ free_ids.append(model_id)
37
+
38
+ self._all_models = all_ids
39
+ self._free_models = free_ids
40
+ return len(self._all_models), len(self._free_models)
41
 
42
+ async def test_single_model_async(
43
+ self,
44
+ session: aiohttp.ClientSession,
45
+ model_id: str,
46
+ api_key: str
47
+ ) -> tuple[str, bool]:
48
+ url = "https://openrouter.ai/api/v1/chat/completions"
49
+ payload = {
50
+ "model": model_id,
51
+ "messages": [{"role": "user", "content": self.test_prompt}],
52
+ "max_tokens": 10
53
+ }
54
+ headers = {
55
+ "Authorization": f"Bearer {api_key}",
56
+ "Content-Type": "application/json"
57
+ }
58
 
59
+ try:
60
+ timeout = aiohttp.ClientTimeout(total=config.get_request_timeout())
61
+ async with session.post(url, json=payload, headers=headers, timeout=timeout) as response:
62
+ is_success = response.status == 200
63
+ is_free = ":free" in model_id
64
+ return model_id, is_success
65
+ except Exception:
66
+ return model_id, False
67
+
68
+ async def scan_all_models_async(self):
69
+ """Async scan all models concurrently"""
70
+ if self._scan_in_progress:
71
+ return {"error": "Scan already in progress"}
72
+
73
+ self._scan_in_progress = True
74
+ print(f"[{datetime.now()}] Starting model scan...")
75
+
76
+ all_count, free_count = self.refresh_model_list()
77
+ print(f"Total models: {all_count}, Free models: {free_count}")
78
+
79
+ api_keys = config.get_api_keys()
80
+ api_key = random.choice(api_keys)
81
+
82
+ available: Set[str] = set()
83
+ available_free: Set[str] = set()
84
+
85
+ async with aiohttp.ClientSession() as session:
86
+ semaphore = asyncio.Semaphore(self.max_concurrency)
87
+
88
+ async def test_with_semaphore(model_id: str):
89
+ async with semaphore:
90
+ return await self.test_single_model_async(session, model_id, api_key)
91
+
92
+ tasks = [test_with_semaphore(m) for m in self._all_models]
93
+ results = await asyncio.gather(*tasks, return_exceptions=True)
94
+
95
+ for result in results:
96
+ if isinstance(result, tuple):
97
+ model_id, success = result
98
+ cleaned = clean_model_name(model_id)
99
+ if success:
100
+ available.add(cleaned)
101
+ if ":free" in model_id:
102
+ available_free.add(cleaned)
103
+
104
+ self._available_models = sorted(list(available))
105
+ self._available_free_models = sorted(list(available_free))
106
+ self._last_scan_time = datetime.now()
107
+ self._scan_in_progress = False
108
+
109
+ print(f"Scan complete: {len(self._available_free_models)} free, {len(self._available_models)} total available")
110
+
111
+ return {
112
+ "available_models": self._available_models,
113
+ "available_free_models": self._available_free_models,
114
+ "total_available": len(self._available_models),
115
+ "free_available": len(self._available_free_models),
116
+ "timestamp": self._last_scan_time.isoformat() if self._last_scan_time else None
117
+ }
118
+
119
+ def scan_all_models(self):
120
+ """Sync wrapper for scan"""
121
+ return asyncio.run(self.scan_all_models_async())
122
+
123
+ def get_available_models(self, free_only: bool = False) -> List[str]:
124
+ """Get available models list"""
125
+ if free_only:
126
+ return self._available_free_models
127
+ return self._available_models
128
 
129
  async def try_model_direct(
130
  self,
 
166
  except Exception as e:
167
  return {"success": False, "model": model_id, "error": str(e), "method": "direct"}
168
 
169
+ async def try_best_available_model(
170
  self,
171
  session: aiohttp.ClientSession,
172
  keyword: str,
173
  api_key: str
174
  ) -> Optional[Dict[str, Any]]:
175
+ available = self.get_available_models()
176
+ available_free = self.get_available_models(free_only=True)
177
 
178
+ candidates = []
179
 
180
+ if available_free:
181
+ if keyword:
182
+ matched = [m for m in available_free if keyword.lower() in m.lower()]
183
+ if matched:
184
+ candidates.extend([(m, "free_matched") for m in matched])
185
+ if not candidates:
186
+ candidates.extend([(m, "free_random") for m in available_free[:10]])
187
+
188
+ if available:
189
+ if keyword:
190
+ matched = [m for m in available if keyword.lower() in m.lower()]
191
+ if matched:
192
+ candidates.extend([(m, "matched") for m in matched])
193
+ if not any(c[1].startswith("free") for c in candidates):
194
+ if available_free:
195
+ candidates.extend([(m, "free_fallback") for m in available_free[:5]])
196
+ else:
197
+ candidates.extend([(m, "random") for m in available[:10]])
198
+
199
+ for model_id, match_type in candidates:
200
+ full_model = f"{model_id}:free" if ":free" not in model_id else model_id
201
+ result = await self.try_model_direct(session, full_model, api_key)
202
  if result and result.get("success"):
203
+ result["method"] = f"list_{match_type}"
204
  return result
205
+
206
  return {
207
  "success": False,
208
+ "model": candidates[0][0] if candidates else None,
209
  "error": "No available model found",
210
+ "method": "list_fallback"
211
  }
212
 
213
  async def chat_completion(self, prompt: str, model_hint: Optional[str] = None) -> Dict[str, Any]:
214
  api_keys = config.get_api_keys()
215
  api_key = random.choice(api_keys)
216
 
 
 
217
  async with aiohttp.ClientSession() as session:
218
  tasks = []
219
 
 
224
  ))
225
 
226
  tasks.append(asyncio.create_task(
227
+ self.try_best_available_model(session, model_hint or "", api_key)
228
  ))
229
 
230
  done, pending = await asyncio.wait(
 
241
  return {
242
  "success": True,
243
  "response": result.get("response"),
244
+ "method": result.get("method"),
245
  "model": result.get("model")
246
  }
247
 
 
251
  return {
252
  "success": True,
253
  "response": result.get("response"),
254
+ "method": result.get("method"),
255
  "model": result.get("model")
256
  }
257
  else:
258
  return {
259
  "success": False,
260
  "error": result.get("error", "Unknown error"),
261
+ "method": result.get("method")
262
  }
263
 
264
  return {
 
276
  return cleaned_name, is_available
277
 
278
  def test_all_models(self) -> Dict[str, Any]:
279
+ """Legacy sync method - use scan_all_models instead"""
280
+ return self.scan_all_models()