prompt_engine 1.0.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.
Files changed (97) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +67 -0
  4. data/Rakefile +22 -0
  5. data/app/assets/stylesheets/prompt_engine/application.css +22 -0
  6. data/app/assets/stylesheets/prompt_engine/buttons.css +124 -0
  7. data/app/assets/stylesheets/prompt_engine/cards.css +63 -0
  8. data/app/assets/stylesheets/prompt_engine/comparison.css +244 -0
  9. data/app/assets/stylesheets/prompt_engine/components/_test_runs.css +144 -0
  10. data/app/assets/stylesheets/prompt_engine/dashboard.css +343 -0
  11. data/app/assets/stylesheets/prompt_engine/evaluations.css +124 -0
  12. data/app/assets/stylesheets/prompt_engine/forms.css +198 -0
  13. data/app/assets/stylesheets/prompt_engine/foundation.css +182 -0
  14. data/app/assets/stylesheets/prompt_engine/layout.css +75 -0
  15. data/app/assets/stylesheets/prompt_engine/loading.css +229 -0
  16. data/app/assets/stylesheets/prompt_engine/notifications.css +78 -0
  17. data/app/assets/stylesheets/prompt_engine/overrides.css +42 -0
  18. data/app/assets/stylesheets/prompt_engine/prompts.css +237 -0
  19. data/app/assets/stylesheets/prompt_engine/sidebar.css +90 -0
  20. data/app/assets/stylesheets/prompt_engine/tables.css +250 -0
  21. data/app/assets/stylesheets/prompt_engine/utilities.css +52 -0
  22. data/app/assets/stylesheets/prompt_engine/versions.css +370 -0
  23. data/app/clients/prompt_engine/open_ai_evals_client.rb +135 -0
  24. data/app/controllers/prompt_engine/admin/base_controller.rb +7 -0
  25. data/app/controllers/prompt_engine/application_controller.rb +4 -0
  26. data/app/controllers/prompt_engine/dashboard_controller.rb +24 -0
  27. data/app/controllers/prompt_engine/eval_runs_controller.rb +23 -0
  28. data/app/controllers/prompt_engine/eval_sets_controller.rb +200 -0
  29. data/app/controllers/prompt_engine/evaluations_controller.rb +32 -0
  30. data/app/controllers/prompt_engine/playground_controller.rb +57 -0
  31. data/app/controllers/prompt_engine/playground_run_results_controller.rb +41 -0
  32. data/app/controllers/prompt_engine/prompts_controller.rb +70 -0
  33. data/app/controllers/prompt_engine/settings_controller.rb +28 -0
  34. data/app/controllers/prompt_engine/test_cases_controller.rb +231 -0
  35. data/app/controllers/prompt_engine/versions_controller.rb +90 -0
  36. data/app/helpers/prompt_engine/application_helper.rb +4 -0
  37. data/app/jobs/prompt_engine/application_job.rb +4 -0
  38. data/app/mailers/prompt_engine/application_mailer.rb +6 -0
  39. data/app/models/prompt_engine/application_record.rb +5 -0
  40. data/app/models/prompt_engine/eval_result.rb +19 -0
  41. data/app/models/prompt_engine/eval_run.rb +40 -0
  42. data/app/models/prompt_engine/eval_set.rb +97 -0
  43. data/app/models/prompt_engine/parameter.rb +126 -0
  44. data/app/models/prompt_engine/parameter_parser.rb +39 -0
  45. data/app/models/prompt_engine/playground_run_result.rb +20 -0
  46. data/app/models/prompt_engine/prompt.rb +192 -0
  47. data/app/models/prompt_engine/prompt_version.rb +72 -0
  48. data/app/models/prompt_engine/setting.rb +45 -0
  49. data/app/models/prompt_engine/test_case.rb +29 -0
  50. data/app/services/prompt_engine/evaluation_runner.rb +258 -0
  51. data/app/services/prompt_engine/playground_executor.rb +124 -0
  52. data/app/services/prompt_engine/variable_detector.rb +97 -0
  53. data/app/views/layouts/prompt_engine/admin.html.erb +65 -0
  54. data/app/views/layouts/prompt_engine/application.html.erb +17 -0
  55. data/app/views/prompt_engine/dashboard/index.html.erb +230 -0
  56. data/app/views/prompt_engine/eval_runs/show.html.erb +204 -0
  57. data/app/views/prompt_engine/eval_sets/compare.html.erb +229 -0
  58. data/app/views/prompt_engine/eval_sets/edit.html.erb +111 -0
  59. data/app/views/prompt_engine/eval_sets/index.html.erb +63 -0
  60. data/app/views/prompt_engine/eval_sets/metrics.html.erb +371 -0
  61. data/app/views/prompt_engine/eval_sets/new.html.erb +113 -0
  62. data/app/views/prompt_engine/eval_sets/show.html.erb +235 -0
  63. data/app/views/prompt_engine/evaluations/index.html.erb +194 -0
  64. data/app/views/prompt_engine/playground/result.html.erb +58 -0
  65. data/app/views/prompt_engine/playground/show.html.erb +129 -0
  66. data/app/views/prompt_engine/playground_run_results/index.html.erb +99 -0
  67. data/app/views/prompt_engine/playground_run_results/show.html.erb +123 -0
  68. data/app/views/prompt_engine/prompts/_form.html.erb +224 -0
  69. data/app/views/prompt_engine/prompts/edit.html.erb +9 -0
  70. data/app/views/prompt_engine/prompts/index.html.erb +80 -0
  71. data/app/views/prompt_engine/prompts/new.html.erb +9 -0
  72. data/app/views/prompt_engine/prompts/show.html.erb +297 -0
  73. data/app/views/prompt_engine/settings/edit.html.erb +93 -0
  74. data/app/views/prompt_engine/shared/_form_errors.html.erb +16 -0
  75. data/app/views/prompt_engine/test_cases/edit.html.erb +72 -0
  76. data/app/views/prompt_engine/test_cases/import.html.erb +92 -0
  77. data/app/views/prompt_engine/test_cases/import_preview.html.erb +103 -0
  78. data/app/views/prompt_engine/test_cases/new.html.erb +79 -0
  79. data/app/views/prompt_engine/versions/_version_card.html.erb +56 -0
  80. data/app/views/prompt_engine/versions/compare.html.erb +82 -0
  81. data/app/views/prompt_engine/versions/index.html.erb +96 -0
  82. data/app/views/prompt_engine/versions/show.html.erb +98 -0
  83. data/config/routes.rb +61 -0
  84. data/db/migrate/20250124000001_create_eval_tables.rb +43 -0
  85. data/db/migrate/20250124000002_add_open_ai_fields_to_evals.rb +11 -0
  86. data/db/migrate/20250125000001_add_grader_fields_to_eval_sets.rb +8 -0
  87. data/db/migrate/20250723161909_create_prompts.rb +17 -0
  88. data/db/migrate/20250723184757_create_prompt_engine_versions.rb +24 -0
  89. data/db/migrate/20250723203838_create_prompt_engine_parameters.rb +20 -0
  90. data/db/migrate/20250724160623_create_prompt_engine_playground_run_results.rb +30 -0
  91. data/db/migrate/20250724165118_create_prompt_engine_settings.rb +14 -0
  92. data/lib/prompt_engine/engine.rb +25 -0
  93. data/lib/prompt_engine/version.rb +3 -0
  94. data/lib/prompt_engine.rb +33 -0
  95. data/lib/tasks/active_prompt_tasks.rake +32 -0
  96. data/lib/tasks/eval_demo.rake +149 -0
  97. metadata +293 -0
