ruby_llm-agents 0.3.1 → 0.3.4

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +88 -0
  3. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +68 -4
  4. data/app/models/ruby_llm/agents/execution/analytics.rb +114 -13
  5. data/app/models/ruby_llm/agents/execution/scopes.rb +10 -0
  6. data/app/models/ruby_llm/agents/execution.rb +26 -58
  7. data/app/views/layouts/rubyllm/agents/application.html.erb +103 -352
  8. data/app/views/rubyllm/agents/agents/_agent.html.erb +87 -0
  9. data/app/views/rubyllm/agents/agents/index.html.erb +2 -71
  10. data/app/views/rubyllm/agents/agents/show.html.erb +349 -416
  11. data/app/views/rubyllm/agents/dashboard/_action_center.html.erb +7 -7
  12. data/app/views/rubyllm/agents/dashboard/_agent_comparison.html.erb +46 -0
  13. data/app/views/rubyllm/agents/dashboard/_budgets_bar.html.erb +0 -90
  14. data/app/views/rubyllm/agents/dashboard/_execution_item.html.erb +54 -39
  15. data/app/views/rubyllm/agents/dashboard/_now_strip.html.erb +79 -5
  16. data/app/views/rubyllm/agents/dashboard/_top_errors.html.erb +49 -0
  17. data/app/views/rubyllm/agents/dashboard/index.html.erb +76 -151
  18. data/app/views/rubyllm/agents/executions/show.html.erb +256 -93
  19. data/app/views/rubyllm/agents/settings/show.html.erb +1 -1
  20. data/app/views/rubyllm/agents/shared/_breadcrumbs.html.erb +48 -0
  21. data/app/views/rubyllm/agents/shared/_nav_link.html.erb +27 -0
  22. data/config/routes.rb +2 -0
  23. data/lib/generators/ruby_llm_agents/templates/add_tool_calls_migration.rb.tt +28 -0
  24. data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +7 -0
  25. data/lib/generators/ruby_llm_agents/upgrade_generator.rb +13 -0
  26. data/lib/ruby_llm/agents/base/caching.rb +43 -0
  27. data/lib/ruby_llm/agents/base/cost_calculation.rb +103 -0
  28. data/lib/ruby_llm/agents/base/dsl.rb +261 -0
  29. data/lib/ruby_llm/agents/base/execution.rb +206 -0
  30. data/lib/ruby_llm/agents/base/reliability_execution.rb +131 -0
  31. data/lib/ruby_llm/agents/base/response_building.rb +86 -0
  32. data/lib/ruby_llm/agents/base/tool_tracking.rb +57 -0
  33. data/lib/ruby_llm/agents/base.rb +19 -619
  34. data/lib/ruby_llm/agents/instrumentation.rb +36 -3
  35. data/lib/ruby_llm/agents/result.rb +235 -0
  36. data/lib/ruby_llm/agents/version.rb +1 -1
  37. data/lib/ruby_llm/agents.rb +1 -0
  38. metadata +15 -20
  39. data/app/channels/ruby_llm/agents/executions_channel.rb +0 -46
  40. data/app/javascript/ruby_llm/agents/controllers/filter_controller.js +0 -56
  41. data/app/javascript/ruby_llm/agents/controllers/index.js +0 -12
  42. data/app/javascript/ruby_llm/agents/controllers/refresh_controller.js +0 -83
  43. data/app/views/rubyllm/agents/dashboard/_now_strip_values.html.erb +0 -71
@@ -4,224 +4,149 @@
4
4
  <!-- Now Strip -->
5
5
  <%= render partial: "rubyllm/agents/dashboard/now_strip", locals: { now_strip: @now_strip } %>
6
6
 
