@bivelio/savings-layer 0.5.2 → 1.1.0
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/CHANGELOG.md +138 -0
- package/LICENSE +183 -163
- package/LICENSE-CLOUD.md +174 -0
- package/NOTICE +11 -0
- package/README.md +228 -122
- package/dist/adapters/ai-sdk.js +78 -78
- package/dist/adapters/anthropic-sdk.js +88 -88
- package/dist/adapters/langchain.js +58 -58
- package/dist/adapters/mastra.js +70 -70
- package/dist/gateway/bin.js +1291 -1082
- package/dist/index.d.ts +393 -194
- package/dist/index.js +1704 -1612
- package/docs/terminales/README.md +39 -33
- package/docs/terminales/aider.md +1 -1
- package/docs/terminales/claude-code.md +2 -2
- package/docs/terminales/cline.md +1 -1
- package/docs/terminales/codex.md +3 -3
- package/docs/terminales/copilot.md +1 -1
- package/docs/terminales/crush.md +1 -1
- package/docs/terminales/cursor.md +32 -0
- package/docs/terminales/deepseek.md +28 -0
- package/docs/terminales/droid.md +1 -1
- package/docs/terminales/gemini.md +38 -0
- package/docs/terminales/goose.md +2 -2
- package/docs/terminales/kilo.md +2 -2
- package/docs/terminales/no-soportados.md +16 -27
- package/docs/terminales/ollama.md +19 -0
- package/docs/terminales/opencode.md +2 -2
- package/docs/terminales/qwen-code.md +2 -2
- package/package.json +50 -20
package/README.md
CHANGED
|
@@ -11,50 +11,124 @@
|
|
|
11
11
|
<p align="center">
|
|
12
12
|
<a href="https://www.npmjs.com/package/@bivelio/savings-layer"><img src="https://img.shields.io/npm/v/%40bivelio%2Fsavings-layer?color=2F63D8&label=npm" alt="npm version"></a>
|
|
13
13
|
<img src="https://img.shields.io/badge/node-%E2%89%A520-14A06F" alt="Node ≥ 20">
|
|
14
|
-
<img src="https://img.shields.io/badge/license-
|
|
14
|
+
<img src="https://img.shields.io/badge/license-Apache--2.0-2F63D8" alt="Apache-2.0">
|
|
15
|
+
<img src="https://img.shields.io/badge/runtime%20dependencies-0-14A06F" alt="0 runtime dependencies">
|
|
16
|
+
<img src="https://img.shields.io/badge/telemetry-off%20by%20default-0F1218" alt="Telemetry off by default">
|
|
15
17
|
</p>
|
|
16
18
|
|
|
17
|
-
###
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
that don't have to happen
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
19
|
+
### Open core: install it and it saves. No key. No signup. No telemetry.
|
|
20
|
+
|
|
21
|
+
`@bivelio/savings-layer` (**BV‑SALA**) sits between your app — or your coding agent —
|
|
22
|
+
and the LLM provider, and lets through only what has to be paid for: it skips calls
|
|
23
|
+
that don't have to happen (exact + semantic cache, deduplication), trims what does
|
|
24
|
+
(prompt-cache placement, context and tool-output compaction, serialization), and
|
|
25
|
+
routes each request to the cheapest execution that meets your quality bar (model
|
|
26
|
+
cascade, sibling models, provider failover) — on OpenAI, Anthropic, Gemini,
|
|
27
|
+
**DeepSeek, Qwen, GLM, Moonshot**, any OpenAI‑compatible endpoint, and local
|
|
28
|
+
servers (Ollama, vLLM, LM Studio).
|
|
29
|
+
|
|
30
|
+
It is not a promise you take on faith: **every saving is reported request by
|
|
31
|
+
request, valued with your provider's own usage counter at versioned list prices**,
|
|
32
|
+
split into four ledgers (`avoided_calls`, `eliminated_tokens`, `compressed_tokens`,
|
|
33
|
+
`provider_cached_tokens`) and labelled `measured`, `reference` or `estimated`.
|
|
34
|
+
Nothing is sent to BiVelio unless you choose to add a key: **zero telemetry by
|
|
35
|
+
default**, and prompts, completions and provider keys never reach BiVelio
|
|
36
|
+
(the exact per-request fields a cloud key sends are in [LICENSE-CLOUD.md](./LICENSE-CLOUD.md) §5).
|
|
37
|
+
|
|
38
|
+
The free part is the whole savings layer. What a (paid, optional) BiVelio key adds
|
|
39
|
+
is the cloud: the team dashboard, shared semantic caches across seats, org
|
|
40
|
+
entitlements and license management. The cloud subscription is a fixed fee, paid
|
|
41
|
+
whether or not you save — Pro €15 / seat / month or €150 / seat / year; Team
|
|
42
|
+
€129 / month with 10 seats included (€12 per extra seat; €1,290 / year, €120 per
|
|
43
|
+
extra seat), EUR, VAT not included — and there is never a commission on what you
|
|
44
|
+
save (see [LICENSE-CLOUD.md](./LICENSE-CLOUD.md) §6).
|
|
45
|
+
|
|
46
|
+
### Install anywhere, in 60 seconds
|
|
47
|
+
|
|
48
|
+
```shell
|
|
49
|
+
# 1 · Any OpenAI SDK, any language — point it at the local gateway (Python, Node, Cursor, Aider, Cline, OpenWebUI…)
|
|
50
|
+
npx @bivelio/savings-layer gateway --upstream openai --port 8403 # or: deepseek | qwen | glm | moonshot | gemini | ollama | vllm | lmstudio
|
|
51
|
+
# python: OpenAI(base_url="http://127.0.0.1:8403/v1")
|
|
52
|
+
|
|
53
|
+
# 2 · Coding agents, one command, zero configuration
|
|
54
|
+
npx @bivelio/savings-layer claude # Claude Code through the gateway
|
|
55
|
+
npx @bivelio/savings-layer gemini # Gemini CLI (native Gemini protocol)
|
|
56
|
+
npx @bivelio/savings-layer codex # Codex CLI (OpenAI or ChatGPT auth)
|
|
57
|
+
npx @bivelio/savings-layer qwen # Qwen Code
|
|
58
|
+
npx @bivelio/savings-layer aider # Aider
|
|
59
|
+
|
|
60
|
+
# Claude Code with DeepSeek's Anthropic-format endpoint (cache reads at $0.003/1M):
|
|
61
|
+
# ANTHROPIC_BASE_URL=http://127.0.0.1:8402 ANTHROPIC_AUTH_TOKEN=<deepseek key> claude
|
|
62
|
+
# with: npx @bivelio/savings-layer gateway --upstream deepseek
|
|
63
|
+
|
|
64
|
+
# 3 · Your app, TypeScript/JavaScript — the SDK in your code (zero runtime dependencies)
|
|
65
|
+
npm i @bivelio/savings-layer
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
// The imports come from the "Wrap the call" block below; this block compiles
|
|
70
|
+
// together with it (the repo test concatenates every README block into one
|
|
71
|
+
// program, as if you pasted them in order).
|
|
72
|
+
const provider = new OpenAICompatibleProvider({
|
|
73
|
+
baseUrl: "https://api.openai.com/v1",
|
|
74
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
75
|
+
});
|
|
76
|
+
const layer = createSavingsLayer({
|
|
77
|
+
provider,
|
|
78
|
+
// OPEN CORE: nothing else is required — COMMUNITY mode, zero telemetry.
|
|
79
|
+
// For the cloud dashboard + team metering, add (all optional):
|
|
80
|
+
// // licenseKey: process.env.BIVELIO_LICENSE_KEY, (optional: cloud)
|
|
81
|
+
// // licenseServerUrl: "https://savings.bivelio.com",
|
|
82
|
+
// serviceAccountKey: process.env.BIVELIO_SERVICE_KEY,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const { text, report } = await layer.generate({
|
|
86
|
+
model: "openai/gpt-4o-mini",
|
|
87
|
+
messages: [{ role: "user", content: "What is 2 + 2 * 3?" }],
|
|
88
|
+
});
|
|
89
|
+
console.log(text); // "8" — computed locally, no LLM call
|
|
90
|
+
console.log(report.calls.avoidedLlm); // → 1: a whole call you never paid for
|
|
91
|
+
console.log(report.cost.basis); // "estimated": nothing ran, so there is no provider counter
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`npx @bivelio/savings-layer init` prints the exact three steps for your stack
|
|
95
|
+
(Next.js, Express, Vercel AI SDK, Mastra, LangChain.js, Anthropic/OpenAI SDK) and
|
|
96
|
+
`npx @bivelio/savings-layer doctor` prints the exact configuration for every
|
|
97
|
+
installed coding agent. The published benchmark, trial by trial, with the raw CSV,
|
|
98
|
+
is at **[savings.bivelio.com/en/how-it-works](https://savings.bivelio.com/en/how-it-works)**.
|
|
99
|
+
|
|
100
|
+
📄 **The paper:** [Verifiable Measurement of Cost Savings in LLM Applications
|
|
101
|
+
(open-core edition, v2)](./docs/PAPER.md) — live double-runs, percentile cluster
|
|
102
|
+
bootstrap, four ledgers, and every limitation stated. Also at
|
|
103
|
+
**[savings.bivelio.com/paper](https://savings.bivelio.com/paper)**.
|
|
104
|
+
|
|
105
|
+
### Community vs Cloud
|
|
106
|
+
|
|
107
|
+
| | Community (default, no keys) | Cloud (optional `BIVELIO_*` keys) |
|
|
108
|
+
| --- | --- | --- |
|
|
109
|
+
| Exact + semantic cache, deduplication of identical requests | ✅ | ✅ |
|
|
110
|
+
| Model cascade, sibling routing, prompt-cache placement | ✅ | ✅ |
|
|
111
|
+
| Compression, output contracts, four savings ledgers | ✅ | ✅ |
|
|
112
|
+
| Gateway + `bvsala` CLI + MCP server | ✅ | ✅ |
|
|
113
|
+
| Team dashboard, seat metering, shared caches | — | ✅ |
|
|
114
|
+
| Anything sent to BiVelio | **Nothing** | Per-request usage only (counts, cost, model id, opaque ids — [LICENSE-CLOUD.md](./LICENSE-CLOUD.md) §5), never prompts or keys |
|
|
115
|
+
|
|
116
|
+
A license key that **is** configured but invalid or expired still fails closed with
|
|
117
|
+
`LicenseError` — only its absence is a working product.
|
|
118
|
+
|
|
119
|
+
### Cloud pricing (the optional, paid part)
|
|
120
|
+
|
|
121
|
+
The cloud subscription is a fixed fee, paid whether or not you save: **Pro
|
|
122
|
+
€15 / seat / month** or **€150 / seat / year**; **Team €129 / month with 10 seats included** (**€12 per extra seat**; **€1,290 / year, €120 per extra seat**), EUR, VAT not included. The first month carries no commission, and the
|
|
123
|
+
commission on measured savings runs from **10 % down to 4 %** by volume —
|
|
124
|
+
keeping you **at least 90 % of every euro saved**, never from estimates, and
|
|
125
|
+
shadow-mode usage is never part of any commission. No trial. The free
|
|
126
|
+
**Measure** license (no card) still exists for the cloud: it runs the layer in
|
|
127
|
+
**shadow mode** — nothing is served from any cache, nothing you send is
|
|
128
|
+
changed — and shows what the levers would have saved, request by request. The
|
|
129
|
+
older commission-only modality is **closed to new customers**. Your
|
|
130
|
+
prompts, completions and provider keys never reach BiVelio: a cloud key sends
|
|
131
|
+
only the per-request usage fields listed in [LICENSE-CLOUD.md](./LICENSE-CLOUD.md) §5.
|
|
58
132
|
|
|
59
133
|
## Where it installs
|
|
60
134
|
|
|
@@ -74,25 +148,22 @@ products charge a flat subscription, so there is no per‑token bill to cut. The
|
|
|
74
148
|
story, in plain words:
|
|
75
149
|
**[Where does the saving install?](https://savings.bivelio.com/en/how-it-works)**
|
|
76
150
|
|
|
77
|
-
**A coding agent with an API key?** The package also ships a local
|
|
78
|
-
|
|
79
|
-
`copilot` with your own Anthropic key, `aider`) launches the agent through it and
|
|
80
|
-
|
|
81
|
-
persisted. On exit it tells you how many requests it metered and which
|
|
82
|
-
could not, and why. For Crush, Droid, Cline, Continue or Copilot
|
|
83
|
-
`bvsala doctor` prints the exact config block.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
subscription there is no per-token invoice, so that traffic is metered at
|
|
94
|
-
list-price equivalent and never carries commission; the per-seat license applies
|
|
95
|
-
as for any other use.
|
|
151
|
+
**A coding agent with an API key?** The package also ships a local gateway:
|
|
152
|
+
`bvsala claude` (or `bvsala codex`, `goose`, `qwen`, `opencode`, `kilo`, `gemini`,
|
|
153
|
+
`copilot` with your own Anthropic key, `aider`) launches the agent through it and
|
|
154
|
+
your real spend is metered locally, token by token — nothing to configure,
|
|
155
|
+
nothing persisted. On exit it tells you how many requests it metered and which
|
|
156
|
+
ones it could not, and why. For Crush, Droid, Cline, Continue, Cursor or Copilot
|
|
157
|
+
in VS Code, `bvsala doctor` prints the exact config block. The gateway starts
|
|
158
|
+
with **no keys at all** (COMMUNITY mode: every local saving lever armed, zero
|
|
159
|
+
telemetry); `BIVELIO_SERVICE_KEY` adds the cloud dashboard and org entitlements.
|
|
160
|
+
Once running it never interrupts a request in flight: with a live license that
|
|
161
|
+
lapses, only new requests are refused (HTTP 402) until it is renewed. The
|
|
162
|
+
cache-write TTL lever (1h→5m, the saving that changes the rate, never the
|
|
163
|
+
content the model reads) is armed by default; the other levers (sibling model,
|
|
164
|
+
per-turn effort) switch on with their flags. On a Claude or ChatGPT subscription
|
|
165
|
+
there is no per-token invoice, so that traffic is metered at list-price
|
|
166
|
+
equivalent and never carries commission.
|
|
96
167
|
|
|
97
168
|
**The gateway forwards what your terminal sends.** With no lever armed, your
|
|
98
169
|
provider receives the request body byte for byte and every header your client
|
|
@@ -117,6 +188,8 @@ the language:
|
|
|
117
188
|
```bash
|
|
118
189
|
bvsala gateway --upstream openai --port 8403 # one gateway per upstream
|
|
119
190
|
bvsala gateway # Anthropic, the default, on 8402
|
|
191
|
+
bvsala gateway --upstream deepseek # DeepSeek: /v1/messages → its /anthropic endpoint
|
|
192
|
+
bvsala gateway --upstream gemini # Gemini protocol (bvsala gemini wraps the CLI)
|
|
120
193
|
```
|
|
121
194
|
|
|
122
195
|
Flags and environment variables are in English (`--port`, `--project`, `--force`,
|
|
@@ -142,13 +215,14 @@ claude = Anthropic(base_url="http://127.0.0.1:8402") # NO /v1
|
|
|
142
215
|
```
|
|
143
216
|
|
|
144
217
|
With `BIVELIO_SERVICE_KEY` set, the gateway measures every request whose usage the
|
|
145
|
-
provider reports — Anthropic Messages, OpenAI Chat Completions and Responses,
|
|
146
|
-
|
|
218
|
+
provider reports — Anthropic Messages, OpenAI Chat Completions and Responses, and
|
|
219
|
+
the Gemini protocol (`:generateContent` / `:streamGenerateContent`), streamed or
|
|
220
|
+
not — for models with a list price in its catalog, priced at that list price. A
|
|
147
221
|
request it cannot meter (a model without a list price, a body it cannot read, a stream
|
|
148
|
-
that never asked for usage) is still forwarded, and its log line says so and why.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
`bvsala doctor` prints the recipe for each SDK.
|
|
222
|
+
that never asked for usage) is still forwarded, and its log line says so and why.
|
|
223
|
+
Without a key (COMMUNITY mode) the metering numbers stay local: the local report
|
|
224
|
+
routes below work the same, and nothing is sent to BiVelio.
|
|
225
|
+
`bvsala doctor` prints the recipe for each SDK and agent.
|
|
152
226
|
|
|
153
227
|
Each request can also bring its report back to your code. Send the header
|
|
154
228
|
`x-bvsala-report: 1` (in Python, `default_headers={"x-bvsala-report": "1"}` on
|
|
@@ -163,12 +237,11 @@ provider, and a request without it is not changed at all:
|
|
|
163
237
|
|
|
164
238
|
## Check it before you install
|
|
165
239
|
|
|
166
|
-
What you can check before
|
|
167
|
-
trial by trial, with the raw CSV linked, at
|
|
168
|
-
**[savings.bivelio.com/en/how-it-works](https://savings.bivelio.com/en/how-it-works)
|
|
169
|
-
|
|
170
|
-
traffic and
|
|
171
|
-
lives in your dashboard once you sign in.
|
|
240
|
+
What you can check before installing is what we have measured: the published
|
|
241
|
+
benchmark, trial by trial, with the raw CSV linked, at
|
|
242
|
+
**[savings.bivelio.com/en/how-it-works](https://savings.bivelio.com/en/how-it-works)**.
|
|
243
|
+
With no keys at all, the layer runs the full savings stack locally on your own
|
|
244
|
+
traffic and reports per request — nothing leaves your machine.
|
|
172
245
|
|
|
173
246
|
**Upgrading from 0.4?** Every breaking change of 0.5, with a before and after for
|
|
174
247
|
each, is in [Migrating from 0.4 to 0.5](https://savings.bivelio.com/en/migrate-to-0.5).
|
|
@@ -204,13 +277,13 @@ to a built‑in estimate and the reconstructed savings are less accurate. Either
|
|
|
204
277
|
saving where the layer removed something is reported as `estimated`, and estimated
|
|
205
278
|
savings are never billed — the SDK warns once when the tokenizer is missing.
|
|
206
279
|
|
|
207
|
-
**2 · The
|
|
208
|
-
|
|
209
|
-
|
|
280
|
+
**2 · The cloud (optional)** — a [BiVelio key](https://savings.bivelio.com/dashboard/install)
|
|
281
|
+
adds the team dashboard, shared caches and license management. Skip this step and
|
|
282
|
+
nothing changes except the dashboard: the layer keeps working, fully, offline.
|
|
210
283
|
|
|
211
284
|
```bash
|
|
212
|
-
export BIVELIO_LICENSE_KEY=… #
|
|
213
|
-
export BIVELIO_SERVICE_KEY=… #
|
|
285
|
+
export BIVELIO_LICENSE_KEY=… # optional: a key that IS set but invalid still fails closed
|
|
286
|
+
export BIVELIO_SERVICE_KEY=… # optional: cloud metering + license auto-renewal
|
|
214
287
|
```
|
|
215
288
|
|
|
216
289
|
**3 · Wrap the call** — this is the entire code change. The package is ESM only:
|
|
@@ -232,9 +305,11 @@ const provider = new OpenAICompatibleProvider({
|
|
|
232
305
|
|
|
233
306
|
const layer = createSavingsLayer({
|
|
234
307
|
provider,
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
308
|
+
// OPEN CORE: nothing else is required — COMMUNITY mode, zero telemetry.
|
|
309
|
+
// For the cloud dashboard + team metering, add (all optional):
|
|
310
|
+
// // licenseKey: process.env.BIVELIO_LICENSE_KEY, (optional: cloud)
|
|
311
|
+
// // licenseServerUrl: "https://savings.bivelio.com",
|
|
312
|
+
// serviceAccountKey: process.env.BIVELIO_SERVICE_KEY,
|
|
238
313
|
});
|
|
239
314
|
|
|
240
315
|
const { text, report } = await layer.generate({
|
|
@@ -372,7 +447,7 @@ point, from a subpath of this same package. The four adapters behave alike:
|
|
|
372
447
|
- **Tools are treated as having side effects** unless you list them in
|
|
373
448
|
`readOnlyTools`: a request that offers any other tool is never answered from a
|
|
374
449
|
cache, because the action has to run.
|
|
375
|
-
- **
|
|
450
|
+
- **Open core, like the SDK**: with no key the adapter runs in COMMUNITY mode; a key that is set but invalid fails closed with
|
|
376
451
|
`LicenseError` (a framework may wrap it, keeping its name and message) and the
|
|
377
452
|
model is not called. **Put the adapter last**, so nothing sits between it and the
|
|
378
453
|
model.
|
|
@@ -392,8 +467,8 @@ import { openai } from "@ai-sdk/openai";
|
|
|
392
467
|
import { savingsMiddleware, getSavingsReport } from "@bivelio/savings-layer/ai-sdk";
|
|
393
468
|
|
|
394
469
|
const savings = savingsMiddleware({
|
|
395
|
-
licenseKey: process.env.BIVELIO_LICENSE_KEY,
|
|
396
|
-
licenseServerUrl: "https://savings.bivelio.com",
|
|
470
|
+
// licenseKey: process.env.BIVELIO_LICENSE_KEY, (optional: cloud)
|
|
471
|
+
// licenseServerUrl: "https://savings.bivelio.com",
|
|
397
472
|
serviceAccountKey: process.env.BIVELIO_SERVICE_KEY,
|
|
398
473
|
readOnlyTools: ["get_weather"],
|
|
399
474
|
});
|
|
@@ -419,8 +494,8 @@ import { Agent } from "@mastra/core/agent";
|
|
|
419
494
|
import { savingsProcessor } from "@bivelio/savings-layer/mastra";
|
|
420
495
|
|
|
421
496
|
const bivelio = savingsProcessor({
|
|
422
|
-
licenseKey: process.env.BIVELIO_LICENSE_KEY,
|
|
423
|
-
licenseServerUrl: "https://savings.bivelio.com",
|
|
497
|
+
// licenseKey: process.env.BIVELIO_LICENSE_KEY, (optional: cloud)
|
|
498
|
+
// licenseServerUrl: "https://savings.bivelio.com",
|
|
424
499
|
serviceAccountKey: process.env.BIVELIO_SERVICE_KEY,
|
|
425
500
|
onReport: (r) => console.log(r.cost.basis),
|
|
426
501
|
});
|
|
@@ -451,8 +526,8 @@ import { createAgent } from "langchain";
|
|
|
451
526
|
import { savingsAgentMiddleware, getSavingsReportFromMessage } from "@bivelio/savings-layer/langchain";
|
|
452
527
|
|
|
453
528
|
const agentSavings = savingsAgentMiddleware({
|
|
454
|
-
licenseKey: process.env.BIVELIO_LICENSE_KEY,
|
|
455
|
-
licenseServerUrl: "https://savings.bivelio.com",
|
|
529
|
+
// licenseKey: process.env.BIVELIO_LICENSE_KEY, (optional: cloud)
|
|
530
|
+
// licenseServerUrl: "https://savings.bivelio.com",
|
|
456
531
|
serviceAccountKey: process.env.BIVELIO_SERVICE_KEY,
|
|
457
532
|
});
|
|
458
533
|
const lcAgent = createAgent({ model: "openai:gpt-4o-mini", tools: [], middleware: [agentSavings] }); // last
|
|
@@ -474,8 +549,8 @@ import Anthropic from "@anthropic-ai/sdk";
|
|
|
474
549
|
import { anthropicSavings } from "@bivelio/savings-layer/anthropic-sdk";
|
|
475
550
|
|
|
476
551
|
const claudeSavings = anthropicSavings({
|
|
477
|
-
licenseKey: process.env.BIVELIO_LICENSE_KEY,
|
|
478
|
-
licenseServerUrl: "https://savings.bivelio.com",
|
|
552
|
+
// licenseKey: process.env.BIVELIO_LICENSE_KEY, (optional: cloud)
|
|
553
|
+
// licenseServerUrl: "https://savings.bivelio.com",
|
|
479
554
|
serviceAccountKey: process.env.BIVELIO_SERVICE_KEY,
|
|
480
555
|
});
|
|
481
556
|
const client = new Anthropic({ middleware: [claudeSavings.middleware] }); // last in the list
|
|
@@ -578,18 +653,20 @@ single-shot requests only: requests that declare tools, carry tool calls or resu
|
|
|
578
653
|
hold more than one user turn are left out unless you set
|
|
579
654
|
`savings.semanticCache: "include-agentic"`.
|
|
580
655
|
|
|
581
|
-
**Calibrated reuse (
|
|
582
|
-
every embedder and every workload, so
|
|
583
|
-
traffic
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
and
|
|
591
|
-
|
|
592
|
-
|
|
656
|
+
**Calibrated reuse (required to serve).** A fixed similarity floor means something different
|
|
657
|
+
for every embedder and every workload, so the cache must earn its floor on your own
|
|
658
|
+
traffic: `new InMemorySemanticCache({ embed, calibration: { maxErrorRate: 0.02 } })`.
|
|
659
|
+
Without `calibration`, it runs in shadow mode: it may store answers for future calibration,
|
|
660
|
+
but `find()` and `lookup()` never return a hit. With calibration it serves no reworded answer
|
|
661
|
+
until your labels show, at 95 % confidence, that at most 2 % of the answers it would reuse
|
|
662
|
+
from some score up are wrong, and it re-tests that floor as labels accumulate. Until then
|
|
663
|
+
every candidate goes to your provider, exactly as it would without the cache, and the fresh
|
|
664
|
+
answer is compared with the stored one by your `judge` (by default only identical answers
|
|
665
|
+
count, which suits classification and structured output; pass your own for free text). Each
|
|
666
|
+
calibrated reuse carries its bound and the labels behind it in
|
|
667
|
+
`report.qualityLedger.cacheMatch.calibration`. Calibration costs reuses, never an extra call,
|
|
668
|
+
and it does not change how anything is billed. If you already have labeled pairs,
|
|
669
|
+
`calibrateSemanticFloor()` runs the same test offline.
|
|
593
670
|
|
|
594
671
|
And a promise you can hold it to: **never worse than your JSON.** Payload encodings
|
|
595
672
|
are chosen by counting real tokens with the destination model's own tokenizer, and
|
|
@@ -606,8 +683,8 @@ your provider, turn on verification sampling:
|
|
|
606
683
|
```ts
|
|
607
684
|
const checkedLayer = createSavingsLayer({
|
|
608
685
|
provider,
|
|
609
|
-
licenseKey: process.env.BIVELIO_LICENSE_KEY,
|
|
610
|
-
licenseServerUrl: "https://savings.bivelio.com",
|
|
686
|
+
// licenseKey: process.env.BIVELIO_LICENSE_KEY, (optional: cloud)
|
|
687
|
+
// licenseServerUrl: "https://savings.bivelio.com",
|
|
611
688
|
serviceAccountKey: process.env.BIVELIO_SERVICE_KEY,
|
|
612
689
|
verification: { sampleRate: 0.02 }, // about 2 in every 100 eligible requests
|
|
613
690
|
});
|
|
@@ -640,9 +717,11 @@ and every pair as a CSV download.
|
|
|
640
717
|
- **Only counts leave your infrastructure.** The copy goes to your own provider, like
|
|
641
718
|
the original; BiVelio receives token counts, costs, model ids and outcomes — never
|
|
642
719
|
prompts or answers.
|
|
643
|
-
- **`close()` settles every copy still running
|
|
644
|
-
|
|
645
|
-
|
|
720
|
+
- **`await close()` settles every copy still running and drains usage:** an
|
|
721
|
+
unfinished copy is recorded as abandoned, never as a pair. Awaiting close makes
|
|
722
|
+
a bounded final delivery attempt; calling it without await remains safe. On
|
|
723
|
+
serverless platforms, pass your runtime's `waitUntil` as `verification.waitUntil`
|
|
724
|
+
so checks can finish.
|
|
646
725
|
|
|
647
726
|
</details>
|
|
648
727
|
|
|
@@ -686,7 +765,7 @@ default was not run.
|
|
|
686
765
|
```ts
|
|
687
766
|
const leveredLayer = createSavingsLayer({
|
|
688
767
|
provider,
|
|
689
|
-
licenseKey: process.env.BIVELIO_LICENSE_KEY,
|
|
768
|
+
// licenseKey: process.env.BIVELIO_LICENSE_KEY, (optional: cloud)
|
|
690
769
|
priceLevers: { deferrable: true, onCapacityError: "standard" }, // layer-wide defaults
|
|
691
770
|
});
|
|
692
771
|
|
|
@@ -757,7 +836,7 @@ verbatim, so an agent that edits by exact match still sees the file as it is.
|
|
|
757
836
|
```ts
|
|
758
837
|
const agentLayer = createSavingsLayer({
|
|
759
838
|
provider,
|
|
760
|
-
licenseKey: process.env.BIVELIO_LICENSE_KEY,
|
|
839
|
+
// licenseKey: process.env.BIVELIO_LICENSE_KEY, (optional: cloud)
|
|
761
840
|
toolOutputCompaction: true, // or { exceptTools: ["read_logs"], minSavedChars: 512 }
|
|
762
841
|
});
|
|
763
842
|
// or per request: savings: { toolOutputCompaction: true }
|
|
@@ -825,7 +904,7 @@ saving and never part of any commission.
|
|
|
825
904
|
```ts
|
|
826
905
|
const placedLayer = createSavingsLayer({
|
|
827
906
|
provider, // Anthropic, or OpenAI GPT-5.6+ on OpenAI's own API
|
|
828
|
-
licenseKey: process.env.BIVELIO_LICENSE_KEY,
|
|
907
|
+
// licenseKey: process.env.BIVELIO_LICENSE_KEY, (optional: cloud)
|
|
829
908
|
cacheBreakpoints: "auto",
|
|
830
909
|
});
|
|
831
910
|
```
|
|
@@ -860,7 +939,7 @@ async function serverlessLayer(kv: SharedKeyValue) {
|
|
|
860
939
|
|
|
861
940
|
const layer = createSavingsLayer({
|
|
862
941
|
provider,
|
|
863
|
-
licenseKey: process.env.BIVELIO_LICENSE_KEY,
|
|
942
|
+
// licenseKey: process.env.BIVELIO_LICENSE_KEY, (optional: cloud)
|
|
864
943
|
semanticCacheStore: semanticCache,
|
|
865
944
|
// Passing the cache does not switch it on; this asks for it on every request.
|
|
866
945
|
semanticCacheDefault: true,
|
|
@@ -893,13 +972,14 @@ async function serverlessLayer(kv: SharedKeyValue) {
|
|
|
893
972
|
verifier, judge or configuration is refused with `CalibrationStateError`. If you pass
|
|
894
973
|
your own `embed`, `verify` or `judge`, name them (`embedderId`, `verifierId`,
|
|
895
974
|
`calibration.judgeId`) so a state can say what it was recorded with.
|
|
896
|
-
- **One line of history
|
|
897
|
-
|
|
898
|
-
each spend its remaining confidence again, and the last
|
|
899
|
-
others
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
975
|
+
- **One line of history for in-memory snapshots.** Restoring and saving the latest
|
|
976
|
+
state continues one line. Instances that manually continue the same snapshot at
|
|
977
|
+
the same time each spend its remaining confidence again, and the last save
|
|
978
|
+
overwrites the others. Use one long-lived process or offline calibration for that
|
|
979
|
+
manual model. For shared records and one coordinated history across instances,
|
|
980
|
+
use `PgSemanticCache`: its transactions and observation IDs prevent lost labels
|
|
981
|
+
and repeated confidence spending. The in-memory cached answers still live in
|
|
982
|
+
each instance unless you inject a persistent store.
|
|
903
983
|
- **Why a predicted cache read did not happen** (Anthropic's own API). With
|
|
904
984
|
`cacheMissDiagnostics: true`, the layer asks Anthropic to compare each marked request
|
|
905
985
|
with the one whose cache entry it expected to read, and puts the provider's
|
|
@@ -909,6 +989,32 @@ async function serverlessLayer(kv: SharedKeyValue) {
|
|
|
909
989
|
|
|
910
990
|
</details>
|
|
911
991
|
|
|
992
|
+
<details>
|
|
993
|
+
<summary><b>Customer-operated PostgreSQL for caches and learning</b></summary>
|
|
994
|
+
|
|
995
|
+
The optional `@bivelio/persistence-postgres` package provides `PgExactCache`,
|
|
996
|
+
`PgSemanticCache`, `PgCacheBreakpointStore` and `PgConstraintMemoryStore` without
|
|
997
|
+
adding a database driver to the core SDK. You supply the `pg` pool, apply its
|
|
998
|
+
versioned SQL migrations yourself, and inject the stores; nothing connects or
|
|
999
|
+
changes your schema automatically. Exact and semantic entries contain customer
|
|
1000
|
+
queries, answers or derived embeddings and need the same access, retention and
|
|
1001
|
+
deletion controls as the original data. Breakpoints contain fingerprints,
|
|
1002
|
+
timestamps and counters, never prompt text. Constraint learning stores only a
|
|
1003
|
+
hash of tenant, scope, provider, served model and contract.
|
|
1004
|
+
|
|
1005
|
+
The breakpoint adapter uses revisions and compare-and-set. Semantic calibration
|
|
1006
|
+
uses short transactions and idempotent observation IDs, so concurrent instances
|
|
1007
|
+
continue one history instead of spending the same confidence budget independently.
|
|
1008
|
+
Both use the same deterministic candidate guards and statistical journal as memory.
|
|
1009
|
+
The semantic backend requires the PostgreSQL vector extension and is opt-in; existing key-value stores
|
|
1010
|
+
and in-memory defaults keep their previous behavior. Removing `constraintMemoryStore`
|
|
1011
|
+
rolls constraint learning back to process memory without reading the new table.
|
|
1012
|
+
|
|
1013
|
+
See [`packages/persistence-postgres/README.md`](packages/persistence-postgres/README.md)
|
|
1014
|
+
for setup, ownership, cleanup, benchmark evidence and shutdown.
|
|
1015
|
+
|
|
1016
|
+
</details>
|
|
1017
|
+
|
|
912
1018
|
<details>
|
|
913
1019
|
<summary><b>Structured output — validated <code>data</code>, not just text</b></summary>
|
|
914
1020
|
|
|
@@ -953,7 +1059,7 @@ import { GoogleProvider } from "@bivelio/savings-layer";
|
|
|
953
1059
|
|
|
954
1060
|
const gemini = createSavingsLayer({
|
|
955
1061
|
provider: new GoogleProvider({ apiKey: process.env.GEMINI_API_KEY, requireUsage: true }),
|
|
956
|
-
licenseKey: process.env.BIVELIO_LICENSE_KEY,
|
|
1062
|
+
// licenseKey: process.env.BIVELIO_LICENSE_KEY, (optional: cloud)
|
|
957
1063
|
});
|
|
958
1064
|
|
|
959
1065
|
const { text: answer, report: geminiReport } = await gemini.generate({
|
|
@@ -1159,7 +1265,7 @@ import { AnthropicProvider } from "@bivelio/savings-layer";
|
|
|
1159
1265
|
|
|
1160
1266
|
const claude = createSavingsLayer({
|
|
1161
1267
|
provider: new AnthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY }),
|
|
1162
|
-
licenseKey: process.env.BIVELIO_LICENSE_KEY,
|
|
1268
|
+
// licenseKey: process.env.BIVELIO_LICENSE_KEY, (optional: cloud)
|
|
1163
1269
|
});
|
|
1164
1270
|
|
|
1165
1271
|
const mcpTools: ToolDescriptor[] = []; // every tool your MCP servers list
|
|
@@ -1359,7 +1465,7 @@ BVSALA_SEAT_ID=worker-03 # stable per replica; [A-Za-z0-9._:/-], max 128 chars
|
|
|
1359
1465
|
BV‑SALA is commercial software. The plans are **Measure** (free), **Pro** (per seat),
|
|
1360
1466
|
**Team** (a number of seats included, plus each extra seat) and **Enterprise** (by
|
|
1361
1467
|
contract); Pro and Team are paid whether or not you save and carry **no commission** on
|
|
1362
|
-
your savings. The prices are at the top of this page, in [LICENSE](./LICENSE) §6 and in
|
|
1468
|
+
your savings. The prices are at the top of this page, in [LICENSE-CLOUD.md](./LICENSE-CLOUD.md) §6 and in
|
|
1363
1469
|
the Published Pricing it refers to,
|
|
1364
1470
|
**[savings.bivelio.com/pricing](https://savings.bivelio.com/pricing)**. No trial. There is one free license,
|
|
1365
1471
|
**Measure** — no card, one organization per verified account, up to 5 seats and 20,000
|
|
@@ -1383,7 +1489,7 @@ The license is a signed token your SDK and the
|
|
|
1383
1489
|
gateway verify **offline**; the managed control plane and the revocation kill‑switch
|
|
1384
1490
|
are served from BiVelio. Your LLM traffic goes **straight to your provider** —
|
|
1385
1491
|
BiVelio receives per‑request usage (counts, cost, the model id and opaque ids), never
|
|
1386
|
-
prompts, completions or provider keys; [LICENSE](./LICENSE) §5 lists every field, and
|
|
1492
|
+
prompts, completions or provider keys; [LICENSE-CLOUD.md](./LICENSE-CLOUD.md) §5 lists every field, and
|
|
1387
1493
|
**[savings.bivelio.com/privacy](https://savings.bivelio.com/privacy)** says how long
|
|
1388
1494
|
each one is kept.
|
|
1389
1495
|
|