- Nouvelle commande `inkflow benchmark` : compare la sortie d'analyse aux fichiers de référence (data/<slug>/reference/), met plusieurs modèles en concurrence, table rich + rapport JSON. Métriques : attribution de locuteur, incises, type/glued. Flags --models, --temperature, --reasoning, --stream, --use-cached + suivi par chapitre. - analysis/benchmark.py : scoring pur (testable) + runner multi-modèles (un MLX à la fois). - gemma.py : support des modèles à raisonnement (retrait de la pensée, désactivation via enable_thinking hors --reasoning, arrêt anticipé sur JSON complet, plafond + température dédiés anti-boucle), récupération du chat_template manquant (fix Mistral), streaming des tokens (set_token_sink). - settings.py : gemma_reasoning, gemma_reasoning_max_tokens, gemma_reasoning_temperature. - Tests : test_benchmark.py (scoring pur), test_gemma_reasoning.py. Conclusion benchmark : Qwen3.6-27B-8bit non-raisonnant = meilleur modèle d'analyse.
68 lines
2.4 KiB
Python
68 lines
2.4 KiB
Python
"""Tests purs de `_strip_reasoning` (retrait de la chaine de pensee).
|
|
|
|
Sans charger de modele : on verifie que la pensee est retiree et que
|
|
`_extract_json` recupere bien la reponse FINALE (et non un fragment JSON
|
|
parasite present dans la pensee).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from inkflow.analysis.gemma import (
|
|
_extract_json,
|
|
_has_complete_json,
|
|
_strip_reasoning,
|
|
)
|
|
|
|
|
|
def test_has_complete_json_arret_anticipe():
|
|
# JSON complet -> True (on peut stopper la generation)
|
|
assert _has_complete_json('voici: {"speaker": "Marie"}')
|
|
assert _has_complete_json('[{"a": 1}]')
|
|
# JSON tronque (reponse pas encore finie) -> False (on continue)
|
|
assert not _has_complete_json('{"speaker": "Mar')
|
|
assert not _has_complete_json('texte sans json')
|
|
# cas streaming reel : pensee close + fence json en cours mais objet complet
|
|
buf = _strip_reasoning('<think>...</think>```json\n{"speaker": "Marie"}')
|
|
assert _has_complete_json(buf)
|
|
|
|
|
|
def test_format_a_canaux_gemma4():
|
|
raw = (
|
|
"<|channel>thought\n"
|
|
"Thinking Process: la capitale est Paris. Exemple: {\"capitale\": \"...\"}\n"
|
|
"<channel|>```json\n{\"capitale\": \"Paris\"}\n```"
|
|
)
|
|
cleaned = _strip_reasoning(raw)
|
|
# la pensee (et son JSON d'exemple parasite) a disparu
|
|
assert "Thinking Process" not in cleaned
|
|
assert '"..."' not in cleaned
|
|
# le JSON extrait est bien la reponse finale
|
|
assert _extract_json(cleaned) == {"capitale": "Paris"}
|
|
|
|
|
|
def test_balises_think_deepseek():
|
|
raw = "<think>je reflechis, peut-etre [1,2]</think>\n[{\"speaker\": \"Holden\"}]"
|
|
cleaned = _strip_reasoning(raw)
|
|
assert "reflechis" not in cleaned
|
|
assert _extract_json(cleaned) == [{"speaker": "Holden"}]
|
|
|
|
|
|
def test_sans_raisonnement_inchange():
|
|
raw = '{"speaker": "Kajri"}'
|
|
assert _strip_reasoning(raw) == raw
|
|
assert _extract_json(_strip_reasoning(raw)) == {"speaker": "Kajri"}
|
|
|
|
|
|
def test_pensee_tronquee_sans_fermeture():
|
|
# pensee non fermee (budget de tokens epuise) : le prefixe de canal saute,
|
|
# on ne renvoie pas le marqueur d'ouverture.
|
|
raw = "<|channel>thought\nje commence a reflechir mais c'est coupe"
|
|
cleaned = _strip_reasoning(raw)
|
|
assert not cleaned.startswith("<|channel")
|
|
assert "<channel" not in cleaned
|
|
|
|
|
|
def test_dernier_marqueur_gagne():
|
|
# plusieurs blocs : seule la derniere reponse finale compte
|
|
raw = "<think>a</think>milieu<think>b</think>FINAL"
|
|
assert _strip_reasoning(raw) == "FINAL"
|