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
@@ -0,0 +1,348 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Agents
5
+ # Resolves API configuration with priority chain
6
+ #
7
+ # Resolution order:
8
+ # 1. Tenant-specific database config (if tenant_id provided)
9
+ # 2. Global database config
10
+ # 3. RubyLLM.configuration (set via initializer or environment)
11
+ #
12
+ # This class provides a unified interface for accessing configuration
13
+ # values regardless of their source, and can apply the resolved
14
+ # configuration to RubyLLM.
15
+ #
16
+ # @example Basic resolution
17
+ # resolved = ResolvedConfig.new(
18
+ # tenant_config: ApiConfiguration.for_tenant("acme"),
19
+ # global_config: ApiConfiguration.global,
20
+ # ruby_llm_config: RubyLLM.configuration
21
+ # )
22
+ #
23
+ # resolved.openai_api_key # => Returns from highest priority source
24
+ # resolved.source_for(:openai_api_key) # => "tenant:acme"
25
+ #
26
+ # @example Applying to RubyLLM
27
+ # resolved.apply_to_ruby_llm! # Applies all resolved values
28
+ #
29
+ # @see ApiConfiguration
30
+ # @api public
31
+ class ResolvedConfig
32
+ # Returns all resolvable attributes (API keys + settings)
33
+ # Lazy-loaded to avoid circular dependency with ApiConfiguration
34
+ #
35
+ # @return [Array<Symbol>]
36
+ def self.resolvable_attributes
37
+ @resolvable_attributes ||= (
38
+ ApiConfiguration::API_KEY_ATTRIBUTES +
39
+ ApiConfiguration::NON_KEY_ATTRIBUTES
40
+ ).freeze
41
+ end
42
+
43
+ # @return [ApiConfiguration, nil] Tenant-specific configuration
44
+ attr_reader :tenant_config
45
+
46
+ # @return [ApiConfiguration, nil] Global database configuration
47
+ attr_reader :global_config
48
+
49
+ # @return [Object] RubyLLM configuration object
50
+ attr_reader :ruby_llm_config
51
+
52
+ # Creates a new resolved configuration
53
+ #
54
+ # @param tenant_config [ApiConfiguration, nil] Tenant-specific config
55
+ # @param global_config [ApiConfiguration, nil] Global database config
56
+ # @param ruby_llm_config [Object] RubyLLM.configuration
57
+ def initialize(tenant_config:, global_config:, ruby_llm_config:)
58
+ @tenant_config = tenant_config
59
+ @global_config = global_config
60
+ @ruby_llm_config = ruby_llm_config
61
+ @resolved_cache = {}
62
+ end
63
+
64
+ # Resolves a specific attribute value using the priority chain
65
+ #
66
+ # @param attr_name [Symbol, String] The attribute name
67
+ # @return [Object, nil] The resolved value
68
+ def resolve(attr_name)
69
+ attr_sym = attr_name.to_sym
70
+ return @resolved_cache[attr_sym] if @resolved_cache.key?(attr_sym)
71
+
72
+ @resolved_cache[attr_sym] = resolve_attribute(attr_sym)
73
+ end
74
+
75
+ # Returns the source of a resolved attribute value
76
+ #
77
+ # @param attr_name [Symbol, String] The attribute name
78
+ # @return [String] Source label: "tenant:ID", "global_db", "ruby_llm_config", or "not_set"
79
+ def source_for(attr_name)
80
+ attr_sym = attr_name.to_sym
81
+
82
+ # Check tenant config first (if present and inherits or has value)
83
+ if tenant_config&.has_value?(attr_sym)
84
+ return "tenant:#{tenant_config.scope_id}"
85
+ end
86
+
87
+ # Check global DB config (only if tenant inherits or no tenant)
88
+ if should_check_global?(attr_sym) && global_config&.has_value?(attr_sym)
89
+ return "global_db"
90
+ end
91
+
92
+ # Check RubyLLM config
93
+ if ruby_llm_value_present?(attr_sym)
94
+ return "ruby_llm_config"
95
+ end
96
+
97
+ "not_set"
98
+ end
99
+
100
+ # Returns all resolved values as a hash
101
+ #
102
+ # @return [Hash] All resolved configuration values
103
+ def to_hash
104
+ self.class.resolvable_attributes.each_with_object({}) do |attr, hash|
105
+ value = resolve(attr)
106
+ hash[attr] = value if value.present?
107
+ end
108
+ end
109
+
110
+ # Returns a hash suitable for RubyLLM configuration
111
+ #
112
+ # Only includes values that differ from or override the current
113
+ # RubyLLM configuration.
114
+ #
115
+ # @return [Hash] Configuration hash for RubyLLM
116
+ def to_ruby_llm_options
117
+ to_hash.slice(*ruby_llm_configurable_attributes)
118
+ end
119
+
120
+ # Applies the resolved configuration to RubyLLM
121
+ #
122
+ # This temporarily overrides RubyLLM.configuration with the
123
+ # resolved values. Useful for per-request configuration.
124
+ #
125
+ # @return [void]
126
+ def apply_to_ruby_llm!
127
+ options = to_ruby_llm_options
128
+ return if options.empty?
129
+
130
+ RubyLLM.configure do |config|
131
+ options.each do |key, value|
132
+ setter = "#{key}="
133
+ config.public_send(setter, value) if config.respond_to?(setter)
134
+ end
135
+ end
136
+ end
137
+
138
+ # Dynamic accessor for resolvable attributes
139
+ # Uses method_missing to provide accessors without eager loading constants
140
+ #
141
+ # @param method_name [Symbol] The method being called
142
+ # @param args [Array] Method arguments
143
+ # @return [Object] The resolved value for the attribute
144
+ def method_missing(method_name, *args)
145
+ if self.class.resolvable_attributes.include?(method_name)
146
+ resolve(method_name)
147
+ else
148
+ super
149
+ end
150
+ end
151
+
152
+ # Indicates which dynamic methods are supported
153
+ #
154
+ # @param method_name [Symbol] The method name to check
155
+ # @param include_private [Boolean] Whether to include private methods
156
+ # @return [Boolean] True if the method is a resolvable attribute
157
+ def respond_to_missing?(method_name, include_private = false)
158
+ self.class.resolvable_attributes.include?(method_name) || super
159
+ end
160
+
161
+ # Returns provider status with source information
162
+ #
163
+ # @return [Array<Hash>] Provider status with source info
164
+ def provider_statuses_with_source
165
+ ApiConfiguration::PROVIDERS.map do |key, info|
166
+ key_attr = info[:key_attr]
167
+ value = resolve(key_attr)
168
+
169
+ {
170
+ key: key,
171
+ name: info[:name],
172
+ configured: value.present?,
173
+ masked_key: value.present? ? mask_string(value) : nil,
174
+ source: source_for(key_attr),
175
+ capabilities: info[:capabilities]
176
+ }
177
+ end
178
+ end
179
+
180
+ # Checks if any database configuration exists
181
+ #
182
+ # @return [Boolean]
183
+ def has_db_config?
184
+ tenant_config.present? || global_config.present?
185
+ end
186
+
187
+ # Returns summary of configuration sources
188
+ #
189
+ # @return [Hash] Summary with counts per source
190
+ def source_summary
191
+ summary = Hash.new(0)
192
+
193
+ self.class.resolvable_attributes.each do |attr|
194
+ source = source_for(attr)
195
+ summary[source] += 1 if resolve(attr).present?
196
+ end
197
+
198
+ summary
199
+ end
200
+
201
+ # Public method to get raw RubyLLM config value for an attribute
202
+ # This returns the value from RubyLLM.configuration (initializer/ENV)
203
+ # regardless of any database overrides.
204
+ #
205
+ # @param attr_name [Symbol, String] The attribute name
206
+ # @return [Object, nil] The RubyLLM config value
207
+ def ruby_llm_value_for(attr_name)
208
+ ruby_llm_value(attr_name.to_sym)
209
+ end
210
+
211
+ # Masks a string for display (public wrapper)
212
+ #
213
+ # @param value [String] The string to mask
214
+ # @return [String] Masked string
215
+ def mask_string(value)
216
+ return nil if value.blank?
217
+ return "****" if value.length <= 8
218
+
219
+ "#{value[0..1]}****#{value[-4..]}"
220
+ end
221
+
222
+ private
223
+
224
+ # Resolves a single attribute using the priority chain
225
+ #
226
+ # @param attr_sym [Symbol] The attribute name
227
+ # @return [Object, nil] The resolved value
228
+ def resolve_attribute(attr_sym)
229
+ # 1. Check tenant config
230
+ if tenant_config&.has_value?(attr_sym)
231
+ return tenant_config.send(attr_sym)
232
+ end
233
+
234
+ # 2. Check global DB config (if tenant allows inheritance or no tenant)
235
+ if should_check_global?(attr_sym)
236
+ if global_config&.has_value?(attr_sym)
237
+ return global_config.send(attr_sym)
238
+ end
239
+ end
240
+
241
+ # 3. Fall back to RubyLLM config
242
+ ruby_llm_value(attr_sym)
243
+ end
244
+
245
+ # Determines if we should check global config
246
+ #
247
+ # @param attr_sym [Symbol] The attribute name
248
+ # @return [Boolean]
249
+ def should_check_global?(attr_sym)
250
+ return true unless tenant_config
251
+
252
+ # If tenant has inherit_global_defaults enabled, check global
253
+ tenant_config.inherit_global_defaults != false
254
+ end
255
+
256
+ # Gets a value from RubyLLM configuration
257
+ #
258
+ # @param attr_sym [Symbol] The attribute name
259
+ # @return [Object, nil]
260
+ def ruby_llm_value(attr_sym)
261
+ return nil unless ruby_llm_config
262
+
263
+ # Map our attribute names to RubyLLM config method names
264
+ method_name = ruby_llm_config_mapping(attr_sym)
265
+ return nil unless method_name
266
+
267
+ if ruby_llm_config.respond_to?(method_name)
268
+ ruby_llm_config.send(method_name)
269
+ end
270
+ rescue StandardError
271
+ nil
272
+ end
273
+
274
+ # Checks if a RubyLLM config value is present
275
+ #
276
+ # @param attr_sym [Symbol] The attribute name
277
+ # @return [Boolean]
278
+ def ruby_llm_value_present?(attr_sym)
279
+ value = ruby_llm_value(attr_sym)
280
+ value.present?
281
+ end
282
+
283
+ # Maps our attribute names to RubyLLM configuration method names
284
+ #
285
+ # @param attr_sym [Symbol] Our attribute name
286
+ # @return [Symbol, nil] RubyLLM config method name
287
+ def ruby_llm_config_mapping(attr_sym)
288
+ # Most attributes map directly
289
+ mapping = {
290
+ openai_api_key: :openai_api_key,
291
+ anthropic_api_key: :anthropic_api_key,
292
+ gemini_api_key: :gemini_api_key,
293
+ deepseek_api_key: :deepseek_api_key,
294
+ mistral_api_key: :mistral_api_key,
295
+ perplexity_api_key: :perplexity_api_key,
296
+ openrouter_api_key: :openrouter_api_key,
297
+ gpustack_api_key: :gpustack_api_key,
298
+ xai_api_key: :xai_api_key,
299
+ ollama_api_key: :ollama_api_key,
300
+ bedrock_api_key: :bedrock_api_key,
301
+ bedrock_secret_key: :bedrock_secret_key,
302
+ bedrock_session_token: :bedrock_session_token,
303
+ bedrock_region: :bedrock_region,
304
+ vertexai_credentials: :vertexai_credentials,
305
+ vertexai_project_id: :vertexai_project_id,
306
+ vertexai_location: :vertexai_location,
307
+ openai_api_base: :openai_api_base,
308
+ gemini_api_base: :gemini_api_base,
309
+ ollama_api_base: :ollama_api_base,
310
+ gpustack_api_base: :gpustack_api_base,
311
+ xai_api_base: :xai_api_base,
312
+ openai_organization_id: :openai_organization_id,
313
+ openai_project_id: :openai_project_id,
314
+ default_model: :default_model,
315
+ default_embedding_model: :default_embedding_model,
316
+ default_image_model: :default_image_model,
317
+ default_moderation_model: :default_moderation_model,
318
+ request_timeout: :request_timeout,
319
+ max_retries: :max_retries,
320
+ retry_interval: :retry_interval,
321
+ retry_backoff_factor: :retry_backoff_factor,
322
+ retry_interval_randomness: :retry_interval_randomness,
323
+ http_proxy: :http_proxy
324
+ }
325
+
326
+ mapping[attr_sym]
327
+ end
328
+
329
+ # Returns attributes that can be set on RubyLLM configuration
330
+ #
331
+ # @return [Array<Symbol>]
332
+ def ruby_llm_configurable_attributes
333
+ ApiConfiguration::API_KEY_ATTRIBUTES +
334
+ ApiConfiguration::ENDPOINT_ATTRIBUTES +
335
+ ApiConfiguration::MODEL_ATTRIBUTES +
336
+ ApiConfiguration::CONNECTION_ATTRIBUTES +
337
+ %i[
338
+ openai_organization_id
339
+ openai_project_id
340
+ bedrock_region
341
+ vertexai_project_id
342
+ vertexai_location
343
+ ]
344
+ end
345
+
346
+ end
347
+ end
348
+ end
@@ -4,6 +4,6 @@ module RubyLLM
4
4
  module Agents
