arbiter-gem 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 +7 -0
- data/CHANGELOG.md +6 -0
- data/LICENSE +21 -0
- data/README.md +109 -0
- data/lib/arbiter/client.rb +116 -0
- data/lib/arbiter/errors.rb +23 -0
- data/lib/arbiter/flow.rb +54 -0
- data/lib/arbiter/ranking.rb +65 -0
- data/lib/arbiter/version.rb +4 -0
- data/lib/arbiter.rb +41 -0
- metadata +49 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c1390ac17a355433c6f9e2f9f7a47e6b9b8a8ce166c8c37b1389e1950eb1696a
|
|
4
|
+
data.tar.gz: 7ef97bb093b05948eeed2746e1d30fc3df630b6bd13eea53d2f163898003d4eb
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a0a0a9382852eb361ca410cfe5a8aa28db293b2a052d490a7e64da5c34338ff30b33bafa0d3efa8b0d30e2389c0be0ab545d7034f40e6cb55269f9032aa0f108
|
|
7
|
+
data.tar.gz: a52e0d340a0a267cb354c18dccf7a91c59e6b1eaefc861632317d48a7f24676db0a1cdee576d755cf2dae0a58398f91eeb3c0385fb77c5defbf7a8e8dd22eb95
|
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Actual General Intelligence, Inc.
|
|
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,109 @@
|
|
|
1
|
+
# ARBITER for Ruby
|
|
2
|
+
|
|
3
|
+
**Gem:** `arbiter-gem`
|
|
4
|
+
**Ruby module:** `Arbiter`
|
|
5
|
+
**Concept:** The Last Conditional — measure a finite permitted action field instead of extending contextual branching logic.
|
|
6
|
+
|
|
7
|
+
Deterministic remote ARBITER measurement in ordinary Ruby applications. No native engine, CLI installation, model weights, or Regolith required.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
gem install arbiter-gem
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Gemfile:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
gem "arbiter-gem"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Use
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
require "arbiter"
|
|
25
|
+
|
|
26
|
+
result = Arbiter.compare(
|
|
27
|
+
query: "Resolve a failed payment",
|
|
28
|
+
candidates: ["Retry", "Escalate", "Notify"],
|
|
29
|
+
top_k: 3
|
|
30
|
+
)
|
|
31
|
+
puts result # raw ARBITER JSON object
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The `compare` call returns the raw decoded API JSON object. To obtain a normalized ranking for documented formats, use `Arbiter.rank(...)`. Unknown response formats raise `Arbiter::InvalidResponse` rather than invent a winner.
|
|
35
|
+
|
|
36
|
+
Embeddings:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
response = Arbiter.embed(texts: ["one", "two"])
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### The Last Conditional
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
permitted = ->(name, _action) { name != :retry || payment.retry_allowed? }
|
|
46
|
+
|
|
47
|
+
actions = {
|
|
48
|
+
retry: { description: "Retry the payment", run: -> { retry_payment } },
|
|
49
|
+
escalate: { description: "Escalate to billing", run: -> { escalate_payment } },
|
|
50
|
+
notify: { description: "Notify customer", run: -> { notify_customer } }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# Measurement only — never runs Ruby callables:
|
|
54
|
+
choice = Arbiter.decide(intent: "Resolve the payment", actions: actions, permitted: permitted)
|
|
55
|
+
puts choice[:action]
|
|
56
|
+
|
|
57
|
+
# Opt-in execution with permission revalidated immediately before invocation:
|
|
58
|
+
# Arbiter.dispatch(intent: "Resolve the payment", actions: actions, permitted: permitted)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Authority is external to measurement.** Filter prohibited actions first. Hard policy rules, authorization, cryptographic checks and transactional safety still belong to the application. `dispatch` never executes unknown actions returned by the API. Both the input field and the selected action are checked.
|
|
62
|
+
|
|
63
|
+
## Configuration
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
export ARBITER_API_KEY="<credit-api-key>"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`GRIP_API_KEY` is also recognized. If the GRIP API uses a different header for credits, adjust the `Client` configuration before publishing; the currently implemented key header is `Authorization: Bearer` and has not been live-verified against the credit-pack gateway.
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
Arbiter.configure(
|
|
73
|
+
api_key: ENV.fetch("ARBITER_API_KEY"),
|
|
74
|
+
base_url: "https://api.grip.fyi"
|
|
75
|
+
)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Endpoints: `POST /v1/compare` with `query`, `candidates`, optional `top_k`; `POST /v1/embed` with `texts`.
|
|
79
|
+
|
|
80
|
+
### HTTP 402
|
|
81
|
+
|
|
82
|
+
The ARBITER service may request x402 payment. This client never signs wallet transactions or spends automatically:
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
begin
|
|
86
|
+
Arbiter.compare(query: "...", candidates: ["A", "B"])
|
|
87
|
+
rescue Arbiter::PaymentRequired => e
|
|
88
|
+
puts "HTTP #{e.status} — payment required"
|
|
89
|
+
# e.challenge contains payment-related response headers, if supplied
|
|
90
|
+
end
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Use funded API-key credits if supported by the deployment, or integrate a separately authorized wallet payment mechanism. RubyGems publication credentials **are unrelated** to API credits.
|
|
94
|
+
|
|
95
|
+
## Testing
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
ruby -Ilib:test test/test_arbiter.rb
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Publishing
|
|
102
|
+
|
|
103
|
+
Run `./PUBLISH.command` on a machine already authenticated to RubyGems. It performs tests, builds the gem, then calls `gem push` using the existing RubyGems credential store. It never copies, prints, or packages credentials. Two-factor authentication may still require an OTP.
|
|
104
|
+
|
|
105
|
+
## Scope
|
|
106
|
+
|
|
107
|
+
Version 0.1.0 is a release candidate tested against stubbed HTTP responses. Before public release, validate the live API response schema, credit-key header, and Gem name availability. No x402 wallet signer is bundled.
|
|
108
|
+
|
|
109
|
+
MIT © 2026 Actual General Intelligence, Inc.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "json"
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "uri"
|
|
5
|
+
|
|
6
|
+
module Arbiter
|
|
7
|
+
class Client
|
|
8
|
+
DEFAULT_BASE_URL = "https://api.grip.fyi".freeze
|
|
9
|
+
attr_reader :base_url
|
|
10
|
+
|
|
11
|
+
def initialize(api_key: ENV["ARBITER_API_KEY"] || ENV["GRIP_API_KEY"],
|
|
12
|
+
base_url: ENV.fetch("ARBITER_BASE_URL", DEFAULT_BASE_URL),
|
|
13
|
+
open_timeout: 5, read_timeout: 30, transport: nil)
|
|
14
|
+
@api_key = api_key
|
|
15
|
+
@base_url = base_url.to_s.sub(%r{/+$}, "")
|
|
16
|
+
@open_timeout = open_timeout
|
|
17
|
+
@read_timeout = read_timeout
|
|
18
|
+
@transport = transport
|
|
19
|
+
validate_url!
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def compare(query:, candidates:, top_k: nil)
|
|
23
|
+
unless query.is_a?(String) && !query.strip.empty?
|
|
24
|
+
raise InvalidInput, "query must be a nonempty String"
|
|
25
|
+
end
|
|
26
|
+
unless candidates.is_a?(Array) && candidates.length >= 2 &&
|
|
27
|
+
candidates.all? { |c| c.is_a?(String) && !c.strip.empty? }
|
|
28
|
+
raise InvalidInput, "candidates must be an Array of at least two nonempty Strings"
|
|
29
|
+
end
|
|
30
|
+
payload = { query: query, candidates: candidates }
|
|
31
|
+
unless top_k.nil?
|
|
32
|
+
unless top_k.is_a?(Integer) && top_k.between?(1, candidates.length)
|
|
33
|
+
raise InvalidInput, "top_k must be an Integer between 1 and candidate count"
|
|
34
|
+
end
|
|
35
|
+
payload[:top_k] = top_k
|
|
36
|
+
end
|
|
37
|
+
request("/v1/compare", payload)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def embed(texts:)
|
|
41
|
+
unless texts.is_a?(Array) && !texts.empty? &&
|
|
42
|
+
texts.all? { |t| t.is_a?(String) && !t.strip.empty? }
|
|
43
|
+
raise InvalidInput, "texts must be a nonempty Array of nonempty Strings"
|
|
44
|
+
end
|
|
45
|
+
request("/v1/embed", { texts: texts })
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def validate_url!
|
|
51
|
+
uri = URI(@base_url)
|
|
52
|
+
ok = uri.scheme == "https" && uri.host && !uri.host.empty? &&
|
|
53
|
+
uri.userinfo.nil? && uri.query.nil? && uri.fragment.nil?
|
|
54
|
+
# Plain HTTP may be used only for loopback development/test endpoints.
|
|
55
|
+
ok ||= uri.scheme == "http" && ["localhost", "127.0.0.1", "::1"].include?(uri.host) &&
|
|
56
|
+
uri.userinfo.nil? && uri.query.nil? && uri.fragment.nil?
|
|
57
|
+
raise InvalidInput, "base_url requires HTTPS (or loopback HTTP)" unless ok
|
|
58
|
+
rescue URI::InvalidURIError
|
|
59
|
+
raise InvalidInput, "base_url is invalid"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def request(path, payload)
|
|
63
|
+
uri = URI("#{@base_url}#{path}")
|
|
64
|
+
headers = { "content-type" => "application/json", "accept" => "application/json" }
|
|
65
|
+
headers["authorization"] = "Bearer #{@api_key}" if @api_key && !@api_key.empty?
|
|
66
|
+
body = JSON.generate(payload)
|
|
67
|
+
response = if @transport
|
|
68
|
+
@transport.call(uri, headers, body)
|
|
69
|
+
else
|
|
70
|
+
http_post(uri, headers, body)
|
|
71
|
+
end
|
|
72
|
+
status = response.code.to_i
|
|
73
|
+
case status
|
|
74
|
+
when 200..299
|
|
75
|
+
parsed = parse_json(response.body)
|
|
76
|
+
raise InvalidResponse, "ARBITER response must contain a JSON object" unless parsed.is_a?(Hash)
|
|
77
|
+
parsed
|
|
78
|
+
when 402
|
|
79
|
+
# Do not auto-sign or spend funds. Preserve challenge for a caller-controlled payment flow.
|
|
80
|
+
challenge = response.each_header.to_h.select { |key, _| key.match?(/payment|x402/i) }
|
|
81
|
+
raise PaymentRequired.new("ARBITER returned HTTP 402; configure credits or handle the payment challenge", challenge: challenge)
|
|
82
|
+
when 401, 403
|
|
83
|
+
raise Unauthorized, "ARBITER authentication or permission failed (HTTP #{status})"
|
|
84
|
+
else
|
|
85
|
+
raise HTTPError.new(status, "ARBITER HTTP #{status}: #{safe_detail(response.body)}")
|
|
86
|
+
end
|
|
87
|
+
rescue JSON::ParserError
|
|
88
|
+
raise InvalidResponse, "ARBITER returned invalid JSON"
|
|
89
|
+
rescue Net::OpenTimeout, Net::ReadTimeout, SocketError, SystemCallError => e
|
|
90
|
+
raise HTTPError.new(0, "ARBITER transport error (#{e.class})")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def http_post(uri, headers, body)
|
|
94
|
+
req = Net::HTTP::Post.new(uri)
|
|
95
|
+
headers.each { |key, value| req[key] = value }
|
|
96
|
+
req.body = body
|
|
97
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https",
|
|
98
|
+
open_timeout: @open_timeout, read_timeout: @read_timeout) do |http|
|
|
99
|
+
http.request(req)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def parse_json(body)
|
|
104
|
+
JSON.parse(body.to_s)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def safe_detail(body)
|
|
108
|
+
obj = JSON.parse(body.to_s)
|
|
109
|
+
return "request failed" unless obj.is_a?(Hash)
|
|
110
|
+
detail = obj["error"] || obj["message"] || "request failed"
|
|
111
|
+
detail.is_a?(String) ? detail[0, 250] : "request failed"
|
|
112
|
+
rescue JSON::ParserError
|
|
113
|
+
"request failed"
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Arbiter
|
|
3
|
+
class Error < StandardError; end
|
|
4
|
+
class InvalidInput < Error; end
|
|
5
|
+
class InvalidResponse < Error; end
|
|
6
|
+
class Unauthorized < Error; end
|
|
7
|
+
class PaymentRequired < Error
|
|
8
|
+
attr_reader :status, :challenge
|
|
9
|
+
def initialize(message = "ARBITER payment required", challenge: {})
|
|
10
|
+
@status = 402
|
|
11
|
+
@challenge = challenge.freeze
|
|
12
|
+
super(message)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
class HTTPError < Error
|
|
16
|
+
attr_reader :status
|
|
17
|
+
def initialize(status, message)
|
|
18
|
+
@status = status
|
|
19
|
+
super(message)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
class ActionNotPermitted < Error; end
|
|
23
|
+
end
|
data/lib/arbiter/flow.rb
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Arbiter
|
|
3
|
+
module Flow
|
|
4
|
+
module_function
|
|
5
|
+
|
|
6
|
+
# The caller supplies only authorized actions. A separate recheck runs just
|
|
7
|
+
# before execution to catch changed permissions after measurement.
|
|
8
|
+
#
|
|
9
|
+
# actions: { retry: { description: 'Retry card', run: -> { ... } }, ... }
|
|
10
|
+
# permitted: optional ->(name, action) { ... } policy callback
|
|
11
|
+
def decide(intent:, actions:, client: Arbiter.client, permitted: nil)
|
|
12
|
+
approved = validate_actions(actions, permitted)
|
|
13
|
+
raise ActionNotPermitted, "at least two permitted actions required" if approved.length < 2
|
|
14
|
+
descriptions = approved.values.map { |action| action.fetch(:description) }
|
|
15
|
+
if descriptions.uniq.length != descriptions.length
|
|
16
|
+
raise InvalidInput, "action descriptions must be unique"
|
|
17
|
+
end
|
|
18
|
+
raw = client.compare(query: intent, candidates: descriptions, top_k: approved.length)
|
|
19
|
+
ranking = Ranking.extract(raw, descriptions)
|
|
20
|
+
raise InvalidResponse, "ARBITER did not return a ranked candidate" if ranking.empty?
|
|
21
|
+
id = approved.keys.fetch(ranking.first.fetch(:index))
|
|
22
|
+
{ action: id, ranking: ranking, raw: raw, permitted_actions: approved.keys }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def dispatch(intent:, actions:, client: Arbiter.client, permitted: nil)
|
|
26
|
+
decision = decide(intent: intent, actions: actions, client: client, permitted: permitted)
|
|
27
|
+
name = decision.fetch(:action)
|
|
28
|
+
action = actions.fetch(name)
|
|
29
|
+
# Revalidation at execution boundary. The caller's policy remains authoritative.
|
|
30
|
+
if permitted && !permitted.call(name, action)
|
|
31
|
+
raise ActionNotPermitted, "selected action is no longer permitted"
|
|
32
|
+
end
|
|
33
|
+
result = action.fetch(:run).call
|
|
34
|
+
decision.merge(executed: true, value: result)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def validate_actions(actions, permitted)
|
|
38
|
+
raise InvalidInput, "actions must be a Hash" unless actions.is_a?(Hash) && !actions.empty?
|
|
39
|
+
unless permitted.nil? || permitted.respond_to?(:call)
|
|
40
|
+
raise InvalidInput, "permitted must be a callable"
|
|
41
|
+
end
|
|
42
|
+
approved = {}
|
|
43
|
+
actions.each do |name, action|
|
|
44
|
+
unless (name.is_a?(Symbol) || name.is_a?(String)) && action.is_a?(Hash) &&
|
|
45
|
+
action[:description].is_a?(String) && !action[:description].strip.empty? &&
|
|
46
|
+
action[:run].respond_to?(:call)
|
|
47
|
+
raise InvalidInput, "each action needs an id, description, and callable :run"
|
|
48
|
+
end
|
|
49
|
+
approved[name] = action if !permitted || permitted.call(name, action)
|
|
50
|
+
end
|
|
51
|
+
approved
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Arbiter
|
|
3
|
+
# Normalizes only unambiguous score/ranking data. Unknown response formats raise.
|
|
4
|
+
# Raw compare() responses remain available without normalization.
|
|
5
|
+
module Ranking
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def extract(response, candidates)
|
|
9
|
+
raise InvalidResponse, "response must be a Hash" unless response.is_a?(Hash)
|
|
10
|
+
raise InvalidInput, "candidate strings must be unique for ranking" unless candidates.uniq.length == candidates.length
|
|
11
|
+
|
|
12
|
+
# Some integrations wrap the API body in { "result" => {...} }.
|
|
13
|
+
body = response["result"].is_a?(Hash) ? response["result"] : response
|
|
14
|
+
|
|
15
|
+
["results", "ranking", "ranked", "ranked_candidates", "matches"].each do |field|
|
|
16
|
+
next unless body[field].is_a?(Array)
|
|
17
|
+
ranking = from_items(body[field], candidates)
|
|
18
|
+
return ranking if ranking
|
|
19
|
+
end
|
|
20
|
+
if body["scores"].is_a?(Array) && body["scores"].length == candidates.length &&
|
|
21
|
+
body["scores"].all? { |score| numeric?(score) }
|
|
22
|
+
return candidates.each_with_index.map { |candidate, i| row(candidate, i, Float(body["scores"][i])) }
|
|
23
|
+
.sort_by { |item| [-item[:score], item[:index]] }
|
|
24
|
+
end
|
|
25
|
+
raise InvalidResponse, "unrecognized ARBITER ranking response; inspect raw compare() result"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def from_items(items, candidates)
|
|
29
|
+
return nil if items.empty?
|
|
30
|
+
used = {}
|
|
31
|
+
rows = items.map do |item|
|
|
32
|
+
idx, score = case item
|
|
33
|
+
when Hash
|
|
34
|
+
index = item["index"] || item["candidate_index"]
|
|
35
|
+
label = item["candidate"] || item["text"] || item["value"]
|
|
36
|
+
index = candidates.index(label) if index.nil? && label.is_a?(String)
|
|
37
|
+
[index, item["score"] || item["resonance"]]
|
|
38
|
+
when String
|
|
39
|
+
[candidates.index(item), nil]
|
|
40
|
+
when Integer
|
|
41
|
+
[item, nil]
|
|
42
|
+
else
|
|
43
|
+
return nil
|
|
44
|
+
end
|
|
45
|
+
return nil unless idx.is_a?(Integer) && idx.between?(0, candidates.length - 1)
|
|
46
|
+
return nil if used[idx]
|
|
47
|
+
return nil unless score.nil? || numeric?(score)
|
|
48
|
+
used[idx] = true
|
|
49
|
+
row(candidates[idx], idx, score.nil? ? nil : Float(score))
|
|
50
|
+
end
|
|
51
|
+
# Prefer API order if it supplies ordered items; never re-sort returned rankings.
|
|
52
|
+
rows
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def row(candidate, index, score)
|
|
56
|
+
{ candidate: candidate, index: index, score: score }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def numeric?(value)
|
|
60
|
+
(value.is_a?(Numeric) || value.is_a?(String)) && Float(value).finite?
|
|
61
|
+
rescue ArgumentError, TypeError
|
|
62
|
+
false
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
data/lib/arbiter.rb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative "arbiter/version"
|
|
3
|
+
require_relative "arbiter/errors"
|
|
4
|
+
require_relative "arbiter/client"
|
|
5
|
+
require_relative "arbiter/ranking"
|
|
6
|
+
require_relative "arbiter/flow"
|
|
7
|
+
|
|
8
|
+
module Arbiter
|
|
9
|
+
class << self
|
|
10
|
+
attr_writer :client
|
|
11
|
+
|
|
12
|
+
def client
|
|
13
|
+
@client ||= Client.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def configure(**settings)
|
|
17
|
+
self.client = Client.new(**settings)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def compare(**args)
|
|
21
|
+
client.compare(**args)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def embed(**args)
|
|
25
|
+
client.embed(**args)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def rank(query:, candidates:, top_k: nil)
|
|
29
|
+
response = compare(query: query, candidates: candidates, top_k: top_k)
|
|
30
|
+
Ranking.extract(response, candidates)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def decide(**args)
|
|
34
|
+
Flow.decide(**args)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def dispatch(**args)
|
|
38
|
+
Flow.dispatch(**args)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: arbiter-gem
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Actual General Intelligence, Inc.
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: Ruby client for ARBITER compare and embed, plus explicit finite-action
|
|
13
|
+
dispatch. Remote measurement, no local engine or binary.
|
|
14
|
+
executables: []
|
|
15
|
+
extensions: []
|
|
16
|
+
extra_rdoc_files: []
|
|
17
|
+
files:
|
|
18
|
+
- CHANGELOG.md
|
|
19
|
+
- LICENSE
|
|
20
|
+
- README.md
|
|
21
|
+
- lib/arbiter.rb
|
|
22
|
+
- lib/arbiter/client.rb
|
|
23
|
+
- lib/arbiter/errors.rb
|
|
24
|
+
- lib/arbiter/flow.rb
|
|
25
|
+
- lib/arbiter/ranking.rb
|
|
26
|
+
- lib/arbiter/version.rb
|
|
27
|
+
homepage: https://arbiter.grip.fyi
|
|
28
|
+
licenses:
|
|
29
|
+
- MIT
|
|
30
|
+
metadata:
|
|
31
|
+
homepage_uri: https://arbiter.grip.fyi
|
|
32
|
+
rdoc_options: []
|
|
33
|
+
require_paths:
|
|
34
|
+
- lib
|
|
35
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '3.1'
|
|
40
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
41
|
+
requirements:
|
|
42
|
+
- - ">="
|
|
43
|
+
- !ruby/object:Gem::Version
|
|
44
|
+
version: '0'
|
|
45
|
+
requirements: []
|
|
46
|
+
rubygems_version: 4.0.3
|
|
47
|
+
specification_version: 4
|
|
48
|
+
summary: ARBITER deterministic measurement for Ruby decisions and control flow
|
|
49
|
+
test_files: []
|