ruby_llm-agents 3.0.0 → 3.2.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.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -0
  3. data/app/controllers/ruby_llm/agents/agents_controller.rb +16 -14
  4. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +20 -20
  5. data/app/controllers/ruby_llm/agents/executions_controller.rb +5 -7
  6. data/app/helpers/ruby_llm/agents/application_helper.rb +57 -58
  7. data/app/models/ruby_llm/agents/execution/analytics.rb +27 -27
  8. data/app/models/ruby_llm/agents/execution/scopes.rb +4 -6
  9. data/app/models/ruby_llm/agents/execution.rb +26 -26
  10. data/app/models/ruby_llm/agents/tenant/budgetable.rb +16 -10
  11. data/app/models/ruby_llm/agents/tenant/resettable.rb +12 -12
  12. data/app/models/ruby_llm/agents/tenant/trackable.rb +7 -7
  13. data/app/services/ruby_llm/agents/agent_registry.rb +6 -6
  14. data/app/views/layouts/ruby_llm/agents/application.html.erb +142 -11
  15. data/app/views/ruby_llm/agents/agents/show.html.erb +10 -10
  16. data/app/views/ruby_llm/agents/dashboard/index.html.erb +10 -10
  17. data/app/views/ruby_llm/agents/executions/show.html.erb +13 -0
  18. data/lib/generators/ruby_llm_agents/agent_generator.rb +4 -4
  19. data/lib/generators/ruby_llm_agents/background_remover_generator.rb +6 -6
  20. data/lib/generators/ruby_llm_agents/embedder_generator.rb +4 -4
  21. data/lib/generators/ruby_llm_agents/image_analyzer_generator.rb +7 -7
  22. data/lib/generators/ruby_llm_agents/image_editor_generator.rb +4 -4
  23. data/lib/generators/ruby_llm_agents/image_generator_generator.rb +6 -6
  24. data/lib/generators/ruby_llm_agents/image_pipeline_generator.rb +9 -9
  25. data/lib/generators/ruby_llm_agents/image_transformer_generator.rb +6 -6
  26. data/lib/generators/ruby_llm_agents/image_upscaler_generator.rb +4 -4
  27. data/lib/generators/ruby_llm_agents/image_variator_generator.rb +4 -4
  28. data/lib/generators/ruby_llm_agents/install_generator.rb +3 -3
  29. data/lib/generators/ruby_llm_agents/migrate_structure_generator.rb +4 -4
  30. data/lib/generators/ruby_llm_agents/multi_tenancy_generator.rb +2 -2
  31. data/lib/generators/ruby_llm_agents/restructure_generator.rb +13 -13
  32. data/lib/generators/ruby_llm_agents/speaker_generator.rb +6 -6
  33. data/lib/generators/ruby_llm_agents/templates/add_assistant_prompt_migration.rb.tt +9 -0
  34. data/lib/generators/ruby_llm_agents/templates/split_execution_details_migration.rb.tt +2 -1
  35. data/lib/generators/ruby_llm_agents/transcriber_generator.rb +4 -4
  36. data/lib/generators/ruby_llm_agents/upgrade_generator.rb +22 -3
  37. data/lib/ruby_llm/agents/audio/speaker.rb +40 -31
  38. data/lib/ruby_llm/agents/audio/speech_client.rb +328 -0
  39. data/lib/ruby_llm/agents/audio/speech_pricing.rb +273 -0
  40. data/lib/ruby_llm/agents/audio/transcriber.rb +33 -33
  41. data/lib/ruby_llm/agents/base_agent.rb +16 -15
  42. data/lib/ruby_llm/agents/core/base/callbacks.rb +3 -3
  43. data/lib/ruby_llm/agents/core/configuration.rb +86 -73
  44. data/lib/ruby_llm/agents/core/errors.rb +27 -2
  45. data/lib/ruby_llm/agents/core/instrumentation.rb +101 -65
  46. data/lib/ruby_llm/agents/core/llm_tenant.rb +7 -7
  47. data/lib/ruby_llm/agents/core/version.rb +1 -1
  48. data/lib/ruby_llm/agents/dsl/base.rb +3 -3
  49. data/lib/ruby_llm/agents/dsl/reliability.rb +9 -9
  50. data/lib/ruby_llm/agents/image/analyzer/dsl.rb +1 -1
  51. data/lib/ruby_llm/agents/image/analyzer/execution.rb +4 -4
  52. data/lib/ruby_llm/agents/image/background_remover/dsl.rb +1 -1
  53. data/lib/ruby_llm/agents/image/background_remover/execution.rb +3 -3
  54. data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +8 -8
  55. data/lib/ruby_llm/agents/image/editor/execution.rb +1 -1
  56. data/lib/ruby_llm/agents/image/generator/pricing.rb +9 -10
  57. data/lib/ruby_llm/agents/image/generator.rb +6 -6
  58. data/lib/ruby_llm/agents/image/pipeline/dsl.rb +6 -6
  59. data/lib/ruby_llm/agents/image/pipeline/execution.rb +9 -9
  60. data/lib/ruby_llm/agents/image/pipeline.rb +1 -1
  61. data/lib/ruby_llm/agents/image/transformer/execution.rb +1 -1
  62. data/lib/ruby_llm/agents/image/upscaler/dsl.rb +1 -1
  63. data/lib/ruby_llm/agents/image/upscaler/execution.rb +3 -5
  64. data/lib/ruby_llm/agents/image/variator/execution.rb +1 -1
  65. data/lib/ruby_llm/agents/infrastructure/alert_manager.rb +4 -4
  66. data/lib/ruby_llm/agents/infrastructure/attempt_tracker.rb +4 -4
  67. data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +9 -9
  68. data/lib/ruby_llm/agents/infrastructure/budget/config_resolver.rb +3 -3
  69. data/lib/ruby_llm/agents/infrastructure/budget/forecaster.rb +1 -1
  70. data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +17 -17
  71. data/lib/ruby_llm/agents/infrastructure/circuit_breaker.rb +1 -0
  72. data/lib/ruby_llm/agents/infrastructure/execution_logger_job.rb +1 -1
  73. data/lib/ruby_llm/agents/infrastructure/reliability.rb +6 -6
  74. data/lib/ruby_llm/agents/pipeline/builder.rb +11 -11
  75. data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +3 -3
  76. data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +4 -4
  77. data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +62 -21
  78. data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +2 -3
  79. data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +82 -4
  80. data/lib/ruby_llm/agents/results/background_removal_result.rb +6 -6
  81. data/lib/ruby_llm/agents/results/embedding_result.rb +15 -15
  82. data/lib/ruby_llm/agents/results/image_analysis_result.rb +7 -7
  83. data/lib/ruby_llm/agents/results/image_edit_result.rb +4 -4
  84. data/lib/ruby_llm/agents/results/image_generation_result.rb +5 -5
  85. data/lib/ruby_llm/agents/results/image_pipeline_result.rb +4 -4
  86. data/lib/ruby_llm/agents/results/image_transform_result.rb +4 -4
  87. data/lib/ruby_llm/agents/results/image_upscale_result.rb +5 -5
  88. data/lib/ruby_llm/agents/results/image_variation_result.rb +4 -4
  89. data/lib/ruby_llm/agents/results/transcription_result.rb +1 -1
  90. data/lib/ruby_llm/agents/text/embedder.rb +13 -13
  91. metadata +4 -1
