ruby_llm-agents 1.3.3 → 2.0.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 +101 -334
- data/app/controllers/concerns/ruby_llm/agents/sortable.rb +0 -1
- data/app/controllers/ruby_llm/agents/agents_controller.rb +5 -56
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +22 -106
- data/app/controllers/ruby_llm/agents/executions_controller.rb +4 -114
- data/app/controllers/ruby_llm/agents/tenants_controller.rb +30 -2
- data/app/helpers/ruby_llm/agents/application_helper.rb +19 -53
- data/app/models/ruby_llm/agents/execution/analytics.rb +13 -54
- data/app/models/ruby_llm/agents/execution/scopes.rb +61 -14
- data/app/models/ruby_llm/agents/execution.rb +46 -10
- data/app/models/ruby_llm/agents/execution_detail.rb +18 -0
- data/app/models/ruby_llm/agents/tenant/budgetable.rb +132 -24
- data/app/models/ruby_llm/agents/tenant/incrementable.rb +117 -0
- data/app/models/ruby_llm/agents/tenant/resettable.rb +128 -0
- data/app/models/ruby_llm/agents/tenant/trackable.rb +46 -12
- data/app/models/ruby_llm/agents/tenant.rb +2 -3
- data/app/models/ruby_llm/agents/tenant_budget.rb +6 -3
- data/app/services/ruby_llm/agents/agent_registry.rb +6 -112
- data/app/views/layouts/ruby_llm/agents/application.html.erb +87 -252
- data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +71 -218
- data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +20 -63
- data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +44 -131
- data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +16 -57
- data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +39 -104
- data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +29 -82
- data/app/views/ruby_llm/agents/agents/_empty_state.html.erb +4 -14
- data/app/views/ruby_llm/agents/agents/index.html.erb +105 -274
- data/app/views/ruby_llm/agents/agents/show.html.erb +248 -378
- data/app/views/ruby_llm/agents/dashboard/_action_center.html.erb +29 -52
- data/app/views/ruby_llm/agents/dashboard/_tenant_budget.html.erb +73 -99
- data/app/views/ruby_llm/agents/dashboard/index.html.erb +228 -433
- data/app/views/ruby_llm/agents/executions/_execution.html.erb +1 -1
- data/app/views/ruby_llm/agents/executions/_filters.html.erb +4 -25
- data/app/views/ruby_llm/agents/executions/_list.html.erb +111 -152
- data/app/views/ruby_llm/agents/executions/index.html.erb +5 -7
- data/app/views/ruby_llm/agents/executions/show.html.erb +528 -989
- data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +5 -21
- data/app/views/ruby_llm/agents/shared/_executions_table.html.erb +70 -191
- data/app/views/ruby_llm/agents/shared/_filter_dropdown.html.erb +16 -44
- data/app/views/ruby_llm/agents/shared/_select_dropdown.html.erb +12 -41
- data/app/views/ruby_llm/agents/shared/_status_badge.html.erb +11 -65
- data/app/views/ruby_llm/agents/shared/_tenant_filter.html.erb +6 -5
- data/app/views/ruby_llm/agents/system_config/show.html.erb +240 -351
- data/app/views/ruby_llm/agents/tenants/_form.html.erb +67 -77
- data/app/views/ruby_llm/agents/tenants/edit.html.erb +7 -9
- data/app/views/ruby_llm/agents/tenants/index.html.erb +100 -122
- data/app/views/ruby_llm/agents/tenants/show.html.erb +146 -336
- data/config/routes.rb +0 -13
- data/lib/generators/ruby_llm_agents/install_generator.rb +9 -14
- data/lib/generators/ruby_llm_agents/migrate_structure_generator.rb +2 -12
- data/lib/generators/ruby_llm_agents/restructure_generator.rb +0 -2
- data/lib/generators/ruby_llm_agents/templates/add_usage_counters_to_tenants_migration.rb.tt +37 -0
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +1 -2
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +1 -1
- data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +0 -1
- data/lib/generators/ruby_llm_agents/templates/create_execution_details_migration.rb.tt +27 -0
- data/lib/generators/ruby_llm_agents/templates/create_tenants_migration.rb.tt +25 -0
- data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +0 -1
- data/lib/generators/ruby_llm_agents/templates/initializer.rb.tt +9 -12
- data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +40 -71
- data/lib/generators/ruby_llm_agents/templates/remove_agent_version_migration.rb.tt +13 -0
- data/lib/generators/ruby_llm_agents/templates/remove_workflow_columns_migration.rb.tt +19 -0
- data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +2 -4
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +0 -1
- data/lib/generators/ruby_llm_agents/templates/split_execution_details_migration.rb.tt +232 -0
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +58 -262
- data/lib/ruby_llm/agents/audio/speaker.rb +0 -1
- data/lib/ruby_llm/agents/audio/transcriber.rb +0 -1
- data/lib/ruby_llm/agents/base_agent.rb +52 -6
- data/lib/ruby_llm/agents/core/base/callbacks.rb +142 -0
- data/lib/ruby_llm/agents/core/base.rb +23 -55
- data/lib/ruby_llm/agents/core/configuration.rb +58 -117
- data/lib/ruby_llm/agents/core/errors.rb +0 -58
- data/lib/ruby_llm/agents/core/instrumentation.rb +157 -110
- data/lib/ruby_llm/agents/core/llm_tenant.rb +8 -7
- data/lib/ruby_llm/agents/core/version.rb +1 -1
- data/lib/ruby_llm/agents/dsl/base.rb +157 -17
- data/lib/ruby_llm/agents/dsl/caching.rb +33 -2
- data/lib/ruby_llm/agents/dsl/reliability.rb +148 -0
- data/lib/ruby_llm/agents/dsl.rb +1 -2
- data/lib/ruby_llm/agents/image/analyzer/execution.rb +1 -2
- data/lib/ruby_llm/agents/image/background_remover/execution.rb +1 -2
- data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +1 -13
- data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +2 -2
- data/lib/ruby_llm/agents/image/editor/dsl.rb +0 -14
- data/lib/ruby_llm/agents/image/editor/execution.rb +1 -10
- data/lib/ruby_llm/agents/image/editor.rb +0 -1
- data/lib/ruby_llm/agents/image/generator.rb +0 -21
- data/lib/ruby_llm/agents/image/pipeline/dsl.rb +0 -13
- data/lib/ruby_llm/agents/image/pipeline/execution.rb +0 -1
- data/lib/ruby_llm/agents/image/transformer/dsl.rb +0 -13
- data/lib/ruby_llm/agents/image/transformer/execution.rb +1 -10
- data/lib/ruby_llm/agents/image/transformer.rb +0 -1
- data/lib/ruby_llm/agents/image/upscaler/execution.rb +1 -2
- data/lib/ruby_llm/agents/image/variator/execution.rb +1 -2
- data/lib/ruby_llm/agents/infrastructure/alert_manager.rb +78 -173
- data/lib/ruby_llm/agents/infrastructure/attempt_tracker.rb +1 -0
- data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +66 -2
- data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +0 -12
- data/lib/ruby_llm/agents/infrastructure/circuit_breaker.rb +10 -13
- data/lib/ruby_llm/agents/infrastructure/reliability.rb +37 -2
- data/lib/ruby_llm/agents/pipeline/context.rb +0 -1
- data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +28 -4
- data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +3 -10
- data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +88 -55
- data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +5 -41
- data/lib/ruby_llm/agents/rails/engine.rb +6 -6
- data/lib/ruby_llm/agents/results/base.rb +1 -49
- data/lib/ruby_llm/agents/text/embedder.rb +0 -1
- data/lib/ruby_llm/agents.rb +1 -9
- data/lib/tasks/ruby_llm_agents.rake +34 -0
- metadata +12 -81
- data/app/controllers/ruby_llm/agents/api_configurations_controller.rb +0 -214
- data/app/controllers/ruby_llm/agents/workflows_controller.rb +0 -544
- data/app/mailers/ruby_llm/agents/alert_mailer.rb +0 -84
- data/app/mailers/ruby_llm/agents/application_mailer.rb +0 -28
- data/app/models/ruby_llm/agents/api_configuration.rb +0 -386
- data/app/models/ruby_llm/agents/execution/workflow.rb +0 -170
- data/app/models/ruby_llm/agents/tenant/configurable.rb +0 -135
- data/app/views/ruby_llm/agents/agents/_agent.html.erb +0 -98
- data/app/views/ruby_llm/agents/agents/_version_comparison.html.erb +0 -186
- data/app/views/ruby_llm/agents/agents/_workflow.html.erb +0 -126
- data/app/views/ruby_llm/agents/alert_mailer/alert_notification.html.erb +0 -107
- data/app/views/ruby_llm/agents/alert_mailer/alert_notification.text.erb +0 -18
- data/app/views/ruby_llm/agents/api_configurations/_api_key_field.html.erb +0 -34
- data/app/views/ruby_llm/agents/api_configurations/_form.html.erb +0 -288
- data/app/views/ruby_llm/agents/api_configurations/edit.html.erb +0 -95
- data/app/views/ruby_llm/agents/api_configurations/edit_tenant.html.erb +0 -97
- data/app/views/ruby_llm/agents/api_configurations/show.html.erb +0 -214
- data/app/views/ruby_llm/agents/api_configurations/tenant.html.erb +0 -179
- data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +0 -73
- data/app/views/ruby_llm/agents/dashboard/_alerts_feed.html.erb +0 -62
- data/app/views/ruby_llm/agents/dashboard/_breaker_strip.html.erb +0 -47
- data/app/views/ruby_llm/agents/dashboard/_budgets_bar.html.erb +0 -75
- data/app/views/ruby_llm/agents/dashboard/_model_comparison.html.erb +0 -56
- data/app/views/ruby_llm/agents/dashboard/_model_cost_breakdown.html.erb +0 -115
- data/app/views/ruby_llm/agents/dashboard/_now_strip.html.erb +0 -59
- data/app/views/ruby_llm/agents/dashboard/_top_errors.html.erb +0 -60
- data/app/views/ruby_llm/agents/executions/_workflow_summary.html.erb +0 -86
- data/app/views/ruby_llm/agents/executions/dry_run.html.erb +0 -149
- data/app/views/ruby_llm/agents/shared/_breadcrumbs.html.erb +0 -48
- data/app/views/ruby_llm/agents/shared/_nav_link.html.erb +0 -27
- data/app/views/ruby_llm/agents/shared/_stat_card.html.erb +0 -14
- data/app/views/ruby_llm/agents/shared/_workflow_type_badge.html.erb +0 -35
- data/app/views/ruby_llm/agents/workflows/_empty_state.html.erb +0 -22
- data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +0 -228
- data/app/views/ruby_llm/agents/workflows/_structure_dsl.html.erb +0 -539
- data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +0 -76
- data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +0 -74
- data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +0 -108
- data/app/views/ruby_llm/agents/workflows/_workflow_diagram.html.erb +0 -920
- data/app/views/ruby_llm/agents/workflows/index.html.erb +0 -179
- data/app/views/ruby_llm/agents/workflows/show.html.erb +0 -467
- data/lib/generators/ruby_llm_agents/api_configuration_generator.rb +0 -100
- data/lib/generators/ruby_llm_agents/templates/add_workflow_migration.rb.tt +0 -38
- data/lib/generators/ruby_llm_agents/templates/application_workflow.rb.tt +0 -48
- data/lib/generators/ruby_llm_agents/templates/create_api_configurations_migration.rb.tt +0 -90
- data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +0 -551
- data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +0 -181
- data/lib/ruby_llm/agents/core/base/moderation_execution.rb +0 -274
- data/lib/ruby_llm/agents/core/resolved_config.rb +0 -348
- data/lib/ruby_llm/agents/image/generator/content_policy.rb +0 -95
- data/lib/ruby_llm/agents/infrastructure/redactor.rb +0 -130
- data/lib/ruby_llm/agents/results/moderation_result.rb +0 -158
- data/lib/ruby_llm/agents/text/moderator.rb +0 -237
- data/lib/ruby_llm/agents/workflow/approval.rb +0 -205
- data/lib/ruby_llm/agents/workflow/approval_store.rb +0 -179
- data/lib/ruby_llm/agents/workflow/async.rb +0 -220
- data/lib/ruby_llm/agents/workflow/async_executor.rb +0 -156
- data/lib/ruby_llm/agents/workflow/dsl/executor.rb +0 -467
- data/lib/ruby_llm/agents/workflow/dsl/input_schema.rb +0 -244
- data/lib/ruby_llm/agents/workflow/dsl/iteration_executor.rb +0 -289
- data/lib/ruby_llm/agents/workflow/dsl/parallel_group.rb +0 -107
- data/lib/ruby_llm/agents/workflow/dsl/route_builder.rb +0 -150
- data/lib/ruby_llm/agents/workflow/dsl/schedule_helpers.rb +0 -187
- data/lib/ruby_llm/agents/workflow/dsl/step_config.rb +0 -352
- data/lib/ruby_llm/agents/workflow/dsl/step_executor.rb +0 -415
- data/lib/ruby_llm/agents/workflow/dsl/wait_config.rb +0 -257
- data/lib/ruby_llm/agents/workflow/dsl/wait_executor.rb +0 -317
- data/lib/ruby_llm/agents/workflow/dsl.rb +0 -576
- data/lib/ruby_llm/agents/workflow/instrumentation.rb +0 -249
- data/lib/ruby_llm/agents/workflow/notifiers/base.rb +0 -117
- data/lib/ruby_llm/agents/workflow/notifiers/email.rb +0 -117
- data/lib/ruby_llm/agents/workflow/notifiers/slack.rb +0 -180
- data/lib/ruby_llm/agents/workflow/notifiers/webhook.rb +0 -121
- data/lib/ruby_llm/agents/workflow/notifiers.rb +0 -70
- data/lib/ruby_llm/agents/workflow/orchestrator.rb +0 -416
- data/lib/ruby_llm/agents/workflow/result.rb +0 -592
- data/lib/ruby_llm/agents/workflow/thread_pool.rb +0 -185
- data/lib/ruby_llm/agents/workflow/throttle_manager.rb +0 -206
- data/lib/ruby_llm/agents/workflow/wait_result.rb +0 -213
|
@@ -88,63 +88,5 @@ module RubyLLM
|
|
|
88
88
|
|
|
89
89
|
# Raised for configuration issues
|
|
90
90
|
class ConfigurationError < Error; end
|
|
91
|
-
|
|
92
|
-
# Raised when content is flagged during moderation
|
|
93
|
-
#
|
|
94
|
-
# Contains the full moderation result and the phase where
|
|
95
|
-
# the content was flagged.
|
|
96
|
-
#
|
|
97
|
-
# @example Handling moderation errors
|
|
98
|
-
# begin
|
|
99
|
-
# result = MyAgent.call(message: user_input)
|
|
100
|
-
# rescue RubyLLM::Agents::ModerationError => e
|
|
101
|
-
# puts "Content blocked: #{e.flagged_categories.join(', ')}"
|
|
102
|
-
# puts "Phase: #{e.phase}"
|
|
103
|
-
# puts "Scores: #{e.category_scores}"
|
|
104
|
-
# end
|
|
105
|
-
#
|
|
106
|
-
# @api public
|
|
107
|
-
class ModerationError < Error
|
|
108
|
-
# @return [Object] The raw moderation result from RubyLLM
|
|
109
|
-
attr_reader :moderation_result
|
|
110
|
-
|
|
111
|
-
# @return [Symbol] The phase where content was flagged (:input or :output)
|
|
112
|
-
attr_reader :phase
|
|
113
|
-
|
|
114
|
-
# Creates a new ModerationError
|
|
115
|
-
#
|
|
116
|
-
# @param moderation_result [Object] The moderation result from RubyLLM
|
|
117
|
-
# @param phase [Symbol] The phase where content was flagged
|
|
118
|
-
def initialize(moderation_result, phase)
|
|
119
|
-
@moderation_result = moderation_result
|
|
120
|
-
@phase = phase
|
|
121
|
-
|
|
122
|
-
categories = moderation_result.flagged_categories
|
|
123
|
-
category_list = categories.respond_to?(:join) ? categories.join(", ") : categories.to_s
|
|
124
|
-
|
|
125
|
-
super("Content flagged during #{phase} moderation: #{category_list}")
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
# Returns the flagged categories from the moderation result
|
|
129
|
-
#
|
|
130
|
-
# @return [Array<String, Symbol>] List of flagged categories
|
|
131
|
-
def flagged_categories
|
|
132
|
-
moderation_result.flagged_categories
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
# Returns the category scores from the moderation result
|
|
136
|
-
#
|
|
137
|
-
# @return [Hash{String, Symbol => Float}] Category to score mapping
|
|
138
|
-
def category_scores
|
|
139
|
-
moderation_result.category_scores
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
# Returns whether the moderation result was flagged
|
|
143
|
-
#
|
|
144
|
-
# @return [Boolean] Always true for ModerationError
|
|
145
|
-
def flagged?
|
|
146
|
-
true
|
|
147
|
-
end
|
|
148
|
-
end
|
|
149
91
|
end
|
|
150
92
|
end
|
|
@@ -15,7 +15,7 @@ module RubyLLM
|
|
|
15
15
|
#
|
|
16
16
|
# @example Adding custom metadata to executions
|
|
17
17
|
# class MyAgent < ApplicationAgent
|
|
18
|
-
# def
|
|
18
|
+
# def metadata
|
|
19
19
|
# { user_id: Current.user&.id, request_id: request.uuid }
|
|
20
20
|
# end
|
|
21
21
|
# end
|
|
@@ -232,36 +232,33 @@ module RubyLLM
|
|
|
232
232
|
# @return [RubyLLM::Agents::Execution, nil] The created record, or nil on failure
|
|
233
233
|
def create_running_execution(started_at, fallback_chain: [])
|
|
234
234
|
config = RubyLLM::Agents.configuration
|
|
235
|
-
|
|
235
|
+
agent_metadata = metadata
|
|
236
|
+
|
|
237
|
+
# Separate niche tracing fields into metadata
|
|
238
|
+
exec_metadata = agent_metadata.dup
|
|
239
|
+
exec_metadata["span_id"] = exec_metadata.delete(:span_id) if exec_metadata[:span_id]
|
|
236
240
|
|
|
237
241
|
execution_data = {
|
|
238
242
|
agent_type: self.class.name,
|
|
239
|
-
agent_version: self.class.version,
|
|
240
243
|
model_id: model,
|
|
241
244
|
temperature: temperature,
|
|
242
245
|
started_at: started_at,
|
|
243
246
|
status: "running",
|
|
244
|
-
|
|
245
|
-
metadata: metadata,
|
|
246
|
-
system_prompt: config.persist_prompts ? redacted_system_prompt : nil,
|
|
247
|
-
user_prompt: config.persist_prompts ? redacted_user_prompt : nil,
|
|
247
|
+
metadata: exec_metadata,
|
|
248
248
|
streaming: self.class.streaming,
|
|
249
|
-
messages_count: resolved_messages.size
|
|
250
|
-
messages_summary: config.persist_messages_summary ? messages_summary : {}
|
|
249
|
+
messages_count: resolved_messages.size
|
|
251
250
|
}
|
|
252
251
|
|
|
253
252
|
# Extract tracing fields from metadata if present
|
|
254
|
-
execution_data[:request_id] =
|
|
255
|
-
execution_data[:trace_id] =
|
|
256
|
-
execution_data[:
|
|
257
|
-
execution_data[:
|
|
258
|
-
execution_data[:root_execution_id] = metadata[:root_execution_id] if metadata[:root_execution_id]
|
|
253
|
+
execution_data[:request_id] = agent_metadata[:request_id] if agent_metadata[:request_id]
|
|
254
|
+
execution_data[:trace_id] = agent_metadata[:trace_id] if agent_metadata[:trace_id]
|
|
255
|
+
execution_data[:parent_execution_id] = agent_metadata[:parent_execution_id] if agent_metadata[:parent_execution_id]
|
|
256
|
+
execution_data[:root_execution_id] = agent_metadata[:root_execution_id] if agent_metadata[:root_execution_id]
|
|
259
257
|
|
|
260
|
-
# Add fallback chain
|
|
258
|
+
# Add fallback chain tracking (count only on execution, chain stored in detail)
|
|
261
259
|
if fallback_chain.any?
|
|
262
|
-
execution_data[:fallback_chain] = fallback_chain
|
|
263
|
-
execution_data[:attempts] = []
|
|
264
260
|
execution_data[:attempts_count] = 0
|
|
261
|
+
@_pending_detail_data = { fallback_chain: fallback_chain, attempts: [] }
|
|
265
262
|
end
|
|
266
263
|
|
|
267
264
|
# Add tenant_id if multi-tenancy is enabled
|
|
@@ -269,7 +266,22 @@ module RubyLLM
|
|
|
269
266
|
execution_data[:tenant_id] = config.current_tenant_id
|
|
270
267
|
end
|
|
271
268
|
|
|
272
|
-
RubyLLM::Agents::Execution.create!(execution_data)
|
|
269
|
+
execution = RubyLLM::Agents::Execution.create!(execution_data)
|
|
270
|
+
|
|
271
|
+
# Create detail record with prompts and parameters
|
|
272
|
+
detail_data = {
|
|
273
|
+
parameters: sanitized_parameters,
|
|
274
|
+
messages_summary: config.persist_messages_summary ? messages_summary : {},
|
|
275
|
+
system_prompt: config.persist_prompts ? stored_system_prompt : nil,
|
|
276
|
+
user_prompt: config.persist_prompts ? stored_user_prompt : nil
|
|
277
|
+
}
|
|
278
|
+
detail_data.merge!(@_pending_detail_data) if @_pending_detail_data
|
|
279
|
+
@_pending_detail_data = nil
|
|
280
|
+
|
|
281
|
+
has_data = detail_data.values.any? { |v| v.present? && v != {} && v != [] }
|
|
282
|
+
execution.create_detail!(detail_data) if has_data
|
|
283
|
+
|
|
284
|
+
execution
|
|
273
285
|
rescue StandardError => e
|
|
274
286
|
# Log error but don't fail the agent execution itself
|
|
275
287
|
Rails.logger.error("[RubyLLM::Agents] Failed to create execution record: #{e.message}")
|
|
@@ -299,26 +311,43 @@ module RubyLLM
|
|
|
299
311
|
status: status
|
|
300
312
|
}
|
|
301
313
|
|
|
302
|
-
#
|
|
303
|
-
|
|
314
|
+
# Store niche streaming metrics in metadata
|
|
315
|
+
if respond_to?(:time_to_first_token_ms) && time_to_first_token_ms
|
|
316
|
+
update_data[:metadata] = (execution.metadata || {}).merge("time_to_first_token_ms" => time_to_first_token_ms)
|
|
317
|
+
end
|
|
304
318
|
|
|
305
319
|
# Add response data if available (using safe extraction)
|
|
306
320
|
response_data = safe_extract_response_data(response)
|
|
307
321
|
if response_data.any?
|
|
308
|
-
|
|
322
|
+
# Separate execution-level fields from detail-level fields
|
|
323
|
+
detail_fields = response_data.extract!(:response, :tool_calls, :cache_creation_tokens)
|
|
324
|
+
update_data.merge!(response_data.except(:tool_calls_count))
|
|
325
|
+
update_data[:tool_calls_count] = detail_fields[:tool_calls]&.size || 0
|
|
309
326
|
update_data[:model_id] ||= model
|
|
310
327
|
end
|
|
311
328
|
|
|
312
|
-
# Add error
|
|
329
|
+
# Add error class on execution (error_message goes to detail)
|
|
313
330
|
if error
|
|
314
|
-
update_data.
|
|
315
|
-
error_message: error.message,
|
|
316
|
-
error_class: error.class.name
|
|
317
|
-
)
|
|
331
|
+
update_data[:error_class] = error.class.name
|
|
318
332
|
end
|
|
319
333
|
|
|
320
334
|
execution.update!(update_data)
|
|
321
335
|
|
|
336
|
+
# Update or create detail record with completion data
|
|
337
|
+
detail_update = {}
|
|
338
|
+
detail_update[:response] = detail_fields[:response] if detail_fields&.dig(:response)
|
|
339
|
+
detail_update[:tool_calls] = detail_fields[:tool_calls] if detail_fields&.dig(:tool_calls)
|
|
340
|
+
detail_update[:cache_creation_tokens] = detail_fields[:cache_creation_tokens] if detail_fields&.dig(:cache_creation_tokens)
|
|
341
|
+
detail_update[:error_message] = error.message if error
|
|
342
|
+
|
|
343
|
+
if detail_update.values.any?(&:present?)
|
|
344
|
+
if execution.detail
|
|
345
|
+
execution.detail.update!(detail_update)
|
|
346
|
+
else
|
|
347
|
+
execution.create_detail!(detail_update)
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
322
351
|
# Calculate costs if token data is available
|
|
323
352
|
if execution.input_tokens && execution.output_tokens
|
|
324
353
|
begin
|
|
@@ -368,7 +397,6 @@ module RubyLLM
|
|
|
368
397
|
completed_at: completed_at,
|
|
369
398
|
duration_ms: duration_ms,
|
|
370
399
|
status: status,
|
|
371
|
-
attempts: attempt_tracker.to_json_array,
|
|
372
400
|
attempts_count: attempt_tracker.attempts_count,
|
|
373
401
|
chosen_model_id: attempt_tracker.chosen_model_id,
|
|
374
402
|
input_tokens: attempt_tracker.total_input_tokens,
|
|
@@ -377,8 +405,11 @@ module RubyLLM
|
|
|
377
405
|
cached_tokens: attempt_tracker.total_cached_tokens
|
|
378
406
|
}
|
|
379
407
|
|
|
380
|
-
#
|
|
381
|
-
|
|
408
|
+
# Store niche streaming metrics in metadata
|
|
409
|
+
merged_metadata = execution.metadata || {}
|
|
410
|
+
if respond_to?(:time_to_first_token_ms) && time_to_first_token_ms
|
|
411
|
+
merged_metadata["time_to_first_token_ms"] = time_to_first_token_ms
|
|
412
|
+
end
|
|
382
413
|
|
|
383
414
|
# Add finish reason from response if available
|
|
384
415
|
if @last_response
|
|
@@ -386,31 +417,46 @@ module RubyLLM
|
|
|
386
417
|
update_data[:finish_reason] = finish_reason if finish_reason
|
|
387
418
|
end
|
|
388
419
|
|
|
389
|
-
#
|
|
420
|
+
# Store routing/retry niche fields in metadata
|
|
390
421
|
routing_data = extract_routing_data(attempt_tracker, error)
|
|
391
|
-
|
|
422
|
+
merged_metadata["fallback_reason"] = routing_data[:fallback_reason] if routing_data[:fallback_reason]
|
|
423
|
+
merged_metadata["retryable"] = routing_data[:retryable] if routing_data.key?(:retryable)
|
|
424
|
+
merged_metadata["rate_limited"] = routing_data[:rate_limited] if routing_data.key?(:rate_limited)
|
|
392
425
|
|
|
393
|
-
|
|
394
|
-
if @last_response && config.persist_responses
|
|
395
|
-
update_data[:response] = redacted_response(@last_response)
|
|
396
|
-
end
|
|
426
|
+
update_data[:metadata] = merged_metadata if merged_metadata.any?
|
|
397
427
|
|
|
398
|
-
#
|
|
428
|
+
# Tool calls count on execution
|
|
399
429
|
if respond_to?(:accumulated_tool_calls) && accumulated_tool_calls.present?
|
|
400
|
-
update_data[:tool_calls] = accumulated_tool_calls
|
|
401
430
|
update_data[:tool_calls_count] = accumulated_tool_calls.size
|
|
402
431
|
end
|
|
403
432
|
|
|
404
|
-
#
|
|
433
|
+
# Error class on execution (error_message goes to detail)
|
|
405
434
|
if error
|
|
406
|
-
update_data.
|
|
407
|
-
error_message: error.message.to_s.truncate(65535),
|
|
408
|
-
error_class: error.class.name
|
|
409
|
-
)
|
|
435
|
+
update_data[:error_class] = error.class.name
|
|
410
436
|
end
|
|
411
437
|
|
|
412
438
|
execution.update!(update_data)
|
|
413
439
|
|
|
440
|
+
# Update or create detail record
|
|
441
|
+
detail_update = {
|
|
442
|
+
attempts: attempt_tracker.to_json_array
|
|
443
|
+
}
|
|
444
|
+
if @last_response && config.persist_responses
|
|
445
|
+
detail_update[:response] = stored_response(@last_response)
|
|
446
|
+
end
|
|
447
|
+
if respond_to?(:accumulated_tool_calls) && accumulated_tool_calls.present?
|
|
448
|
+
detail_update[:tool_calls] = accumulated_tool_calls
|
|
449
|
+
end
|
|
450
|
+
if error
|
|
451
|
+
detail_update[:error_message] = error.message.to_s.truncate(65535)
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
if execution.detail
|
|
455
|
+
execution.detail.update!(detail_update)
|
|
456
|
+
else
|
|
457
|
+
execution.create_detail!(detail_update)
|
|
458
|
+
end
|
|
459
|
+
|
|
414
460
|
# Calculate costs from all attempts
|
|
415
461
|
if attempt_tracker.attempts_count > 0
|
|
416
462
|
begin
|
|
@@ -452,35 +498,40 @@ module RubyLLM
|
|
|
452
498
|
|
|
453
499
|
execution_data = {
|
|
454
500
|
agent_type: self.class.name,
|
|
455
|
-
agent_version: self.class.version,
|
|
456
501
|
model_id: model,
|
|
457
502
|
temperature: temperature,
|
|
458
503
|
started_at: Time.current,
|
|
459
504
|
completed_at: completed_at,
|
|
460
505
|
duration_ms: 0,
|
|
461
506
|
status: status,
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
system_prompt: safe_system_prompt,
|
|
465
|
-
user_prompt: safe_user_prompt,
|
|
466
|
-
messages_count: resolved_messages.size,
|
|
467
|
-
messages_summary: config.persist_messages_summary ? messages_summary : {}
|
|
507
|
+
metadata: metadata,
|
|
508
|
+
messages_count: resolved_messages.size
|
|
468
509
|
}
|
|
469
510
|
|
|
470
511
|
# Add response data if available (using safe extraction)
|
|
471
512
|
response_data = safe_extract_response_data(response)
|
|
472
513
|
if response_data.any?
|
|
473
|
-
|
|
514
|
+
detail_fields = response_data.extract!(:response, :tool_calls, :cache_creation_tokens)
|
|
515
|
+
execution_data.merge!(response_data.except(:tool_calls_count))
|
|
516
|
+
execution_data[:tool_calls_count] = detail_fields[:tool_calls]&.size || 0
|
|
474
517
|
execution_data[:model_id] ||= model
|
|
475
518
|
end
|
|
476
519
|
|
|
477
520
|
if error
|
|
478
|
-
execution_data.
|
|
479
|
-
error_message: error.message,
|
|
480
|
-
error_class: error.class.name
|
|
481
|
-
)
|
|
521
|
+
execution_data[:error_class] = error.class.name
|
|
482
522
|
end
|
|
483
523
|
|
|
524
|
+
# Detail data stored separately
|
|
525
|
+
detail_data = {
|
|
526
|
+
parameters: sanitized_parameters,
|
|
527
|
+
system_prompt: safe_system_prompt,
|
|
528
|
+
user_prompt: safe_user_prompt,
|
|
529
|
+
messages_summary: config.persist_messages_summary ? messages_summary : {},
|
|
530
|
+
error_message: error&.message
|
|
531
|
+
}.merge(detail_fields || {})
|
|
532
|
+
|
|
533
|
+
execution_data[:_detail_data] = detail_data
|
|
534
|
+
|
|
484
535
|
if RubyLLM::Agents.configuration.async_logging
|
|
485
536
|
RubyLLM::Agents::ExecutionLoggerJob.perform_later(execution_data)
|
|
486
537
|
else
|
|
@@ -488,44 +539,25 @@ module RubyLLM
|
|
|
488
539
|
end
|
|
489
540
|
end
|
|
490
541
|
|
|
491
|
-
# Sanitizes parameters by removing
|
|
542
|
+
# Sanitizes parameters by removing internal options
|
|
492
543
|
#
|
|
493
|
-
# @
|
|
494
|
-
# @return [Hash] Sanitized parameters safe for logging
|
|
544
|
+
# @return [Hash] Parameters safe for logging
|
|
495
545
|
def sanitized_parameters
|
|
496
|
-
|
|
546
|
+
@options.except(:skip_cache, :dry_run)
|
|
497
547
|
end
|
|
498
548
|
|
|
499
|
-
# Returns
|
|
549
|
+
# Returns the system prompt for storage
|
|
500
550
|
#
|
|
501
|
-
#
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
#
|
|
505
|
-
# @return [Hash] Redacted parameters safe for logging
|
|
506
|
-
def redacted_parameters
|
|
507
|
-
params = @options.except(:skip_cache, :dry_run)
|
|
508
|
-
Redactor.redact(params)
|
|
551
|
+
# @return [String, nil] The system prompt
|
|
552
|
+
def stored_system_prompt
|
|
553
|
+
safe_system_prompt
|
|
509
554
|
end
|
|
510
555
|
|
|
511
|
-
# Returns the
|
|
556
|
+
# Returns the user prompt for storage
|
|
512
557
|
#
|
|
513
|
-
# @return [String, nil] The
|
|
514
|
-
def
|
|
515
|
-
|
|
516
|
-
return nil unless prompt
|
|
517
|
-
|
|
518
|
-
Redactor.redact_string(prompt)
|
|
519
|
-
end
|
|
520
|
-
|
|
521
|
-
# Returns the user prompt with redaction applied
|
|
522
|
-
#
|
|
523
|
-
# @return [String, nil] The redacted user prompt
|
|
524
|
-
def redacted_user_prompt
|
|
525
|
-
prompt = safe_user_prompt
|
|
526
|
-
return nil unless prompt
|
|
527
|
-
|
|
528
|
-
Redactor.redact_string(prompt)
|
|
558
|
+
# @return [String, nil] The user prompt
|
|
559
|
+
def stored_user_prompt
|
|
560
|
+
safe_user_prompt
|
|
529
561
|
end
|
|
530
562
|
|
|
531
563
|
# Returns a summary of messages (first and last, truncated)
|
|
@@ -545,7 +577,7 @@ module RubyLLM
|
|
|
545
577
|
if msgs.first
|
|
546
578
|
summary[:first] = {
|
|
547
579
|
role: msgs.first[:role].to_s,
|
|
548
|
-
content:
|
|
580
|
+
content: msgs.first[:content].to_s.truncate(max_len)
|
|
549
581
|
}
|
|
550
582
|
end
|
|
551
583
|
|
|
@@ -553,20 +585,19 @@ module RubyLLM
|
|
|
553
585
|
if msgs.size > 1 && msgs.last
|
|
554
586
|
summary[:last] = {
|
|
555
587
|
role: msgs.last[:role].to_s,
|
|
556
|
-
content:
|
|
588
|
+
content: msgs.last[:content].to_s.truncate(max_len)
|
|
557
589
|
}
|
|
558
590
|
end
|
|
559
591
|
|
|
560
592
|
summary
|
|
561
593
|
end
|
|
562
594
|
|
|
563
|
-
# Returns the response
|
|
595
|
+
# Returns the response for storage
|
|
564
596
|
#
|
|
565
597
|
# @param response [RubyLLM::Message] The LLM response
|
|
566
|
-
# @return [Hash]
|
|
567
|
-
def
|
|
568
|
-
|
|
569
|
-
Redactor.redact(data)
|
|
598
|
+
# @return [Hash] Response data
|
|
599
|
+
def stored_response(response)
|
|
600
|
+
safe_serialize_response(response)
|
|
570
601
|
end
|
|
571
602
|
|
|
572
603
|
# Hook for subclasses to add custom metadata to executions
|
|
@@ -576,10 +607,10 @@ module RubyLLM
|
|
|
576
607
|
#
|
|
577
608
|
# @return [Hash] Custom metadata to store with the execution
|
|
578
609
|
# @example
|
|
579
|
-
# def
|
|
610
|
+
# def metadata
|
|
580
611
|
# { user_id: Current.user&.id, experiment: "v2" }
|
|
581
612
|
# end
|
|
582
|
-
def
|
|
613
|
+
def metadata
|
|
583
614
|
{}
|
|
584
615
|
end
|
|
585
616
|
|
|
@@ -816,40 +847,37 @@ module RubyLLM
|
|
|
816
847
|
completed_at = Time.current
|
|
817
848
|
duration_ms = ((completed_at - started_at) * 1000).round
|
|
818
849
|
|
|
850
|
+
exec_metadata = metadata.dup
|
|
851
|
+
exec_metadata["response_cache_key"] = cache_key
|
|
852
|
+
exec_metadata["span_id"] = exec_metadata.delete(:span_id) if exec_metadata[:span_id]
|
|
853
|
+
|
|
819
854
|
execution_data = {
|
|
820
855
|
agent_type: self.class.name,
|
|
821
|
-
agent_version: self.class.version,
|
|
822
856
|
model_id: model,
|
|
823
857
|
temperature: temperature,
|
|
824
858
|
status: "success",
|
|
825
859
|
cache_hit: true,
|
|
826
|
-
response_cache_key: cache_key,
|
|
827
|
-
cached_at: completed_at,
|
|
828
860
|
started_at: started_at,
|
|
829
861
|
completed_at: completed_at,
|
|
830
862
|
duration_ms: duration_ms,
|
|
831
863
|
input_tokens: 0,
|
|
832
864
|
output_tokens: 0,
|
|
833
865
|
cached_tokens: 0,
|
|
834
|
-
cache_creation_tokens: 0,
|
|
835
866
|
total_tokens: 0,
|
|
836
867
|
input_cost: 0,
|
|
837
868
|
output_cost: 0,
|
|
838
869
|
total_cost: 0,
|
|
839
|
-
|
|
840
|
-
metadata: execution_metadata,
|
|
870
|
+
metadata: exec_metadata,
|
|
841
871
|
streaming: self.class.streaming,
|
|
842
|
-
messages_count: resolved_messages.size
|
|
843
|
-
messages_summary: config.persist_messages_summary ? messages_summary : {}
|
|
872
|
+
messages_count: resolved_messages.size
|
|
844
873
|
}
|
|
845
874
|
|
|
846
875
|
# Add tracing fields from metadata if present
|
|
847
|
-
|
|
848
|
-
execution_data[:request_id] =
|
|
849
|
-
execution_data[:trace_id] =
|
|
850
|
-
execution_data[:
|
|
851
|
-
execution_data[:
|
|
852
|
-
execution_data[:root_execution_id] = metadata[:root_execution_id] if metadata[:root_execution_id]
|
|
876
|
+
agent_metadata = metadata
|
|
877
|
+
execution_data[:request_id] = agent_metadata[:request_id] if agent_metadata[:request_id]
|
|
878
|
+
execution_data[:trace_id] = agent_metadata[:trace_id] if agent_metadata[:trace_id]
|
|
879
|
+
execution_data[:parent_execution_id] = agent_metadata[:parent_execution_id] if agent_metadata[:parent_execution_id]
|
|
880
|
+
execution_data[:root_execution_id] = agent_metadata[:root_execution_id] if agent_metadata[:root_execution_id]
|
|
853
881
|
|
|
854
882
|
# Add tenant_id if multi-tenancy is enabled
|
|
855
883
|
if config.multi_tenancy_enabled?
|
|
@@ -859,7 +887,15 @@ module RubyLLM
|
|
|
859
887
|
if config.async_logging
|
|
860
888
|
RubyLLM::Agents::ExecutionLoggerJob.perform_later(execution_data)
|
|
861
889
|
else
|
|
862
|
-
RubyLLM::Agents::Execution.create!(execution_data)
|
|
890
|
+
execution = RubyLLM::Agents::Execution.create!(execution_data)
|
|
891
|
+
# Create detail with cache-related fields
|
|
892
|
+
detail_data = {
|
|
893
|
+
parameters: sanitized_parameters,
|
|
894
|
+
messages_summary: config.persist_messages_summary ? messages_summary : {},
|
|
895
|
+
cached_at: completed_at,
|
|
896
|
+
cache_creation_tokens: 0
|
|
897
|
+
}
|
|
898
|
+
execution.create_detail!(detail_data) if detail_data.values.any?(&:present?)
|
|
863
899
|
end
|
|
864
900
|
rescue StandardError => e
|
|
865
901
|
Rails.logger.error("[RubyLLM::Agents] Failed to record cache hit execution: #{e.message}")
|
|
@@ -920,11 +956,22 @@ module RubyLLM
|
|
|
920
956
|
update_data = {
|
|
921
957
|
status: "error",
|
|
922
958
|
completed_at: Time.current,
|
|
923
|
-
error_class: error.class.name
|
|
924
|
-
error_message: error_message.to_s.truncate(65535)
|
|
959
|
+
error_class: error.class.name
|
|
925
960
|
}
|
|
926
961
|
|
|
927
962
|
execution.class.where(id: execution.id, status: "running").update_all(update_data)
|
|
963
|
+
|
|
964
|
+
# Store error_message in detail table (best-effort)
|
|
965
|
+
begin
|
|
966
|
+
detail_attrs = { error_message: error_message.to_s.truncate(65535) }
|
|
967
|
+
if execution.detail
|
|
968
|
+
execution.detail.update_columns(detail_attrs)
|
|
969
|
+
else
|
|
970
|
+
RubyLLM::Agents::ExecutionDetail.create!(detail_attrs.merge(execution_id: execution.id))
|
|
971
|
+
end
|
|
972
|
+
rescue StandardError
|
|
973
|
+
# Non-critical — error_class on execution is sufficient for filtering
|
|
974
|
+
end
|
|
928
975
|
rescue StandardError => e
|
|
929
976
|
Rails.logger.error("[RubyLLM::Agents] CRITICAL: Failed emergency status update for execution #{execution&.id}: #{e.message}")
|
|
930
977
|
end
|
|
@@ -68,13 +68,7 @@ module RubyLLM
|
|
|
68
68
|
extend ActiveSupport::Concern
|
|
69
69
|
|
|
70
70
|
included do
|
|
71
|
-
#
|
|
72
|
-
has_many :llm_executions,
|
|
73
|
-
class_name: "RubyLLM::Agents::Execution",
|
|
74
|
-
as: :tenant_record,
|
|
75
|
-
dependent: :nullify
|
|
76
|
-
|
|
77
|
-
# Link to gem's Tenant model (new name)
|
|
71
|
+
# Link to gem's Tenant model via polymorphic association
|
|
78
72
|
has_one :llm_tenant_record,
|
|
79
73
|
class_name: "RubyLLM::Agents::Tenant",
|
|
80
74
|
as: :tenant_record,
|
|
@@ -110,6 +104,13 @@ module RubyLLM
|
|
|
110
104
|
api_keys: api_keys
|
|
111
105
|
}
|
|
112
106
|
|
|
107
|
+
# Executions tracked for this tenant via tenant_id string column
|
|
108
|
+
has_many :llm_executions,
|
|
109
|
+
class_name: "RubyLLM::Agents::Execution",
|
|
110
|
+
foreign_key: :tenant_id,
|
|
111
|
+
primary_key: id,
|
|
112
|
+
dependent: :nullify
|
|
113
|
+
|
|
113
114
|
# Auto-create tenant record callback
|
|
114
115
|
after_create :create_default_llm_tenant if llm_tenant_options[:budget]
|
|
115
116
|
end
|