ayushKishor commited on
Commit
b5e99b3
·
1 Parent(s): a70f2fd

Harden provider response handling

Browse files
Files changed (2) hide show
  1. mp1/pluto/dispatcher.py +76 -19
  2. mp1/pluto/modes.py +15 -4
mp1/pluto/dispatcher.py CHANGED
@@ -73,10 +73,62 @@ def _select_nvidia_env_var(model_id: str) -> str:
73
  def _resolve_nvidia_api_key(model_id: str) -> tuple[str, str]:
74
  """Resolve model-specific NVIDIA credentials with a global fallback."""
75
  env_var = _select_nvidia_env_var(model_id)
76
- api_key = os.getenv(env_var) or os.getenv("NVIDIA_API_KEY", "")
77
  return env_var, api_key
78
 
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  def dispatch(
81
  mode_name: str,
82
  prompt: str,
@@ -84,6 +136,7 @@ def dispatch(
84
  images: list[bytes] | None = None,
85
  ) -> str:
86
  """Route prompt to the configured provider for the selected mode."""
 
87
  cfg = get_mode(mode_name)
88
 
89
  if tracer:
@@ -102,11 +155,11 @@ def dispatch(
102
 
103
  try:
104
  if cfg.provider == "nvidia":
105
- text = _call_nvidia(cfg, prompt)
106
  elif cfg.provider == "groq":
107
- text = _call_groq(cfg, prompt)
108
  elif cfg.provider == "mistral":
109
- text = _call_mistral(cfg, prompt)
110
  else:
111
  raise ValueError(f"Unknown provider: {cfg.provider}")
112
 
@@ -114,9 +167,9 @@ def dispatch(
114
  print(f" [WARNING] {cfg.provider} failed: {e}")
115
 
116
  if cfg.provider == "groq":
117
- text = _call_best_fallback(cfg, prompt, allow_groq=False)
118
  else:
119
- text = _call_best_fallback(cfg, prompt, allow_groq=True)
120
 
121
  elapsed = time.perf_counter() - t0
122
  if tracer:
@@ -133,7 +186,7 @@ def dispatch(
133
 
134
  def _call_best_fallback(cfg: ModeConfig, prompt: str, allow_groq: bool) -> str:
135
  """Try the configured fallback providers without confusing NVIDIA and Mistral keys."""
136
- groq_key = os.getenv("GROQ_API_KEY", "").strip()
137
  if allow_groq and groq_key:
138
  print(" [FALLBACK] Trying Groq...")
139
  fb = ModeConfig(
@@ -146,7 +199,7 @@ def _call_best_fallback(cfg: ModeConfig, prompt: str, allow_groq: bool) -> str:
146
  )
147
  return _call_groq(fb, prompt)
148
 
149
- mistral_key = os.getenv("MISTRAL_API_KEY", "").strip()
150
  if mistral_key and not _looks_like_nvidia_key(mistral_key):
151
  print(" [FALLBACK] Trying Mistral...")
152
  fb = ModeConfig(
@@ -159,7 +212,7 @@ def _call_best_fallback(cfg: ModeConfig, prompt: str, allow_groq: bool) -> str:
159
  )
160
  return _call_mistral(fb, prompt)
161
 
162
- nvidia_key = os.getenv("NVIDIA_API_KEY", "").strip()
163
  if nvidia_key:
164
  print(" [FALLBACK] Trying NVIDIA-hosted Mistral...")
165
  return _call_nvidia_hosted_mistral(cfg, prompt, nvidia_key)
@@ -170,7 +223,7 @@ def _call_best_fallback(cfg: ModeConfig, prompt: str, allow_groq: bool) -> str:
170
 
171
 
172
  def _looks_like_nvidia_key(api_key: str) -> bool:
173
- return api_key.strip().startswith("nvapi-")
174
 
175
 
176
  def _call_groq(cfg: ModeConfig, prompt: str) -> str:
@@ -188,7 +241,7 @@ def _call_groq(cfg: ModeConfig, prompt: str) -> str:
188
  temperature=cfg.temperature,
189
  max_tokens=cfg.max_tokens,
190
  )
191
- return resp.choices[0].message.content or ""
192
 
193
  except Exception as e:
194
  err = str(e).lower()
@@ -204,9 +257,11 @@ def _call_groq(cfg: ModeConfig, prompt: str) -> str:
204
 
205
  def _call_mistral(cfg: ModeConfig, prompt: str) -> str:
206
  """Call Mistral AI REST API with retry on rate-limit."""
207
- api_key = os.getenv("MISTRAL_API_KEY", "")
208
  if not api_key:
209
  raise ValueError("MISTRAL_API_KEY not set")
 
 
210
 
211
  headers = {
212
  "Authorization": f"Bearer {api_key}",
@@ -228,7 +283,7 @@ def _call_mistral(cfg: ModeConfig, prompt: str) -> str:
228
  timeout=60,
229
  )
230
  if response.status_code == 200:
231
- return response.json()["choices"][0]["message"]["content"]
232
  if response.status_code == 429:
233
  delay = 10 * (attempt + 1)
234
  print(f" [RETRY] Mistral rate-limit — waiting {delay}s (attempt {attempt + 1})")
@@ -248,7 +303,10 @@ def _call_nvidia(cfg: ModeConfig, prompt: str) -> str:
248
  env_var, api_key = _resolve_nvidia_api_key(cfg.model_id)
249
  if not api_key:
250
  raise ValueError(f"{env_var} or NVIDIA_API_KEY not set")
251
- has_fallback = bool(os.getenv("GROQ_API_KEY", "") or os.getenv("MISTRAL_API_KEY", ""))
 
 
 
252
  max_retries = NVIDIA_MAX_RETRIES_WITH_FALLBACK if has_fallback else NVIDIA_MAX_RETRIES_DEFAULT
253
  request_timeout = NVIDIA_TIMEOUT_WITH_FALLBACK if has_fallback else NVIDIA_TIMEOUT_DEFAULT
254
 
@@ -279,8 +337,7 @@ def _call_nvidia(cfg: ModeConfig, prompt: str) -> str:
279
  timeout=request_timeout,
280
  )
281
  if response.status_code == 200:
282
- data = response.json()
283
- return data["choices"][0]["message"]["content"]
284
  raise Exception(f"NVIDIA {response.status_code}: {response.text[:300]}")
285
  except Exception as e:
286
  err = str(e).lower()
@@ -318,6 +375,7 @@ def _call_nvidia(cfg: ModeConfig, prompt: str) -> str:
318
 
319
  def _call_nvidia_hosted_mistral(cfg: ModeConfig, prompt: str, api_key: str) -> str:
320
  """Call Mistral served through NVIDIA NIM using NVIDIA credentials."""
 
321
  payload = {
322
  "model": NVIDIA_MISTRAL_FALLBACK_MODEL,
323
  "messages": [{"role": "user", "content": prompt}],
@@ -335,8 +393,7 @@ def _call_nvidia_hosted_mistral(cfg: ModeConfig, prompt: str, api_key: str) -> s
335
  )
336
  if response.status_code != 200:
337
  raise RuntimeError(f"NVIDIA-hosted Mistral {response.status_code}: {response.text[:300]}")
338
- data = response.json()
339
- return data["choices"][0]["message"]["content"]
340
 
341
 
342
  def rerank(query: str, passages: list[str]) -> list[float]:
@@ -346,7 +403,7 @@ def rerank(query: str, passages: list[str]) -> list[float]:
346
  Returns scores in the same order as the input passages and falls back to
347
  uniform scores if the reranker is unavailable.
348
  """
349
- api_key = os.getenv("NVIDIA_API_KEY_RERANK") or os.getenv("NVIDIA_API_KEY", "")
350
  if not api_key or not passages:
351
  return [1.0] * len(passages)
352
 
 
73
  def _resolve_nvidia_api_key(model_id: str) -> tuple[str, str]:
74
  """Resolve model-specific NVIDIA credentials with a global fallback."""
75
  env_var = _select_nvidia_env_var(model_id)
76
+ api_key = _clean_api_key(os.getenv(env_var) or os.getenv("NVIDIA_API_KEY", ""))
77
  return env_var, api_key
78
 
79
 
80
+ def _clean_api_key(api_key: str | None) -> str:
81
+ """Normalize secret values pasted with optional auth prefixes or whitespace."""
82
+ cleaned = str(api_key or "").strip().strip('"').strip("'")
83
+ if cleaned.lower().startswith("bearer "):
84
+ cleaned = cleaned[7:].strip()
85
+ return cleaned
86
+
87
+
88
+ def _normalize_model_text(value: Any) -> str:
89
+ """Turn provider message content into a safe string."""
90
+ if value is None:
91
+ return ""
92
+ if isinstance(value, str):
93
+ return value
94
+ if isinstance(value, list):
95
+ parts: list[str] = []
96
+ for item in value:
97
+ if isinstance(item, str):
98
+ parts.append(item)
99
+ elif isinstance(item, dict):
100
+ text = item.get("text") or item.get("content") or item.get("value")
101
+ if text is not None:
102
+ parts.append(str(text))
103
+ return "\n".join(part for part in parts if part)
104
+ return str(value)
105
+
106
+
107
+ def _chat_response_text(data: dict[str, Any], provider: str) -> str:
108
+ """Extract assistant text from OpenAI-compatible chat response JSON."""
109
+ choices = data.get("choices")
110
+ if not choices:
111
+ raise RuntimeError(f"{provider} response did not include choices")
112
+
113
+ message = choices[0].get("message") if isinstance(choices[0], dict) else None
114
+ if not isinstance(message, dict):
115
+ raise RuntimeError(f"{provider} response did not include a message")
116
+
117
+ text = _normalize_model_text(message.get("content"))
118
+ if not text:
119
+ text = _normalize_model_text(message.get("reasoning_content"))
120
+ if not text:
121
+ text = _normalize_model_text(message.get("reasoning"))
122
+ return text
123
+
124
+
125
+ def _require_model_text(text: str, provider: str) -> str:
126
+ normalized = _normalize_model_text(text)
127
+ if not normalized.strip():
128
+ raise RuntimeError(f"{provider} returned an empty response")
129
+ return normalized
130
+
131
+
132
  def dispatch(
133
  mode_name: str,
134
  prompt: str,
 
136
  images: list[bytes] | None = None,
137
  ) -> str:
138
  """Route prompt to the configured provider for the selected mode."""
139
+ prompt = str(prompt or "")
140
  cfg = get_mode(mode_name)
141
 
142
  if tracer:
 
155
 
156
  try:
157
  if cfg.provider == "nvidia":
158
+ text = _require_model_text(_call_nvidia(cfg, prompt), "NVIDIA")
159
  elif cfg.provider == "groq":
160
+ text = _require_model_text(_call_groq(cfg, prompt), "Groq")
161
  elif cfg.provider == "mistral":
162
+ text = _require_model_text(_call_mistral(cfg, prompt), "Mistral")
163
  else:
164
  raise ValueError(f"Unknown provider: {cfg.provider}")
165
 
 
167
  print(f" [WARNING] {cfg.provider} failed: {e}")
168
 
169
  if cfg.provider == "groq":
170
+ text = _require_model_text(_call_best_fallback(cfg, prompt, allow_groq=False), "fallback")
171
  else:
172
+ text = _require_model_text(_call_best_fallback(cfg, prompt, allow_groq=True), "fallback")
173
 
174
  elapsed = time.perf_counter() - t0
175
  if tracer:
 
186
 
187
  def _call_best_fallback(cfg: ModeConfig, prompt: str, allow_groq: bool) -> str:
188
  """Try the configured fallback providers without confusing NVIDIA and Mistral keys."""
189
+ groq_key = _clean_api_key(os.getenv("GROQ_API_KEY", ""))
190
  if allow_groq and groq_key:
191
  print(" [FALLBACK] Trying Groq...")
192
  fb = ModeConfig(
 
199
  )
200
  return _call_groq(fb, prompt)
201
 
202
+ mistral_key = _clean_api_key(os.getenv("MISTRAL_API_KEY", ""))
203
  if mistral_key and not _looks_like_nvidia_key(mistral_key):
204
  print(" [FALLBACK] Trying Mistral...")
205
  fb = ModeConfig(
 
212
  )
213
  return _call_mistral(fb, prompt)
214
 
215
+ nvidia_key = _clean_api_key(os.getenv("NVIDIA_API_KEY", ""))
216
  if nvidia_key:
217
  print(" [FALLBACK] Trying NVIDIA-hosted Mistral...")
218
  return _call_nvidia_hosted_mistral(cfg, prompt, nvidia_key)
 
223
 
224
 
225
  def _looks_like_nvidia_key(api_key: str) -> bool:
226
+ return _clean_api_key(api_key).startswith("nvapi-")
227
 
228
 
229
  def _call_groq(cfg: ModeConfig, prompt: str) -> str:
 
241
  temperature=cfg.temperature,
242
  max_tokens=cfg.max_tokens,
243
  )
244
+ return _normalize_model_text(resp.choices[0].message.content)
245
 
246
  except Exception as e:
247
  err = str(e).lower()
 
257
 
258
  def _call_mistral(cfg: ModeConfig, prompt: str) -> str:
259
  """Call Mistral AI REST API with retry on rate-limit."""
260
+ api_key = _clean_api_key(os.getenv("MISTRAL_API_KEY", ""))
261
  if not api_key:
262
  raise ValueError("MISTRAL_API_KEY not set")
263
+ if _looks_like_nvidia_key(api_key):
264
+ raise ValueError("MISTRAL_API_KEY contains an NVIDIA key; move it to NVIDIA_API_KEY")
265
 
266
  headers = {
267
  "Authorization": f"Bearer {api_key}",
 
283
  timeout=60,
284
  )
285
  if response.status_code == 200:
286
+ return _chat_response_text(response.json(), "Mistral")
287
  if response.status_code == 429:
288
  delay = 10 * (attempt + 1)
289
  print(f" [RETRY] Mistral rate-limit — waiting {delay}s (attempt {attempt + 1})")
 
303
  env_var, api_key = _resolve_nvidia_api_key(cfg.model_id)
304
  if not api_key:
305
  raise ValueError(f"{env_var} or NVIDIA_API_KEY not set")
306
+ has_fallback = bool(
307
+ _clean_api_key(os.getenv("GROQ_API_KEY", ""))
308
+ or _clean_api_key(os.getenv("MISTRAL_API_KEY", ""))
309
+ )
310
  max_retries = NVIDIA_MAX_RETRIES_WITH_FALLBACK if has_fallback else NVIDIA_MAX_RETRIES_DEFAULT
311
  request_timeout = NVIDIA_TIMEOUT_WITH_FALLBACK if has_fallback else NVIDIA_TIMEOUT_DEFAULT
312
 
 
337
  timeout=request_timeout,
338
  )
339
  if response.status_code == 200:
340
+ return _chat_response_text(response.json(), "NVIDIA")
 
341
  raise Exception(f"NVIDIA {response.status_code}: {response.text[:300]}")
342
  except Exception as e:
343
  err = str(e).lower()
 
375
 
376
  def _call_nvidia_hosted_mistral(cfg: ModeConfig, prompt: str, api_key: str) -> str:
377
  """Call Mistral served through NVIDIA NIM using NVIDIA credentials."""
378
+ api_key = _clean_api_key(api_key)
379
  payload = {
380
  "model": NVIDIA_MISTRAL_FALLBACK_MODEL,
381
  "messages": [{"role": "user", "content": prompt}],
 
393
  )
394
  if response.status_code != 200:
395
  raise RuntimeError(f"NVIDIA-hosted Mistral {response.status_code}: {response.text[:300]}")
396
+ return _chat_response_text(response.json(), "NVIDIA-hosted Mistral")
 
397
 
398
 
399
  def rerank(query: str, passages: list[str]) -> list[float]:
 
403
  Returns scores in the same order as the input passages and falls back to
404
  uniform scores if the reranker is unavailable.
405
  """
406
+ api_key = _clean_api_key(os.getenv("NVIDIA_API_KEY_RERANK") or os.getenv("NVIDIA_API_KEY", ""))
407
  if not api_key or not passages:
408
  return [1.0] * len(passages)
409
 
mp1/pluto/modes.py CHANGED
@@ -23,6 +23,17 @@ from dotenv import load_dotenv
23
  load_dotenv()
24
 
25
 
 
 
 
 
 
 
 
 
 
 
 
26
  @dataclass(frozen=True)
27
  class ModeConfig:
28
  """Concrete model configuration for a single processing mode."""
@@ -66,9 +77,9 @@ def _build_registry() -> dict[str, ModeConfig]:
66
  "NVIDIA_API_KEY_VL", "NVIDIA_API_KEY_EMBED", "NVIDIA_API_KEY_RERANK",
67
  "NVIDIA_API_KEY_ULTRA"
68
  ]
69
- nvidia_ready = any(os.getenv(k) for k in nvidia_keys)
70
- groq_key = os.getenv("GROQ_API_KEY", "").strip()
71
- mistral_key = os.getenv("MISTRAL_API_KEY", "").strip()
72
 
73
  if nvidia_ready:
74
  return {
@@ -158,7 +169,7 @@ def _build_registry() -> dict[str, ModeConfig]:
158
  provider="groq",
159
  ),
160
  }
