ruby_llm-agents 3.0.0 → 3.1.0
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/app/models/ruby_llm/agents/execution.rb +1 -1
- data/app/views/layouts/ruby_llm/agents/application.html.erb +142 -11
- data/app/views/ruby_llm/agents/agents/show.html.erb +10 -10
- data/app/views/ruby_llm/agents/dashboard/index.html.erb +10 -10
- data/app/views/ruby_llm/agents/executions/show.html.erb +13 -0
- data/lib/generators/ruby_llm_agents/templates/add_assistant_prompt_migration.rb.tt +9 -0
- data/lib/generators/ruby_llm_agents/templates/split_execution_details_migration.rb.tt +2 -1
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +20 -1
- data/lib/ruby_llm/agents/base_agent.rb +2 -1
- data/lib/ruby_llm/agents/core/instrumentation.rb +39 -1
- data/lib/ruby_llm/agents/core/version.rb +1 -1
- data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +29 -0
- data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +78 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 552fe8427ac0b47f41b14e4652ac65eac1c7b0389a603b71576f8171aba504f7
|
|
4
|
+
data.tar.gz: d356d7fc391f93194bc0d4dc1cd179178361af5db8c6b00270c8cbd9bcea19b8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8d7d722cc72e9baa30a3da29f234ed039965f12743f541ae354f7d477aa7a9a5798c0639ddf37872fa1b67c58c7244b7490d0d9f548a03f3734e101a717153a9
|
|
7
|
+
data.tar.gz: 33f0e37172dbd4b607f86ca27d27b2be997b1650544dcbf41877dd00e094f66bde86b81afaa7aacbf31d7049eda2f9567f3a27b3970fad64c01113a6e436785b
|
|
@@ -74,7 +74,7 @@ module RubyLLM
|
|
|
74
74
|
foreign_key: :execution_id, dependent: :destroy
|
|
75
75
|
|
|
76
76
|
# Delegations so existing code keeps working transparently
|
|
77
|
-
delegate :system_prompt, :user_prompt, :response, :error_message,
|
|
77
|
+
delegate :system_prompt, :user_prompt, :assistant_prompt, :response, :error_message,
|
|
78
78
|
:messages_summary, :tool_calls, :attempts, :fallback_chain,
|
|
79
79
|
:parameters, :routed_to, :classification_result,
|
|
80
80
|
:cached_at, :cache_creation_tokens,
|
|
@@ -19,20 +19,151 @@
|
|
|
19
19
|
})();
|
|
20
20
|
</script>
|
|
21
21
|
|
|
22
|
+
<!-- Gruvbox-aware color palette: CSS custom properties swapped in .dark -->
|
|
23
|
+
<style>
|
|
24
|
+
:root {
|
|
25
|
+
/* Gray (Tailwind defaults) */
|
|
26
|
+
--c-gray-50: 249 250 251; --c-gray-100: 243 244 246; --c-gray-200: 229 231 235;
|
|
27
|
+
--c-gray-300: 209 213 219; --c-gray-400: 156 163 175; --c-gray-500: 107 114 128;
|
|
28
|
+
--c-gray-600: 75 85 99; --c-gray-700: 55 65 81; --c-gray-800: 31 41 55;
|
|
29
|
+
--c-gray-900: 17 24 39; --c-gray-950: 3 7 18;
|
|
30
|
+
/* Blue */
|
|
31
|
+
--c-blue-50: 239 246 255; --c-blue-100: 219 234 254; --c-blue-200: 191 219 254;
|
|
32
|
+
--c-blue-300: 147 197 253; --c-blue-400: 96 165 250; --c-blue-500: 59 130 246;
|
|
33
|
+
--c-blue-600: 37 99 235; --c-blue-700: 29 78 216; --c-blue-800: 30 64 175;
|
|
34
|
+
--c-blue-900: 30 58 138; --c-blue-950: 23 37 84;
|
|
35
|
+
/* Red */
|
|
36
|
+
--c-red-50: 254 242 242; --c-red-100: 254 226 226; --c-red-200: 254 202 202;
|
|
37
|
+
--c-red-300: 252 165 165; --c-red-400: 248 113 113; --c-red-500: 239 68 68;
|
|
38
|
+
--c-red-600: 220 38 38; --c-red-700: 185 28 28; --c-red-800: 153 27 27;
|
|
39
|
+
--c-red-900: 127 29 29; --c-red-950: 69 10 10;
|
|
40
|
+
/* Green */
|
|
41
|
+
--c-green-50: 240 253 244; --c-green-100: 220 252 231; --c-green-200: 187 247 208;
|
|
42
|
+
--c-green-300: 134 239 172; --c-green-400: 74 222 128; --c-green-500: 34 197 94;
|
|
43
|
+
--c-green-600: 22 163 74; --c-green-700: 21 128 61; --c-green-800: 22 101 52;
|
|
44
|
+
--c-green-900: 20 83 45; --c-green-950: 5 46 22;
|
|
45
|
+
/* Yellow */
|
|
46
|
+
--c-yellow-50: 254 252 232; --c-yellow-100: 254 249 195; --c-yellow-200: 254 240 138;
|
|
47
|
+
--c-yellow-300: 253 224 71; --c-yellow-400: 250 204 21; --c-yellow-500: 234 179 8;
|
|
48
|
+
--c-yellow-600: 202 138 4; --c-yellow-700: 161 98 7; --c-yellow-800: 133 77 14;
|
|
49
|
+
--c-yellow-900: 113 63 18; --c-yellow-950: 66 32 6;
|
|
50
|
+
/* Orange */
|
|
51
|
+
--c-orange-50: 255 247 237; --c-orange-100: 255 237 213; --c-orange-200: 254 215 170;
|
|
52
|
+
--c-orange-300: 253 186 116; --c-orange-400: 251 146 60; --c-orange-500: 249 115 22;
|
|
53
|
+
--c-orange-600: 234 88 12; --c-orange-700: 194 65 12; --c-orange-800: 154 52 18;
|
|
54
|
+
--c-orange-900: 124 45 18; --c-orange-950: 67 20 7;
|
|
55
|
+
/* Purple */
|
|
56
|
+
--c-purple-50: 250 245 255; --c-purple-100: 243 232 255; --c-purple-200: 233 213 255;
|
|
57
|
+
--c-purple-300: 216 180 254; --c-purple-400: 192 132 252; --c-purple-500: 168 85 247;
|
|
58
|
+
--c-purple-600: 147 51 234; --c-purple-700: 126 34 206; --c-purple-800: 107 33 168;
|
|
59
|
+
--c-purple-900: 88 28 135; --c-purple-950: 59 7 100;
|
|
60
|
+
/* Cyan */
|
|
61
|
+
--c-cyan-50: 236 254 255; --c-cyan-100: 207 250 254; --c-cyan-200: 165 243 252;
|
|
62
|
+
--c-cyan-300: 103 232 249; --c-cyan-400: 34 211 238; --c-cyan-500: 6 182 212;
|
|
63
|
+
--c-cyan-600: 8 145 178; --c-cyan-700: 14 116 144; --c-cyan-800: 21 94 117;
|
|
64
|
+
--c-cyan-900: 22 78 99; --c-cyan-950: 8 51 68;
|
|
65
|
+
/* Pink */
|
|
66
|
+
--c-pink-50: 253 242 248; --c-pink-100: 252 231 243; --c-pink-200: 251 207 232;
|
|
67
|
+
--c-pink-300: 249 168 212; --c-pink-400: 244 114 182; --c-pink-500: 236 72 153;
|
|
68
|
+
--c-pink-600: 219 39 119; --c-pink-700: 190 24 93; --c-pink-800: 157 23 77;
|
|
69
|
+
--c-pink-900: 131 24 67; --c-pink-950: 80 7 36;
|
|
70
|
+
/* Amber */
|
|
71
|
+
--c-amber-50: 255 251 235; --c-amber-100: 254 243 199; --c-amber-200: 253 230 138;
|
|
72
|
+
--c-amber-300: 252 211 77; --c-amber-400: 251 191 36; --c-amber-500: 245 158 11;
|
|
73
|
+
--c-amber-600: 217 119 6; --c-amber-700: 180 83 9; --c-amber-800: 146 64 14;
|
|
74
|
+
--c-amber-900: 120 53 15; --c-amber-950: 69 26 3;
|
|
75
|
+
}
|
|
76
|
+
.dark {
|
|
77
|
+
/* Gray → Gruvbox bg/fg */
|
|
78
|
+
--c-gray-50: 251 241 199; --c-gray-100: 235 219 178; --c-gray-200: 213 196 161;
|
|
79
|
+
--c-gray-300: 189 174 147; --c-gray-400: 189 174 147; --c-gray-500: 168 153 132;
|
|
80
|
+
--c-gray-600: 146 131 116; --c-gray-700: 102 92 84; --c-gray-800: 80 73 69;
|
|
81
|
+
--c-gray-900: 60 56 54; --c-gray-950: 40 40 40;
|
|
82
|
+
/* Blue → Gruvbox aqua (#83a598 / #458588) */
|
|
83
|
+
--c-blue-50: 195 215 205; --c-blue-100: 174 199 188; --c-blue-200: 153 182 170;
|
|
84
|
+
--c-blue-300: 131 165 152; --c-blue-400: 100 149 144; --c-blue-500: 69 133 136;
|
|
85
|
+
--c-blue-600: 58 112 115; --c-blue-700: 48 93 95; --c-blue-800: 38 73 75;
|
|
86
|
+
--c-blue-900: 28 55 57; --c-blue-950: 20 40 42;
|
|
87
|
+
/* Red → Gruvbox red (#fb4934 / #cc241d) */
|
|
88
|
+
--c-red-50: 253 165 155; --c-red-100: 253 135 122; --c-red-200: 252 104 87;
|
|
89
|
+
--c-red-300: 251 73 52; --c-red-400: 228 55 41; --c-red-500: 251 89 66;
|
|
90
|
+
--c-red-600: 204 36 29; --c-red-700: 136 24 19; --c-red-800: 102 18 14;
|
|
91
|
+
--c-red-900: 72 12 10; --c-red-950: 50 8 7;
|
|
92
|
+
/* Green → Gruvbox green (#b8bb26 / #98971a) */
|
|
93
|
+
--c-green-50: 225 227 145; --c-green-100: 213 215 112; --c-green-200: 199 201 75;
|
|
94
|
+
--c-green-300: 184 187 38; --c-green-400: 168 169 32; --c-green-500: 152 151 26;
|
|
95
|
+
--c-green-600: 126 125 22; --c-green-700: 101 100 17; --c-green-800: 77 77 13;
|
|
96
|
+
--c-green-900: 55 55 9; --c-green-950: 38 38 6;
|
|
97
|
+
/* Yellow → Gruvbox yellow (#fabd2f / #d79921) */
|
|
98
|
+
--c-yellow-50: 253 225 155; --c-yellow-100: 252 215 121; --c-yellow-200: 251 202 84;
|
|
99
|
+
--c-yellow-300: 250 189 47; --c-yellow-400: 233 171 40; --c-yellow-500: 215 153 33;
|
|
100
|
+
--c-yellow-600: 179 127 27; --c-yellow-700: 143 102 22; --c-yellow-800: 109 78 17;
|
|
101
|
+
--c-yellow-900: 77 55 12; --c-yellow-950: 53 38 8;
|
|
102
|
+
/* Orange → Gruvbox orange (#fe8019 / #d65d0e) */
|
|
103
|
+
--c-orange-50: 255 197 140; --c-orange-100: 255 172 102; --c-orange-200: 254 150 64;
|
|
104
|
+
--c-orange-300: 254 128 25; --c-orange-400: 234 111 20; --c-orange-500: 214 93 14;
|
|
105
|
+
--c-orange-600: 178 77 12; --c-orange-700: 142 62 9; --c-orange-800: 108 47 7;
|
|
106
|
+
--c-orange-900: 76 33 5; --c-orange-950: 52 23 3;
|
|
107
|
+
/* Purple → Gruvbox purple (#d3869b / #b16286) */
|
|
108
|
+
--c-purple-50: 235 186 200; --c-purple-100: 227 168 184; --c-purple-200: 219 151 170;
|
|
109
|
+
--c-purple-300: 211 134 155; --c-purple-400: 194 116 145; --c-purple-500: 177 98 134;
|
|
110
|
+
--c-purple-600: 147 81 111; --c-purple-700: 118 65 89; --c-purple-800: 90 50 68;
|
|
111
|
+
--c-purple-900: 63 35 48; --c-purple-950: 43 24 33;
|
|
112
|
+
/* Cyan → Gruvbox green-cyan (#8ec07c / #689d6a) */
|
|
113
|
+
--c-cyan-50: 194 228 184; --c-cyan-100: 177 216 164; --c-cyan-200: 160 204 144;
|
|
114
|
+
--c-cyan-300: 142 192 124; --c-cyan-400: 123 175 115; --c-cyan-500: 104 157 106;
|
|
115
|
+
--c-cyan-600: 86 131 88; --c-cyan-700: 69 105 70; --c-cyan-800: 53 80 54;
|
|
116
|
+
--c-cyan-900: 37 57 38; --c-cyan-950: 26 40 27;
|
|
117
|
+
/* Pink → Gruvbox purple (no pink in Gruvbox) */
|
|
118
|
+
--c-pink-50: 235 186 200; --c-pink-100: 227 168 184; --c-pink-200: 219 151 170;
|
|
119
|
+
--c-pink-300: 211 134 155; --c-pink-400: 194 116 145; --c-pink-500: 177 98 134;
|
|
120
|
+
--c-pink-600: 147 81 111; --c-pink-700: 118 65 89; --c-pink-800: 90 50 68;
|
|
121
|
+
--c-pink-900: 63 35 48; --c-pink-950: 43 24 33;
|
|
122
|
+
/* Amber → Gruvbox yellow-orange */
|
|
123
|
+
--c-amber-50: 254 224 155; --c-amber-100: 253 213 121; --c-amber-200: 252 198 84;
|
|
124
|
+
--c-amber-300: 251 184 47; --c-amber-400: 235 162 37; --c-amber-500: 215 140 26;
|
|
125
|
+
--c-amber-600: 179 116 22; --c-amber-700: 143 93 17; --c-amber-800: 109 71 13;
|
|
126
|
+
--c-amber-900: 77 50 9; --c-amber-950: 53 35 6;
|
|
127
|
+
}
|
|
128
|
+
</style>
|
|
129
|
+
|
|
22
130
|
<!-- Tailwind CSS via CDN -->
|
|
23
131
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
24
132
|
|
|
25
133
|
<script>
|
|
26
134
|
tailwind.config = {
|
|
27
|
-
darkMode: 'class'
|
|
135
|
+
darkMode: 'class',
|
|
136
|
+
theme: {
|
|
137
|
+
extend: {
|
|
138
|
+
colors: (function() {
|
|
139
|
+
var families = ['gray','blue','red','green','yellow','orange','purple','cyan','pink','amber'];
|
|
140
|
+
var shades = [50,100,200,300,400,500,600,700,800,900,950];
|
|
141
|
+
var c = {};
|
|
142
|
+
families.forEach(function(f) {
|
|
143
|
+
c[f] = {};
|
|
144
|
+
shades.forEach(function(s) {
|
|
145
|
+
c[f][s] = 'rgb(var(--c-' + f + '-' + s + ') / <alpha-value>)';
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
return c;
|
|
149
|
+
})()
|
|
150
|
+
}
|
|
151
|
+
}
|
|
28
152
|
}
|
|
29
153
|
</script>
|
|
30
154
|
|
|
31
155
|
<!-- Highcharts for charts -->
|
|
32
156
|
<script src="https://code.highcharts.com/highcharts.js"></script>
|
|
33
157
|
|
|
34
|
-
<!--
|
|
158
|
+
<!-- Chart color helpers + Highcharts defaults -->
|
|
35
159
|
<script>
|
|
160
|
+
function chartColor(lightHex, darkHex) {
|
|
161
|
+
return document.documentElement.classList.contains('dark') ? darkHex : lightHex;
|
|
162
|
+
}
|
|
163
|
+
function chartColorAlpha(lightRgba, darkR, darkG, darkB, alpha) {
|
|
164
|
+
return document.documentElement.classList.contains('dark')
|
|
165
|
+
? 'rgba(' + darkR + ',' + darkG + ',' + darkB + ',' + alpha + ')' : lightRgba;
|
|
166
|
+
}
|
|
36
167
|
Highcharts.setOptions({
|
|
37
168
|
credits: { enabled: false },
|
|
38
169
|
chart: {
|
|
@@ -41,22 +172,22 @@
|
|
|
41
172
|
},
|
|
42
173
|
title: { text: null },
|
|
43
174
|
xAxis: {
|
|
44
|
-
labels: { style: { color: '#6B7280' } },
|
|
45
|
-
lineColor: 'rgba(107, 114, 128, 0.1)',
|
|
46
|
-
tickColor: 'rgba(107, 114, 128, 0.1)'
|
|
175
|
+
labels: { style: { color: chartColor('#6B7280', '#928374') } },
|
|
176
|
+
lineColor: chartColorAlpha('rgba(107, 114, 128, 0.1)', 146, 131, 116, 0.1),
|
|
177
|
+
tickColor: chartColorAlpha('rgba(107, 114, 128, 0.1)', 146, 131, 116, 0.1)
|
|
47
178
|
},
|
|
48
179
|
yAxis: {
|
|
49
|
-
labels: { style: { color: '#6B7280' } },
|
|
50
|
-
gridLineColor: 'rgba(107, 114, 128, 0.1)'
|
|
180
|
+
labels: { style: { color: chartColor('#6B7280', '#928374') } },
|
|
181
|
+
gridLineColor: chartColorAlpha('rgba(107, 114, 128, 0.1)', 146, 131, 116, 0.1)
|
|
51
182
|
},
|
|
52
183
|
legend: {
|
|
53
|
-
itemStyle: { color: '#6B7280' },
|
|
54
|
-
itemHoverStyle: { color: '#9CA3AF' }
|
|
184
|
+
itemStyle: { color: chartColor('#6B7280', '#928374') },
|
|
185
|
+
itemHoverStyle: { color: chartColor('#9CA3AF', '#bdae93') }
|
|
55
186
|
},
|
|
56
187
|
tooltip: {
|
|
57
|
-
backgroundColor: 'rgba(0, 0, 0, 0.85)',
|
|
188
|
+
backgroundColor: chartColorAlpha('rgba(0, 0, 0, 0.85)', 40, 40, 40, 0.95),
|
|
58
189
|
borderColor: 'transparent',
|
|
59
|
-
style: { color: '#E5E7EB', fontFamily: 'ui-monospace, monospace' }
|
|
190
|
+
style: { color: chartColor('#E5E7EB', '#ebdbb2'), fontFamily: 'ui-monospace, monospace' }
|
|
60
191
|
}
|
|
61
192
|
});
|
|
62
193
|
</script>
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
title: { text: null },
|
|
110
110
|
xAxis: {
|
|
111
111
|
type: 'datetime',
|
|
112
|
-
labels: { style: { color: '#6B7280', fontSize: '9px', fontFamily: 'ui-monospace, monospace' }, format: '{value:%b %d}' },
|
|
112
|
+
labels: { style: { color: chartColor('#6B7280', '#928374'), fontSize: '9px', fontFamily: 'ui-monospace, monospace' }, format: '{value:%b %d}' },
|
|
113
113
|
lineColor: 'transparent',
|
|
114
114
|
tickLength: 0,
|
|
115
115
|
gridLineWidth: 0
|
|
@@ -118,19 +118,19 @@
|
|
|
118
118
|
title: { text: null },
|
|
119
119
|
min: 0,
|
|
120
120
|
allowDecimals: false,
|
|
121
|
-
labels: { style: { color: '#6B7280', fontSize: '9px', fontFamily: 'ui-monospace, monospace' } },
|
|
122
|
-
gridLineColor: 'rgba(107, 114, 128, 0.08)'
|
|
121
|
+
labels: { style: { color: chartColor('#6B7280', '#928374'), fontSize: '9px', fontFamily: 'ui-monospace, monospace' } },
|
|
122
|
+
gridLineColor: chartColorAlpha('rgba(107, 114, 128, 0.08)', 146, 131, 116, 0.08)
|
|
123
123
|
},
|
|
124
124
|
legend: { enabled: false },
|
|
125
125
|
credits: { enabled: false },
|
|
126
126
|
tooltip: {
|
|
127
|
-
backgroundColor: 'rgba(0, 0, 0, 0.85)',
|
|
127
|
+
backgroundColor: chartColorAlpha('rgba(0, 0, 0, 0.85)', 40, 40, 40, 0.95),
|
|
128
128
|
borderColor: 'transparent',
|
|
129
129
|
borderRadius: 3,
|
|
130
|
-
style: { color: '#E5E7EB', fontSize: '10px', fontFamily: 'ui-monospace, monospace' },
|
|
130
|
+
style: { color: chartColor('#E5E7EB', '#ebdbb2'), fontSize: '10px', fontFamily: 'ui-monospace, monospace' },
|
|
131
131
|
shared: true,
|
|
132
132
|
formatter: function() {
|
|
133
|
-
let html = '<span style="color
|
|
133
|
+
let html = '<span style="color:' + chartColor('#9CA3AF', '#bdae93') + '">' + Highcharts.dateFormat('%b %d', this.x) + '</span>';
|
|
134
134
|
let cost = trendData.find(d => new Date(d.date).getTime() === this.x);
|
|
135
135
|
this.points.forEach(p => html += '<br/>' + p.series.name + ': <b>' + p.y + '</b>');
|
|
136
136
|
if (cost) html += '<br/>cost: <b>$' + cost.cost.toFixed(4) + '</b>';
|
|
@@ -148,14 +148,14 @@
|
|
|
148
148
|
{
|
|
149
149
|
name: 'errors',
|
|
150
150
|
data: errorData,
|
|
151
|
-
color: '#EF4444',
|
|
152
|
-
fillColor: { linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 }, stops: [[0, 'rgba(239, 68, 68, 0.08)'], [1, 'rgba(239, 68, 68, 0)']] }
|
|
151
|
+
color: chartColor('#EF4444', '#fb4934'),
|
|
152
|
+
fillColor: { linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 }, stops: [[0, chartColorAlpha('rgba(239, 68, 68, 0.08)', 251, 73, 52, 0.08)], [1, chartColorAlpha('rgba(239, 68, 68, 0)', 251, 73, 52, 0)]] }
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
name: 'success',
|
|
156
156
|
data: successData,
|
|
157
|
-
color: '#10B981',
|
|
158
|
-
fillColor: { linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 }, stops: [[0, 'rgba(16, 185, 129, 0.08)'], [1, 'rgba(16, 185, 129, 0)']] }
|
|
157
|
+
color: chartColor('#10B981', '#b8bb26'),
|
|
158
|
+
fillColor: { linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 }, stops: [[0, chartColorAlpha('rgba(16, 185, 129, 0.08)', 184, 187, 38, 0.08)], [1, chartColorAlpha('rgba(16, 185, 129, 0)', 184, 187, 38, 0)]] }
|
|
159
159
|
}
|
|
160
160
|
]
|
|
161
161
|
});
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
type: 'datetime',
|
|
61
61
|
min: now - getTimeRangeMs(range),
|
|
62
62
|
max: now,
|
|
63
|
-
labels: { style: { color: '#6B7280', fontSize: '9px', fontFamily: 'ui-monospace, monospace' }, format: '{value:' + fmt + '}' },
|
|
63
|
+
labels: { style: { color: chartColor('#6B7280', '#928374'), fontSize: '9px', fontFamily: 'ui-monospace, monospace' }, format: '{value:' + fmt + '}' },
|
|
64
64
|
lineColor: 'transparent',
|
|
65
65
|
tickLength: 0,
|
|
66
66
|
gridLineWidth: 0
|
|
@@ -69,19 +69,19 @@
|
|
|
69
69
|
title: { text: null },
|
|
70
70
|
min: 0,
|
|
71
71
|
allowDecimals: false,
|
|
72
|
-
labels: { style: { color: '#6B7280', fontSize: '9px', fontFamily: 'ui-monospace, monospace' } },
|
|
73
|
-
gridLineColor: 'rgba(107, 114, 128, 0.08)'
|
|
72
|
+
labels: { style: { color: chartColor('#6B7280', '#928374'), fontSize: '9px', fontFamily: 'ui-monospace, monospace' } },
|
|
73
|
+
gridLineColor: chartColorAlpha('rgba(107, 114, 128, 0.08)', 146, 131, 116, 0.08)
|
|
74
74
|
},
|
|
75
75
|
legend: { enabled: false },
|
|
76
76
|
credits: { enabled: false },
|
|
77
77
|
tooltip: {
|
|
78
|
-
backgroundColor: 'rgba(0, 0, 0, 0.85)',
|
|
78
|
+
backgroundColor: chartColorAlpha('rgba(0, 0, 0, 0.85)', 40, 40, 40, 0.95),
|
|
79
79
|
borderColor: 'transparent',
|
|
80
80
|
borderRadius: 3,
|
|
81
|
-
style: { color: '#E5E7EB', fontSize: '10px', fontFamily: 'ui-monospace, monospace' },
|
|
81
|
+
style: { color: chartColor('#E5E7EB', '#ebdbb2'), fontSize: '10px', fontFamily: 'ui-monospace, monospace' },
|
|
82
82
|
shared: true,
|
|
83
83
|
formatter: function() {
|
|
84
|
-
let html = '<span style="color
|
|
84
|
+
let html = '<span style="color:' + chartColor('#9CA3AF', '#bdae93') + '">' + Highcharts.dateFormat(fmt, this.x) + '</span>';
|
|
85
85
|
this.points.forEach(p => html += '<br/>' + p.series.name + ': <b>' + p.y + '</b>');
|
|
86
86
|
return html;
|
|
87
87
|
}
|
|
@@ -97,14 +97,14 @@
|
|
|
97
97
|
{
|
|
98
98
|
name: 'errors',
|
|
99
99
|
data: toDatetimePoints(data.series[1].data, range),
|
|
100
|
-
color: '#EF4444',
|
|
101
|
-
fillColor: { linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 }, stops: [[0, 'rgba(239, 68, 68, 0.08)'], [1, 'rgba(239, 68, 68, 0)']] }
|
|
100
|
+
color: chartColor('#EF4444', '#fb4934'),
|
|
101
|
+
fillColor: { linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 }, stops: [[0, chartColorAlpha('rgba(239, 68, 68, 0.08)', 251, 73, 52, 0.08)], [1, chartColorAlpha('rgba(239, 68, 68, 0)', 251, 73, 52, 0)]] }
|
|
102
102
|
},
|
|
103
103
|
{
|
|
104
104
|
name: 'success',
|
|
105
105
|
data: toDatetimePoints(data.series[0].data, range),
|
|
106
|
-
color: '#10B981',
|
|
107
|
-
fillColor: { linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 }, stops: [[0, 'rgba(16, 185, 129, 0.08)'], [1, 'rgba(16, 185, 129, 0)']] }
|
|
106
|
+
color: chartColor('#10B981', '#b8bb26'),
|
|
107
|
+
fillColor: { linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 }, stops: [[0, chartColorAlpha('rgba(16, 185, 129, 0.08)', 184, 187, 38, 0.08)], [1, chartColorAlpha('rgba(16, 185, 129, 0)', 184, 187, 38, 0)]] }
|
|
108
108
|
}
|
|
109
109
|
]
|
|
110
110
|
});
|
|
@@ -439,6 +439,19 @@
|
|
|
439
439
|
<pre id="user-prompt-content" class="hidden bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded p-4 text-xs overflow-x-auto max-h-96 font-mono whitespace-pre-wrap"><%= @execution.user_prompt %></pre>
|
|
440
440
|
<% end %>
|
|
441
441
|
|
|
442
|
+
<!-- ── assistant prompt ──────────────────── -->
|
|
443
|
+
<% if @execution.respond_to?(:assistant_prompt) && @execution.assistant_prompt.present? %>
|
|
444
|
+
<div class="flex items-center gap-3 mt-6 mb-3">
|
|
445
|
+
<span class="text-[10px] font-medium text-gray-400 dark:text-gray-600 uppercase tracking-widest font-mono">assistant prompt</span>
|
|
446
|
+
<div class="flex-1 border-t border-gray-200 dark:border-gray-800"></div>
|
|
447
|
+
<button type="button" onclick="togglePrompt('assistant')" class="font-mono text-xs text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
|
|
448
|
+
<span id="assistant-prompt-toggle">expand</span>
|
|
449
|
+
</button>
|
|
450
|
+
</div>
|
|
451
|
+
<p id="assistant-prompt-preview" class="text-xs text-gray-600 dark:text-gray-300 font-mono bg-gray-50 dark:bg-gray-900 rounded p-3 truncate"><%= @execution.assistant_prompt.truncate(150) %></p>
|
|
452
|
+
<pre id="assistant-prompt-content" class="hidden bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 rounded p-4 text-xs overflow-x-auto max-h-96 font-mono whitespace-pre-wrap"><%= @execution.assistant_prompt %></pre>
|
|
453
|
+
<% end %>
|
|
454
|
+
|
|
442
455
|
<!-- ── conversation ──────────────────── -->
|
|
443
456
|
<% if @execution.respond_to?(:messages_count) && @execution.messages_count.to_i > 0 %>
|
|
444
457
|
<%
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddAssistantPromptToExecutionDetails < ActiveRecord::Migration<%= migration_version %>
|
|
4
|
+
def change
|
|
5
|
+
unless column_exists?(:ruby_llm_agents_execution_details, :assistant_prompt)
|
|
6
|
+
add_column :ruby_llm_agents_execution_details, :assistant_prompt, :text
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
class SplitExecutionDetailsFromExecutions < ActiveRecord::Migration<%= migration_version %>
|
|
10
10
|
# Columns that belong on execution_details, not executions
|
|
11
11
|
DETAIL_COLUMNS = %i[
|
|
12
|
-
error_message system_prompt user_prompt response messages_summary
|
|
12
|
+
error_message system_prompt user_prompt assistant_prompt response messages_summary
|
|
13
13
|
tool_calls attempts fallback_chain parameters routed_to
|
|
14
14
|
classification_result cached_at cache_creation_tokens
|
|
15
15
|
].freeze
|
|
@@ -50,6 +50,7 @@ class SplitExecutionDetailsFromExecutions < ActiveRecord::Migration<%= migration
|
|
|
50
50
|
t.text :error_message
|
|
51
51
|
t.text :system_prompt
|
|
52
52
|
t.text :user_prompt
|
|
53
|
+
t.text :assistant_prompt
|
|
53
54
|
t.json :response, default: {}
|
|
54
55
|
t.json :messages_summary, default: {}, null: false
|
|
55
56
|
t.json :tool_calls, default: [], null: false
|
|
@@ -60,6 +60,25 @@ module RubyLlmAgents
|
|
|
60
60
|
)
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
+
# Add assistant_prompt column to execution_details (v3.0 -> v3.1 upgrade)
|
|
64
|
+
def create_add_assistant_prompt_migration
|
|
65
|
+
if column_exists?(:ruby_llm_agents_execution_details, :assistant_prompt)
|
|
66
|
+
say_status :skip, "assistant_prompt column already exists on execution_details", :yellow
|
|
67
|
+
return
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
unless table_exists?(:ruby_llm_agents_execution_details)
|
|
71
|
+
say_status :skip, "execution_details table does not exist yet", :yellow
|
|
72
|
+
return
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
say_status :upgrade, "Adding assistant_prompt to execution_details", :blue
|
|
76
|
+
migration_template(
|
|
77
|
+
"add_assistant_prompt_migration.rb.tt",
|
|
78
|
+
File.join(db_migrate_path, "add_assistant_prompt_to_execution_details.rb")
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
63
82
|
def suggest_config_consolidation
|
|
64
83
|
ruby_llm_initializer = File.join(destination_root, "config/initializers/ruby_llm.rb")
|
|
65
84
|
agents_initializer = File.join(destination_root, "config/initializers/ruby_llm_agents.rb")
|
|
@@ -116,7 +135,7 @@ module RubyLlmAgents
|
|
|
116
135
|
|
|
117
136
|
# Detail columns that should only exist on execution_details, not executions
|
|
118
137
|
DETAIL_COLUMNS = %i[
|
|
119
|
-
error_message system_prompt user_prompt response messages_summary
|
|
138
|
+
error_message system_prompt user_prompt assistant_prompt response messages_summary
|
|
120
139
|
tool_calls attempts fallback_chain parameters routed_to
|
|
121
140
|
classification_result cached_at cache_creation_tokens
|
|
122
141
|
].freeze
|
|
@@ -275,6 +275,9 @@ module RubyLLM
|
|
|
275
275
|
system_prompt: config.persist_prompts ? stored_system_prompt : nil,
|
|
276
276
|
user_prompt: config.persist_prompts ? stored_user_prompt : nil
|
|
277
277
|
}
|
|
278
|
+
if config.persist_prompts && assistant_prompt_column_exists?
|
|
279
|
+
detail_data[:assistant_prompt] = stored_assistant_prompt
|
|
280
|
+
end
|
|
278
281
|
detail_data.merge!(@_pending_detail_data) if @_pending_detail_data
|
|
279
282
|
@_pending_detail_data = nil
|
|
280
283
|
|
|
@@ -528,7 +531,9 @@ module RubyLLM
|
|
|
528
531
|
user_prompt: safe_user_prompt,
|
|
529
532
|
messages_summary: config.persist_messages_summary ? messages_summary : {},
|
|
530
533
|
error_message: error&.message
|
|
531
|
-
}
|
|
534
|
+
}
|
|
535
|
+
detail_data[:assistant_prompt] = safe_assistant_prompt if assistant_prompt_column_exists?
|
|
536
|
+
detail_data.merge!(detail_fields || {})
|
|
532
537
|
|
|
533
538
|
execution_data[:_detail_data] = detail_data
|
|
534
539
|
|
|
@@ -634,6 +639,23 @@ module RubyLLM
|
|
|
634
639
|
nil
|
|
635
640
|
end
|
|
636
641
|
|
|
642
|
+
# Safely captures assistant prompt, handling errors gracefully
|
|
643
|
+
#
|
|
644
|
+
# @return [String, nil] The assistant prompt or nil if unavailable
|
|
645
|
+
def safe_assistant_prompt
|
|
646
|
+
respond_to?(:assistant_prompt) ? assistant_prompt&.to_s : nil
|
|
647
|
+
rescue StandardError => e
|
|
648
|
+
Rails.logger.warn("[RubyLLM::Agents] Could not capture assistant_prompt: #{e.message}")
|
|
649
|
+
nil
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
# Returns the assistant prompt for storage
|
|
653
|
+
#
|
|
654
|
+
# @return [String, nil] The assistant prompt
|
|
655
|
+
def stored_assistant_prompt
|
|
656
|
+
safe_assistant_prompt
|
|
657
|
+
end
|
|
658
|
+
|
|
637
659
|
# Safely extracts a value from response object
|
|
638
660
|
#
|
|
639
661
|
# @param response [Object] The response object
|
|
@@ -923,6 +945,22 @@ module RubyLLM
|
|
|
923
945
|
end
|
|
924
946
|
end
|
|
925
947
|
|
|
948
|
+
# Checks if the assistant_prompt column exists on execution_details
|
|
949
|
+
#
|
|
950
|
+
# Memoized to avoid repeated schema queries. Returns false for older installs
|
|
951
|
+
# that haven't run the migration yet.
|
|
952
|
+
#
|
|
953
|
+
# @return [Boolean] true if the column exists
|
|
954
|
+
def assistant_prompt_column_exists?
|
|
955
|
+
return @_assistant_prompt_column_exists if defined?(@_assistant_prompt_column_exists)
|
|
956
|
+
|
|
957
|
+
@_assistant_prompt_column_exists = begin
|
|
958
|
+
RubyLLM::Agents::ExecutionDetail.column_names.include?("assistant_prompt")
|
|
959
|
+
rescue StandardError
|
|
960
|
+
false
|
|
961
|
+
end
|
|
962
|
+
end
|
|
963
|
+
|
|
926
964
|
# Emergency fallback to mark execution as failed
|
|
927
965
|
#
|
|
928
966
|
# Uses update_all to bypass ActiveRecord callbacks and validations,
|
|
@@ -253,6 +253,13 @@ module RubyLLM
|
|
|
253
253
|
|
|
254
254
|
detail_data = {}
|
|
255
255
|
|
|
256
|
+
if global_config.persist_prompts
|
|
257
|
+
exec_opts = context.options[:options] || {}
|
|
258
|
+
detail_data[:system_prompt] = exec_opts[:system_prompt]
|
|
259
|
+
detail_data[:user_prompt] = context.input.to_s.presence
|
|
260
|
+
detail_data[:assistant_prompt] = exec_opts[:assistant_prefill] if assistant_prompt_column_exists?
|
|
261
|
+
end
|
|
262
|
+
|
|
256
263
|
if context.error
|
|
257
264
|
detail_data[:error_message] = truncate_error_message(context.error.message)
|
|
258
265
|
end
|
|
@@ -349,6 +356,12 @@ module RubyLLM
|
|
|
349
356
|
|
|
350
357
|
# Store detail data for separate creation
|
|
351
358
|
detail_data = { parameters: sanitize_parameters(context) }
|
|
359
|
+
if global_config.persist_prompts
|
|
360
|
+
exec_opts = context.options[:options] || {}
|
|
361
|
+
detail_data[:system_prompt] = exec_opts[:system_prompt]
|
|
362
|
+
detail_data[:user_prompt] = context.input.to_s.presence
|
|
363
|
+
detail_data[:assistant_prompt] = exec_opts[:assistant_prefill] if assistant_prompt_column_exists?
|
|
364
|
+
end
|
|
352
365
|
detail_data[:error_message] = truncate_error_message(context.error.message) if context.error
|
|
353
366
|
detail_data[:tool_calls] = context[:tool_calls] if context[:tool_calls].present?
|
|
354
367
|
detail_data[:attempts] = context[:reliability_attempts] if context[:reliability_attempts].present?
|
|
@@ -496,6 +509,22 @@ module RubyLLM
|
|
|
496
509
|
false
|
|
497
510
|
end
|
|
498
511
|
|
|
512
|
+
# Checks if the assistant_prompt column exists on execution_details
|
|
513
|
+
#
|
|
514
|
+
# Memoized to avoid repeated schema queries.
|
|
515
|
+
#
|
|
516
|
+
# @return [Boolean]
|
|
517
|
+
def assistant_prompt_column_exists?
|
|
518
|
+
return @_assistant_prompt_column_exists if defined?(@_assistant_prompt_column_exists)
|
|
519
|
+
|
|
520
|
+
@_assistant_prompt_column_exists = begin
|
|
521
|
+
defined?(RubyLLM::Agents::ExecutionDetail) &&
|
|
522
|
+
RubyLLM::Agents::ExecutionDetail.column_names.include?("assistant_prompt")
|
|
523
|
+
rescue StandardError
|
|
524
|
+
false
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
|
|
499
528
|
# Returns whether the Execution model is available
|
|
500
529
|
#
|
|
501
530
|
# @return [Boolean]
|
|
@@ -42,6 +42,7 @@ module RubyLLM
|
|
|
42
42
|
# @return [Context] The context with tenant fields populated
|
|
43
43
|
def call(context)
|
|
44
44
|
resolve_tenant!(context)
|
|
45
|
+
ensure_tenant_record!(context)
|
|
45
46
|
apply_api_configuration!(context)
|
|
46
47
|
@app.call(context)
|
|
47
48
|
end
|
|
@@ -84,6 +85,83 @@ module RubyLLM
|
|
|
84
85
|
end
|
|
85
86
|
end
|
|
86
87
|
|
|
88
|
+
# Ensures a Tenant record exists in the database for the resolved tenant.
|
|
89
|
+
#
|
|
90
|
+
# When a host model (e.g., Organization) with LLMTenant is passed as
|
|
91
|
+
# tenant: to an agent, the after_create callback only fires for new records.
|
|
92
|
+
# Pre-existing records won't have a Tenant row yet. This method auto-creates
|
|
93
|
+
# it on first use so budget tracking and the dashboard work correctly.
|
|
94
|
+
#
|
|
95
|
+
# @param context [Context] The execution context
|
|
96
|
+
def ensure_tenant_record!(context)
|
|
97
|
+
return unless context.tenant_id.present?
|
|
98
|
+
return unless tenant_table_exists?
|
|
99
|
+
|
|
100
|
+
tenant_object = context.tenant_object
|
|
101
|
+
|
|
102
|
+
# Only auto-create when the tenant object uses the LLMTenant concern
|
|
103
|
+
if tenant_object.respond_to?(:llm_tenant_id) && tenant_object.is_a?(::ActiveRecord::Base)
|
|
104
|
+
ensure_tenant_for_model!(tenant_object)
|
|
105
|
+
else
|
|
106
|
+
# For hash-based or string tenants, ensure a minimal record exists
|
|
107
|
+
RubyLLM::Agents::Tenant.find_or_create_by!(tenant_id: context.tenant_id)
|
|
108
|
+
end
|
|
109
|
+
rescue StandardError => e
|
|
110
|
+
# Don't fail the execution if tenant record creation fails
|
|
111
|
+
log_tenant_warning("ensure tenant record", e)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Creates a Tenant record linked to the host model if one doesn't exist
|
|
115
|
+
#
|
|
116
|
+
# @param tenant_object [ActiveRecord::Base] The host model with LLMTenant
|
|
117
|
+
def ensure_tenant_for_model!(tenant_object)
|
|
118
|
+
# Check polymorphic link first, then tenant_id
|
|
119
|
+
existing = RubyLLM::Agents::Tenant.find_by(tenant_record: tenant_object) ||
|
|
120
|
+
RubyLLM::Agents::Tenant.find_by(tenant_id: tenant_object.llm_tenant_id)
|
|
121
|
+
return if existing
|
|
122
|
+
|
|
123
|
+
options = tenant_object.class.try(:llm_tenant_options) || {}
|
|
124
|
+
limits = options[:limits] || {}
|
|
125
|
+
name_method = options[:name] || :to_s
|
|
126
|
+
|
|
127
|
+
RubyLLM::Agents::Tenant.create!(
|
|
128
|
+
tenant_id: tenant_object.llm_tenant_id,
|
|
129
|
+
name: tenant_object.send(name_method).to_s,
|
|
130
|
+
tenant_record: tenant_object,
|
|
131
|
+
daily_limit: limits[:daily_cost],
|
|
132
|
+
monthly_limit: limits[:monthly_cost],
|
|
133
|
+
daily_token_limit: limits[:daily_tokens],
|
|
134
|
+
monthly_token_limit: limits[:monthly_tokens],
|
|
135
|
+
daily_execution_limit: limits[:daily_executions],
|
|
136
|
+
monthly_execution_limit: limits[:monthly_executions],
|
|
137
|
+
enforcement: options[:enforcement]&.to_s || "soft",
|
|
138
|
+
inherit_global_defaults: options.fetch(:inherit_global, true)
|
|
139
|
+
)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Checks if the tenants table exists (memoized)
|
|
143
|
+
#
|
|
144
|
+
# @return [Boolean]
|
|
145
|
+
def tenant_table_exists?
|
|
146
|
+
return @tenant_table_exists if defined?(@tenant_table_exists)
|
|
147
|
+
|
|
148
|
+
@tenant_table_exists = ::ActiveRecord::Base.connection.table_exists?(:ruby_llm_agents_tenants)
|
|
149
|
+
rescue StandardError
|
|
150
|
+
@tenant_table_exists = false
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Logs a warning without failing the execution
|
|
154
|
+
#
|
|
155
|
+
# @param action [String] What was being attempted
|
|
156
|
+
# @param error [StandardError] The error
|
|
157
|
+
def log_tenant_warning(action, error)
|
|
158
|
+
return unless defined?(Rails) && Rails.respond_to?(:logger)
|
|
159
|
+
|
|
160
|
+
Rails.logger.warn(
|
|
161
|
+
"[RubyLLM::Agents] Failed to #{action}: #{error.message}"
|
|
162
|
+
)
|
|
163
|
+
end
|
|
164
|
+
|
|
87
165
|
# Applies API configuration to RubyLLM based on resolved tenant
|
|
88
166
|
#
|
|
89
167
|
# @param context [Context] The execution context
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby_llm-agents
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- adham90
|
|
@@ -146,6 +146,7 @@ files:
|
|
|
146
146
|
- lib/generators/ruby_llm_agents/multi_tenancy_generator.rb
|
|
147
147
|
- lib/generators/ruby_llm_agents/restructure_generator.rb
|
|
148
148
|
- lib/generators/ruby_llm_agents/speaker_generator.rb
|
|
149
|
+
- lib/generators/ruby_llm_agents/templates/add_assistant_prompt_migration.rb.tt
|
|
149
150
|
- lib/generators/ruby_llm_agents/templates/add_attempts_migration.rb.tt
|
|
150
151
|
- lib/generators/ruby_llm_agents/templates/add_caching_migration.rb.tt
|
|
151
152
|
- lib/generators/ruby_llm_agents/templates/add_execution_type_migration.rb.tt
|