@cloudraker/milliseconds 0.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/LICENSE +21 -0
- package/README.md +280 -0
- package/dist/cli.js +1402 -0
- package/dist/index.cjs +456 -0
- package/dist/index.d.cts +330 -0
- package/dist/index.d.ts +330 -0
- package/dist/index.js +452 -0
- package/package.json +76 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/errors.ts
|
|
3
|
+
/** A brand property, so a duplicated copy of the package in a bundle still matches. */
|
|
4
|
+
const BRAND = "~milliseconds.error";
|
|
5
|
+
/**
|
|
6
|
+
* One error class. Switch on `code`: the union narrows exhaustively and never goes stale
|
|
7
|
+
* when the API adds a code.
|
|
8
|
+
*/
|
|
9
|
+
var MillisecondsError = class extends Error {
|
|
10
|
+
name = "MillisecondsError";
|
|
11
|
+
[BRAND] = true;
|
|
12
|
+
code;
|
|
13
|
+
/** 0 when the call never reached the API. */
|
|
14
|
+
status;
|
|
15
|
+
/** The API's own message, unchanged. `message` adds one hint line. */
|
|
16
|
+
apiMessage;
|
|
17
|
+
/** Seconds from the retry-after header. Only 429 rate_limit_exceeded carries it. */
|
|
18
|
+
retryAfter;
|
|
19
|
+
rateLimit;
|
|
20
|
+
response;
|
|
21
|
+
/** Attempts this call made, including the first. */
|
|
22
|
+
attempts;
|
|
23
|
+
/** True for the codes the SDK retries. */
|
|
24
|
+
retryable;
|
|
25
|
+
constructor(init) {
|
|
26
|
+
super(init.message ?? init.apiMessage, init.cause === void 0 ? void 0 : { cause: init.cause });
|
|
27
|
+
this.code = init.code;
|
|
28
|
+
this.status = init.status ?? 0;
|
|
29
|
+
this.apiMessage = init.apiMessage;
|
|
30
|
+
this.retryAfter = init.retryAfter ?? null;
|
|
31
|
+
this.rateLimit = init.rateLimit ?? null;
|
|
32
|
+
this.response = init.response ?? null;
|
|
33
|
+
this.attempts = init.attempts ?? 1;
|
|
34
|
+
this.retryable = init.retryable ?? false;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
function isMillisecondsError(e) {
|
|
38
|
+
return typeof e === "object" && e !== null && e[BRAND] === true;
|
|
39
|
+
}
|
|
40
|
+
/** Every client-side check throws this: status 0, code client_error, nothing sent. */
|
|
41
|
+
const clientError = (message) => new MillisecondsError({
|
|
42
|
+
code: "client_error",
|
|
43
|
+
status: 0,
|
|
44
|
+
apiMessage: message
|
|
45
|
+
});
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/validate.ts
|
|
48
|
+
const MAX_CHARS = 2e4;
|
|
49
|
+
const MAX_TEXTS = 32;
|
|
50
|
+
const MAX_ITEMS = 32;
|
|
51
|
+
const n = (x) => x.toLocaleString("en-US");
|
|
52
|
+
const count = (size) => size === 1 ? "1 entry" : `${n(size)} entries`;
|
|
53
|
+
/** The key is a secret, so a browser bundle needs an explicit opt-in. */
|
|
54
|
+
function checkRuntime(apiKey, allowBrowser) {
|
|
55
|
+
if (!apiKey) throw clientError("No API key. Pass new DecisionMachine({ apiKey }) or set MS_API_KEY. Get a key at https://console.milliseconds.ai.");
|
|
56
|
+
if (typeof globalThis === "object" && typeof globalThis.document?.createElement === "function" && !allowBrowser) throw clientError("The API key is a secret. Call the API from your server, or pass dangerouslyAllowBrowser: true when the bundle never reaches a user.");
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* `texts` is 1 to 32 items, and every text is 1 to 20,000 characters.
|
|
60
|
+
*
|
|
61
|
+
* An empty text is a 400, not the 200-with-empty-results trap. That trap needs `text` and
|
|
62
|
+
* `texts` both absent, and the SDK always sends one of them. This check only replaces a
|
|
63
|
+
* round trip with a local error.
|
|
64
|
+
*/
|
|
65
|
+
function checkInput(input) {
|
|
66
|
+
if (typeof input === "string") {
|
|
67
|
+
if (input === "") throw empty("text");
|
|
68
|
+
if (input.length > MAX_CHARS) throw tooLong("text", input.length);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (input.length === 0) throw clientError("texts is empty. Send at least one text.");
|
|
72
|
+
if (input.length > MAX_TEXTS) throw clientError(`texts has ${n(input.length)} items. The limit is ${n(MAX_TEXTS)}. Split the batch.`);
|
|
73
|
+
for (const [i, text] of input.entries()) {
|
|
74
|
+
if (text === "") throw empty(`texts[${i}]`);
|
|
75
|
+
if (text.length > MAX_CHARS) throw tooLong(`texts[${i}]`, text.length);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const empty = (where) => clientError(`${where} is empty. Send at least one character.`);
|
|
79
|
+
const tooLong = (where, length) => clientError(`${where} is ${n(length)} characters. The limit is ${n(MAX_CHARS)}. Split on paragraphs and send the parts as texts.`);
|
|
80
|
+
/**
|
|
81
|
+
* `statements` and `questions` are 1 to 32 items. The wire message for a body without one
|
|
82
|
+
* is `provide statement or statements, not both`, which names both fields and misleads.
|
|
83
|
+
*/
|
|
84
|
+
function checkSpec(name, value) {
|
|
85
|
+
if (typeof value === "string") {
|
|
86
|
+
if (value !== "") return;
|
|
87
|
+
if (name === "statements") throw clientError("yes-no needs a statement. The wire message for a body without one names both fields and misleads.");
|
|
88
|
+
throw clientError(`${name} has 0 items. Send 1 to ${MAX_ITEMS}.`);
|
|
89
|
+
}
|
|
90
|
+
if (value.length === 0 || value.length > MAX_ITEMS) throw clientError(`${name} has ${n(value.length)} items. Send 1 to ${MAX_ITEMS}.`);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* `labels` is 2 to 64, `types` is 1 to 64 and `scale` is 2 to 10. Three limits, because
|
|
94
|
+
* `decide.schema.ts` holds three. One shared rule would reject a legal single-type
|
|
95
|
+
* `entities` call.
|
|
96
|
+
*
|
|
97
|
+
* `decide.schema.ts` bounds the array branch of `labels` and `types` only: the
|
|
98
|
+
* `name -> description` branch is a plain `z.record`, so any number of described labels is
|
|
99
|
+
* legal. `classify-tree` passes `bounded` because its own `superRefine` bounds every level.
|
|
100
|
+
*/
|
|
101
|
+
function checkList(name, value, min, max, capability, bounded = Array.isArray(value)) {
|
|
102
|
+
const size = Array.isArray(value) ? value.length : Object.keys(value).length;
|
|
103
|
+
if (bounded ? size >= min && size <= max : size > 0) return;
|
|
104
|
+
const found = size === 0 ? `${name} is empty.` : `${name} has ${count(size)}.`;
|
|
105
|
+
throw clientError(`${found} ${capability} needs ${min} to ${max}.`);
|
|
106
|
+
}
|
|
107
|
+
/** `planFor` throws unless the schema is an object with properties. Beat it locally. */
|
|
108
|
+
function checkSchema(schema) {
|
|
109
|
+
const s = schema;
|
|
110
|
+
if ((Array.isArray(s?.type) ? s.type.find((t) => t !== "null") : s?.type) !== "object" || typeof s?.properties !== "object" || s.properties === null) throw clientError("The schema must be an object with properties.");
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/client.ts
|
|
114
|
+
const USER_AGENT = `cloudraker-milliseconds-js/0.1.0`;
|
|
115
|
+
const DEFAULT_BASE_URL = "https://api.milliseconds.ai";
|
|
116
|
+
const RETRY = /* @__PURE__ */ new Set([
|
|
117
|
+
"rate_limit_exceeded",
|
|
118
|
+
"runner_error",
|
|
119
|
+
"overloaded",
|
|
120
|
+
"connection_error",
|
|
121
|
+
"timeout"
|
|
122
|
+
]);
|
|
123
|
+
/** Full jitter, capped at 8 s. */
|
|
124
|
+
const backoff = (n) => Math.random() * Math.min(500 * 2 ** n, 8e3);
|
|
125
|
+
/** Resolves early when the caller aborts. The loop then throws the caller's reason. */
|
|
126
|
+
const sleep = (ms, signal) => new Promise((resolve) => {
|
|
127
|
+
const timer = setTimeout(resolve, ms);
|
|
128
|
+
signal?.addEventListener("abort", () => {
|
|
129
|
+
clearTimeout(timer);
|
|
130
|
+
resolve();
|
|
131
|
+
}, { once: true });
|
|
132
|
+
});
|
|
133
|
+
/** Reads an environment variable without assuming `process` exists. */
|
|
134
|
+
function env(name) {
|
|
135
|
+
try {
|
|
136
|
+
return globalThis.process?.env?.[name];
|
|
137
|
+
} catch {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** A Promise of the result, with the headers one call away. No Promise subclass. */
|
|
142
|
+
const decision = (p) => {
|
|
143
|
+
const result = p.then((r) => r.result);
|
|
144
|
+
return Object.assign(result, { withUsage: () => {
|
|
145
|
+
result.catch(() => {});
|
|
146
|
+
return p;
|
|
147
|
+
} });
|
|
148
|
+
};
|
|
149
|
+
function rateLimitOf(h) {
|
|
150
|
+
const limitRequests = h.get("x-ratelimit-limit-requests");
|
|
151
|
+
if (limitRequests === null) return null;
|
|
152
|
+
const num = (k) => Number(h.get(k)) || 0;
|
|
153
|
+
return {
|
|
154
|
+
limitRequests: Number(limitRequests) || 0,
|
|
155
|
+
remainingRequests: num("x-ratelimit-remaining-requests"),
|
|
156
|
+
resetRequests: h.get("x-ratelimit-reset-requests") ?? "",
|
|
157
|
+
limitTokens: num("x-ratelimit-limit-tokens"),
|
|
158
|
+
remainingTokens: num("x-ratelimit-remaining-tokens"),
|
|
159
|
+
resetTokens: h.get("x-ratelimit-reset-tokens") ?? ""
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function usageOf(response) {
|
|
163
|
+
const h = response.headers;
|
|
164
|
+
const num = (k) => Number(h.get(k)) || 0;
|
|
165
|
+
return {
|
|
166
|
+
inputChars: num("x-input-chars"),
|
|
167
|
+
inputTokens: num("x-input-tokens"),
|
|
168
|
+
inferenceMs: num("x-inference-ms"),
|
|
169
|
+
rateLimit: rateLimitOf(h),
|
|
170
|
+
headers: h
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/** The hint line under the API message. Section 8.2 of DESIGN.md holds the copy. */
|
|
174
|
+
function describe(code, status, apiMessage, attempts, ms) {
|
|
175
|
+
const over = attempts > 1 ? ` after ${attempts} attempts over ${(ms / 1e3).toFixed(1)}s` : "";
|
|
176
|
+
const tail = apiMessage ? `:\n${apiMessage}` : ".";
|
|
177
|
+
switch (code) {
|
|
178
|
+
case "missing_api_key":
|
|
179
|
+
case "invalid_api_key": return `milliseconds rejected the API key (${status} ${code}).\nKeys start with "sk-ms-". Check MS_API_KEY, or create a key at\nhttps://console.milliseconds.ai.`;
|
|
180
|
+
case "rate_limit_exceeded": return `rate limited (${status} ${code})${over}${tail}\n Lower your concurrency, or raise maxRetries. Limits are per organization and\n shared by every key.`;
|
|
181
|
+
case "insufficient_quota": return `no token credits left (${status} ${code})${tail}\n Not retried. A timer retry will not help.`;
|
|
182
|
+
case "overloaded": return `every inference slot stayed busy (${status} ${code})${over}${tail}\n This is backpressure, not a fault. Send fewer texts per call, or back off further.\n A texts batch of 32 asks for 32 slots at once.`;
|
|
183
|
+
case "runner_error": return `inference failed twice (${status} ${code})${over}${tail}\n The model, not your request. Retry later, or raise maxRetries.`;
|
|
184
|
+
case "timeout": return `the request timed out${over}${tail}\n Raise timeout, or send fewer texts per call.`;
|
|
185
|
+
case "connection_error": return `could not reach the API${over}${tail}\n Check baseUrl and the network. maxRetries: 0 fails fast.`;
|
|
186
|
+
default: return `milliseconds rejected the request (${status} ${code})${tail}${apiMessage.startsWith("labels:") ? "\n classify needs two or more labels. Describe each one. Described labels score\n measurably better than bare names." : ""}`;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/** One AbortSignal for the attempt: the per-attempt timeout, plus the caller's signal. */
|
|
190
|
+
function attemptSignal(timeout, signal) {
|
|
191
|
+
const hasAny = typeof AbortSignal.any === "function";
|
|
192
|
+
if (typeof AbortSignal.timeout === "function" && (!signal || hasAny)) {
|
|
193
|
+
const t = AbortSignal.timeout(timeout);
|
|
194
|
+
return {
|
|
195
|
+
signal: signal ? AbortSignal.any([signal, t]) : t,
|
|
196
|
+
done: () => {}
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
const controller = new AbortController();
|
|
200
|
+
const timer = setTimeout(() => controller.abort(new DOMException("The operation timed out.", "TimeoutError")), timeout);
|
|
201
|
+
signal?.addEventListener("abort", () => controller.abort(signal.reason), { once: true });
|
|
202
|
+
return {
|
|
203
|
+
signal: controller.signal,
|
|
204
|
+
done: () => clearTimeout(timer)
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/** Transport: fetch, retries, errors and usage. Every capability is two lines on top. */
|
|
208
|
+
var Client = class {
|
|
209
|
+
baseUrl;
|
|
210
|
+
#apiKey;
|
|
211
|
+
#timeout;
|
|
212
|
+
#maxRetries;
|
|
213
|
+
#headers;
|
|
214
|
+
#fetch;
|
|
215
|
+
constructor(options = {}) {
|
|
216
|
+
const apiKey = options.apiKey ?? env("MS_API_KEY");
|
|
217
|
+
checkRuntime(apiKey, options.dangerouslyAllowBrowser);
|
|
218
|
+
this.#apiKey = apiKey;
|
|
219
|
+
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
220
|
+
this.#timeout = options.timeout ?? 6e4;
|
|
221
|
+
this.#maxRetries = options.maxRetries ?? 2;
|
|
222
|
+
this.#headers = { ...options.headers };
|
|
223
|
+
this.#fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
224
|
+
}
|
|
225
|
+
/** Escape hatch. Your path, your body, your type, the SDK's auth, retries and errors. */
|
|
226
|
+
post(path, body, options) {
|
|
227
|
+
return this.call(path, body, options, (raw) => raw);
|
|
228
|
+
}
|
|
229
|
+
/** `unwrap` maps the parsed body to the result. It follows the request, never the response. */
|
|
230
|
+
call(path, body, options, unwrap) {
|
|
231
|
+
return decision(this.send(path, body, options ?? {}).then(({ response, raw }) => ({
|
|
232
|
+
result: unwrap(raw),
|
|
233
|
+
usage: usageOf(response),
|
|
234
|
+
response
|
|
235
|
+
})));
|
|
236
|
+
}
|
|
237
|
+
async send(path, body, call) {
|
|
238
|
+
const url = `${this.baseUrl}${path}`;
|
|
239
|
+
const maxRetries = call.maxRetries ?? this.#maxRetries;
|
|
240
|
+
const timeout = call.timeout ?? this.#timeout;
|
|
241
|
+
const headers = new Headers({
|
|
242
|
+
"content-type": "application/json",
|
|
243
|
+
"user-agent": USER_AGENT
|
|
244
|
+
});
|
|
245
|
+
for (const [k, v] of Object.entries({
|
|
246
|
+
...this.#headers,
|
|
247
|
+
...call.headers
|
|
248
|
+
})) headers.set(k, v);
|
|
249
|
+
headers.set("authorization", `Bearer ${this.#apiKey}`);
|
|
250
|
+
const init = {
|
|
251
|
+
method: "POST",
|
|
252
|
+
headers,
|
|
253
|
+
body: JSON.stringify(body)
|
|
254
|
+
};
|
|
255
|
+
const started = Date.now();
|
|
256
|
+
for (let attempt = 1;; attempt++) {
|
|
257
|
+
if (call.signal?.aborted) throw call.signal.reason;
|
|
258
|
+
const outcome = await this.attempt(url, init, timeout, call.signal, attempt, started);
|
|
259
|
+
if (!(outcome instanceof MillisecondsError)) return outcome;
|
|
260
|
+
if (!outcome.retryable || attempt > maxRetries) throw outcome;
|
|
261
|
+
const wait = outcome.retryAfter === null ? backoff(attempt - 1) : Math.min(outcome.retryAfter, 60) * 1e3;
|
|
262
|
+
await sleep(wait, call.signal);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
async attempt(url, init, timeout, signal, attempt, started) {
|
|
266
|
+
const guard = attemptSignal(timeout, signal);
|
|
267
|
+
try {
|
|
268
|
+
const response = await this.#fetch(url, {
|
|
269
|
+
...init,
|
|
270
|
+
signal: guard.signal
|
|
271
|
+
});
|
|
272
|
+
if (response.ok) return {
|
|
273
|
+
response,
|
|
274
|
+
raw: await response.json()
|
|
275
|
+
};
|
|
276
|
+
return await httpError(response, attempt, Date.now() - started);
|
|
277
|
+
} catch (cause) {
|
|
278
|
+
if (signal?.aborted) throw cause;
|
|
279
|
+
return transportError(cause, attempt, Date.now() - started);
|
|
280
|
+
} finally {
|
|
281
|
+
guard.done();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
function transportError(cause, attempts, ms) {
|
|
286
|
+
const name = cause?.name ?? "";
|
|
287
|
+
const code = name === "TimeoutError" || name === "AbortError" ? "timeout" : "connection_error";
|
|
288
|
+
const apiMessage = cause?.message ?? String(cause);
|
|
289
|
+
return new MillisecondsError({
|
|
290
|
+
code,
|
|
291
|
+
status: 0,
|
|
292
|
+
apiMessage,
|
|
293
|
+
message: describe(code, 0, apiMessage, attempts, ms),
|
|
294
|
+
attempts,
|
|
295
|
+
retryable: true,
|
|
296
|
+
cause
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
async function httpError(response, attempts, ms) {
|
|
300
|
+
const body = await response.clone().json().catch(() => null);
|
|
301
|
+
const code = body?.error?.code ?? (response.status >= 500 ? "internal_error" : "http_error");
|
|
302
|
+
const apiMessage = body?.error?.message ?? response.statusText;
|
|
303
|
+
const retryAfter = response.headers.get("retry-after");
|
|
304
|
+
return new MillisecondsError({
|
|
305
|
+
code,
|
|
306
|
+
status: response.status,
|
|
307
|
+
apiMessage,
|
|
308
|
+
message: describe(code, response.status, apiMessage, attempts, ms),
|
|
309
|
+
retryAfter: retryAfter === null ? null : Number(retryAfter) || 0,
|
|
310
|
+
rateLimit: rateLimitOf(response.headers),
|
|
311
|
+
response,
|
|
312
|
+
attempts,
|
|
313
|
+
retryable: RETRY.has(code) || response.status >= 502 && response.status <= 504
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
//#endregion
|
|
317
|
+
//#region src/schema.ts
|
|
318
|
+
/**
|
|
319
|
+
* Brands a JSON Schema with the type it produces. One cast, no runtime cost.
|
|
320
|
+
*
|
|
321
|
+
* `dm.extract(text, typed<z.infer<typeof S>>(z.toJSONSchema(S)))`
|
|
322
|
+
*/
|
|
323
|
+
const typed = (schema) => schema;
|
|
324
|
+
const CONVERT = "Pass a JSON Schema. zod: typed<z.infer<typeof S>>(z.toJSONSchema(S)). valibot: typed<v.InferOutput<typeof S>>(toJsonSchema(S)). arktype works directly.";
|
|
325
|
+
/**
|
|
326
|
+
* The object to send. A Standard Schema with a converter method (arktype) is converted.
|
|
327
|
+
* A Standard Schema without one cannot be: the SDK has zero dependencies, so it names the
|
|
328
|
+
* one line that converts it instead of importing zod.
|
|
329
|
+
*/
|
|
330
|
+
function toJsonSchema(schema) {
|
|
331
|
+
const s = schema;
|
|
332
|
+
const convert = typeof s.toJsonSchema === "function" ? s.toJsonSchema : s.toJSONSchema;
|
|
333
|
+
if (typeof convert === "function") return convert.call(s);
|
|
334
|
+
if ("~standard" in s) throw new MillisecondsError({
|
|
335
|
+
code: "invalid_schema",
|
|
336
|
+
status: 0,
|
|
337
|
+
apiMessage: CONVERT
|
|
338
|
+
});
|
|
339
|
+
return s;
|
|
340
|
+
}
|
|
341
|
+
//#endregion
|
|
342
|
+
//#region src/decision-machine.ts
|
|
343
|
+
const MODEL = "decision-machine-1";
|
|
344
|
+
/** The batch shape follows the request, never the response. */
|
|
345
|
+
const same = (raw) => raw;
|
|
346
|
+
const key = (name) => (raw) => raw[name];
|
|
347
|
+
/** `text` for one result, `texts` for a batch. The mutual exclusion is impossible here. */
|
|
348
|
+
const inputBody = (input) => typeof input === "string" ? { text: input } : { texts: input };
|
|
349
|
+
/**
|
|
350
|
+
* `decision-machine-1` at api.milliseconds.ai. Every capability is a pure function, so
|
|
351
|
+
* every retry is safe.
|
|
352
|
+
*/
|
|
353
|
+
var DecisionMachine = class extends Client {
|
|
354
|
+
/** Paths are `${baseUrl}/v1/${model}/<capability>`. */
|
|
355
|
+
static model = MODEL;
|
|
356
|
+
/**
|
|
357
|
+
* Answers each statement with yes or no and a probability. The statements share one
|
|
358
|
+
* inference call, so extra statements are nearly free.
|
|
359
|
+
*/
|
|
360
|
+
yesNo(input, statements, options) {
|
|
361
|
+
checkInput(input);
|
|
362
|
+
checkSpec("statements", statements);
|
|
363
|
+
const { when_true, when_false, ...call } = options ?? {};
|
|
364
|
+
const body = {
|
|
365
|
+
...inputBody(input),
|
|
366
|
+
...typeof statements === "string" ? { statement: statements } : { statements },
|
|
367
|
+
...when_true === void 0 ? {} : { when_true },
|
|
368
|
+
...when_false === void 0 ? {} : { when_false }
|
|
369
|
+
};
|
|
370
|
+
return this.capability("yes-no", body, input, call, typeof statements === "string" ? same : key("results"));
|
|
371
|
+
}
|
|
372
|
+
/** Picks one label and returns the full distribution. Describe each label. */
|
|
373
|
+
classify(input, labels, options) {
|
|
374
|
+
checkInput(input);
|
|
375
|
+
checkList("labels", labels, 2, 64, "classify");
|
|
376
|
+
return this.capability("classify", {
|
|
377
|
+
...inputBody(input),
|
|
378
|
+
labels
|
|
379
|
+
}, input, options, same);
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Runs classify once per level of a nested tree, descending into the winner.
|
|
383
|
+
*
|
|
384
|
+
* The per-level `input_chars` and `input_tokens` do not sum to `Usage.inputChars`: the
|
|
385
|
+
* header counts one pass over the body, and each level re-sends the text.
|
|
386
|
+
*/
|
|
387
|
+
classifyTree(input, tree, options) {
|
|
388
|
+
checkInput(input);
|
|
389
|
+
checkList("labels", tree, 2, 64, "classify-tree", true);
|
|
390
|
+
return this.capability("classify-tree", {
|
|
391
|
+
...inputBody(input),
|
|
392
|
+
tree
|
|
393
|
+
}, input, options, same);
|
|
394
|
+
}
|
|
395
|
+
/** Places the text on an ordered scale of described levels, low to high. */
|
|
396
|
+
rate(input, scale, options) {
|
|
397
|
+
checkInput(input);
|
|
398
|
+
checkList("scale", scale, 2, 10, "rate");
|
|
399
|
+
return this.capability("rate", {
|
|
400
|
+
...inputBody(input),
|
|
401
|
+
scale
|
|
402
|
+
}, input, options, same);
|
|
403
|
+
}
|
|
404
|
+
/** Quotes the answer out of the text, with its offsets. `answer` is null when nothing fits. */
|
|
405
|
+
answer(input, questions, options) {
|
|
406
|
+
checkInput(input);
|
|
407
|
+
checkSpec("questions", questions);
|
|
408
|
+
const body = {
|
|
409
|
+
...inputBody(input),
|
|
410
|
+
...typeof questions === "string" ? { question: questions } : { questions }
|
|
411
|
+
};
|
|
412
|
+
return this.capability("answer", body, input, options, typeof questions === "string" ? same : key("results"));
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Fills a JSON Schema from the text. Missing values are null, arrays of objects come back
|
|
416
|
+
* empty, arrays of scalars come back as strings, and enums are not checked server side.
|
|
417
|
+
*/
|
|
418
|
+
extract(input, schema, options) {
|
|
419
|
+
checkInput(input);
|
|
420
|
+
const json = toJsonSchema(schema);
|
|
421
|
+
checkSchema(json);
|
|
422
|
+
return this.capability("extract", {
|
|
423
|
+
...inputBody(input),
|
|
424
|
+
schema: json
|
|
425
|
+
}, input, options, key("data"));
|
|
426
|
+
}
|
|
427
|
+
/** Finds every span matching each type, with offsets, sorted by start. */
|
|
428
|
+
entities(input, types, options) {
|
|
429
|
+
checkInput(input);
|
|
430
|
+
checkList("types", types, 1, 64, "entities");
|
|
431
|
+
return this.capability("entities", {
|
|
432
|
+
...inputBody(input),
|
|
433
|
+
types
|
|
434
|
+
}, input, options, key("entities"));
|
|
435
|
+
}
|
|
436
|
+
/** Checks whether the text says `value` for `field`. */
|
|
437
|
+
verify(input, field, value, options) {
|
|
438
|
+
checkInput(input);
|
|
439
|
+
const body = {
|
|
440
|
+
...inputBody(input),
|
|
441
|
+
field: typeof field === "string" ? { name: field } : field,
|
|
442
|
+
value
|
|
443
|
+
};
|
|
444
|
+
return this.capability("verify", body, input, options, same);
|
|
445
|
+
}
|
|
446
|
+
/** `{ results }` is unwrapped for a batch, and the per-text envelope for one text. */
|
|
447
|
+
capability(name, body, input, options, one) {
|
|
448
|
+
const unwrap = typeof input === "string" ? one : (raw) => (raw.results ?? []).map(one);
|
|
449
|
+
return this.call(`/v1/${MODEL}/${name}`, body, options, unwrap);
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
//#endregion
|
|
453
|
+
exports.DecisionMachine = DecisionMachine;
|
|
454
|
+
exports.MillisecondsError = MillisecondsError;
|
|
455
|
+
exports.isMillisecondsError = isMillisecondsError;
|
|
456
|
+
exports.typed = typed;
|