@@ -83,9 +83,25 @@ def detect_provider(url: str) -> tuple[str, bool]:
8383 "google/gemini-2.5-flash-lite" : [
8484 "google/gemini-3.1-flash-lite-preview" ,
8585 ],
86+ "google/gemini-3-pro-preview" : [
87+ "google/gemini-3.1-pro-preview" ,
88+ ],
89+ "google/gemini-3.1-pro" : [
90+ "google/gemini-3.1-pro-preview" ,
91+ ],
8692 "xai/grok-4-0709" : [
8793 "x-ai/grok-4-1-fast-non-reasoning" ,
8894 ],
95+ "xai/grok-4-1-fast-reasoning" : [
96+ "x-ai/grok-4.1-fast-reasoning" ,
97+ "x-ai/grok-4-1-fast-reasoning" ,
98+ ],
99+ "xai/grok-4-1-fast-non-reasoning" : [
100+ "x-ai/grok-4-1-fast-non-reasoning" ,
101+ ],
102+ "xai/grok-code-fast-1" : [
103+ "x-ai/grok-code-fast-1" ,
104+ ],
89105 "openai/gpt-4o" : [
90106 "openai/gpt-4.1" ,
91107 ],
@@ -139,6 +155,28 @@ def _parse_upstream_pricing(raw: dict | None) -> ModelPricing:
139155
140156_REASONING_POSITIVE = ("reason" , "r1" , "thinking" , "think" , "o1-" , "o3" , "o4-" , "gpt-5" )
141157_REASONING_NEGATIVE = ("non-reason" , "non_reason" , "no-reason" , "non-thinking" )
158+ _NON_CHAT_MODEL_MARKERS = (
159+ "-image" ,
160+ "image-" ,
161+ "-video" ,
162+ "video-" ,
163+ "-audio" ,
164+ "audio-" ,
165+ "speech" ,
166+ "tts" ,
167+ "embedding" ,
168+ "embed" ,
169+ )
170+
171+
172+ def is_routable_chat_model (model_id : str ) -> bool :
173+ """Return False for modality-specific generation models in /v1/models.
174+
175+ Gateways often list image, video, audio, and embedding models beside chat
176+ models. The chat router should not select those for text/tool requests.
177+ """
178+ core = _core (str (model_id or "" ).lower ())
179+ return not any (marker in core for marker in _NON_CHAT_MODEL_MARKERS )
142180
143181
144182def infer_capabilities (
@@ -163,6 +201,8 @@ def infer_capabilities(
163201 if provider == "anthropic" and "opus" in core :
164202 reasoning = True
165203
204+ image_generation = not is_routable_chat_model (model_id ) and "image" in core
205+
166206 vision = False
167207 if any (p in core for p in ("-vl" , "vision" , "image" )):
168208 vision = True
@@ -171,7 +211,7 @@ def infer_capabilities(
171211 if provider == "openai" and any (p in core for p in ("4o" , "gpt-5" , "gpt-4" )):
172212 vision = True
173213
174- tool_calling = True
214+ tool_calling = not image_generation
175215
176216 free = (
177217 has_explicit_pricing
@@ -367,6 +407,8 @@ def _fuzzy_match(self, internal: str) -> str | None:
367407 best : str | None = None
368408
369409 for upstream in self ._upstream_models :
410+ if is_routable_chat_model (internal ) and not is_routable_chat_model (upstream ):
411+ continue
370412 up_core = _core (upstream )
371413 up_norm = _normalize (up_core )
372414 up_prov = _provider_prefix (upstream )
@@ -459,6 +501,11 @@ def available_models(self) -> list[str]:
459501 """All available upstream model IDs, sorted."""
460502 return sorted (self ._pool .keys ())
461503
504+ @property
505+ def routable_models (self ) -> list [str ]:
506+ """Chat-routable upstream model IDs, sorted."""
507+ return [model for model in self .available_models if is_routable_chat_model (model )]
508+
462509 def get_pricing (self , model_id : str ) -> ModelPricing | None :
463510 """Look up pricing for a single model (internal or upstream ID)."""
464511 if model_id in self ._pool :
0 commit comments