lluminary 0.1.1 → 0.1.3

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/lib/lluminary/config.rb +6 -1
  3. data/lib/lluminary/models/base.rb +235 -0
  4. data/lib/lluminary/models/bedrock/amazon_nova_pro_v1.rb +19 -0
  5. data/lib/lluminary/models/bedrock/anthropic_claude_instant_v1.rb +17 -0
  6. data/lib/lluminary/models/bedrock/base.rb +29 -0
  7. data/lib/lluminary/models/openai/gpt35_turbo.rb +20 -0
  8. data/lib/lluminary/provider_error.rb +2 -1
  9. data/lib/lluminary/providers/base.rb +20 -3
  10. data/lib/lluminary/providers/bedrock.rb +52 -32
  11. data/lib/lluminary/providers/openai.rb +41 -24
  12. data/lib/lluminary/providers/test.rb +14 -13
  13. data/lib/lluminary/result.rb +5 -2
  14. data/lib/lluminary/schema.rb +59 -15
  15. data/lib/lluminary/schema_model.rb +67 -10
  16. data/lib/lluminary/task.rb +58 -99
  17. data/lib/lluminary/validation_error.rb +2 -1
  18. data/lib/lluminary/version.rb +3 -2
  19. data/lib/lluminary.rb +25 -7
  20. data/spec/examples/analyze_text_spec.rb +7 -4
  21. data/spec/examples/color_analyzer_spec.rb +22 -22
  22. data/spec/examples/content_analyzer_spec.rb +27 -44
  23. data/spec/examples/historical_event_analyzer_spec.rb +18 -15
  24. data/spec/examples/meal_suggester_spec.rb +64 -0
  25. data/spec/examples/price_analyzer_spec.rb +22 -28
  26. data/spec/examples/quote_task_spec.rb +9 -8
  27. data/spec/examples/sentiment_analysis_spec.rb +13 -10
  28. data/spec/examples/summarize_text_spec.rb +7 -4
  29. data/spec/lluminary/config_spec.rb +28 -26
  30. data/spec/lluminary/models/base_spec.rb +581 -0
  31. data/spec/lluminary/models/bedrock/amazon_nova_pro_v1_spec.rb +30 -0
  32. data/spec/lluminary/models/bedrock/anthropic_claude_instant_v1_spec.rb +21 -0
  33. data/spec/lluminary/models/openai/gpt35_turbo_spec.rb +22 -0
  34. data/spec/lluminary/providers/bedrock_spec.rb +86 -57
  35. data/spec/lluminary/providers/openai_spec.rb +58 -34
  36. data/spec/lluminary/providers/test_spec.rb +46 -16
  37. data/spec/lluminary/result_spec.rb +17 -10
  38. data/spec/lluminary/schema_model_spec.rb +108 -22
  39. data/spec/lluminary/schema_spec.rb +241 -107
  40. data/spec/lluminary/task_spec.rb +118 -584
  41. data/spec/spec_helper.rb +8 -2
  42. metadata +73 -22
  43. data/lib/lluminary/field_description.rb +0 -148
  44. data/spec/lluminary/field_description_spec.rb +0 -36
  45. data/spec/lluminary/providers/base_spec.rb +0 -17
@@ -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
@@ -11,570 +12,79 @@ RSpec.describe Lluminary::Task do
11
12
  string :summary, description: "A brief summary of the message"
12
13
  end
13
14
 
14
- private
15
-
16
15
  def task_prompt
17
16
  "Say: #{message}"
18
17
  end
19
18
  end
20
19
  end
21
20
 
22
- let(:task_with_test) do
23
- Class.new(described_class) do
24
- use_provider :test
25
- end
26
- end
21
+ let(:task_with_test) { Class.new(described_class) { use_provider :test } }
27
22
 
28
- describe '.call' do
29
- it 'returns a result with a raw response from the provider' do
23
+ describe ".call" do
24
+ it "returns a result with a raw response from the provider" do
30
25
  result = task_class.call(message: "hello")
31
- expect(result.output.raw_response).to eq('{"summary": "Test string value"}')
32
- end
33
-
34
- it 'string input allows providing a string input' do
35
- result = task_class.call(message: "hello")
36
- expect(result.output.raw_response).to eq('{"summary": "Test string value"}')
26
+ expect(result.output.raw_response).to eq(
27
+ '{"summary": "Test string value"}'
28
+ )
37
29
  end
38
30
 
