Files

87 lines
3.1 KiB
Go

package pipeline
import (
"context"
"testing"
"time"
"ai-operator/internal/ai"
"ai-operator/internal/config"
"ai-operator/internal/media"
)
func TestTextChunker(t *testing.T) {
c := NewTextChunker(10, 80, 20, time.Millisecond, true)
chunks := c.Add("Первое предложение. Второе", false)
if len(chunks) != 1 || chunks[0] != "Первое предложение." {
t.Fatalf("chunks=%v", chunks)
}
flush := c.Flush()
if len(flush) != 1 || flush[0] != "Второе" {
t.Fatalf("flush=%v", flush)
}
}
func TestNaturalizer(t *testing.T) {
cfg := config.NaturalnessConfig{Enabled: true, AudioTagsEnabled: true, AllowNonverbalTags: true, MaxAudioTagsPerResponse: 2}
got := NaturalizeForVoice("Здравствуйте, меня зовут Жанна. Чем могу помочь?", "ru", cfg)
if got == "" || got == "Здравствуйте, меня зовут Жанна. Чем могу помочь?" {
t.Fatalf("not naturalized: %q", got)
}
if stripped := RemoveAudioTags(got); stripped == "" || stripped == got {
t.Fatalf("tags not stripped: %q", stripped)
}
cfg.AllowCough = false
if got := NaturalizeForVoice("Первичное подключение газа бесплатно.", "ru", cfg); containsRune(got, "cough") {
t.Fatalf("cough added unexpectedly: %q", got)
}
}
func TestStreamingProviderFakeEndToEnd(t *testing.T) {
cfg := config.Config{
STT: config.STTConfig{Model: "scribe_realtime_v2", SampleRate: 16000, InputFormat: "pcm_16000"},
LLM: config.LLMConfig{StreamChunkMinChars: 10, StreamChunkMaxChars: 160, FirstChunkTimeoutMS: 1, MaxOutputTokens: 100, Temperature: 0.2},
Eleven: config.ElevenLabsConfig{VoiceIDRU: "voice", TTSModelID: "eleven_flash_v2_5", TTSOutputFormat: "pcm_16000", TTSSampleRate: 16000},
Natural: config.NaturalnessConfig{Enabled: false},
Pipeline: config.PipelineConfig{InitialGreeting: false, BargeIn: true, MaxTurnSeconds: 2, TTSStartAfterChars: 20, TTSStartAfterPunctuation: true},
}
factory := &FakeTTSFactory{}
p := NewStreamingProviderWithDeps(cfg, nil, NewFakeSTT(), FakeLLM{}, factory.New)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
if err := p.StartSession(ctx, ai.VoiceSessionConfig{CallID: "c1", SystemPrompt: "prompt"}); err != nil {
t.Fatal(err)
}
if err := p.SendAudio(ctx, media.AudioChunk{Data: []byte{0, 0}}); err != nil {
t.Fatal(err)
}
tool := false
audio := false
for !audio {
select {
case ev := <-p.Events():
if ev.Type == ai.VoiceEventToolCall {
tool = true
_ = p.SendToolResult(ctx, ai.ToolResult{CallID: ev.CallID, ToolCallID: ev.ToolCall.ID, Result: map[string]any{"answer_text": "Первичное подключение бесплатно."}})
}
if ev.Type == ai.VoiceEventAssistantAudioDelta {
audio = true
}
case <-ctx.Done():
t.Fatal("timeout")
}
}
if !tool || !audio || len(factory.Texts) == 0 {
t.Fatalf("tool=%t audio=%t texts=%v", tool, audio, factory.Texts)
}
}
func containsRune(s, sub string) bool {
for i := 0; i+len(sub) <= len(s); i++ {
if s[i:i+len(sub)] == sub {
return true
}
}
return false
}