lluminary 0.1.4 → 0.2.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.
@@ -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
- expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
42
- # name
43
- Type: string
44
- Description: The person's name
45
- Example: your name here
46
- DESCRIPTION
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
- expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
59
- # age
60
- Type: integer
61
- Description: The person's age
62
- Example: 0
63
- DESCRIPTION
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
- expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
76
- # active
77
- Type: boolean
78
- Description: Whether the person is active
79
- Example: true
80
- DESCRIPTION
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
- expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
93
- # score
94
- Type: float
95
- Description: The person's score
96
- Example: 0.0
97
- DESCRIPTION
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
- expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
110
- # created_at
111
- Type: datetime in ISO8601 format
112
- Description: When the person was created
113
- Example: 2024-01-01T12:00:00+00:00
114
- DESCRIPTION
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
- expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
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
- Example: ["first tag", "second tag", "..."]
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
- expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
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
- Example: [1.0, 2.0, 3.0]
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
- expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
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
- Example: ["2024-01-01T12:00:00+00:00", "2024-01-02T12:00:00+00:00"]
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 { integer }
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
- expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
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
- Example: [[1, 2, 3], [1, 2, 3]]
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 { array { string } }
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
- expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
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
- Example: [[["first item", "second item", "..."], ["first item", "second item", "..."]], [["first item", "second item", "..."], ["first item", "second item", "..."]]]
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"
211
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
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
- expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
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
- expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
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
- expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
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
- expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
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
- expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
786
+ prompt = model.format_prompt(task)
787
+
788
+ expected_description = <<~DESCRIPTION.chomp
303
789
  # password
304
- Type: string
305
790
  Description: The password
306
- Validations: must be at least 8 characters, must be at most 20 characters
307
- Example: your password here
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
- expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
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,13 +840,197 @@ RSpec.describe Lluminary::Models::Base do
348
840
  end
349
841
 
350
842
  it "includes all validations in field description" do
351
- expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
843
+ prompt = model.format_prompt(task)
844
+
845
+ expected_description = <<~DESCRIPTION.chomp
352
846
  # username
353
- Type: string
354
847
  Description: The username
355
- Validations: must be present, must be between 3 and 20 characters, must match format: (?-mix:\\A[a-z0-9_]+\\z)
356
- Example: your username here
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
@@ -366,11 +1042,15 @@ RSpec.describe Lluminary::Models::Base do
366
1042
  string :name, description: "The person's name"
367
1043
  end
368
1044
 
369
- expect(model.format_prompt(task)).to include(<<~JSON.chomp)
370
- {
371
- "name": "your name here"
372
- }
373
- JSON
1045
+ prompt = model.format_prompt(task)
1046
+
1047
+ expected_json = <<~JSON.chomp
1048
+ {
1049
+ "name": "your name here"
1050
+ }
1051
+ JSON
1052
+
1053
+ expect(prompt).to include(expected_json)
374
1054
  end
375
1055
 
376
1056
  it "generates correct JSON example for integer field" do
@@ -378,11 +1058,15 @@ RSpec.describe Lluminary::Models::Base do
378
1058
  integer :age, description: "The person's age"
379
1059
  end
380
1060
 
381
- expect(model.format_prompt(task)).to include(<<~JSON.chomp)
1061
+ prompt = model.format_prompt(task)
1062
+
1063
+ expected_json = <<~JSON.chomp
382
1064
  {
383
1065
  "age": 0
384
1066
  }
385
1067
  JSON
1068
+
1069
+ expect(prompt).to include(expected_json)
386
1070
  end
387
1071
 
388
1072
  it "generates correct JSON example for boolean field" do
@@ -390,21 +1074,29 @@ RSpec.describe Lluminary::Models::Base do
390
1074
  boolean :is_active, description: "Whether the person is active"
391
1075
  end
392
1076
 
393
- expect(model.format_prompt(task)).to include(<<~JSON.chomp)
1077
+ prompt = model.format_prompt(task)
1078
+
1079
+ expected_json = <<~JSON.chomp
394
1080
  {
395
1081
  "is_active": true
396
1082
  }
397
1083
  JSON
1084
+
1085
+ expect(prompt).to include(expected_json)
398
1086
  end
399
1087
 
400
1088
  it "generates correct JSON example for float field" do
401
1089
  task_class.output_schema { float :score, description: "The score" }
402
1090
 
