@sohqureshi/tokenwise 1.0.7 → 1.0.10
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 +48 -5
- package/dist/{chunk-IOMOVRC7.js → chunk-2TDVL6BJ.js} +2 -2
- package/dist/chunk-BN3L7TUV.js +241 -0
- package/dist/{chunk-P6KXISNT.js → chunk-FBJ3DHS6.js} +2 -2
- package/dist/chunk-L3HY6AWL.js +69 -0
- package/dist/cli.cjs +169 -19
- package/dist/cli.js +5 -5
- package/dist/core/analyze.cjs +32 -18
- package/dist/core/analyze.js +2 -2
- package/dist/core/natural.cjs +136 -0
- package/dist/core/natural.js +1 -1
- package/dist/core/token.cjs +31 -17
- package/dist/core/token.d.cts +2 -8
- package/dist/core/token.d.ts +2 -8
- package/dist/core/token.js +1 -1
- package/dist/index.cjs +168 -18
- package/dist/index.js +4 -4
- package/package.json +1 -1
- package/dist/chunk-D4CFTFM3.js +0 -105
- package/dist/chunk-U433WUUT.js +0 -55
package/README.md
CHANGED
|
@@ -147,9 +147,25 @@ toNatural({
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
});
|
|
150
|
-
//
|
|
150
|
+
// Carlos Rivera has policy HLT-2048. The claim is under review for 64,000.
|
|
151
151
|
```
|
|
152
152
|
|
|
153
|
+
`toNatural()` uses semantic templates when the field relationships are
|
|
154
|
+
unambiguous (for example, a policy holder, policy number, and claim). Unknown
|
|
155
|
+
JSON shapes use a conservative key-value fallback so the formatter does not
|
|
156
|
+
invent relationships or facts. For dynamic entity-shaped objects, it can
|
|
157
|
+
recognize common subject fields such as `name`, `title`, `label`, `displayName`,
|
|
158
|
+
`entityName`, `fullName`, `userName`, `customerName`, `ownerName`,
|
|
159
|
+
`accountName`, `companyName`, `organizationName`, `teamName`, `projectName`,
|
|
160
|
+
`productName`, `serviceName`, `resourceName`, `deviceName`, `patientName`,
|
|
161
|
+
`taskName`, and `eventName`. These can be paired with state fields such as
|
|
162
|
+
`status`, `state`, `condition`, `stage`, `role`, `type`, `category`, `priority`,
|
|
163
|
+
`phase`, `mode`, `availability`, `outcome`, `result`, `health`, `progress`,
|
|
164
|
+
`visibility`, `access`, `membership`, `sentiment`, and `severity`.
|
|
165
|
+
Matching is case-insensitive and also supports keys that contain these
|
|
166
|
+
concepts, such as `patientName`, `orderStatus`, `currentPhase`, or
|
|
167
|
+
`incidentSeverity`.
|
|
168
|
+
|
|
153
169
|
### `toTOON()`
|
|
154
170
|
|
|
155
171
|
Converts JSON into a compact TOON-like text format. Arrays of objects become table-style rows.
|
|
@@ -210,10 +226,37 @@ LLMs charge and reason over tokens. Sending raw JSON often includes repeated key
|
|
|
210
226
|
node demo.js
|
|
211
227
|
```
|
|
212
228
|
|
|
213
|
-
Use `--analyze` to compare serialized input and output with
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
229
|
+
Use `--analyze` to compare serialized input and output with the model's
|
|
230
|
+
`tiktoken` encoding. `estimateTokens()` and `analyze()` use exact
|
|
231
|
+
model-aware counts by default; pass `exact: false` to opt into the
|
|
232
|
+
four-characters-per-token heuristic. Use `model` to select the tokenizer
|
|
233
|
+
used by `tiktoken`.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Release Notes
|
|
238
|
+
|
|
239
|
+
### v1.0.10 — 2026-09-13
|
|
240
|
+
|
|
241
|
+
- Add conservative contains-based semantic matching for dynamic subject and state keys, including fields such as `patientName`, `orderStatus`, and `incidentSeverity`.
|
|
242
|
+
- Expand `toNatural()` regression coverage for dynamic entities, nested objects, arrays, metadata filtering, and alternate subject fields.
|
|
243
|
+
- Update the browser demo to use the v1.0.10 CDN package.
|
|
244
|
+
|
|
245
|
+
### v1.0.8 — 2026-09-06
|
|
246
|
+
|
|
247
|
+
- Expose exact tokenizer metadata when available (model + encoding), and fall back to a clear, model-aware estimator in browser demos.
|
|
248
|
+
- Demo updated to show selected model and expected encoding when the exact tokenizer (tiktoken) is not available in-browser.
|
|
249
|
+
- Updated analyze() to surface tokenizer encoding in analysis output so the demo shows "Estimator: model=gpt-4 expected_encoding=cl100k_base" even when using the heuristic fallback.
|
|
250
|
+
- Misc: build artifacts updated.
|
|
251
|
+
|
|
252
|
+
### v1.0.9 — 2026-09-13
|
|
253
|
+
|
|
254
|
+
- Use `tiktoken` model-aware token counts by default in `estimateTokens()` and `analyze()`.
|
|
255
|
+
- Keep the four-characters-per-token estimate available with `exact: false`.
|
|
256
|
+
- Correctly identify `o200k_base` for modern OpenAI models such as `gpt-4o`, `gpt-4.1`, `o1`, and `o3`.
|
|
257
|
+
- Update the browser demo to use the v1.0.9 CDN package.
|
|
258
|
+
- Make demo analysis actionable by showing signed token and character changes, the selected transformation, and an explanation of whether the result is cheaper or larger.
|
|
259
|
+
- Rename the demo's input-only Analyze view to Inspect, showing input size and structure without applying a transformation.
|
|
217
260
|
|
|
218
261
|
---
|
|
219
262
|
|
|
@@ -13,12 +13,12 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
estimateTokensWithMeta,
|
|
15
15
|
serializeForTokenEstimate
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-L3HY6AWL.js";
|
|
17
17
|
|
|
18
18
|
// src/core/analyze.ts
|
|
19
19
|
function analyze(input, options = {}) {
|
|
20
20
|
const {
|
|
21
|
-
exact =
|
|
21
|
+
exact = true,
|
|
22
22
|
model = "gpt-4o-mini",
|
|
23
23
|
fallbackToHeuristic = true
|
|
24
24
|
} = options;
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// src/core/natural.ts
|
|
2
|
+
function toNatural(data, depth = 0) {
|
|
3
|
+
if (data === null || data === void 0) return "nothing";
|
|
4
|
+
if (typeof data === "string") return data;
|
|
5
|
+
if (typeof data === "number") return String(data);
|
|
6
|
+
if (typeof data === "boolean") return data ? "yes" : "no";
|
|
7
|
+
if (Array.isArray(data)) {
|
|
8
|
+
if (data.length === 0) return "empty list";
|
|
9
|
+
if (data.every(isPlainObject)) {
|
|
10
|
+
return data.map((item, index) => `${index + 1}. ${stripTrailingPeriod(toNatural(item, depth + 1))}.`).join(" ");
|
|
11
|
+
}
|
|
12
|
+
const items = data.map((item) => toNatural(item, depth + 1));
|
|
13
|
+
return joinNaturalList(items);
|
|
14
|
+
}
|
|
15
|
+
if (typeof data === "object") {
|
|
16
|
+
return buildContextualStory(data, depth);
|
|
17
|
+
}
|
|
18
|
+
return String(data);
|
|
19
|
+
}
|
|
20
|
+
function buildContextualStory(obj, depth = 0) {
|
|
21
|
+
const semanticStory = buildSemanticStory(obj);
|
|
22
|
+
if (semanticStory) return semanticStory;
|
|
23
|
+
const nestedEntity = Object.values(obj).find((value) => {
|
|
24
|
+
return isPlainObject(value) && buildSemanticStory(value) !== null;
|
|
25
|
+
});
|
|
26
|
+
if (nestedEntity) {
|
|
27
|
+
const nestedStory = buildSemanticStory(nestedEntity);
|
|
28
|
+
if (nestedStory) return nestedStory;
|
|
29
|
+
}
|
|
30
|
+
let name = obj.name || obj.userName || obj.user;
|
|
31
|
+
if (typeof name === "object" && name !== null && name.name) {
|
|
32
|
+
name = name.name;
|
|
33
|
+
}
|
|
34
|
+
const entries = Object.entries(obj).filter(([key]) => {
|
|
35
|
+
return !["id", "timestamp", "apiKey"].includes(key);
|
|
36
|
+
});
|
|
37
|
+
if (entries.length === 0) return "";
|
|
38
|
+
let story = name && typeof name === "string" ? `User ${name}` : "";
|
|
39
|
+
const clauses = entries.map(([key, value]) => {
|
|
40
|
+
if (key === "user" && name && typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
41
|
+
const userProps = Object.entries(value).filter(([k]) => !["id", "name", "timestamp", "apiKey"].includes(k)).map(([k, v]) => formatPropertyClause(k, v, depth + 1)).filter((c) => c !== null && c !== "").join(", ");
|
|
42
|
+
return userProps ? `(${userProps})` : null;
|
|
43
|
+
}
|
|
44
|
+
return formatPropertyClause(key, value, depth);
|
|
45
|
+
}).filter((c) => c !== null && c !== "");
|
|
46
|
+
if (clauses.length === 0) return story;
|
|
47
|
+
if (story) {
|
|
48
|
+
story += " " + clauses.join(", ");
|
|
49
|
+
} else {
|
|
50
|
+
story = clauses.join(", ");
|
|
51
|
+
}
|
|
52
|
+
return story + ".";
|
|
53
|
+
}
|
|
54
|
+
function buildSemanticStory(obj) {
|
|
55
|
+
if (typeof obj.holderName === "string" && typeof obj.policyNumber === "string") {
|
|
56
|
+
const subject2 = `${obj.holderName} has policy ${obj.policyNumber}`;
|
|
57
|
+
const details = [
|
|
58
|
+
typeof obj.planType === "string" ? `a ${obj.planType} plan` : null,
|
|
59
|
+
typeof obj.premiumAmount === "number" ? `with a premium of ${formatNumber(obj.premiumAmount)}` : null
|
|
60
|
+
].filter((value) => value !== null);
|
|
61
|
+
const sentences = [`${subject2}${details.length ? `, ${details.join(", ")}` : ""}.`];
|
|
62
|
+
if (isPlainObject(obj.claim)) {
|
|
63
|
+
const claim = describeClaim(obj.claim);
|
|
64
|
+
if (claim) sentences.push(claim);
|
|
65
|
+
}
|
|
66
|
+
if (Array.isArray(obj.dependents) && obj.dependents.length > 0) {
|
|
67
|
+
sentences.push(`${obj.holderName}'s dependents are ${joinNaturalList(obj.dependents.map(String))}.`);
|
|
68
|
+
}
|
|
69
|
+
return sentences.join(" ");
|
|
70
|
+
}
|
|
71
|
+
const subject = findSubject(obj);
|
|
72
|
+
const stateEntry = findSemanticEntry(obj, [
|
|
73
|
+
"status",
|
|
74
|
+
"state",
|
|
75
|
+
"condition",
|
|
76
|
+
"stage",
|
|
77
|
+
"role",
|
|
78
|
+
"type",
|
|
79
|
+
"category",
|
|
80
|
+
"classification",
|
|
81
|
+
"priority",
|
|
82
|
+
"phase",
|
|
83
|
+
"mode",
|
|
84
|
+
"availability",
|
|
85
|
+
"outcome",
|
|
86
|
+
"result",
|
|
87
|
+
"health",
|
|
88
|
+
"progress",
|
|
89
|
+
"visibility",
|
|
90
|
+
"access",
|
|
91
|
+
"membership",
|
|
92
|
+
"sentiment",
|
|
93
|
+
"severity"
|
|
94
|
+
]);
|
|
95
|
+
if (subject && stateEntry && typeof stateEntry[1] === "string") {
|
|
96
|
+
const details = Object.entries(obj).filter(([key, value]) => key !== stateEntry[0] && key !== subject.key && shouldDescribeSemantically(key, value)).map(([key, value]) => `${camelToWords(key)} ${formatSemanticValue(value)}`);
|
|
97
|
+
return `${subject.value} is ${stateEntry[1]}${details.length ? ` and has ${details.join(", ")}` : ""}.`;
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
function findSubject(obj) {
|
|
102
|
+
const subjectKeys = [
|
|
103
|
+
"name",
|
|
104
|
+
"title",
|
|
105
|
+
"label",
|
|
106
|
+
"displayName",
|
|
107
|
+
"entityName",
|
|
108
|
+
"fullName",
|
|
109
|
+
"userName",
|
|
110
|
+
"username",
|
|
111
|
+
"personName",
|
|
112
|
+
"customerName",
|
|
113
|
+
"clientName",
|
|
114
|
+
"ownerName",
|
|
115
|
+
"accountName",
|
|
116
|
+
"companyName",
|
|
117
|
+
"organizationName",
|
|
118
|
+
"teamName",
|
|
119
|
+
"departmentName",
|
|
120
|
+
"projectName",
|
|
121
|
+
"productName",
|
|
122
|
+
"serviceName",
|
|
123
|
+
"resourceName",
|
|
124
|
+
"fileName",
|
|
125
|
+
"deviceName",
|
|
126
|
+
"hostName",
|
|
127
|
+
"applicationName",
|
|
128
|
+
"appName",
|
|
129
|
+
"taskName",
|
|
130
|
+
"eventName",
|
|
131
|
+
"itemName",
|
|
132
|
+
"orderName",
|
|
133
|
+
"patientName",
|
|
134
|
+
"employeeName"
|
|
135
|
+
];
|
|
136
|
+
const entries = Object.entries(obj);
|
|
137
|
+
for (const key of subjectKeys) {
|
|
138
|
+
if (typeof obj[key] === "string" && obj[key].trim()) {
|
|
139
|
+
return { key, value: obj[key].trim() };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const nameEntry = entries.find(([key, value]) => {
|
|
143
|
+
return key.toLowerCase().includes("name") && typeof value === "string" && value.trim().length > 0;
|
|
144
|
+
});
|
|
145
|
+
if (nameEntry) {
|
|
146
|
+
return { key: nameEntry[0], value: nameEntry[1].trim() };
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
function findSemanticEntry(obj, keys) {
|
|
151
|
+
const entries = Object.entries(obj);
|
|
152
|
+
const exactEntry = entries.find(([key]) => keys.includes(key));
|
|
153
|
+
if (exactEntry) return exactEntry;
|
|
154
|
+
return entries.find(([key]) => {
|
|
155
|
+
const normalizedKey = key.toLowerCase();
|
|
156
|
+
return keys.some((semanticKey) => normalizedKey.includes(semanticKey.toLowerCase()));
|
|
157
|
+
}) ?? null;
|
|
158
|
+
}
|
|
159
|
+
function shouldDescribeSemantically(key, value) {
|
|
160
|
+
return (typeof value === "string" || typeof value === "number" || typeof value === "boolean") && !["id", "timestamp", "createdAt", "updatedAt", "apiKey", "debug"].includes(key);
|
|
161
|
+
}
|
|
162
|
+
function formatSemanticValue(value) {
|
|
163
|
+
if (typeof value === "number") return formatNumber(value);
|
|
164
|
+
if (typeof value === "boolean") return value ? "enabled" : "disabled";
|
|
165
|
+
return String(value);
|
|
166
|
+
}
|
|
167
|
+
function describeClaim(claim) {
|
|
168
|
+
const parts = [];
|
|
169
|
+
if (typeof claim.status === "string") parts.push(`The claim is ${claim.status}`);
|
|
170
|
+
if (typeof claim.requestedAmount === "number") {
|
|
171
|
+
parts.push(`for ${formatNumber(claim.requestedAmount)}`);
|
|
172
|
+
}
|
|
173
|
+
if (typeof claim.claimNumber === "string") {
|
|
174
|
+
parts.push(`(reference ${claim.claimNumber})`);
|
|
175
|
+
}
|
|
176
|
+
return parts.length ? `${parts.join(" ")}.` : null;
|
|
177
|
+
}
|
|
178
|
+
function formatNumber(value) {
|
|
179
|
+
return new Intl.NumberFormat("en-US").format(value);
|
|
180
|
+
}
|
|
181
|
+
function formatPropertyClause(key, value, depth) {
|
|
182
|
+
const naturalKey = camelToWords(key);
|
|
183
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
184
|
+
if (["internal", "metadata", "debug"].includes(key)) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
const nested = Object.entries(value).filter(([k]) => !["id", "timestamp", "apiKey", "createdAt", "updatedAt"].includes(k)).map(([k, v]) => {
|
|
188
|
+
if (typeof v === "object" && v !== null) {
|
|
189
|
+
return formatPropertyClause(k, v, depth + 1);
|
|
190
|
+
}
|
|
191
|
+
const propValue = toNatural(v, depth + 1);
|
|
192
|
+
if (propValue === "yes" || propValue === "no") {
|
|
193
|
+
return `${camelToWords(k)} ${propValue === "yes" ? "enabled" : "disabled"}`;
|
|
194
|
+
}
|
|
195
|
+
return `${camelToWords(k)} ${propValue}`;
|
|
196
|
+
}).filter((c) => c && c.trim()).join(", ");
|
|
197
|
+
if (!nested) return null;
|
|
198
|
+
return `${naturalKey}: ${nested}`;
|
|
199
|
+
}
|
|
200
|
+
if (Array.isArray(value)) {
|
|
201
|
+
if (value.length === 0) return null;
|
|
202
|
+
if (["skills", "hobbies", "interests", "tags", "languages", "items"].includes(key)) {
|
|
203
|
+
const items2 = value.map((item) => {
|
|
204
|
+
const str = String(item);
|
|
205
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
206
|
+
});
|
|
207
|
+
return `Having ${joinNaturalList(items2)}`;
|
|
208
|
+
}
|
|
209
|
+
const items = joinNaturalList(value.map((item) => toNatural(item, depth + 1)));
|
|
210
|
+
return `${naturalKey}: ${items}`;
|
|
211
|
+
}
|
|
212
|
+
const naturalValue = toNatural(value, depth + 1);
|
|
213
|
+
if (naturalValue === "yes") return `${naturalKey} enabled`;
|
|
214
|
+
if (naturalValue === "no") return `${naturalKey} disabled`;
|
|
215
|
+
if (key === "theme") return `prefers the ${naturalValue} ${key}`;
|
|
216
|
+
if (key === "age") return `age: ${naturalValue}`;
|
|
217
|
+
if (key === "email") return `email: ${naturalValue}`;
|
|
218
|
+
if (key === "city") return `address: ${naturalValue}`;
|
|
219
|
+
if (key === "debug" && naturalValue === "yes") return `debug enabled`;
|
|
220
|
+
return `${naturalKey}: ${naturalValue}`;
|
|
221
|
+
}
|
|
222
|
+
function camelToWords(str) {
|
|
223
|
+
return str.replace(/([A-Z])/g, " $1").toLowerCase().trim();
|
|
224
|
+
}
|
|
225
|
+
function isPlainObject(value) {
|
|
226
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
227
|
+
}
|
|
228
|
+
function joinNaturalList(items) {
|
|
229
|
+
if (items.length === 0) return "";
|
|
230
|
+
if (items.length === 1) return items[0];
|
|
231
|
+
if (items.length === 2) return `${items[0]} and ${items[1]}`;
|
|
232
|
+
const lastItem = items[items.length - 1];
|
|
233
|
+
return `${items.slice(0, -1).join(", ")} and ${lastItem}`;
|
|
234
|
+
}
|
|
235
|
+
function stripTrailingPeriod(value) {
|
|
236
|
+
return value.replace(/\.+$/, "");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export {
|
|
240
|
+
toNatural
|
|
241
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
analyze
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-2TDVL6BJ.js";
|
|
4
4
|
import {
|
|
5
5
|
toTOON
|
|
6
6
|
} from "./chunk-6VRLDRJK.js";
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "./chunk-L7BC62MT.js";
|
|
13
13
|
import {
|
|
14
14
|
toNatural
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-BN3L7TUV.js";
|
|
16
16
|
import {
|
|
17
17
|
prune
|
|
18
18
|
} from "./chunk-ZD536GZF.js";
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// src/core/token.ts
|
|
2
|
+
import { createRequire } from "module";
|
|
3
|
+
var require2 = createRequire(import.meta.url);
|
|
4
|
+
function serializeForTokenEstimate(value) {
|
|
5
|
+
if (typeof value === "string") return value;
|
|
6
|
+
const serialized = JSON.stringify(value);
|
|
7
|
+
return serialized ?? "";
|
|
8
|
+
}
|
|
9
|
+
function estimateTokensHeuristic(value) {
|
|
10
|
+
return Math.ceil(serializeForTokenEstimate(value).length / 4);
|
|
11
|
+
}
|
|
12
|
+
function expectedEncodingForModel(model) {
|
|
13
|
+
const normalizedModel = model.toLowerCase();
|
|
14
|
+
if (normalizedModel.includes("gpt-4o") || normalizedModel.includes("gpt-4.1") || normalizedModel.includes("o1") || normalizedModel.includes("o3") || normalizedModel.includes("o4")) {
|
|
15
|
+
return "o200k_base";
|
|
16
|
+
}
|
|
17
|
+
if (normalizedModel.includes("davinci") || normalizedModel.startsWith("text-") || normalizedModel.includes("babbage") || normalizedModel.includes("curie")) {
|
|
18
|
+
return "r50k_base";
|
|
19
|
+
}
|
|
20
|
+
return "cl100k_base";
|
|
21
|
+
}
|
|
22
|
+
function estimateTokens(value, options = {}) {
|
|
23
|
+
return estimateTokensWithMeta(value, options).count;
|
|
24
|
+
}
|
|
25
|
+
function estimateTokensWithMeta(value, options = {}) {
|
|
26
|
+
const { exact = true, model = "gpt-4o-mini", fallbackToHeuristic = true } = options;
|
|
27
|
+
const heuristic = estimateTokensHeuristic(value);
|
|
28
|
+
const heuristicEstimator = "heuristic: 1 token \u2248 4 characters";
|
|
29
|
+
if (!exact) {
|
|
30
|
+
return { count: heuristic, estimator: heuristicEstimator };
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const tiktoken = require2("tiktoken");
|
|
34
|
+
const encoder = tiktoken.encoding_for_model(model);
|
|
35
|
+
const count = encoder.encode(serializeForTokenEstimate(value)).length;
|
|
36
|
+
let encodingName = encoder.name;
|
|
37
|
+
if (!encodingName && typeof tiktoken.model_to_encoding === "function") {
|
|
38
|
+
try {
|
|
39
|
+
encodingName = tiktoken.model_to_encoding(model);
|
|
40
|
+
} catch {
|
|
41
|
+
encodingName = void 0;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
encodingName ?? (encodingName = expectedEncodingForModel(model));
|
|
45
|
+
encoder.free?.();
|
|
46
|
+
return {
|
|
47
|
+
count,
|
|
48
|
+
estimator: `exact tokenizer: model=${model} encoding=${encodingName}`
|
|
49
|
+
};
|
|
50
|
+
} catch {
|
|
51
|
+
const expectedEncoding = expectedEncodingForModel(model);
|
|
52
|
+
if (!fallbackToHeuristic) {
|
|
53
|
+
return {
|
|
54
|
+
count: heuristic,
|
|
55
|
+
estimator: `exact requested but tokenizer unavailable (expected encoding=${expectedEncoding} for model=${model})`
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
count: heuristic,
|
|
60
|
+
estimator: `${heuristicEstimator} (model=${model} expected_encoding=${expectedEncoding})`
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export {
|
|
66
|
+
serializeForTokenEstimate,
|
|
67
|
+
estimateTokens,
|
|
68
|
+
estimateTokensWithMeta
|
|
69
|
+
};
|
package/dist/cli.cjs
CHANGED
|
@@ -153,8 +153,18 @@ function serializeForTokenEstimate(value) {
|
|
|
153
153
|
function estimateTokensHeuristic(value) {
|
|
154
154
|
return Math.ceil(serializeForTokenEstimate(value).length / 4);
|
|
155
155
|
}
|
|
156
|
+
function expectedEncodingForModel(model) {
|
|
157
|
+
const normalizedModel = model.toLowerCase();
|
|
158
|
+
if (normalizedModel.includes("gpt-4o") || normalizedModel.includes("gpt-4.1") || normalizedModel.includes("o1") || normalizedModel.includes("o3") || normalizedModel.includes("o4")) {
|
|
159
|
+
return "o200k_base";
|
|
160
|
+
}
|
|
161
|
+
if (normalizedModel.includes("davinci") || normalizedModel.startsWith("text-") || normalizedModel.includes("babbage") || normalizedModel.includes("curie")) {
|
|
162
|
+
return "r50k_base";
|
|
163
|
+
}
|
|
164
|
+
return "cl100k_base";
|
|
165
|
+
}
|
|
156
166
|
function estimateTokensWithMeta(value, options = {}) {
|
|
157
|
-
const { exact =
|
|
167
|
+
const { exact = true, model = "gpt-4o-mini", fallbackToHeuristic = true } = options;
|
|
158
168
|
const heuristic = estimateTokensHeuristic(value);
|
|
159
169
|
const heuristicEstimator = "heuristic: 1 token \u2248 4 characters";
|
|
160
170
|
if (!exact) {
|
|
@@ -163,36 +173,40 @@ function estimateTokensWithMeta(value, options = {}) {
|
|
|
163
173
|
try {
|
|
164
174
|
const tiktoken = require2("tiktoken");
|
|
165
175
|
const encoder = tiktoken.encoding_for_model(model);
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
let encodingName = null;
|
|
169
|
-
if (encoder.name) encodingName = encoder.name;
|
|
176
|
+
const count = encoder.encode(serializeForTokenEstimate(value)).length;
|
|
177
|
+
let encodingName = encoder.name;
|
|
170
178
|
if (!encodingName && typeof tiktoken.model_to_encoding === "function") {
|
|
171
179
|
try {
|
|
172
180
|
encodingName = tiktoken.model_to_encoding(model);
|
|
173
|
-
} catch
|
|
174
|
-
encodingName =
|
|
181
|
+
} catch {
|
|
182
|
+
encodingName = void 0;
|
|
175
183
|
}
|
|
176
184
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
+
encodingName ?? (encodingName = expectedEncodingForModel(model));
|
|
186
|
+
encoder.free?.();
|
|
187
|
+
return {
|
|
188
|
+
count,
|
|
189
|
+
estimator: `exact tokenizer: model=${model} encoding=${encodingName}`
|
|
190
|
+
};
|
|
191
|
+
} catch {
|
|
192
|
+
const expectedEncoding = expectedEncodingForModel(model);
|
|
185
193
|
if (!fallbackToHeuristic) {
|
|
186
|
-
return {
|
|
194
|
+
return {
|
|
195
|
+
count: heuristic,
|
|
196
|
+
estimator: `exact requested but tokenizer unavailable (expected encoding=${expectedEncoding} for model=${model})`
|
|
197
|
+
};
|
|
187
198
|
}
|
|
188
|
-
return {
|
|
199
|
+
return {
|
|
200
|
+
count: heuristic,
|
|
201
|
+
estimator: `${heuristicEstimator} (model=${model} expected_encoding=${expectedEncoding})`
|
|
202
|
+
};
|
|
189
203
|
}
|
|
190
204
|
}
|
|
191
205
|
|
|
192
206
|
// src/core/analyze.ts
|
|
193
207
|
function analyze(input, options = {}) {
|
|
194
208
|
const {
|
|
195
|
-
exact =
|
|
209
|
+
exact = true,
|
|
196
210
|
model = "gpt-4o-mini",
|
|
197
211
|
fallbackToHeuristic = true
|
|
198
212
|
} = options;
|
|
@@ -271,6 +285,15 @@ function toNatural(data, depth = 0) {
|
|
|
271
285
|
return String(data);
|
|
272
286
|
}
|
|
273
287
|
function buildContextualStory(obj, depth = 0) {
|
|
288
|
+
const semanticStory = buildSemanticStory(obj);
|
|
289
|
+
if (semanticStory) return semanticStory;
|
|
290
|
+
const nestedEntity = Object.values(obj).find((value) => {
|
|
291
|
+
return isPlainObject2(value) && buildSemanticStory(value) !== null;
|
|
292
|
+
});
|
|
293
|
+
if (nestedEntity) {
|
|
294
|
+
const nestedStory = buildSemanticStory(nestedEntity);
|
|
295
|
+
if (nestedStory) return nestedStory;
|
|
296
|
+
}
|
|
274
297
|
let name = obj.name || obj.userName || obj.user;
|
|
275
298
|
if (typeof name === "object" && name !== null && name.name) {
|
|
276
299
|
name = name.name;
|
|
@@ -295,6 +318,133 @@ function buildContextualStory(obj, depth = 0) {
|
|
|
295
318
|
}
|
|
296
319
|
return story + ".";
|
|
297
320
|
}
|
|
321
|
+
function buildSemanticStory(obj) {
|
|
322
|
+
if (typeof obj.holderName === "string" && typeof obj.policyNumber === "string") {
|
|
323
|
+
const subject2 = `${obj.holderName} has policy ${obj.policyNumber}`;
|
|
324
|
+
const details = [
|
|
325
|
+
typeof obj.planType === "string" ? `a ${obj.planType} plan` : null,
|
|
326
|
+
typeof obj.premiumAmount === "number" ? `with a premium of ${formatNumber(obj.premiumAmount)}` : null
|
|
327
|
+
].filter((value) => value !== null);
|
|
328
|
+
const sentences = [`${subject2}${details.length ? `, ${details.join(", ")}` : ""}.`];
|
|
329
|
+
if (isPlainObject2(obj.claim)) {
|
|
330
|
+
const claim = describeClaim(obj.claim);
|
|
331
|
+
if (claim) sentences.push(claim);
|
|
332
|
+
}
|
|
333
|
+
if (Array.isArray(obj.dependents) && obj.dependents.length > 0) {
|
|
334
|
+
sentences.push(`${obj.holderName}'s dependents are ${joinNaturalList(obj.dependents.map(String))}.`);
|
|
335
|
+
}
|
|
336
|
+
return sentences.join(" ");
|
|
337
|
+
}
|
|
338
|
+
const subject = findSubject(obj);
|
|
339
|
+
const stateEntry = findSemanticEntry(obj, [
|
|
340
|
+
"status",
|
|
341
|
+
"state",
|
|
342
|
+
"condition",
|
|
343
|
+
"stage",
|
|
344
|
+
"role",
|
|
345
|
+
"type",
|
|
346
|
+
"category",
|
|
347
|
+
"classification",
|
|
348
|
+
"priority",
|
|
349
|
+
"phase",
|
|
350
|
+
"mode",
|
|
351
|
+
"availability",
|
|
352
|
+
"outcome",
|
|
353
|
+
"result",
|
|
354
|
+
"health",
|
|
355
|
+
"progress",
|
|
356
|
+
"visibility",
|
|
357
|
+
"access",
|
|
358
|
+
"membership",
|
|
359
|
+
"sentiment",
|
|
360
|
+
"severity"
|
|
361
|
+
]);
|
|
362
|
+
if (subject && stateEntry && typeof stateEntry[1] === "string") {
|
|
363
|
+
const details = Object.entries(obj).filter(([key, value]) => key !== stateEntry[0] && key !== subject.key && shouldDescribeSemantically(key, value)).map(([key, value]) => `${camelToWords(key)} ${formatSemanticValue(value)}`);
|
|
364
|
+
return `${subject.value} is ${stateEntry[1]}${details.length ? ` and has ${details.join(", ")}` : ""}.`;
|
|
365
|
+
}
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
function findSubject(obj) {
|
|
369
|
+
const subjectKeys = [
|
|
370
|
+
"name",
|
|
371
|
+
"title",
|
|
372
|
+
"label",
|
|
373
|
+
"displayName",
|
|
374
|
+
"entityName",
|
|
375
|
+
"fullName",
|
|
376
|
+
"userName",
|
|
377
|
+
"username",
|
|
378
|
+
"personName",
|
|
379
|
+
"customerName",
|
|
380
|
+
"clientName",
|
|
381
|
+
"ownerName",
|
|
382
|
+
"accountName",
|
|
383
|
+
"companyName",
|
|
384
|
+
"organizationName",
|
|
385
|
+
"teamName",
|
|
386
|
+
"departmentName",
|
|
387
|
+
"projectName",
|
|
388
|
+
"productName",
|
|
389
|
+
"serviceName",
|
|
390
|
+
"resourceName",
|
|
391
|
+
"fileName",
|
|
392
|
+
"deviceName",
|
|
393
|
+
"hostName",
|
|
394
|
+
"applicationName",
|
|
395
|
+
"appName",
|
|
396
|
+
"taskName",
|
|
397
|
+
"eventName",
|
|
398
|
+
"itemName",
|
|
399
|
+
"orderName",
|
|
400
|
+
"patientName",
|
|
401
|
+
"employeeName"
|
|
402
|
+
];
|
|
403
|
+
const entries = Object.entries(obj);
|
|
404
|
+
for (const key of subjectKeys) {
|
|
405
|
+
if (typeof obj[key] === "string" && obj[key].trim()) {
|
|
406
|
+
return { key, value: obj[key].trim() };
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
const nameEntry = entries.find(([key, value]) => {
|
|
410
|
+
return key.toLowerCase().includes("name") && typeof value === "string" && value.trim().length > 0;
|
|
411
|
+
});
|
|
412
|
+
if (nameEntry) {
|
|
413
|
+
return { key: nameEntry[0], value: nameEntry[1].trim() };
|
|
414
|
+
}
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
function findSemanticEntry(obj, keys) {
|
|
418
|
+
const entries = Object.entries(obj);
|
|
419
|
+
const exactEntry = entries.find(([key]) => keys.includes(key));
|
|
420
|
+
if (exactEntry) return exactEntry;
|
|
421
|
+
return entries.find(([key]) => {
|
|
422
|
+
const normalizedKey = key.toLowerCase();
|
|
423
|
+
return keys.some((semanticKey) => normalizedKey.includes(semanticKey.toLowerCase()));
|
|
424
|
+
}) ?? null;
|
|
425
|
+
}
|
|
426
|
+
function shouldDescribeSemantically(key, value) {
|
|
427
|
+
return (typeof value === "string" || typeof value === "number" || typeof value === "boolean") && !["id", "timestamp", "createdAt", "updatedAt", "apiKey", "debug"].includes(key);
|
|
428
|
+
}
|
|
429
|
+
function formatSemanticValue(value) {
|
|
430
|
+
if (typeof value === "number") return formatNumber(value);
|
|
431
|
+
if (typeof value === "boolean") return value ? "enabled" : "disabled";
|
|
432
|
+
return String(value);
|
|
433
|
+
}
|
|
434
|
+
function describeClaim(claim) {
|
|
435
|
+
const parts = [];
|
|
436
|
+
if (typeof claim.status === "string") parts.push(`The claim is ${claim.status}`);
|
|
437
|
+
if (typeof claim.requestedAmount === "number") {
|
|
438
|
+
parts.push(`for ${formatNumber(claim.requestedAmount)}`);
|
|
439
|
+
}
|
|
440
|
+
if (typeof claim.claimNumber === "string") {
|
|
441
|
+
parts.push(`(reference ${claim.claimNumber})`);
|
|
442
|
+
}
|
|
443
|
+
return parts.length ? `${parts.join(" ")}.` : null;
|
|
444
|
+
}
|
|
445
|
+
function formatNumber(value) {
|
|
446
|
+
return new Intl.NumberFormat("en-US").format(value);
|
|
447
|
+
}
|
|
298
448
|
function formatPropertyClause(key, value, depth) {
|
|
299
449
|
const naturalKey = camelToWords(key);
|
|
300
450
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
@@ -395,7 +545,7 @@ Usage:
|
|
|
395
545
|
Options:
|
|
396
546
|
--toon Convert JSON to TOON format
|
|
397
547
|
--compact Convert JSON to compact format
|
|
398
|
-
--analyze Show token analysis (
|
|
548
|
+
--analyze Show token analysis (tiktoken estimate)
|
|
399
549
|
`);
|
|
400
550
|
process.exit(0);
|
|
401
551
|
}
|
package/dist/cli.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
AIChain
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-FBJ3DHS6.js";
|
|
5
|
+
import "./chunk-2TDVL6BJ.js";
|
|
6
6
|
import "./chunk-6VRLDRJK.js";
|
|
7
7
|
import "./chunk-XE36GLJP.js";
|
|
8
8
|
import "./chunk-L7BC62MT.js";
|
|
9
|
-
import "./chunk-
|
|
9
|
+
import "./chunk-BN3L7TUV.js";
|
|
10
10
|
import "./chunk-ZD536GZF.js";
|
|
11
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-L3HY6AWL.js";
|
|
12
12
|
|
|
13
13
|
// src/cli.ts
|
|
14
14
|
import fs from "fs";
|
|
@@ -22,7 +22,7 @@ Usage:
|
|
|
22
22
|
Options:
|
|
23
23
|
--toon Convert JSON to TOON format
|
|
24
24
|
--compact Convert JSON to compact format
|
|
25
|
-
--analyze Show token analysis (
|
|
25
|
+
--analyze Show token analysis (tiktoken estimate)
|
|
26
26
|
`);
|
|
27
27
|
process.exit(0);
|
|
28
28
|
}
|