lluminary 0.1.2 → 0.1.4

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.
@@ -0,0 +1,581 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+
4
+ RSpec.describe Lluminary::Models::Base do
5
+ let(:model) { described_class.new }
6
+
7
+ describe "#compatible_with?" do
8
+ it "raises NotImplementedError" do
9
+ expect { model.compatible_with?(:openai) }.to raise_error(
10
+ NotImplementedError
11
+ )
12
+ end
13
+ end
14
+
15
+ describe "#name" do
16
+ it "raises NotImplementedError" do
17
+ expect { model.name }.to raise_error(NotImplementedError)
18
+ end
19
+ end
20
+
21
+ describe "#format_prompt" do
22
+ let(:task_class) do
23
+ Class.new(Lluminary::Task) do
24
+ def task_prompt
25
+ "Test prompt"
26
+ end
27
+ end
28
+ end
29
+
30
+ let(:task) { task_class.new }
31
+
32
+ context "with simple field types" do
33
+ context "with string field type" do
34
+ before do
35
+ task_class.output_schema do
36
+ string :name, description: "The person's name"
37
+ end
38
+ end
39
+
40
+ 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
47
+ end
48
+ end
49
+
50
+ context "with integer field type" do
51
+ before do
52
+ task_class.output_schema do
53
+ integer :age, description: "The person's age"
54
+ end
55
+ end
56
+
57
+ 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
64
+ end
65
+ end
66
+
67
+ context "with boolean field type" do
68
+ before do
69
+ task_class.output_schema do
70
+ boolean :active, description: "Whether the person is active"
71
+ end
72
+ end
73
+
74
+ 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
81
+ end
82
+ end
83
+
84
+ context "with float field type" do
85
+ before do
86
+ task_class.output_schema do
87
+ float :score, description: "The person's score"
88
+ end
89
+ end
90
+
91
+ 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
98
+ end
99
+ end
100
+
101
+ context "with datetime field type" do
102
+ before do
103
+ task_class.output_schema do
104
+ datetime :created_at, description: "When the person was created"
105
+ end
106
+ end
107
+
108
+ 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
115
+ end
116
+ end
117
+ end
118
+
119
+ context "with array fields" do
120
+ context "with simple array of strings" do
121
+ before do
122
+ task_class.output_schema do
123
+ array :tags, description: "List of tags" do
124
+ string
125
+ end
126
+ end
127
+ end
128
+
129
+ it "formats array of strings field description correctly" do
130
+ expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
131
+ # tags
132
+ Type: array of string
133
+ Description: List of tags
134
+ Example: ["first tag", "second tag", "..."]
135
+ DESCRIPTION
136
+ end
137
+ end
138
+
139
+ context "with array of floats" do
140
+ before do
141
+ task_class.output_schema do
142
+ array :scores, description: "List of scores" do
143
+ float
144
+ end
145
+ end
146
+ end
147
+
148
+ it "formats array of floats field description correctly" do
149
+ expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
150
+ # scores
151
+ Type: array of float
152
+ Description: List of scores
153
+ Example: [1.0, 2.0, 3.0]
154
+ DESCRIPTION
155
+ end
156
+ end
157
+
158
+ context "with array of datetimes" do
159
+ before do
160
+ task_class.output_schema do
161
+ array :dates, description: "List of important dates" do
162
+ datetime
163
+ end
164
+ end
165
+ end
166
+
167
+ it "formats array of datetimes field description correctly" do
168
+ expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
169
+ # dates
170
+ Type: array of datetime in ISO8601 format
171
+ Description: List of important dates
172
+ Example: ["2024-01-01T12:00:00+00:00", "2024-01-02T12:00:00+00:00"]
173
+ DESCRIPTION
174
+ end
175
+ end
176
+
177
+ context "with 2D array (matrix)" do
178
+ before do
179
+ task_class.output_schema do
180
+ array :matrix, description: "2D array of numbers" do
181
+ array { integer }
182
+ end
183
+ end
184
+ end
185
+
186
+ it "formats 2D array field description correctly" do
187
+ expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
188
+ # matrix
189
+ Type: array of array of integer
190
+ Description: 2D array of numbers
191
+ Example: [[1, 2, 3], [1, 2, 3]]
192
+ DESCRIPTION
193
+ end
194
+ end
195
+
196
+ context "with 3D array (cube)" do
197
+ before do
198
+ task_class.output_schema do
199
+ array :cube, description: "3D array of strings" do
200
+ array { array { string } }
201
+ end
202
+ end
203
+ end
204
+
205
+ it "formats 3D array field description correctly" do
206
+ expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
207
+ # cube
208
+ Type: array of array of array of string
209
+ Description: 3D array of strings
210
+ Example: [[["first item", "second item", "..."], ["first item", "second item", "..."]], [["first item", "second item", "..."], ["first item", "second item", "..."]]]
211
+ DESCRIPTION
212
+ end
213
+ end
214
+ end
215
+
216
+ context "with validations" do
217
+ context "presence validation" do
218
+ before do
219
+ task_class.output_schema do
220
+ string :name, description: "The person's name"
221
+ validates :name, presence: true
222
+ end
223
+ end
224
+
225
+ it "includes presence validation in field description" do
226
+ expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
227
+ # name
228
+ Type: string
229
+ Description: The person's name
230
+ Validations: must be present
231
+ Example: your name here
232
+ DESCRIPTION
233
+ end
234
+ end
235
+
236
+ context "inclusion validation" do
237
+ before do
238
+ task_class.output_schema do
239
+ string :status, description: "The status"
240
+ validates :status, inclusion: { in: %w[active inactive] }
241
+ end
242
+ end
243
+
244
+ it "includes inclusion validation in field description" do
245
+ expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
246
+ # status
247
+ Type: string
248
+ Description: The status
249
+ Validations: must be one of: active, inactive
250
+ Example: your status here
251
+ DESCRIPTION
252
+ end
253
+ end
254
+
255
+ context "exclusion validation" do
256
+ before do
257
+ task_class.output_schema do
258
+ string :status, description: "The status"
259
+ validates :status, exclusion: { in: %w[banned blocked] }
260
+ end
261
+ end
262
+
263
+ it "includes exclusion validation in field description" do
264
+ expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
265
+ # status
266
+ Type: string
267
+ Description: The status
268
+ Validations: must not be one of: banned, blocked
269
+ Example: your status here
270
+ DESCRIPTION
271
+ end
272
+ end
273
+
274
+ context "format validation" do
275
+ before do
276
+ task_class.output_schema do
277
+ string :email, description: "Email address"
278
+ validates :email, format: { with: /\A[^@\s]+@[^@\s]+\z/ }
279
+ end
280
+ end
281
+
282
+ it "includes format validation in field description" do
283
+ expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
284
+ # email
285
+ Type: string
286
+ Description: Email address
287
+ Validations: must match format: (?-mix:\\A[^@\\s]+@[^@\\s]+\\z)
288
+ Example: your email here
289
+ DESCRIPTION
290
+ end
291
+ end
292
+
293
+ context "length validation" do
294
+ before do
295
+ task_class.output_schema do
296
+ string :password, description: "The password"
297
+ validates :password, length: { minimum: 8, maximum: 20 }
298
+ end
299
+ end
300
+
301
+ it "includes length validation in field description" do
302
+ expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
303
+ # password
304
+ Type: string
305
+ Description: The password
306
+ Validations: must be at least 8 characters, must be at most 20 characters
307
+ Example: your password here
308
+ DESCRIPTION
309
+ end
310
+ end
311
+
312
+ context "numericality validation" do
313
+ before do
314
+ task_class.output_schema do
315
+ integer :age, description: "The age"
316
+ validates :age,
317
+ numericality: {
318
+ greater_than: 0,
319
+ less_than_or_equal_to: 120
320
+ }
321
+ end
322
+ end
323
+
324
+ it "includes numericality validation in field description" do
325
+ expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
326
+ # age
327
+ Type: integer
328
+ Description: The age
329
+ Validations: must be greater than 0, must be less than or equal to 120
330
+ Example: 0
331
+ DESCRIPTION
332
+ end
333
+ end
334
+
335
+ context "multiple validations" do
336
+ before do
337
+ task_class.output_schema do
338
+ string :username, description: "The username"
339
+ validates :username,
340
+ presence: true,
341
+ length: {
342
+ in: 3..20
343
+ },
344
+ format: {
345
+ with: /\A[a-z0-9_]+\z/
346
+ }
347
+ end
348
+ end
349
+
350
+ it "includes all validations in field description" do
351
+ expect(model.format_prompt(task)).to include(<<~DESCRIPTION.chomp)
352
+ # username
353
+ Type: string
354
+ 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
357
+ DESCRIPTION
358
+ end
359
+ end
360
+ end
361
+
362
+ context "JSON example generation" do
363
+ context "with simple field types" do
364
+ it "generates correct JSON example for string field" do
365
+ task_class.output_schema do
366
+ string :name, description: "The person's name"
367
+ end
368
+
369
+ expect(model.format_prompt(task)).to include(<<~JSON.chomp)
370
+ {
371
+ "name": "your name here"
372
+ }
373
+ JSON
374
+ end
375
+
376
+ it "generates correct JSON example for integer field" do
377
+ task_class.output_schema do
378
+ integer :age, description: "The person's age"
379
+ end
380
+
381
+ expect(model.format_prompt(task)).to include(<<~JSON.chomp)
382
+ {
383
+ "age": 0
384
+ }
385
+ JSON
386
+ end
387
+
388
+ it "generates correct JSON example for boolean field" do
389
+ task_class.output_schema do
390
+ boolean :is_active, description: "Whether the person is active"
391
+ end
392
+
393
+ expect(model.format_prompt(task)).to include(<<~JSON.chomp)
394
+ {
395
+ "is_active": true
396
+ }
397
+ JSON
398
+ end
399
+
400
+ it "generates correct JSON example for float field" do
401
+ task_class.output_schema { float :score, description: "The score" }
402
+
403
+ expect(model.format_prompt(task)).to include(<<~JSON.chomp)
404
+ {
405
+ "score": 0.0
406
+ }
407
+ JSON
408
+ end
409
+
410
+ it "generates correct JSON example for datetime field" do
411
+ task_class.output_schema do
412
+ datetime :created_at, description: "When it was created"
413
+ end
414
+
415
+ expect(model.format_prompt(task)).to include(<<~JSON.chomp)
416
+ {
417
+ "created_at": "2024-01-01T12:00:00+00:00"
418
+ }
419
+ JSON
420
+ end
421
+ end
422
+
423
+ context "with array fields" do
424
+ it "generates correct JSON example for array of strings" do
425
+ task_class.output_schema do
426
+ array :tags, description: "List of tags" do
427
+ string
428
+ end
429
+ end
430
+
431
+ expect(model.format_prompt(task)).to include(<<~JSON.chomp)
432
+ {
433
+ "tags": [
434
+ "first tag",
435
+ "second tag",
436
+ "..."
437
+ ]
438
+ }
439
+ JSON
440
+ end
441
+
442
+ it "generates correct JSON example for array of integers" do
443
+ task_class.output_schema do
444
+ array :counts, description: "List of counts" do
445
+ integer
446
+ end
447
+ end
448
+
449
+ expect(model.format_prompt(task)).to include(<<~JSON.chomp)
450
+ {
451
+ "counts": [
452
+ 1,
453
+ 2,
454
+ 3
455
+ ]
456
+ }
457
+ JSON
458
+ end
459
+
460
+ it "generates correct JSON example for array of datetimes" do
461
+ task_class.output_schema do
462
+ array :timestamps, description: "List of timestamps" do
463
+ datetime
464
+ end
465
+ end
466
+
467
+ expect(model.format_prompt(task)).to include(<<~JSON.chomp)
468
+ {
469
+ "timestamps": [
470
+ "2024-01-01T12:00:00+00:00",
471
+ "2024-01-02T12:00:00+00:00"
472
+ ]
473
+ }
474
+ JSON
475
+ end
476
+
477
+ it "generates correct JSON example for array without element type" do
478
+ task_class.output_schema do
479
+ array :items, description: "List of items"
480
+ end
481
+
482
+ expect(model.format_prompt(task)).to include(<<~JSON.chomp)
483
+ {
484
+ "items": []
485
+ }
486
+ JSON
487
+ end
488
+
489
+ it "generates correct JSON example for nested array of strings" do
490
+ task_class.output_schema do
491
+ array :groups, description: "Groups of items" do
492
+ array { string }
493
+ end
494
+ end
495
+
496
+ expect(model.format_prompt(task)).to include(<<~JSON.chomp)
497
+ {
498
+ "groups": [
499
+ [
500
+ "first item",
501
+ "second item",
502
+ "..."
503
+ ],
504
+ [
505
+ "first item",
506
+ "second item",
507
+ "..."
508
+ ]
509
+ ]
510
+ }
511
+ JSON
512
+ end
513
+
514
+ it "generates correct JSON example for three-dimensional array of integers" do
515
+ task_class.output_schema do
516
+ array :matrices, description: "Collection of matrices" do
517
+ array { array { integer } }
518
+ end
519
+ end
520
+
521
+ expect(model.format_prompt(task)).to include(<<~JSON.chomp)
522
+ {
523
+ "matrices": [
524
+ [
525
+ [
526
+ 1,
527
+ 2,
528
+ 3
529
+ ],
530
+ [
531
+ 1,
532
+ 2,
533
+ 3
534
+ ]
535
+ ],
536
+ [
537
+ [
538
+ 1,
539
+ 2,
540
+ 3
541
+ ],
542
+ [
543
+ 1,
544
+ 2,
545
+ 3
546
+ ]
547
+ ]
548
+ ]
549
+ }
550
+ JSON
551
+ end
552
+ end
553
+
554
+ context "with multiple fields" do
555
+ it "generates correct JSON example for mixed field types" do
556
+ task_class.output_schema do
557
+ string :name, description: "The person's name"
558
+ integer :age, description: "The person's age"
559
+ array :hobbies, description: "List of hobbies" do
560
+ string
561
+ end
562
+ datetime :joined_at, description: "When they joined"
563
+ end
564
+
565
+ expect(model.format_prompt(task)).to include(<<~JSON.chomp)
566
+ {
567
+ "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"
575
+ }
576
+ JSON
577
+ end
578
+ end
579
+ end
580
+ end
581
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "lluminary/models/bedrock/amazon_nova_pro_v1"
5
+
6
+ RSpec.describe Lluminary::Models::Bedrock::AmazonNovaProV1 do
7
+ let(:model) { described_class.new }
8
+
9
+ describe "NAME" do
10
+ it "has the correct model name" do
11
+ expect(described_class::NAME).to eq("amazon.nova-pro-v1")
12
+ end
13
+ end
14
+
15
+ describe "#compatible_with?" do
16
+ it "returns true for bedrock provider" do
17
+ expect(model.compatible_with?(:bedrock)).to be true
18
+ end
19
+
20
+ it "returns false for other providers" do
21
+ expect(model.compatible_with?(:openai)).to be false
22
+ end
23
+ end
24
+
25
+ describe "#name" do
26
+ it "returns the model name" do
27
+ expect(model.name).to eq("amazon.nova-pro-v1:0")
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,21 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe Lluminary::Models::Bedrock::AnthropicClaudeInstantV1 do
4
+ subject(:model) { described_class.new }
5
+
6
+ describe "#NAME" do
7
+ it "returns the correct model name" do
8
+ expect(described_class::NAME).to eq("anthropic.claude-instant-v1")
9
+ end
10
+ end
11
+
12
+ describe "#compatible_with?" do
13
+ it "returns true for :bedrock provider" do
14
+ expect(model.compatible_with?(:bedrock)).to be true
15
+ end
16
+
17
+ it "returns false for other providers" do
18
+ expect(model.compatible_with?(:openai)).to be false
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+
4
+ RSpec.describe Lluminary::Models::OpenAi::Gpt35Turbo do
5
+ subject(:model) { described_class.new }
6
+
7
+ describe "#NAME" do
8
+ it "returns the correct model name" do
9
+ expect(described_class::NAME).to eq("gpt-3.5-turbo")
10
+ end
11
+ end
12
+
13
+ describe "#compatible_with?" do
14
+ it "returns true for :openai provider" do
15
+ expect(model.compatible_with?(:openai)).to be true
16
+ end
17
+
18
+ it "returns false for other providers" do
19
+ expect(model.compatible_with?(:bedrock)).to be false
20
+ end
21
+ end
22
+ end
@@ -18,6 +18,45 @@ RSpec.describe Lluminary::Providers::Bedrock do
18
18
  end
