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.
- checksums.yaml +4 -4
- data/README.md +4 -0
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +155 -10
- data/app/helpers/ruby_llm/agents/application_helper.rb +15 -1
- data/app/models/ruby_llm/agents/execution/replayable.rb +124 -0
- data/app/models/ruby_llm/agents/execution/scopes.rb +42 -1
- data/app/models/ruby_llm/agents/execution.rb +50 -1
- data/app/models/ruby_llm/agents/tenant/budgetable.rb +28 -4
- data/app/views/layouts/ruby_llm/agents/application.html.erb +41 -28
- data/app/views/ruby_llm/agents/agents/show.html.erb +16 -1
- data/app/views/ruby_llm/agents/dashboard/_top_tenants.html.erb +47 -0
- data/app/views/ruby_llm/agents/dashboard/index.html.erb +397 -100
- data/lib/generators/ruby_llm_agents/rename_agent_generator.rb +53 -0
- data/lib/generators/ruby_llm_agents/templates/rename_agent_migration.rb.tt +19 -0
- data/lib/ruby_llm/agents/agent_tool.rb +125 -0
- data/lib/ruby_llm/agents/audio/speaker.rb +5 -3
- data/lib/ruby_llm/agents/audio/speech_pricing.rb +63 -187
- data/lib/ruby_llm/agents/audio/transcriber.rb +5 -3
- data/lib/ruby_llm/agents/audio/transcription_pricing.rb +5 -7
- data/lib/ruby_llm/agents/base_agent.rb +144 -5
- data/lib/ruby_llm/agents/core/configuration.rb +178 -53
- data/lib/ruby_llm/agents/core/errors.rb +3 -77
- data/lib/ruby_llm/agents/core/instrumentation.rb +0 -17
- data/lib/ruby_llm/agents/core/version.rb +1 -1
- data/lib/ruby_llm/agents/dsl/base.rb +0 -8
- data/lib/ruby_llm/agents/dsl/queryable.rb +124 -0
- data/lib/ruby_llm/agents/dsl.rb +1 -0
- data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +2 -1
- data/lib/ruby_llm/agents/image/generator/pricing.rb +75 -217
- data/lib/ruby_llm/agents/image/generator.rb +5 -3
- data/lib/ruby_llm/agents/infrastructure/attempt_tracker.rb +8 -0
- data/lib/ruby_llm/agents/infrastructure/circuit_breaker.rb +4 -2
- data/lib/ruby_llm/agents/pipeline/builder.rb +43 -0
- data/lib/ruby_llm/agents/pipeline/context.rb +11 -1
- data/lib/ruby_llm/agents/pipeline/executor.rb +1 -25
- data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +26 -1
- data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +18 -0
- data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +130 -3
- data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +29 -0
- data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +11 -4
- data/lib/ruby_llm/agents/pipeline.rb +0 -92
- data/lib/ruby_llm/agents/results/background_removal_result.rb +11 -1
- data/lib/ruby_llm/agents/results/base.rb +23 -1
- data/lib/ruby_llm/agents/results/embedding_result.rb +14 -1
- data/lib/ruby_llm/agents/results/image_analysis_result.rb +11 -1
- data/lib/ruby_llm/agents/results/image_edit_result.rb +11 -1
- data/lib/ruby_llm/agents/results/image_generation_result.rb +12 -3
- data/lib/ruby_llm/agents/results/image_pipeline_result.rb +11 -1
- data/lib/ruby_llm/agents/results/image_transform_result.rb +11 -1
- data/lib/ruby_llm/agents/results/image_upscale_result.rb +11 -1
- data/lib/ruby_llm/agents/results/image_variation_result.rb +11 -1
- data/lib/ruby_llm/agents/results/speech_result.rb +20 -1
- data/lib/ruby_llm/agents/results/transcription_result.rb +20 -1
- data/lib/ruby_llm/agents/text/embedder.rb +23 -18
- data/lib/ruby_llm/agents.rb +70 -5
- data/lib/tasks/ruby_llm_agents.rake +21 -0
- metadata +7 -6
- data/lib/ruby_llm/agents/infrastructure/reliability/breaker_manager.rb +0 -80
- data/lib/ruby_llm/agents/infrastructure/reliability/execution_constraints.rb +0 -69
- data/lib/ruby_llm/agents/infrastructure/reliability/executor.rb +0 -125
- data/lib/ruby_llm/agents/infrastructure/reliability/fallback_routing.rb +0 -72
- 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.
|
|
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 =
|
|
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 =
|
|
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
|
|
769
|
-
|
|
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
|
-
|
|
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
|
|
1041
|
+
# Returns all sensitive API key values as a hash
|
|
923
1042
|
#
|
|
924
|
-
# @return [
|
|
925
|
-
def
|
|
926
|
-
|
|
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
|
-
#
|
|
1053
|
+
# Validates that a middleware class inherits from Pipeline::Middleware::Base
|
|
930
1054
|
#
|
|
931
|
-
# @
|
|
932
|
-
|
|
933
|
-
|
|
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,
|
|
@@ -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
|