39
- it 'string output returns the output in the result' do
31
+ it "string output returns the output in the result" do
40
32
  result = task_class.call(message: "hello")
41
33
  expect(result.output.summary).to eq("Test string value")
42
34
  end
43
-
44
- it 'includes schema descriptions in the prompt' do
45
- result = task_class.call(message: "hello")
46
- expected_schema = <<~SCHEMA
47
- You must respond with ONLY a valid JSON object. Do not include any other text, explanations, or formatting.
48
- The JSON object must contain the following fields:
49
-
50
- summary (string): A brief summary of the message
51
- Example: "your summary here"
52
-
53
- Your response must be ONLY this JSON object:
54
- {
55
- "summary": "your summary here"
56
- }
57
- SCHEMA
58
- expect(result.prompt).to include(expected_schema.chomp)
59
- end
60
35
  end
61
36
 
62
- describe '.call without descriptions' do
63
- let(:task_without_descriptions) do
64
- Class.new(described_class) do
65
- input_schema do
66
- string :message
67
- end
68
-
69
- output_schema do
70
- string :summary
71
- end
72
-
73
- private
74
-
75
- def task_prompt
76
- "Say: #{message}"
77
- end
78
- end
79
- end
80
-
81
- it 'includes basic schema in the prompt' do
82
- result = task_without_descriptions.call(message: "hello")
83
- expected_schema = <<~SCHEMA
84
- You must respond with ONLY a valid JSON object. Do not include any other text, explanations, or formatting.
85
- The JSON object must contain the following fields:
86
-
87
- summary (string)
88
- Example: "your summary here"
89
-
90
- Your response must be ONLY this JSON object:
91
- {
92
- "summary": "your summary here"
93
- }
94
- SCHEMA
95
- expect(result.prompt).to include(expected_schema.chomp)
96
- end
97
- end
98
-
99
- describe '.provider' do
100
- it 'defaults to Test provider' do
37
+ describe ".provider" do
38
+ it "defaults to Test provider" do
101
39
  expect(task_class.provider).to be_a(Lluminary::Providers::Test)
102
40
  end
103
41
 
104
- it 'allows setting a custom provider' do
105
- custom_provider = double('CustomProvider')
42
+ it "allows setting a custom provider" do
43
+ custom_provider = double("CustomProvider")
106
44
  task_class.provider = custom_provider
107
45
  expect(task_class.provider).to eq(custom_provider)
108
46
  end
109
47
  end
110
48
 
111
- describe '.use_provider' do
112
- it 'with :test provider sets the test provider' do
49
+ describe ".use_provider" do
50
+ it "with :test provider sets the test provider" do
113
51
  expect(task_with_test.provider).to be_a(Lluminary::Providers::Test)
114
52
  end
115
53
 
116
- it 'with :openai instantiates OpenAI provider with config' do
117
- task_class.use_provider(:openai, api_key: 'test')
54
+ it "with :openai instantiates OpenAI provider with config" do
55
+ task_class.use_provider(:openai, api_key: "test")
118
56
  expect(task_class.provider).to be_a(Lluminary::Providers::OpenAI)
