mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
mcpPromptLine relisait le préfixe « [MCP: <serveur>] » des descriptions en le réécrivant à la main : changer le format dans mcpTools aurait fait disparaître la ligne en silence. Un nom de serveur contenant « ] » y était aussi coupé. - mcpTagDescription pose le préfixe, mcpToolServer le relit : un seul endroit. - mcpToolServer retient la coupure dont la forme assainie est celle du nom d'outil — « a] b » reste « a] b ». - Les tests qui appellent EnabledTools isolent $LOKI_HOME : la configuration MCP de la machine de test (et ses connexions) ne fausse plus le cas « sans outil MCP » ni le budget du préambule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
90 lines
3.7 KiB
Go
90 lines
3.7 KiB
Go
package loki
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func mcpTestTool(server, tool string) Tool {
|
|
return Tool{Type: "function", Function: ToolFunction{
|
|
Name: mcpExposedName(server, tool),
|
|
Description: mcpTagDescription(server, "décrit "+tool),
|
|
}}
|
|
}
|
|
|
|
// La ligne MCP du préambule se déduit des outils envoyés sur le MÊME tour : elle
|
|
// ne peut ni manquer au premier tour après un démarrage (pool encore vide quand
|
|
// le préambule était bâti), ni compter un outil masqué, ni apparaître pour un
|
|
// rôle qui ne reçoit aucun outil MCP.
|
|
func TestMCPPromptLineDeriveDesOutils(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
tools []Tool
|
|
want string
|
|
}{
|
|
{"aucun outil", nil, ""},
|
|
{"aucun outil MCP", []Tool{bashTool(), readTool()}, ""},
|
|
{"un serveur", []Tool{bashTool(), mcpTestTool("github", "issues"), mcpTestTool("github", "prs")},
|
|
"github (2)"},
|
|
{"plusieurs serveurs, triés", []Tool{mcpTestTool("zeta", "a"), mcpTestTool("alpha.io", "b"), mcpTestTool("zeta", "c")},
|
|
"alpha.io (1), zeta (2)"},
|
|
{"description sans le préfixe [MCP: …] ignorée", []Tool{{Type: "function", Function: ToolFunction{Name: "mcp__x__y", Description: "rien"}}},
|
|
""},
|
|
{"description vide après le préfixe", []Tool{{Type: "function", Function: ToolFunction{Name: "mcp__x__y", Description: "[MCP: x] "}}},
|
|
"x (1)"},
|
|
{"nom de serveur contenant « ] »", []Tool{mcpTestTool("a] b", "t"), mcpTestTool("a] b", "u")},
|
|
"a] b (2)"},
|
|
{"préfixe d'un autre serveur que celui du nom d'outil", []Tool{{Type: "function", Function: ToolFunction{Name: "mcp__x__y", Description: "[MCP: z] y"}}},
|
|
""},
|
|
}
|
|
for _, c := range cases {
|
|
got := mcpPromptLine(c.tools)
|
|
if c.want == "" {
|
|
if got != "" {
|
|
t.Errorf("%s : ligne %q, attendu aucune", c.name, got)
|
|
}
|
|
continue
|
|
}
|
|
if !strings.HasSuffix(got, ": "+c.want+".") {
|
|
t.Errorf("%s : ligne %q, attendu la liste %q", c.name, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Le préambule et les outils du tour viennent de la même tranche : le rôle
|
|
// planner (aucun outil MCP) n'a pas de ligne MCP, le fil principal l'a dès le
|
|
// premier tour.
|
|
func TestBaseSystemPromptLigneMCPSuitLesOutils(t *testing.T) {
|
|
// $LOKI_HOME isolé : EnabledTools lit la configuration MCP, et celle de la
|
|
// machine de test (serveurs réels, connexions comprises) fausserait le cas
|
|
// « sans outil MCP ».
|
|
testHome(t)
|
|
caps := Caps{Agent: true, Mem: MemOff}
|
|
with := append(EnabledTools(caps), mcpTestTool("github", "issues"))
|
|
if sp := baseSystemPrompt(caps, with); !strings.Contains(sp, "MCP servers connected") || !strings.Contains(sp, "github (1)") {
|
|
t.Fatalf("ligne MCP absente alors qu'un outil MCP part sur ce tour :\n%s", sp)
|
|
}
|
|
if sp := baseSystemPrompt(caps, EnabledTools(caps)); strings.Contains(sp, "MCP servers") {
|
|
t.Fatalf("ligne MCP sans aucun outil MCP envoyé :\n%s", sp)
|
|
}
|
|
planner := Caps{Agent: true, Code: true, Role: "planner", Mem: MemOff}
|
|
if sp := baseSystemPrompt(planner, EnabledTools(planner)); strings.Contains(sp, "MCP servers") {
|
|
t.Fatalf("le planner ne reçoit aucun outil MCP, sa ligne ne doit pas l'annoncer :\n%s", sp)
|
|
}
|
|
}
|
|
|
|
// prepareTurn rend la séquence ET les outils qu'elle décrit : le préambule
|
|
// injecté est exactement celui de baseSystemPrompt sur ces outils.
|
|
func TestPrepareTurnPreambuleCoherentAvecOutils(t *testing.T) {
|
|
testHome(t)
|
|
caps := Caps{Agent: true, Mem: MemOff}
|
|
sent, tools := prepareTurn([]Message{{Role: "user", Content: "salut"}}, caps)
|
|
if len(sent) != 2 || sent[0].Role != "system" {
|
|
t.Fatalf("séquence inattendue : %#v", sent)
|
|
}
|
|
sys, _ := sent[0].Content.(string)
|
|
if !strings.HasPrefix(sys, baseSystemPrompt(caps, tools)) {
|
|
t.Fatalf("le préambule ne correspond pas aux outils renvoyés :\n%s", sys)
|
|
}
|
|
}
|