mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
Cache : corrections de relecture — la ligne MCP et les descriptions d'outils partagent un seul format
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>
This commit is contained in:
1 parent
7dd319a058
commit
eaf5e313d9
3 files changed
+48
-15
No files matched your search
@@ -49,6 +49,9 @@ import (
|
|||||||
const promptCharBudget = 11000 // ~2750 tokens, tout allumé
|
const promptCharBudget = 11000 // ~2750 tokens, tout allumé
|
||||||
|
|
||||||
func TestSystemPromptStaysLean(t *testing.T) {
|
func TestSystemPromptStaysLean(t *testing.T) {
|
||||||
|
// $LOKI_HOME isolé : les outils MCP configurés sur la machine de test (et
|
||||||
|
// leur ligne au préambule) ne comptent pas dans le budget de Loki.
|
||||||
|
testHome(t)
|
||||||
caps := Caps{Agent: true, Internet: true, Mem: MemAlways}
|
caps := Caps{Agent: true, Internet: true, Mem: MemAlways}
|
||||||
tools := EnabledTools(caps)
|
tools := EnabledTools(caps)
|
||||||
sp := baseSystemPrompt(caps, tools)
|
sp := baseSystemPrompt(caps, tools)
|
||||||
|
|||||||
+36
-14
@@ -307,7 +307,7 @@ func mcpTools() []Tool {
|
|||||||
}
|
}
|
||||||
// Préfixe le serveur d'origine dans la description : aide le modèle à
|
// Préfixe le serveur d'origine dans la description : aide le modèle à
|
||||||
// choisir entre outils similaires de serveurs différents.
|
// choisir entre outils similaires de serveurs différents.
|
||||||
desc = "[MCP: " + name + "] " + desc
|
desc = mcpTagDescription(name, desc)
|
||||||
out = append(out, Tool{
|
out = append(out, Tool{
|
||||||
Type: "function",
|
Type: "function",
|
||||||
Function: ToolFunction{
|
Function: ToolFunction{
|
||||||
@@ -449,6 +449,39 @@ func flattenMCPContent(res *mcpsdk.CallToolResult) string {
|
|||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mcpTagDescription préfixe la description d'un outil MCP par le nom d'origine
|
||||||
|
// de son serveur, que le nom d'outil (assaini) ne garde pas. mcpToolServer le
|
||||||
|
// relit pour la ligne du préambule : ce format n'existe qu'ici, pour que les
|
||||||
|
// deux ne puissent pas diverger en silence.
|
||||||
|
func mcpTagDescription(server, desc string) string {
|
||||||
|
return "[MCP: " + server + "] " + desc
|
||||||
|
}
|
||||||
|
|
||||||
|
// mcpToolServer retrouve le serveur d'origine d'un outil MCP annoncé, d'après
|
||||||
|
// le préfixe posé par mcpTagDescription. Le nom brut d'un serveur peut lui-même
|
||||||
|
// contenir « ] » : on retient la première coupure dont la forme assainie est
|
||||||
|
// bien celle qui figure dans le nom de l'outil.
|
||||||
|
func mcpToolServer(t Tool) (string, bool) {
|
||||||
|
name := t.Function.Name
|
||||||
|
if !strings.HasPrefix(name, "mcp__") {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
rest, ok := strings.CutPrefix(t.Function.Description, "[MCP: ")
|
||||||
|
if !ok {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
for i := 0; ; {
|
||||||
|
j := strings.Index(rest[i:], "] ")
|
||||||
|
if j < 0 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
if server := rest[:i+j]; strings.HasPrefix(name, "mcp__"+mcpSanitize(server)+"__") {
|
||||||
|
return server, true
|
||||||
|
}
|
||||||
|
i += j + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// mcpPromptLine renvoie une ligne système listant les serveurs MCP annoncés et
|
// mcpPromptLine renvoie une ligne système listant les serveurs MCP annoncés et
|
||||||
// leur nombre d'outils, pour situer le modèle. Elle se DÉDUIT des outils
|
// leur nombre d'outils, pour situer le modèle. Elle se DÉDUIT des outils
|
||||||
// réellement envoyés ce tour-ci (EnabledTools), pas de l'état du pool.
|
// réellement envoyés ce tour-ci (EnabledTools), pas de l'état du pool.
|
||||||
@@ -463,21 +496,10 @@ func flattenMCPContent(res *mcpsdk.CallToolResult) string {
|
|||||||
func mcpPromptLine(tools []Tool) string {
|
func mcpPromptLine(tools []Tool) string {
|
||||||
counts := map[string]int{}
|
counts := map[string]int{}
|
||||||
for _, t := range tools {
|
for _, t := range tools {
|
||||||
if !strings.HasPrefix(t.Function.Name, "mcp__") {
|
if server, ok := mcpToolServer(t); ok {
|
||||||
continue
|
|
||||||
}
|
|
||||||
// mcpTools préfixe chaque description par « [MCP: <serveur>] » : le nom
|
|
||||||
// d'origine du serveur, que le nom d'outil (assaini) ne garde pas.
|
|
||||||
rest, ok := strings.CutPrefix(t.Function.Description, "[MCP: ")
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
server, _, ok := strings.Cut(rest, "] ")
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
counts[server]++
|
counts[server]++
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if len(counts) == 0 {
|
if len(counts) == 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
func mcpTestTool(server, tool string) Tool {
|
func mcpTestTool(server, tool string) Tool {
|
||||||
return Tool{Type: "function", Function: ToolFunction{
|
return Tool{Type: "function", Function: ToolFunction{
|
||||||
Name: mcpExposedName(server, tool),
|
Name: mcpExposedName(server, tool),
|
||||||
Description: "[MCP: " + server + "] décrit " + tool,
|
Description: mcpTagDescription(server, "décrit "+tool),
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,6 +32,10 @@ func TestMCPPromptLineDeriveDesOutils(t *testing.T) {
|
|||||||
""},
|
""},
|
||||||
{"description vide après le préfixe", []Tool{{Type: "function", Function: ToolFunction{Name: "mcp__x__y", Description: "[MCP: x] "}}},
|
{"description vide après le préfixe", []Tool{{Type: "function", Function: ToolFunction{Name: "mcp__x__y", Description: "[MCP: x] "}}},
|
||||||
"x (1)"},
|
"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 {
|
for _, c := range cases {
|
||||||
got := mcpPromptLine(c.tools)
|
got := mcpPromptLine(c.tools)
|
||||||
@@ -51,6 +55,10 @@ func TestMCPPromptLineDeriveDesOutils(t *testing.T) {
|
|||||||
// planner (aucun outil MCP) n'a pas de ligne MCP, le fil principal l'a dès le
|
// planner (aucun outil MCP) n'a pas de ligne MCP, le fil principal l'a dès le
|
||||||
// premier tour.
|
// premier tour.
|
||||||
func TestBaseSystemPromptLigneMCPSuitLesOutils(t *testing.T) {
|
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}
|
caps := Caps{Agent: true, Mem: MemOff}
|
||||||
with := append(EnabledTools(caps), mcpTestTool("github", "issues"))
|
with := append(EnabledTools(caps), mcpTestTool("github", "issues"))
|
||||||
if sp := baseSystemPrompt(caps, with); !strings.Contains(sp, "MCP servers connected") || !strings.Contains(sp, "github (1)") {
|
if sp := baseSystemPrompt(caps, with); !strings.Contains(sp, "MCP servers connected") || !strings.Contains(sp, "github (1)") {
|
||||||
|
|||||||
Reference in new issue
Block a user