119
- expect(task_class.provider.config).to eq(api_key: 'test', model: 'gpt-4o')
120
- end
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")
126
- end
127
- end
128
-
129
- describe '#json_schema_example' do
130
- it 'generates a schema example with descriptions' do
131
- task = task_class.new(message: "test")
132
- expected_output = <<~SCHEMA
133
- You must respond with ONLY a valid JSON object. Do not include any other text, explanations, or formatting.
134
- The JSON object must contain the following fields:
135
-
136
- summary (string): A brief summary of the message
137
- Example: "your summary here"
138
-
139
- Your response must be ONLY this JSON object:
140
- {
141
- "summary": "your summary here"
142
- }
143
- SCHEMA
144
- expect(task.send(:json_schema_example)).to eq(expected_output.chomp)
145
- end
146
-
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
152
-
153
- output_schema do
154
- datetime :start_time, description: "When the event starts"
155
- end
156
-
157
- private
158
-
159
- def task_prompt
160
- "Say: #{message}"
161
- end
162
- end
163
-
164
- task = task_with_datetime.new(message: "test")
165
- expected_output = <<~SCHEMA
166
- You must respond with ONLY a valid JSON object. Do not include any other text, explanations, or formatting.
167
- The JSON object must contain the following fields:
168
-
169
- start_time (datetime in ISO8601 format): When the event starts
170
- Example: "2024-01-01T12:00:00+00:00"
171
-
172
- Your response must be ONLY this JSON object:
173
- {
174
- "start_time": "2024-01-01T12:00:00+00:00"
175
- }
176
- SCHEMA
177
- expect(task.send(:json_schema_example)).to eq(expected_output.chomp)
178
- end
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
185
-
186
- output_schema do
187
- boolean :is_valid, description: "Whether the input is valid"
188
- end
189
-
190
- private
191
-
192
- def task_prompt
193
- "Say: #{message}"
194
- end
195
- end
196
-
197
- task = task_with_boolean.new(message: "test")
198
- expected_output = <<~SCHEMA
199
- You must respond with ONLY a valid JSON object. Do not include any other text, explanations, or formatting.
200
- The JSON object must contain the following fields:
201
-
202
- is_valid (boolean): Whether the input is valid
203
- Example: true
204
-
205
- Your response must be ONLY this JSON object:
206
- {
207
- "is_valid": true
208
- }
209
- SCHEMA
210
- expect(task.send(:json_schema_example)).to eq(expected_output.chomp)
211
- end
212
-
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
218
-
219
- output_schema do
220
- float :score, description: "The confidence score"
221
- end
222
-
223
- private
224
-
225
- def task_prompt
226
- "Say: #{message}"
227
- end
228
- end
229
-
230
- task = task_with_float.new(message: "test")
231
- expected_output = <<~SCHEMA
232
- You must respond with ONLY a valid JSON object. Do not include any other text, explanations, or formatting.
233
- The JSON object must contain the following fields:
234
-
235
- score (float): The confidence score
236
- Example: 0.0
237
-
238
- Your response must be ONLY this JSON object:
239
- {
240
- "score": 0.0
241
- }
242
- SCHEMA
243
- expect(task.send(:json_schema_example)).to eq(expected_output.chomp)
57
+ expect(task_class.provider.config).to include(
58
+ api_key: "test",
59
+ model: Lluminary::Models::OpenAi::Gpt35Turbo
60
+ )
244
61
  end
245
62
 
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
251
-
252
- output_schema do
253
- string :summary
254
- end
255
-
256
- private
257
-
258
- def task_prompt
259
- "Say: #{message}"
260
- end
261
- end
262
-
263
- task = task_without_descriptions.new(message: "test")
264
- expected_output = <<~SCHEMA
265
- You must respond with ONLY a valid JSON object. Do not include any other text, explanations, or formatting.
266
- The JSON object must contain the following fields:
267
-
268
- summary (string)
269
- Example: "your summary here"
270
-
271
- Your response must be ONLY this JSON object:
272
- {
273
- "summary": "your summary here"
274
- }
275
- SCHEMA
276
- expect(task.send(:json_schema_example)).to eq(expected_output.chomp)
63
+ it "with :bedrock instantiates Bedrock provider with config" do
64
+ task_class.use_provider(
65
+ :bedrock,
66
+ access_key_id: "test",
67
+ secret_access_key: "test",
68
+ region: "us-east-1"
69
+ )
70
+ expect(task_class.provider).to be_a(Lluminary::Providers::Bedrock)
71
+ expect(task_class.provider.config).to include(
72
+ access_key_id: "test",
73
+ secret_access_key: "test",
74
+ region: "us-east-1",
75
+ model: Lluminary::Models::Bedrock::AnthropicClaudeInstantV1
76
+ )
277
77
  end
