feat(jarvis): exclude Gemini 2.5 from Jarvis dropdown + remove diag
Browse filesGemini 2.5 Flash Lite emet un AIMessage vide (tcs=0 content_len=0
finish=STOP) apres une serie de validate_candidate echoues, ce qui
termine prematurement le stream langgraph. Le comportement n'apparait
pas en chat (mono-tour, pas de boucle de tool calls) — donc 2.5 reste
disponible cote chat mais retire du dropdown Jarvis.
Implementation :
- JARVIS_BLACKLIST = {'gemini-2.5-flash-lite'} (extensible)
- build_model_choices(for_jarvis=False) filtre selon ce set
- _safe_jarvis_value() fallback sur 3.1 si user a selectionne 2.5
cote chat puis ouvre Jarvis (sinon dropdown value invalid)
- Tous les callsites jarvis (init dropdown + 3 handlers de refresh
cross-sync) passent for_jarvis=True
Removed temporary diag (🐛 chunk#N AIMsg/ToolMsg dump) qui etait dans
jarvis.py — diagnostic confirme, plus besoin.
|
@@ -659,17 +659,31 @@ def refresh_dropdowns_silent():
|
|
| 659 |
)
|
| 660 |
|
| 661 |
|
| 662 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 663 |
"""Construit la liste de (label, value) du dropdown des modèles.
|
| 664 |
|
| 665 |
Marquage server-side (les deux dropdowns) :
|
| 666 |
- ✅ devant le modèle courant (_CURRENT_MODEL)
|
| 667 |
- suffixe `— épuisé sur cette clé` pour les modèles Gemini natifs
|
| 668 |
blown sur la clé courante (le JS ajoute ❌ + grisage CSS)
|
|
|
|
|
|
|
|
|
|
| 669 |
"""
|
| 670 |
import re as _re
|
| 671 |
out: list[tuple[str, str]] = []
|
| 672 |
for key, label in ALL_MODELS.items():
|
|
|
|
|
|
|
| 673 |
# Le check « épuisé » s'applique à TOUS les Gemini natifs
|
| 674 |
# (3.1, 3.5, 2.5…), pas seulement NATIVE_REQUIRED. Sinon
|
| 675 |
# 2.5 hit PerDay → mark_blown → MAIS dropdown ne montre rien
|
|
@@ -685,6 +699,15 @@ def build_model_choices(for_chatbot: bool = False) -> list[tuple[str, str]]:
|
|
| 685 |
return out
|
| 686 |
|
| 687 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 688 |
def pick_unblown_gemini_key(model: str,
|
| 689 |
skip: Optional[str] = None) -> Optional[str]:
|
| 690 |
"""Renvoie une clé du pool non-épuisée pour `model`, ou None si
|
|
@@ -2713,7 +2736,7 @@ with gr.Blocks(theme=THEME, title="JDMAgent Demo", head=_HEAD_JS, css=_CHATBOT_C
|
|
| 2713 |
render=False,
|
| 2714 |
)
|
| 2715 |
jarvis_model = gr.Dropdown(
|
| 2716 |
-
choices=build_model_choices(),
|
| 2717 |
value="gemini-3.1-flash-lite",
|
| 2718 |
label="Modèle",
|
| 2719 |
filterable=False,
|
|
@@ -4482,7 +4505,8 @@ with gr.Blocks(theme=THEME, title="JDMAgent Demo", head=_HEAD_JS, css=_CHATBOT_C
|
|
| 4482 |
cur = _CURRENT_MODEL or "gemini-3.1-flash-lite"
|
| 4483 |
return (
|
| 4484 |
gr.update(choices=build_model_choices(), value=cur),
|
| 4485 |
-
gr.update(choices=build_model_choices(
|
|
|
|
| 4486 |
)
|
| 4487 |
main_tabs.select(
|
| 4488 |
_refresh_both_dropdowns,
|
|
@@ -4506,11 +4530,12 @@ with gr.Blocks(theme=THEME, title="JDMAgent Demo", head=_HEAD_JS, css=_CHATBOT_C
|
|
| 4506 |
next_key = pick_unblown_gemini_key(cur_mod, skip=cur_key)
|
| 4507 |
if next_key and next_key != cur_key:
|
| 4508 |
set_current_gemini_key(next_key)
|
| 4509 |
-
|
|
|
|
| 4510 |
new_label = _switch_key_btn_label()
|
| 4511 |
return (
|
| 4512 |
-
gr.update(choices=
|
| 4513 |
-
gr.update(choices=
|
| 4514 |
gr.update(value=new_label),
|
| 4515 |
gr.update(value=new_label),
|
| 4516 |
)
|
|
@@ -4547,10 +4572,11 @@ with gr.Blocks(theme=THEME, title="JDMAgent Demo", head=_HEAD_JS, css=_CHATBOT_C
|
|
| 4547 |
thinking_update = (gr.update(interactive=True)
|
| 4548 |
if m in THINKING_SUPPORTED_MODELS
|
| 4549 |
else gr.update(interactive=False, value=False))
|
| 4550 |
-
# Refresh choices avec ✅ devant le NOUVEAU modèle courant
|
| 4551 |
-
#
|
| 4552 |
-
#
|
| 4553 |
-
|
|
|
|
| 4554 |
return (
|
| 4555 |
gr.update( # key_in
|
| 4556 |
interactive=needs_key,
|
|
@@ -4558,8 +4584,9 @@ with gr.Blocks(theme=THEME, title="JDMAgent Demo", head=_HEAD_JS, css=_CHATBOT_C
|
|
| 4558 |
else "Non requis pour les modèles Gemini hébergés"),
|
| 4559 |
),
|
| 4560 |
thinking_update, # chat_thinking
|
| 4561 |
-
gr.update(choices=
|
| 4562 |
-
gr.update(choices=
|
|
|
|
| 4563 |
)
|
| 4564 |
model_in.change(
|
| 4565 |
_on_model_change_chat,
|
|
@@ -4576,11 +4603,14 @@ with gr.Blocks(theme=THEME, title="JDMAgent Demo", head=_HEAD_JS, css=_CHATBOT_C
|
|
| 4576 |
thinking_update = (gr.update(interactive=True)
|
| 4577 |
if m in THINKING_SUPPORTED_MODELS
|
| 4578 |
else gr.update(interactive=False, value=False))
|
| 4579 |
-
|
|
|
|
|
|
|
|
|
|
| 4580 |
return (
|
| 4581 |
thinking_update, # jarvis_thinking
|
| 4582 |
-
gr.update(choices=
|
| 4583 |
-
gr.update(choices=
|
| 4584 |
)
|
| 4585 |
jarvis_model.change(
|
| 4586 |
_on_jarvis_model_change,
|
|
|
|
| 659 |
)
|
| 660 |
|
| 661 |
|
| 662 |
+
# Modeles exclus du dropdown JARVIS uniquement (restent dispo dans
|
| 663 |
+
# l'onglet « 🤖 Agent » / chat libre). Diag empirique : Gemini 2.5
|
| 664 |
+
# Flash Lite emet un AIMessage VIDE (`tcs=0 content_len=0 finish=STOP`)
|
| 665 |
+
# apres une serie de validate_candidate echoues, ce qui termine le
|
| 666 |
+
# stream langgraph. Le comportement n'apparait pas en chat (mono-tour,
|
| 667 |
+
# pas de boucle de tool calls) — d'ou le filtrage cible.
|
| 668 |
+
JARVIS_BLACKLIST = {"gemini-2.5-flash-lite"}
|
| 669 |
+
|
| 670 |
+
|
| 671 |
+
def build_model_choices(for_jarvis: bool = False) -> list[tuple[str, str]]:
|
| 672 |
"""Construit la liste de (label, value) du dropdown des modèles.
|
| 673 |
|
| 674 |
Marquage server-side (les deux dropdowns) :
|
| 675 |
- ✅ devant le modèle courant (_CURRENT_MODEL)
|
| 676 |
- suffixe `— épuisé sur cette clé` pour les modèles Gemini natifs
|
| 677 |
blown sur la clé courante (le JS ajoute ❌ + grisage CSS)
|
| 678 |
+
|
| 679 |
+
`for_jarvis=True` filtre les modèles listés dans `JARVIS_BLACKLIST`
|
| 680 |
+
(ex. Gemini 2.5 — bail en mode tool-call loop, cf. constante).
|
| 681 |
"""
|
| 682 |
import re as _re
|
| 683 |
out: list[tuple[str, str]] = []
|
| 684 |
for key, label in ALL_MODELS.items():
|
| 685 |
+
if for_jarvis and key in JARVIS_BLACKLIST:
|
| 686 |
+
continue
|
| 687 |
# Le check « épuisé » s'applique à TOUS les Gemini natifs
|
| 688 |
# (3.1, 3.5, 2.5…), pas seulement NATIVE_REQUIRED. Sinon
|
| 689 |
# 2.5 hit PerDay → mark_blown → MAIS dropdown ne montre rien
|
|
|
|
| 699 |
return out
|
| 700 |
|
| 701 |
|
| 702 |
+
def _safe_jarvis_value(model_id: str) -> str:
|
| 703 |
+
"""Renvoie un model_id utilisable comme `value` du dropdown jarvis.
|
| 704 |
+
Si `model_id` est dans la blacklist (ex. user a selectionne Gemini 2.5
|
| 705 |
+
cote chat puis ouvre Jarvis), fallback sur un default sur (3.1)."""
|
| 706 |
+
if model_id in JARVIS_BLACKLIST:
|
| 707 |
+
return "gemini-3.1-flash-lite"
|
| 708 |
+
return model_id
|
| 709 |
+
|
| 710 |
+
|
| 711 |
def pick_unblown_gemini_key(model: str,
|
| 712 |
skip: Optional[str] = None) -> Optional[str]:
|
| 713 |
"""Renvoie une clé du pool non-épuisée pour `model`, ou None si
|
|
|
|
| 2736 |
render=False,
|
| 2737 |
)
|
| 2738 |
jarvis_model = gr.Dropdown(
|
| 2739 |
+
choices=build_model_choices(for_jarvis=True),
|
| 2740 |
value="gemini-3.1-flash-lite",
|
| 2741 |
label="Modèle",
|
| 2742 |
filterable=False,
|
|
|
|
| 4505 |
cur = _CURRENT_MODEL or "gemini-3.1-flash-lite"
|
| 4506 |
return (
|
| 4507 |
gr.update(choices=build_model_choices(), value=cur),
|
| 4508 |
+
gr.update(choices=build_model_choices(for_jarvis=True),
|
| 4509 |
+
value=_safe_jarvis_value(cur)),
|
| 4510 |
)
|
| 4511 |
main_tabs.select(
|
| 4512 |
_refresh_both_dropdowns,
|
|
|
|
| 4530 |
next_key = pick_unblown_gemini_key(cur_mod, skip=cur_key)
|
| 4531 |
if next_key and next_key != cur_key:
|
| 4532 |
set_current_gemini_key(next_key)
|
| 4533 |
+
choices_chat = build_model_choices()
|
| 4534 |
+
choices_jarvis = build_model_choices(for_jarvis=True)
|
| 4535 |
new_label = _switch_key_btn_label()
|
| 4536 |
return (
|
| 4537 |
+
gr.update(choices=choices_chat),
|
| 4538 |
+
gr.update(choices=choices_jarvis),
|
| 4539 |
gr.update(value=new_label),
|
| 4540 |
gr.update(value=new_label),
|
| 4541 |
)
|
|
|
|
| 4572 |
thinking_update = (gr.update(interactive=True)
|
| 4573 |
if m in THINKING_SUPPORTED_MODELS
|
| 4574 |
else gr.update(interactive=False, value=False))
|
| 4575 |
+
# Refresh choices avec ✅ devant le NOUVEAU modèle courant.
|
| 4576 |
+
# jarvis_model utilise un set filtre (sans 2.5) et un value
|
| 4577 |
+
# safe (fallback 3.1 si user vient de selectionner 2.5 cote chat).
|
| 4578 |
+
choices_chat = build_model_choices()
|
| 4579 |
+
choices_jarvis = build_model_choices(for_jarvis=True)
|
| 4580 |
return (
|
| 4581 |
gr.update( # key_in
|
| 4582 |
interactive=needs_key,
|
|
|
|
| 4584 |
else "Non requis pour les modèles Gemini hébergés"),
|
| 4585 |
),
|
| 4586 |
thinking_update, # chat_thinking
|
| 4587 |
+
gr.update(choices=choices_chat, value=m), # model_in
|
| 4588 |
+
gr.update(choices=choices_jarvis, # jarvis_model
|
| 4589 |
+
value=_safe_jarvis_value(m)),
|
| 4590 |
)
|
| 4591 |
model_in.change(
|
| 4592 |
_on_model_change_chat,
|
|
|
|
| 4603 |
thinking_update = (gr.update(interactive=True)
|
| 4604 |
if m in THINKING_SUPPORTED_MODELS
|
| 4605 |
else gr.update(interactive=False, value=False))
|
| 4606 |
+
# m vient du dropdown jarvis donc pas blacklisté par construction.
|
| 4607 |
+
# On rebuild les choices separement pour chaque dropdown.
|
| 4608 |
+
choices_chat = build_model_choices()
|
| 4609 |
+
choices_jarvis = build_model_choices(for_jarvis=True)
|
| 4610 |
return (
|
| 4611 |
thinking_update, # jarvis_thinking
|
| 4612 |
+
gr.update(choices=choices_chat, value=m), # model_in
|
| 4613 |
+
gr.update(choices=choices_jarvis, value=m), # jarvis_model
|
| 4614 |
)
|
| 4615 |
jarvis_model.change(
|
| 4616 |
_on_jarvis_model_change,
|
|
@@ -1424,10 +1424,6 @@ def run_jarvis_flow(
|
|
| 1424 |
consecutive_rate_limit_hits = 0
|
| 1425 |
MAX_CONSECUTIVE_RATE_LIMIT = 3
|
| 1426 |
proactive_condense_count = 0
|
| 1427 |
-
# === DIAG TEMPORAIRE : numerotation des chunks reçus du stream
|
| 1428 |
-
# pour traquer le bug Gemini 2.5 « fin de stream prematuree ».
|
| 1429 |
-
# À supprimer une fois le bug identifie. ===
|
| 1430 |
-
_diag_chunk_n = 0
|
| 1431 |
with budget_context(limit=limit) as budget:
|
| 1432 |
# boucle retry quota : ILLIMITÉ tant que le délai
|
| 1433 |
# retry est court (cf. detect_rate_limit_retry, cap
|
|
@@ -1452,45 +1448,6 @@ def run_jarvis_flow(
|
|
| 1452 |
# Accumulation pour permettre la reprise
|
| 1453 |
# après pause quota (cf. retry plus bas).
|
| 1454 |
accumulated_messages.append(m)
|
| 1455 |
-
# === DIAG : log TOUS les messages (AIMessage
|
| 1456 |
-
# ET ToolMessage) avec le nom du node qui
|
| 1457 |
-
# les a emis. Le bug observe (chunk vide
|
| 1458 |
-
# tcs=0 content_len=0) peut etre un artefact
|
| 1459 |
-
# langgraph (END node, par exemple) plutot
|
| 1460 |
-
# que la vraie reponse Gemini 2.5. ===
|
| 1461 |
-
_diag_chunk_n += 1
|
| 1462 |
-
try:
|
| 1463 |
-
_diag_addl = getattr(m, "additional_kwargs", {}) or {}
|
| 1464 |
-
_diag_meta = getattr(m, "response_metadata", {}) or {}
|
| 1465 |
-
_diag_invalid = getattr(m, "invalid_tool_calls", None)
|
| 1466 |
-
_diag_content_repr = repr(getattr(m, "content", ""))[:200]
|
| 1467 |
-
_diag_finish = (
|
| 1468 |
-
_diag_meta.get("finish_reason")
|
| 1469 |
-
or _diag_meta.get("stop_reason")
|
| 1470 |
-
or "?"
|
| 1471 |
-
)
|
| 1472 |
-
_diag_msgcls = type(m).__name__
|
| 1473 |
-
_diag_tcs_n = len(getattr(m, "tool_calls", []) or [])
|
| 1474 |
-
_diag_line = (
|
| 1475 |
-
f"<div class=\"jdm-narration\">"
|
| 1476 |
-
f"🐛 <b>chunk#{_diag_chunk_n}</b> "
|
| 1477 |
-
f"node=<code>{_node}</code> "
|
| 1478 |
-
f"cls={_diag_msgcls} "
|
| 1479 |
-
f"tcs={_diag_tcs_n} "
|
| 1480 |
-
f"finish={_diag_finish} "
|
| 1481 |
-
f"addl_keys={list(_diag_addl.keys())} "
|
| 1482 |
-
f"invalid_tcs={_diag_invalid!r} "
|
| 1483 |
-
f"content_len={len(str(getattr(m,'content','') or ''))} "
|
| 1484 |
-
f"content_repr={_diag_content_repr}"
|
| 1485 |
-
f"</div>"
|
| 1486 |
-
)
|
| 1487 |
-
_add_line(_diag_line)
|
| 1488 |
-
except Exception as _diag_e:
|
| 1489 |
-
_add_line(
|
| 1490 |
-
f"<div class=\"jdm-narration\">"
|
| 1491 |
-
f"🐛 chunk#{_diag_chunk_n} diag-error: {_diag_e}"
|
| 1492 |
-
f"</div>"
|
| 1493 |
-
)
|
| 1494 |
if isinstance(m, AIMessage):
|
| 1495 |
tcs = getattr(m, "tool_calls", []) or []
|
| 1496 |
# 1) Chain-of-thought (Anthropic Extended,
|
|
|
|
| 1424 |
consecutive_rate_limit_hits = 0
|
| 1425 |
MAX_CONSECUTIVE_RATE_LIMIT = 3
|
| 1426 |
proactive_condense_count = 0
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1427 |
with budget_context(limit=limit) as budget:
|
| 1428 |
# boucle retry quota : ILLIMITÉ tant que le délai
|
| 1429 |
# retry est court (cf. detect_rate_limit_retry, cap
|
|
|
|
| 1448 |
# Accumulation pour permettre la reprise
|
| 1449 |
# après pause quota (cf. retry plus bas).
|
| 1450 |
accumulated_messages.append(m)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1451 |
if isinstance(m, AIMessage):
|
| 1452 |
tcs = getattr(m, "tool_calls", []) or []
|
| 1453 |
# 1) Chain-of-thought (Anthropic Extended,
|