<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Adham EL-Deeb</title>
    <description>The latest articles on DEV Community by Adham EL-Deeb (@adham90).</description>
    <link>https://dev.to/adham90</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F87394%2F2ecea431-a6b3-4df3-9353-fd32e3b7930a.jpg</url>
      <title>DEV Community: Adham EL-Deeb</title>
      <link>https://dev.to/adham90</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adham90"/>
    <language>en</language>
    <item>
      <title>Building Production-Ready AI Agents in Rails with RubyLLM::Agents</title>
      <dc:creator>Adham EL-Deeb</dc:creator>
      <pubDate>Sun, 18 Jan 2026 23:04:01 +0000</pubDate>
      <link>https://dev.to/adham90/building-production-ready-ai-agents-in-rails-with-rubyllmagents-h2k</link>
      <guid>https://dev.to/adham90/building-production-ready-ai-agents-in-rails-with-rubyllmagents-h2k</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; RubyLLM::Agents is a Rails engine that makes LLM-powered systems production-ready by providing retries, fallbacks, circuit breakers, cost and budget controls, multi-tenant limits, workflow orchestration, and a real-time dashboard — all through a clean Ruby DSL.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why most LLM prototypes fail in production
&lt;/h2&gt;

&lt;p&gt;Prototypes are deceptively easy. You call an LLM API, get a response, and ship. Everything looks fine in development.&lt;/p&gt;

&lt;p&gt;Then production happens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;APIs time out under real traffic&lt;/li&gt;
&lt;li&gt;A single runaway prompt causes a billing spike&lt;/li&gt;
&lt;li&gt;A provider deprecates a model and a background job starts failing at 3 AM&lt;/li&gt;
&lt;li&gt;You have no visibility into which agents are burning budget&lt;/li&gt;
&lt;li&gt;Rate limits surface as user-facing errors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not edge cases. They are the natural outcome of running LLMs at scale without reliability and observability built in.&lt;/p&gt;

&lt;h2&gt;
  
  
  What RubyLLM::Agents gives you
&lt;/h2&gt;

&lt;p&gt;RubyLLM::Agents is a Rails engine built on top of RubyLLM, the provider-agnostic LLM client. It treats production concerns as first-class citizens: execution tracking, reliability patterns, budget enforcement, tenant isolation, multi-agent workflows, streaming, PII redaction, and a live dashboard.&lt;/p&gt;

&lt;p&gt;Instead of stitching this together yourself, you get a coherent framework designed for long-running, revenue-impacting AI features.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick start
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Gemfile&lt;/span&gt;
gem &lt;span class="s2"&gt;"ruby_llm-agents"&lt;/span&gt;

bundle &lt;span class="nb"&gt;install
&lt;/span&gt;rails generate ruby_llm_agents:install
rails db:migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installation, you have database tables for execution tracking, a configuration initializer, and a dashboard you can mount immediately.&lt;/p&gt;




&lt;h2&gt;
  
  
  Defining your first agent
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/agents/search_intent_agent.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SearchIntentAgent&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationAgent&lt;/span&gt;
  &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="s2"&gt;"gpt-4o"&lt;/span&gt;
  &lt;span class="n"&gt;temperature&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="s2"&gt;"Extracts search intent and filters from user queries"&lt;/span&gt;

  &lt;span class="n"&gt;param&lt;/span&gt; &lt;span class="ss"&gt;:query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;required: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;param&lt;/span&gt; &lt;span class="ss"&gt;:limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;system_prompt&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;PROMPT&lt;/span&gt;&lt;span class="sh"&gt;
      You are a search query analyzer. Extract the user's intent
      and any filters (price, color, category, etc.) from their query.
      Return structured JSON.
