expAge commited on
Commit
213924e
·
1 Parent(s): 5e70029

feat(jarvis): exclude Gemini 2.5 from Jarvis dropdown + remove diag

Browse files

Gemini 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.

Files changed (2) hide show
  1. app.py +45 -15
  2. jarvis.py +0 -43
app.py CHANGED
@@ -659,17 +659,31 @@ def refresh_dropdowns_silent():
659
  )
660
 
661
 
662
- def build_model_choices(for_chatbot: bool = False) -> list[tuple[str, str]]:
 
 
 
 
 
 
 
 
 
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(), value=cur),
 
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
- choices = build_model_choices()
 
4510
  new_label = _switch_key_btn_label()
4511
  return (
4512
- gr.update(choices=choices),
4513
- gr.update(choices=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
- # même operation pour les deux dropdowns. show_progress=
4552
- # "hidden" rend l'aller-retour invisible.
4553
- choices = build_model_choices()
 
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=choices, value=m), # model_in
4562
- gr.update(choices=choices, value=m), # jarvis_model
 
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
- choices = build_model_choices()
 
 
 
4580
  return (
4581
  thinking_update, # jarvis_thinking
4582
- gr.update(choices=choices, value=m), # model_in
4583
- gr.update(choices=choices, value=m), # jarvis_model
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,
jarvis.py CHANGED
@@ -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,