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

Constant Summary collapse

VERDICTS =

Finish reasons that are the provider's verdict on the content itself: a retry of the same input meets the same filter, and a harness that re-rolls against a policy verdict is machinery for evading it, so these fail fast.

%w[SAFETY RECITATION LANGUAGE BLOCKLIST PROHIBITED_CONTENT SPII
IMAGE_SAFETY IMAGE_PROHIBITED_CONTENT IMAGE_RECITATION].freeze
INPUT_ERRORS =

Missing replay state accuses the request rather than the generated content, but retrying the same history is equally deterministic.

%w[MISSING_THOUGHT_SIGNATURE].freeze
FUMBLES =

The model fumbled its own output (an invalid or runaway tool call, a missing image), or the API stopped for a reason it cannot name (OTHER is documented as "Unknown reason"). The input stands accused of nothing, so these error retryably; a regeneration usually lands.

%w[MALFORMED_FUNCTION_CALL UNEXPECTED_TOOL_CALL TOO_MANY_TOOL_CALLS
NO_IMAGE OTHER IMAGE_OTHER MALFORMED_RESPONSE
FINISH_REASON_UNSPECIFIED].freeze

Instance Method Summary collapse

Constructor Details

#initialize(model:, catalog_pricing: true, service_tier: nil) ⇒ Assembler

Returns a new instance of Assembler.



17
18
19
20
21
22
23
24
25
26
27
# File 'lib/mistri/providers/gemini/assembler.rb', line 17

def initialize(model:, catalog_pricing: true, service_tier: nil)
  @model = model
  @catalog_pricing = catalog_pricing
  @service_tier = service_tier
  @pricing_at = Time.now
  @blocks = []
  @current = nil
  @usage = Usage.new
  @finish_reason = nil
  @terminal = false
end

Instance Method Details

#abortObject



106
107
108
109
110
# File 'lib/mistri/providers/gemini/assembler.rb', line 106

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

#fail_stream(reason, usage_known: false) ⇒ Object



112
113
114
115
116
117
118
119
120
121
# File 'lib/mistri/providers/gemini/assembler.rb', line 112

def fail_stream(reason, usage_known: false, &)
  close_current(&)
  invalidate_cost unless usage_known
  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



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

def feed(record, &)
  return after_terminal(record, &) if @terminal
  return record_error(record["error"]) if record["error"]

  fold_record(record, &)
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.



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/mistri/providers/gemini/assembler.rb', line 85

def finish(&emit)
  return fail_stream(@error, &emit) if @error
  if (refused = blocked)
    return fail_stream(refused, usage_known: @usage_authoritative, &emit)
  end
  return fail_stream("stream ended without a finish reason", &emit) unless @finish_reason

  if @blocks.any?(ToolCall) && !%w[STOP MAX_TOKENS].include?(@finish_reason)
    invalidate_tool_calls
    reason = ProviderError.new("generation ended before its tool calls were confirmed: " \
                               "#{@finish_reason}")
    return fail_stream(reason, usage_known: @usage_authoritative, &emit)
  end

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

#messageObject



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

def message = @message ||= finish

#start(&emit) ⇒ Object



46
47
48
# File 'lib/mistri/providers/gemini/assembler.rb', line 46

def start(&emit)
  emit&.call(Event.new(type: :start, partial: assemble))
end