mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-24 15:38:12 +02:00
Raisonnement : REASONING_ECHO renvoie au moteur local la réflexion du modèle, en opt-in
Les gabarits Qwen3.5/3.6 rendent <think>…</think> pour chaque message assistant après la dernière question : c'est leur format entraîné. Loki ne gardait que le texte et les appels, donc à chaque étape d'une boucle d'outils le modèle relisait ses étapes précédentes avec des blocs vides, et le moteur recalculait le dernier message. Nouvelle clé REASONING_ECHO (off par défaut) : avec on, le raisonnement séparé par le serveur est gardé avec chaque message et renvoyé au même modèle. Sans la clé, rien n'est capturé ni envoyé : la requête est identique à l'octet près (testé). - Message.ReasoningContent (reasoning_content) + ReasoningModel, persistés ; seulement depuis reasoning_content du flux, jamais depuis le découpage « </think> » fait chez nous (rendu en double) - un seul point de sortie (echoMessages) : étiquette toujours retirée, raisonnement retiré pour une API externe, un autre modèle, un message sans texte ni appel, ou si la sonde dit que le gabarit ne le rend jamais - comptage : estimateTokens/compactBounds comptent ce qui part et que le gabarit rend (passé seulement si la sonde dit qu'il le garde, ou l'ignore) ; ctxAfter ne retire plus un raisonnement renvoyé - débordement : relance d'abord sans raisonnement, avant toute compaction ; shrinkToFit retire le raisonnement le plus ancien avant tout le reste ; le torse compacté le perd comme le résumé - erreur de gabarit (raise_exception, thinking, Failed to parse messages) : relance une fois sans raisonnement avant tool_choice none / outils coupés ; retenu pour le modèle si la relance passe - runBuilderTurn applique enfin NewHistory comme generate (compaction perdue pendant une passe de correction) ; forwardStream affiche la bannière - sonde de gabarit : nouveau verdict renders_reasoning - REASONING_PRESERVE (lot 1) inchangée, son interaction documentée Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3af481ff05
commit
7c9aa04fd9
12 files changed
+1121
-37
No files matched your search
@@ -45,7 +45,8 @@ func freshTplProbe(t *testing.T) {
|
||||
//
|
||||
// qwen3 : le raisonnement n'est rendu qu'après le dernier message utilisateur ;
|
||||
// keep : il est toujours rendu ;
|
||||
// noagp : comme keep, mais add_generation_prompt est ignoré (vieux moteur).
|
||||
// noagp : comme keep, mais add_generation_prompt est ignoré (vieux moteur) ;
|
||||
// drop : reasoning_content n'est jamais rendu.
|
||||
type fakeTpl struct {
|
||||
mu sync.Mutex
|
||||
mode string
|
||||
@@ -74,7 +75,7 @@ func (f *fakeTpl) render(body map[string]any) (string, int) {
|
||||
m := raw.(map[string]any)
|
||||
role, _ := m["role"].(string)
|
||||
b.WriteString("<|im_start|>" + role + "\n")
|
||||
if r, _ := m["reasoning_content"].(string); r != "" && role == "assistant" && (f.mode != "qwen3" || i > lastUser) {
|
||||
if r, _ := m["reasoning_content"].(string); r != "" && role == "assistant" && f.mode != "drop" && (f.mode != "qwen3" || i > lastUser) {
|
||||
b.WriteString("<think>" + r + "</think>")
|
||||
}
|
||||
c, _ := m["content"].(string)
|
||||
@@ -182,7 +183,7 @@ func TestSondeGabaritQwen3(t *testing.T) {
|
||||
f.start(t)
|
||||
kw := map[string]any{"enable_thinking": false}
|
||||
r := tplProbeEnsure(context.Background(), newTplShape(probeTools(), kw, ""))
|
||||
if r.PreservesHistory != tplNo || r.PrefixStable != tplNo {
|
||||
if r.PreservesHistory != tplNo || r.PrefixStable != tplNo || r.RendersReasoning != tplYes {
|
||||
t.Fatalf("Qwen3 retire le raisonnement passé : %+v", r)
|
||||
}
|
||||
if r.Build != "b7000-abc" || r.Model != "Qwen3-8B-Q8_0.gguf" || r.Tools != 1 || !r.cacheable {
|
||||
@@ -239,7 +240,7 @@ func TestSondeGabaritConserveLHistorique(t *testing.T) {
|
||||
f := &fakeTpl{mode: "keep"}
|
||||
f.start(t)
|
||||
r := tplProbeEnsure(context.Background(), newTplShape(nil, nil, ""))
|
||||
if r.PreservesHistory != tplYes || r.PrefixStable != tplYes {
|
||||
if r.PreservesHistory != tplYes || r.PrefixStable != tplYes || r.RendersReasoning != tplYes {
|
||||
t.Fatalf("gabarit qui garde tout : %+v", r)
|
||||
}
|
||||
for _, a := range f.applies {
|
||||
@@ -249,6 +250,22 @@ func TestSondeGabaritConserveLHistorique(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Gabarit (ou moteur) qui ignore reasoning_content partout : « jamais rendu »,
|
||||
// ce qui suffit à REASONING_ECHO pour ne rien renvoyer d'inutile.
|
||||
func TestSondeGabaritRaisonnementJamaisRendu(t *testing.T) {
|
||||
testHome(t)
|
||||
freshTplProbe(t)
|
||||
f := &fakeTpl{mode: "drop"}
|
||||
f.start(t)
|
||||
r := tplProbeEnsure(context.Background(), newTplShape(probeTools(), nil, ""))
|
||||
if r.RendersReasoning != tplNo || r.PreservesHistory != tplNo || !strings.Contains(r.Note, "jamais rendu") {
|
||||
t.Fatalf("raisonnement jamais rendu : %+v", r)
|
||||
}
|
||||
if !strings.Contains(tplProbeLine(r), "raisonnement_rendu=no") {
|
||||
t.Fatalf("journal : %s", tplProbeLine(r))
|
||||
}
|
||||
}
|
||||
|
||||
// Un moteur qui ignore add_generation_prompt ne doit pas faire conclure
|
||||
// « instable » : l'amorce rendue dans les deux cas fausserait la comparaison.
|
||||
func TestSondeGabaritAmorceIgnoree(t *testing.T) {
|
||||
@@ -270,7 +287,7 @@ func TestSondeGabarit404(t *testing.T) {
|
||||
f := &fakeTpl{mode: "qwen3", applyStatus: []int{404, 404, 404, 404}, applyBody: "Not Found"}
|
||||
f.start(t)
|
||||
r := tplProbeEnsure(context.Background(), newTplShape(probeTools(), nil, ""))
|
||||
if r.PreservesHistory != tplUnknown || r.PrefixStable != tplUnknown || !r.cacheable {
|
||||
if r.PreservesHistory != tplUnknown || r.PrefixStable != tplUnknown || r.RendersReasoning != tplUnknown || !r.cacheable {
|
||||
t.Fatalf("404 : %+v", r)
|
||||
}
|
||||
f.assertNoCompletion(t)
|
||||
|
||||
Reference in new issue
Block a user