@@ -166,7 +166,7 @@ module RubyLLM
166
166
 
167
167
  def default_transcription_model
168
168
  RubyLLM::Agents.configuration.default_transcription_model
169
- rescue StandardError
169
+ rescue
170
170
  "whisper-1"
171
171
  end
172
172
  end
@@ -334,22 +334,22 @@ module RubyLLM
334
334
  def agent_cache_key
335
335
  # Generate content hash based on input type
336
336
  content_hash = case @audio
337
- when String
338
- if @audio.start_with?("http://", "https://")
339
- Digest::SHA256.hexdigest(@audio)
340
- elsif File.exist?(@audio)
341
- Digest::SHA256.file(@audio).hexdigest
342
- else
343
- Digest::SHA256.hexdigest(@audio)
344
- end
345
- when File, IO
346
- @audio.rewind if @audio.respond_to?(:rewind)
347
- Digest::SHA256.hexdigest(@audio.read).tap do
348
- @audio.rewind if @audio.respond_to?(:rewind)
349
- end
350
- else
351
- Digest::SHA256.hexdigest(@audio.to_s)
352
- end
337
+ when String
338
+ if @audio.start_with?("http://", "https://")
339
+ Digest::SHA256.hexdigest(@audio)
340
+ elsif File.exist?(@audio)
341
+ Digest::SHA256.file(@audio).hexdigest
342
+ else
343
+ Digest::SHA256.hexdigest(@audio)
344
+ end
345
+ when File, IO
346
+ @audio.rewind if @audio.respond_to?(:rewind)
347
+ Digest::SHA256.hexdigest(@audio.read).tap do
348
+ @audio.rewind if @audio.respond_to?(:rewind)
349
+ end
350
+ else
351
+ Digest::SHA256.hexdigest(@audio.to_s)
352
+ end
353
353
 
