ruby_llm-agents 3.5.4 → 3.6.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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -0
  3. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +155 -10
  4. data/app/helpers/ruby_llm/agents/application_helper.rb +15 -1
  5. data/app/models/ruby_llm/agents/execution/replayable.rb +124 -0
  6. data/app/models/ruby_llm/agents/execution/scopes.rb +42 -1
  7. data/app/models/ruby_llm/agents/execution.rb +50 -1
  8. data/app/models/ruby_llm/agents/tenant/budgetable.rb +28 -4
  9. data/app/views/layouts/ruby_llm/agents/application.html.erb +41 -28
  10. data/app/views/ruby_llm/agents/agents/show.html.erb +16 -1
  11. data/app/views/ruby_llm/agents/dashboard/_top_tenants.html.erb +47 -0
  12. data/app/views/ruby_llm/agents/dashboard/index.html.erb +397 -100
  13. data/lib/generators/ruby_llm_agents/rename_agent_generator.rb +53 -0
  14. data/lib/generators/ruby_llm_agents/templates/rename_agent_migration.rb.tt +19 -0
  15. data/lib/ruby_llm/agents/agent_tool.rb +125 -0
  16. data/lib/ruby_llm/agents/audio/speaker.rb +5 -3
  17. data/lib/ruby_llm/agents/audio/speech_pricing.rb +63 -187
  18. data/lib/ruby_llm/agents/audio/transcriber.rb +5 -3
  19. data/lib/ruby_llm/agents/audio/transcription_pricing.rb +5 -7
  20. data/lib/ruby_llm/agents/base_agent.rb +144 -5
  21. data/lib/ruby_llm/agents/core/configuration.rb +178 -53
  22. data/lib/ruby_llm/agents/core/errors.rb +3 -77
  23. data/lib/ruby_llm/agents/core/instrumentation.rb +0 -17
  24. data/lib/ruby_llm/agents/core/version.rb +1 -1
  25. data/lib/ruby_llm/agents/dsl/base.rb +0 -8
  26. data/lib/ruby_llm/agents/dsl/queryable.rb +124 -0
  27. data/lib/ruby_llm/agents/dsl.rb +1 -0
  28. data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +2 -1
  29. data/lib/ruby_llm/agents/image/generator/pricing.rb +75 -217
  30. data/lib/ruby_llm/agents/image/generator.rb +5 -3
  31. data/lib/ruby_llm/agents/infrastructure/attempt_tracker.rb +8 -0
  32. data/lib/ruby_llm/agents/infrastructure/circuit_breaker.rb +4 -2
  33. data/lib/ruby_llm/agents/pipeline/builder.rb +43 -0
  34. data/lib/ruby_llm/agents/pipeline/context.rb +11 -1
  35. data/lib/ruby_llm/agents/pipeline/executor.rb +1 -25
  36. data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +26 -1
  37. data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +18 -0
  38. data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +130 -3
  39. data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +29 -0
  40. data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +11 -4
  41. data/lib/ruby_llm/agents/pipeline.rb +0 -92
  42. data/lib/ruby_llm/agents/results/background_removal_result.rb +11 -1
  43. data/lib/ruby_llm/agents/results/base.rb +23 -1
  44. data/lib/ruby_llm/agents/results/embedding_result.rb +14 -1
  45. data/lib/ruby_llm/agents/results/image_analysis_result.rb +11 -1
  46. data/lib/ruby_llm/agents/results/image_edit_result.rb +11 -1
  47. data/lib/ruby_llm/agents/results/image_generation_result.rb +12 -3
  48. data/lib/ruby_llm/agents/results/image_pipeline_result.rb +11 -1
  49. data/lib/ruby_llm/agents/results/image_transform_result.rb +11 -1
  50. data/lib/ruby_llm/agents/results/image_upscale_result.rb +11 -1
  51. data/lib/ruby_llm/agents/results/image_variation_result.rb +11 -1
  52. data/lib/ruby_llm/agents/results/speech_result.rb +20 -1
  53. data/lib/ruby_llm/agents/results/transcription_result.rb +20 -1
  54. data/lib/ruby_llm/agents/text/embedder.rb +23 -18
  55. data/lib/ruby_llm/agents.rb +70 -5
  56. data/lib/tasks/ruby_llm_agents.rake +21 -0
  57. metadata +7 -6
  58. data/lib/ruby_llm/agents/infrastructure/reliability/breaker_manager.rb +0 -80
  59. data/lib/ruby_llm/agents/infrastructure/reliability/execution_constraints.rb +0 -69
  60. data/lib/ruby_llm/agents/infrastructure/reliability/executor.rb +0 -125
  61. data/lib/ruby_llm/agents/infrastructure/reliability/fallback_routing.rb +0 -72
  62. data/lib/ruby_llm/agents/infrastructure/reliability/retry_strategy.rb +0 -82
