toolgz 0.2.10 → 0.2.12
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 +89 -10
- package/dist/index.js +4 -1
- package/dist/recommend.d.ts +5 -0
- package/dist/recommend.js +28 -2
- package/dist/types.d.ts +18 -0
- package/llms.txt +17 -4
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<p><strong>Your agent spends 30–70k tokens of context on tool definitions before the user types a word. toolgz gets up to ~85% of it back.</strong></p>
|
|
4
4
|
|
|
5
|
-
<p><em>On a large tool set, at level 3. The default (level 1) reclaims 13–39% and gives up nothing. <code>recommendLevel()</code> tells you which one your tools want; you pass its answer in.</em></p>
|
|
5
|
+
<p><em>On a large tool set, at level 3. The default (level 1) reclaims 13–39% on real-world tool definitions and gives up nothing. <code>recommendLevel()</code> tells you which one your tools want; you pass its answer in.</em></p>
|
|
6
6
|
|
|
7
7
|
<p>
|
|
8
8
|
<a href="#measured-results">420-run cross-provider sweep</a> ·
|
|
@@ -44,7 +44,7 @@ Reclaiming the room is what this does.
|
|
|
44
44
|
```ts
|
|
45
45
|
import { compress, recommendLevel, forAnthropic } from "toolgz";
|
|
46
46
|
|
|
47
|
-
const { level } = recommendLevel(myTools); // advice: 1
|
|
47
|
+
const { level } = recommendLevel(myTools); // advice: 0, 1 or 3 — see below
|
|
48
48
|
const c = compress(myTools, { level }); // your existing MCP/SDK tool array
|
|
49
49
|
const { tools, system } = forAnthropic(c); // send these instead
|
|
50
50
|
```
|
|
@@ -54,6 +54,11 @@ provider schema enforcement intact, and 13–39% smaller (13–32% on the synthe
|
|
|
54
54
|
**level 3**, which is what `recommendLevel` returns once a tool set is big enough to
|
|
55
55
|
amortise the dispatcher.
|
|
56
56
|
|
|
57
|
+
Level 1 earns that by stripping prose, so it needs prose to strip: if your descriptions
|
|
58
|
+
are already one short sentence, it **adds** ~15% instead and `recommendLevel` returns
|
|
59
|
+
**0** — leave the tools alone. Real MCP catalogues are verbose, which is why they land at
|
|
60
|
+
the top of that range.
|
|
61
|
+
|
|
57
62
|
**Note the shape of those three lines: `recommendLevel` only advises, and you pass its
|
|
58
63
|
answer back in.** Nothing upgrades itself — `compress(myTools)` is level 1 whether you
|
|
59
64
|
have 2 tools or 500. See [Which level to use](#which-level-to-use) for why.
|
|
@@ -78,8 +83,10 @@ committed in [`bench/results/`](bench/results/); recompute any figure with
|
|
|
78
83
|
|
|
79
84
|
**The table below uses a synthetic catalogue** — 100 realistic-but-invented MCP-style
|
|
80
85
|
tools across 9 namespaces — because it lets us build deliberately confusable clusters
|
|
81
|
-
that a real catalogue may not contain.
|
|
82
|
-
|
|
86
|
+
that a real catalogue may not contain. Four of the five scenarios draw a 30-tool
|
|
87
|
+
confusable subset from it; the fifth (`acc-haystack`) uses all 100.
|
|
88
|
+
|
|
89
|
+
Two things follow from that, and the first is uncomfortable:
|
|
83
90
|
|
|
84
91
|
- Synthetic naming can flatter a compression style. One map style measured −21% on
|
|
85
92
|
this fixture because every tool name carried a `namespace_op` prefix to factor out.
|
|
@@ -206,7 +213,7 @@ the tool name that compression takes away.
|
|
|
206
213
|
|
|
207
214
|
<picture>
|
|
208
215
|
<source media="(prefers-color-scheme: dark)" srcset="docs/img/reliability-dark.svg">
|
|
209
|
-
<img src="docs/img/reliability-light.svg" alt="Task completion by level-3 map style and provider
|
|
216
|
+
<img src="docs/img/reliability-light.svg" alt="Task completion by level-3 map style and provider. Bare names fail on grok-4.5; that style and the terse-description style were removed in 0.2.0 and are marked as such">
|
|
210
217
|
</picture>
|
|
211
218
|
|
|
212
219
|
The model doesn't lose the ability to choose — it converts a recall problem into a retrieval
|
|
@@ -214,6 +221,10 @@ problem and looks up what it needs. The default map style exists because of the
|
|
|
214
221
|
bare tool names failed on `grok-4.5` **deterministically**, 3 of 3 attempts on one scenario,
|
|
215
222
|
answering with zero tool calls and no error raised. Naming the required arguments fixed it.
|
|
216
223
|
|
|
224
|
+
Two of the four rows are marked **(removed)**: those styles were deleted in 0.2.0 and you
|
|
225
|
+
cannot select them. They are charted anyway because they *are* the evidence for the
|
|
226
|
+
default — deleting the losing arms would delete the reason.
|
|
227
|
+
|
|
217
228
|
### How we found the cost story, and got it wrong twice
|
|
218
229
|
|
|
219
230
|
<picture>
|
|
@@ -243,6 +254,11 @@ tried and removed in 0.2.0 because they were smaller and still worse.
|
|
|
243
254
|
|
|
244
255
|
### The whole idea, in plain English
|
|
245
256
|
|
|
257
|
+
<picture>
|
|
258
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/img/metaphor-dark.svg">
|
|
259
|
+
<img src="docs/img/metaphor-light.svg" alt="Three panels. Level 0: the full menu, 149 tools on the wire, nothing saved, kitchen checks the order. Level 1: the same menu with prose cut, 149 tools on the wire, 45.2% smaller, kitchen still checks the order. Level 3: a small numbered card and one waiter, 2 tools on the wire, 96.5% smaller, toolgz checks the order instead of the provider.">
|
|
260
|
+
</picture>
|
|
261
|
+
|
|
246
262
|
Your tool definitions are a **menu** handed to the model at the start of every single
|
|
247
263
|
conversation. It is long, and most of it is flowery prose about each dish.
|
|
248
264
|
|
|
@@ -267,7 +283,7 @@ So: **level 1 is smaller. Level 3 is much smaller and you take over order-checki
|
|
|
267
283
|
compress(myTools) // level 1. ALWAYS — 2 tools or 500.
|
|
268
284
|
compress(myTools, { level: 3 }) // level 3, because you asked for it
|
|
269
285
|
|
|
270
|
-
const { level } = recommendLevel(myTools); // just advice: returns 1 or 3
|
|
286
|
+
const { level } = recommendLevel(myTools); // just advice: returns 0, 1 or 3
|
|
271
287
|
compress(myTools, { level }); // now it's 3, because you passed it in
|
|
272
288
|
```
|
|
273
289
|
|
|
@@ -297,7 +313,7 @@ saving.
|
|
|
297
313
|
|
|
298
314
|
### The levels in full
|
|
299
315
|
|
|
300
|
-
Ask the library. It returns 1 or 3
|
|
316
|
+
Ask the library. It returns 0, 1 or 3 — never 2 — and explains itself:
|
|
301
317
|
|
|
302
318
|
```ts
|
|
303
319
|
import { recommendLevel } from "toolgz";
|
|
@@ -422,6 +438,35 @@ style in 0.2.0.
|
|
|
422
438
|
|
|
423
439
|
---
|
|
424
440
|
|
|
441
|
+
## See it run
|
|
442
|
+
|
|
443
|
+
A demo you can put on a screen. It prints every step — the tool definitions going in,
|
|
444
|
+
what `compress()` turns them into, the request, the model's actual response, the
|
|
445
|
+
`resolve()` translation, and your dispatcher running unchanged.
|
|
446
|
+
|
|
447
|
+
```bash
|
|
448
|
+
npm run demo -- --level=3 # one level, every step
|
|
449
|
+
npm run demo -- --compare # levels 0, 1 and 3 back to back
|
|
450
|
+
npm run demo -- --level=3 --offline # no API key: scripted model, real library
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
`--compare` ends with the side-by-side:
|
|
454
|
+
|
|
455
|
+
```
|
|
456
|
+
level wire tools definition chars reclaimed turns argument checking
|
|
457
|
+
0 6 → 6 3,283 → 3,289 -0.2% 2 provider
|
|
458
|
+
1 6 → 6 3,283 → 1,999 39.1% 2 provider
|
|
459
|
+
3 6 → 2 3,283 → 807 75.4% 2 toolgz
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
It really runs the loop against a real model unless you pass `--offline`, and it says
|
|
463
|
+
which it did. Level 3 also demonstrates the two outcomes a happy path never shows —
|
|
464
|
+
a bad code coming back `kind: "error"` and recoverable, and a `q()` lookup coming back
|
|
465
|
+
`kind: "meta"` with nothing dispatched.
|
|
466
|
+
|
|
467
|
+
Not shipped in the npm package: it imports a provider SDK, which is a devDependency,
|
|
468
|
+
and the library's zero-runtime-dependency guarantee is not negotiable.
|
|
469
|
+
|
|
425
470
|
## Runnable examples
|
|
426
471
|
|
|
427
472
|
Five files in [`examples/`](examples), all offline — no API key, no cost. Every one is
|
|
@@ -935,11 +980,37 @@ number can be recomputed rather than trusted.
|
|
|
935
980
|
| `mapStyle` | `"name+required" \| "explicit" \| "signature"` | `"name+required"` | level 3 only |
|
|
936
981
|
| `namespaceOf` | `(name) => {ns, op}` | split on first `_`/`.` | levels 2–3 grouping |
|
|
937
982
|
| `aliasOf` | `(ns) => string` | identity | level 2 tool naming |
|
|
983
|
+
| `signaturePrefix` | `boolean` | `true` | level 1 only; see below |
|
|
938
984
|
| `searchLimit` | `number` | `8` | max results from a `q` search |
|
|
939
985
|
| `validate` | `boolean` | `true` | **leave this on** |
|
|
940
986
|
| `model` | `string` | — | exact model id; picks the measured style. Omit and nothing changes |
|
|
941
987
|
| `objective` | `"occupancy" \| "cost"` | `"occupancy"` | what the pick optimises. Only `cost` has entries |
|
|
942
988
|
|
|
989
|
+
#### `signaturePrefix` — measured, and not the default for a reason
|
|
990
|
+
|
|
991
|
+
Level 1 prepends `name(a,b?) — ` to each description while keeping the full
|
|
992
|
+
`input_schema`. That prefix restates the tool name, property names, required list, enums
|
|
993
|
+
and item types that the schema already carries, and it is **18.5% of the level-1
|
|
994
|
+
payload** on the real corpus. Setting it `false` makes level 1 strictly smaller — 45.2%
|
|
995
|
+
→ **55.3%** on 149 real tools — and removes the case where level 1 *inflates* a terse
|
|
996
|
+
catalogue at all (−14.4% → −0.6%, which is level 0's own floor).
|
|
997
|
+
|
|
998
|
+
It stays `true` by default because size was not the question. Over 64 live runs, removing
|
|
999
|
+
it was a clear win on three providers and **not** on the fourth:
|
|
1000
|
+
|
|
1001
|
+
| Provider | Block | Median cost | Turns |
|
|
1002
|
+
|---|---:|---:|---|
|
|
1003
|
+
| `gemini-3.1-pro-preview` | −18.5% | −15.3% | 2.00 → 2.00 |
|
|
1004
|
+
| `gpt-5.6-sol` | −26.0% | −13.8% | 2.38 → 2.13 |
|
|
1005
|
+
| `grok-4.5` | −18.2% | −17.2% | 3.00 → 3.00 |
|
|
1006
|
+
| `claude-opus-5` | −18.3% | **+3.8%** | **3.88 → 4.63** |
|
|
1007
|
+
|
|
1008
|
+
On `claude-opus-5` the smaller block is spent on extra turns, and one turn is worth
|
|
1009
|
+
~3,300 prompt tokens here. **Zero malformed arguments and zero hallucinated names on both
|
|
1010
|
+
arms, 64/64 tasks.** So it is safe either way; it is just not universally cheaper. Try it
|
|
1011
|
+
if you are not on Anthropic, and measure. Full write-up in
|
|
1012
|
+
[docs/RESULTS.md](docs/RESULTS.md) Round 7.
|
|
1013
|
+
|
|
943
1014
|
Returns:
|
|
944
1015
|
|
|
945
1016
|
| Field | Type | Notes |
|
|
@@ -964,13 +1035,21 @@ Returns:
|
|
|
964
1035
|
> Use it as a local signal. For anything you publish, measure with your provider's token
|
|
965
1036
|
> counter. `originalChars` and `compressedChars` are on `stats` for the raw counts.
|
|
966
1037
|
>
|
|
967
|
-
> A negative value is possible and correct
|
|
968
|
-
>
|
|
1038
|
+
> A negative value is possible and correct. Level 1 pays for a signature line and gets
|
|
1039
|
+
> paid by the prose it strips, so on tools whose descriptions are already terse it comes
|
|
1040
|
+
> out **~15% larger** — `recommendLevel` returns 0 in that case rather than recommending
|
|
1041
|
+
> it. Even level 0 reports **−0.6%**: `c.tools` is Anthropic-shaped, and `input_schema` is
|
|
1042
|
+
> one character longer than the `inputSchema` most callers pass in. Structural, not waste.
|
|
969
1043
|
| `encodeCallForTest(name, args)` | `→ {name, args}` | build the raw call a model would emit; test aid |
|
|
970
1044
|
|
|
971
1045
|
### `recommendLevel(tools, namespaceOf?) → Recommendation`
|
|
972
1046
|
|
|
973
|
-
`{ level, reason, toolCount, namespaceCount, opsPerNamespace }`. Returns 1 or 3, never 2.
|
|
1047
|
+
`{ level, reason, toolCount, namespaceCount, opsPerNamespace }`. Returns 0, 1 or 3, never 2.
|
|
1048
|
+
|
|
1049
|
+
**`level: 0` means this library has nothing to offer your tool set.** Level 1 would
|
|
1050
|
+
inflate it — terse descriptions leave no prose to strip, so the signature line is pure
|
|
1051
|
+
addition — and the block is too small for level 3's trade to be worth it. Telling you that
|
|
1052
|
+
is more useful than recommending a transform that makes the payload bigger.
|
|
974
1053
|
|
|
975
1054
|
**It advises; it does not act.** Pass the answer back in yourself —
|
|
976
1055
|
`compress(tools, { level })`. Calling `compress(tools)` alone is level 1 regardless of
|
package/dist/index.js
CHANGED
|
@@ -228,11 +228,14 @@ export function compress(input, options = {}) {
|
|
|
228
228
|
// Level 1 — signature flattening, native tools, real names
|
|
229
229
|
// -------------------------------------------------------------------------
|
|
230
230
|
if (level === 1) {
|
|
231
|
+
const withSig = options.signaturePrefix ?? true;
|
|
231
232
|
const wire = tools.map((t) => {
|
|
232
233
|
const d = firstSentence(t.description);
|
|
234
|
+
// With no description the signature is the only content there is, so it stays
|
|
235
|
+
// regardless — an empty description would leave the model nothing to read.
|
|
233
236
|
return {
|
|
234
237
|
name: t.name,
|
|
235
|
-
description: d ? `${signatureLine(t)} — ${d}` : signatureLine(t),
|
|
238
|
+
description: withSig || !d ? (d ? `${signatureLine(t)} — ${d}` : signatureLine(t)) : d,
|
|
236
239
|
input_schema: flattenSchema(t.schema),
|
|
237
240
|
};
|
|
238
241
|
});
|
package/dist/recommend.d.ts
CHANGED
|
@@ -44,6 +44,11 @@ export type Recommendation = {
|
|
|
44
44
|
* there unless the caller passes a level explicitly — level 3 trades away the
|
|
45
45
|
* provider's own schema enforcement, and that is not a trade to make on a
|
|
46
46
|
* caller's behalf because their tool array grew.
|
|
47
|
+
*
|
|
48
|
+
* Returns 0, 1 or 3 — never 2. **0 means "this library has nothing to offer
|
|
49
|
+
* here"**: level 1 would inflate the block, and it is too small for level 3's
|
|
50
|
+
* trade to be worth it. Saying so is more useful than recommending a transform
|
|
51
|
+
* that makes the payload bigger.
|
|
47
52
|
*/
|
|
48
53
|
export declare function recommendLevel(tools: Tool[], namespaceOf?: (n: string) => {
|
|
49
54
|
ns: string;
|
package/dist/recommend.js
CHANGED
|
@@ -38,6 +38,11 @@ import { compress } from "./index.js";
|
|
|
38
38
|
* there unless the caller passes a level explicitly — level 3 trades away the
|
|
39
39
|
* provider's own schema enforcement, and that is not a trade to make on a
|
|
40
40
|
* caller's behalf because their tool array grew.
|
|
41
|
+
*
|
|
42
|
+
* Returns 0, 1 or 3 — never 2. **0 means "this library has nothing to offer
|
|
43
|
+
* here"**: level 1 would inflate the block, and it is too small for level 3's
|
|
44
|
+
* trade to be worth it. Saying so is more useful than recommending a transform
|
|
45
|
+
* that makes the payload bigger.
|
|
41
46
|
*/
|
|
42
47
|
export function recommendLevel(tools, namespaceOf = defaultNamespaceOf) {
|
|
43
48
|
const namespaces = new Set(tools.map((t) => namespaceOf(t.name).ns));
|
|
@@ -53,7 +58,8 @@ export function recommendLevel(tools, namespaceOf = defaultNamespaceOf) {
|
|
|
53
58
|
// real MCP tools the ratio is a stable ~2.1 chars/token (1.98 at 10 tools, 2.15 at
|
|
54
59
|
// 149), so dividing gives a fair local estimate with no API call.
|
|
55
60
|
const CHARS_PER_TOKEN = 2.1;
|
|
56
|
-
const
|
|
61
|
+
const l1 = compress(tools, { level: 1 });
|
|
62
|
+
const l1Tokens = Math.round(l1.stats.compressedChars / CHARS_PER_TOKEN);
|
|
57
63
|
// The old heuristic gated level 3 on `opsPerNamespace >= 4`. That is a LEVEL-2
|
|
58
64
|
// question: level 2 pays dispatcher overhead per namespace, so its shape matters
|
|
59
65
|
// there. Level 3 uses one flat dispatcher and does not care about namespaces at all.
|
|
@@ -73,10 +79,30 @@ export function recommendLevel(tools, namespaceOf = defaultNamespaceOf) {
|
|
|
73
79
|
// 4,000 threshold for pushing 14-tool sets into dispatcher mode; they were right.
|
|
74
80
|
const THRESHOLD_TOKENS = 10000;
|
|
75
81
|
if (l1Tokens < THRESHOLD_TOKENS) {
|
|
82
|
+
// Level 1 is not unconditionally free, and this function used to assume it was.
|
|
83
|
+
//
|
|
84
|
+
// Level 1 saves by truncating each description to its first sentence and stripping
|
|
85
|
+
// per-property prose from the schema, and it spends by prepending a signature line.
|
|
86
|
+
// On a real MCP catalogue the prose dominates and it nets ~45%. On tools whose
|
|
87
|
+
// descriptions are already one short sentence there is nothing to strip, the
|
|
88
|
+
// signature is pure addition, and level 1 comes out ~15% LARGER than doing nothing.
|
|
89
|
+
//
|
|
90
|
+
// Reported by an external reviewer at 25-1,000 generated tools; they read it as
|
|
91
|
+
// correct behaviour for level 1, which it is. What is not correct is *recommending*
|
|
92
|
+
// it: a recommendation that inflates the payload is worse than no recommendation.
|
|
93
|
+
// So say so, and name level 3 as the real alternative rather than implying the
|
|
94
|
+
// choice is between level 1 and nothing.
|
|
95
|
+
if (l1.stats.savedPct <= 0) {
|
|
96
|
+
return {
|
|
97
|
+
...base,
|
|
98
|
+
level: 0,
|
|
99
|
+
reason: `Level 1 would make this block ${Math.abs(l1.stats.savedPct).toFixed(1)}% LARGER, not smaller — your descriptions are already terse, so there is no prose to strip and level 1's signature line is pure overhead. Leave the tools alone. If you want the room anyway, level 3 does compress this (${compress(tools, { level: 3 }).stats.savedPct.toFixed(1)}%), at the cost of the provider's own argument validation; at ~${l1Tokens.toLocaleString()} tokens that trade is usually not worth it.`,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
76
102
|
return {
|
|
77
103
|
...base,
|
|
78
104
|
level: 1,
|
|
79
|
-
reason: `The tool block is only ~${l1Tokens.toLocaleString()} tokens at level 1. Level 3 would be smaller, but not by enough to be worth giving up the provider's own argument validation, which level 1 keeps. Reach for level 3 when the block is large enough that reclaiming it changes what fits in your context.`,
|
|
105
|
+
reason: `The tool block is only ~${l1Tokens.toLocaleString()} tokens at level 1, which saves ${l1.stats.savedPct.toFixed(1)}%. Level 3 would be smaller, but not by enough to be worth giving up the provider's own argument validation, which level 1 keeps. Reach for level 3 when the block is large enough that reclaiming it changes what fits in your context.`,
|
|
80
106
|
};
|
|
81
107
|
}
|
|
82
108
|
return {
|
package/dist/types.d.ts
CHANGED
|
@@ -80,6 +80,24 @@ export type CompressOptions = {
|
|
|
80
80
|
};
|
|
81
81
|
/** Override the short alias used for a namespace at level 2. */
|
|
82
82
|
aliasOf?: (ns: string) => string;
|
|
83
|
+
/**
|
|
84
|
+
* Level 1 only. Prepend `name(a,b?)` to each description. Default true, which is
|
|
85
|
+
* the historical behaviour and what every published level-1 figure was measured with.
|
|
86
|
+
*
|
|
87
|
+
* **Experimental, and measured before it may become a default.** The prefix is fully
|
|
88
|
+
* redundant with the `input_schema` level 1 retains — the model already has the tool
|
|
89
|
+
* name, the property names, the required list, the enums and the item types — and it
|
|
90
|
+
* costs 18.5% of the level-1 payload on the real 149-tool corpus. Setting this false
|
|
91
|
+
* makes level 1 strictly smaller and removes the case where level 1 *inflates* a
|
|
92
|
+
* terse catalogue.
|
|
93
|
+
*
|
|
94
|
+
* It is not the default because "smaller" is not the question. Every arm that
|
|
95
|
+
* measured clean — zero malformed arguments, no extra turns — had the prefix, and a
|
|
96
|
+
* one-line signature may be easier for a model to read than the equivalent JSON.
|
|
97
|
+
* Whether that matters at level 1, where the schema is present anyway, is a
|
|
98
|
+
* benchmark result and not yet in hand.
|
|
99
|
+
*/
|
|
100
|
+
signaturePrefix?: boolean;
|
|
83
101
|
/** Cap how many results a search/query meta-call returns. Default 8. */
|
|
84
102
|
searchLimit?: number;
|
|
85
103
|
/** Validate arguments against the original schema before dispatch. Default true. */
|
package/llms.txt
CHANGED
|
@@ -75,12 +75,18 @@ before toolgz existed.
|
|
|
75
75
|
|
|
76
76
|
## Choosing a level
|
|
77
77
|
|
|
78
|
-
Ask, don't guess: `recommendLevel(myTools)` returns `{ level, reason }` — 1 or 3
|
|
78
|
+
Ask, don't guess: `recommendLevel(myTools)` returns `{ level, reason }` — **0, 1 or 3**,
|
|
79
|
+
never 2.
|
|
80
|
+
|
|
81
|
+
`level: 0` means *leave the tools alone*: level 1 would make this block larger, and it is
|
|
82
|
+
too small for level 3 to be worth giving up provider-side validation. Level 1 is not
|
|
83
|
+
unconditionally free — it saves by stripping prose, so on tools whose descriptions are
|
|
84
|
+
already one short sentence it adds ~15% instead. Do not override a 0 without measuring.
|
|
79
85
|
|
|
80
86
|
| Level | Wire | Names | Provider schema enforcement | When |
|
|
81
87
|
|:-:|---|:-:|:-:|---|
|
|
82
88
|
| 0 | unchanged | real | yes | A/B control only |
|
|
83
|
-
| **1** | one native tool each, flattened schemas | real | **yes** | **default.** Reclaims 13–39
|
|
89
|
+
| **1** | one native tool each, flattened schemas | real | **yes** | **default.** Reclaims 13–39% on verbose real-world schemas; **inflates ~15% on already-terse ones** |
|
|
84
90
|
| 2 | one tool per namespace | real | no | only if you need real op names on the wire |
|
|
85
91
|
| **3** | one dispatcher + one lookup | codes | no | **large tool sets.** Up to ~85–96% |
|
|
86
92
|
|
|
@@ -164,8 +170,15 @@ recommendLevel(tools, namespaceOf?) → { level, reason, toolCount, namespaceCou
|
|
|
164
170
|
selectMapStyle(options) → { mapStyle, requestedMapStyle?, fallbackReason? } // pure
|
|
165
171
|
```
|
|
166
172
|
|
|
167
|
-
`CompressOptions`: `level`, `mapStyle`, `namespaceOf`, `aliasOf`, `
|
|
168
|
-
`validate`, `model`, `objective`.
|
|
173
|
+
`CompressOptions`: `level`, `mapStyle`, `namespaceOf`, `aliasOf`, `signaturePrefix`,
|
|
174
|
+
`searchLimit`, `validate`, `model`, `objective`.
|
|
175
|
+
|
|
176
|
+
`signaturePrefix` (level 1, default true) controls the `name(a,b?) — ` prefix on each
|
|
177
|
+
description. Setting it false makes level 1 smaller — 45.2% → 55.3% on real MCP tools —
|
|
178
|
+
and measured cheaper on Gemini, OpenAI and xAI over 64 runs. On `claude-opus-5` it was
|
|
179
|
+
**+3.8% dearer** because turns rose 3.88 → 4.63. Safe either way (zero malformed
|
|
180
|
+
arguments on both arms); not universally cheaper. Do not set it without measuring your
|
|
181
|
+
own workload.
|
|
169
182
|
|
|
170
183
|
`CompressResult`: `tools`, `systemPreamble`, `cachePreamble`, `resolve()`, `codeFor()`,
|
|
171
184
|
`encodeCallForTest()`, `stats`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolgz",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.12",
|
|
4
4
|
"description": "Compress LLM tool definitions to reclaim context window. Measured across Anthropic, OpenAI, Google and xAI.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
"test:live": "vitest run tests/integration",
|
|
13
13
|
"bench:tier1": "tsx -r dotenv/config bench/harness/run-multi.ts --provider=all --suite=real --variants --reps=1 --scenario=real-daily-vs-weekly,real-overwrite-vs-append,real-status-vs-result,real-rollup-vs-detail",
|
|
14
14
|
"bench:tier2": "tsx -r dotenv/config bench/harness/run-multi.ts --provider=all --suite=real --variants --reps=2 --scenario=real-daily-vs-weekly,real-overwrite-vs-append,real-status-vs-result,real-rollup-vs-detail,real-two-step-scheduling,real-summary-vs-details",
|
|
15
|
-
"bench:tier3": "tsx -r dotenv/config bench/harness/run-multi.ts --provider=all --suite=real --variants --reps=3"
|
|
15
|
+
"bench:tier3": "tsx -r dotenv/config bench/harness/run-multi.ts --provider=all --suite=real --variants --reps=3",
|
|
16
|
+
"img:metaphor": "tsx bench/metaphor.ts",
|
|
17
|
+
"demo": "tsx demo/cli.ts",
|
|
18
|
+
"bench:noprefix": "tsx -r dotenv/config bench/harness/run-multi.ts --provider=all --suite=real --variants --arms=signatures,signatures-noprefix --reps=2 --scenario=real-daily-vs-weekly,real-overwrite-vs-append,real-status-vs-result,real-rollup-vs-detail"
|
|
16
19
|
},
|
|
17
20
|
"keywords": [
|
|
18
21
|
"llm",
|