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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +67 -0
- data/Rakefile +22 -0
- data/app/assets/stylesheets/prompt_engine/application.css +22 -0
- data/app/assets/stylesheets/prompt_engine/buttons.css +124 -0
- data/app/assets/stylesheets/prompt_engine/cards.css +63 -0
- data/app/assets/stylesheets/prompt_engine/comparison.css +244 -0
- data/app/assets/stylesheets/prompt_engine/components/_test_runs.css +144 -0
- data/app/assets/stylesheets/prompt_engine/dashboard.css +343 -0
- data/app/assets/stylesheets/prompt_engine/evaluations.css +124 -0
- data/app/assets/stylesheets/prompt_engine/forms.css +198 -0
- data/app/assets/stylesheets/prompt_engine/foundation.css +182 -0
- data/app/assets/stylesheets/prompt_engine/layout.css +75 -0
- data/app/assets/stylesheets/prompt_engine/loading.css +229 -0
- data/app/assets/stylesheets/prompt_engine/notifications.css +78 -0
- data/app/assets/stylesheets/prompt_engine/overrides.css +42 -0
- data/app/assets/stylesheets/prompt_engine/prompts.css +237 -0
- data/app/assets/stylesheets/prompt_engine/sidebar.css +90 -0
- data/app/assets/stylesheets/prompt_engine/tables.css +250 -0
- data/app/assets/stylesheets/prompt_engine/utilities.css +52 -0
- data/app/assets/stylesheets/prompt_engine/versions.css +370 -0
- data/app/clients/prompt_engine/open_ai_evals_client.rb +135 -0
- data/app/controllers/prompt_engine/admin/base_controller.rb +7 -0
- data/app/controllers/prompt_engine/application_controller.rb +4 -0
- data/app/controllers/prompt_engine/dashboard_controller.rb +24 -0
- data/app/controllers/prompt_engine/eval_runs_controller.rb +23 -0
- data/app/controllers/prompt_engine/eval_sets_controller.rb +200 -0
- data/app/controllers/prompt_engine/evaluations_controller.rb +32 -0
- data/app/controllers/prompt_engine/playground_controller.rb +57 -0
- data/app/controllers/prompt_engine/playground_run_results_controller.rb +41 -0
- data/app/controllers/prompt_engine/prompts_controller.rb +70 -0
- data/app/controllers/prompt_engine/settings_controller.rb +28 -0
- data/app/controllers/prompt_engine/test_cases_controller.rb +231 -0
- data/app/controllers/prompt_engine/versions_controller.rb +90 -0
- data/app/helpers/prompt_engine/application_helper.rb +4 -0
- data/app/jobs/prompt_engine/application_job.rb +4 -0
- data/app/mailers/prompt_engine/application_mailer.rb +6 -0
- data/app/models/prompt_engine/application_record.rb +5 -0
- data/app/models/prompt_engine/eval_result.rb +19 -0
- data/app/models/prompt_engine/eval_run.rb +40 -0
- data/app/models/prompt_engine/eval_set.rb +97 -0
- data/app/models/prompt_engine/parameter.rb +126 -0
- data/app/models/prompt_engine/parameter_parser.rb +39 -0
- data/app/models/prompt_engine/playground_run_result.rb +20 -0
- data/app/models/prompt_engine/prompt.rb +192 -0
- data/app/models/prompt_engine/prompt_version.rb +72 -0
- data/app/models/prompt_engine/setting.rb +45 -0
- data/app/models/prompt_engine/test_case.rb +29 -0
- data/app/services/prompt_engine/evaluation_runner.rb +258 -0
- data/app/services/prompt_engine/playground_executor.rb +124 -0
- data/app/services/prompt_engine/variable_detector.rb +97 -0
- data/app/views/layouts/prompt_engine/admin.html.erb +65 -0
- data/app/views/layouts/prompt_engine/application.html.erb +17 -0
- data/app/views/prompt_engine/dashboard/index.html.erb +230 -0
- data/app/views/prompt_engine/eval_runs/show.html.erb +204 -0
- data/app/views/prompt_engine/eval_sets/compare.html.erb +229 -0
- data/app/views/prompt_engine/eval_sets/edit.html.erb +111 -0
- data/app/views/prompt_engine/eval_sets/index.html.erb +63 -0
- data/app/views/prompt_engine/eval_sets/metrics.html.erb +371 -0
- data/app/views/prompt_engine/eval_sets/new.html.erb +113 -0
- data/app/views/prompt_engine/eval_sets/show.html.erb +235 -0
- data/app/views/prompt_engine/evaluations/index.html.erb +194 -0
- data/app/views/prompt_engine/playground/result.html.erb +58 -0
- data/app/views/prompt_engine/playground/show.html.erb +129 -0
- data/app/views/prompt_engine/playground_run_results/index.html.erb +99 -0
- data/app/views/prompt_engine/playground_run_results/show.html.erb +123 -0
- data/app/views/prompt_engine/prompts/_form.html.erb +224 -0
- data/app/views/prompt_engine/prompts/edit.html.erb +9 -0
- data/app/views/prompt_engine/prompts/index.html.erb +80 -0
- data/app/views/prompt_engine/prompts/new.html.erb +9 -0
- data/app/views/prompt_engine/prompts/show.html.erb +297 -0
- data/app/views/prompt_engine/settings/edit.html.erb +93 -0
- data/app/views/prompt_engine/shared/_form_errors.html.erb +16 -0
- data/app/views/prompt_engine/test_cases/edit.html.erb +72 -0
- data/app/views/prompt_engine/test_cases/import.html.erb +92 -0
- data/app/views/prompt_engine/test_cases/import_preview.html.erb +103 -0
- data/app/views/prompt_engine/test_cases/new.html.erb +79 -0
- data/app/views/prompt_engine/versions/_version_card.html.erb +56 -0
- data/app/views/prompt_engine/versions/compare.html.erb +82 -0
- data/app/views/prompt_engine/versions/index.html.erb +96 -0
- data/app/views/prompt_engine/versions/show.html.erb +98 -0
- data/config/routes.rb +61 -0
- data/db/migrate/20250124000001_create_eval_tables.rb +43 -0
- data/db/migrate/20250124000002_add_open_ai_fields_to_evals.rb +11 -0
- data/db/migrate/20250125000001_add_grader_fields_to_eval_sets.rb +8 -0
- data/db/migrate/20250723161909_create_prompts.rb +17 -0
- data/db/migrate/20250723184757_create_prompt_engine_versions.rb +24 -0
- data/db/migrate/20250723203838_create_prompt_engine_parameters.rb +20 -0
- data/db/migrate/20250724160623_create_prompt_engine_playground_run_results.rb +30 -0
- data/db/migrate/20250724165118_create_prompt_engine_settings.rb +14 -0
- data/lib/prompt_engine/engine.rb +25 -0
- data/lib/prompt_engine/version.rb +3 -0
- data/lib/prompt_engine.rb +33 -0
- data/lib/tasks/active_prompt_tasks.rake +32 -0
- data/lib/tasks/eval_demo.rake +149 -0
- 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,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
|