ruby_llm-agents 0.2.3 → 0.3.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 +273 -0
- data/app/channels/ruby_llm/agents/executions_channel.rb +24 -1
- data/app/controllers/concerns/ruby_llm/agents/filterable.rb +81 -0
- data/app/controllers/concerns/ruby_llm/agents/paginatable.rb +51 -0
- data/app/controllers/ruby_llm/agents/agents_controller.rb +228 -59
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +167 -12
- data/app/controllers/ruby_llm/agents/executions_controller.rb +189 -31
- data/app/controllers/ruby_llm/agents/settings_controller.rb +20 -0
- data/app/helpers/ruby_llm/agents/application_helper.rb +307 -7
- data/app/models/ruby_llm/agents/execution/analytics.rb +224 -20
- data/app/models/ruby_llm/agents/execution/metrics.rb +41 -25
- data/app/models/ruby_llm/agents/execution/scopes.rb +234 -14
- data/app/models/ruby_llm/agents/execution.rb +259 -16
- data/app/services/ruby_llm/agents/agent_registry.rb +49 -12
- data/app/views/layouts/rubyllm/agents/application.html.erb +351 -85
- data/app/views/rubyllm/agents/agents/_version_comparison.html.erb +186 -0
- data/app/views/rubyllm/agents/agents/show.html.erb +233 -10
- data/app/views/rubyllm/agents/dashboard/_action_center.html.erb +62 -0
- data/app/views/rubyllm/agents/dashboard/_alerts_feed.html.erb +62 -0
- data/app/views/rubyllm/agents/dashboard/_breaker_strip.html.erb +47 -0
- data/app/views/rubyllm/agents/dashboard/_budgets_bar.html.erb +165 -0
- data/app/views/rubyllm/agents/dashboard/_now_strip.html.erb +10 -0
- data/app/views/rubyllm/agents/dashboard/_now_strip_values.html.erb +71 -0
- data/app/views/rubyllm/agents/dashboard/index.html.erb +215 -109
- data/app/views/rubyllm/agents/executions/_filters.html.erb +152 -155
- data/app/views/rubyllm/agents/executions/_list.html.erb +103 -12
- data/app/views/rubyllm/agents/executions/dry_run.html.erb +149 -0
- data/app/views/rubyllm/agents/executions/index.html.erb +17 -72
- data/app/views/rubyllm/agents/executions/index.turbo_stream.erb +16 -2
- data/app/views/rubyllm/agents/executions/show.html.erb +693 -14
- data/app/views/rubyllm/agents/settings/show.html.erb +369 -0
- data/app/views/rubyllm/agents/shared/_filter_dropdown.html.erb +121 -0
- data/app/views/rubyllm/agents/shared/_select_dropdown.html.erb +85 -0
- data/config/routes.rb +7 -0
- data/lib/generators/ruby_llm_agents/templates/add_attempts_migration.rb.tt +27 -0
- data/lib/generators/ruby_llm_agents/templates/add_caching_migration.rb.tt +23 -0
- data/lib/generators/ruby_llm_agents/templates/add_finish_reason_migration.rb.tt +19 -0
- data/lib/generators/ruby_llm_agents/templates/add_routing_migration.rb.tt +19 -0
- data/lib/generators/ruby_llm_agents/templates/add_streaming_migration.rb.tt +8 -0
- data/lib/generators/ruby_llm_agents/templates/add_tracing_migration.rb.tt +34 -0
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +66 -4
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +53 -6
- data/lib/generators/ruby_llm_agents/templates/initializer.rb.tt +139 -8
- data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +38 -1
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +78 -0
- data/lib/ruby_llm/agents/alert_manager.rb +207 -0
- data/lib/ruby_llm/agents/attempt_tracker.rb +295 -0
- data/lib/ruby_llm/agents/base.rb +580 -112
- data/lib/ruby_llm/agents/budget_tracker.rb +360 -0
- data/lib/ruby_llm/agents/circuit_breaker.rb +197 -0
- data/lib/ruby_llm/agents/configuration.rb +279 -1
- data/lib/ruby_llm/agents/engine.rb +59 -6
- data/lib/ruby_llm/agents/execution_logger_job.rb +17 -6
- data/lib/ruby_llm/agents/inflections.rb +13 -2
- data/lib/ruby_llm/agents/instrumentation.rb +538 -87
- data/lib/ruby_llm/agents/redactor.rb +130 -0
- data/lib/ruby_llm/agents/reliability.rb +185 -0
- data/lib/ruby_llm/agents/version.rb +3 -1
- data/lib/ruby_llm/agents.rb +52 -0
- metadata +41 -2
- data/app/controllers/ruby_llm/agents/application_controller.rb +0 -37
|
@@ -2,12 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
module RubyLLM
|
|
4
4
|
module Agents
|
|
5
|
+
# Controller for browsing and searching execution records
|
|
6
|
+
#
|
|
7
|
+
# Provides paginated listing, filtering, and detail views for all
|
|
8
|
+
# agent executions. Supports both HTML and Turbo Stream responses
|
|
9
|
+
# for seamless filtering without full page reloads.
|
|
10
|
+
#
|
|
11
|
+
# @see Paginatable For pagination implementation
|
|
12
|
+
# @see Filterable For filter parsing and validation
|
|
13
|
+
# @api private
|
|
5
14
|
class ExecutionsController < ApplicationController
|
|
15
|
+
include Paginatable
|
|
16
|
+
include Filterable
|
|
17
|
+
|
|
18
|
+
CSV_COLUMNS = %w[id agent_type agent_version status model_id total_tokens total_cost
|
|
19
|
+
duration_ms created_at error_class error_message].freeze
|
|
20
|
+
|
|
21
|
+
# Lists all executions with filtering and pagination
|
|
22
|
+
#
|
|
23
|
+
# @return [void]
|
|
6
24
|
def index
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
load_paginated_executions
|
|
10
|
-
load_filter_stats
|
|
25
|
+
load_filter_options
|
|
26
|
+
load_executions_with_stats
|
|
11
27
|
|
|
12
28
|
respond_to do |format|
|
|
13
29
|
format.html
|
|
@@ -15,15 +31,22 @@ module RubyLLM
|
|
|
15
31
|
end
|
|
16
32
|
end
|
|
17
33
|
|
|
34
|
+
# Shows a single execution's details
|
|
35
|
+
#
|
|
36
|
+
# @return [void]
|
|
18
37
|
def show
|
|
19
38
|
@execution = Execution.find(params[:id])
|
|
20
39
|
end
|
|
21
40
|
|
|
41
|
+
# Handles filter search requests via Turbo Stream
|
|
42
|
+
#
|
|
43
|
+
# Returns the same data as index but optimized for AJAX/Turbo
|
|
44
|
+
# requests, replacing only the executions list partial.
|
|
45
|
+
#
|
|
46
|
+
# @return [void]
|
|
22
47
|
def search
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
load_paginated_executions
|
|
26
|
-
load_filter_stats
|
|
48
|
+
load_filter_options
|
|
49
|
+
load_executions_with_stats
|
|
27
50
|
|
|
28
51
|
respond_to do |format|
|
|
29
52
|
format.html { render :index }
|
|
@@ -37,25 +60,145 @@ module RubyLLM
|
|
|
37
60
|
end
|
|
38
61
|
end
|
|
39
62
|
|
|
63
|
+
# Reruns an execution with the same parameters
|
|
64
|
+
#
|
|
65
|
+
# Supports both dry-run mode (returns prompt info without API call)
|
|
66
|
+
# and real reruns that create a new execution.
|
|
67
|
+
#
|
|
68
|
+
# @return [void]
|
|
69
|
+
def rerun
|
|
70
|
+
@execution = Execution.find(params[:id])
|
|
71
|
+
dry_run = params[:dry_run] == "true"
|
|
72
|
+
|
|
73
|
+
agent_class = AgentRegistry.find(@execution.agent_type)
|
|
74
|
+
|
|
75
|
+
unless agent_class
|
|
76
|
+
flash[:alert] = "Agent class '#{@execution.agent_type}' not found. Cannot rerun."
|
|
77
|
+
redirect_to execution_path(@execution)
|
|
78
|
+
return
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Prepare parameters from original execution
|
|
82
|
+
original_params = @execution.parameters&.symbolize_keys || {}
|
|
83
|
+
|
|
84
|
+
if dry_run
|
|
85
|
+
# Dry run mode - show what would be sent without making API call
|
|
86
|
+
result = agent_class.call(**original_params, dry_run: true)
|
|
87
|
+
@dry_run_result = result
|
|
88
|
+
|
|
89
|
+
respond_to do |format|
|
|
90
|
+
format.html { render :dry_run }
|
|
91
|
+
format.json { render json: result }
|
|
92
|
+
end
|
|
93
|
+
else
|
|
94
|
+
# Real rerun - execute the agent
|
|
95
|
+
begin
|
|
96
|
+
agent_class.call(**original_params)
|
|
97
|
+
flash[:notice] = "Execution rerun successfully! Check the executions list for the new result."
|
|
98
|
+
rescue StandardError => e
|
|
99
|
+
flash[:alert] = "Rerun failed: #{e.message}"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
redirect_to executions_path
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Exports filtered executions as CSV
|
|
107
|
+
#
|
|
108
|
+
# Streams CSV data with redacted error messages to protect
|
|
109
|
+
# sensitive information. Respects all current filter parameters.
|
|
110
|
+
#
|
|
111
|
+
# @return [void]
|
|
112
|
+
def export
|
|
113
|
+
filename = "executions-#{Date.current.iso8601}.csv"
|
|
114
|
+
|
|
115
|
+
headers["Content-Type"] = "text/csv"
|
|
116
|
+
headers["Content-Disposition"] = "attachment; filename=\"#{filename}\""
|
|
117
|
+
|
|
118
|
+
response.status = 200
|
|
119
|
+
|
|
120
|
+
self.response_body = Enumerator.new do |yielder|
|
|
121
|
+
yielder << CSV.generate_line(CSV_COLUMNS)
|
|
122
|
+
|
|
123
|
+
filtered_executions.find_each(batch_size: 1000) do |execution|
|
|
124
|
+
yielder << generate_csv_row(execution)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
40
129
|
private
|
|
41
130
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
131
|
+
# Generates a CSV row for a single execution with redacted values
|
|
132
|
+
#
|
|
133
|
+
# @param execution [Execution] The execution record
|
|
134
|
+
# @return [String] CSV row string
|
|
135
|
+
def generate_csv_row(execution)
|
|
136
|
+
redacted_error_message = if execution.error_message.present?
|
|
137
|
+
Redactor.redact_string(execution.error_message)
|
|
138
|
+
end
|
|
46
139
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
140
|
+
CSV.generate_line([
|
|
141
|
+
execution.id,
|
|
142
|
+
execution.agent_type,
|
|
143
|
+
execution.agent_version,
|
|
144
|
+
execution.status,
|
|
145
|
+
execution.model_id,
|
|
146
|
+
execution.total_tokens,
|
|
147
|
+
execution.total_cost&.to_f,
|
|
148
|
+
execution.duration_ms,
|
|
149
|
+
execution.created_at.iso8601,
|
|
150
|
+
execution.error_class,
|
|
151
|
+
redacted_error_message
|
|
152
|
+
])
|
|
153
|
+
end
|
|
50
154
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
155
|
+
# Loads available options for filter dropdowns
|
|
156
|
+
#
|
|
157
|
+
# Populates @agent_types with all agent types that have executions,
|
|
158
|
+
# @model_ids with all distinct models used, and @statuses with all
|
|
159
|
+
# possible status values.
|
|
160
|
+
#
|
|
161
|
+
# @return [void]
|
|
162
|
+
def load_filter_options
|
|
163
|
+
@agent_types = available_agent_types
|
|
164
|
+
@model_ids = available_model_ids
|
|
165
|
+
@statuses = Execution.statuses.keys
|
|
57
166
|
end
|
|
58
167
|
|
|
168
|
+
# Returns distinct agent types from execution history
|
|
169
|
+
#
|
|
170
|
+
# Memoized to avoid duplicate queries within a request.
|
|
171
|
+
#
|
|
172
|
+
# @return [Array<String>] Agent type names
|
|
173
|
+
def available_agent_types
|
|
174
|
+
@available_agent_types ||= Execution.distinct.pluck(:agent_type)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Returns distinct model IDs from execution history
|
|
178
|
+
#
|
|
179
|
+
# Memoized to avoid duplicate queries within a request.
|
|
180
|
+
#
|
|
181
|
+
# @return [Array<String>] Model IDs
|
|
182
|
+
def available_model_ids
|
|
183
|
+
@available_model_ids ||= Execution.where.not(model_id: nil).distinct.pluck(:model_id).sort
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Loads paginated executions and associated statistics
|
|
187
|
+
#
|
|
188
|
+
# Sets @executions, @pagination, and @filter_stats instance variables
|
|
189
|
+
# for use in views.
|
|
190
|
+
#
|
|
191
|
+
# @return [void]
|
|
192
|
+
def load_executions_with_stats
|
|
193
|
+
result = paginate(filtered_executions)
|
|
194
|
+
@executions = result[:records]
|
|
195
|
+
@pagination = result[:pagination]
|
|
196
|
+
load_filter_stats
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Calculates aggregate statistics for the current filter
|
|
200
|
+
#
|
|
201
|
+
# @return [void]
|
|
59
202
|
def load_filter_stats
|
|
60
203
|
scope = filtered_executions
|
|
61
204
|
@filter_stats = {
|
|
@@ -65,26 +208,41 @@ module RubyLLM
|
|
|
65
208
|
}
|
|
66
209
|
end
|
|
67
210
|
|
|
211
|
+
# Builds a filtered execution scope based on request params
|
|
212
|
+
#
|
|
213
|
+
# Applies filters in order: search, agent type, status, then time range.
|
|
214
|
+
# Each filter is optional and validated before application.
|
|
215
|
+
#
|
|
216
|
+
# @return [ActiveRecord::Relation] Filtered execution scope
|
|
68
217
|
def filtered_executions
|
|
69
218
|
scope = Execution.all
|
|
70
219
|
|
|
71
|
-
#
|
|
72
|
-
if params[:
|
|
73
|
-
|
|
74
|
-
|
|
220
|
+
# Apply search filter
|
|
221
|
+
scope = scope.search(params[:q]) if params[:q].present?
|
|
222
|
+
|
|
223
|
+
# Apply agent type filter
|
|
224
|
+
agent_types = parse_array_param(:agent_types)
|
|
225
|
+
if agent_types.any?
|
|
226
|
+
scope = scope.where(agent_type: agent_types)
|
|
75
227
|
elsif params[:agent_type].present?
|
|
76
228
|
scope = scope.by_agent(params[:agent_type])
|
|
77
229
|
end
|
|
78
230
|
|
|
79
|
-
#
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
scope = scope
|
|
231
|
+
# Apply status filter with validation
|
|
232
|
+
statuses = parse_array_param(:statuses)
|
|
233
|
+
if statuses.any?
|
|
234
|
+
scope = apply_status_filter(scope, statuses)
|
|
83
235
|
elsif params[:status].present?
|
|
84
|
-
scope = scope
|
|
236
|
+
scope = apply_status_filter(scope, [params[:status]])
|
|
85
237
|
end
|
|
86
238
|
|
|
87
|
-
|
|
239
|
+
# Apply time filter with validation
|
|
240
|
+
days = parse_days_param
|
|
241
|
+
scope = apply_time_filter(scope, days)
|
|
242
|
+
|
|
243
|
+
# Apply model filter
|
|
244
|
+
model_ids = parse_array_param(:model_ids)
|
|
245
|
+
scope = scope.where(model_id: model_ids) if model_ids.any?
|
|
88
246
|
|
|
89
247
|
scope
|
|
90
248
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Agents
|
|
5
|
+
# Controller for displaying global configuration settings
|
|
6
|
+
#
|
|
7
|
+
# Shows all configuration options from RubyLLM::Agents.configuration
|
|
8
|
+
# in a read-only dashboard view.
|
|
9
|
+
#
|
|
10
|
+
# @api private
|
|
11
|
+
class SettingsController < ApplicationController
|
|
12
|
+
# Displays the global configuration settings
|
|
13
|
+
#
|
|
14
|
+
# @return [void]
|
|
15
|
+
def show
|
|
16
|
+
@config = RubyLLM::Agents.configuration
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|