Subh775 commited on
Commit
ee3ac6d
·
1 Parent(s): e96d7c2

dynamic model selection..

Browse files
Files changed (2) hide show
  1. app.py +4 -0
  2. graph.py +107 -12
app.py CHANGED
@@ -313,6 +313,10 @@ def chat(request: ChatRequest, req: Request):
313
  "Free credits exhausted. Please add credits at openrouter.ai/settings/credits "
314
  "or update your API key in Settings."
315
  )
 
 
 
 
316
  elif "401" in str(e) or "unauthorized" in error_str or "invalid" in error_str:
317
  yield sse_error(
318
  "API key issue. Please check your OpenRouter API key in Settings."
 
313
  "Free credits exhausted. Please add credits at openrouter.ai/settings/credits "
314
  "or update your API key in Settings."
315
  )
316
+ elif "404" in str(e) or "not found" in error_str or "no endpoints" in error_str:
317
+ yield sse_error(
318
+ "The AI model is temporarily unavailable. Please try again in a moment."
319
+ )
320
  elif "401" in str(e) or "unauthorized" in error_str or "invalid" in error_str:
321
  yield sse_error(
322
  "API key issue. Please check your OpenRouter API key in Settings."
graph.py CHANGED
@@ -1,9 +1,7 @@
1
  """
2
- LangGraph definition — single chat node with free OpenRouter model selection.
3
- Checkpointer: SQLite via official LangGraph SqliteSaver.
4
-
5
- Text model: nvidia/llama-3.1-nemotron-ultra-253b-v1:free
6
- Vision model: nex-agi/nex-n2-pro:free
7
  """
8
 
9
  from langgraph.graph import StateGraph, START, END
@@ -14,6 +12,10 @@ from langchain_openrouter import ChatOpenRouter
14
  from typing import TypedDict, Annotated
15
 
16
  import sqlite3
 
 
 
 
17
  from langgraph.checkpoint.sqlite import SqliteSaver
18
  from config import OPENROUTER_API_KEY, DB_PATH
19
  import prompts
@@ -24,24 +26,117 @@ _conn = sqlite3.connect(DB_PATH, check_same_thread=False)
24
  checkpointer = SqliteSaver(conn=_conn)
25
 
26
 
27
- # --- Free model config ---
28
- TEXT_MODEL = "nvidia/llama-3.1-nemotron-ultra-253b-v1:free"
29
- VISION_MODEL = "nex-agi/nex-n2-pro:free"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
 
32
  def _get_llm(api_key: str = "", model: str = "", has_image: bool = False):
33
- """Create an LLM instance. Picks vision model if image is attached."""
34
  key = api_key or OPENROUTER_API_KEY
35
  if not key:
36
  raise ValueError("No API key available. Please add your OpenRouter key in Settings.")
37
 
38
- # Model priority: explicit override > auto-select based on image
39
  if model:
40
  mdl = model
41
  elif has_image:
42
- mdl = VISION_MODEL
43
  else:
44
- mdl = TEXT_MODEL
45
 
46
  return ChatOpenRouter(
47
  model=mdl,
 
1
  """
2
+ LangGraph definition — single chat node with dynamic free OpenRouter model selection.
3
+ Fetches available free models from OpenRouter API, caches for 10 minutes.
4
+ Defaults to openai/gpt-oss-120b:free for text, nex-agi/nex-n2-pro:free for vision.
 
 
5
  """
6
 
7
  from langgraph.graph import StateGraph, START, END
 
12
  from typing import TypedDict, Annotated
13
 
14
  import sqlite3
15
+ import time
16
+ import json
17
+ import urllib.request
18
+ import threading
19
  from langgraph.checkpoint.sqlite import SqliteSaver
20
  from config import OPENROUTER_API_KEY, DB_PATH
21
  import prompts
 
26
  checkpointer = SqliteSaver(conn=_conn)
27
 
28
 
29
+ # --- Dynamic free model selection ---
30
+
31
+ DEFAULT_TEXT_MODEL = "openai/gpt-oss-120b:free"
32
+ DEFAULT_VISION_MODEL = "nex-agi/nex-n2-pro:free"
33
+
34
+ _models_cache = None
35
+ _models_cache_at = 0
36
+ _models_lock = threading.Lock()
37
+ MODELS_TTL = 10 * 60 # 10 minutes
38
+
39
+
40
+ def _fetch_free_models() -> list[dict]:
41
+ """Fetch available free models from OpenRouter API."""
42
+ global _models_cache, _models_cache_at
43
+
44
+ with _models_lock:
45
+ if _models_cache and (time.time() - _models_cache_at) < MODELS_TTL:
46
+ return _models_cache
47
+
48
+ try:
49
+ req = urllib.request.Request(
50
+ "https://openrouter.ai/api/v1/models",
51
+ headers={"HTTP-Referer": "https://stemcopilot.app", "User-Agent": "STEMCopilot/1.0"},
52
+ )
53
+ with urllib.request.urlopen(req, timeout=8) as resp:
54
+ data = json.loads(resp.read().decode())
55
+
56
+ all_models = [
57
+ {"id": m["id"], "name": m.get("name", m["id"])}
58
+ for m in data.get("data", [])
59
+ if m.get("id", "").endswith(":free")
60
+ ]
61
+
62
+ # Separate text and vision-capable models
63
+ with _models_lock:
64
+ _models_cache = all_models
65
+ _models_cache_at = time.time()
66
+
67
+ return all_models
68
+
69
+ except Exception as e:
70
+ print(f"[MODELS] Could not fetch free models: {e}")
71
+ return _models_cache or []
72
+
73
+
74
+ def _pick_text_model() -> str:
75
+ """Pick the best free text model. Prefers GPT OSS 120B, then NVIDIA nemotron models."""
76
+ models = _fetch_free_models()
77
+ model_ids = {m["id"] for m in models}
78
+
79
+ # Priority order
80
+ preferred = [
81
+ DEFAULT_TEXT_MODEL,
82
+ "openai/gpt-oss-120b:free",
83
+ ]
84
+
85
+ # Check preferred first
86
+ for mid in preferred:
87
+ if mid in model_ids:
88
+ return mid
89
+
90
+ # Then any NVIDIA model
91
+ nvidia = [m["id"] for m in models if m["id"].startswith("nvidia/")]
92
+ if nvidia:
93
+ return nvidia[0]
94
+
95
+ # Then any free model that isn't tiny
96
+ if models:
97
+ return models[0]["id"]
98
+
99
+ # Ultimate fallback
100
+ return DEFAULT_TEXT_MODEL
101
+
102
+
103
+ def _pick_vision_model() -> str:
104
+ """Pick the best free vision/multimodal model."""
105
+ models = _fetch_free_models()
106
+ model_ids = {m["id"] for m in models}
107
+
108
+ # Priority order for vision
109
+ preferred = [
110
+ DEFAULT_VISION_MODEL,
111
+ "nex-agi/nex-n2-pro:free",
112
+ ]
113
+
114
+ for mid in preferred:
115
+ if mid in model_ids:
116
+ return mid
117
+
118
+ # Fallback to text model — it may handle images poorly but won't 404
119
+ return _pick_text_model()
120
+
121
+
122
+ # Expose for app.py logging
123
+ TEXT_MODEL = DEFAULT_TEXT_MODEL
124
+ VISION_MODEL = DEFAULT_VISION_MODEL
125
 
126
 
127
  def _get_llm(api_key: str = "", model: str = "", has_image: bool = False):
128
+ """Create an LLM instance. Dynamically picks the best available free model."""
129
  key = api_key or OPENROUTER_API_KEY
130
  if not key:
131
  raise ValueError("No API key available. Please add your OpenRouter key in Settings.")
132
 
133
+ # Model priority: explicit override > dynamic selection
134
  if model:
135
  mdl = model
136
  elif has_image:
137
+ mdl = _pick_vision_model()
138
  else:
139
+ mdl = _pick_text_model()
140
 
141
  return ChatOpenRouter(
142
  model=mdl,