278
78
 
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
287
-
288
- private
289
- def task_prompt; "Test prompt"; end
290
- end
291
-
292
- task = task_class.new
293
- expect(task.send(:json_schema_example)).to include(
294
- "name (string): The person's name\nValidation: must be present\nExample: \"your name here\""
295
- )
296
- end
297
- end
298
-
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
306
-
307
- private
308
- def task_prompt; "Test prompt"; end
309
- end
310
-
311
- task = task_class.new
312
- expect(task.send(:json_schema_example)).to include(
313
- "name (string): The person's name\nValidation: must be at least 2 characters\nExample: \"your name here\""
314
- )
315
- end
316
-
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
323
-
324
- private
325
- def task_prompt; "Test prompt"; end
326
- end
327
-
328
- task = task_class.new
329
- expect(task.send(:json_schema_example)).to include(
330
- "name (string): The person's name\nValidation: must be at most 20 characters\nExample: \"your name here\""
331
- )
332
- end
333
-
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
340
-
341
- private
342
- def task_prompt; "Test prompt"; end
343
- end
344
-
345
- 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
- )
349
- end
350
-
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
360
-
361
- private
362
- def task_prompt; "Test prompt"; end
363
- end
364
-
365
- 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
- )
369
- end
370
- end
371
-
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
379
-
380
- private
381
- def task_prompt; "Test prompt"; end
382
- end
383
-
384
- task = task_class.new
385
- expect(task.send(:json_schema_example)).to include(
386
- "number (integer): Odd number\nValidation: must be odd\nExample: 0"
387
- )
388
- end
389
-
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
-
397
- private
398
- def task_prompt; "Test prompt"; end
399
- end
400
-
401
- task = task_class.new
402
- expect(task.send(:json_schema_example)).to include(
403
- "level (integer): Level\nValidation: must be equal to 5\nExample: 0"
404
- )
405
- end
406
-
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
413
-
414
- private
415
- def task_prompt; "Test prompt"; end
416
- end
417
-
418
- task = task_class.new
419
- expect(task.send(:json_schema_example)).to include(
420
- "score (integer): Score\nValidation: must be less than or equal to 100\nExample: 0"
421
- )
422
- end
423
-
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
430
-
431
- private
432
- def task_prompt; "Test prompt"; end
433
- end
434
-
435
- task = task_class.new
436
- expect(task.send(:json_schema_example)).to include(
437
- "value (integer): Value\nValidation: must be other than 0\nExample: 0"
438
- )
439
- end
440
-
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
447
-
448
- private
449
- def task_prompt; "Test prompt"; end
450
- end
451
-
452
- task = task_class.new
453
- expect(task.send(:json_schema_example)).to include(
454
- "rating (integer): Rating\nValidation: must be in: 1, 2, 3, 4, 5\nExample: 0"
455
- )
456
- end
457
-
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
467
-
468
- private
469
- def task_prompt; "Test prompt"; end
470
- end
471
-
472
- task = task_class.new
473
- expect(task.send(:json_schema_example)).to include(
474
- "score (integer): Score\nValidation: must be greater than 0, must be less than or equal to 100\nExample: 0"
475
- )
476
- end
477
-
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
487
-
488
- private
489
- def task_prompt; "Test prompt"; end
490
- end
491
-
492
- task = task_class.new
493
- expect(task.send(:json_schema_example)).to include(
494
- "age (integer): Age\nValidation: must be greater than 0, must be less than 120\nExample: 0"
495
- )
496
- end
497
- end
498
-
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
506
-
507
- private
508
- def task_prompt; "Test prompt"; end
509
- end
510
-
511
- 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
- )
515
- end
516
- end
517
-
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
525
-
526
- private
527
- def task_prompt; "Test prompt"; end
528
- end
529
-
530
- task = task_class.new
531
- expect(task.send(:json_schema_example)).to include(
532
- "role (string): User role\nValidation: must be one of: admin, user, guest\nExample: \"your role here\""
533
- )
534
- end
535
- end
536
-
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
544
-
545
- private
546
- def task_prompt; "Test prompt"; end
547
- end
548
-
549
- task = task_class.new
550
- expect(task.send(:json_schema_example)).to include(
551
- "status (string): Status\nValidation: must not be one of: banned, blocked\nExample: \"your status here\""
552
- )
553
- end
554
- end
555
-
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
563
-
564
- private
565
- def task_prompt; "Test prompt"; end
566
- end
567
-
568
- task = task_class.new
569
- expect(task.send(:json_schema_example)).to include(
570
- "deleted_at (string): Deletion timestamp\nValidation: must be absent\nExample: \"your deleted_at here\""
571
- )
572
- end
573
- end
79
+ it "raises ArgumentError for unknown provider" do
80
+ expect { task_class.use_provider(:unknown) }.to raise_error(
81
+ ArgumentError,
82
+ "Unknown provider: unknown"
83
+ )
574
84
  end
575
85
  end
576
86
 
577
- describe '#validate_input' do
87
+ describe "#validate_input" do
578
88
  let(:task_with_types) do
579
89
  Class.new(described_class) do
580
90
  input_schema do
@@ -583,46 +93,58 @@ RSpec.describe Lluminary::Task do
583
93
  datetime :start_time
584
94
  end
585
95
 
586
- private
587
-
588
96
  def task_prompt
589
97
  "Test prompt"
590
98
  end
