ruby_llm-agents 3.8.0 → 3.9.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 +30 -10
- data/app/controllers/ruby_llm/agents/requests_controller.rb +117 -0
- data/app/views/layouts/ruby_llm/agents/application.html.erb +4 -2
- data/app/views/ruby_llm/agents/requests/index.html.erb +153 -0
- data/app/views/ruby_llm/agents/requests/show.html.erb +136 -0
- data/config/routes.rb +2 -0
- data/lib/generators/ruby_llm_agents/agent_generator.rb +2 -2
- data/lib/generators/ruby_llm_agents/demo_generator.rb +102 -0
- data/lib/generators/ruby_llm_agents/doctor_generator.rb +196 -0
- data/lib/generators/ruby_llm_agents/install_generator.rb +7 -19
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +27 -80
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +18 -51
- data/lib/generators/ruby_llm_agents/templates/initializer.rb.tt +19 -17
- data/lib/ruby_llm/agents/base_agent.rb +68 -7
- data/lib/ruby_llm/agents/core/base.rb +4 -0
- data/lib/ruby_llm/agents/core/configuration.rb +10 -0
- data/lib/ruby_llm/agents/core/version.rb +1 -1
- data/lib/ruby_llm/agents/pipeline/context.rb +26 -0
- data/lib/ruby_llm/agents/pipeline/middleware/base.rb +58 -4
- data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +17 -15
- data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +34 -22
- data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +105 -50
- data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +7 -5
- data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +6 -4
- data/lib/ruby_llm/agents/rails/engine.rb +11 -0
- data/lib/ruby_llm/agents/results/background_removal_result.rb +7 -1
- data/lib/ruby_llm/agents/results/base.rb +24 -2
- data/lib/ruby_llm/agents/results/embedding_result.rb +4 -0
- data/lib/ruby_llm/agents/results/image_analysis_result.rb +7 -1
- data/lib/ruby_llm/agents/results/image_edit_result.rb +7 -1
- data/lib/ruby_llm/agents/results/image_generation_result.rb +7 -1
- data/lib/ruby_llm/agents/results/image_pipeline_result.rb +7 -1
- data/lib/ruby_llm/agents/results/image_transform_result.rb +7 -1
- data/lib/ruby_llm/agents/results/image_upscale_result.rb +7 -1
- data/lib/ruby_llm/agents/results/image_variation_result.rb +7 -1
- data/lib/ruby_llm/agents/results/speech_result.rb +6 -0
- data/lib/ruby_llm/agents/results/trackable.rb +25 -0
- data/lib/ruby_llm/agents/results/transcription_result.rb +6 -0
- data/lib/ruby_llm/agents/text/embedder.rb +7 -4
- data/lib/ruby_llm/agents/track_report.rb +127 -0
- data/lib/ruby_llm/agents/tracker.rb +32 -0
- data/lib/ruby_llm/agents.rb +208 -0
- data/lib/tasks/ruby_llm_agents.rake +6 -0
- metadata +10 -2
|
@@ -46,6 +46,9 @@ module RubyLLM
|
|
|
46
46
|
# Response metadata
|
|
47
47
|
attr_accessor :model_used, :finish_reason, :time_to_first_token_ms
|
|
48
48
|
|
|
49
|
+
# Debug trace (set when debug: true is passed)
|
|
50
|
+
attr_accessor :trace
|
|
51
|
+
|
|
49
52
|
# Streaming support
|
|
50
53
|
attr_accessor :stream_block, :skip_cache
|
|
51
54
|
|
|
@@ -85,6 +88,10 @@ module RubyLLM
|
|
|
85
88
|
@skip_cache = skip_cache
|
|
86
89
|
@stream_block = stream_block
|
|
87
90
|
|
|
91
|
+
# Debug trace
|
|
92
|
+
@trace = []
|
|
93
|
+
@trace_enabled = options[:debug] == true
|
|
94
|
+
|
|
88
95
|
# Initialize tracking fields
|
|
89
96
|
@attempt = 0
|
|
90
97
|
@attempts_made = 0
|
|
@@ -108,6 +115,23 @@ module RubyLLM
|
|
|
108
115
|
((@completed_at - @started_at) * 1000).to_i
|
|
109
116
|
end
|
|
110
117
|
|
|
118
|
+
# Is debug tracing enabled?
|
|
119
|
+
#
|
|
120
|
+
# @return [Boolean]
|
|
121
|
+
def trace_enabled?
|
|
122
|
+
@trace_enabled
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Adds a trace entry for a middleware execution
|
|
126
|
+
#
|
|
127
|
+
# @param middleware_name [String] Name of the middleware
|
|
128
|
+
# @param started_at [Time] When the middleware started
|
|
129
|
+
# @param duration_ms [Float] How long the middleware took in ms
|
|
130
|
+
# @param action [String, nil] Optional action description (e.g., "cache hit")
|
|
131
|
+
def add_trace(middleware_name, started_at:, duration_ms:, action: nil)
|
|
132
|
+
@trace << {middleware: middleware_name, started_at: started_at, duration_ms: duration_ms, action: action}.compact
|
|
133
|
+
end
|
|
134
|
+
|
|
111
135
|
# Was the result served from cache?
|
|
112
136
|
#
|
|
113
137
|
# @return [Boolean]
|
|
@@ -230,6 +254,8 @@ module RubyLLM
|
|
|
230
254
|
# Preserve execution hierarchy
|
|
231
255
|
new_ctx.parent_execution_id = @parent_execution_id
|
|
232
256
|
new_ctx.root_execution_id = @root_execution_id
|
|
257
|
+
# Preserve trace across retries
|
|
258
|
+
new_ctx.trace = @trace
|
|
233
259
|
new_ctx
|
|
234
260
|
end
|
|
235
261
|
|
|
@@ -41,6 +41,8 @@ module RubyLLM
|
|
|
41
41
|
# @abstract Subclass and implement {#call}
|
|
42
42
|
#
|
|
43
43
|
class Base
|
|
44
|
+
LOG_TAG = "[RubyLLM::Agents::Pipeline]"
|
|
45
|
+
|
|
44
46
|
# @param app [#call] The next handler in the chain
|
|
45
47
|
# @param agent_class [Class] The agent class (for reading DSL config)
|
|
46
48
|
def initialize(app, agent_class)
|
|
@@ -100,22 +102,74 @@ module RubyLLM
|
|
|
100
102
|
RubyLLM::Agents.configuration
|
|
101
103
|
end
|
|
102
104
|
|
|
105
|
+
# Builds a log prefix with context from the execution
|
|
106
|
+
#
|
|
107
|
+
# Includes agent type, execution ID, and tenant when available
|
|
108
|
+
# so log messages can be traced through the full pipeline.
|
|
109
|
+
#
|
|
110
|
+
# @param context [Context, nil] The execution context
|
|
111
|
+
# @return [String] Formatted log prefix
|
|
112
|
+
def log_prefix(context = nil)
|
|
113
|
+
return LOG_TAG unless context
|
|
114
|
+
|
|
115
|
+
parts = [LOG_TAG]
|
|
116
|
+
parts << context.agent_class.name if context.agent_class
|
|
117
|
+
parts << "exec=#{context.execution_id}" if context.execution_id
|
|
118
|
+
parts << "tenant=#{context.tenant_id}" if context.tenant_id
|
|
119
|
+
parts.join(" ")
|
|
120
|
+
end
|
|
121
|
+
|
|
103
122
|
# Log a debug message if Rails logger is available
|
|
104
123
|
#
|
|
105
124
|
# @param message [String] The message to log
|
|
106
|
-
|
|
125
|
+
# @param context [Context, nil] Optional execution context for structured prefix
|
|
126
|
+
def debug(message, context = nil)
|
|
107
127
|
return unless defined?(Rails) && Rails.logger
|
|
108
128
|
|
|
109
|
-
Rails.logger.debug("
|
|
129
|
+
Rails.logger.debug("#{log_prefix(context)} #{message}")
|
|
110
130
|
end
|
|
111
131
|
|
|
112
132
|
# Log an error message if Rails logger is available
|
|
113
133
|
#
|
|
114
134
|
# @param message [String] The message to log
|
|
115
|
-
|
|
135
|
+
# @param context [Context, nil] Optional execution context for structured prefix
|
|
136
|
+
def error(message, context = nil)
|
|
137
|
+
return unless defined?(Rails) && Rails.logger
|
|
138
|
+
|
|
139
|
+
Rails.logger.error("#{log_prefix(context)} #{message}")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Traces middleware execution when debug mode is enabled.
|
|
143
|
+
#
|
|
144
|
+
# Wraps a block with timing instrumentation. When tracing is not
|
|
145
|
+
# enabled, yields directly with zero overhead.
|
|
146
|
+
#
|
|
147
|
+
# @param context [Context] The execution context
|
|
148
|
+
# @param action [String, nil] Optional action description
|
|
149
|
+
# @yield The block to trace
|
|
150
|
+
# @return [Object] The block's return value
|
|
151
|
+
def trace(context, action: nil)
|
|
152
|
+
unless context.trace_enabled?
|
|
153
|
+
return yield
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
middleware_name = self.class.name&.split("::")&.last || self.class.to_s
|
|
157
|
+
started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
158
|
+
result = yield
|
|
159
|
+
duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at) * 1000).round(2)
|
|
160
|
+
context.add_trace(middleware_name, started_at: Time.current, duration_ms: duration_ms, action: action)
|
|
161
|
+
debug("#{middleware_name} completed in #{duration_ms}ms#{" (#{action})" if action}", context)
|
|
162
|
+
result
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Log a warning message if Rails logger is available
|
|
166
|
+
#
|
|
167
|
+
# @param message [String] The message to log
|
|
168
|
+
# @param context [Context, nil] Optional execution context for structured prefix
|
|
169
|
+
def warn(message, context = nil)
|
|
116
170
|
return unless defined?(Rails) && Rails.logger
|
|
117
171
|
|
|
118
|
-
Rails.logger.
|
|
172
|
+
Rails.logger.warn("#{log_prefix(context)} #{message}")
|
|
119
173
|
end
|
|
120
174
|
end
|
|
121
175
|
end
|
|
@@ -32,21 +32,23 @@ module RubyLLM
|
|
|
32
32
|
def call(context)
|
|
33
33
|
return @app.call(context) unless budgets_enabled?
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
trace(context) do
|
|
36
|
+
# Check budget before execution
|
|
37
|
+
check_budget!(context)
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
# Execute the chain
|
|
40
|
+
@app.call(context)
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
# Record spend after successful execution (if not cached)
|
|
43
|
+
if context.success? && !context.cached?
|
|
44
|
+
record_spend!(context)
|
|
45
|
+
emit_budget_notification("ruby_llm_agents.budget.record", context,
|
|
46
|
+
total_cost: context.total_cost,
|
|
47
|
+
total_tokens: context.total_tokens)
|
|
48
|
+
end
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
context
|
|
51
|
+
end
|
|
50
52
|
end
|
|
51
53
|
|
|
52
54
|
private
|
|
@@ -65,7 +67,7 @@ module RubyLLM
|
|
|
65
67
|
}.merge(extras)
|
|
66
68
|
)
|
|
67
69
|
rescue => e
|
|
68
|
-
debug("Budget notification failed: #{e.message}")
|
|
70
|
+
debug("Budget notification failed: #{e.message}", context)
|
|
69
71
|
end
|
|
70
72
|
|
|
71
73
|
# Returns whether budgets are enabled globally
|
|
@@ -106,7 +108,7 @@ module RubyLLM
|
|
|
106
108
|
raise
|
|
107
109
|
rescue => e
|
|
108
110
|
# Log at error level so unexpected failures are visible in logs
|
|
109
|
-
error("Budget check failed: #{e.class}: #{e.message}")
|
|
111
|
+
error("Budget check failed: #{e.class}: #{e.message}", context)
|
|
110
112
|
end
|
|
111
113
|
|
|
112
114
|
# Records spend after execution
|
|
@@ -145,7 +147,7 @@ module RubyLLM
|
|
|
145
147
|
)
|
|
146
148
|
end
|
|
147
149
|
rescue => e
|
|
148
|
-
error("Failed to record spend: #{e.message}")
|
|
150
|
+
error("Failed to record spend: #{e.message}", context)
|
|
149
151
|
end
|
|
150
152
|
end
|
|
151
153
|
end
|
|
@@ -31,34 +31,46 @@ module RubyLLM
|
|
|
31
31
|
def call(context)
|
|
32
32
|
return @app.call(context) unless cache_enabled?
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
#
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
34
|
+
cache_action = nil
|
|
35
|
+
result = trace(context, action: "cache") do
|
|
36
|
+
cache_key = generate_cache_key(context)
|
|
37
|
+
|
|
38
|
+
# Skip cache read if skip_cache is true
|
|
39
|
+
unless context.skip_cache
|
|
40
|
+
# Try to read from cache
|
|
41
|
+
if (cached = cache_read(cache_key))
|
|
42
|
+
context.output = cached
|
|
43
|
+
context.cached = true
|
|
44
|
+
context[:cache_key] = cache_key
|
|
45
|
+
cache_action = "hit"
|
|
46
|
+
debug("Cache hit for #{cache_key}", context)
|
|
47
|
+
emit_cache_notification("ruby_llm_agents.cache.hit", cache_key)
|
|
48
|
+
next context
|
|
49
|
+
end
|
|
46
50
|
end
|
|
47
|
-
end
|
|
48
51
|
|
|
49
|
-
|
|
52
|
+
cache_action = "miss"
|
|
53
|
+
emit_cache_notification("ruby_llm_agents.cache.miss", cache_key)
|
|
54
|
+
|
|
55
|
+
# Execute the chain
|
|
56
|
+
@app.call(context)
|
|
50
57
|
|
|
51
|
-
|
|
52
|
-
|
|
58
|
+
# Cache successful results
|
|
59
|
+
if context.success?
|
|
60
|
+
cache_write(cache_key, context.output)
|
|
61
|
+
debug("Cache write for #{cache_key}", context)
|
|
62
|
+
emit_cache_notification("ruby_llm_agents.cache.write", cache_key)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
context
|
|
66
|
+
end
|
|
53
67
|
|
|
54
|
-
#
|
|
55
|
-
if context.
|
|
56
|
-
|
|
57
|
-
debug("Cache write for #{cache_key}")
|
|
58
|
-
emit_cache_notification("ruby_llm_agents.cache.write", cache_key)
|
|
68
|
+
# Update the last trace entry with the specific cache action
|
|
69
|
+
if context.trace_enabled? && cache_action && context.trace.last
|
|
70
|
+
context.trace.last[:action] = cache_action
|
|
59
71
|
end
|
|
60
72
|
|
|
61
|
-
|
|
73
|
+
result
|
|
62
74
|
end
|
|
63
75
|
|
|
64
76
|
private
|
|
@@ -39,45 +39,47 @@ module RubyLLM
|
|
|
39
39
|
def call(context)
|
|
40
40
|
context.started_at = Time.current
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
begin
|
|
50
|
-
@app.call(context)
|
|
51
|
-
context.completed_at = Time.current
|
|
52
|
-
|
|
53
|
-
begin
|
|
54
|
-
complete_execution(execution, context, status: "success")
|
|
55
|
-
status_update_completed = true
|
|
56
|
-
rescue
|
|
57
|
-
# Let ensure block handle via mark_execution_failed!
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
emit_complete_notification(context, "success")
|
|
61
|
-
rescue => e
|
|
62
|
-
context.completed_at = Time.current
|
|
63
|
-
context.error = e
|
|
64
|
-
raised_exception = e
|
|
42
|
+
trace(context) do
|
|
43
|
+
# Create "running" record immediately (SYNC - must appear on dashboard)
|
|
44
|
+
execution = create_running_execution(context)
|
|
45
|
+
context.execution_id = execution&.id
|
|
46
|
+
emit_start_notification(context)
|
|
47
|
+
status_update_completed = false
|
|
48
|
+
raised_exception = nil
|
|
65
49
|
|
|
66
50
|
begin
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
51
|
+
@app.call(context)
|
|
52
|
+
context.completed_at = Time.current
|
|
53
|
+
|
|
54
|
+
begin
|
|
55
|
+
complete_execution(execution, context, status: "success")
|
|
56
|
+
status_update_completed = true
|
|
57
|
+
rescue
|
|
58
|
+
# Let ensure block handle via mark_execution_failed!
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
emit_complete_notification(context, "success")
|
|
62
|
+
rescue => e
|
|
63
|
+
context.completed_at = Time.current
|
|
64
|
+
context.error = e
|
|
65
|
+
raised_exception = e
|
|
66
|
+
|
|
67
|
+
begin
|
|
68
|
+
complete_execution(execution, context, status: determine_error_status(e))
|
|
69
|
+
status_update_completed = true
|
|
70
|
+
rescue
|
|
71
|
+
# Let ensure block handle via mark_execution_failed!
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
emit_complete_notification(context, determine_error_status(e))
|
|
75
|
+
raise
|
|
76
|
+
ensure
|
|
77
|
+
# Emergency fallback if update failed
|
|
78
|
+
mark_execution_failed!(execution, error: raised_exception || $!) unless status_update_completed
|
|
71
79
|
end
|
|
72
80
|
|
|
73
|
-
|
|
74
|
-
raise
|
|
75
|
-
ensure
|
|
76
|
-
# Emergency fallback if update failed
|
|
77
|
-
mark_execution_failed!(execution, error: raised_exception || $!) unless status_update_completed
|
|
81
|
+
context
|
|
78
82
|
end
|
|
79
|
-
|
|
80
|
-
context
|
|
81
83
|
end
|
|
82
84
|
|
|
83
85
|
private
|
|
@@ -111,7 +113,7 @@ module RubyLLM
|
|
|
111
113
|
|
|
112
114
|
execution
|
|
113
115
|
rescue => e
|
|
114
|
-
error("Failed to create running execution record: #{e.message}")
|
|
116
|
+
error("Failed to create running execution record: #{e.message}", context)
|
|
115
117
|
nil
|
|
116
118
|
end
|
|
117
119
|
|
|
@@ -142,7 +144,7 @@ module RubyLLM
|
|
|
142
144
|
# Save detail data (prompts, responses, tool calls, etc.)
|
|
143
145
|
save_execution_details(execution, context, status)
|
|
144
146
|
rescue => e
|
|
145
|
-
error("Failed to complete execution record: #{e.message}")
|
|
147
|
+
error("Failed to complete execution record: #{e.message}", context)
|
|
146
148
|
raise # Re-raise for ensure block to handle via mark_execution_failed!
|
|
147
149
|
end
|
|
148
150
|
|
|
@@ -158,7 +160,7 @@ module RubyLLM
|
|
|
158
160
|
return unless execution&.id
|
|
159
161
|
return unless execution.status == "running"
|
|
160
162
|
|
|
161
|
-
error_message = error
|
|
163
|
+
error_message = build_error_message(error)
|
|
162
164
|
|
|
163
165
|
update_data = {
|
|
164
166
|
status: "error",
|
|
@@ -183,6 +185,28 @@ module RubyLLM
|
|
|
183
185
|
error("CRITICAL: Failed emergency status update for execution #{execution&.id}: #{e.message}")
|
|
184
186
|
end
|
|
185
187
|
|
|
188
|
+
# Builds an informative error message including backtrace context
|
|
189
|
+
#
|
|
190
|
+
# Preserves the error class, message, and the most relevant
|
|
191
|
+
# backtrace frames (up to 10) so developers can trace the
|
|
192
|
+
# failure origin without needing to reproduce it.
|
|
193
|
+
#
|
|
194
|
+
# @param error [Exception, nil] The exception
|
|
195
|
+
# @return [String] Formatted error message with backtrace
|
|
196
|
+
def build_error_message(error)
|
|
197
|
+
return "Unknown error" unless error
|
|
198
|
+
|
|
199
|
+
parts = ["#{error.class}: #{error.message}"]
|
|
200
|
+
|
|
201
|
+
if error.backtrace&.any?
|
|
202
|
+
relevant_frames = error.backtrace.first(10)
|
|
203
|
+
parts << "Backtrace (first #{relevant_frames.size} frames):"
|
|
204
|
+
parts.concat(relevant_frames.map { |frame| " #{frame}" })
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
parts.join("\n").truncate(5000)
|
|
208
|
+
end
|
|
209
|
+
|
|
186
210
|
# Determines the status based on error type
|
|
187
211
|
#
|
|
188
212
|
# @param error [Exception] The exception that occurred
|
|
@@ -206,7 +230,7 @@ module RubyLLM
|
|
|
206
230
|
execution_id: context.execution_id
|
|
207
231
|
)
|
|
208
232
|
rescue => e
|
|
209
|
-
debug("Start notification failed: #{e.message}")
|
|
233
|
+
debug("Start notification failed: #{e.message}", context)
|
|
210
234
|
end
|
|
211
235
|
|
|
212
236
|
# Emits an AS::Notification for execution completion or error
|
|
@@ -243,7 +267,7 @@ module RubyLLM
|
|
|
243
267
|
error_message: context.error&.message
|
|
244
268
|
)
|
|
245
269
|
rescue => e
|
|
246
|
-
debug("Complete notification failed: #{e.message}")
|
|
270
|
+
debug("Complete notification failed: #{e.message}", context)
|
|
247
271
|
end
|
|
248
272
|
|
|
249
273
|
# Builds data for initial running execution record
|
|
@@ -295,6 +319,9 @@ module RubyLLM
|
|
|
295
319
|
data[:root_execution_id] = context.root_execution_id || context.parent_execution_id
|
|
296
320
|
end
|
|
297
321
|
|
|
322
|
+
# Inject tracker request_id and tags
|
|
323
|
+
inject_tracker_data(context, data)
|
|
324
|
+
|
|
298
325
|
data
|
|
299
326
|
end
|
|
300
327
|
|
|
@@ -322,7 +349,7 @@ module RubyLLM
|
|
|
322
349
|
context_meta = begin
|
|
323
350
|
context.metadata.dup
|
|
324
351
|
rescue => e
|
|
325
|
-
debug("Failed to read context metadata: #{e.message}")
|
|
352
|
+
debug("Failed to read context metadata: #{e.message}", context)
|
|
326
353
|
{}
|
|
327
354
|
end
|
|
328
355
|
context_meta.transform_keys!(&:to_s)
|
|
@@ -365,7 +392,7 @@ module RubyLLM
|
|
|
365
392
|
end
|
|
366
393
|
|
|
367
394
|
if context.error
|
|
368
|
-
detail_data[:error_message] =
|
|
395
|
+
detail_data[:error_message] = build_error_message(context.error)
|
|
369
396
|
end
|
|
370
397
|
|
|
371
398
|
if context[:tool_calls].present?
|
|
@@ -392,7 +419,7 @@ module RubyLLM
|
|
|
392
419
|
execution.create_detail!(detail_data)
|
|
393
420
|
end
|
|
394
421
|
rescue => e
|
|
395
|
-
error("Failed to save execution details: #{e.message}")
|
|
422
|
+
error("Failed to save execution details: #{e.message}", context)
|
|
396
423
|
end
|
|
397
424
|
|
|
398
425
|
# Persists execution data to database (legacy fallback)
|
|
@@ -412,7 +439,7 @@ module RubyLLM
|
|
|
412
439
|
create_execution_record(data)
|
|
413
440
|
end
|
|
414
441
|
rescue => e
|
|
415
|
-
error("Failed to record execution: #{e.message}")
|
|
442
|
+
error("Failed to record execution: #{e.message}", context)
|
|
416
443
|
end
|
|
417
444
|
|
|
418
445
|
# Builds execution data hash for the legacy single-step persistence path.
|
|
@@ -435,7 +462,7 @@ module RubyLLM
|
|
|
435
462
|
detail_data[:user_prompt] = context.input.to_s.presence
|
|
436
463
|
detail_data[:assistant_prompt] = exec_opts[:assistant_prefill] if assistant_prompt_column_exists?
|
|
437
464
|
end
|
|
438
|
-
detail_data[:error_message] =
|
|
465
|
+
detail_data[:error_message] = build_error_message(context.error) if context.error
|
|
439
466
|
detail_data[:tool_calls] = context[:tool_calls] if context[:tool_calls].present?
|
|
440
467
|
detail_data[:attempts] = context[:reliability_attempts] if context[:reliability_attempts].present?
|
|
441
468
|
if global_config.persist_responses && context.output.respond_to?(:content)
|
|
@@ -474,7 +501,7 @@ module RubyLLM
|
|
|
474
501
|
params = begin
|
|
475
502
|
context.agent_instance.send(:options)
|
|
476
503
|
rescue => e
|
|
477
|
-
debug("Failed to extract agent options: #{e.message}")
|
|
504
|
+
debug("Failed to extract agent options: #{e.message}", context)
|
|
478
505
|
{}
|
|
479
506
|
end
|
|
480
507
|
params = params.dup
|
|
@@ -505,10 +532,38 @@ module RubyLLM
|
|
|
505
532
|
result = context.agent_instance.metadata
|
|
506
533
|
result.is_a?(Hash) ? result : {}
|
|
507
534
|
rescue => e
|
|
508
|
-
debug("Failed to retrieve agent metadata: #{e.message}")
|
|
535
|
+
debug("Failed to retrieve agent metadata: #{e.message}", context)
|
|
509
536
|
{}
|
|
510
537
|
end
|
|
511
538
|
|
|
539
|
+
# Injects tracker request_id and tags into execution data
|
|
540
|
+
#
|
|
541
|
+
# Reads @_track_request_id and @_track_tags from the agent instance,
|
|
542
|
+
# which are set by BaseAgent#initialize when a Tracker is active.
|
|
543
|
+
#
|
|
544
|
+
# @param context [Context] The execution context
|
|
545
|
+
# @param data [Hash] The execution data hash to modify
|
|
546
|
+
def inject_tracker_data(context, data)
|
|
547
|
+
agent = context.agent_instance
|
|
548
|
+
return unless agent
|
|
549
|
+
|
|
550
|
+
# Inject request_id
|
|
551
|
+
track_request_id = agent.instance_variable_get(:@_track_request_id)
|
|
552
|
+
if track_request_id && data[:request_id].blank?
|
|
553
|
+
data[:request_id] = track_request_id
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
# Merge tracker tags into metadata
|
|
557
|
+
track_tags = agent.instance_variable_get(:@_track_tags)
|
|
558
|
+
if track_tags.is_a?(Hash) && track_tags.any?
|
|
559
|
+
data[:metadata] = (data[:metadata] || {}).merge(
|
|
560
|
+
"tags" => track_tags.transform_keys(&:to_s)
|
|
561
|
+
)
|
|
562
|
+
end
|
|
563
|
+
rescue
|
|
564
|
+
# Never let tracker data injection break execution
|
|
565
|
+
end
|
|
566
|
+
|
|
512
567
|
# Sensitive parameter keys that should be redacted
|
|
513
568
|
SENSITIVE_KEYS = %w[
|
|
514
569
|
password token api_key secret credential auth key
|
|
@@ -527,7 +582,7 @@ module RubyLLM
|
|
|
527
582
|
def truncate_error_message(message)
|
|
528
583
|
return "" if message.nil?
|
|
529
584
|
|
|
530
|
-
message.to_s.truncate(
|
|
585
|
+
message.to_s.truncate(5000)
|
|
531
586
|
rescue
|
|
532
587
|
message.to_s[0, 1000]
|
|
533
588
|
end
|
|
@@ -554,7 +609,7 @@ module RubyLLM
|
|
|
554
609
|
|
|
555
610
|
response_data
|
|
556
611
|
rescue => e
|
|
557
|
-
error("Failed to serialize response: #{e.message}")
|
|
612
|
+
error("Failed to serialize response: #{e.message}", context)
|
|
558
613
|
nil
|
|
559
614
|
end
|
|
560
615
|
|
|
@@ -581,7 +636,7 @@ module RubyLLM
|
|
|
581
636
|
|
|
582
637
|
detail_data[:response] = serialize_audio_response(context.output)
|
|
583
638
|
rescue => e
|
|
584
|
-
error("Failed to persist audio response: #{e.message}")
|
|
639
|
+
error("Failed to persist audio response: #{e.message}", context)
|
|
585
640
|
end
|
|
586
641
|
|
|
587
642
|
# Serializes a SpeechResult into a hash for the response column
|
|
@@ -637,7 +692,7 @@ module RubyLLM
|
|
|
637
692
|
cfg.track_executions
|
|
638
693
|
end
|
|
639
694
|
rescue => e
|
|
640
|
-
debug("Failed to check tracking config: #{e.message}")
|
|
695
|
+
debug("Failed to check tracking config: #{e.message}", context)
|
|
641
696
|
false
|
|
642
697
|
end
|
|
643
698
|
|
|
@@ -41,11 +41,13 @@ module RubyLLM
|
|
|
41
41
|
def call(context)
|
|
42
42
|
return @app.call(context) unless reliability_enabled?
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
trace(context) do
|
|
45
|
+
config = reliability_config
|
|
46
|
+
models_to_try = build_models_list(context, config)
|
|
47
|
+
total_deadline = calculate_deadline(config)
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
execute_with_reliability(context, models_to_try, config, total_deadline)
|
|
50
|
+
end
|
|
49
51
|
end
|
|
50
52
|
|
|
51
53
|
private
|
|
@@ -103,7 +105,7 @@ module RubyLLM
|
|
|
103
105
|
# Check circuit breaker for this model
|
|
104
106
|
breaker = get_circuit_breaker(current_model, context)
|
|
105
107
|
if breaker&.open?
|
|
106
|
-
debug("Circuit breaker open for #{current_model}, skipping")
|
|
108
|
+
debug("Circuit breaker open for #{current_model}, skipping", context)
|
|
107
109
|
tracker.record_short_circuit(current_model)
|
|
108
110
|
next
|
|
109
111
|
end
|
|
@@ -41,10 +41,12 @@ module RubyLLM
|
|
|
41
41
|
# @param context [Context] The execution context
|
|
42
42
|
# @return [Context] The context with tenant fields populated
|
|
43
43
|
def call(context)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
trace(context) do
|
|
45
|
+
resolve_tenant!(context)
|
|
46
|
+
ensure_tenant_record!(context)
|
|
47
|
+
apply_api_configuration!(context)
|
|
48
|
+
@app.call(context)
|
|
49
|
+
end
|
|
48
50
|
end
|
|
49
51
|
|
|
50
52
|
private
|
|
@@ -53,6 +53,17 @@ module RubyLLM
|
|
|
53
53
|
helper RubyLLM::Agents::ApplicationHelper
|
|
54
54
|
before_action :authenticate_dashboard!
|
|
55
55
|
|
|
56
|
+
rescue_from ::ActiveRecord::StatementInvalid do |e|
|
|
57
|
+
if e.message.include?("ruby_llm_agents_")
|
|
58
|
+
render plain: "RubyLLM::Agents migrations are pending.\n\n" \
|
|
59
|
+
"Run: rails db:migrate\n" \
|
|
60
|
+
"Or: rails ruby_llm_agents:doctor",
|
|
61
|
+
status: :service_unavailable
|
|
62
|
+
else
|
|
63
|
+
raise
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
56
67
|
private
|
|
57
68
|
|
|
58
69
|
# Authenticates dashboard access using configured method
|
|
@@ -15,6 +15,8 @@ module RubyLLM
|
|
|
15
15
|
# result.success? # => true
|
|
16
16
|
#
|
|
17
17
|
class BackgroundRemovalResult
|
|
18
|
+
include Trackable
|
|
19
|
+
|
|
18
20
|
attr_reader :foreground, :mask, :source_image, :model_id, :output_format,
|
|
19
21
|
:alpha_matting, :refine_edges,
|
|
20
22
|
:started_at, :completed_at, :tenant_id, :remover_class,
|
|
@@ -38,7 +40,7 @@ module RubyLLM
|
|
|
38
40
|
# @param error_message [String, nil] Error message if failed
|
|
39
41
|
def initialize(foreground:, mask:, source_image:, model_id:, output_format:,
|
|
40
42
|
alpha_matting:, refine_edges:, started_at:, completed_at:,
|
|
41
|
-
tenant_id:, remover_class:, error_class: nil, error_message: nil)
|
|
43
|
+
tenant_id:, remover_class:, error_class: nil, error_message: nil, agent_class_name: nil)
|
|
42
44
|
@foreground = foreground
|
|
43
45
|
@mask = mask
|
|
44
46
|
@source_image = source_image
|
|
@@ -53,6 +55,10 @@ module RubyLLM
|
|
|
53
55
|
@error_class = error_class
|
|
54
56
|
@error_message = error_message
|
|
55
57
|
@execution_id = nil
|
|
58
|
+
|
|
59
|
+
# Tracking
|
|
60
|
+
@agent_class_name = agent_class_name
|
|
61
|
+
register_with_tracker
|
|
56
62
|
end
|
|
57
63
|
|
|
58
64
|
# Loads the associated Execution record from the database
|