lluminary 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/lib/lluminary/config.rb +11 -1
  3. data/lib/lluminary/field_description.rb +34 -29
  4. data/lib/lluminary/provider_error.rb +2 -1
  5. data/lib/lluminary/providers/base.rb +5 -1
  6. data/lib/lluminary/providers/bedrock.rb +32 -30
  7. data/lib/lluminary/providers/openai.rb +28 -21
  8. data/lib/lluminary/providers/test.rb +9 -14
  9. data/lib/lluminary/result.rb +5 -2
  10. data/lib/lluminary/schema.rb +9 -16
  11. data/lib/lluminary/schema_model.rb +13 -10
  12. data/lib/lluminary/task.rb +84 -59
  13. data/lib/lluminary/validation_error.rb +2 -1
  14. data/lib/lluminary/version.rb +3 -2
  15. data/lib/lluminary.rb +23 -7
  16. data/spec/examples/analyze_text_spec.rb +7 -4
  17. data/spec/examples/color_analyzer_spec.rb +22 -22
  18. data/spec/examples/content_analyzer_spec.rb +27 -44
  19. data/spec/examples/historical_event_analyzer_spec.rb +18 -15
  20. data/spec/examples/price_analyzer_spec.rb +22 -28
  21. data/spec/examples/quote_task_spec.rb +9 -8
  22. data/spec/examples/sentiment_analysis_spec.rb +13 -10
  23. data/spec/examples/summarize_text_spec.rb +7 -4
  24. data/spec/lluminary/config_spec.rb +28 -26
  25. data/spec/lluminary/field_description_spec.rb +19 -21
  26. data/spec/lluminary/providers/base_spec.rb +11 -8
  27. data/spec/lluminary/providers/bedrock_spec.rb +47 -57
  28. data/spec/lluminary/providers/openai_spec.rb +27 -35
  29. data/spec/lluminary/providers/test_spec.rb +21 -16
  30. data/spec/lluminary/result_spec.rb +17 -10
  31. data/spec/lluminary/schema_model_spec.rb +31 -22
  32. data/spec/lluminary/schema_spec.rb +95 -107
  33. data/spec/lluminary/task_spec.rb +366 -300
  34. data/spec/spec_helper.rb +7 -2
  35. metadata +40 -22
@@ -1,4 +1,5 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
2
3
 
3
4
  RSpec.describe Lluminary::Task do
4
5
  let(:task_class) do
@@ -19,29 +20,29 @@ RSpec.describe Lluminary::Task do
19
20
  end
20
21
  end
21
22
 
22
- let(:task_with_test) do
23
- Class.new(described_class) do
24
- use_provider :test
25
- end
26
- end
23
+ let(:task_with_test) { Class.new(described_class) { use_provider :test } }
27
24
 
28
- describe '.call' do
29
- it 'returns a result with a raw response from the provider' do
25
+ describe ".call" do
26
+ it "returns a result with a raw response from the provider" do
30
27
  result = task_class.call(message: "hello")
31
- expect(result.output.raw_response).to eq('{"summary": "Test string value"}')
28
+ expect(result.output.raw_response).to eq(
29
+ '{"summary": "Test string value"}'
30
+ )
32
31
  end
33
32
 
34
- it 'string input allows providing a string input' do
33
+ it "string input allows providing a string input" do
35
34
  result = task_class.call(message: "hello")
36
- expect(result.output.raw_response).to eq('{"summary": "Test string value"}')
35
+ expect(result.output.raw_response).to eq(
36
+ '{"summary": "Test string value"}'
37
+ )
37
38
  end
38
39
 
39
- it 'string output returns the output in the result' do
40
+ it "string output returns the output in the result" do
40
41
  result = task_class.call(message: "hello")
41
42
  expect(result.output.summary).to eq("Test string value")
42
43
  end
43
44
 
44
- it 'includes schema descriptions in the prompt' do
45
+ it "includes schema descriptions in the prompt" do
45
46
  result = task_class.call(message: "hello")
46
47
  expected_schema = <<~SCHEMA
47
48
  You must respond with ONLY a valid JSON object. Do not include any other text, explanations, or formatting.
@@ -59,16 +60,12 @@ RSpec.describe Lluminary::Task do
59
60
  end
60
61
  end
61
62
 
62
- describe '.call without descriptions' do
63
+ describe ".call without descriptions" do
63
64
  let(:task_without_descriptions) do
64
65
  Class.new(described_class) do
65
- input_schema do
66
- string :message
67
- end
66
+ input_schema { string :message }
68
67
 
69
- output_schema do
70
- string :summary
71
- end
68
+ output_schema { string :summary }
72
69
 
73
70
  private
74
71
 
@@ -78,7 +75,7 @@ RSpec.describe Lluminary::Task do
78
75
  end
79
76
  end
