@bivelio/savings-layer 0.4.30 → 0.4.31

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/dist/index.d.ts CHANGED
@@ -325,7 +325,7 @@ declare function precioDeLista(model: string, at?: Date | number): CostModel | u
325
325
  * on the customer's machine. `test/version-declarada.test.ts` ties this
326
326
  * constant to package.json so the two can never drift.
327
327
  */
328
- declare const BV_SALA_VERSION = "0.4.30";
328
+ declare const BV_SALA_VERSION = "0.4.31";
329
329
 
330
330
  /**
331
331
  * Environment-agnostic UUID generation.
@@ -546,7 +546,11 @@ interface OutputContract {
546
546
  /** Stable identifier, e.g. "research-answer:v3". */
547
547
  contractId: string;
548
548
  schema: JsonSchema;
549
- /** Hard ceiling on generated tokens. */
549
+ /**
550
+ * Hard ceiling on generated tokens for this contract. A free-text request (no
551
+ * schema) sets its ceiling with {@link SavingsRequest.maxOutputTokens}; when
552
+ * both are set, the smaller one is sent.
553
+ */
550
554
  maxOutputTokens?: number;
551
555
  /** Version marker included in the exact-cache key. */
552
556
  version?: string;
@@ -1103,6 +1107,19 @@ interface SavingsRequest {
1103
1107
  /** Sampling controls that participate in the exact-cache key. */
1104
1108
  temperature?: number;
1105
1109
  seed?: number;
1110
+ /**
1111
+ * Hard ceiling on generated tokens for this request, with or without an
1112
+ * output contract. It travels to every adapter: Anthropic `max_tokens`,
1113
+ * Chat Completions `max_completion_tokens` on OpenAI's own API and
1114
+ * `max_tokens` on any other OpenAI-compatible endpoint (see
1115
+ * `OpenAICompatibleOptions.maxTokensParam`). When `response.maxOutputTokens`
1116
+ * is also set, the smaller of the two is sent. Unset, nothing changes.
1117
+ *
1118
+ * It is a limit you set, not a saving: it never raises the estimated output
1119
+ * of an avoided call and is never credited as output avoided. An answer cut
1120
+ * by it (`finishReason: "length"`) is not cached. Must be a positive integer.
1121
+ */
1122
+ maxOutputTokens?: number;
1106
1123
  /** Tokenizer id to cost against; defaults to a model-derived estimate. */
1107
1124
  tokenizer?: string;
1108
1125
  /** Arbitrary structured payload to be serialized into the prompt. */
@@ -1336,11 +1353,12 @@ interface QualityTruncationCheck {
1336
1353
  /** The stop reason exactly as the provider named it; absent when it reported none. */
1337
1354
  finishReason?: string;
1338
1355
  /**
1339
- * Whether you set the limit (`response.maxOutputTokens`). When false, any
1356
+ * Whether you set the limit (`maxOutputTokens` on the request or on
1357
+ * `response`). When false, any
1340
1358
  * limit that applied was a provider or adapter default.
1341
1359
  */
1342
1360
  capSetByCaller: boolean;
1343
- /** The limit you set, in output tokens. Present only when `capSetByCaller`. */
1361
+ /** The limit that was sent (the smaller of the two when both were set). Present only when `capSetByCaller`. */
1344
1362
  outputLimit?: number;
1345
1363
  }
1346
1364
  /** How a cache hit matched this request. */
@@ -2240,8 +2258,13 @@ interface SemanticCacheOptions {
2240
2258
  *
2241
2259
  * Pass your own for true semantics; the local one only sees LEXICAL closeness.
2242
2260
  * Either way the deterministic guard below still rejects a candidate whose
2243
- * numbers, negation or entities differ, so a weaker vector can only cost you a
2244
- * cache miss — never a wrong answer.
2261
+ * numbers (in digits or spelled out in en/es/ca: "three", "twice", "half"),
2262
+ * negation parity or content words differ beyond its overlap floor. It is a
2263
+ * lexical guard, not a proof: a one-word entity swap in a long query can
2264
+ * still clear the overlap floor, and through 0.4.30 a number written in words
2265
+ * cleared it too (GSM8K: 477 of 564 such variants got their original's
2266
+ * answer). Calibrate on your own traffic before trusting it with answers
2267
+ * that differ by one word.
2245
2268
  */
2246
2269
  embed?: EmbeddingFn;
2247
2270
  /**
@@ -2558,7 +2581,20 @@ interface ToolSelection {
2558
2581
  setIsStable: boolean;
2559
2582
  }
2560
2583
  interface ToolSelectorOptions {
2561
- /** Max tools to include (schema-token budget guard). */
2584
+ /**
2585
+ * Max tools the selector keeps when it has a SIGNAL to select on: a catalogue
2586
+ * whose tools declare `intents` and/or `risk`, with the router on. Default 8.
2587
+ *
2588
+ * It never truncates a catalogue it cannot rank. A catalogue that declares no
2589
+ * `intents` and no `risk` (the usual case: MCP and OpenAI tool lists) and every
2590
+ * request with `savings: { router: false }` go to the provider WHOLE, exactly as
2591
+ * the caller sent them, and the layer credits nothing for the tool block:
2592
+ * cutting by position left the model unable to call the rest (on BFCL
2593
+ * multi-turn, gpt-4o-mini hit the reference call 8/10 times directly and 1/10
2594
+ * through the layer). When the selector does prune a typed catalogue, the
2595
+ * report's `tool-selector` trace lists what was left out and the SDK warns
2596
+ * (once per process, silenced by `silenceWarnings`).
2597
+ */
2562
2598
  maxTools?: number;
2563
2599
  /** Tokenizer-aware scorer for tie-breaking (optional). */
2564
2600
  scoreIntentMatch?: (text: string, tool: ToolDescriptor) => number;
@@ -2604,16 +2640,9 @@ declare class ToolSelector {
2604
2640
  private readonly freezer;
2605
2641
  constructor(opts?: ToolSelectorOptions);
2606
2642
  /**
2607
- * Todas las herramientas que pasó el llamante, acotadas SOLO por el
2608
- * presupuesto de esquema (`maxTools`).
2609
- *
2610
- * Se usa cuando el llamante apaga el router (`savings:{router:false}`): sin
2611
- * clasificación no hay de dónde derivar el riesgo, y filtrar sobre el
2612
- * `defaultRoute()` tiraba en silencio las herramientas de escritura. Apagar el
2613
- * router significa "yo elijo las herramientas", así que la relevancia pasa a
2614
- * ser del llamante — pero el tope de presupuesto NO se levanta: mandar 200
2615
- * esquemas al proveedor es un 400 seguro y una petición que antes funcionaba
2616
- * pasaría a fallar en duro.
2643
+ * Every tool the caller passed, as passed: none left out, none credited.
2644
+ * Used when there is no signal to select on (`savings: { router: false }`,
2645
+ * or a catalogue with no `intents` and no `risk`).
2617
2646
  */
2618
2647
  passthrough(tools: ToolDescriptor[] | undefined): ToolSelection;
2619
2648
  select(text: string, tools: ToolDescriptor[] | undefined, execution: ExecutionClass,
@@ -3014,6 +3043,10 @@ interface ProviderRequest {
3014
3043
  tools?: ToolDescriptor[];
3015
3044
  /** Output contract to enforce via constrained decoding, when available. */
3016
3045
  schema?: JsonSchema;
3046
+ /**
3047
+ * Output cap chosen by the caller: the smaller of `SavingsRequest.maxOutputTokens`
3048
+ * and `response.maxOutputTokens`. Each adapter maps it to its own field.
3049
+ */
3017
3050
  maxOutputTokens?: number;
3018
3051
  temperature?: number;
3019
3052
  seed?: number;
@@ -3504,7 +3537,28 @@ interface SavingsLayerConfig {
3504
3537
  provider: Provider;
3505
3538
  tokenizer: TokenizerRegistry;
3506
3539
  exactCache?: ExactCache;
3540
+ /**
3541
+ * WHERE reworded questions are looked up and stored. Giving the layer a cache
3542
+ * does NOT switch it on: a request still has to ask for it with
3543
+ * `savings.semanticCache`, or you set {@link semanticCacheDefault} to ask for
3544
+ * it on every request. A layer that has a cache and a request that neither
3545
+ * asks for it nor turns it off gets a one-time console warning (silenced by
3546
+ * `silenceWarnings`, or by setting `semanticCacheDefault`, `false` included).
3547
+ */
3507
3548
  semanticCache?: InMemorySemanticCache;
3549
+ /**
3550
+ * Default for `savings.semanticCache` on every request of this layer. A
3551
+ * request's own value always wins, `false` included. Unset, the semantic cache
3552
+ * is used only by requests that ask for it.
3553
+ *
3554
+ * OPT-IN, and deliberately a separate field from {@link semanticCache}: the
3555
+ * cache is the backend, this is the decision to serve answers to reworded
3556
+ * questions, which can serve a wrong answer. Prefer it with a cache built with
3557
+ * `calibration`, which serves nothing until your own traffic certifies a
3558
+ * floor. `false` states that you switch it on per request, and silences the
3559
+ * warning about a cache that no request uses.
3560
+ */
3561
+ semanticCacheDefault?: boolean | "safe-only" | "include-agentic";
3508
3562
  singleflight?: Singleflight;
3509
3563
  resolvers?: ResolverRegistry;
3510
3564
  router?: ExecutionRouter;
@@ -4587,6 +4641,25 @@ interface OpenAICompatibleOptions {
4587
4641
  * saving. Benchmarks that make real cost claims must set this.
4588
4642
  */
4589
4643
  requireUsage?: boolean;
4644
+ /**
4645
+ * Which Chat Completions field carries the output cap (`maxOutputTokens`).
4646
+ *
4647
+ * Default (undefined): `max_completion_tokens` when `baseUrl` is OpenAI's own
4648
+ * API (https://api.openai.com), `max_tokens` everywhere else. OpenAI
4649
+ * deprecated `max_tokens` when it introduced `max_completion_tokens`
4650
+ * (2024-09-12) and its reasoning models (o-series, gpt-5.x) reject it with a
4651
+ * 400; `max_completion_tokens` is accepted by every current OpenAI chat
4652
+ * model. OpenAI-compatible servers (proxies, self-hosted inference servers,
4653
+ * routers, Azure with an older api-version, a local gateway) understand
4654
+ * `max_tokens`, and some silently IGNORE a field they do not know, which
4655
+ * would drop the cap without an error.
4656
+ *
4657
+ * With the default, a 400/422 that names the field sent as unsupported is
4658
+ * retried once with the other one (it is rejected before any token is
4659
+ * generated, so nothing is billed twice) and the model is remembered. An
4660
+ * explicit value always wins and is never switched.
4661
+ */
4662
+ maxTokensParam?: "max_tokens" | "max_completion_tokens";
4590
4663
  }
4591
4664
  /**
4592
4665
  * ¿Cumple este esquema las reglas del modo `strict` de OpenAI?
@@ -4616,6 +4689,7 @@ declare class OpenAICompatibleProvider implements Provider {
4616
4689
  /** Models of this endpoint already known to reject `response_format`. */
4617
4690
  private readonly sinResponseFormat;
4618
4691
  private streamOptionsRejected;
4692
+ private readonly campoDeTopeAprendido;
4619
4693
  private readonly origen;
4620
4694
  constructor(opts: OpenAICompatibleOptions);
4621
4695
  generate(req: ProviderRequest, options?: ProviderStreamOptions): Promise<ProviderResponse>;
@@ -4629,6 +4703,8 @@ declare class OpenAICompatibleProvider implements Provider {
4629
4703
  * as one text event.
4630
4704
  */
4631
4705
  generateStream(req: ProviderRequest, options?: ProviderStreamOptions): AsyncGenerator<ProviderStreamEvent, void, undefined>;
4706
+ private campoDeTope;
4707
+ private corregirCampoDeTope;
4632
4708
  private enviar;
4633
4709
  private construirCuerpo;
4634
4710
  /** Translate a 2xx endpoint response into the provider-neutral shape. */