Module: RubyLLM::Protocols::Deepgram::StreamingTranscription

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

Overview

:nodoc: all

Instance Method Summary collapse

Instance Method Details

#build_live_transcription(segments, metadata, model:, language:) ⇒ Object



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

def build_live_transcription(segments, , model:, language:)
  RubyLLM::Transcription.new(
    text: segments.map { |segment| segment.fetch('text') }.join(' '), model:, language:,
    duration: ['duration'], segments:, words: segments.flat_map { |segment| segment['words'] || [] }
  )
end

#process_transcription_result(event, segments) {|TranscriptionChunk.new(type: TranscriptionChunk::SEGMENT, delta:, segment:, raw: event)| ... } ⇒ Object

Yields:



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/ruby_llm/protocols/deepgram/streaming_transcription.rb', line 58

def process_transcription_result(event, segments)
  alternative = event.dig('channel', 'alternatives', 0) || {}
  text = alternative['transcript'].to_s
  return if text.empty?

  unless event['is_final']
    yield TranscriptionChunk.new(type: TranscriptionChunk::PARTIAL, text:, raw: event)
    return
  end

  delta = segments.empty? ? text : " #{text}"
  segment = {
    'text' => text,
    'start' => event['start'],
    'end' => event['start'].to_f + event['duration'].to_f,
    'channel' => event.dig('channel_index', 0),
    'words' => alternative['words']
  }.compact
  segments << segment
  yield TranscriptionChunk.new(type: TranscriptionChunk::SEGMENT, delta:, segment:, raw: event)
end

#send_transcription_audio(socket, audio) ⇒ Object



49
50
51
52
53
54
55
56
# File 'lib/ruby_llm/protocols/deepgram/streaming_transcription.rb', line 49

def send_transcription_audio(socket, audio)
  offset = 0
  while offset < audio.bytesize
    socket.send_binary(audio.byteslice(offset, 16_384))
    offset += 16_384
  end
  socket.send_text(JSON.generate(type: 'CloseStream'))
end

#stream_live_transcription(audio_file, model:, language:, speaker_names:, provider_options:, prompt:, &block) ⇒ Object

Raises:

  • (ArgumentError)


7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/ruby_llm/protocols/deepgram/streaming_transcription.rb', line 7

def stream_live_transcription(audio_file, model:, language:, speaker_names:, provider_options:, prompt:, &block)
  attachment = Attachment.wrap(audio_file, config: @config)
  raise ArgumentError, 'Transcription requires exactly one audio file' unless attachment.one?

  track_usage(:transcription) do
    url = streaming_transcription_url(model:, language:, speaker_names:, provider_options:, prompt:)
    segments = []
     = nil
    @usage_tracker.start
    Transport::WebsocketConnection.open(url, headers: @provider.headers, config: @config) do |socket|
      write = ->(connection) { send_transcription_audio(connection, attachment.first.content) }
      socket.each_message(write:) do |message|
        event = JSON.parse(message)
        case event['type']
        when 'Results'
          process_transcription_result(event, segments, &block)
        when 'Metadata'
           = event
        when 'Error'
          raise Error, event['description'] || event['message'] || 'Deepgram transcription failed'
        end
      end
    end
    raise Error, 'Deepgram transcription ended before its completion metadata' unless 

    result = build_live_transcription(segments, , model:, language:)
    block.call(TranscriptionChunk.new(type: TranscriptionChunk::DONE, text: result.text, raw: ))
    result
  end
end

#streaming_transcription_url(model:, language:, speaker_names:, provider_options:, prompt:) ⇒ Object



38
39
40
41
42
43
44
45
46
47
# File 'lib/ruby_llm/protocols/deepgram/streaming_transcription.rb', line 38

def streaming_transcription_url(model:, language:, speaker_names:, provider_options:, prompt:)
  params = { model:, language:, smart_format: true, interim_results: true }
  params[:diarize_model] = DIARIZE_MODEL if speaker_names
  params[:keyterm] = Array(prompt) if prompt
  params.merge!(provider_options)
  params = params.compact.flat_map { |key, value| Array(value).map { |item| [key, item] } }
  uri = URI.join("#{@provider.api_base.sub(%r{/+\z}, '')}/", "v1/listen?#{URI.encode_www_form(params)}")
  uri.scheme = uri.scheme == 'https' ? 'wss' : 'ws'
  uri.to_s
end