403
- expect(model.format_prompt(task)).to include(<<~JSON.chomp)
1091
+ prompt = model.format_prompt(task)
1092
+
1093
+ expected_json = <<~JSON.chomp
404
1094
  {
405
1095
  "score": 0.0
406
1096
  }
407
1097
  JSON
1098
+
1099
+ expect(prompt).to include(expected_json)
408
1100
  end
409
1101
 
410
1102
  it "generates correct JSON example for datetime field" do
@@ -412,11 +1104,15 @@ RSpec.describe Lluminary::Models::Base do
412
1104
  datetime :created_at, description: "When it was created"
413
1105
  end
414
1106
 
415
- expect(model.format_prompt(task)).to include(<<~JSON.chomp)
1107
+ prompt = model.format_prompt(task)
1108
+
1109
+ expected_json = <<~JSON.chomp
416
1110
  {
417
1111
  "created_at": "2024-01-01T12:00:00+00:00"
418
1112
  }
419
1113
  JSON
1114
+
1115
+ expect(prompt).to include(expected_json)
420
1116
  end
421
1117
  end
422
1118
 
@@ -428,15 +1124,18 @@ RSpec.describe Lluminary::Models::Base do
428
1124
  end
429
1125
  end
430
1126
 
431
- expect(model.format_prompt(task)).to include(<<~JSON.chomp)
1127
+ prompt = model.format_prompt(task)
1128
+
1129
+ expected_json = <<~JSON.chomp
432
1130
  {
433
1131
  "tags": [
434
1132
  "first tag",
435
- "second tag",
436
- "..."
1133
+ "second tag"
437
1134
  ]
438
1135
  }
439
1136
  JSON
1137
+
1138
+ expect(prompt).to include(expected_json)
440
1139
  end
441
1140
 
442
1141
  it "generates correct JSON example for array of integers" do
@@ -446,7 +1145,9 @@ RSpec.describe Lluminary::Models::Base do
446
1145
  end
447
1146
  end
448
1147
 
449
- expect(model.format_prompt(task)).to include(<<~JSON.chomp)
1148
+ prompt = model.format_prompt(task)
1149
+
1150
+ expected_json = <<~JSON.chomp
450
1151
  {
451
1152
  "counts": [
452
1153
  1,
@@ -455,6 +1156,8 @@ RSpec.describe Lluminary::Models::Base do
455
1156
  ]
456
1157
  }
457
1158
  JSON
1159
+
1160
+ expect(prompt).to include(expected_json)
458
1161
  end
459
1162
 
460
1163
  it "generates correct JSON example for array of datetimes" do
@@ -464,7 +1167,9 @@ RSpec.describe Lluminary::Models::Base do
464
1167
  end
465
1168
  end
466
1169
 
467
- expect(model.format_prompt(task)).to include(<<~JSON.chomp)
1170
+ prompt = model.format_prompt(task)
1171
+
1172
+ expected_json = <<~JSON.chomp
468
1173
  {
469
1174
  "timestamps": [
470
1175
  "2024-01-01T12:00:00+00:00",
@@ -472,6 +1177,8 @@ RSpec.describe Lluminary::Models::Base do
472
1177
  ]
473
1178
  }
474
1179
  JSON
1180
+
1181
+ expect(prompt).to include(expected_json)
475
1182
  end
476
1183
 
477
1184
  it "generates correct JSON example for array without element type" do
@@ -479,11 +1186,15 @@ RSpec.describe Lluminary::Models::Base do
479
1186
  array :items, description: "List of items"
480
1187
  end
481
1188
 
482
- expect(model.format_prompt(task)).to include(<<~JSON.chomp)
1189
+ prompt = model.format_prompt(task)
1190
+
1191
+ expected_json = <<~JSON.chomp
483
1192
  {
484
1193
  "items": []
485
1194
  }
486
1195
  JSON
1196
+
1197
+ expect(prompt).to include(expected_json)
487
1198
  end
488
1199
 
489
1200
  it "generates correct JSON example for nested array of strings" do
@@ -493,22 +1204,24 @@ RSpec.describe Lluminary::Models::Base do
493
1204
  end
494
1205
  end
495
1206
 
496
- expect(model.format_prompt(task)).to include(<<~JSON.chomp)
1207
+ prompt = model.format_prompt(task)
1208
+
1209
+ expected_json = <<~JSON.chomp
497
1210
  {
498
1211
  "groups": [
499
1212
  [
500
1213
  "first item",
501
- "second item",
502
- "..."
1214
+ "second item"
503
1215
  ],
504
1216
  [
505
1217
  "first item",
506
- "second item",
507
- "..."
1218
+ "second item"
508
1219
  ]
509
1220
  ]
510
1221
  }
