Skip to content

Callbacks

ActiveAgent provides callbacks for four different lifecycles:

  • Generation callbacks (before_generation, after_generation, around_generation) - Wrap both prompting and embedding operations for rate limiting, authentication, and logging
  • Prompting callbacks (before_prompt, after_prompt, around_prompt) - Specific to prompt execution
  • Embedding callbacks (before_embed, after_embed, around_embed) - Specific to embedding operations
  • Streaming callbacks (on_stream_open, on_stream, on_stream_close) - Handle real-time streaming responses as they arrive

Use generation callbacks for cross-cutting concerns, prompting/embedding callbacks for operation-specific behavior, and streaming callbacks for processing responses in real-time.

Generation Callbacks

Generation callbacks wrap both prompting and embedding operations for rate limiting, authentication, and resource management.

Before Generation

Runs before any generation executes. Use for setup, rate limiting, or validation:

ruby
class MyAgent < ApplicationAgent
  before_generation :load_context

  def chat
    prompt(message: params[:message])
  end

  private

  def load_context
    @user_data = User.find(params[:user_id])
  end
end

After Generation

Runs after any generation completes. Use for logging, usage tracking, or cleanup:

ruby
class LoggingAgent < ApplicationAgent
  after_generation :log_completion

  def chat
    prompt(message: params[:message])
  end

  private

  def log_completion
    Rails.logger.info "Completed generation"
  end
end

After callbacks are skipped if the callback chain is terminated with throw :abort.

Around Generation

Wraps the entire generation process. Use for timing, transactions, or resource management:

ruby
class TimingAgent < ApplicationAgent
  around_generation :measure_time

  def chat
    prompt(message: params[:message])
  end

  private

  def measure_time
    start = Time.current
    yield
    duration = Time.current - start
    Rails.logger.info "Generation took #{duration}s"
  end
end

Rate Limiting Example

ruby
class RateLimitedAgent < ApplicationAgent
  before_generation :check_rate_limit
  after_generation :record_usage

  def chat
    prompt(message: params[:message])
  end

  private

  def check_rate_limit
    if RateLimiter.exceeded?(params[:user_id])
      throw :abort
    end
  end

  def record_usage
    RateLimiter.increment(params[:user_id])
  end
end

Prompting Callbacks

Prompting callbacks are specific to prompt execution:

ruby
class MyAgent < ApplicationAgent
  before_prompt :load_context
  after_prompt :cache_response
  around_prompt :measure_time

  def chat
    prompt(message: params[:message])
  end

  private

  def load_context
    @user_data = User.find(params[:user_id])
  end

  def cache_response
    Rails.cache.write("response_#{params[:id]}", "cached")
  end

  def measure_time
    start = Time.current
    yield
    Rails.logger.info "Prompt took #{Time.current - start}s"
  end
end

Embedding Callbacks

Embedding callbacks are specific to embedding operations:

ruby
class MyAgent < ApplicationAgent
  before_embed :validate_input
  after_embed :store_embedding
  around_embed :measure_time

  def process_text
    embed(input: params[:text])
  end

  private

  def validate_input
    raise "Input too long" if params[:text].length > 8000
  end

  def store_embedding
    VectorDatabase.store(params[:text])
  end

  def measure_time
    start = Time.current
    yield
    Rails.logger.info "Embedding took #{Time.current - start}s"
  end
end

Streaming Callbacks

Streaming callbacks handle real-time streaming responses as they arrive:

ruby
class StreamingAgent < ApplicationAgent
  on_stream_open :initialize_stream
  on_stream :process_chunk
  on_stream_close :finalize_stream

  def chat
    prompt(message: params[:message], stream: true)
  end

  private

  def initialize_stream
    @start_time = Time.current
    @chunk_count = 0
  end

  def process_chunk(chunk)
    @chunk_count += 1
    # Process each chunk as it arrives
    broadcast_chunk(chunk)
  end

  def finalize_stream
    duration = Time.current - @start_time
    Rails.logger.info "Streamed #{@chunk_count} chunks in #{duration}s"
  end

  def broadcast_chunk(chunk)
    # Broadcast implementation
  end
end

Use on_stream_open for initialization, on_stream to process each chunk, and on_stream_close for cleanup. See Streaming for complete documentation.

Multiple and Conditional Callbacks

Register multiple callbacks and apply them conditionally with :if and :unless:

ruby
class AdvancedAgent < ApplicationAgent
  before_generation :load_context
  before_generation :check_rate_limit, if: :rate_limiting_enabled?
  after_generation :log_response

  def chat
    prompt(message: params[:message])
  end

  private

  def load_context
    @user_data = User.find(params[:user_id])
  end

  def check_rate_limit
    raise "Rate limit exceeded" if rate_limited?
  end

  def log_response
    Rails.logger.info "Completed generation"
  end

  def rate_limiting_enabled?
    Rails.env.production?
  end

  def test_environment?
    Rails.env.test?
  end

  def rate_limited?
    false
  end
end

Callbacks execute in registration order (before/around) or reverse order (after).

Callback Control

Use prepend_*, skip_*, and append_* variants for all callback types:

ruby
class BaseAgent < ApplicationAgent
  before_generation :base_setup
  after_prompt :log_response

  def chat
    prompt(message: params[:message])
  end

  private

  def base_setup
    # Base setup logic
  end

  def log_response
    # Log response
  end
end

class ChildAgent < BaseAgent
  prepend_before_generation :critical_auth  # Runs before inherited callbacks
  skip_after_prompt :log_response           # Remove inherited callback
  append_before_prompt :final_setup         # Same as before_prompt

  private

  def critical_auth
    # Critical authentication logic
  end

  def final_setup
    # Final setup logic
  end
end