feat(voice): add audio-model transcription support

- Add `AudioModelTranscriber` for model-based audio transcription via LLM providers
- Support selecting a transcription model with `voice.model_name` in config
- Keep Groq transcription as a fallback and move it into dedicated files with focused tests
- Serialize `data:audio/...` media as input_audio for OpenAI-compatible providers
- Improve transcription logging by rendering error fields as strings
- Add coverage for transcriber detection, audio-model behavior, provider audio serialization, and Groq transcription

Fixes #1890.
This commit is contained in:
RussellLuo
2026-03-22 19:51:32 +08:00
parent dd82794255
commit 8ad4b9b497
12 changed files with 693 additions and 226 deletions
+2
View File
@@ -256,6 +256,8 @@ func appendFields(event *zerolog.Event, fields map[string]any) {
for k, v := range fields {
// Type switch to avoid double JSON serialization of strings
switch val := v.(type) {
case error:
event.Str(k, val.Error())
case string:
event.Str(k, val)
case int:
+28
View File
@@ -1,7 +1,12 @@
package logger
import (
"bytes"
"encoding/json"
"errors"
"testing"
"github.com/rs/zerolog"
)
func TestLogLevelFiltering(t *testing.T) {
@@ -337,3 +342,26 @@ func TestSetLevelFromString(t *testing.T) {
t.Errorf("after SetLevelFromString(\"FATAL\"): GetLevel() = %v, want FATAL", got)
}
}
func TestAppendFields_ErrorUsesErrorString(t *testing.T) {
var buf bytes.Buffer
l := zerolog.New(&buf)
event := l.Info()
appendFields(event, map[string]any{"error": errors.New("transcription request failed")})
event.Msg("test")
lines := bytes.Split(bytes.TrimSpace(buf.Bytes()), []byte("\n"))
if len(lines) == 0 {
t.Fatal("expected log output, got none")
}
var got map[string]any
if err := json.Unmarshal(lines[0], &got); err != nil {
t.Fatalf("unmarshal log line: %v", err)
}
if got["error"] != "transcription request failed" {
t.Fatalf("error field = %#v, want %q", got["error"], "transcription request failed")
}
}