@@ -46,6 +46,7 @@ module RubyLLM
46
46
  extend DSL::Base
47
47
  extend DSL::Reliability
48
48
  extend DSL::Caching
49
+ extend DSL::Queryable
49
50
  include CacheHelper
50
51
 
51
52
  class << self
@@ -119,6 +120,83 @@ module RubyLLM
119
120
  :conversation
120
121
  end
121
122
 
123
+ # Declares previous class names for this agent
124
+ #
125
+ # When an agent is renamed, old execution records still reference the
126
+ # previous class name. Declaring aliases allows scopes, analytics, and
127
+ # budget checks to automatically include records from all previous names.
128
+ #
129
+ # @param names [Array<String>] Previous class names
130
+ # @return [Array<String>] All declared aliases
131
+ #
132
+ # @example
133
+ # class SupportBot < ApplicationAgent
134
+ # aliases "CustomerSupportAgent", "HelpDeskAgent"
135
+ # end
136
+ def aliases(*names)
137
+ if names.any?
138
+ @agent_aliases = names.map(&:to_s)
139
+ end
140
+ @agent_aliases || []
141
+ end
142
+
143
+ # Returns all known names for this agent (current + aliases)
144
+ #
145
+ # @return [Array<String>] Current name followed by any aliases
146
+ def all_agent_names
147
+ [name, *aliases].compact.uniq
148
+ end
149
+
150
+ # Returns a summary of the agent's DSL configuration
151
+ #
152
+ # Useful for debugging in the Rails console to see how an agent
153
+ # is configured without instantiating it.
154
+ #
155
+ # @return [Hash] Agent configuration summary
156
+ # @example
157
+ # MyAgent.config_summary
158
+ def config_summary
159
+ {
160
+ agent_type: agent_type,
161
+ model: model,
162
+ temperature: temperature,
163
+ timeout: timeout,
164
+ streaming: streaming,
165
+ system_prompt: system_config,
166
+ user_prompt: user_config,
167
+ assistant_prompt: assistant_config,
168
+ description: description,
169
+ schema: schema&.respond_to?(:name) ? schema.name : schema&.class&.name,
170
+ tools: tools.map { |t| t.respond_to?(:name) ? t.name : t.to_s },
171
+ parameters: params.transform_values { |v| v.slice(:type, :required, :default, :desc) },
172
+ thinking: thinking_config,
173
+ caching: caching_config,
174
+ reliability: reliability_configured? ? reliability_config : nil
175
+ }.compact
176
+ end
177
+
178
+ # @!group Custom Middleware DSL
179
+
180
+ # Registers a custom middleware for this agent class
181
+ #
182
+ # @param middleware_class [Class] Must inherit from Pipeline::Middleware::Base
183
+ # @param before [Class, nil] Insert before this built-in middleware
184
+ # @param after [Class, nil] Insert after this built-in middleware
185
+ # @return [void]
186
+ def use_middleware(middleware_class, before: nil, after: nil)
187
+ @agent_middleware ||= []
188
+ @agent_middleware << {klass: middleware_class, before: before, after: after}
189
+ end
190
+
191
+ # Returns custom middleware registered on this agent (including inherited)
192
+ #
193
+ # @return [Array<Hash>] Middleware entries with :klass, :before, :after keys
194
+ def agent_middleware
195
+ @agent_middleware || (superclass.respond_to?(:agent_middleware) ? superclass.agent_middleware : []) || []
196
+ end
197
+
198
+ # @!endgroup
199
+
122
200
  # @!group Parameter DSL
123
201
 
124
202
  # Defines a parameter for the agent
