ruby_llm-agents 0.4.0 → 1.0.0.beta.1

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 (208) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +225 -34
  3. data/app/controllers/ruby_llm/agents/agents_controller.rb +136 -16
  4. data/app/controllers/ruby_llm/agents/api_configurations_controller.rb +214 -0
  5. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +29 -9
  6. data/app/controllers/ruby_llm/agents/{settings_controller.rb → system_config_controller.rb} +3 -3
  7. data/app/controllers/ruby_llm/agents/tenants_controller.rb +109 -0
  8. data/app/controllers/ruby_llm/agents/workflows_controller.rb +355 -0
  9. data/app/helpers/ruby_llm/agents/application_helper.rb +25 -0
  10. data/app/models/ruby_llm/agents/api_configuration.rb +386 -0
  11. data/app/models/ruby_llm/agents/execution.rb +3 -0
  12. data/app/models/ruby_llm/agents/tenant_budget.rb +112 -14
  13. data/app/services/ruby_llm/agents/agent_registry.rb +51 -12
  14. data/app/views/layouts/ruby_llm/agents/application.html.erb +5 -30
  15. data/app/views/ruby_llm/agents/agents/_agent.html.erb +13 -1
  16. data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +235 -0
  17. data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +70 -0
  18. data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +152 -0
  19. data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +63 -0
  20. data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +108 -0
  21. data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +91 -0
  22. data/app/views/ruby_llm/agents/agents/_workflow.html.erb +1 -1
  23. data/app/views/ruby_llm/agents/agents/index.html.erb +74 -9
  24. data/app/views/ruby_llm/agents/agents/show.html.erb +18 -378
  25. data/app/views/ruby_llm/agents/api_configurations/_api_key_field.html.erb +34 -0
  26. data/app/views/ruby_llm/agents/api_configurations/_form.html.erb +288 -0
  27. data/app/views/ruby_llm/agents/api_configurations/edit.html.erb +95 -0
  28. data/app/views/ruby_llm/agents/api_configurations/edit_tenant.html.erb +97 -0
  29. data/app/views/ruby_llm/agents/api_configurations/show.html.erb +211 -0
  30. data/app/views/ruby_llm/agents/api_configurations/tenant.html.erb +179 -0
  31. data/app/views/ruby_llm/agents/dashboard/_action_center.html.erb +1 -1
  32. data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +269 -15
  33. data/app/views/ruby_llm/agents/executions/show.html.erb +98 -0
  34. data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +93 -0
  35. data/app/views/ruby_llm/agents/{settings → system_config}/show.html.erb +1 -1
  36. data/app/views/ruby_llm/agents/tenants/_form.html.erb +150 -0
  37. data/app/views/ruby_llm/agents/tenants/edit.html.erb +13 -0
  38. data/app/views/ruby_llm/agents/tenants/index.html.erb +129 -0
  39. data/app/views/ruby_llm/agents/tenants/show.html.erb +374 -0
  40. data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +236 -0
  41. data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +76 -0
  42. data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +74 -0
  43. data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +108 -0
  44. data/app/views/ruby_llm/agents/workflows/show.html.erb +442 -0
  45. data/config/routes.rb +13 -1
  46. data/lib/generators/ruby_llm_agents/agent_generator.rb +56 -7
  47. data/lib/generators/ruby_llm_agents/api_configuration_generator.rb +100 -0
  48. data/lib/generators/ruby_llm_agents/background_remover_generator.rb +110 -0
  49. data/lib/generators/ruby_llm_agents/embedder_generator.rb +107 -0
  50. data/lib/generators/ruby_llm_agents/image_analyzer_generator.rb +115 -0
  51. data/lib/generators/ruby_llm_agents/image_editor_generator.rb +108 -0
  52. data/lib/generators/ruby_llm_agents/image_generator_generator.rb +116 -0
  53. data/lib/generators/ruby_llm_agents/image_pipeline_generator.rb +178 -0
  54. data/lib/generators/ruby_llm_agents/image_transformer_generator.rb +109 -0
  55. data/lib/generators/ruby_llm_agents/image_upscaler_generator.rb +103 -0
  56. data/lib/generators/ruby_llm_agents/image_variator_generator.rb +102 -0
  57. data/lib/generators/ruby_llm_agents/install_generator.rb +76 -4
  58. data/lib/generators/ruby_llm_agents/restructure_generator.rb +292 -0
  59. data/lib/generators/ruby_llm_agents/speaker_generator.rb +121 -0
  60. data/lib/generators/ruby_llm_agents/templates/add_execution_type_migration.rb.tt +8 -0
  61. data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +99 -84
  62. data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +42 -40
  63. data/lib/generators/ruby_llm_agents/templates/application_background_remover.rb.tt +26 -0
  64. data/lib/generators/ruby_llm_agents/templates/application_embedder.rb.tt +50 -0
  65. data/lib/generators/ruby_llm_agents/templates/application_image_analyzer.rb.tt +26 -0
  66. data/lib/generators/ruby_llm_agents/templates/application_image_editor.rb.tt +20 -0
  67. data/lib/generators/ruby_llm_agents/templates/application_image_generator.rb.tt +38 -0
  68. data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +139 -0
  69. data/lib/generators/ruby_llm_agents/templates/application_image_transformer.rb.tt +21 -0
  70. data/lib/generators/ruby_llm_agents/templates/application_image_upscaler.rb.tt +20 -0
  71. data/lib/generators/ruby_llm_agents/templates/application_image_variator.rb.tt +20 -0
  72. data/lib/generators/ruby_llm_agents/templates/application_speaker.rb.tt +49 -0
  73. data/lib/generators/ruby_llm_agents/templates/application_transcriber.rb.tt +53 -0
  74. data/lib/generators/ruby_llm_agents/templates/background_remover.rb.tt +44 -0
  75. data/lib/generators/ruby_llm_agents/templates/create_api_configurations_migration.rb.tt +90 -0
  76. data/lib/generators/ruby_llm_agents/templates/embedder.rb.tt +41 -0
  77. data/lib/generators/ruby_llm_agents/templates/image_analyzer.rb.tt +45 -0
  78. data/lib/generators/ruby_llm_agents/templates/image_editor.rb.tt +35 -0
  79. data/lib/generators/ruby_llm_agents/templates/image_generator.rb.tt +47 -0
  80. data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +50 -0
  81. data/lib/generators/ruby_llm_agents/templates/image_transformer.rb.tt +44 -0
  82. data/lib/generators/ruby_llm_agents/templates/image_upscaler.rb.tt +38 -0
  83. data/lib/generators/ruby_llm_agents/templates/image_variator.rb.tt +33 -0
  84. data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +228 -0
  85. data/lib/generators/ruby_llm_agents/templates/skills/BACKGROUND_REMOVERS.md.tt +131 -0
  86. data/lib/generators/ruby_llm_agents/templates/skills/EMBEDDERS.md.tt +255 -0
  87. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_ANALYZERS.md.tt +120 -0
  88. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_EDITORS.md.tt +102 -0
  89. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_GENERATORS.md.tt +282 -0
  90. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +228 -0
  91. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_TRANSFORMERS.md.tt +120 -0
  92. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_UPSCALERS.md.tt +110 -0
  93. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_VARIATORS.md.tt +120 -0
  94. data/lib/generators/ruby_llm_agents/templates/skills/SPEAKERS.md.tt +212 -0
  95. data/lib/generators/ruby_llm_agents/templates/skills/TOOLS.md.tt +227 -0
  96. data/lib/generators/ruby_llm_agents/templates/skills/TRANSCRIBERS.md.tt +251 -0
  97. data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +300 -0
  98. data/lib/generators/ruby_llm_agents/templates/speaker.rb.tt +56 -0
  99. data/lib/generators/ruby_llm_agents/templates/transcriber.rb.tt +51 -0
  100. data/lib/generators/ruby_llm_agents/transcriber_generator.rb +107 -0
  101. data/lib/generators/ruby_llm_agents/upgrade_generator.rb +152 -1
  102. data/lib/ruby_llm/agents/audio/speaker.rb +553 -0
  103. data/lib/ruby_llm/agents/audio/transcriber.rb +669 -0
  104. data/lib/ruby_llm/agents/base_agent.rb +675 -0
  105. data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +181 -0
  106. data/lib/ruby_llm/agents/core/base/moderation_execution.rb +274 -0
  107. data/lib/ruby_llm/agents/core/base.rb +135 -0
  108. data/lib/ruby_llm/agents/core/configuration.rb +981 -0
  109. data/lib/ruby_llm/agents/core/errors.rb +150 -0
  110. data/lib/ruby_llm/agents/{instrumentation.rb → core/instrumentation.rb} +93 -4
  111. data/lib/ruby_llm/agents/core/llm_tenant.rb +358 -0
  112. data/lib/ruby_llm/agents/core/resolved_config.rb +348 -0
  113. data/lib/ruby_llm/agents/{version.rb → core/version.rb} +1 -1
  114. data/lib/ruby_llm/agents/dsl/base.rb +110 -0
  115. data/lib/ruby_llm/agents/dsl/caching.rb +142 -0
  116. data/lib/ruby_llm/agents/dsl/reliability.rb +307 -0
  117. data/lib/ruby_llm/agents/dsl.rb +41 -0
  118. data/lib/ruby_llm/agents/image/analyzer/dsl.rb +130 -0
  119. data/lib/ruby_llm/agents/image/analyzer/execution.rb +402 -0
  120. data/lib/ruby_llm/agents/image/analyzer.rb +90 -0
  121. data/lib/ruby_llm/agents/image/background_remover/dsl.rb +154 -0
  122. data/lib/ruby_llm/agents/image/background_remover/execution.rb +240 -0
  123. data/lib/ruby_llm/agents/image/background_remover.rb +89 -0
  124. data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +91 -0
  125. data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +165 -0
  126. data/lib/ruby_llm/agents/image/editor/dsl.rb +56 -0
  127. data/lib/ruby_llm/agents/image/editor/execution.rb +207 -0
  128. data/lib/ruby_llm/agents/image/editor.rb +92 -0
  129. data/lib/ruby_llm/agents/image/generator/active_storage_support.rb +127 -0
  130. data/lib/ruby_llm/agents/image/generator/content_policy.rb +95 -0
  131. data/lib/ruby_llm/agents/image/generator/pricing.rb +353 -0
  132. data/lib/ruby_llm/agents/image/generator/templates.rb +124 -0
  133. data/lib/ruby_llm/agents/image/generator.rb +455 -0
  134. data/lib/ruby_llm/agents/image/pipeline/dsl.rb +213 -0
  135. data/lib/ruby_llm/agents/image/pipeline/execution.rb +382 -0
  136. data/lib/ruby_llm/agents/image/pipeline.rb +97 -0
  137. data/lib/ruby_llm/agents/image/transformer/dsl.rb +148 -0
  138. data/lib/ruby_llm/agents/image/transformer/execution.rb +223 -0
  139. data/lib/ruby_llm/agents/image/transformer.rb +95 -0
  140. data/lib/ruby_llm/agents/image/upscaler/dsl.rb +83 -0
  141. data/lib/ruby_llm/agents/image/upscaler/execution.rb +219 -0
  142. data/lib/ruby_llm/agents/image/upscaler.rb +81 -0
  143. data/lib/ruby_llm/agents/image/variator/dsl.rb +62 -0
  144. data/lib/ruby_llm/agents/image/variator/execution.rb +189 -0
  145. data/lib/ruby_llm/agents/image/variator.rb +80 -0
  146. data/lib/ruby_llm/agents/{alert_manager.rb → infrastructure/alert_manager.rb} +17 -22
  147. data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +145 -0
  148. data/lib/ruby_llm/agents/infrastructure/budget/config_resolver.rb +149 -0
  149. data/lib/ruby_llm/agents/infrastructure/budget/forecaster.rb +68 -0
  150. data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +279 -0
  151. data/lib/ruby_llm/agents/infrastructure/budget_tracker.rb +275 -0
  152. data/lib/ruby_llm/agents/{execution_logger_job.rb → infrastructure/execution_logger_job.rb} +17 -1
  153. data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/executor.rb +2 -1
  154. data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/retry_strategy.rb +9 -3
  155. data/lib/ruby_llm/agents/{reliability.rb → infrastructure/reliability.rb} +11 -21
  156. data/lib/ruby_llm/agents/pipeline/builder.rb +215 -0
  157. data/lib/ruby_llm/agents/pipeline/context.rb +255 -0
  158. data/lib/ruby_llm/agents/pipeline/executor.rb +86 -0
  159. data/lib/ruby_llm/agents/pipeline/middleware/base.rb +124 -0
  160. data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +95 -0
  161. data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +171 -0
  162. data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +415 -0
  163. data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +276 -0
  164. data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +196 -0
  165. data/lib/ruby_llm/agents/pipeline.rb +68 -0
  166. data/lib/ruby_llm/agents/{engine.rb → rails/engine.rb} +79 -10
  167. data/lib/ruby_llm/agents/results/background_removal_result.rb +286 -0
  168. data/lib/ruby_llm/agents/{result.rb → results/base.rb} +73 -1
  169. data/lib/ruby_llm/agents/results/embedding_result.rb +243 -0
  170. data/lib/ruby_llm/agents/results/image_analysis_result.rb +314 -0
  171. data/lib/ruby_llm/agents/results/image_edit_result.rb +250 -0
  172. data/lib/ruby_llm/agents/results/image_generation_result.rb +346 -0
  173. data/lib/ruby_llm/agents/results/image_pipeline_result.rb +399 -0
  174. data/lib/ruby_llm/agents/results/image_transform_result.rb +251 -0
  175. data/lib/ruby_llm/agents/results/image_upscale_result.rb +255 -0
  176. data/lib/ruby_llm/agents/results/image_variation_result.rb +237 -0
  177. data/lib/ruby_llm/agents/results/moderation_result.rb +158 -0
  178. data/lib/ruby_llm/agents/results/speech_result.rb +338 -0
  179. data/lib/ruby_llm/agents/results/transcription_result.rb +408 -0
  180. data/lib/ruby_llm/agents/text/embedder.rb +444 -0
  181. data/lib/ruby_llm/agents/text/moderator.rb +237 -0
  182. data/lib/ruby_llm/agents/workflow/async.rb +220 -0
  183. data/lib/ruby_llm/agents/workflow/async_executor.rb +156 -0
  184. data/lib/ruby_llm/agents/{workflow.rb → workflow/orchestrator.rb} +6 -5
  185. data/lib/ruby_llm/agents/workflow/parallel.rb +34 -17
  186. data/lib/ruby_llm/agents/workflow/thread_pool.rb +185 -0
  187. data/lib/ruby_llm/agents.rb +86 -20
  188. metadata +189 -35
  189. data/lib/ruby_llm/agents/base/caching.rb +0 -40
  190. data/lib/ruby_llm/agents/base/cost_calculation.rb +0 -105
  191. data/lib/ruby_llm/agents/base/dsl.rb +0 -324
  192. data/lib/ruby_llm/agents/base/execution.rb +0 -283
  193. data/lib/ruby_llm/agents/base/reliability_dsl.rb +0 -82
  194. data/lib/ruby_llm/agents/base/reliability_execution.rb +0 -136
  195. data/lib/ruby_llm/agents/base/response_building.rb +0 -86
  196. data/lib/ruby_llm/agents/base/tool_tracking.rb +0 -57
  197. data/lib/ruby_llm/agents/base.rb +0 -209
  198. data/lib/ruby_llm/agents/budget_tracker.rb +0 -471
  199. data/lib/ruby_llm/agents/configuration.rb +0 -357
  200. /data/lib/ruby_llm/agents/{deprecations.rb → core/deprecations.rb} +0 -0
  201. /data/lib/ruby_llm/agents/{inflections.rb → core/inflections.rb} +0 -0
  202. /data/lib/ruby_llm/agents/{attempt_tracker.rb → infrastructure/attempt_tracker.rb} +0 -0
  203. /data/lib/ruby_llm/agents/{cache_helper.rb → infrastructure/cache_helper.rb} +0 -0
  204. /data/lib/ruby_llm/agents/{circuit_breaker.rb → infrastructure/circuit_breaker.rb} +0 -0
  205. /data/lib/ruby_llm/agents/{redactor.rb → infrastructure/redactor.rb} +0 -0
  206. /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/breaker_manager.rb +0 -0
  207. /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/execution_constraints.rb +0 -0
  208. /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/fallback_routing.rb +0 -0
