mekosotto commited on
Commit
e175fb0
·
1 Parent(s): 4d00c18

feat(llm): classify 401 as fatal+actionable, 400 as skip-this-model

Browse files
Files changed (1) hide show
  1. src/llm/explainer.py +20 -0
src/llm/explainer.py CHANGED
@@ -302,6 +302,26 @@ def _llm_explain(payload: ExplainPayload, modality: str = "bbb") -> tuple[str, s
302
  continue
303
  except APIStatusError as e:
304
  status = getattr(e, "status_code", None)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
  # 402 credits / 403 access / 404 retired-id / 5xx upstream → next.
306
  if status in (402, 403, 404) or (status is not None and 500 <= status < 600):
307
  logger.info("OpenRouter %s on %s; advancing to next free model.", status, model)
 
302
  continue
303
  except APIStatusError as e:
304
  status = getattr(e, "status_code", None)
305
+ # 401 = unauthorized — the key is bad, no model in this chain
306
+ # will succeed. Surface a loud, actionable hint and bail.
307
+ if status == 401:
308
+ logger.warning(
309
+ "OpenRouter 401 unauthorized on %s. The OPENROUTER_API_KEY "
310
+ "is rejected — verify it is current at "
311
+ "https://openrouter.ai/keys and that free-model data-sharing "
312
+ "is enabled at https://openrouter.ai/settings/privacy. "
313
+ "Falling back to deterministic template.",
314
+ model,
315
+ )
316
+ return None
317
+ # 400 = malformed prompt for this specific model (e.g. it
318
+ # rejected our system role). Skip this model, try the next.
319
+ if status == 400:
320
+ logger.info(
321
+ "OpenRouter 400 on %s (likely prompt-shape mismatch); "
322
+ "advancing to next free model.", model,
323
+ )
324
+ continue
325
  # 402 credits / 403 access / 404 retired-id / 5xx upstream → next.
326
  if status in (402, 403, 404) or (status is not None and 500 <= status < 600):
327
  logger.info("OpenRouter %s on %s; advancing to next free model.", status, model)