ruby_llm-agents 0.3.0 → 0.3.3
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 +228 -0
- data/app/models/ruby_llm/agents/execution/scopes.rb +10 -0
- data/app/models/ruby_llm/agents/execution.rb +7 -0
- data/app/views/layouts/rubyllm/agents/application.html.erb +11 -2
- 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 +20 -33
- data/app/views/rubyllm/agents/dashboard/_action_center.html.erb +7 -7
- data/app/views/rubyllm/agents/dashboard/_execution_item.html.erb +54 -39
- data/app/views/rubyllm/agents/dashboard/index.html.erb +4 -34
- data/app/views/rubyllm/agents/executions/show.html.erb +166 -52
- data/lib/generators/ruby_llm_agents/templates/add_tool_calls_migration.rb.tt +28 -0
- data/lib/generators/ruby_llm_agents/templates/initializer.rb.tt +4 -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.rb +227 -20
- data/lib/ruby_llm/agents/instrumentation.rb +36 -3
- data/lib/ruby_llm/agents/redactor.rb +1 -1
- 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 +4 -1
|
@@ -1,48 +1,63 @@
|
|
|
1
|
-
<div id="execution-<%= execution.id %>" class="py-
|
|
1
|
+
<div id="execution-<%= execution.id %>" class="py-2.5 hover:bg-gray-50 dark:hover:bg-gray-700/50 -mx-2 px-2 rounded-lg transition-colors">
|
|
2
2
|
<%= link_to ruby_llm_agents.execution_path(execution), data: { turbo: false }, class: "block" do %>
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
<div class="flex
|
|
3
|
+
<!-- Row 1: Status dot + Agent + Badges + Timestamp -->
|
|
4
|
+
<div class="flex items-center justify-between gap-2">
|
|
5
|
+
<div class="flex items-center gap-2 min-w-0">
|
|
6
6
|
<%= render "rubyllm/agents/shared/status_dot", status: execution.status %>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
<div class="flex items-center
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
7
|
+
<span class="font-medium text-sm text-gray-900 dark:text-gray-100 truncate">
|
|
8
|
+
<%= execution.agent_type.gsub(/Agent$/, '') %>
|
|
9
|
+
</span>
|
|
10
|
+
<span class="hidden sm:inline text-xs text-gray-400 dark:text-gray-500">v<%= execution.agent_version %></span>
|
|
11
|
+
<% unless execution.status_running? %>
|
|
12
|
+
<div class="hidden sm:flex items-center gap-1">
|
|
13
|
+
<% if execution.streaming? %>
|
|
14
|
+
<span class="badge badge-cyan text-[10px] px-1.5 py-0.5">Stream</span>
|
|
15
|
+
<% end %>
|
|
16
|
+
<% if execution.cache_hit? %>
|
|
17
|
+
<span class="badge badge-purple text-[10px] px-1.5 py-0.5">Cached</span>
|
|
18
|
+
<% end %>
|
|
19
|
+
<% if execution.rate_limited? %>
|
|
20
|
+
<span class="badge badge-orange text-[10px] px-1.5 py-0.5">Limited</span>
|
|
19
21
|
<% end %>
|
|
20
22
|
</div>
|
|
21
|
-
<p class="text-xs text-gray-400 dark:text-gray-500">
|
|
22
|
-
<%= time_ago_in_words(execution.created_at) %> ago
|
|
23
|
-
</p>
|
|
24
|
-
</div>
|
|
25
|
-
|
|
26
|
-
<% if execution.status_running? %>
|
|
27
|
-
<p class="text-xs text-blue-500 dark:text-blue-400 mt-1">
|
|
28
|
-
In progress...
|
|
29
|
-
</p>
|
|
30
|
-
<% else %>
|
|
31
|
-
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
|
32
|
-
<%= number_to_human_short(execution.total_tokens || 0) %> tokens
|
|
33
|
-
<span class="text-gray-300 dark:text-gray-600 mx-1">·</span>
|
|
34
|
-
<%= number_to_human_short(execution.total_cost || 0, prefix: "$", precision: 2) %>
|
|
35
|
-
<span class="text-gray-300 dark:text-gray-600 mx-1">·</span>
|
|
36
|
-
<%= number_to_human_short(execution.duration_ms || 0) %>ms
|
|
37
|
-
</p>
|
|
38
|
-
<% end %>
|
|
39
|
-
|
|
40
|
-
<% if execution.status_error? && execution.error_message.present? %>
|
|
41
|
-
<p class="text-xs text-red-600 dark:text-red-400 mt-1.5 bg-red-50 dark:bg-red-900/30 rounded px-2 py-1">
|
|
42
|
-
<%= execution.error_class %>: <%= truncate(execution.error_message, length: 80) %>
|
|
43
|
-
</p>
|
|
44
23
|
<% end %>
|
|
45
24
|
</div>
|
|
25
|
+
<span class="text-xs text-gray-400 dark:text-gray-500 whitespace-nowrap flex-shrink-0">
|
|
26
|
+
<%= time_ago_in_words(execution.created_at) %> ago
|
|
27
|
+
</span>
|
|
46
28
|
</div>
|
|
29
|
+
|
|
30
|
+
<!-- Row 2: Model + Metrics -->
|
|
31
|
+
<div class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
|
32
|
+
<% if execution.status_running? %>
|
|
33
|
+
<span class="text-blue-500 dark:text-blue-400 animate-pulse">Processing...</span>
|
|
34
|
+
<% else %>
|
|
35
|
+
<!-- Mobile: compact -->
|
|
36
|
+
<span class="sm:hidden">
|
|
37
|
+
<%= number_to_human_short(execution.total_tokens || 0) %> tokens · $<%= number_with_precision(execution.total_cost || 0, precision: 2) %>
|
|
38
|
+
</span>
|
|
39
|
+
<!-- Desktop: full -->
|
|
40
|
+
<span class="hidden sm:inline">
|
|
41
|
+
<span class="font-mono text-gray-400 dark:text-gray-500"><%= execution.model_id %></span>
|
|
42
|
+
<span class="mx-1.5 text-gray-300 dark:text-gray-600">·</span>
|
|
43
|
+
<%= number_to_human_short(execution.total_tokens || 0) %> tokens
|
|
44
|
+
<span class="mx-1.5 text-gray-300 dark:text-gray-600">·</span>
|
|
45
|
+
$<%= number_with_precision(execution.total_cost || 0, precision: 4) %>
|
|
46
|
+
<span class="mx-1.5 text-gray-300 dark:text-gray-600">·</span>
|
|
47
|
+
<%= number_to_human_short(execution.duration_ms || 0) %>ms
|
|
48
|
+
<% if execution.attempts_count && execution.attempts_count > 1 %>
|
|
49
|
+
<span class="mx-1.5 text-gray-300 dark:text-gray-600">·</span>
|
|
50
|
+
<span class="text-amber-600 dark:text-amber-400"><%= execution.attempts_count %> retries</span>
|
|
51
|
+
<% end %>
|
|
52
|
+
</span>
|
|
53
|
+
<% end %>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<!-- Error display -->
|
|
57
|
+
<% if execution.status_error? && execution.error_message.present? %>
|
|
58
|
+
<p class="mt-1.5 text-xs text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/30 rounded px-2 py-1">
|
|
59
|
+
<%= execution.error_class %>: <%= truncate(execution.error_message, length: 80) %>
|
|
60
|
+
</p>
|
|
61
|
+
<% end %>
|
|
47
62
|
<% end %>
|
|
48
63
|
</div>
|
|
@@ -32,8 +32,7 @@
|
|
|
32
32
|
|
|
33
33
|
<div
|
|
34
34
|
id="hourly-chart"
|
|
35
|
-
class="p-6 pb-3 pt-8"
|
|
36
|
-
style="height: 400px;"
|
|
35
|
+
class="p-6 pb-3 pt-8 h-[280px] sm:h-[400px]"
|
|
37
36
|
data-chart-url="<%= ruby_llm_agents.chart_data_path %>"
|
|
38
37
|
>
|
|
39
38
|
<div id="activity-chart" style="width: 100%; height: 100%;"></div>
|
|
@@ -159,38 +158,9 @@
|
|
|
159
158
|
>
|
|
160
159
|
<div class="px-6 py-4 border-b border-gray-100 dark:border-gray-700">
|
|
161
160
|
<div class="flex justify-between items-center">
|
|
162
|
-
<
|
|
163
|
-
|
|
164
|
-
|
|
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>
|
|
161
|
+
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
|
162
|
+
Live Activity
|
|
163
|
+
</h3>
|
|
194
164
|
|
|
195
165
|
<%= 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
166
|
</div>
|
|
@@ -19,56 +19,109 @@
|
|
|
19
19
|
</div>
|
|
20
20
|
|
|
21
21
|
<!-- Header -->
|
|
22
|
-
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
22
|
+
<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">
|
|
23
|
+
<!-- Desktop: Row 1 - Agent name + badges + buttons + date -->
|
|
24
|
+
<div class="hidden sm:flex sm:items-center sm:justify-between gap-4">
|
|
25
|
+
<div class="flex items-center gap-3 min-w-0">
|
|
26
|
+
<h2 class="text-lg font-bold text-gray-900 dark:text-gray-100 truncate">
|
|
27
|
+
<%= @execution.agent_type.gsub(/Agent$/, '') %>
|
|
28
|
+
</h2>
|
|
29
|
+
<%= render "rubyllm/agents/shared/status_badge", status: @execution.status, size: :md %>
|
|
30
|
+
<% if @execution.streaming? %>
|
|
31
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-cyan-100 dark:bg-cyan-900/50 text-cyan-800 dark:text-cyan-300">Stream</span>
|
|
32
|
+
<% end %>
|
|
33
|
+
<% if @execution.cache_hit %>
|
|
34
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-purple-100 dark:bg-purple-900/50 text-purple-800 dark:text-purple-300">Cached</span>
|
|
35
|
+
<% end %>
|
|
36
|
+
<% if @execution.finish_reason.present? %>
|
|
37
|
+
<% finish_colors = {
|
|
38
|
+
'stop' => 'bg-green-100 text-green-800 dark:bg-green-900/50 dark:text-green-300',
|
|
39
|
+
'length' => 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/50 dark:text-yellow-300',
|
|
40
|
+
'content_filter' => 'bg-red-100 text-red-800 dark:bg-red-900/50 dark:text-red-300',
|
|
41
|
+
'tool_calls' => 'bg-blue-100 text-blue-800 dark:bg-blue-900/50 dark:text-blue-300'
|
|
42
|
+
} %>
|
|
43
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium <%= finish_colors[@execution.finish_reason] || 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300' %>"><%= @execution.finish_reason %></span>
|
|
44
|
+
<% end %>
|
|
45
|
+
</div>
|
|
46
|
+
<div class="flex items-center gap-3 flex-shrink-0">
|
|
47
|
+
<%= button_to rerun_execution_path(@execution, dry_run: true),
|
|
48
|
+
method: :post,
|
|
49
|
+
data: { turbo: false },
|
|
50
|
+
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",
|
|
51
|
+
title: "Preview what would be sent without making an API call" do %>
|
|
52
|
+
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
53
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
54
|
+
<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"/>
|
|
55
|
+
</svg>
|
|
56
|
+
Dry Run
|
|
57
|
+
<% end %>
|
|
58
|
+
<button
|
|
59
|
+
type="button"
|
|
60
|
+
onclick="confirmRerun()"
|
|
61
|
+
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"
|
|
62
|
+
title="Re-execute this agent with the same parameters"
|
|
63
|
+
>
|
|
64
|
+
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
65
|
+
<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"/>
|
|
66
|
+
</svg>
|
|
67
|
+
Rerun
|
|
68
|
+
</button>
|
|
69
|
+
<span class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap"><%= @execution.created_at.strftime("%b %d, %H:%M") %></span>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
43
72
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
<% end %>
|
|
55
|
-
</div>
|
|
73
|
+
<!-- Desktop: Row 2 - Info line + relative time -->
|
|
74
|
+
<div class="hidden sm:flex sm:items-center sm:justify-between mt-1.5">
|
|
75
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">
|
|
76
|
+
#<%= @execution.id %> · v<%= @execution.agent_version %>
|
|
77
|
+
<% if @execution.model_provider.present? %>
|
|
78
|
+
· <%= @execution.model_provider %>
|
|
79
|
+
<% end %>
|
|
80
|
+
</p>
|
|
81
|
+
<span class="text-xs text-gray-400 dark:text-gray-500"><%= time_ago_in_words(@execution.created_at) %> ago</span>
|
|
82
|
+
</div>
|
|
56
83
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
84
|
+
<!-- Mobile: Stacked layout -->
|
|
85
|
+
<div class="sm:hidden">
|
|
86
|
+
<h2 class="text-lg font-bold text-gray-900 dark:text-gray-100 truncate">
|
|
87
|
+
<%= @execution.agent_type.gsub(/Agent$/, '') %>
|
|
88
|
+
</h2>
|
|
89
|
+
<div class="flex flex-wrap items-center gap-2 mt-2">
|
|
90
|
+
<%= render "rubyllm/agents/shared/status_badge", status: @execution.status, size: :md %>
|
|
91
|
+
<% if @execution.streaming? %>
|
|
92
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-medium bg-cyan-100 dark:bg-cyan-900/50 text-cyan-800 dark:text-cyan-300">Stream</span>
|
|
93
|
+
<% end %>
|
|
94
|
+
<% if @execution.cache_hit %>
|
|
95
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-medium bg-purple-100 dark:bg-purple-900/50 text-purple-800 dark:text-purple-300">Cached</span>
|
|
96
|
+
<% end %>
|
|
97
|
+
<% if @execution.finish_reason.present? %>
|
|
98
|
+
<% finish_colors = {
|
|
99
|
+
'stop' => 'bg-green-100 text-green-800 dark:bg-green-900/50 dark:text-green-300',
|
|
100
|
+
'length' => 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/50 dark:text-yellow-300',
|
|
101
|
+
'content_filter' => 'bg-red-100 text-red-800 dark:bg-red-900/50 dark:text-red-300',
|
|
102
|
+
'tool_calls' => 'bg-blue-100 text-blue-800 dark:bg-blue-900/50 dark:text-blue-300'
|
|
103
|
+
} %>
|
|
104
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-medium <%= finish_colors[@execution.finish_reason] || 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300' %>"><%= @execution.finish_reason %></span>
|
|
105
|
+
<% end %>
|
|
63
106
|
</div>
|
|
107
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mt-2">
|
|
108
|
+
#<%= @execution.id %> · v<%= @execution.agent_version %>
|
|
109
|
+
<% if @execution.model_provider.present? %>
|
|
110
|
+
· <%= @execution.model_provider %>
|
|
111
|
+
<% end %>
|
|
112
|
+
</p>
|
|
64
113
|
|
|
65
|
-
|
|
66
|
-
|
|
114
|
+
<!-- Mobile: Date + Buttons -->
|
|
115
|
+
<div class="mt-4 pt-4 border-t border-gray-100 dark:border-gray-700">
|
|
116
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
|
|
117
|
+
<%= @execution.created_at.strftime("%b %d, %Y at %H:%M") %>
|
|
118
|
+
<span class="text-gray-400 dark:text-gray-500">· <%= time_ago_in_words(@execution.created_at) %> ago</span>
|
|
119
|
+
</p>
|
|
67
120
|
<div class="flex items-center gap-2">
|
|
68
121
|
<%= button_to rerun_execution_path(@execution, dry_run: true),
|
|
69
122
|
method: :post,
|
|
70
123
|
data: { turbo: false },
|
|
71
|
-
class: "inline-flex items-center gap-1.5 px-3 py-
|
|
124
|
+
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
125
|
title: "Preview what would be sent without making an API call" do %>
|
|
73
126
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
74
127
|
<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 +129,10 @@
|
|
|
76
129
|
</svg>
|
|
77
130
|
Dry Run
|
|
78
131
|
<% end %>
|
|
79
|
-
|
|
80
132
|
<button
|
|
81
133
|
type="button"
|
|
82
134
|
onclick="confirmRerun()"
|
|
83
|
-
class="inline-flex items-center gap-1.5 px-3 py-
|
|
135
|
+
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
136
|
title="Re-execute this agent with the same parameters"
|
|
85
137
|
>
|
|
86
138
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -89,14 +141,6 @@
|
|
|
89
141
|
Rerun
|
|
90
142
|
</button>
|
|
91
143
|
</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
144
|
</div>
|
|
101
145
|
</div>
|
|
102
146
|
</div>
|
|
@@ -438,6 +482,76 @@
|
|
|
438
482
|
</div>
|
|
439
483
|
<% end %>
|
|
440
484
|
|
|
485
|
+
<!-- Tool Calls -->
|
|
486
|
+
<% if @execution.tool_calls.present? && @execution.tool_calls.any? %>
|
|
487
|
+
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 mb-6">
|
|
488
|
+
<div class="flex items-center justify-between mb-4">
|
|
489
|
+
<div class="flex items-center gap-2">
|
|
490
|
+
<svg class="w-5 h-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
491
|
+
<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"/>
|
|
492
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
493
|
+
</svg>
|
|
494
|
+
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Tool Calls</h3>
|
|
495
|
+
<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">
|
|
496
|
+
<%= @execution.tool_calls.size %>
|
|
497
|
+
</span>
|
|
498
|
+
</div>
|
|
499
|
+
<button
|
|
500
|
+
type="button"
|
|
501
|
+
data-copy-json="<%= Base64.strict_encode64(JSON.pretty_generate(@execution.tool_calls)) %>"
|
|
502
|
+
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"
|
|
503
|
+
>
|
|
504
|
+
<svg class="w-4 h-4 copy-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
505
|
+
<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"/>
|
|
506
|
+
</svg>
|
|
507
|
+
<svg class="w-4 h-4 check-icon hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
508
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
|
509
|
+
</svg>
|
|
510
|
+
<span>Copy</span>
|
|
511
|
+
</button>
|
|
512
|
+
</div>
|
|
513
|
+
|
|
514
|
+
<div class="space-y-4">
|
|
515
|
+
<% @execution.tool_calls.each_with_index do |tool_call, index| %>
|
|
516
|
+
<%
|
|
517
|
+
# Handle both symbol and string keys
|
|
518
|
+
tool_id = tool_call['id'] || tool_call[:id]
|
|
519
|
+
tool_name = tool_call['name'] || tool_call[:name]
|
|
520
|
+
tool_args = tool_call['arguments'] || tool_call[:arguments] || {}
|
|
521
|
+
%>
|
|
522
|
+
<div class="border border-gray-100 dark:border-gray-700 rounded-lg overflow-hidden">
|
|
523
|
+
<!-- Tool Call Header -->
|
|
524
|
+
<div class="bg-gray-50 dark:bg-gray-900/50 px-4 py-3 flex items-center justify-between">
|
|
525
|
+
<div class="flex items-center gap-3">
|
|
526
|
+
<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">
|
|
527
|
+
<%= index + 1 %>
|
|
528
|
+
</span>
|
|
529
|
+
<code class="text-sm font-semibold text-gray-900 dark:text-gray-100"><%= tool_name %></code>
|
|
530
|
+
</div>
|
|
531
|
+
<% if tool_id.present? %>
|
|
532
|
+
<span class="text-xs text-gray-400 dark:text-gray-500 font-mono truncate max-w-xs" title="<%= tool_id %>">
|
|
533
|
+
<%= tool_id.to_s.truncate(24) %>
|
|
534
|
+
</span>
|
|
535
|
+
<% end %>
|
|
536
|
+
</div>
|
|
537
|
+
|
|
538
|
+
<!-- Tool Call Arguments -->
|
|
539
|
+
<% if tool_args.present? && tool_args.any? %>
|
|
540
|
+
<div class="px-4 py-3">
|
|
541
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-2">Arguments</p>
|
|
542
|
+
<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>
|
|
543
|
+
</div>
|
|
544
|
+
<% else %>
|
|
545
|
+
<div class="px-4 py-3">
|
|
546
|
+
<p class="text-xs text-gray-400 dark:text-gray-500 italic">No arguments</p>
|
|
547
|
+
</div>
|
|
548
|
+
<% end %>
|
|
549
|
+
</div>
|
|
550
|
+
<% end %>
|
|
551
|
+
</div>
|
|
552
|
+
</div>
|
|
553
|
+
<% end %>
|
|
554
|
+
|
|
441
555
|
<!-- Metadata -->
|
|
442
556
|
<% if @execution.metadata.present? && @execution.metadata.any? %>
|
|
443
557
|
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 mb-6">
|
|
@@ -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
|
|
@@ -18,6 +18,10 @@ RubyLLM::Agents.configure do |config|
|
|
|
18
18
|
# Default timeout in seconds for each LLM request
|
|
19
19
|
# config.default_timeout = 60
|
|
20
20
|
|
|
21
|
+
# Enable streaming by default for all agents
|
|
22
|
+
# When enabled, agents stream responses and track time-to-first-token
|
|
23
|
+
# config.default_streaming = false
|
|
24
|
+
|
|
21
25
|
# ============================================
|
|
22
26
|
# Caching
|
|
23
27
|
# ============================================
|
|
@@ -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
|