@bivelio/savings-layer 0.4.22 → 0.4.24

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.
package/README.md CHANGED
@@ -19,8 +19,9 @@
19
19
  Every request your app sends to an AI is paid for by the text going in and out.
20
20
  **Savings** sits on that path and lets through only what's needed — skipping calls
21
21
  that don't have to happen, and trimming what does — so you pay for less. It is not a
22
- promise you take on faith: **every saving is measured with the provider's official
23
- meter, the same one on your bill**, and handed back to you request by request.
22
+ promise you take on faith: **every saving we charge on is measured with the provider's
23
+ official meter, the same one on your bill**, and every saving — measured or estimated,
24
+ and labelled as such — is handed back to you request by request.
24
25
 
25
26
  **We only earn when you save.** Your first month carries no commission at all: we
26
27
  measure everything and show you the statement of what it would have cost. After
@@ -66,14 +67,55 @@ story, in plain words:
66
67
 
67
68
  **Claude Code, Codex CLI, Goose or Qwen Code with an API key?** The package also
68
69
  ships a local measuring gateway: `bvsala claude` (or `bvsala codex`, `bvsala
69
- goose`, `bvsala qwen`) launches the terminal through it and your real spend
70
- shows up in your dashboard, token by token — nothing to configure, nothing
71
- persisted. For opencode, Crush, Droid, Cline or Kilo Code, `bvsala doctor`
72
- prints the exact config block for each one you have installed. It measures every
73
- request. With a PRO license it also applies one saving by default: it shortens the
74
- cache-write TTL while your session stays hot, which changes the price, never the
75
- content the model reads. The other levers (sibling model, per-turn effort) stay off
76
- until you switch them on.
70
+ goose`, `bvsala qwen`) launches the terminal through it and your real spend shows
71
+ up in your dashboard, token by token — nothing to configure, nothing persisted.
72
+ For opencode, Crush, Droid, Cline or Kilo Code, `bvsala doctor` prints the exact
73
+ config block for each one you have installed. Like the SDK, the gateway is
74
+ fail‑closed: it starts only with your service key (`BIVELIO_SERVICE_KEY`) and an
75
+ active BiVelio license, which it collects with that key and verifies against the
76
+ key embedded in the package. Once running it never interrupts a request in flight:
77
+ if the license lapses, only new requests are refused (HTTP 402) until it is
78
+ renewed. It measures every request. With a PRO license it also applies one saving
79
+ by default: it shortens the cache-write TTL while your session stays hot, which
80
+ changes the price, never the content the model reads. The other levers (sibling
81
+ model, per-turn effort) stay off until you switch them on. On a Claude or ChatGPT
82
+ subscription there is no per-token invoice, so that traffic is metered at
83
+ list-price equivalent and never carries commission; the per-seat license applies
84
+ as for any other use.
85
+
86
+ ### Python and any other language: the local gateway
87
+
88
+ The same gateway sits in front of any SDK that lets you set a base URL, whatever
89
+ the language:
90
+
91
+ ```bash
92
+ bvsala gateway --upstream openai --port 8403 # one gateway per upstream
93
+ bvsala gateway # Anthropic, the default, on 8402
94
+ ```
95
+
96
+ ```python
97
+ from openai import OpenAI
98
+ client = OpenAI(base_url="http://127.0.0.1:8403/v1") # WITH /v1
99
+ stream = client.chat.completions.create(
100
+ model="gpt-4o-mini", messages=[{"role": "user", "content": "Hi"}], stream=True,
101
+ stream_options={"include_usage": True}, # without it a stream carries no usage and is not metered
102
+ )
103
+ for chunk in stream:
104
+ if chunk.choices: # the last chunk carries only the usage
105
+ print(chunk.choices[0].delta.content or "", end="")
106
+
107
+ from anthropic import Anthropic
108
+ claude = Anthropic(base_url="http://127.0.0.1:8402") # NO /v1
109
+ ```
110
+
111
+ With `BIVELIO_SERVICE_KEY` set, the gateway measures every request whose usage the
112
+ provider reports — Anthropic Messages, OpenAI Chat Completions and Responses, streamed
113
+ or not — for models with a list price in its catalog, priced at that list price. A
114
+ request it cannot meter (a model without a list price, a body it cannot read, a stream
115
+ that never asked for usage) is still forwarded, and its log line says so and why. That
116
+ is measurement, not savings: the saving levers of the SDK are TypeScript, and the
117
+ gateway's own levers apply to Anthropic `/v1/messages` traffic with a PRO license.
118
+ `bvsala doctor` prints the recipe for each SDK.
77
119
 
78
120
  ## Check it before you install
79
121
 
@@ -89,16 +131,22 @@ lives in your dashboard once you sign in.
89
131
  **1 · The package**
90
132
 
91
133
  ```bash
