ruby_llm-agents 0.5.0 → 1.0.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 +189 -31
- data/app/controllers/ruby_llm/agents/agents_controller.rb +136 -16
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +29 -9
- data/app/controllers/ruby_llm/agents/workflows_controller.rb +355 -0
- data/app/helpers/ruby_llm/agents/application_helper.rb +25 -0
- data/app/models/ruby_llm/agents/execution.rb +3 -0
- data/app/models/ruby_llm/agents/tenant_budget.rb +58 -15
- data/app/services/ruby_llm/agents/agent_registry.rb +51 -12
- data/app/views/layouts/ruby_llm/agents/application.html.erb +2 -29
- data/app/views/ruby_llm/agents/agents/_agent.html.erb +13 -1
- data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +235 -0
- data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +70 -0
- data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +152 -0
- data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +63 -0
- data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +108 -0
- data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +91 -0
- data/app/views/ruby_llm/agents/agents/_workflow.html.erb +1 -1
- data/app/views/ruby_llm/agents/agents/index.html.erb +74 -9
- data/app/views/ruby_llm/agents/agents/show.html.erb +18 -378
- data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +269 -15
- data/app/views/ruby_llm/agents/executions/show.html.erb +16 -0
- data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +93 -0
- data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +236 -0
- data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +76 -0
- data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +74 -0
- data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +108 -0
- data/app/views/ruby_llm/agents/workflows/show.html.erb +442 -0
- data/config/routes.rb +1 -0
- data/lib/generators/ruby_llm_agents/agent_generator.rb +56 -7
- data/lib/generators/ruby_llm_agents/background_remover_generator.rb +110 -0
- data/lib/generators/ruby_llm_agents/embedder_generator.rb +107 -0
- data/lib/generators/ruby_llm_agents/image_analyzer_generator.rb +115 -0
- data/lib/generators/ruby_llm_agents/image_editor_generator.rb +108 -0
- data/lib/generators/ruby_llm_agents/image_generator_generator.rb +116 -0
- data/lib/generators/ruby_llm_agents/image_pipeline_generator.rb +178 -0
- data/lib/generators/ruby_llm_agents/image_transformer_generator.rb +109 -0
- data/lib/generators/ruby_llm_agents/image_upscaler_generator.rb +103 -0
- data/lib/generators/ruby_llm_agents/image_variator_generator.rb +102 -0
- data/lib/generators/ruby_llm_agents/install_generator.rb +76 -4
- data/lib/generators/ruby_llm_agents/restructure_generator.rb +292 -0
- data/lib/generators/ruby_llm_agents/speaker_generator.rb +121 -0
- data/lib/generators/ruby_llm_agents/templates/add_execution_type_migration.rb.tt +8 -0
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +99 -84
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +42 -40
- data/lib/generators/ruby_llm_agents/templates/application_background_remover.rb.tt +26 -0
- data/lib/generators/ruby_llm_agents/templates/application_embedder.rb.tt +50 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_analyzer.rb.tt +26 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_editor.rb.tt +20 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_generator.rb.tt +38 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +139 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_transformer.rb.tt +21 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_upscaler.rb.tt +20 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_variator.rb.tt +20 -0
- data/lib/generators/ruby_llm_agents/templates/application_speaker.rb.tt +49 -0
- data/lib/generators/ruby_llm_agents/templates/application_transcriber.rb.tt +53 -0
- data/lib/generators/ruby_llm_agents/templates/background_remover.rb.tt +44 -0
- data/lib/generators/ruby_llm_agents/templates/embedder.rb.tt +41 -0
- data/lib/generators/ruby_llm_agents/templates/image_analyzer.rb.tt +45 -0
- data/lib/generators/ruby_llm_agents/templates/image_editor.rb.tt +35 -0
- data/lib/generators/ruby_llm_agents/templates/image_generator.rb.tt +47 -0
- data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +50 -0
- data/lib/generators/ruby_llm_agents/templates/image_transformer.rb.tt +44 -0
- data/lib/generators/ruby_llm_agents/templates/image_upscaler.rb.tt +38 -0
- data/lib/generators/ruby_llm_agents/templates/image_variator.rb.tt +33 -0
- data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +228 -0
- data/lib/generators/ruby_llm_agents/templates/skills/BACKGROUND_REMOVERS.md.tt +131 -0
- data/lib/generators/ruby_llm_agents/templates/skills/EMBEDDERS.md.tt +255 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_ANALYZERS.md.tt +120 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_EDITORS.md.tt +102 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_GENERATORS.md.tt +282 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +228 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_TRANSFORMERS.md.tt +120 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_UPSCALERS.md.tt +110 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_VARIATORS.md.tt +120 -0
- data/lib/generators/ruby_llm_agents/templates/skills/SPEAKERS.md.tt +212 -0
- data/lib/generators/ruby_llm_agents/templates/skills/TOOLS.md.tt +227 -0
- data/lib/generators/ruby_llm_agents/templates/skills/TRANSCRIBERS.md.tt +251 -0
- data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +300 -0
- data/lib/generators/ruby_llm_agents/templates/speaker.rb.tt +56 -0
- data/lib/generators/ruby_llm_agents/templates/transcriber.rb.tt +51 -0
- data/lib/generators/ruby_llm_agents/transcriber_generator.rb +107 -0
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +152 -1
- data/lib/ruby_llm/agents/audio/speaker.rb +553 -0
- data/lib/ruby_llm/agents/audio/transcriber.rb +669 -0
- data/lib/ruby_llm/agents/base_agent.rb +675 -0
- data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +181 -0
- data/lib/ruby_llm/agents/core/base/moderation_execution.rb +274 -0
- data/lib/ruby_llm/agents/core/base.rb +135 -0
- data/lib/ruby_llm/agents/core/configuration.rb +981 -0
- data/lib/ruby_llm/agents/core/errors.rb +150 -0
- data/lib/ruby_llm/agents/{instrumentation.rb → core/instrumentation.rb} +22 -1
- data/lib/ruby_llm/agents/core/llm_tenant.rb +358 -0
- data/lib/ruby_llm/agents/{version.rb → core/version.rb} +1 -1
- data/lib/ruby_llm/agents/dsl/base.rb +110 -0
- data/lib/ruby_llm/agents/dsl/caching.rb +142 -0
- data/lib/ruby_llm/agents/dsl/reliability.rb +307 -0
- data/lib/ruby_llm/agents/dsl.rb +41 -0
- data/lib/ruby_llm/agents/image/analyzer/dsl.rb +130 -0
- data/lib/ruby_llm/agents/image/analyzer/execution.rb +402 -0
- data/lib/ruby_llm/agents/image/analyzer.rb +90 -0
- data/lib/ruby_llm/agents/image/background_remover/dsl.rb +154 -0
- data/lib/ruby_llm/agents/image/background_remover/execution.rb +240 -0
- data/lib/ruby_llm/agents/image/background_remover.rb +89 -0
- data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +91 -0
- data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +165 -0
- data/lib/ruby_llm/agents/image/editor/dsl.rb +56 -0
- data/lib/ruby_llm/agents/image/editor/execution.rb +207 -0
- data/lib/ruby_llm/agents/image/editor.rb +92 -0
- data/lib/ruby_llm/agents/image/generator/active_storage_support.rb +127 -0
- data/lib/ruby_llm/agents/image/generator/content_policy.rb +95 -0
- data/lib/ruby_llm/agents/image/generator/pricing.rb +353 -0
- data/lib/ruby_llm/agents/image/generator/templates.rb +124 -0
- data/lib/ruby_llm/agents/image/generator.rb +455 -0
- data/lib/ruby_llm/agents/image/pipeline/dsl.rb +213 -0
- data/lib/ruby_llm/agents/image/pipeline/execution.rb +382 -0
- data/lib/ruby_llm/agents/image/pipeline.rb +97 -0
- data/lib/ruby_llm/agents/image/transformer/dsl.rb +148 -0
- data/lib/ruby_llm/agents/image/transformer/execution.rb +223 -0
- data/lib/ruby_llm/agents/image/transformer.rb +95 -0
- data/lib/ruby_llm/agents/image/upscaler/dsl.rb +83 -0
- data/lib/ruby_llm/agents/image/upscaler/execution.rb +219 -0
- data/lib/ruby_llm/agents/image/upscaler.rb +81 -0
- data/lib/ruby_llm/agents/image/variator/dsl.rb +62 -0
- data/lib/ruby_llm/agents/image/variator/execution.rb +189 -0
- data/lib/ruby_llm/agents/image/variator.rb +80 -0
- data/lib/ruby_llm/agents/{alert_manager.rb → infrastructure/alert_manager.rb} +17 -22
- data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +145 -0
- data/lib/ruby_llm/agents/infrastructure/budget/config_resolver.rb +149 -0
- data/lib/ruby_llm/agents/infrastructure/budget/forecaster.rb +68 -0
- data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +279 -0
- data/lib/ruby_llm/agents/infrastructure/budget_tracker.rb +275 -0
- data/lib/ruby_llm/agents/{execution_logger_job.rb → infrastructure/execution_logger_job.rb} +17 -1
- data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/executor.rb +2 -1
- data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/retry_strategy.rb +9 -3
- data/lib/ruby_llm/agents/{reliability.rb → infrastructure/reliability.rb} +11 -21
- data/lib/ruby_llm/agents/pipeline/builder.rb +215 -0
- data/lib/ruby_llm/agents/pipeline/context.rb +255 -0
- data/lib/ruby_llm/agents/pipeline/executor.rb +86 -0
- data/lib/ruby_llm/agents/pipeline/middleware/base.rb +124 -0
- data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +95 -0
- data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +171 -0
- data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +415 -0
- data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +276 -0
- data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +196 -0
- data/lib/ruby_llm/agents/pipeline.rb +68 -0
- data/lib/ruby_llm/agents/{engine.rb → rails/engine.rb} +79 -11
- data/lib/ruby_llm/agents/results/background_removal_result.rb +286 -0
- data/lib/ruby_llm/agents/{result.rb → results/base.rb} +73 -1
- data/lib/ruby_llm/agents/results/embedding_result.rb +243 -0
- data/lib/ruby_llm/agents/results/image_analysis_result.rb +314 -0
- data/lib/ruby_llm/agents/results/image_edit_result.rb +250 -0
- data/lib/ruby_llm/agents/results/image_generation_result.rb +346 -0
- data/lib/ruby_llm/agents/results/image_pipeline_result.rb +399 -0
- data/lib/ruby_llm/agents/results/image_transform_result.rb +251 -0
- data/lib/ruby_llm/agents/results/image_upscale_result.rb +255 -0
- data/lib/ruby_llm/agents/results/image_variation_result.rb +237 -0
- data/lib/ruby_llm/agents/results/moderation_result.rb +158 -0
- data/lib/ruby_llm/agents/results/speech_result.rb +338 -0
- data/lib/ruby_llm/agents/results/transcription_result.rb +408 -0
- data/lib/ruby_llm/agents/text/embedder.rb +444 -0
- data/lib/ruby_llm/agents/text/moderator.rb +237 -0
- data/lib/ruby_llm/agents/workflow/async.rb +220 -0
- data/lib/ruby_llm/agents/workflow/async_executor.rb +156 -0
- data/lib/ruby_llm/agents/{workflow.rb → workflow/orchestrator.rb} +6 -5
- data/lib/ruby_llm/agents/workflow/parallel.rb +34 -17
- data/lib/ruby_llm/agents/workflow/thread_pool.rb +185 -0
- data/lib/ruby_llm/agents.rb +86 -20
- metadata +172 -34
- data/lib/ruby_llm/agents/base/caching.rb +0 -40
- data/lib/ruby_llm/agents/base/cost_calculation.rb +0 -105
- data/lib/ruby_llm/agents/base/dsl.rb +0 -324
- data/lib/ruby_llm/agents/base/execution.rb +0 -366
- data/lib/ruby_llm/agents/base/reliability_dsl.rb +0 -82
- data/lib/ruby_llm/agents/base/reliability_execution.rb +0 -136
- data/lib/ruby_llm/agents/base/response_building.rb +0 -86
- data/lib/ruby_llm/agents/base/tool_tracking.rb +0 -57
- data/lib/ruby_llm/agents/base.rb +0 -210
- data/lib/ruby_llm/agents/budget_tracker.rb +0 -733
- data/lib/ruby_llm/agents/configuration.rb +0 -394
- /data/lib/ruby_llm/agents/{deprecations.rb → core/deprecations.rb} +0 -0
- /data/lib/ruby_llm/agents/{inflections.rb → core/inflections.rb} +0 -0
- /data/lib/ruby_llm/agents/{resolved_config.rb → core/resolved_config.rb} +0 -0
- /data/lib/ruby_llm/agents/{attempt_tracker.rb → infrastructure/attempt_tracker.rb} +0 -0
- /data/lib/ruby_llm/agents/{cache_helper.rb → infrastructure/cache_helper.rb} +0 -0
- /data/lib/ruby_llm/agents/{circuit_breaker.rb → infrastructure/circuit_breaker.rb} +0 -0
- /data/lib/ruby_llm/agents/{redactor.rb → infrastructure/redactor.rb} +0 -0
- /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/breaker_manager.rb +0 -0
- /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/execution_constraints.rb +0 -0
- /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/fallback_routing.rb +0 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Agents
|
|
5
|
+
module DSL
|
|
6
|
+
# Caching DSL for agent response caching.
|
|
7
|
+
#
|
|
8
|
+
# This module provides configuration methods for caching features
|
|
9
|
+
# that can be mixed into any agent class.
|
|
10
|
+
#
|
|
11
|
+
# @example Basic usage
|
|
12
|
+
# class MyAgent < RubyLLM::Agents::BaseAgent
|
|
13
|
+
# extend DSL::Caching
|
|
14
|
+
#
|
|
15
|
+
# cache_for 1.hour
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# @example With conditional caching
|
|
19
|
+
# class MyAgent < RubyLLM::Agents::BaseAgent
|
|
20
|
+
# extend DSL::Caching
|
|
21
|
+
#
|
|
22
|
+
# cache_for 30.minutes
|
|
23
|
+
# cache_key_includes :user_id, :query
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
module Caching
|
|
27
|
+
# Default cache TTL when none specified
|
|
28
|
+
DEFAULT_CACHE_TTL = 1.hour
|
|
29
|
+
|
|
30
|
+
# @!group Caching DSL
|
|
31
|
+
|
|
32
|
+
# Enables caching for this agent with explicit TTL
|
|
33
|
+
#
|
|
34
|
+
# This is the preferred method for enabling caching.
|
|
35
|
+
#
|
|
36
|
+
# @param ttl [ActiveSupport::Duration] Time-to-live for cached responses
|
|
37
|
+
# @return [void]
|
|
38
|
+
# @example
|
|
39
|
+
# cache_for 1.hour
|
|
40
|
+
# cache_for 30.minutes
|
|
41
|
+
def cache_for(ttl)
|
|
42
|
+
@cache_enabled = true
|
|
43
|
+
@cache_ttl = ttl
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Alias for cache_for (for backward compatibility)
|
|
47
|
+
alias cache cache_for
|
|
48
|
+
|
|
49
|
+
# Returns whether caching is enabled for this agent
|
|
50
|
+
#
|
|
51
|
+
# Caching IS inherited from parent classes following Ruby patterns.
|
|
52
|
+
#
|
|
53
|
+
# @return [Boolean] true if caching is enabled
|
|
54
|
+
def cache_enabled?
|
|
55
|
+
return @cache_enabled if defined?(@cache_enabled)
|
|
56
|
+
|
|
57
|
+
inherited_cache_enabled
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Returns the cache TTL for this agent
|
|
61
|
+
#
|
|
62
|
+
# @return [ActiveSupport::Duration] The cache TTL
|
|
63
|
+
def cache_ttl
|
|
64
|
+
@cache_ttl || inherited_cache_ttl || DEFAULT_CACHE_TTL
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Specifies which parameters should be included in the cache key
|
|
68
|
+
#
|
|
69
|
+
# By default, all options except :skip_cache and :dry_run are included.
|
|
70
|
+
# Use this to explicitly define which parameters affect caching.
|
|
71
|
+
#
|
|
72
|
+
# @param keys [Array<Symbol>] Parameter keys to include in cache key
|
|
73
|
+
# @return [Array<Symbol>, nil] The current cache key includes
|
|
74
|
+
# @example
|
|
75
|
+
# cache_key_includes :user_id, :query, :context
|
|
76
|
+
def cache_key_includes(*keys)
|
|
77
|
+
@cache_key_includes = keys.flatten if keys.any?
|
|
78
|
+
@cache_key_includes || inherited_cache_key_includes
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Specifies which parameters should be excluded from the cache key
|
|
82
|
+
#
|
|
83
|
+
# @param keys [Array<Symbol>] Parameter keys to exclude from cache key
|
|
84
|
+
# @return [Array<Symbol>] The current cache key excludes
|
|
85
|
+
# @example
|
|
86
|
+
# cache_key_excludes :timestamp, :request_id
|
|
87
|
+
def cache_key_excludes(*keys)
|
|
88
|
+
@cache_key_excludes = keys.flatten if keys.any?
|
|
89
|
+
@cache_key_excludes || inherited_cache_key_excludes || default_cache_key_excludes
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Returns the complete caching configuration hash
|
|
93
|
+
#
|
|
94
|
+
# Used by the Cache middleware to get all settings.
|
|
95
|
+
#
|
|
96
|
+
# @return [Hash, nil] The caching configuration
|
|
97
|
+
def caching_config
|
|
98
|
+
return nil unless cache_enabled?
|
|
99
|
+
|
|
100
|
+
{
|
|
101
|
+
enabled: true,
|
|
102
|
+
ttl: cache_ttl,
|
|
103
|
+
key_includes: cache_key_includes,
|
|
104
|
+
key_excludes: cache_key_excludes
|
|
105
|
+
}.compact
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# @!endgroup
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def inherited_cache_enabled
|
|
113
|
+
return false unless superclass.respond_to?(:cache_enabled?)
|
|
114
|
+
|
|
115
|
+
superclass.cache_enabled?
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def inherited_cache_ttl
|
|
119
|
+
return nil unless superclass.respond_to?(:cache_ttl)
|
|
120
|
+
|
|
121
|
+
superclass.cache_ttl
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def inherited_cache_key_includes
|
|
125
|
+
return nil unless superclass.respond_to?(:cache_key_includes)
|
|
126
|
+
|
|
127
|
+
superclass.cache_key_includes
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def inherited_cache_key_excludes
|
|
131
|
+
return nil unless superclass.respond_to?(:cache_key_excludes)
|
|
132
|
+
|
|
133
|
+
superclass.cache_key_excludes
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def default_cache_key_excludes
|
|
137
|
+
%i[skip_cache dry_run with]
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Agents
|
|
5
|
+
module DSL
|
|
6
|
+
# Reliability DSL for retries, fallbacks, and circuit breakers.
|
|
7
|
+
#
|
|
8
|
+
# This module provides configuration methods for reliability features
|
|
9
|
+
# that can be mixed into any agent class.
|
|
10
|
+
#
|
|
11
|
+
# @example Block-style configuration
|
|
12
|
+
# class MyAgent < RubyLLM::Agents::BaseAgent
|
|
13
|
+
# extend DSL::Reliability
|
|
14
|
+
#
|
|
15
|
+
# reliability do
|
|
16
|
+
# retries max: 3, backoff: :exponential
|
|
17
|
+
# fallback_models "gpt-4o-mini"
|
|
18
|
+
# total_timeout 30
|
|
19
|
+
# circuit_breaker errors: 5, within: 60
|
|
20
|
+
# end
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# @example Individual method configuration
|
|
24
|
+
# class MyAgent < RubyLLM::Agents::BaseAgent
|
|
25
|
+
# extend DSL::Reliability
|
|
26
|
+
#
|
|
27
|
+
# retries max: 3
|
|
28
|
+
# fallback_models "gpt-4o-mini", "gpt-3.5-turbo"
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
module Reliability
|
|
32
|
+
# @!group Reliability DSL
|
|
33
|
+
|
|
34
|
+
# Configures reliability features using a block syntax
|
|
35
|
+
#
|
|
36
|
+
# Groups all reliability configuration in a single block for clarity.
|
|
37
|
+
#
|
|
38
|
+
# @yield Block containing reliability configuration
|
|
39
|
+
# @return [void]
|
|
40
|
+
# @example
|
|
41
|
+
# reliability do
|
|
42
|
+
# retries max: 3, backoff: :exponential
|
|
43
|
+
# fallback_models "gpt-4o-mini"
|
|
44
|
+
# total_timeout 30
|
|
45
|
+
# circuit_breaker errors: 5
|
|
46
|
+
# end
|
|
47
|
+
def reliability(&block)
|
|
48
|
+
builder = ReliabilityBuilder.new
|
|
49
|
+
builder.instance_eval(&block)
|
|
50
|
+
|
|
51
|
+
@retries_config = builder.retries_config if builder.retries_config
|
|
52
|
+
@fallback_models = builder.fallback_models_list if builder.fallback_models_list.any?
|
|
53
|
+
@fallback_providers = builder.fallback_providers_list if builder.fallback_providers_list.any?
|
|
54
|
+
@total_timeout = builder.total_timeout_value if builder.total_timeout_value
|
|
55
|
+
@circuit_breaker_config = builder.circuit_breaker_config if builder.circuit_breaker_config
|
|
56
|
+
@retryable_patterns = builder.retryable_patterns_list if builder.retryable_patterns_list
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Returns the complete reliability configuration hash
|
|
60
|
+
#
|
|
61
|
+
# Used by the Reliability middleware to get all settings.
|
|
62
|
+
#
|
|
63
|
+
# @return [Hash, nil] The reliability configuration
|
|
64
|
+
def reliability_config
|
|
65
|
+
return nil unless reliability_configured?
|
|
66
|
+
|
|
67
|
+
{
|
|
68
|
+
retries: retries_config,
|
|
69
|
+
fallback_models: fallback_models,
|
|
70
|
+
fallback_providers: fallback_providers,
|
|
71
|
+
total_timeout: total_timeout,
|
|
72
|
+
circuit_breaker: circuit_breaker_config,
|
|
73
|
+
retryable_patterns: retryable_patterns
|
|
74
|
+
}.compact
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Checks if any reliability features are configured
|
|
78
|
+
#
|
|
79
|
+
# @return [Boolean] true if reliability is configured
|
|
80
|
+
def reliability_configured?
|
|
81
|
+
(retries_config && retries_config[:max]&.positive?) ||
|
|
82
|
+
fallback_models.any? ||
|
|
83
|
+
fallback_providers.any? ||
|
|
84
|
+
circuit_breaker_config.present?
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Configures retry behavior for this agent
|
|
88
|
+
#
|
|
89
|
+
# @param max [Integer] Maximum number of retry attempts (default: 0)
|
|
90
|
+
# @param backoff [Symbol] Backoff strategy (:constant or :exponential)
|
|
91
|
+
# @param base [Float] Base delay in seconds
|
|
92
|
+
# @param max_delay [Float] Maximum delay between retries
|
|
93
|
+
# @param on [Array<Class>] Error classes to retry on (extends defaults)
|
|
94
|
+
# @return [Hash] The current retry configuration
|
|
95
|
+
# @example
|
|
96
|
+
# retries max: 2, backoff: :exponential
|
|
97
|
+
def retries(max: nil, backoff: nil, base: nil, max_delay: nil, on: nil)
|
|
98
|
+
if max || backoff || base || max_delay || on
|
|
99
|
+
@retries_config ||= default_retries_config.dup
|
|
100
|
+
@retries_config[:max] = max if max
|
|
101
|
+
@retries_config[:backoff] = backoff if backoff
|
|
102
|
+
@retries_config[:base] = base if base
|
|
103
|
+
@retries_config[:max_delay] = max_delay if max_delay
|
|
104
|
+
@retries_config[:on] = on if on
|
|
105
|
+
end
|
|
106
|
+
@retries_config || inherited_retry_config || default_retries_config
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Returns the retry configuration
|
|
110
|
+
#
|
|
111
|
+
# @return [Hash, nil] The retry configuration
|
|
112
|
+
def retries_config
|
|
113
|
+
@retries_config || inherited_retry_config
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Sets or returns fallback models to try when primary model fails
|
|
117
|
+
#
|
|
118
|
+
# @param models [Array<String>] Model identifiers to use as fallbacks
|
|
119
|
+
# @return [Array<String>] The current fallback models
|
|
120
|
+
# @example
|
|
121
|
+
# fallback_models "gpt-4o-mini", "gpt-3.5-turbo"
|
|
122
|
+
def fallback_models(*models)
|
|
123
|
+
@fallback_models = models.flatten if models.any?
|
|
124
|
+
@fallback_models || inherited_fallback_models || []
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Sets or returns fallback providers to try when primary provider fails
|
|
128
|
+
#
|
|
129
|
+
# Used primarily by audio agents (Speaker, Transcriber) that may need
|
|
130
|
+
# to fall back to a different provider entirely.
|
|
131
|
+
#
|
|
132
|
+
# @param provider [Symbol] The provider to fall back to
|
|
133
|
+
# @param options [Hash] Provider-specific options (e.g., voice:, model:)
|
|
134
|
+
# @return [Array<Hash>] The current fallback providers
|
|
135
|
+
# @example
|
|
136
|
+
# fallback_provider :openai, voice: "nova"
|
|
137
|
+
def fallback_provider(provider = nil, **options)
|
|
138
|
+
if provider
|
|
139
|
+
@fallback_providers ||= []
|
|
140
|
+
@fallback_providers << { provider: provider, **options }
|
|
141
|
+
end
|
|
142
|
+
@fallback_providers || inherited_fallback_providers || []
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Returns the fallback providers list
|
|
146
|
+
#
|
|
147
|
+
# @return [Array<Hash>] The fallback providers
|
|
148
|
+
def fallback_providers
|
|
149
|
+
@fallback_providers || inherited_fallback_providers || []
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Sets or returns the total timeout for all retry/fallback attempts
|
|
153
|
+
#
|
|
154
|
+
# @param seconds [Integer, nil] Total timeout in seconds
|
|
155
|
+
# @return [Integer, nil] The current total timeout
|
|
156
|
+
# @example
|
|
157
|
+
# total_timeout 30
|
|
158
|
+
def total_timeout(seconds = nil)
|
|
159
|
+
@total_timeout = seconds if seconds
|
|
160
|
+
@total_timeout || inherited_total_timeout
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Configures circuit breaker for this agent
|
|
164
|
+
#
|
|
165
|
+
# @param errors [Integer] Number of errors to trigger open state
|
|
166
|
+
# @param within [Integer] Rolling window in seconds
|
|
167
|
+
# @param cooldown [Integer] Cooldown period in seconds when open
|
|
168
|
+
# @return [Hash, nil] The current circuit breaker configuration
|
|
169
|
+
# @example
|
|
170
|
+
# circuit_breaker errors: 10, within: 60, cooldown: 300
|
|
171
|
+
def circuit_breaker(errors: nil, within: nil, cooldown: nil)
|
|
172
|
+
if errors || within || cooldown
|
|
173
|
+
@circuit_breaker_config ||= { errors: 10, within: 60, cooldown: 300 }
|
|
174
|
+
@circuit_breaker_config[:errors] = errors if errors
|
|
175
|
+
@circuit_breaker_config[:within] = within if within
|
|
176
|
+
@circuit_breaker_config[:cooldown] = cooldown if cooldown
|
|
177
|
+
end
|
|
178
|
+
@circuit_breaker_config || inherited_circuit_breaker_config
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Returns the circuit breaker configuration
|
|
182
|
+
#
|
|
183
|
+
# @return [Hash, nil] The circuit breaker configuration
|
|
184
|
+
def circuit_breaker_config
|
|
185
|
+
@circuit_breaker_config || inherited_circuit_breaker_config
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Sets or returns additional retryable patterns for error message matching
|
|
189
|
+
#
|
|
190
|
+
# @param patterns [Array<String>] Pattern strings to match in error messages
|
|
191
|
+
# @return [Array<String>, nil] The current retryable patterns
|
|
192
|
+
# @example
|
|
193
|
+
# retryable_patterns "custom_error", "another_pattern"
|
|
194
|
+
def retryable_patterns(*patterns)
|
|
195
|
+
@retryable_patterns = patterns.flatten if patterns.any?
|
|
196
|
+
@retryable_patterns || inherited_retryable_patterns
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# @!endgroup
|
|
200
|
+
|
|
201
|
+
private
|
|
202
|
+
|
|
203
|
+
def inherited_retry_config
|
|
204
|
+
return nil unless superclass.respond_to?(:retries_config)
|
|
205
|
+
|
|
206
|
+
superclass.retries_config
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def inherited_fallback_models
|
|
210
|
+
return nil unless superclass.respond_to?(:fallback_models)
|
|
211
|
+
|
|
212
|
+
superclass.fallback_models
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def inherited_fallback_providers
|
|
216
|
+
return nil unless superclass.respond_to?(:fallback_providers)
|
|
217
|
+
|
|
218
|
+
superclass.fallback_providers
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def inherited_total_timeout
|
|
222
|
+
return nil unless superclass.respond_to?(:total_timeout)
|
|
223
|
+
|
|
224
|
+
superclass.total_timeout
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def inherited_circuit_breaker_config
|
|
228
|
+
return nil unless superclass.respond_to?(:circuit_breaker_config)
|
|
229
|
+
|
|
230
|
+
superclass.circuit_breaker_config
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def inherited_retryable_patterns
|
|
234
|
+
return nil unless superclass.respond_to?(:retryable_patterns)
|
|
235
|
+
|
|
236
|
+
superclass.retryable_patterns
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def default_retries_config
|
|
240
|
+
{
|
|
241
|
+
max: 0,
|
|
242
|
+
backoff: :exponential,
|
|
243
|
+
base: 0.4,
|
|
244
|
+
max_delay: 3.0,
|
|
245
|
+
on: []
|
|
246
|
+
}
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Inner builder class for block-style configuration
|
|
250
|
+
class ReliabilityBuilder
|
|
251
|
+
attr_reader :retries_config, :fallback_models_list, :total_timeout_value,
|
|
252
|
+
:circuit_breaker_config, :retryable_patterns_list, :fallback_providers_list
|
|
253
|
+
|
|
254
|
+
def initialize
|
|
255
|
+
@retries_config = nil
|
|
256
|
+
@fallback_models_list = []
|
|
257
|
+
@total_timeout_value = nil
|
|
258
|
+
@circuit_breaker_config = nil
|
|
259
|
+
@retryable_patterns_list = nil
|
|
260
|
+
@fallback_providers_list = []
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def retries(max: 0, backoff: :exponential, base: 0.4, max_delay: 3.0, on: [])
|
|
264
|
+
@retries_config = {
|
|
265
|
+
max: max,
|
|
266
|
+
backoff: backoff,
|
|
267
|
+
base: base,
|
|
268
|
+
max_delay: max_delay,
|
|
269
|
+
on: on
|
|
270
|
+
}
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def fallback_models(*models)
|
|
274
|
+
@fallback_models_list = models.flatten
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Configures a fallback provider with optional settings
|
|
278
|
+
#
|
|
279
|
+
# @param provider [Symbol] The provider to fall back to (e.g., :openai, :elevenlabs)
|
|
280
|
+
# @param options [Hash] Provider-specific options (e.g., voice:, model:)
|
|
281
|
+
# @example
|
|
282
|
+
# fallback_provider :openai, voice: "nova"
|
|
283
|
+
# fallback_provider :elevenlabs, voice: "Rachel", model: "eleven_multilingual_v2"
|
|
284
|
+
def fallback_provider(provider, **options)
|
|
285
|
+
@fallback_providers_list << { provider: provider, **options }
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def total_timeout(seconds)
|
|
289
|
+
@total_timeout_value = seconds
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def circuit_breaker(errors: 10, within: 60, cooldown: 300)
|
|
293
|
+
@circuit_breaker_config = {
|
|
294
|
+
errors: errors,
|
|
295
|
+
within: within,
|
|
296
|
+
cooldown: cooldown
|
|
297
|
+
}
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def retryable_patterns(*patterns)
|
|
301
|
+
@retryable_patterns_list = patterns.flatten
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "dsl/base"
|
|
4
|
+
require_relative "dsl/reliability"
|
|
5
|
+
require_relative "dsl/caching"
|
|
6
|
+
|
|
7
|
+
module RubyLLM
|
|
8
|
+
module Agents
|
|
9
|
+
# Domain-Specific Language modules for agent configuration.
|
|
10
|
+
#
|
|
11
|
+
# The DSL modules provide a clean, declarative way to configure agents
|
|
12
|
+
# at the class level. Each module focuses on a specific concern:
|
|
13
|
+
#
|
|
14
|
+
# - {DSL::Base} - Core settings (model, version, description, timeout)
|
|
15
|
+
# - {DSL::Reliability} - Retries, fallbacks, circuit breakers
|
|
16
|
+
# - {DSL::Caching} - Response caching configuration
|
|
17
|
+
#
|
|
18
|
+
# @example Using all DSL modules
|
|
19
|
+
# class MyAgent < RubyLLM::Agents::BaseAgent
|
|
20
|
+
# extend DSL::Base
|
|
21
|
+
# extend DSL::Reliability
|
|
22
|
+
# extend DSL::Caching
|
|
23
|
+
#
|
|
24
|
+
# model "gpt-4o"
|
|
25
|
+
# version "2.0"
|
|
26
|
+
# description "A helpful agent"
|
|
27
|
+
# timeout 30
|
|
28
|
+
#
|
|
29
|
+
# reliability do
|
|
30
|
+
# retries max: 3, backoff: :exponential
|
|
31
|
+
# fallback_models "gpt-4o-mini"
|
|
32
|
+
# circuit_breaker errors: 5, within: 60
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
# cache_for 1.hour
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
module DSL
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../concerns/image_operation_dsl"
|
|
4
|
+
|
|
5
|
+
module RubyLLM
|
|
6
|
+
module Agents
|
|
7
|
+
class ImageAnalyzer
|
|
8
|
+
# DSL for configuring image analyzers
|
|
9
|
+
#
|
|
10
|
+
# Provides class-level methods to configure model, analysis type,
|
|
11
|
+
# and feature extraction options.
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# class ProductAnalyzer < RubyLLM::Agents::ImageAnalyzer
|
|
15
|
+
# model "gpt-4o"
|
|
16
|
+
# analysis_type :detailed
|
|
17
|
+
# extract_colors true
|
|
18
|
+
# detect_objects true
|
|
19
|
+
# max_tags 20
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
module DSL
|
|
23
|
+
include Concerns::ImageOperationDSL
|
|
24
|
+
|
|
25
|
+
VALID_ANALYSIS_TYPES = %i[caption detailed tags objects colors all].freeze
|
|
26
|
+
|
|
27
|
+
# Set or get the analysis type
|
|
28
|
+
#
|
|
29
|
+
# @param value [Symbol, nil] Analysis type (:caption, :detailed, :tags, :objects, :colors, :all)
|
|
30
|
+
# @return [Symbol] The analysis type
|
|
31
|
+
def analysis_type(value = nil)
|
|
32
|
+
if value
|
|
33
|
+
unless VALID_ANALYSIS_TYPES.include?(value)
|
|
34
|
+
raise ArgumentError, "Analysis type must be one of: #{VALID_ANALYSIS_TYPES.join(', ')}"
|
|
35
|
+
end
|
|
36
|
+
@analysis_type = value
|
|
37
|
+
else
|
|
38
|
+
@analysis_type || inherited_or_default(:analysis_type, :detailed)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Set or get whether to extract colors
|
|
43
|
+
#
|
|
44
|
+
# When enabled, extracts dominant colors from the image with
|
|
45
|
+
# hex values, names, and percentages.
|
|
46
|
+
#
|
|
47
|
+
# @param value [Boolean, nil] Enable color extraction
|
|
48
|
+
# @return [Boolean] Whether color extraction is enabled
|
|
49
|
+
def extract_colors(value = nil)
|
|
50
|
+
if value.nil?
|
|
51
|
+
result = @extract_colors
|
|
52
|
+
result = inherited_or_default(:extract_colors, false) if result.nil?
|
|
53
|
+
result
|
|
54
|
+
else
|
|
55
|
+
@extract_colors = value
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Set or get whether to detect objects
|
|
60
|
+
#
|
|
61
|
+
# When enabled, detects and identifies objects in the image
|
|
62
|
+
# with bounding boxes and confidence scores.
|
|
63
|
+
#
|
|
64
|
+
# @param value [Boolean, nil] Enable object detection
|
|
65
|
+
# @return [Boolean] Whether object detection is enabled
|
|
66
|
+
def detect_objects(value = nil)
|
|
67
|
+
if value.nil?
|
|
68
|
+
result = @detect_objects
|
|
69
|
+
result = inherited_or_default(:detect_objects, false) if result.nil?
|
|
70
|
+
result
|
|
71
|
+
else
|
|
72
|
+
@detect_objects = value
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Set or get whether to extract text (OCR)
|
|
77
|
+
#
|
|
78
|
+
# When enabled, extracts text visible in the image using OCR.
|
|
79
|
+
#
|
|
80
|
+
# @param value [Boolean, nil] Enable text extraction
|
|
81
|
+
# @return [Boolean] Whether text extraction is enabled
|
|
82
|
+
def extract_text(value = nil)
|
|
83
|
+
if value.nil?
|
|
84
|
+
result = @extract_text
|
|
85
|
+
result = inherited_or_default(:extract_text, false) if result.nil?
|
|
86
|
+
result
|
|
87
|
+
else
|
|
88
|
+
@extract_text = value
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Set or get a custom analysis prompt
|
|
93
|
+
#
|
|
94
|
+
# Allows specifying a custom prompt for the vision model
|
|
95
|
+
# to customize what information is extracted.
|
|
96
|
+
#
|
|
97
|
+
# @param value [String, nil] Custom prompt
|
|
98
|
+
# @return [String, nil] The custom prompt
|
|
99
|
+
def custom_prompt(value = nil)
|
|
100
|
+
if value
|
|
101
|
+
@custom_prompt = value
|
|
102
|
+
else
|
|
103
|
+
@custom_prompt || inherited_or_default(:custom_prompt, nil)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Set or get maximum number of tags
|
|
108
|
+
#
|
|
109
|
+
# @param value [Integer, nil] Maximum tags to return
|
|
110
|
+
# @return [Integer] The maximum number of tags
|
|
111
|
+
def max_tags(value = nil)
|
|
112
|
+
if value
|
|
113
|
+
unless value.is_a?(Integer) && value > 0
|
|
114
|
+
raise ArgumentError, "Max tags must be a positive integer"
|
|
115
|
+
end
|
|
116
|
+
@max_tags = value
|
|
117
|
+
else
|
|
118
|
+
@max_tags || inherited_or_default(:max_tags, 10)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
def default_model
|
|
125
|
+
config.default_analyzer_model || "gpt-4o"
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|