354
354
  components = [
355
355
  "ruby_llm_agents",
@@ -389,15 +389,15 @@ module RubyLLM
389
389
  case audio
390
390
  when String
391
391
  if audio.start_with?("http://", "https://")
392
- { source: audio, type: :url }
392
+ {source: audio, type: :url}
393
393
  elsif looks_like_file_path?(audio)
394
- { source: audio, type: :file_path }
394
+ {source: audio, type: :file_path}
395
395
  else
396
396
  # Assume it's binary data
397
- { source: audio, type: :binary, format: format }
397
+ {source: audio, type: :binary, format: format}
398
398
  end
399
399
  when File, IO
400
- { source: audio, type: :file_object }
400
+ {source: audio, type: :file_object}
401
401
  else
402
402
  raise ArgumentError, "audio must be a file path, URL, File object, or binary data"
403
403
  end
@@ -451,7 +451,7 @@ module RubyLLM
451
451
 
452
452
  begin
453
453
  return execute_transcription(audio_input, model)
454
- rescue StandardError => e
454
+ rescue => e
455
455
  last_error = e
456
456
  retries += 1
457
457
 
@@ -498,7 +498,7 @@ module RubyLLM
498
498
  # @param model [String] Model to use
499
499
  # @return [Hash] Options for transcription
500
500
  def build_transcribe_options(model)
501
- options = { model: model }
501
+ options = {model: model}
502
502
 
503
503
  # Add language if specified
504
504
  lang = resolved_language
@@ -618,15 +618,15 @@ module RubyLLM
618
618
  # Estimate based on model and duration
619
619
  model = raw_result[:model].to_s
620
620
  price_per_minute = case model
621
- when /whisper-1/
622
- 0.006
623
- when /gpt-4o-transcribe/
624
- 0.01
625
- when /gpt-4o-mini-transcribe/
626
- 0.005
627
- else
628
- 0.006 # Default to whisper pricing
629
- end
621
+ when /whisper-1/
622
+ 0.006
623
+ when /gpt-4o-transcribe/
624
+ 0.01
625
+ when /gpt-4o-mini-transcribe/
626
+ 0.005
627
+ else
628
+ 0.006 # Default to whisper pricing
629
+ end
630
630
 
631
631
  duration_minutes * price_per_minute
632
632
  end
@@ -657,7 +657,7 @@ module RubyLLM
657
657
  # Calculates exponential backoff delay
658
658
  def calculate_backoff(attempt)
659
659
  config = self.class.reliability_config
660
- base = config&.backoff == :constant ? 1.0 : 0.4
660
+ base = (config&.backoff == :constant) ? 1.0 : 0.4
661
661
  max_delay = 10.0
662
662
 
663
663
  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] = { required: required, default: default, type: type }
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 StandardError
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 StandardError
230
+ rescue
231
231
  false
232
232
  end
233
233
 
234
234
  def default_temperature
235
235
  RubyLLM::Agents.configuration.default_temperature
236
- rescue StandardError
236
+ rescue
237
237
  0.7
238
238
  end
239
239
  end
@@ -380,7 +380,8 @@ module RubyLLM
380
380
  base_data.merge(
381
381
  model: model,
382
382
  system_prompt: system_prompt,
383
- user_prompt: user_prompt
383
+ user_prompt: user_prompt,
384
+ assistant_prompt: assistant_prompt
384
385
  )
385
386
  end
386
387
 
@@ -454,7 +455,7 @@ module RubyLLM
454
455
  if tenant_value.is_a?(Hash)
455
456
  tenant_value
456
457
  elsif tenant_value.respond_to?(:llm_tenant_id)
457
- { id: tenant_value.llm_tenant_id, object: tenant_value }
458
+ {id: tenant_value.llm_tenant_id, object: tenant_value}
458
459
  else