80
77
 
81
- it 'includes basic schema in the prompt' do
78
+ it "includes basic schema in the prompt" do
82
79
  result = task_without_descriptions.call(message: "hello")
83
80
  expected_schema = <<~SCHEMA
84
81
  You must respond with ONLY a valid JSON object. Do not include any other text, explanations, or formatting.
@@ -96,38 +93,42 @@ RSpec.describe Lluminary::Task do
96
93
  end
97
94
  end
98
95
 
99
- describe '.provider' do
100
- it 'defaults to Test provider' do
96
+ describe ".provider" do
97
+ it "defaults to Test provider" do
101
98
  expect(task_class.provider).to be_a(Lluminary::Providers::Test)
102
99
  end
103
100
 
104
- it 'allows setting a custom provider' do
105
- custom_provider = double('CustomProvider')
101
+ it "allows setting a custom provider" do
102
+ custom_provider = double("CustomProvider")
106
103
  task_class.provider = custom_provider
107
104
  expect(task_class.provider).to eq(custom_provider)
108
105
  end
109
106
  end
110
107
 
111
- describe '.use_provider' do
112
- it 'with :test provider sets the test provider' do
108
+ describe ".use_provider" do
109
+ it "with :test provider sets the test provider" do
113
110
  expect(task_with_test.provider).to be_a(Lluminary::Providers::Test)
114
111
  end
115
112
 
116
- it 'with :openai instantiates OpenAI provider with config' do
117
- task_class.use_provider(:openai, api_key: 'test')
113
+ it "with :openai instantiates OpenAI provider with config" do
114
+ task_class.use_provider(:openai, api_key: "test")
118
115
  expect(task_class.provider).to be_a(Lluminary::Providers::OpenAI)
119
- expect(task_class.provider.config).to eq(api_key: 'test', model: 'gpt-4o')
116
+ expect(task_class.provider.config).to eq(
117
+ api_key: "test",
118
+ model: "gpt-3.5-turbo"
119
+ )
120
120
  end
121
121
 
122
- it 'raises ArgumentError for unknown provider' do
123
- expect {
124
- task_class.use_provider(:unknown)
125
- }.to raise_error(ArgumentError, "Unknown provider: unknown")
122
+ it "raises ArgumentError for unknown provider" do
123
+ expect { task_class.use_provider(:unknown) }.to raise_error(
124
+ ArgumentError,
125
+ "Unknown provider: unknown"
126
+ )
126
127
  end
127
128
  end
128
129
 
129
- describe '#json_schema_example' do
130
- it 'generates a schema example with descriptions' do
130
+ describe "#json_schema_example" do
131
+ it "generates a schema example with descriptions" do
131
132
  task = task_class.new(message: "test")
132
133
  expected_output = <<~SCHEMA
133
134
  You must respond with ONLY a valid JSON object. Do not include any other text, explanations, or formatting.
@@ -144,22 +145,21 @@ RSpec.describe Lluminary::Task do
144
145
  expect(task.send(:json_schema_example)).to eq(expected_output.chomp)
145
146
  end
146
147
 
147
- it 'generates a schema example with datetime field' do
148
- task_with_datetime = Class.new(described_class) do
149
- input_schema do
150
- string :message
151
- end
148
+ it "generates a schema example with datetime field" do
149
+ task_with_datetime =
150
+ Class.new(described_class) do
151
+ input_schema { string :message }
152
152
 
153
- output_schema do
154
- datetime :start_time, description: "When the event starts"
155
- end
153
+ output_schema do
154
+ datetime :start_time, description: "When the event starts"
155
+ end
156
156
 
157
- private
157
+ private
158
158
 
159
- def task_prompt
160
- "Say: #{message}"
159
+ def task_prompt
160
+ "Say: #{message}"
161
+ end
161
162
  end
162
- end
163
163
 
164
164
  task = task_with_datetime.new(message: "test")
165
165
  expected_output = <<~SCHEMA
@@ -177,22 +177,21 @@ RSpec.describe Lluminary::Task do
177
177
  expect(task.send(:json_schema_example)).to eq(expected_output.chomp)
178
178
  end
179
179
 
180
- it 'generates a schema example with boolean field' do
181
- task_with_boolean = Class.new(described_class) do
182
- input_schema do
183
- string :message
184
- end
180
+ it "generates a schema example with boolean field" do
181
+ task_with_boolean =
182
+ Class.new(described_class) do
183
+ input_schema { string :message }
185
184
 
186
- output_schema do
187
- boolean :is_valid, description: "Whether the input is valid"
188
- end
185
+ output_schema do
186
+ boolean :is_valid, description: "Whether the input is valid"
187
+ end
189
188
 
190
- private
189
+ private
191
190
 
