ruby_llm-agents 0.3.3 → 0.3.5

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.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +132 -1263
  3. data/app/controllers/concerns/ruby_llm/agents/filterable.rb +5 -1
  4. data/app/controllers/concerns/ruby_llm/agents/paginatable.rb +2 -1
  5. data/app/controllers/ruby_llm/agents/agents_controller.rb +21 -2
  6. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +137 -9
  7. data/app/controllers/ruby_llm/agents/executions_controller.rb +83 -5
  8. data/app/models/ruby_llm/agents/execution/analytics.rb +103 -12
  9. data/app/models/ruby_llm/agents/execution/scopes.rb +25 -0
  10. data/app/models/ruby_llm/agents/execution/workflow.rb +299 -0
  11. data/app/models/ruby_llm/agents/execution.rb +28 -59
  12. data/app/models/ruby_llm/agents/tenant_budget.rb +165 -0
  13. data/app/services/ruby_llm/agents/agent_registry.rb +118 -7
  14. data/app/views/layouts/ruby_llm/agents/application.html.erb +430 -0
  15. data/app/views/ruby_llm/agents/agents/_empty_state.html.erb +23 -0
  16. data/app/views/ruby_llm/agents/agents/_workflow.html.erb +125 -0
  17. data/app/views/ruby_llm/agents/agents/index.html.erb +93 -0
  18. data/app/views/ruby_llm/agents/agents/show.html.erb +775 -0
  19. data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +112 -0
  20. data/app/views/ruby_llm/agents/dashboard/_budgets_bar.html.erb +75 -0
  21. data/app/views/{rubyllm → ruby_llm}/agents/dashboard/_execution_item.html.erb +7 -4
  22. data/app/views/ruby_llm/agents/dashboard/_now_strip.html.erb +84 -0
  23. data/app/views/ruby_llm/agents/dashboard/_tenant_budget.html.erb +115 -0
  24. data/app/views/ruby_llm/agents/dashboard/_top_errors.html.erb +49 -0
  25. data/app/views/ruby_llm/agents/dashboard/index.html.erb +155 -0
  26. data/app/views/{rubyllm → ruby_llm}/agents/executions/_execution.html.erb +1 -1
  27. data/app/views/{rubyllm → ruby_llm}/agents/executions/_filters.html.erb +39 -11
  28. data/app/views/{rubyllm → ruby_llm}/agents/executions/_list.html.erb +19 -9
  29. data/app/views/ruby_llm/agents/executions/_workflow_summary.html.erb +101 -0
  30. data/app/views/ruby_llm/agents/executions/index.html.erb +88 -0
  31. data/app/views/{rubyllm → ruby_llm}/agents/executions/show.html.erb +260 -200
  32. data/app/views/{rubyllm → ruby_llm}/agents/settings/show.html.erb +1 -1
  33. data/app/views/ruby_llm/agents/shared/_breadcrumbs.html.erb +48 -0
  34. data/app/views/ruby_llm/agents/shared/_executions_table.html.erb +251 -0
  35. data/app/views/{rubyllm → ruby_llm}/agents/shared/_filter_dropdown.html.erb +1 -1
  36. data/app/views/ruby_llm/agents/shared/_nav_link.html.erb +27 -0
  37. data/app/views/{rubyllm → ruby_llm}/agents/shared/_select_dropdown.html.erb +1 -1
  38. data/app/views/{rubyllm → ruby_llm}/agents/shared/_status_badge.html.erb +1 -1
  39. data/app/views/{rubyllm → ruby_llm}/agents/shared/_status_dot.html.erb +1 -1
  40. data/app/views/ruby_llm/agents/shared/_tenant_filter.html.erb +26 -0
  41. data/app/views/ruby_llm/agents/shared/_workflow_type_badge.html.erb +61 -0
  42. data/config/routes.rb +2 -0
  43. data/lib/generators/ruby_llm_agents/multi_tenancy_generator.rb +97 -0
  44. data/lib/generators/ruby_llm_agents/templates/add_attempts_migration.rb.tt +3 -3
  45. data/lib/generators/ruby_llm_agents/templates/add_tenant_to_executions_migration.rb.tt +23 -0
  46. data/lib/generators/ruby_llm_agents/templates/add_tool_calls_migration.rb.tt +2 -2
  47. data/lib/generators/ruby_llm_agents/templates/add_workflow_migration.rb.tt +38 -0
  48. data/lib/generators/ruby_llm_agents/templates/create_tenant_budgets_migration.rb.tt +45 -0
  49. data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +17 -5
  50. data/lib/generators/ruby_llm_agents/upgrade_generator.rb +13 -0
  51. data/lib/ruby_llm/agents/alert_manager.rb +20 -16
  52. data/lib/ruby_llm/agents/base/caching.rb +40 -0
  53. data/lib/ruby_llm/agents/base/cost_calculation.rb +105 -0
  54. data/lib/ruby_llm/agents/base/dsl.rb +261 -0
  55. data/lib/ruby_llm/agents/base/execution.rb +258 -0
  56. data/lib/ruby_llm/agents/base/reliability_execution.rb +136 -0
  57. data/lib/ruby_llm/agents/base/response_building.rb +86 -0
  58. data/lib/ruby_llm/agents/base/tool_tracking.rb +57 -0
  59. data/lib/ruby_llm/agents/base.rb +37 -801
  60. data/lib/ruby_llm/agents/budget_tracker.rb +250 -139
  61. data/lib/ruby_llm/agents/cache_helper.rb +98 -0
  62. data/lib/ruby_llm/agents/circuit_breaker.rb +48 -30
  63. data/lib/ruby_llm/agents/configuration.rb +40 -1
  64. data/lib/ruby_llm/agents/engine.rb +65 -1
  65. data/lib/ruby_llm/agents/inflections.rb +14 -0
  66. data/lib/ruby_llm/agents/instrumentation.rb +66 -0
  67. data/lib/ruby_llm/agents/reliability.rb +8 -2
  68. data/lib/ruby_llm/agents/version.rb +1 -1
  69. data/lib/ruby_llm/agents/workflow/instrumentation.rb +254 -0
  70. data/lib/ruby_llm/agents/workflow/parallel.rb +282 -0
  71. data/lib/ruby_llm/agents/workflow/pipeline.rb +306 -0
  72. data/lib/ruby_llm/agents/workflow/result.rb +390 -0
  73. data/lib/ruby_llm/agents/workflow/router.rb +429 -0
  74. data/lib/ruby_llm/agents/workflow.rb +232 -0
  75. data/lib/ruby_llm/agents.rb +1 -0
  76. metadata +57 -75
  77. data/app/channels/ruby_llm/agents/executions_channel.rb +0 -46
  78. data/app/javascript/ruby_llm/agents/controllers/filter_controller.js +0 -56
  79. data/app/javascript/ruby_llm/agents/controllers/index.js +0 -12
  80. data/app/javascript/ruby_llm/agents/controllers/refresh_controller.js +0 -83
  81. data/app/views/layouts/rubyllm/agents/application.html.erb +0 -626
  82. data/app/views/rubyllm/agents/agents/index.html.erb +0 -20
  83. data/app/views/rubyllm/agents/agents/show.html.erb +0 -772
  84. data/app/views/rubyllm/agents/dashboard/_budgets_bar.html.erb +0 -165
  85. data/app/views/rubyllm/agents/dashboard/_now_strip.html.erb +0 -10
  86. data/app/views/rubyllm/agents/dashboard/_now_strip_values.html.erb +0 -71
  87. data/app/views/rubyllm/agents/dashboard/index.html.erb +0 -197
  88. data/app/views/rubyllm/agents/executions/index.html.erb +0 -28
  89. data/app/views/rubyllm/agents/executions/index.turbo_stream.erb +0 -18
  90. data/app/views/rubyllm/agents/shared/_executions_table.html.erb +0 -193
  91. /data/app/views/{rubyllm → ruby_llm}/agents/agents/_agent.html.erb +0 -0
  92. /data/app/views/{rubyllm → ruby_llm}/agents/agents/_version_comparison.html.erb +0 -0
  93. /data/app/views/{rubyllm → ruby_llm}/agents/dashboard/_action_center.html.erb +0 -0
  94. /data/app/views/{rubyllm → ruby_llm}/agents/dashboard/_alerts_feed.html.erb +0 -0
  95. /data/app/views/{rubyllm → ruby_llm}/agents/dashboard/_breaker_strip.html.erb +0 -0
  96. /data/app/views/{rubyllm → ruby_llm}/agents/executions/dry_run.html.erb +0 -0
  97. /data/app/views/{rubyllm → ruby_llm}/agents/shared/_stat_card.html.erb +0 -0