@@ -0,0 +1,370 @@
1
+ /* Page Header */
2
+ .page-header {
3
+ display: flex;
4
+ justify-content: space-between;
5
+ align-items: flex-start;
6
+ margin-bottom: var(--spacing-xl);
7
+ gap: var(--spacing-lg);
8
+ }
9
+
10
+ .page-header__content {
11
+ flex: 1;
12
+ }
13
+
14
+ .page-header__title {
15
+ margin: 0 0 var(--spacing-xs) 0;
16
+ font-size: var(--font-size-2xl);
17
+ font-weight: 600;
18
+ color: var(--color-gray-900);
19
+ }
20
+
21
+ .page-header__subtitle {
22
+ margin: 0;
23
+ font-size: var(--font-size-base);
24
+ color: var(--color-gray-600);
25
+ }
26
+
27
+ .page-header__actions {
28
+ display: flex;
29
+ gap: var(--spacing-sm);
30
+ align-items: center;
31
+ }
32
+
33
+ /* Versions Timeline */
34
+ .versions-timeline {
35
+ position: relative;
36
+ margin-top: var(--spacing-xl);
37
+ }
38
+
39
+ /* Version Item */
40
+ .version-item {
41
+ display: flex;
42
+ gap: var(--spacing-lg);
43
+ margin-bottom: var(--spacing-xl);
44
+ position: relative;
45
+ }
46
+
47
+ .version-item--current .version-card {
48
+ border-color: var(--color-primary-600);
49
+ background-color: var(--color-primary-50);
50
+ }
51
+
52
+ /* Version Timeline Marker */
53
+ .version-item__marker {
54
+ position: relative;
55
+ flex-shrink: 0;
56
+ width: 40px;
57
+ }
58
+
59
+ .version-item__marker-dot {
60
+ width: 12px;
61
+ height: 12px;
62
+ border-radius: 50%;
63
+ background-color: var(--color-gray-400);
64
+ border: 3px solid var(--color-white);
65
+ box-shadow: 0 0 0 1px var(--color-gray-300);
66
+ position: absolute;
67
+ left: 14px;
68
+ top: 8px;
69
+ }
70
+
71
+ .version-item--current .version-item__marker-dot {
72
+ background-color: var(--color-primary-600);
73
+ box-shadow: 0 0 0 1px var(--color-primary-600);
74
+ }
75
+
76
+ .version-item__marker-line {
77
+ position: absolute;
78
+ left: 19px;
79
+ top: 28px;
80
+ width: 2px;
81
+ height: calc(100% + var(--spacing-xl));
82
+ background-color: var(--color-gray-200);
83
+ }
84
+
85
+ .version-item__content {
86
+ flex: 1;
87
+ min-width: 0;
88
+ }
89
+
90
+ /* Version Card */
91
+ .version-card {
92
+ background-color: var(--color-white);
93
+ border: 1px solid var(--color-gray-200);
94
+ border-radius: var(--radius-lg);
95
+ padding: var(--spacing-lg);
96
+ transition: all 0.2s ease;
97
+ }
98
+
99
+ .version-card:hover {
100
+ box-shadow: var(--shadow-md);
101
+ transform: translateY(-1px);
102
+ }
103
+
104
+ .version-card__header {
105
+ display: flex;
106
+ justify-content: space-between;
107
+ align-items: flex-start;
108
+ margin-bottom: var(--spacing-md);
109
+ }
110
+
111
+ .version-card__header-main {
112
+ flex: 1;
113
+ min-width: 0;
114
+ }
115
+
116
+ .version-card__header-actions {
117
+ display: flex;
118
+ gap: var(--spacing-sm);
119
+ flex-shrink: 0;
120
+ }
121
+
122
+ .version-card__title {
123
+ font-size: var(--font-size-lg);
124
+ font-weight: var(--font-weight-semibold);
125
+ color: var(--color-gray-900);
126
+ margin: 0 0 var(--spacing-xs) 0;
127
+ display: flex;
128
+ align-items: center;
129
+ gap: var(--spacing-sm);
130
+ }
131
+
132
+ .version-card__summary {
133
+ font-size: var(--font-size-base);
134
+ color: var(--color-gray-600);
135
+ margin: 0;
136
+ }
137
+
138
+ /* Version Card Metadata */
139
+ .version-card__metadata {
140
+ display: flex;
141
+ flex-wrap: wrap;
142
+ gap: var(--spacing-lg);
143
+ padding-top: var(--spacing-md);
144
+ border-top: 1px solid var(--color-gray-100);
145
+ }
146
+
147
+ .version-card__metadata-item {
148
+ display: flex;
149
+ flex-direction: column;
150
+ gap: var(--spacing-xs);
151
+ }
152
+
153
+ .version-card__metadata-label {
154
+ font-size: var(--font-size-sm);
155
+ font-weight: var(--font-weight-medium);
156
+ color: var(--color-gray-500);
157
+ }
158
+
159
+ .version-card__metadata-value {
160
+ font-size: var(--font-size-sm);
161
+ color: var(--color-gray-700);
162
+ }
163
+
164
+ /* Version Card Preview */
165
+ .version-card__preview {
166
+ margin-top: var(--spacing-md);
167
+ padding-top: var(--spacing-md);
168
+ border-top: 1px solid var(--color-gray-100);
169
+ }
170
+
171
+ .version-card__preview-label {
172
+ display: block;
173
+ font-size: var(--font-size-sm);
174
+ font-weight: var(--font-weight-medium);
175
+ color: var(--color-gray-600);
176
+ margin-bottom: var(--spacing-sm);
177
+ }
178
+
179
+ .version-card__preview-content {
180
+ background-color: var(--color-gray-50);
181
+ border: 1px solid var(--color-gray-200);
182
+ border-radius: var(--radius-md);
183
+ padding: var(--spacing-md);
184
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
185
+ font-size: var(--font-size-sm);
186
+ line-height: var(--line-height-relaxed);
187
+ white-space: pre-wrap;
188
+ word-wrap: break-word;
189
+ margin: 0;
190
+ color: var(--color-gray-700);
191
+ max-height: 120px;
192
+ overflow: hidden;
193
+ position: relative;
194
+ }
195
+
196
+ /* Version Details */
197
+ .version-details {
198
+ display: flex;
199
+ flex-direction: column;
200
+ gap: var(--spacing-lg);
201
+ margin-top: var(--spacing-xl);
202
+ }
203
+
204
+ /* Version Compare */
205
+ .version-compare {
206
+ margin-top: var(--spacing-xl);
207
+ }
208
+
209
+ .version-compare__header {
210
+ display: flex;
211
+ align-items: center;
212
+ justify-content: center;
213
+ gap: var(--spacing-xl);
214
+ margin-bottom: var(--spacing-xl);
215
+ padding: var(--spacing-lg);
216
+ background-color: var(--color-gray-50);
217
+ border-radius: var(--radius-lg);
218
+ }
219
+
220
+ .version-compare__version {
221
+ text-align: center;
222
+ }
223
+
224
+ .version-compare__version h3 {
225
+ font-size: var(--font-size-xl);
226
+ font-weight: var(--font-weight-semibold);
227
+ margin: 0 0 var(--spacing-xs) 0;
228
+ }
229
+
230
+ .version-compare__arrow {
231
+ font-size: var(--font-size-2xl);
232
+ color: var(--color-gray-400);
233
+ font-weight: var(--font-weight-bold);
234
+ }
235
+
236
+ .version-compare__section {
237
+ margin-bottom: var(--spacing-lg);
238
+ }
239
+
240
+ /* Version Compare Diff */
241
+ .version-compare__diff {
242
+ display: grid;
243
+ grid-template-columns: 1fr 1fr;
244
+ gap: var(--spacing-lg);
245
+ }
246
+
247
+ .version-compare__diff-side {
248
+ position: relative;
249
+ }
250
+
251
+ .version-compare__diff-label {
252
+ font-size: var(--font-size-sm);
253
+ font-weight: var(--font-weight-medium);
254
+ color: var(--color-gray-600);
255
+ margin-bottom: var(--spacing-sm);
256
+ }
257
+
258
+ .version-compare__diff-content,
259
+ .version-compare__diff-value {
260
+ background-color: var(--color-gray-50);
261
+ border: 1px solid var(--color-gray-200);
262
+ border-radius: var(--radius-md);
263
+ padding: var(--spacing-md);
264
+ font-size: var(--font-size-sm);
265
+ margin: 0;
266
+ }
267
+
268
+ .version-compare__diff-content {
269
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
270
+ line-height: var(--line-height-relaxed);
271
+ white-space: pre-wrap;
272
+ word-wrap: break-word;
273
+ max-height: 300px;
274
+ overflow-y: auto;
275
+ }
276
+
277
+ .version-compare__diff-content--old,
278
+ .version-compare__diff-value--old {
279
+ background-color: var(--color-red-50);
280
+ border-color: var(--color-red-200);
281
+ color: var(--color-red-900);
282
+ }
283
+
284
+ .version-compare__diff-content--new,
285
+ .version-compare__diff-value--new {
286
+ background-color: var(--color-green-50);
287
+ border-color: var(--color-green-200);
288
+ color: var(--color-green-900);
289
+ }
290
+
291
+ .version-compare__summary {
292
+ display: flex;
293
+ flex-direction: column;
294
+ gap: var(--spacing-md);
295
+ }
296
+
297
+ .version-compare__summary-item {
298
+ padding: var(--spacing-md);
299
+ background-color: var(--color-gray-50);
300
+ border-radius: var(--radius-md);
301
+ font-size: var(--font-size-base);
302
+ line-height: var(--line-height-relaxed);
303
+ }
304
+
305
+ /* Metadata List */
306
+ .metadata-list {
307
+ display: grid;
308
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
309
+ gap: var(--spacing-lg);
310
+ margin: 0;
311
+ }
312
+
313
+ .metadata-list__item {
314
+ display: flex;
315
+ flex-direction: column;
316
+ gap: var(--spacing-xs);
317
+ }
318
+
319
+ .metadata-list__label {
320
+ font-size: var(--font-size-sm);
321
+ font-weight: var(--font-weight-medium);
322
+ color: var(--color-gray-600);
323
+ }
324
+
325
+ .metadata-list__value {
326
+ font-size: var(--font-size-base);
327
+ color: var(--color-gray-900);
328
+ }
329
+
330
+ /* Badge */
331
+ .badge {
332
+ display: inline-flex;
333
+ align-items: center;
334
+ padding: var(--spacing-xs) var(--spacing-sm);
335
+ font-size: var(--font-size-xs);
336
+ font-weight: var(--font-weight-medium);
337
+ border-radius: var(--radius-full);
338
+ text-transform: uppercase;
339
+ letter-spacing: 0.05em;
340
+ }
341
+
342
+ .badge--primary {
343
+ background-color: var(--color-primary-100);
344
+ color: var(--color-primary-700);
345
+ }
346
+
347
+ /* Responsive */
348
+ @media (max-width: 768px) {
349
+ .version-compare__diff {
350
+ grid-template-columns: 1fr;
351
+ }
352
+
353
+ .version-compare__header {
354
+ flex-direction: column;
355
+ }
356
+
357
+ .version-compare__arrow {
358
+ transform: rotate(90deg);
359
+ }
360
+
361
+ .version-card__header {
362
+ flex-direction: column;
363
+ gap: var(--spacing-md);
364
+ }
365
+
366
+ .version-card__header-actions {
367
+ width: 100%;
368
+ justify-content: flex-start;
369
+ }
370
+ }
@@ -0,0 +1,135 @@
1
+ module PromptEngine
2
+ class OpenAiEvalsClient
3
+ BASE_URL = "https://api.openai.com/v1"
4
+
5
+ class APIError < StandardError; end
6
+ class AuthenticationError < APIError; end
7
+ class RateLimitError < APIError; end
8
+ class NotFoundError < APIError; end
9
+
10
+ def initialize(api_key: nil)
11
+ # Try to get API key from: 1) parameter, 2) Settings, 3) Rails credentials
12
+ @api_key = api_key || fetch_api_key_from_settings || Rails.application.credentials.dig(:openai, :api_key)
13
+ raise AuthenticationError, "OpenAI API key not configured" if @api_key.blank?
14
+ end
15
+
16
+ def create_eval(name:, data_source_config:, testing_criteria:)
17
+ post("/evals", {
18
+ name: name,
19
+ data_source_config: data_source_config,
20
+ testing_criteria: testing_criteria
21
+ })
22
+ end
23
+
24
+ def create_run(eval_id:, name:, data_source:)
25
+ post("/evals/#{eval_id}/runs", {
26
+ name: name,
27
+ data_source: data_source
28
+ })
29
+ end
30
+
31
+ def get_run(eval_id:, run_id:)
32
+ get("/evals/#{eval_id}/runs/#{run_id}")
33
+ end
34
+
35
+ def upload_file(file_path, purpose: "evals")
36
+ uri = URI("#{BASE_URL}/files")
37
+ request = Net::HTTP::Post.new(uri)
38
+ request["Authorization"] = "Bearer #{@api_key}"
39
+
40
+ File.open(file_path, "rb") do |file|
41
+ form_data = [
42
+ [ "purpose", purpose ],
43
+ [ "file", file, { filename: File.basename(file_path) } ]
44
+ ]
45
+ request.set_form(form_data, "multipart/form-data")
46
+
47
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
48
+ http.request(request)
49
+ end
50
+
51
+ handle_response(response)
52
+ end
53
+ rescue Errno::ENOENT => e
54
+ raise APIError, "File not found: #{file_path}"
55
+ rescue => e
56
+ raise APIError, "File upload failed: #{e.message}"
57
+ end
58
+
59
+ private
60
+
61
+ def fetch_api_key_from_settings
62
+ PromptEngine::Setting.instance.openai_api_key
63
+ rescue ActiveRecord::RecordNotFound
64
+ nil
65
+ end
66
+
67
+ def post(path, body)
68
+ uri = URI("#{BASE_URL}#{path}")
69
+ http = Net::HTTP.new(uri.host, uri.port)
70
+ http.use_ssl = true
71
+ http.read_timeout = 30
72
+ http.open_timeout = 10
73
+
74
+ request = Net::HTTP::Post.new(uri)
75
+ request["Authorization"] = "Bearer #{@api_key}"
76
+ request["Content-Type"] = "application/json"
77
+ request.body = body.to_json
78
+
79
+ response = http.request(request)
80
+ handle_response(response)
81
+ rescue Net::ReadTimeout => e
82
+ raise APIError, "Request timed out"
83
+ rescue Net::OpenTimeout => e
84
+ raise APIError, "Connection timed out"
85
+ rescue => e
86
+ raise APIError, "Request failed: #{e.message}"
87
+ end
88
+
89
+ def get(path)
90
+ uri = URI("#{BASE_URL}#{path}")
91
+ http = Net::HTTP.new(uri.host, uri.port)
92
+ http.use_ssl = true
93
+ http.read_timeout = 30
94
+ http.open_timeout = 10
95
+
96
+ request = Net::HTTP::Get.new(uri)
97
+ request["Authorization"] = "Bearer #{@api_key}"
98
+
99
+ response = http.request(request)
100
+ handle_response(response)
101
+ rescue Net::ReadTimeout => e
102
+ raise APIError, "Request timed out"
103
+ rescue Net::OpenTimeout => e
104
+ raise APIError, "Connection timed out"
105
+ rescue => e
106
+ raise APIError, "Request failed: #{e.message}"
107
+ end
108
+
109
+ def handle_response(response)
110
+ case response.code.to_i
111
+ when 200..299
112
+ JSON.parse(response.body)
113
+ when 401
114
+ raise AuthenticationError, "Invalid API key"
115
+ when 404
116
+ raise NotFoundError, parse_error_message(response)
117
+ when 429
118
+ raise RateLimitError, "Rate limit exceeded. Please try again later."
119
+ when 400..499
120
+ raise APIError, "Client error: #{parse_error_message(response)}"
121
+ when 500..599
122
+ raise APIError, "Server error: #{parse_error_message(response)}"
123
+ else
124
+ raise APIError, "Unexpected response: #{response.code} - #{response.body}"
125
+ end
126
+ end
127
+
128
+ def parse_error_message(response)
129
+ body = JSON.parse(response.body)
130
+ body.dig("error", "message") || body["error"] || response.body
131
+ rescue JSON::ParserError
132
+ response.body
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,7 @@
1
+ module PromptEngine
2
+ module Admin
3
+ class BaseController < ApplicationController
4
+ layout "prompt_engine/admin"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ module PromptEngine
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,24 @@
1
+ module PromptEngine
2
+ class DashboardController < ApplicationController
3
+ layout "prompt_engine/admin"
4
+
5
+ def index
6
+ @recent_prompts = Prompt.includes(:parameters).order(updated_at: :desc).limit(5)
7
+ @recent_test_runs = PlaygroundRunResult.includes(prompt_version: :prompt).order(created_at: :desc).limit(5)
8
+
9
+ # Statistics
10
+ @total_prompts = Prompt.count
11
+ @prompt_engines = Prompt.active.count
12
+ @total_test_runs = PlaygroundRunResult.count
13
+ @total_tokens_used = PlaygroundRunResult.sum(:token_count) || 0
14
+
15
+ # Evaluation statistics
16
+ @total_eval_sets = EvalSet.count
17
+ @total_eval_runs = EvalRun.count
18
+ @recent_eval_runs = EvalRun.includes(eval_set: :prompt)
19
+ .where(status: "completed")
20
+ .order(created_at: :desc)
21
+ .limit(5)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ module PromptEngine
2
+ class EvalRunsController < ApplicationController
3
+ layout "prompt_engine/admin"
4
+
5
+ before_action :set_prompt
6
+ before_action :set_eval_run
7
+
8
+ def show
9
+ # Note: Individual eval results are not fetched in MVP
10
+ # Only aggregate counts from OpenAI are displayed
11
+ end
12
+
13
+ private
14
+
15
+ def set_prompt
16
+ @prompt = Prompt.find(params[:prompt_id])
17
+ end
18
+
19
+ def set_eval_run
20
+ @eval_run = EvalRun.find(params[:id])
21
+ end
22
+ end
23
+ end