ai-lite 0.2.0 → 0.3.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/README.md +98 -19
- data/lib/ai_lite/version.rb +1 -1
- data/lib/ai_lite.rb +62 -3
- data/test/ai_lite_test.rb +117 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cd1b2ead0a8ae48d9617b9e73460649f817fc158430e11cc7b9ac798a86846d2
|
|
4
|
+
data.tar.gz: f3dad03f56f3907e537dc8c72d63bf7e57e069d4b28ef666151d348b98f6b3e6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6e70409efd1d697b2578ace5b878e74ef4e6214efbcbae2c295aae066e0e9ce4887cd39c95fcdde8bc73e0f6b0cfe60434dc7f1ca2611c77e649cc645b94351a
|
|
7
|
+
data.tar.gz: 1faba64c32c14038f18c1d50a2f73bd92a0c940d8b303b3eecb89c2193d944d3b189334c5ecde81f55e8268db01e01e562686b74f20111105f0c0fbbeea387c6
|
data/README.md
CHANGED
|
@@ -18,6 +18,7 @@ It is not meant to replace the official OpenAI SDK. It is a small wrapper for pr
|
|
|
18
18
|
```ruby
|
|
19
19
|
ai.chat("Say hello")
|
|
20
20
|
ai.moderate("User submitted text")
|
|
21
|
+
ai.embed("Text to vectorize")
|
|
21
22
|
```
|
|
22
23
|
|
|
23
24
|
## Usage
|
|
@@ -49,6 +50,7 @@ AiLite.configure do |config|
|
|
|
49
50
|
config.api_key = ENV["OPENAI_API_KEY"]
|
|
50
51
|
config.model = "gpt-5.5"
|
|
51
52
|
config.moderation_model = "omni-moderation-latest"
|
|
53
|
+
config.embedding_model = "text-embedding-3-small"
|
|
52
54
|
config.timeout = 120
|
|
53
55
|
config.max_output_tokens = 2000
|
|
54
56
|
end
|
|
@@ -95,16 +97,43 @@ The OpenAI API URL is fixed to `https://api.openai.com/v1/responses`.
|
|
|
95
97
|
|
|
96
98
|
## Moderation
|
|
97
99
|
|
|
98
|
-
Use `moderate` to classify text or images for potentially harmful content
|
|
100
|
+
Use `moderate` to classify user-submitted text or images for potentially harmful content before saving, publishing, or sending it into another AI call.
|
|
101
|
+
|
|
102
|
+
A common use case is checking a comment before it is shown publicly:
|
|
99
103
|
|
|
100
104
|
```ruby
|
|
101
|
-
|
|
105
|
+
comment = "User submitted comment"
|
|
106
|
+
result = ai.moderate(comment)
|
|
102
107
|
|
|
103
108
|
if result["content"]["flagged"]
|
|
104
|
-
# hide, block, or route to review
|
|
109
|
+
# hide, block, or route the comment to review
|
|
110
|
+
else
|
|
111
|
+
# publish the comment
|
|
105
112
|
end
|
|
106
113
|
```
|
|
107
114
|
|
|
115
|
+
Moderation responses include an overall `flagged` value plus category booleans and scores:
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
{
|
|
119
|
+
"content" => {
|
|
120
|
+
"flagged" => false,
|
|
121
|
+
"categories" => {
|
|
122
|
+
"hate" => false,
|
|
123
|
+
"violence" => false
|
|
124
|
+
},
|
|
125
|
+
"category_scores" => {
|
|
126
|
+
"hate" => 0.01,
|
|
127
|
+
"violence" => 0.02
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
"response_id" => "modr_...",
|
|
131
|
+
"status" => 200,
|
|
132
|
+
"error" => nil,
|
|
133
|
+
"raw" => nil
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
108
137
|
`moderate` sends a `POST` request to `/v1/moderations` with:
|
|
109
138
|
|
|
110
139
|
- `model`
|
|
@@ -144,6 +173,72 @@ result = ai.moderate([
|
|
|
144
173
|
])
|
|
145
174
|
```
|
|
146
175
|
|
|
176
|
+
## Embeddings
|
|
177
|
+
|
|
178
|
+
Use `embed` to turn text into vectors that can be stored and compared for semantic search, recommendations, duplicate detection, or retrieval-augmented generation.
|
|
179
|
+
|
|
180
|
+
```ruby
|
|
181
|
+
result = ai.embed("How do I reset my password?")
|
|
182
|
+
vector = result["content"]
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
For one string input, `content` is the embedding vector:
|
|
186
|
+
|
|
187
|
+
```ruby
|
|
188
|
+
{
|
|
189
|
+
"content" => [0.012, -0.44, 0.203],
|
|
190
|
+
"response_id" => nil,
|
|
191
|
+
"status" => 200,
|
|
192
|
+
"error" => nil,
|
|
193
|
+
"raw" => nil
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
For multiple inputs, `content` is an array of vectors in the same order:
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
result = ai.embed([
|
|
201
|
+
"How do I reset my password?",
|
|
202
|
+
"How do I update my billing card?"
|
|
203
|
+
])
|
|
204
|
+
|
|
205
|
+
vectors = result["content"]
|
|
206
|
+
first_vector = vectors[0]
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
{
|
|
211
|
+
"content" => [
|
|
212
|
+
[0.012, -0.44, 0.203],
|
|
213
|
+
[0.332, 0.021, -0.118]
|
|
214
|
+
],
|
|
215
|
+
"response_id" => nil,
|
|
216
|
+
"status" => 200,
|
|
217
|
+
"error" => nil,
|
|
218
|
+
"raw" => nil
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
`embed` sends a `POST` request to `/v1/embeddings` with:
|
|
223
|
+
|
|
224
|
+
- `model`
|
|
225
|
+
- `input`
|
|
226
|
+
- optional `dimensions`
|
|
227
|
+
- optional `encoding_format`
|
|
228
|
+
- optional `debug`
|
|
229
|
+
- optional extra `options`
|
|
230
|
+
|
|
231
|
+
The default embedding model is `text-embedding-3-small`.
|
|
232
|
+
|
|
233
|
+
Pass `debug: true` to include the raw OpenAI response, including token usage:
|
|
234
|
+
|
|
235
|
+
```ruby
|
|
236
|
+
result = ai.embed("Hello", debug: true)
|
|
237
|
+
|
|
238
|
+
result["content"] # embedding vector
|
|
239
|
+
result["raw"]["usage"] # token usage
|
|
240
|
+
```
|
|
241
|
+
|
|
147
242
|
## Multi-Turn Chat
|
|
148
243
|
|
|
149
244
|
Responses include a `response_id` that can be passed back through `options` as `previous_response_id`:
|
|
@@ -189,22 +284,6 @@ JSON-looking model output:
|
|
|
189
284
|
}
|
|
190
285
|
```
|
|
191
286
|
|
|
192
|
-
Moderation output:
|
|
193
|
-
|
|
194
|
-
```ruby
|
|
195
|
-
{
|
|
196
|
-
"content" => {
|
|
197
|
-
"flagged" => false,
|
|
198
|
-
"categories" => { ... },
|
|
199
|
-
"category_scores" => { ... }
|
|
200
|
-
},
|
|
201
|
-
"response_id" => "modr_...",
|
|
202
|
-
"status" => 200,
|
|
203
|
-
"error" => nil,
|
|
204
|
-
"raw" => nil
|
|
205
|
-
}
|
|
206
|
-
```
|
|
207
|
-
|
|
208
287
|
Failure:
|
|
209
288
|
|
|
210
289
|
```ruby
|
data/lib/ai_lite/version.rb
CHANGED
data/lib/ai_lite.rb
CHANGED
|
@@ -8,6 +8,7 @@ class AiLite
|
|
|
8
8
|
API_BASE_URL = "https://api.openai.com/v1".freeze
|
|
9
9
|
DEFAULT_MODEL = "gpt-5.5".freeze
|
|
10
10
|
DEFAULT_MODERATION_MODEL = "omni-moderation-latest".freeze
|
|
11
|
+
DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small".freeze
|
|
11
12
|
DEFAULT_TIMEOUT = 120
|
|
12
13
|
DEFAULT_MAX_OUTPUT_TOKENS = 2000
|
|
13
14
|
IMAGE_MIME_TYPES = {
|
|
@@ -19,12 +20,13 @@ class AiLite
|
|
|
19
20
|
}.freeze
|
|
20
21
|
|
|
21
22
|
class Configuration
|
|
22
|
-
attr_accessor :api_key, :model, :moderation_model, :timeout, :max_output_tokens
|
|
23
|
+
attr_accessor :api_key, :model, :moderation_model, :embedding_model, :timeout, :max_output_tokens
|
|
23
24
|
|
|
24
25
|
def initialize
|
|
25
26
|
@api_key = nil
|
|
26
27
|
@model = DEFAULT_MODEL
|
|
27
28
|
@moderation_model = DEFAULT_MODERATION_MODEL
|
|
29
|
+
@embedding_model = DEFAULT_EMBEDDING_MODEL
|
|
28
30
|
@timeout = DEFAULT_TIMEOUT
|
|
29
31
|
@max_output_tokens = DEFAULT_MAX_OUTPUT_TOKENS
|
|
30
32
|
end
|
|
@@ -59,19 +61,24 @@ class AiLite
|
|
|
59
61
|
client.moderate(input, **kwargs)
|
|
60
62
|
end
|
|
61
63
|
|
|
64
|
+
def embed(input, **kwargs)
|
|
65
|
+
client.embed(input, **kwargs)
|
|
66
|
+
end
|
|
67
|
+
|
|
62
68
|
def reset_client!
|
|
63
69
|
@client = nil
|
|
64
70
|
end
|
|
65
71
|
end
|
|
66
72
|
|
|
67
|
-
attr_reader :api_key, :model, :moderation_model, :timeout, :max_output_tokens, :headers
|
|
73
|
+
attr_reader :api_key, :model, :moderation_model, :embedding_model, :timeout, :max_output_tokens, :headers
|
|
68
74
|
|
|
69
|
-
def initialize(api_key: nil, model: nil, moderation_model: nil, timeout: nil, max_output_tokens: nil)
|
|
75
|
+
def initialize(api_key: nil, model: nil, moderation_model: nil, embedding_model: nil, timeout: nil, max_output_tokens: nil)
|
|
70
76
|
@api_key = api_key || self.class.configuration.api_key || ENV["OPENAI_API_KEY"] || ENV["OPEN_AI_TOKEN"]
|
|
71
77
|
raise ArgumentError, "Missing OpenAI API key" if @api_key.to_s.strip.empty?
|
|
72
78
|
|
|
73
79
|
@model = model || self.class.configuration.model
|
|
74
80
|
@moderation_model = moderation_model || self.class.configuration.moderation_model
|
|
81
|
+
@embedding_model = embedding_model || self.class.configuration.embedding_model
|
|
75
82
|
@timeout = timeout || self.class.configuration.timeout
|
|
76
83
|
@max_output_tokens = max_output_tokens || self.class.configuration.max_output_tokens
|
|
77
84
|
@headers = {
|
|
@@ -104,6 +111,19 @@ class AiLite
|
|
|
104
111
|
prettify_data(status: "unknown", error: e.message, raw: nil, debug: debug)
|
|
105
112
|
end
|
|
106
113
|
|
|
114
|
+
def embed(input, model: nil, dimensions: nil, encoding_format: nil, debug: false, options: {})
|
|
115
|
+
payload = options.merge(
|
|
116
|
+
model: model || embedding_model,
|
|
117
|
+
input: input
|
|
118
|
+
)
|
|
119
|
+
payload[:dimensions] = dimensions if dimensions
|
|
120
|
+
payload[:encoding_format] = encoding_format if encoding_format
|
|
121
|
+
|
|
122
|
+
extract_embedding(post(payload, endpoint: embedding_endpoint), multiple: input.is_a?(Array), debug: debug)
|
|
123
|
+
rescue => e
|
|
124
|
+
prettify_data(status: "unknown", error: e.message, raw: nil, debug: debug)
|
|
125
|
+
end
|
|
126
|
+
|
|
107
127
|
private
|
|
108
128
|
|
|
109
129
|
def post(payload, endpoint: response_endpoint)
|
|
@@ -131,6 +151,10 @@ class AiLite
|
|
|
131
151
|
"#{API_BASE_URL}/moderations"
|
|
132
152
|
end
|
|
133
153
|
|
|
154
|
+
def embedding_endpoint
|
|
155
|
+
"#{API_BASE_URL}/embeddings"
|
|
156
|
+
end
|
|
157
|
+
|
|
134
158
|
def extract_content(response, debug: false)
|
|
135
159
|
status = response.code.to_i
|
|
136
160
|
parsed_response = JSON.parse(response.body)
|
|
@@ -187,6 +211,33 @@ class AiLite
|
|
|
187
211
|
prettify_data(status: response_status(response), error: e.message, raw: nil, debug: debug)
|
|
188
212
|
end
|
|
189
213
|
|
|
214
|
+
def extract_embedding(response, multiple:, debug: false)
|
|
215
|
+
status = response.code.to_i
|
|
216
|
+
parsed_response = JSON.parse(response.body)
|
|
217
|
+
|
|
218
|
+
unless success_status?(status)
|
|
219
|
+
return prettify_data(
|
|
220
|
+
status: status,
|
|
221
|
+
error: error_message(parsed_response),
|
|
222
|
+
response_id: parsed_response["id"],
|
|
223
|
+
raw: parsed_response,
|
|
224
|
+
debug: debug
|
|
225
|
+
)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
prettify_data(
|
|
229
|
+
status: status,
|
|
230
|
+
content: embedding_content(parsed_response, multiple: multiple),
|
|
231
|
+
response_id: parsed_response["id"],
|
|
232
|
+
raw: parsed_response,
|
|
233
|
+
debug: debug
|
|
234
|
+
)
|
|
235
|
+
rescue JSON::ParserError => e
|
|
236
|
+
prettify_data(status: response_status(response), error: e.message, raw: response&.body, debug: debug)
|
|
237
|
+
rescue => e
|
|
238
|
+
prettify_data(status: response_status(response), error: e.message, raw: nil, debug: debug)
|
|
239
|
+
end
|
|
240
|
+
|
|
190
241
|
def extract_output_text(raw)
|
|
191
242
|
Array(raw["output"]).flat_map do |item|
|
|
192
243
|
next [] unless item.is_a?(Hash) && item["type"] == "message"
|
|
@@ -253,6 +304,14 @@ class AiLite
|
|
|
253
304
|
results.length == 1 ? results.first : results
|
|
254
305
|
end
|
|
255
306
|
|
|
307
|
+
def embedding_content(raw, multiple:)
|
|
308
|
+
embeddings = Array(raw["data"]).map do |item|
|
|
309
|
+
item["embedding"] if item.is_a?(Hash)
|
|
310
|
+
end.compact
|
|
311
|
+
|
|
312
|
+
multiple ? embeddings : embeddings.first
|
|
313
|
+
end
|
|
314
|
+
|
|
256
315
|
def success_status?(status)
|
|
257
316
|
status >= 200 && status < 300
|
|
258
317
|
end
|
data/test/ai_lite_test.rb
CHANGED
|
@@ -39,6 +39,7 @@ class AiLiteTest < Minitest::Test
|
|
|
39
39
|
assert_equal "explicit-key", client.api_key
|
|
40
40
|
assert_equal "gpt-test", client.model
|
|
41
41
|
assert_equal "omni-moderation-latest", client.moderation_model
|
|
42
|
+
assert_equal "text-embedding-3-small", client.embedding_model
|
|
42
43
|
assert_equal 10, client.timeout
|
|
43
44
|
assert_equal 2000, client.max_output_tokens
|
|
44
45
|
assert_equal "Bearer explicit-key", client.headers["Authorization"]
|
|
@@ -52,6 +53,7 @@ class AiLiteTest < Minitest::Test
|
|
|
52
53
|
config.api_key = "configured-key"
|
|
53
54
|
config.model = "gpt-config"
|
|
54
55
|
config.moderation_model = "omni-moderation-test"
|
|
56
|
+
config.embedding_model = "text-embedding-test"
|
|
55
57
|
config.timeout = 15
|
|
56
58
|
config.max_output_tokens = 750
|
|
57
59
|
end
|
|
@@ -61,6 +63,7 @@ class AiLiteTest < Minitest::Test
|
|
|
61
63
|
assert_equal "configured-key", client.api_key
|
|
62
64
|
assert_equal "gpt-config", client.model
|
|
63
65
|
assert_equal "omni-moderation-test", client.moderation_model
|
|
66
|
+
assert_equal "text-embedding-test", client.embedding_model
|
|
64
67
|
assert_equal 15, client.timeout
|
|
65
68
|
assert_equal 750, client.max_output_tokens
|
|
66
69
|
assert_same client, AiLite.client
|
|
@@ -83,6 +86,7 @@ class AiLiteTest < Minitest::Test
|
|
|
83
86
|
config.api_key = "configured-key"
|
|
84
87
|
config.model = "gpt-config"
|
|
85
88
|
config.moderation_model = "omni-moderation-config"
|
|
89
|
+
config.embedding_model = "text-embedding-config"
|
|
86
90
|
config.timeout = 15
|
|
87
91
|
config.max_output_tokens = 750
|
|
88
92
|
end
|
|
@@ -91,6 +95,7 @@ class AiLiteTest < Minitest::Test
|
|
|
91
95
|
api_key: "explicit-key",
|
|
92
96
|
model: "gpt-explicit",
|
|
93
97
|
moderation_model: "omni-moderation-explicit",
|
|
98
|
+
embedding_model: "text-embedding-explicit",
|
|
94
99
|
timeout: 5,
|
|
95
100
|
max_output_tokens: 300
|
|
96
101
|
)
|
|
@@ -98,6 +103,7 @@ class AiLiteTest < Minitest::Test
|
|
|
98
103
|
assert_equal "explicit-key", client.api_key
|
|
99
104
|
assert_equal "gpt-explicit", client.model
|
|
100
105
|
assert_equal "omni-moderation-explicit", client.moderation_model
|
|
106
|
+
assert_equal "text-embedding-explicit", client.embedding_model
|
|
101
107
|
assert_equal 5, client.timeout
|
|
102
108
|
assert_equal 300, client.max_output_tokens
|
|
103
109
|
end
|
|
@@ -427,6 +433,98 @@ class AiLiteTest < Minitest::Test
|
|
|
427
433
|
assert_nil result["raw"]
|
|
428
434
|
end
|
|
429
435
|
|
|
436
|
+
def test_embed_sends_post_to_embeddings_with_default_payload
|
|
437
|
+
client = AiLite.new(api_key: "token-abc")
|
|
438
|
+
|
|
439
|
+
with_stubbed_http(embedding_response([[0.12, -0.34, 0.56]])) do |captured, _response|
|
|
440
|
+
result = client.embed("Text to vectorize")
|
|
441
|
+
request = captured[:http].last_request
|
|
442
|
+
payload = JSON.parse(request.body)
|
|
443
|
+
|
|
444
|
+
assert_equal [0.12, -0.34, 0.56], result["content"]
|
|
445
|
+
assert_nil result["response_id"]
|
|
446
|
+
assert_equal 200, result["status"]
|
|
447
|
+
assert_nil result["error"]
|
|
448
|
+
assert_nil result["raw"]
|
|
449
|
+
assert_equal "api.openai.com", captured[:host]
|
|
450
|
+
assert_equal 443, captured[:port]
|
|
451
|
+
assert_equal true, captured[:use_ssl]
|
|
452
|
+
assert_instance_of Net::HTTP::Post, request
|
|
453
|
+
assert_equal "/v1/embeddings", request.path
|
|
454
|
+
assert_equal "Bearer token-abc", request["Authorization"]
|
|
455
|
+
assert_equal "application/json", request["Content-Type"]
|
|
456
|
+
assert_equal "text-embedding-3-small", payload["model"]
|
|
457
|
+
assert_equal "Text to vectorize", payload["input"]
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
def test_embed_returns_vector_list_for_multiple_inputs
|
|
462
|
+
client = AiLite.new(api_key: "token-abc")
|
|
463
|
+
embeddings = [
|
|
464
|
+
[0.11, 0.22, 0.33],
|
|
465
|
+
[0.44, 0.55, 0.66]
|
|
466
|
+
]
|
|
467
|
+
|
|
468
|
+
with_stubbed_http(embedding_response(embeddings)) do |captured, _response|
|
|
469
|
+
result = client.embed(["First text", "Second text"])
|
|
470
|
+
payload = JSON.parse(captured[:http].last_request.body)
|
|
471
|
+
|
|
472
|
+
assert_equal ["First text", "Second text"], payload["input"]
|
|
473
|
+
assert_equal embeddings, result["content"]
|
|
474
|
+
assert_equal 200, result["status"]
|
|
475
|
+
assert_nil result["error"]
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
def test_embed_includes_options_dimensions_encoding_format_and_model
|
|
480
|
+
client = AiLite.new(api_key: "token-abc")
|
|
481
|
+
|
|
482
|
+
with_stubbed_http(embedding_response([[0.12, -0.34]])) do |captured, _response|
|
|
483
|
+
client.embed(
|
|
484
|
+
"Short text",
|
|
485
|
+
model: "text-embedding-3-large",
|
|
486
|
+
dimensions: 2,
|
|
487
|
+
encoding_format: "float",
|
|
488
|
+
options: {
|
|
489
|
+
user: "user-123"
|
|
490
|
+
}
|
|
491
|
+
)
|
|
492
|
+
payload = JSON.parse(captured[:http].last_request.body)
|
|
493
|
+
|
|
494
|
+
assert_equal "text-embedding-3-large", payload["model"]
|
|
495
|
+
assert_equal "Short text", payload["input"]
|
|
496
|
+
assert_equal 2, payload["dimensions"]
|
|
497
|
+
assert_equal "float", payload["encoding_format"]
|
|
498
|
+
assert_equal "user-123", payload["user"]
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
def test_embed_uses_class_level_configured_client
|
|
503
|
+
AiLite.configure do |config|
|
|
504
|
+
config.api_key = "configured-key"
|
|
505
|
+
config.embedding_model = "text-embedding-config"
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
with_stubbed_http(embedding_response([[0.12, -0.34, 0.56]])) do |captured, _response|
|
|
509
|
+
AiLite.embed("Use configured defaults")
|
|
510
|
+
payload = JSON.parse(captured[:http].last_request.body)
|
|
511
|
+
|
|
512
|
+
assert_equal "text-embedding-config", payload["model"]
|
|
513
|
+
assert_equal "Bearer configured-key", captured[:http].last_request["Authorization"]
|
|
514
|
+
end
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
def test_embed_debug_true_returns_raw_usage
|
|
518
|
+
client = AiLite.new(api_key: "token-abc")
|
|
519
|
+
|
|
520
|
+
with_stubbed_http(embedding_response([[0.12, -0.34, 0.56]])) do |_captured, _response|
|
|
521
|
+
result = client.embed("Text to vectorize", debug: true)
|
|
522
|
+
|
|
523
|
+
assert_equal [0.12, -0.34, 0.56], result["content"]
|
|
524
|
+
assert_equal({ "prompt_tokens" => 4, "total_tokens" => 4 }, result["raw"]["usage"])
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
|
|
430
528
|
def test_http_errors_return_standard_envelope
|
|
431
529
|
client = AiLite.new(api_key: "token-abc")
|
|
432
530
|
body = JSON.generate("error" => { "message" => "Invalid API key" })
|
|
@@ -542,6 +640,25 @@ class AiLiteTest < Minitest::Test
|
|
|
542
640
|
}
|
|
543
641
|
end
|
|
544
642
|
|
|
643
|
+
def embedding_response(embeddings)
|
|
644
|
+
body = JSON.generate(
|
|
645
|
+
"object" => "list",
|
|
646
|
+
"data" => embeddings.each_with_index.map do |embedding, index|
|
|
647
|
+
{
|
|
648
|
+
"object" => "embedding",
|
|
649
|
+
"index" => index,
|
|
650
|
+
"embedding" => embedding
|
|
651
|
+
}
|
|
652
|
+
end,
|
|
653
|
+
"model" => "text-embedding-3-small",
|
|
654
|
+
"usage" => {
|
|
655
|
+
"prompt_tokens" => 4,
|
|
656
|
+
"total_tokens" => 4
|
|
657
|
+
}
|
|
658
|
+
)
|
|
659
|
+
FakeResponse.new("200", body)
|
|
660
|
+
end
|
|
661
|
+
|
|
545
662
|
def with_env(values)
|
|
546
663
|
originals = {}
|
|
547
664
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ai-lite
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- William Basmayor
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-07-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: AI Lite is a dependency-light Ruby client for Rails apps and plain Ruby
|
|
14
14
|
projects that need simple OpenAI Responses API calls.
|