@@ -131,9 +209,9 @@ module RubyLLM
131
209
  # @param default [Object, nil] Default value if not provided
132
210
  # @param type [Class, nil] Optional type for validation
133
211
  # @return [void]
134
- def param(name, required: false, default: nil, type: nil)
212
+ def param(name, required: false, default: nil, type: nil, desc: nil, description: nil)
135
213
  @params ||= {}
136
- @params[name] = {required: required, default: default, type: type}
214
+ @params[name] = {required: required, default: default, type: type, desc: desc || description}
137
215
  define_method(name) do
138
216
  @options[name] || @options[name.to_s] || self.class.params.dig(name, :default)
139
217
  end
@@ -255,6 +333,8 @@ module RubyLLM
255
333
  # @param options [Hash] Agent parameters defined via the param DSL
256
334
  def initialize(model: self.class.model, temperature: self.class.temperature, **options)
257
335
  @ask_message = options.delete(:_ask_message)
336
+ @parent_execution_id = options.delete(:_parent_execution_id)
337
+ @root_execution_id = options.delete(:_root_execution_id)
258
338
  @model = model
259
339
  @temperature = temperature
260
340
  @options = options
@@ -347,7 +427,7 @@ module RubyLLM
347
427
  content = response.content
348
428
  return content unless content.is_a?(Hash)
349
429
 
350
- content.transform_keys(&:to_sym)
430
+ content.deep_symbolize_keys
351
431
  end
352
432
 
353
433
  # @!endgroup
@@ -424,6 +504,8 @@ module RubyLLM
424
504
  tenant: resolve_tenant,
425
505
  skip_cache: @options[:skip_cache],
426
506
  stream_block: (block if streaming_enabled?),
507
+ parent_execution_id: @parent_execution_id,
508
+ root_execution_id: @root_execution_id,
427
509
  options: execution_options
428
510
  )
429
511
  end
@@ -463,13 +545,62 @@ module RubyLLM
463
545
 
464
546
  # Resolves tools for this execution
465
547
  #
548
+ # Agent classes in the tools list are automatically wrapped as
549
+ # RubyLLM::Tool subclasses via AgentTool.for. Regular tool classes
550
+ # pass through unchanged.
551
+ #
466
552
  # @return [Array<Class>] Tool classes to use
553
+ # @raise [ArgumentError] If duplicate tool names are detected
467
554
  def resolved_tools
468
- if self.class.method_defined?(:tools, false)
555
+ raw = if self.class.method_defined?(:tools, false)
469
556
  tools
470
557
  else
471
558
  self.class.tools
472
559
  end
560
+
561
+ wrapped = raw.map { |tool_class| wrap_if_agent(tool_class) }
562
+ detect_duplicate_tool_names!(wrapped)
563
+ wrapped
564
+ end
565
+
566
+ # Wraps an agent class as a tool, or returns the tool class as-is.
567
+ #
568
+ # @param tool_class [Class] A tool or agent class
569
+ # @return [Class] The original or wrapped class
570
+ def wrap_if_agent(tool_class)
571
+ if tool_class.respond_to?(:ancestors) && tool_class.ancestors.include?(RubyLLM::Agents::BaseAgent)
572
+ AgentTool.for(tool_class)
573
+ else
574
+ tool_class
575
+ end
576
+ end
577
+
578
+ # Raises if two tools resolve to the same name.
579
+ #
580
+ # @param tools [Array] Resolved tool classes or instances
581
+ # @raise [ArgumentError] On duplicate names
582
+ def detect_duplicate_tool_names!(tools)
583
+ names = tools.map { |t| tool_name_for(t) }
584
+ duplicates = names.group_by(&:itself).select { |_, v| v.size > 1 }.keys
585
+ raise ArgumentError, "Duplicate tool names: #{duplicates.join(", ")}" if duplicates.any?
586
+ end
587
+
588
+ # Extracts a tool name from a tool class or instance.
589
+ #
590
+ # @param tool [Class, Object] A tool class or instance
591
+ # @return [String] The tool name
592
+ def tool_name_for(tool)
593
+ return tool.tool_name if tool.respond_to?(:tool_name)
594
+
595
+ if tool.is_a?(Class) && tool < RubyLLM::Tool
596
+ tool.new.name
597
+ elsif tool.is_a?(Class)
598
+ tool.name.to_s
599
+ elsif tool.respond_to?(:name)
600
+ tool.name.to_s
601
+ else
602
+ tool.to_s
603
+ end
473
604
  end
