mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
Erreur 500 : la consigne de repli ne doit pas casser le gabarit du modèle
Après un 500 de llama-server, l'amont relance le tour sans outils en ajoutant une consigne système À LA FIN de la conversation. Or plusieurs gabarits exigent que le message système soit le PREMIER : gpt-oss (rôle « developer ») lève « System message must be at the beginning ». La tentative de récupération produisait donc elle-même un 500, et cette exception Jinja remplaçait l'erreur d'origine sous les yeux de l'utilisateur. steerSystem fusionne désormais la consigne dans le message système de tête, ou la pose en première position s'il n'y en a pas — jamais ailleurs. La liste de l'appelant n'est pas modifiée. Tests : fusion sans ajout de message, création en tête quand il n'y a pas de système, et non-modification de la liste d'origine.
This commit is contained in:
2 files changed
+72
-1
No files matched your search
@@ -241,6 +241,26 @@ func InjectSkills(msgs []Message, caps Caps) []Message {
|
||||
return append([]Message{{Role: "system", Content: prefix}}, msgs...)
|
||||
}
|
||||
|
||||
// steerSystem ajoute une consigne système SANS jamais créer un second message
|
||||
// système ailleurs qu'en tête : elle est fusionnée dans celui d'ouverture, ou
|
||||
// posée en première position s'il n'y en a pas.
|
||||
//
|
||||
// Un message système ajouté À LA FIN faisait échouer le tour sur les modèles
|
||||
// dont le gabarit l'interdit : gpt-oss (rôle « developer ») lève « System
|
||||
// message must be at the beginning », et ce 500 du gabarit remplaçait l'erreur
|
||||
// d'origine que la nouvelle tentative cherchait justement à contourner —
|
||||
// l'utilisateur voyait une exception Jinja au lieu du vrai problème.
|
||||
func steerSystem(msgs []Message, hint string) []Message {
|
||||
if len(msgs) > 0 && msgs[0].Role == "system" {
|
||||
if existing, ok := msgs[0].Content.(string); ok {
|
||||
out := append([]Message(nil), msgs...)
|
||||
out[0] = Message{Role: "system", Content: existing + "\n\n" + hint}
|
||||
return out
|
||||
}
|
||||
}
|
||||
return append([]Message{{Role: "system", Content: hint}}, msgs...)
|
||||
}
|
||||
|
||||
// EnabledTools returns the tools to advertise on the next inference call.
|
||||
func EnabledTools(caps Caps) []Tool {
|
||||
tools := []Tool{}
|
||||
@@ -651,7 +671,7 @@ func runChat(ctx context.Context, messages []Message, temperature float64, caps
|
||||
// Nudge the model to answer in plain text from what it already
|
||||
// gathered, so it doesn't immediately re-emit a tool call that
|
||||
// llama.cpp would again fail to parse.
|
||||
messages = append(messages, Message{Role: "system", Content: "N'appelle plus d'outil. Réponds maintenant directement en français à partir des informations déjà obtenues."})
|
||||
messages = steerSystem(messages, "N'appelle plus d'outil. Réponds maintenant directement en français à partir des informations déjà obtenues.")
|
||||
continue
|
||||
}
|
||||
err := fmt.Errorf("llama-server a renvoyé %d : %s", resp.StatusCode, msg)
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package loki
|
||||
|
||||
import "testing"
|
||||
|
||||
// Plusieurs gabarits de conversation exigent que le message système soit le
|
||||
// PREMIER — gpt-oss lève « System message must be at the beginning ». La
|
||||
// consigne posée en cours de tour (repli après un 500) ne doit donc jamais
|
||||
// arriver en fin de liste, sinon la tentative de récupération échoue à son tour
|
||||
// et masque l'erreur d'origine.
|
||||
func TestSteerSystemResteEnTete(t *testing.T) {
|
||||
hint := "N'appelle plus d'outil."
|
||||
|
||||
t.Run("fusionne dans le système existant", func(t *testing.T) {
|
||||
msgs := []Message{
|
||||
{Role: "system", Content: "Tu es Loki."},
|
||||
{Role: "user", Content: "bonjour"},
|
||||
{Role: "assistant", Content: "salut"},
|
||||
}
|
||||
out := steerSystem(msgs, hint)
|
||||
if len(out) != len(msgs) {
|
||||
t.Fatalf("%d messages, attendu %d : aucun message ne doit être ajouté", len(out), len(msgs))
|
||||
}
|
||||
if got := out[0].Content.(string); got != "Tu es Loki.\n\n"+hint {
|
||||
t.Fatalf("consigne non fusionnée en tête : %q", got)
|
||||
}
|
||||
for i, m := range out[1:] {
|
||||
if m.Role == "system" {
|
||||
t.Fatalf("message système en position %d — il doit rester unique et en tête", i+1)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("sans système existant, il est posé en tête", func(t *testing.T) {
|
||||
msgs := []Message{{Role: "user", Content: "bonjour"}}
|
||||
out := steerSystem(msgs, hint)
|
||||
if out[0].Role != "system" || out[0].Content.(string) != hint {
|
||||
t.Fatalf("premier message = %+v, attendu le système", out[0])
|
||||
}
|
||||
if out[1].Role != "user" {
|
||||
t.Fatalf("la conversation d'origine doit suivre, obtenu %+v", out[1])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ne modifie pas la liste d'origine", func(t *testing.T) {
|
||||
msgs := []Message{{Role: "system", Content: "Tu es Loki."}, {Role: "user", Content: "bonjour"}}
|
||||
_ = steerSystem(msgs, hint)
|
||||
if got := msgs[0].Content.(string); got != "Tu es Loki." {
|
||||
t.Fatalf("liste d'appelant modifiée : %q", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user