Class: LlmLogs::Batch

Inherits:
ApplicationRecord show all
Defined in:
app/models/llm_logs/batch.rb,
app/jobs/llm_logs/batch/poll_job.rb,
app/jobs/llm_logs/batch/flush_job.rb,
app/models/llm_logs/batch/submitter.rb,
app/models/llm_logs/batch/reconciler.rb,
app/models/llm_logs/batch/schema_format.rb,
app/models/llm_logs/batch/trace_recorder.rb,
app/models/llm_logs/batch/adapters/bedrock.rb,
app/models/llm_logs/batch/handler_registry.rb,
app/models/llm_logs/batch/adapters/openai_responses.rb

Defined Under Namespace

Modules: Adapters, HandlerRegistry, SchemaFormat, TraceRecorder Classes: FlushJob, PollJob, Reconciler, Submitter

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.adapter_for(provider) ⇒ Object



46
47
48
49
50
# File 'app/models/llm_logs/batch.rb', line 46

def self.adapter_for(provider)
  LlmLogs.batch_adapters.fetch(provider.to_sym) do
    raise ArgumentError, "no batch adapter registered for provider #{provider.inspect}"
  end
end

.batch_provider_for(model) ⇒ Object

Which batch provider (if any) serves this model. Bedrock wins for Claude models when the Bedrock adapter is configured; otherwise fall back to the OpenAI provider when the model resolves there; otherwise nil (run synchronously).



61
62
63
64
65
66
# File 'app/models/llm_logs/batch.rb', line 61

def self.batch_provider_for(model)
  return :bedrock if bedrock_serves?(model)
  return :openai_responses if openai_serves?(model)

  nil
end

.batchable?(model) ⇒ Boolean

Returns:

  • (Boolean)


52
53
54
55
56
# File 'app/models/llm_logs/batch.rb', line 52

def self.batchable?(model)
  return false unless LlmLogs.batch_enabled?

  !batch_provider_for(model).nil?
end

.bedrock_serves?(model) ⇒ Boolean

Returns:

  • (Boolean)


73
74
75
76
77
78
79
80
# File 'app/models/llm_logs/batch.rb', line 73

def self.bedrock_serves?(model)
  config = LlmLogs.bedrock_batch
  return false if config.nil?
  return false unless LlmLogs.batch_adapters.key?(:bedrock)

  matcher = config.model_matcher
  matcher.respond_to?(:call) ? matcher.call(model.to_s) : matcher.match?(model.to_s)
end

.enqueue(purpose:, model:, input:, instructions:, schema:, routing:, temperature: nil, reasoning_effort: nil) ⇒ Object



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'app/models/llm_logs/batch.rb', line 21

def self.enqueue(purpose:, model:, input:, instructions:, schema:, routing:, temperature: nil, reasoning_effort: nil)
  BatchRequest.create!(
    purpose: purpose,
    model: model,
    status: :pending,
    custom_id: "req_#{SecureRandom.hex(8)}",
    routing: routing,
    payload: {
      "input" => input,
      "instructions" => instructions,
      "schema" => schema,
      "temperature" => temperature,
      "reasoning_effort" => reasoning_effort
    }.compact
  )
end

.min_records_for(model) ⇒ Object

The Bedrock minimum records-per-job floor for this model (0 when Bedrock does not serve it).



69
70
71
# File 'app/models/llm_logs/batch.rb', line 69

def self.min_records_for(model)
  batch_provider_for(model) == :bedrock ? LlmLogs.bedrock_batch.min_records.to_i : 0
end

.openai_serves?(model) ⇒ Boolean

Returns:

  • (Boolean)


82
83
84
85
86
# File 'app/models/llm_logs/batch.rb', line 82

def self.openai_serves?(model)
  return false unless defined?(RubyLLM::Providers::OpenAIResponses)

  servable_by_batch_provider?(model)
end

.servable_by_batch_provider?(model) ⇒ Boolean

The batch path submits via RubyLLM.batch(provider: batch_provider). A model is only batchable if that provider can actually serve it -- i.e. the model resolves under batch_provider. Models that belong to a different provider (e.g. Bedrock / Anthropic) don't resolve there, so they return false and the caller runs them synchronously instead of enqueueing work that would fail at submit time.

Returns:

  • (Boolean)


93
94
95
96
97
98
99
100
# File 'app/models/llm_logs/batch.rb', line 93

def self.servable_by_batch_provider?(model)
  RubyLLM::Models.resolve(
    model, provider: LlmLogs.batch_provider, assume_exists: false, config: RubyLLM.config
  )
  true
rescue RubyLLM::ModelNotFoundError
  false
end

.submit_pending(purpose:, model:, metadata: {}) ⇒ Object



38
39
40
# File 'app/models/llm_logs/batch.rb', line 38

def self.submit_pending(purpose:, model:, metadata: {})
  Submitter.new(purpose: purpose, model: model, metadata: ).call
end

Instance Method Details

#reconcile!Object



42
43
44
# File 'app/models/llm_logs/batch.rb', line 42

def reconcile!
  Reconciler.new(self).call
end