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.
- checksums.yaml +4 -4
- data/README.md +88 -0
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +68 -4
- data/app/models/ruby_llm/agents/execution/analytics.rb +114 -13
- data/app/models/ruby_llm/agents/execution/scopes.rb +10 -0
- data/app/models/ruby_llm/agents/execution.rb +26 -58
- data/app/views/layouts/rubyllm/agents/application.html.erb +103 -352
- data/app/views/rubyllm/agents/agents/_agent.html.erb +87 -0
- data/app/views/rubyllm/agents/agents/index.html.erb +2 -71
- data/app/views/rubyllm/agents/agents/show.html.erb +349 -416
- data/app/views/rubyllm/agents/dashboard/_action_center.html.erb +7 -7
- data/app/views/rubyllm/agents/dashboard/_agent_comparison.html.erb +46 -0
- data/app/views/rubyllm/agents/dashboard/_budgets_bar.html.erb +0 -90
- data/app/views/rubyllm/agents/dashboard/_execution_item.html.erb +54 -39
- data/app/views/rubyllm/agents/dashboard/_now_strip.html.erb +79 -5
- data/app/views/rubyllm/agents/dashboard/_top_errors.html.erb +49 -0
- data/app/views/rubyllm/agents/dashboard/index.html.erb +76 -151
- data/app/views/rubyllm/agents/executions/show.html.erb +256 -93
- data/app/views/rubyllm/agents/settings/show.html.erb +1 -1
- data/app/views/rubyllm/agents/shared/_breadcrumbs.html.erb +48 -0
- data/app/views/rubyllm/agents/shared/_nav_link.html.erb +27 -0
- data/config/routes.rb +2 -0
- data/lib/generators/ruby_llm_agents/templates/add_tool_calls_migration.rb.tt +28 -0
- data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +7 -0
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +13 -0
- data/lib/ruby_llm/agents/base/caching.rb +43 -0
- data/lib/ruby_llm/agents/base/cost_calculation.rb +103 -0
- data/lib/ruby_llm/agents/base/dsl.rb +261 -0
- data/lib/ruby_llm/agents/base/execution.rb +206 -0
- data/lib/ruby_llm/agents/base/reliability_execution.rb +131 -0
- data/lib/ruby_llm/agents/base/response_building.rb +86 -0
- data/lib/ruby_llm/agents/base/tool_tracking.rb +57 -0
- data/lib/ruby_llm/agents/base.rb +19 -619
- data/lib/ruby_llm/agents/instrumentation.rb +36 -3
- data/lib/ruby_llm/agents/result.rb +235 -0
- data/lib/ruby_llm/agents/version.rb +1 -1
- data/lib/ruby_llm/agents.rb +1 -0
- metadata +15 -20
- data/app/channels/ruby_llm/agents/executions_channel.rb +0 -46
- data/app/javascript/ruby_llm/agents/controllers/filter_controller.js +0 -56
- data/app/javascript/ruby_llm/agents/controllers/index.js +0 -12
- data/app/javascript/ruby_llm/agents/controllers/refresh_controller.js +0 -83
- data/app/views/rubyllm/agents/dashboard/_now_strip_values.html.erb +0 -71
|
@@ -1,74 +1,153 @@
|
|
|
1
1
|
<div id="execution-detail" data-execution-id="<%= @execution.id %>" data-status="<%= @execution.status %>">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
stroke="currentColor"
|
|
8
|
-
viewBox="0 0 24 24"
|
|
9
|
-
>
|
|
10
|
-
<path
|
|
11
|
-
stroke-linecap="round"
|
|
12
|
-
stroke-linejoin="round"
|
|
13
|
-
stroke-width="2"
|
|
14
|
-
d="M10 19l-7-7m0 0l7-7m-7 7h18"
|
|
15
|
-
/>
|
|
16
|
-
</svg>
|
|
17
|
-
Back to Executions
|
|
18
|
-
<% end %>
|
|
19
|
-
</div>
|
|
2
|
+
<%= render "rubyllm/agents/shared/breadcrumbs", items: [
|
|
3
|
+
{ label: "Dashboard", path: ruby_llm_agents.root_path },
|
|
4
|
+
{ label: "Executions", path: ruby_llm_agents.executions_path },
|
|
5
|
+
{ label: "##{@execution.id}" }
|
|
6
|
+
] %>
|
|
20
7
|
|
|
21
8
|
<!-- Header -->
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
9
|
+
<%
|
|
10
|
+
# Collect secondary badges
|
|
11
|
+
secondary_badges = []
|
|
12
|
+
secondary_badges << { label: "Stream", color: "cyan" } if @execution.streaming?
|
|
13
|
+
secondary_badges << { label: "Cached", color: "purple" } if @execution.cache_hit
|
|
14
|
+
if @execution.finish_reason.present?
|
|
15
|
+
finish_color = case @execution.finish_reason
|
|
16
|
+
when 'stop' then 'green'
|
|
17
|
+
when 'length' then 'yellow'
|
|
18
|
+
when 'content_filter' then 'red'
|
|
19
|
+
when 'tool_calls' then 'blue'
|
|
20
|
+
else 'gray'
|
|
21
|
+
end
|
|
22
|
+
secondary_badges << { label: @execution.finish_reason, color: finish_color }
|
|
23
|
+
end
|
|
24
|
+
secondary_badges << { label: "Rate Limited", color: "orange" } if @execution.respond_to?(:rate_limited?) && @execution.rate_limited?
|
|
25
|
+
%>
|
|
26
|
+
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-4 sm:p-5 mb-6">
|
|
27
|
+
<!-- Desktop: Row 1 - Agent name + badges + buttons + date -->
|
|
28
|
+
<div class="hidden sm:flex sm:items-center sm:justify-between gap-4">
|
|
29
|
+
<div class="flex items-center gap-3 min-w-0">
|
|
30
|
+
<h2 class="text-lg font-bold text-gray-900 dark:text-gray-100 truncate">
|
|
31
|
+
<%= @execution.agent_type.gsub(/Agent$/, '') %>
|
|
32
|
+
</h2>
|
|
33
|
+
<%= render "rubyllm/agents/shared/status_badge", status: @execution.status, size: :md %>
|
|
34
|
+
<% if secondary_badges.any? %>
|
|
35
|
+
<div class="relative" x-data="{ showDetails: false }">
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
@mouseenter="showDetails = true"
|
|
39
|
+
@mouseleave="showDetails = false"
|
|
40
|
+
class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
|
|
41
|
+
>
|
|
42
|
+
+<%= secondary_badges.size %>
|
|
43
|
+
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
44
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
45
|
+
</svg>
|
|
46
|
+
</button>
|
|
47
|
+
<div
|
|
48
|
+
x-show="showDetails"
|
|
49
|
+
x-cloak
|
|
50
|
+
x-transition:enter="transition ease-out duration-100"
|
|
51
|
+
x-transition:enter-start="opacity-0 scale-95"
|
|
52
|
+
x-transition:enter-end="opacity-100 scale-100"
|
|
53
|
+
class="absolute left-0 mt-1 z-10 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 p-2 min-w-max"
|
|
54
|
+
>
|
|
55
|
+
<div class="flex flex-wrap gap-1.5">
|
|
56
|
+
<% secondary_badges.each do |badge| %>
|
|
57
|
+
<% badge_classes = case badge[:color]
|
|
58
|
+
when 'cyan' then 'bg-cyan-100 dark:bg-cyan-900/50 text-cyan-800 dark:text-cyan-300'
|
|
59
|
+
when 'purple' then 'bg-purple-100 dark:bg-purple-900/50 text-purple-800 dark:text-purple-300'
|
|
60
|
+
when 'green' then 'bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-300'
|
|
61
|
+
when 'yellow' then 'bg-yellow-100 dark:bg-yellow-900/50 text-yellow-800 dark:text-yellow-300'
|
|
62
|
+
when 'red' then 'bg-red-100 dark:bg-red-900/50 text-red-800 dark:text-red-300'
|
|
63
|
+
when 'blue' then 'bg-blue-100 dark:bg-blue-900/50 text-blue-800 dark:text-blue-300'
|
|
64
|
+
when 'orange' then 'bg-orange-100 dark:bg-orange-900/50 text-orange-800 dark:text-orange-300'
|
|
65
|
+
else 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300'
|
|
66
|
+
end %>
|
|
67
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium <%= badge_classes %>"><%= badge[:label] %></span>
|
|
68
|
+
<% end %>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
<% end %>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="flex items-center gap-3 flex-shrink-0">
|
|
75
|
+
<%= button_to rerun_execution_path(@execution, dry_run: true),
|
|
76
|
+
method: :post,
|
|
77
|
+
data: { turbo: false },
|
|
78
|
+
class: "inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors",
|
|
79
|
+
title: "Preview what would be sent without making an API call" do %>
|
|
80
|
+
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
81
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
82
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
|
83
|
+
</svg>
|
|
84
|
+
Dry Run
|
|
85
|
+
<% end %>
|
|
86
|
+
<button
|
|
87
|
+
type="button"
|
|
88
|
+
onclick="confirmRerun()"
|
|
89
|
+
class="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors"
|
|
90
|
+
title="Re-execute this agent with the same parameters"
|
|
91
|
+
>
|
|
92
|
+
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
93
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
|
|
94
|
+
</svg>
|
|
95
|
+
Rerun
|
|
96
|
+
</button>
|
|
97
|
+
<span class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap"><%= @execution.created_at.strftime("%b %d, %H:%M") %></span>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
43
100
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
<% end %>
|
|
55
|
-
</div>
|
|
101
|
+
<!-- Desktop: Row 2 - Info line + relative time -->
|
|
102
|
+
<div class="hidden sm:flex sm:items-center sm:justify-between mt-1.5">
|
|
103
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">
|
|
104
|
+
#<%= @execution.id %> · v<%= @execution.agent_version %>
|
|
105
|
+
<% if @execution.model_provider.present? %>
|
|
106
|
+
· <%= @execution.model_provider %>
|
|
107
|
+
<% end %>
|
|
108
|
+
</p>
|
|
109
|
+
<span class="text-xs text-gray-400 dark:text-gray-500"><%= time_ago_in_words(@execution.created_at) %> ago</span>
|
|
110
|
+
</div>
|
|
56
111
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
112
|
+
<!-- Mobile: Stacked layout -->
|
|
113
|
+
<div class="sm:hidden">
|
|
114
|
+
<h2 class="text-lg font-bold text-gray-900 dark:text-gray-100 truncate">
|
|
115
|
+
<%= @execution.agent_type.gsub(/Agent$/, '') %>
|
|
116
|
+
</h2>
|
|
117
|
+
<div class="flex flex-wrap items-center gap-2 mt-2">
|
|
118
|
+
<%= render "rubyllm/agents/shared/status_badge", status: @execution.status, size: :md %>
|
|
119
|
+
<% secondary_badges.each do |badge| %>
|
|
120
|
+
<% badge_classes = case badge[:color]
|
|
121
|
+
when 'cyan' then 'bg-cyan-100 dark:bg-cyan-900/50 text-cyan-800 dark:text-cyan-300'
|
|
122
|
+
when 'purple' then 'bg-purple-100 dark:bg-purple-900/50 text-purple-800 dark:text-purple-300'
|
|
123
|
+
when 'green' then 'bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-300'
|
|
124
|
+
when 'yellow' then 'bg-yellow-100 dark:bg-yellow-900/50 text-yellow-800 dark:text-yellow-300'
|
|
125
|
+
when 'red' then 'bg-red-100 dark:bg-red-900/50 text-red-800 dark:text-red-300'
|
|
126
|
+
when 'blue' then 'bg-blue-100 dark:bg-blue-900/50 text-blue-800 dark:text-blue-300'
|
|
127
|
+
when 'orange' then 'bg-orange-100 dark:bg-orange-900/50 text-orange-800 dark:text-orange-300'
|
|
128
|
+
else 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300'
|
|
129
|
+
end %>
|
|
130
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-medium <%= badge_classes %>"><%= badge[:label] %></span>
|
|
131
|
+
<% end %>
|
|
63
132
|
</div>
|
|
133
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mt-2">
|
|
134
|
+
#<%= @execution.id %> · v<%= @execution.agent_version %>
|
|
135
|
+
<% if @execution.model_provider.present? %>
|
|
136
|
+
· <%= @execution.model_provider %>
|
|
137
|
+
<% end %>
|
|
138
|
+
</p>
|
|
64
139
|
|
|
65
|
-
|
|
66
|
-
|
|
140
|
+
<!-- Mobile: Date + Buttons -->
|
|
141
|
+
<div class="mt-4 pt-4 border-t border-gray-100 dark:border-gray-700">
|
|
142
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
|
|
143
|
+
<%= @execution.created_at.strftime("%b %d, %Y at %H:%M") %>
|
|
144
|
+
<span class="text-gray-400 dark:text-gray-500">· <%= time_ago_in_words(@execution.created_at) %> ago</span>
|
|
145
|
+
</p>
|
|
67
146
|
<div class="flex items-center gap-2">
|
|
68
147
|
<%= button_to rerun_execution_path(@execution, dry_run: true),
|
|
69
148
|
method: :post,
|
|
70
149
|
data: { turbo: false },
|
|
71
|
-
class: "inline-flex items-center gap-1.5 px-3 py-
|
|
150
|
+
class: "flex-1 inline-flex items-center justify-center gap-1.5 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors",
|
|
72
151
|
title: "Preview what would be sent without making an API call" do %>
|
|
73
152
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
74
153
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
@@ -76,11 +155,10 @@
|
|
|
76
155
|
</svg>
|
|
77
156
|
Dry Run
|
|
78
157
|
<% end %>
|
|
79
|
-
|
|
80
158
|
<button
|
|
81
159
|
type="button"
|
|
82
160
|
onclick="confirmRerun()"
|
|
83
|
-
class="inline-flex items-center gap-1.5 px-3 py-
|
|
161
|
+
class="flex-1 inline-flex items-center justify-center gap-1.5 px-3 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors"
|
|
84
162
|
title="Re-execute this agent with the same parameters"
|
|
85
163
|
>
|
|
86
164
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -89,14 +167,6 @@
|
|
|
89
167
|
Rerun
|
|
90
168
|
</button>
|
|
91
169
|
</div>
|
|
92
|
-
|
|
93
|
-
<div class="text-right text-sm text-gray-500 dark:text-gray-400">
|
|
94
|
-
<p><%= @execution.created_at.strftime("%b %d, %Y at %H:%M") %></p>
|
|
95
|
-
|
|
96
|
-
<p class="text-xs text-gray-400 dark:text-gray-500">
|
|
97
|
-
<%= time_ago_in_words(@execution.created_at) %> ago
|
|
98
|
-
</p>
|
|
99
|
-
</div>
|
|
100
170
|
</div>
|
|
101
171
|
</div>
|
|
102
172
|
</div>
|
|
@@ -438,28 +508,116 @@
|
|
|
438
508
|
</div>
|
|
439
509
|
<% end %>
|
|
440
510
|
|
|
441
|
-
<!--
|
|
442
|
-
<% if @execution.
|
|
443
|
-
|
|
511
|
+
<!-- Tool Calls -->
|
|
512
|
+
<% if @execution.tool_calls.present? && @execution.tool_calls.any? %>
|
|
513
|
+
<% tool_call_count = @execution.tool_calls.size %>
|
|
514
|
+
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 mb-6" x-data="{ expanded: <%= tool_call_count <= 3 %> }">
|
|
444
515
|
<div class="flex items-center justify-between mb-4">
|
|
445
|
-
<
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
data-copy-json-original="<%= Base64.strict_encode64(JSON.pretty_generate(@execution.metadata)) %>"
|
|
450
|
-
class="copy-json-btn inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors"
|
|
451
|
-
>
|
|
452
|
-
<svg class="w-4 h-4 copy-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
453
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
|
|
454
|
-
</svg>
|
|
455
|
-
<svg class="w-4 h-4 check-icon hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
456
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
|
516
|
+
<div class="flex items-center gap-2">
|
|
517
|
+
<svg class="w-5 h-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
518
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
|
|
519
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
457
520
|
</svg>
|
|
458
|
-
<
|
|
459
|
-
|
|
521
|
+
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Tool Calls</h3>
|
|
522
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 dark:bg-blue-900/50 text-blue-800 dark:text-blue-300">
|
|
523
|
+
<%= tool_call_count %>
|
|
524
|
+
</span>
|
|
525
|
+
</div>
|
|
526
|
+
<div class="flex items-center gap-2">
|
|
527
|
+
<button
|
|
528
|
+
type="button"
|
|
529
|
+
data-copy-json="<%= Base64.strict_encode64(JSON.pretty_generate(@execution.tool_calls)) %>"
|
|
530
|
+
class="copy-json-btn inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors"
|
|
531
|
+
>
|
|
532
|
+
<svg class="w-4 h-4 copy-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
533
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
|
|
534
|
+
</svg>
|
|
535
|
+
<svg class="w-4 h-4 check-icon hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
536
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
|
537
|
+
</svg>
|
|
538
|
+
<span>Copy</span>
|
|
539
|
+
</button>
|
|
540
|
+
<% if tool_call_count > 3 %>
|
|
541
|
+
<button type="button" @click="expanded = !expanded" class="text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
|
|
542
|
+
<span x-text="expanded ? 'Collapse' : 'Expand'">Expand</span>
|
|
543
|
+
</button>
|
|
544
|
+
<% end %>
|
|
545
|
+
</div>
|
|
546
|
+
</div>
|
|
547
|
+
|
|
548
|
+
<div class="space-y-4" x-show="expanded" <%= tool_call_count > 3 ? 'x-cloak' : '' %>>
|
|
549
|
+
<% @execution.tool_calls.each_with_index do |tool_call, index| %>
|
|
550
|
+
<%
|
|
551
|
+
# Handle both symbol and string keys
|
|
552
|
+
tool_id = tool_call['id'] || tool_call[:id]
|
|
553
|
+
tool_name = tool_call['name'] || tool_call[:name]
|
|
554
|
+
tool_args = tool_call['arguments'] || tool_call[:arguments] || {}
|
|
555
|
+
%>
|
|
556
|
+
<div class="border border-gray-100 dark:border-gray-700 rounded-lg overflow-hidden">
|
|
557
|
+
<!-- Tool Call Header -->
|
|
558
|
+
<div class="bg-gray-50 dark:bg-gray-900/50 px-4 py-3 flex items-center justify-between">
|
|
559
|
+
<div class="flex items-center gap-3">
|
|
560
|
+
<span class="inline-flex items-center justify-center w-6 h-6 rounded-full bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 text-xs font-medium">
|
|
561
|
+
<%= index + 1 %>
|
|
562
|
+
</span>
|
|
563
|
+
<code class="text-sm font-semibold text-gray-900 dark:text-gray-100"><%= tool_name %></code>
|
|
564
|
+
</div>
|
|
565
|
+
<% if tool_id.present? %>
|
|
566
|
+
<span class="text-xs text-gray-400 dark:text-gray-500 font-mono truncate max-w-xs" title="<%= tool_id %>">
|
|
567
|
+
<%= tool_id.to_s.truncate(24) %>
|
|
568
|
+
</span>
|
|
569
|
+
<% end %>
|
|
570
|
+
</div>
|
|
571
|
+
|
|
572
|
+
<!-- Tool Call Arguments -->
|
|
573
|
+
<% if tool_args.present? && tool_args.any? %>
|
|
574
|
+
<div class="px-4 py-3">
|
|
575
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-2">Arguments</p>
|
|
576
|
+
<pre class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded-lg p-3 text-sm overflow-x-auto font-mono"><%= highlight_json(tool_args) %></pre>
|
|
577
|
+
</div>
|
|
578
|
+
<% else %>
|
|
579
|
+
<div class="px-4 py-3">
|
|
580
|
+
<p class="text-xs text-gray-400 dark:text-gray-500 italic">No arguments</p>
|
|
581
|
+
</div>
|
|
582
|
+
<% end %>
|
|
583
|
+
</div>
|
|
584
|
+
<% end %>
|
|
585
|
+
</div>
|
|
586
|
+
</div>
|
|
587
|
+
<% end %>
|
|
588
|
+
|
|
589
|
+
<!-- Metadata -->
|
|
590
|
+
<% if @execution.metadata.present? && @execution.metadata.any? %>
|
|
591
|
+
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 mb-6" x-data="{ expanded: false }">
|
|
592
|
+
<div class="flex items-center justify-between">
|
|
593
|
+
<div class="flex items-center gap-2">
|
|
594
|
+
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Metadata</h3>
|
|
595
|
+
<span class="text-xs text-gray-400 dark:text-gray-500">(<%= @execution.metadata.keys.count %> keys)</span>
|
|
596
|
+
</div>
|
|
597
|
+
<div class="flex items-center gap-2">
|
|
598
|
+
<button
|
|
599
|
+
type="button"
|
|
600
|
+
data-copy-json="<%= Base64.strict_encode64(JSON.pretty_generate(redact_for_display(@execution.metadata))) %>"
|
|
601
|
+
data-copy-json-original="<%= Base64.strict_encode64(JSON.pretty_generate(@execution.metadata)) %>"
|
|
602
|
+
class="copy-json-btn inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors"
|
|
603
|
+
>
|
|
604
|
+
<svg class="w-4 h-4 copy-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
605
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
|
|
606
|
+
</svg>
|
|
607
|
+
<svg class="w-4 h-4 check-icon hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
608
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
|
609
|
+
</svg>
|
|
610
|
+
<span>Copy</span>
|
|
611
|
+
</button>
|
|
612
|
+
<button type="button" @click="expanded = !expanded" class="text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
|
|
613
|
+
<span x-text="expanded ? 'Collapse' : 'Expand'">Expand</span>
|
|
614
|
+
</button>
|
|
615
|
+
</div>
|
|
616
|
+
</div>
|
|
617
|
+
<div x-show="expanded" x-cloak class="mt-4">
|
|
618
|
+
<pre id="metadata-masked" class="maskable-content bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded-lg p-4 text-sm overflow-x-auto font-mono"><%= highlight_json_redacted(@execution.metadata) %></pre>
|
|
619
|
+
<pre id="metadata-original" class="maskable-content hidden bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded-lg p-4 text-sm overflow-x-auto font-mono"><%= highlight_json(@execution.metadata) %></pre>
|
|
460
620
|
</div>
|
|
461
|
-
<pre id="metadata-masked" class="maskable-content bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded-lg p-4 text-sm overflow-x-auto font-mono"><%= highlight_json_redacted(@execution.metadata) %></pre>
|
|
462
|
-
<pre id="metadata-original" class="maskable-content hidden bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded-lg p-4 text-sm overflow-x-auto font-mono"><%= highlight_json(@execution.metadata) %></pre>
|
|
463
621
|
</div>
|
|
464
622
|
<% end %>
|
|
465
623
|
|
|
@@ -504,6 +662,7 @@
|
|
|
504
662
|
<span id="system-prompt-toggle">Expand</span>
|
|
505
663
|
</button>
|
|
506
664
|
</div>
|
|
665
|
+
<p id="system-prompt-preview" class="text-sm text-gray-600 dark:text-gray-300 font-mono bg-gray-50 dark:bg-gray-900 rounded-lg p-3 truncate"><%= @execution.system_prompt.truncate(150) %></p>
|
|
507
666
|
<pre id="system-prompt-content" class="hidden bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded-lg p-4 text-sm overflow-x-auto max-h-96 font-mono whitespace-pre-wrap"><%= @execution.system_prompt %></pre>
|
|
508
667
|
</div>
|
|
509
668
|
<% end %>
|
|
@@ -517,6 +676,7 @@
|
|
|
517
676
|
<span id="user-prompt-toggle">Expand</span>
|
|
518
677
|
</button>
|
|
519
678
|
</div>
|
|
679
|
+
<p id="user-prompt-preview" class="text-sm text-gray-600 dark:text-gray-300 font-mono bg-gray-50 dark:bg-gray-900 rounded-lg p-3 truncate"><%= @execution.user_prompt.truncate(150) %></p>
|
|
520
680
|
<pre id="user-prompt-content" class="hidden bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded-lg p-4 text-sm overflow-x-auto max-h-96 font-mono whitespace-pre-wrap"><%= @execution.user_prompt %></pre>
|
|
521
681
|
</div>
|
|
522
682
|
<% end %>
|
|
@@ -753,9 +913,12 @@
|
|
|
753
913
|
// Prompt toggle function
|
|
754
914
|
function togglePrompt(type) {
|
|
755
915
|
const content = document.getElementById(type + '-prompt-content');
|
|
916
|
+
const preview = document.getElementById(type + '-prompt-preview');
|
|
756
917
|
const toggle = document.getElementById(type + '-prompt-toggle');
|
|
757
|
-
content.classList.
|
|
758
|
-
|
|
918
|
+
const isHidden = content.classList.contains('hidden');
|
|
919
|
+
content.classList.toggle('hidden', !isHidden);
|
|
920
|
+
if (preview) preview.classList.toggle('hidden', isHidden);
|
|
921
|
+
toggle.textContent = isHidden ? 'Collapse' : 'Expand';
|
|
759
922
|
}
|
|
760
923
|
|
|
761
924
|
// Masking state (persisted in localStorage)
|
|
@@ -858,8 +1021,8 @@
|
|
|
858
1021
|
}
|
|
859
1022
|
});
|
|
860
1023
|
|
|
861
|
-
// Diagnostics panel toggle
|
|
862
|
-
let diagnosticsExpanded = localStorage.getItem('ruby_llm_agents_diagnostics_expanded')
|
|
1024
|
+
// Diagnostics panel toggle (default to expanded)
|
|
1025
|
+
let diagnosticsExpanded = localStorage.getItem('ruby_llm_agents_diagnostics_expanded') !== 'false';
|
|
863
1026
|
|
|
864
1027
|
function toggleDiagnostics() {
|
|
865
1028
|
diagnosticsExpanded = !diagnosticsExpanded;
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">Parent Controller</p>
|
|
129
129
|
<p class="text-xs text-gray-500 dark:text-gray-400">Dashboard inherits from this</p>
|
|
130
130
|
</div>
|
|
131
|
-
<code class="bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 px-2 py-1 rounded text-
|
|
131
|
+
<code class="bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 px-2 py-1 rounded text-xs"><%= @config.dashboard_parent_controller %></code>
|
|
132
132
|
</div>
|
|
133
133
|
<div class="flex justify-between items-center">
|
|
134
134
|
<div>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<%
|
|
2
|
+
# Breadcrumb navigation partial
|
|
3
|
+
# Usage:
|
|
4
|
+
# render "rubyllm/agents/shared/breadcrumbs", items: [
|
|
5
|
+
# { label: "Dashboard", path: ruby_llm_agents.root_path },
|
|
6
|
+
# { label: "Executions", path: ruby_llm_agents.executions_path },
|
|
7
|
+
# { label: "#123" } # Current page (no path)
|
|
8
|
+
# ]
|
|
9
|
+
#
|
|
10
|
+
# Or with simple array:
|
|
11
|
+
# render "rubyllm/agents/shared/breadcrumbs", items: [
|
|
12
|
+
# ["Dashboard", ruby_llm_agents.root_path],
|
|
13
|
+
# ["Executions", ruby_llm_agents.executions_path],
|
|
14
|
+
# ["#123"]
|
|
15
|
+
# ]
|
|
16
|
+
|
|
17
|
+
items = local_assigns[:items] || []
|
|
18
|
+
%>
|
|
19
|
+
<nav class="flex items-center text-sm mb-4" aria-label="Breadcrumb">
|
|
20
|
+
<ol class="flex items-center space-x-1">
|
|
21
|
+
<% items.each_with_index do |item, index| %>
|
|
22
|
+
<%
|
|
23
|
+
# Normalize item format
|
|
24
|
+
if item.is_a?(Array)
|
|
25
|
+
label = item[0]
|
|
26
|
+
path = item[1]
|
|
27
|
+
else
|
|
28
|
+
label = item[:label]
|
|
29
|
+
path = item[:path]
|
|
30
|
+
end
|
|
31
|
+
is_last = index == items.length - 1
|
|
32
|
+
%>
|
|
33
|
+
<li class="flex items-center">
|
|
34
|
+
<% if index > 0 %>
|
|
35
|
+
<svg class="w-4 h-4 text-gray-400 dark:text-gray-500 mx-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
36
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
|
37
|
+
</svg>
|
|
38
|
+
<% end %>
|
|
39
|
+
|
|
40
|
+
<% if path.present? && !is_last %>
|
|
41
|
+
<%= link_to label, path, class: "text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors" %>
|
|
42
|
+
<% else %>
|
|
43
|
+
<span class="text-gray-900 dark:text-gray-100 font-medium"><%= label %></span>
|
|
44
|
+
<% end %>
|
|
45
|
+
</li>
|
|
46
|
+
<% end %>
|
|
47
|
+
</ol>
|
|
48
|
+
</nav>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<%# Reusable navigation link for desktop and mobile %>
|
|
2
|
+
<%
|
|
3
|
+
# Determine if link is active
|
|
4
|
+
is_active = if path == ruby_llm_agents.root_path
|
|
5
|
+
current_page?(path)
|
|
6
|
+
elsif path == ruby_llm_agents.agents_path
|
|
7
|
+
request.path.start_with?(path)
|
|
8
|
+
else
|
|
9
|
+
current_page?(path)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Style classes
|
|
13
|
+
base_classes = mobile ? "flex items-center px-3 py-2 text-base font-medium rounded-md" : "inline-flex items-center px-3 py-1.5 text-sm font-medium rounded-md"
|
|
14
|
+
active_classes = "bg-gray-200 dark:bg-gray-700 text-gray-900 dark:text-gray-100"
|
|
15
|
+
inactive_classes = "text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-700"
|
|
16
|
+
icon_classes = mobile ? "w-5 h-5 mr-3" : "w-4 h-4 mr-1.5"
|
|
17
|
+
|
|
18
|
+
link_options = { class: "#{base_classes} #{is_active ? active_classes : inactive_classes}" }
|
|
19
|
+
link_options["x-on:click"] = "mobileMenuOpen = false" if mobile
|
|
20
|
+
%>
|
|
21
|
+
|
|
22
|
+
<%= link_to path, **link_options do %>
|
|
23
|
+
<svg class="<%= icon_classes %>" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
24
|
+
<%= icon.html_safe %>
|
|
25
|
+
</svg>
|
|
26
|
+
<%= label %>
|
|
27
|
+
<% end %>
|
data/config/routes.rb
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Migration to add dedicated tool_calls column to executions
|
|
4
|
+
#
|
|
5
|
+
# This migration adds a dedicated column for storing tool call data,
|
|
6
|
+
# enabling easier querying and display of tool/function calls made
|
|
7
|
+
# during agent execution.
|
|
8
|
+
#
|
|
9
|
+
# Tool call structure:
|
|
10
|
+
# [
|
|
11
|
+
# { "id": "call_abc123", "name": "search", "arguments": { "query": "..." } },
|
|
12
|
+
# { "id": "call_def456", "name": "calculate", "arguments": { ... } }
|
|
13
|
+
# ]
|
|
14
|
+
#
|
|
15
|
+
# Run with: rails db:migrate
|
|
16
|
+
class AddToolCallsToRubyLLMAgentsExecutions < ActiveRecord::Migration<%= migration_version %>
|
|
17
|
+
def change
|
|
18
|
+
# Add tool_calls JSONB array for storing tool call details
|
|
19
|
+
# Each tool call contains: id, name, arguments
|
|
20
|
+
add_column :ruby_llm_agents_executions, :tool_calls, :jsonb, null: false, default: []
|
|
21
|
+
|
|
22
|
+
# Add counter for quick access to tool call count
|
|
23
|
+
add_column :ruby_llm_agents_executions, :tool_calls_count, :integer, null: false, default: 0
|
|
24
|
+
|
|
25
|
+
# Add index for querying executions that have tool calls
|
|
26
|
+
add_index :ruby_llm_agents_executions, :tool_calls_count
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -67,6 +67,10 @@ class CreateRubyLLMAgentsExecutions < ActiveRecord::Migration<%= migration_versi
|
|
|
67
67
|
t.text :system_prompt
|
|
68
68
|
t.text :user_prompt
|
|
69
69
|
|
|
70
|
+
# Tool calls tracking
|
|
71
|
+
t.jsonb :tool_calls, null: false, default: []
|
|
72
|
+
t.integer :tool_calls_count, null: false, default: 0
|
|
73
|
+
|
|
70
74
|
t.timestamps
|
|
71
75
|
end
|
|
72
76
|
|
|
@@ -89,6 +93,9 @@ class CreateRubyLLMAgentsExecutions < ActiveRecord::Migration<%= migration_versi
|
|
|
89
93
|
# Caching index
|
|
90
94
|
add_index :ruby_llm_agents_executions, :response_cache_key
|
|
91
95
|
|
|
96
|
+
# Tool calls index
|
|
97
|
+
add_index :ruby_llm_agents_executions, :tool_calls_count
|
|
98
|
+
|
|
92
99
|
# Foreign keys for execution hierarchy
|
|
93
100
|
add_foreign_key :ruby_llm_agents_executions, :ruby_llm_agents_executions,
|
|
94
101
|
column: :parent_execution_id, on_delete: :nullify
|
|
@@ -107,6 +107,19 @@ module RubyLlmAgents
|
|
|
107
107
|
)
|
|
108
108
|
end
|
|
109
109
|
|
|
110
|
+
def create_add_tool_calls_migration
|
|
111
|
+
# Check if columns already exist
|
|
112
|
+
if column_exists?(:ruby_llm_agents_executions, :tool_calls)
|
|
113
|
+
say_status :skip, "tool_calls column already exists", :yellow
|
|
114
|
+
return
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
migration_template(
|
|
118
|
+
"add_tool_calls_migration.rb.tt",
|
|
119
|
+
File.join(db_migrate_path, "add_tool_calls_to_ruby_llm_agents_executions.rb")
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
|
|
110
123
|
def show_post_upgrade_message
|
|
111
124
|
say ""
|
|
112
125
|
say "RubyLLM::Agents upgrade migration created!", :green
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Agents
|
|
5
|
+
class Base
|
|
6
|
+
# Cache management for agent responses
|
|
7
|
+
#
|
|
8
|
+
# Handles cache key generation and store access for
|
|
9
|
+
# caching agent execution results.
|
|
10
|
+
module Caching
|
|
11
|
+
# Returns the configured cache store
|
|
12
|
+
#
|
|
13
|
+
# @return [ActiveSupport::Cache::Store] The cache store
|
|
14
|
+
def cache_store
|
|
15
|
+
RubyLLM::Agents.configuration.cache_store
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Generates the full cache key for this agent invocation
|
|
19
|
+
#
|
|
20
|
+
# @return [String] Cache key in format "ruby_llm_agent/ClassName/version/hash"
|
|
21
|
+
def cache_key
|
|
22
|
+
["ruby_llm_agent", self.class.name, self.class.version, cache_key_hash].join("/")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Generates a hash of the cache key data
|
|
26
|
+
#
|
|
27
|
+
# @return [String] SHA256 hex digest of the cache key data
|
|
28
|
+
def cache_key_hash
|
|
29
|
+
Digest::SHA256.hexdigest(cache_key_data.to_json)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Returns data to include in cache key generation
|
|
33
|
+
#
|
|
34
|
+
# Override to customize what parameters affect cache invalidation.
|
|
35
|
+
#
|
|
36
|
+
# @return [Hash] Data to hash for cache key
|
|
37
|
+
def cache_key_data
|
|
38
|
+
@options.except(:skip_cache, :dry_run, :with)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|