&lt;/span&gt;&lt;span class="no"&gt;    PROMPT&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;user_prompt&lt;/span&gt;
    &lt;span class="s2"&gt;"Analyze this search query: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;schema&lt;/span&gt;
    &lt;span class="vi"&gt;@schema&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;RubyLLM&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:refined_query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;description: &lt;/span&gt;&lt;span class="s2"&gt;"Cleaned search query"&lt;/span&gt;
      &lt;span class="n"&gt;array&lt;/span&gt; &lt;span class="ss"&gt;:filters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;of: :string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;description: &lt;/span&gt;&lt;span class="s2"&gt;"Extracted filters"&lt;/span&gt;
      &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:intent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;description: &lt;/span&gt;&lt;span class="s2"&gt;"User intent category"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Calling the agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;SearchIntentAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;query: &lt;/span&gt;&lt;span class="s2"&gt;"red summer dress under $50"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;content&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; { refined_query: "summer dress", filters: ["color:red", "price:&amp;lt;50"], intent: "product_search" }&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;total_cost&lt;/span&gt;     &lt;span class="c1"&gt;# =&amp;gt; 0.00025&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;total_tokens&lt;/span&gt;   &lt;span class="c1"&gt;# =&amp;gt; 150&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;duration_ms&lt;/span&gt;    &lt;span class="c1"&gt;# =&amp;gt; 850&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success?&lt;/span&gt;       &lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every execution is automatically persisted with full metadata: parameters, model used, retries, fallbacks, cost, latency, and errors.&lt;/p&gt;




&lt;h2&gt;
  
  
  Production reliability with the Reliability DSL
&lt;/h2&gt;

&lt;p&gt;In production, LLM calls must handle transient failures, degraded providers, and latency spikes. RubyLLM::Agents exposes this via a declarative reliability DSL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CriticalAgent&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationAgent&lt;/span&gt;
  &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="s2"&gt;"gpt-4o"&lt;/span&gt;

  &lt;span class="n"&gt;reliability&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;retries&lt;/span&gt; &lt;span class="ss"&gt;max: &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;backoff: :exponential&lt;/span&gt;
    &lt;span class="n"&gt;fallback_models&lt;/span&gt; &lt;span class="s2"&gt;"gpt-4o-mini"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"claude-3-5-sonnet"&lt;/span&gt;
    &lt;span class="n"&gt;circuit_breaker&lt;/span&gt; &lt;span class="ss"&gt;errors: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;within: &lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;cooldown: &lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;
    &lt;span class="n"&gt;total_timeout&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At runtime:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The primary model is attempted first.&lt;/li&gt;
&lt;li&gt;Failures trigger retries with exponential backoff.&lt;/li&gt;
&lt;li&gt;After retries, fallback models are attempted in order.&lt;/li&gt;
&lt;li&gt;Repeated failures open a circuit breaker to fail fast and protect upstream services.&lt;/li&gt;
&lt;li&gt;A total timeout bounds the entire call sequence.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result object makes this transparent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chosen_model_id&lt;/span&gt;  &lt;span class="c1"&gt;# Model that produced the final response&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attempts_count&lt;/span&gt;   &lt;span class="c1"&gt;# Total attempts including retries&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fallback_reason&lt;/span&gt;  &lt;span class="c1"&gt;# Why a fallback was used (timeout, rate_limit, etc.)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Cost control and budget enforcement
&lt;/h2&gt;

&lt;p&gt;Unbounded LLM usage is a liability. RubyLLM::Agents provides hard and soft budget enforcement at multiple levels.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/initializers/ruby_llm_agents.rb&lt;/span&gt;
&lt;span class="no"&gt;RubyLLM&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Agents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;budgets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;global_daily: &lt;/span&gt;&lt;span class="mf"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;global_monthly: &lt;/span&gt;&lt;span class="mf"&gt;2000.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="ss"&gt;per_agent_daily: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"ExpensiveAgent"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;50.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"CheapAgent"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;5.0&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="ss"&gt;enforcement: :hard&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;:hard&lt;/code&gt; enforcement, calls raise &lt;code&gt;BudgetExceededError&lt;/code&gt; once a limit is crossed. With &lt;code&gt;:soft&lt;/code&gt;, limits trigger alerts but allow execution to continue.&lt;/p&gt;

