bynn-decisionnode 0.4.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 +7 -0
- data/CHANGELOG.md +128 -0
- data/LICENSE +21 -0
- data/README.md +908 -0
- data/lib/decisionnode/batches.rb +246 -0
- data/lib/decisionnode/boosters.rb +692 -0
- data/lib/decisionnode/checks.rb +722 -0
- data/lib/decisionnode/client.rb +1100 -0
- data/lib/decisionnode/collections.rb +535 -0
- data/lib/decisionnode/document.rb +179 -0
- data/lib/decisionnode/errors.rb +138 -0
- data/lib/decisionnode/image.rb +194 -0
- data/lib/decisionnode/image_header.rb +121 -0
- data/lib/decisionnode/keys.rb +383 -0
- data/lib/decisionnode/questions.rb +135 -0
- data/lib/decisionnode/results.rb +539 -0
- data/lib/decisionnode/session.rb +286 -0
- data/lib/decisionnode/session_stream.rb +255 -0
- data/lib/decisionnode/supervisor.rb +107 -0
- data/lib/decisionnode/transport.rb +249 -0
- data/lib/decisionnode/uploads.rb +422 -0
- data/lib/decisionnode/version.rb +9 -0
- data/lib/decisionnode/video.rb +128 -0
- data/lib/decisionnode.rb +39 -0
- metadata +85 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class DecisionNode
|
|
4
|
+
# One frame of a video: its time in seconds and its image.
|
|
5
|
+
#
|
|
6
|
+
# DecisionNode::VideoFrame.new(30.0, data: jpeg_bytes) # media type from the bytes
|
|
7
|
+
# DecisionNode::VideoFrame.new(30.5, path: "frames/0001.jpg")
|
|
8
|
+
# DecisionNode::VideoFrame.new(31.0, base64: "/9j/4AAQ...", media_type: "image/jpeg")
|
|
9
|
+
#
|
|
10
|
+
# Bytes are sent as base64; base64 text is sent exactly as given and needs
|
|
11
|
+
# its `media_type`. A frame is always sent inline: it is never uploaded and
|
|
12
|
+
# never counted toward the inline threshold of images. It is never resized
|
|
13
|
+
# or re-encoded.
|
|
14
|
+
#
|
|
15
|
+
# The values are checked with the request, before anything is sent: a time
|
|
16
|
+
# below 0, times that do not increase, or a media type other than
|
|
17
|
+
# image/jpeg, image/png or image/webp raise BadRequestError as the API
|
|
18
|
+
# would. A time that is not a number, or a source of the wrong type, raises
|
|
19
|
+
# ArgumentError here.
|
|
20
|
+
class VideoFrame
|
|
21
|
+
# The frame's time in seconds.
|
|
22
|
+
attr_reader :t
|
|
23
|
+
# "image/jpeg", "image/png" or "image/webp": as given, else detected from
|
|
24
|
+
# the bytes; nil when neither says.
|
|
25
|
+
attr_reader :media_type
|
|
26
|
+
# The bytes (a frozen binary String), or nil for base64 text.
|
|
27
|
+
attr_reader :data
|
|
28
|
+
|
|
29
|
+
def initialize(t, data: nil, base64: nil, path: nil, media_type: nil)
|
|
30
|
+
raise ArgumentError, "frame time t must be a number of seconds, got #{t.inspect}" unless t.is_a?(Integer) || t.is_a?(Float)
|
|
31
|
+
raise ArgumentError, "give exactly one of data:, base64: or path:" unless [data, base64, path].compact.size == 1
|
|
32
|
+
|
|
33
|
+
@t = t
|
|
34
|
+
@base64 = base64.nil? ? nil : text(base64)
|
|
35
|
+
@data = (@base64 ? nil : read_bytes(data, path)).freeze
|
|
36
|
+
declared = media_type.nil? || media_type.to_s.empty? ? nil : media_type.to_s
|
|
37
|
+
@media_type = declared || (@data && ImageHeader.media_type(@data))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# The frame's wire form: `{"t", "media_type", "data"}`.
|
|
41
|
+
def to_h
|
|
42
|
+
{ "t" => t, "media_type" => media_type, "data" => @base64 || [@data].pack("m0") }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def inspect
|
|
46
|
+
size = @base64 ? "#{@base64.bytesize} base64 characters" : "#{@data.bytesize} bytes"
|
|
47
|
+
"#<#{self.class.name} t=#{t} #{media_type || 'unknown type'} #{size}>"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def text(value)
|
|
53
|
+
raise ArgumentError, "frame base64: must be a String of base64 text" unless value.is_a?(String)
|
|
54
|
+
|
|
55
|
+
value
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def read_bytes(data, path)
|
|
59
|
+
if path
|
|
60
|
+
begin
|
|
61
|
+
File.binread(path.to_s)
|
|
62
|
+
rescue SystemCallError, IOError => e
|
|
63
|
+
raise ArgumentError, "frame at #{t}: cannot read #{path}: #{e.message}"
|
|
64
|
+
end
|
|
65
|
+
elsif data.respond_to?(:read)
|
|
66
|
+
data.read.to_s.b
|
|
67
|
+
elsif data.is_a?(String)
|
|
68
|
+
data.b
|
|
69
|
+
else
|
|
70
|
+
raise ArgumentError, "frame data: must be a String of bytes or an IO"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# A video sent as the frames the caller sampled, each with its time.
|
|
76
|
+
#
|
|
77
|
+
# frames = jpegs.each_with_index.map { |bytes, i| DecisionNode::VideoFrame.new(i * 0.5, data: bytes) }
|
|
78
|
+
# DecisionNode::Video.new("dock", frames: frames)
|
|
79
|
+
# DecisionNode::Video.new("dock", frames: frames, fps: 4, start: 30, end: 90)
|
|
80
|
+
#
|
|
81
|
+
# `fps` caps the rate the API reads (above 0, at most 4; the API's default
|
|
82
|
+
# is 2); `start` and `end` are the window read, on the frames' own times.
|
|
83
|
+
# Each is sent only when given. Only these frames are sent, never a video
|
|
84
|
+
# file, a URL or sound.
|
|
85
|
+
#
|
|
86
|
+
# The id is unique across the request's images and videos. The model sees
|
|
87
|
+
# the video as "Video <n>" (its place in the request's videos, from 1) with
|
|
88
|
+
# this id beside it, so text in the state or the instructions can refer to
|
|
89
|
+
# it by either.
|
|
90
|
+
class Video
|
|
91
|
+
attr_reader :id, :frames, :fps, :start, :end
|
|
92
|
+
|
|
93
|
+
def initialize(id, frames:, fps: nil, start: nil, end: nil)
|
|
94
|
+
last = binding.local_variable_get(:end)
|
|
95
|
+
raise ArgumentError, "frames: must be an Array of DecisionNode::VideoFrame" unless frames.is_a?(Array)
|
|
96
|
+
|
|
97
|
+
frames.each do |frame|
|
|
98
|
+
next if frame.is_a?(VideoFrame) || frame.is_a?(Hash)
|
|
99
|
+
|
|
100
|
+
raise ArgumentError, "each frame must be a DecisionNode::VideoFrame or a wire-form Hash, got #{frame.class}"
|
|
101
|
+
end
|
|
102
|
+
{ "fps" => fps, "start" => start, "end" => last }.each do |name, value|
|
|
103
|
+
next if value.nil? || value.is_a?(Integer) || value.is_a?(Float)
|
|
104
|
+
|
|
105
|
+
raise ArgumentError, "#{name} must be a number, got #{value.inspect}"
|
|
106
|
+
end
|
|
107
|
+
@id = id.is_a?(Symbol) ? id.to_s : id
|
|
108
|
+
@frames = frames.dup.freeze
|
|
109
|
+
@fps = fps
|
|
110
|
+
@start = start
|
|
111
|
+
@end = last
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# The video's wire form, `fps`, `start` and `end` only when given.
|
|
115
|
+
def to_h
|
|
116
|
+
wire = { "id" => id }
|
|
117
|
+
wire["fps"] = fps unless fps.nil?
|
|
118
|
+
wire["start"] = start unless start.nil?
|
|
119
|
+
wire["end"] = self.end unless self.end.nil?
|
|
120
|
+
wire["frames"] = frames.map { |f| f.is_a?(VideoFrame) ? f.to_h : f }
|
|
121
|
+
wire
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def inspect
|
|
125
|
+
"#<#{self.class.name} #{id.inspect} #{frames.size} frames>"
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
data/lib/decisionnode.rb
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# DecisionNode is both the client and the namespace of this gem:
|
|
4
|
+
#
|
|
5
|
+
# require "decisionnode"
|
|
6
|
+
#
|
|
7
|
+
# dn = DecisionNode.new # reads DECISIONNODE_API_KEY
|
|
8
|
+
# d = dn.decide(state: "...", questions: { "escalate" => DecisionNode.truth("Escalate?") })
|
|
9
|
+
# d["escalate"].truth
|
|
10
|
+
class DecisionNode
|
|
11
|
+
# One result of #decide_many: a Decision, or the error that request ended
|
|
12
|
+
# with. Both include this module, so `result.is_a?(DecisionNode::DecisionResult)`
|
|
13
|
+
# holds for each, and `result.is_a?(DecisionNode::Decision)` tells them apart.
|
|
14
|
+
module DecisionResult; end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
require_relative "decisionnode/version"
|
|
18
|
+
require_relative "decisionnode/errors"
|
|
19
|
+
require_relative "decisionnode/questions"
|
|
20
|
+
require_relative "decisionnode/image_header"
|
|
21
|
+
require_relative "decisionnode/image"
|
|
22
|
+
require_relative "decisionnode/document"
|
|
23
|
+
require_relative "decisionnode/video"
|
|
24
|
+
require_relative "decisionnode/boosters"
|
|
25
|
+
require_relative "decisionnode/checks"
|
|
26
|
+
require_relative "decisionnode/results"
|
|
27
|
+
require_relative "decisionnode/transport"
|
|
28
|
+
require_relative "decisionnode/uploads"
|
|
29
|
+
require_relative "decisionnode/session"
|
|
30
|
+
require_relative "decisionnode/supervisor"
|
|
31
|
+
require_relative "decisionnode/batches"
|
|
32
|
+
require_relative "decisionnode/collections"
|
|
33
|
+
require_relative "decisionnode/keys"
|
|
34
|
+
require_relative "decisionnode/client"
|
|
35
|
+
|
|
36
|
+
class DecisionNode
|
|
37
|
+
# Loaded with the WebSocket library on the first session.
|
|
38
|
+
autoload :SessionStream, File.expand_path("decisionnode/session_stream", __dir__)
|
|
39
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: bynn-decisionnode
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.4.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Bynn Intelligence, Inc.
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-10-22 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: websocket-driver
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - '='
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 0.8.2
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - '='
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 0.8.2
|
|
27
|
+
description: 'A typed client for DecisionNode, a decision engine: ask truth, choice,
|
|
28
|
+
score and number questions about any state, its images and its videos, and get calibrated
|
|
29
|
+
answers back, with image uploads, sessions, the Batch API, retries, typed errors
|
|
30
|
+
and the safety check result.'
|
|
31
|
+
email:
|
|
32
|
+
executables: []
|
|
33
|
+
extensions: []
|
|
34
|
+
extra_rdoc_files: []
|
|
35
|
+
files:
|
|
36
|
+
- CHANGELOG.md
|
|
37
|
+
- LICENSE
|
|
38
|
+
- README.md
|
|
39
|
+
- lib/decisionnode.rb
|
|
40
|
+
- lib/decisionnode/batches.rb
|
|
41
|
+
- lib/decisionnode/boosters.rb
|
|
42
|
+
- lib/decisionnode/checks.rb
|
|
43
|
+
- lib/decisionnode/client.rb
|
|
44
|
+
- lib/decisionnode/collections.rb
|
|
45
|
+
- lib/decisionnode/document.rb
|
|
46
|
+
- lib/decisionnode/errors.rb
|
|
47
|
+
- lib/decisionnode/image.rb
|
|
48
|
+
- lib/decisionnode/image_header.rb
|
|
49
|
+
- lib/decisionnode/keys.rb
|
|
50
|
+
- lib/decisionnode/questions.rb
|
|
51
|
+
- lib/decisionnode/results.rb
|
|
52
|
+
- lib/decisionnode/session.rb
|
|
53
|
+
- lib/decisionnode/session_stream.rb
|
|
54
|
+
- lib/decisionnode/supervisor.rb
|
|
55
|
+
- lib/decisionnode/transport.rb
|
|
56
|
+
- lib/decisionnode/uploads.rb
|
|
57
|
+
- lib/decisionnode/version.rb
|
|
58
|
+
- lib/decisionnode/video.rb
|
|
59
|
+
homepage: https://decisionnode.com
|
|
60
|
+
licenses:
|
|
61
|
+
- MIT
|
|
62
|
+
metadata:
|
|
63
|
+
source_code_uri: https://github.com/Bynn-Intelligence/decision-node-ruby
|
|
64
|
+
changelog_uri: https://github.com/Bynn-Intelligence/decision-node-ruby/blob/main/CHANGELOG.md
|
|
65
|
+
rubygems_mfa_required: 'true'
|
|
66
|
+
post_install_message:
|
|
67
|
+
rdoc_options: []
|
|
68
|
+
require_paths:
|
|
69
|
+
- lib
|
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '3.2'
|
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
|
+
requirements:
|
|
77
|
+
- - ">="
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: '0'
|
|
80
|
+
requirements: []
|
|
81
|
+
rubygems_version: 3.5.22
|
|
82
|
+
signing_key:
|
|
83
|
+
specification_version: 4
|
|
84
|
+
summary: Ruby client for the DecisionNode decision engine API.
|
|
85
|
+
test_files: []
|