459
460
  raise ArgumentError, "tenant must be a Hash or respond to :llm_tenant_id"
460
461
  end
@@ -464,7 +465,7 @@ module RubyLLM
464
465
  #
465
466
  # @return [Array<Class>] Tool classes to use
466
467
  def resolved_tools
467
- if self.class.instance_methods(false).include?(:tools)
468
+ if self.class.method_defined?(:tools, false)
468
469
  tools
469
470
  else
470
471
  self.class.tools
@@ -492,7 +493,7 @@ module RubyLLM
492
493
  prefill = assistant_prompt
493
494
  return nil if prefill.nil? || (prefill.is_a?(String) && prefill.empty?)
494
495
 
495
- { role: :assistant, content: prefill }
496
+ {role: :assistant, content: prefill}
496
497
  end
497
498
 
498
499
  # Returns whether streaming is enabled
@@ -543,7 +544,7 @@ module RubyLLM
543
544
 
544
545
  if config[:type] && has_value && !value.nil? && !value.is_a?(config[:type])
545
546
  raise ArgumentError,
546
- "#{self.class} expected #{config[:type]} for :#{name}, got #{value.class}"
547
+ "#{self.class} expected #{config[:type]} for :#{name}, got #{value.class}"
547
548
  end
548
549
  end
549
550
  end
@@ -593,8 +594,8 @@ module RubyLLM
593
594
  def build_client(context = nil)
594
595
  effective_model = context&.model || model
595
596
  client = RubyLLM.chat
596
- .with_model(effective_model)
597
- .with_temperature(temperature)
597
+ .with_model(effective_model)
598
+ .with_temperature(temperature)
598
599
 
599
600
  client = client.with_instructions(system_prompt) if system_prompt
600
601
  client = client.with_schema(schema) if schema
@@ -734,7 +735,7 @@ module RubyLLM
734
735
  return nil unless defined?(RubyLLM::Models)
735
736
 
736
737
  RubyLLM::Models.find(model_id)
737
- rescue StandardError
738
+ rescue
738
739
  nil
739
740
  end
740
741
 
@@ -788,7 +789,7 @@ module RubyLLM
788
789
  # @return [Hash] Hash with thinking data or empty hash
789
790
  def safe_extract_thinking_data(response)
790
791
  result_thinking_data(response)
791
- rescue StandardError
792
+ rescue
792
793
  {}
793
794
  end
794
795
 
@@ -893,7 +894,7 @@ module RubyLLM
893
894
  content = result.to_s
894
895
  end
895
896
 
896
- { content: content, status: status, error_message: error_message }
897
+ {content: content, status: status, error_message: error_message}
897
898
  end
898
899
 
899
900
  # Truncates tool result if it exceeds the configured max length
@@ -916,7 +917,7 @@ module RubyLLM
916
917
  # @return [Integer] Max length
917
918
  def tool_result_max_length
918
919
  RubyLLM::Agents.configuration.tool_result_max_length || 10_000
919
- rescue StandardError
920
+ rescue
920
921
  10_000
921
922
  end
922
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 ||= { before: [], after: [] }
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 ||= { before: [], after: [] }
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 ||= { before: [], after: [] }
115
+ @callbacks ||= {before: [], after: []}
116
116
  end
117
117
  end
118
118
 
@@ -390,79 +390,84 @@ module RubyLLM
390
390
  end
391
391
  end
392
392
 
393
+ # Attributes with custom setters (validation) — only readers here
394
+ attr_reader :default_embedding_dimensions, :default_embedding_batch_size
395
+
393
396
  # Attributes without validation (simple accessors)
394
397
  attr_accessor :default_model,
