@itsoltech/basal-client 0.1.0 → 0.2.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 +40 -2
- package/dist/cjs/index.cjs +1 -0
- package/dist/cjs/index.d.cts +68 -6
- package/dist/cjs/media.cjs +20 -0
- package/dist/cjs/media.d.cts +5 -0
- package/dist/cjs/questions.cjs +128 -23
- package/dist/cjs/questions.d.cts +20 -13
- package/dist/esm/index.d.ts +68 -6
- package/dist/esm/index.js +1 -0
- package/dist/esm/media.d.ts +5 -0
- package/dist/esm/media.js +16 -0
- package/dist/esm/questions.d.ts +20 -13
- package/dist/esm/questions.js +124 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -57,8 +57,8 @@ in `cause`. `BasalHTTPError.body` remains directly accessible but is non-enumera
|
|
|
57
57
|
inspection and JSON.stringify do not log it. There are no automatic retries or redirects. Types describe the native contract; successful
|
|
58
58
|
responses are checked to be JSON objects without full schema validation. Unknown JSON fields are preserved.
|
|
59
59
|
|
|
60
|
-
Supports `choice`, `noul`, `score`, `multi`, `act`, `facts` and `evidence
|
|
61
|
-
options. Criterion insertion order matters; JavaScript enumerates integer-like object keys numerically,
|
|
60
|
+
Supports `choice`, `noul`, `score`, `multi`, `act` and `tool`, with model-dependent `facts` and `evidence` capabilities. Native choice/score accepts 2–255
|
|
61
|
+
options for format-2 models (older model limits are checked by the server). Criterion insertion order matters; JavaScript enumerates integer-like object keys numerically,
|
|
62
62
|
so use textual keys (e.g. `low`, `high`) or an ordered score array. Evidence offsets count Unicode code
|
|
63
63
|
points; use the returned text, or `Array.from(state).slice(start, end).join("")` for textual states.
|
|
64
64
|
|
|
@@ -76,3 +76,41 @@ retains the native JSON without those defaults. The one-argument raw `decide(req
|
|
|
76
76
|
|
|
77
77
|
|
|
78
78
|
[Full documentation and publication setup](https://github.com/itsoltech/basal-rs/blob/main/docs/SDK.md).
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
## Basal 2.0 and Omni
|
|
82
|
+
|
|
83
|
+
Typed choice/score and act outcomes allow 2–255 options. The server remains
|
|
84
|
+
responsible for limits of older models and for calibration/model capabilities.
|
|
85
|
+
`tool` accepts OpenAI, MCP and Anthropic definition objects, optional non-tool
|
|
86
|
+
choices (`answer_directly`, `ask_user`, `none`), ranking, multi selection and
|
|
87
|
+
certified deferral. The SDK returns the decision; it never executes tools.
|
|
88
|
+
|
|
89
|
+
Multi selection supports `threshold`, `exact`, `iou` and `f1`, count limits,
|
|
90
|
+
`exclusive`, `fallback` and `fallback_threshold`. `threshold` is optional in
|
|
91
|
+
results because objective-based responses instead contain `objective` and
|
|
92
|
+
`expected_score`; constraint diagnostics remain available. Single tool selection
|
|
93
|
+
can be null on certified deferral, while multi mode returns a tool list.
|
|
94
|
+
|
|
95
|
+
Typed request options include `lang` (`pl`/`en`), ordered inline `media` and
|
|
96
|
+
`detail` (`default`/`high`). Media helpers accept encoded bytes, base64 or a data
|
|
97
|
+
URL; they never fetch URLs. Read files explicitly in your application. Decoding,
|
|
98
|
+
model support and resource limits are checked by the server. Omni requires a
|
|
99
|
+
CUDA server started with media enabled. `calibration: "text"` means text
|
|
100
|
+
calibration applied to media, not validated calibration for images/audio.
|
|
101
|
+
|
|
102
|
+
Usage exposes `longest_prompt_tokens`, `second_order_branches` and media counts,
|
|
103
|
+
soft tokens and detail. Discovery exposes `prompt_format`, `orders`, enabled
|
|
104
|
+
media and calibration temperatures. Model names remain strings: use exact names
|
|
105
|
+
from model discovery, not a hardcoded model enumeration. Keep old models and
|
|
106
|
+
raw JSON access available. These SDKs cover server-supported format-2 decisions;
|
|
107
|
+
upstream 2.1 task types are not implemented by this server.
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { BasalClient, image, tool, multi } from "@itsoltech/basal-client";
|
|
111
|
+
const response = await new BasalClient().decide("Inspect the supplied material.", {
|
|
112
|
+
route: tool("Choose a tool.", [{name:"inspect", description:"Inspect material", inputSchema:{type:"object"}}], {extra:["none"], rank:2}),
|
|
113
|
+
labels: multi("Select visible objects.", {animal:"An animal", vehicle:"A vehicle"}, {objective:"f1"}),
|
|
114
|
+
}, {model:"basal-omni-1.0-mini", lang:"en", media:[image(new Uint8Array(await file.arrayBuffer()))]});
|
|
115
|
+
console.log(response.answers.route, response.answers.labels.expected_score);
|
|
116
|
+
```
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -17,6 +17,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
exports.BasalClient = exports.BasalResponseTooLargeError = exports.BasalProtocolError = exports.BasalTransportError = exports.BasalHTTPError = exports.BasalError = void 0;
|
|
18
18
|
const questions_cjs_1 = require("./questions.cjs");
|
|
19
19
|
__exportStar(require("./questions.cjs"), exports);
|
|
20
|
+
__exportStar(require("./media.cjs"), exports);
|
|
20
21
|
class BasalError extends Error {
|
|
21
22
|
}
|
|
22
23
|
exports.BasalError = BasalError;
|
package/dist/cjs/index.d.cts
CHANGED
|
@@ -1,11 +1,29 @@
|
|
|
1
1
|
import { type QuestionSet, type TypedDecisionResponse, type DecisionOptions } from "./questions.cjs";
|
|
2
2
|
export * from "./questions.cjs";
|
|
3
|
+
export * from "./media.cjs";
|
|
3
4
|
/** Native Basal wire types. Criterion order is significant; do not sort keys. */
|
|
4
5
|
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
5
6
|
[key: string]: JsonValue;
|
|
6
7
|
};
|
|
8
|
+
export type Calibration = "validated" | "unvalidated" | "text";
|
|
9
|
+
export type MultiObjective = "threshold" | "exact" | "iou" | "f1";
|
|
10
|
+
export interface MediaInput {
|
|
11
|
+
type: "image" | "audio";
|
|
12
|
+
data: string;
|
|
13
|
+
}
|
|
14
|
+
export interface MediaUsage {
|
|
15
|
+
images: number;
|
|
16
|
+
audio: number;
|
|
17
|
+
media_tokens: number;
|
|
18
|
+
detail: "default" | "high";
|
|
19
|
+
}
|
|
20
|
+
export interface ToolRanking {
|
|
21
|
+
tool: string;
|
|
22
|
+
probability: number;
|
|
23
|
+
}
|
|
24
|
+
export type ToolDefinition = Record<string, JsonValue>;
|
|
7
25
|
export interface Question {
|
|
8
|
-
type?: "choice" | "noul" | "score" | "multi" | "act";
|
|
26
|
+
type?: "choice" | "noul" | "score" | "multi" | "act" | "tool";
|
|
9
27
|
instructions?: JsonValue;
|
|
10
28
|
criteria?: Record<string, JsonValue> | JsonValue[];
|
|
11
29
|
levels?: Record<string, JsonValue> | JsonValue[];
|
|
@@ -18,12 +36,23 @@ export interface Question {
|
|
|
18
36
|
costs?: Record<string, number> | Record<string, Record<string, number>>;
|
|
19
37
|
defer_action?: string;
|
|
20
38
|
max_error?: number;
|
|
39
|
+
objective?: MultiObjective;
|
|
40
|
+
exclusive?: readonly string[];
|
|
41
|
+
fallback?: string;
|
|
42
|
+
fallback_threshold?: number;
|
|
43
|
+
tools?: readonly ToolDefinition[];
|
|
44
|
+
extra?: readonly ("answer_directly" | "ask_user" | "none")[];
|
|
45
|
+
rank?: number;
|
|
46
|
+
multi?: boolean;
|
|
21
47
|
}
|
|
22
48
|
export interface DecisionRequest {
|
|
23
49
|
state: JsonValue;
|
|
24
50
|
questions: Record<string, Question>;
|
|
25
51
|
model?: string;
|
|
26
52
|
facts?: "off" | "auto";
|
|
53
|
+
lang?: "pl" | "en";
|
|
54
|
+
media?: readonly MediaInput[];
|
|
55
|
+
detail?: "default" | "high";
|
|
27
56
|
}
|
|
28
57
|
export interface EvidenceSpan {
|
|
29
58
|
text: string;
|
|
@@ -34,6 +63,7 @@ export interface EvidenceSpan {
|
|
|
34
63
|
interface BaseAnswer<K extends string = string> {
|
|
35
64
|
probabilities: Record<K, number>;
|
|
36
65
|
evidence?: EvidenceSpan[];
|
|
66
|
+
calibration?: Calibration;
|
|
37
67
|
}
|
|
38
68
|
export interface ChoiceAnswer<K extends string = string> extends BaseAnswer<K> {
|
|
39
69
|
type: "choice";
|
|
@@ -51,29 +81,57 @@ export interface ScoreAnswer extends BaseAnswer {
|
|
|
51
81
|
legend: Record<string, string>;
|
|
52
82
|
confidence: number;
|
|
53
83
|
}
|
|
54
|
-
export interface
|
|
84
|
+
export interface SelectionResult<K extends string = string> {
|
|
85
|
+
threshold?: number;
|
|
86
|
+
objective?: MultiObjective;
|
|
87
|
+
expected_score?: number | null;
|
|
88
|
+
constraints_applied?: JsonValue;
|
|
89
|
+
set_confidence: number;
|
|
90
|
+
}
|
|
91
|
+
export interface MultiAnswer<K extends string = string> extends BaseAnswer<K>, SelectionResult<K> {
|
|
55
92
|
type: "multi";
|
|
56
93
|
selected: K[];
|
|
57
|
-
threshold: number;
|
|
58
|
-
set_confidence: number;
|
|
59
94
|
}
|
|
95
|
+
export interface SingleToolAnswer extends BaseAnswer {
|
|
96
|
+
type: "tool";
|
|
97
|
+
multi?: false;
|
|
98
|
+
tool: string | null;
|
|
99
|
+
call: boolean;
|
|
100
|
+
confidence: number;
|
|
101
|
+
ranking?: ToolRanking[];
|
|
102
|
+
deferred?: boolean;
|
|
103
|
+
refused?: string;
|
|
104
|
+
max_error?: string;
|
|
105
|
+
}
|
|
106
|
+
export interface MultiToolAnswer extends BaseAnswer, SelectionResult {
|
|
107
|
+
type: "tool";
|
|
108
|
+
multi: true;
|
|
109
|
+
tools: string[];
|
|
110
|
+
call: boolean;
|
|
111
|
+
ranking?: ToolRanking[];
|
|
112
|
+
}
|
|
113
|
+
export type ToolAnswer = SingleToolAnswer | MultiToolAnswer;
|
|
60
114
|
export interface ActAnswer<K extends string = string, A extends string = string> extends BaseAnswer<K> {
|
|
61
115
|
type: "act";
|
|
62
116
|
action: A;
|
|
63
117
|
answer: K;
|
|
64
118
|
expected_costs: Record<A, number>;
|
|
65
119
|
confidence: number;
|
|
66
|
-
calibration:
|
|
120
|
+
calibration: Calibration;
|
|
67
121
|
refused?: string;
|
|
68
122
|
max_error?: string;
|
|
123
|
+
deferred?: boolean;
|
|
69
124
|
}
|
|
70
|
-
export type Answer = ChoiceAnswer | NoulAnswer | ScoreAnswer | MultiAnswer | ActAnswer;
|
|
125
|
+
export type Answer = ChoiceAnswer | NoulAnswer | ScoreAnswer | MultiAnswer | ActAnswer | ToolAnswer;
|
|
71
126
|
export interface Usage {
|
|
72
127
|
input_tokens: number;
|
|
73
128
|
output_tokens: number;
|
|
74
129
|
questions?: number;
|
|
75
130
|
branches?: number;
|
|
76
131
|
latency_ms?: number;
|
|
132
|
+
longest_prompt_tokens?: number;
|
|
133
|
+
second_order_branches?: number;
|
|
134
|
+
media?: MediaUsage;
|
|
77
135
|
}
|
|
78
136
|
export interface DecisionResponse {
|
|
79
137
|
model: string;
|
|
@@ -86,6 +144,10 @@ export interface ModelMetadata {
|
|
|
86
144
|
release_date?: string;
|
|
87
145
|
mode?: string;
|
|
88
146
|
early_exit?: JsonValue[];
|
|
147
|
+
prompt_format?: number;
|
|
148
|
+
orders?: JsonValue;
|
|
149
|
+
temperature_per_prim?: Record<string, number>;
|
|
150
|
+
media?: ("image" | "audio")[];
|
|
89
151
|
}
|
|
90
152
|
export interface ModelsResponse {
|
|
91
153
|
models: ModelMetadata[];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.image = image;
|
|
4
|
+
exports.audio = audio;
|
|
5
|
+
function media(type, data) {
|
|
6
|
+
if (data instanceof Uint8Array) {
|
|
7
|
+
// Bounded chunks avoid the argument limit of spreading a whole image/audio file.
|
|
8
|
+
const chunks = [];
|
|
9
|
+
for (let offset = 0; offset < data.length; offset += 8192)
|
|
10
|
+
chunks.push(String.fromCharCode(...data.subarray(offset, offset + 8192)));
|
|
11
|
+
data = btoa(chunks.join(''));
|
|
12
|
+
}
|
|
13
|
+
if (typeof data !== 'string' || !data.trim() || /^https?:\/\//i.test(data))
|
|
14
|
+
throw new TypeError('media data must be bytes, base64 or a data URL');
|
|
15
|
+
return { type, data };
|
|
16
|
+
}
|
|
17
|
+
/** Inline image; never fetches a URL or reads a file. */
|
|
18
|
+
function image(data) { return media('image', data); }
|
|
19
|
+
/** Inline audio; never fetches a URL or reads a file. */
|
|
20
|
+
function audio(data) { return media('audio', data); }
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { MediaInput } from './index.cjs';
|
|
2
|
+
/** Inline image; never fetches a URL or reads a file. */
|
|
3
|
+
export declare function image(data: Uint8Array | string): MediaInput;
|
|
4
|
+
/** Inline audio; never fetches a URL or reads a file. */
|
|
5
|
+
export declare function audio(data: Uint8Array | string): MediaInput;
|
package/dist/cjs/questions.cjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ActQuestion = exports.MultiQuestion = exports.ScoreQuestion = exports.NoulQuestion = exports.ChoiceQuestion = exports.QuestionSpec = void 0;
|
|
3
|
+
exports.ActQuestion = exports.ToolQuestion = exports.MultiQuestion = exports.ScoreQuestion = exports.NoulQuestion = exports.ChoiceQuestion = exports.QuestionSpec = void 0;
|
|
4
4
|
exports.choice = choice;
|
|
5
5
|
exports.noul = noul;
|
|
6
6
|
exports.score = score;
|
|
7
7
|
exports.multi = multi;
|
|
8
8
|
exports.act = act;
|
|
9
|
+
exports.tool = tool;
|
|
9
10
|
exports.buildRequest = buildRequest;
|
|
10
11
|
function text(value, name) {
|
|
11
12
|
if (typeof value !== 'string' || !value.trim())
|
|
@@ -51,7 +52,8 @@ class QuestionSpec {
|
|
|
51
52
|
const fields = {
|
|
52
53
|
choice: { choice: 'string', confidence: 'number' }, noul: { noul: 'number', confidence: 'number' },
|
|
53
54
|
score: { score: 'number', legend: 'object', confidence: 'number' },
|
|
54
|
-
multi: { selected: 'object',
|
|
55
|
+
multi: { selected: 'object', set_confidence: 'number' },
|
|
56
|
+
tool: answer.type === 'tool' && answer.multi ? { tools: 'object', call: 'boolean', set_confidence: 'number' } : { call: 'boolean', confidence: 'number' },
|
|
55
57
|
act: { action: 'string', answer: 'string', expected_costs: 'object', confidence: 'number', calibration: 'string' },
|
|
56
58
|
};
|
|
57
59
|
const data = answer;
|
|
@@ -84,14 +86,45 @@ class QuestionSpec {
|
|
|
84
86
|
if (answer.type === 'score' && (Array.isArray(answer.legend) || Object.values(answer.legend).some(v => typeof v !== 'string')))
|
|
85
87
|
throw new TypeError('invalid legend');
|
|
86
88
|
if (answer.type === 'act') {
|
|
87
|
-
if (!['validated', 'unvalidated'].includes(answer.calibration) || !Object.hasOwn(this.wire.costs ?? {}, answer.action) || !expectedKeys.includes(answer.answer))
|
|
89
|
+
if (!['validated', 'unvalidated', 'text'].includes(answer.calibration) || !Object.hasOwn(this.wire.costs ?? {}, answer.action) || !expectedKeys.includes(answer.answer))
|
|
88
90
|
throw new TypeError('invalid action');
|
|
89
91
|
if (Array.isArray(answer.expected_costs) || Object.keys(this.wire.costs ?? {}).some(key => !Object.hasOwn(answer.expected_costs, key)))
|
|
90
92
|
throw new TypeError('invalid expected costs');
|
|
91
93
|
for (const value of Object.values(answer.expected_costs))
|
|
92
94
|
number(value, 'expected cost');
|
|
93
95
|
}
|
|
94
|
-
|
|
96
|
+
if (answer.type === 'multi' || (answer.type === 'tool' && answer.multi)) {
|
|
97
|
+
validateSelection(answer, this.wire.objective ?? 'threshold');
|
|
98
|
+
}
|
|
99
|
+
if (answer.type === 'tool') {
|
|
100
|
+
if ((answer.multi ?? false) !== (this.wire.multi ?? false))
|
|
101
|
+
throw new TypeError('mismatched tool mode');
|
|
102
|
+
if (answer.multi) {
|
|
103
|
+
if (!Array.isArray(answer.tools) || answer.tools.some(k => typeof k !== 'string' || !Object.hasOwn(answer.probabilities, k)))
|
|
104
|
+
throw new TypeError('invalid selected tools');
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
if (answer.tool === null ? answer.deferred !== true : typeof answer.tool !== 'string' || !Object.hasOwn(answer.probabilities, answer.tool))
|
|
108
|
+
throw new TypeError('invalid selected tool');
|
|
109
|
+
if (answer.deferred !== undefined && typeof answer.deferred !== 'boolean')
|
|
110
|
+
throw new TypeError('invalid deferred');
|
|
111
|
+
for (const key of ['refused', 'max_error'])
|
|
112
|
+
if (answer[key] !== undefined && typeof answer[key] !== 'string')
|
|
113
|
+
throw new TypeError('invalid tool metadata');
|
|
114
|
+
}
|
|
115
|
+
if (answer.ranking !== undefined) {
|
|
116
|
+
if (!Array.isArray(answer.ranking))
|
|
117
|
+
throw new TypeError('invalid ranking');
|
|
118
|
+
for (const entry of answer.ranking) {
|
|
119
|
+
if (!entry || typeof entry.tool !== 'string' || !Object.hasOwn(answer.probabilities, entry.tool))
|
|
120
|
+
throw new TypeError('invalid ranking entry');
|
|
121
|
+
number(entry.probability, 'ranking probability', true);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (answer.type === 'act' && answer.deferred !== undefined && typeof answer.deferred !== 'boolean')
|
|
126
|
+
throw new TypeError('invalid deferred');
|
|
127
|
+
return { ...answer, ...(answer.type === 'multi' || answer.type === 'tool' ? {} : { evidence: answer.evidence ?? [] }) };
|
|
95
128
|
}
|
|
96
129
|
}
|
|
97
130
|
exports.QuestionSpec = QuestionSpec;
|
|
@@ -99,7 +132,7 @@ class ChoiceQuestion extends QuestionSpec {
|
|
|
99
132
|
constructor(instructions, values, options = {}) {
|
|
100
133
|
if (options.option_keys !== undefined && !['show', 'hide'].includes(options.option_keys))
|
|
101
134
|
throw new TypeError('invalid option_keys');
|
|
102
|
-
super('choice', instructions, { criteria: criteria(values, 2,
|
|
135
|
+
super('choice', instructions, { criteria: criteria(values, 2, 255), ...evidence(options), ...(options.option_keys === 'hide' ? { option_keys: 'hide' } : {}) });
|
|
103
136
|
}
|
|
104
137
|
}
|
|
105
138
|
exports.ChoiceQuestion = ChoiceQuestion;
|
|
@@ -109,36 +142,101 @@ class NoulQuestion extends QuestionSpec {
|
|
|
109
142
|
exports.NoulQuestion = NoulQuestion;
|
|
110
143
|
class ScoreQuestion extends QuestionSpec {
|
|
111
144
|
constructor(instructions, levels, options = {}) {
|
|
112
|
-
if (!Array.isArray(levels) || levels.length < 2 || levels.length >
|
|
113
|
-
throw new TypeError('score requires 2–
|
|
145
|
+
if (!Array.isArray(levels) || levels.length < 2 || levels.length > 255)
|
|
146
|
+
throw new TypeError('score requires 2–255 ordered levels');
|
|
114
147
|
super('score', instructions, { criteria: levels.map(v => text(v, 'level')), ...evidence(options) });
|
|
115
148
|
}
|
|
116
149
|
}
|
|
117
150
|
exports.ScoreQuestion = ScoreQuestion;
|
|
151
|
+
function selectionOptions(options, keys) {
|
|
152
|
+
const fields = {};
|
|
153
|
+
const objective = options.objective ?? 'threshold';
|
|
154
|
+
if (!['threshold', 'exact', 'iou', 'f1'].includes(objective))
|
|
155
|
+
throw new TypeError('invalid objective');
|
|
156
|
+
if (objective !== 'threshold')
|
|
157
|
+
fields.objective = objective;
|
|
158
|
+
if (options.threshold !== undefined) {
|
|
159
|
+
if (objective !== 'threshold')
|
|
160
|
+
throw new TypeError('threshold applies only to objective threshold');
|
|
161
|
+
fields.threshold = number(options.threshold, 'threshold', true);
|
|
162
|
+
}
|
|
163
|
+
for (const key of ['min', 'max']) {
|
|
164
|
+
const v = options[key];
|
|
165
|
+
if (v !== undefined) {
|
|
166
|
+
if (!Number.isSafeInteger(v) || v < 0 || (keys && v > keys.length))
|
|
167
|
+
throw new TypeError(`invalid ${key}`);
|
|
168
|
+
fields[key] = v;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (options.min !== undefined && options.max !== undefined && options.min > options.max)
|
|
172
|
+
throw new TypeError('min exceeds max');
|
|
173
|
+
if (options.exclusive !== undefined) {
|
|
174
|
+
if (!Array.isArray(options.exclusive))
|
|
175
|
+
throw new TypeError('exclusive must be an array');
|
|
176
|
+
fields.exclusive = options.exclusive.map(k => { text(k, 'exclusive key'); if (keys && !keys.includes(k))
|
|
177
|
+
throw new TypeError('unknown exclusive key'); return k; });
|
|
178
|
+
}
|
|
179
|
+
if (options.fallback !== undefined) {
|
|
180
|
+
fields.fallback = text(options.fallback, 'fallback');
|
|
181
|
+
if (keys && !keys.includes(options.fallback))
|
|
182
|
+
throw new TypeError('unknown fallback');
|
|
183
|
+
}
|
|
184
|
+
if (options.fallback_threshold !== undefined) {
|
|
185
|
+
if (options.fallback === undefined)
|
|
186
|
+
throw new TypeError('fallback_threshold requires fallback');
|
|
187
|
+
fields.fallback_threshold = number(options.fallback_threshold, 'fallback_threshold', true);
|
|
188
|
+
}
|
|
189
|
+
return fields;
|
|
190
|
+
}
|
|
191
|
+
function validateSelection(answer, objective) {
|
|
192
|
+
number(answer.set_confidence, 'set_confidence', true);
|
|
193
|
+
if (objective === 'threshold')
|
|
194
|
+
number(answer.threshold, 'threshold', true);
|
|
195
|
+
else {
|
|
196
|
+
if (answer.objective !== objective)
|
|
197
|
+
throw new TypeError('mismatched objective');
|
|
198
|
+
if (answer.expected_score !== null && answer.expected_score !== undefined)
|
|
199
|
+
number(answer.expected_score, 'expected_score', true);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
118
202
|
class MultiQuestion extends QuestionSpec {
|
|
119
203
|
constructor(instructions, values, options = {}) {
|
|
120
204
|
if ('evidence' in options)
|
|
121
205
|
throw new TypeError('multi does not support evidence');
|
|
122
|
-
|
|
123
|
-
if (options.threshold !== undefined)
|
|
124
|
-
fields.threshold = number(options.threshold, 'threshold', true);
|
|
125
|
-
for (const key of ['min', 'max']) {
|
|
126
|
-
const value = options[key];
|
|
127
|
-
if (value !== undefined) {
|
|
128
|
-
if (!Number.isInteger(value) || value < 0 || value > Object.keys(values).length)
|
|
129
|
-
throw new TypeError(`invalid ${key}`);
|
|
130
|
-
fields[key] = value;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
if (options.min !== undefined && options.max !== undefined && options.min > options.max)
|
|
134
|
-
throw new TypeError('min exceeds max');
|
|
135
|
-
super('multi', instructions, fields);
|
|
206
|
+
super('multi', instructions, { criteria: criteria(values, 1), ...selectionOptions(options, Object.keys(values)) });
|
|
136
207
|
}
|
|
137
208
|
}
|
|
138
209
|
exports.MultiQuestion = MultiQuestion;
|
|
210
|
+
class ToolQuestion extends QuestionSpec {
|
|
211
|
+
constructor(instructions, tools, options = {}) {
|
|
212
|
+
if (!Array.isArray(tools) || tools.some(t => !t || typeof t !== 'object' || Array.isArray(t)))
|
|
213
|
+
throw new TypeError('tools must contain definition objects');
|
|
214
|
+
if (options.extra !== undefined && (!Array.isArray(options.extra) || options.extra.some(k => !['answer_directly', 'ask_user', 'none'].includes(k))))
|
|
215
|
+
throw new TypeError('invalid extra tool option');
|
|
216
|
+
if (options.multi !== undefined && typeof options.multi !== 'boolean')
|
|
217
|
+
throw new TypeError('invalid multi');
|
|
218
|
+
const fields = { tools, ...(options.extra === undefined ? {} : { extra: options.extra }) };
|
|
219
|
+
if (options.rank !== undefined) {
|
|
220
|
+
if (!Number.isSafeInteger(options.rank) || options.rank < 1)
|
|
221
|
+
throw new TypeError('rank must be a positive integer');
|
|
222
|
+
fields.rank = options.rank;
|
|
223
|
+
}
|
|
224
|
+
if (options.max_error !== undefined)
|
|
225
|
+
fields.max_error = number(options.max_error, 'max_error', true);
|
|
226
|
+
const selection = selectionOptions(options);
|
|
227
|
+
if (!options.multi && Object.keys(selection).length)
|
|
228
|
+
throw new TypeError('selection options require multi');
|
|
229
|
+
if (options.multi) {
|
|
230
|
+
fields.multi = true;
|
|
231
|
+
Object.assign(fields, selection);
|
|
232
|
+
}
|
|
233
|
+
super('tool', instructions, fields);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
exports.ToolQuestion = ToolQuestion;
|
|
139
237
|
class ActQuestion extends QuestionSpec {
|
|
140
238
|
constructor(instructions, costs, options = {}) {
|
|
141
|
-
const optionsMap = options.criteria === undefined ? undefined : criteria(options.criteria, 2,
|
|
239
|
+
const optionsMap = options.criteria === undefined ? undefined : criteria(options.criteria, 2, 255);
|
|
142
240
|
const outcomes = Object.keys(optionsMap ?? { 'true': 'Yes', 'false': 'No' });
|
|
143
241
|
if (!costs || typeof costs !== 'object' || Array.isArray(costs) || !Object.keys(costs).length)
|
|
144
242
|
throw new TypeError('act requires a cost matrix');
|
|
@@ -162,6 +260,7 @@ function noul(instructions, options) { return new NoulQuestion(instructions, opt
|
|
|
162
260
|
function score(instructions, levels, options) { return new ScoreQuestion(instructions, levels, options); }
|
|
163
261
|
function multi(instructions, values, options) { return new MultiQuestion(instructions, values, options); }
|
|
164
262
|
function act(instructions, costs, options) { return new ActQuestion(instructions, costs, options); }
|
|
263
|
+
function tool(instructions, tools, options) { return new ToolQuestion(instructions, tools, options); }
|
|
165
264
|
function buildRequest(state, questions, options) {
|
|
166
265
|
if (state === null || state === undefined)
|
|
167
266
|
throw new TypeError('state must not be null/undefined');
|
|
@@ -175,5 +274,11 @@ function buildRequest(state, questions, options) {
|
|
|
175
274
|
text(options.model, 'model');
|
|
176
275
|
if (options.facts !== undefined && !['off', 'auto'].includes(options.facts))
|
|
177
276
|
throw new TypeError('invalid facts');
|
|
178
|
-
|
|
277
|
+
if (options.lang !== undefined && !['pl', 'en'].includes(options.lang))
|
|
278
|
+
throw new TypeError('invalid lang');
|
|
279
|
+
if (options.detail !== undefined && !['default', 'high'].includes(options.detail))
|
|
280
|
+
throw new TypeError('invalid detail');
|
|
281
|
+
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())))
|
|
282
|
+
throw new TypeError('invalid media');
|
|
283
|
+
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 }) };
|
|
179
284
|
}
|
package/dist/cjs/questions.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ActAnswer, Answer, ChoiceAnswer, DecisionResponse, EvidenceSpan, JsonValue, MultiAnswer, NoulAnswer, Question, ScoreAnswer } from './index.cjs';
|
|
1
|
+
import type { ActAnswer, Answer, ChoiceAnswer, DecisionResponse, EvidenceSpan, JsonValue, MultiAnswer, NoulAnswer, Question, ScoreAnswer, ToolAnswer, ToolDefinition, MultiObjective, DecisionRequest } from './index.cjs';
|
|
2
2
|
/** Required result metadata on the typed path; missing spans become an empty array. */
|
|
3
3
|
export type TypedChoiceAnswer<K extends string = string> = ChoiceAnswer<K> & {
|
|
4
4
|
evidence: EvidenceSpan[];
|
|
@@ -19,19 +19,27 @@ export interface EvidenceOptions {
|
|
|
19
19
|
export interface ChoiceOptions extends EvidenceOptions {
|
|
20
20
|
option_keys?: 'show' | 'hide';
|
|
21
21
|
}
|
|
22
|
-
export interface MultiOptions {
|
|
22
|
+
export interface MultiOptions<K extends string = string> {
|
|
23
23
|
threshold?: number;
|
|
24
24
|
min?: number;
|
|
25
25
|
max?: number;
|
|
26
|
+
objective?: MultiObjective;
|
|
27
|
+
exclusive?: readonly K[];
|
|
28
|
+
fallback?: K;
|
|
29
|
+
fallback_threshold?: number;
|
|
30
|
+
}
|
|
31
|
+
export interface ToolOptions extends MultiOptions {
|
|
32
|
+
extra?: readonly ("answer_directly" | "ask_user" | "none")[];
|
|
33
|
+
rank?: number;
|
|
34
|
+
multi?: boolean;
|
|
35
|
+
max_error?: number;
|
|
26
36
|
}
|
|
27
37
|
export interface ActOptions<K extends string = string, A extends string = string> extends EvidenceOptions {
|
|
28
38
|
criteria?: Record<K, string>;
|
|
29
39
|
defer_action?: A;
|
|
30
40
|
max_error?: number;
|
|
31
41
|
}
|
|
32
|
-
export interface DecisionOptions {
|
|
33
|
-
model?: string;
|
|
34
|
-
facts?: 'off' | 'auto';
|
|
42
|
+
export interface DecisionOptions extends Pick<DecisionRequest, 'model' | 'facts' | 'lang' | 'media' | 'detail'> {
|
|
35
43
|
signal?: AbortSignal;
|
|
36
44
|
}
|
|
37
45
|
/** Immutable snapshot of a typed question. Use factories or the concrete constructors. */
|
|
@@ -52,7 +60,10 @@ export declare class ScoreQuestion extends QuestionSpec<TypedScoreAnswer> {
|
|
|
52
60
|
constructor(instructions: string, levels: readonly string[], options?: EvidenceOptions);
|
|
53
61
|
}
|
|
54
62
|
export declare class MultiQuestion<K extends string = string> extends QuestionSpec<TypedMultiAnswer<K>> {
|
|
55
|
-
constructor(instructions: string, values: Record<K, string>, options?: MultiOptions);
|
|
63
|
+
constructor(instructions: string, values: Record<K, string>, options?: MultiOptions<K>);
|
|
64
|
+
}
|
|
65
|
+
export declare class ToolQuestion extends QuestionSpec<ToolAnswer> {
|
|
66
|
+
constructor(instructions: string, tools: readonly ToolDefinition[], options?: ToolOptions);
|
|
56
67
|
}
|
|
57
68
|
export declare class ActQuestion<K extends string = 'true' | 'false', A extends string = string> extends QuestionSpec<TypedActAnswer<K, A>> {
|
|
58
69
|
constructor(instructions: string, costs: Record<A, Record<NoInfer<K>, number>>, options?: ActOptions<K, NoInfer<A>>);
|
|
@@ -60,8 +71,9 @@ export declare class ActQuestion<K extends string = 'true' | 'false', A extends
|
|
|
60
71
|
export declare function choice<const K extends string>(instructions: string, values: Record<K, string>, options?: ChoiceOptions): ChoiceQuestion<K>;
|
|
61
72
|
export declare function noul(instructions: string, options?: EvidenceOptions): NoulQuestion;
|
|
62
73
|
export declare function score(instructions: string, levels: readonly string[], options?: EvidenceOptions): ScoreQuestion;
|
|
63
|
-
export declare function multi<const K extends string>(instructions: string, values: Record<K, string>, options?: MultiOptions): MultiQuestion<K>;
|
|
74
|
+
export declare function multi<const K extends string>(instructions: string, values: Record<K, string>, options?: MultiOptions<K>): MultiQuestion<K>;
|
|
64
75
|
export declare function act<const K extends string = 'true' | 'false', const A extends string = string>(instructions: string, costs: Record<A, Record<NoInfer<K>, number>>, options?: ActOptions<K, NoInfer<A>>): ActQuestion<K, A>;
|
|
76
|
+
export declare function tool(instructions: string, tools: readonly ToolDefinition[], options?: ToolOptions): ToolQuestion;
|
|
65
77
|
export type QuestionSet = Record<string, QuestionSpec>;
|
|
66
78
|
export type TypedDecisionResponse<Q extends QuestionSet> = Omit<DecisionResponse, 'answers'> & {
|
|
67
79
|
answers: {
|
|
@@ -70,9 +82,4 @@ export type TypedDecisionResponse<Q extends QuestionSet> = Omit<DecisionResponse
|
|
|
70
82
|
answer<K extends keyof Q & (string | number)>(key: K): Q[K] extends QuestionSpec<infer A> ? A : never;
|
|
71
83
|
raw: DecisionResponse;
|
|
72
84
|
};
|
|
73
|
-
export declare function buildRequest(state: JsonValue, questions: QuestionSet, options: DecisionOptions):
|
|
74
|
-
state: JsonValue;
|
|
75
|
-
questions: Record<string, Question>;
|
|
76
|
-
model?: string;
|
|
77
|
-
facts?: 'off' | 'auto';
|
|
78
|
-
};
|
|
85
|
+
export declare function buildRequest(state: JsonValue, questions: QuestionSet, options: DecisionOptions): DecisionRequest;
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,11 +1,29 @@
|
|
|
1
1
|
import { type QuestionSet, type TypedDecisionResponse, type DecisionOptions } from "./questions.js";
|
|
2
2
|
export * from "./questions.js";
|
|
3
|
+
export * from "./media.js";
|
|
3
4
|
/** Native Basal wire types. Criterion order is significant; do not sort keys. */
|
|
4
5
|
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
5
6
|
[key: string]: JsonValue;
|
|
6
7
|
};
|
|
8
|
+
export type Calibration = "validated" | "unvalidated" | "text";
|
|
9
|
+
export type MultiObjective = "threshold" | "exact" | "iou" | "f1";
|
|
10
|
+
export interface MediaInput {
|
|
11
|
+
type: "image" | "audio";
|
|
12
|
+
data: string;
|
|
13
|
+
}
|
|
14
|
+
export interface MediaUsage {
|
|
15
|
+
images: number;
|
|
16
|
+
audio: number;
|
|
17
|
+
media_tokens: number;
|
|
18
|
+
detail: "default" | "high";
|
|
19
|
+
}
|
|
20
|
+
export interface ToolRanking {
|
|
21
|
+
tool: string;
|
|
22
|
+
probability: number;
|
|
23
|
+
}
|
|
24
|
+
export type ToolDefinition = Record<string, JsonValue>;
|
|
7
25
|
export interface Question {
|
|
8
|
-
type?: "choice" | "noul" | "score" | "multi" | "act";
|
|
26
|
+
type?: "choice" | "noul" | "score" | "multi" | "act" | "tool";
|
|
9
27
|
instructions?: JsonValue;
|
|
10
28
|
criteria?: Record<string, JsonValue> | JsonValue[];
|
|
11
29
|
levels?: Record<string, JsonValue> | JsonValue[];
|
|
@@ -18,12 +36,23 @@ export interface Question {
|
|
|
18
36
|
costs?: Record<string, number> | Record<string, Record<string, number>>;
|
|
19
37
|
defer_action?: string;
|
|
20
38
|
max_error?: number;
|
|
39
|
+
objective?: MultiObjective;
|
|
40
|
+
exclusive?: readonly string[];
|
|
41
|
+
fallback?: string;
|
|
42
|
+
fallback_threshold?: number;
|
|
43
|
+
tools?: readonly ToolDefinition[];
|
|
44
|
+
extra?: readonly ("answer_directly" | "ask_user" | "none")[];
|
|
45
|
+
rank?: number;
|
|
46
|
+
multi?: boolean;
|
|
21
47
|
}
|
|
22
48
|
export interface DecisionRequest {
|
|
23
49
|
state: JsonValue;
|
|
24
50
|
questions: Record<string, Question>;
|
|
25
51
|
model?: string;
|
|
26
52
|
facts?: "off" | "auto";
|
|
53
|
+
lang?: "pl" | "en";
|
|
54
|
+
media?: readonly MediaInput[];
|
|
55
|
+
detail?: "default" | "high";
|
|
27
56
|
}
|
|
28
57
|
export interface EvidenceSpan {
|
|
29
58
|
text: string;
|
|
@@ -34,6 +63,7 @@ export interface EvidenceSpan {
|
|
|
34
63
|
interface BaseAnswer<K extends string = string> {
|
|
35
64
|
probabilities: Record<K, number>;
|
|
36
65
|
evidence?: EvidenceSpan[];
|
|
66
|
+
calibration?: Calibration;
|
|
37
67
|
}
|
|
38
68
|
export interface ChoiceAnswer<K extends string = string> extends BaseAnswer<K> {
|
|
39
69
|
type: "choice";
|
|
@@ -51,29 +81,57 @@ export interface ScoreAnswer extends BaseAnswer {
|
|
|
51
81
|
legend: Record<string, string>;
|
|
52
82
|
confidence: number;
|
|
53
83
|
}
|
|
54
|
-
export interface
|
|
84
|
+
export interface SelectionResult<K extends string = string> {
|
|
85
|
+
threshold?: number;
|
|
86
|
+
objective?: MultiObjective;
|
|
87
|
+
expected_score?: number | null;
|
|
88
|
+
constraints_applied?: JsonValue;
|
|
89
|
+
set_confidence: number;
|
|
90
|
+
}
|
|
91
|
+
export interface MultiAnswer<K extends string = string> extends BaseAnswer<K>, SelectionResult<K> {
|
|
55
92
|
type: "multi";
|
|
56
93
|
selected: K[];
|
|
57
|
-
threshold: number;
|
|
58
|
-
set_confidence: number;
|
|
59
94
|
}
|
|
95
|
+
export interface SingleToolAnswer extends BaseAnswer {
|
|
96
|
+
type: "tool";
|
|
97
|
+
multi?: false;
|
|
98
|
+
tool: string | null;
|
|
99
|
+
call: boolean;
|
|
100
|
+
confidence: number;
|
|
101
|
+
ranking?: ToolRanking[];
|
|
102
|
+
deferred?: boolean;
|
|
103
|
+
refused?: string;
|
|
104
|
+
max_error?: string;
|
|
105
|
+
}
|
|
106
|
+
export interface MultiToolAnswer extends BaseAnswer, SelectionResult {
|
|
107
|
+
type: "tool";
|
|
108
|
+
multi: true;
|
|
109
|
+
tools: string[];
|
|
110
|
+
call: boolean;
|
|
111
|
+
ranking?: ToolRanking[];
|
|
112
|
+
}
|
|
113
|
+
export type ToolAnswer = SingleToolAnswer | MultiToolAnswer;
|
|
60
114
|
export interface ActAnswer<K extends string = string, A extends string = string> extends BaseAnswer<K> {
|
|
61
115
|
type: "act";
|
|
62
116
|
action: A;
|
|
63
117
|
answer: K;
|
|
64
118
|
expected_costs: Record<A, number>;
|
|
65
119
|
confidence: number;
|
|
66
|
-
calibration:
|
|
120
|
+
calibration: Calibration;
|
|
67
121
|
refused?: string;
|
|
68
122
|
max_error?: string;
|
|
123
|
+
deferred?: boolean;
|
|
69
124
|
}
|
|
70
|
-
export type Answer = ChoiceAnswer | NoulAnswer | ScoreAnswer | MultiAnswer | ActAnswer;
|
|
125
|
+
export type Answer = ChoiceAnswer | NoulAnswer | ScoreAnswer | MultiAnswer | ActAnswer | ToolAnswer;
|
|
71
126
|
export interface Usage {
|
|
72
127
|
input_tokens: number;
|
|
73
128
|
output_tokens: number;
|
|
74
129
|
questions?: number;
|
|
75
130
|
branches?: number;
|
|
76
131
|
latency_ms?: number;
|
|
132
|
+
longest_prompt_tokens?: number;
|
|
133
|
+
second_order_branches?: number;
|
|
134
|
+
media?: MediaUsage;
|
|
77
135
|
}
|
|
78
136
|
export interface DecisionResponse {
|
|
79
137
|
model: string;
|
|
@@ -86,6 +144,10 @@ export interface ModelMetadata {
|
|
|
86
144
|
release_date?: string;
|
|
87
145
|
mode?: string;
|
|
88
146
|
early_exit?: JsonValue[];
|
|
147
|
+
prompt_format?: number;
|
|
148
|
+
orders?: JsonValue;
|
|
149
|
+
temperature_per_prim?: Record<string, number>;
|
|
150
|
+
media?: ("image" | "audio")[];
|
|
89
151
|
}
|
|
90
152
|
export interface ModelsResponse {
|
|
91
153
|
models: ModelMetadata[];
|
package/dist/esm/index.js
CHANGED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { MediaInput } from './index.js';
|
|
2
|
+
/** Inline image; never fetches a URL or reads a file. */
|
|
3
|
+
export declare function image(data: Uint8Array | string): MediaInput;
|
|
4
|
+
/** Inline audio; never fetches a URL or reads a file. */
|
|
5
|
+
export declare function audio(data: Uint8Array | string): MediaInput;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
function media(type, data) {
|
|
2
|
+
if (data instanceof Uint8Array) {
|
|
3
|
+
// Bounded chunks avoid the argument limit of spreading a whole image/audio file.
|
|
4
|
+
const chunks = [];
|
|
5
|
+
for (let offset = 0; offset < data.length; offset += 8192)
|
|
6
|
+
chunks.push(String.fromCharCode(...data.subarray(offset, offset + 8192)));
|
|
7
|
+
data = btoa(chunks.join(''));
|
|
8
|
+
}
|
|
9
|
+
if (typeof data !== 'string' || !data.trim() || /^https?:\/\//i.test(data))
|
|
10
|
+
throw new TypeError('media data must be bytes, base64 or a data URL');
|
|
11
|
+
return { type, data };
|
|
12
|
+
}
|
|
13
|
+
/** Inline image; never fetches a URL or reads a file. */
|
|
14
|
+
export function image(data) { return media('image', data); }
|
|
15
|
+
/** Inline audio; never fetches a URL or reads a file. */
|
|
16
|
+
export function audio(data) { return media('audio', data); }
|
package/dist/esm/questions.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ActAnswer, Answer, ChoiceAnswer, DecisionResponse, EvidenceSpan, JsonValue, MultiAnswer, NoulAnswer, Question, ScoreAnswer } from './index.js';
|
|
1
|
+
import type { ActAnswer, Answer, ChoiceAnswer, DecisionResponse, EvidenceSpan, JsonValue, MultiAnswer, NoulAnswer, Question, ScoreAnswer, ToolAnswer, ToolDefinition, MultiObjective, DecisionRequest } from './index.js';
|
|
2
2
|
/** Required result metadata on the typed path; missing spans become an empty array. */
|
|
3
3
|
export type TypedChoiceAnswer<K extends string = string> = ChoiceAnswer<K> & {
|
|
4
4
|
evidence: EvidenceSpan[];
|
|
@@ -19,19 +19,27 @@ export interface EvidenceOptions {
|
|
|
19
19
|
export interface ChoiceOptions extends EvidenceOptions {
|
|
20
20
|
option_keys?: 'show' | 'hide';
|
|
21
21
|
}
|
|
22
|
-
export interface MultiOptions {
|
|
22
|
+
export interface MultiOptions<K extends string = string> {
|
|
23
23
|
threshold?: number;
|
|
24
24
|
min?: number;
|
|
25
25
|
max?: number;
|
|
26
|
+
objective?: MultiObjective;
|
|
27
|
+
exclusive?: readonly K[];
|
|
28
|
+
fallback?: K;
|
|
29
|
+
fallback_threshold?: number;
|
|
30
|
+
}
|
|
31
|
+
export interface ToolOptions extends MultiOptions {
|
|
32
|
+
extra?: readonly ("answer_directly" | "ask_user" | "none")[];
|
|
33
|
+
rank?: number;
|
|
34
|
+
multi?: boolean;
|
|
35
|
+
max_error?: number;
|
|
26
36
|
}
|
|
27
37
|
export interface ActOptions<K extends string = string, A extends string = string> extends EvidenceOptions {
|
|
28
38
|
criteria?: Record<K, string>;
|
|
29
39
|
defer_action?: A;
|
|
30
40
|
max_error?: number;
|
|
31
41
|
}
|
|
32
|
-
export interface DecisionOptions {
|
|
33
|
-
model?: string;
|
|
34
|
-
facts?: 'off' | 'auto';
|
|
42
|
+
export interface DecisionOptions extends Pick<DecisionRequest, 'model' | 'facts' | 'lang' | 'media' | 'detail'> {
|
|
35
43
|
signal?: AbortSignal;
|
|
36
44
|
}
|
|
37
45
|
/** Immutable snapshot of a typed question. Use factories or the concrete constructors. */
|
|
@@ -52,7 +60,10 @@ export declare class ScoreQuestion extends QuestionSpec<TypedScoreAnswer> {
|
|
|
52
60
|
constructor(instructions: string, levels: readonly string[], options?: EvidenceOptions);
|
|
53
61
|
}
|
|
54
62
|
export declare class MultiQuestion<K extends string = string> extends QuestionSpec<TypedMultiAnswer<K>> {
|
|
55
|
-
constructor(instructions: string, values: Record<K, string>, options?: MultiOptions);
|
|
63
|
+
constructor(instructions: string, values: Record<K, string>, options?: MultiOptions<K>);
|
|
64
|
+
}
|
|
65
|
+
export declare class ToolQuestion extends QuestionSpec<ToolAnswer> {
|
|
66
|
+
constructor(instructions: string, tools: readonly ToolDefinition[], options?: ToolOptions);
|
|
56
67
|
}
|
|
57
68
|
export declare class ActQuestion<K extends string = 'true' | 'false', A extends string = string> extends QuestionSpec<TypedActAnswer<K, A>> {
|
|
58
69
|
constructor(instructions: string, costs: Record<A, Record<NoInfer<K>, number>>, options?: ActOptions<K, NoInfer<A>>);
|
|
@@ -60,8 +71,9 @@ export declare class ActQuestion<K extends string = 'true' | 'false', A extends
|
|
|
60
71
|
export declare function choice<const K extends string>(instructions: string, values: Record<K, string>, options?: ChoiceOptions): ChoiceQuestion<K>;
|
|
61
72
|
export declare function noul(instructions: string, options?: EvidenceOptions): NoulQuestion;
|
|
62
73
|
export declare function score(instructions: string, levels: readonly string[], options?: EvidenceOptions): ScoreQuestion;
|
|
63
|
-
export declare function multi<const K extends string>(instructions: string, values: Record<K, string>, options?: MultiOptions): MultiQuestion<K>;
|
|
74
|
+
export declare function multi<const K extends string>(instructions: string, values: Record<K, string>, options?: MultiOptions<K>): MultiQuestion<K>;
|
|
64
75
|
export declare function act<const K extends string = 'true' | 'false', const A extends string = string>(instructions: string, costs: Record<A, Record<NoInfer<K>, number>>, options?: ActOptions<K, NoInfer<A>>): ActQuestion<K, A>;
|
|
76
|
+
export declare function tool(instructions: string, tools: readonly ToolDefinition[], options?: ToolOptions): ToolQuestion;
|
|
65
77
|
export type QuestionSet = Record<string, QuestionSpec>;
|
|
66
78
|
export type TypedDecisionResponse<Q extends QuestionSet> = Omit<DecisionResponse, 'answers'> & {
|
|
67
79
|
answers: {
|
|
@@ -70,9 +82,4 @@ export type TypedDecisionResponse<Q extends QuestionSet> = Omit<DecisionResponse
|
|
|
70
82
|
answer<K extends keyof Q & (string | number)>(key: K): Q[K] extends QuestionSpec<infer A> ? A : never;
|
|
71
83
|
raw: DecisionResponse;
|
|
72
84
|
};
|
|
73
|
-
export declare function buildRequest(state: JsonValue, questions: QuestionSet, options: DecisionOptions):
|
|
74
|
-
state: JsonValue;
|
|
75
|
-
questions: Record<string, Question>;
|
|
76
|
-
model?: string;
|
|
77
|
-
facts?: 'off' | 'auto';
|
|
78
|
-
};
|
|
85
|
+
export declare function buildRequest(state: JsonValue, questions: QuestionSet, options: DecisionOptions): DecisionRequest;
|
package/dist/esm/questions.js
CHANGED
|
@@ -42,7 +42,8 @@ export class QuestionSpec {
|
|
|
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,98 @@ 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.rank !== undefined) {
|
|
205
|
+
if (!Number.isSafeInteger(options.rank) || options.rank < 1)
|
|
206
|
+
throw new TypeError('rank must be a positive integer');
|
|
207
|
+
fields.rank = options.rank;
|
|
208
|
+
}
|
|
209
|
+
if (options.max_error !== undefined)
|
|
210
|
+
fields.max_error = number(options.max_error, 'max_error', true);
|
|
211
|
+
const selection = selectionOptions(options);
|
|
212
|
+
if (!options.multi && Object.keys(selection).length)
|
|
213
|
+
throw new TypeError('selection options require multi');
|
|
214
|
+
if (options.multi) {
|
|
215
|
+
fields.multi = true;
|
|
216
|
+
Object.assign(fields, selection);
|
|
119
217
|
}
|
|
120
|
-
|
|
121
|
-
throw new TypeError('min exceeds max');
|
|
122
|
-
super('multi', instructions, fields);
|
|
218
|
+
super('tool', instructions, fields);
|
|
123
219
|
}
|
|
124
220
|
}
|
|
125
221
|
export class ActQuestion extends QuestionSpec {
|
|
126
222
|
constructor(instructions, costs, options = {}) {
|
|
127
|
-
const optionsMap = options.criteria === undefined ? undefined : criteria(options.criteria, 2,
|
|
223
|
+
const optionsMap = options.criteria === undefined ? undefined : criteria(options.criteria, 2, 255);
|
|
128
224
|
const outcomes = Object.keys(optionsMap ?? { 'true': 'Yes', 'false': 'No' });
|
|
129
225
|
if (!costs || typeof costs !== 'object' || Array.isArray(costs) || !Object.keys(costs).length)
|
|
130
226
|
throw new TypeError('act requires a cost matrix');
|
|
@@ -147,6 +243,7 @@ export function noul(instructions, options) { return new NoulQuestion(instructio
|
|
|
147
243
|
export function score(instructions, levels, options) { return new ScoreQuestion(instructions, levels, options); }
|
|
148
244
|
export function multi(instructions, values, options) { return new MultiQuestion(instructions, values, options); }
|
|
149
245
|
export function act(instructions, costs, options) { return new ActQuestion(instructions, costs, options); }
|
|
246
|
+
export function tool(instructions, tools, options) { return new ToolQuestion(instructions, tools, options); }
|
|
150
247
|
export function buildRequest(state, questions, options) {
|
|
151
248
|
if (state === null || state === undefined)
|
|
152
249
|
throw new TypeError('state must not be null/undefined');
|
|
@@ -160,5 +257,11 @@ export function buildRequest(state, questions, options) {
|
|
|
160
257
|
text(options.model, 'model');
|
|
161
258
|
if (options.facts !== undefined && !['off', 'auto'].includes(options.facts))
|
|
162
259
|
throw new TypeError('invalid facts');
|
|
163
|
-
|
|
260
|
+
if (options.lang !== undefined && !['pl', 'en'].includes(options.lang))
|
|
261
|
+
throw new TypeError('invalid lang');
|
|
262
|
+
if (options.detail !== undefined && !['default', 'high'].includes(options.detail))
|
|
263
|
+
throw new TypeError('invalid detail');
|
|
264
|
+
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())))
|
|
265
|
+
throw new TypeError('invalid media');
|
|
266
|
+
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
267
|
}
|