0.0
No release in over 3 years
Automatically report AI agent health, response times, and model info to your ups.dev status page.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

>= 1.0, < 3.0
>= 1.13.0, < 3.0
 Project Readme

RubyLLM::Ups

Connect your RubyLLM-powered agents to ups.dev status pages.

After each LLM response, the gem sends a lightweight heartbeat to ups.dev — model, provider, response time, tool count. No message content is ever sent. Status degradation and incidents are reported by you, when you decide they matter.

Installation

gem "ruby_llm-ups"

Quick Start

1. Configure

RubyLLM::Ups.configure do |c|
  c.api_key = "ups_live_abc123_xyz789"
  c.status_page_id = "my-page-slug"  # your status page slug or numeric ID
end

In Rails, credentials are auto-loaded — no initializer needed if you set:

# config/credentials.yml.enc
ups:
  api_key: ups_live_abc123_xyz789
  status_page_id: my-page-slug

2. Include Monitored in your base agent

class ApplicationAgent < RubyLLM::Agent
  include RubyLLM::Ups::Monitored
end

That's it. Every agent that inherits from ApplicationAgent gets its own component on ups.dev, auto-created by name. ResearchAgent becomes "Research Agent", DataProcessor becomes "Data Processor Agent".

class ResearchAgent < ApplicationAgent
  model "claude-sonnet-4-20250514"
  tools SearchTool, SummarizeTool
end

agent = ResearchAgent.new    # → "Research Agent" component created on ups.dev
agent.ask("Hello")           # → heartbeat: operational + metadata

Override the default name with ups_component:

class MyAgent < ApplicationAgent
  ups_component "Customer Support Bot"
end

What gets reported

Every successful LLM response sends:

{
  model: "claude-sonnet-4-20250514",   # which model handled the request
  provider: "anthropic",                # the LLM provider
  last_response_time_ms: 1230,          # end-to-end response time
  tool_count: 3                         # number of tools available
}

Failed LLM calls report nothing — the monitor only fires on successful responses. You decide when a failure is worth reporting (see below).

Reporting failures

Status degradation

Use report_status when you detect a problem — partial failures, elevated error rates, slow responses:

RubyLLM::Ups.report_status(:degraded_performance,
  component_id: component_id,
  agent_metadata: {
    failed_step: "summarization",
    error: "Provider timeout"
  }
)

Incidents

Create incidents for failures your users should know about:

RubyLLM::Ups.create_incident(
  title: "Processing pipeline down",
  impact: :major,
  status: :investigating,
  description: "Failed at enrichment stage: API rate limit exceeded"
)

Pipeline pattern

A common pattern for multi-stage agent pipelines:

class Pipeline
  def call
    stages.each { |stage| run_stage(stage) }
    ups_report_operational
  rescue => e
    ups_create_incident(e)
    raise
  end

  def run_stage(stage)
    stage.call
  rescue => e
    if optional_stage?(stage)
      ups_report_degraded(stage, e)  # partial failure — degraded, not down
    else
      raise                          # critical failure — will create incident
    end
  end

  private

  def pipeline_component_id
    @pipeline_component_id ||= begin
      component = RubyLLM::Ups.component_registry.find_or_create("My Pipeline")
      component["id"]
    end
  end

  def ups_report_operational
    RubyLLM::Ups.report_status(:operational, component_id: pipeline_component_id)
  rescue => e
    Rails.logger.warn("[ups.dev] #{e.message}")
  end

  def ups_report_degraded(stage, error)
    RubyLLM::Ups.report_status(:degraded_performance,
      component_id: pipeline_component_id,
      agent_metadata: { failed_stage: stage.name, error: error.message }
    )
  rescue => e
    Rails.logger.warn("[ups.dev] #{e.message}")
  end

  def ups_create_incident(error)
    RubyLLM::Ups.create_incident(
      title: "Pipeline failed",
      impact: :major,
      status: :investigating,
      description: error.message
    )
  rescue => e
    Rails.logger.warn("[ups.dev] #{e.message}")
  end
end

Key points:

  • Use component_registry.find_or_create to get a named component for non-agent things
  • Always rescue ups.dev calls — monitoring failures should never break your app
  • report_status(:operational) after success resets a previously degraded component

Incident lifecycle

# Open
incident = RubyLLM::Ups.create_incident(
  title: "Agent responding slowly",
  impact: :minor,               # :none, :minor, :major, :critical
  status: :investigating        # :investigating, :identified, :monitoring, :resolved
)

# Update
RubyLLM::Ups.update_incident(incident["incident"]["id"],
  status: :identified,
  update_message: "Root cause: Anthropic API degradation"
)

# Resolve
RubyLLM::Ups.resolve_incident(incident["incident"]["id"])

Configuration

Option Default Description
api_key required Your ups.dev API key
status_page_id required Status page slug or numeric ID
component_id Default component for monitor and report_status (optional with Monitored)
base_url https://ups.dev API base URL
request_timeout 30 HTTP timeout in seconds
on_error stderr Error handler proc
async true Send heartbeats in a background thread
flush_interval 5 Seconds between async flushes
circuit_breaker_threshold 5 Consecutive failures before opening circuit
circuit_breaker_timeout 60 Seconds to keep circuit open before retrying

Error handling

Monitoring errors never interrupt your LLM workflows. Route them somewhere useful:

RubyLLM::Ups.configure do |c|
  c.on_error = ->(e) { Rails.logger.warn("[ups.dev] #{e.message}") }
end

Manual monitoring

If you don't want Monitored, call monitor() directly:

RubyLLM::Ups.configure do |c|
  c.api_key = "..."
  c.status_page_id = "..."
  c.component_id = "my-agent-id"
end

chat = RubyLLM.chat(model: "claude-sonnet-4-20250514")
RubyLLM::Ups.monitor(chat)
chat.ask("Hello")  # → heartbeat sent

Direct client access

client = RubyLLM::Ups.client

client.list_components
client.create_component(name: "New Agent", component_type: "agent")
client.update_component("comp-id", status: :operational, agent_metadata: { model: "gpt-4" })

License

MIT