7
- <!-- Today's Activity Chart -->
8
- <div
9
- class="
10
- bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100
11
- dark:border-gray-700 mb-6
12
- "
13
- >
14
- <div class="px-6 py-4 border-b border-gray-100 dark:border-gray-700">
15
- <div class="flex justify-between items-center">
16
- <div class="flex items-center space-x-2">
17
- <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
18
- Activity
19
- </h3>
20
-
21
- <span class="flex items-center text-xs text-green-600 dark:text-green-400">
22
- <span class="w-1.5 h-1.5 bg-green-500 rounded-full mr-1 animate-pulse"></span>
23
- Live
24
- </span>
25
- </div>
26
-
27
- <span class="text-xs text-gray-400 dark:text-gray-500">
28
- Updates every 5s
29
- </span>
7
+ <!-- Activity Chart -->
8
+ <div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 mb-6">
9
+ <div class="px-6 py-3 border-b border-gray-100 dark:border-gray-700">
10
+ <div class="flex items-center justify-between">
11
+ <h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">Activity</h3>
12
+ <span id="chart-totals" class="text-sm text-gray-500 dark:text-gray-400"></span>
30
13
  </div>
31
14
  </div>
32
15
 
33
- <div
34
- id="hourly-chart"
35
- class="p-6 pb-3 pt-8"
36
- style="height: 400px;"
37
- data-chart-url="<%= ruby_llm_agents.chart_data_path %>"
38
- >
16
+ <div id="activity-chart-container" class="p-4 h-[320px] sm:h-[380px]">
39
17
  <div id="activity-chart" style="width: 100%; height: 100%;"></div>
40
18
  </div>
41
19
 
42
20
  <script>