192
- def task_prompt
193
- "Say: #{message}"
191
+ def task_prompt
192
+ "Say: #{message}"
193
+ end
194
194
  end
195
- end
196
195
 
197
196
  task = task_with_boolean.new(message: "test")
198
197
  expected_output = <<~SCHEMA
@@ -210,22 +209,19 @@ RSpec.describe Lluminary::Task do
210
209
  expect(task.send(:json_schema_example)).to eq(expected_output.chomp)
211
210
  end
212
211
 
213
- it 'generates a schema example with float field' do
214
- task_with_float = Class.new(described_class) do
215
- input_schema do
216
- string :message
217
- end
212
+ it "generates a schema example with float field" do
213
+ task_with_float =
214
+ Class.new(described_class) do
215
+ input_schema { string :message }
218
216
 
219
- output_schema do
220
- float :score, description: "The confidence score"
221
- end
217
+ output_schema { float :score, description: "The confidence score" }
222
218
 
223
- private
219
+ private
224
220
 
225
- def task_prompt
226
- "Say: #{message}"
221
+ def task_prompt
222
+ "Say: #{message}"
223
+ end
227
224
  end
228
- end
229
225
 
230
226
  task = task_with_float.new(message: "test")
231
227
  expected_output = <<~SCHEMA
@@ -243,22 +239,19 @@ RSpec.describe Lluminary::Task do
243
239
  expect(task.send(:json_schema_example)).to eq(expected_output.chomp)
244
240
  end
245
241
 
246
- it 'generates a schema example without descriptions' do
247
- task_without_descriptions = Class.new(described_class) do
248
- input_schema do
249
- string :message
250
- end
242
+ it "generates a schema example without descriptions" do
243
+ task_without_descriptions =
244
+ Class.new(described_class) do
245
+ input_schema { string :message }
251
246
 
252
- output_schema do
253
- string :summary
254
- end
247
+ output_schema { string :summary }
255
248
 
256
- private
249
+ private
257
250
 
258
- def task_prompt
259
- "Say: #{message}"
251
+ def task_prompt
252
+ "Say: #{message}"
253
+ end
260
254
  end
261
- end
262
255
 
263
256
  task = task_without_descriptions.new(message: "test")
264
257
  expected_output = <<~SCHEMA
@@ -276,18 +269,20 @@ RSpec.describe Lluminary::Task do
276
269
  expect(task.send(:json_schema_example)).to eq(expected_output.chomp)
277
270
  end
278
271
 
279
- context 'validation descriptions' do
280
- context 'presence validation' do
281
- it 'includes presence validation description' do
282
- task_class = Class.new(described_class) do
283
- output_schema do
284
- string :name, description: "The person's name"
285
- validates :name, presence: true
286
- end
272
+ context "validation descriptions" do
273
+ context "presence validation" do
274
+ it "includes presence validation description" do
275
+ task_class =
276
+ Class.new(described_class) do
277
+ output_schema do
278
+ string :name, description: "The person's name"
279
+ validates :name, presence: true
280
+ end
287
281
 
288
- private
289
- def task_prompt; "Test prompt"; end
290
- end
282
+ private
283
+
284
+ def task_prompt = "Test prompt"
285
+ end
291
286
 
292
287
  task = task_class.new
