Skip to content

Commit ea625a5

Browse files
committed
Suite 1.17: Batch has no price_factor, the batch price being each model's batch_price_factor in models() and each line's charge in its usage; the docs name the prices a models() entry carries, and the conformance driver reads {"$absent": true} as a key that must not be there [skip publish]
1 parent f20068b commit ea625a5

6 files changed

Lines changed: 39 additions & 15 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ permissions:
1919
contents: read
2020

2121
env:
22-
# The conformance suite commit this SDK is tested against (suite 1.16, tag v1.16, for specification
22+
# The conformance suite commit this SDK is tested against (suite 1.17, tag v1.17, for specification
2323
# 1.3). Change it here only.
24-
CONFORMANCE_REF: 2d2c5a97c1d57e3be74c9edb4c5b9117511802a2
24+
CONFORMANCE_REF: 23957bf6992fa53918a9f63ab16c8a6c8b9203f9
2525
UV_VERSION: "0.7.13"
2626

2727
concurrency:

‎CHANGELOG.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ Implements version 1.3 of the SDK specification (`SPEC_VERSION` is `"1.3"`).
9797
- An image id of more than 64 characters is refused before anything is sent:
9898
`BadRequestError` `api_usage_error`, "Image <n>: its id is longer than 64 characters."
9999

100+
### Removed
101+
102+
- `Batch.price_factor`. The API does not send it: the batch price is each model's
103+
`batch_price_factor` in `models()` (none on a fine-tuned model), and each line's charge
104+
is in its usage.
105+
100106
## [0.2.0] - Unreleased
101107

102108
Implements version 1.2 of the SDK specification (`SPEC_VERSION` is `"1.2"`) and passes

‎README.md‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -532,9 +532,11 @@ the async client the feed may be an async iterator and `on_reply` a coroutine fu
532532
## Batches
533533