&lt;p&gt;Alerts integrate directly with Slack or webhooks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;alerts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;on_events: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:budget_soft_cap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:budget_hard_cap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:breaker_open&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="ss"&gt;slack_webhook_url: &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'SLACK_WEBHOOK_URL'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Multi-tenancy and per-customer limits
&lt;/h2&gt;

&lt;p&gt;For SaaS products, each tenant can have isolated budgets and circuit breakers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;SearchAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;query: &lt;/span&gt;&lt;span class="s2"&gt;"find products"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;tenant: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="s2"&gt;"acme-corp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;daily_limit: &lt;/span&gt;&lt;span class="mf"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;monthly_limit: &lt;/span&gt;&lt;span class="mf"&gt;1000.0&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures a single noisy customer cannot affect system reliability or cost for others.&lt;/p&gt;




&lt;h2&gt;
  
  
  Workflow orchestration
&lt;/h2&gt;

&lt;p&gt;Real-world AI systems rarely rely on a single LLM call. RubyLLM::Agents supports structured orchestration through three workflow types.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pipeline (sequential)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ContentPipeline&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;RubyLLM&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Agents&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Workflow&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Pipeline&lt;/span&gt;
  &lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seconds&lt;/span&gt;
  &lt;span class="n"&gt;max_cost&lt;/span&gt; &lt;span class="mf"&gt;1.00&lt;/span&gt;

  &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:classify&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;agent: &lt;/span&gt;&lt;span class="no"&gt;ClassifierAgent&lt;/span&gt;
  &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:enrich&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="ss"&gt;agent: &lt;/span&gt;&lt;span class="no"&gt;EnricherAgent&lt;/span&gt;
  &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ss"&gt;:format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="ss"&gt;agent: &lt;/span&gt;&lt;span class="no"&gt;FormatterAgent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;optional: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ContentPipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="n"&gt;raw_content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pipelines support conditional steps, input transformation between steps, and per-step error handling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parallel (concurrent)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AnalysisPipeline&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;RubyLLM&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Agents&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Workflow&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Parallel&lt;/span&gt;
  &lt;span class="n"&gt;concurrency&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
  &lt;span class="n"&gt;fail_fast&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;

  &lt;span class="n"&gt;branch&lt;/span&gt; &lt;span class="ss"&gt;:sentiment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;agent: &lt;/span&gt;&lt;span class="no"&gt;SentimentAgent&lt;/span&gt;
  &lt;span class="n"&gt;branch&lt;/span&gt; &lt;span class="ss"&gt;:entities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="ss"&gt;agent: &lt;/span&gt;&lt;span class="no"&gt;EntityAgent&lt;/span&gt;
  &lt;span class="n"&gt;branch&lt;/span&gt; &lt;span class="ss"&gt;:summary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="ss"&gt;agent: &lt;/span&gt;&lt;span class="no"&gt;SummaryAgent&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Branches execute concurrently with thread-safe aggregation and optional failure isolation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Router (conditional)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SupportRouter&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;RubyLLM&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Agents&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Workflow&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Router&lt;/span&gt;
  &lt;span class="n"&gt;classifier_model&lt;/span&gt; &lt;span class="s2"&gt;"gpt-4o-mini"&lt;/span&gt;

  &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="ss"&gt;:technical&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="no"&gt;TechSupportAgent&lt;/span&gt;
  &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="ss"&gt;:billing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="no"&gt;BillingAgent&lt;/span&gt;
  &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="ss"&gt;:default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="no"&gt;GeneralAgent&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Routers combine low-cost classification with rule-based overrides for efficient dispatch.&lt;/p&gt;

&lt;p&gt;All workflows return aggregated metrics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;total_cost&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;duration_ms&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;        &lt;span class="c1"&gt;# success, partial, or error&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Observability and the dashboard
&lt;/h2&gt;