293
288
  expect(task.send(:json_schema_example)).to include(
@@ -296,17 +291,19 @@ RSpec.describe Lluminary::Task do
296
291
  end
297
292
  end
298
293
 
299
- context 'length validation' do
300
- it 'includes minimum length validation description' do
301
- task_class = Class.new(described_class) do
302
- output_schema do
303
- string :name, description: "The person's name"
304
- validates :name, length: { minimum: 2 }
305
- end
294
+ context "length validation" do
295
+ it "includes minimum length validation description" do
296
+ task_class =
297
+ Class.new(described_class) do
298
+ output_schema do
299
+ string :name, description: "The person's name"
300
+ validates :name, length: { minimum: 2 }
301
+ end
306
302
 
307
- private
308
- def task_prompt; "Test prompt"; end
309
- end
303
+ private
304
+
305
+ def task_prompt = "Test prompt"
306
+ end
310
307
 
311
308
  task = task_class.new
312
309
  expect(task.send(:json_schema_example)).to include(
@@ -314,16 +311,18 @@ RSpec.describe Lluminary::Task do
314
311
  )
315
312
  end
316
313
 
317
- it 'includes maximum length validation description' do
318
- task_class = Class.new(described_class) do
319
- output_schema do
320
- string :name, description: "The person's name"
321
- validates :name, length: { maximum: 20 }
322
- end
314
+ it "includes maximum length validation description" do
315
+ task_class =
316
+ Class.new(described_class) do
317
+ output_schema do
318
+ string :name, description: "The person's name"
319
+ validates :name, length: { maximum: 20 }
320
+ end
323
321
 
324
- private
325
- def task_prompt; "Test prompt"; end
326
- end
322
+ private
323
+
324
+ def task_prompt = "Test prompt"
325
+ end
327
326
 
328
327
  task = task_class.new
329
328
  expect(task.send(:json_schema_example)).to include(
@@ -331,55 +330,62 @@ RSpec.describe Lluminary::Task do
331
330
  )
332
331
  end
333
332
 
334
- it 'includes range length validation description' do
335
- task_class = Class.new(described_class) do
336
- output_schema do
337
- string :password, description: "The password"
338
- validates :password, length: { in: 8..20 }
339
- end
333
+ it "includes range length validation description" do
334
+ task_class =
335
+ Class.new(described_class) do
336
+ output_schema do
337
+ string :password, description: "The password"
338
+ validates :password, length: { in: 8..20 }
339
+ end
340
340
 
341
- private
342
- def task_prompt; "Test prompt"; end
343
- end
341
+ private
342
+
343
+ def task_prompt = "Test prompt"
344
+ end
344
345
 
345
346
  task = task_class.new
346
- expect(task.send(:json_schema_example)).to include(
347
- "password (string): The password\nValidation: must be between 8 and 20 characters\nExample: \"your password here\""
348
- )
347
+ expect(task.send(:json_schema_example)).to include(<<~DESCRIPTION)
348
+ password (string): The password
349
+ Validation: must be between 8 and 20 characters
350
+ Example: "your password here"
351
+ DESCRIPTION
349
352
  end
350
353
 
351
- it 'includes multiple length validations in description' do
352
- task_class = Class.new(described_class) do
353
- output_schema do
354
- string :username, description: "The username"
355
- validates :username, length: {
356
- minimum: 3,
357
- maximum: 20
358
- }
359
- end
354
+ it "includes multiple length validations in description" do
355
+ task_class =
356
+ Class.new(described_class) do
357
+ output_schema do
358
+ string :username, description: "The username"
359
+ validates :username, length: { minimum: 3, maximum: 20 }
360
+ end
360
361
 
361
- private
362
- def task_prompt; "Test prompt"; end
363
- end
362
+ private
363
+
364
+ def task_prompt = "Test prompt"
365
+ end
364
366
 
365
367
  task = task_class.new
366
- expect(task.send(:json_schema_example)).to include(
367
- "username (string): The username\nValidation: must be at least 3 characters, must be at most 20 characters\nExample: \"your username here\""
368
- )
368
+ expect(task.send(:json_schema_example)).to include(<<~DESCRIPTION)
369
+ username (string): The username
370
+ Validation: must be at least 3 characters, must be at most 20 characters
371
+ Example: "your username here"
372
+ DESCRIPTION
369
373
  end
370
374
  end
371
375
 
372
- context 'numericality validation' do
373
- it 'includes odd validation description' do
374
- task_class = Class.new(described_class) do
375
- output_schema do
376
- integer :number, description: "Odd number"
377
- validates :number, numericality: { odd: true }
378
- end
376
+ context "numericality validation" do
377
+ it "includes odd validation description" do
378
+ task_class =
379
+ Class.new(described_class) do
380
+ output_schema do
381
+ integer :number, description: "Odd number"
382
+ validates :number, numericality: { odd: true }
383
+ end
379
384
 
380
- private
381
- def task_prompt; "Test prompt"; end
382
- end
385
+ private
386
+
387
+ def task_prompt = "Test prompt"
388
+ end
383
389
 
384
390
  task = task_class.new
385
391
  expect(task.send(:json_schema_example)).to include(
@@ -387,16 +393,18 @@ RSpec.describe Lluminary::Task do
387
393
  )
388
394
  end
389
395
 
390
- it 'includes equal to validation description' do
391
- task_class = Class.new(described_class) do
392
- output_schema do
393
- integer :level, description: "Level"
394
- validates :level, numericality: { equal_to: 5 }
395
- end
396
+ it "includes equal to validation description" do
397
+ task_class =
398
+ Class.new(described_class) do
399
+ output_schema do
400
+ integer :level, description: "Level"
401
+ validates :level, numericality: { equal_to: 5 }
402
+ end
396
403
 
397
- private
398
- def task_prompt; "Test prompt"; end
399
- end
404
+ private
405
+
406
+ def task_prompt = "Test prompt"
407
+ end
400
408
 
401
409
  task = task_class.new
402
410
  expect(task.send(:json_schema_example)).to include(
@@ -404,16 +412,18 @@ RSpec.describe Lluminary::Task do
404
412
  )
405
413
  end
406
414
 
407
- it 'includes less than or equal to validation description' do
408
- task_class = Class.new(described_class) do
409
- output_schema do
410
- integer :score, description: "Score"
411
- validates :score, numericality: { less_than_or_equal_to: 100 }
412
- end
415
+ it "includes less than or equal to validation description" do
416
+ task_class =
417
+ Class.new(described_class) do
418
+ output_schema do
419
+ integer :score, description: "Score"
420
+ validates :score, numericality: { less_than_or_equal_to: 100 }
421
+ end
413
422
 
414
- private
415
- def task_prompt; "Test prompt"; end
416
- end
423
+ private
424
+
425
+ def task_prompt = "Test prompt"
426
+ end
417
427
 
418
428
  task = task_class.new
419
429
  expect(task.send(:json_schema_example)).to include(
@@ -421,16 +431,18 @@ RSpec.describe Lluminary::Task do
421
431
  )
422
432
  end
423
433
 
424
- it 'includes other than validation description' do
425
- task_class = Class.new(described_class) do
426
- output_schema do
427
- integer :value, description: "Value"
428
- validates :value, numericality: { other_than: 0 }
429
- end
434
+ it "includes other than validation description" do
435
+ task_class =
436
+ Class.new(described_class) do
437
+ output_schema do
438
+ integer :value, description: "Value"
439
+ validates :value, numericality: { other_than: 0 }
440
+ end
430
441
 
431
- private
432
- def task_prompt; "Test prompt"; end
433
- end
442
+ private
443
+
444
+ def task_prompt = "Test prompt"
445
+ end
434
446
 
435
447
  task = task_class.new
436
448
  expect(task.send(:json_schema_example)).to include(
@@ -438,16 +450,18 @@ RSpec.describe Lluminary::Task do
438
450
  )
439
451
  end
440
452
 
441
- it 'includes in range validation description' do
442
- task_class = Class.new(described_class) do
443
- output_schema do
444
- integer :rating, description: "Rating"
445
- validates :rating, numericality: { in: 1..5 }
446
- end
453
+ it "includes in range validation description" do
454
+ task_class =
455
+ Class.new(described_class) do
456
+ output_schema do
457
+ integer :rating, description: "Rating"
458
+ validates :rating, numericality: { in: 1..5 }
459
+ end
447
460
 
448
- private
449
- def task_prompt; "Test prompt"; end
450
- end
461
+ private
462
+
463
+ def task_prompt = "Test prompt"
464
+ end
451
465
 
452
466
  task = task_class.new
453
467
  expect(task.send(:json_schema_example)).to include(
@@ -455,19 +469,22 @@ RSpec.describe Lluminary::Task do
455
469
  )
456
470
  end
457
471
 
458
- it 'includes multiple numericality validations in description' do
459
- task_class = Class.new(described_class) do
460
- output_schema do
461
- integer :score, description: "Score"
462
- validates :score, numericality: {
463
- greater_than: 0,
464
- less_than_or_equal_to: 100
465
- }
466
- end
472
+ it "includes multiple numericality validations in description" do
473
+ task_class =
474
+ Class.new(described_class) do
475
+ output_schema do
476
+ integer :score, description: "Score"
477
+ validates :score,
478
+ numericality: {
479
+ greater_than: 0,
480
+ less_than_or_equal_to: 100
481
+ }
482
+ end
467
483
 
468
- private
469
- def task_prompt; "Test prompt"; end
470
- end
484
+ private
485
+
486
+ def task_prompt = "Test prompt"
487
+ end
471
488
 
472
489
  task = task_class.new
473
490
  expect(task.send(:json_schema_example)).to include(
@@ -475,19 +492,18 @@ RSpec.describe Lluminary::Task do
475
492
  )
476
493
  end
477
494
 
478
- it 'includes multiple comparison validations in description' do
479
- task_class = Class.new(described_class) do
480
- output_schema do
481
- integer :age, description: "Age"
482
- validates :age, comparison: {
483
- greater_than: 0,
484
- less_than: 120
485
- }
486
- end
495
+ it "includes multiple comparison validations in description" do
496
+ task_class =
497
+ Class.new(described_class) do
498
+ output_schema do
499
+ integer :age, description: "Age"
500
+ validates :age, comparison: { greater_than: 0, less_than: 120 }
501
+ end
487
502
 
488
- private
489
- def task_prompt; "Test prompt"; end
490
- end
503
+ private
504
+
505
+ def task_prompt = "Test prompt"
506
+ end
491
507
 
492
508
  task = task_class.new
493
509
  expect(task.send(:json_schema_example)).to include(
@@ -496,36 +512,50 @@ RSpec.describe Lluminary::Task do
496
512
  end
497
513
  end
498
514
 
499
- context 'format validation' do
500
- it 'includes format validation description' do
501
- task_class = Class.new(described_class) do
502
- output_schema do
503
- string :email, description: "Email address"
504
- validates :email, format: { with: /\A[^@\s]+@[^@\s]+\z/ }
505
- end
515
+ context "format validation" do
516
+ it "includes format validation description" do
517
+ task_class =
518
+ Class.new(described_class) do
519
+ output_schema do
520
+ string :email, description: "Email address"
521
+ validates :email, format: { with: /\A[^@\s]+@[^@\s]+\z/ }
522
+ end
506
523
 
507
- private
508
- def task_prompt; "Test prompt"; end
509
- end
524
+ private
525
+
526
+ def task_prompt = "Test prompt"
527
+ end
510
528
 
511
529
  task = task_class.new
512
- expect(task.send(:json_schema_example)).to include(
513
- "email (string): Email address\nValidation: must match format: (?-mix:\\A[^@\\s]+@[^@\\s]+\\z)\nExample: \"your email here\""
514
- )
530
+ expect(task.send(:json_schema_example)).to eq(<<~SCHEMA.chomp)
531
+ You must respond with ONLY a valid JSON object. Do not include any other text, explanations, or formatting.
532
+ The JSON object must contain the following fields:
533
+
534
+ email (string): Email address
535
+ Validation: must match format: (?-mix:\\A[^@\\s]+@[^@\\s]+\\z)
536
+ Example: "your email here"
537
+
538
+ Your response must be ONLY this JSON object:
539
+ {
540
+ "email": "your email here"
541
+ }
542
+ SCHEMA
515
543
  end
516
544
  end
517
545
 
518
- context 'inclusion validation' do
519
- it 'includes inclusion validation description' do
520
- task_class = Class.new(described_class) do
521
- output_schema do
522
- string :role, description: "User role"
523
- validates :role, inclusion: { in: %w[admin user guest] }
524
- end
546
+ context "inclusion validation" do
547
+ it "includes inclusion validation description" do
548
+ task_class =
549
+ Class.new(described_class) do
550
+ output_schema do
551
+ string :role, description: "User role"
552
+ validates :role, inclusion: { in: %w[admin user guest] }
553
+ end
525
554
 
526
- private
527
- def task_prompt; "Test prompt"; end
528
- end
555
+ private
556
+
557
+ def task_prompt = "Test prompt"
558
+ end
529
559
 
530
560
  task = task_class.new
531
561
  expect(task.send(:json_schema_example)).to include(
@@ -534,17 +564,19 @@ RSpec.describe Lluminary::Task do
534
564
  end
535
565
  end
536
566
 
537
- context 'exclusion validation' do
538
- it 'includes exclusion validation description' do
539
- task_class = Class.new(described_class) do
540
- output_schema do
541
- string :status, description: "Status"
542
- validates :status, exclusion: { in: %w[banned blocked] }
543
- end
567
+ context "exclusion validation" do
568
+ it "includes exclusion validation description" do
569
+ task_class =
570
+ Class.new(described_class) do
571
+ output_schema do
572
+ string :status, description: "Status"
573
+ validates :status, exclusion: { in: %w[banned blocked] }
574
+ end
544
575
 
545
- private
546
- def task_prompt; "Test prompt"; end
547
- end
576
+ private
577
+
578
+ def task_prompt = "Test prompt"
579
+ end
548
580
 
549
581
  task = task_class.new
550
582
  expect(task.send(:json_schema_example)).to include(
@@ -553,17 +585,19 @@ RSpec.describe Lluminary::Task do
553
585
  end
554
586
  end
555
587
 
556
- context 'absence validation' do
557
- it 'includes absence validation description' do
558
- task_class = Class.new(described_class) do
559
- output_schema do
560
- string :deleted_at, description: "Deletion timestamp"
561
- validates :deleted_at, absence: true
562
- end
588
+ context "absence validation" do
589
+ it "includes absence validation description" do
590
+ task_class =
591
+ Class.new(described_class) do
592
+ output_schema do
593
+ string :deleted_at, description: "Deletion timestamp"
594
+ validates :deleted_at, absence: true
595
+ end
563
596
 
564
- private
565
- def task_prompt; "Test prompt"; end
566
- end
597
+ private
598
+
599
+ def task_prompt = "Test prompt"
600
+ end
567
601
 
568
602
  task = task_class.new
569
603
  expect(task.send(:json_schema_example)).to include(
@@ -574,7 +608,7 @@ RSpec.describe Lluminary::Task do
574
608
  end
575
609
  end
576
610
 
577
- describe '#validate_input' do
611
+ describe "#validate_input" do
578
612
  let(:task_with_types) do
579
613
  Class.new(described_class) do
580
614
  input_schema do
@@ -591,38 +625,52 @@ RSpec.describe Lluminary::Task do
591
625
  end
592
626
  end
593
627
 
594
- it 'validates string input type' do
595
- task = task_with_types.new(name: "John", age: 30, start_time: DateTime.now)
628
+ it "validates string input type" do
629
+ task =
630
+ task_with_types.new(name: "John", age: 30, start_time: DateTime.now)
596
631
  expect { task.send(:validate_input) }.not_to raise_error
597
632
  end
598
633
 
599
- it 'raises error for invalid string input' do
634
+ it "raises error for invalid string input" do
600
635
  task = task_with_types.new(name: 123, age: 30, start_time: DateTime.now)
601
- expect { task.send(:validate_input) }.to raise_error(Lluminary::ValidationError, "Name must be a String")
636
+ expect { task.send(:validate_input) }.to raise_error(
637
+ Lluminary::ValidationError,
638
+ "Name must be a String"
639
+ )
602
640
  end
603
641
 
604
- it 'validates integer input type' do
605
- task = task_with_types.new(name: "John", age: 30, start_time: DateTime.now)
642
+ it "validates integer input type" do
643
+ task =
644
+ task_with_types.new(name: "John", age: 30, start_time: DateTime.now)
606
645
  expect { task.send(:validate_input) }.not_to raise_error
607
646
  end
608
647
 
609
- it 'raises error for invalid integer input' do
610
- task = task_with_types.new(name: "John", age: "30", start_time: DateTime.now)
611
- expect { task.send(:validate_input) }.to raise_error(Lluminary::ValidationError, "Age must be an Integer")
648
+ it "raises error for invalid integer input" do
649
+ task =
650
+ task_with_types.new(name: "John", age: "30", start_time: DateTime.now)
651
+ expect { task.send(:validate_input) }.to raise_error(
652
+ Lluminary::ValidationError,
653
+ "Age must be an Integer"
654
+ )
612
655
  end
613
656
 
614
- it 'validates datetime input type' do
615
- task = task_with_types.new(name: "John", age: 30, start_time: DateTime.now)
657
+ it "validates datetime input type" do
658
+ task =
659
+ task_with_types.new(name: "John", age: 30, start_time: DateTime.now)
616
660
  expect { task.send(:validate_input) }.not_to raise_error
617
661
  end
618
662
 
619
- it 'raises error for invalid datetime input' do
620
- task = task_with_types.new(name: "John", age: 30, start_time: "2024-01-01")
621
- expect { task.send(:validate_input) }.to raise_error(Lluminary::ValidationError, "Start time must be a DateTime")
663
+ it "raises error for invalid datetime input" do
664
+ task =
665
+ task_with_types.new(name: "John", age: 30, start_time: "2024-01-01")
666
+ expect { task.send(:validate_input) }.to raise_error(
667
+ Lluminary::ValidationError,
668
+ "Start time must be a DateTime"
669
+ )
622
670
  end
623
671
  end
624
672
 
625
- describe 'SchemaModel integration' do
673
+ describe "SchemaModel integration" do
626
674
  let(:task_with_schema) do
627
675
  Class.new(described_class) do
628
676
  input_schema do
@@ -630,7 +678,11 @@ RSpec.describe Lluminary::Task do
630
678
  integer :min_length
631
679
 
632
680
  validates :text, presence: true
633
- validates :min_length, presence: true, numericality: { greater_than: 0 }
681
+ validates :min_length,
682
+ presence: true,
683
+ numericality: {
684
+ greater_than: 0
685
+ }
634
686
  end
635
687
 
636
688
  output_schema do
@@ -646,20 +698,20 @@ RSpec.describe Lluminary::Task do
646
698
  end
647
699
  end
648
700
 
649
- it 'wraps input in a SchemaModel instance' do
701
+ it "wraps input in a SchemaModel instance" do
650
702
  result = task_with_schema.call(text: "hello", min_length: 3)
651
703
  expect(result.input).to be_a(Lluminary::SchemaModel)
652
704
  expect(result.input.text).to eq("hello")
653
705
  expect(result.input.min_length).to eq(3)
654
706
  end
655
707
 
656
- it 'validates input using SchemaModel' do
708
+ it "validates input using SchemaModel" do
657
709
  result = task_with_schema.call(text: "hello", min_length: 3)
658
710
  expect(result.input.valid?).to be true
659
711
  expect(result.input.errors).to be_empty
660
712
  end
661
713
 
662
- it 'returns validation errors for invalid input' do
714
+ it "returns validation errors for invalid input" do
663
715
  result = task_with_schema.call(text: nil, min_length: nil)
664
716
  expect(result.input.valid?).to be false
665
717
  expect(result.input.errors.full_messages).to contain_exactly(
@@ -669,33 +721,37 @@ RSpec.describe Lluminary::Task do
669
721
  )
670
722
  end
671
723
 
672
- it 'does not execute task when input is invalid' do
724
+ it "does not execute task when input is invalid" do
673
725
  result = task_with_schema.call(text: nil, min_length: nil)
674
726
  expect(result.parsed_response).to be_nil
675
727
  expect(result.output).to be_nil
676
728
  end
677
729
 
678
- it 'raises ValidationError for invalid input when using call!' do
679
- expect {
730
+ it "raises ValidationError for invalid input when using call!" do
731
+ expect do
680
732
  task_with_schema.call!(text: nil, min_length: nil)
681
- }.to raise_error(Lluminary::ValidationError, "Text can't be blank, Min length can't be blank, Min length is not a number")
733
+ end.to raise_error(
734
+ Lluminary::ValidationError,
735
+ "Text can't be blank, Min length can't be blank, Min length is not a number"
736
+ )
682
737
  end
683
738
 
684
- it 'validates that the response is valid JSON' do
739
+ it "validates that the response is valid JSON" do
685
740
  task = task_with_schema.new(text: "hello", min_length: 3)
686
- allow(task.class.provider).to receive(:call).and_return({
687
- raw: "not valid json at all",
688
- parsed: nil
689
- })
741
+ allow(task.class.provider).to receive(:call).and_return(
742
+ { raw: "not valid json at all", parsed: nil }
743
+ )
690
744
 
691
745
  result = task.call
692
746
  expect(result.input.valid?).to be true
693
747
  expect(result.output.valid?).to be false
694
- expect(result.output.errors.full_messages).to include("Raw response must be valid JSON")
748
+ expect(result.output.errors.full_messages).to include(
749
+ "Raw response must be valid JSON"
750
+ )
695
751
  end
696
752
  end
697
753
 
698
- describe 'tasks without inputs' do
754
+ describe "tasks without inputs" do
699
755
  let(:quote_task) do
700
756
  Class.new(described_class) do
701
757
  use_provider :test
@@ -713,13 +769,13 @@ RSpec.describe Lluminary::Task do
713
769
  end
714
770
  end
715
771
 
716
- it 'can be called without any input parameters' do
772
+ it "can be called without any input parameters" do
717
773
  result = quote_task.call
718
774
  expect(result.output.quote).to be_a(String)
719
775
  expect(result.output.author).to be_a(String)
720
776
  end
721
777
 
722
- it 'returns a valid result object' do
778
+ it "returns a valid result object" do
723
779
  result = quote_task.call
724
780
  expect(result).to be_a(Lluminary::Task)
725
781
  expect(result.input).to be_a(Lluminary::SchemaModel)
@@ -727,7 +783,7 @@ RSpec.describe Lluminary::Task do
727
783
  end
728
784
  end
729
785
 
730
- describe 'datetime handling' do
786
+ describe "datetime handling" do
731
787
  let(:task_with_datetime) do
732
788
  Class.new(described_class) do
733
789
  use_provider :test
@@ -744,12 +800,16 @@ RSpec.describe Lluminary::Task do
744
800
  end
745
801
  end
746
802
 
747
- it 'converts ISO8601 datetime strings to DateTime objects' do
803
+ it "converts ISO8601 datetime strings to DateTime objects" do
748
804
  task = task_with_datetime.new
749
- allow(task.class.provider).to receive(:call).and_return({
750
- raw: '{"event_time": "2024-01-01T12:00:00+00:00"}',
751
- parsed: { "event_time" => "2024-01-01T12:00:00+00:00" }
752
- })
805
+ allow(task.class.provider).to receive(:call).and_return(
806
+ {
807
+ raw: '{"event_time": "2024-01-01T12:00:00+00:00"}',
808
+ parsed: {
809
+ "event_time" => "2024-01-01T12:00:00+00:00"
810
+ }
811
+ }
812
+ )
753
813
 
754
814
  result = task.call
755
815
  expect(result.output.valid?).to be true
@@ -762,16 +822,22 @@ RSpec.describe Lluminary::Task do
762
822
  expect(result.output.event_time.second).to eq(0)
763
823
  end
764
824
 
765
- it 'handles invalid datetime strings' do
825
+ it "handles invalid datetime strings" do
766
826
  task = task_with_datetime.new
767
- allow(task.class.provider).to receive(:call).and_return({
768
- raw: '{"event_time": "not a valid datetime"}',
769
- parsed: { "event_time" => "not a valid datetime" }
770
- })
827
+ allow(task.class.provider).to receive(:call).and_return(
828
+ {
829
+ raw: '{"event_time": "not a valid datetime"}',
830
+ parsed: {
831
+ "event_time" => "not a valid datetime"
832
+ }
833
+ }
834
+ )
771
835
 
772
836
  result = task.call
773
837
  expect(result.output.valid?).to be false
774
- expect(result.output.errors.full_messages).to include("Event time must be a DateTime")
838
+ expect(result.output.errors.full_messages).to include(
839
+ "Event time must be a DateTime"
840
+ )
775
841
  end
776
842
  end
777
- end
843
+ end