data/README.md CHANGED
@@ -1,657 +1,152 @@
1
1
  # RubyLLM::Agents
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/ruby_llm-agents.svg)](https://badge.fury.io/rb/ruby_llm-agents)
3
+ > **Production-ready Rails engine for building, managing, and monitoring LLM-powered AI agents**
4
4
 
5
- A powerful Rails engine for building, managing, and monitoring LLM-powered agents using [RubyLLM](https://github.com/crmne/ruby_llm).
5
+ [![Gem Version](https://badge.fury.io/rb/ruby_llm-agents.svg)](https://rubygems.org/gems/ruby_llm-agents)
6
+ [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.1-ruby.svg)](https://www.ruby-lang.org)
7
+ [![Rails](https://img.shields.io/badge/rails-%3E%3D%207.0-red.svg)](https://rubyonrails.org)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
+ [![Documentation](https://img.shields.io/badge/docs-wiki-blue.svg)](https://github.com/adham90/ruby_llm-agents/wiki)
6
10
 
7
- ## Features
8
-
9
- - **🤖 Agent DSL** - Declarative configuration for LLM agents with model, temperature, parameters, and caching
10
- - **📊 Execution Tracking** - Automatic logging of all agent executions with token usage and costs
11
- - **💰 Cost Analytics** - Track spending by agent, model, and time period with detailed breakdowns
12
- - **📈 Dashboard UI** - Beautiful Turbo-powered dashboard for monitoring agents
13
- - **⚡ Performance** - Built-in caching with configurable TTL and cache key versioning
14
- - **🛠️ Generators** - Quickly scaffold new agents with customizable templates
15
- - **🔍 Anomaly Detection** - Automatic warnings for unusual cost or duration patterns
16
- - **🎯 Type Safety** - Structured output with RubyLLM::Schema integration
17
- - **⚡ Real-time Streaming** - Stream LLM responses with time-to-first-token tracking
18
- - **📎 Attachments** - Send images, PDFs, and files to vision-capable models
19
- - **📋 Rich Results** - Access token counts, costs, timing, and model info from every execution
20
- - **🔄 Reliability** - Automatic retries, model fallbacks, and circuit breakers for resilient agents
21
- - **💵 Budget Controls** - Daily/monthly spending limits with hard and soft enforcement
22
- - **🔔 Alerts** - Slack, webhook, and custom notifications for budget and circuit breaker events
23
- - **🔒 PII Redaction** - Automatic sanitization of sensitive data in execution logs
24
-
25
- ## Requirements
11
+ Build intelligent AI agents in Ruby with a clean DSL, automatic execution tracking, cost analytics, budget controls, and a beautiful real-time dashboard. Supports **OpenAI GPT-4**, **Anthropic Claude**, **Google Gemini**, and more through [RubyLLM](https://github.com/crmne/ruby_llm).
26
12
 
27
- - **Ruby**: >= 3.1.0
28
- - **Rails**: >= 7.0
13
+ ## Why RubyLLM::Agents?
29
14
 
30
- ## Dependencies
15
+ - **Rails-Native** - Seamlessly integrates with your Rails app: models, jobs, caching, and Hotwire
16
+ - **Production-Ready** - Built-in retries, model fallbacks, circuit breakers, and budget limits
17
+ - **Full Observability** - Track every execution with costs, tokens, duration, and errors
18
+ - **Workflow Orchestration** - Compose agents into pipelines, parallel tasks, and conditional routers
19
+ - **Zero Lock-in** - Works with any LLM provider supported by RubyLLM
31
20
 
32
- The gem includes the following runtime dependencies:
21
+ ## Features
33
22
 
34
- ```ruby
35
- gem "rails", ">= 7.0"
36
- gem "ruby_llm", ">= 1.0" # LLM client library
37
- gem "turbo-rails", ">= 1.0" # Hotwire Turbo for real-time UI
38
- gem "stimulus-rails", ">= 1.0" # Hotwire Stimulus for JavaScript
39
- gem "chartkick", ">= 5.0" # Beautiful charts for analytics
40
- ```
23
+ | Feature | Description | Docs |
24
+ |---------|-------------|------|
25
+ | **Agent DSL** | Declarative configuration with model, temperature, parameters | [Agent DSL](https://github.com/adham90/ruby_llm-agents/wiki/Agent-DSL) |
26
+ | **Execution Tracking** | Automatic logging with token usage and cost analytics | [Tracking](https://github.com/adham90/ruby_llm-agents/wiki/Execution-Tracking) |
27
+ | **Cost Analytics** | Track spending by agent, model, and time period | [Analytics](https://github.com/adham90/ruby_llm-agents/wiki/Execution-Tracking) |
28
+ | **Reliability** | Automatic retries, model fallbacks, circuit breakers | [Reliability](https://github.com/adham90/ruby_llm-agents/wiki/Reliability) |
29
+ | **Budget Controls** | Daily/monthly limits with hard and soft enforcement | [Budgets](https://github.com/adham90/ruby_llm-agents/wiki/Budget-Controls) |
30
+ | **Workflows** | Pipelines, parallel execution, conditional routers | [Workflows](https://github.com/adham90/ruby_llm-agents/wiki/Workflows) |
31
+ | **Dashboard** | Real-time Turbo-powered monitoring UI | [Dashboard](https://github.com/adham90/ruby_llm-agents/wiki/Dashboard) |
32
+ | **Streaming** | Real-time response streaming with TTFT tracking | [Streaming](https://github.com/adham90/ruby_llm-agents/wiki/Streaming) |
33
+ | **Conversation History** | Multi-turn conversations with message history | [Conversation History](https://github.com/adham90/ruby_llm-agents/wiki/Conversation-History) |
34
+ | **Attachments** | Images, PDFs, and multimodal support | [Attachments](https://github.com/adham90/ruby_llm-agents/wiki/Attachments) |
35
+ | **PII Redaction** | Automatic sensitive data protection | [Security](https://github.com/adham90/ruby_llm-agents/wiki/PII-Redaction) |
36
+ | **Alerts** | Slack, webhook, and custom notifications | [Alerts](https://github.com/adham90/ruby_llm-agents/wiki/Alerts) |
41
37
 
42
- ## Installation
38
+ ## Quick Start
43
39
 
44
- ### 1. Add to your Gemfile
40
+ ### Installation
45
41
 
46
42
  ```ruby
43
+ # Gemfile
47
44
  gem "ruby_llm-agents"
48
45
  ```
49
46
 
50
- Then run:
51
-
52
47
  ```bash
53
48
  bundle install
54
- ```
55
-
56
- ### 2. Run the install generator
57
-
58
- ```bash
59
49
  rails generate ruby_llm_agents:install
60
50
  rails db:migrate
61
51
  ```
62
52
 
63
- This will:
64
-
65
- - Create the `ruby_llm_agents_executions` table for execution tracking
66
- - Add an initializer at `config/initializers/ruby_llm_agents.rb`
67
- - Create `app/agents/application_agent.rb` as the base class for your agents
68
- - Mount the dashboard at `/agents` in your routes
69
-
70
- ### 3. Configure your LLM provider
71
-
72
- Set up your API keys for the LLM providers you want to use:
53
+ ### Configure API Keys
73
54
 
74
55
  ```bash
75
- # .env or Rails credentials
76
- GOOGLE_API_KEY=your_key_here
77
- OPENAI_API_KEY=your_key_here
78
- ANTHROPIC_API_KEY=your_key_here
56
+ # .env
57
+ OPENAI_API_KEY=sk-...
58
+ ANTHROPIC_API_KEY=sk-ant-...
59
+ GOOGLE_API_KEY=...
79
60
  ```
80
61
 
81
- ## Quick Start
82
-
83
- ### Creating Your First Agent
84
-
85
- Use the generator to create a new agent:
62
+ ### Create Your First Agent
86
63
 
87
64
  ```bash
88
- rails generate ruby_llm_agents:agent SearchIntent query:required limit:10
65
+ rails generate ruby_llm_agents:agent SearchIntent query:required
89
66
  ```
90
67
 
91
- This creates `app/agents/search_intent_agent.rb`:
92
-
93
68
  ```ruby
69
+ # app/agents/search_intent_agent.rb
94
70
  class SearchIntentAgent < ApplicationAgent
95
- model "gemini-2.0-flash"
71
+ model "gpt-4o"
96
72
  temperature 0.0
97
- version "1.0"
98
73
 
99
74
  param :query, required: true
100
- param :limit, default: 10
101
-
102
- private
103
-
104
- def system_prompt
105
- <<~PROMPT
106
- You are a search assistant that parses user queries
107
- and extracts structured search filters.
108
- PROMPT
109
- end
110
75
 
111
76
  def user_prompt
112
- query
77
+ "Extract search intent from: #{query}"
113
78
  end
114
79
 
115
80
  def schema
116
81
  @schema ||= RubyLLM::Schema.create do
117
- string :refined_query, description: "Cleaned and refined search query"
118
- array :filters, of: :string, description: "Extracted search filters"
119
- integer :category_id, description: "Detected product category", nullable: true
82
+ string :refined_query, description: "Cleaned search query"
83
+ array :filters, of: :string, description: "Extracted filters"
120
84
  end
121
85
  end
122
86
  end
123
87
  ```
124
88
 
125
- ### Calling the Agent
89
+ ### Call the Agent
126
90
 
127
91
  ```ruby
128
- # Basic call
129
92
  result = SearchIntentAgent.call(query: "red summer dress under $50")
130
- # => {
131
- # refined_query: "red summer dress",
132
- # filters: ["color:red", "season:summer", "price:<50"],
133
- # category_id: 42
134
- # }
135
-
136
- # With custom parameters
137
- result = SearchIntentAgent.call(
138
- query: "blue jeans",
139
- limit: 20
140
- )
141
93
 
142
- # Debug mode (no API call, shows prompt)
143
- SearchIntentAgent.call(query: "test", dry_run: true)
144
- # => {
145
- # dry_run: true,
146
- # agent: "SearchIntentAgent",
147
- # model: "gemini-2.0-flash",
148
- # temperature: 0.0,
149
- # system_prompt: "You are a search assistant...",
150
- # user_prompt: "test",
151
- # schema: "RubyLLM::Schema"
152
- # }
153
-
154
- # Skip cache
155
- SearchIntentAgent.call(query: "test", skip_cache: true)
94
+ result.content # => { refined_query: "red dress", filters: ["color:red", "price:<50"] }
95
+ result.total_cost # => 0.00025
96
+ result.total_tokens # => 150
97
+ result.duration_ms # => 850
156
98
  ```
157
99
 
158
- ### Streaming Responses
100
+ ### Multi-Turn Conversations
159
101
 
160
- Enable real-time streaming to receive LLM responses as they're generated:
102
+ Build chatbots and conversational agents with message history:
161
103
 
162
104
  ```ruby
163
- class StreamingAgent < ApplicationAgent
164
- model "gpt-4o"
165
- streaming true # Enable streaming for this agent
166
-
167
- param :prompt, required: true
168
-
169
- def user_prompt
170
- prompt
171
- end
172
- end
173
- ```
174
-
175
- #### Using Streaming with a Block
176
-
177
- ```ruby
178
- # Stream responses in real-time
179
- StreamingAgent.call(prompt: "Write a story") do |chunk|
180
- print chunk # Process each chunk as it arrives
181
- end
182
- ```
183
-
184
- #### HTTP Streaming with ActionController::Live
185
-
186
- ```ruby
187
- class StreamingController < ApplicationController
188
- include ActionController::Live
189
-
190
- def stream_response
191
- response.headers['Content-Type'] = 'text/event-stream'
192
- response.headers['Cache-Control'] = 'no-cache'
193
-
194
- StreamingAgent.call(prompt: params[:prompt]) do |chunk|
195
- response.stream.write "data: #{chunk}\n\n"
196
- end
197
- ensure
198
- response.stream.close
199
- end
200
- end
201
- ```
202
-
203
- #### Time-to-First-Token Tracking
204
-
205
- Streaming executions automatically track latency metrics:
206
-
207
- ```ruby
208
- execution = RubyLLM::Agents::Execution.last
209
- execution.streaming? # => true
210
- execution.time_to_first_token_ms # => 245 (milliseconds to first chunk)
211
- ```
212
-
213
- #### Global Streaming Configuration
214
-
215
- Enable streaming by default for all agents:
216
-
217
- ```ruby
218
- # config/initializers/ruby_llm_agents.rb
219
- RubyLLM::Agents.configure do |config|
220
- config.default_streaming = true
221
- end
222
- ```
223
-
224
- ### Attachments (Vision & Multimodal)
225
-
226
- Send images, PDFs, and other files to vision-capable models using the `with:` option:
227
-
228
- ```ruby
229
- class VisionAgent < ApplicationAgent
230
- model "gpt-4o" # Use a vision-capable model
231
- param :question, required: true
232
-
233
- def user_prompt
234
- question
235
- end
236
- end
237
- ```
238
-
239
- #### Single Attachment
240
-
241
- ```ruby
242
- # Local file
243
- VisionAgent.call(question: "Describe this image", with: "photo.jpg")
244
-
245
- # URL
246
- VisionAgent.call(question: "What architecture is shown?", with: "https://example.com/building.jpg")
247
- ```
248
-
249
- #### Multiple Attachments
250
-
251
- ```ruby
252
- VisionAgent.call(
253
- question: "Compare these two screenshots",
254
- with: ["screenshot_v1.png", "screenshot_v2.png"]
255
- )
256
- ```
257
-
258
- #### Supported File Types
259
-
260
- RubyLLM automatically detects file types:
261
-
262
- - **Images:** `.jpg`, `.jpeg`, `.png`, `.gif`, `.webp`, `.bmp`
263
- - **Videos:** `.mp4`, `.mov`, `.avi`, `.webm`
264
- - **Audio:** `.mp3`, `.wav`, `.m4a`, `.ogg`, `.flac`
265
- - **Documents:** `.pdf`, `.txt`, `.md`, `.csv`, `.json`, `.xml`
266
- - **Code:** `.rb`, `.py`, `.js`, `.html`, `.css`, and many others
267
-
268
- #### Debug Mode with Attachments
269
-
270
- ```ruby
271
- VisionAgent.call(question: "test", with: "image.png", dry_run: true)
272
- # => { ..., attachments: "image.png", ... }
273
- ```
274
-
275
- ### Execution Results
276
-
277
- Every agent call returns a `Result` object with full execution metadata:
278
-
279
- ```ruby
280
- result = SearchAgent.call(query: "red dress")
281
-
282
- # Access the processed response
283
- result.content # => { refined_query: "red dress", ... }
284
-
285
- # Token usage
286
- result.input_tokens # => 150
287
- result.output_tokens # => 50
288
- result.total_tokens # => 200
289
- result.cached_tokens # => 0
290
-
291
- # Cost calculation
292
- result.input_cost # => 0.000150
293
- result.output_cost # => 0.000100
294
- result.total_cost # => 0.000250
295
-
296
- # Model info
297
- result.model_id # => "gpt-4o"
298
- result.chosen_model_id # => "gpt-4o" (may differ if fallback used)
299
- result.temperature # => 0.0
300
-
301
- # Timing
302
- result.duration_ms # => 1234
303
- result.started_at # => 2025-11-27 10:30:00 UTC
304
- result.completed_at # => 2025-11-27 10:30:01 UTC
305
- result.time_to_first_token_ms # => 245 (streaming only)
306
-
307
- # Status
308
- result.finish_reason # => "stop", "length", "tool_calls", etc.
309
- result.streaming? # => false
310
- result.success? # => true
311
- result.truncated? # => false (true if hit max_tokens)
312
-
313
- # Tool calls (for agents with tools)
314
- result.tool_calls # => [{ "id" => "call_abc", "name" => "search", "arguments" => {...} }]
315
- result.tool_calls_count # => 1
316
- result.has_tool_calls? # => true
317
-
318
- # Reliability info
319
- result.attempts_count # => 1
320
- result.used_fallback? # => false
321
- ```
322
-
323
- #### Backward Compatibility
324
-
325
- The Result object delegates hash methods to content, so existing code continues to work:
326
-
327
- ```ruby
328
- # Old style (still works)
329
- result[:refined_query]
330
- result.dig(:nested, :key)
331
-
332
- # New style (access metadata)
333
- result.content[:refined_query]
334
- result.total_cost
335
- ```
336
-
337
- #### Full Metadata Hash
338
-
339
- ```ruby
340
- result.to_h
341
- # => {
342
- # content: { refined_query: "red dress", ... },
343
- # input_tokens: 150,
344
- # output_tokens: 50,
345
- # total_tokens: 200,
346
- # cached_tokens: 0,
347
- # input_cost: 0.000150,
348
- # output_cost: 0.000100,
349
- # total_cost: 0.000250,
350
- # model_id: "gpt-4o",
351
- # chosen_model_id: "gpt-4o",
352
- # temperature: 0.0,
353
- # duration_ms: 1234,
354
- # finish_reason: "stop",
355
- # streaming: false,
356
- # tool_calls: [...],
357
- # tool_calls_count: 0,
358
- # ...
359
- # }
360
- ```
361
-
362
- ## Usage Guide
363
-
364
- ### Agent DSL
365
-
366
- #### Model Configuration
367
-
368
- ```ruby
369
- class MyAgent < ApplicationAgent
370
- # LLM model to use
371
- model "gpt-4o" # OpenAI GPT-4
372
- # model "claude-3-5-sonnet" # Anthropic Claude
373
- # model "gemini-2.0-flash" # Google Gemini (default)
374
-
375
- # Randomness (0.0 = deterministic, 1.0 = creative)
376
- temperature 0.7
377
-
378
- # Version for cache key generation
379
- version "2.0"
380
-
381
- # Request timeout in seconds
382
- timeout 30
383
-
384
- # Enable caching with TTL
385
- cache 1.hour
386
- end
387
- ```
388
-
389
- #### Parameter Definition
390
-
391
- ```ruby
392
- class ProductSearchAgent < ApplicationAgent
393
- # Required parameter - raises ArgumentError if not provided
394
- param :query, required: true
395
-
396
- # Optional parameter with default value
397
- param :limit, default: 10
398
-
399
- # Optional parameter (no default)
400
- param :filters
401
-
402
- # Multiple required parameters
403
- param :user_id, required: true
404
- param :session_id, required: true
405
- end
406
- ```
407
-
408
- #### Prompt Methods
409
-
410
- ```ruby
411
- class ContentGeneratorAgent < ApplicationAgent
412
- param :topic, required: true
413
- param :tone, default: "professional"
414
- param :word_count, default: 500
415
-
416
- private
417
-
418
- # System prompt (optional) - sets the AI's role and instructions
419
- def system_prompt
420
- <<~PROMPT
421
- You are a professional content writer specializing in #{topic}.
422
- Write in a #{tone} tone.
423
- PROMPT
424
- end
425
-
426
- # User prompt (required) - the main request to the AI
427
- def user_prompt
428
- <<~PROMPT
429
- Write a #{word_count}-word article about: #{topic}
430
-
431
- Requirements:
432
- - Clear structure with introduction, body, and conclusion
433
- - Use examples and data where relevant
434
- - Maintain a #{tone} tone throughout
435
- PROMPT
436
- end
437
- end
438
- ```
439
-
440
- #### Structured Output with Schema
441
-
442
- ```ruby
443
- class EmailClassifierAgent < ApplicationAgent
444
- param :email_content, required: true
445
-
446
- private
447
-
448
- def system_prompt
449
- "You are an email classification system. Analyze emails and categorize them."
450
- end
451
-
452
- def user_prompt
453
- email_content
454
- end
455
-
456
- def schema
457
- @schema ||= RubyLLM::Schema.create do
458
- string :category,
459
- enum: ["urgent", "important", "spam", "newsletter", "personal"],
460
- description: "Email category"
461
-
462
- number :priority,
463
- description: "Priority score from 0 to 10"
464
-
465
- array :tags,
466
- of: :string,
467
- description: "Relevant tags for the email"
468
-
469
- boolean :requires_response,
470
- description: "Whether the email requires a response"
471
-
472
- object :sender_info do
473
- string :name, nullable: true
474
- string :company, nullable: true
475
- boolean :is_known_contact
476
- end
477
- end
478
- end
479
- end
480
- ```
481
-
482
- #### Response Processing
483
-
484
- ```ruby
485
- class DataExtractorAgent < ApplicationAgent
486
- param :text, required: true
487
-
488
- private
489
-
490
- def user_prompt
491
- "Extract key information from: #{text}"
492
- end
493
-
494
- def schema
495
- @schema ||= RubyLLM::Schema.create do
496
- string :summary
497
- array :entities, of: :string
498
- end
499
- end
500
-
501
- # Post-process the LLM response
502
- def process_response(response)
503
- result = super(response)
504
-
505
- # Add custom processing
506
- result[:entities] = result[:entities].map(&:downcase).uniq
507
- result[:word_count] = result[:summary].split.length
508
- result[:extracted_at] = Time.current
509
-
510
- result
511
- end
512
- end
513
- ```
514
-
515
- #### Custom Metadata
516
-
517
- Add custom data to execution logs for filtering and analytics:
518
-
519
- ```ruby
520
- class UserQueryAgent < ApplicationAgent
521
- param :query, required: true
522
- param :user_id, required: true
523
- param :source, default: "web"
524
-
525
- # This data will be stored in the execution record's metadata column
526
- def execution_metadata
527
- {
528
- user_id: user_id,
529
- source: source,
530
- query_length: query.length,
531
- timestamp: Time.current.iso8601
532
- }
533
- end
534
-
535
- private
536
-
537
- def user_prompt
538
- query
539
- end
540
- end
541
- ```
542
-
543
- ### Advanced Examples
544
-
545
- #### Multi-Step Agent with Conversation History
546
-
547
- ```ruby
548
- class ConversationAgent < ApplicationAgent
549
- param :messages, required: true # Array of {role:, content:} hashes
550
- param :context, default: {}
551
-
552
- def call
553
- return dry_run_response if @options[:dry_run]
554
-
555
- instrument_execution do
556
- Timeout.timeout(self.class.timeout) do
557
- client = build_client_with_messages(messages)
558
- response = client.ask(user_prompt)
559
- process_response(capture_response(response))
560
- end
561
- end
562
- end
563
-
564
- private
565
-
566
- def system_prompt
567
- "You are a helpful assistant. Remember the conversation context."
568
- end
569
-
570
- def user_prompt
571
- messages.last[:content]
572
- end
573
- end
574
-
575
- # Usage
576
- ConversationAgent.call(
105
+ result = ChatAgent.call(
106
+ query: "What's my name?",
577
107
  messages: [
578
- { role: "user", content: "What's the weather like?" },
579
- { role: "assistant", content: "I don't have real-time weather data." },
580
- { role: "user", content: "Okay, tell me a joke then." }
108
+ { role: :user, content: "My name is Alice" },
109
+ { role: :assistant, content: "Nice to meet you, Alice!" }
581
110
  ]
582
111
  )
112
+ # => "Your name is Alice!"
583
113
  ```
584
114
 
585
- #### Agent with Custom Cache Key
586
-
587
- ```ruby
588
- class RecommendationAgent < ApplicationAgent
589
- param :user_id, required: true
590
- param :category, required: true
591
- param :limit, default: 10
592
-
593
- cache 30.minutes
594
-
595
- private
115
+ See [Conversation History](https://github.com/adham90/ruby_llm-agents/wiki/Conversation-History) for more patterns.
596
116
 
597
- # Customize what goes into the cache key
598
- # This excludes 'limit' from cache key, so different limits
599
- # will return the same cached result
600
- def cache_key_data
601
- { user_id: user_id, category: category }
602
- end
117
+ ## Documentation
603
118
 
604
- def user_prompt
605
- "Generate #{limit} recommendations for user #{user_id} in category #{category}"
606
- end
607
- end
608
- ```
119
+ | Guide | Description |
120
+ |-------|-------------|
121
+ | [Getting Started](https://github.com/adham90/ruby_llm-agents/wiki/Getting-Started) | Installation, configuration, first agent |
122
+ | [Agent DSL](https://github.com/adham90/ruby_llm-agents/wiki/Agent-DSL) | All DSL options: model, temperature, params, caching |
123
+ | [Reliability](https://github.com/adham90/ruby_llm-agents/wiki/Reliability) | Retries, fallbacks, circuit breakers, timeouts |
124
+ | [Workflows](https://github.com/adham90/ruby_llm-agents/wiki/Workflows) | Pipelines, parallel execution, routers |
125
+ | [Budget Controls](https://github.com/adham90/ruby_llm-agents/wiki/Budget-Controls) | Spending limits, alerts, enforcement |
126
+ | [Dashboard](https://github.com/adham90/ruby_llm-agents/wiki/Dashboard) | Setup, authentication, analytics |
127
+ | [Production](https://github.com/adham90/ruby_llm-agents/wiki/Production-Deployment) | Deployment best practices, background jobs |
128
+ | [API Reference](https://github.com/adham90/ruby_llm-agents/wiki/API-Reference) | Complete class documentation |
129
+ | [Examples](https://github.com/adham90/ruby_llm-agents/wiki/Examples) | Real-world use cases and patterns |
609
130
 
610
131
  ## Reliability Features
611
132
 
612
- RubyLLM::Agents provides built-in reliability features to make your agents resilient against API failures, rate limits, and transient errors.
613
-
614
- ### Automatic Retries
615
-
616
- Configure retry behavior for transient failures:
133
+ Build resilient agents with built-in fault tolerance:
617
134
 
618
135
  ```ruby
619
136
  class ReliableAgent < ApplicationAgent
620
137
  model "gpt-4o"
621
138
 
622
- # Retry up to 3 times with exponential backoff
623
- retries max: 3, backoff: :exponential, base: 0.5, max_delay: 10.0
624
-
625
- # Only retry on specific errors (defaults include timeout, network errors)
626
- retries max: 3, on: [Timeout::Error, Net::ReadTimeout, Faraday::TimeoutError]
627
-
628
- param :query, required: true
629
-
630
- def user_prompt
631
- query
632
- end
633
- end
634
- ```
635
-
636
- Backoff strategies:
637
- - `:exponential` - Delay doubles each retry (0.5s, 1s, 2s, 4s...)
638
- - `:constant` - Same delay each retry
639
- - Jitter is automatically added to prevent thundering herd
640
-
641
- ### Model Fallbacks
642
-
643
- Automatically try alternative models if the primary fails:
139
+ # Retry on failures with exponential backoff
140
+ retries max: 3, backoff: :exponential
644
141
 
645
- ```ruby
646
- class FallbackAgent < ApplicationAgent
647
- model "gpt-4o"
142
+ # Fall back to alternative models
143
+ fallback_models "gpt-4o-mini", "claude-3-5-sonnet"
648
144
 
649
- # Try these models in order if primary fails
650
- fallback_models "gpt-4o-mini", "claude-3-5-sonnet", "gemini-2.0-flash"
145
+ # Prevent cascading failures
146
+ circuit_breaker errors: 10, within: 60, cooldown: 300
651
147
 
652
- # Combine with retries
653
- retries max: 2
654
- fallback_models "gpt-4o-mini", "claude-3-sonnet"
148
+ # Maximum time for all attempts
149
+ total_timeout 30
655
150
 
656
151
  param :query, required: true
657
152
 
@@ -661,739 +156,113 @@ class FallbackAgent < ApplicationAgent
661
156
  end
662
157
  ```
663
158
 
664
- The agent will try `gpt-4o` (with 2 retries), then `gpt-4o-mini` (with 2 retries), and so on.
159
+ ## Workflow Orchestration
665
160
 
666
- ### Circuit Breaker
667
-
668
- Prevent cascading failures by temporarily blocking requests to failing models:
161
+ Compose agents into complex workflows:
669
162
 
670
163
  ```ruby
671
- class ProtectedAgent < ApplicationAgent
672
- model "gpt-4o"
673
- fallback_models "claude-3-sonnet"
674
-
675
- # Open circuit after 10 errors within 60 seconds
676
- # Keep circuit open for 5 minutes before retrying
677
- circuit_breaker errors: 10, within: 60, cooldown: 300
678
-
679
- param :query, required: true
164
+ # Sequential pipeline - each step's output feeds the next
165
+ class ContentPipeline < RubyLLM::Agents::Workflow::Pipeline
166
+ timeout 60.seconds
167
+ max_cost 1.00
680
168
 
681
- def user_prompt
682
- query
683
- end
169
+ step :classify, agent: ClassifierAgent
170
+ step :enrich, agent: EnricherAgent
171
+ step :format, agent: FormatterAgent, optional: true
684
172
  end
685
- ```
686
-
687
- Circuit breaker states:
688
- - **Closed** - Normal operation, requests pass through
689
- - **Open** - Model is blocked, requests skip to fallback or fail fast
690
- - **Half-Open** - After cooldown, one request is allowed to test recovery
691
173
 
692
- ### Total Timeout
174
+ result = ContentPipeline.call(text: data)
175
+ result.steps[:classify].content # Individual step result
176
+ result.total_cost # Sum of all steps
693
177
 
694
- Set a maximum time for the entire operation including all retries:
695
-
696
- ```ruby
697
- class TimeBoundAgent < ApplicationAgent
698
- model "gpt-4o"
699
- retries max: 5
700
- fallback_models "gpt-4o-mini"
701
-
702
- # Abort everything after 30 seconds total
703
- total_timeout 30
704
-
705
- param :query, required: true
178
+ # Parallel execution - run agents concurrently
179
+ class AnalysisPipeline < RubyLLM::Agents::Workflow::Parallel
180
+ fail_fast false # Continue even if a branch fails
706
181
 
707
- def user_prompt
708
- query
709
- end
182
+ branch :sentiment, agent: SentimentAgent
183
+ branch :entities, agent: EntityAgent
184
+ branch :summary, agent: SummaryAgent
710
185
  end
711
- ```
712
186
 
713
- ### Viewing Attempt Details
187
+ result = AnalysisPipeline.call(text: content)
188
+ result.branches[:sentiment].content # Individual branch result
714
189
 
715
- When reliability features are enabled, the dashboard shows all attempts:
190
+ # Conditional routing - dispatch based on classification
191
+ class SupportRouter < RubyLLM::Agents::Workflow::Router
192
+ classifier_model "gpt-4o-mini"
716
193
 
717
- ```ruby
718
- execution = RubyLLM::Agents::Execution.last
719
-
720
- # Check if retries/fallbacks were used
721
- execution.has_retries? # => true
722
- execution.used_fallback? # => true
723
- execution.attempts_count # => 3
724
-
725
- # Get attempt details
726
- execution.attempts.each do |attempt|
727
- puts "Model: #{attempt['model_id']}"
728
- puts "Duration: #{attempt['duration_ms']}ms"
729
- puts "Error: #{attempt['error_class']}" if attempt['error_class']
730
- puts "Short-circuited: #{attempt['short_circuited']}"
194
+ route :support, to: SupportAgent, description: "Technical support issues"
195
+ route :sales, to: SalesAgent, description: "Sales and pricing questions"
196
+ route :default, to: GeneralAgent
731
197
  end
732
198
 
733
- # Find the successful attempt
734
- execution.successful_attempt # => Hash with attempt data
735
- execution.chosen_model_id # => "claude-3-sonnet" (the model that succeeded)
199
+ result = SupportRouter.call(message: user_input)
200
+ result.routed_to # :support, :sales, or :default
736
201
  ```
737
202
 
738
- ## Governance & Cost Controls
739
-
740
- ### Budget Limits
203
+ ## Cost & Budget Controls
741
204
 
742
- Set spending limits at global and per-agent levels:
205
+ Track and limit LLM spending:
743
206
 
744
207
  ```ruby
745
208
  # config/initializers/ruby_llm_agents.rb
746
209
  RubyLLM::Agents.configure do |config|
747
210
  config.budgets = {
748
- # Global limits apply to all agents combined
749
- global_daily: 100.0, # $100/day across all agents
750
- global_monthly: 2000.0, # $2000/month across all agents
751
-
752
- # Per-agent limits
211
+ global_daily: 100.0, # $100/day limit
212
+ global_monthly: 2000.0, # $2000/month limit
753
213
  per_agent_daily: {
754
- "ExpensiveAgent" => 50.0, # $50/day for this agent
755
- "CheapAgent" => 5.0 # $5/day for this agent
214
+ "ExpensiveAgent" => 50.0
756
215
  },
757
- per_agent_monthly: {
758
- "ExpensiveAgent" => 500.0
759
- },
760
-
761
- # Enforcement mode
762
- # :hard - Block requests when budget exceeded
763
- # :soft - Allow requests but log warnings
764
- enforcement: :hard
216
+ enforcement: :hard # Block when exceeded
765
217
  }
766
- end
767
- ```
768
218
 
769
- Querying budget status:
770
-
771
- ```ruby
772
- # Get current budget status
773
- status = RubyLLM::Agents::BudgetTracker.status(agent_type: "MyAgent")
774
- # => {
775
- # global_daily: { limit: 100.0, current: 45.50, remaining: 54.50, percentage_used: 45.5 },
776
- # global_monthly: { limit: 2000.0, current: 890.0, remaining: 1110.0, percentage_used: 44.5 }
777
- # }
778
-
779
- # Check remaining budget
780
- RubyLLM::Agents::BudgetTracker.remaining_budget(:global, :daily)
781
- # => 54.50
782
- ```
783
-
784
- ### Alerts
785
-
786
- Get notified when important events occur:
787
-
788
- ```ruby
789
- # config/initializers/ruby_llm_agents.rb
790
- RubyLLM::Agents.configure do |config|
791
219
  config.alerts = {
792
- # Events to alert on
793
- on_events: [
794
- :budget_soft_cap, # Budget threshold reached (configurable %)
795
- :budget_hard_cap, # Budget exceeded (with hard enforcement)
796
- :breaker_open # Circuit breaker opened
797
- ],
798
-
799
- # Slack webhook
800
- slack_webhook_url: ENV['SLACK_WEBHOOK_URL'],
801
-
802
- # Generic webhook (receives JSON payload)
803
- webhook_url: "https://your-app.com/webhooks/llm-alerts",
804
-
805
- # Custom handler
806
- custom: ->(event, payload) {
807
- # event: :budget_hard_cap
808
- # payload: { scope: :global_daily, limit: 100.0, current: 105.0 }
809
-
810
- MyNotificationService.notify(
811
- title: "LLM Budget Alert",
812
- message: "#{event}: #{payload}"
813
- )
814
- }
815
- }
816
- end
817
- ```
818
-
819
- Alert payload examples:
820
-
821
- ```ruby
822
- # Budget alert
823
- {
824
- event: :budget_hard_cap,
825
- scope: :global_daily,
826
- limit: 100.0,
827
- current: 105.50,
828
- agent_type: "ExpensiveAgent"
829
- }
830
-
831
- # Circuit breaker alert
832
- {
833
- event: :breaker_open,
834
- agent_type: "MyAgent",
835
- model_id: "gpt-4o",
836
- failure_count: 10,
837
- window_seconds: 60
838
- }
839
- ```
840
-
841
- ### PII Redaction
842
-
843
- Automatically redact sensitive data from execution logs:
844
-
845
- ```ruby
846
- # config/initializers/ruby_llm_agents.rb
847
- RubyLLM::Agents.configure do |config|
848
- config.redaction = {
849
- # Fields to redact (applied to parameters)
850
- # Default: password, token, api_key, secret, credential, auth, key, access_token
851
- fields: %w[ssn credit_card phone_number],
852
-
853
- # Regex patterns to redact from prompts/responses
854
- patterns: [
855
- /\b\d{3}-\d{2}-\d{4}\b/, # SSN
856
- /\b\d{16}\b/, # Credit card
857
- /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i # Email
858
- ],
859
-
860
- # Replacement text
861
- placeholder: "[REDACTED]",
862
-
863
- # Truncate long values
864
- max_value_length: 1000
220
+ on_events: [:budget_soft_cap, :budget_hard_cap, :breaker_open],
221
+ slack_webhook_url: ENV['SLACK_WEBHOOK_URL']
865
222
  }
866
-
867
- # Control what gets persisted
868
- config.persist_prompts = true # Store system/user prompts
869
- config.persist_responses = true # Store LLM responses
870
- end
871
- ```
872
-
873
- ## Configuration
874
-
875
- Edit `config/initializers/ruby_llm_agents.rb`:
876
-
877
- ```ruby
878
- RubyLLM::Agents.configure do |config|
879
- # ============================================================================
880
- # Default Settings for All Agents
881
- # ============================================================================
882
-
883
- # Default LLM model (can be overridden per agent)
884
- config.default_model = "gemini-2.0-flash"
885
-
886
- # Default temperature (0.0 = deterministic, 1.0 = creative)
887
- config.default_temperature = 0.0
888
-
889
- # Default timeout for LLM requests (in seconds)
890
- config.default_timeout = 60
891
-
892
- # Enable streaming by default for all agents
893
- config.default_streaming = false
894
-
895
- # ============================================================================
896
- # Caching Configuration
897
- # ============================================================================
898
-
899
- # Cache store for agent responses (default: Rails.cache)
900
- config.cache_store = Rails.cache
901
- # config.cache_store = ActiveSupport::Cache::MemoryStore.new
902
- # config.cache_store = ActiveSupport::Cache::RedisCacheStore.new(url: ENV['REDIS_URL'])
903
-
904
- # ============================================================================
905
- # Execution Logging
906
- # ============================================================================
907
-
908
- # Use background job for logging (recommended for production)
909
- config.async_logging = true
910
-
911
- # How long to retain execution records (for cleanup tasks)
912
- config.retention_period = 30.days
913
-
914
- # ============================================================================
915
- # Anomaly Detection
916
- # ============================================================================
917
-
918
- # Log warning if an execution costs more than this (in dollars)
919
- config.anomaly_cost_threshold = 5.00
920
-
921
- # Log warning if an execution takes longer than this (in milliseconds)
922
- config.anomaly_duration_threshold = 10_000 # 10 seconds
923
-
924
- # ============================================================================
925
- # Dashboard Configuration
926
- # ============================================================================
927
-
928
- # Authentication for dashboard access
929
- # Return true to allow access, false to deny
930
- config.dashboard_auth = ->(controller) {
931
- controller.current_user&.admin?
932
- }
933
-
934
- # Customize the parent controller for dashboard
935
- config.dashboard_parent_controller = "ApplicationController"
936
223
  end
937
224
  ```
938
225
 
939
226
  ## Dashboard
940
227
 
941
- ### Mounting the Dashboard
942
-
943
- The install generator automatically mounts the dashboard, but you can customize the path:
228
+ Mount the real-time monitoring dashboard:
944
229
 
945
230
  ```ruby
946
231
  # config/routes.rb
947
232
  mount RubyLLM::Agents::Engine => "/agents"
948
- # or
949
- mount RubyLLM::Agents::Engine => "/admin/ai-agents", as: "agents_dashboard"
950
- ```
951
-
952
- ### Dashboard Features
953
-
954
- The dashboard provides:
955
-
956
- 1. **Overview Page** (`/agents`)
957
- - Today's execution stats (total, success rate, failures)
958
- - Real-time cost tracking
959
- - Performance trends (7-day chart)
960
- - Top agents by usage
961
-
962
- 2. **Executions List** (`/agents/executions`)
963
- - Filterable by agent type, status, date range
964
- - Sortable by cost, duration, timestamp
965
- - Real-time updates via Turbo Streams
966
- - Search by parameters
967
-
968
- 3. **Execution Detail** (`/agents/executions/:id`)
969
- - Full system and user prompts
970
- - Complete LLM response
971
- - Token usage breakdown (input, output, cached)
972
- - Cost calculation
973
- - Execution metadata
974
- - Error details (if failed)
975
-
976
- ### Authentication
977
-
978
- Protect your dashboard by configuring authentication:
979
-
980
- ```ruby
981
- # config/initializers/ruby_llm_agents.rb
982
- RubyLLM::Agents.configure do |config|
983
- config.dashboard_auth = ->(controller) {
984
- # Example: Devise authentication
985
- controller.authenticate_user! && controller.current_user.admin?
986
-
987
- # Example: Basic auth
988
- # controller.authenticate_or_request_with_http_basic do |username, password|
989
- # username == ENV['DASHBOARD_USERNAME'] &&
990
- # password == ENV['DASHBOARD_PASSWORD']
991
- # end
992
-
993
- # Example: IP whitelist
994
- # ['127.0.0.1', '::1'].include?(controller.request.remote_ip)
995
- }
996
- end
997
- ```
998
-
999
- ## Analytics & Reporting
1000
-
1001
- Query execution data programmatically:
1002
-
1003
- ### Daily Reports
1004
-
1005
- ```ruby
1006
- # Get today's summary
1007
- report = RubyLLM::Agents::Execution.daily_report
1008
- # => {
1009
- # total_executions: 1250,
1010
- # successful: 1180,
1011
- # failed: 70,
1012
- # success_rate: 94.4,
1013
- # total_cost: 12.45,
1014
- # avg_duration_ms: 850,
1015
- # total_tokens: 450000
1016
- # }
1017
- ```
1018
-
1019
- ### Cost Analysis
1020
-
1021
- ```ruby
1022
- # Cost breakdown by agent for this week
1023
- costs = RubyLLM::Agents::Execution.cost_by_agent(period: :this_week)
1024
- # => [
1025
- # { agent_type: "SearchIntentAgent", total_cost: 5.67, executions: 450 },
1026
- # { agent_type: "ContentGeneratorAgent", total_cost: 3.21, executions: 120 }
1027
- # ]
1028
-
1029
- # Cost breakdown by model
1030
- costs = RubyLLM::Agents::Execution.cost_by_model(period: :today)
1031
- ```
1032
-
1033
- ### Agent Statistics
1034
-
1035
- ```ruby
1036
- # Stats for a specific agent
1037
- stats = RubyLLM::Agents::Execution.stats_for("SearchIntentAgent", period: :today)
1038
- # => {
1039
- # total: 150,
1040
- # successful: 145,
1041
- # failed: 5,
1042
- # success_rate: 96.67,
1043
- # avg_cost: 0.012,
1044
- # total_cost: 1.80,
1045
- # avg_duration_ms: 450,
1046
- # total_tokens: 75000
1047
- # }
1048
- ```
1049
-
1050
- ### Version Comparison
1051
-
1052
- ```ruby
1053
- # Compare two versions of an agent
1054
- comparison = RubyLLM::Agents::Execution.compare_versions(
1055
- "SearchIntentAgent",
1056
- "1.0",
1057
- "2.0",
1058
- period: :this_week
1059
- )
1060
- # => {
1061
- # "1.0" => { total: 450, success_rate: 94.2, avg_cost: 0.015 },
1062
- # "2.0" => { total: 550, success_rate: 96.8, avg_cost: 0.012 }
1063
- # }
1064
233
  ```
1065
234
 
1066
- ### Trend Analysis
235
+ ![RubyLLM Agents Dashboard](screenshot.png)
1067
236
 
1068
- ```ruby
1069
- # 7-day trend for an agent
1070
- trend = RubyLLM::Agents::Execution.trend_analysis(
1071
- agent_type: "SearchIntentAgent",
1072
- days: 7
1073
- )
1074
- # => [
1075
- # { date: "2024-01-01", executions: 120, cost: 1.45, avg_duration: 450 },
1076
- # { date: "2024-01-02", executions: 135, cost: 1.62, avg_duration: 430 },
1077
- # ...
1078
- # ]
1079
- ```
1080
-
1081
- ### Streaming Analytics
237
+ Features:
238
+ - Execution history with filtering and search
239
+ - Cost analytics by agent, model, and time period
240
+ - Performance trends and charts
241
+ - Token usage breakdowns
242
+ - Error tracking and debugging
1082
243
 
1083
- ```ruby
1084
- # Percentage of executions using streaming
1085
- RubyLLM::Agents::Execution.streaming_rate
1086
- # => 45.5
1087
-
1088
- # Average time-to-first-token for streaming executions (milliseconds)
1089
- RubyLLM::Agents::Execution.avg_time_to_first_token
1090
- # => 245.3
1091
- ```
1092
-
1093
- ### Scopes
1094
-
1095
- Chain scopes for complex queries:
1096
-
1097
- ```ruby
1098
- # All successful executions today
1099
- RubyLLM::Agents::Execution.today.successful
1100
-
1101
- # Failed executions for specific agent
1102
- RubyLLM::Agents::Execution.by_agent("SearchIntentAgent").failed
1103
-
1104
- # Expensive executions this week
1105
- RubyLLM::Agents::Execution.this_week.expensive(1.00) # cost > $1
1106
-
1107
- # Slow executions
1108
- RubyLLM::Agents::Execution.slow(5000) # duration > 5 seconds
1109
-
1110
- # Complex query
1111
- expensive_slow_failures = RubyLLM::Agents::Execution
1112
- .this_week
1113
- .by_agent("ContentGeneratorAgent")
1114
- .failed
1115
- .expensive(0.50)
1116
- .slow(3000)
1117
- .order(created_at: :desc)
1118
- ```
1119
-
1120
- ### Available Scopes
1121
-
1122
- ```ruby
1123
- # Time-based
1124
- .today
1125
- .this_week
1126
- .this_month
1127
- .yesterday
1128
-
1129
- # Status
1130
- .successful
1131
- .failed
1132
- .status_error
1133
- .status_timeout
1134
- .status_running
1135
-
1136
- # Agent/Model
1137
- .by_agent("AgentName")
1138
- .by_model("gpt-4o")
1139
-
1140
- # Performance
1141
- .expensive(threshold) # cost > threshold
1142
- .slow(milliseconds) # duration > ms
1143
-
1144
- # Token usage
1145
- .high_token_usage(threshold)
1146
-
1147
- # Streaming
1148
- .streaming
1149
- .non_streaming
1150
- ```
1151
-
1152
- ## Generators
1153
-
1154
- ### Agent Generator
1155
-
1156
- ```bash
1157
- # Basic agent
1158
- rails generate ruby_llm_agents:agent MyAgent
1159
-
1160
- # Agent with parameters
1161
- rails generate ruby_llm_agents:agent SearchAgent query:required limit:10 filters
1162
-
1163
- # Agent with custom model and temperature
1164
- rails generate ruby_llm_agents:agent ContentAgent \
1165
- topic:required \
1166
- --model=gpt-4o \
1167
- --temperature=0.7
1168
-
1169
- # Agent with caching
1170
- rails generate ruby_llm_agents:agent CachedAgent \
1171
- key:required \
1172
- --cache=1.hour
1173
- ```
1174
-
1175
- ### Install Generator
1176
-
1177
- ```bash
1178
- # Initial setup
1179
- rails generate ruby_llm_agents:install
1180
- ```
1181
-
1182
- ### Upgrade Generator
1183
-
1184
- ```bash
1185
- # Upgrade to latest schema (when gem is updated)
1186
- rails generate ruby_llm_agents:upgrade
1187
- rails db:migrate
1188
- ```
1189
-
1190
- This creates migrations for new features like:
1191
- - `system_prompt` and `user_prompt` columns for prompt persistence
1192
- - `attempts` JSONB column for reliability tracking
1193
- - `chosen_model_id` for fallback model tracking
1194
-
1195
- ## Background Jobs
1196
-
1197
- For production environments, enable async logging:
1198
-
1199
- ```ruby
1200
- # config/initializers/ruby_llm_agents.rb
1201
- RubyLLM::Agents.configure do |config|
1202
- config.async_logging = true
1203
- end
1204
- ```
1205
-
1206
- This uses `RubyLLM::Agents::ExecutionLoggerJob` to log executions in the background.
1207
-
1208
- Make sure you have a job processor running:
1209
-
1210
- ```bash
1211
- # Using Solid Queue (Rails 7.1+)
1212
- bin/jobs
1213
-
1214
- # Or Sidekiq
1215
- bundle exec sidekiq
1216
- ```
1217
-
1218
- ## Maintenance Tasks
1219
-
1220
- ### Cleanup Old Executions
1221
-
1222
- ```ruby
1223
- # In a rake task or scheduled job
1224
- retention_period = RubyLLM::Agents.configuration.retention_period
1225
- RubyLLM::Agents::Execution.where("created_at < ?", retention_period.ago).delete_all
1226
- ```
1227
-
1228
- ### Export Data
1229
-
1230
- ```ruby
1231
- # Export to CSV
1232
- require 'csv'
1233
-
1234
- CSV.open("agent_executions.csv", "wb") do |csv|
1235
- csv << ["Agent", "Status", "Cost", "Duration", "Timestamp"]
1236
-
1237
- RubyLLM::Agents::Execution.this_month.find_each do |execution|
1238
- csv << [
1239
- execution.agent_type,
1240
- execution.status,
1241
- execution.total_cost,
1242
- execution.duration_ms,
1243
- execution.created_at
1244
- ]
1245
- end
1246
- end
1247
- ```
1248
-
1249
- ## Testing
1250
-
1251
- ### RSpec Example
1252
-
1253
- ```ruby
1254
- # spec/agents/search_intent_agent_spec.rb
1255
- require 'rails_helper'
1256
-
1257
- RSpec.describe SearchIntentAgent do
1258
- describe ".call" do
1259
- it "extracts search intent from query" do
1260
- result = described_class.call(
1261
- query: "red summer dress under $50",
1262
- dry_run: true # Use dry_run for testing without API calls
1263
- )
1264
-
1265
- expect(result[:dry_run]).to be true
1266
- expect(result[:agent]).to eq("SearchIntentAgent")
1267
- end
1268
- end
1269
-
1270
- describe "parameter validation" do
1271
- it "requires query parameter" do
1272
- expect {
1273
- described_class.call(limit: 10)
1274
- }.to raise_error(ArgumentError, /missing required params/)
1275
- end
1276
-
1277
- it "uses default limit" do
1278
- agent = described_class.new(query: "test")
1279
- expect(agent.limit).to eq(10)
1280
- end
1281
- end
1282
- end
1283
- ```
1284
-
1285
- ### Mocking LLM Responses
1286
-
1287
- ```ruby
1288
- # spec/support/llm_helpers.rb
1289
- module LLMHelpers
1290
- def mock_llm_response(data)
1291
- response = instance_double(RubyLLM::Response, content: data)
1292
- allow_any_instance_of(RubyLLM::Chat).to receive(:ask).and_return(response)
1293
- end
1294
- end
1295
-
1296
- # In your spec
1297
- RSpec.describe SearchIntentAgent do
1298
- include LLMHelpers
1299
-
1300
- it "processes search intent" do
1301
- mock_llm_response({
1302
- refined_query: "red dress",
1303
- filters: ["color:red"],
1304
- category_id: 42
1305
- })
1306
-
1307
- result = described_class.call(query: "red summer dress")
1308
-
1309
- expect(result[:refined_query]).to eq("red dress")
1310
- expect(result[:filters]).to include("color:red")
1311
- end
1312
- end
1313
- ```
1314
-
1315
- ## Development
1316
-
1317
- After checking out the repo:
1318
-
1319
- ```bash
1320
- # Install dependencies
1321
- bin/setup
1322
-
1323
- # Run tests
1324
- bundle exec rake spec
1325
-
1326
- # Run linter
1327
- bundle exec standardrb
1328
-
1329
- # Fix linting issues
1330
- bundle exec standardrb --fix
1331
-
1332
- # Run console
1333
- bin/rails console
1334
- ```
1335
-
1336
- ## Troubleshooting
1337
-
1338
- ### Agent execution fails with timeout
1339
-
1340
- Increase timeout for specific agent:
1341
-
1342
- ```ruby
1343
- class SlowAgent < ApplicationAgent
1344
- timeout 120 # 2 minutes
1345
- end
1346
- ```
1347
-
1348
- ### Cache not working
1349
-
1350
- Ensure Rails cache is configured:
1351
-
1352
- ```ruby
1353
- # config/environments/production.rb
1354
- config.cache_store = :redis_cache_store, { url: ENV['REDIS_URL'] }
1355
- ```
1356
-
1357
- ### Dashboard not accessible
1358
-
1359
- Check route mounting and authentication:
1360
-
1361
- ```ruby
1362
- # config/routes.rb
1363
- mount RubyLLM::Agents::Engine => "/agents"
1364
-
1365
- # config/initializers/ruby_llm_agents.rb
1366
- config.dashboard_auth = ->(controller) { true } # Allow all (dev only!)
1367
- ```
1368
-
1369
- ### High costs
1370
-
1371
- Monitor and set limits:
1372
-
1373
- ```ruby
1374
- # config/initializers/ruby_llm_agents.rb
1375
- config.anomaly_cost_threshold = 1.00 # Alert at $1
244
+ ## Requirements
1376
245
 
1377
- # Check expensive executions
1378
- RubyLLM::Agents::Execution.this_week.expensive(0.50)
1379
- ```
246
+ - **Ruby** >= 3.1.0
247
+ - **Rails** >= 7.0
248
+ - **RubyLLM** >= 1.0
1380
249
 
1381
250
  ## Contributing
1382
251
 
1383
- Bug reports and pull requests are welcome on GitHub at https://github.com/adham90/ruby_llm-agents.
252
+ Bug reports and pull requests are welcome at [GitHub](https://github.com/adham90/ruby_llm-agents).
1384
253
 
1385
254
  1. Fork the repository
1386
- 2. Create your feature branch (`git checkout -b my-new-feature`)
1387
- 3. Commit your changes (`git commit -am 'Add some feature'`)
1388
- 4. Push to the branch (`git push origin my-new-feature`)
1389
- 5. Create a new Pull Request
255
+ 2. Create your feature branch (`git checkout -b my-feature`)
256
+ 3. Commit your changes (`git commit -am 'Add feature'`)
257
+ 4. Push to the branch (`git push origin my-feature`)
258
+ 5. Create a Pull Request
1390
259
 
1391
260
  ## License
1392
261
 
1393
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
262
+ The gem is available as open source under the [MIT License](https://opensource.org/licenses/MIT).
1394
263
 
1395
264
  ## Credits
1396
265
 
1397
- Built with ❤️ by [Adham Eldeeb](https://github.com/adham90)
266
+ Built with love by [Adham Eldeeb](https://github.com/adham90)
1398
267
 
1399
268
  Powered by [RubyLLM](https://github.com/crmne/ruby_llm)