534534
The Batch API answers many decide requests at the batch price: 50% off the live price on
535-
`decisionnode-flash`, 30% off on `decisionnode-moe` (each model's `batch_price_factor` in
536-
`models()`, 0.5 and 0.7). There is no set time at which a batch runs or finishes, so poll
537-
for it. Requests not answered within 24 hours of finalizing expire and are not charged.
535+
`decisionnode-flash`, 30% off on `decisionnode-moe`. The batch price is each model's, its
536+
`batch_price_factor` in `models()` (0.5 and 0.7; a fine-tuned model's entry states none),
537+
never the batch's, and each line's charge is in its usage. There is no set time at which a
538+
batch runs or finishes, so poll for it. Requests not answered within 24 hours of finalizing
539+
expire and are not charged.
538540

539541
```python
540542
requests = [
@@ -587,8 +589,9 @@ for m in dn.models():
587589
```
588590

589591
Each entry is a `ModelInfo`. `limits` holds every other field the model publishes: the
590-
token limit, the number grid limits, the upload limits, and the `sessions` and `batch`
591-
objects.
592+
prices (`price_per_million_input`, `price_per_million_output`, `price_per_million_image`,
593+
`price_per_million_video`, in USD per million tokens, and `batch_price_factor`), the token
594+
limit, the number grid limits, the upload limits, and the `sessions` and `batch` objects.
592595

593596
| Model | |
594597
|---|---|
@@ -931,7 +934,7 @@ The tests include the DecisionNode SDK conformance suite
931934
(`Bynn-Intelligence/decision-node-conformance`), run with both the sync and the async
932935
client. Set `DECISIONNODE_CONFORMANCE_DIR` to a checkout of it, or check it out at
933936
`conformance/` in this repository; without it the conformance tests fail rather than
934-
skip. The tests pass suite 1.16; CI checks the suite out at the commit pinned in
937+
skip. The tests pass suite 1.17; CI checks the suite out at the commit pinned in
935938
`CONFORMANCE_REF` in `.github/workflows/ci.yml`, with every action pinned to a commit SHA
936939
and no checkout keeping its credentials.
937940

‎src/decisionnode/_batches.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@
4444
class Batch:
4545
"""A batch: every field of the API's batch object.
4646
47+
A batch states no price: the batch price is each model's ``batch_price_factor`` in
48+
``models()``, and each line's charge is in its usage.
49+
4750
Attributes:
4851
id: ``batch_`` and 10 characters.
4952
state: ``draft``, ``queued``, ``running``, ``completed``, ``failed``,
@@ -53,7 +56,6 @@ class Batch:
5356
created_at, finalized_at, expires_at, ended_at, results_expire_at: ISO 8601 UTC
5457
times, or ``None``.
5558
results_url: Where the results are, once the batch has ended.
56-
price_factor: The share of the live price a line costs.
5759
usage: ``input_tokens`` and ``output_tokens`` of the lines answered so far.
5860
error: For a ``failed`` batch, ``error_type`` and ``message``.
5961
raw: The batch object as received.
@@ -68,7 +70,6 @@ class Batch:
6870
ended_at: Optional[str]
6971
results_url: Optional[str]
7072
results_expire_at: Optional[str]
71-
price_factor: Optional[float]
7273
usage: Dict[str, Any]
7374
error: Optional[Dict[str, Any]]
7475
raw: Dict[str, Any] = field(repr=False)
@@ -143,7 +144,6 @@ def parse_batch(body: Any) -> Batch:
143144
ended_at=raw.get("ended_at"),
144145
results_url=raw.get("results_url"),
145146
results_expire_at=raw.get("results_expire_at"),
146-
price_factor=raw.get("price_factor"),
147147
usage=dict(usage) if isinstance(usage, Mapping) else {},
148148
error=dict(error) if isinstance(error, Mapping) else None,
149149
raw=raw,

‎tests/conformance/test_conformance.py‎

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,17 @@ def same(a: Any, b: Any) -> bool:
185185
return type(a) is type(b) and a == b
186186

187187

188+
# An expected value meaning the key must not be there at all (suite 1.17).
189+
ABSENT = {"$absent": True}
190+
191+
188192
def match(expected: Any, actual: Any, path: str) -> None:
189193
if isinstance(expected, dict) and isinstance(actual, dict) and not is_pattern(expected):
190194
for k, v in expected.items():
195+
if v == ABSENT:
196+
if k in actual:
197+
raise Fail("%s.%s: expected no such key, got %s" % (path, k, short(actual[k])))
198+
continue
191199
if k not in actual:
192200
raise Fail("%s.%s: missing (expected %s)" % (path, k, short(v)))
193201
if k in EXACT:
@@ -202,6 +210,14 @@ def match(expected: Any, actual: Any, path: str) -> None:
202210
raise Fail("%s: expected %s, got %s" % (path, short(expected), short(actual)))
203211

204212

213+
def match_batch(expected: Any, batch: Any, path: str) -> None:
214+
"""A batch compares as sent; a key expected absent is neither sent nor a field of Batch."""
215+
match(expected, batch.raw, path)
216+
for k, v in expected.items() if isinstance(expected, dict) else ():
217+
if v == ABSENT and hasattr(batch, k):
218+
raise Fail("%s.%s: expected no such field on Batch, got %s" % (path, k, short(getattr(batch, k))))
219+
220+
205221
def pointer(doc: Any, ptr: str) -> Tuple[bool, Any]:
206222
cur = doc
207223
for raw in ptr[1:].split("/") if ptr else []:
@@ -761,7 +777,7 @@ def check_outcome(spec: Dict[str, Any], result: Any, path: str) -> None:
761777
elif kind == "batch":
762778
if not isinstance(result, dn.Batch):
763779
raise Fail("%s: expected a Batch, got %r" % (path, result))
764-
match(spec.get("batch", {}), result.raw, path + ".batch")
780+
match_batch(spec.get("batch", {}), result, path + ".batch")
765781
elif kind == "batch_page":
766782
if not isinstance(result, dn.BatchPage):
767783
raise Fail("%s: expected a BatchPage, got %r" % (path, result))
@@ -771,7 +787,7 @@ def check_outcome(spec: Dict[str, Any], result: Any, path: str) -> None:
771787
elif kind == "batch_run":
772788
if not isinstance(result, dn.BatchRun):
773789
raise Fail("%s: expected a BatchRun, got %r" % (path, result))
774-
match(spec.get("batch", {}), result.batch.raw, path + ".batch")
790+
match_batch(spec.get("batch", {}), result.batch, path + ".batch")
775791
check_results_spec(spec, result.results, path)
776792
elif kind in COLLECTION_KINDS:
777793
check_collection_result(kind, spec.get(kind, {}), result, path + "." + kind)
@@ -1068,7 +1084,7 @@ async def run_response(case: Dict[str, Any], mock: Mock, mode: str, env: pytest.
10681084
raise Fail("raw: expected %s, got %s" % (short(exp["raw"]), short(result)))
10691085
elif "batch" in exp:
10701086
assert isinstance(result, dn.Batch)
1071-
match(exp["batch"], result.raw, "batch")
1087+
match_batch(exp["batch"], result, "batch")
10721088
elif "batch_page" in exp:
10731089
assert isinstance(result, dn.BatchPage)
10741090
match(exp["batch_page"], result.raw, "batch_page")

‎tests/test_calls.py‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
"ended_at": None,
3939
"results_url": None,
4040
"results_expire_at": None,
41-
"price_factor": 0.5,
4241
"usage": {"input_tokens": 0, "output_tokens": 0},
4342
"error": None,
4443
}

0 commit comments

Comments
 (0)