ruby_llm-agents 0.3.6 → 0.5.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 +46 -13
- data/app/controllers/ruby_llm/agents/api_configurations_controller.rb +214 -0
- data/app/controllers/ruby_llm/agents/{settings_controller.rb → system_config_controller.rb} +3 -3
- data/app/controllers/ruby_llm/agents/tenants_controller.rb +109 -0
- data/app/models/ruby_llm/agents/api_configuration.rb +386 -0
- data/app/models/ruby_llm/agents/tenant_budget.rb +62 -7
- data/app/views/layouts/ruby_llm/agents/application.html.erb +3 -1
- data/app/views/ruby_llm/agents/api_configurations/_api_key_field.html.erb +34 -0
- data/app/views/ruby_llm/agents/api_configurations/_form.html.erb +288 -0
- data/app/views/ruby_llm/agents/api_configurations/edit.html.erb +95 -0
- data/app/views/ruby_llm/agents/api_configurations/edit_tenant.html.erb +97 -0
- data/app/views/ruby_llm/agents/api_configurations/show.html.erb +211 -0
- data/app/views/ruby_llm/agents/api_configurations/tenant.html.erb +179 -0
- data/app/views/ruby_llm/agents/dashboard/_action_center.html.erb +1 -1
- data/app/views/ruby_llm/agents/executions/show.html.erb +82 -0
- data/app/views/ruby_llm/agents/{settings → system_config}/show.html.erb +1 -1
- data/app/views/ruby_llm/agents/tenants/_form.html.erb +150 -0
- data/app/views/ruby_llm/agents/tenants/edit.html.erb +13 -0
- data/app/views/ruby_llm/agents/tenants/index.html.erb +129 -0
- data/app/views/ruby_llm/agents/tenants/show.html.erb +374 -0
- data/config/routes.rb +12 -1
- data/lib/generators/ruby_llm_agents/api_configuration_generator.rb +100 -0
- data/lib/generators/ruby_llm_agents/templates/create_api_configurations_migration.rb.tt +90 -0
- data/lib/ruby_llm/agents/base/dsl.rb +65 -13
- data/lib/ruby_llm/agents/base/execution.rb +113 -6
- data/lib/ruby_llm/agents/base/reliability_dsl.rb +82 -0
- data/lib/ruby_llm/agents/base.rb +28 -0
- data/lib/ruby_llm/agents/budget_tracker.rb +285 -23
- data/lib/ruby_llm/agents/configuration.rb +38 -1
- data/lib/ruby_llm/agents/deprecations.rb +77 -0
- data/lib/ruby_llm/agents/engine.rb +1 -0
- data/lib/ruby_llm/agents/instrumentation.rb +71 -3
- data/lib/ruby_llm/agents/reliability/breaker_manager.rb +80 -0
- data/lib/ruby_llm/agents/reliability/execution_constraints.rb +69 -0
- data/lib/ruby_llm/agents/reliability/executor.rb +124 -0
- data/lib/ruby_llm/agents/reliability/fallback_routing.rb +72 -0
- data/lib/ruby_llm/agents/reliability/retry_strategy.rb +76 -0
- data/lib/ruby_llm/agents/resolved_config.rb +348 -0
- data/lib/ruby_llm/agents/result.rb +72 -2
- data/lib/ruby_llm/agents/version.rb +1 -1
- data/lib/ruby_llm/agents.rb +6 -0
- metadata +26 -3
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Migration to create the api_configurations table
|
|
4
|
+
#
|
|
5
|
+
# This table stores API key configurations that can be managed via the dashboard.
|
|
6
|
+
# Supports both global settings and per-tenant overrides.
|
|
7
|
+
#
|
|
8
|
+
# Resolution priority: per-tenant DB > global DB > config file (RubyLLM.configure)
|
|
9
|
+
#
|
|
10
|
+
# Features:
|
|
11
|
+
# - Encrypted storage for all API keys (using Rails encrypted attributes)
|
|
12
|
+
# - Support for all major LLM providers
|
|
13
|
+
# - Custom endpoint configuration
|
|
14
|
+
# - Connection settings
|
|
15
|
+
# - Default model configuration
|
|
16
|
+
#
|
|
17
|
+
# Run with: rails db:migrate
|
|
18
|
+
class CreateRubyLLMAgentsApiConfigurations < ActiveRecord::Migration<%= migration_version %>
|
|
19
|
+
def change
|
|
20
|
+
create_table :ruby_llm_agents_api_configurations do |t|
|
|
21
|
+
# Scope type: 'global' or 'tenant'
|
|
22
|
+
t.string :scope_type, null: false, default: 'global'
|
|
23
|
+
# Tenant ID when scope_type='tenant'
|
|
24
|
+
t.string :scope_id
|
|
25
|
+
|
|
26
|
+
# === Encrypted API Keys ===
|
|
27
|
+
# Rails encrypts stores encrypted data in the same-named column
|
|
28
|
+
# Primary providers
|
|
29
|
+
t.text :openai_api_key
|
|
30
|
+
t.text :anthropic_api_key
|
|
31
|
+
t.text :gemini_api_key
|
|
32
|
+
|
|
33
|
+
# Additional providers
|
|
34
|
+
t.text :deepseek_api_key
|
|
35
|
+
t.text :mistral_api_key
|
|
36
|
+
t.text :perplexity_api_key
|
|
37
|
+
t.text :openrouter_api_key
|
|
38
|
+
t.text :gpustack_api_key
|
|
39
|
+
t.text :xai_api_key
|
|
40
|
+
t.text :ollama_api_key
|
|
41
|
+
|
|
42
|
+
# AWS Bedrock
|
|
43
|
+
t.text :bedrock_api_key
|
|
44
|
+
t.text :bedrock_secret_key
|
|
45
|
+
t.text :bedrock_session_token
|
|
46
|
+
t.string :bedrock_region
|
|
47
|
+
|
|
48
|
+
# Google Vertex AI
|
|
49
|
+
t.text :vertexai_credentials
|
|
50
|
+
t.string :vertexai_project_id
|
|
51
|
+
t.string :vertexai_location
|
|
52
|
+
|
|
53
|
+
# === Custom Endpoints ===
|
|
54
|
+
t.string :openai_api_base
|
|
55
|
+
t.string :gemini_api_base
|
|
56
|
+
t.string :ollama_api_base
|
|
57
|
+
t.string :gpustack_api_base
|
|
58
|
+
t.string :xai_api_base
|
|
59
|
+
|
|
60
|
+
# === OpenAI Options ===
|
|
61
|
+
t.string :openai_organization_id
|
|
62
|
+
t.string :openai_project_id
|
|
63
|
+
|
|
64
|
+
# === Default Models ===
|
|
65
|
+
t.string :default_model
|
|
66
|
+
t.string :default_embedding_model
|
|
67
|
+
t.string :default_image_model
|
|
68
|
+
t.string :default_moderation_model
|
|
69
|
+
|
|
70
|
+
# === Connection Settings ===
|
|
71
|
+
t.integer :request_timeout
|
|
72
|
+
t.integer :max_retries
|
|
73
|
+
t.decimal :retry_interval, precision: 5, scale: 2
|
|
74
|
+
t.decimal :retry_backoff_factor, precision: 5, scale: 2
|
|
75
|
+
t.decimal :retry_interval_randomness, precision: 5, scale: 2
|
|
76
|
+
t.string :http_proxy
|
|
77
|
+
|
|
78
|
+
# Whether to inherit from global config for unset values
|
|
79
|
+
t.boolean :inherit_global_defaults, default: true
|
|
80
|
+
|
|
81
|
+
t.timestamps
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Ensure unique scope_type + scope_id combinations
|
|
85
|
+
add_index :ruby_llm_agents_api_configurations, [:scope_type, :scope_id], unique: true, name: 'idx_api_configs_scope'
|
|
86
|
+
|
|
87
|
+
# Index for faster tenant lookups
|
|
88
|
+
add_index :ruby_llm_agents_api_configurations, :scope_id, name: 'idx_api_configs_scope_id'
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "reliability_dsl"
|
|
4
|
+
|
|
3
5
|
module RubyLLM
|
|
4
6
|
module Agents
|
|
5
7
|
class Base
|
|
@@ -74,6 +76,31 @@ module RubyLLM
|
|
|
74
76
|
|
|
75
77
|
# @!group Reliability DSL
|
|
76
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
|
+
|
|
77
104
|
# Configures retry behavior for this agent
|
|
78
105
|
#
|
|
79
106
|
# @param max [Integer] Maximum number of retry attempts (default: 0)
|
|
@@ -162,13 +189,18 @@ module RubyLLM
|
|
|
162
189
|
# @param name [Symbol] The parameter name
|
|
163
190
|
# @param required [Boolean] Whether the parameter is required
|
|
164
191
|
# @param default [Object, nil] Default value if not provided
|
|
192
|
+
# @param type [Class, nil] Optional type for validation (e.g., String, Integer, Array)
|
|
165
193
|
# @return [void]
|
|
166
|
-
# @example
|
|
194
|
+
# @example Without type (accepts anything)
|
|
167
195
|
# param :query, required: true
|
|
168
|
-
# param :
|
|
169
|
-
|
|
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)
|
|
170
202
|
@params ||= {}
|
|
171
|
-
@params[name] = { required: required, default: default }
|
|
203
|
+
@params[name] = { required: required, default: default, type: type }
|
|
172
204
|
define_method(name) do
|
|
173
205
|
@options[name] || @options[name.to_s] || self.class.params.dig(name, :default)
|
|
174
206
|
end
|
|
@@ -186,17 +218,37 @@ module RubyLLM
|
|
|
186
218
|
|
|
187
219
|
# @!group Caching DSL
|
|
188
220
|
|
|
189
|
-
# Enables caching for this agent with
|
|
221
|
+
# Enables caching for this agent with explicit TTL
|
|
222
|
+
#
|
|
223
|
+
# This is the preferred method for enabling caching.
|
|
190
224
|
#
|
|
191
225
|
# @param ttl [ActiveSupport::Duration] Time-to-live for cached responses
|
|
192
226
|
# @return [void]
|
|
193
227
|
# @example
|
|
194
|
-
#
|
|
195
|
-
|
|
228
|
+
# cache_for 1.hour
|
|
229
|
+
# cache_for 30.minutes
|
|
230
|
+
def cache_for(ttl)
|
|
196
231
|
@cache_enabled = true
|
|
197
232
|
@cache_ttl = ttl
|
|
198
233
|
end
|
|
199
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
|
+
|
|
200
252
|
# Returns whether caching is enabled for this agent
|
|
201
253
|
#
|
|
202
254
|
# @return [Boolean] true if caching is enabled
|
|
@@ -243,13 +295,13 @@ module RubyLLM
|
|
|
243
295
|
#
|
|
244
296
|
# @param tool_classes [Array<Class>] Tool classes to make available
|
|
245
297
|
# @return [Array<Class>] The current tools
|
|
298
|
+
# @example With array (preferred)
|
|
299
|
+
# tools [WeatherTool, SearchTool, CalculatorTool]
|
|
246
300
|
# @example Single tool
|
|
247
|
-
# tools WeatherTool
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
if tool_classes.any?
|
|
252
|
-
@tools = tool_classes.flatten
|
|
301
|
+
# tools [WeatherTool]
|
|
302
|
+
def tools(tool_classes = nil)
|
|
303
|
+
if tool_classes
|
|
304
|
+
@tools = Array(tool_classes)
|
|
253
305
|
end
|
|
254
306
|
@tools || inherited_or_default(:tools, RubyLLM::Agents.configuration.default_tools)
|
|
255
307
|
end
|
|
@@ -17,6 +17,9 @@ module RubyLLM
|
|
|
17
17
|
# @yieldparam chunk [RubyLLM::Chunk] A streaming chunk with content
|
|
18
18
|
# @return [Object] The processed LLM response
|
|
19
19
|
def call(&block)
|
|
20
|
+
# Resolve tenant configuration before execution
|
|
21
|
+
resolve_tenant_context!
|
|
22
|
+
|
|
20
23
|
return dry_run_response if @options[:dry_run]
|
|
21
24
|
return uncached_call(&block) if @options[:skip_cache] || !self.class.cache_enabled?
|
|
22
25
|
|
|
@@ -36,6 +39,52 @@ module RubyLLM
|
|
|
36
39
|
end
|
|
37
40
|
end
|
|
38
41
|
|
|
42
|
+
# Resolves tenant context from the :tenant option
|
|
43
|
+
#
|
|
44
|
+
# The tenant option can be:
|
|
45
|
+
# - String: Just the tenant_id (uses resolver or DB for config)
|
|
46
|
+
# - Hash: Full config { id:, name:, daily_limit:, daily_token_limit:, ... }
|
|
47
|
+
#
|
|
48
|
+
# @return [void]
|
|
49
|
+
def resolve_tenant_context!
|
|
50
|
+
# Idempotency guard - only resolve once
|
|
51
|
+
return if defined?(@tenant_context_resolved) && @tenant_context_resolved
|
|
52
|
+
|
|
53
|
+
tenant_option = @options[:tenant]
|
|
54
|
+
return unless tenant_option
|
|
55
|
+
|
|
56
|
+
if tenant_option.is_a?(Hash)
|
|
57
|
+
# Full config passed - extract id and store config
|
|
58
|
+
@tenant_id = tenant_option[:id]&.to_s
|
|
59
|
+
@tenant_config = tenant_option.except(:id)
|
|
60
|
+
else
|
|
61
|
+
# Just tenant_id passed
|
|
62
|
+
@tenant_id = tenant_option.to_s
|
|
63
|
+
@tenant_config = nil
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
@tenant_context_resolved = true
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Returns the resolved tenant ID
|
|
70
|
+
#
|
|
71
|
+
# @return [String, nil] The tenant identifier
|
|
72
|
+
def resolved_tenant_id
|
|
73
|
+
return @tenant_id if defined?(@tenant_id) && @tenant_id.present?
|
|
74
|
+
|
|
75
|
+
config = RubyLLM::Agents.configuration
|
|
76
|
+
return nil unless config.multi_tenancy_enabled?
|
|
77
|
+
|
|
78
|
+
config.current_tenant_id
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Returns the runtime tenant config (if passed via :tenant option)
|
|
82
|
+
#
|
|
83
|
+
# @return [Hash, nil] Runtime tenant configuration
|
|
84
|
+
def runtime_tenant_config
|
|
85
|
+
@tenant_config if defined?(@tenant_config)
|
|
86
|
+
end
|
|
87
|
+
|
|
39
88
|
# Executes the agent without caching
|
|
40
89
|
#
|
|
41
90
|
# Routes to reliability-enabled execution if configured, otherwise
|
|
@@ -62,7 +111,7 @@ module RubyLLM
|
|
|
62
111
|
reset_accumulated_tool_calls!
|
|
63
112
|
|
|
64
113
|
Timeout.timeout(self.class.timeout) do
|
|
65
|
-
if
|
|
114
|
+
if streaming_enabled? && block_given?
|
|
66
115
|
execute_with_streaming(current_client, &block)
|
|
67
116
|
else
|
|
68
117
|
response = current_client.ask(user_prompt, **ask_options)
|
|
@@ -177,6 +226,16 @@ module RubyLLM
|
|
|
177
226
|
config[:circuit_breaker].present?
|
|
178
227
|
end
|
|
179
228
|
|
|
229
|
+
# Returns whether streaming is enabled for this execution
|
|
230
|
+
#
|
|
231
|
+
# Checks both class-level DSL setting and instance-level override
|
|
232
|
+
# (set by the stream class method).
|
|
233
|
+
#
|
|
234
|
+
# @return [Boolean] true if streaming is enabled
|
|
235
|
+
def streaming_enabled?
|
|
236
|
+
@force_streaming || self.class.streaming
|
|
237
|
+
end
|
|
238
|
+
|
|
180
239
|
# Returns options to pass to the ask method
|
|
181
240
|
#
|
|
182
241
|
# Currently supports :with for attachments (images, PDFs, etc.)
|
|
@@ -188,20 +247,37 @@ module RubyLLM
|
|
|
188
247
|
opts
|
|
189
248
|
end
|
|
190
249
|
|
|
191
|
-
# Validates that all required parameters are present
|
|
250
|
+
# Validates that all required parameters are present and types match
|
|
192
251
|
#
|
|
193
|
-
# @raise [ArgumentError] If required parameters are missing
|
|
252
|
+
# @raise [ArgumentError] If required parameters are missing or types don't match
|
|
194
253
|
# @return [void]
|
|
195
254
|
def validate_required_params!
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
255
|
+
self.class.params.each do |name, config|
|
|
256
|
+
value = @options[name] || @options[name.to_s]
|
|
257
|
+
has_value = @options.key?(name) || @options.key?(name.to_s)
|
|
258
|
+
|
|
259
|
+
# Check required
|
|
260
|
+
if config[:required] && !has_value
|
|
261
|
+
raise ArgumentError, "#{self.class} missing required param: #{name}"
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Check type if specified and value is present (not nil)
|
|
265
|
+
if config[:type] && has_value && !value.nil?
|
|
266
|
+
unless value.is_a?(config[:type])
|
|
267
|
+
raise ArgumentError,
|
|
268
|
+
"#{self.class} expected #{config[:type]} for :#{name}, got #{value.class}"
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
end
|
|
199
272
|
end
|
|
200
273
|
|
|
201
274
|
# Builds and configures the RubyLLM client
|
|
202
275
|
#
|
|
203
276
|
# @return [RubyLLM::Chat] Configured chat client
|
|
204
277
|
def build_client
|
|
278
|
+
# Apply database-backed API configuration if available
|
|
279
|
+
apply_api_configuration!
|
|
280
|
+
|
|
205
281
|
client = RubyLLM.chat
|
|
206
282
|
.with_model(model)
|
|
207
283
|
.with_temperature(temperature)
|
|
@@ -212,11 +288,42 @@ module RubyLLM
|
|
|
212
288
|
client
|
|
213
289
|
end
|
|
214
290
|
|
|
291
|
+
# Applies database-backed API configuration to RubyLLM
|
|
292
|
+
#
|
|
293
|
+
# Resolution priority: per-tenant DB > global DB > RubyLLM.configure
|
|
294
|
+
# Only applies if the api_configurations table exists.
|
|
295
|
+
#
|
|
296
|
+
# @return [void]
|
|
297
|
+
def apply_api_configuration!
|
|
298
|
+
return unless api_configuration_available?
|
|
299
|
+
|
|
300
|
+
resolved_config = ApiConfiguration.resolve(tenant_id: resolved_tenant_id)
|
|
301
|
+
resolved_config.apply_to_ruby_llm!
|
|
302
|
+
rescue StandardError => e
|
|
303
|
+
Rails.logger.warn("[RubyLLM::Agents] Failed to apply API config: #{e.message}")
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Checks if API configuration table is available
|
|
307
|
+
#
|
|
308
|
+
# @return [Boolean] true if table exists and is accessible
|
|
309
|
+
def api_configuration_available?
|
|
310
|
+
return @api_config_available if defined?(@api_config_available)
|
|
311
|
+
|
|
312
|
+
@api_config_available = begin
|
|
313
|
+
ApiConfiguration.table_exists?
|
|
314
|
+
rescue StandardError
|
|
315
|
+
false
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
215
319
|
# Builds a client with a specific model
|
|
216
320
|
#
|
|
217
321
|
# @param model_id [String] The model identifier
|
|
218
322
|
# @return [RubyLLM::Chat] Configured chat client
|
|
219
323
|
def build_client_with_model(model_id)
|
|
324
|
+
# Apply database-backed API configuration if available
|
|
325
|
+
apply_api_configuration!
|
|
326
|
+
|
|
220
327
|
client = RubyLLM.chat
|
|
221
328
|
.with_model(model_id)
|
|
222
329
|
.with_temperature(temperature)
|
|
@@ -0,0 +1,82 @@
|
|
|
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
|
data/lib/ruby_llm/agents/base.rb
CHANGED
|
@@ -83,6 +83,33 @@ module RubyLLM
|
|
|
83
83
|
def call(*args, **kwargs, &block)
|
|
84
84
|
new(*args, **kwargs).call(&block)
|
|
85
85
|
end
|
|
86
|
+
|
|
87
|
+
# Streams agent execution, yielding chunks as they arrive
|
|
88
|
+
#
|
|
89
|
+
# A more explicit alternative to passing a block to call.
|
|
90
|
+
# Forces streaming mode for this invocation regardless of class setting.
|
|
91
|
+
#
|
|
92
|
+
# @param kwargs [Hash] Agent parameters
|
|
93
|
+
# @yield [chunk] Yields each chunk as it arrives
|
|
94
|
+
# @yieldparam chunk [RubyLLM::Chunk] Streaming chunk with content
|
|
95
|
+
# @return [Result] The final result after streaming completes
|
|
96
|
+
# @raise [ArgumentError] If no block is provided
|
|
97
|
+
#
|
|
98
|
+
# @example Basic streaming
|
|
99
|
+
# MyAgent.stream(query: "test") do |chunk|
|
|
100
|
+
# print chunk.content
|
|
101
|
+
# end
|
|
102
|
+
#
|
|
103
|
+
# @example With result metadata
|
|
104
|
+
# result = MyAgent.stream(query: "test") { |c| print c.content }
|
|
105
|
+
# puts "\nTokens: #{result.total_tokens}"
|
|
106
|
+
def stream(**kwargs, &block)
|
|
107
|
+
raise ArgumentError, "Block required for streaming" unless block_given?
|
|
108
|
+
|
|
109
|
+
instance = new(**kwargs)
|
|
110
|
+
instance.instance_variable_set(:@force_streaming, true)
|
|
111
|
+
instance.call(&block)
|
|
112
|
+
end
|
|
86
113
|
end
|
|
87
114
|
|
|
88
115
|
# @!attribute [r] model
|
|
@@ -109,6 +136,7 @@ module RubyLLM
|
|
|
109
136
|
@options = options
|
|
110
137
|
@accumulated_tool_calls = []
|
|
111
138
|
validate_required_params!
|
|
139
|
+
resolve_tenant_context! # Resolve tenant before building client for API key resolution
|
|
112
140
|
@client = build_client
|
|
113
141
|
end
|
|
114
142
|
|