whitebox 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e220dba2b201dbefd83250e9c32280b7fa75e4ac2c96ee4b69504977a0b7788d
4
+ data.tar.gz: 39d5c7e514dc44ac58d2c81a0ee86bb068713945f8855893edc46007058efdea
5
+ SHA512:
6
+ metadata.gz: 2a7cbd567d6e67ed5369ddbd795400770cac507d126d8d53413ffb184a5bc76349bb91045d3b754534dca91efb7653bb117c330f676985a36d70a69f73f71781
7
+ data.tar.gz: 583a95bb0182f3edb0cf20989ea7287d11717e5f12e5c1af4016a0ed1098c536f65fb7f337e0bd17ff6dc77f93bcc31574e8ec810821ebaf77c09222f02e17f1
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 WhiteBox (Spaceman Tech LLC)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,125 @@
1
+ # WhiteBox Ruby SDK
2
+
3
+ Official Ruby client for [WhiteBox](https://whiteboxhq.ai) -- AI Decision Observability.
4
+
5
+ Run every AI classification through multiple models. Measure agreement. Ship with confidence or escalate to a human.
6
+
7
+ ## Install
8
+
9
+ ```
10
+ gem install whitebox
11
+ ```
12
+
13
+ Or add to your Gemfile:
14
+
15
+ ```ruby
16
+ gem "whitebox"
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ```ruby
22
+ require "whitebox"
23
+
24
+ wb = Whitebox::Client.new(api_key: "wb_live_...")
25
+
26
+ # Classify something
27
+ result = wb.decide(
28
+ input: "dish soap, lemon scent, 750ml",
29
+ options: ["personal_care", "household_cleaning", "kitchen", "grocery"],
30
+ prompt: "Classify the product into one category"
31
+ )
32
+
33
+ puts result.value # "household_cleaning"
34
+ puts result.confidence # 0.94
35
+ puts result.verdict # "ship"
36
+ puts result.runs.size # 7
37
+ ```
38
+
39
+ ## Fast Mode
40
+
41
+ 3 runs, 2 models, sub-second latency:
42
+
43
+ ```ruby
44
+ result = wb.decide_fast(
45
+ input: "my card was charged twice",
46
+ options: ["billing", "fraud", "shipping", "general"]
47
+ )
48
+ ```
49
+
50
+ ## Custom Models
51
+
52
+ Pick which models vote on your decision:
53
+
54
+ ```ruby
55
+ result = wb.decide(
56
+ input: "suspicious login from new device",
57
+ options: ["legitimate", "suspicious", "fraudulent"],
58
+ models: ["openai/gpt-4o", "anthropic/claude-sonnet-4", "mistralai/mistral-large"],
59
+ runs: 9
60
+ )
61
+ ```
62
+
63
+ ## Bulk Decisions
64
+
65
+ Submit up to 100 items at once:
66
+
67
+ ```ruby
68
+ batch = wb.decide_bulk(
69
+ items: [
70
+ { input: "dish soap, lemon, 750ml" },
71
+ { input: "rose hand cream, 100ml" },
72
+ { input: "bamboo cutting board" },
73
+ ],
74
+ options: ["personal_care", "household_cleaning", "kitchen"],
75
+ webhook_url: "https://yourapp.com/webhooks/whitebox"
76
+ )
77
+
78
+ puts batch.id # UUID
79
+ puts batch.status # "processing"
80
+ puts batch.total # 3
81
+
82
+ # Poll for results
83
+ loop do
84
+ b = wb.get_batch(batch.id)
85
+ break if b.complete?
86
+ sleep 2
87
+ end
88
+
89
+ results = wb.get_batch_results(batch.id)
90
+ ```
91
+
92
+ ## Human Review
93
+
94
+ List and resolve escalated decisions:
95
+
96
+ ```ruby
97
+ reviews = wb.list_reviews
98
+ reviews.each do |r|
99
+ puts "#{r.input} -- confidence: #{r.confidence}"
100
+ end
101
+
102
+ wb.resolve_review(reviews.first.id, answer: "household_cleaning")
103
+ ```
104
+
105
+ ## Error Handling
106
+
107
+ ```ruby
108
+ begin
109
+ wb.decide(input: "test", options: ["a", "b"])
110
+ rescue Whitebox::AuthenticationError
111
+ puts "Bad API key"
112
+ rescue Whitebox::InsufficientCreditsError
113
+ puts "Buy more credits"
114
+ rescue Whitebox::RateLimitError => e
115
+ puts "Rate limited, retry in #{e.retry_after}s"
116
+ rescue Whitebox::Error => e
117
+ puts "Error: #{e.message} (#{e.status_code})"
118
+ end
119
+ ```
120
+
121
+ ## Links
122
+
123
+ - Documentation: https://whiteboxhq.ai/api-docs
124
+ - Dashboard: https://whiteboxhq.ai/dashboard
125
+ - Supported models: https://whiteboxhq.ai/api/v1/models
@@ -0,0 +1,155 @@
1
+ require "net/http"
2
+ require "json"
3
+ require "uri"
4
+
5
+ module Whitebox
6
+ class Client
7
+ BASE_URL = "https://whiteboxhq.ai/api/v1"
8
+
9
+ def initialize(api_key:, base_url: nil, timeout: 30)
10
+ @api_key = api_key
11
+ @base_url = base_url || BASE_URL
12
+ @timeout = timeout
13
+ end
14
+
15
+ # Single decision
16
+ def decide(input:, options:, prompt: nil, runs: 7, threshold: 0.75, sync: true, mode: "standard", models: nil)
17
+ body = {
18
+ input: input,
19
+ options: options,
20
+ prompt: prompt,
21
+ runs: runs,
22
+ threshold: threshold,
23
+ sync: sync,
24
+ mode: mode,
25
+ }
26
+ body[:models] = models if models
27
+
28
+ data = request(:post, "/decide", body)
29
+ Decision.from_hash(data)
30
+ end
31
+
32
+ # Fast mode: 3 runs, 2 models, sync
33
+ def decide_fast(input:, options:, prompt: nil, threshold: 0.75)
34
+ decide(input: input, options: options, prompt: prompt, threshold: threshold, mode: "fast", sync: true)
35
+ end
36
+
37
+ # Bulk decisions
38
+ def decide_bulk(items:, prompt: nil, options: nil, runs: 7, threshold: 0.75, webhook_url: nil)
39
+ body = {
40
+ items: items,
41
+ prompt: prompt,
42
+ options: options,
43
+ runs: runs,
44
+ threshold: threshold,
45
+ webhook_url: webhook_url,
46
+ }.compact
47
+
48
+ data = request(:post, "/decide/bulk", body)
49
+ Batch.from_hash(data)
50
+ end
51
+
52
+ # Get a single decision
53
+ def get_decision(id)
54
+ data = request(:get, "/decisions/#{id}")
55
+ Decision.from_hash(data)
56
+ end
57
+
58
+ # List decisions
59
+ def list_decisions(page: 1, per_page: 20)
60
+ data = request(:get, "/decisions?page=#{page}&per_page=#{per_page}")
61
+ (data["decisions"] || []).map { |d| Decision.from_hash(d) }
62
+ end
63
+
64
+ # Get batch status
65
+ def get_batch(id)
66
+ data = request(:get, "/batches/#{id}")
67
+ Batch.from_hash(data)
68
+ end
69
+
70
+ # Get batch results
71
+ def get_batch_results(id)
72
+ request(:get, "/batches/#{id}/results")
73
+ end
74
+
75
+ # List pending reviews
76
+ def list_reviews
77
+ data = request(:get, "/reviews")
78
+ (data.is_a?(Array) ? data : []).map { |r| Review.from_hash(r) }
79
+ end
80
+
81
+ # Resolve a review
82
+ def resolve_review(id, answer:)
83
+ data = request(:patch, "/reviews/#{id}", { answer: answer })
84
+ Review.from_hash(data)
85
+ end
86
+
87
+ # List supported models
88
+ def models
89
+ request(:get, "/models")
90
+ end
91
+
92
+ private
93
+
94
+ def request(method, path, body = nil)
95
+ uri = URI("#{@base_url}#{path}")
96
+
97
+ http = Net::HTTP.new(uri.host, uri.port)
98
+ http.use_ssl = uri.scheme == "https"
99
+ http.read_timeout = @timeout
100
+ http.open_timeout = 10
101
+
102
+ req = case method
103
+ when :get then Net::HTTP::Get.new(uri)
104
+ when :post then Net::HTTP::Post.new(uri)
105
+ when :patch then Net::HTTP::Patch.new(uri)
106
+ when :delete then Net::HTTP::Delete.new(uri)
107
+ end
108
+
109
+ req["Authorization"] = "Bearer #{@api_key}"
110
+ req["Content-Type"] = "application/json"
111
+ req["Accept"] = "application/json"
112
+
113
+ if body
114
+ req.body = JSON.generate(body)
115
+ end
116
+
117
+ response = http.request(req)
118
+ handle_response(response)
119
+ end
120
+
121
+ def handle_response(response)
122
+ body = begin
123
+ JSON.parse(response.body)
124
+ rescue JSON::ParserError
125
+ { "error" => response.body }
126
+ end
127
+
128
+ case response.code.to_i
129
+ when 200..299
130
+ body
131
+ when 401
132
+ raise AuthenticationError.new(
133
+ body["message"] || "Unauthorized",
134
+ status_code: 401, response: body
135
+ )
136
+ when 402
137
+ raise InsufficientCreditsError.new(
138
+ body["error"] || "Insufficient credits",
139
+ status_code: 402, response: body
140
+ )
141
+ when 429
142
+ raise RateLimitError.new(
143
+ body["message"] || "Rate limited",
144
+ status_code: 429, response: body,
145
+ retry_after: response["Retry-After"]&.to_i
146
+ )
147
+ else
148
+ raise Error.new(
149
+ body["error"] || body["message"] || "Request failed (#{response.code})",
150
+ status_code: response.code.to_i, response: body
151
+ )
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,23 @@
1
+ module Whitebox
2
+ class Error < StandardError
3
+ attr_reader :status_code, :response
4
+
5
+ def initialize(message, status_code: nil, response: nil)
6
+ super(message)
7
+ @status_code = status_code
8
+ @response = response
9
+ end
10
+ end
11
+
12
+ class AuthenticationError < Error; end
13
+ class InsufficientCreditsError < Error; end
14
+
15
+ class RateLimitError < Error
16
+ attr_reader :retry_after
17
+
18
+ def initialize(message, retry_after: nil, **kwargs)
19
+ super(message, **kwargs)
20
+ @retry_after = retry_after
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,67 @@
1
+ module Whitebox
2
+ Run = Data.define(:model, :answer, :logprob, :latency_ms) do
3
+ def self.from_hash(h)
4
+ new(
5
+ model: h["model"],
6
+ answer: h["answer"],
7
+ logprob: h["logprob"],
8
+ latency_ms: h["latency_ms"]
9
+ )
10
+ end
11
+ end
12
+
13
+ Decision = Data.define(:id, :status, :value, :confidence, :verdict, :escalated, :runs, :latency_ms, :cost_usd, :created_at, :mode) do
14
+ def self.from_hash(h)
15
+ new(
16
+ id: h["id"],
17
+ status: h["status"],
18
+ value: h["value"],
19
+ confidence: h["confidence"],
20
+ verdict: h["verdict"],
21
+ escalated: h["escalated"],
22
+ runs: (h["runs"] || []).map { |r| Run.from_hash(r) },
23
+ latency_ms: h["latency_ms"],
24
+ cost_usd: h["cost_usd"],
25
+ created_at: h["created_at"],
26
+ mode: h["mode"]
27
+ )
28
+ end
29
+
30
+ def shipped? = verdict == "ship"
31
+ def escalated? = escalated == true
32
+ end
33
+
34
+ Batch = Data.define(:id, :status, :total, :completed, :failed, :progress, :webhook_url, :completed_at, :created_at) do
35
+ def self.from_hash(h)
36
+ new(
37
+ id: h["id"],
38
+ status: h["status"],
39
+ total: h["total"],
40
+ completed: h["completed"],
41
+ failed: h["failed"],
42
+ progress: h["progress"],
43
+ webhook_url: h["webhook_url"],
44
+ completed_at: h["completed_at"],
45
+ created_at: h["created_at"]
46
+ )
47
+ end
48
+
49
+ def complete? = status == "complete"
50
+ end
51
+
52
+ Review = Data.define(:id, :decision_id, :status, :input, :options, :model_votes, :confidence, :sla_deadline, :created_at) do
53
+ def self.from_hash(h)
54
+ new(
55
+ id: h["id"],
56
+ decision_id: h["decision_id"],
57
+ status: h["status"],
58
+ input: h["input"],
59
+ options: h["options"],
60
+ model_votes: h["model_votes"],
61
+ confidence: h["confidence"],
62
+ sla_deadline: h["sla_deadline"],
63
+ created_at: h["created_at"]
64
+ )
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,3 @@
1
+ module Whitebox
2
+ VERSION = "0.1.0"
3
+ end
data/lib/whitebox.rb ADDED
@@ -0,0 +1,7 @@
1
+ require_relative "whitebox/version"
2
+ require_relative "whitebox/errors"
3
+ require_relative "whitebox/models"
4
+ require_relative "whitebox/client"
5
+
6
+ module Whitebox
7
+ end
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: whitebox
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - WhiteBox
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-04-28 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Run every AI classification through multiple models. Measure agreement.
14
+ Ship with confidence or escalate to a human.
15
+ email:
16
+ - hello@whiteboxhq.ai
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - LICENSE
22
+ - README.md
23
+ - lib/whitebox.rb
24
+ - lib/whitebox/client.rb
25
+ - lib/whitebox/errors.rb
26
+ - lib/whitebox/models.rb
27
+ - lib/whitebox/version.rb
28
+ homepage: https://whiteboxhq.ai
29
+ licenses:
30
+ - MIT
31
+ metadata:
32
+ homepage_uri: https://whiteboxhq.ai
33
+ source_code_uri: https://github.com/ja-roque/whitebox
34
+ changelog_uri: https://github.com/ja-roque/whitebox/blob/main/CHANGELOG.md
35
+ post_install_message:
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '3.0'
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubygems_version: 3.5.9
51
+ signing_key:
52
+ specification_version: 4
53
+ summary: WhiteBox SDK - AI Decision Observability
54
+ test_files: []