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
|
@@ -390,79 +390,85 @@ 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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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,
|
|
456
|
+
:persist_audio_data
|
|
451
457
|
|
|
452
458
|
# Attributes with validation (readers only, custom setters below)
|
|
453
459
|
attr_reader :default_temperature,
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
460
|
+
:default_timeout,
|
|
461
|
+
:anomaly_cost_threshold,
|
|
462
|
+
:anomaly_duration_threshold,
|
|
463
|
+
:per_page,
|
|
464
|
+
:recent_executions_limit,
|
|
465
|
+
:job_retry_attempts,
|
|
466
|
+
:messages_summary_max_length,
|
|
467
|
+
:dashboard_auth,
|
|
468
|
+
:tenant_resolver,
|
|
469
|
+
:tenant_config_resolver,
|
|
470
|
+
:default_retries,
|
|
471
|
+
:budgets
|
|
466
472
|
|
|
467
473
|
attr_writer :cache_store
|
|
468
474
|
|
|
@@ -597,7 +603,7 @@ module RubyLLM
|
|
|
597
603
|
# @param value [Integer, nil] Dimensions (must be nil or > 0)
|
|
598
604
|
# @raise [ArgumentError] If value is not nil or positive
|
|
599
605
|
def default_embedding_dimensions=(value)
|
|
600
|
-
|
|
606
|
+
if !value.nil? && !(value.is_a?(Numeric) && value > 0)
|
|
601
607
|
raise ArgumentError, "default_embedding_dimensions must be nil or greater than 0"
|
|
602
608
|
end
|
|
603
609
|
|
|
@@ -627,13 +633,13 @@ module RubyLLM
|
|
|
627
633
|
@job_retry_attempts = 3
|
|
628
634
|
|
|
629
635
|
# Reliability defaults (all disabled by default for backward compatibility)
|
|
630
|
-
@default_retries = {
|
|
636
|
+
@default_retries = {max: 0, backoff: :exponential, base: 0.4, max_delay: 3.0, on: []}
|
|
631
637
|
@default_fallback_models = []
|
|
632
638
|
@default_total_timeout = nil
|
|
633
639
|
@default_retryable_patterns = {
|
|
634
640
|
rate_limiting: ["rate limit", "rate_limit", "too many requests", "429", "quota"],
|
|
635
641
|
server_errors: ["500", "502", "503", "504", "service unavailable",
|
|
636
|
-
|
|
642
|
+
"internal server error", "bad gateway", "gateway timeout"],
|
|
637
643
|
capacity: ["overloaded", "capacity"]
|
|
638
644
|
}
|
|
639
645
|
|
|
@@ -650,7 +656,7 @@ module RubyLLM
|
|
|
650
656
|
|
|
651
657
|
# Multi-tenancy defaults (disabled for backward compatibility)
|
|
652
658
|
@multi_tenancy_enabled = false
|
|
653
|
-
@tenant_resolver = -> {
|
|
659
|
+
@tenant_resolver = -> {}
|
|
654
660
|
@tenant_config_resolver = nil
|
|
655
661
|
|
|
656
662
|
# Messages summary defaults
|
|
@@ -673,6 +679,14 @@ module RubyLLM
|
|
|
673
679
|
@default_tts_voice = "nova"
|
|
674
680
|
@track_speech = true
|
|
675
681
|
|
|
682
|
+
# ElevenLabs defaults
|
|
683
|
+
@elevenlabs_api_key = nil
|
|
684
|
+
@elevenlabs_api_base = "https://api.elevenlabs.io"
|
|
685
|
+
|
|
686
|
+
# TTS pricing defaults
|
|
687
|
+
@tts_model_pricing = {}
|
|
688
|
+
@default_tts_cost = 0.015
|
|
689
|
+
|
|
676
690
|
# Execution/conversation agent tracking
|
|
677
691
|
@track_executions = true
|
|
678
692
|
@track_audio = true
|
|
@@ -721,6 +735,9 @@ module RubyLLM
|
|
|
721
735
|
|
|
722
736
|
# Redaction defaults (disabled by default)
|
|
723
737
|
@redaction = nil
|
|
738
|
+
|
|
739
|
+
# Audio data persistence (disabled by default — base64 audio can be large)
|
|
740
|
+
@persist_audio_data = false
|
|
724
741
|
end
|
|
725
742
|
|
|
726
743
|
# Returns the configured cache store, falling back to Rails.cache
|
|
@@ -783,7 +800,7 @@ module RubyLLM
|
|
|
783
800
|
return false unless async_available?
|
|
784
801
|
|
|
785
802
|
defined?(::Async::Task) && ::Async::Task.current?
|
|
786
|
-
rescue
|
|
803
|
+
rescue
|
|
787
804
|
false
|
|
788
805
|
end
|
|
789
806
|
|
|
@@ -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#{
|
|
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#{
|
|
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
|
|
@@ -63,48 +63,46 @@ module RubyLLM
|
|
|
63
63
|
# Use catch to intercept successful early returns from the block
|
|
64
64
|
# The block uses `throw :execution_success, result` instead of `return`
|
|
65
65
|
result = catch(:execution_success) do
|
|
66
|
+
yield(attempt_tracker)
|
|
67
|
+
# If we reach here normally (no throw), the block completed without success
|
|
68
|
+
# This happens when AllModelsExhaustedError is raised
|
|
69
|
+
nil
|
|
70
|
+
rescue Timeout::Error, Reliability::TotalTimeoutError => e
|
|
71
|
+
raised_exception = e
|
|
72
|
+
begin
|
|
73
|
+
complete_execution_with_attempts(
|
|
74
|
+
execution,
|
|
75
|
+
attempt_tracker: attempt_tracker,
|
|
76
|
+
completed_at: Time.current,
|
|
77
|
+
status: "timeout",
|
|
78
|
+
error: e
|
|
79
|
+
)
|
|
80
|
+
@status_update_completed = true
|
|
81
|
+
rescue => completion_err
|
|
82
|
+
completion_error = completion_err
|
|
83
|
+
end
|
|
84
|
+
raise
|
|
85
|
+
rescue => e
|
|
86
|
+
raised_exception = e
|
|
66
87
|
begin
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
raise
|
|
86
|
-
rescue StandardError => e
|
|
87
|
-
raised_exception = e
|
|
88
|
-
begin
|
|
89
|
-
complete_execution_with_attempts(
|
|
90
|
-
execution,
|
|
91
|
-
attempt_tracker: attempt_tracker,
|
|
92
|
-
completed_at: Time.current,
|
|
93
|
-
status: "error",
|
|
94
|
-
error: e
|
|
95
|
-
)
|
|
96
|
-
@status_update_completed = true
|
|
97
|
-
rescue StandardError => completion_err
|
|
98
|
-
completion_error = completion_err
|
|
99
|
-
end
|
|
100
|
-
raise
|
|
101
|
-
ensure
|
|
102
|
-
# Only run emergency fallback if we haven't completed AND we're not in success path
|
|
103
|
-
# The success path completion happens AFTER the catch block
|
|
104
|
-
unless @status_update_completed || !$!
|
|
105
|
-
actual_error = completion_error || raised_exception || $!
|
|
106
|
-
mark_execution_failed!(execution, error: actual_error)
|
|
107
|
-
end
|
|
88
|
+
complete_execution_with_attempts(
|
|
89
|
+
execution,
|
|
90
|
+
attempt_tracker: attempt_tracker,
|
|
91
|
+
completed_at: Time.current,
|
|
92
|
+
status: "error",
|
|
93
|
+
error: e
|
|
94
|
+
)
|
|
95
|
+
@status_update_completed = true
|
|
96
|
+
rescue => completion_err
|
|
97
|
+
completion_error = completion_err
|
|
98
|
+
end
|
|
99
|
+
raise
|
|
100
|
+
ensure
|
|
101
|
+
# Only run emergency fallback if we haven't completed AND we're not in success path
|
|
102
|
+
# The success path completion happens AFTER the catch block
|
|
103
|
+
unless @status_update_completed || !$!
|
|
104
|
+
actual_error = completion_error || raised_exception || $!
|
|
105
|
+
mark_execution_failed!(execution, error: actual_error)
|
|
108
106
|
end
|
|
109
107
|
end
|
|
110
108
|
|
|
@@ -119,7 +117,7 @@ module RubyLLM
|
|
|
119
117
|
status: "success"
|
|
120
118
|
)
|
|
121
119
|
@status_update_completed = true
|
|
122
|
-
rescue
|
|
120
|
+
rescue => e
|
|
123
121
|
Rails.logger.error("[RubyLLM::Agents] Failed to complete successful execution: #{e.class}: #{e.message}")
|
|
124
122
|
mark_execution_failed!(execution, error: e)
|
|
125
123
|
end
|
|
@@ -165,7 +163,7 @@ module RubyLLM
|
|
|
165
163
|
response: @last_response
|
|
166
164
|
)
|
|
167
165
|
@status_update_completed = true
|
|
168
|
-
rescue
|
|
166
|
+
rescue => e
|
|
169
167
|
completion_error = e
|
|
170
168
|
# Don't re-raise - let ensure block handle via mark_execution_failed!
|
|
171
169
|
end
|
|
@@ -181,11 +179,11 @@ module RubyLLM
|
|
|
181
179
|
error: e
|
|
182
180
|
)
|
|
183
181
|
@status_update_completed = true
|
|
184
|
-
rescue
|
|
182
|
+
rescue => completion_err
|
|
185
183
|
completion_error = completion_err
|
|
186
184
|
end
|
|
187
185
|
raise
|
|
188
|
-
rescue
|
|
186
|
+
rescue => e
|
|
189
187
|
raised_exception = e
|
|
190
188
|
begin
|
|
191
189
|
complete_execution(
|
|
@@ -195,7 +193,7 @@ module RubyLLM
|
|
|
195
193
|
error: e
|
|
196
194
|
)
|
|
197
195
|
@status_update_completed = true
|
|
198
|
-
rescue
|
|
196
|
+
rescue => completion_err
|
|
199
197
|
completion_error = completion_err
|
|
200
198
|
end
|
|
201
199
|
raise
|
|
@@ -258,7 +256,7 @@ module RubyLLM
|
|
|
258
256
|
# Add fallback chain tracking (count only on execution, chain stored in detail)
|
|
259
257
|
if fallback_chain.any?
|
|
260
258
|
execution_data[:attempts_count] = 0
|
|
261
|
-
@_pending_detail_data = {
|
|
259
|
+
@_pending_detail_data = {fallback_chain: fallback_chain, attempts: []}
|
|
262
260
|
end
|
|
263
261
|
|
|
264
262
|
# Add tenant_id if multi-tenancy is enabled
|
|
@@ -285,7 +283,7 @@ module RubyLLM
|
|
|
285
283
|
execution.create_detail!(detail_data) if has_data
|
|
286
284
|
|
|
287
285
|
execution
|
|
288
|
-
rescue
|
|
286
|
+
rescue => e
|
|
289
287
|
# Log error but don't fail the agent execution itself
|
|
290
288
|
Rails.logger.error("[RubyLLM::Agents] Failed to create execution record: #{e.message}")
|
|
291
289
|
nil
|
|
@@ -356,7 +354,7 @@ module RubyLLM
|
|
|
356
354
|
begin
|
|
357
355
|
execution.calculate_costs!
|
|
358
356
|
execution.save!
|
|
359
|
-
rescue
|
|
357
|
+
rescue => cost_error
|
|
360
358
|
Rails.logger.warn("[RubyLLM::Agents] Cost calculation failed: #{cost_error.message}")
|
|
361
359
|
end
|
|
362
360
|
end
|
|
@@ -364,12 +362,12 @@ module RubyLLM
|
|
|
364
362
|
# Record token usage for budget tracking
|
|
365
363
|
record_token_usage(execution)
|
|
366
364
|
rescue ActiveRecord::RecordInvalid => e
|
|
367
|
-
Rails.logger.error("[RubyLLM::Agents] Validation failed for execution #{execution&.id}: #{e.record.errors.full_messages.join(
|
|
365
|
+
Rails.logger.error("[RubyLLM::Agents] Validation failed for execution #{execution&.id}: #{e.record.errors.full_messages.join(", ")}")
|
|
368
366
|
if Rails.env.development? || Rails.env.test?
|
|
369
367
|
Rails.logger.error("[RubyLLM::Agents] Update data: #{update_data.inspect}")
|
|
370
368
|
end
|
|
371
369
|
raise
|
|
372
|
-
rescue
|
|
370
|
+
rescue => e
|
|
373
371
|
Rails.logger.error("[RubyLLM::Agents] Failed to update execution record #{execution&.id}: #{e.class}: #{e.message}")
|
|
374
372
|
if Rails.env.development? || Rails.env.test?
|
|
375
373
|
Rails.logger.error("[RubyLLM::Agents] Update data: #{update_data.inspect}")
|
|
@@ -465,7 +463,7 @@ module RubyLLM
|
|
|
465
463
|
begin
|
|
466
464
|
execution.aggregate_attempt_costs!
|
|
467
465
|
execution.save!
|
|
468
|
-
rescue
|
|
466
|
+
rescue => cost_error
|
|
469
467
|
Rails.logger.warn("[RubyLLM::Agents] Cost calculation failed: #{cost_error.message}")
|
|
470
468
|
end
|
|
471
469
|
end
|
|
@@ -473,12 +471,12 @@ module RubyLLM
|
|
|
473
471
|
# Record token usage for budget tracking
|
|
474
472
|
record_token_usage(execution)
|
|
475
473
|
rescue ActiveRecord::RecordInvalid => e
|
|
476
|
-
Rails.logger.error("[RubyLLM::Agents] Validation failed for execution #{execution&.id}: #{e.record.errors.full_messages.join(
|
|
474
|
+
Rails.logger.error("[RubyLLM::Agents] Validation failed for execution #{execution&.id}: #{e.record.errors.full_messages.join(", ")}")
|
|
477
475
|
if Rails.env.development? || Rails.env.test?
|
|
478
476
|
Rails.logger.error("[RubyLLM::Agents] Update data: #{update_data.inspect}")
|
|
479
477
|
end
|
|
480
478
|
raise
|
|
481
|
-
rescue
|
|
479
|
+
rescue => e
|
|
482
480
|
Rails.logger.error("[RubyLLM::Agents] Failed to update execution record #{execution&.id}: #{e.class}: #{e.message}")
|
|
483
481
|
if Rails.env.development? || Rails.env.test?
|
|
484
482
|
Rails.logger.error("[RubyLLM::Agents] Update data: #{update_data.inspect}")
|
|
@@ -624,7 +622,7 @@ module RubyLLM
|
|
|
624
622
|
# @return [String, nil] The system prompt or nil if unavailable
|
|
625
623
|
def safe_system_prompt
|
|
626
624
|
respond_to?(:system_prompt) ? system_prompt.to_s : nil
|
|
627
|
-
rescue
|
|
625
|
+
rescue => e
|
|
628
626
|
Rails.logger.warn("[RubyLLM::Agents] Could not capture system_prompt: #{e.message}")
|
|
629
627
|
nil
|
|
630
628
|
end
|
|
@@ -634,7 +632,7 @@ module RubyLLM
|
|
|
634
632
|
# @return [String, nil] The user prompt or nil if unavailable
|
|
635
633
|
def safe_user_prompt
|
|
636
634
|
respond_to?(:user_prompt) ? user_prompt.to_s : nil
|
|
637
|
-
rescue
|
|
635
|
+
rescue => e
|
|
638
636
|
Rails.logger.warn("[RubyLLM::Agents] Could not capture user_prompt: #{e.message}")
|
|
639
637
|
nil
|
|
640
638
|
end
|
|
@@ -644,7 +642,7 @@ module RubyLLM
|
|
|
644
642
|
# @return [String, nil] The assistant prompt or nil if unavailable
|
|
645
643
|
def safe_assistant_prompt
|
|
646
644
|
respond_to?(:assistant_prompt) ? assistant_prompt&.to_s : nil
|
|
647
|
-
rescue
|
|
645
|
+
rescue => e
|
|
648
646
|
Rails.logger.warn("[RubyLLM::Agents] Could not capture assistant_prompt: #{e.message}")
|
|
649
647
|
nil
|
|
650
648
|
end
|
|
@@ -665,7 +663,7 @@ module RubyLLM
|
|
|
665
663
|
def safe_response_value(response, method, default = nil)
|
|
666
664
|
return default unless response.respond_to?(method)
|
|
667
665
|
response.public_send(method)
|
|
668
|
-
rescue
|
|
666
|
+
rescue
|
|
669
667
|
default
|
|
670
668
|
end
|
|
671
669
|
|
|
@@ -702,7 +700,7 @@ module RubyLLM
|
|
|
702
700
|
# @return [String, nil] Normalized finish reason
|
|
703
701
|
def safe_extract_finish_reason(response)
|
|
704
702
|
reason = safe_response_value(response, :finish_reason) ||
|
|
705
|
-
|
|
703
|
+
safe_response_value(response, :stop_reason)
|
|
706
704
|
return nil unless reason
|
|
707
705
|
|
|
708
706
|
# Normalize to standard values
|
|
@@ -850,7 +848,7 @@ module RubyLLM
|
|
|
850
848
|
if tool_call.respond_to?(:to_h)
|
|
851
849
|
tool_call.to_h
|
|
852
850
|
else
|
|
853
|
-
{
|
|
851
|
+
{id: id, name: tool_call[:name], arguments: tool_call[:arguments]}
|
|
854
852
|
end
|
|
855
853
|
end
|
|
856
854
|
end
|
|
@@ -919,7 +917,7 @@ module RubyLLM
|
|
|
919
917
|
}
|
|
920
918
|
execution.create_detail!(detail_data) if detail_data.values.any?(&:present?)
|
|
921
919
|
end
|
|
922
|
-
rescue
|
|
920
|
+
rescue => e
|
|
923
921
|
Rails.logger.error("[RubyLLM::Agents] Failed to record cache hit execution: #{e.message}")
|
|
924
922
|
end
|
|
925
923
|
|
|
@@ -940,7 +938,7 @@ module RubyLLM
|
|
|
940
938
|
tenant_id: tenant_id,
|
|
941
939
|
tenant_config: tenant_config
|
|
942
940
|
)
|
|
943
|
-
rescue
|
|
941
|
+
rescue => e
|
|
944
942
|
Rails.logger.warn("[RubyLLM::Agents] Failed to record token usage: #{e.message}")
|
|
945
943
|
end
|
|
946
944
|
end
|
|
@@ -956,7 +954,7 @@ module RubyLLM
|
|
|
956
954
|
|
|
957
955
|
@_assistant_prompt_column_exists = begin
|
|
958
956
|
RubyLLM::Agents::ExecutionDetail.column_names.include?("assistant_prompt")
|
|
959
|
-
rescue
|
|
957
|
+
rescue
|
|
960
958
|
false
|
|
961
959
|
end
|
|
962
960
|
end
|
|
@@ -1001,16 +999,16 @@ module RubyLLM
|
|
|
1001
999
|
|
|
1002
1000
|
# Store error_message in detail table (best-effort)
|
|
1003
1001
|
begin
|
|
1004
|
-
detail_attrs = {
|
|
1002
|
+
detail_attrs = {error_message: error_message.to_s.truncate(65535)}
|
|
1005
1003
|
if execution.detail
|
|
1006
1004
|
execution.detail.update_columns(detail_attrs)
|
|
1007
1005
|
else
|
|
1008
1006
|
RubyLLM::Agents::ExecutionDetail.create!(detail_attrs.merge(execution_id: execution.id))
|
|
1009
1007
|
end
|
|
1010
|
-
rescue
|
|
1008
|
+
rescue
|
|
1011
1009
|
# Non-critical — error_class on execution is sufficient for filtering
|
|
1012
1010
|
end
|
|
1013
|
-
rescue
|
|
1011
|
+
rescue => e
|
|
1014
1012
|
Rails.logger.error("[RubyLLM::Agents] CRITICAL: Failed emergency status update for execution #{execution&.id}: #{e.message}")
|
|
1015
1013
|
end
|
|
1016
1014
|
end
|
|
@@ -70,9 +70,9 @@ module RubyLLM
|
|
|
70
70
|
included do
|
|
71
71
|
# Link to gem's Tenant model via polymorphic association
|
|
72
72
|
has_one :llm_tenant_record,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
class_name: "RubyLLM::Agents::Tenant",
|
|
74
|
+
as: :tenant_record,
|
|
75
|
+
dependent: :destroy
|
|
76
76
|
|
|
77
77
|
# Backward compatible alias (llm_budget points to same Tenant record)
|
|
78
78
|
# @deprecated Use llm_tenant_record instead
|
|
@@ -106,10 +106,10 @@ module RubyLLM
|
|
|
106
106
|
|
|
107
107
|
# Executions tracked for this tenant via tenant_id string column
|
|
108
108
|
has_many :llm_executions,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
class_name: "RubyLLM::Agents::Execution",
|
|
110
|
+
foreign_key: :tenant_id,
|
|
111
|
+
primary_key: id,
|
|
112
|
+
dependent: :nullify
|
|
113
113
|
|
|
114
114
|
# Auto-create tenant record callback
|
|
115
115
|
after_create :create_default_llm_tenant if llm_tenant_options[:budget]
|
|
@@ -56,7 +56,7 @@ module RubyLLM
|
|
|
56
56
|
#
|
|
57
57
|
module Base
|
|
58
58
|
# Regex pattern to extract {placeholder} parameters from prompt strings
|
|
59
|
-
PLACEHOLDER_PATTERN = /\{(\w+)\}
|
|
59
|
+
PLACEHOLDER_PATTERN = /\{(\w+)\}/
|
|
60
60
|
|
|
61
61
|
# @!group Configuration DSL
|
|
62
62
|
|
|
@@ -306,7 +306,7 @@ module RubyLLM
|
|
|
306
306
|
# @return [String] The default model
|
|
307
307
|
def default_model
|
|
308
308
|
RubyLLM::Agents.configuration.default_model
|
|
309
|
-
rescue
|
|
309
|
+
rescue
|
|
310
310
|
"gpt-4o"
|
|
311
311
|
end
|
|
312
312
|
|
|
@@ -315,7 +315,7 @@ module RubyLLM
|
|
|
315
315
|
# @return [Integer] The default timeout
|
|
316
316
|
def default_timeout
|
|
317
317
|
RubyLLM::Agents.configuration.default_timeout
|
|
318
|
-
rescue
|
|
318
|
+
rescue
|
|
319
319
|
120
|
|
320
320
|
end
|
|
321
321
|
end
|