591
99
  end
592
100
  end
593
101
 
594
- it 'validates string input type' do
595
- task = task_with_types.new(name: "John", age: 30, start_time: DateTime.now)
102
+ it "validates string input type" do
103
+ task =
104
+ task_with_types.new(name: "John", age: 30, start_time: DateTime.now)
596
105
  expect { task.send(:validate_input) }.not_to raise_error
597
106
  end
598
107
 
599
- it 'raises error for invalid string input' do
108
+ it "raises error for invalid string input" do
600
109
  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")
110
+ expect { task.send(:validate_input) }.to raise_error(
111
+ Lluminary::ValidationError,
112
+ "Name must be a String"
113
+ )
602
114
  end
603
115
 
604
- it 'validates integer input type' do
605
- task = task_with_types.new(name: "John", age: 30, start_time: DateTime.now)
116
+ it "validates integer input type" do
117
+ task =
118
+ task_with_types.new(name: "John", age: 30, start_time: DateTime.now)
606
119
  expect { task.send(:validate_input) }.not_to raise_error
607
120
  end
608
121
 
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")
122
+ it "raises error for invalid integer input" do
123
+ task =
124
+ task_with_types.new(name: "John", age: "30", start_time: DateTime.now)
125
+ expect { task.send(:validate_input) }.to raise_error(
126
+ Lluminary::ValidationError,
127
+ "Age must be an Integer"
128
+ )
612
129
  end
613
130
 
614
- it 'validates datetime input type' do
615
- task = task_with_types.new(name: "John", age: 30, start_time: DateTime.now)
131
+ it "validates datetime input type" do
132
+ task =
133
+ task_with_types.new(name: "John", age: 30, start_time: DateTime.now)
616
134
  expect { task.send(:validate_input) }.not_to raise_error
617
135
  end
618
136
 
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")
137
+ it "raises error for invalid datetime input" do
138
+ task =
139
+ task_with_types.new(name: "John", age: 30, start_time: "2024-01-01")
140
+ expect { task.send(:validate_input) }.to raise_error(
141
+ Lluminary::ValidationError,
142
+ "Start time must be a DateTime"
143
+ )
622
144
  end
623
145
  end
624
146
 
625
- describe 'SchemaModel integration' do
147
+ describe "SchemaModel integration" do
626
148
  let(:task_with_schema) do
627
149
  Class.new(described_class) do
628
150
  input_schema do
@@ -630,7 +152,11 @@ RSpec.describe Lluminary::Task do
630
152
  integer :min_length
631
153
 
632
154
  validates :text, presence: true
633
- validates :min_length, presence: true, numericality: { greater_than: 0 }
155
+ validates :min_length,
156
+ presence: true,
157
+ numericality: {
158
+ greater_than: 0
159
+ }
634
160
  end
635
161
 
636
162
  output_schema do
@@ -638,28 +164,26 @@ RSpec.describe Lluminary::Task do
638
164
  integer :word_count
639
165
  end
640
166
 
641
- private
642
-
643
167
  def task_prompt
644
168
  "Test prompt"
645
169
  end
646
170
  end
647
171
  end
648
172
 
649
- it 'wraps input in a SchemaModel instance' do
173
+ it "wraps input in a SchemaModel instance" do
650
174
  result = task_with_schema.call(text: "hello", min_length: 3)
651
175
  expect(result.input).to be_a(Lluminary::SchemaModel)
652
176
  expect(result.input.text).to eq("hello")
653
177
  expect(result.input.min_length).to eq(3)
654
178
  end
655
179
 
656
- it 'validates input using SchemaModel' do
180
+ it "validates input using SchemaModel" do
657
181
  result = task_with_schema.call(text: "hello", min_length: 3)
658
182
  expect(result.input.valid?).to be true
659
183
  expect(result.input.errors).to be_empty
660
184
  end
661
185
 
662
- it 'returns validation errors for invalid input' do
186
+ it "returns validation errors for invalid input" do
663
187
  result = task_with_schema.call(text: nil, min_length: nil)
664
188
  expect(result.input.valid?).to be false
665
189
  expect(result.input.errors.full_messages).to contain_exactly(
@@ -669,33 +193,37 @@ RSpec.describe Lluminary::Task do
669
193
  )
670
194
  end
671
195
 
672
- it 'does not execute task when input is invalid' do
196
+ it "does not execute task when input is invalid" do
673
197
  result = task_with_schema.call(text: nil, min_length: nil)
