Files
viettranx ee57ec0fdc feat(tts): expand per-provider params + validation + golden fixtures
Phase 1 of the TTS params/layout/agent-override plan.

Capability schema:
- `ParamSchema.Group` field (`"basic"` default, `"advanced"` when set).
- Tag existing advanced params across openai/elevenlabs/minimax/gemini.

Gemini:
- Expose `temperature` (basic, 0.0–2.0, default 1.0, subtle-effect note),
  `seed`, `presencePenalty`, `frequencyPenalty` (advanced, experimental).
- Merge into `generationConfig` via explicit-presence resolvers so
  nil-params bodies stay byte-equivalent.

ElevenLabs:
- `output_format` enum (27 variants, default `mp3_44100_128`, advanced).
- `FormatMeta` lookup drives SynthResult MIME + extension.
- URL built via `net/url.Values.Encode()`; regex pre-validation for
  `output_format` (`^[a-z0-9_]+$`) and `language_code`
  (`^[a-z]{2,3}(-[A-Z]{2})?$`) blocks query-string injection.
- Telegram opus contract preserved: `opts.Format=="opus"` forces
  `audio/ogg; codecs=opus` regardless of user-set `output_format`.

MiniMax:
- `language_boost` (basic enum), `subtitle_enable` (basic bool),
  `pronunciation_dict` (advanced text, 8KB cap, wrapped as
  `{"tone":[...]}`). Parse failure logs length only + omits.

Validation:
- `audio.ValidateParams` enforces Min/Max/Enum + rejects unknown keys.
- Wired into `/v1/tts/synthesize` and `/v1/tts/config` write paths.
- `loadParamsBlob` capped at 16KB.
- i18n keys `MsgTtsParamOutOfRange`, `MsgTtsParamInvalidJSON`,
  `MsgTtsParamUnknownKey` added to en/vi/zh catalogs.

Tests:
- Golden `testdata/default_body.golden.json` per provider (gemini,
  elevenlabs, minimax, openai) checked in; invariant tests diff
  against file instead of self-referential capture.
- Round-trip tests for each new param + Telegram opus contract +
  URL-injection attempts.
2026-04-20 00:20:50 +07:00

83 lines
2.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package gemini
import "github.com/nextlevelbuilder/goclaw/internal/audio"
var (
geminiTempMin = 0.0
geminiTempMax = 2.0
geminiTempStep = 0.01
geminiPenaltyMin = -2.0
geminiPenaltyMax = 2.0
geminiPenaltyStep = 0.01
)
// geminiParams is the enriched param schema for Gemini TTS.
// All params have nil Default so they are omitted from the request body
// when not explicitly set, preserving characterization byte-equivalence.
//
// Tagging:
// - temperature: basic (Group="")
// - seed, presencePenalty, frequencyPenalty: advanced (Group="advanced")
var geminiParams = []audio.ParamSchema{
{
Key: "temperature",
Type: audio.ParamTypeRange,
Label: "Temperature",
// Finding #14: note subtle effect on TTS; primary expressiveness via audio tags.
Description: "Sampling temperature (0.0–2.0, default 1.0). Effect is subtle on TTS — primary expressiveness comes from audio tags inserted in the text.",
Default: nil, // omit from body when not set; API default is 1.0
Min: &geminiTempMin,
Max: &geminiTempMax,
Step: &geminiTempStep,
Group: "", // basic
},
{
Key: "seed",
Type: audio.ParamTypeInteger,
Label: "Seed",
Description: "Deterministic seed for reproducible output. Advanced.",
Default: nil,
Group: "advanced",
},
{
Key: "presencePenalty",
Type: audio.ParamTypeRange,
Label: "Presence Penalty",
Description: "Presence penalty (-2.0–2.0). Experimental — effect on TTS is not well-characterised. Advanced.",
Default: nil,
Min: &geminiPenaltyMin,
Max: &geminiPenaltyMax,
Step: &geminiPenaltyStep,
Group: "advanced",
},
{
Key: "frequencyPenalty",
Type: audio.ParamTypeRange,
Label: "Frequency Penalty",
Description: "Frequency penalty (-2.0–2.0). Experimental — effect on TTS is not well-characterised. Advanced.",
Default: nil,
Min: &geminiPenaltyMin,
Max: &geminiPenaltyMax,
Step: &geminiPenaltyStep,
Group: "advanced",
},
}
// Capabilities returns the static capability schema for the Gemini TTS provider.
// CustomFeatures drives frontend custom slot rendering in Phase C.
func (p *Provider) Capabilities() audio.ProviderCapabilities {
return audio.ProviderCapabilities{
Provider: "gemini",
DisplayName: "Google Gemini TTS",
RequiresAPIKey: true,
Models: geminiModels,
Voices: geminiVoices,
Params: geminiParams,
CustomFeatures: map[string]any{
"multi_speaker": true,
"audio_tags": true,
},
}
}