474
605
 
475
606
  # Resolves messages for this execution
@@ -581,10 +712,17 @@ module RubyLLM
581
712
  # @return [void] Sets context.output with the result
582
713
  def execute(context)
583
714
  client = build_client(context)
715
+
716
+ # Make context available to AgentTool instances during tool execution
717
+ previous_context = Thread.current[:ruby_llm_agents_caller_context]
718
+ Thread.current[:ruby_llm_agents_caller_context] = context
719
+
584
720
  response = execute_llm_call(client, context)
585
721
  capture_response(response, context)
586
722
  result = build_result(process_response(response), response, context)
587
723
  context.output = result
724
+ ensure
725
+ Thread.current[:ruby_llm_agents_caller_context] = previous_context
588
726
  end
589
727
 
590
728
  # Builds and configures the RubyLLM client
@@ -761,7 +899,8 @@ module RubyLLM
761
899
  time_to_first_token_ms: context.time_to_first_token_ms,
762
900
  finish_reason: context.finish_reason,
763
901
  streaming: streaming_enabled?,
764
- attempts_count: context.attempts_made || 1
902
+ attempts_count: context.attempts_made || 1,
903
+ execution_id: context.execution_id
765
904
  )
766
905
  end
767
906
 
@@ -308,13 +308,6 @@ module RubyLLM
308
308
  # @example
309
309
  # config.track_speech = false
310
310
 
311
- # @!attribute [rw] async_max_concurrency
312
- # Maximum number of concurrent async operations when using batch processing.
313
- # Controls the semaphore limit for Async::Semaphore.
314
- # @return [Integer] Max concurrent operations (default: 10)
315
- # @example
316
- # config.async_max_concurrency = 20
317
-
318
311
  # @!attribute [rw] root_directory
319
312
  # The root directory name under app/ for all agent components.
320
313
  # This allows customization of the directory structure.
@@ -339,18 +332,6 @@ module RubyLLM
339
332
  # @example
340
333
  # config.tool_result_max_length = 5000
341
334
 
342
- # @!attribute [rw] redaction
343
- # Configuration for PII and sensitive data redaction.
344
- # When set, sensitive data is redacted before storing in execution records.
345
- # @return [Hash, nil] Redaction config with :fields, :patterns, :placeholder, :max_value_length keys
346
- # @example
347
- # config.redaction = {
348
- # fields: %w[ssn credit_card phone_number email],
349
- # patterns: [/\b\d{3}-\d{2}-\d{4}\b/],
350
- # placeholder: "[REDACTED]",
351
- # max_value_length: 5000
352
- # }
353
-
354
335
  # API key and provider attributes forwarded to RubyLLM.
355
336
  # These let users configure everything in one place through
356
337
  # RubyLLM::Agents.configure instead of a separate RubyLLM.configure block.
@@ -391,12 +372,12 @@ module RubyLLM
391
372
  end
392
373
 
393
374
  # Attributes with custom setters (validation) — only readers here
394
- attr_reader :default_embedding_dimensions, :default_embedding_batch_size
375
+ attr_reader :default_embedding_dimensions, :default_embedding_batch_size,
376
+ :middleware_stack
395
377
 
396
378
  # Attributes without validation (simple accessors)
397
379
  attr_accessor :default_model,
398
380
  :async_logging,
399
- :async_max_concurrency,
400
381
  :retention_period,
401
382
  :dashboard_parent_controller,
402
383
  :basic_auth_username,
@@ -452,7 +433,6 @@ module RubyLLM
452
433
  :root_directory,
453
434
  :root_namespace,
454
435
  :tool_result_max_length,
455
- :redaction,
456
436
  :persist_audio_data,
457
437
  :elevenlabs_base_cost_per_1k,
458
438
  :elevenlabs_models_cache_ttl,
@@ -633,7 +613,6 @@ module RubyLLM
633
613
  @default_timeout = 60
634
614
  @cache_store = nil
635
615
  @async_logging = true
636
- @async_max_concurrency = 10
637
616
  @retention_period = 30.days
638
617
  @anomaly_cost_threshold = 5.00