43
21
  (function() {
44
- const chartUrl = '<%= ruby_llm_agents.chart_data_path %>';
45
- let activityChart = null;
22
+ const range = '<%= @selected_range %>';
23
+ const chartUrl = '<%= ruby_llm_agents.chart_data_path %>?range=' + range;
46
24
  const hourMs = 3600000;
47
25
  const dayMs = 24 * hourMs;
48
26
 
49
- // Convert server data to datetime points
50
- function convertToDatetimePoints(data) {
27
+ function convertToDatetimePoints(data, range) {
51
28
  const now = Date.now();
52
29
  const numPoints = data.length;
53
- return data.map((val, i) => {
54
- // Each point is 1 hour apart, last point at current hour
55
- return [now - (numPoints - 1 - i) * hourMs, val];
56
- });
30
+ if (range === 'today') {
31
+ return data.map((val, i) => [now - (numPoints - 1 - i) * hourMs, val]);
32
+ } else {
33
+ return data.map((val, i) => [now - (numPoints - 1 - i) * dayMs, val]);
34
+ }
57
35
  }
58
36
 
59
- function initLiveChart() {
37
+ function formatNumber(num) {
38
+ return num.toLocaleString();
39
+ }
40
+
41
+ function initChart() {
60
42
  fetch(chartUrl)
61
43
  .then(res => res.json())
62
44
  .then(data => {
63
45
  const now = Date.now();
64
- const successData = convertToDatetimePoints(data.series[0].data);
65
- const failedData = convertToDatetimePoints(data.series[1].data);
46
+ const successData = convertToDatetimePoints(data.series[0].data, range);
47
+ const failedData = convertToDatetimePoints(data.series[1].data, range);
48
+ const costData = convertToDatetimePoints(data.series[2].data, range);
66
49
 
67
- activityChart = Highcharts.chart('activity-chart', {
68
- chart: {
69
- type: 'spline',
70
- backgroundColor: 'transparent',
71
- animation: { duration: 300 }
72
- },
50
+ // Update totals in header
51
+ const totals = data.totals;
52
+ const totalExecs = totals.success + totals.failed;
53
+ document.getElementById('chart-totals').textContent =
54
+ formatNumber(totalExecs) + ' executions • $' + totals.cost.toFixed(2);
55
+
56
+ const timeRange = range === 'today' ? dayMs : (range === '7d' ? 7 * dayMs : 30 * dayMs);
57
+ const dateFormat = range === 'today' ? '%H:%M' : '%b %d';
58
+
59
+ Highcharts.chart('activity-chart', {
60
+ chart: { type: 'areaspline', backgroundColor: 'transparent' },
73
61
  title: { text: null },
74
62
  xAxis: {
75
63
  type: 'datetime',
76
- tickPixelInterval: 150,
77
- min: now - dayMs,
64
+ min: now - timeRange,
78
65
  max: now,
79
- labels: { style: { color: '#9CA3AF' } },
66
+ labels: { style: { color: '#9CA3AF' }, format: '{value:' + dateFormat + '}' },
80
67
  lineColor: 'rgba(156, 163, 175, 0.2)'
81
68
  },
82
- yAxis: {
69
+ yAxis: [{
83
70
  title: { text: null },
84
71
  min: 0,
85
72
  allowDecimals: false,
86
73
  labels: { style: { color: '#9CA3AF' } },
87
74
  gridLineColor: 'rgba(156, 163, 175, 0.2)'
88
- },
75
+ }, {
76
+ title: { text: null },
77
+ min: 0,
78
+ labels: { style: { color: '#F59E0B' }, format: '${value}' },
79
+ opposite: true,
80
+ gridLineWidth: 0
81
+ }],
89
82
  legend: {
83
+ enabled: true,
90
84
  align: 'center',
91
85
  verticalAlign: 'bottom',
92
- itemStyle: { color: '#9CA3AF' }
86
+ itemStyle: { color: '#9CA3AF', fontWeight: 'normal' },
87
+ itemHoverStyle: { color: '#D1D5DB' }
93
88
  },
94
89
  credits: { enabled: false },
95
90
  tooltip: {
96
- backgroundColor: 'rgba(17, 24, 39, 0.9)',
91
+ shared: true,
92
+ backgroundColor: 'rgba(17, 24, 39, 0.95)',
97
93
  borderColor: 'rgba(75, 85, 99, 0.5)',
98
94
  style: { color: '#F3F4F6' },
99
- headerFormat: '<b>{series.name}</b><br/>',
100
- pointFormat: '{point.x:%H:%M} - {point.y}'
95
+ xDateFormat: range === 'today' ? '%H:%M' : '%b %d'
101
96
  },
102
97
  plotOptions: {
98
+ areaspline: {
99
+ fillOpacity: 0.15,
100
+ lineWidth: 2,
101
+ marker: { enabled: false, states: { hover: { enabled: true, radius: 4 } } }
102
+ },
103
103
  spline: {
104
104
  lineWidth: 2,
105
- marker: {
106
- enabled: true,
107
- radius: 4,
108
- symbol: 'circle'
109
- }
105
+ marker: { enabled: false, states: { hover: { enabled: true, radius: 4 } } }
110
106
  }
111
107
  },
112
- series: [{
113
- name: 'Success',
114
- color: '#10B981',
115
- data: successData
116
- }, {
117
- name: 'Failed',
118
- color: '#EF4444',
119
- data: failedData
120
- }]
108
+ series: [
109
+ { name: 'Success', type: 'areaspline', color: '#10B981', data: successData, yAxis: 0 },
110
+ { name: 'Failed', type: 'areaspline', color: '#EF4444', data: failedData, yAxis: 0 },
111
+ { name: 'Cost', type: 'spline', color: '#F59E0B', data: costData, yAxis: 1, tooltip: { valuePrefix: '$' } }
112
+ ]
121
113
  });
122
-
123
- // Update chart every 5 seconds with fresh data
124
- setInterval(function() {
125
- fetch(chartUrl)
126
- .then(res => res.json())
127
- .then(newData => {
128
- const now = Date.now();
129
- const newSuccessData = convertToDatetimePoints(newData.series[0].data);
130
- const newFailedData = convertToDatetimePoints(newData.series[1].data);
131
-
132
- // Update x-axis range
133
- activityChart.xAxis[0].setExtremes(now - dayMs, now, false);
134
-
135
- // Replace series data
136
- activityChart.series[0].setData(newSuccessData, false);
137
- activityChart.series[1].setData(newFailedData, true);
138
- });
139
- }, 5000);
140
114
  })
141
- .catch(err => console.log('[RubyLLM::Agents] Chart init error:', err));
115
+ .catch(err => console.log('[RubyLLM::Agents] Chart error:', err));
142
116
  }
143
117
 
144
118
  if (document.readyState === 'loading') {
145
- document.addEventListener('DOMContentLoaded', initLiveChart);
119
+ document.addEventListener('DOMContentLoaded', initChart);
146
120
  } else {
147
- initLiveChart();
121
+ initChart();
148
122
  }
149
123
  })();
150
124
  </script>
151
125
  </div>
152
126
 
153
127
  <!-- Live Activity Feed -->
154
- <div
155
- class="
156
- bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100
157
- dark:border-gray-700
158
- "
159
- >
160
- <div class="px-6 py-4 border-b border-gray-100 dark:border-gray-700">
128
+ <div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 mb-6">
129
+ <div class="px-6 py-3 border-b border-gray-100 dark:border-gray-700">
161
130
  <div class="flex justify-between items-center">
162
- <div class="flex items-center space-x-3">
163
- <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
164
- Live Activity
165
- </h3>
166
-
167
- <div
168
- class="
169
- flex items-center space-x-3 text-xs text-gray-400
170
- dark:text-gray-500
171
- "
172
- >
173
- <span class="flex items-center">
174
- <span class="w-2 h-2 bg-blue-500 rounded-full mr-1 animate-pulse"></span>
175
- running
176
- </span>
177
-
178
- <span class="flex items-center">
179
- <span class="w-2 h-2 bg-green-500 rounded-full mr-1"></span>
180
- success
181
- </span>
182
-
183
- <span class="flex items-center">
184
- <span class="w-2 h-2 bg-red-500 rounded-full mr-1"></span>
185
- error
186
- </span>
187
-
188
- <span class="flex items-center">
189
- <span class="w-2 h-2 bg-yellow-500 rounded-full mr-1"></span>
190
- timeout
191
- </span>
192
- </div>
193
- </div>
194
-
131
+ <h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">Recent Activity</h3>
195
132
  <%= link_to "View All", ruby_llm_agents.executions_path, data: { turbo: false }, class: "text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 text-sm font-medium" %>
196
133
  </div>
197
134
  </div>
198
-
199
135
  <div id="activity-feed" class="px-6 py-2">
200
136
  <% if @recent_executions.any? %>
201
- <% @recent_executions.each do |execution| %>
137
+ <% @recent_executions.first(5).each do |execution| %>
202
138
  <%= render partial: "rubyllm/agents/dashboard/execution_item", locals: { execution: execution } %>
203
139
  <% end %>
204
140
  <% else %>
205
- <div
206
- id="empty-state"
207
- class="py-10 text-center text-gray-500 dark:text-gray-400"
208
- >
209
- <svg
210
- class="w-12 h-12 mx-auto mb-3 text-gray-300 dark:text-gray-600"
211
- fill="none"
212
- stroke="currentColor"
213
- viewBox="0 0 24 24"
214
- >
215
- <path
216
- stroke-linecap="round"
217
- stroke-linejoin="round"
218
- stroke-width="1.5"
219
- d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"
220
- />
221
- </svg>
222
-
223
- <p>No executions yet. Create an agent and make a call!</p>
141
+ <div class="py-8 text-center text-gray-500 dark:text-gray-400">
142
+ <p class="text-sm">No executions yet</p>
224
143
  </div>
225
144
  <% end %>
226
145
  </div>
227
146
  </div>
147
+
148
+ <!-- Agent Comparison + Top Errors -->
149
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
150
+ <%= render partial: "rubyllm/agents/dashboard/agent_comparison", locals: { agent_stats: @agent_stats } %>
151
+ <%= render partial: "rubyllm/agents/dashboard/top_errors", locals: { top_errors: @top_errors } %>
152
+ </div>