vibe-sort 0.2.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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +27 -0
- data/CHANGELOG.md +101 -0
- data/LICENSE.txt +21 -0
- data/README.md +281 -0
- data/Rakefile +12 -0
- data/docs/IMPLEMENTATION_PLAN.md +534 -0
- data/docs/README.md +211 -0
- data/docs/api_reference.md +440 -0
- data/docs/architecture.md +320 -0
- data/docs/development.md +566 -0
- data/docs/v0.2.0_UPDATE.md +377 -0
- data/lib/vibe/sort/version.rb +7 -0
- data/lib/vibe/sort.rb +10 -0
- data/lib/vibe_sort/client.rb +87 -0
- data/lib/vibe_sort/configuration.rb +21 -0
- data/lib/vibe_sort/error.rb +17 -0
- data/lib/vibe_sort/sorter.rb +125 -0
- data/lib/vibe_sort/version.rb +5 -0
- data/lib/vibe_sort.rb +14 -0
- data/sig/vibe/sort.rbs +6 -0
- metadata +83 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
# VibeSort v0.2.0 Update Summary
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
VibeSort has been updated to **version 0.2.0** with support for sorting arrays containing **integers, floats, and strings**. This expands the gem's capabilities while maintaining the same clean API and architecture.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🎯 What Changed
|
|
10
|
+
|
|
11
|
+
### Core Functionality
|
|
12
|
+
|
|
13
|
+
#### Before (v0.1.0)
|
|
14
|
+
- ✅ Sort arrays of numbers (integers and floats)
|
|
15
|
+
- ❌ Strings not supported
|
|
16
|
+
- ❌ Mixed-type arrays rejected
|
|
17
|
+
|
|
18
|
+
#### After (v0.2.0)
|
|
19
|
+
- ✅ Sort arrays of numbers (integers and floats)
|
|
20
|
+
- ✅ Sort arrays of strings (case-sensitive)
|
|
21
|
+
- ✅ Sort mixed-type arrays (numbers + strings)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 📝 Technical Changes
|
|
26
|
+
|
|
27
|
+
### 1. Input Validation (`VibeSort::Client`)
|
|
28
|
+
|
|
29
|
+
**File:** `lib/vibe_sort/client.rb`
|
|
30
|
+
|
|
31
|
+
**Before:**
|
|
32
|
+
```ruby
|
|
33
|
+
def valid_input?(array)
|
|
34
|
+
return false unless array.is_a?(Array)
|
|
35
|
+
return false if array.empty?
|
|
36
|
+
array.all? { |item| item.is_a?(Numeric) }
|
|
37
|
+
end
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**After:**
|
|
41
|
+
```ruby
|
|
42
|
+
def valid_input?(array)
|
|
43
|
+
return false unless array.is_a?(Array)
|
|
44
|
+
return false if array.empty?
|
|
45
|
+
array.all? { |item| item.is_a?(Numeric) || item.is_a?(String) }
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Error Message Updated:**
|
|
50
|
+
- Before: `"Input must be an array of numbers"`
|
|
51
|
+
- After: `"Input must be an array of numbers or strings"`
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
### 2. AI Prompting (`VibeSort::Sorter`)
|
|
56
|
+
|
|
57
|
+
**File:** `lib/vibe_sort/sorter.rb`
|
|
58
|
+
|
|
59
|
+
**Before:**
|
|
60
|
+
```ruby
|
|
61
|
+
{
|
|
62
|
+
role: "system",
|
|
63
|
+
content: "You are an assistant that only sorts number arrays.
|
|
64
|
+
Return a JSON object with a single key 'sorted_array'
|
|
65
|
+
containing the numbers sorted in ascending order."
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**After:**
|
|
70
|
+
```ruby
|
|
71
|
+
{
|
|
72
|
+
role: "system",
|
|
73
|
+
content: "You are an assistant that only sorts arrays.
|
|
74
|
+
The array may contain numbers and strings.
|
|
75
|
+
Sort the array in ascending order.
|
|
76
|
+
Follow standard sorting rules: numbers should come before strings,
|
|
77
|
+
and string sorting should be case-sensitive.
|
|
78
|
+
Return a JSON object with a single key 'sorted_array'
|
|
79
|
+
containing the sorted elements."
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
### 3. Output Validation (`VibeSort::Sorter`)
|
|
86
|
+
|
|
87
|
+
**File:** `lib/vibe_sort/sorter.rb`
|
|
88
|
+
|
|
89
|
+
**Before:**
|
|
90
|
+
```ruby
|
|
91
|
+
def validate_sorted_array!(sorted_array)
|
|
92
|
+
unless sorted_array.is_a?(Array)
|
|
93
|
+
raise ApiError.new("Response does not contain a valid 'sorted_array'")
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
unless sorted_array.all? { |item| item.is_a?(Numeric) }
|
|
97
|
+
raise ApiError.new("Sorted array contains non-numeric values")
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**After:**
|
|
103
|
+
```ruby
|
|
104
|
+
def validate_sorted_array!(sorted_array)
|
|
105
|
+
unless sorted_array.is_a?(Array)
|
|
106
|
+
raise ApiError.new("Response does not contain a valid 'sorted_array'")
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
unless sorted_array.all? { |item| item.is_a?(Numeric) || item.is_a?(String) }
|
|
110
|
+
raise ApiError.new("Sorted array contains invalid values (must be numbers or strings)")
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 🚀 New Usage Examples
|
|
118
|
+
|
|
119
|
+
### Sorting Strings
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
client = VibeSort::Client.new(api_key: ENV['OPENAI_API_KEY'])
|
|
123
|
+
|
|
124
|
+
words = ["banana", "Apple", "cherry", "date"]
|
|
125
|
+
result = client.sort(words)
|
|
126
|
+
|
|
127
|
+
puts result[:sorted_array]
|
|
128
|
+
# => ["Apple", "banana", "cherry", "date"]
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Sorting Mixed Types
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
mixed_items = [42, "hello", 8, "world", 15.5, "Apple"]
|
|
135
|
+
result = client.sort(mixed_items)
|
|
136
|
+
|
|
137
|
+
puts result[:sorted_array]
|
|
138
|
+
# => [8, 15.5, 42, "Apple", "hello", "world"]
|
|
139
|
+
# Note: Numbers come before strings
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## 📋 Sorting Rules
|
|
145
|
+
|
|
146
|
+
The AI follows these standard sorting conventions:
|
|
147
|
+
|
|
148
|
+
1. **Numbers Only**: Ascending numerical order
|
|
149
|
+
- `[5, 2, 8, 1]` → `[1, 2, 5, 8]`
|
|
150
|
+
|
|
151
|
+
2. **Strings Only**: Ascending alphabetical order (case-sensitive)
|
|
152
|
+
- `["banana", "Apple", "cherry"]` → `["Apple", "banana", "cherry"]`
|
|
153
|
+
- Capital letters come before lowercase in ASCII ordering
|
|
154
|
+
|
|
155
|
+
3. **Mixed Types**: Numbers first, then strings
|
|
156
|
+
- `[42, "hello", 8, "world"]` → `[8, 42, "hello", "world"]`
|
|
157
|
+
- Each group is sorted within itself
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 📚 Documentation Updates
|
|
162
|
+
|
|
163
|
+
### Updated Files
|
|
164
|
+
|
|
165
|
+
1. **README.md**
|
|
166
|
+
- Added string sorting examples
|
|
167
|
+
- Added mixed-type sorting examples
|
|
168
|
+
- Updated features list
|
|
169
|
+
- Added sorting behavior section
|
|
170
|
+
- Updated error message examples
|
|
171
|
+
|
|
172
|
+
2. **docs/api_reference.md**
|
|
173
|
+
- Updated method signatures
|
|
174
|
+
- Added sorting rules documentation
|
|
175
|
+
- Updated examples with strings and mixed types
|
|
176
|
+
- Updated error messages
|
|
177
|
+
|
|
178
|
+
3. **CHANGELOG.md**
|
|
179
|
+
- Added v0.2.0 release notes
|
|
180
|
+
- Documented all changes
|
|
181
|
+
- Listed technical details
|
|
182
|
+
|
|
183
|
+
4. **lib/vibe_sort/version.rb**
|
|
184
|
+
- Updated version to `0.2.0`
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## ✅ Backward Compatibility
|
|
189
|
+
|
|
190
|
+
**Good News:** This update is **fully backward compatible**!
|
|
191
|
+
|
|
192
|
+
- All v0.1.0 code continues to work without changes
|
|
193
|
+
- Number-only arrays still work exactly the same
|
|
194
|
+
- No breaking changes to the API
|
|
195
|
+
- Existing applications can upgrade without modification
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
# This still works exactly as before
|
|
199
|
+
client = VibeSort::Client.new(api_key: ENV['OPENAI_API_KEY'])
|
|
200
|
+
result = client.sort([5, 2, 8, 1, 9])
|
|
201
|
+
# => { success: true, sorted_array: [1, 2, 5, 8, 9] }
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## 🧪 Testing Recommendations
|
|
207
|
+
|
|
208
|
+
When upgrading to v0.2.0, test these scenarios:
|
|
209
|
+
|
|
210
|
+
### 1. String Arrays
|
|
211
|
+
```ruby
|
|
212
|
+
client.sort(["zebra", "Apple", "banana", "cherry"])
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### 2. Mixed Arrays
|
|
216
|
+
```ruby
|
|
217
|
+
client.sort([100, "test", 5, "alpha", 3.14])
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### 3. Edge Cases
|
|
221
|
+
```ruby
|
|
222
|
+
# Empty strings
|
|
223
|
+
client.sort(["", "hello", "world"])
|
|
224
|
+
|
|
225
|
+
# Special characters
|
|
226
|
+
client.sort(["@symbol", "123", "abc"])
|
|
227
|
+
|
|
228
|
+
# Unicode
|
|
229
|
+
client.sort(["café", "apple", "über"])
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### 4. Invalid Input (should fail gracefully)
|
|
233
|
+
```ruby
|
|
234
|
+
client.sort([1, :symbol, "text"]) # Contains symbol
|
|
235
|
+
client.sort([1, { key: "value" }]) # Contains hash
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## 🔍 Implementation Notes
|
|
241
|
+
|
|
242
|
+
### Why These Changes?
|
|
243
|
+
|
|
244
|
+
1. **Input Validation**: Extended to accept strings, maintaining type safety
|
|
245
|
+
2. **AI Prompt**: Generalized to handle multiple data types while maintaining clear sorting rules
|
|
246
|
+
3. **Output Validation**: Ensures API returns only expected types
|
|
247
|
+
|
|
248
|
+
### AI Model Behavior
|
|
249
|
+
|
|
250
|
+
The OpenAI GPT model (gpt-3.5-turbo-1106) handles mixed-type sorting intelligently:
|
|
251
|
+
|
|
252
|
+
- Understands standard sorting conventions
|
|
253
|
+
- Consistently places numbers before strings
|
|
254
|
+
- Handles case-sensitive string sorting
|
|
255
|
+
- Maintains JSON output format
|
|
256
|
+
|
|
257
|
+
### Performance Considerations
|
|
258
|
+
|
|
259
|
+
- No performance difference from v0.1.0
|
|
260
|
+
- Same API latency (~1-3 seconds)
|
|
261
|
+
- Same cost per request (~$0.001-0.002)
|
|
262
|
+
- Same token usage (~100-200 tokens)
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## 🎨 Architecture Remains Clean
|
|
267
|
+
|
|
268
|
+
The core architecture is unchanged:
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
User Application
|
|
272
|
+
↓
|
|
273
|
+
VibeSort::Client (validates input)
|
|
274
|
+
↓
|
|
275
|
+
VibeSort::Configuration (stores settings)
|
|
276
|
+
↓
|
|
277
|
+
VibeSort::Sorter (communicates with OpenAI)
|
|
278
|
+
↓
|
|
279
|
+
OpenAI API (processes request)
|
|
280
|
+
↓
|
|
281
|
+
Sorted Array (returned to user)
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## 📦 Upgrade Instructions
|
|
287
|
+
|
|
288
|
+
### Using Bundler
|
|
289
|
+
|
|
290
|
+
Update your `Gemfile`:
|
|
291
|
+
|
|
292
|
+
```ruby
|
|
293
|
+
gem 'vibe-sort', '~> 0.2.0'
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Then run:
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
bundle update vibe-sort
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Manual Installation
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
gem install vibe-sort -v 0.2.0
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## 🐛 Known Limitations
|
|
311
|
+
|
|
312
|
+
Same limitations as v0.1.0:
|
|
313
|
+
|
|
314
|
+
- Requires internet connection
|
|
315
|
+
- Incurs API costs
|
|
316
|
+
- Not suitable for production use
|
|
317
|
+
- 1-3 second latency per request
|
|
318
|
+
- Limited by OpenAI rate limits
|
|
319
|
+
- Ascending order only (no descending option)
|
|
320
|
+
|
|
321
|
+
**New considerations:**
|
|
322
|
+
|
|
323
|
+
- String sorting is case-sensitive (ASCII order)
|
|
324
|
+
- Unicode characters may sort unexpectedly
|
|
325
|
+
- Very long strings may increase token usage
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## 🔮 Future Enhancements
|
|
330
|
+
|
|
331
|
+
Potential features for future versions:
|
|
332
|
+
|
|
333
|
+
1. **Custom Sort Orders**
|
|
334
|
+
- Descending order option
|
|
335
|
+
- Case-insensitive string sorting
|
|
336
|
+
- Custom comparator functions
|
|
337
|
+
|
|
338
|
+
2. **More Data Types**
|
|
339
|
+
- Date/time sorting
|
|
340
|
+
- Boolean values
|
|
341
|
+
- Custom object sorting
|
|
342
|
+
|
|
343
|
+
3. **Advanced Features**
|
|
344
|
+
- Batch sorting
|
|
345
|
+
- Caching
|
|
346
|
+
- Retry logic
|
|
347
|
+
- Streaming responses
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## 📞 Support
|
|
352
|
+
|
|
353
|
+
- 🐛 [Report Issues](https://github.com/chayut/vibe-sort/issues)
|
|
354
|
+
- 💡 [Feature Requests](https://github.com/chayut/vibe-sort/issues/new?labels=enhancement)
|
|
355
|
+
- 📖 [Documentation](https://github.com/chayut/vibe-sort/tree/main/docs)
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## ✨ Summary
|
|
360
|
+
|
|
361
|
+
Version 0.2.0 expands VibeSort's capabilities while maintaining:
|
|
362
|
+
|
|
363
|
+
- ✅ Same clean API
|
|
364
|
+
- ✅ Same architecture
|
|
365
|
+
- ✅ Full backward compatibility
|
|
366
|
+
- ✅ Same performance characteristics
|
|
367
|
+
- ✅ Comprehensive documentation
|
|
368
|
+
|
|
369
|
+
The gem now handles a wider variety of data types, making it more versatile for educational purposes and demonstrations.
|
|
370
|
+
|
|
371
|
+
**Happy Sorting!** 🌀
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
**Version:** 0.2.0
|
|
376
|
+
**Release Date:** October 16, 2025
|
|
377
|
+
**Status:** Stable
|
data/lib/vibe/sort.rb
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module VibeSort
|
|
4
|
+
# Client is the main public interface for the VibeSort gem
|
|
5
|
+
class Client
|
|
6
|
+
attr_reader :config
|
|
7
|
+
|
|
8
|
+
# Initialize a new VibeSort client
|
|
9
|
+
#
|
|
10
|
+
# @param api_key [String] OpenAI API key
|
|
11
|
+
# @param temperature [Float] Temperature for the model (default: 0.0)
|
|
12
|
+
# @raise [ArgumentError] if api_key is invalid
|
|
13
|
+
#
|
|
14
|
+
# @example
|
|
15
|
+
# client = VibeSort::Client.new(api_key: ENV['OPENAI_API_KEY'])
|
|
16
|
+
def initialize(api_key:, temperature: 0.0)
|
|
17
|
+
@config = Configuration.new(api_key: api_key, temperature: temperature)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Sort an array of numbers and/or strings using OpenAI API
|
|
21
|
+
#
|
|
22
|
+
# @param array [Array] Array of numbers and/or strings to sort
|
|
23
|
+
# @return [Hash] Result hash with keys:
|
|
24
|
+
# - :success [Boolean] whether the operation succeeded
|
|
25
|
+
# - :sorted_array [Array] the sorted array (empty on failure)
|
|
26
|
+
# - :error [String] error message (only present on failure)
|
|
27
|
+
#
|
|
28
|
+
# @example Successful sort with numbers
|
|
29
|
+
# result = client.sort([5, 2, 8, 1, 9])
|
|
30
|
+
# #=> { success: true, sorted_array: [1, 2, 5, 8, 9] }
|
|
31
|
+
#
|
|
32
|
+
# @example Successful sort with strings
|
|
33
|
+
# result = client.sort(["banana", "Apple", "cherry"])
|
|
34
|
+
# #=> { success: true, sorted_array: ["Apple", "banana", "cherry"] }
|
|
35
|
+
#
|
|
36
|
+
# @example Successful sort with mixed types
|
|
37
|
+
# result = client.sort([42, "hello", 8, "world"])
|
|
38
|
+
# #=> { success: true, sorted_array: [8, 42, "hello", "world"] }
|
|
39
|
+
#
|
|
40
|
+
# @example Invalid input
|
|
41
|
+
# result = client.sort([1, :symbol, 3])
|
|
42
|
+
# #=> { success: false, sorted_array: [], error: "Input must be an array of numbers or strings" }
|
|
43
|
+
#
|
|
44
|
+
# @example API error
|
|
45
|
+
# result = client.sort([1, 2, 3]) # with invalid API key
|
|
46
|
+
# #=> { success: false, sorted_array: [], error: "OpenAI API error: Invalid API key" }
|
|
47
|
+
def sort(array)
|
|
48
|
+
# Validate input
|
|
49
|
+
unless valid_input?(array)
|
|
50
|
+
return {
|
|
51
|
+
success: false,
|
|
52
|
+
sorted_array: [],
|
|
53
|
+
error: "Input must be an array of numbers or strings"
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Perform the sort via API
|
|
58
|
+
sorter = Sorter.new(config)
|
|
59
|
+
sorter.perform(array)
|
|
60
|
+
rescue ApiError => e
|
|
61
|
+
{
|
|
62
|
+
success: false,
|
|
63
|
+
sorted_array: [],
|
|
64
|
+
error: e.message
|
|
65
|
+
}
|
|
66
|
+
rescue StandardError => e
|
|
67
|
+
{
|
|
68
|
+
success: false,
|
|
69
|
+
sorted_array: [],
|
|
70
|
+
error: "Unexpected error: #{e.message}"
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
# Validate that input is an array of numbers and/or strings
|
|
77
|
+
#
|
|
78
|
+
# @param array [Object] Input to validate
|
|
79
|
+
# @return [Boolean] true if valid, false otherwise
|
|
80
|
+
def valid_input?(array)
|
|
81
|
+
return false unless array.is_a?(Array)
|
|
82
|
+
return false if array.empty?
|
|
83
|
+
|
|
84
|
+
array.all? { |item| item.is_a?(Numeric) || item.is_a?(String) }
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module VibeSort
|
|
4
|
+
# Configuration class for VibeSort
|
|
5
|
+
# Holds API key and temperature settings
|
|
6
|
+
class Configuration
|
|
7
|
+
attr_reader :api_key, :temperature
|
|
8
|
+
|
|
9
|
+
# Initialize a new Configuration
|
|
10
|
+
#
|
|
11
|
+
# @param api_key [String] OpenAI API key
|
|
12
|
+
# @param temperature [Float] Temperature for the model (0.0 to 2.0)
|
|
13
|
+
# @raise [ArgumentError] if api_key is nil or empty
|
|
14
|
+
def initialize(api_key:, temperature: 0.0)
|
|
15
|
+
raise ArgumentError, "API key cannot be nil or empty" if api_key.nil? || api_key.empty?
|
|
16
|
+
|
|
17
|
+
@api_key = api_key
|
|
18
|
+
@temperature = temperature
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module VibeSort
|
|
4
|
+
# Custom error class for API-related errors
|
|
5
|
+
class ApiError < StandardError
|
|
6
|
+
attr_reader :response
|
|
7
|
+
|
|
8
|
+
# Initialize a new ApiError
|
|
9
|
+
#
|
|
10
|
+
# @param message [String] Error message
|
|
11
|
+
# @param response [Faraday::Response, nil] HTTP response object
|
|
12
|
+
def initialize(message, response = nil)
|
|
13
|
+
super(message)
|
|
14
|
+
@response = response
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module VibeSort
|
|
4
|
+
# Sorter class handles the API call to OpenAI
|
|
5
|
+
class Sorter
|
|
6
|
+
OPENAI_API_URL = "https://api.openai.com/v1/chat/completions"
|
|
7
|
+
DEFAULT_MODEL = "gpt-3.5-turbo-1106"
|
|
8
|
+
|
|
9
|
+
attr_reader :config
|
|
10
|
+
|
|
11
|
+
# Initialize a new Sorter
|
|
12
|
+
#
|
|
13
|
+
# @param config [VibeSort::Configuration] Configuration object
|
|
14
|
+
def initialize(config)
|
|
15
|
+
@config = config
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Perform the sorting operation via OpenAI API
|
|
19
|
+
#
|
|
20
|
+
# @param array [Array] Array of numbers and/or strings to sort
|
|
21
|
+
# @return [Hash] Result hash with :success, :sorted_array, and optional :error keys
|
|
22
|
+
# @raise [VibeSort::ApiError] if the API request fails
|
|
23
|
+
def perform(array)
|
|
24
|
+
response = connection.post do |req|
|
|
25
|
+
req.body = build_payload(array)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
handle_response(response)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
# Build the connection to OpenAI API
|
|
34
|
+
#
|
|
35
|
+
# @return [Faraday::Connection] Faraday connection object
|
|
36
|
+
def connection
|
|
37
|
+
@connection ||= Faraday.new(url: OPENAI_API_URL) do |f|
|
|
38
|
+
f.request :json # Encodes request body as JSON
|
|
39
|
+
f.response :json # Decodes response body as JSON
|
|
40
|
+
f.headers["Authorization"] = "Bearer #{config.api_key}"
|
|
41
|
+
f.headers["Content-Type"] = "application/json"
|
|
42
|
+
f.adapter Faraday.default_adapter
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Build the request payload for OpenAI API
|
|
47
|
+
#
|
|
48
|
+
# @param array [Array] Array to sort (numbers and/or strings)
|
|
49
|
+
# @return [Hash] Request payload
|
|
50
|
+
def build_payload(array)
|
|
51
|
+
{
|
|
52
|
+
model: DEFAULT_MODEL,
|
|
53
|
+
temperature: config.temperature,
|
|
54
|
+
response_format: { type: "json_object" },
|
|
55
|
+
messages: [
|
|
56
|
+
{
|
|
57
|
+
role: "system",
|
|
58
|
+
content: "You are an assistant that only sorts arrays. The array may contain numbers and strings. Sort the array in ascending order. Follow standard sorting rules: numbers should come before strings, and string sorting should be case-sensitive. Return a JSON object with a single key 'sorted_array' containing the sorted elements."
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
role: "user",
|
|
62
|
+
content: "Please sort this array: #{array.to_json}"
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Handle the API response
|
|
69
|
+
#
|
|
70
|
+
# @param response [Faraday::Response] HTTP response
|
|
71
|
+
# @return [Hash] Result hash
|
|
72
|
+
# @raise [VibeSort::ApiError] if response is invalid or parsing fails
|
|
73
|
+
def handle_response(response)
|
|
74
|
+
unless response.success?
|
|
75
|
+
error_message = extract_error_message(response)
|
|
76
|
+
raise ApiError.new("OpenAI API error: #{error_message}", response)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
parse_sorted_array(response)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Extract error message from failed response
|
|
83
|
+
#
|
|
84
|
+
# @param response [Faraday::Response] HTTP response
|
|
85
|
+
# @return [String] Error message
|
|
86
|
+
def extract_error_message(response)
|
|
87
|
+
return "Unknown error" unless response.body.is_a?(Hash)
|
|
88
|
+
|
|
89
|
+
response.body.dig("error", "message") || "HTTP #{response.status}"
|
|
90
|
+
rescue StandardError
|
|
91
|
+
"HTTP #{response.status}"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Parse the sorted array from the API response
|
|
95
|
+
#
|
|
96
|
+
# @param response [Faraday::Response] HTTP response
|
|
97
|
+
# @return [Hash] Success result with sorted array
|
|
98
|
+
# @raise [VibeSort::ApiError] if parsing fails or validation fails
|
|
99
|
+
def parse_sorted_array(response)
|
|
100
|
+
content = response.body.dig("choices", 0, "message", "content")
|
|
101
|
+
raise ApiError.new("Invalid response structure", response) if content.nil?
|
|
102
|
+
|
|
103
|
+
parsed_content = JSON.parse(content)
|
|
104
|
+
sorted_array = parsed_content["sorted_array"]
|
|
105
|
+
|
|
106
|
+
validate_sorted_array!(sorted_array)
|
|
107
|
+
|
|
108
|
+
{ success: true, sorted_array: sorted_array }
|
|
109
|
+
rescue JSON::ParserError => e
|
|
110
|
+
raise ApiError.new("Failed to parse JSON response: #{e.message}", response)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Validate that the sorted array is valid
|
|
114
|
+
#
|
|
115
|
+
# @param sorted_array [Object] Parsed sorted array
|
|
116
|
+
# @raise [VibeSort::ApiError] if validation fails
|
|
117
|
+
def validate_sorted_array!(sorted_array)
|
|
118
|
+
raise ApiError, "Response does not contain a valid 'sorted_array'" unless sorted_array.is_a?(Array)
|
|
119
|
+
|
|
120
|
+
return if sorted_array.all? { |item| item.is_a?(Numeric) || item.is_a?(String) }
|
|
121
|
+
|
|
122
|
+
raise ApiError, "Sorted array contains invalid values (must be numbers or strings)"
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
data/lib/vibe_sort.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
require_relative "vibe_sort/version"
|
|
7
|
+
require_relative "vibe_sort/error"
|
|
8
|
+
require_relative "vibe_sort/configuration"
|
|
9
|
+
require_relative "vibe_sort/sorter"
|
|
10
|
+
require_relative "vibe_sort/client"
|
|
11
|
+
|
|
12
|
+
module VibeSort
|
|
13
|
+
class Error < StandardError; end
|
|
14
|
+
end
|