5
5
  # Current version of the RubyLLM::Agents gem
6
6
  # @return [String] Semantic version string
7
- VERSION = "0.4.0"
7
+ VERSION = "1.0.0.beta.1"
8
8
  end
9
9
  end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Agents
5
+ module DSL
6
+ # Base DSL available to all agents.
7
+ #
8
+ # Provides common configuration methods that every agent type needs:
9
+ # - model: The LLM model to use
10
+ # - version: Cache invalidation version
11
+ # - description: Human-readable description
12
+ # - timeout: Request timeout
13
+ #
14
+ # @example Basic usage
15
+ # class MyAgent < RubyLLM::Agents::BaseAgent
16
+ # extend DSL::Base
17
+ #
18
+ # model "gpt-4o"
19
+ # version "2.0"
20
+ # description "A helpful agent"
21
+ # end
22
+ #
23
+ module Base
24
+ # @!group Configuration DSL
25
+
26
+ # Sets or returns the LLM model for this agent class
27
+ #
28
+ # @param value [String, nil] The model identifier to set
29
+ # @return [String] The current model setting
30
+ # @example
31
+ # model "gpt-4o"
32
+ def model(value = nil)
33
+ @model = value if value
34
+ @model || inherited_or_default(:model, default_model)
35
+ end
36
+
37
+ # Sets or returns the version string for cache invalidation
38
+ #
39
+ # Change this when you want to invalidate cached results
40
+ # (e.g., after changing prompts or behavior).
41
+ #
42
+ # @param value [String, nil] Version string
43
+ # @return [String] The current version
44
+ # @example
45
+ # version "2.0"
46
+ def version(value = nil)
47
+ @version = value if value
48
+ @version || inherited_or_default(:version, "1.0")
49
+ end
50
+
51
+ # Sets or returns the description for this agent class
52
+ #
53
+ # Useful for documentation and tool registration.
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, default_timeout)
73
+ end
74
+
75
+ # @!endgroup
76
+
77
+ private
78
+
79
+ # Looks up setting from superclass or uses default
80
+ #
81
+ # @param method [Symbol] The method to call on superclass
82
+ # @param default [Object] Default value if not found
83
+ # @return [Object] The resolved value
84
+ def inherited_or_default(method, default)
85
+ return default unless superclass.respond_to?(method)
86
+
87
+ superclass.send(method)
88
+ end
89
+
90
+ # Returns the default model from configuration
91
+ #
92
+ # @return [String] The default model
93
+ def default_model
94
+ RubyLLM::Agents.configuration.default_model
95
+ rescue StandardError
96
+ "gpt-4o"
97
+ end
98
+
99
+ # Returns the default timeout from configuration
100
+ #
101
+ # @return [Integer] The default timeout
102
+ def default_timeout
103
+ RubyLLM::Agents.configuration.default_timeout
104
+ rescue StandardError
105
+ 120
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -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