Spaces:
Sleeping
Sleeping
Deploy CellQuant
Browse files- cellquant/gemini_summary.py +21 -3
- tests/test_validation.py +6 -0
cellquant/gemini_summary.py
CHANGED
|
@@ -96,8 +96,26 @@ def _generate(prompt: str, overlay: Image.Image | None, model: str | None) -> st
|
|
| 96 |
resp = client.models.generate_content(model=mid, contents=contents)
|
| 97 |
text = (resp.text or "").strip()
|
| 98 |
return text if text else "Gemini returned an empty response."
|
| 99 |
-
except Exception as e: # noqa: BLE001 - try the next model id
|
| 100 |
last = f"{type(e).__name__}: {e}"
|
| 101 |
-
if
|
| 102 |
break
|
| 103 |
-
return f"Gemini summary
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
resp = client.models.generate_content(model=mid, contents=contents)
|
| 97 |
text = (resp.text or "").strip()
|
| 98 |
return text if text else "Gemini returned an empty response."
|
| 99 |
+
except Exception as e: # noqa: BLE001 - try the next model id on a missing model or an exhausted quota
|
| 100 |
last = f"{type(e).__name__}: {e}"
|
| 101 |
+
if not _retryable(e):
|
| 102 |
break
|
| 103 |
+
return f"Gemini summary unavailable right now ({_short_error(last)}). The counts above do not depend on it."
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def _retryable(e: Exception) -> bool:
|
| 107 |
+
"""Move on to the next model when this one is unknown or its quota / rate limit is used up."""
|
| 108 |
+
msg = str(e)
|
| 109 |
+
return ("NOT_FOUND" in msg or "not found" in msg.lower()
|
| 110 |
+
or "RESOURCE_EXHAUSTED" in msg or "429" in msg[:40] or "quota" in msg.lower() or "rate limit" in msg.lower())
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def _short_error(text: str, limit: int = 220) -> str:
|
| 114 |
+
"""Status line of an API error plus the API's own message (not the JSON payload), trimmed."""
|
| 115 |
+
import re
|
| 116 |
+
head = text.split("{", 1)[0].strip().rstrip(".") or text
|
| 117 |
+
m = re.search(r"'message':\s*'([^']*)'", text) or re.search(r'"message":\s*"([^"]*)"', text)
|
| 118 |
+
if m:
|
| 119 |
+
msg = m.group(1).split(" Learn more")[0].strip()
|
| 120 |
+
head = f"{head}: {msg}"
|
| 121 |
+
return head[:limit]
|
tests/test_validation.py
CHANGED
|
@@ -178,3 +178,9 @@ def test_gemini_prompts_and_disabled_paths(monkeypatch):
|
|
| 178 |
assert "do not round" in txt and "training set" in txt
|
| 179 |
assert g.summarize_comparison("C100", rows, COMPARISON_HEADERS).startswith("Gemini summary disabled")
|
| 180 |
assert g.summarize([{"image": "a", "counts": {"Cell, Antibody +": 1}}]).startswith("Gemini summary disabled")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
assert "do not round" in txt and "training set" in txt
|
| 179 |
assert g.summarize_comparison("C100", rows, COMPARISON_HEADERS).startswith("Gemini summary disabled")
|
| 180 |
assert g.summarize([{"image": "a", "counts": {"Cell, Antibody +": 1}}]).startswith("Gemini summary disabled")
|
| 181 |
+
assert g._retryable(Exception("404 NOT_FOUND. model x")) and g._retryable(Exception("429 RESOURCE_EXHAUSTED. {'error': {}}"))
|
| 182 |
+
assert not g._retryable(Exception("400 INVALID_ARGUMENT. bad key"))
|
| 183 |
+
assert g._short_error("ClientError: 429 RESOURCE_EXHAUSTED. {'error': {'code': 429}}") == "ClientError: 429 RESOURCE_EXHAUSTED"
|
| 184 |
+
cap = ("ClientError: 429 RESOURCE_EXHAUSTED. {'error': {'code': 429, 'message': 'Your billing account has exceeded its "
|
| 185 |
+
"monthly spending cap. Please go to AI Studio. Learn more at https://x', 'status': 'RESOURCE_EXHAUSTED'}}")
|
| 186 |
+
assert g._short_error(cap) == "ClientError: 429 RESOURCE_EXHAUSTED: Your billing account has exceeded its monthly spending cap. Please go to AI Studio."
|