Class: Mistri::Providers::Gemini::Assembler

Inherits:
Object
  • Object
show all
Defined in:
lib/mistri/providers/gemini/assembler.rb

Overview

Folds streamGenerateContent records into the event union. Each record carries delta parts: plain text extends a text block, thought parts a thinking block, and a functionCall arrives whole, so its three events emit back to back. A kind switch closes the open block.

Thought signatures ride on individual parts and are captured onto the block they arrived with, verbatim, for replay.

Defined Under Namespace

Classes: Builder

Instance Method Summary collapse

Constructor Details

#initialize(model:) ⇒ Assembler

Returns a new instance of Assembler.



16
17
18
19
20
21
22
# File 'lib/mistri/providers/gemini/assembler.rb', line 16

def initialize(model:)
  @model = model
  @blocks = []
  @current = nil
  @usage = Usage.zero
  @finish_reason = nil
end

Instance Method Details

#abortObject



49
50
51
52
# File 'lib/mistri/providers/gemini/assembler.rb', line 49

def abort(&)
  close_current
  terminal(StopReason::ABORTED, "aborted", &)
end

#fail_stream(reason) ⇒ Object



54
55
56
57
58
59
60
61
62
# File 'lib/mistri/providers/gemini/assembler.rb', line 54

def fail_stream(reason, &)
  close_current
  text = case reason
         when ProviderError then "#{reason.class}: #{reason.describe}"
         when Exception then "#{reason.class}: #{reason.message}"
         else reason.to_s
         end
  terminal(StopReason::ERROR, text, error: ErrorData.for(reason), &)
end

#feed(record) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/mistri/providers/gemini/assembler.rb', line 24

def feed(record, &)
  if (error = record["error"])
    @error = ProviderError.new(error["message"] || "provider error",
                               status: error["code"])
    return
  end

  candidate = record.dig("candidates", 0) || {}
  Array(candidate.dig("content", "parts")).each { |part| fold_part(part, &) }
  @finish_reason = candidate["finishReason"] if candidate["finishReason"]
  @usage = parse_usage(record["usageMetadata"]) if record["usageMetadata"]
end

#finish(&emit) ⇒ Object

A stream that ended without a finishReason was truncated, not cancelled: fail it so the loop can treat it as retryable.



39
40
41
42
43
44
45
46
47
# File 'lib/mistri/providers/gemini/assembler.rb', line 39

def finish(&emit)
  return fail_stream(@error, &emit) if @error
  return fail_stream("stream ended without a finish reason", &emit) unless @finish_reason

  close_current(&emit)
  @message = assemble(stop_reason: stop_reason)
  emit&.call(Event.new(type: :done, reason: @message.stop_reason, message: @message))
  @message
end

#messageObject



64
# File 'lib/mistri/providers/gemini/assembler.rb', line 64

def message = @message ||= finish