@itsoltech/basal-client 0.1.0 → 0.3.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/README.md +54 -2
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/index.d.cts +81 -7
- package/dist/cjs/media.cjs +20 -0
- package/dist/cjs/media.d.cts +5 -0
- package/dist/cjs/questions.cjs +135 -25
- package/dist/cjs/questions.d.cts +22 -13
- package/dist/cjs/tasks.cjs +175 -0
- package/dist/cjs/tasks.d.cts +108 -0
- package/dist/esm/index.d.ts +81 -7
- package/dist/esm/index.js +2 -0
- package/dist/esm/media.d.ts +5 -0
- package/dist/esm/media.js +16 -0
- package/dist/esm/questions.d.ts +22 -13
- package/dist/esm/questions.js +131 -23
- package/dist/esm/tasks.d.ts +108 -0
- package/dist/esm/tasks.js +164 -0
- package/package.json +1 -1
package/dist/esm/questions.js
CHANGED
|
@@ -30,19 +30,20 @@ function evidence(options) {
|
|
|
30
30
|
export class QuestionSpec {
|
|
31
31
|
wire;
|
|
32
32
|
constructor(kind, instructions, fields) {
|
|
33
|
-
this.wire = structuredClone({ ...fields, type: kind, instructions: text(instructions, 'instructions') });
|
|
33
|
+
this.wire = structuredClone({ ...fields, type: kind, ...(['classify', 'verify', 'compress', 'extract'].includes(kind) ? (instructions ? { instructions } : {}) : { instructions: text(instructions, 'instructions') }) });
|
|
34
34
|
}
|
|
35
35
|
toJSON() { return structuredClone(this.wire); }
|
|
36
36
|
/** Validate and retrieve an answer with this question's result type. */
|
|
37
37
|
read(response, id) {
|
|
38
38
|
const answer = response.answers?.[id];
|
|
39
|
-
if (!answer || answer.type !== this.wire.type || !answer.probabilities || typeof answer.probabilities !== 'object' || Array.isArray(answer.probabilities)) {
|
|
39
|
+
if (!answer || !('probabilities' in answer) || answer.type !== this.wire.type || !answer.probabilities || typeof answer.probabilities !== 'object' || Array.isArray(answer.probabilities)) {
|
|
40
40
|
throw new TypeError('Basal returned a missing or mismatched answer');
|
|
41
41
|
}
|
|
42
42
|
const fields = {
|
|
43
43
|
choice: { choice: 'string', confidence: 'number' }, noul: { noul: 'number', confidence: 'number' },
|
|
44
44
|
score: { score: 'number', legend: 'object', confidence: 'number' },
|
|
45
|
-
multi: { selected: 'object',
|
|
45
|
+
multi: { selected: 'object', set_confidence: 'number' },
|
|
46
|
+
tool: answer.type === 'tool' && answer.multi ? { tools: 'object', call: 'boolean', set_confidence: 'number' } : { call: 'boolean', confidence: 'number' },
|
|
46
47
|
act: { action: 'string', answer: 'string', expected_costs: 'object', confidence: 'number', calibration: 'string' },
|
|
47
48
|
};
|
|
48
49
|
const data = answer;
|
|
@@ -75,21 +76,52 @@ export class QuestionSpec {
|
|
|
75
76
|
if (answer.type === 'score' && (Array.isArray(answer.legend) || Object.values(answer.legend).some(v => typeof v !== 'string')))
|
|
76
77
|
throw new TypeError('invalid legend');
|
|
77
78
|
if (answer.type === 'act') {
|
|
78
|
-
if (!['validated', 'unvalidated'].includes(answer.calibration) || !Object.hasOwn(this.wire.costs ?? {}, answer.action) || !expectedKeys.includes(answer.answer))
|
|
79
|
+
if (!['validated', 'unvalidated', 'text'].includes(answer.calibration) || !Object.hasOwn(this.wire.costs ?? {}, answer.action) || !expectedKeys.includes(answer.answer))
|
|
79
80
|
throw new TypeError('invalid action');
|
|
80
81
|
if (Array.isArray(answer.expected_costs) || Object.keys(this.wire.costs ?? {}).some(key => !Object.hasOwn(answer.expected_costs, key)))
|
|
81
82
|
throw new TypeError('invalid expected costs');
|
|
82
83
|
for (const value of Object.values(answer.expected_costs))
|
|
83
84
|
number(value, 'expected cost');
|
|
84
85
|
}
|
|
85
|
-
|
|
86
|
+
if (answer.type === 'multi' || (answer.type === 'tool' && answer.multi)) {
|
|
87
|
+
validateSelection(answer, this.wire.objective ?? 'threshold');
|
|
88
|
+
}
|
|
89
|
+
if (answer.type === 'tool') {
|
|
90
|
+
if ((answer.multi ?? false) !== (this.wire.multi ?? false))
|
|
91
|
+
throw new TypeError('mismatched tool mode');
|
|
92
|
+
if (answer.multi) {
|
|
93
|
+
if (!Array.isArray(answer.tools) || answer.tools.some(k => typeof k !== 'string' || !Object.hasOwn(answer.probabilities, k)))
|
|
94
|
+
throw new TypeError('invalid selected tools');
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
if (answer.tool === null ? answer.deferred !== true : typeof answer.tool !== 'string' || !Object.hasOwn(answer.probabilities, answer.tool))
|
|
98
|
+
throw new TypeError('invalid selected tool');
|
|
99
|
+
if (answer.deferred !== undefined && typeof answer.deferred !== 'boolean')
|
|
100
|
+
throw new TypeError('invalid deferred');
|
|
101
|
+
for (const key of ['refused', 'max_error'])
|
|
102
|
+
if (answer[key] !== undefined && typeof answer[key] !== 'string')
|
|
103
|
+
throw new TypeError('invalid tool metadata');
|
|
104
|
+
}
|
|
105
|
+
if (answer.ranking !== undefined) {
|
|
106
|
+
if (!Array.isArray(answer.ranking))
|
|
107
|
+
throw new TypeError('invalid ranking');
|
|
108
|
+
for (const entry of answer.ranking) {
|
|
109
|
+
if (!entry || typeof entry.tool !== 'string' || !Object.hasOwn(answer.probabilities, entry.tool))
|
|
110
|
+
throw new TypeError('invalid ranking entry');
|
|
111
|
+
number(entry.probability, 'ranking probability', true);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (answer.type === 'act' && answer.deferred !== undefined && typeof answer.deferred !== 'boolean')
|
|
116
|
+
throw new TypeError('invalid deferred');
|
|
117
|
+
return { ...answer, ...(answer.type === 'multi' || answer.type === 'tool' ? {} : { evidence: answer.evidence ?? [] }) };
|
|
86
118
|
}
|
|
87
119
|
}
|
|
88
120
|
export class ChoiceQuestion extends QuestionSpec {
|
|
89
121
|
constructor(instructions, values, options = {}) {
|
|
90
122
|
if (options.option_keys !== undefined && !['show', 'hide'].includes(options.option_keys))
|
|
91
123
|
throw new TypeError('invalid option_keys');
|
|
92
|
-
super('choice', instructions, { criteria: criteria(values, 2,
|
|
124
|
+
super('choice', instructions, { criteria: criteria(values, 2, 255), ...evidence(options), ...(options.option_keys === 'hide' ? { option_keys: 'hide' } : {}) });
|
|
93
125
|
}
|
|
94
126
|
}
|
|
95
127
|
export class NoulQuestion extends QuestionSpec {
|
|
@@ -97,34 +129,103 @@ export class NoulQuestion extends QuestionSpec {
|
|
|
97
129
|
}
|
|
98
130
|
export class ScoreQuestion extends QuestionSpec {
|
|
99
131
|
constructor(instructions, levels, options = {}) {
|
|
100
|
-
if (!Array.isArray(levels) || levels.length < 2 || levels.length >
|
|
101
|
-
throw new TypeError('score requires 2–
|
|
132
|
+
if (!Array.isArray(levels) || levels.length < 2 || levels.length > 255)
|
|
133
|
+
throw new TypeError('score requires 2–255 ordered levels');
|
|
102
134
|
super('score', instructions, { criteria: levels.map(v => text(v, 'level')), ...evidence(options) });
|
|
103
135
|
}
|
|
104
136
|
}
|
|
137
|
+
function selectionOptions(options, keys) {
|
|
138
|
+
const fields = {};
|
|
139
|
+
const objective = options.objective ?? 'threshold';
|
|
140
|
+
if (!['threshold', 'exact', 'iou', 'f1'].includes(objective))
|
|
141
|
+
throw new TypeError('invalid objective');
|
|
142
|
+
if (objective !== 'threshold')
|
|
143
|
+
fields.objective = objective;
|
|
144
|
+
if (options.threshold !== undefined) {
|
|
145
|
+
if (objective !== 'threshold')
|
|
146
|
+
throw new TypeError('threshold applies only to objective threshold');
|
|
147
|
+
fields.threshold = number(options.threshold, 'threshold', true);
|
|
148
|
+
}
|
|
149
|
+
for (const key of ['min', 'max']) {
|
|
150
|
+
const v = options[key];
|
|
151
|
+
if (v !== undefined) {
|
|
152
|
+
if (!Number.isSafeInteger(v) || v < 0 || (keys && v > keys.length))
|
|
153
|
+
throw new TypeError(`invalid ${key}`);
|
|
154
|
+
fields[key] = v;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (options.min !== undefined && options.max !== undefined && options.min > options.max)
|
|
158
|
+
throw new TypeError('min exceeds max');
|
|
159
|
+
if (options.exclusive !== undefined) {
|
|
160
|
+
if (!Array.isArray(options.exclusive))
|
|
161
|
+
throw new TypeError('exclusive must be an array');
|
|
162
|
+
fields.exclusive = options.exclusive.map(k => { text(k, 'exclusive key'); if (keys && !keys.includes(k))
|
|
163
|
+
throw new TypeError('unknown exclusive key'); return k; });
|
|
164
|
+
}
|
|
165
|
+
if (options.fallback !== undefined) {
|
|
166
|
+
fields.fallback = text(options.fallback, 'fallback');
|
|
167
|
+
if (keys && !keys.includes(options.fallback))
|
|
168
|
+
throw new TypeError('unknown fallback');
|
|
169
|
+
}
|
|
170
|
+
if (options.fallback_threshold !== undefined) {
|
|
171
|
+
if (options.fallback === undefined)
|
|
172
|
+
throw new TypeError('fallback_threshold requires fallback');
|
|
173
|
+
fields.fallback_threshold = number(options.fallback_threshold, 'fallback_threshold', true);
|
|
174
|
+
}
|
|
175
|
+
return fields;
|
|
176
|
+
}
|
|
177
|
+
function validateSelection(answer, objective) {
|
|
178
|
+
number(answer.set_confidence, 'set_confidence', true);
|
|
179
|
+
if (objective === 'threshold')
|
|
180
|
+
number(answer.threshold, 'threshold', true);
|
|
181
|
+
else {
|
|
182
|
+
if (answer.objective !== objective)
|
|
183
|
+
throw new TypeError('mismatched objective');
|
|
184
|
+
if (answer.expected_score !== null && answer.expected_score !== undefined)
|
|
185
|
+
number(answer.expected_score, 'expected_score', true);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
105
188
|
export class MultiQuestion extends QuestionSpec {
|
|
106
189
|
constructor(instructions, values, options = {}) {
|
|
107
190
|
if ('evidence' in options)
|
|
108
191
|
throw new TypeError('multi does not support evidence');
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
192
|
+
super('multi', instructions, { criteria: criteria(values, 1), ...selectionOptions(options, Object.keys(values)) });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
export class ToolQuestion extends QuestionSpec {
|
|
196
|
+
constructor(instructions, tools, options = {}) {
|
|
197
|
+
if (!Array.isArray(tools) || tools.some(t => !t || typeof t !== 'object' || Array.isArray(t)))
|
|
198
|
+
throw new TypeError('tools must contain definition objects');
|
|
199
|
+
if (options.extra !== undefined && (!Array.isArray(options.extra) || options.extra.some(k => !['answer_directly', 'ask_user', 'none'].includes(k))))
|
|
200
|
+
throw new TypeError('invalid extra tool option');
|
|
201
|
+
if (options.multi !== undefined && typeof options.multi !== 'boolean')
|
|
202
|
+
throw new TypeError('invalid multi');
|
|
203
|
+
const fields = { tools, ...(options.extra === undefined ? {} : { extra: options.extra }) };
|
|
204
|
+
if (options.description_chars !== undefined) {
|
|
205
|
+
if (!Number.isSafeInteger(options.description_chars) || options.description_chars < 0)
|
|
206
|
+
throw new TypeError('description_chars must be a non-negative safe integer');
|
|
207
|
+
fields.description_chars = options.description_chars;
|
|
208
|
+
}
|
|
209
|
+
if (options.rank !== undefined) {
|
|
210
|
+
if (!Number.isSafeInteger(options.rank) || options.rank < 1)
|
|
211
|
+
throw new TypeError('rank must be a positive integer');
|
|
212
|
+
fields.rank = options.rank;
|
|
213
|
+
}
|
|
214
|
+
if (options.max_error !== undefined)
|
|
215
|
+
fields.max_error = number(options.max_error, 'max_error', true);
|
|
216
|
+
const selection = selectionOptions(options);
|
|
217
|
+
if (!options.multi && Object.keys(selection).length)
|
|
218
|
+
throw new TypeError('selection options require multi');
|
|
219
|
+
if (options.multi) {
|
|
220
|
+
fields.multi = true;
|
|
221
|
+
Object.assign(fields, selection);
|
|
119
222
|
}
|
|
120
|
-
|
|
121
|
-
throw new TypeError('min exceeds max');
|
|
122
|
-
super('multi', instructions, fields);
|
|
223
|
+
super('tool', instructions, fields);
|
|
123
224
|
}
|
|
124
225
|
}
|
|
125
226
|
export class ActQuestion extends QuestionSpec {
|
|
126
227
|
constructor(instructions, costs, options = {}) {
|
|
127
|
-
const optionsMap = options.criteria === undefined ? undefined : criteria(options.criteria, 2,
|
|
228
|
+
const optionsMap = options.criteria === undefined ? undefined : criteria(options.criteria, 2, 255);
|
|
128
229
|
const outcomes = Object.keys(optionsMap ?? { 'true': 'Yes', 'false': 'No' });
|
|
129
230
|
if (!costs || typeof costs !== 'object' || Array.isArray(costs) || !Object.keys(costs).length)
|
|
130
231
|
throw new TypeError('act requires a cost matrix');
|
|
@@ -147,6 +248,7 @@ export function noul(instructions, options) { return new NoulQuestion(instructio
|
|
|
147
248
|
export function score(instructions, levels, options) { return new ScoreQuestion(instructions, levels, options); }
|
|
148
249
|
export function multi(instructions, values, options) { return new MultiQuestion(instructions, values, options); }
|
|
149
250
|
export function act(instructions, costs, options) { return new ActQuestion(instructions, costs, options); }
|
|
251
|
+
export function tool(instructions, tools, options) { return new ToolQuestion(instructions, tools, options); }
|
|
150
252
|
export function buildRequest(state, questions, options) {
|
|
151
253
|
if (state === null || state === undefined)
|
|
152
254
|
throw new TypeError('state must not be null/undefined');
|
|
@@ -160,5 +262,11 @@ export function buildRequest(state, questions, options) {
|
|
|
160
262
|
text(options.model, 'model');
|
|
161
263
|
if (options.facts !== undefined && !['off', 'auto'].includes(options.facts))
|
|
162
264
|
throw new TypeError('invalid facts');
|
|
163
|
-
|
|
265
|
+
if (options.lang !== undefined && !['pl', 'en'].includes(options.lang))
|
|
266
|
+
throw new TypeError('invalid lang');
|
|
267
|
+
if (options.detail !== undefined && !['default', 'high'].includes(options.detail))
|
|
268
|
+
throw new TypeError('invalid detail');
|
|
269
|
+
if (options.media !== undefined && (!Array.isArray(options.media) || options.media.length < 1 || options.media.length > 8 || options.media.some(m => !m || !['image', 'audio'].includes(m.type) || typeof m.data !== 'string' || !m.data.trim())))
|
|
270
|
+
throw new TypeError('invalid media');
|
|
271
|
+
return { state, questions: Object.fromEntries(entries), ...(options.lang === undefined ? {} : { lang: options.lang }), ...(options.detail === undefined ? {} : { detail: options.detail }), ...(options.media === undefined ? {} : { media: structuredClone(options.media) }), ...(options.model === undefined ? {} : { model: options.model }), ...(options.facts === undefined ? {} : { facts: options.facts }) };
|
|
164
272
|
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { QuestionSpec } from './questions.js';
|
|
2
|
+
import type { DecisionResponse, JsonValue, Question, MultiObjective } from './index.js';
|
|
3
|
+
/** Task automation is unknown (null) without a threshold certified for that task/error target. */
|
|
4
|
+
export type Automation = Record<string, boolean | null>;
|
|
5
|
+
export interface ClassifyAnswer<K extends string = string> {
|
|
6
|
+
type: 'classify';
|
|
7
|
+
experimental: true;
|
|
8
|
+
scores: Record<K, number>;
|
|
9
|
+
label?: K;
|
|
10
|
+
top_label?: K;
|
|
11
|
+
confidence?: number;
|
|
12
|
+
automated?: Automation;
|
|
13
|
+
labels?: K[];
|
|
14
|
+
set_confidence?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface ClaimResult {
|
|
17
|
+
verdict: 'supported' | 'contradicted' | 'not_stated' | 'not_supported';
|
|
18
|
+
probabilities: Record<string, number>;
|
|
19
|
+
confidence: number;
|
|
20
|
+
automated: Automation;
|
|
21
|
+
}
|
|
22
|
+
export interface VerifyAnswer {
|
|
23
|
+
type: 'verify';
|
|
24
|
+
experimental: true;
|
|
25
|
+
claims: Record<string, ClaimResult>;
|
|
26
|
+
all_supported: boolean;
|
|
27
|
+
not_supported: string[];
|
|
28
|
+
}
|
|
29
|
+
/** Inclusive start/exclusive end count Unicode code points, not JavaScript UTF-16 units. */
|
|
30
|
+
export interface CompressionUnit {
|
|
31
|
+
start: number;
|
|
32
|
+
end: number;
|
|
33
|
+
probability: number;
|
|
34
|
+
}
|
|
35
|
+
export interface CompressAnswer {
|
|
36
|
+
type: 'compress';
|
|
37
|
+
experimental: true;
|
|
38
|
+
compressed: string;
|
|
39
|
+
kept: number[];
|
|
40
|
+
units: CompressionUnit[];
|
|
41
|
+
unit: 'sentence' | 'line' | 'paragraph';
|
|
42
|
+
original_chars: number;
|
|
43
|
+
compressed_chars: number;
|
|
44
|
+
ratio: number;
|
|
45
|
+
}
|
|
46
|
+
export interface ExtractField {
|
|
47
|
+
value: JsonValue;
|
|
48
|
+
confidence: number;
|
|
49
|
+
source: 'choice' | 'span';
|
|
50
|
+
calibration: 'validated' | 'per decision';
|
|
51
|
+
probabilities?: Record<string, number>;
|
|
52
|
+
present_probability?: number;
|
|
53
|
+
text?: string;
|
|
54
|
+
start?: number;
|
|
55
|
+
end?: number;
|
|
56
|
+
line_probability?: number;
|
|
57
|
+
span_probability?: number;
|
|
58
|
+
error?: string;
|
|
59
|
+
}
|
|
60
|
+
export interface ExtractAnswer {
|
|
61
|
+
type: 'extract';
|
|
62
|
+
experimental: true;
|
|
63
|
+
data: Record<string, JsonValue>;
|
|
64
|
+
fields: Record<string, ExtractField>;
|
|
65
|
+
}
|
|
66
|
+
export type TaskAnswer = ClassifyAnswer | VerifyAnswer | CompressAnswer | ExtractAnswer;
|
|
67
|
+
export interface ClassifyOptions {
|
|
68
|
+
multi_label?: boolean;
|
|
69
|
+
none?: boolean;
|
|
70
|
+
max_error?: number | readonly number[];
|
|
71
|
+
option_keys?: 'show' | 'hide';
|
|
72
|
+
threshold?: number;
|
|
73
|
+
min?: number;
|
|
74
|
+
max?: number;
|
|
75
|
+
objective?: MultiObjective;
|
|
76
|
+
}
|
|
77
|
+
export interface VerifyOptions {
|
|
78
|
+
binary?: boolean;
|
|
79
|
+
max_error?: number | readonly number[];
|
|
80
|
+
}
|
|
81
|
+
export interface CompressOptions {
|
|
82
|
+
unit?: 'sentence' | 'line' | 'paragraph';
|
|
83
|
+
keep_ratio?: number;
|
|
84
|
+
threshold?: number;
|
|
85
|
+
min_keep?: number;
|
|
86
|
+
}
|
|
87
|
+
declare abstract class TaskQuestion<A extends TaskAnswer> extends QuestionSpec<A> {
|
|
88
|
+
protected constructor(kind: A['type'], text: string, fields: Question);
|
|
89
|
+
read(response: DecisionResponse, id: string): A;
|
|
90
|
+
}
|
|
91
|
+
export declare class ClassifyQuestion<K extends string = string> extends TaskQuestion<ClassifyAnswer<K>> {
|
|
92
|
+
constructor(text: string, labels: Record<K, JsonValue> | readonly JsonValue[], options?: ClassifyOptions);
|
|
93
|
+
}
|
|
94
|
+
export declare class VerifyQuestion extends TaskQuestion<VerifyAnswer> {
|
|
95
|
+
constructor(text: string, claims: Record<string, JsonValue> | readonly JsonValue[], options?: VerifyOptions);
|
|
96
|
+
}
|
|
97
|
+
export declare class CompressQuestion extends TaskQuestion<CompressAnswer> {
|
|
98
|
+
constructor(text?: string, options?: CompressOptions);
|
|
99
|
+
}
|
|
100
|
+
export declare class ExtractQuestion extends TaskQuestion<ExtractAnswer> {
|
|
101
|
+
constructor(text: string, schema: Record<string, JsonValue>);
|
|
102
|
+
}
|
|
103
|
+
export declare function classify<const K extends string>(text: string, labels: Record<K, JsonValue>, options?: ClassifyOptions): ClassifyQuestion<K>;
|
|
104
|
+
export declare function classify(text: string, labels: readonly JsonValue[], options?: ClassifyOptions): ClassifyQuestion;
|
|
105
|
+
export declare function verify(text: string, claims: Record<string, JsonValue> | readonly JsonValue[], options?: VerifyOptions): VerifyQuestion;
|
|
106
|
+
export declare function compress(text?: string, options?: CompressOptions): CompressQuestion;
|
|
107
|
+
export declare function extract(text: string, schema: Record<string, JsonValue>): ExtractQuestion;
|
|
108
|
+
export {};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { QuestionSpec } from './questions.js';
|
|
2
|
+
function object(v) {
|
|
3
|
+
if (!v || typeof v !== 'object' || Array.isArray(v))
|
|
4
|
+
throw new TypeError('expected task object');
|
|
5
|
+
return v;
|
|
6
|
+
}
|
|
7
|
+
function probability(v) { if (typeof v !== 'number' || !Number.isFinite(v) || v < 0 || v > 1)
|
|
8
|
+
throw new TypeError('invalid probability'); }
|
|
9
|
+
function probabilities(v) { for (const n of Object.values(object(v)))
|
|
10
|
+
probability(n); }
|
|
11
|
+
function automation(v) { if (Object.values(object(v)).some(n => n !== null && typeof n !== 'boolean'))
|
|
12
|
+
throw new TypeError('invalid automation'); }
|
|
13
|
+
function strings(v) { if (!Array.isArray(v) || v.some(k => typeof k !== 'string'))
|
|
14
|
+
throw new TypeError('expected string list'); }
|
|
15
|
+
function offset(v) { if (!Number.isSafeInteger(v) || v < 0)
|
|
16
|
+
throw new TypeError('invalid Unicode code point offset/count'); }
|
|
17
|
+
function span(v) { offset(v.start); offset(v.end); if (v.end < v.start)
|
|
18
|
+
throw new TypeError('reversed source span'); }
|
|
19
|
+
function risks(v) {
|
|
20
|
+
if (v === undefined)
|
|
21
|
+
return;
|
|
22
|
+
const a = Array.isArray(v) ? v : [v];
|
|
23
|
+
if (a.length > 64 || a.some(n => typeof n !== 'number' || !Number.isFinite(n) || n <= 0 || n >= 1))
|
|
24
|
+
throw new TypeError('invalid max_error');
|
|
25
|
+
}
|
|
26
|
+
function instructions(v) { if (typeof v !== 'string')
|
|
27
|
+
throw new TypeError('instructions must be a string'); return v; }
|
|
28
|
+
class TaskQuestion extends QuestionSpec {
|
|
29
|
+
constructor(kind, text, fields) { super(kind, instructions(text), fields); }
|
|
30
|
+
read(response, id) {
|
|
31
|
+
const d = object(response.answers?.[id]);
|
|
32
|
+
if (d.type !== this.toJSON().type || d.experimental !== true)
|
|
33
|
+
throw new TypeError('Basal returned a missing or mismatched task');
|
|
34
|
+
if (d.type === 'classify') {
|
|
35
|
+
probabilities(d.scores);
|
|
36
|
+
const scores = object(d.scores);
|
|
37
|
+
if (this.toJSON().multi_label) {
|
|
38
|
+
strings(d.labels);
|
|
39
|
+
if (d.labels.some(k => !Object.hasOwn(scores, k)))
|
|
40
|
+
throw new TypeError('unknown label');
|
|
41
|
+
probability(d.set_confidence);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
if (typeof d.label !== 'string' || !Object.hasOwn(scores, d.label) || d.top_label !== d.label)
|
|
45
|
+
throw new TypeError('invalid label');
|
|
46
|
+
probability(d.confidence);
|
|
47
|
+
automation(d.automated);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else if (d.type === 'verify') {
|
|
51
|
+
const claims = object(d.claims);
|
|
52
|
+
for (const raw of Object.values(claims)) {
|
|
53
|
+
const c = object(raw);
|
|
54
|
+
const p = object(c.probabilities);
|
|
55
|
+
probabilities(p);
|
|
56
|
+
if (typeof c.verdict !== 'string' || !['supported', 'contradicted', 'not_stated', 'not_supported'].includes(c.verdict) || !Object.hasOwn(p, c.verdict))
|
|
57
|
+
throw new TypeError('invalid verdict');
|
|
58
|
+
probability(c.confidence);
|
|
59
|
+
automation(c.automated);
|
|
60
|
+
}
|
|
61
|
+
strings(d.not_supported);
|
|
62
|
+
if (typeof d.all_supported !== 'boolean' || d.not_supported.some(k => !Object.hasOwn(claims, k)))
|
|
63
|
+
throw new TypeError('invalid verification summary');
|
|
64
|
+
}
|
|
65
|
+
else if (d.type === 'compress') {
|
|
66
|
+
if (typeof d.compressed !== 'string' || !['sentence', 'line', 'paragraph'].includes(d.unit) || !Array.isArray(d.units) || !Array.isArray(d.kept))
|
|
67
|
+
throw new TypeError('invalid compression');
|
|
68
|
+
for (const u of d.units) {
|
|
69
|
+
const v = object(u);
|
|
70
|
+
span(v);
|
|
71
|
+
probability(v.probability);
|
|
72
|
+
}
|
|
73
|
+
for (const k of d.kept) {
|
|
74
|
+
offset(k);
|
|
75
|
+
if (k >= d.units.length)
|
|
76
|
+
throw new TypeError('invalid kept unit');
|
|
77
|
+
}
|
|
78
|
+
offset(d.original_chars);
|
|
79
|
+
offset(d.compressed_chars);
|
|
80
|
+
if (typeof d.ratio !== 'number' || !Number.isFinite(d.ratio) || d.ratio < 0)
|
|
81
|
+
throw new TypeError('invalid ratio');
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
object(d.data);
|
|
85
|
+
for (const raw of Object.values(object(d.fields))) {
|
|
86
|
+
const f = object(raw);
|
|
87
|
+
if (!Object.hasOwn(f, 'value') || !['choice', 'span'].includes(f.source) || !['validated', 'per decision'].includes(f.calibration))
|
|
88
|
+
throw new TypeError('invalid extraction field');
|
|
89
|
+
probability(f.confidence);
|
|
90
|
+
if (f.probabilities !== undefined)
|
|
91
|
+
probabilities(f.probabilities);
|
|
92
|
+
for (const k of ['present_probability', 'line_probability', 'span_probability'])
|
|
93
|
+
if (f[k] !== undefined)
|
|
94
|
+
probability(f[k]);
|
|
95
|
+
if (f.text !== undefined) {
|
|
96
|
+
if (typeof f.text !== 'string')
|
|
97
|
+
throw new TypeError('invalid source text');
|
|
98
|
+
span(f);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return structuredClone(d);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export class ClassifyQuestion extends TaskQuestion {
|
|
106
|
+
constructor(text, labels, options = {}) {
|
|
107
|
+
if (!labels || typeof labels !== 'object' || Object.keys(labels).length < 1 || Object.keys(labels).length > 256)
|
|
108
|
+
throw new TypeError('classify requires 1..256 labels');
|
|
109
|
+
risks(options.max_error);
|
|
110
|
+
for (const k of ['multi_label', 'none'])
|
|
111
|
+
if (options[k] !== undefined && typeof options[k] !== 'boolean')
|
|
112
|
+
throw new TypeError(`invalid ${k}`);
|
|
113
|
+
if (options.option_keys !== undefined && !['show', 'hide'].includes(options.option_keys))
|
|
114
|
+
throw new TypeError('invalid option_keys');
|
|
115
|
+
if (options.threshold !== undefined) {
|
|
116
|
+
probability(options.threshold);
|
|
117
|
+
if (options.objective !== undefined && options.objective !== 'threshold')
|
|
118
|
+
throw new TypeError('threshold requires threshold objective');
|
|
119
|
+
}
|
|
120
|
+
for (const k of ['min', 'max'])
|
|
121
|
+
if (options[k] !== undefined)
|
|
122
|
+
offset(options[k]);
|
|
123
|
+
if (options.min !== undefined && options.max !== undefined && options.min > options.max)
|
|
124
|
+
throw new TypeError('min exceeds max');
|
|
125
|
+
if (options.objective !== undefined && !['threshold', 'exact', 'iou', 'f1'].includes(options.objective))
|
|
126
|
+
throw new TypeError('invalid objective');
|
|
127
|
+
if (!options.multi_label && ['threshold', 'min', 'max', 'objective'].some(k => Object.hasOwn(options, k)))
|
|
128
|
+
throw new TypeError('selection options require multi_label');
|
|
129
|
+
super('classify', text, { labels: structuredClone(labels), ...options });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
export class VerifyQuestion extends TaskQuestion {
|
|
133
|
+
constructor(text, claims, options = {}) {
|
|
134
|
+
if (!claims || typeof claims !== 'object' || !Object.keys(claims).length)
|
|
135
|
+
throw new TypeError('verify requires claims');
|
|
136
|
+
risks(options.max_error);
|
|
137
|
+
if (options.binary !== undefined && typeof options.binary !== 'boolean')
|
|
138
|
+
throw new TypeError('invalid binary');
|
|
139
|
+
super('verify', text, { claims: structuredClone(claims), ...options });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
export class CompressQuestion extends TaskQuestion {
|
|
143
|
+
constructor(text = '', options = {}) {
|
|
144
|
+
if (options.unit !== undefined && !['sentence', 'line', 'paragraph'].includes(options.unit))
|
|
145
|
+
throw new TypeError('invalid unit');
|
|
146
|
+
if (options.keep_ratio !== undefined) {
|
|
147
|
+
probability(options.keep_ratio);
|
|
148
|
+
if (options.keep_ratio === 0)
|
|
149
|
+
throw new TypeError('keep_ratio must be positive');
|
|
150
|
+
}
|
|
151
|
+
if (options.threshold !== undefined)
|
|
152
|
+
probability(options.threshold);
|
|
153
|
+
if (options.min_keep !== undefined)
|
|
154
|
+
offset(options.min_keep);
|
|
155
|
+
super('compress', text, options);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
export class ExtractQuestion extends TaskQuestion {
|
|
159
|
+
constructor(text, schema) { object(schema); super('extract', text, { schema }); }
|
|
160
|
+
}
|
|
161
|
+
export function classify(text, labels, options) { return new ClassifyQuestion(text, labels, options); }
|
|
162
|
+
export function verify(text, claims, options) { return new VerifyQuestion(text, claims, options); }
|
|
163
|
+
export function compress(text = '', options) { return new CompressQuestion(text, options); }
|
|
164
|
+
export function extract(text, schema) { return new ExtractQuestion(text, schema); }
|