639
618
  @anomaly_duration_threshold = 10_000
@@ -711,7 +690,7 @@ module RubyLLM
711
690
 
712
691
  # TTS pricing defaults
713
692
  @tts_model_pricing = {}
714
- @default_tts_cost = 0.015
693
+ @default_tts_cost = nil
715
694
 
716
695
  # Execution/conversation agent tracking
717
696
  @track_executions = true
@@ -734,7 +713,7 @@ module RubyLLM
734
713
  # Image Pricing defaults
735
714
  @litellm_pricing_url = nil # Use default from Pricing module
736
715
  @litellm_pricing_cache_ttl = nil # Use default (24 hours)
737
- @default_image_cost = 0.04 # Fallback cost per image
716
+ @default_image_cost = nil # User-configurable fallback cost per image
738
717
  @image_model_pricing = {} # User-defined pricing overrides
739
718
 
740
719
  # Phase 2: Image Variation, Editing, Transformation, Upscaling defaults
@@ -756,17 +735,18 @@ module RubyLLM
756
735
  @root_directory = "agents" # Root directory under app/
757
736
  @root_namespace = nil # No namespace (top-level classes)
758
737
 
738
+ # Custom middleware stack
739
+ @middleware_stack = []
740
+
759
741
  # Tool tracking defaults
760
742
  @tool_result_max_length = 10_000
761
743
 
762
- # Redaction defaults (disabled by default)
763
- @redaction = nil
764
-
765
744
  # Audio data persistence (disabled by default — base64 audio can be large)
766
745
  @persist_audio_data = false
767
746
 
768
- # ElevenLabs dynamic pricing: base cost per 1K characters (Pro plan overage rate)
769
- @elevenlabs_base_cost_per_1k = 0.30
747
+ # ElevenLabs dynamic pricing: base cost per 1K characters
748
+ # Set to your plan's overage rate (e.g., 0.30 for Pro) to enable API-based pricing
749
+ @elevenlabs_base_cost_per_1k = nil
770
750
  # ElevenLabs models cache TTL in seconds (6 hours)
771
751
  @elevenlabs_models_cache_ttl = 21_600
772
752
  end
@@ -801,6 +781,27 @@ module RubyLLM
801
781
  default_retryable_patterns.values.flatten.uniq
802
782
  end
803
783
 
784
+ # Registers a custom middleware class for all agents
785
+ #
786
+ # @param middleware_class [Class] Must inherit from Pipeline::Middleware::Base
787
+ # @param before [Class, nil] Insert before this built-in middleware
788
+ # @param after [Class, nil] Insert after this built-in middleware
789
+ # @return [self] For method chaining
790
+ # @raise [ArgumentError] If middleware_class is invalid
791
+ def use_middleware(middleware_class, before: nil, after: nil)
792
+ validate_middleware_class!(middleware_class)
793
+ @middleware_stack << {klass: middleware_class, before: before, after: after}
794
+ self
795
+ end
796
+
797
+ # Removes all custom middleware
798
+ #
799
+ # @return [self] For method chaining
800
+ def clear_middleware!
801
+ @middleware_stack = []
802
+ self
803
+ end
804
+
804
805
  # Returns whether multi-tenancy is enabled
805
806
  #
806
807
  # @return [Boolean] true if multi-tenancy is enabled
@@ -889,6 +890,136 @@ module RubyLLM
889
890
  end
890
891
  end
891
892
 