395
- :async_logging,
396
- :async_max_concurrency,
397
- :retention_period,
398
- :dashboard_parent_controller,
399
- :basic_auth_username,
400
- :basic_auth_password,
401
- :default_fallback_models,
402
- :default_total_timeout,
403
- :default_streaming,
404
- :default_tools,
405
- :default_thinking,
406
- :on_alert,
407
- :persist_prompts,
408
- :persist_responses,
409
- :multi_tenancy_enabled,
410
- :persist_messages_summary,
411
- :default_retryable_patterns,
412
- :default_embedding_model,
413
- :default_embedding_dimensions,
414
- :default_embedding_batch_size,
415
- :track_embeddings,
416
- :default_transcription_model,
417
- :track_transcriptions,
418
- :default_tts_provider,
419
- :default_tts_model,
420
- :default_tts_voice,
421
- :track_speech,
422
- :track_executions,
423
- :track_audio,
424
- :track_cache_hits,
425
- :default_image_model,
426
- :default_image_size,
427
- :default_image_quality,
428
- :default_image_style,
429
- :max_image_prompt_length,
430
- :track_image_generation,
431
- :image_model_aliases,
432
- :litellm_pricing_url,
433
- :litellm_pricing_cache_ttl,
434
- :default_image_cost,
435
- :image_model_pricing,
436
- :default_variator_model,
437
- :default_editor_model,
438
- :default_transformer_model,
439
- :default_upscaler_model,
440
- :default_variation_strength,
441
- :default_transform_strength,
442
- :default_analyzer_model,
443
- :default_analysis_type,
444
- :default_analyzer_max_tags,
445
- :default_background_remover_model,
446
- :default_background_output_format,
447
- :root_directory,
448
- :root_namespace,
449
- :tool_result_max_length,
450
- :redaction
398
+ :async_logging,
399
+ :async_max_concurrency,
400
+ :retention_period,
401
+ :dashboard_parent_controller,
402
+ :basic_auth_username,
403
+ :basic_auth_password,
404
+ :default_fallback_models,
405
+ :default_total_timeout,
406
+ :default_streaming,
407
+ :default_tools,
408
+ :default_thinking,
409
+ :on_alert,
410
+ :persist_prompts,
411
+ :persist_responses,
412
+ :multi_tenancy_enabled,
413
+ :persist_messages_summary,
414
+ :default_retryable_patterns,
415
+ :default_embedding_model,
416
+ :track_embeddings,
417
+ :default_transcription_model,
418
+ :track_transcriptions,
419
+ :default_tts_provider,
420
+ :default_tts_model,
421
+ :default_tts_voice,
422
+ :track_speech,
423
+ :elevenlabs_api_key,
424
+ :elevenlabs_api_base,
425
+ :tts_model_pricing,
426
+ :default_tts_cost,
427
+ :track_executions,
428
+ :track_audio,
429
+ :track_cache_hits,
430
+ :default_image_model,
431
+ :default_image_size,
432
+ :default_image_quality,
433
+ :default_image_style,
434
+ :max_image_prompt_length,
435
+ :track_image_generation,
436
+ :image_model_aliases,
437
+ :litellm_pricing_url,
438
+ :litellm_pricing_cache_ttl,
439
+ :default_image_cost,
440
+ :image_model_pricing,
441
+ :default_variator_model,
442
+ :default_editor_model,
443
+ :default_transformer_model,
444
+ :default_upscaler_model,
445
+ :default_variation_strength,
446
+ :default_transform_strength,
447
+ :default_analyzer_model,
448
+ :default_analysis_type,
449
+ :default_analyzer_max_tags,
450
+ :default_background_remover_model,
451
+ :default_background_output_format,
452
+ :root_directory,
453
+ :root_namespace,
454
+ :tool_result_max_length,
455
+ :redaction
451
456
 
452
457
  # Attributes with validation (readers only, custom setters below)
453
458
  attr_reader :default_temperature,
454
- :default_timeout,
455
- :anomaly_cost_threshold,
456
- :anomaly_duration_threshold,
457
- :per_page,
458
- :recent_executions_limit,
459
- :job_retry_attempts,
460
- :messages_summary_max_length,
461
- :dashboard_auth,
462
- :tenant_resolver,
463
- :tenant_config_resolver,
464
- :default_retries,
465
- :budgets
459
+ :default_timeout,
460
+ :anomaly_cost_threshold,
461
+ :anomaly_duration_threshold,
462
+ :per_page,
463
+ :recent_executions_limit,
464
+ :job_retry_attempts,
465
+ :messages_summary_max_length,
466
+ :dashboard_auth,
467
+ :tenant_resolver,
468
+ :tenant_config_resolver,
469
+ :default_retries,
470
+ :budgets
466
471
 
467
472
  attr_writer :cache_store
468
473
 
@@ -597,7 +602,7 @@ module RubyLLM
597
602
  # @param value [Integer, nil] Dimensions (must be nil or > 0)
598
603
  # @raise [ArgumentError] If value is not nil or positive
599
604
  def default_embedding_dimensions=(value)