&lt;p&gt;Mount the engine to enable a real-time dashboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/routes.rb&lt;/span&gt;
&lt;span class="n"&gt;mount&lt;/span&gt; &lt;span class="no"&gt;RubyLLM&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Agents&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Engine&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"/agents"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dashboard provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Execution history with filtering by agent, model, and status&lt;/li&gt;
&lt;li&gt;Cost and token analytics over time&lt;/li&gt;
&lt;li&gt;Performance and throughput trends&lt;/li&gt;
&lt;li&gt;Error tracking with stack traces and context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The UI updates live using Turbo, without page refreshes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Streaming, multi-turn conversations, and attachments
&lt;/h2&gt;

&lt;p&gt;RubyLLM::Agents supports advanced interaction patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Streaming:&lt;/strong&gt; Yield partial responses for chat and live UIs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-turn conversations:&lt;/strong&gt; Pass prior messages for context-aware agents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attachments:&lt;/strong&gt; Images, PDFs, and documents for vision and analysis tasks.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;ChatAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;query: &lt;/span&gt;&lt;span class="s2"&gt;"What's my name?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;messages: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;role: :user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;content: &lt;/span&gt;&lt;span class="s2"&gt;"My name is Alice"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;role: :assistant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;content: &lt;/span&gt;&lt;span class="s2"&gt;"Nice to meet you, Alice!"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  PII protection and redaction
&lt;/h2&gt;

&lt;p&gt;Sensitive data is automatically redacted from logs and dashboards. You can also define custom redaction patterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RubyLLM&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Agents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redaction_patterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;api_key: &lt;/span&gt;&lt;span class="sr"&gt;/sk-[a-zA-Z0-9]+/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;internal_id: &lt;/span&gt;&lt;span class="sr"&gt;/INTERNAL-\d+/&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Testing agents
&lt;/h2&gt;

&lt;p&gt;Agents can be tested in dry-run mode without calling external APIs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;SearchIntentAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;query: &lt;/span&gt;&lt;span class="s2"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dry_run: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows validation of parameters, prompts, and schemas as part of your test suite.&lt;/p&gt;




&lt;h2&gt;
  
  
  Generators
&lt;/h2&gt;

&lt;p&gt;Rails generators speed up adoption:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails generate ruby_llm_agents:agent ProductSearch query:required category
rails generate ruby_llm_agents:agent Chat::Support message:required user_id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why not call LLM APIs directly?
&lt;/h2&gt;

&lt;p&gt;Direct API calls are fine for prototypes. Production systems require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Structured execution logs&lt;/li&gt;
&lt;li&gt;Predictable cost controls&lt;/li&gt;
&lt;li&gt;Reliability patterns and graceful degradation&lt;/li&gt;
&lt;li&gt;Tenant isolation for SaaS&lt;/li&gt;
&lt;li&gt;Centralized observability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;RubyLLM::Agents provides these capabilities out of the box for Rails applications.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gem &lt;span class="s2"&gt;"ruby_llm-agents"&lt;/span&gt;

bundle &lt;span class="nb"&gt;install
&lt;/span&gt;rails generate ruby_llm_agents:install
rails db:migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Documentation and source:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/adham90/ruby_llm-agents" rel="noopener noreferrer"&gt;https://github.com/adham90/ruby_llm-agents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/adham90/ruby_llm-agents/wiki" rel="noopener noreferrer"&gt;https://github.com/adham90/ruby_llm-agents/wiki&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Roadmap
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;v0.5.0 introduced tenant token limits and database-backed API configuration&lt;/li&gt;
&lt;li&gt;v0.4.0 added the reliability DSL and enhanced execution tracking&lt;/li&gt;
&lt;li&gt;Upcoming releases will expand workflow primitives and analytics&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Building AI features that matter means treating LLMs like any other critical dependency. RubyLLM::Agents gives Rails developers the tooling needed to do exactly that.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>llm</category>
      <category>rails</category>
      <category>ruby</category>
    </item>
  </channel>
</rss>
