Files
goclaw/internal/tools/tts_systemconfigs_fallback_test.go
Duc Nguyen e3dad3c60c fix(tts): unify voice resolution — dashboard settings affect tts tool (#1175)
The dashboard /tts page writes to system_configs[tts.<provider>.voice],
but the LLM-invoked tts tool was only checking args > agent OtherConfig
> builtin_tool_tenant_configs[tts].default_voice_id. Two different
storage locations → the user's chosen voice was ignored, Edge defaulted
to en-US-AriaNeural even when "HoaiMy" (vi-VN-HoaiMyNeural) was set
correctly in the dashboard.

Add system_configs as a 4th-level fallback so the dashboard becomes
the single source of truth.

- TtsTool gains SetSystemConfigStore(s) setter
- resolveVoiceAndModel takes providerName + looks up
  tts.<provider>.voice/model when no higher-precedence source set
- effectiveProvider resolved BEFORE voice/model so the right key is hit
- Wired at gateway boot in cmd/gateway.go (after pgStores ready)
- 4 unit tests covering: fallback, arg precedence, no-store, empty provider

Verified against production trace 019e6036-44bb-703b-85fa-dee34f7ab2c0
where the tts tool was called with provider=edge, no voice arg, and
defaulted to English instead of the configured Vietnamese voice.
2026-07-18 00:22:48 +07:00

77 lines
2.2 KiB
Go

package tools
import (
"context"
"testing"
)
type fakeSystemConfigStore struct {
data map[string]string
}
func (f *fakeSystemConfigStore) Get(_ context.Context, key string) (string, error) {
return f.data[key], nil
}
func (f *fakeSystemConfigStore) Set(_ context.Context, key, value string) error {
if f.data == nil {
f.data = map[string]string{}
}
f.data[key] = value
return nil
}
func (f *fakeSystemConfigStore) Delete(_ context.Context, key string) error {
delete(f.data, key)
return nil
}
func (f *fakeSystemConfigStore) List(_ context.Context) (map[string]string, error) {
return f.data, nil
}
func TestResolveVoiceAndModel_SystemConfigsFallback(t *testing.T) {
tool := NewTtsTool(nil)
sc := &fakeSystemConfigStore{data: map[string]string{
"tts.edge.voice": "vi-VN-HoaiMyNeural",
"tts.edge.model": "edge-tts-1",
}}
tool.SetSystemConfigStore(sc)
v, m, _ := tool.resolveVoiceAndModel(context.Background(), "edge", "", "")
if v != "vi-VN-HoaiMyNeural" {
t.Errorf("voice fallback failed: got %q, want vi-VN-HoaiMyNeural", v)
}
if m != "edge-tts-1" {
t.Errorf("model fallback failed: got %q, want edge-tts-1", m)
}
}
func TestResolveVoiceAndModel_ArgWinsOverSystemConfigs(t *testing.T) {
tool := NewTtsTool(nil)
tool.SetSystemConfigStore(&fakeSystemConfigStore{data: map[string]string{
"tts.edge.voice": "vi-VN-HoaiMyNeural",
}})
v, _, _ := tool.resolveVoiceAndModel(context.Background(), "edge", "en-US-AriaNeural", "")
if v != "en-US-AriaNeural" {
t.Errorf("arg voice must win over system_configs: got %q", v)
}
}
func TestResolveVoiceAndModel_NoStoreNoFallback(t *testing.T) {
tool := NewTtsTool(nil)
v, m, _ := tool.resolveVoiceAndModel(context.Background(), "edge", "", "")
if v != "" || m != "" {
t.Errorf("expected empty fallback when no system_configs wired, got voice=%q model=%q", v, m)
}
}
func TestResolveVoiceAndModel_EmptyProviderSkipsFallback(t *testing.T) {
tool := NewTtsTool(nil)
tool.SetSystemConfigStore(&fakeSystemConfigStore{data: map[string]string{
"tts..voice": "should-not-match",
}})
v, m, _ := tool.resolveVoiceAndModel(context.Background(), "", "", "")
if v != "" || m != "" {
t.Errorf("empty provider must skip lookup, got voice=%q model=%q", v, m)
}
}