Module: RubyLLM::Protocols::Deepgram::Speech

Defined in:
lib/ruby_llm/protocols/deepgram/speech.rb

Overview

Speech dialect for the Deepgram text-to-speech API. Deepgram names the voice inside the model id, so aura-2-thalia-en is the Thalia voice of Aura 2: asking for a voice: swaps that segment of the model id. The container is an encoding query value, the body carries only the text, and the response is raw audio bytes.

Constant Summary collapse

DEFAULT_FORMAT =
'mp3'
OUTPUT_FORMATS =

RubyLLM format names in Deepgram's encoding vocabulary. Deepgram wraps linear16 in a WAV header unless the container is turned off, and the compressed encodings carry their own.

{
  'aac' => { encoding: 'aac' },
  'alaw' => { encoding: 'alaw', container: 'wav' },
  'flac' => { encoding: 'flac' },
  'mp3' => { encoding: 'mp3' },
  'mulaw' => { encoding: 'mulaw', container: 'wav' },
  'opus' => { encoding: 'opus' },
  'pcm' => { encoding: 'linear16', container: 'none' },
  'wav' => { encoding: 'linear16', container: 'wav' }
}.freeze

Instance Method Summary collapse

Instance Method Details

#format_for(format) ⇒ Object



87
88
89
# File 'lib/ruby_llm/protocols/deepgram/speech.rb', line 87

def format_for(format)
  (format || DEFAULT_FORMAT).to_s
end

#parse_speech_response(response, model:, voice:, format:) ⇒ Object



59
60
61
62
63
64
65
66
# File 'lib/ruby_llm/protocols/deepgram/speech.rb', line 59

def parse_speech_response(response, model:, voice:, format:)
  RubyLLM::Speech.new(
    data: response.body,
    model: model,
    voice: voice,
    format: format_for(format)
  )
end

#render_speech_payload(input, model:, voice: nil, format: nil, provider_options: {}) ⇒ Object

rubocop:disable-next Lint/UnusedMethodArgument



55
56
57
# File 'lib/ruby_llm/protocols/deepgram/speech.rb', line 55

def render_speech_payload(input, model:, voice: nil, format: nil, provider_options: {})
  { text: input }
end

#speak(input, model:, voice:, format:, provider_options: {}, &block) ⇒ Object



30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/ruby_llm/protocols/deepgram/speech.rb', line 30

def speak(input, model:, voice:, format:, provider_options: {}, &block)
  track_usage(:speech) do
    spoken_model = speech_model_for(model, voice)
    payload = render_speech_payload(input, model: spoken_model, voice:, format:)
    url = speech_url(model: spoken_model, format:, provider_options:)
    if block
      next stream_speech_response(url, payload, model: spoken_model, voice: voice_for(spoken_model),
                                                format:, &block)
    end

    response = @connection.post url, payload, usage: @usage_tracker
    parse_speech_response(response, model: spoken_model, voice: voice_for(spoken_model), format:)
  end
end

#speech_model_for(model, voice) ⇒ Object

Puts voice into the voice segment of model, so Aura 2 with voice 'zeus' is aura-2-zeus-en. A voice that already names a whole model is used as it stands.



71
72
73
74
75
76
77
78
# File 'lib/ruby_llm/protocols/deepgram/speech.rb', line 71

def speech_model_for(model, voice)
  return model unless voice

  segments = model.to_s.split('-')
  return voice if voice.include?('-') || segments.size < 3

  (segments[0..-3] + [voice, segments[-1]]).join('-')
end

#speech_params(model:, format: nil, provider_options: {}) ⇒ Object



49
50
51
52
# File 'lib/ruby_llm/protocols/deepgram/speech.rb', line 49

def speech_params(model:, format: nil, provider_options: {})
  { model: model }.merge(OUTPUT_FORMATS.fetch(format_for(format), encoding: format_for(format)))
                  .merge(provider_options).compact
end

#speech_url(model:, format: nil, provider_options: {}) ⇒ Object



45
46
47
# File 'lib/ruby_llm/protocols/deepgram/speech.rb', line 45

def speech_url(model:, format: nil, provider_options: {})
  "v1/speak?#{URI.encode_www_form(speech_params(model:, format:, provider_options:))}"
end

#voice_for(model) ⇒ Object

The voice segment of a Deepgram model id, so aura-2-thalia-en speaks as 'thalia'.



82
83
84
85
# File 'lib/ruby_llm/protocols/deepgram/speech.rb', line 82

def voice_for(model)
  segments = model.to_s.split('-')
  segments[-2] if segments.size >= 3
end