893
+ # Attribute names that contain sensitive values (API keys, passwords)
894
+ SENSITIVE_ATTRIBUTES = (
895
+ FORWARDED_RUBY_LLM_ATTRIBUTES.select { |a| a.to_s.match?(/api_key|secret_key|session_token/) } +
896
+ %i[basic_auth_password elevenlabs_api_key]
897
+ ).freeze
898
+
899
+ # Returns all configuration as a hash grouped by category
900
+ #
901
+ # Useful for debugging in the Rails console. Sensitive values
902
+ # (API keys, passwords) are hidden by default.
903
+ #
904
+ # @param include_sensitive [Boolean] Whether to include API keys and passwords
905
+ # @return [Hash] Configuration grouped by category
906
+ # @example
907
+ # RubyLLM::Agents.configuration.to_h
908
+ # RubyLLM::Agents.configuration.to_h(include_sensitive: true)
909
+ def to_h(include_sensitive: false)
910
+ {
911
+ model: {
912
+ default_model: default_model,
913
+ default_temperature: default_temperature,
914
+ default_timeout: default_timeout,
915
+ default_streaming: default_streaming,
916
+ default_thinking: default_thinking
917
+ },
918
+ reliability: {
919
+ default_retries: default_retries,
920
+ default_fallback_models: default_fallback_models,
921
+ default_total_timeout: default_total_timeout,
922
+ default_retryable_patterns: default_retryable_patterns
923
+ },
924
+ governance: {
925
+ budgets: budgets,
926
+ on_alert: on_alert&.class&.name,
927
+ persist_prompts: persist_prompts,
928
+ persist_responses: persist_responses,
929
+ persist_messages_summary: persist_messages_summary,
930
+ messages_summary_max_length: messages_summary_max_length
931
+ },
932
+ multi_tenancy: {
933
+ enabled: multi_tenancy_enabled,
934
+ tenant_resolver: tenant_resolver&.class&.name,
935
+ tenant_config_resolver: tenant_config_resolver&.class&.name
936
+ },
937
+ dashboard: {
938
+ per_page: per_page,
939
+ recent_executions_limit: recent_executions_limit,
940
+ dashboard_parent_controller: dashboard_parent_controller,
941
+ basic_auth_username: basic_auth_username,
942
+ dashboard_auth: dashboard_auth&.class&.name
943
+ },
944
+ logging: {
945
+ async_logging: async_logging,
946
+ retention_period: retention_period,
947
+ job_retry_attempts: job_retry_attempts,
948
+ track_executions: track_executions,
949
+ track_cache_hits: track_cache_hits,
950
+ track_audio: track_audio
951
+ },
952
+ anomaly: {
953
+ anomaly_cost_threshold: anomaly_cost_threshold,
954
+ anomaly_duration_threshold: anomaly_duration_threshold
955
+ },
956
+ tools: {
957
+ default_tools: default_tools,
958
+ tool_result_max_length: tool_result_max_length
959
+ },
960
+ embedding: {
961
+ default_embedding_model: default_embedding_model,
962
+ default_embedding_dimensions: default_embedding_dimensions,
963
+ default_embedding_batch_size: default_embedding_batch_size,
964
+ track_embeddings: track_embeddings
965
+ },
966
+ transcription: {
967
+ default_transcription_model: default_transcription_model,
968
+ track_transcriptions: track_transcriptions,
969
+ transcription_model_pricing: transcription_model_pricing,
970
+ default_transcription_cost: default_transcription_cost
971
+ },
972
+ speech: {
973
+ default_tts_provider: default_tts_provider,
974
+ default_tts_model: default_tts_model,
975
+ default_tts_voice: default_tts_voice,
976
+ track_speech: track_speech,
977
+ tts_model_pricing: tts_model_pricing,
978
+ default_tts_cost: default_tts_cost,
979
+ persist_audio_data: persist_audio_data,
980
+ elevenlabs_api_base: elevenlabs_api_base,
981
+ elevenlabs_base_cost_per_1k: elevenlabs_base_cost_per_1k,
982
+ elevenlabs_models_cache_ttl: elevenlabs_models_cache_ttl
983
+ },
984
+ image: {
985
+ default_image_model: default_image_model,
986
+ default_image_size: default_image_size,
987
+ default_image_quality: default_image_quality,
988
+ default_image_style: default_image_style,
989
+ max_image_prompt_length: max_image_prompt_length,
990
+ track_image_generation: track_image_generation,
991
+ image_model_aliases: image_model_aliases,
992
+ default_image_cost: default_image_cost,
993
+ image_model_pricing: image_model_pricing,
994
+ default_variator_model: default_variator_model,
995
+ default_editor_model: default_editor_model,
996
+ default_transformer_model: default_transformer_model,
997
+ default_upscaler_model: default_upscaler_model,
998
+ default_variation_strength: default_variation_strength,
999
+ default_transform_strength: default_transform_strength,
1000
+ default_analyzer_model: default_analyzer_model,
1001
+ default_analysis_type: default_analysis_type,
1002
+ default_analyzer_max_tags: default_analyzer_max_tags,
1003
+ default_background_remover_model: default_background_remover_model,
1004
+ default_background_output_format: default_background_output_format
1005
+ },
1006
+ pricing: {
1007
+ pricing_cache_ttl: pricing_cache_ttl,
1008
+ portkey_pricing_enabled: portkey_pricing_enabled,
1009
+ openrouter_pricing_enabled: openrouter_pricing_enabled,
1010
+ helicone_pricing_enabled: helicone_pricing_enabled,
1011
+ llmpricing_enabled: llmpricing_enabled,
1012
+ litellm_pricing_url: litellm_pricing_url,
1013
+ litellm_pricing_cache_ttl: litellm_pricing_cache_ttl
1014
+ },
1015
+ directory: {
1016
+ root_directory: root_directory,
1017
+ root_namespace: root_namespace
1018
+ },
1019
+ api_keys: include_sensitive ? sensitive_api_keys : "(hidden, pass include_sensitive: true)"
1020
+ }
1021
+ end
1022
+
892
1023
  # Returns all autoload paths for LLM components