92
- pnpm add @bivelio/savings-layer
93
- # or: npm i @bivelio/savings-layer · yarn add @bivelio/savings-layer
134
+ pnpm add @bivelio/savings-layer gpt-tokenizer
135
+ # or: npm i @bivelio/savings-layer gpt-tokenizer · yarn add @bivelio/savings-layer gpt-tokenizer
94
136
  ```
95
137
 
138
+ `gpt-tokenizer` is optional, but install it if you call OpenAI models: it is the
139
+ provider's own tokenizer, and the SDK loads it by itself. Without it, counts fall back
140
+ to a built‑in estimate and the reconstructed savings are less accurate. Either way, a
141
+ saving where the layer removed something is reported as `estimated`, and estimated
142
+ savings are never billed — the SDK warns once when the tokenizer is missing.
143
+
96
144
  **2 · The license** — from your [dashboard](https://savings.bivelio.com/dashboard/install)
97
145
  (**€4 / seat / month** or **€39 / seat / year**, EUR)
98
146
 
99
147
  ```bash
100
148
  export BIVELIO_LICENSE_KEY=… # your license key
101
- export BIVELIO_SERVICE_KEY=… # service-account key: auto-renewal + seat metering
149
+ export BIVELIO_SERVICE_KEY=… # service-account key: auto-renewal + seat metering (required by the gateway)
102
150
  ```
103
151
 
104
152
  **3 · Wrap the call** — this is the entire code change. The package is ESM only:
@@ -137,11 +185,48 @@ console.log(report.cost.basis); // → "estimated": nothing ran, so there
137
185
 
138
186
  That's it. From the first request, `report` carries the saving and says how it
139
187
  was obtained: `actual` is what the provider metered; `basis` is `"measured"` when
140
- the request went through untouched, `"estimated"` when the layer removed something
141
- (the baseline is then reconstructed with the model's tokenizer, or with a
142
- calibrated estimate where no exact tokenizer exists, as for Anthropic models),
143
- and `"unmeasured"` when the provider returned no usage. Only measured figures
144
- reach your invoice.
188
+ the request went through untouched, `"reference"` when a repeat of a byte‑identical
189
+ request was served without calling the provider — its baseline is the provider's own
190
+ counter for the identical request that already ran and that you already paid
191
+ (`report.cache.reference` points to it; with an output schema, only when your schema
192
+ traveled exactly as sent, in strict mode, in the provider's native response‑format
193
+ parameter, with nothing added to the prompt), valued at the provider's cached‑input
194
+ rate, the lowest it bills for repeating a prompt; the output counts only under deterministic
195
+ sampling (`temperature: 0`), on a model with a list price in the catalog that does not
196
+ reason and honors that setting, and when the call that ran carried no hidden reasoning (the
197
+ provider reported zero reasoning tokens or, where it does not break them out, its answer
198
+ had no reasoning block), and then at 95 % of its counter, a margin for the small drift
199
+ `temperature: 0` still has —
200
+ `"estimated"` when the layer removed something (the baseline is then reconstructed
201
+ with the model's tokenizer, or with a calibrated estimate where no exact tokenizer
202
+ exists, as for Anthropic models), and `"unmeasured"` when the provider returned no
203
+ usage. Only `measured` and `reference` figures reach your invoice; estimated ones
204
+ never do.
205
+
206
+ **Streaming.** `generateStream()` takes the same request and hands you the answer as
207
+ the provider writes it, then the same `SavingsReport`:
208
+
209
+ ```ts
210
+ const stream = layer.generateStream({
211
+ model: "openai/gpt-4o-mini",
212
+ messages: [{ role: "user", content: "Summarize the release notes in one line." }],
213
+ });
214
+ for await (const event of stream) {
215
+ if (event.type === "text") process.stdout.write(event.delta);
216
+ }
217
+ const { report: streamed } = await stream.result;
218
+ console.log(streamed.cost.basis); // same rules as generate(), read from the provider's final usage
219
+ console.log(streamed.streaming?.delivery); // "live", or "whole" when the answer arrived in one piece
220
+ ```
221
+
222
+ The SDK asks the provider for its final usage at the end of every stream (on
223
+ OpenAI‑compatible endpoints, `stream_options.include_usage`); a stream that ends
224
+ without it is reported as `unmeasured` and never billed. With an output contract, or
225
+ a request declared `deferrable`, the answer is produced exactly as `generate()` would
226
+ and arrives as a single `text` event. Breaking out of the loop while the provider is
227
+ still writing cancels its request, and nothing is cached or recorded; once its answer
228
+ is complete, or when it arrives in one piece, nothing is cancelled.
229
+ `await stream.result` tells you which happened.
145
230
 
146
231
  ## What you get on every request
147
232
 
@@ -160,6 +245,56 @@ are chosen by counting real tokens with the destination model's own tokenizer, a
160
245
  BV‑SALA only moves away from plain JSON when the saving clearly clears the bar —
161
246
  `report.serialization` shows what was chosen and what each candidate would have cost.
162
247
 
248
+ <details>
249
+ <summary><b>Your own A/B — verification sampling (opt-in, off by default)</b></summary>
250
+
251
+ The per-request figures above are what the layer can show without running your
252
+ request twice. To see what it does on your own traffic, with both sides metered by
253
+ your provider, turn on verification sampling:
254
+
255
+ ```ts
256
+ const checkedLayer = createSavingsLayer({
257
+ provider,
258
+ licenseKey: process.env.BIVELIO_LICENSE_KEY,
259
+ licenseServerUrl: "https://savings.bivelio.com",
260
+ serviceAccountKey: process.env.BIVELIO_SERVICE_KEY,
261
+ verification: { sampleRate: 0.02 }, // about 2 in every 100 eligible requests
262
+ });
263
+ ```
264
+
265
+ For that share of eligible requests, after your answer has been returned, the SDK
266
+ sends the same request once more to your provider exactly as it would go without the
267
+ layer — the model you asked for, all your tools, your data inline, no cache marks —
268
+ and discards the second answer. Each pair (what the unoptimized copy cost and what the
269
+ optimized request cost, both from your provider's usage counters) appears under
270
+ **Proof › Your bench** in your dashboard, with the mean difference, its 95 % interval
271
+ and every pair as a CSV download.
272
+
273
+ - **It costs you money.** You pay your provider for every copy: roughly the sampling
274
+ rate times what that traffic would cost without the layer. The SDK says so once
275
+ per process when it starts (`silenceWarnings: true` mutes that notice too).
276
+ - **It is never billed.** Pairs travel on their own channel into their own table.
277
+ They never enter measured savings, your statement or any commission.
278
+ - **Only requests served intact are checked.** Requests that took the fail-open path,
279
+ were cut off, missed their output contract or came back without a provider counter
280
+ are counted as excluded and never sent twice. `savings: { verification: false }`
281
+ excludes a single request.
282
+ - **Both sides follow the same rule.** A pair is compared only when the copy also
283
+ finished normally and met your contract; a copy cut off by the output limit is
284
+ counted and exported, never scored as a saving.
285
+ - **The headline is the floor, and it waits.** It is the lower bound of the 95 %
286
+ interval, shown once there are enough pairs overall and groups with too few pairs
287
+ of their own are a small share of your traffic, so a group where the layer costs
288
+ you money cannot be left out of it.
289
+ - **Only counts leave your infrastructure.** The copy goes to your own provider, like
290
+ the original; BiVelio receives token counts, costs, model ids and outcomes — never
291
+ prompts or answers.
292
+ - **`close()` settles every copy still running:** it is recorded as abandoned, never
293
+ as a pair. On serverless platforms, pass your runtime's `waitUntil` as
294
+ `verification.waitUntil` so checks can finish.
295
+
296
+ </details>
297
+
163
298
  <details>
164
299
  <summary><b>Structured output — validated <code>data</code>, not just text</b></summary>
165
300
 
@@ -255,8 +390,10 @@ BVSALA_SEAT_ID=worker-03 # stable per replica; [A-Za-z0-9._:/-], max 128 chars
255
390
 
256
391
  - **Node ≥ 20.** The package is **ESM‑only** (`"type": "module"`); from CommonJS,
257
392
  `require()` works on Node ≥ 22.
258
- - Optional peer `gpt-tokenizer` for exact OpenAI token counts — without it a
259
- built‑in estimator is used, so it is not required to get started.
393
+ - Optional peer `gpt-tokenizer` for exact OpenAI token counts, loaded automatically
394
+ when installed — without it a built‑in estimator is used. It improves the accuracy
395
+ of estimated figures; what reaches your invoice is `measured` and `reference`, which
396
+ come from the provider's own counter either way.
260
397
  - Every component (cache, vector store, tokenizer, compressor, constraint backend)
261
398
  ships with an in‑memory default and is independently swappable behind the same
262
399
  interfaces.
@@ -267,7 +404,7 @@ BVSALA_SEAT_ID=worker-03 # stable per replica; [A-Za-z0-9._:/-], max 128 chars
267
404
 
268
405
  BV‑SALA is commercial, per‑seat software (**€4 / seat / month** or **€39 / seat /
269
406
  year**, EUR; VAT not included). No free tier and no trial: what you can check before
270
- paying is the published measurement. The license is a signed token your SDK verifies **offline**; the
407
+ paying is the published measurement. The license is a signed token your SDK and the gateway verify **offline**; the
271
408
  managed control plane and the revocation kill‑switch are served from BiVelio. Your
272
409
  LLM traffic goes **straight to your provider** — BiVelio sees counts and cost,
273
410
  never prompts or completions.