19
19
  end
20
20
 
21
+ describe "#models" do
22
+ let(:mock_models_response) do
23
+ OpenStruct.new(
24
+ foundation_models: [
25
+ OpenStruct.new(
26
+ model_id: "anthropic.claude-instant-v1",
27
+ model_name: "Claude Instant",
28
+ provider_name: "Anthropic",
29
+ input_modalities: ["TEXT"],
30
+ output_modalities: ["TEXT"],
31
+ customizations_supported: []
32
+ ),
33
+ OpenStruct.new(
34
+ model_id: "anthropic.claude-v2",
35
+ model_name: "Claude V2",
36
+ provider_name: "Anthropic",
37
+ input_modalities: ["TEXT"],
38
+ output_modalities: ["TEXT"],
39
+ customizations_supported: []
40
+ )
41
+ ]
42
+ )
43
+ end
44
+
45
+ before do
46
+ models_client = double("BedrockClient")
47
+ allow(Aws::Bedrock::Client).to receive(:new).and_return(models_client)
48
+ allow(models_client).to receive(:list_foundation_models).and_return(
49
+ mock_models_response
50
+ )
51
+ end
52
+
53
+ it "returns an array of model IDs as strings" do
54
+ expect(provider.models).to eq(
55
+ %w[anthropic.claude-instant-v1 anthropic.claude-v2]
56
+ )
57
+ end
58
+ end
59
+
21
60
  describe "#call" do
22
61
  let(:prompt) { "Test prompt" }
23
62
  let(:task) { "Test task" }