511
1222
  JSON
1223
+
1224
+ expect(prompt).to include(expected_json)
512
1225
  end
513
1226
 
514
1227
  it "generates correct JSON example for three-dimensional array of integers" do
@@ -518,7 +1231,9 @@ RSpec.describe Lluminary::Models::Base do
518
1231
  end
519
1232
  end
520
1233
 
521
- expect(model.format_prompt(task)).to include(<<~JSON.chomp)
1234
+ prompt = model.format_prompt(task)
1235
+
1236
+ expected_json = <<~JSON.chomp
522
1237
  {
523
1238
  "matrices": [
524
1239
  [
@@ -548,6 +1263,8 @@ RSpec.describe Lluminary::Models::Base do
548
1263
  ]
549
1264
  }
550
1265
  JSON
1266
+
1267
+ expect(prompt).to include(expected_json)
551
1268
  end
552
1269
  end
553
1270
 
@@ -562,18 +1279,102 @@ RSpec.describe Lluminary::Models::Base do
562
1279
  datetime :joined_at, description: "When they joined"
563
1280
  end
564
1281
 
565
- expect(model.format_prompt(task)).to include(<<~JSON.chomp)
566
- {
1282
+ prompt = model.format_prompt(task)
1283
+
1284
+ expected_json = <<~JSON.chomp
1285
+ {
1286
+ "name": "your name here",
1287
+ "age": 0,
1288
+ "hobbies": [
1289
+ "first hobby",
1290
+ "second hobby"
1291
+ ],
1292
+ "joined_at": "2024-01-01T12:00:00+00:00"
1293
+ }
1294
+ JSON
1295
+
1296
+ expect(prompt).to include(expected_json)
1297
+ end
1298
+ end
1299
+
1300
+ context "with hash fields" do
1301
+ it "generates correct JSON example for simple hash" do
1302
+ task_class.output_schema do
1303
+ hash :config, description: "Configuration options" do
1304
+ string :host, description: "Server hostname"
1305
+ integer :port
1306
+ end
1307
+ end
1308
+
1309
+ prompt = model.format_prompt(task)
1310
+
1311
+ expected_json = <<~JSON.chomp
1312
+ {
1313
+ "config": {
1314
+ "host": "your host here",
1315
+ "port": 0
1316
+ }
1317
+ }
1318
+ JSON
1319
+
1320
+ expect(prompt).to include(expected_json)
1321
+ end
1322
+
1323
+ it "generates correct JSON example for nested hash" do
1324
+ task_class.output_schema do
1325
+ hash :user, description: "User profile" do
1326
+ string :name
1327
+ hash :address do
1328
+ string :street
1329
+ string :city
1330
+ string :country
1331
+ end
1332
+ end
1333
+ end
1334
+
1335
+ prompt = model.format_prompt(task)
1336
+
1337
+ expected_json = <<~JSON.chomp
1338
+ {
1339
+ "user": {
567
1340
  "name": "your name here",
568
- "age": 0,
569
- "hobbies": [
570
- "first hobby",
571
- "second hobby",
572
- "..."
573
- ],
574
- "joined_at": "2024-01-01T12:00:00+00:00"
1341
+ "address": {
1342
+ "street": "your street here",
1343
+ "city": "your city here",
1344
+ "country": "your country here"
1345
+ }
575
1346
  }
1347
+ }
576
1348
  JSON
1349
+
1350
+ expect(prompt).to include(expected_json)
1351
+ end
1352
+
1353
+ it "generates correct JSON example for hash with array" do
1354
+ task_class.output_schema do
1355
+ hash :user_data, description: "User data" do
1356
+ string :username
1357
+ array :permissions do
1358
+ string
1359
+ end
1360
+ end
1361
+ end
1362
+
1363
+ prompt = model.format_prompt(task)
1364
+
1365
+ expected_json = <<~JSON.chomp
1366
+ {
1367
+ "user_data": {
1368
+ "username": "your username here",
1369
+ "permissions": [
1370
+ "first permission",
1371
+ "second permission"
1372
+ ]
1373
+ }
1374
+ }
1375
+ JSON
1376
+
1377
+ expect(prompt).to include(expected_json)
577
1378
  end
578
1379
  end
579
1380
  end