ai-lite 0.6.1 → 1.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/README.md +368 -8
- data/lib/ai_lite/gemini/chat.rb +78 -0
- data/lib/ai_lite/gemini/configuration.rb +19 -0
- data/lib/ai_lite/gemini/models.rb +102 -0
- data/lib/ai_lite/gemini/streaming.rb +148 -0
- data/lib/ai_lite/gemini/youtube.rb +36 -0
- data/lib/ai_lite/gemini.rb +92 -0
- data/lib/ai_lite/openai/configuration.rb +51 -0
- data/lib/ai_lite/openai/models.rb +96 -0
- data/lib/ai_lite/openai/streaming.rb +130 -0
- data/lib/ai_lite/openai.rb +607 -0
- data/lib/ai_lite/version.rb +1 -1
- data/lib/ai_lite.rb +32 -624
- data/test/ai_lite_test.rb +196 -72
- data/test/gemini_models_test.rb +112 -0
- data/test/gemini_streaming_test.rb +166 -0
- data/test/gemini_test.rb +200 -0
- data/test/gemini_youtube_test.rb +57 -0
- data/test/openai_models_test.rb +138 -0
- data/test/openai_streaming_test.rb +189 -0
- data/test/run.rb +3 -0
- metadata +27 -6
data/test/ai_lite_test.rb
CHANGED
|
@@ -20,8 +20,27 @@ class AiLiteTest < Minitest::Test
|
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
def provider
|
|
24
|
+
AiLite::OpenAI
|
|
25
|
+
end
|
|
26
|
+
|
|
23
27
|
def setup
|
|
24
|
-
|
|
28
|
+
provider.reset_configuration!
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_http_200_failed_or_incomplete_generation_is_not_success
|
|
32
|
+
client = provider.new(api_key: "test-key")
|
|
33
|
+
%w[failed cancelled incomplete].each do |state|
|
|
34
|
+
body = JSON.generate("id" => "resp_partial", "status" => state,
|
|
35
|
+
"incomplete_details" => { "reason" => "max_output_tokens" }, "output" => [])
|
|
36
|
+
with_stubbed_http(FakeResponse.new("200", body)) do
|
|
37
|
+
result = client.chat("Hi", debug: true)
|
|
38
|
+
assert_equal 200, result["status"]
|
|
39
|
+
assert_includes result["error"], state
|
|
40
|
+
assert_equal "resp_partial", result["response_id"]
|
|
41
|
+
refute_nil result["raw"]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
25
44
|
end
|
|
26
45
|
|
|
27
46
|
def test_has_a_version_number
|
|
@@ -30,7 +49,7 @@ class AiLiteTest < Minitest::Test
|
|
|
30
49
|
|
|
31
50
|
def test_stores_initializer_config_and_headers
|
|
32
51
|
with_env("OPENAI_API_KEY" => nil, "OPEN_AI_TOKEN" => nil) do
|
|
33
|
-
client =
|
|
52
|
+
client = provider.new(
|
|
34
53
|
api_key: "explicit-key",
|
|
35
54
|
model: "gpt-test",
|
|
36
55
|
image_model: "gpt-image-test",
|
|
@@ -57,7 +76,7 @@ class AiLiteTest < Minitest::Test
|
|
|
57
76
|
|
|
58
77
|
def test_configure_sets_defaults_for_client
|
|
59
78
|
with_env("OPENAI_API_KEY" => nil, "OPEN_AI_TOKEN" => nil) do
|
|
60
|
-
|
|
79
|
+
provider.configure do |config|
|
|
61
80
|
config.api_key = "configured-key"
|
|
62
81
|
config.model = "gpt-config"
|
|
63
82
|
config.moderation_model = "omni-moderation-test"
|
|
@@ -70,7 +89,7 @@ class AiLiteTest < Minitest::Test
|
|
|
70
89
|
config.max_output_tokens = 750
|
|
71
90
|
end
|
|
72
91
|
|
|
73
|
-
client =
|
|
92
|
+
client = provider.client
|
|
74
93
|
|
|
75
94
|
assert_equal "configured-key", client.api_key
|
|
76
95
|
assert_equal "gpt-config", client.model
|
|
@@ -82,23 +101,23 @@ class AiLiteTest < Minitest::Test
|
|
|
82
101
|
assert_equal "gpt-transcribe-test", client.transcription_model
|
|
83
102
|
assert_equal 15, client.timeout
|
|
84
103
|
assert_equal 750, client.max_output_tokens
|
|
85
|
-
assert_same client,
|
|
104
|
+
assert_same client, provider.client
|
|
86
105
|
end
|
|
87
106
|
end
|
|
88
107
|
|
|
89
108
|
def test_configure_resets_cached_client
|
|
90
|
-
|
|
91
|
-
first_client =
|
|
109
|
+
provider.configure { |config| config.api_key = "first-key" }
|
|
110
|
+
first_client = provider.client
|
|
92
111
|
|
|
93
|
-
|
|
94
|
-
second_client =
|
|
112
|
+
provider.configure { |config| config.api_key = "second-key" }
|
|
113
|
+
second_client = provider.client
|
|
95
114
|
|
|
96
115
|
refute_same first_client, second_client
|
|
97
116
|
assert_equal "second-key", second_client.api_key
|
|
98
117
|
end
|
|
99
118
|
|
|
100
119
|
def test_instance_arguments_override_configuration
|
|
101
|
-
|
|
120
|
+
provider.configure do |config|
|
|
102
121
|
config.api_key = "configured-key"
|
|
103
122
|
config.model = "gpt-config"
|
|
104
123
|
config.moderation_model = "omni-moderation-config"
|
|
@@ -111,7 +130,7 @@ class AiLiteTest < Minitest::Test
|
|
|
111
130
|
config.max_output_tokens = 750
|
|
112
131
|
end
|
|
113
132
|
|
|
114
|
-
client =
|
|
133
|
+
client = provider.new(
|
|
115
134
|
api_key: "explicit-key",
|
|
116
135
|
model: "gpt-explicit",
|
|
117
136
|
moderation_model: "omni-moderation-explicit",
|
|
@@ -138,32 +157,32 @@ class AiLiteTest < Minitest::Test
|
|
|
138
157
|
|
|
139
158
|
def test_api_key_fallback_order
|
|
140
159
|
with_env("OPENAI_API_KEY" => "openai-key", "OPEN_AI_TOKEN" => "legacy-key") do
|
|
141
|
-
assert_equal "openai-key",
|
|
142
|
-
assert_equal "explicit-key",
|
|
160
|
+
assert_equal "openai-key", provider.new.api_key
|
|
161
|
+
assert_equal "explicit-key", provider.new(api_key: "explicit-key").api_key
|
|
143
162
|
end
|
|
144
163
|
|
|
145
164
|
with_env("OPENAI_API_KEY" => nil, "OPEN_AI_TOKEN" => "legacy-key") do
|
|
146
|
-
assert_equal "legacy-key",
|
|
165
|
+
assert_equal "legacy-key", provider.new.api_key
|
|
147
166
|
end
|
|
148
167
|
end
|
|
149
168
|
|
|
150
169
|
def test_configured_api_key_takes_precedence_over_env
|
|
151
|
-
|
|
170
|
+
provider.configure { |config| config.api_key = "configured-key" }
|
|
152
171
|
|
|
153
172
|
with_env("OPENAI_API_KEY" => "openai-key", "OPEN_AI_TOKEN" => "legacy-key") do
|
|
154
|
-
assert_equal "configured-key",
|
|
155
|
-
assert_equal "explicit-key",
|
|
173
|
+
assert_equal "configured-key", provider.new.api_key
|
|
174
|
+
assert_equal "explicit-key", provider.new(api_key: "explicit-key").api_key
|
|
156
175
|
end
|
|
157
176
|
end
|
|
158
177
|
|
|
159
178
|
def test_missing_api_key_raises
|
|
160
179
|
with_env("OPENAI_API_KEY" => nil, "OPEN_AI_TOKEN" => nil) do
|
|
161
|
-
assert_raises(ArgumentError) {
|
|
180
|
+
assert_raises(ArgumentError) { provider.new }
|
|
162
181
|
end
|
|
163
182
|
end
|
|
164
183
|
|
|
165
184
|
def test_chat_sends_post_to_responses_with_default_payload
|
|
166
|
-
client =
|
|
185
|
+
client = provider.new(api_key: "token-abc")
|
|
167
186
|
|
|
168
187
|
with_stubbed_http(success_response("Hello!")) do |captured, _response|
|
|
169
188
|
result = client.chat("Say hello")
|
|
@@ -189,7 +208,7 @@ class AiLiteTest < Minitest::Test
|
|
|
189
208
|
end
|
|
190
209
|
|
|
191
210
|
def test_chat_allows_overriding_max_output_tokens
|
|
192
|
-
client =
|
|
211
|
+
client = provider.new(api_key: "token-abc")
|
|
193
212
|
|
|
194
213
|
with_stubbed_http(success_response("Short")) do |captured, _response|
|
|
195
214
|
client.chat("Summarize", max_output_tokens: 500)
|
|
@@ -200,13 +219,13 @@ class AiLiteTest < Minitest::Test
|
|
|
200
219
|
end
|
|
201
220
|
|
|
202
221
|
def test_chat_uses_configured_max_output_tokens
|
|
203
|
-
|
|
222
|
+
provider.configure do |config|
|
|
204
223
|
config.api_key = "configured-key"
|
|
205
224
|
config.max_output_tokens = 750
|
|
206
225
|
end
|
|
207
226
|
|
|
208
227
|
with_stubbed_http(success_response("Configured")) do |captured, _response|
|
|
209
|
-
|
|
228
|
+
provider.chat("Use configured defaults")
|
|
210
229
|
payload = JSON.parse(captured[:http].last_request.body)
|
|
211
230
|
|
|
212
231
|
assert_equal 750, payload["max_output_tokens"]
|
|
@@ -215,7 +234,7 @@ class AiLiteTest < Minitest::Test
|
|
|
215
234
|
end
|
|
216
235
|
|
|
217
236
|
def test_chat_includes_instructions_model_and_options
|
|
218
|
-
client =
|
|
237
|
+
client = provider.new(api_key: "token-abc")
|
|
219
238
|
|
|
220
239
|
with_stubbed_http(success_response("Aye")) do |captured, _response|
|
|
221
240
|
client.chat(
|
|
@@ -239,7 +258,7 @@ class AiLiteTest < Minitest::Test
|
|
|
239
258
|
end
|
|
240
259
|
|
|
241
260
|
def test_chat_supports_previous_response_id
|
|
242
|
-
client =
|
|
261
|
+
client = provider.new(api_key: "token-abc")
|
|
243
262
|
|
|
244
263
|
with_stubbed_http(success_response("Follow-up")) do |captured, _response|
|
|
245
264
|
result = client.chat(
|
|
@@ -255,7 +274,7 @@ class AiLiteTest < Minitest::Test
|
|
|
255
274
|
end
|
|
256
275
|
|
|
257
276
|
def test_chat_supports_message_array_input
|
|
258
|
-
client =
|
|
277
|
+
client = provider.new(api_key: "token-abc")
|
|
259
278
|
messages = [
|
|
260
279
|
{ role: "developer", content: "Be concise." },
|
|
261
280
|
{ role: "user", content: "Say hello." }
|
|
@@ -276,7 +295,7 @@ class AiLiteTest < Minitest::Test
|
|
|
276
295
|
end
|
|
277
296
|
|
|
278
297
|
def test_chat_first_class_previous_response_id_overrides_options
|
|
279
|
-
client =
|
|
298
|
+
client = provider.new(api_key: "token-abc")
|
|
280
299
|
|
|
281
300
|
with_stubbed_http(success_response("Follow-up")) do |captured, _response|
|
|
282
301
|
client.chat(
|
|
@@ -291,7 +310,7 @@ class AiLiteTest < Minitest::Test
|
|
|
291
310
|
end
|
|
292
311
|
|
|
293
312
|
def test_chat_keeps_supporting_previous_response_id_through_options
|
|
294
|
-
client =
|
|
313
|
+
client = provider.new(api_key: "token-abc")
|
|
295
314
|
|
|
296
315
|
with_stubbed_http(success_response("Follow-up")) do |captured, _response|
|
|
297
316
|
client.chat("Continue", options: { previous_response_id: "resp_options" })
|
|
@@ -302,7 +321,7 @@ class AiLiteTest < Minitest::Test
|
|
|
302
321
|
end
|
|
303
322
|
|
|
304
323
|
def test_extracts_text_from_nested_output_text_items
|
|
305
|
-
client =
|
|
324
|
+
client = provider.new(api_key: "token-abc")
|
|
306
325
|
body = JSON.generate(
|
|
307
326
|
"id" => "resp_nested_123",
|
|
308
327
|
"output" => [
|
|
@@ -328,7 +347,7 @@ class AiLiteTest < Minitest::Test
|
|
|
328
347
|
end
|
|
329
348
|
|
|
330
349
|
def test_parses_json_looking_output_with_string_keys
|
|
331
|
-
client =
|
|
350
|
+
client = provider.new(api_key: "token-abc")
|
|
332
351
|
|
|
333
352
|
with_stubbed_http(success_response(JSON.generate("valid" => true))) do |_captured, _response|
|
|
334
353
|
result = client.chat("Validate")
|
|
@@ -341,7 +360,7 @@ class AiLiteTest < Minitest::Test
|
|
|
341
360
|
end
|
|
342
361
|
|
|
343
362
|
def test_debug_true_returns_raw_success_response
|
|
344
|
-
client =
|
|
363
|
+
client = provider.new(api_key: "token-abc")
|
|
345
364
|
|
|
346
365
|
with_stubbed_http(success_response("Hello!")) do |_captured, _response|
|
|
347
366
|
result = client.chat("Say hello", debug: true)
|
|
@@ -364,7 +383,7 @@ class AiLiteTest < Minitest::Test
|
|
|
364
383
|
end
|
|
365
384
|
|
|
366
385
|
def test_moderate_sends_post_to_moderations_with_default_payload
|
|
367
|
-
client =
|
|
386
|
+
client = provider.new(api_key: "token-abc")
|
|
368
387
|
|
|
369
388
|
with_stubbed_http(moderation_response) do |captured, _response|
|
|
370
389
|
result = client.moderate("Some user submitted text")
|
|
@@ -389,13 +408,13 @@ class AiLiteTest < Minitest::Test
|
|
|
389
408
|
end
|
|
390
409
|
|
|
391
410
|
def test_moderate_uses_class_level_configured_client
|
|
392
|
-
|
|
411
|
+
provider.configure do |config|
|
|
393
412
|
config.api_key = "configured-key"
|
|
394
413
|
config.moderation_model = "omni-moderation-config"
|
|
395
414
|
end
|
|
396
415
|
|
|
397
416
|
with_stubbed_http(moderation_response) do |captured, _response|
|
|
398
|
-
|
|
417
|
+
provider.moderate("Use configured defaults")
|
|
399
418
|
payload = JSON.parse(captured[:http].last_request.body)
|
|
400
419
|
|
|
401
420
|
assert_equal "omni-moderation-config", payload["model"]
|
|
@@ -404,7 +423,7 @@ class AiLiteTest < Minitest::Test
|
|
|
404
423
|
end
|
|
405
424
|
|
|
406
425
|
def test_moderate_accepts_text_and_image_url_keywords
|
|
407
|
-
client =
|
|
426
|
+
client = provider.new(api_key: "token-abc")
|
|
408
427
|
|
|
409
428
|
with_stubbed_http(moderation_response) do |captured, _response|
|
|
410
429
|
client.moderate(
|
|
@@ -427,7 +446,7 @@ class AiLiteTest < Minitest::Test
|
|
|
427
446
|
end
|
|
428
447
|
|
|
429
448
|
def test_moderate_accepts_raw_openai_input_shape
|
|
430
|
-
client =
|
|
449
|
+
client = provider.new(api_key: "token-abc")
|
|
431
450
|
input = [
|
|
432
451
|
{ type: "text", text: "Caption" },
|
|
433
452
|
{
|
|
@@ -456,7 +475,7 @@ class AiLiteTest < Minitest::Test
|
|
|
456
475
|
end
|
|
457
476
|
|
|
458
477
|
def test_moderate_reads_image_path_as_data_url
|
|
459
|
-
client =
|
|
478
|
+
client = provider.new(api_key: "token-abc")
|
|
460
479
|
|
|
461
480
|
Tempfile.create(["moderation", ".png"]) do |file|
|
|
462
481
|
file.binmode
|
|
@@ -481,7 +500,7 @@ class AiLiteTest < Minitest::Test
|
|
|
481
500
|
end
|
|
482
501
|
|
|
483
502
|
def test_moderate_returns_all_results_for_multiple_inputs
|
|
484
|
-
client =
|
|
503
|
+
client = provider.new(api_key: "token-abc")
|
|
485
504
|
safe_result = moderation_result(flagged: false)
|
|
486
505
|
flagged_result = moderation_result(flagged: true)
|
|
487
506
|
|
|
@@ -495,7 +514,7 @@ class AiLiteTest < Minitest::Test
|
|
|
495
514
|
end
|
|
496
515
|
|
|
497
516
|
def test_moderate_local_input_errors_return_standard_envelope
|
|
498
|
-
client =
|
|
517
|
+
client = provider.new(api_key: "token-abc")
|
|
499
518
|
|
|
500
519
|
result = client.moderate
|
|
501
520
|
|
|
@@ -507,7 +526,7 @@ class AiLiteTest < Minitest::Test
|
|
|
507
526
|
end
|
|
508
527
|
|
|
509
528
|
def test_embed_sends_post_to_embeddings_with_default_payload
|
|
510
|
-
client =
|
|
529
|
+
client = provider.new(api_key: "token-abc")
|
|
511
530
|
|
|
512
531
|
with_stubbed_http(embedding_response([[0.12, -0.34, 0.56]])) do |captured, _response|
|
|
513
532
|
result = client.embed("Text to vectorize")
|
|
@@ -532,7 +551,7 @@ class AiLiteTest < Minitest::Test
|
|
|
532
551
|
end
|
|
533
552
|
|
|
534
553
|
def test_embed_returns_vector_list_for_multiple_inputs
|
|
535
|
-
client =
|
|
554
|
+
client = provider.new(api_key: "token-abc")
|
|
536
555
|
embeddings = [
|
|
537
556
|
[0.11, 0.22, 0.33],
|
|
538
557
|
[0.44, 0.55, 0.66]
|
|
@@ -550,7 +569,7 @@ class AiLiteTest < Minitest::Test
|
|
|
550
569
|
end
|
|
551
570
|
|
|
552
571
|
def test_embed_includes_options_dimensions_encoding_format_and_model
|
|
553
|
-
client =
|
|
572
|
+
client = provider.new(api_key: "token-abc")
|
|
554
573
|
|
|
555
574
|
with_stubbed_http(embedding_response([[0.12, -0.34]])) do |captured, _response|
|
|
556
575
|
client.embed(
|
|
@@ -573,13 +592,13 @@ class AiLiteTest < Minitest::Test
|
|
|
573
592
|
end
|
|
574
593
|
|
|
575
594
|
def test_embed_uses_class_level_configured_client
|
|
576
|
-
|
|
595
|
+
provider.configure do |config|
|
|
577
596
|
config.api_key = "configured-key"
|
|
578
597
|
config.embedding_model = "text-embedding-config"
|
|
579
598
|
end
|
|
580
599
|
|
|
581
600
|
with_stubbed_http(embedding_response([[0.12, -0.34, 0.56]])) do |captured, _response|
|
|
582
|
-
|
|
601
|
+
provider.embed("Use configured defaults")
|
|
583
602
|
payload = JSON.parse(captured[:http].last_request.body)
|
|
584
603
|
|
|
585
604
|
assert_equal "text-embedding-config", payload["model"]
|
|
@@ -588,7 +607,7 @@ class AiLiteTest < Minitest::Test
|
|
|
588
607
|
end
|
|
589
608
|
|
|
590
609
|
def test_embed_debug_true_returns_raw_usage
|
|
591
|
-
client =
|
|
610
|
+
client = provider.new(api_key: "token-abc")
|
|
592
611
|
|
|
593
612
|
with_stubbed_http(embedding_response([[0.12, -0.34, 0.56]])) do |_captured, _response|
|
|
594
613
|
result = client.embed("Text to vectorize", debug: true)
|
|
@@ -599,7 +618,7 @@ class AiLiteTest < Minitest::Test
|
|
|
599
618
|
end
|
|
600
619
|
|
|
601
620
|
def test_image_sends_post_to_images_with_default_payload
|
|
602
|
-
client =
|
|
621
|
+
client = provider.new(api_key: "token-abc")
|
|
603
622
|
image_data = Base64.strict_encode64("fake image")
|
|
604
623
|
|
|
605
624
|
with_stubbed_http(image_response(b64_json: image_data)) do |captured, _response|
|
|
@@ -625,7 +644,7 @@ class AiLiteTest < Minitest::Test
|
|
|
625
644
|
end
|
|
626
645
|
|
|
627
646
|
def test_image_includes_options_size_quality_background_output_format_and_model
|
|
628
|
-
client =
|
|
647
|
+
client = provider.new(api_key: "token-abc")
|
|
629
648
|
|
|
630
649
|
with_stubbed_http(image_response) do |captured, _response|
|
|
631
650
|
client.image(
|
|
@@ -654,13 +673,13 @@ class AiLiteTest < Minitest::Test
|
|
|
654
673
|
end
|
|
655
674
|
|
|
656
675
|
def test_image_uses_class_level_configured_client
|
|
657
|
-
|
|
676
|
+
provider.configure do |config|
|
|
658
677
|
config.api_key = "configured-key"
|
|
659
678
|
config.image_model = "gpt-image-config"
|
|
660
679
|
end
|
|
661
680
|
|
|
662
681
|
with_stubbed_http(image_response) do |captured, _response|
|
|
663
|
-
|
|
682
|
+
provider.image("Use configured defaults")
|
|
664
683
|
payload = JSON.parse(captured[:http].last_request.body)
|
|
665
684
|
|
|
666
685
|
assert_equal "gpt-image-config", payload["model"]
|
|
@@ -669,7 +688,7 @@ class AiLiteTest < Minitest::Test
|
|
|
669
688
|
end
|
|
670
689
|
|
|
671
690
|
def test_image_writes_decoded_content_to_output_path
|
|
672
|
-
client =
|
|
691
|
+
client = provider.new(api_key: "token-abc")
|
|
673
692
|
image_data = Base64.strict_encode64("fake image")
|
|
674
693
|
|
|
675
694
|
Tempfile.create(["generated", ".png"]) do |file|
|
|
@@ -683,7 +702,7 @@ class AiLiteTest < Minitest::Test
|
|
|
683
702
|
end
|
|
684
703
|
|
|
685
704
|
def test_image_debug_true_returns_raw_usage
|
|
686
|
-
client =
|
|
705
|
+
client = provider.new(api_key: "token-abc")
|
|
687
706
|
image_data = Base64.strict_encode64("fake image")
|
|
688
707
|
|
|
689
708
|
with_stubbed_http(image_response(b64_json: image_data)) do |_captured, _response|
|
|
@@ -695,7 +714,7 @@ class AiLiteTest < Minitest::Test
|
|
|
695
714
|
end
|
|
696
715
|
|
|
697
716
|
def test_image_output_path_without_image_data_returns_standard_envelope
|
|
698
|
-
client =
|
|
717
|
+
client = provider.new(api_key: "token-abc")
|
|
699
718
|
|
|
700
719
|
Tempfile.create(["generated", ".png"]) do |file|
|
|
701
720
|
with_stubbed_http(image_response(b64_json: nil)) do |_captured, _response|
|
|
@@ -711,7 +730,7 @@ class AiLiteTest < Minitest::Test
|
|
|
711
730
|
end
|
|
712
731
|
|
|
713
732
|
def test_speak_sends_post_to_audio_speech_with_default_payload
|
|
714
|
-
client =
|
|
733
|
+
client = provider.new(api_key: "token-abc")
|
|
715
734
|
|
|
716
735
|
with_stubbed_http(speech_response("fake audio")) do |captured, _response|
|
|
717
736
|
result = client.speak("Read this aloud")
|
|
@@ -737,7 +756,7 @@ class AiLiteTest < Minitest::Test
|
|
|
737
756
|
end
|
|
738
757
|
|
|
739
758
|
def test_speak_includes_options_response_format_speed_instructions_model_and_voice
|
|
740
|
-
client =
|
|
759
|
+
client = provider.new(api_key: "token-abc")
|
|
741
760
|
|
|
742
761
|
with_stubbed_http(speech_response) do |captured, _response|
|
|
743
762
|
client.speak(
|
|
@@ -764,14 +783,14 @@ class AiLiteTest < Minitest::Test
|
|
|
764
783
|
end
|
|
765
784
|
|
|
766
785
|
def test_speak_uses_class_level_configured_client
|
|
767
|
-
|
|
786
|
+
provider.configure do |config|
|
|
768
787
|
config.api_key = "configured-key"
|
|
769
788
|
config.speech_model = "gpt-speech-config"
|
|
770
789
|
config.speech_voice = "marin"
|
|
771
790
|
end
|
|
772
791
|
|
|
773
792
|
with_stubbed_http(speech_response) do |captured, _response|
|
|
774
|
-
|
|
793
|
+
provider.speak("Use configured defaults")
|
|
775
794
|
payload = JSON.parse(captured[:http].last_request.body)
|
|
776
795
|
|
|
777
796
|
assert_equal "gpt-speech-config", payload["model"]
|
|
@@ -781,7 +800,7 @@ class AiLiteTest < Minitest::Test
|
|
|
781
800
|
end
|
|
782
801
|
|
|
783
802
|
def test_speak_writes_audio_to_output_path_and_returns_metadata
|
|
784
|
-
client =
|
|
803
|
+
client = provider.new(api_key: "token-abc")
|
|
785
804
|
|
|
786
805
|
Tempfile.create(["speech", ".mp3"]) do |file|
|
|
787
806
|
with_stubbed_http(speech_response("fake audio")) do |_captured, _response|
|
|
@@ -801,7 +820,7 @@ class AiLiteTest < Minitest::Test
|
|
|
801
820
|
end
|
|
802
821
|
|
|
803
822
|
def test_speak_base64_true_returns_encoded_audio
|
|
804
|
-
client =
|
|
823
|
+
client = provider.new(api_key: "token-abc")
|
|
805
824
|
|
|
806
825
|
with_stubbed_http(speech_response("fake audio")) do |_captured, _response|
|
|
807
826
|
result = client.speak("Encode this", base64: true)
|
|
@@ -812,7 +831,7 @@ class AiLiteTest < Minitest::Test
|
|
|
812
831
|
end
|
|
813
832
|
|
|
814
833
|
def test_speak_debug_true_returns_raw_audio
|
|
815
|
-
client =
|
|
834
|
+
client = provider.new(api_key: "token-abc")
|
|
816
835
|
|
|
817
836
|
with_stubbed_http(speech_response("fake audio")) do |_captured, _response|
|
|
818
837
|
result = client.speak("Debug this", debug: true)
|
|
@@ -823,7 +842,7 @@ class AiLiteTest < Minitest::Test
|
|
|
823
842
|
end
|
|
824
843
|
|
|
825
844
|
def test_speak_http_errors_return_standard_envelope
|
|
826
|
-
client =
|
|
845
|
+
client = provider.new(api_key: "token-abc")
|
|
827
846
|
body = JSON.generate("error" => { "message" => "Unsupported voice" })
|
|
828
847
|
|
|
829
848
|
with_stubbed_http(FakeResponse.new("400", body)) do |_captured, _response|
|
|
@@ -838,7 +857,7 @@ class AiLiteTest < Minitest::Test
|
|
|
838
857
|
end
|
|
839
858
|
|
|
840
859
|
def test_transcribe_sends_post_to_audio_transcriptions_with_default_multipart_payload
|
|
841
|
-
client =
|
|
860
|
+
client = provider.new(api_key: "token-abc")
|
|
842
861
|
|
|
843
862
|
Tempfile.create(["meeting", ".mp3"]) do |file|
|
|
844
863
|
file.binmode
|
|
@@ -868,7 +887,7 @@ class AiLiteTest < Minitest::Test
|
|
|
868
887
|
end
|
|
869
888
|
|
|
870
889
|
def test_transcribe_includes_options_and_optional_fields
|
|
871
|
-
client =
|
|
890
|
+
client = provider.new(api_key: "token-abc")
|
|
872
891
|
|
|
873
892
|
Tempfile.create(["meeting", ".wav"]) do |file|
|
|
874
893
|
file.binmode
|
|
@@ -908,7 +927,7 @@ class AiLiteTest < Minitest::Test
|
|
|
908
927
|
end
|
|
909
928
|
|
|
910
929
|
def test_transcribe_uses_class_level_configured_client
|
|
911
|
-
|
|
930
|
+
provider.configure do |config|
|
|
912
931
|
config.api_key = "configured-key"
|
|
913
932
|
config.transcription_model = "gpt-transcription-config"
|
|
914
933
|
end
|
|
@@ -919,7 +938,7 @@ class AiLiteTest < Minitest::Test
|
|
|
919
938
|
file.flush
|
|
920
939
|
|
|
921
940
|
with_stubbed_http(transcription_response) do |captured, _response|
|
|
922
|
-
|
|
941
|
+
provider.transcribe(file.path)
|
|
923
942
|
request = captured[:http].last_request
|
|
924
943
|
|
|
925
944
|
assert_multipart_field request.body, "model", "gpt-transcription-config"
|
|
@@ -929,7 +948,7 @@ class AiLiteTest < Minitest::Test
|
|
|
929
948
|
end
|
|
930
949
|
|
|
931
950
|
def test_transcribe_returns_plain_text_response_formats
|
|
932
|
-
client =
|
|
951
|
+
client = provider.new(api_key: "token-abc")
|
|
933
952
|
|
|
934
953
|
Tempfile.create(["meeting", ".webm"]) do |file|
|
|
935
954
|
file.binmode
|
|
@@ -948,7 +967,7 @@ class AiLiteTest < Minitest::Test
|
|
|
948
967
|
end
|
|
949
968
|
|
|
950
969
|
def test_transcribe_debug_true_returns_raw_response
|
|
951
|
-
client =
|
|
970
|
+
client = provider.new(api_key: "token-abc")
|
|
952
971
|
|
|
953
972
|
Tempfile.create(["meeting", ".ogg"]) do |file|
|
|
954
973
|
file.binmode
|
|
@@ -965,7 +984,7 @@ class AiLiteTest < Minitest::Test
|
|
|
965
984
|
end
|
|
966
985
|
|
|
967
986
|
def test_transcribe_http_errors_return_standard_envelope
|
|
968
|
-
client =
|
|
987
|
+
client = provider.new(api_key: "token-abc")
|
|
969
988
|
body = JSON.generate("error" => { "message" => "Unsupported audio format" })
|
|
970
989
|
|
|
971
990
|
Tempfile.create(["meeting", ".mp3"]) do |file|
|
|
@@ -986,7 +1005,7 @@ class AiLiteTest < Minitest::Test
|
|
|
986
1005
|
end
|
|
987
1006
|
|
|
988
1007
|
def test_transcribe_local_file_errors_return_standard_envelope
|
|
989
|
-
client =
|
|
1008
|
+
client = provider.new(api_key: "token-abc")
|
|
990
1009
|
|
|
991
1010
|
missing_result = client.transcribe("tmp/missing-audio.mp3")
|
|
992
1011
|
assert_nil missing_result["content"]
|
|
@@ -1003,7 +1022,7 @@ class AiLiteTest < Minitest::Test
|
|
|
1003
1022
|
end
|
|
1004
1023
|
|
|
1005
1024
|
def test_http_errors_return_standard_envelope
|
|
1006
|
-
client =
|
|
1025
|
+
client = provider.new(api_key: "token-abc")
|
|
1007
1026
|
body = JSON.generate("error" => { "message" => "Invalid API key" })
|
|
1008
1027
|
|
|
1009
1028
|
with_stubbed_http(FakeResponse.new("401", body)) do |_captured, _response|
|
|
@@ -1018,7 +1037,7 @@ class AiLiteTest < Minitest::Test
|
|
|
1018
1037
|
end
|
|
1019
1038
|
|
|
1020
1039
|
def test_debug_true_returns_raw_http_error_response
|
|
1021
|
-
client =
|
|
1040
|
+
client = provider.new(api_key: "token-abc")
|
|
1022
1041
|
body = JSON.generate("error" => { "message" => "Invalid API key" })
|
|
1023
1042
|
|
|
1024
1043
|
with_stubbed_http(FakeResponse.new("401", body)) do |_captured, _response|
|
|
@@ -1032,7 +1051,7 @@ class AiLiteTest < Minitest::Test
|
|
|
1032
1051
|
end
|
|
1033
1052
|
|
|
1034
1053
|
def test_response_parse_errors_return_standard_envelope
|
|
1035
|
-
client =
|
|
1054
|
+
client = provider.new(api_key: "token-abc")
|
|
1036
1055
|
|
|
1037
1056
|
with_stubbed_http(FakeResponse.new("200", "not-json")) do |_captured, _response|
|
|
1038
1057
|
result = client.chat("Say hello")
|
|
@@ -1046,7 +1065,7 @@ class AiLiteTest < Minitest::Test
|
|
|
1046
1065
|
end
|
|
1047
1066
|
|
|
1048
1067
|
def test_debug_true_returns_raw_response_parse_error_body
|
|
1049
|
-
client =
|
|
1068
|
+
client = provider.new(api_key: "token-abc")
|
|
1050
1069
|
|
|
1051
1070
|
with_stubbed_http(FakeResponse.new("200", "not-json")) do |_captured, _response|
|
|
1052
1071
|
result = client.chat("Say hello", debug: true)
|
|
@@ -1059,7 +1078,7 @@ class AiLiteTest < Minitest::Test
|
|
|
1059
1078
|
end
|
|
1060
1079
|
|
|
1061
1080
|
def test_network_errors_return_standard_envelope
|
|
1062
|
-
client =
|
|
1081
|
+
client = provider.new(api_key: "token-abc")
|
|
1063
1082
|
original_start = Net::HTTP.method(:start)
|
|
1064
1083
|
|
|
1065
1084
|
Net::HTTP.singleton_class.send(:define_method, :start) do |_host, _port, use_ssl:, &_block|
|
|
@@ -1208,3 +1227,108 @@ class AiLiteTest < Minitest::Test
|
|
|
1208
1227
|
Net::HTTP.singleton_class.send(:define_method, :start, original_start)
|
|
1209
1228
|
end
|
|
1210
1229
|
end
|
|
1230
|
+
|
|
1231
|
+
# Run the same HTTP and result contract against every legacy entry point.
|
|
1232
|
+
class AiLiteLegacyTest < AiLiteTest
|
|
1233
|
+
def provider
|
|
1234
|
+
AiLite
|
|
1235
|
+
end
|
|
1236
|
+
end
|
|
1237
|
+
|
|
1238
|
+
class AiLiteNamespaceTest < Minitest::Test
|
|
1239
|
+
def setup
|
|
1240
|
+
AiLite::OpenAI.reset_configuration!
|
|
1241
|
+
end
|
|
1242
|
+
|
|
1243
|
+
def test_provider_calls_are_quiet_and_constants_remain_compatible
|
|
1244
|
+
_stdout, stderr = capture_io do
|
|
1245
|
+
AiLite::OpenAI.configure { |config| config.api_key = "test-key" }
|
|
1246
|
+
assert_instance_of AiLite::OpenAI, AiLite::OpenAI.client
|
|
1247
|
+
AiLite::OpenAI.new
|
|
1248
|
+
end
|
|
1249
|
+
|
|
1250
|
+
assert_empty stderr
|
|
1251
|
+
assert_same AiLite::OpenAI::Configuration, AiLite::Configuration
|
|
1252
|
+
assert_equal AiLite::OpenAI::API_BASE_URL, AiLite::API_BASE_URL
|
|
1253
|
+
refute AiLite::OpenAI < AiLite
|
|
1254
|
+
end
|
|
1255
|
+
|
|
1256
|
+
def test_legacy_configuration_and_client_are_shared_with_openai
|
|
1257
|
+
capture_io do
|
|
1258
|
+
AiLite.configure { |config| config.api_key = "legacy-key" }
|
|
1259
|
+
assert_same AiLite.configuration, AiLite::OpenAI.configuration
|
|
1260
|
+
assert_same AiLite.client, AiLite::OpenAI.client
|
|
1261
|
+
assert_equal "legacy-key", AiLite::OpenAI.new.api_key
|
|
1262
|
+
assert_instance_of AiLite::OpenAI, AiLite.new
|
|
1263
|
+
|
|
1264
|
+
first_client = AiLite.client
|
|
1265
|
+
AiLite::OpenAI.configure { |config| config.api_key = "provider-key" }
|
|
1266
|
+
refute_same first_client, AiLite.client
|
|
1267
|
+
assert_equal "provider-key", AiLite.new.api_key
|
|
1268
|
+
|
|
1269
|
+
second_client = AiLite::OpenAI.client
|
|
1270
|
+
AiLite.reset_client!
|
|
1271
|
+
refute_same second_client, AiLite::OpenAI.client
|
|
1272
|
+
AiLite.reset_configuration!
|
|
1273
|
+
assert_nil AiLite::OpenAI.configuration.api_key
|
|
1274
|
+
end
|
|
1275
|
+
end
|
|
1276
|
+
|
|
1277
|
+
def test_each_legacy_entry_point_warns_only_once_and_preserves_arguments
|
|
1278
|
+
previous = AiLite.instance_variable_get(:@deprecated_entry_points)
|
|
1279
|
+
AiLite.instance_variable_set(:@deprecated_entry_points, {})
|
|
1280
|
+
methods = %i[new configuration configure reset_configuration! client reset_client!
|
|
1281
|
+
chat chat_stream moderate embed image speak transcribe
|
|
1282
|
+
available_models model_available? configured_models check_configured_models]
|
|
1283
|
+
|
|
1284
|
+
stdout, stderr = capture_io do
|
|
1285
|
+
methods.each do |name|
|
|
1286
|
+
test_case = self
|
|
1287
|
+
handler = lambda do |*args, **kwargs, &block|
|
|
1288
|
+
test_case.assert_equal ["input"], args
|
|
1289
|
+
test_case.assert_equal({ debug: true }, kwargs)
|
|
1290
|
+
test_case.assert_equal :block_result, block.call
|
|
1291
|
+
:result
|
|
1292
|
+
end
|
|
1293
|
+
next if name == :new
|
|
1294
|
+
|
|
1295
|
+
original = AiLite::OpenAI.method(name)
|
|
1296
|
+
begin
|
|
1297
|
+
AiLite::OpenAI.define_singleton_method(name, &handler)
|
|
1298
|
+
2.times do
|
|
1299
|
+
assert_equal :result, AiLite.public_send(name, "input", debug: true) { :block_result }
|
|
1300
|
+
end
|
|
1301
|
+
ensure
|
|
1302
|
+
AiLite::OpenAI.define_singleton_method(name, original)
|
|
1303
|
+
end
|
|
1304
|
+
end
|
|
1305
|
+
2.times { AiLite.new(api_key: "test-key") }
|
|
1306
|
+
end
|
|
1307
|
+
|
|
1308
|
+
assert_empty stdout
|
|
1309
|
+
assert_equal methods.length, stderr.lines.length
|
|
1310
|
+
methods.each do |name|
|
|
1311
|
+
assert_includes stderr, "AiLite.#{name} is deprecated. Use AiLite::OpenAI.#{name} instead."
|
|
1312
|
+
end
|
|
1313
|
+
assert_includes stderr, "removed in v2.0"
|
|
1314
|
+
ensure
|
|
1315
|
+
AiLite.instance_variable_set(:@deprecated_entry_points, previous)
|
|
1316
|
+
end
|
|
1317
|
+
|
|
1318
|
+
def test_provider_can_be_required_directly_in_a_fresh_process
|
|
1319
|
+
require "open3"
|
|
1320
|
+
require "rbconfig"
|
|
1321
|
+
script = <<~'CODE'
|
|
1322
|
+
require "ai_lite/openai"
|
|
1323
|
+
abort unless AiLite::OpenAI.new(api_key: "test-key").model == AiLite::OpenAI::DEFAULT_MODEL
|
|
1324
|
+
require "ai_lite"
|
|
1325
|
+
abort unless AiLite::Configuration.equal?(AiLite::OpenAI::Configuration)
|
|
1326
|
+
CODE
|
|
1327
|
+
stdout, stderr, status = Open3.capture3(
|
|
1328
|
+
RbConfig.ruby, "-I#{File.expand_path('../lib', __dir__)}", "-e", script
|
|
1329
|
+
)
|
|
1330
|
+
assert status.success?, stderr
|
|
1331
|
+
assert_empty stdout
|
|
1332
|
+
assert_empty stderr
|
|
1333
|
+
end
|
|
1334
|
+
end
|