674
198
  expect(result.parsed_response).to be_nil
675
199
  expect(result.output).to be_nil
676
200
  end
677
201
 
678
- it 'raises ValidationError for invalid input when using call!' do
679
- expect {
202
+ it "raises ValidationError for invalid input when using call!" do
203
+ expect do
680
204
  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")
205
+ end.to raise_error(
206
+ Lluminary::ValidationError,
207
+ "Text can't be blank, Min length can't be blank, Min length is not a number"
208
+ )
682
209
  end
683
210
 
684
- it 'validates that the response is valid JSON' do
211
+ it "validates that the response is valid JSON" do
685
212
  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
- })
213
+ allow(task.class.provider).to receive(:call).and_return(
214
+ { raw: "not valid json at all", parsed: nil }
215
+ )
690
216
 
691
217
  result = task.call
692
218
  expect(result.input.valid?).to be true
693
219
  expect(result.output.valid?).to be false
694
- expect(result.output.errors.full_messages).to include("Raw response must be valid JSON")
220
+ expect(result.output.errors.full_messages).to include(
221
+ "Raw response must be valid JSON"
222
+ )
695
223
  end
696
224
  end
697
225
 
698
- describe 'tasks without inputs' do
226
+ describe "tasks without inputs" do
699
227
  let(:quote_task) do
700
228
  Class.new(described_class) do
701
229
  use_provider :test
@@ -705,21 +233,19 @@ RSpec.describe Lluminary::Task do
705
233
  string :author, description: "The person who said the quote"
706
234
  end
707
235
 
708
- private
709
-
710
236
  def task_prompt
711
237
  "Generate an inspirational quote and its author"
712
238
  end
713
239
  end
714
240
  end
715
241
 
716
- it 'can be called without any input parameters' do
242
+ it "can be called without any input parameters" do
717
243
  result = quote_task.call
718
244
  expect(result.output.quote).to be_a(String)
719
245
  expect(result.output.author).to be_a(String)
720
246
  end
721
247
 
722
- it 'returns a valid result object' do
248
+ it "returns a valid result object" do
723
249
  result = quote_task.call
724
250
  expect(result).to be_a(Lluminary::Task)
725
251
  expect(result.input).to be_a(Lluminary::SchemaModel)
@@ -727,7 +253,7 @@ RSpec.describe Lluminary::Task do
727
253
  end
728
254
  end
729
255
 
730
- describe 'datetime handling' do
256
+ describe "datetime handling" do
731
257
  let(:task_with_datetime) do
732
258
  Class.new(described_class) do
733
259
  use_provider :test
@@ -736,20 +262,22 @@ RSpec.describe Lluminary::Task do
736
262
  datetime :event_time, description: "When the event occurred"
737
263
  end
738
264
 
739
- private
740
-
741
265
  def task_prompt
742
266
  "Test prompt"
743
267
  end
744
268
  end
745
269
  end
746
270
 
747
- it 'converts ISO8601 datetime strings to DateTime objects' do
271
+ it "converts ISO8601 datetime strings to DateTime objects" do
748
272
  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
- })
273
+ allow(task.class.provider).to receive(:call).and_return(
274
+ {
275
+ raw: '{"event_time": "2024-01-01T12:00:00+00:00"}',
276
+ parsed: {
277
+ "event_time" => "2024-01-01T12:00:00+00:00"
278
+ }
279
+ }
280
+ )
753
281
 
754
282
  result = task.call
755
283
  expect(result.output.valid?).to be true
@@ -762,16 +290,22 @@ RSpec.describe Lluminary::Task do
762
290
  expect(result.output.event_time.second).to eq(0)
763
291
  end
764
292
 
765
- it 'handles invalid datetime strings' do
293
+ it "handles invalid datetime strings" do
766
294
  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
- })
295
+ allow(task.class.provider).to receive(:call).and_return(
296
+ {
297
+ raw: '{"event_time": "not a valid datetime"}',
298
+ parsed: {
299
+ "event_time" => "not a valid datetime"
300
+ }
301
+ }
302
+ )
771
303
 
772
304
  result = task.call
773
305
  expect(result.output.valid?).to be false
774
- expect(result.output.errors.full_messages).to include("Event time must be a DateTime")
306
+ expect(result.output.errors.full_messages).to include(
307
+ "Event time must be a DateTime"
308
+ )
775
309
  end
776
310
  end
777
- end
311
+ end