e7ecce2859
Three tangled defects, fixed together because each one hid the others. DefaultActMatcher matched an exact English prefix and internal/tool.Matcher delegated straight to it, so no Russian utterance could reach a tool: 55 of the 69 lines in models/seeds/act.txt routed to IntentAct and fell to proposeGap. Tools now carry spoken aliases from deploy/mavend.json, matched as exact leading tokens, longest phrase first. Config data, not a stem pattern in code. The comment claiming "the production matcher is fuzzy" was false and is gone. Seven lines were exact duplicates inside models/seeds/query.txt, each one a second identical vector double-weighting its region. "как дела у сервера" carried both a query and a system label. It leaves system.txt, because replySystem's stats arm answers "системная статистика пока не подключена." and always did. The mode inventory records that shape as act.tool.hoststats rather than a system mode. Fixture unchanged at 69/91, and it cannot see any of this: no host-stat case and no Russian act in it. TestActMatcherAliases is the coverage. docs/evals/2026-08-06-russian-acts-reach-tools.md has the numbers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117tgnmbgZpHVV3XSNw8Qua
73 lines
3.0 KiB
Go
73 lines
3.0 KiB
Go
package router
|
|
|
|
import "testing"
|
|
|
|
// Before V-633 the matcher only ever matched the English tool name, so no
|
|
// Russian utterance could reach a tool: 55 of the 69 lines in models/seeds/act.txt
|
|
// routed to IntentAct and then fell to proposeGap. These are those lines.
|
|
func TestActMatcherAliases(t *testing.T) {
|
|
m := DefaultActMatcher{
|
|
Fns: []string{"status", "ps", "uptime", "disk", "memory", "logs",
|
|
"restart", "stop", "start", "docker-restart", "docker-stop", "reboot"},
|
|
Aliases: map[string][]string{
|
|
"status": {"статус", "покажи статус"},
|
|
"ps": {"статус докера", "что запущено"},
|
|
"uptime": {"покажи uptime", "как работает сервер"},
|
|
"disk": {"сколько места на диске"},
|
|
"memory": {"свободная память"},
|
|
"logs": {"покажи логи", "логи"},
|
|
"restart": {"перезагрузи", "перезапусти"},
|
|
"docker-restart": {"перезагрузи контейнер"},
|
|
"reboot": {"перезагрузи сервер"},
|
|
},
|
|
}
|
|
cases := []struct {
|
|
utterance string
|
|
wantFn string
|
|
wantArgs []string
|
|
}{
|
|
{"покажи статус nginx", "status", []string{"nginx"}},
|
|
{"статус sshd", "status", []string{"sshd"}},
|
|
{"статус докера", "ps", nil},
|
|
{"что запущено", "ps", nil},
|
|
{"сколько места на диске", "disk", nil},
|
|
{"свободная память", "memory", nil},
|
|
{"покажи uptime", "uptime", nil},
|
|
{"логи nginx", "logs", []string{"nginx"}},
|
|
{"перезагрузи nginx", "restart", []string{"nginx"}},
|
|
// Longest phrase first, so the two-word alias wins over the one word
|
|
// inside it and the act reaches the right tool.
|
|
{"перезагрузи контейнер maven", "docker-restart", []string{"maven"}},
|
|
{"перезагрузи сервер", "reboot", nil},
|
|
// The English names still match, unchanged.
|
|
{"restart nginx", "restart", []string{"nginx"}},
|
|
{"uptime", "uptime", nil},
|
|
}
|
|
for _, c := range cases {
|
|
fn, args, ok := m.Match(c.utterance)
|
|
if !ok || fn != c.wantFn {
|
|
t.Errorf("%q: got fn=%q ok=%v, want %q", c.utterance, fn, ok, c.wantFn)
|
|
continue
|
|
}
|
|
if len(args) != len(c.wantArgs) {
|
|
t.Errorf("%q: got args=%v, want %v", c.utterance, args, c.wantArgs)
|
|
continue
|
|
}
|
|
for i := range args {
|
|
if args[i] != c.wantArgs[i] {
|
|
t.Errorf("%q: got args=%v, want %v", c.utterance, args, c.wantArgs)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
// Past tense is a fact, not a command, and aliases match exact tokens so it
|
|
// stays one. This is the trap cmd/mavend/quiet_toggle.go documents.
|
|
if fn, _, ok := m.Match("перезагрузил роутер"); ok {
|
|
t.Errorf("past tense reached a tool: fn=%q", fn)
|
|
}
|
|
// A phrase nobody configured still refuses, so the router can clarify.
|
|
if fn, _, ok := m.Match("свари кофе"); ok {
|
|
t.Errorf("unconfigured phrase reached a tool: fn=%q", fn)
|
|
}
|
|
}
|