ruby_llm-agents 1.3.4 → 2.1.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 +112 -336
- 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 +52 -12
- 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 +89 -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 +526 -1037
- 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 +13 -17
- 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 +33 -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 +77 -259
- 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 +54 -23
- 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 +97 -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/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/execution_logger_job.rb +8 -0
- 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 +14 -83
- 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
|
@@ -15,7 +15,6 @@ module RubyLLM
|
|
|
15
15
|
# @example Creating an agent
|
|
16
16
|
# class SearchAgent < RubyLLM::Agents::BaseAgent
|
|
17
17
|
# model "gpt-4o"
|
|
18
|
-
# version "1.0"
|
|
19
18
|
# description "Searches for relevant documents"
|
|
20
19
|
# timeout 30
|
|
21
20
|
#
|
|
@@ -246,24 +245,38 @@ module RubyLLM
|
|
|
246
245
|
|
|
247
246
|
# User prompt to send to the LLM
|
|
248
247
|
#
|
|
249
|
-
#
|
|
248
|
+
# If a class-level `prompt` DSL is defined (string template or block),
|
|
249
|
+
# it will be used. Otherwise, subclasses must implement this method.
|
|
250
|
+
#
|
|
250
251
|
# @return [String] The user prompt
|
|
251
252
|
def user_prompt
|
|
252
|
-
|
|
253
|
+
prompt_config = self.class.prompt_config
|
|
254
|
+
return resolve_prompt_from_config(prompt_config) if prompt_config
|
|
255
|
+
|
|
256
|
+
raise NotImplementedError, "#{self.class} must implement #user_prompt or use the prompt DSL"
|
|
253
257
|
end
|
|
254
258
|
|
|
255
259
|
# System prompt for LLM instructions
|
|
256
260
|
#
|
|
261
|
+
# If a class-level `system` DSL is defined, it will be used.
|
|
262
|
+
# Otherwise returns nil.
|
|
263
|
+
#
|
|
257
264
|
# @return [String, nil] System instructions, or nil for none
|
|
258
265
|
def system_prompt
|
|
266
|
+
system_config = self.class.system_config
|
|
267
|
+
return resolve_prompt_from_config(system_config) if system_config
|
|
268
|
+
|
|
259
269
|
nil
|
|
260
270
|
end
|
|
261
271
|
|
|
262
272
|
# Response schema for structured output
|
|
263
273
|
#
|
|
274
|
+
# Delegates to the class-level schema DSL by default.
|
|
275
|
+
# Override in subclass instances to customize per-instance.
|
|
276
|
+
#
|
|
264
277
|
# @return [RubyLLM::Schema, nil] Schema definition, or nil for free-form
|
|
265
278
|
def schema
|
|
266
|
-
|
|
279
|
+
self.class.schema
|
|
267
280
|
end
|
|
268
281
|
|
|
269
282
|
# Conversation history for multi-turn conversations
|
|
@@ -288,9 +301,12 @@ module RubyLLM
|
|
|
288
301
|
|
|
289
302
|
# Generates the cache key for this agent invocation
|
|
290
303
|
#
|
|
291
|
-
#
|
|
304
|
+
# Cache keys are content-based, using a hash of the prompts and parameters.
|
|
305
|
+
# This automatically invalidates caches when prompts change.
|
|
306
|
+
#
|
|
307
|
+
# @return [String] Cache key in format "ruby_llm_agent/ClassName/hash"
|
|
292
308
|
def agent_cache_key
|
|
293
|
-
["ruby_llm_agent", self.class.name,
|
|
309
|
+
["ruby_llm_agent", self.class.name, cache_key_hash].join("/")
|
|
294
310
|
end
|
|
295
311
|
|
|
296
312
|
# Generates a hash of the cache key data
|
|
@@ -462,6 +478,36 @@ module RubyLLM
|
|
|
462
478
|
end
|
|
463
479
|
end
|
|
464
480
|
|
|
481
|
+
# Resolves a prompt from DSL configuration (template string or block)
|
|
482
|
+
#
|
|
483
|
+
# For string templates, interpolates {placeholder} with parameter values.
|
|
484
|
+
# For blocks, evaluates in the instance context.
|
|
485
|
+
#
|
|
486
|
+
# @param config [String, Proc] The prompt configuration
|
|
487
|
+
# @return [String] The resolved prompt
|
|
488
|
+
def resolve_prompt_from_config(config)
|
|
489
|
+
case config
|
|
490
|
+
when String
|
|
491
|
+
interpolate_template(config)
|
|
492
|
+
when Proc
|
|
493
|
+
instance_eval(&config)
|
|
494
|
+
else
|
|
495
|
+
config.to_s
|
|
496
|
+
end
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
# Interpolates {placeholder} patterns in a template string
|
|
500
|
+
#
|
|
501
|
+
# @param template [String] Template with {placeholder} syntax
|
|
502
|
+
# @return [String] Interpolated string
|
|
503
|
+
def interpolate_template(template)
|
|
504
|
+
template.gsub(/\{(\w+)\}/) do
|
|
505
|
+
param_name = ::Regexp.last_match(1).to_sym
|
|
506
|
+
value = send(param_name) if respond_to?(param_name)
|
|
507
|
+
value.to_s
|
|
508
|
+
end
|
|
509
|
+
end
|
|
510
|
+
|
|
465
511
|
# Execute the core LLM call
|
|
466
512
|
#
|
|
467
513
|
# This is called by the Pipeline::Executor after all middleware
|
|
@@ -567,29 +613,14 @@ module RubyLLM
|
|
|
567
613
|
input_tokens = context.input_tokens || 0
|
|
568
614
|
output_tokens = context.output_tokens || 0
|
|
569
615
|
|
|
570
|
-
input_price =
|
|
571
|
-
output_price =
|
|
616
|
+
input_price = model_info.pricing&.text_tokens&.input || 0
|
|
617
|
+
output_price = model_info.pricing&.text_tokens&.output || 0
|
|
572
618
|
|
|
573
619
|
context.input_cost = (input_tokens / 1_000_000.0) * input_price
|
|
574
620
|
context.output_cost = (output_tokens / 1_000_000.0) * output_price
|
|
575
621
|
context.total_cost = (context.input_cost + context.output_cost).round(6)
|
|
576
622
|
end
|
|
577
623
|
|
|
578
|
-
# Extracts price from model info (supports both hash and object access)
|
|
579
|
-
#
|
|
580
|
-
# @param model_info [Hash, Object] Model info
|
|
581
|
-
# @param key [Symbol] The price key
|
|
582
|
-
# @return [Float] The price, or 0 if not found
|
|
583
|
-
def extract_model_price(model_info, key)
|
|
584
|
-
if model_info.respond_to?(key)
|
|
585
|
-
model_info.send(key) || 0
|
|
586
|
-
elsif model_info.respond_to?(:[])
|
|
587
|
-
model_info[key] || 0
|
|
588
|
-
else
|
|
589
|
-
0
|
|
590
|
-
end
|
|
591
|
-
end
|
|
592
|
-
|
|
593
624
|
# Finds model pricing info
|
|
594
625
|
#
|
|
595
626
|
# @param model_id [String] The model ID
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Agents
|
|
5
|
+
# DSL and execution support for before_call/after_call hooks
|
|
6
|
+
#
|
|
7
|
+
# Provides callbacks that run before and after the LLM call,
|
|
8
|
+
# allowing custom preprocessing (redaction, moderation, validation)
|
|
9
|
+
# and postprocessing (logging, transformation) logic.
|
|
10
|
+
#
|
|
11
|
+
# @example Using callbacks
|
|
12
|
+
# class MyAgent < ApplicationAgent
|
|
13
|
+
# before_call :sanitize_input
|
|
14
|
+
# after_call :log_response
|
|
15
|
+
#
|
|
16
|
+
# # Or with blocks
|
|
17
|
+
# before_call { |context| context.params[:timestamp] = Time.current }
|
|
18
|
+
# after_call { |context, response| notify(response) }
|
|
19
|
+
#
|
|
20
|
+
# private
|
|
21
|
+
#
|
|
22
|
+
# def sanitize_input(context)
|
|
23
|
+
# # Mutate context as needed
|
|
24
|
+
# # Raise to block execution
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# def log_response(context, response)
|
|
28
|
+
# Rails.logger.info("Response: #{response}")
|
|
29
|
+
# end
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
module CallbacksDSL
|
|
33
|
+
# Add a callback to run before the LLM call
|
|
34
|
+
#
|
|
35
|
+
# Callbacks receive the pipeline context and can:
|
|
36
|
+
# - Mutate the context (params, prompts, etc.)
|
|
37
|
+
# - Raise an exception to block execution
|
|
38
|
+
# - Return value is ignored
|
|
39
|
+
#
|
|
40
|
+
# @param method_name [Symbol, nil] Instance method to call
|
|
41
|
+
# @yield [context] Block to execute
|
|
42
|
+
# @yieldparam context [Pipeline::Context] The execution context
|
|
43
|
+
# @return [void]
|
|
44
|
+
#
|
|
45
|
+
# @example With method name
|
|
46
|
+
# before_call :validate_input
|
|
47
|
+
#
|
|
48
|
+
# @example With block
|
|
49
|
+
# before_call { |context| context.params[:sanitized] = true }
|
|
50
|
+
#
|
|
51
|
+
def before_call(method_name = nil, &block)
|
|
52
|
+
@callbacks ||= { before: [], after: [] }
|
|
53
|
+
@callbacks[:before] << (block || method_name)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Add a callback to run after the LLM call
|
|
57
|
+
#
|
|
58
|
+
# Callbacks receive the pipeline context and the response.
|
|
59
|
+
# Return value is ignored.
|
|
60
|
+
#
|
|
61
|
+
# @param method_name [Symbol, nil] Instance method to call
|
|
62
|
+
# @yield [context, response] Block to execute
|
|
63
|
+
# @yieldparam context [Pipeline::Context] The execution context
|
|
64
|
+
# @yieldparam response [Object] The LLM response
|
|
65
|
+
# @return [void]
|
|
66
|
+
#
|
|
67
|
+
# @example With method name
|
|
68
|
+
# after_call :log_response
|
|
69
|
+
#
|
|
70
|
+
# @example With block
|
|
71
|
+
# after_call { |context, response| notify_completion(response) }
|
|
72
|
+
#
|
|
73
|
+
def after_call(method_name = nil, &block)
|
|
74
|
+
@callbacks ||= { before: [], after: [] }
|
|
75
|
+
@callbacks[:after] << (block || method_name)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Simplified alias for before_call (block-only)
|
|
79
|
+
#
|
|
80
|
+
# This is the preferred method in the simplified DSL.
|
|
81
|
+
#
|
|
82
|
+
# @yield [context] Block to execute before the LLM call
|
|
83
|
+
# @yieldparam context [Pipeline::Context] The execution context
|
|
84
|
+
# @return [void]
|
|
85
|
+
#
|
|
86
|
+
# @example
|
|
87
|
+
# before { |ctx| ctx.params[:timestamp] = Time.current }
|
|
88
|
+
# before { |ctx| validate_input!(ctx.params[:query]) }
|
|
89
|
+
#
|
|
90
|
+
def before(&block)
|
|
91
|
+
before_call(&block)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Simplified alias for after_call (block-only)
|
|
95
|
+
#
|
|
96
|
+
# This is the preferred method in the simplified DSL.
|
|
97
|
+
#
|
|
98
|
+
# @yield [context, response] Block to execute after the LLM call
|
|
99
|
+
# @yieldparam context [Pipeline::Context] The execution context
|
|
100
|
+
# @yieldparam response [Object] The LLM response
|
|
101
|
+
# @return [void]
|
|
102
|
+
#
|
|
103
|
+
# @example
|
|
104
|
+
# after { |ctx, result| Rails.logger.info("Completed: #{result}") }
|
|
105
|
+
# after { |ctx, result| notify_slack(result) if result.confidence < 0.5 }
|
|
106
|
+
#
|
|
107
|
+
def after(&block)
|
|
108
|
+
after_call(&block)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Get all registered callbacks
|
|
112
|
+
#
|
|
113
|
+
# @return [Hash] Hash with :before and :after arrays
|
|
114
|
+
def callbacks
|
|
115
|
+
@callbacks ||= { before: [], after: [] }
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Instance methods for running callbacks
|
|
120
|
+
module CallbacksExecution
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
# Run callbacks of the specified type
|
|
124
|
+
#
|
|
125
|
+
# @param type [Symbol] :before or :after
|
|
126
|
+
# @param args [Array] Arguments to pass to callbacks
|
|
127
|
+
# @return [void]
|
|
128
|
+
def run_callbacks(type, *args)
|
|
129
|
+
callbacks = self.class.callbacks[type] || []
|
|
130
|
+
|
|
131
|
+
callbacks.each do |callback|
|
|
132
|
+
case callback
|
|
133
|
+
when Symbol
|
|
134
|
+
send(callback, *args)
|
|
135
|
+
when Proc
|
|
136
|
+
instance_exec(*args, &callback)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "base/
|
|
4
|
-
require_relative "base/moderation_execution"
|
|
3
|
+
require_relative "base/callbacks"
|
|
5
4
|
|
|
6
5
|
module RubyLLM
|
|
7
6
|
module Agents
|
|
8
7
|
# Base class for LLM-powered conversational agents
|
|
9
8
|
#
|
|
10
9
|
# Inherits from BaseAgent to use the middleware pipeline architecture
|
|
11
|
-
# while adding
|
|
10
|
+
# while adding callback hooks for custom preprocessing and postprocessing.
|
|
12
11
|
#
|
|
13
12
|
# @example Creating an agent
|
|
14
13
|
# class SearchAgent < ApplicationAgent
|
|
15
14
|
# model "gpt-4o"
|
|
16
15
|
# temperature 0.0
|
|
17
|
-
# version "1.0"
|
|
18
16
|
# timeout 30
|
|
19
17
|
# cache_for 1.hour
|
|
20
18
|
#
|
|
@@ -30,14 +28,19 @@ module RubyLLM
|
|
|
30
28
|
# end
|
|
31
29
|
# end
|
|
32
30
|
#
|
|
33
|
-
# @example With
|
|
31
|
+
# @example With callbacks
|
|
34
32
|
# class SafeAgent < ApplicationAgent
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
# moderation :both
|
|
33
|
+
# before_call :redact_pii
|
|
34
|
+
# after_call :log_response
|
|
38
35
|
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
36
|
+
# private
|
|
37
|
+
#
|
|
38
|
+
# def redact_pii(context)
|
|
39
|
+
# # Custom redaction logic
|
|
40
|
+
# end
|
|
41
|
+
#
|
|
42
|
+
# def log_response(context, response)
|
|
43
|
+
# Rails.logger.info("Response received")
|
|
41
44
|
# end
|
|
42
45
|
# end
|
|
43
46
|
#
|
|
@@ -49,8 +52,8 @@ module RubyLLM
|
|
|
49
52
|
# @see RubyLLM::Agents::BaseAgent
|
|
50
53
|
# @api public
|
|
51
54
|
class Base < BaseAgent
|
|
52
|
-
extend
|
|
53
|
-
include
|
|
55
|
+
extend CallbacksDSL
|
|
56
|
+
include CallbacksExecution
|
|
54
57
|
|
|
55
58
|
class << self
|
|
56
59
|
# Returns the agent type for conversation agents
|
|
@@ -61,43 +64,27 @@ module RubyLLM
|
|
|
61
64
|
end
|
|
62
65
|
end
|
|
63
66
|
|
|
64
|
-
# Execute the core LLM call with
|
|
67
|
+
# Execute the core LLM call with callback support
|
|
65
68
|
#
|
|
66
|
-
# This extends BaseAgent's execute method to add
|
|
67
|
-
#
|
|
69
|
+
# This extends BaseAgent's execute method to add before/after
|
|
70
|
+
# callback hooks for custom preprocessing and postprocessing.
|
|
68
71
|
#
|
|
69
72
|
# @param context [Pipeline::Context] The execution context
|
|
70
73
|
# @return [void] Sets context.output with the result
|
|
71
74
|
def execute(context)
|
|
72
75
|
@execution_started_at = context.started_at || Time.current
|
|
73
76
|
|
|
74
|
-
#
|
|
75
|
-
|
|
76
|
-
input_text = build_moderation_input
|
|
77
|
-
moderate_input(input_text)
|
|
77
|
+
# Run before_call callbacks
|
|
78
|
+
run_callbacks(:before, context)
|
|
78
79
|
|
|
79
|
-
|
|
80
|
-
context.output = build_moderation_blocked_result(:input)
|
|
81
|
-
return
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
# Execute the LLM call via parent
|
|
80
|
+
# Execute the LLM call
|
|
86
81
|
client = build_client(context)
|
|
87
82
|
response = execute_llm_call(client, context)
|
|
88
83
|
capture_response(response, context)
|
|
89
84
|
processed_content = process_response(response)
|
|
90
85
|
|
|
91
|
-
#
|
|
92
|
-
|
|
93
|
-
output_text = processed_content.is_a?(String) ? processed_content : processed_content.to_s
|
|
94
|
-
moderate_output(output_text)
|
|
95
|
-
|
|
96
|
-
if moderation_blocked?
|
|
97
|
-
context.output = build_moderation_blocked_result(:output)
|
|
98
|
-
return
|
|
99
|
-
end
|
|
100
|
-
end
|
|
86
|
+
# Run after_call callbacks
|
|
87
|
+
run_callbacks(:after, context, response)
|
|
101
88
|
|
|
102
89
|
context.output = build_result(processed_content, response, context)
|
|
103
90
|
end
|
|
@@ -111,25 +98,6 @@ module RubyLLM
|
|
|
111
98
|
|
|
112
99
|
tenant.is_a?(Hash) ? tenant[:id]&.to_s : nil
|
|
113
100
|
end
|
|
114
|
-
|
|
115
|
-
private
|
|
116
|
-
|
|
117
|
-
# Check if execution model is available for moderation tracking
|
|
118
|
-
#
|
|
119
|
-
# @return [Boolean] true if Execution model can be used
|
|
120
|
-
def execution_model_available?
|
|
121
|
-
return @execution_model_available if defined?(@execution_model_available)
|
|
122
|
-
|
|
123
|
-
@execution_model_available = begin
|
|
124
|
-
RubyLLM::Agents::Execution.table_exists?
|
|
125
|
-
rescue StandardError
|
|
126
|
-
false
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
101
|
end
|
|
130
102
|
end
|
|
131
103
|
end
|
|
132
|
-
|
|
133
|
-
# Load moderation modules after class is defined (they reopen the class)
|
|
134
|
-
require_relative "base/moderation_dsl"
|
|
135
|
-
require_relative "base/moderation_execution"
|