ruby_llm-agents 3.1.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -0
- data/app/controllers/ruby_llm/agents/agents_controller.rb +16 -14
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +20 -20
- data/app/controllers/ruby_llm/agents/executions_controller.rb +5 -7
- data/app/helpers/ruby_llm/agents/application_helper.rb +57 -58
- data/app/models/ruby_llm/agents/execution/analytics.rb +27 -27
- data/app/models/ruby_llm/agents/execution/scopes.rb +4 -6
- data/app/models/ruby_llm/agents/execution.rb +25 -25
- data/app/models/ruby_llm/agents/tenant/budgetable.rb +16 -10
- data/app/models/ruby_llm/agents/tenant/resettable.rb +12 -12
- data/app/models/ruby_llm/agents/tenant/trackable.rb +7 -7
- data/app/services/ruby_llm/agents/agent_registry.rb +6 -6
- data/app/views/ruby_llm/agents/executions/_audio_player.html.erb +57 -0
- data/app/views/ruby_llm/agents/executions/show.html.erb +8 -0
- data/lib/generators/ruby_llm_agents/agent_generator.rb +4 -4
- data/lib/generators/ruby_llm_agents/background_remover_generator.rb +6 -6
- data/lib/generators/ruby_llm_agents/embedder_generator.rb +4 -4
- data/lib/generators/ruby_llm_agents/image_analyzer_generator.rb +7 -7
- data/lib/generators/ruby_llm_agents/image_editor_generator.rb +4 -4
- data/lib/generators/ruby_llm_agents/image_generator_generator.rb +6 -6
- data/lib/generators/ruby_llm_agents/image_pipeline_generator.rb +9 -9
- data/lib/generators/ruby_llm_agents/image_transformer_generator.rb +6 -6
- data/lib/generators/ruby_llm_agents/image_upscaler_generator.rb +4 -4
- data/lib/generators/ruby_llm_agents/image_variator_generator.rb +4 -4
- data/lib/generators/ruby_llm_agents/install_generator.rb +3 -3
- data/lib/generators/ruby_llm_agents/migrate_structure_generator.rb +4 -4
- data/lib/generators/ruby_llm_agents/multi_tenancy_generator.rb +2 -2
- data/lib/generators/ruby_llm_agents/restructure_generator.rb +13 -13
- data/lib/generators/ruby_llm_agents/speaker_generator.rb +6 -6
- data/lib/generators/ruby_llm_agents/transcriber_generator.rb +4 -4
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +2 -2
- data/lib/ruby_llm/agents/audio/speaker/active_storage_support.rb +87 -0
- data/lib/ruby_llm/agents/audio/speaker.rb +50 -31
- data/lib/ruby_llm/agents/audio/speech_client.rb +328 -0
- data/lib/ruby_llm/agents/audio/speech_pricing.rb +273 -0
- data/lib/ruby_llm/agents/audio/transcriber.rb +43 -33
- data/lib/ruby_llm/agents/base_agent.rb +14 -14
- data/lib/ruby_llm/agents/core/base/callbacks.rb +3 -3
- data/lib/ruby_llm/agents/core/configuration.rb +90 -73
- data/lib/ruby_llm/agents/core/errors.rb +27 -2
- data/lib/ruby_llm/agents/core/instrumentation.rb +64 -66
- data/lib/ruby_llm/agents/core/llm_tenant.rb +7 -7
- data/lib/ruby_llm/agents/core/version.rb +1 -1
- data/lib/ruby_llm/agents/dsl/base.rb +3 -3
- data/lib/ruby_llm/agents/dsl/reliability.rb +9 -9
- data/lib/ruby_llm/agents/image/analyzer/dsl.rb +1 -1
- data/lib/ruby_llm/agents/image/analyzer/execution.rb +4 -4
- data/lib/ruby_llm/agents/image/background_remover/dsl.rb +1 -1
- data/lib/ruby_llm/agents/image/background_remover/execution.rb +3 -3
- data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +8 -8
- data/lib/ruby_llm/agents/image/editor/execution.rb +1 -1
- data/lib/ruby_llm/agents/image/generator/pricing.rb +9 -10
- data/lib/ruby_llm/agents/image/generator.rb +6 -6
- data/lib/ruby_llm/agents/image/pipeline/dsl.rb +6 -6
- data/lib/ruby_llm/agents/image/pipeline/execution.rb +9 -9
- data/lib/ruby_llm/agents/image/pipeline.rb +1 -1
- data/lib/ruby_llm/agents/image/transformer/execution.rb +1 -1
- data/lib/ruby_llm/agents/image/upscaler/dsl.rb +1 -1
- data/lib/ruby_llm/agents/image/upscaler/execution.rb +3 -5
- data/lib/ruby_llm/agents/image/variator/execution.rb +1 -1
- data/lib/ruby_llm/agents/infrastructure/alert_manager.rb +4 -4
- data/lib/ruby_llm/agents/infrastructure/attempt_tracker.rb +4 -4
- data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +9 -9
- data/lib/ruby_llm/agents/infrastructure/budget/config_resolver.rb +3 -3
- data/lib/ruby_llm/agents/infrastructure/budget/forecaster.rb +1 -1
- data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +17 -17
- data/lib/ruby_llm/agents/infrastructure/circuit_breaker.rb +1 -0
- data/lib/ruby_llm/agents/infrastructure/execution_logger_job.rb +1 -1
- data/lib/ruby_llm/agents/infrastructure/reliability.rb +6 -6
- data/lib/ruby_llm/agents/pipeline/builder.rb +11 -11
- data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +3 -3
- data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +4 -4
- data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +83 -22
- data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +2 -3
- data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +7 -7
- data/lib/ruby_llm/agents/results/background_removal_result.rb +6 -6
- data/lib/ruby_llm/agents/results/embedding_result.rb +15 -15
- data/lib/ruby_llm/agents/results/image_analysis_result.rb +7 -7
- data/lib/ruby_llm/agents/results/image_edit_result.rb +4 -4
- data/lib/ruby_llm/agents/results/image_generation_result.rb +5 -5
- data/lib/ruby_llm/agents/results/image_pipeline_result.rb +4 -4
- data/lib/ruby_llm/agents/results/image_transform_result.rb +4 -4
- data/lib/ruby_llm/agents/results/image_upscale_result.rb +5 -5
- data/lib/ruby_llm/agents/results/image_variation_result.rb +4 -4
- data/lib/ruby_llm/agents/results/speech_result.rb +12 -7
- data/lib/ruby_llm/agents/results/transcription_result.rb +1 -1
- data/lib/ruby_llm/agents/text/embedder.rb +13 -13
- metadata +5 -1
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module RubyLLM
|
|
7
|
+
module Agents
|
|
8
|
+
module Audio
|
|
9
|
+
# Dynamic pricing resolution for text-to-speech models.
|
|
10
|
+
#
|
|
11
|
+
# Uses the same three-tier strategy as ImageGenerator::Pricing:
|
|
12
|
+
# 1. LiteLLM JSON (primary) - future-proof, auto-updating
|
|
13
|
+
# 2. Configurable pricing table - user overrides via config.tts_model_pricing
|
|
14
|
+
# 3. Hardcoded fallbacks - per-model defaults
|
|
15
|
+
#
|
|
16
|
+
# All prices are per 1,000 characters.
|
|
17
|
+
#
|
|
18
|
+
# @example Get cost for a speech operation
|
|
19
|
+
# SpeechPricing.calculate_cost(provider: :openai, model_id: "tts-1", characters: 5000)
|
|
20
|
+
# # => 0.075
|
|
21
|
+
#
|
|
22
|
+
# @example User-configured pricing
|
|
23
|
+
# RubyLLM::Agents.configure do |c|
|
|
24
|
+
# c.tts_model_pricing = {
|
|
25
|
+
# "eleven_v3" => 0.24,
|
|
26
|
+
# "tts-1" => 0.015
|
|
27
|
+
# }
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
module SpeechPricing
|
|
31
|
+
extend self
|
|
32
|
+
|
|
33
|
+
LITELLM_PRICING_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json"
|
|
34
|
+
DEFAULT_CACHE_TTL = 24 * 60 * 60 # 24 hours
|
|
35
|
+
|
|
36
|
+
# Calculate total cost for a speech operation
|
|
37
|
+
#
|
|
38
|
+
# @param provider [Symbol] :openai or :elevenlabs
|
|
39
|
+
# @param model_id [String] The model identifier
|
|
40
|
+
# @param characters [Integer] Number of characters synthesized
|
|
41
|
+
# @return [Float] Total cost in USD
|
|
42
|
+
def calculate_cost(provider:, model_id:, characters:)
|
|
43
|
+
price_per_1k = cost_per_1k_characters(provider, model_id)
|
|
44
|
+
((characters / 1000.0) * price_per_1k).round(6)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Get cost per 1,000 characters for a model
|
|
48
|
+
#
|
|
49
|
+
# @param provider [Symbol] Provider identifier
|
|
50
|
+
# @param model_id [String] Model identifier
|
|
51
|
+
# @return [Float] Cost per 1K characters in USD
|
|
52
|
+
def cost_per_1k_characters(provider, model_id)
|
|
53
|
+
if (litellm_price = from_litellm(model_id))
|
|
54
|
+
return litellm_price
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
if (config_price = from_config(model_id))
|
|
58
|
+
return config_price
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
fallback_price(provider, model_id)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Force refresh of cached LiteLLM data
|
|
65
|
+
def refresh!
|
|
66
|
+
@litellm_data = nil
|
|
67
|
+
@litellm_fetched_at = nil
|
|
68
|
+
litellm_data
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Expose all known pricing for debugging/dashboard
|
|
72
|
+
def all_pricing
|
|
73
|
+
{
|
|
74
|
+
litellm: litellm_tts_models,
|
|
75
|
+
configured: config.tts_model_pricing || {},
|
|
76
|
+
fallbacks: fallback_pricing_table
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
# ============================================================
|
|
83
|
+
# Tier 1: LiteLLM
|
|
84
|
+
# ============================================================
|
|
85
|
+
|
|
86
|
+
def from_litellm(model_id)
|
|
87
|
+
data = litellm_data
|
|
88
|
+
return nil unless data
|
|
89
|
+
|
|
90
|
+
model_data = find_litellm_model(data, model_id)
|
|
91
|
+
return nil unless model_data
|
|
92
|
+
|
|
93
|
+
extract_litellm_tts_price(model_data)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def find_litellm_model(data, model_id)
|
|
97
|
+
normalized = normalize_model_id(model_id)
|
|
98
|
+
|
|
99
|
+
candidates = [
|
|
100
|
+
model_id,
|
|
101
|
+
normalized,
|
|
102
|
+
"tts/#{model_id}",
|
|
103
|
+
"openai/#{model_id}",
|
|
104
|
+
"elevenlabs/#{model_id}"
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
candidates.each do |key|
|
|
108
|
+
return data[key] if data[key]
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
data.find do |key, _|
|
|
112
|
+
key.to_s.downcase.include?(normalized.downcase)
|
|
113
|
+
end&.last
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def extract_litellm_tts_price(model_data)
|
|
117
|
+
if model_data["input_cost_per_character"]
|
|
118
|
+
return model_data["input_cost_per_character"] * 1000
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
if model_data["output_cost_per_character"]
|
|
122
|
+
return model_data["output_cost_per_character"] * 1000
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
if model_data["output_cost_per_audio_token"]
|
|
126
|
+
return model_data["output_cost_per_audio_token"] * 250
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
nil
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def litellm_data
|
|
133
|
+
return @litellm_data if @litellm_data && !cache_expired?
|
|
134
|
+
|
|
135
|
+
@litellm_data = fetch_litellm_data
|
|
136
|
+
@litellm_fetched_at = Time.now
|
|
137
|
+
@litellm_data
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def fetch_litellm_data
|
|
141
|
+
if defined?(Rails) && Rails.respond_to?(:cache) && Rails.cache
|
|
142
|
+
Rails.cache.fetch("litellm_tts_pricing_data", expires_in: cache_ttl) do
|
|
143
|
+
fetch_from_url
|
|
144
|
+
end
|
|
145
|
+
else
|
|
146
|
+
fetch_from_url
|
|
147
|
+
end
|
|
148
|
+
rescue => e
|
|
149
|
+
warn "[RubyLLM::Agents] Failed to fetch LiteLLM TTS pricing: #{e.message}"
|
|
150
|
+
{}
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def fetch_from_url
|
|
154
|
+
uri = URI(config.litellm_pricing_url || LITELLM_PRICING_URL)
|
|
155
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
156
|
+
http.use_ssl = uri.scheme == "https"
|
|
157
|
+
http.open_timeout = 5
|
|
158
|
+
http.read_timeout = 10
|
|
159
|
+
|
|
160
|
+
request = Net::HTTP::Get.new(uri)
|
|
161
|
+
response = http.request(request)
|
|
162
|
+
|
|
163
|
+
if response.is_a?(Net::HTTPSuccess)
|
|
164
|
+
JSON.parse(response.body)
|
|
165
|
+
else
|
|
166
|
+
{}
|
|
167
|
+
end
|
|
168
|
+
rescue => e
|
|
169
|
+
warn "[RubyLLM::Agents] HTTP error fetching LiteLLM pricing: #{e.message}"
|
|
170
|
+
{}
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def cache_expired?
|
|
174
|
+
return true unless @litellm_fetched_at
|
|
175
|
+
Time.now - @litellm_fetched_at > cache_ttl
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def cache_ttl
|
|
179
|
+
ttl = config.litellm_pricing_cache_ttl
|
|
180
|
+
return DEFAULT_CACHE_TTL unless ttl
|
|
181
|
+
ttl.respond_to?(:to_i) ? ttl.to_i : ttl
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def litellm_tts_models
|
|
185
|
+
litellm_data.select do |key, value|
|
|
186
|
+
value.is_a?(Hash) && (
|
|
187
|
+
value["input_cost_per_character"] ||
|
|
188
|
+
key.to_s.match?(/tts|speech|eleven/i)
|
|
189
|
+
)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# ============================================================
|
|
194
|
+
# Tier 2: User configuration
|
|
195
|
+
# ============================================================
|
|
196
|
+
|
|
197
|
+
def from_config(model_id)
|
|
198
|
+
table = config.tts_model_pricing
|
|
199
|
+
return nil unless table.is_a?(Hash) && !table.empty?
|
|
200
|
+
|
|
201
|
+
normalized = normalize_model_id(model_id)
|
|
202
|
+
|
|
203
|
+
price = table[model_id] || table[normalized] ||
|
|
204
|
+
table[model_id.to_sym] || table[normalized.to_sym]
|
|
205
|
+
|
|
206
|
+
price if price.is_a?(Numeric)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# ============================================================
|
|
210
|
+
# Tier 3: Hardcoded fallbacks
|
|
211
|
+
# ============================================================
|
|
212
|
+
|
|
213
|
+
def fallback_price(provider, model_id)
|
|
214
|
+
normalized = normalize_model_id(model_id)
|
|
215
|
+
|
|
216
|
+
case provider
|
|
217
|
+
when :openai
|
|
218
|
+
openai_fallback_price(normalized)
|
|
219
|
+
when :elevenlabs
|
|
220
|
+
elevenlabs_fallback_price(normalized)
|
|
221
|
+
else
|
|
222
|
+
config.default_tts_cost || 0.015
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def openai_fallback_price(model_id)
|
|
227
|
+
case model_id
|
|
228
|
+
when /tts-1-hd/ then 0.030
|
|
229
|
+
when /tts-1/ then 0.015
|
|
230
|
+
else 0.015
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def elevenlabs_fallback_price(model_id)
|
|
235
|
+
case model_id
|
|
236
|
+
when /eleven_flash_v2/ then 0.15
|
|
237
|
+
when /eleven_turbo_v2/ then 0.15
|
|
238
|
+
when /eleven_v3/ then 0.30
|
|
239
|
+
when /eleven_multilingual_v2/ then 0.30
|
|
240
|
+
when /eleven_multilingual_v1/ then 0.30
|
|
241
|
+
when /eleven_monolingual_v1/ then 0.30
|
|
242
|
+
else 0.30
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def fallback_pricing_table
|
|
247
|
+
{
|
|
248
|
+
"tts-1" => 0.015,
|
|
249
|
+
"tts-1-hd" => 0.030,
|
|
250
|
+
"eleven_monolingual_v1" => 0.30,
|
|
251
|
+
"eleven_multilingual_v1" => 0.30,
|
|
252
|
+
"eleven_multilingual_v2" => 0.30,
|
|
253
|
+
"eleven_turbo_v2" => 0.15,
|
|
254
|
+
"eleven_flash_v2" => 0.15,
|
|
255
|
+
"eleven_turbo_v2_5" => 0.15,
|
|
256
|
+
"eleven_flash_v2_5" => 0.15,
|
|
257
|
+
"eleven_v3" => 0.30
|
|
258
|
+
}
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def normalize_model_id(model_id)
|
|
262
|
+
model_id.to_s.downcase
|
|
263
|
+
.gsub(/[^a-z0-9._-]/, "-").squeeze("-")
|
|
264
|
+
.gsub(/^-|-$/, "")
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def config
|
|
268
|
+
RubyLLM::Agents.configuration
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
end
|
|
@@ -166,7 +166,7 @@ module RubyLLM
|
|
|
166
166
|
|
|
167
167
|
def default_transcription_model
|
|
168
168
|
RubyLLM::Agents.configuration.default_transcription_model
|
|
169
|
-
rescue
|
|
169
|
+
rescue
|
|
170
170
|
"whisper-1"
|
|
171
171
|
end
|
|
172
172
|
end
|
|
@@ -318,6 +318,16 @@ module RubyLLM
|
|
|
318
318
|
context.output_tokens = 0
|
|
319
319
|
context.total_cost = calculate_cost(raw_result)
|
|
320
320
|
|
|
321
|
+
# Store transcription-specific metadata for execution tracking
|
|
322
|
+
context[:language] = resolved_language if resolved_language
|
|
323
|
+
context[:detected_language] = raw_result[:language] if raw_result[:language]
|
|
324
|
+
context[:audio_duration_seconds] = raw_result[:duration] if raw_result[:duration]
|
|
325
|
+
context[:audio_minutes] = (raw_result[:duration] / 60.0).round(4) if raw_result[:duration]
|
|
326
|
+
context[:output_format] = self.class.output_format.to_s
|
|
327
|
+
context[:timestamp_granularity] = self.class.include_timestamps.to_s
|
|
328
|
+
context[:segment_count] = raw_result[:segments]&.size if raw_result[:segments]
|
|
329
|
+
context[:word_count] = raw_result[:text]&.split(/\s+/)&.size if raw_result[:text]
|
|
330
|
+
|
|
321
331
|
# Build final result
|
|
322
332
|
context.output = build_result(
|
|
323
333
|
raw_result,
|
|
@@ -334,22 +344,22 @@ module RubyLLM
|
|
|
334
344
|
def agent_cache_key
|
|
335
345
|
# Generate content hash based on input type
|
|
336
346
|
content_hash = case @audio
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
347
|
+
when String
|
|
348
|
+
if @audio.start_with?("http://", "https://")
|
|
349
|
+
Digest::SHA256.hexdigest(@audio)
|
|
350
|
+
elsif File.exist?(@audio)
|
|
351
|
+
Digest::SHA256.file(@audio).hexdigest
|
|
352
|
+
else
|
|
353
|
+
Digest::SHA256.hexdigest(@audio)
|
|
354
|
+
end
|
|
355
|
+
when File, IO
|
|
356
|
+
@audio.rewind if @audio.respond_to?(:rewind)
|
|
357
|
+
Digest::SHA256.hexdigest(@audio.read).tap do
|
|
358
|
+
@audio.rewind if @audio.respond_to?(:rewind)
|
|
359
|
+
end
|
|
360
|
+
else
|
|
361
|
+
Digest::SHA256.hexdigest(@audio.to_s)
|
|
362
|
+
end
|
|
353
363
|
|
|
354
364
|
components = [
|
|
355
365
|
"ruby_llm_agents",
|
|
@@ -389,15 +399,15 @@ module RubyLLM
|
|
|
389
399
|
case audio
|
|
390
400
|
when String
|
|
391
401
|
if audio.start_with?("http://", "https://")
|
|
392
|
-
{
|
|
402
|
+
{source: audio, type: :url}
|
|
393
403
|
elsif looks_like_file_path?(audio)
|
|
394
|
-
{
|
|
404
|
+
{source: audio, type: :file_path}
|
|
395
405
|
else
|
|
396
406
|
# Assume it's binary data
|
|
397
|
-
{
|
|
407
|
+
{source: audio, type: :binary, format: format}
|
|
398
408
|
end
|
|
399
409
|
when File, IO
|
|
400
|
-
{
|
|
410
|
+
{source: audio, type: :file_object}
|
|
401
411
|
else
|
|
402
412
|
raise ArgumentError, "audio must be a file path, URL, File object, or binary data"
|
|
403
413
|
end
|
|
@@ -451,7 +461,7 @@ module RubyLLM
|
|
|
451
461
|
|
|
452
462
|
begin
|
|
453
463
|
return execute_transcription(audio_input, model)
|
|
454
|
-
rescue
|
|
464
|
+
rescue => e
|
|
455
465
|
last_error = e
|
|
456
466
|
retries += 1
|
|
457
467
|
|
|
@@ -498,7 +508,7 @@ module RubyLLM
|
|
|
498
508
|
# @param model [String] Model to use
|
|
499
509
|
# @return [Hash] Options for transcription
|
|
500
510
|
def build_transcribe_options(model)
|
|
501
|
-
options = {
|
|
511
|
+
options = {model: model}
|
|
502
512
|
|
|
503
513
|
# Add language if specified
|
|
504
514
|
lang = resolved_language
|
|
@@ -618,15 +628,15 @@ module RubyLLM
|
|
|
618
628
|
# Estimate based on model and duration
|
|
619
629
|
model = raw_result[:model].to_s
|
|
620
630
|
price_per_minute = case model
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
631
|
+
when /whisper-1/
|
|
632
|
+
0.006
|
|
633
|
+
when /gpt-4o-transcribe/
|
|
634
|
+
0.01
|
|
635
|
+
when /gpt-4o-mini-transcribe/
|
|
636
|
+
0.005
|
|
637
|
+
else
|
|
638
|
+
0.006 # Default to whisper pricing
|
|
639
|
+
end
|
|
630
640
|
|
|
631
641
|
duration_minutes * price_per_minute
|
|
632
642
|
end
|
|
@@ -657,7 +667,7 @@ module RubyLLM
|
|
|
657
667
|
# Calculates exponential backoff delay
|
|
658
668
|
def calculate_backoff(attempt)
|
|
659
669
|
config = self.class.reliability_config
|
|
660
|
-
base = config&.backoff == :constant ? 1.0 : 0.4
|
|
670
|
+
base = (config&.backoff == :constant) ? 1.0 : 0.4
|
|
661
671
|
max_delay = 10.0
|
|
662
672
|
|
|
663
673
|
delay = base * (2**(attempt - 1))
|
|
@@ -133,7 +133,7 @@ module RubyLLM
|
|
|
133
133
|
# @return [void]
|
|
134
134
|
def param(name, required: false, default: nil, type: nil)
|
|
135
135
|
@params ||= {}
|
|
136
|
-
@params[name] = {
|
|
136
|
+
@params[name] = {required: required, default: default, type: type}
|
|
137
137
|
define_method(name) do
|
|
138
138
|
@options[name] || @options[name.to_s] || self.class.params.dig(name, :default)
|
|
139
139
|
end
|
|
@@ -217,7 +217,7 @@ module RubyLLM
|
|
|
217
217
|
|
|
218
218
|
# Fall back to global configuration default
|
|
219
219
|
RubyLLM::Agents.configuration.default_thinking
|
|
220
|
-
rescue
|
|
220
|
+
rescue
|
|
221
221
|
nil
|
|
222
222
|
end
|
|
223
223
|
|
|
@@ -227,13 +227,13 @@ module RubyLLM
|
|
|
227
227
|
|
|
228
228
|
def default_streaming
|
|
229
229
|
RubyLLM::Agents.configuration.default_streaming
|
|
230
|
-
rescue
|
|
230
|
+
rescue
|
|
231
231
|
false
|
|
232
232
|
end
|
|
233
233
|
|
|
234
234
|
def default_temperature
|
|
235
235
|
RubyLLM::Agents.configuration.default_temperature
|
|
236
|
-
rescue
|
|
236
|
+
rescue
|
|
237
237
|
0.7
|
|
238
238
|
end
|
|
239
239
|
end
|
|
@@ -455,7 +455,7 @@ module RubyLLM
|
|
|
455
455
|
if tenant_value.is_a?(Hash)
|
|
456
456
|
tenant_value
|
|
457
457
|
elsif tenant_value.respond_to?(:llm_tenant_id)
|
|
458
|
-
{
|
|
458
|
+
{id: tenant_value.llm_tenant_id, object: tenant_value}
|
|
459
459
|
else
|
|
460
460
|
raise ArgumentError, "tenant must be a Hash or respond to :llm_tenant_id"
|
|
461
461
|
end
|
|
@@ -465,7 +465,7 @@ module RubyLLM
|
|
|
465
465
|
#
|
|
466
466
|
# @return [Array<Class>] Tool classes to use
|
|
467
467
|
def resolved_tools
|
|
468
|
-
if self.class.
|
|
468
|
+
if self.class.method_defined?(:tools, false)
|
|
469
469
|
tools
|
|
470
470
|
else
|
|
471
471
|
self.class.tools
|
|
@@ -493,7 +493,7 @@ module RubyLLM
|
|
|
493
493
|
prefill = assistant_prompt
|
|
494
494
|
return nil if prefill.nil? || (prefill.is_a?(String) && prefill.empty?)
|
|
495
495
|
|
|
496
|
-
{
|
|
496
|
+
{role: :assistant, content: prefill}
|
|
497
497
|
end
|
|
498
498
|
|
|
499
499
|
# Returns whether streaming is enabled
|
|
@@ -544,7 +544,7 @@ module RubyLLM
|
|
|
544
544
|
|
|
545
545
|
if config[:type] && has_value && !value.nil? && !value.is_a?(config[:type])
|
|
546
546
|
raise ArgumentError,
|
|
547
|
-
|
|
547
|
+
"#{self.class} expected #{config[:type]} for :#{name}, got #{value.class}"
|
|
548
548
|
end
|
|
549
549
|
end
|
|
550
550
|
end
|
|
@@ -594,8 +594,8 @@ module RubyLLM
|
|
|
594
594
|
def build_client(context = nil)
|
|
595
595
|
effective_model = context&.model || model
|
|
596
596
|
client = RubyLLM.chat
|
|
597
|
-
|
|
598
|
-
|
|
597
|
+
.with_model(effective_model)
|
|
598
|
+
.with_temperature(temperature)
|
|
599
599
|
|
|
600
600
|
client = client.with_instructions(system_prompt) if system_prompt
|
|
601
601
|
client = client.with_schema(schema) if schema
|
|
@@ -735,7 +735,7 @@ module RubyLLM
|
|
|
735
735
|
return nil unless defined?(RubyLLM::Models)
|
|
736
736
|
|
|
737
737
|
RubyLLM::Models.find(model_id)
|
|
738
|
-
rescue
|
|
738
|
+
rescue
|
|
739
739
|
nil
|
|
740
740
|
end
|
|
741
741
|
|
|
@@ -789,7 +789,7 @@ module RubyLLM
|
|
|
789
789
|
# @return [Hash] Hash with thinking data or empty hash
|
|
790
790
|
def safe_extract_thinking_data(response)
|
|
791
791
|
result_thinking_data(response)
|
|
792
|
-
rescue
|
|
792
|
+
rescue
|
|
793
793
|
{}
|
|
794
794
|
end
|
|
795
795
|
|
|
@@ -894,7 +894,7 @@ module RubyLLM
|
|
|
894
894
|
content = result.to_s
|
|
895
895
|
end
|
|
896
896
|
|
|
897
|
-
{
|
|
897
|
+
{content: content, status: status, error_message: error_message}
|
|
898
898
|
end
|
|
899
899
|
|
|
900
900
|
# Truncates tool result if it exceeds the configured max length
|
|
@@ -917,7 +917,7 @@ module RubyLLM
|
|
|
917
917
|
# @return [Integer] Max length
|
|
918
918
|
def tool_result_max_length
|
|
919
919
|
RubyLLM::Agents.configuration.tool_result_max_length || 10_000
|
|
920
|
-
rescue
|
|
920
|
+
rescue
|
|
921
921
|
10_000
|
|
922
922
|
end
|
|
923
923
|
|
|
@@ -49,7 +49,7 @@ module RubyLLM
|
|
|
49
49
|
# before_call { |context| context.params[:sanitized] = true }
|
|
50
50
|
#
|
|
51
51
|
def before_call(method_name = nil, &block)
|
|
52
|
-
@callbacks ||= {
|
|
52
|
+
@callbacks ||= {before: [], after: []}
|
|
53
53
|
@callbacks[:before] << (block || method_name)
|
|
54
54
|
end
|
|
55
55
|
|
|
@@ -71,7 +71,7 @@ module RubyLLM
|
|
|
71
71
|
# after_call { |context, response| notify_completion(response) }
|
|
72
72
|
#
|
|
73
73
|
def after_call(method_name = nil, &block)
|
|
74
|
-
@callbacks ||= {
|
|
74
|
+
@callbacks ||= {before: [], after: []}
|
|
75
75
|
@callbacks[:after] << (block || method_name)
|
|
76
76
|
end
|
|
77
77
|
|
|
@@ -112,7 +112,7 @@ module RubyLLM
|
|
|
112
112
|
#
|
|
113
113
|
# @return [Hash] Hash with :before and :after arrays
|
|
114
114
|
def callbacks
|
|
115
|
-
@callbacks ||= {
|
|
115
|
+
@callbacks ||= {before: [], after: []}
|
|
116
116
|
end
|
|
117
117
|
end
|
|
118
118
|
|