lluminary 0.1.4 → 0.2.1
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/lib/lluminary/models/base.rb +177 -66
- data/lib/lluminary/schema.rb +37 -7
- data/lib/lluminary/schema_model.rb +149 -65
- data/lib/lluminary/task.rb +37 -0
- data/lib/lluminary/tasks/describe_openai_model.rb +61 -0
- data/lib/lluminary/tasks/identify_and_describe_open_ai_models.rb +51 -0
- data/spec/examples/character_profiler_spec.rb +85 -0
- data/spec/lluminary/models/base_spec.rb +933 -100
- data/spec/lluminary/schema_model_spec.rb +259 -0
- data/spec/lluminary/schema_spec.rb +228 -134
- data/spec/lluminary/task_custom_validation_spec.rb +262 -0
- data/spec/spec_helper.rb +3 -0
- metadata +20 -2
@@ -1,6 +1,130 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require "spec_helper"
|
3
3
|
|
4
|
+
# The following is an example of our goals for the output when describing fields in the schema:
|
5
|
+
#
|
6
|
+
# # user_profile
|
7
|
+
# Description: A user's complete profile
|
8
|
+
# Type: object
|
9
|
+
# Example: {
|
10
|
+
# "name": "your name here",
|
11
|
+
# "age": 0,
|
12
|
+
# "preferences": {
|
13
|
+
# "theme": "your theme here",
|
14
|
+
# "notifications_enabled": true
|
15
|
+
# }
|
16
|
+
# }
|
17
|
+
#
|
18
|
+
# # name
|
19
|
+
# Description: A person's full name
|
20
|
+
# Type: string
|
21
|
+
# Example: "your name here"
|
22
|
+
#
|
23
|
+
# # age
|
24
|
+
# Type: integer
|
25
|
+
# Example: 0
|
26
|
+
#
|
27
|
+
# # preferences
|
28
|
+
# Description: User's system preferences
|
29
|
+
# Type: object
|
30
|
+
# Example: {
|
31
|
+
# "theme": "your theme here",
|
32
|
+
# "notifications_enabled": true
|
33
|
+
# }
|
34
|
+
#
|
35
|
+
# # preferences.theme
|
36
|
+
# Description: The UI color theme
|
37
|
+
# Type: string
|
38
|
+
# Example: "your theme here"
|
39
|
+
#
|
40
|
+
# # preferences.notifications_enabled
|
41
|
+
# Type: boolean
|
42
|
+
# Example: true
|
43
|
+
#
|
44
|
+
# # security
|
45
|
+
# Description: Security and authentication settings
|
46
|
+
# Type: object
|
47
|
+
# Example: {
|
48
|
+
# "credentials": {
|
49
|
+
# "last_login": "2024-01-01T12:00:00+00:00"
|
50
|
+
# }
|
51
|
+
# }
|
52
|
+
#
|
53
|
+
# # security.credentials
|
54
|
+
# Type: object
|
55
|
+
# Example: {
|
56
|
+
# "last_login": "2024-01-01T12:00:00+00:00"
|
57
|
+
# }
|
58
|
+
#
|
59
|
+
# # security.credentials.last_login
|
60
|
+
# Type: datetime in ISO8601 format
|
61
|
+
# Description: Most recent successful login
|
62
|
+
# Example: "2024-01-01T12:00:00+00:00"
|
63
|
+
#
|
64
|
+
# # tags
|
65
|
+
# Description: User's associated tags
|
66
|
+
# Type: array of string
|
67
|
+
# Example: ["first tag", "second tag"]
|
68
|
+
#
|
69
|
+
# # roles
|
70
|
+
# Description: User's system roles
|
71
|
+
# Type: array of objects
|
72
|
+
# Example: [
|
73
|
+
# {
|
74
|
+
# "name": "your name here",
|
75
|
+
# "permissions": ["first permission", "second permission"]
|
76
|
+
# },
|
77
|
+
# {
|
78
|
+
# "name": "your name here",
|
79
|
+
# "permissions": ["first permission", "second permission"]
|
80
|
+
# }
|
81
|
+
# ]
|
82
|
+
#
|
83
|
+
# # roles[].name
|
84
|
+
# Description: Name of the role
|
85
|
+
# Type: string
|
86
|
+
# Example: "your name here"
|
87
|
+
#
|
88
|
+
# # roles[].permissions
|
89
|
+
# Description: Permissions granted by this role
|
90
|
+
# Type: array of strings
|
91
|
+
# Example: ["first permission", "second permission"]
|
92
|
+
#
|
93
|
+
# # matrix
|
94
|
+
# Description: A 2D grid of numbers
|
95
|
+
# Type: array of arrays
|
96
|
+
# Example: [[1, 2, 3], [4, 5, 6]]
|
97
|
+
#
|
98
|
+
# # settings
|
99
|
+
# Description: User configuration settings
|
100
|
+
# Type: object
|
101
|
+
# Example: {
|
102
|
+
# "theme": "your theme here",
|
103
|
+
# "favorites": ["first favorite", "second favorite"],
|
104
|
+
# "notifications": {
|
105
|
+
# "email": true,
|
106
|
+
# "channels": ["first channel", "second channel"]
|
107
|
+
# }
|
108
|
+
# }
|
109
|
+
#
|
110
|
+
# # settings.favorites
|
111
|
+
# Description: User's favorite items
|
112
|
+
# Type: array of strings
|
113
|
+
# Example: ["first favorite", "second favorite"]
|
114
|
+
#
|
115
|
+
# # settings.notifications
|
116
|
+
# Description: Notification preferences
|
117
|
+
# Type: object
|
118
|
+
# Example: {
|
119
|
+
# "email": true,
|
120
|
+
# "channels": ["first channel", "second channel"]
|
121
|
+
# }
|
122
|
+
#
|
123
|
+
# # settings.notifications.channels
|
124
|
+
# Description: Notification channels to use
|
125
|
+
# Type: array of strings
|
126
|
+
# Example: ["first channel", "second channel"]
|
127
|
+
|
4
128
|
RSpec.describe Lluminary::Models::Base do
|
5
129
|
let(:model) { described_class.new }
|
6
130
|
|
@@ -38,12 +162,16 @@ RSpec.describe Lluminary::Models::Base do
|
|
38
162
|
end
|
39
163
|
|
40
164
|
it "formats string field description correctly" do
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
165
|
+
prompt = model.format_prompt(task)
|
166
|
+
|
167
|
+
expected_description = <<~DESCRIPTION.chomp
|
168
|
+
# name
|
169
|
+
Description: The person's name
|
170
|
+
Type: string
|
171
|
+
Example: "your name here"
|
172
|
+
DESCRIPTION
|
173
|
+
|
174
|
+
expect(prompt).to include(expected_description)
|
47
175
|
end
|
48
176
|
end
|
49
177
|
|
@@ -55,12 +183,16 @@ RSpec.describe Lluminary::Models::Base do
|
|
55
183
|
end
|
56
184
|
|
57
185
|
it "formats integer field description correctly" do
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
186
|
+
prompt = model.format_prompt(task)
|
187
|
+
|
188
|
+
expected_description = <<~DESCRIPTION.chomp
|
189
|
+
# age
|
190
|
+
Description: The person's age
|
191
|
+
Type: integer
|
192
|
+
Example: 0
|
193
|
+
DESCRIPTION
|
194
|
+
|
195
|
+
expect(prompt).to include(expected_description)
|
64
196
|
end
|
65
197
|
end
|
66
198
|
|
@@ -72,12 +204,16 @@ RSpec.describe Lluminary::Models::Base do
|
|
72
204
|
end
|
73
205
|
|
74
206
|
it "formats boolean field description correctly" do
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
207
|
+
prompt = model.format_prompt(task)
|
208
|
+
|
209
|
+
expected_description = <<~DESCRIPTION.chomp
|
210
|
+
# active
|
211
|
+
Description: Whether the person is active
|
212
|
+
Type: boolean
|
213
|
+
Example: true
|
214
|
+
DESCRIPTION
|
215
|
+
|
216
|
+
expect(prompt).to include(expected_description)
|
81
217
|
end
|
82
218
|
end
|
83
219
|
|
@@ -89,12 +225,16 @@ RSpec.describe Lluminary::Models::Base do
|
|
89
225
|
end
|
90
226
|
|
91
227
|
it "formats float field description correctly" do
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
228
|
+
prompt = model.format_prompt(task)
|
229
|
+
|
230
|
+
expected_description = <<~DESCRIPTION.chomp
|
231
|
+
# score
|
232
|
+
Description: The person's score
|
233
|
+
Type: float
|
234
|
+
Example: 0.0
|
235
|
+
DESCRIPTION
|
236
|
+
|
237
|
+
expect(prompt).to include(expected_description)
|
98
238
|
end
|
99
239
|
end
|
100
240
|
|
@@ -106,12 +246,16 @@ RSpec.describe Lluminary::Models::Base do
|
|
106
246
|
end
|
107
247
|
|
108
248
|
it "formats datetime field description correctly" do
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
249
|
+
prompt = model.format_prompt(task)
|
250
|
+
|
251
|
+
expected_description = <<~DESCRIPTION.chomp
|
252
|
+
# created_at
|
253
|
+
Description: When the person was created
|
254
|
+
Type: datetime in ISO8601 format
|
255
|
+
Example: "2024-01-01T12:00:00+00:00"
|
256
|
+
DESCRIPTION
|
257
|
+
|
258
|
+
expect(prompt).to include(expected_description)
|
115
259
|
end
|
116
260
|
end
|
117
261
|
end
|
@@ -127,12 +271,16 @@ RSpec.describe Lluminary::Models::Base do
|
|
127
271
|
end
|
128
272
|
|
129
273
|
it "formats array of strings field description correctly" do
|
130
|
-
|
274
|
+
prompt = model.format_prompt(task)
|
275
|
+
|
276
|
+
expected_description = <<~DESCRIPTION.chomp
|
131
277
|
# tags
|
132
|
-
Type: array of string
|
133
278
|
Description: List of tags
|
134
|
-
|
279
|
+
Type: array of strings
|
280
|
+
Example: ["first tag","second tag"]
|
135
281
|
DESCRIPTION
|
282
|
+
|
283
|
+
expect(prompt).to include(expected_description)
|
136
284
|
end
|
137
285
|
end
|
138
286
|
|
@@ -146,12 +294,16 @@ RSpec.describe Lluminary::Models::Base do
|
|
146
294
|
end
|
147
295
|
|
148
296
|
it "formats array of floats field description correctly" do
|
149
|
-
|
297
|
+
prompt = model.format_prompt(task)
|
298
|
+
|
299
|
+
expected_description = <<~DESCRIPTION.chomp
|
150
300
|
# scores
|
151
|
-
Type: array of float
|
152
301
|
Description: List of scores
|
153
|
-
|
302
|
+
Type: array of floats
|
303
|
+
Example: [1.0,2.0,3.0]
|
154
304
|
DESCRIPTION
|
305
|
+
|
306
|
+
expect(prompt).to include(expected_description)
|
155
307
|
end
|
156
308
|
end
|
157
309
|
|
@@ -165,31 +317,83 @@ RSpec.describe Lluminary::Models::Base do
|
|
165
317
|
end
|
166
318
|
|
167
319
|
it "formats array of datetimes field description correctly" do
|
168
|
-
|
320
|
+
prompt = model.format_prompt(task)
|
321
|
+
|
322
|
+
expected_description = <<~DESCRIPTION.chomp
|
169
323
|
# dates
|
170
|
-
Type: array of datetime in ISO8601 format
|
171
324
|
Description: List of important dates
|
172
|
-
|
325
|
+
Type: array of datetimes in ISO8601 format
|
326
|
+
Example: ["2024-01-01T12:00:00+00:00","2024-01-02T12:00:00+00:00"]
|
173
327
|
DESCRIPTION
|
328
|
+
|
329
|
+
expect(prompt).to include(expected_description)
|
174
330
|
end
|
175
331
|
end
|
176
332
|
|
177
|
-
context "with 2D array (matrix)" do
|
333
|
+
context "with 2D array (matrix) with descriptions" do
|
178
334
|
before do
|
179
335
|
task_class.output_schema do
|
180
336
|
array :matrix, description: "2D array of numbers" do
|
181
|
-
array
|
337
|
+
array description: "1D array of numbers" do
|
338
|
+
integer description: "A number"
|
339
|
+
end
|
182
340
|
end
|
183
341
|
end
|
184
342
|
end
|
185
343
|
|
186
344
|
it "formats 2D array field description correctly" do
|
187
|
-
|
345
|
+
prompt = model.format_prompt(task)
|
346
|
+
|
347
|
+
expected_description = <<~DESCRIPTION.chomp
|
188
348
|
# matrix
|
189
|
-
Type: array of array of integer
|
190
349
|
Description: 2D array of numbers
|
191
|
-
|
350
|
+
Type: array of arrays
|
351
|
+
Example: [[1,2,3],[1,2,3]]
|
352
|
+
|
353
|
+
# matrix[]
|
354
|
+
Description: 1D array of numbers
|
355
|
+
Type: array of integers
|
356
|
+
Example: [1,2,3]
|
357
|
+
|
358
|
+
# matrix[][]
|
359
|
+
Description: A number
|
360
|
+
Type: integer
|
361
|
+
Example: 0
|
192
362
|
DESCRIPTION
|
363
|
+
|
364
|
+
expect(prompt).to include(expected_description)
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
context "with 2D array (matrix) with some descriptions" do
|
369
|
+
before do
|
370
|
+
task_class.output_schema do
|
371
|
+
array :matrix, description: "2D array of numbers" do
|
372
|
+
array { integer description: "A number" }
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
it "formats 2D array field description correctly" do
|
378
|
+
prompt = model.format_prompt(task)
|
379
|
+
|
380
|
+
expected_description = <<~DESCRIPTION.chomp
|
381
|
+
# matrix
|
382
|
+
Description: 2D array of numbers
|
383
|
+
Type: array of arrays
|
384
|
+
Example: [[1,2,3],[1,2,3]]
|
385
|
+
|
386
|
+
# matrix[]
|
387
|
+
Type: array of integers
|
388
|
+
Example: [1,2,3]
|
389
|
+
|
390
|
+
# matrix[][]
|
391
|
+
Description: A number
|
392
|
+
Type: integer
|
393
|
+
Example: 0
|
394
|
+
DESCRIPTION
|
395
|
+
|
396
|
+
expect(prompt).to include(expected_description)
|
193
397
|
end
|
194
398
|
end
|
195
399
|
|
@@ -197,18 +401,282 @@ RSpec.describe Lluminary::Models::Base do
|
|
197
401
|
before do
|
198
402
|
task_class.output_schema do
|
199
403
|
array :cube, description: "3D array of strings" do
|
200
|
-
array
|
404
|
+
array do
|
405
|
+
array description: "1D array" do
|
406
|
+
string
|
407
|
+
end
|
408
|
+
end
|
201
409
|
end
|
202
410
|
end
|
203
411
|
end
|
204
412
|
|
205
413
|
it "formats 3D array field description correctly" do
|
206
|
-
|
414
|
+
prompt = model.format_prompt(task)
|
415
|
+
|
416
|
+
expected_description = <<~DESCRIPTION.chomp
|
207
417
|
# cube
|
208
|
-
Type: array of array of array of string
|
209
418
|
Description: 3D array of strings
|
210
|
-
|
419
|
+
Type: array of arrays
|
420
|
+
Example: [[["first item","second item"],["first item","second item"]],[["first item","second item"],["first item","second item"]]]
|
421
|
+
|
422
|
+
# cube[]
|
423
|
+
Type: array of arrays
|
424
|
+
Example: [["first item","second item"],["first item","second item"]]
|
425
|
+
|
426
|
+
# cube[][]
|
427
|
+
Description: 1D array
|
428
|
+
Type: array of strings
|
429
|
+
Example: ["first item","second item"]
|
430
|
+
|
431
|
+
# cube[][][]
|
432
|
+
Type: string
|
433
|
+
Example: "first item"
|
434
|
+
DESCRIPTION
|
435
|
+
|
436
|
+
expect(prompt).to include(expected_description)
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
context "with array containing hash" do
|
441
|
+
before do
|
442
|
+
task_class.output_schema do
|
443
|
+
array :contacts, description: "List of contacts" do
|
444
|
+
hash { string :name, description: "Contact name" }
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
it "formats array of hashes field description correctly" do
|
450
|
+
prompt = model.format_prompt(task)
|
451
|
+
|
452
|
+
expected_description = <<~DESCRIPTION.chomp
|
453
|
+
# contacts
|
454
|
+
Description: List of contacts
|
455
|
+
Type: array of objects
|
456
|
+
Example: [{"name":"your name here"},{"name":"your name here"}]
|
457
|
+
|
458
|
+
# contacts[].name
|
459
|
+
Description: Contact name
|
460
|
+
Type: string
|
461
|
+
Example: "your name here"
|
462
|
+
DESCRIPTION
|
463
|
+
|
464
|
+
expect(prompt).to include(expected_description)
|
465
|
+
end
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
context "with hash fields" do
|
470
|
+
context "with simple hash with one field" do
|
471
|
+
before do
|
472
|
+
task_class.output_schema do
|
473
|
+
hash :person do
|
474
|
+
string :name, description: "The person's name"
|
475
|
+
end
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
it "formats hash field description correctly" do
|
480
|
+
prompt = model.format_prompt(task)
|
481
|
+
|
482
|
+
expected_description = <<~DESCRIPTION.chomp
|
483
|
+
# person
|
484
|
+
Type: object
|
485
|
+
Example: {"name":"your name here"}
|
486
|
+
|
487
|
+
# person.name
|
488
|
+
Description: The person's name
|
489
|
+
Type: string
|
490
|
+
Example: "your name here"
|
491
|
+
DESCRIPTION
|
492
|
+
|
493
|
+
expect(prompt).to include(expected_description)
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
context "with hash with two fields (one with description)" do
|
498
|
+
before do
|
499
|
+
task_class.output_schema do
|
500
|
+
hash :person, description: "A person" do
|
501
|
+
string :name, description: "The person's name"
|
502
|
+
integer :age
|
503
|
+
end
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
it "formats hash field descriptions correctly" do
|
508
|
+
prompt = model.format_prompt(task)
|
509
|
+
|
510
|
+
expected_description = <<~DESCRIPTION.chomp
|
511
|
+
# person
|
512
|
+
Description: A person
|
513
|
+
Type: object
|
514
|
+
Example: {"name":"your name here","age":0}
|
515
|
+
|
516
|
+
# person.name
|
517
|
+
Description: The person's name
|
518
|
+
Type: string
|
519
|
+
Example: "your name here"
|
520
|
+
|
521
|
+
# person.age
|
522
|
+
Type: integer
|
523
|
+
Example: 0
|
524
|
+
DESCRIPTION
|
525
|
+
|
526
|
+
expect(prompt).to include(expected_description)
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
context "with hash containing datetime field" do
|
531
|
+
before do
|
532
|
+
task_class.output_schema do
|
533
|
+
hash :event, description: "An event" do
|
534
|
+
string :title
|
535
|
+
datetime :scheduled_at, description: "When the event is scheduled"
|
536
|
+
end
|
537
|
+
end
|
538
|
+
end
|
539
|
+
|
540
|
+
it "formats hash with datetime field description correctly" do
|
541
|
+
prompt = model.format_prompt(task)
|
542
|
+
|
543
|
+
expected_description = <<~DESCRIPTION.chomp
|
544
|
+
# event
|
545
|
+
Description: An event
|
546
|
+
Type: object
|
547
|
+
Example: {"title":"your title here","scheduled_at":"2024-01-01T12:00:00+00:00"}
|
548
|
+
|
549
|
+
# event.title
|
550
|
+
Type: string
|
551
|
+
Example: "your title here"
|
552
|
+
|
553
|
+
# event.scheduled_at
|
554
|
+
Description: When the event is scheduled
|
555
|
+
Type: datetime in ISO8601 format
|
556
|
+
Example: "2024-01-01T12:00:00+00:00"
|
557
|
+
DESCRIPTION
|
558
|
+
|
559
|
+
expect(prompt).to include(expected_description)
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
563
|
+
context "with hash containing array field" do
|
564
|
+
before do
|
565
|
+
task_class.output_schema do
|
566
|
+
hash :user, description: "A user profile" do
|
567
|
+
string :name, description: "The person's name"
|
568
|
+
array :tags, description: "User tags" do
|
569
|
+
string
|
570
|
+
end
|
571
|
+
end
|
572
|
+
end
|
573
|
+
end
|
574
|
+
|
575
|
+
it "formats hash with array field description correctly" do
|
576
|
+
prompt = model.format_prompt(task)
|
577
|
+
|
578
|
+
expected_description = <<~DESCRIPTION.chomp
|
579
|
+
# user
|
580
|
+
Description: A user profile
|
581
|
+
Type: object
|
582
|
+
Example: {"name":"your name here","tags":["first tag","second tag"]}
|
583
|
+
|
584
|
+
# user.name
|
585
|
+
Description: The person's name
|
586
|
+
Type: string
|
587
|
+
Example: "your name here"
|
588
|
+
|
589
|
+
# user.tags
|
590
|
+
Description: User tags
|
591
|
+
Type: array of strings
|
592
|
+
Example: ["first tag","second tag"]
|
593
|
+
DESCRIPTION
|
594
|
+
|
595
|
+
expect(prompt).to include(expected_description)
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
context "with nested hashes (mixed descriptions)" do
|
600
|
+
before do
|
601
|
+
task_class.output_schema do
|
602
|
+
hash :person, description: "A person profile" do
|
603
|
+
string :name, description: "The person's name"
|
604
|
+
integer :age
|
605
|
+
hash :address do
|
606
|
+
string :street, description: "Street name"
|
607
|
+
string :city
|
608
|
+
string :country
|
609
|
+
end
|
610
|
+
hash :preferences, description: "User preferences" do
|
611
|
+
boolean :notifications
|
612
|
+
hash :theme do
|
613
|
+
string :color, description: "Theme color"
|
614
|
+
boolean :dark_mode
|
615
|
+
end
|
616
|
+
end
|
617
|
+
end
|
618
|
+
end
|
619
|
+
end
|
620
|
+
|
621
|
+
it "formats nested hash fields correctly with mixed descriptions" do
|
622
|
+
prompt = model.format_prompt(task)
|
623
|
+
|
624
|
+
expected_description = <<~DESCRIPTION.chomp
|
625
|
+
# person
|
626
|
+
Description: A person profile
|
627
|
+
Type: object
|
628
|
+
Example: {"name":"your name here","age":0,"address":{"street":"your street here","city":"your city here","country":"your country here"},"preferences":{"notifications":true,"theme":{"color":"your color here","dark_mode":true}}}
|
629
|
+
|
630
|
+
# person.name
|
631
|
+
Description: The person's name
|
632
|
+
Type: string
|
633
|
+
Example: "your name here"
|
634
|
+
|
635
|
+
# person.age
|
636
|
+
Type: integer
|
637
|
+
Example: 0
|
638
|
+
|
639
|
+
# person.address
|
640
|
+
Type: object
|
641
|
+
Example: {"street":"your street here","city":"your city here","country":"your country here"}
|
642
|
+
|
643
|
+
# person.address.street
|
644
|
+
Description: Street name
|
645
|
+
Type: string
|
646
|
+
Example: "your street here"
|
647
|
+
|
648
|
+
# person.address.city
|
649
|
+
Type: string
|
650
|
+
Example: "your city here"
|
651
|
+
|
652
|
+
# person.address.country
|
653
|
+
Type: string
|
654
|
+
Example: "your country here"
|
655
|
+
|
656
|
+
# person.preferences
|
657
|
+
Description: User preferences
|
658
|
+
Type: object
|
659
|
+
Example: {"notifications":true,"theme":{"color":"your color here","dark_mode":true}}
|
660
|
+
|
661
|
+
# person.preferences.notifications
|
662
|
+
Type: boolean
|
663
|
+
Example: true
|
664
|
+
|
665
|
+
# person.preferences.theme
|
666
|
+
Type: object
|
667
|
+
Example: {"color":"your color here","dark_mode":true}
|
668
|
+
|
669
|
+
# person.preferences.theme.color
|
670
|
+
Description: Theme color
|
671
|
+
Type: string
|
672
|
+
Example: "your color here"
|
673
|
+
|
674
|
+
# person.preferences.theme.dark_mode
|
675
|
+
Type: boolean
|
676
|
+
Example: true
|
211
677
|
DESCRIPTION
|
678
|
+
|
679
|
+
expect(prompt).to include(expected_description)
|
212
680
|
end
|
213
681
|
end
|
214
682
|
end
|
@@ -223,13 +691,17 @@ RSpec.describe Lluminary::Models::Base do
|
|
223
691
|
end
|
224
692
|
|
225
693
|
it "includes presence validation in field description" do
|
226
|
-
|
694
|
+
prompt = model.format_prompt(task)
|
695
|
+
|
696
|
+
expected_description = <<~DESCRIPTION.chomp
|
227
697
|
# name
|
228
|
-
Type: string
|
229
698
|
Description: The person's name
|
699
|
+
Type: string
|
230
700
|
Validations: must be present
|
231
|
-
Example: your name here
|
701
|
+
Example: "your name here"
|
232
702
|
DESCRIPTION
|
703
|
+
|
704
|
+
expect(prompt).to include(expected_description)
|
233
705
|
end
|
234
706
|
end
|
235
707
|
|
@@ -242,13 +714,17 @@ RSpec.describe Lluminary::Models::Base do
|
|
242
714
|
end
|
243
715
|
|
244
716
|
it "includes inclusion validation in field description" do
|
245
|
-
|
717
|
+
prompt = model.format_prompt(task)
|
718
|
+
|
719
|
+
expected_description = <<~DESCRIPTION.chomp
|
246
720
|
# status
|
247
|
-
Type: string
|
248
721
|
Description: The status
|
722
|
+
Type: string
|
249
723
|
Validations: must be one of: active, inactive
|
250
|
-
Example: your status here
|
724
|
+
Example: "your status here"
|
251
725
|
DESCRIPTION
|
726
|
+
|
727
|
+
expect(prompt).to include(expected_description)
|
252
728
|
end
|
253
729
|
end
|
254
730
|
|
@@ -261,13 +737,17 @@ RSpec.describe Lluminary::Models::Base do
|
|
261
737
|
end
|
262
738
|
|
263
739
|
it "includes exclusion validation in field description" do
|
264
|
-
|
740
|
+
prompt = model.format_prompt(task)
|
741
|
+
|
742
|
+
expected_description = <<~DESCRIPTION.chomp
|
265
743
|
# status
|
266
|
-
Type: string
|
267
744
|
Description: The status
|
745
|
+
Type: string
|
268
746
|
Validations: must not be one of: banned, blocked
|
269
|
-
Example: your status here
|
747
|
+
Example: "your status here"
|
270
748
|
DESCRIPTION
|
749
|
+
|
750
|
+
expect(prompt).to include(expected_description)
|
271
751
|
end
|
272
752
|
end
|
273
753
|
|
@@ -280,13 +760,17 @@ RSpec.describe Lluminary::Models::Base do
|
|
280
760
|
end
|
281
761
|
|
282
762
|
it "includes format validation in field description" do
|
283
|
-
|
763
|
+
prompt = model.format_prompt(task)
|
764
|
+
|
765
|
+
expected_description = <<~DESCRIPTION.chomp
|
284
766
|
# email
|
285
|
-
Type: string
|
286
767
|
Description: Email address
|
768
|
+
Type: string
|
287
769
|
Validations: must match format: (?-mix:\\A[^@\\s]+@[^@\\s]+\\z)
|
288
|
-
Example: your email here
|
770
|
+
Example: "your email here"
|
289
771
|
DESCRIPTION
|
772
|
+
|
773
|
+
expect(prompt).to include(expected_description)
|
290
774
|
end
|
291
775
|
end
|
292
776
|
|
@@ -299,13 +783,17 @@ RSpec.describe Lluminary::Models::Base do
|
|
299
783
|
end
|
300
784
|
|
301
785
|
it "includes length validation in field description" do
|
302
|
-
|
786
|
+
prompt = model.format_prompt(task)
|
787
|
+
|
788
|
+
expected_description = <<~DESCRIPTION.chomp
|
303
789
|
# password
|
304
|
-
Type: string
|
305
790
|
Description: The password
|
306
|
-
|
307
|
-
|
791
|
+
Type: string
|
792
|
+
Validations: must have at least 8 characters, must have at most 20 characters
|
793
|
+
Example: "your password here"
|
308
794
|
DESCRIPTION
|
795
|
+
|
796
|
+
expect(prompt).to include(expected_description)
|
309
797
|
end
|
310
798
|
end
|
311
799
|
|
@@ -322,13 +810,17 @@ RSpec.describe Lluminary::Models::Base do
|
|
322
810
|
end
|
323
811
|
|
324
812
|
it "includes numericality validation in field description" do
|
325
|
-
|
813
|
+
prompt = model.format_prompt(task)
|
814
|
+
|
815
|
+
expected_description = <<~DESCRIPTION.chomp
|
326
816
|
# age
|
327
|
-
Type: integer
|
328
817
|
Description: The age
|
818
|
+
Type: integer
|
329
819
|
Validations: must be greater than 0, must be less than or equal to 120
|
330
820
|
Example: 0
|
331
821
|
DESCRIPTION
|
822
|
+
|
823
|
+
expect(prompt).to include(expected_description)
|
332
824
|
end
|
333
825
|
end
|
334
826
|
|
@@ -348,17 +840,233 @@ RSpec.describe Lluminary::Models::Base do
|
|
348
840
|
end
|
349
841
|
|
350
842
|
it "includes all validations in field description" do
|
351
|
-
|
843
|
+
prompt = model.format_prompt(task)
|
844
|
+
|
845
|
+
expected_description = <<~DESCRIPTION.chomp
|
352
846
|
# username
|
353
|
-
Type: string
|
354
847
|
Description: The username
|
355
|
-
|
356
|
-
|
848
|
+
Type: string
|
849
|
+
Validations: must be present, must have between 3 and 20 characters, must match format: (?-mix:\\A[a-z0-9_]+\\z)
|
850
|
+
Example: "your username here"
|
357
851
|
DESCRIPTION
|
852
|
+
|
853
|
+
expect(prompt).to include(expected_description)
|
854
|
+
end
|
855
|
+
end
|
856
|
+
|
857
|
+
context "array validations" do
|
858
|
+
context "presence validation" do
|
859
|
+
before do
|
860
|
+
task_class.output_schema do
|
861
|
+
array :tags, description: "List of tags" do
|
862
|
+
string
|
863
|
+
end
|
864
|
+
validates :tags, presence: true
|
865
|
+
end
|
866
|
+
end
|
867
|
+
|
868
|
+
it "includes presence validation in array description" do
|
869
|
+
prompt = model.format_prompt(task)
|
870
|
+
|
871
|
+
expected_description = <<~DESCRIPTION.chomp
|
872
|
+
# tags
|
873
|
+
Description: List of tags
|
874
|
+
Type: array of strings
|
875
|
+
Validations: must be present
|
876
|
+
Example: ["first tag","second tag"]
|
877
|
+
DESCRIPTION
|
878
|
+
|
879
|
+
expect(prompt).to include(expected_description)
|
880
|
+
end
|
881
|
+
end
|
882
|
+
|
883
|
+
context "length validation - minimum" do
|
884
|
+
before do
|
885
|
+
task_class.output_schema do
|
886
|
+
array :categories, description: "List of categories" do
|
887
|
+
string
|
888
|
+
end
|
889
|
+
validates :categories, length: { minimum: 2 }
|
890
|
+
end
|
891
|
+
end
|
892
|
+
|
893
|
+
it "includes minimum length validation in array description" do
|
894
|
+
prompt = model.format_prompt(task)
|
895
|
+
|
896
|
+
expected_description = <<~DESCRIPTION.chomp
|
897
|
+
# categories
|
898
|
+
Description: List of categories
|
899
|
+
Type: array of strings
|
900
|
+
Validations: must have at least 2 elements
|
901
|
+
Example: ["first category","second category"]
|
902
|
+
DESCRIPTION
|
903
|
+
|
904
|
+
expect(prompt).to include(expected_description)
|
905
|
+
end
|
906
|
+
end
|
907
|
+
|
908
|
+
context "length validation - maximum" do
|
909
|
+
before do
|
910
|
+
task_class.output_schema do
|
911
|
+
array :roles, description: "User roles" do
|
912
|
+
string
|
913
|
+
end
|
914
|
+
validates :roles, length: { maximum: 5 }
|
915
|
+
end
|
916
|
+
end
|
917
|
+
|
918
|
+
it "includes maximum length validation in array description" do
|
919
|
+
prompt = model.format_prompt(task)
|
920
|
+
|
921
|
+
expected_description = <<~DESCRIPTION.chomp
|
922
|
+
# roles
|
923
|
+
Description: User roles
|
924
|
+
Type: array of strings
|
925
|
+
Validations: must have at most 5 elements
|
926
|
+
Example: ["first role","second role"]
|
927
|
+
DESCRIPTION
|
928
|
+
|
929
|
+
expect(prompt).to include(expected_description)
|
930
|
+
end
|
931
|
+
end
|
932
|
+
|
933
|
+
context "length validation - range" do
|
934
|
+
before do
|
935
|
+
task_class.output_schema do
|
936
|
+
array :features, description: "Product features" do
|
937
|
+
string
|
938
|
+
end
|
939
|
+
validates :features, length: { in: 2..4 }
|
940
|
+
end
|
941
|
+
end
|
942
|
+
|
943
|
+
it "includes range length validation in array description" do
|
944
|
+
prompt = model.format_prompt(task)
|
945
|
+
|
946
|
+
expected_description = <<~DESCRIPTION.chomp
|
947
|
+
# features
|
948
|
+
Description: Product features
|
949
|
+
Type: array of strings
|
950
|
+
Validations: must have between 2 and 4 elements
|
951
|
+
Example: ["first feature","second feature"]
|
952
|
+
DESCRIPTION
|
953
|
+
|
954
|
+
expect(prompt).to include(expected_description)
|
955
|
+
end
|
956
|
+
end
|
957
|
+
|
958
|
+
context "length validation - exact" do
|
959
|
+
before do
|
960
|
+
task_class.output_schema do
|
961
|
+
array :coordinates, description: "Point coordinates" do
|
962
|
+
float
|
963
|
+
end
|
964
|
+
validates :coordinates, length: { is: 3 }
|
965
|
+
end
|
966
|
+
end
|
967
|
+
|
968
|
+
it "includes exact length validation in array description" do
|
969
|
+
prompt = model.format_prompt(task)
|
970
|
+
|
971
|
+
expected_description = <<~DESCRIPTION.chomp
|
972
|
+
# coordinates
|
973
|
+
Description: Point coordinates
|
974
|
+
Type: array of floats
|
975
|
+
Validations: must have exactly 3 elements
|
976
|
+
Example: [1.0,2.0,3.0]
|
977
|
+
DESCRIPTION
|
978
|
+
|
979
|
+
expect(prompt).to include(expected_description)
|
980
|
+
end
|
981
|
+
end
|
982
|
+
|
983
|
+
context "absence validation" do
|
984
|
+
before do
|
985
|
+
task_class.output_schema do
|
986
|
+
array :deprecated_fields, description: "Deprecated fields" do
|
987
|
+
string
|
988
|
+
end
|
989
|
+
validates :deprecated_fields, absence: true
|
990
|
+
end
|
991
|
+
end
|
992
|
+
|
993
|
+
it "includes absence validation in array description" do
|
994
|
+
prompt = model.format_prompt(task)
|
995
|
+
|
996
|
+
expected_description = <<~DESCRIPTION.chomp
|
997
|
+
# deprecated_fields
|
998
|
+
Description: Deprecated fields
|
999
|
+
Type: array of strings
|
1000
|
+
Validations: must be absent
|
1001
|
+
Example: ["first deprecated_field","second deprecated_field"]
|
1002
|
+
DESCRIPTION
|
1003
|
+
|
1004
|
+
expect(prompt).to include(expected_description)
|
1005
|
+
end
|
1006
|
+
end
|
1007
|
+
end
|
1008
|
+
|
1009
|
+
context "hash validations" do
|
1010
|
+
context "presence validation" do
|
1011
|
+
before do
|
1012
|
+
task_class.output_schema do
|
1013
|
+
hash :user_settings, description: "User configuration settings" do
|
1014
|
+
string :theme
|
1015
|
+
boolean :notifications_enabled
|
1016
|
+
end
|
1017
|
+
validates :user_settings, presence: true
|
1018
|
+
end
|
1019
|
+
end
|
1020
|
+
|
1021
|
+
it "includes presence validation in hash field description" do
|
1022
|
+
prompt = model.format_prompt(task)
|
1023
|
+
|
1024
|
+
expected_description = <<~DESCRIPTION.chomp
|
1025
|
+
# user_settings
|
1026
|
+
Description: User configuration settings
|
1027
|
+
Type: object
|
1028
|
+
Validations: must be present
|
1029
|
+
Example: {"theme":"your theme here","notifications_enabled":true}
|
1030
|
+
DESCRIPTION
|
1031
|
+
|
1032
|
+
expect(prompt).to include(expected_description)
|
1033
|
+
end
|
358
1034
|
end
|
359
1035
|
end
|
360
1036
|
end
|
361
1037
|
|
1038
|
+
context "with custom validation descriptions" do
|
1039
|
+
before do
|
1040
|
+
task_class.output_schema do
|
1041
|
+
string :name, description: "The person's name"
|
1042
|
+
integer :confidence, description: "Confidence score from 0-100"
|
1043
|
+
validate :validate_confidence_score,
|
1044
|
+
description: "Confidence score must be between 0 and 100"
|
1045
|
+
validate :validate_other_thing, description: nil
|
1046
|
+
end
|
1047
|
+
end
|
1048
|
+
|
1049
|
+
it "includes an Additional Validations section with non-nil descriptions" do
|
1050
|
+
prompt = model.format_prompt(task)
|
1051
|
+
expect(prompt).to include("Additional Validations:")
|
1052
|
+
expect(prompt).to include(
|
1053
|
+
"- Confidence score must be between 0 and 100"
|
1054
|
+
)
|
1055
|
+
expect(prompt).not_to include("- \n") # Should not include a blank bullet for nil
|
1056
|
+
end
|
1057
|
+
|
1058
|
+
it "omits Additional Validations section if all descriptions are nil" do
|
1059
|
+
# Redefine schema with only nil descriptions
|
1060
|
+
task_class.output_schema do
|
1061
|
+
string :name, description: "The person's name"
|
1062
|
+
validate :validate_confidence_score, description: nil
|
1063
|
+
validate :validate_other_thing, description: nil
|
1064
|
+
end
|
1065
|
+
prompt = model.format_prompt(task)
|
1066
|
+
expect(prompt).not_to include("Additional Validations:")
|
1067
|
+
end
|
1068
|
+
end
|
1069
|
+
|
362
1070
|
context "JSON example generation" do
|
363
1071
|
context "with simple field types" do
|
364
1072
|
it "generates correct JSON example for string field" do
|
@@ -366,11 +1074,15 @@ RSpec.describe Lluminary::Models::Base do
|
|
366
1074
|
string :name, description: "The person's name"
|
367
1075
|
end
|
368
1076
|
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
1077
|
+
prompt = model.format_prompt(task)
|
1078
|
+
|
1079
|
+
expected_json = <<~JSON.chomp
|
1080
|
+
{
|
1081
|
+
"name": "your name here"
|
1082
|
+
}
|
1083
|
+
JSON
|
1084
|
+
|
1085
|
+
expect(prompt).to include(expected_json)
|
374
1086
|
end
|
375
1087
|
|
376
1088
|
it "generates correct JSON example for integer field" do
|
@@ -378,11 +1090,15 @@ RSpec.describe Lluminary::Models::Base do
|
|
378
1090
|
integer :age, description: "The person's age"
|
379
1091
|
end
|
380
1092
|
|
381
|
-
|
1093
|
+
prompt = model.format_prompt(task)
|
1094
|
+
|
1095
|
+
expected_json = <<~JSON.chomp
|
382
1096
|
{
|
383
1097
|
"age": 0
|
384
1098
|
}
|
385
1099
|
JSON
|
1100
|
+
|
1101
|
+
expect(prompt).to include(expected_json)
|
386
1102
|
end
|
387
1103
|
|
388
1104
|
it "generates correct JSON example for boolean field" do
|
@@ -390,21 +1106,29 @@ RSpec.describe Lluminary::Models::Base do
|
|
390
1106
|
boolean :is_active, description: "Whether the person is active"
|
391
1107
|
end
|
392
1108
|
|
393
|
-
|
1109
|
+
prompt = model.format_prompt(task)
|
1110
|
+
|
1111
|
+
expected_json = <<~JSON.chomp
|
394
1112
|
{
|
395
1113
|
"is_active": true
|
396
1114
|
}
|
397
1115
|
JSON
|
1116
|
+
|
1117
|
+
expect(prompt).to include(expected_json)
|
398
1118
|
end
|
399
1119
|
|
400
1120
|
it "generates correct JSON example for float field" do
|
401
1121
|
task_class.output_schema { float :score, description: "The score" }
|
402
1122
|
|
403
|
-
|
1123
|
+
prompt = model.format_prompt(task)
|
1124
|
+
|
1125
|
+
expected_json = <<~JSON.chomp
|
404
1126
|
{
|
405
1127
|
"score": 0.0
|
406
1128
|
}
|
407
1129
|
JSON
|
1130
|
+
|
1131
|
+
expect(prompt).to include(expected_json)
|
408
1132
|
end
|
409
1133
|
|
410
1134
|
it "generates correct JSON example for datetime field" do
|
@@ -412,11 +1136,15 @@ RSpec.describe Lluminary::Models::Base do
|
|
412
1136
|
datetime :created_at, description: "When it was created"
|
413
1137
|
end
|
414
1138
|
|
415
|
-
|
1139
|
+
prompt = model.format_prompt(task)
|
1140
|
+
|
1141
|
+
expected_json = <<~JSON.chomp
|
416
1142
|
{
|
417
1143
|
"created_at": "2024-01-01T12:00:00+00:00"
|
418
1144
|
}
|
419
1145
|
JSON
|
1146
|
+
|
1147
|
+
expect(prompt).to include(expected_json)
|
420
1148
|
end
|
421
1149
|
end
|
422
1150
|
|
@@ -428,15 +1156,18 @@ RSpec.describe Lluminary::Models::Base do
|
|
428
1156
|
end
|
429
1157
|
end
|
430
1158
|
|
431
|
-
|
1159
|
+
prompt = model.format_prompt(task)
|
1160
|
+
|
1161
|
+
expected_json = <<~JSON.chomp
|
432
1162
|
{
|
433
1163
|
"tags": [
|
434
1164
|
"first tag",
|
435
|
-
"second tag"
|
436
|
-
"..."
|
1165
|
+
"second tag"
|
437
1166
|
]
|
438
1167
|
}
|
439
1168
|
JSON
|
1169
|
+
|
1170
|
+
expect(prompt).to include(expected_json)
|
440
1171
|
end
|
441
1172
|
|
442
1173
|
it "generates correct JSON example for array of integers" do
|
@@ -446,7 +1177,9 @@ RSpec.describe Lluminary::Models::Base do
|
|
446
1177
|
end
|
447
1178
|
end
|
448
1179
|
|
449
|
-
|
1180
|
+
prompt = model.format_prompt(task)
|
1181
|
+
|
1182
|
+
expected_json = <<~JSON.chomp
|
450
1183
|
{
|
451
1184
|
"counts": [
|
452
1185
|
1,
|
@@ -455,6 +1188,8 @@ RSpec.describe Lluminary::Models::Base do
|
|
455
1188
|
]
|
456
1189
|
}
|
457
1190
|
JSON
|
1191
|
+
|
1192
|
+
expect(prompt).to include(expected_json)
|
458
1193
|
end
|
459
1194
|
|
460
1195
|
it "generates correct JSON example for array of datetimes" do
|
@@ -464,7 +1199,9 @@ RSpec.describe Lluminary::Models::Base do
|
|
464
1199
|
end
|
465
1200
|
end
|
466
1201
|
|
467
|
-
|
1202
|
+
prompt = model.format_prompt(task)
|
1203
|
+
|
1204
|
+
expected_json = <<~JSON.chomp
|
468
1205
|
{
|
469
1206
|
"timestamps": [
|
470
1207
|
"2024-01-01T12:00:00+00:00",
|
@@ -472,6 +1209,8 @@ RSpec.describe Lluminary::Models::Base do
|
|
472
1209
|
]
|
473
1210
|
}
|
474
1211
|
JSON
|
1212
|
+
|
1213
|
+
expect(prompt).to include(expected_json)
|
475
1214
|
end
|
476
1215
|
|
477
1216
|
it "generates correct JSON example for array without element type" do
|
@@ -479,11 +1218,15 @@ RSpec.describe Lluminary::Models::Base do
|
|
479
1218
|
array :items, description: "List of items"
|
480
1219
|
end
|
481
1220
|
|
482
|
-
|
1221
|
+
prompt = model.format_prompt(task)
|
1222
|
+
|
1223
|
+
expected_json = <<~JSON.chomp
|
483
1224
|
{
|
484
1225
|
"items": []
|
485
1226
|
}
|
486
1227
|
JSON
|
1228
|
+
|
1229
|
+
expect(prompt).to include(expected_json)
|
487
1230
|
end
|
488
1231
|
|
489
1232
|
it "generates correct JSON example for nested array of strings" do
|
@@ -493,22 +1236,24 @@ RSpec.describe Lluminary::Models::Base do
|
|
493
1236
|
end
|
494
1237
|
end
|
495
1238
|
|
496
|
-
|
1239
|
+
prompt = model.format_prompt(task)
|
1240
|
+
|
1241
|
+
expected_json = <<~JSON.chomp
|
497
1242
|
{
|
498
1243
|
"groups": [
|
499
1244
|
[
|
500
1245
|
"first item",
|
501
|
-
"second item"
|
502
|
-
"..."
|
1246
|
+
"second item"
|
503
1247
|
],
|
504
1248
|
[
|
505
1249
|
"first item",
|
506
|
-
"second item"
|
507
|
-
"..."
|
1250
|
+
"second item"
|
508
1251
|
]
|
509
1252
|
]
|
510
1253
|
}
|
511
1254
|
JSON
|
1255
|
+
|
1256
|
+
expect(prompt).to include(expected_json)
|
512
1257
|
end
|
513
1258
|
|
514
1259
|
it "generates correct JSON example for three-dimensional array of integers" do
|
@@ -518,7 +1263,9 @@ RSpec.describe Lluminary::Models::Base do
|
|
518
1263
|
end
|
519
1264
|
end
|
520
1265
|
|
521
|
-
|
1266
|
+
prompt = model.format_prompt(task)
|
1267
|
+
|
1268
|
+
expected_json = <<~JSON.chomp
|
522
1269
|
{
|
523
1270
|
"matrices": [
|
524
1271
|
[
|
@@ -548,6 +1295,8 @@ RSpec.describe Lluminary::Models::Base do
|
|
548
1295
|
]
|
549
1296
|
}
|
550
1297
|
JSON
|
1298
|
+
|
1299
|
+
expect(prompt).to include(expected_json)
|
551
1300
|
end
|
552
1301
|
end
|
553
1302
|
|
@@ -562,18 +1311,102 @@ RSpec.describe Lluminary::Models::Base do
|
|
562
1311
|
datetime :joined_at, description: "When they joined"
|
563
1312
|
end
|
564
1313
|
|
565
|
-
|
566
|
-
|
1314
|
+
prompt = model.format_prompt(task)
|
1315
|
+
|
1316
|
+
expected_json = <<~JSON.chomp
|
1317
|
+
{
|
1318
|
+
"name": "your name here",
|
1319
|
+
"age": 0,
|
1320
|
+
"hobbies": [
|
1321
|
+
"first hobby",
|
1322
|
+
"second hobby"
|
1323
|
+
],
|
1324
|
+
"joined_at": "2024-01-01T12:00:00+00:00"
|
1325
|
+
}
|
1326
|
+
JSON
|
1327
|
+
|
1328
|
+
expect(prompt).to include(expected_json)
|
1329
|
+
end
|
1330
|
+
end
|
1331
|
+
|
1332
|
+
context "with hash fields" do
|
1333
|
+
it "generates correct JSON example for simple hash" do
|
1334
|
+
task_class.output_schema do
|
1335
|
+
hash :config, description: "Configuration options" do
|
1336
|
+
string :host, description: "Server hostname"
|
1337
|
+
integer :port
|
1338
|
+
end
|
1339
|
+
end
|
1340
|
+
|
1341
|
+
prompt = model.format_prompt(task)
|
1342
|
+
|
1343
|
+
expected_json = <<~JSON.chomp
|
1344
|
+
{
|
1345
|
+
"config": {
|
1346
|
+
"host": "your host here",
|
1347
|
+
"port": 0
|
1348
|
+
}
|
1349
|
+
}
|
1350
|
+
JSON
|
1351
|
+
|
1352
|
+
expect(prompt).to include(expected_json)
|
1353
|
+
end
|
1354
|
+
|
1355
|
+
it "generates correct JSON example for nested hash" do
|
1356
|
+
task_class.output_schema do
|
1357
|
+
hash :user, description: "User profile" do
|
1358
|
+
string :name
|
1359
|
+
hash :address do
|
1360
|
+
string :street
|
1361
|
+
string :city
|
1362
|
+
string :country
|
1363
|
+
end
|
1364
|
+
end
|
1365
|
+
end
|
1366
|
+
|
1367
|
+
prompt = model.format_prompt(task)
|
1368
|
+
|
1369
|
+
expected_json = <<~JSON.chomp
|
1370
|
+
{
|
1371
|
+
"user": {
|
567
1372
|
"name": "your name here",
|
568
|
-
"
|
569
|
-
|
570
|
-
"
|
571
|
-
"
|
572
|
-
|
573
|
-
|
574
|
-
|
1373
|
+
"address": {
|
1374
|
+
"street": "your street here",
|
1375
|
+
"city": "your city here",
|
1376
|
+
"country": "your country here"
|
1377
|
+
}
|
1378
|
+
}
|
1379
|
+
}
|
1380
|
+
JSON
|
1381
|
+
|
1382
|
+
expect(prompt).to include(expected_json)
|
1383
|
+
end
|
1384
|
+
|
1385
|
+
it "generates correct JSON example for hash with array" do
|
1386
|
+
task_class.output_schema do
|
1387
|
+
hash :user_data, description: "User data" do
|
1388
|
+
string :username
|
1389
|
+
array :permissions do
|
1390
|
+
string
|
1391
|
+
end
|
1392
|
+
end
|
1393
|
+
end
|
1394
|
+
|
1395
|
+
prompt = model.format_prompt(task)
|
1396
|
+
|
1397
|
+
expected_json = <<~JSON.chomp
|
1398
|
+
{
|
1399
|
+
"user_data": {
|
1400
|
+
"username": "your username here",
|
1401
|
+
"permissions": [
|
1402
|
+
"first permission",
|
1403
|
+
"second permission"
|
1404
|
+
]
|
575
1405
|
}
|
1406
|
+
}
|
576
1407
|
JSON
|
1408
|
+
|
1409
|
+
expect(prompt).to include(expected_json)
|
577
1410
|
end
|
578
1411
|
end
|
579
1412
|
end
|