600
- unless value.nil? || (value.is_a?(Numeric) && value > 0)
605
+ if !value.nil? && !(value.is_a?(Numeric) && value > 0)
601
606
  raise ArgumentError, "default_embedding_dimensions must be nil or greater than 0"
602
607
  end
603
608
 
@@ -627,13 +632,13 @@ module RubyLLM
627
632
  @job_retry_attempts = 3
628
633
 
629
634
  # Reliability defaults (all disabled by default for backward compatibility)
630
- @default_retries = { max: 0, backoff: :exponential, base: 0.4, max_delay: 3.0, on: [] }
635
+ @default_retries = {max: 0, backoff: :exponential, base: 0.4, max_delay: 3.0, on: []}
631
636
  @default_fallback_models = []
632
637
  @default_total_timeout = nil
633
638
  @default_retryable_patterns = {
634
639
  rate_limiting: ["rate limit", "rate_limit", "too many requests", "429", "quota"],
635
640
  server_errors: ["500", "502", "503", "504", "service unavailable",
636
- "internal server error", "bad gateway", "gateway timeout"],
641
+ "internal server error", "bad gateway", "gateway timeout"],
637
642
  capacity: ["overloaded", "capacity"]
638
643
  }
639
644
 
@@ -650,7 +655,7 @@ module RubyLLM
650
655
 
651
656
  # Multi-tenancy defaults (disabled for backward compatibility)
652
657
  @multi_tenancy_enabled = false
653
- @tenant_resolver = -> { nil }
658
+ @tenant_resolver = -> {}
654
659
  @tenant_config_resolver = nil
655
660
 
656
661
  # Messages summary defaults
@@ -673,6 +678,14 @@ module RubyLLM
673
678
  @default_tts_voice = "nova"
674
679
  @track_speech = true
675
680
 
681
+ # ElevenLabs defaults
682
+ @elevenlabs_api_key = nil
683
+ @elevenlabs_api_base = "https://api.elevenlabs.io"
684
+
685
+ # TTS pricing defaults
686
+ @tts_model_pricing = {}
687
+ @default_tts_cost = 0.015
688
+
676
689
  # Execution/conversation agent tracking
677
690
  @track_executions = true
678
691
  @track_audio = true
@@ -783,7 +796,7 @@ module RubyLLM
783
796
  return false unless async_available?
784
797
 
785
798
  defined?(::Async::Task) && ::Async::Task.current?
786
- rescue StandardError
799
+ rescue
787
800
  false
788
801
  end
789
802
 
@@ -29,7 +29,7 @@ module RubyLLM
29
29
 
30
30
  def initialize(message = nil, model: nil)
31
31
  @model = model
32
- super(message || "Circuit breaker is open#{model ? " for #{model}" : ""}")
32
+ super(message || "Circuit breaker is open#{" for #{model}" if model}")
33
33
  end
34
34
  end
35
35
 
@@ -78,7 +78,7 @@ module RubyLLM
78
78
  def initialize(message = nil, tenant_id: nil, budget_type: nil)
79
79
  @tenant_id = tenant_id
80
80
  @budget_type = budget_type
81
- super(message || "Budget exceeded#{tenant_id ? " for tenant #{tenant_id}" : ""}")
81
+ super(message || "Budget exceeded#{" for tenant #{tenant_id}" if tenant_id}")
82
82
  end
83
83
  end
84
84
 
@@ -88,5 +88,30 @@ module RubyLLM
88
88
 
89
89
  # Raised for configuration issues
90
90
  class ConfigurationError < Error; end
91
+
92
+ # ============================================================
93
+ # Speech/Audio Errors
94
+ # ============================================================
95
+
96
+ # Raised when a TTS provider is not supported
97
+ class UnsupportedProviderError < Error
98
+ attr_reader :provider
99
+
100
+ def initialize(message = nil, provider: nil)
101
+ @provider = provider
102
+ super(message || "Provider :#{provider} is not supported for this operation")
103
+ end
104
+ end
105
+
106
+ # Raised when the TTS API returns an error response
107
+ class SpeechApiError < Error
108
+ attr_reader :status, :response_body
109
+
110
+ def initialize(message = nil, status: nil, response_body: nil)
111
+ @status = status
112
+ @response_body = response_body
113
+ super(message || "Speech API error (status: #{status})")
114
+ end
115
+ end
91
116
  end
92
117
  end