893
1024
  #
894
1025
  # @return [Array<String>] List of paths relative to Rails.root
@@ -905,36 +1036,30 @@ module RubyLLM
905
1036
  ]
906
1037
  end
907
1038
 
908
- # Returns the redaction fields (parameter names to redact)
909
- #
910
- # @return [Array<String>] Fields to redact
911
- def redaction_fields
912
- redaction&.dig(:fields) || []
913
- end
914
-
915
- # Returns the redaction regex patterns
916
- #
917
- # @return [Array<Regexp>] Patterns to match and redact
918
- def redaction_patterns
919
- redaction&.dig(:patterns) || []
920
- end
1039
+ private
921
1040
 
922
- # Returns the redaction placeholder string
1041
+ # Returns all sensitive API key values as a hash
923
1042
  #
924
- # @return [String] Placeholder for redacted values (default: "[REDACTED]")
925
- def redaction_placeholder
926
- redaction&.dig(:placeholder) || "[REDACTED]"
1043
+ # @return [Hash] API key attributes and their values
1044
+ def sensitive_api_keys
1045
+ FORWARDED_RUBY_LLM_ATTRIBUTES.each_with_object({}) do |attr, h|
1046
+ h[attr] = public_send(attr)
1047
+ end.merge(
1048
+ elevenlabs_api_key: elevenlabs_api_key,
1049
+ basic_auth_password: basic_auth_password
1050
+ )
927
1051
  end
928
1052
 
929
- # Returns the max value length for redaction
1053
+ # Validates that a middleware class inherits from Pipeline::Middleware::Base
930
1054
  #
931
- # @return [Integer, nil] Max length before truncation, or nil for no limit
932
- def redaction_max_value_length
933
- redaction&.dig(:max_value_length)
1055
+ # @param klass [Class] The class to validate
1056
+ # @raise [ArgumentError] If the class is invalid
1057
+ def validate_middleware_class!(klass)
1058
+ unless klass.is_a?(Class) && klass <= RubyLLM::Agents::Pipeline::Middleware::Base
1059
+ raise ArgumentError, "#{klass} must inherit from RubyLLM::Agents::Pipeline::Middleware::Base"
1060
+ end
934
1061
  end
935
1062
 
936
- private
937
-
938
1063
  # Validates that a value is within a range
939
1064
  #
940
1065
  # @param attr [Symbol] Attribute name for error message
@@ -5,83 +5,6 @@ module RubyLLM
5
5
  # Base error class for RubyLLM::Agents
6
6
  class Error < StandardError; end
7
7
 
