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
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 71bfdfea0011f1259b63e6cf280a6e1f1c676491be21d0d0f8e6c965606b86e2
|
|
4
|
+
data.tar.gz: 4adfaec7bf5307ca5ee2fa8c451b0ede109f2d14f60a93d4b71724ccbdbe084c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c4644111c9b70c419dfae4ceffb1a9e6a1520de526836550fbad255edd4b14dde30c8363d00f5e007f0a7ec961bd4f8abbd8e4fc83b4a2f6c5401656c8a4e12b
|
|
7
|
+
data.tar.gz: 1428b218ef143a74fc6e0696b40a1636db65e9cb7fba978a617597ebdf68ad1542b1a251396434ef45b84c1dcceeb3c319819a9316e5174d970724eb9bf399ce
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this gem are listed here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the gem uses
|
|
5
|
+
[Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [0.4.1] - Unreleased
|
|
8
|
+
|
|
9
|
+
Implements version 1.4 of the DecisionNode SDK surface (`DecisionNode::SPEC_VERSION` is `"1.4"`): knowledge.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Knowledge bases: `dn.collections.create(name:, type: "text", scope_key:)`, a collection of type `text` holding your own documents, with an optional scope key (1 to 40 of `a-z 0-9 _`, sent only when given). A knowledge base's object (`scope_key`, `chunks`, `tokens`, `version`, `status_counts`, `pinned`) is returned as sent, and `delete` returns `{"deleted" => true}` or `{"status" => "purging"}` as sent.
|
|
14
|
+
- `DecisionNode::Document`: a text (`text:`, `format:` `"markdown"` or `"text"`), a file by `path:`, its bytes (`data:`), an `io:` read to its end once, or an `upload_id:`. A file's type comes from `content_type:`, else its name's extension, else its first bytes (PDF, PNG, JPEG, TIFF, WebP).
|
|
15
|
+
- `collections.add_document(id, document:, title:, reference:, pinned:, pin_rank:, label:, metadata:, wait:)` and `replace_document(id, item_id, document:, ...)`: a text, and a text or Markdown file under the inline threshold that is UTF-8 without a NUL, are sent inline (a file titled by its name without the extension unless `title:` is given); every other file is uploaded with the knowledge purpose (`POST /v1/uploads` with `purpose`, both checksums and the file name; one PUT of 120 seconds per attempt, or the client's `upload_timeout:`) and named by its `upload_id`. Each call sends an `Idempotency-Key`, the same on every attempt; an upload the call made that the API calls stale is uploaded again once, with a new key, while `upload_purpose_mismatch` and an upload id you gave are raised at once. `wait: true` sends `?wait=true` and waits 20 seconds per attempt unless you set `timeout:`.
|
|
16
|
+
- `update_item` takes `pinned:`, `pin_rank:` and `title:` for a document.
|
|
17
|
+
- `collections.get_item(id, item_id, include_text:)`, `list_chunks(id, item_id, offset:, limit:)`, `delete_failed(id)` (`DELETE /v1/collections/{id}/items?status=failed`, answering `{"deleted" => n}`), each returning the answer as sent.
|
|
18
|
+
- `collections.wait_until_ready(id, item_id = nil, timeout: 300, poll_interval: 1)`: reads the item until it is `ready` or `failed` (a failed item is returned, its `error` saying why), or the knowledge base until nothing is queued or processing; the wait grows by half after each read, up to 10 seconds, and `DecisionNode::TimeoutError` is raised past the timeout.
|
|
19
|
+
- `collections.search(id, query:, scope:, match:, filter:, top_k:, pinned:, max_tokens:)`, each option sent only when given, returning a `KnowledgeReading` with every passage's text.
|
|
20
|
+
- `collections.list_items` takes `q:` (1 to 200 characters, matched against a document's title, file name or reference) and `sort:` (`"newest"`, `"oldest"` or `"title"`), each sent only when given and checked before sending with the API's `BadRequestError`.
|
|
21
|
+
- `dn.upload_documents(documents)`: 1 to 16 documents uploaded ahead of time, one upload id each.
|
|
22
|
+
- The `knowledge` booster: `DecisionNode.knowledge(collection, query:, scope:, match:, filter:, top_k:, max_tokens:, pinned:, min_score:, include_text:, require:)`, each option sent only when given and `require` only when true. A request naming knowledge never takes the 30-second `ocr` timeout.
|
|
23
|
+
- `DecisionNode::KnowledgeReading` (`collection`, `collection_name`, `corpus`, `query`, `budget`, `scope`, `warnings`, `passages`, `omitted`, `pending_items`, `search`, `truncated`) with each passage a `DecisionNode::KnowledgePassage` (`n`, `pinned`, `item`, `chunk`, `byte_start`, `byte_end`, `reference`, `title`, `heading`, `tokens`, `score`, `lexical`, `rendered`, `text`). A reading or a passage with a field missing or of the wrong type is an `UnknownReading`.
|
|
24
|
+
- Checked before sending, with the API's `ValidationError` or `BadRequestError`: a collection type other than `face` or `text`, the scope key, a document's title, `pin_rank`, `pinned`, format and empty text, and a file's type (one the API takes) and size (1 to 52,428,800 bytes); a file whose type cannot be told from its name or its first bytes raises ArgumentError.
|
|
25
|
+
- The README's Knowledge section: creating a knowledge base, adding documents and waiting for them, pinning and scoping, a decision with knowledge and its citations, search, the errors and the limits.
|
|
26
|
+
- The conformance tests run suite 1.27.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- A 529 `booster_unavailable` whose `details` reason is `ingestion_delayed` is raised at once, never retried.
|
|
31
|
+
- The API's 422 with no list of entries (`invalid_text`) is a `ValidationError` whose `entries` is an empty Array.
|
|
32
|
+
|
|
33
|
+
## [0.3.0] - Unreleased
|
|
34
|
+
|
|
35
|
+
Implements version 1.3 of the DecisionNode SDK surface (`DecisionNode::SPEC_VERSION` is `"1.3"`): boosters and fine-tuned models.
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- Boosters: `boosters:` on `decide`, `check`, `classify`, `score`, `number`, `gate`, `decide_many` requests, batch requests and `check_request`, taking names (`"age"`), `DecisionNode::Booster.new(name, images:, require:, options:)` or wire-form Hashes, sent in the order given; a `Booster` is sent as `{"name" => ...}`, each argument only when given, or as the bare name when none is. Without boosters, or with an empty Array, the request has no `boosters` field. Nothing about boosters is checked before sending, except that their options count toward the nesting limit. Sessions take no boosters.
|
|
40
|
+
- `Decision#evidence`: booster name to `DecisionNode::Evidence` (`version`, `status`, `latency_ms`, `billed_calls`, `results`, `raw`) in the request's order, `nil` for a request without boosters. Readings are `AgeReading` (`image`, `faces`, `age_years`, `low`, `high`, `confidence`), `AiGeneratedReading` (`image`, `generated`, `confidence`, `method`, `top_generator` as a `TopGenerator` with `name` and `probability`, `nil` unless the reading says likely generated, `c2pa_signer`, and `c2pa`, `"untrusted"` for Content Credentials that validate but are not trusted) and, for a booster this version does not know or a reading with a field missing or of the wrong type, `UnknownReading` with its `raw` fields; a reading never fails the decision. A batch result's decision has them too.
|
|
41
|
+
- `usage.booster_tokens` and `usage.boosters`, `nil` when the response has none.
|
|
42
|
+
- `usage.cost`: what the answer cost, a `DecisionNode::Cost` (`usd`, `model_usd`, `boosters_usd`, `free_tier`), read as sent and never summed; `nil` when the response has none. Decisions, batch result lines and session frame replies carry it.
|
|
43
|
+
- `DecisionNodeError#boosters`: the `x-decisionnode-boosters` header as booster name to `DecisionNode::BoosterStatus` (`status`, `latency_ms`), in the request's order; an empty Hash when there is none.
|
|
44
|
+
- Fine-tuned models: a fine-tuned model's name (`decisionnode-moe@acme/fraud`, or pinned with `:3`) is sent as given, and `Decision#model` is the pinned name that answered. `ModelInfo#fine_tuned`, `#base`, `#adapter_version` and `#status` (and `fine_tuned?`), `nil` on a catalogue model.
|
|
45
|
+
- `RequestTooLargeError`, for the API's 413 on any call (never retried) and raised before sending when the encoded body is over 26,214,400 bytes (25 MiB), the API's limit: on `decide`, the helpers, `decide_many` (for the whole call, before anything is sent), each batch line as sent and `check_request`. An image that would be uploaded counts by its upload id, so nothing is uploaded to measure. `decide_raw` sends its body as given.
|
|
46
|
+
- Several places in one answer: `DecisionNode.point` and `DecisionNode.box` take `max:` (sent only when given), and such a question is answered with a `DecisionNode::PointsAnswer` or `DecisionNode::BoxesAnswer` (`count`, `present`, `complete`, `points` or `boxes` with their own `confidence`, `image`, `width`, `height`), told apart by the answer's own list, so batch results read the same; one with a field missing or of the wrong type is an `UnknownAnswer`. `max:` that is not an Integer from 2 to 16 raises a `ValidationError` before sending; whether a model version answers several places is the API's to say.
|
|
47
|
+
- The fallback: `fallback:` (`true` or `false`, sent only when given) on `decide`, the helpers, `decide_many` requests, batch requests and `check_request`; any other value is an `ArgumentError`. `Decision#fallback` is a `DecisionNode::Fallback` (`served_model`, `requested_model`) from the `x-decisionnode-fallback` and `x-decisionnode-requested-model` headers when another model answered in place of the one asked for, `nil` otherwise; a batch result's decision reads them from its line's headers.
|
|
48
|
+
- An image id longer than 64 characters (counted in characters, not bytes) raises `BadRequestError` (`api_usage_error`, "Image 1: its id is longer than 64 characters.") before anything is sent, after the empty-id check and before the duplicate check.
|
|
49
|
+
- Readings of the `face_detection`, `face_recognition` and `ocr` boosters: `FaceDetectionReading` (`faces`, `detections`, each a `FaceDetection` with a `FaceBox` and a `confidence`), `FaceRecognitionReading` (`faces`, `matches`, each a `FaceMatch` with a `FaceBox`, a `score`, the `collection` searched, the item's `reference`, and `name` or `label`, nil when absent) and `OcrReading` (`text`, `truncated`). Box corners are kept as sent, pixels or fractions; a reading with a field missing or of the wrong type is an `UnknownReading`.
|
|
50
|
+
- `FaceRecognitionReading#collection_name`, an own collection's display name, and on each `FaceMatch` its `collection_name` and its item's `metadata` (a Hash as sent), each nil when absent and never an empty Hash put in its place; a collection name that is not a String, or metadata that is not a Hash, makes the reading an `UnknownReading`.
|
|
51
|
+
- Collections of faces, `dn.collections`: `create(name:, type: "face")`, `list`, `get`, `delete`, `enroll(collection_id, image:, reference:, label: nil, metadata: nil)` (the image sent as a decision sends one), `update_item(collection_id, item_id, reference:, label:, metadata:)` (`PATCH` with only the fields given; `label: nil` clears the label, `metadata` replaces the whole object), `list_items(collection_id, offset:, limit:, reference:, reference_prefix:)` and `delete_item`, each returning the API's object as sent. `create`, `enroll` and `update_item` send an `Idempotency-Key`, a new UUID for each call and the same on every attempt of it, so the API creates, enrolls and bills once however often the call is tried; they retry as every call does, and also on a 409 `idempotency_in_progress` after its `Retry-After`, while a 409 `idempotency_key_reused` is raised. Reads and deletes carry no key. The name (1 to 80 characters), the reference (1 to 64 of `A-Z a-z 0-9 _ . : -`, its length checked before the pattern), the label (1 to 128 when given), the paging and an image URL's rules are checked before sending, with the API's `ValidationError` or `BadRequestError` and each input echoed as the API echoes it (a string cut to 64 characters and "...", an object or an array as nil); any other fault of the enrolled picture is the API's 422 `image_unreadable`.
|
|
52
|
+
- Item metadata, checked before sending as the API checks it: a Hash of at most 20 keys of 1 to 40 of `a-z 0-9 _`, each value a text of at most 500 characters, a number, true or false, each fault the API's `value_error` entry. `list_items` with both `reference` and `reference_prefix`, or either not 1 to 64 of `A-Z a-z 0-9 _ . : -`, raises the API's `BadRequestError`. Errors gain `details`, the body's `detail.details` when it is an object; a 529 whose `details` reason is `enrollment_unpriced` is never retried.
|
|
53
|
+
- A decision whose boosters name `ocr` waits 30 seconds per attempt when the client has no `timeout:` of its own (a timeout you set is used as given), and is never retried after a timeout, since the server finishes that attempt and may bill it. Its other retries stay.
|
|
54
|
+
- The README documents boosters, how they are billed and their errors, fine-tuned model names and the fallback.
|
|
55
|
+
- `ModelInfo#price_per_million_input`, `price_per_million_output`, `price_per_million_image`, `price_per_million_video` and `batch_price_factor`: the entry's prices as sent, each `nil` when it states none (a fine-tuned model states no `batch_price_factor`). `limits` and `raw` keep them too.
|
|
56
|
+
- Device keys, `dn.keys`, for a Manage device keys key: `create(name, device_id:, labels:, limits:, daily_spend_cap:, expires_at:, may_search_collections:, idempotency_key:)` (a Decisions-only key and its secret, with an `Idempotency-Key`: the caller's `idempotency_key:`, else a new UUID per call, the same on every attempt; retried also on a 409 `idempotency_in_progress` after its `Retry-After`), `list` and `all` (by labels, `device_id` and `status`, a page of 1 to 200), `update` (only the fields given; `nil` clears `labels`, `limits`, `daily_spend_cap` or `expires_at`), `revoke` and `revoke_by_labels(labels, device_ids:, dry_run:)`. Each returns the API's object as sent. Checked before sending, as the API answers them: a key's name (1 to 48 characters once trimmed), `device_id`, labels (at most 16), the shape of `limits` and a `daily_spend_cap` from 0.01 to 1,000 in whole cents (each the API's 422 entry, the first refused in the body's order), an update with nothing to change, a malformed `idempotency_key`, `list`'s filters and `limit`, and a bulk revoke without a label or with over 1,000 `device_ids`.
|
|
57
|
+
- Per-key usage, `dn.usage` (a `DecisionNode::KeyUsage`): `key(key_id, start:, end:, granularity:, group_by:)` (`GET /v1/keys/{key_id}/usage`), `list(start:, end:, ...)` (`GET /v1/usage`, one page) and `all` (every page, `next_cursor` followed until it is `nil`). Each returns the API's object as sent and retries as any read. The query's rules (`granularity`, `group_by`, labels, `device_id`, key ids and at most 100 of them, `scope`, `status`, `include_empty`, `limit`) raise the API's `BadRequestError` before sending.
|
|
58
|
+
- `DecisionNode::DepthReading`, a reading of the `bynn_depth` booster (Depth Vision): `ahead`, `directions`, `ground`, `grid_rows`, `grid_cols`, `grid`, `depth_min`, `depth_max`, `depth_median`, `width` and `height`, in meters and pixels as sent.
|
|
59
|
+
- `DecisionNode::SegmentReading`, a reading of the `bynn_segment` booster (Segment Vision): `width`, `height`, `prompts`, `instances` (each a `SegmentInstance` with `label`, `score`, `box`, `polygon`, `area`, `centroid`, `opening` as a `SegmentOpening`, `depth` as a `SegmentDepth` and `size_m` as a `SegmentSize` with its `SegmentExtent`), `depth_source` and `assumed_hfov_deg`. An absent `ahead`, `depth_source`, `assumed_hfov_deg`, `opening`, `depth`, `size_m` (or a distance inside `depth`, or the size's `opening`) reads as `nil`. A reading with a required field missing or of the wrong type, `directions` other than 7 entries, or a negative or fractional grid size or picture side stays an `UnknownReading`.
|
|
60
|
+
- The README lists every booster the API serves, `face_detection`, `face_recognition`, `ocr`, `bynn_depth` and `bynn_segment` beside `age` and `ai_generated`, with their options and readings.
|
|
61
|
+
- The conformance tests run suite 1.22.
|
|
62
|
+
|
|
63
|
+
### Changed
|
|
64
|
+
|
|
65
|
+
- A 403 whose `error_type` is not `refusal_error` (`booster_not_enabled`, a suspended workspace) is a `PermissionDeniedError`, as before; a 529 `booster_unavailable` is an `OverloadedError`, retried like every 529.
|
|
66
|
+
- An error whose body is not JSON (a page from a proxy or the service's edge, or no body) is classified by its status alone, with `error_type` `nil`, and its message is `HTTP <status> <text>` from a fixed table (`HTTP 413 Content Too Large`), followed by the request id when the response has one. The body is no longer kept on the error.
|
|
67
|
+
- The default model is `decisionnode-moe`, the family id of the main model (it was `decisionnode-latest`), and the documentation uses the family ids `decisionnode-moe` and `decisionnode-flash`. A pinned id is `decisionnode-<family>-<version>`, its version from `models`; the older ids are still accepted by the API and sent as given.
|
|
68
|
+
- A 429 `key_spend_cap_reached` (a key past its daily spend cap) is raised at once and never retried: the cap resets at 00:00 UTC. It stays a `RateLimitError` with `details` `resets_at`, `cap` and `spent`.
|
|
69
|
+
- The README documents the key kinds, a publishable key's 403 `origin_not_allowed` and `model_not_allowed` (with `details["allowed"]`), and its 429s with `details["reason"]` `client_rate` and `in_flight`, retried as every 429.
|
|
70
|
+
|
|
71
|
+
### Removed
|
|
72
|
+
|
|
73
|
+
- `Batch#price_factor`. The API does not send it: the batch price is each model's `batch_price_factor` in `models` (none on a fine-tuned model), and each line's charge is in its usage.
|
|
74
|
+
|
|
75
|
+
## [0.2.0] - Unreleased
|
|
76
|
+
|
|
77
|
+
Implements version 1.2 of the DecisionNode SDK surface (`DecisionNode::SPEC_VERSION` is `"1.2"`): sixteen images a request, videos sent as frames, and point and box questions.
|
|
78
|
+
|
|
79
|
+
### Added
|
|
80
|
+
|
|
81
|
+
- Videos sent as frames: `DecisionNode::Video.new(id, frames:, fps:, start:, end:)` and `DecisionNode::VideoFrame.new(t, data:)`, `VideoFrame.new(t, path:)` or `VideoFrame.new(t, base64:, media_type:)`. A frame's media type comes from its bytes when none is given. `fps`, `start` and `end` are sent only when given. A Hash in the API's wire form is accepted wherever a `Video` or a `VideoFrame` is.
|
|
82
|
+
- `videos:` on `decide`, `check`, `classify`, `score`, `number`, `gate`, `decide_many`, batch requests and `check_request`. Frames are always sent inline as base64: never uploaded, never cached and never counted toward `inline_threshold`. Sessions take no videos.
|
|
83
|
+
- The API's video form rules checked before sending, in its order and words, as `BadRequestError` with `error_type` `api_usage_error`: an id, ids unique across images and videos, a non-empty frame list, frame times of at least 0 and increasing, a JPEG, PNG or WebP media type, `fps` above 0 and at most 4, `start` and `end` at least 0, and `end` after `start`. Frame counts, a frame's bytes and the window are left to the API.
|
|
84
|
+
- Point and box questions: `DecisionNode.point(instructions, image:, units:)` and `DecisionNode.box(...)`, answered as `PointAnswer` (`x`, `y`) and `BoxAnswer` (`x1`, `y1`, `x2`, `y2`), each with `present`, `confidence`, `image`, `width` and `height` as sent; an answer with a field missing or of the wrong type is an `UnknownAnswer`. They go with `decide`, `decide_many`, batch requests and `check_request`. Checked before sending as the API would: a request without an image, a missing or unknown `image`, and `units` other than `pixels` and `normalized` raise a `ValidationError` with the server's entries, and a question without instructions a `BadRequestError`.
|
|
85
|
+
- `Decision#usage.video_frames` and `usage.video_seconds` (nil when the response has none) and `Decision#warnings` (each sentence as sent, an empty Array when none), also on batch results.
|
|
86
|
+
|
|
87
|
+
### Changed
|
|
88
|
+
|
|
89
|
+
- Up to 16 images a request (4 before) and up to 4 videos, checked before sending: more raise the API's `ValidationError` with a `too_long` entry on `images` or `videos` (`ctx.max_length` 16 or 4), listed with the request's other 422 entries after the state's and the questions'. Its `input` is the list in its wire form, a local image as its id and media type without its bytes. A lower image limit held from `models` (on the entry, else in its `images` object) is checked the same way.
|
|
90
|
+
- `upload` takes 1 to 16 images a call; more raise the API's `BadRequestError` (`api_usage_error`) before anything is sent.
|
|
91
|
+
- One `POST /v1/uploads` takes up to 16 files.
|
|
92
|
+
- The conformance tests run suite 1.10.
|
|
93
|
+
|
|
94
|
+
## [0.1.0] - Unreleased
|
|
95
|
+
|
|
96
|
+
First version, implementing version 1.1 of the DecisionNode SDK surface: every public call of the API.
|
|
97
|
+
|
|
98
|
+
### Added
|
|
99
|
+
|
|
100
|
+
- `DecisionNode` client with `decide`, `decide_many`, `check`, `classify`, `score`, `number`, `gate`, `models`, `upload`, `open_session`, `supervise`, `batches`, `run_batch` and `decide_raw`. `check`, `classify`, `score`, `number` and `gate` take `images:` and `model:` like `decide`.
|
|
101
|
+
- `number` questions: the builder `DecisionNode.number(instructions, min:, max:, step:)`, which leaves out a bound not given, the `number` helper, and `NumberAnswer` with `number`, `expected`, `confidence` and `probabilities` keyed by the value as the server wrote it.
|
|
102
|
+
- Images by URL, path, bytes, base64 text, an IO or an upload id. URLs pass through after a local check (`https`, at most 2,048 characters) and are the recommended form. Local images go inline when a request's local images total under `DEFAULT_INLINE_THRESHOLD` (524,288 bytes; the `inline_threshold:` option), otherwise they are uploaded once (one `POST /v1/uploads`, parallel `PUT`s with exactly the returned headers) and sent by upload id. An in-memory cache by SHA-256 reuses an upload whatever the size until 5 minutes before it expires, concurrent calls with the same bytes share one upload, an upload the API reports stale is uploaded again and the request sent once more, and a PUT refused with 403 after its URL's 5 minutes is presigned again once. Upload URLs and cache entries are judged by the client's clock. `upload(images)` uploads explicitly.
|
|
103
|
+
- Every fixed rule the API documents is checked before any request and raised as the server would answer it: the same class, the same `error_type` (null for the API's plain-string 400s and its 422s, `api_usage_error` for images, unknown types, nesting, frames and batch fields, `image_url_refused` for refused URLs), the documented message, and for a 422 the server's `entries`. Covered: the 128-level nesting limit, question types and field types, choice and score counts, truth and number instructions, number grids (types, 2^53, decimals, the decimal bound, order, 4,096 values) counted in exact decimals, images (count, ids, format from the magic bytes, 10,485,760 bytes, 40,000,000 pixels read from the JPEG, PNG or WebP header, upload references, URL form, port, user information and literal private addresses), session `window` and `ttl_seconds`, frames refused by `Session#send` before anything is written or a `seq` is used, `custom_id` rules, an empty add and `batches.list` paging. Limits only `/v1/models` states are checked once `models` has been called.
|
|
104
|
+
- Sessions: `open_session`, `Session#connect`, `#send`, `#replies`, `#next_event`, `#end` and `#close`, with `Reply`, `FrameError` and `SessionClosed` values, `seq` counted from 1, pings answered, a silent socket treated as lost, and `supervise`, which reopens or reconnects by close code with a capped backoff. A session limit raises `SessionLimitError`, a `RateLimitError`, and is never retried.
|
|
105
|
+
- The Batch API: `batches.create`, `add` (split at 10 MiB), `finalize` (a 409 resolved with `get`), `get`, `list`, `results` and `cancel`, with `Batch`, `BatchPage` and `BatchResult`; create, add and finalize retried only on 429 and 529. `run_batch` runs the whole lifecycle with a poll interval of at least 10 seconds, the server's `Retry-After` when longer, and a timeout.
|
|
106
|
+
- `ModelInfo#sessions`, `#batch` and `#limit` for the limits `GET /v1/models` publishes.
|
|
107
|
+
- `check_request(method, request)` and `Session#check_frame`: every local check of a call, run alone; they raise what the call would raise and send and upload nothing. A model's held `/v1/models` limits are found by its name, else by an entry's `alias` or pinned `version`.
|
|
108
|
+
- `upload_timeout:` (default 60 seconds): each image `PUT` to storage has its own timeout, apart from the API's `timeout`.
|
|
109
|
+
- `batches.results` reads the JSON Lines file from the network line by line (`NetHTTPTransport#stream`), however many lines it has, and takes a block to handle each result as it arrives.
|
|
110
|
+
- A session's socket goes through the transport's proxy (else the environment's) with a `CONNECT` tunnel and takes its `net_http:` TLS settings, such as a company CA (`NetHTTPTransport#session_options`).
|
|
111
|
+
- `gate` with fixed question wording, the same in every DecisionNode SDK.
|
|
112
|
+
- `decide_many` returns one `DecisionResult` per request, a `Decision` or a `DecisionNodeError` (an unexpected error is wrapped in `DecisionNodeError` with the original as its `cause`). Every request is checked before any is sent: a `concurrency` below 1 or any invalid request raises `ArgumentError` and nothing is sent.
|
|
113
|
+
- `check`, `classify` and `score` raise `ServerError`, with `status`, `request_id` and `reason`, when their own answer is missing or malformed; `decide` returns it as `UnknownAnswer`.
|
|
114
|
+
- Question builders `DecisionNode.truth`, `DecisionNode.choice` and `DecisionNode.score`, also available through `include DecisionNode::Questions`.
|
|
115
|
+
- Typed answers: `TruthAnswer`, `ChoiceAnswer`, `ScoreAnswer`, `NumberAnswer`, and `UnknownAnswer` for answer types this version does not know. `models` returns `ModelInfo` entries. Missing token counts and latency are `nil`.
|
|
116
|
+
- `DecisionNode::Image`: the media type is taken from the first bytes (JPEG, PNG or WebP). A file is read when the `Image` is made; an unreadable one raises `ArgumentError` there, and bytes in another format raise `BadRequestError` before any request.
|
|
117
|
+
- The safety check result as `decision.safety`.
|
|
118
|
+
- Retries with `retry-after-ms`, `Retry-After` in seconds or as an HTTP date (server waits capped at 60 seconds) and exponential backoff.
|
|
119
|
+
- A timeout that covers the whole attempt, from sending the request to the last byte of the body, with any transport.
|
|
120
|
+
- Argument checks when the client is made: `base_url` must use `https` (`http` only for `localhost`, `127.0.0.1` and `::1`), `timeout` must be above 0 and `max_retries` 0 or more. Bad arguments raise `ArgumentError`, never a DecisionNode class; an empty question map, an unreadable image file and a value that cannot be serialised do too.
|
|
121
|
+
- An answer of a known type with a missing or wrongly typed field is returned as `UnknownAnswer` with its raw fields.
|
|
122
|
+
- `NetHTTPTransport` keeps connections alive per host (`max_idle:`, `keep_alive_timeout:`) and takes `proxy:` and `net_http:` (Net::HTTP settings such as `ca_file`). Settings that could turn TLS or certificate checks off (`use_ssl`, `verify_mode` other than `VERIFY_PEER`, `verify_hostname: false`, `verify_callback`) are refused, and TLS with peer and hostname checks is set again after the caller's settings for an `https` URL. A transport passed as `http_client:` must answer `false` to `follow_redirects?` or it is refused, and a response from another address than the one requested is an error.
|
|
123
|
+
- The README documents the `x-decisionnode-safety-body` request header.
|
|
124
|
+
- Errors: one class per documented status, `PermissionDeniedError` for a 403 that is not a refusal, and the base `DecisionNodeError` with its status for any other status. Only 2xx is a success and redirects are never followed. An error's `message` is the server's message, an empty string when it sent none. There is no second name for the base class.
|
|
125
|
+
- `DecisionNode::SPEC_VERSION`, the version of the SDK surface this gem implements (`"1.1"`), and `DecisionNode::DEFAULT_INLINE_THRESHOLD`.
|
|
126
|
+
- Configuration through `DECISIONNODE_API_KEY`, `DECISIONNODE_BASE_URL` and `DECISIONNODE_MODEL`; an empty argument counts as not given.
|
|
127
|
+
- Optional debug logging that never prints state, questions or answers.
|
|
128
|
+
- The conformance tests fail, never skip, when `DECISIONNODE_CONFORMANCE_DIR` is not set. CI runs conformance suite 1.6 at a pinned commit, with every action pinned to a commit SHA and `persist-credentials: false` on every checkout.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bynn 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.
|