vibe-sort 0.2.0 → 0.3.1
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 +32 -0
- data/README.md +91 -42
- data/docs/IMPLEMENTATION_PLAN.md +50 -50
- data/docs/README.md +20 -20
- data/docs/api_reference.md +68 -28
- data/docs/architecture.md +64 -46
- data/docs/development.md +3 -3
- data/docs/v0.2.0_UPDATE.md +37 -37
- data/lib/vibe/sort.rb +4 -8
- data/lib/vibe_sort/client.rb +17 -7
- data/lib/vibe_sort/configuration.rb +15 -6
- data/lib/vibe_sort/providers/anthropic.rb +68 -0
- data/lib/vibe_sort/providers/base.rb +147 -0
- data/lib/vibe_sort/providers/gemini.rb +43 -0
- data/lib/vibe_sort/providers/groq.rb +21 -0
- data/lib/vibe_sort/providers/openai.rb +41 -0
- data/lib/vibe_sort/providers/space_x_ai.rb +21 -0
- data/lib/vibe_sort/sorter.rb +10 -103
- data/lib/vibe_sort/version.rb +1 -1
- data/lib/vibe_sort.rb +6 -0
- metadata +17 -8
- data/lib/vibe/sort/version.rb +0 -7
- data/sig/vibe/sort.rbs +0 -6
|
@@ -2,20 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
module VibeSort
|
|
4
4
|
# Configuration class for VibeSort
|
|
5
|
-
# Holds API key and temperature settings
|
|
5
|
+
# Holds provider, API key, model, and temperature settings
|
|
6
6
|
class Configuration
|
|
7
|
-
|
|
7
|
+
PROVIDERS = %i[openai anthropic gemini groq spacexai].freeze
|
|
8
|
+
|
|
9
|
+
attr_reader :api_key, :temperature, :provider, :model
|
|
8
10
|
|
|
9
11
|
# Initialize a new Configuration
|
|
10
12
|
#
|
|
11
|
-
# @param api_key [String]
|
|
12
|
-
# @param temperature [Float] Temperature for the model (0.0 to 2.0)
|
|
13
|
-
#
|
|
14
|
-
|
|
13
|
+
# @param api_key [String] API key for the selected provider
|
|
14
|
+
# @param temperature [Float] Temperature for the model (0.0 to 2.0).
|
|
15
|
+
# Ignored by providers whose current models do not accept it (Anthropic).
|
|
16
|
+
# @param provider [Symbol, String] LLM provider: :openai (default), :anthropic, :gemini, :groq, or :spacexai
|
|
17
|
+
# @param model [String, nil] Model ID override (nil uses the provider's default)
|
|
18
|
+
# @raise [ArgumentError] if api_key is nil or empty, or provider is unknown
|
|
19
|
+
def initialize(api_key:, temperature: 0.0, provider: :openai, model: nil)
|
|
15
20
|
raise ArgumentError, "API key cannot be nil or empty" if api_key.nil? || api_key.empty?
|
|
16
21
|
|
|
22
|
+
@provider = provider.to_s.to_sym
|
|
23
|
+
raise ArgumentError, "Unknown provider: #{provider.inspect} (supported: #{PROVIDERS.join(", ")})" unless PROVIDERS.include?(@provider)
|
|
24
|
+
|
|
17
25
|
@api_key = api_key
|
|
18
26
|
@temperature = temperature
|
|
27
|
+
@model = model
|
|
19
28
|
end
|
|
20
29
|
end
|
|
21
30
|
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module VibeSort
|
|
4
|
+
module Providers
|
|
5
|
+
# Adapter for the Anthropic Messages API
|
|
6
|
+
#
|
|
7
|
+
# Uses structured outputs (output_config.format with a JSON schema) so the
|
|
8
|
+
# model is guaranteed to return the { "sorted_array": [...] } shape.
|
|
9
|
+
#
|
|
10
|
+
# Note: current Claude models (Opus 4.7+) reject the temperature parameter,
|
|
11
|
+
# so config.temperature is not forwarded to this provider.
|
|
12
|
+
class Anthropic < Base
|
|
13
|
+
ENDPOINT = "https://api.anthropic.com/v1/messages"
|
|
14
|
+
DEFAULT_MODEL = "claude-opus-4-8"
|
|
15
|
+
API_VERSION = "2023-06-01"
|
|
16
|
+
MAX_TOKENS = 4096
|
|
17
|
+
|
|
18
|
+
OUTPUT_SCHEMA = {
|
|
19
|
+
type: "object",
|
|
20
|
+
properties: {
|
|
21
|
+
sorted_array: {
|
|
22
|
+
type: "array",
|
|
23
|
+
items: { anyOf: [{ type: "number" }, { type: "string" }] }
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
required: ["sorted_array"],
|
|
27
|
+
additionalProperties: false
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def provider_name
|
|
33
|
+
"Anthropic"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def endpoint
|
|
37
|
+
ENDPOINT
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def headers
|
|
41
|
+
{
|
|
42
|
+
"x-api-key" => config.api_key,
|
|
43
|
+
"anthropic-version" => API_VERSION
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def build_payload(array)
|
|
48
|
+
{
|
|
49
|
+
model: model,
|
|
50
|
+
max_tokens: MAX_TOKENS,
|
|
51
|
+
system: SYSTEM_PROMPT,
|
|
52
|
+
output_config: { format: { type: "json_schema", schema: OUTPUT_SCHEMA } },
|
|
53
|
+
messages: [
|
|
54
|
+
{ role: "user", content: user_prompt(array) }
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def extract_content(response)
|
|
60
|
+
blocks = response.body["content"]
|
|
61
|
+
return nil unless blocks.is_a?(Array)
|
|
62
|
+
|
|
63
|
+
text_block = blocks.find { |block| block.is_a?(Hash) && block["type"] == "text" }
|
|
64
|
+
text_block && text_block["text"]
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module VibeSort
|
|
4
|
+
module Providers
|
|
5
|
+
# Base class for LLM provider adapters.
|
|
6
|
+
#
|
|
7
|
+
# Holds everything that is provider-agnostic: the sorting prompt, the
|
|
8
|
+
# Faraday connection plumbing, and validation of the returned array.
|
|
9
|
+
# Subclasses implement the provider-specific hooks: +provider_name+,
|
|
10
|
+
# +endpoint+, +headers+, +build_payload+, and +extract_content+.
|
|
11
|
+
class Base
|
|
12
|
+
SYSTEM_PROMPT = "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."
|
|
13
|
+
|
|
14
|
+
attr_reader :config
|
|
15
|
+
|
|
16
|
+
# @param config [VibeSort::Configuration] Configuration object
|
|
17
|
+
def initialize(config)
|
|
18
|
+
@config = config
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Perform the sorting operation via the provider's API
|
|
22
|
+
#
|
|
23
|
+
# @param array [Array] Array of numbers and/or strings to sort
|
|
24
|
+
# @return [Hash] Result hash with :success and :sorted_array keys
|
|
25
|
+
# @raise [VibeSort::ApiError] if the API request fails
|
|
26
|
+
def perform(array)
|
|
27
|
+
response = connection.post do |req|
|
|
28
|
+
req.body = build_payload(array)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
handle_response(response)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
# Model to use: explicit override from config, or the provider default
|
|
37
|
+
#
|
|
38
|
+
# @return [String] Model ID
|
|
39
|
+
def model
|
|
40
|
+
config.model || self.class::DEFAULT_MODEL
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @param array [Array] Array to sort
|
|
44
|
+
# @return [String] User prompt sent to the model
|
|
45
|
+
def user_prompt(array)
|
|
46
|
+
"Please sort this array: #{array.to_json}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @return [Faraday::Connection] Faraday connection object
|
|
50
|
+
def connection
|
|
51
|
+
@connection ||= Faraday.new(url: endpoint) do |f|
|
|
52
|
+
f.request :json # Encodes request body as JSON
|
|
53
|
+
f.response :json # Decodes response body as JSON
|
|
54
|
+
headers.each { |name, value| f.headers[name] = value }
|
|
55
|
+
f.headers["Content-Type"] = "application/json"
|
|
56
|
+
f.adapter Faraday.default_adapter
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @param response [Faraday::Response] HTTP response
|
|
61
|
+
# @return [Hash] Result hash
|
|
62
|
+
# @raise [VibeSort::ApiError] if response is invalid or parsing fails
|
|
63
|
+
def handle_response(response)
|
|
64
|
+
unless response.success?
|
|
65
|
+
error_message = extract_error_message(response)
|
|
66
|
+
raise ApiError.new("#{provider_name} API error: #{error_message}", response)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
parse_sorted_array(response)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Extract error message from failed response. Works for all three
|
|
73
|
+
# providers: OpenAI, Anthropic, and Gemini all return an "error"
|
|
74
|
+
# object with a "message" key on failure.
|
|
75
|
+
#
|
|
76
|
+
# @param response [Faraday::Response] HTTP response
|
|
77
|
+
# @return [String] Error message
|
|
78
|
+
def extract_error_message(response)
|
|
79
|
+
return "Unknown error" unless response.body.is_a?(Hash)
|
|
80
|
+
|
|
81
|
+
response.body.dig("error", "message") || "HTTP #{response.status}"
|
|
82
|
+
rescue StandardError
|
|
83
|
+
"HTTP #{response.status}"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Parse the sorted array from the API response
|
|
87
|
+
#
|
|
88
|
+
# @param response [Faraday::Response] HTTP response
|
|
89
|
+
# @return [Hash] Success result with sorted array
|
|
90
|
+
# @raise [VibeSort::ApiError] if parsing fails or validation fails
|
|
91
|
+
def parse_sorted_array(response)
|
|
92
|
+
content = extract_content(response)
|
|
93
|
+
raise ApiError.new("Invalid response structure", response) if content.nil?
|
|
94
|
+
|
|
95
|
+
parsed_content = JSON.parse(content)
|
|
96
|
+
sorted_array = parsed_content["sorted_array"]
|
|
97
|
+
|
|
98
|
+
validate_sorted_array!(sorted_array)
|
|
99
|
+
|
|
100
|
+
{ success: true, sorted_array: sorted_array }
|
|
101
|
+
rescue JSON::ParserError => e
|
|
102
|
+
raise ApiError.new("Failed to parse JSON response: #{e.message}", response)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Validate that the sorted array is valid
|
|
106
|
+
#
|
|
107
|
+
# @param sorted_array [Object] Parsed sorted array
|
|
108
|
+
# @raise [VibeSort::ApiError] if validation fails
|
|
109
|
+
def validate_sorted_array!(sorted_array)
|
|
110
|
+
raise ApiError, "Response does not contain a valid 'sorted_array'" unless sorted_array.is_a?(Array)
|
|
111
|
+
|
|
112
|
+
return if sorted_array.all? { |item| item.is_a?(Numeric) || item.is_a?(String) }
|
|
113
|
+
|
|
114
|
+
raise ApiError, "Sorted array contains invalid values (must be numbers or strings)"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# --- Provider-specific hooks ---
|
|
118
|
+
|
|
119
|
+
# @return [String] Human-readable provider name used in error messages
|
|
120
|
+
def provider_name
|
|
121
|
+
raise NotImplementedError
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# @return [String] Full URL of the API endpoint
|
|
125
|
+
def endpoint
|
|
126
|
+
raise NotImplementedError
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @return [Hash] Provider-specific HTTP headers (auth etc.)
|
|
130
|
+
def headers
|
|
131
|
+
raise NotImplementedError
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# @param array [Array] Array to sort
|
|
135
|
+
# @return [Hash] Provider-specific request payload
|
|
136
|
+
def build_payload(array)
|
|
137
|
+
raise NotImplementedError
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# @param response [Faraday::Response] HTTP response
|
|
141
|
+
# @return [String, nil] Raw JSON text produced by the model
|
|
142
|
+
def extract_content(response)
|
|
143
|
+
raise NotImplementedError
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module VibeSort
|
|
4
|
+
module Providers
|
|
5
|
+
# Adapter for the Google Gemini API (generateContent)
|
|
6
|
+
class Gemini < Base
|
|
7
|
+
BASE_URL = "https://generativelanguage.googleapis.com/v1beta/models"
|
|
8
|
+
DEFAULT_MODEL = "gemini-2.5-flash"
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def provider_name
|
|
13
|
+
"Gemini"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# The Gemini endpoint embeds the model ID in the URL path
|
|
17
|
+
def endpoint
|
|
18
|
+
"#{BASE_URL}/#{model}:generateContent"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def headers
|
|
22
|
+
{ "x-goog-api-key" => config.api_key }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def build_payload(array)
|
|
26
|
+
{
|
|
27
|
+
systemInstruction: { parts: [{ text: SYSTEM_PROMPT }] },
|
|
28
|
+
contents: [
|
|
29
|
+
{ role: "user", parts: [{ text: user_prompt(array) }] }
|
|
30
|
+
],
|
|
31
|
+
generationConfig: {
|
|
32
|
+
temperature: config.temperature,
|
|
33
|
+
responseMimeType: "application/json"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def extract_content(response)
|
|
39
|
+
response.body.dig("candidates", 0, "content", "parts", 0, "text")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module VibeSort
|
|
4
|
+
module Providers
|
|
5
|
+
# Adapter for the Groq API (OpenAI-compatible Chat Completions)
|
|
6
|
+
class Groq < OpenAI
|
|
7
|
+
ENDPOINT = "https://api.groq.com/openai/v1/chat/completions"
|
|
8
|
+
DEFAULT_MODEL = "llama-3.3-70b-versatile"
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def provider_name
|
|
13
|
+
"Groq"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def endpoint
|
|
17
|
+
ENDPOINT
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module VibeSort
|
|
4
|
+
module Providers
|
|
5
|
+
# Adapter for the OpenAI Chat Completions API
|
|
6
|
+
class OpenAI < Base
|
|
7
|
+
ENDPOINT = "https://api.openai.com/v1/chat/completions"
|
|
8
|
+
DEFAULT_MODEL = "gpt-4o-mini"
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def provider_name
|
|
13
|
+
"OpenAI"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def endpoint
|
|
17
|
+
ENDPOINT
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def headers
|
|
21
|
+
{ "Authorization" => "Bearer #{config.api_key}" }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def build_payload(array)
|
|
25
|
+
{
|
|
26
|
+
model: model,
|
|
27
|
+
temperature: config.temperature,
|
|
28
|
+
response_format: { type: "json_object" },
|
|
29
|
+
messages: [
|
|
30
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
31
|
+
{ role: "user", content: user_prompt(array) }
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def extract_content(response)
|
|
37
|
+
response.body.dig("choices", 0, "message", "content")
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module VibeSort
|
|
4
|
+
module Providers
|
|
5
|
+
# Adapter for the SpaceXAI (xAI) Grok API (OpenAI-compatible Chat Completions)
|
|
6
|
+
class SpaceXAI < OpenAI
|
|
7
|
+
ENDPOINT = "https://api.x.ai/v1/chat/completions"
|
|
8
|
+
DEFAULT_MODEL = "grok-4"
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def provider_name
|
|
13
|
+
"SpaceXAI"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def endpoint
|
|
17
|
+
ENDPOINT
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
data/lib/vibe_sort/sorter.rb
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module VibeSort
|
|
4
|
-
# Sorter
|
|
4
|
+
# Sorter dispatches the sorting operation to the configured provider adapter
|
|
5
5
|
class Sorter
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
PROVIDER_CLASSES = {
|
|
7
|
+
openai: Providers::OpenAI,
|
|
8
|
+
anthropic: Providers::Anthropic,
|
|
9
|
+
gemini: Providers::Gemini,
|
|
10
|
+
groq: Providers::Groq,
|
|
11
|
+
spacexai: Providers::SpaceXAI
|
|
12
|
+
}.freeze
|
|
8
13
|
|
|
9
14
|
attr_reader :config
|
|
10
15
|
|
|
@@ -15,111 +20,13 @@ module VibeSort
|
|
|
15
20
|
@config = config
|
|
16
21
|
end
|
|
17
22
|
|
|
18
|
-
# Perform the sorting operation via
|
|
23
|
+
# Perform the sorting operation via the configured provider's API
|
|
19
24
|
#
|
|
20
25
|
# @param array [Array] Array of numbers and/or strings to sort
|
|
21
26
|
# @return [Hash] Result hash with :success, :sorted_array, and optional :error keys
|
|
22
27
|
# @raise [VibeSort::ApiError] if the API request fails
|
|
23
28
|
def perform(array)
|
|
24
|
-
|
|
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)"
|
|
29
|
+
PROVIDER_CLASSES.fetch(config.provider).new(config).perform(array)
|
|
123
30
|
end
|
|
124
31
|
end
|
|
125
32
|
end
|
data/lib/vibe_sort/version.rb
CHANGED
data/lib/vibe_sort.rb
CHANGED
|
@@ -6,6 +6,12 @@ require "json"
|
|
|
6
6
|
require_relative "vibe_sort/version"
|
|
7
7
|
require_relative "vibe_sort/error"
|
|
8
8
|
require_relative "vibe_sort/configuration"
|
|
9
|
+
require_relative "vibe_sort/providers/base"
|
|
10
|
+
require_relative "vibe_sort/providers/openai"
|
|
11
|
+
require_relative "vibe_sort/providers/anthropic"
|
|
12
|
+
require_relative "vibe_sort/providers/gemini"
|
|
13
|
+
require_relative "vibe_sort/providers/groq"
|
|
14
|
+
require_relative "vibe_sort/providers/space_x_ai"
|
|
9
15
|
require_relative "vibe_sort/sorter"
|
|
10
16
|
require_relative "vibe_sort/client"
|
|
11
17
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: vibe-sort
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chayut Orapinpatipat
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-07-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -24,8 +24,10 @@ dependencies:
|
|
|
24
24
|
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '2.0'
|
|
27
|
-
description: A proof-of-concept Ruby gem that sorts
|
|
28
|
-
OpenAI Chat Completions
|
|
27
|
+
description: A proof-of-concept Ruby gem that sorts arrays by leveraging LLM APIs
|
|
28
|
+
(OpenAI Chat Completions, Anthropic Messages, Google Gemini, and the OpenAI-compatible
|
|
29
|
+
Groq and SpaceXAI Grok APIs). Demonstrates how AI can be used for computational
|
|
30
|
+
tasks.
|
|
29
31
|
email:
|
|
30
32
|
- chayut_o@hotmail.com
|
|
31
33
|
executables: []
|
|
@@ -45,14 +47,18 @@ files:
|
|
|
45
47
|
- docs/development.md
|
|
46
48
|
- docs/v0.2.0_UPDATE.md
|
|
47
49
|
- lib/vibe/sort.rb
|
|
48
|
-
- lib/vibe/sort/version.rb
|
|
49
50
|
- lib/vibe_sort.rb
|
|
50
51
|
- lib/vibe_sort/client.rb
|
|
51
52
|
- lib/vibe_sort/configuration.rb
|
|
52
53
|
- lib/vibe_sort/error.rb
|
|
54
|
+
- lib/vibe_sort/providers/anthropic.rb
|
|
55
|
+
- lib/vibe_sort/providers/base.rb
|
|
56
|
+
- lib/vibe_sort/providers/gemini.rb
|
|
57
|
+
- lib/vibe_sort/providers/groq.rb
|
|
58
|
+
- lib/vibe_sort/providers/openai.rb
|
|
59
|
+
- lib/vibe_sort/providers/space_x_ai.rb
|
|
53
60
|
- lib/vibe_sort/sorter.rb
|
|
54
61
|
- lib/vibe_sort/version.rb
|
|
55
|
-
- sig/vibe/sort.rbs
|
|
56
62
|
homepage: https://github.com/chayuto/vibe-sort
|
|
57
63
|
licenses:
|
|
58
64
|
- MIT
|
|
@@ -60,6 +66,8 @@ metadata:
|
|
|
60
66
|
homepage_uri: https://github.com/chayuto/vibe-sort
|
|
61
67
|
source_code_uri: https://github.com/chayuto/vibe-sort
|
|
62
68
|
changelog_uri: https://github.com/chayuto/vibe-sort/blob/main/CHANGELOG.md
|
|
69
|
+
documentation_uri: https://rubydoc.info/gems/vibe-sort
|
|
70
|
+
bug_tracker_uri: https://github.com/chayuto/vibe-sort/issues
|
|
63
71
|
rubygems_mfa_required: 'true'
|
|
64
72
|
post_install_message:
|
|
65
73
|
rdoc_options: []
|
|
@@ -76,8 +84,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
76
84
|
- !ruby/object:Gem::Version
|
|
77
85
|
version: '0'
|
|
78
86
|
requirements: []
|
|
79
|
-
rubygems_version: 3.5.
|
|
87
|
+
rubygems_version: 3.5.22
|
|
80
88
|
signing_key:
|
|
81
89
|
specification_version: 4
|
|
82
|
-
summary: AI-powered array sorting using OpenAI
|
|
90
|
+
summary: AI-powered array sorting using OpenAI, Anthropic Claude, Google Gemini, Groq,
|
|
91
|
+
or SpaceXAI Grok
|
|
83
92
|
test_files: []
|
data/lib/vibe/sort/version.rb
DELETED