8
- # ============================================================
9
- # Pipeline Errors
10
- # ============================================================
11
-
12
- # Base class for pipeline-related errors
13
- class PipelineError < Error; end
14
-
15
- # ============================================================
16
- # Reliability Errors
17
- # ============================================================
18
-
19
- # Base class for reliability-related errors
20
- class ReliabilityError < Error; end
21
-
22
- # Raised when an error is retryable (transient)
23
- class RetryableError < ReliabilityError; end
24
-
25
- # Raised when a circuit breaker is open
26
- class CircuitOpenError < ReliabilityError
27
- # @return [String] The model that has an open circuit
28
- attr_reader :model
29
-
30
- def initialize(message = nil, model: nil)
31
- @model = model
32
- super(message || "Circuit breaker is open#{" for #{model}" if model}")
33
- end
34
- end
35
-
36
- # Raised when total timeout is exceeded across all attempts
37
- class TotalTimeoutError < ReliabilityError
38
- # @return [Float] The timeout that was exceeded
39
- attr_reader :timeout
40
-
41
- # @return [Float] The elapsed time
42
- attr_reader :elapsed
43
-
44
- def initialize(message = nil, timeout: nil, elapsed: nil)
45
- @timeout = timeout
46
- @elapsed = elapsed
47
- super(message || "Total timeout of #{timeout}s exceeded (elapsed: #{elapsed&.round(2)}s)")
48
- end
49
- end
50
-
51
- # Raised when all models (primary + fallbacks) fail
52
- class AllModelsFailedError < ReliabilityError
53
- # @return [Array<Hash>] Details of each failed attempt
54
- attr_reader :attempts
55
-
56
- def initialize(message = nil, attempts: [])
57
- @attempts = attempts
58
- models = attempts.map { |a| a[:model] }.compact.join(", ")
59
- super(message || "All models failed: #{models}")
60
- end
61
- end
62
-
63
- # ============================================================
64
- # Budget Errors
65
- # ============================================================
66
-
67
- # Base class for budget-related errors
68
- class BudgetError < Error; end
69
-
70
- # Raised when budget is exceeded
71
- class BudgetExceededError < BudgetError
72
- # @return [String, nil] The tenant ID
73
- attr_reader :tenant_id
74
-
75
- # @return [String, nil] The budget type (daily, monthly, etc.)
76
- attr_reader :budget_type
77
-
78
- def initialize(message = nil, tenant_id: nil, budget_type: nil)
79
- @tenant_id = tenant_id
80
- @budget_type = budget_type
81
- super(message || "Budget exceeded#{" for tenant #{tenant_id}" if tenant_id}")
82
- end
83
- end
84
-
85
8
  # ============================================================
86
9
  # Configuration Errors
87
10
  # ============================================================
@@ -103,6 +26,9 @@ module RubyLLM
103
26
  end
104
27
  end
105
28
 
29
+ # Raised when an execution cannot be replayed
30
+ class ReplayError < Error; end
31
+
106
32
  # Raised when the TTS API returns an error response
107
33
  class SpeechApiError < Error
108
34
  attr_reader :status, :response_body
@@ -836,23 +836,6 @@ module RubyLLM
836
836
  }.compact
837
837
  end
838
838
 
839
- # Serializes tool calls to an array of hashes for storage
840
- #
841
- # @param response [RubyLLM::Message] The LLM response
842
- # @return [Array<Hash>, nil] Serialized tool calls or nil if none
843
- def serialize_tool_calls(response)
844
- tool_calls = safe_response_value(response, :tool_calls)
845
- return nil if tool_calls.nil? || tool_calls.empty?
846
-
847
- tool_calls.map do |id, tool_call|
848
- if tool_call.respond_to?(:to_h)
849
- tool_call.to_h
850
- else
851
- {id: id, name: tool_call[:name], arguments: tool_call[:arguments]}
852
- end
853
- end
854
- end
855
-
856
839
  # Records an execution for a cache hit
857
840
  #
858
841
  # Creates a minimal execution record with cache_hit: true, 0 tokens,
@@ -4,6 +4,6 @@ module RubyLLM
4
4
  module Agents
5
5
  # Current version of the RubyLLM::Agents gem
6
6
  # @return [String] Semantic version string
7
- VERSION = "3.5.4"
7
+ VERSION = "3.6.0"
8
8
  end
9
9
  end
@@ -123,14 +123,6 @@ module RubyLLM
123
123
  @user_template || inherited_or_default(:user_config, nil)
124
124
  end
125
125
 
126
- # Returns the prompt configuration (alias for user_config)
127
- #
128
- # @deprecated Use `user_config` instead
129
- # @return [String, nil] The prompt template, or nil
130
- def prompt_config
131
- user_config
132
- end
133
-
134
126
  # Sets the system prompt/instructions
135
127
  #
136
128
  # When a string is provided, {placeholder} syntax is supported for