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