bynn-decisionnode 0.3.1__tar.gz

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.
@@ -0,0 +1,33 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ wheels/
9
+
10
+ # Virtual environments
11
+ .venv/
12
+ venv/
13
+ env/
14
+
15
+ # Tooling caches
16
+ .pytest_cache/
17
+ .mypy_cache/
18
+ .ruff_cache/
19
+ .coverage
20
+ .coverage.*
21
+ htmlcov/
22
+ .tox/
23
+
24
+ # Local checkout of the conformance cases
25
+ /conformance/
26
+
27
+ # Editor files
28
+ .idea/
29
+ .vscode/
30
+ *.swp
31
+
32
+ # OS noise
33
+ .DS_Store
@@ -0,0 +1,216 @@
1
+ # Changelog
2
+
3
+ All notable changes to this package are listed here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [0.3.0] - Unreleased
8
+
9
+ Implements version 1.3 of the SDK specification (`SPEC_VERSION` is `"1.3"`).
10
+
11
+ ### Added
12
+
13
+ - Several places in one answer: `point()` and `box()` take `max` (2 to 16), and the
14
+ answer is a `PointsAnswer` or `BoxesAnswer` (`count`, `present`, `complete`, `points`
15
+ of `PointPlace` or `boxes` of `BoxPlace`, `image`, `width`, `height`), told by its own
16
+ list, in the order and numbers sent. `max` (a whole number from 2 to 16) is checked
17
+ before sending; whether a model version answers several places is the API's to say.
18
+ - Boosters: `boosters` on `decide`, `check`, `classify`, `score`, `number`, `gate`,
19
+ `decide_many` requests, batch requests and `check_request`, as booster names,
20
+ `Booster(name, images, require, options)` objects (sent as `{"name": ...}` with each
21
+ setting only when given, or as the bare name when none is) or wire-form mappings, sent
22
+ in the order given. Left out or
23
+ empty, the request has no `boosters` field. The client checks nothing about boosters
24
+ itself; the API answers. Sessions take no boosters.
25
+ - `Decision.evidence`: booster name to `Evidence` (`version`, `status`, `latency_ms`,
26
+ `billed_calls`, `results`), with `AgeReading`, `AiGeneratedReading` (its `TopGenerator`, `None` unless
27
+ the reading says likely generated, `c2pa_signer`, and `c2pa`, `untrusted` for Content
28
+ Credentials that validate but are not trusted) and `UnknownReading` for a reading of a
29
+ booster this version does not know or with a field missing or of the wrong type. `None`
30
+ when the request named no boosters.
31
+ - `Decision.usage.booster_tokens` and `Decision.usage.boosters` (`None` when absent).
32
+ - The readings of the `ocr`, `face_detection` and `face_recognition` boosters: `OcrReading`
33
+ (`text`, `truncated`), `FaceDetectionReading` (`faces`, `detections` of `FaceDetection`
34
+ with a `FaceBox` and a `confidence`) and `FaceRecognitionReading` (`faces`, `matches` of
35
+ `FaceMatch` with a `FaceBox`, a `score`, the `collection` searched, the item's
36
+ `reference`, and `name` or `label`, `None` when absent). Box corners are kept as
37
+ received. A malformed reading is an `UnknownReading`.
38
+ - Collections of faces, as `dn.collections`: `create`, `list`, `get`, `delete`, `enroll`
39
+ (one face from one image, sent as a decision's image is), `list_items` and
40
+ `delete_item`, each returning the API's object as sent. `create` and `enroll` send an
41
+ `Idempotency-Key`, a new UUID per call and the same on every attempt of it, so the API
42
+ creates, enrolls and bills once; they retry like any call, and also while the API
43
+ reports the first attempt still running (a 409 `idempotency_in_progress`, after its
44
+ `Retry-After`). A 409 `idempotency_key_reused` is raised and never retried. A
45
+ collection's type and name, an item's reference and label and the items list's `offset`
46
+ and `limit` are checked before anything is sent; of the picture only a URL's rules are,
47
+ every other fault is the API's 422 `image_unreadable`.
48
+ - A decision whose boosters name `ocr` waits 30 seconds per attempt unless a `timeout` was
49
+ set on the client, and is never retried after a timeout, since the server finishes that
50
+ attempt and may bill it; its other retries stay.
51
+ - `Usage.cost`: what the answer cost in USD, a `Cost` (`usd`, `model_usd`, `boosters_usd`,
52
+ `free_tier`) read as sent, on decisions, batch result lines, a session's open and frame
53
+ replies; `None` when absent, never zeros.
54
+ - `DecisionNodeError.boosters`: the `x-decisionnode-boosters` header as booster name to
55
+ `BoosterStatus` (`status`, `latency_ms`), on every error; empty when the header is absent.
56
+ - Fine-tuned models: `ModelInfo.fine_tuned`, `base`, `adapter_version` and `status`
57
+ (`None` on the catalogue's own entries). A fine-tuned model's name is sent as given, and
58
+ `Decision.model` is the pinned name that answered.
59
+ - `RequestTooLargeError` for the API's 25 MiB request body limit: raised for every 413,
60
+ never retried, and before anything is sent or uploaded when a request, a `decide_many`
61
+ request or a batch line is over 26,214,400 bytes as it will be sent (images to upload
62
+ counted by their `upload_id`). `check_request` measures it too.
63
+ - The fallback: `fallback` (`True` or `False`) on `decide`, the helpers, `decide_many`
64
+ requests, batch requests and `check_request`, sent only when given. `Decision.fallback`
65
+ is a `Fallback` (`served_model`, `requested_model`) on an answer from another model than
66
+ the one asked for, read from `x-decisionnode-fallback` and
67
+ `x-decisionnode-requested-model`; `None` otherwise.
68
+
69
+ ### Changed
70
+
71
+ - The default model is `decisionnode-moe` (it was `decisionnode-latest`), and the docs use
72
+ the family ids `decisionnode-moe` and `decisionnode-flash`, which always mean the current
73
+ release; a pinned id is `decisionnode-<family>-<version>`, as `models()` lists it. The
74
+ older ids are still accepted by the API and sent as given.
75
+ - `PermissionDeniedError` is documented as every 403 that is not a refusal, a booster
76
+ turned off for the workspace included; `OverloadedError` as also raised, and retried,
77
+ for a required booster that failed (`booster_unavailable`).
78
+ - An error whose body is not JSON (an HTML or text page, or no body) is classed by its
79
+ status alone, with `error_type` `None`, `request_id` and `reason` from the headers and
80
+ the client's own message, `HTTP <status> <text>` (`HTTP 413 Content Too Large`), with
81
+ the request id when sent. The page is never read: `body` is `None`.
82
+ - An image id of more than 64 characters is refused before anything is sent:
83
+ `BadRequestError` `api_usage_error`, "Image <n>: its id is longer than 64 characters."
84
+
85
+ ## [0.2.0] - Unreleased
86
+
87
+ Implements version 1.2 of the SDK specification (`SPEC_VERSION` is `"1.2"`) and passes
88
+ conformance suite 1.10.
89
+
90
+ ### Added
91
+
92
+ - Videos sent as frames: `Video(id, frames, fps, start, end)` and `VideoFrame(t, data,
93
+ media_type)`, taken by `decide`, `check`, `classify`, `score`, `number`,
94
+ `gate`, `decide_many` requests, batch requests and `check_request` as `videos`. A frame's
95
+ data is bytes (its type read from the first bytes) or base64 text with its media type.
96
+ Frames always go inline.
97
+ - Before anything is sent: ids unique across images and videos, frame times of at least 0
98
+ and strictly increasing, JPEG, PNG or WebP frames, `fps` above 0 and at most 4, and
99
+ `end` after `start`, each raising `BadRequestError` with the API's message. More than 4
100
+ videos raises the API's `ValidationError` (a `too_long` entry on the videos). Frame
101
+ counts are the API's.
102
+ - `Decision.usage.video_frames`, `Decision.usage.video_seconds` (`None` when absent) and
103
+ `Decision.warnings` (an empty list when absent).
104
+ - The `point` and `box` question types: the `point(instructions, image, units)` and
105
+ `box(instructions, image, units)` builders, and `PointAnswer` (`x`, `y`) and `BoxAnswer`
106
+ (`x1`, `y1`, `x2`, `y2`), each with `present`, `confidence`, `image`, `width` and
107
+ `height`. A request with one needs an image, and `image` names one of the request's
108
+ images (required with two or more); with `units` these are checked before anything is
109
+ sent and raise `ValidationError` with the API's entries.
110
+
111
+ ### Removed
112
+
113
+ - `systemone()`. `POST /v1/systemone` is not part of the API; every decision is sent to
114
+ `POST /v1/decide`.
115
+
116
+ ### Changed
117
+
118
+ - A request holds up to 16 images (4 before); a 17th raises the API's `ValidationError`
119
+ before anything is sent: a `too_long` entry on the images, listed with the request's
120
+ other entries, a local image in its `input` without its bytes. Up to 16 images are
121
+ uploaded through one presign; `upload()` of more raises `BadRequestError`
122
+ (`files: 1 to 16 files, got 17.`) before anything is sent.
123
+
124
+ ## [0.1.0] - Unreleased
125
+
126
+ First version. Implements version 1.1 of the specification shared by every official
127
+ DecisionNode SDK, exported as `SPEC_VERSION`, and passes conformance suite 1.6.
128
+
129
+ ### Added
130
+
131
+ - The `number` question type: the `number` builder, the `number` helper and
132
+ `NumberAnswer` (`number`, `expected`, `confidence`, `probabilities` keyed by the value as
133
+ the API writes it).
134
+ - `systemone` for `POST /v1/systemone`, the strict path.
135
+ - Truth answers read as `TruthAnswer` under either wire name the API uses for the type.
136
+ - Images by `upload_id`, and `upload(images)`. Local images whose bytes total
137
+ `DEFAULT_INLINE_THRESHOLD` (512 KiB) or more in a request are uploaded first (one
138
+ presign, parallel `PUT`s with exactly the signed headers) and sent by `upload_id`;
139
+ smaller ones go inline. The client option `inline_threshold` changes the threshold.
140
+ Uploads are remembered by the SHA-256 of their bytes and reused until 5 minutes before
141
+ they expire; a stale one is uploaded again and the request sent once more.
142
+ - Every fixed rule the API documents is checked before any request, in the API's order,
143
+ raising its own error class, `error_type`, message and 422 entries: the 128-level
144
+ nesting limit; question types and shapes, option, level and truth rules and the number
145
+ grid (`number` is refused on `systemone`); the state's type; image count, ids, format
146
+ by magic bytes, size, pixel dimensions read from the JPEG, PNG and WebP headers and an
147
+ upload reference's media type; image URLs (`https`, at most 2,048 characters, no user
148
+ information, port 443, no literal private address); session `window` and
149
+ `ttl_seconds`; frames refused by `Session.send` with nothing written and no `seq` used;
150
+ batch `custom_id` rules, an empty add and `batches.list` paging. Limits a model
151
+ publishes are checked from the values held after a `models()` call, found by the
152
+ model's name or by the alias or pinned version an entry states.
153
+ - `check_request(method, **arguments)` and `Session.check_frame(...)` run every local
154
+ check a call would run, raise the same error, and send and upload nothing.
155
+ - `upload_timeout` (default 60 seconds): each storage `PUT` attempt of an image has its
156
+ own deadline, apart from `timeout`.
157
+ - A session's stream goes through the HTTP client's proxy and uses its TLS settings.
158
+ - Batch results are read from the network line by line, the whole file.
159
+ - A storage `PUT` answered `403` after its URL's lifetime is presigned again once.
160
+ - Sessions: `open_session`, `Session` and `AsyncSession` (`connect`, `send`, `replies`,
161
+ `next_event`, `end`, `close`), `Reply`, `FrameError`, `SessionClosed`, and `supervise`.
162
+ `SessionLimitError`, a `RateLimitError`, for a key at its open-session limit.
163
+ - The Batch API: `batches.create`, `add`, `finalize`, `get`, `list`, `results` and
164
+ `cancel`, and `run_batch`; `Batch`, `BatchPage`, `BatchResult` and `BatchRun`.
165
+ - Connections kept open between calls (up to 100, 30 seconds idle), and an `http2`
166
+ option with the `http2` extra.
167
+
168
+ - `DecisionNode` and `AsyncDecisionNode` clients with `decide`, `decide_many`, `check`,
169
+ `classify`, `score`, `gate`, `models` and `decide_raw`. `check`, `classify`, `score`
170
+ and `gate` take `images` and `model` like `decide`.
171
+ - `gate` with fixed wording, the same in every official SDK.
172
+ - `ModelInfo` for each entry of `models()`, and `DecisionResult` for each result of
173
+ `decide_many`.
174
+ - Question builders `truth`, `choice` and `score`; wire-form mappings are accepted too.
175
+ - Typed answers: `TruthAnswer`, `ChoiceAnswer`, `ScoreAnswer`, and `UnknownAnswer` for
176
+ answer types this version does not know.
177
+ - Images from a file path, bytes (or base64 text) or an `https` URL. The media type of a
178
+ file, and of bytes given without one, is read from the first bytes (JPEG, PNG or WebP);
179
+ a file that cannot be read raises `ValueError` when the `Image` is made.
180
+ - An answer of a known type with a missing or wrongly typed field comes back as
181
+ `UnknownAnswer` with its raw fields; the rest of the decision parses as usual.
182
+ - Retries on 408, 429, 5xx, timeouts and connection errors, honouring `retry-after-ms`
183
+ and `Retry-After` (seconds or an HTTP date), capped at 60 seconds.
184
+ - A 10 second timeout that covers the whole attempt, from sending the request to the
185
+ last byte of the response body. `timeout` must be above 0 and `max_retries` 0 or
186
+ more.
187
+ - Exception classes for the documented statuses, all derived from `DecisionNodeError`,
188
+ including `PermissionDeniedError` for a 403 that is not a refusal. A redirect and any
189
+ other status raise `DecisionNodeError` itself with its `status`; a 2xx whose body is
190
+ not JSON raises `ServerError`. `str(error)` is the server's message, `""` when it
191
+ sent none.
192
+ - Argument problems found before sending (an empty question map, a value that cannot be
193
+ sent as JSON, an unreadable file, a bad `base_url`, `timeout` or `max_retries`) raise
194
+ Python's own `ValueError`, never a `DecisionNodeError`. `decide_many` checks every
195
+ request before sending any.
196
+ - Redirects are never followed, also on an `http_client` passed in that is set to
197
+ follow them.
198
+ - The base URL must be `https`; plain `http` is accepted only for `localhost`,
199
+ `127.0.0.1` and `::1`.
200
+ - `Decision.safety`, parsed from the `x-decisionnode-safety` response header. The
201
+ README shows how to send `x-decisionnode-safety-body: true` to also get the result
202
+ in the response body.
203
+ - Settings from `DECISIONNODE_API_KEY`, `DECISIONNODE_BASE_URL` and `DECISIONNODE_MODEL`;
204
+ an empty argument counts as not given.
205
+ - `decide_many` refuses a `concurrency` below 1 with `ValueError` before sending.
206
+ - `check`, `classify` and `score` raise `ServerError` (`status` 200, `reason` from
207
+ `x-decisionnode-error`) when the answer to their question is missing or malformed;
208
+ `decide` returns such an answer as `UnknownAnswer`.
209
+ - A caller's `http_client` is checked when the client is made: it must be an
210
+ `httpx.Client` (`httpx.AsyncClient`) itself, with httpx's own transports, or it is
211
+ refused with `ValueError`, since another type may follow redirects. For an `https`
212
+ base URL it may not turn off certificate or hostname checks.
213
+ - `DecisionNodeError` is exported by that name and is the base of every exception.
214
+ - CI pins every action to a commit SHA, sets `persist-credentials: false` on every
215
+ checkout, installs a pinned uv and checks out the conformance suite at a pinned
216
+ commit (suite 1.6).
@@ -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.