161
- if mistral_key:
162
  return _build_mistral_registry()
163
  return _build_unconfigured_registry()
164
 
 
23
  load_dotenv()
24
 
25
 
26
+ def _clean_api_key(api_key: str | None) -> str:
27
+ cleaned = str(api_key or "").strip().strip('"').strip("'")
28
+ if cleaned.lower().startswith("bearer "):
29
+ cleaned = cleaned[7:].strip()
30
+ return cleaned
31
+
32
+
33
+ def _looks_like_nvidia_key(api_key: str) -> bool:
34
+ return _clean_api_key(api_key).startswith("nvapi-")
35
+
36
+
37
  @dataclass(frozen=True)
38
  class ModeConfig:
39
  """Concrete model configuration for a single processing mode."""
 
77
  "NVIDIA_API_KEY_VL", "NVIDIA_API_KEY_EMBED", "NVIDIA_API_KEY_RERANK",
78
  "NVIDIA_API_KEY_ULTRA"
79
  ]
80
+ nvidia_ready = any(_clean_api_key(os.getenv(k)) for k in nvidia_keys)
81
+ groq_key = _clean_api_key(os.getenv("GROQ_API_KEY", ""))
82
+ mistral_key = _clean_api_key(os.getenv("MISTRAL_API_KEY", ""))
83
 
84
  if nvidia_ready:
85
  return {
 
169
  provider="groq",
170
  ),
171
  }
172
+ if mistral_key and not _looks_like_nvidia_key(mistral_key):
173
  return _build_mistral_registry()
174
  return _build_unconfigured_registry()
175