@@ -1,324 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "reliability_dsl"
4
-
5
- module RubyLLM
6
- module Agents
7
- class Base
8
- # Class-level DSL for configuring agents
9
- #
10
- # Provides methods for setting model, temperature, timeout, caching,
11
- # reliability, streaming, tools, and parameters.
12
- module DSL
13
- # @!visibility private
14
- VERSION = "1.0"
15
- # @!visibility private
16
- CACHE_TTL = 1.hour
17
-
18
- # @!group Configuration DSL
19
-
20
- # Sets or returns the LLM model for this agent class
21
- #
22
- # @param value [String, nil] The model identifier to set
23
- # @return [String] The current model setting
24
- # @example
25
- # model "gpt-4o"
26
- def model(value = nil)
27
- @model = value if value
28
- @model || inherited_or_default(:model, RubyLLM::Agents.configuration.default_model)
29
- end
30
-
31
- # Sets or returns the temperature for LLM responses
32
- #
33
- # @param value [Float, nil] Temperature value (0.0-2.0)
34
- # @return [Float] The current temperature setting
35
- # @example
36
- # temperature 0.7
37
- def temperature(value = nil)
38
- @temperature = value if value
39
- @temperature || inherited_or_default(:temperature, RubyLLM::Agents.configuration.default_temperature)
40
- end
41
-
42
- # Sets or returns the version string for cache invalidation
43
- #
44
- # @param value [String, nil] Version string
45
- # @return [String] The current version
46
- # @example
47
- # version "2.0"
48
- def version(value = nil)
49
- @version = value if value
50
- @version || inherited_or_default(:version, VERSION)
51
- end
52
-
53
- # Sets or returns the description for this agent class
54
- #
55
- # @param value [String, nil] The description text
56
- # @return [String, nil] The current description
57
- # @example
58
- # description "Searches the knowledge base for relevant documents"
59
- def description(value = nil)
60
- @description = value if value
61
- @description || inherited_or_default(:description, nil)
62
- end
63
-
64
- # Sets or returns the timeout in seconds for LLM requests
65
- #
66
- # @param value [Integer, nil] Timeout in seconds
67
- # @return [Integer] The current timeout setting
68
- # @example
69
- # timeout 30
70
- def timeout(value = nil)
71
- @timeout = value if value
72
- @timeout || inherited_or_default(:timeout, RubyLLM::Agents.configuration.default_timeout)
73
- end
74
-
75
- # @!endgroup
76
-
77
- # @!group Reliability DSL
78
-
79
- # Configures reliability features using a block syntax
80
- #
81
- # Groups all reliability configuration in a single block for clarity.
82
- # Individual methods (retries, fallback_models, etc.) remain available
83
- # for backward compatibility.
84
- #
85
- # @yield Block containing reliability configuration
86
- # @return [void]
87
- # @example
88
- # reliability do
89
- # retries max: 3, backoff: :exponential
90
- # fallback_models "gpt-4o-mini"
91
- # total_timeout 30
92
- # circuit_breaker errors: 5
93
- # end
94
- def reliability(&block)
95
- builder = ReliabilityDSL.new
96
- builder.instance_eval(&block)
97
-
98
- @retries_config = builder.retries_config if builder.retries_config
99
- @fallback_models = builder.fallback_models_list if builder.fallback_models_list.any?
100
- @total_timeout = builder.total_timeout_value if builder.total_timeout_value
101
- @circuit_breaker_config = builder.circuit_breaker_config if builder.circuit_breaker_config
102
- end
103
-
104
- # Configures retry behavior for this agent
105
- #
106
- # @param max [Integer] Maximum number of retry attempts (default: 0)
107
- # @param backoff [Symbol] Backoff strategy (:constant or :exponential)
108
- # @param base [Float] Base delay in seconds
109
- # @param max_delay [Float] Maximum delay between retries
110
- # @param on [Array<Class>] Error classes to retry on (extends defaults)
111
- # @return [Hash] The current retry configuration
112
- # @example
113
- # retries max: 2, backoff: :exponential, base: 0.4, max_delay: 3.0, on: [Timeout::Error]
114
- def retries(max: nil, backoff: nil, base: nil, max_delay: nil, on: nil)
115
- if max || backoff || base || max_delay || on
116
- @retries_config ||= RubyLLM::Agents.configuration.default_retries.dup
117
- @retries_config[:max] = max if max
118
- @retries_config[:backoff] = backoff if backoff
119
- @retries_config[:base] = base if base
120
- @retries_config[:max_delay] = max_delay if max_delay
121
- @retries_config[:on] = on if on
122
- end
123
- @retries_config || inherited_or_default(:retries_config, RubyLLM::Agents.configuration.default_retries)
124
- end
125
-
126
- # Returns the retry configuration for this agent
127
- #
128
- # @return [Hash, nil] The retry configuration
129
- def retries_config
130
- @retries_config || (superclass.respond_to?(:retries_config) ? superclass.retries_config : nil)
131
- end
132
-
133
- # Sets or returns fallback models to try when primary model fails
134
- #
135
- # @param models [Array<String>, nil] Model identifiers to use as fallbacks
136
- # @return [Array<String>] The current fallback models
137
- # @example
138
- # fallback_models ["gpt-4o-mini", "gpt-4o"]
139
- def fallback_models(models = nil)
140
- @fallback_models = models if models
141
- @fallback_models || inherited_or_default(:fallback_models, RubyLLM::Agents.configuration.default_fallback_models)
142
- end
143
-
144
- # Sets or returns the total timeout for all retry/fallback attempts
145
- #
146
- # @param seconds [Integer, nil] Total timeout in seconds
147
- # @return [Integer, nil] The current total timeout
148
- # @example
149
- # total_timeout 20
150
- def total_timeout(seconds = nil)
151
- @total_timeout = seconds if seconds
152
- @total_timeout || inherited_or_default(:total_timeout, RubyLLM::Agents.configuration.default_total_timeout)
153
- end
154
-
155
- # Configures circuit breaker for this agent
156
- #
157
- # @param errors [Integer] Number of errors to trigger open state
158
- # @param within [Integer] Rolling window in seconds
159
- # @param cooldown [Integer] Cooldown period in seconds when open
160
- # @return [Hash, nil] The current circuit breaker configuration
161
- # @example
162
- # circuit_breaker errors: 10, within: 60, cooldown: 300
163
- def circuit_breaker(errors: nil, within: nil, cooldown: nil)
164
- if errors || within || cooldown
165
- @circuit_breaker_config ||= { errors: 10, within: 60, cooldown: 300 }
166
- @circuit_breaker_config[:errors] = errors if errors
167
- @circuit_breaker_config[:within] = within if within
168
- @circuit_breaker_config[:cooldown] = cooldown if cooldown
169
- end
170
- @circuit_breaker_config || (superclass.respond_to?(:circuit_breaker_config) ? superclass.circuit_breaker_config : nil)
171
- end
172
-
173
- # Returns the circuit breaker configuration for this agent
174
- #
175
- # @return [Hash, nil] The circuit breaker configuration
176
- def circuit_breaker_config
177
- @circuit_breaker_config || (superclass.respond_to?(:circuit_breaker_config) ? superclass.circuit_breaker_config : nil)
178
- end
179
-
180
- # @!endgroup
181
-
182
- # @!group Parameter DSL
183
-
184
- # Defines a parameter for the agent
185
- #
186
- # Creates an accessor method for the parameter that retrieves values
187
- # from the options hash, falling back to the default value.
188
- #
189
- # @param name [Symbol] The parameter name
190
- # @param required [Boolean] Whether the parameter is required
191
- # @param default [Object, nil] Default value if not provided
192
- # @param type [Class, nil] Optional type for validation (e.g., String, Integer, Array)
193
- # @return [void]
194
- # @example Without type (accepts anything)
195
- # param :query, required: true
196
- # param :data, default: {}
197
- # @example With type validation
198
- # param :limit, default: 10, type: Integer
199
- # param :name, type: String
200
- # param :tags, type: Array
201
- def param(name, required: false, default: nil, type: nil)
202
- @params ||= {}
203
- @params[name] = { required: required, default: default, type: type }
204
- define_method(name) do
205
- @options[name] || @options[name.to_s] || self.class.params.dig(name, :default)
206
- end
207
- end
208
-
209
- # Returns all defined parameters including inherited ones
210
- #
211
- # @return [Hash{Symbol => Hash}] Parameter definitions
212
- def params
213
- parent = superclass.respond_to?(:params) ? superclass.params : {}
214
- parent.merge(@params || {})
215
- end
216
-
217
- # @!endgroup
218
-
219
- # @!group Caching DSL
220
-
221
- # Enables caching for this agent with explicit TTL
222
- #
223
- # This is the preferred method for enabling caching.
224
- #
225
- # @param ttl [ActiveSupport::Duration] Time-to-live for cached responses
226
- # @return [void]
227
- # @example
228
- # cache_for 1.hour
229
- # cache_for 30.minutes
230
- def cache_for(ttl)
231
- @cache_enabled = true
232
- @cache_ttl = ttl
233
- end
234
-
235
- # Enables caching for this agent with optional TTL
236
- #
237
- # @deprecated Use {#cache_for} instead for clarity.
238
- # This method will be removed in version 1.0.
239
- # @param ttl [ActiveSupport::Duration] Time-to-live for cached responses
240
- # @return [void]
241
- # @example
242
- # cache 1.hour # deprecated
243
- # cache_for 1.hour # preferred
244
- def cache(ttl = CACHE_TTL)
245
- RubyLLM::Agents::Deprecations.warn(
246
- "cache(ttl) is deprecated. Use cache_for(ttl) instead for clarity.",
247
- caller
248
- )
249
- cache_for(ttl)
250
- end
251
-
252
- # Returns whether caching is enabled for this agent
253
- #
254
- # @return [Boolean] true if caching is enabled
255
- def cache_enabled?
256
- @cache_enabled || false
257
- end
258
-
259
- # Returns the cache TTL for this agent
260
- #
261
- # @return [ActiveSupport::Duration] The cache TTL
262
- def cache_ttl
263
- @cache_ttl || CACHE_TTL
264
- end
265
-
266
- # @!endgroup
267
-
268
- # @!group Streaming DSL
269
-
270
- # Enables or returns streaming mode for this agent
271
- #
272
- # When streaming is enabled and a block is passed to call,
273
- # chunks will be yielded to the block as they arrive.
274
- #
275
- # @param value [Boolean, nil] Whether to enable streaming
276
- # @return [Boolean] The current streaming setting
277
- # @example
278
- # streaming true
279
- def streaming(value = nil)
280
- @streaming = value unless value.nil?
281
- return @streaming unless @streaming.nil?
282
-
283
- inherited_or_default(:streaming, RubyLLM::Agents.configuration.default_streaming)
284
- end
285
-
286
- # @!endgroup
287
-
288
- # @!group Tools DSL
289
-
290
- # Sets or returns the tools available to this agent
291
- #
292
- # Tools are RubyLLM::Tool classes that the model can invoke.
293
- # The agent will automatically execute tool calls and continue
294
- # until the model produces a final response.
295
- #
296
- # @param tool_classes [Array<Class>] Tool classes to make available
297
- # @return [Array<Class>] The current tools
298
- # @example With array (preferred)
299
- # tools [WeatherTool, SearchTool, CalculatorTool]
300
- # @example Single tool
301
- # tools [WeatherTool]
302
- def tools(tool_classes = nil)
303
- if tool_classes
304
- @tools = Array(tool_classes)
305
- end
306
- @tools || inherited_or_default(:tools, RubyLLM::Agents.configuration.default_tools)
307
- end
308
-
309
- # @!endgroup
310
-
311
- private
312
-
313
- # Looks up setting from superclass or uses default
314
- #
315
- # @param method [Symbol] The method to call on superclass
316
- # @param default [Object] Default value if not found
317
- # @return [Object] The resolved value
318
- def inherited_or_default(method, default)
319
- superclass.respond_to?(method) ? superclass.send(method) : default
320
- end
321
- end
322
- end
323
- end
324
- end
@@ -1,283 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Agents
5
- class Base
6
- # Main execution flow for agents
7
- #
8
- # Handles the core execution logic including caching, streaming,
9
- # client building, and parameter validation.
10
- module Execution
11
- # Executes the agent and returns the processed response
12
- #
13
- # Handles caching, dry-run mode, and delegates to uncached_call
14
- # for actual LLM execution.
15
- #
16
- # @yield [chunk] Yields chunks when streaming is enabled
17
- # @yieldparam chunk [RubyLLM::Chunk] A streaming chunk with content
18
- # @return [Object] The processed LLM response
19
- def call(&block)
20
- return dry_run_response if @options[:dry_run]
21
- return uncached_call(&block) if @options[:skip_cache] || !self.class.cache_enabled?
22
-
23
- cache_key = agent_cache_key
24
-
25
- # Check for cache hit BEFORE fetch to record it
26
- if cache_store.exist?(cache_key)
27
- started_at = Time.current
28
- cached_result = cache_store.read(cache_key)
29
- record_cache_hit_execution(cache_key, cached_result, started_at) if cached_result
30
- return cached_result
31
- end
32
-
33
- # Cache miss - execute and store
34
- cache_store.fetch(cache_key, expires_in: self.class.cache_ttl) do
35
- uncached_call(&block)
36
- end
37
- end
38
-
39
- # Executes the agent without caching
40
- #
41
- # Routes to reliability-enabled execution if configured, otherwise
42
- # uses simple single-attempt execution.
43
- #
44
- # @yield [chunk] Yields chunks when streaming is enabled
45
- # @return [Object] The processed response
46
- def uncached_call(&block)
47
- if reliability_enabled?
48
- execute_with_reliability(&block)
49
- else
50
- instrument_execution { execute_single_attempt(&block) }
51
- end
52
- end
53
-
54
- # Executes a single LLM attempt with timeout
55
- #
56
- # @param model_override [String, nil] Optional model to use instead of default
57
- # @yield [chunk] Yields chunks when streaming is enabled
58
- # @return [Result] A Result object with processed content and metadata
59
- def execute_single_attempt(model_override: nil, &block)
60
- current_client = model_override ? build_client_with_model(model_override) : client
61
- @execution_started_at ||= Time.current
62
- reset_accumulated_tool_calls!
63
-
64
- Timeout.timeout(self.class.timeout) do
65
- if streaming_enabled? && block_given?
66
- execute_with_streaming(current_client, &block)
67
- else
68
- response = current_client.ask(user_prompt, **ask_options)
69
- extract_tool_calls_from_client(current_client)
70
- capture_response(response)
71
- build_result(process_response(response), response)
72
- end
73
- end
74
- end
75
-
76
- # Executes an LLM request with streaming enabled
77
- #
78
- # Yields chunks to the provided block as they arrive and tracks
79
- # time to first token for latency analysis.
80
- #
81
- # @param current_client [RubyLLM::Chat] The configured client
82
- # @yield [chunk] Yields each chunk as it arrives
83
- # @yieldparam chunk [RubyLLM::Chunk] A streaming chunk
84
- # @return [Result] A Result object with processed content and metadata
85
- def execute_with_streaming(current_client, &block)
86
- first_chunk_at = nil
87
-
88
- response = current_client.ask(user_prompt, **ask_options) do |chunk|
89
- first_chunk_at ||= Time.current
90
- yield chunk if block_given?
91
- end
92
-
93
- if first_chunk_at && @execution_started_at
94
- @time_to_first_token_ms = ((first_chunk_at - @execution_started_at) * 1000).to_i
95
- end
96
-
97
- extract_tool_calls_from_client(current_client)
98
- capture_response(response)
99
- build_result(process_response(response), response)
100
- end
101
-
102
- # Returns prompt info without making an API call (debug mode)
103
- #
104
- # @return [Result] A Result with dry run configuration info
105
- def dry_run_response
106
- Result.new(
107
- content: {
108
- dry_run: true,
109
- agent: self.class.name,
110
- model: model,
111
- temperature: temperature,
112
- timeout: self.class.timeout,
113
- system_prompt: system_prompt,
114
- user_prompt: user_prompt,
115
- attachments: @options[:with],
116
- schema: schema&.class&.name,
117
- streaming: self.class.streaming,
118
- tools: resolved_tools.map { |t| t.respond_to?(:name) ? t.name : t.to_s }
119
- },
120
- model_id: model,
121
- temperature: temperature,
122
- streaming: self.class.streaming
123
- )
124
- end
125
-
126
- # Resolves tools for this execution
127
- #
128
- # Checks for instance method override first (for dynamic tools),
129
- # then falls back to class-level DSL configuration. This allows
130
- # agents to define tools dynamically based on runtime context.
131
- #
132
- # @return [Array<Class>] Tool classes to use
133
- def resolved_tools
134
- # Check if instance defines tools method (not inherited from class singleton)
135
- if self.class.instance_methods(false).include?(:tools)
136
- tools
137
- else
138
- self.class.tools
139
- end
140
- end
141
-
142
- # Resolves messages for this execution
143
- #
144
- # Priority order:
145
- # 1. @override_messages (set via with_messages)
146
- # 2. :messages option passed at call time
147
- # 3. messages template method defined in subclass
148
- #
149
- # @return [Array<Hash>] Messages to apply to conversation
150
- def resolved_messages
151
- return @override_messages if @override_messages&.any?
152
- return @options[:messages] if @options[:messages]&.any?
153
-
154
- messages
155
- end
156
-
157
- # Returns the consolidated reliability configuration for this agent instance
158
- #
159
- # @return [Hash] Reliability config with :retries, :fallback_models, :total_timeout, :circuit_breaker
160
- def reliability_config
161
- default_retries = RubyLLM::Agents.configuration.default_retries
162
- {
163
- retries: self.class.retries || default_retries,
164
- fallback_models: self.class.fallback_models,
165
- total_timeout: self.class.total_timeout,
166
- circuit_breaker: self.class.circuit_breaker_config
167
- }
168
- end
169
-
170
- # Returns whether any reliability features are enabled for this agent
171
- #
172
- # @return [Boolean] true if retries, fallbacks, or circuit breaker is configured
173
- def reliability_enabled?
174
- config = reliability_config
175
- (config[:retries]&.dig(:max) || 0) > 0 ||
176
- config[:fallback_models]&.any? ||
177
- config[:circuit_breaker].present?
178
- end
179
-
180
- # Returns whether streaming is enabled for this execution
181
- #
182
- # Checks both class-level DSL setting and instance-level override
183
- # (set by the stream class method).
184
- #
185
- # @return [Boolean] true if streaming is enabled
186
- def streaming_enabled?
187
- @force_streaming || self.class.streaming
188
- end
189
-
190
- # Returns options to pass to the ask method
191
- #
192
- # Currently supports :with for attachments (images, PDFs, etc.)
193
- #
194
- # @return [Hash] Options for the ask call
195
- def ask_options
196
- opts = {}
197
- opts[:with] = @options[:with] if @options[:with]
198
- opts
199
- end
200
-
201
- # Validates that all required parameters are present and types match
202
- #
203
- # @raise [ArgumentError] If required parameters are missing or types don't match
204
- # @return [void]
205
- def validate_required_params!
206
- self.class.params.each do |name, config|
207
- value = @options[name] || @options[name.to_s]
208
- has_value = @options.key?(name) || @options.key?(name.to_s)
209
-
210
- # Check required
211
- if config[:required] && !has_value
212
- raise ArgumentError, "#{self.class} missing required param: #{name}"
213
- end
214
-
215
- # Check type if specified and value is present (not nil)
216
- if config[:type] && has_value && !value.nil?
217
- unless value.is_a?(config[:type])
218
- raise ArgumentError,
219
- "#{self.class} expected #{config[:type]} for :#{name}, got #{value.class}"
220
- end
221
- end
222
- end
223
- end
224
-
225
- # Builds and configures the RubyLLM client
226
- #
227
- # @return [RubyLLM::Chat] Configured chat client
228
- def build_client
229
- client = RubyLLM.chat
230
- .with_model(model)
231
- .with_temperature(temperature)
232
- client = client.with_instructions(system_prompt) if system_prompt
233
- client = client.with_schema(schema) if schema
234
- client = client.with_tools(*resolved_tools) if resolved_tools.any?
235
- client = apply_messages(client, resolved_messages) if resolved_messages.any?
236
- client
237
- end
238
-
239
- # Builds a client with a specific model
240
- #
241
- # @param model_id [String] The model identifier
242
- # @return [RubyLLM::Chat] Configured chat client
243
- def build_client_with_model(model_id)
244
- client = RubyLLM.chat
245
- .with_model(model_id)
246
- .with_temperature(temperature)
247
- client = client.with_instructions(system_prompt) if system_prompt
248
- client = client.with_schema(schema) if schema
249
- client = client.with_tools(*resolved_tools) if resolved_tools.any?
250
- client = apply_messages(client, resolved_messages) if resolved_messages.any?
251
- client
252
- end
253
-
254
- # Applies conversation history to the client
255
- #
256
- # @param client [RubyLLM::Chat] The chat client
257
- # @param msgs [Array<Hash>] Messages with :role and :content keys
258
- # @return [RubyLLM::Chat] Client with messages applied
259
- def apply_messages(client, msgs)
260
- msgs.each do |message|
261
- client.add_message(role: message[:role].to_sym, content: message[:content])
262
- end
263
- client
264
- end
265
-
266
- # Builds a client with pre-populated conversation history
267
- #
268
- # @deprecated Use resolved_messages and apply_messages instead.
269
- # Override the messages template method or pass messages: option to call.
270
- # @param messages [Array<Hash>] Messages with :role and :content keys
271
- # @return [RubyLLM::Chat] Client with messages added
272
- # @example
273
- # build_client_with_messages([
274
- # { role: "user", content: "Hello" },
275
- # { role: "assistant", content: "Hi there!" }
276
- # ])
277
- def build_client_with_messages(messages)
278
- apply_messages(build_client, messages)
279
- end
280
- end
281
- end
282
- end
283
- end
@@ -1,82 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Agents
5
- class Base
6
- # DSL builder for reliability configuration
7
- #
8
- # Provides a block-based configuration syntax for grouping
9
- # all reliability settings together.
10
- #
11
- # @example Basic usage
12
- # class MyAgent < ApplicationAgent
13
- # reliability do
14
- # retries max: 3, backoff: :exponential
15
- # fallback_models "gpt-4o-mini"
16
- # total_timeout 30
17
- # circuit_breaker errors: 5, within: 60
18
- # end
19
- # end
20
- #
21
- # @api public
22
- class ReliabilityDSL
23
- attr_reader :retries_config, :fallback_models_list, :total_timeout_value, :circuit_breaker_config
24
-
25
- def initialize
26
- @retries_config = nil
27
- @fallback_models_list = []
28
- @total_timeout_value = nil
29
- @circuit_breaker_config = nil
30
- end
31
-
32
- # Configures retry behavior
33
- #
34
- # @param max [Integer] Maximum retry attempts
35
- # @param backoff [Symbol] :constant or :exponential
36
- # @param base [Float] Base delay in seconds
37
- # @param max_delay [Float] Maximum delay between retries
38
- # @param on [Array<Class>] Additional error classes to retry on
39
- # @return [void]
40
- def retries(max: 0, backoff: :exponential, base: 0.4, max_delay: 3.0, on: [])
41
- @retries_config = {
42
- max: max,
43
- backoff: backoff,
44
- base: base,
45
- max_delay: max_delay,
46
- on: on
47
- }
48
- end
49
-
50
- # Sets fallback models
51
- #
52
- # @param models [Array<String>] Model identifiers
53
- # @return [void]
54
- def fallback_models(*models)
55
- @fallback_models_list = models.flatten
56
- end
57
-
58
- # Sets total timeout across all retry/fallback attempts
59
- #
60
- # @param seconds [Integer] Total timeout in seconds
61
- # @return [void]
62
- def total_timeout(seconds)
63
- @total_timeout_value = seconds
64
- end
65
-
66
- # Configures circuit breaker
67
- #
68
- # @param errors [Integer] Failure threshold
69
- # @param within [Integer] Rolling window in seconds
70
- # @param cooldown [Integer] Cooldown period in seconds
71
- # @return [void]
72
- def circuit_breaker(errors: 10, within: 60, cooldown: 300)
73
- @circuit_breaker_config = {
74
- errors: errors,
75
- within: within,
76
- cooldown: cooldown
77
- }
78
- end
79
- end
80
- end
81
- end
82
- end