@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/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,55 @@ 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. The server validates each model’s capabilities.
|
|
107
|
+
Basal 2.1 task helpers are described below.
|
|
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, description_chars:120}),
|
|
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
|
+
```
|
|
117
|
+
|
|
118
|
+
Tool descriptions are normalized by the server. Omit `description_chars`
|
|
119
|
+
(`DescriptionChars` in .NET) to keep the default cap of 300 Unicode code points;
|
|
120
|
+
use `0` to disable truncation. Descriptions longer than a positive cap are
|
|
121
|
+
shortened at a word boundary where possible, with an ellipsis appended. The SDK forwards this option
|
|
122
|
+
without changing the supplied tool definitions.
|
|
123
|
+
|
|
124
|
+
## Experimental Basal 2.1 tasks
|
|
125
|
+
|
|
126
|
+
Typed classify, verify, compress and extract factories preserve nullable task automation,
|
|
127
|
+
Unicode code point offsets and nested JSON extraction values. Task answers do not share
|
|
128
|
+
the primitive probabilities field. See [task semantics](../../docs/TASKS.md) and
|
|
129
|
+
[SDK examples](../../docs/SDK.md#zadania-basal-21). These source changes require a server
|
|
130
|
+
with the experimental task layer; installing an SDK does not update the inference server.
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -17,6 +17,8 @@ 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);
|
|
21
|
+
__exportStar(require("./tasks.cjs"), exports);
|
|
20
22
|
class BasalError extends Error {
|
|
21
23
|
}
|
|
22
24
|
exports.BasalError = BasalError;
|
package/dist/cjs/index.d.cts
CHANGED
|
@@ -1,11 +1,31 @@
|
|
|
1
1
|
import { type QuestionSet, type TypedDecisionResponse, type DecisionOptions } from "./questions.cjs";
|
|
2
2
|
export * from "./questions.cjs";
|
|
3
|
+
export * from "./media.cjs";
|
|
4
|
+
export * from "./tasks.cjs";
|
|
5
|
+
import type { TaskAnswer } from "./tasks.cjs";
|
|
3
6
|
/** Native Basal wire types. Criterion order is significant; do not sort keys. */
|
|
4
7
|
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
5
8
|
[key: string]: JsonValue;
|
|
6
9
|
};
|
|
10
|
+
export type Calibration = "validated" | "unvalidated" | "text";
|
|
11
|
+
export type MultiObjective = "threshold" | "exact" | "iou" | "f1";
|
|
12
|
+
export interface MediaInput {
|
|
13
|
+
type: "image" | "audio";
|
|
14
|
+
data: string;
|
|
15
|
+
}
|
|
16
|
+
export interface MediaUsage {
|
|
17
|
+
images: number;
|
|
18
|
+
audio: number;
|
|
19
|
+
media_tokens: number;
|
|
20
|
+
detail: "default" | "high";
|
|
21
|
+
}
|
|
22
|
+
export interface ToolRanking {
|
|
23
|
+
tool: string;
|
|
24
|
+
probability: number;
|
|
25
|
+
}
|
|
26
|
+
export type ToolDefinition = Record<string, JsonValue>;
|
|
7
27
|
export interface Question {
|
|
8
|
-
type?: "choice" | "noul" | "score" | "multi" | "act";
|
|
28
|
+
type?: "choice" | "noul" | "score" | "multi" | "act" | "tool" | "classify" | "verify" | "compress" | "extract";
|
|
9
29
|
instructions?: JsonValue;
|
|
10
30
|
criteria?: Record<string, JsonValue> | JsonValue[];
|
|
11
31
|
levels?: Record<string, JsonValue> | JsonValue[];
|
|
@@ -17,13 +37,34 @@ export interface Question {
|
|
|
17
37
|
base?: "noul" | "choice" | "score";
|
|
18
38
|
costs?: Record<string, number> | Record<string, Record<string, number>>;
|
|
19
39
|
defer_action?: string;
|
|
20
|
-
max_error?: number;
|
|
40
|
+
max_error?: number | readonly number[];
|
|
41
|
+
objective?: MultiObjective;
|
|
42
|
+
exclusive?: readonly string[];
|
|
43
|
+
fallback?: string;
|
|
44
|
+
fallback_threshold?: number;
|
|
45
|
+
tools?: readonly ToolDefinition[];
|
|
46
|
+
extra?: readonly ("answer_directly" | "ask_user" | "none")[];
|
|
47
|
+
rank?: number;
|
|
48
|
+
description_chars?: number;
|
|
49
|
+
multi?: boolean;
|
|
50
|
+
labels?: Record<string, JsonValue> | JsonValue[];
|
|
51
|
+
multi_label?: boolean;
|
|
52
|
+
none?: boolean;
|
|
53
|
+
claims?: Record<string, JsonValue> | JsonValue[];
|
|
54
|
+
binary?: boolean;
|
|
55
|
+
unit?: 'sentence' | 'line' | 'paragraph';
|
|
56
|
+
keep_ratio?: number;
|
|
57
|
+
min_keep?: number;
|
|
58
|
+
schema?: Record<string, JsonValue>;
|
|
21
59
|
}
|
|
22
60
|
export interface DecisionRequest {
|
|
23
61
|
state: JsonValue;
|
|
24
62
|
questions: Record<string, Question>;
|
|
25
63
|
model?: string;
|
|
26
64
|
facts?: "off" | "auto";
|
|
65
|
+
lang?: "pl" | "en";
|
|
66
|
+
media?: readonly MediaInput[];
|
|
67
|
+
detail?: "default" | "high";
|
|
27
68
|
}
|
|
28
69
|
export interface EvidenceSpan {
|
|
29
70
|
text: string;
|
|
@@ -34,6 +75,7 @@ export interface EvidenceSpan {
|
|
|
34
75
|
interface BaseAnswer<K extends string = string> {
|
|
35
76
|
probabilities: Record<K, number>;
|
|
36
77
|
evidence?: EvidenceSpan[];
|
|
78
|
+
calibration?: Calibration;
|
|
37
79
|
}
|
|
38
80
|
export interface ChoiceAnswer<K extends string = string> extends BaseAnswer<K> {
|
|
39
81
|
type: "choice";
|
|
@@ -51,29 +93,57 @@ export interface ScoreAnswer extends BaseAnswer {
|
|
|
51
93
|
legend: Record<string, string>;
|
|
52
94
|
confidence: number;
|
|
53
95
|
}
|
|
54
|
-
export interface
|
|
96
|
+
export interface SelectionResult<K extends string = string> {
|
|
97
|
+
threshold?: number;
|
|
98
|
+
objective?: MultiObjective;
|
|
99
|
+
expected_score?: number | null;
|
|
100
|
+
constraints_applied?: JsonValue;
|
|
101
|
+
set_confidence: number;
|
|
102
|
+
}
|
|
103
|
+
export interface MultiAnswer<K extends string = string> extends BaseAnswer<K>, SelectionResult<K> {
|
|
55
104
|
type: "multi";
|
|
56
105
|
selected: K[];
|
|
57
|
-
threshold: number;
|
|
58
|
-
set_confidence: number;
|
|
59
106
|
}
|
|
107
|
+
export interface SingleToolAnswer extends BaseAnswer {
|
|
108
|
+
type: "tool";
|
|
109
|
+
multi?: false;
|
|
110
|
+
tool: string | null;
|
|
111
|
+
call: boolean;
|
|
112
|
+
confidence: number;
|
|
113
|
+
ranking?: ToolRanking[];
|
|
114
|
+
deferred?: boolean;
|
|
115
|
+
refused?: string;
|
|
116
|
+
max_error?: string;
|
|
117
|
+
}
|
|
118
|
+
export interface MultiToolAnswer extends BaseAnswer, SelectionResult {
|
|
119
|
+
type: "tool";
|
|
120
|
+
multi: true;
|
|
121
|
+
tools: string[];
|
|
122
|
+
call: boolean;
|
|
123
|
+
ranking?: ToolRanking[];
|
|
124
|
+
}
|
|
125
|
+
export type ToolAnswer = SingleToolAnswer | MultiToolAnswer;
|
|
60
126
|
export interface ActAnswer<K extends string = string, A extends string = string> extends BaseAnswer<K> {
|
|
61
127
|
type: "act";
|
|
62
128
|
action: A;
|
|
63
129
|
answer: K;
|
|
64
130
|
expected_costs: Record<A, number>;
|
|
65
131
|
confidence: number;
|
|
66
|
-
calibration:
|
|
132
|
+
calibration: Calibration;
|
|
67
133
|
refused?: string;
|
|
68
134
|
max_error?: string;
|
|
135
|
+
deferred?: boolean;
|
|
69
136
|
}
|
|
70
|
-
export type Answer = ChoiceAnswer | NoulAnswer | ScoreAnswer | MultiAnswer | ActAnswer;
|
|
137
|
+
export type Answer = ChoiceAnswer | NoulAnswer | ScoreAnswer | MultiAnswer | ActAnswer | ToolAnswer | TaskAnswer;
|
|
71
138
|
export interface Usage {
|
|
72
139
|
input_tokens: number;
|
|
73
140
|
output_tokens: number;
|
|
74
141
|
questions?: number;
|
|
75
142
|
branches?: number;
|
|
76
143
|
latency_ms?: number;
|
|
144
|
+
longest_prompt_tokens?: number;
|
|
145
|
+
second_order_branches?: number;
|
|
146
|
+
media?: MediaUsage;
|
|
77
147
|
}
|
|
78
148
|
export interface DecisionResponse {
|
|
79
149
|
model: string;
|
|
@@ -86,6 +156,10 @@ export interface ModelMetadata {
|
|
|
86
156
|
release_date?: string;
|
|
87
157
|
mode?: string;
|
|
88
158
|
early_exit?: JsonValue[];
|
|
159
|
+
prompt_format?: number;
|
|
160
|
+
orders?: JsonValue;
|
|
161
|
+
temperature_per_prim?: Record<string, number>;
|
|
162
|
+
media?: ("image" | "audio")[];
|
|
89
163
|
}
|
|
90
164
|
export interface ModelsResponse {
|
|
91
165
|
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())
|
|
@@ -39,19 +40,20 @@ function evidence(options) {
|
|
|
39
40
|
class QuestionSpec {
|
|
40
41
|
wire;
|
|
41
42
|
constructor(kind, instructions, fields) {
|
|
42
|
-
this.wire = structuredClone({ ...fields, type: kind, instructions: text(instructions, 'instructions') });
|
|
43
|
+
this.wire = structuredClone({ ...fields, type: kind, ...(['classify', 'verify', 'compress', 'extract'].includes(kind) ? (instructions ? { instructions } : {}) : { instructions: text(instructions, 'instructions') }) });
|
|
43
44
|
}
|
|
44
45
|
toJSON() { return structuredClone(this.wire); }
|
|
45
46
|
/** Validate and retrieve an answer with this question's result type. */
|
|
46
47
|
read(response, id) {
|
|
47
48
|
const answer = response.answers?.[id];
|
|
48
|
-
if (!answer || answer.type !== this.wire.type || !answer.probabilities || typeof answer.probabilities !== 'object' || Array.isArray(answer.probabilities)) {
|
|
49
|
+
if (!answer || !('probabilities' in answer) || answer.type !== this.wire.type || !answer.probabilities || typeof answer.probabilities !== 'object' || Array.isArray(answer.probabilities)) {
|
|
49
50
|
throw new TypeError('Basal returned a missing or mismatched answer');
|
|
50
51
|
}
|
|
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,106 @@ 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.description_chars !== undefined) {
|
|
220
|
+
if (!Number.isSafeInteger(options.description_chars) || options.description_chars < 0)
|
|
221
|
+
throw new TypeError('description_chars must be a non-negative safe integer');
|
|
222
|
+
fields.description_chars = options.description_chars;
|
|
223
|
+
}
|
|
224
|
+
if (options.rank !== undefined) {
|
|
225
|
+
if (!Number.isSafeInteger(options.rank) || options.rank < 1)
|
|
226
|
+
throw new TypeError('rank must be a positive integer');
|
|
227
|
+
fields.rank = options.rank;
|
|
228
|
+
}
|
|
229
|
+
if (options.max_error !== undefined)
|
|
230
|
+
fields.max_error = number(options.max_error, 'max_error', true);
|
|
231
|
+
const selection = selectionOptions(options);
|
|
232
|
+
if (!options.multi && Object.keys(selection).length)
|
|
233
|
+
throw new TypeError('selection options require multi');
|
|
234
|
+
if (options.multi) {
|
|
235
|
+
fields.multi = true;
|
|
236
|
+
Object.assign(fields, selection);
|
|
237
|
+
}
|
|
238
|
+
super('tool', instructions, fields);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
exports.ToolQuestion = ToolQuestion;
|
|
139
242
|
class ActQuestion extends QuestionSpec {
|
|
140
243
|
constructor(instructions, costs, options = {}) {
|
|
141
|
-
const optionsMap = options.criteria === undefined ? undefined : criteria(options.criteria, 2,
|
|
244
|
+
const optionsMap = options.criteria === undefined ? undefined : criteria(options.criteria, 2, 255);
|
|
142
245
|
const outcomes = Object.keys(optionsMap ?? { 'true': 'Yes', 'false': 'No' });
|
|
143
246
|
if (!costs || typeof costs !== 'object' || Array.isArray(costs) || !Object.keys(costs).length)
|
|
144
247
|
throw new TypeError('act requires a cost matrix');
|
|
@@ -162,6 +265,7 @@ function noul(instructions, options) { return new NoulQuestion(instructions, opt
|
|
|
162
265
|
function score(instructions, levels, options) { return new ScoreQuestion(instructions, levels, options); }
|
|
163
266
|
function multi(instructions, values, options) { return new MultiQuestion(instructions, values, options); }
|
|
164
267
|
function act(instructions, costs, options) { return new ActQuestion(instructions, costs, options); }
|
|
268
|
+
function tool(instructions, tools, options) { return new ToolQuestion(instructions, tools, options); }
|
|
165
269
|
function buildRequest(state, questions, options) {
|
|
166
270
|
if (state === null || state === undefined)
|
|
167
271
|
throw new TypeError('state must not be null/undefined');
|
|
@@ -175,5 +279,11 @@ function buildRequest(state, questions, options) {
|
|
|
175
279
|
text(options.model, 'model');
|
|
176
280
|
if (options.facts !== undefined && !['off', 'auto'].includes(options.facts))
|
|
177
281
|
throw new TypeError('invalid facts');
|
|
178
|
-
|
|
282
|
+
if (options.lang !== undefined && !['pl', 'en'].includes(options.lang))
|
|
283
|
+
throw new TypeError('invalid lang');
|
|
284
|
+
if (options.detail !== undefined && !['default', 'high'].includes(options.detail))
|
|
285
|
+
throw new TypeError('invalid detail');
|
|
286
|
+
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())))
|
|
287
|
+
throw new TypeError('invalid media');
|
|
288
|
+
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
289
|
}
|
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,29 @@ 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;
|
|
36
|
+
/** Tool-description truncation cap; omitted keeps the server default (300), zero disables truncation. */
|
|
37
|
+
description_chars?: number;
|
|
26
38
|
}
|
|
27
39
|
export interface ActOptions<K extends string = string, A extends string = string> extends EvidenceOptions {
|
|
28
40
|
criteria?: Record<K, string>;
|
|
29
41
|
defer_action?: A;
|
|
30
42
|
max_error?: number;
|
|
31
43
|
}
|
|
32
|
-
export interface DecisionOptions {
|
|
33
|
-
model?: string;
|
|
34
|
-
facts?: 'off' | 'auto';
|
|
44
|
+
export interface DecisionOptions extends Pick<DecisionRequest, 'model' | 'facts' | 'lang' | 'media' | 'detail'> {
|
|
35
45
|
signal?: AbortSignal;
|
|
36
46
|
}
|
|
37
47
|
/** Immutable snapshot of a typed question. Use factories or the concrete constructors. */
|
|
@@ -52,7 +62,10 @@ export declare class ScoreQuestion extends QuestionSpec<TypedScoreAnswer> {
|
|
|
52
62
|
constructor(instructions: string, levels: readonly string[], options?: EvidenceOptions);
|
|
53
63
|
}
|
|
54
64
|
export declare class MultiQuestion<K extends string = string> extends QuestionSpec<TypedMultiAnswer<K>> {
|
|
55
|
-
constructor(instructions: string, values: Record<K, string>, options?: MultiOptions);
|
|
65
|
+
constructor(instructions: string, values: Record<K, string>, options?: MultiOptions<K>);
|
|
66
|
+
}
|
|
67
|
+
export declare class ToolQuestion extends QuestionSpec<ToolAnswer> {
|
|
68
|
+
constructor(instructions: string, tools: readonly ToolDefinition[], options?: ToolOptions);
|
|
56
69
|
}
|
|
57
70
|
export declare class ActQuestion<K extends string = 'true' | 'false', A extends string = string> extends QuestionSpec<TypedActAnswer<K, A>> {
|
|
58
71
|
constructor(instructions: string, costs: Record<A, Record<NoInfer<K>, number>>, options?: ActOptions<K, NoInfer<A>>);
|
|
@@ -60,8 +73,9 @@ export declare class ActQuestion<K extends string = 'true' | 'false', A extends
|
|
|
60
73
|
export declare function choice<const K extends string>(instructions: string, values: Record<K, string>, options?: ChoiceOptions): ChoiceQuestion<K>;
|
|
61
74
|
export declare function noul(instructions: string, options?: EvidenceOptions): NoulQuestion;
|
|
62
75
|
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>;
|
|
76
|
+
export declare function multi<const K extends string>(instructions: string, values: Record<K, string>, options?: MultiOptions<K>): MultiQuestion<K>;
|
|
64
77
|
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>;
|
|
78
|
+
export declare function tool(instructions: string, tools: readonly ToolDefinition[], options?: ToolOptions): ToolQuestion;
|
|
65
79
|
export type QuestionSet = Record<string, QuestionSpec>;
|
|
66
80
|
export type TypedDecisionResponse<Q extends QuestionSet> = Omit<DecisionResponse, 'answers'> & {
|
|
67
81
|
answers: {
|
|
@@ -70,9 +84,4 @@ export type TypedDecisionResponse<Q extends QuestionSet> = Omit<DecisionResponse
|
|
|
70
84
|
answer<K extends keyof Q & (string | number)>(key: K): Q[K] extends QuestionSpec<infer A> ? A : never;
|
|
71
85
|
raw: DecisionResponse;
|
|
72
86
|
};
|
|
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
|
-
};
|
|
87
|
+
export declare function buildRequest(state: JsonValue, questions: QuestionSet, options: DecisionOptions): DecisionRequest;
|