nexus-ai-pro 2.20.2 → 2.21.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/API_STABILITY.md +30 -0
- package/CHANGELOG.md +76 -1
- package/README.md +7 -6
- package/dist/adapter-kit/gather.d.ts +76 -0
- package/dist/adapter-kit/gather.js +147 -0
- package/dist/adapter-kit/index.d.ts +1 -0
- package/dist/adapter-kit/index.js +1 -0
- package/dist/adapter-kit/record.d.ts +3 -1
- package/dist/adapter-kit/record.js +20 -5
- package/dist/cli/adapters.d.ts +4 -3
- package/dist/cli/adapters.js +71 -2
- package/dist/cli/doctor.js +3 -0
- package/dist/cli.js +3 -1
- package/dist/core/config-builder.d.ts +1 -0
- package/dist/core/config-builder.js +1 -0
- package/dist/core/defaults.d.ts +34 -0
- package/dist/core/defaults.js +39 -0
- package/dist/decisions/completion.d.ts +10 -7
- package/dist/decisions/completion.js +80 -6
- package/dist/decisions/core.d.ts +88 -43
- package/dist/decisions/core.js +115 -28
- package/dist/decisions/errors.d.ts +25 -0
- package/dist/decisions/errors.js +12 -0
- package/dist/decisions/index.d.ts +6 -5
- package/dist/decisions/index.js +2 -1
- package/dist/decisions/integrations.d.ts +14 -5
- package/dist/decisions/integrations.js +16 -1
- package/dist/decisions/mock.d.ts +5 -3
- package/dist/decisions/mock.js +4 -1
- package/dist/decisions/pictures.d.ts +43 -0
- package/dist/decisions/pictures.js +103 -0
- package/dist/decisions/typesafe.d.ts +25 -5
- package/dist/decisions/typesafe.js +101 -7
- package/dist/doctor/index.d.ts +6 -9
- package/dist/doctor/index.js +41 -0
- package/dist/operations/adapters.d.ts +1 -0
- package/dist/operations/adapters.js +3 -1
- package/dist/protocols/a2a.js +22 -3
- package/dist/providers/openai.js +13 -0
- package/dist/server/deployments.d.ts +3 -2
- package/dist/server/deployments.js +6 -1
- package/dist/tracing/stores.d.ts +2 -0
- package/dist/tracing/stores.js +1 -0
- package/dist/tracing/tracer.d.ts +5 -2
- package/dist/tracing/tracer.js +6 -3
- package/dist/types/tracing.d.ts +6 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist-cjs/adapter-kit/gather.d.ts +76 -0
- package/dist-cjs/adapter-kit/gather.js +152 -0
- package/dist-cjs/adapter-kit/index.d.ts +1 -0
- package/dist-cjs/adapter-kit/index.js +4 -1
- package/dist-cjs/adapter-kit/record.d.ts +3 -1
- package/dist-cjs/adapter-kit/record.js +20 -5
- package/dist-cjs/cli/adapters.d.ts +4 -3
- package/dist-cjs/cli/adapters.js +70 -1
- package/dist-cjs/cli/doctor.js +3 -0
- package/dist-cjs/core/config-builder.d.ts +1 -0
- package/dist-cjs/core/config-builder.js +5 -1
- package/dist-cjs/core/defaults.d.ts +34 -0
- package/dist-cjs/core/defaults.js +44 -0
- package/dist-cjs/decisions/completion.d.ts +10 -7
- package/dist-cjs/decisions/completion.js +79 -5
- package/dist-cjs/decisions/core.d.ts +88 -43
- package/dist-cjs/decisions/core.js +134 -46
- package/dist-cjs/decisions/errors.d.ts +25 -0
- package/dist-cjs/decisions/errors.js +16 -0
- package/dist-cjs/decisions/index.d.ts +6 -5
- package/dist-cjs/decisions/index.js +5 -1
- package/dist-cjs/decisions/integrations.d.ts +14 -5
- package/dist-cjs/decisions/integrations.js +16 -1
- package/dist-cjs/decisions/mock.d.ts +5 -3
- package/dist-cjs/decisions/mock.js +4 -1
- package/dist-cjs/decisions/pictures.d.ts +43 -0
- package/dist-cjs/decisions/pictures.js +109 -0
- package/dist-cjs/decisions/typesafe.d.ts +25 -5
- package/dist-cjs/decisions/typesafe.js +106 -12
- package/dist-cjs/doctor/index.d.ts +6 -9
- package/dist-cjs/doctor/index.js +41 -0
- package/dist-cjs/operations/adapters.d.ts +1 -0
- package/dist-cjs/operations/adapters.js +3 -1
- package/dist-cjs/protocols/a2a.js +22 -3
- package/dist-cjs/providers/openai.js +13 -0
- package/dist-cjs/server/deployments.d.ts +3 -2
- package/dist-cjs/server/deployments.js +6 -1
- package/dist-cjs/tracing/stores.d.ts +2 -0
- package/dist-cjs/tracing/stores.js +1 -0
- package/dist-cjs/tracing/tracer.d.ts +5 -2
- package/dist-cjs/tracing/tracer.js +6 -3
- package/dist-cjs/types/tracing.d.ts +6 -0
- package/dist-cjs/version.d.ts +1 -1
- package/dist-cjs/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which release's defaults apply where an option is left out: 2.x's, or a preview of 3.0's. 3.0 turns on
|
|
3
|
+
* three settings that 2.x keeps opt-in, because turning them on changes what an upgrade does; the
|
|
4
|
+
* preview turns on all three at once, so an application can run as 3.0 will before it upgrades. An
|
|
5
|
+
* option set explicitly always wins, under either.
|
|
6
|
+
*/
|
|
7
|
+
/** A release whose defaults apply: `2.x`, the default, or `3.0`, a preview of the next major release's. */
|
|
8
|
+
export type NexusDefaults = '2.x' | '3.0';
|
|
9
|
+
/** One default that 3.0 changes: what it is, what 2.x does, what 3.0 does, and what to do first. */
|
|
10
|
+
export interface DefaultChange {
|
|
11
|
+
/** The setting, as its option is named. */
|
|
12
|
+
setting: string;
|
|
13
|
+
/** Where it is set. */
|
|
14
|
+
module: string;
|
|
15
|
+
/** What 2.x does when the option is left out. */
|
|
16
|
+
today: string;
|
|
17
|
+
/** What 3.0, and the preview, does when it is left out. */
|
|
18
|
+
next: string;
|
|
19
|
+
/** What to do before turning it on, if anything. */
|
|
20
|
+
before: string;
|
|
21
|
+
}
|
|
22
|
+
/** The defaults 3.0 changes, each turned on by the preview. */
|
|
23
|
+
export declare const DEFAULT_CHANGES: readonly DefaultChange[];
|
|
24
|
+
/**
|
|
25
|
+
* Chooses which release's defaults apply to everything created after it: `3.0` previews the next
|
|
26
|
+
* major release's, `2.x` returns to today's. Call it before creating a tracer, an operation store, or
|
|
27
|
+
* a deployment: each reads the defaults when it is created. Overrides `NEXUS_DEFAULTS`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function useDefaults(release: NexusDefaults): void;
|
|
30
|
+
/**
|
|
31
|
+
* The defaults in effect: as `useDefaults()` chose, else the `NEXUS_DEFAULTS` environment variable,
|
|
32
|
+
* `2.x` or `3.0`, else `2.x`. Throws for any other value of the variable, rather than ignore a typo.
|
|
33
|
+
*/
|
|
34
|
+
export declare function nexusDefaults(): NexusDefaults;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export const DEFAULT_CHANGES = [
|
|
2
|
+
{
|
|
3
|
+
setting: 'Tracer incremental',
|
|
4
|
+
module: 'nexus-ai-pro/tracing',
|
|
5
|
+
today: 'false: a trace is written when its root run finishes',
|
|
6
|
+
next: 'true on a durable trace store: each run is written as it starts and again as it ends, so a run in a process that dies is not lost',
|
|
7
|
+
before: 'Nothing; close what a process that died left open with closeAbandonedRuns().',
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
setting: 'RedisOperationStore index',
|
|
11
|
+
module: 'nexus-ai-pro/operations',
|
|
12
|
+
today: 'false: finding queued work reads every record',
|
|
13
|
+
next: 'true where the client has zadd, zrem, and zrangebyscore: queued and leased work is kept in sorted sets',
|
|
14
|
+
before: 'Run 2.2 or later on every worker sharing the prefix, and call reindex() once.',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
setting: 'Deployments bucketStrategy',
|
|
18
|
+
module: 'nexus-ai-pro/server',
|
|
19
|
+
today: 'fnv1a-v1 for a deployment first recorded',
|
|
20
|
+
next: 'hash-v2 for a deployment first recorded, which spreads sequential thread ids evenly; a recorded deployment keeps its own',
|
|
21
|
+
before: 'Nothing; a deployment already recorded keeps the strategy it records, and bucketing() changes one.',
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
let chosen;
|
|
25
|
+
export function useDefaults(release) {
|
|
26
|
+
chosen = checked(release, 'useDefaults()');
|
|
27
|
+
}
|
|
28
|
+
export function nexusDefaults() {
|
|
29
|
+
if (chosen)
|
|
30
|
+
return chosen;
|
|
31
|
+
const variable = globalThis.process?.env
|
|
32
|
+
?.NEXUS_DEFAULTS;
|
|
33
|
+
return variable === undefined || variable === '' ? '2.x' : checked(variable, 'NEXUS_DEFAULTS');
|
|
34
|
+
}
|
|
35
|
+
function checked(release, where) {
|
|
36
|
+
if (release === '2.x' || release === '3.0')
|
|
37
|
+
return release;
|
|
38
|
+
throw new Error(`${where} takes 2.x or 3.0, not ${JSON.stringify(release)}`);
|
|
39
|
+
}
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Decisions from any completion model: each question asked on its own, answered with a one-token code,
|
|
3
|
-
* and its probabilities read from the code token's log probabilities, or counted over samples where
|
|
4
|
-
* the model reports none.
|
|
5
|
-
*/
|
|
6
1
|
import type { CompletionRequest } from '../types/messages.js';
|
|
7
2
|
import type { NexusResponse } from '../types/response.js';
|
|
8
3
|
import { type DecisionProvider } from './core.js';
|
|
@@ -25,8 +20,14 @@ export interface CompletionDecisionsOptions {
|
|
|
25
20
|
* a model that reports none or refuses to, which it then asks by sampling from the start.
|
|
26
21
|
*/
|
|
27
22
|
method?: CompletionDecisionMethod;
|
|
28
|
-
/** Calls per question, when sampling. Defaults to 8. */
|
|
23
|
+
/** Calls per question, when sampling, and per box. Defaults to 8. */
|
|
29
24
|
samples?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Whether the model reads pictures, so a decision about the pictures a state holds, or a box, may
|
|
27
|
+
* be asked of it. Defaults to `true`; set `false` for a text-only model, which such a decision then
|
|
28
|
+
* passes over to a fallback.
|
|
29
|
+
*/
|
|
30
|
+
images?: boolean;
|
|
30
31
|
/** The temperature samples are drawn at. Defaults to 1. */
|
|
31
32
|
temperature?: number;
|
|
32
33
|
/** Questions asked at once. Defaults to 8. */
|
|
@@ -41,7 +42,9 @@ export interface CompletionDecisionsOptions {
|
|
|
41
42
|
* Its probabilities are read from the log probabilities of that token's alternatives where the model
|
|
42
43
|
* reports them, the way TypeSafe AI's Jev reads its own; elsewhere the question is asked `samples`
|
|
43
44
|
* times, and each answer counted. Sampled probabilities come in steps of one sample, and cost as many
|
|
44
|
-
* calls.
|
|
45
|
+
* calls. The pictures a state holds are shown to the model after the state, each labelled by its
|
|
46
|
+
* path; a box is asked `samples` times, and is the median of each edge, its confidence how far the
|
|
47
|
+
* samples overlap it.
|
|
45
48
|
*/
|
|
46
49
|
export declare function completionDecisions(client: DecisionCompletionClient, options: CompletionDecisionsOptions): DecisionProvider & {
|
|
47
50
|
readonly sampledModels: ReadonlySet<string>;
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import { DecisionError, } from './core.js';
|
|
1
|
+
import { boxOverlap, DecisionError, } from './core.js';
|
|
2
|
+
import { replacePictures, statePictures } from './pictures.js';
|
|
2
3
|
const LOGPROB_OPTIONS = 20;
|
|
3
4
|
export function completionDecisions(client, options) {
|
|
4
5
|
const method = options.method ?? 'auto';
|
|
6
|
+
const images = options.images !== false;
|
|
5
7
|
const sampled = new Set();
|
|
6
8
|
const info = {
|
|
7
9
|
name: options.name ?? options.model,
|
|
8
10
|
capabilities: {
|
|
9
|
-
types: ['yes-no', 'choice', 'score'],
|
|
11
|
+
types: images ? ['yes-no', 'choice', 'score', 'box'] : ['yes-no', 'choice', 'score'],
|
|
10
12
|
maxOptions: 255,
|
|
11
13
|
maxLevels: 10,
|
|
14
|
+
images,
|
|
12
15
|
defaultModel: options.model,
|
|
13
16
|
},
|
|
14
17
|
chargedByItsCalls: true,
|
|
@@ -21,7 +24,9 @@ export function completionDecisions(client, options) {
|
|
|
21
24
|
messages: [
|
|
22
25
|
{
|
|
23
26
|
role: 'system',
|
|
24
|
-
content:
|
|
27
|
+
content: question.type === 'box'
|
|
28
|
+
? 'You answer where something is in a picture with exactly four numbers, and nothing else.'
|
|
29
|
+
: 'You answer one question about a state with exactly one of the codes given, and nothing else.',
|
|
25
30
|
},
|
|
26
31
|
{ role: 'user', content: prompt },
|
|
27
32
|
],
|
|
@@ -29,6 +34,8 @@ export function completionDecisions(client, options) {
|
|
|
29
34
|
...(context.tenantId !== undefined ? { tenantId: context.tenantId } : {}),
|
|
30
35
|
...extra,
|
|
31
36
|
});
|
|
37
|
+
if (question.type === 'box')
|
|
38
|
+
return askBox(request, model, usage);
|
|
32
39
|
const readable = codes.length <= LOGPROB_OPTIONS && codes.every((code) => code.length === 1);
|
|
33
40
|
if (method !== 'sampling' && readable && !sampled.has(model)) {
|
|
34
41
|
let response;
|
|
@@ -64,6 +71,28 @@ export function completionDecisions(client, options) {
|
|
|
64
71
|
}
|
|
65
72
|
return distributionOf(question, codes, weights);
|
|
66
73
|
};
|
|
74
|
+
const askBox = async (request, model, usage) => {
|
|
75
|
+
if (method === 'logprobs') {
|
|
76
|
+
throw new DecisionError('A box is read from several samples of the answer, not from log probabilities', 'DECISION_UNSUPPORTED');
|
|
77
|
+
}
|
|
78
|
+
const count = Math.max(1, Math.floor(options.samples ?? 8));
|
|
79
|
+
const readings = await Promise.all(Array.from({ length: count }, () => client.complete(request({ temperature: options.temperature ?? 1, maxTokens: 32 })).then((response) => {
|
|
80
|
+
usage.add(response);
|
|
81
|
+
return boxIn(response.content);
|
|
82
|
+
})));
|
|
83
|
+
const boxes = readings.filter((box) => box !== undefined);
|
|
84
|
+
if (boxes.length === 0) {
|
|
85
|
+
throw new DecisionError(`"${model}" answered none of ${count} samples with a box`, 'DECISION_FAILED');
|
|
86
|
+
}
|
|
87
|
+
const box = {
|
|
88
|
+
top: median(boxes.map((item) => item.top)),
|
|
89
|
+
bottom: median(boxes.map((item) => item.bottom)),
|
|
90
|
+
left: median(boxes.map((item) => item.left)),
|
|
91
|
+
right: median(boxes.map((item) => item.right)),
|
|
92
|
+
};
|
|
93
|
+
const agreement = boxes.reduce((sum, item) => sum + boxOverlap(item, box), 0) / count;
|
|
94
|
+
return { type: 'box', box, confidence: agreement };
|
|
95
|
+
};
|
|
67
96
|
return {
|
|
68
97
|
info,
|
|
69
98
|
sampledModels: sampled,
|
|
@@ -112,6 +141,8 @@ class Totals {
|
|
|
112
141
|
}
|
|
113
142
|
}
|
|
114
143
|
function codesOf(question) {
|
|
144
|
+
if (question.type === 'box')
|
|
145
|
+
return [];
|
|
115
146
|
if (question.type === 'yes-no')
|
|
116
147
|
return ['Y', 'N'];
|
|
117
148
|
if (question.type === 'score')
|
|
@@ -121,10 +152,37 @@ function codesOf(question) {
|
|
|
121
152
|
: question.options.map((_, index) => String(index + 1));
|
|
122
153
|
}
|
|
123
154
|
function promptOf(state, question, codes, instructions) {
|
|
124
|
-
const
|
|
125
|
-
const
|
|
155
|
+
const pictures = typeof state === 'string' ? [] : statePictures(state);
|
|
156
|
+
const shown = pictures.length ? replacePictures(state, (picture) => `[picture \`${picture.path}\`]`) : state;
|
|
157
|
+
const text = typeof shown === 'string' ? shown : JSON.stringify(shown, null, 2);
|
|
158
|
+
const asked = questionLines(question, codes, instructions).join('\n');
|
|
159
|
+
if (pictures.length === 0)
|
|
160
|
+
return `State:\n${text}\n\n${asked}`;
|
|
161
|
+
return [
|
|
162
|
+
{ type: 'text', text: `State:\n${text}\n\nThe pictures it holds, by path:` },
|
|
163
|
+
...pictures.flatMap((picture) => [
|
|
164
|
+
{ type: 'text', text: `\`${picture.path}\`:` },
|
|
165
|
+
imagePart(picture.part),
|
|
166
|
+
]),
|
|
167
|
+
{ type: 'text', text: asked },
|
|
168
|
+
];
|
|
169
|
+
}
|
|
170
|
+
function imagePart(part) {
|
|
171
|
+
if (part.type === 'image')
|
|
172
|
+
return part;
|
|
173
|
+
if (part.type === 'asset')
|
|
174
|
+
return { type: 'image', source: { asset: part.asset } };
|
|
175
|
+
return part;
|
|
176
|
+
}
|
|
177
|
+
function questionLines(question, codes, instructions) {
|
|
178
|
+
const lines = [];
|
|
126
179
|
if (instructions)
|
|
127
180
|
lines.push(instructions, '');
|
|
181
|
+
if (question.type === 'box') {
|
|
182
|
+
lines.push(`Find: ${question.question}`);
|
|
183
|
+
lines.push('', "Answer with four numbers from 0 to 1, separated by spaces: the top, bottom, left, and right edges of a box around it, each a fraction of the picture's height or width from its top-left corner.");
|
|
184
|
+
return lines;
|
|
185
|
+
}
|
|
128
186
|
if (question.type === 'yes-no') {
|
|
129
187
|
lines.push(`Question: ${question.question ?? 'Is the statement true of the state?'}`);
|
|
130
188
|
lines.push(`Y: yes${question.criteria?.yes ? `, ${question.criteria.yes}` : ''}`);
|
|
@@ -143,7 +201,21 @@ function promptOf(state, question, codes, instructions) {
|
|
|
143
201
|
});
|
|
144
202
|
}
|
|
145
203
|
lines.push('', `Answer with one code: ${codes.join(', ')}.`);
|
|
146
|
-
return lines
|
|
204
|
+
return lines;
|
|
205
|
+
}
|
|
206
|
+
function boxIn(content) {
|
|
207
|
+
const numbers = (content.match(/-?\d*\.?\d+/g) ?? []).slice(0, 4).map(Number);
|
|
208
|
+
if (numbers.length < 4 || numbers.some((value) => !(value >= 0 && value <= 1)))
|
|
209
|
+
return undefined;
|
|
210
|
+
const [top, bottom, left, right] = numbers;
|
|
211
|
+
return top <= bottom && left <= right ? { top, bottom, left, right } : undefined;
|
|
212
|
+
}
|
|
213
|
+
function median(values) {
|
|
214
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
215
|
+
const middle = Math.floor(sorted.length / 2);
|
|
216
|
+
return sorted.length % 2
|
|
217
|
+
? sorted[middle]
|
|
218
|
+
: (sorted[middle - 1] + sorted[middle]) / 2;
|
|
147
219
|
}
|
|
148
220
|
function codeOf(token) {
|
|
149
221
|
return token.replace(/^[\s"'`*(]+|[\s"'`*).:,]+$/g, '').toUpperCase();
|
|
@@ -184,6 +256,8 @@ function distributionOf(question, codes, weights) {
|
|
|
184
256
|
return { type: 'yes-no', yes: share(0) };
|
|
185
257
|
if (question.type === 'score')
|
|
186
258
|
return { type: 'score', probabilities: codes.map((_, index) => share(index)) };
|
|
259
|
+
if (question.type === 'box')
|
|
260
|
+
throw new DecisionError('A box has no codes to weigh', 'DECISION_INVALID');
|
|
187
261
|
return {
|
|
188
262
|
type: 'choice',
|
|
189
263
|
probabilities: Object.fromEntries(question.options.map((option, index) => [option.name, share(index)])),
|
package/dist/decisions/core.d.ts
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Typed decisions: questions a model answers with a yes or no, a choice,
|
|
3
|
-
*
|
|
2
|
+
* Typed decisions: questions a model answers with a yes or no, a choice, a score, or a box around
|
|
3
|
+
* something in a picture, each with a confidence read alike from any provider, so software acts on an
|
|
4
|
+
* answer without parsing text.
|
|
4
5
|
*/
|
|
5
6
|
import type { CacheAdapter } from '../cache/adapters.js';
|
|
7
|
+
import type { ImageContent } from '../types/messages.js';
|
|
6
8
|
import type { OperationLifecycleLike, ProviderCallContext } from '../types/lifecycle.js';
|
|
7
9
|
import type { ResponseCost } from '../types/response.js';
|
|
8
10
|
import type { RunKind } from '../types/tracing.js';
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
+
/**
|
|
12
|
+
* The kinds of question there are: a yes or no, a choice among options, a score on levels, or a box
|
|
13
|
+
* around something in a picture.
|
|
14
|
+
*/
|
|
15
|
+
export type DecisionQuestionType = 'yes-no' | 'choice' | 'score' | 'box';
|
|
11
16
|
/** A question answered yes or no, with the probability that it is yes. */
|
|
12
17
|
export interface YesNoQuestion {
|
|
13
18
|
/** Discriminates the question. */
|
|
@@ -38,12 +43,40 @@ export interface ScoreQuestion {
|
|
|
38
43
|
/** What each level means, lowest first. Two to ten. */
|
|
39
44
|
levels: readonly string[];
|
|
40
45
|
}
|
|
46
|
+
/** A question answered with where something is in a picture of the state: a box around it. */
|
|
47
|
+
export interface BoxQuestion {
|
|
48
|
+
/** Discriminates the question. */
|
|
49
|
+
type: 'box';
|
|
50
|
+
/**
|
|
51
|
+
* What to find, and in which picture, by its path in backticks, such as "the dent in `photo`". A box
|
|
52
|
+
* is given even for something the picture does not show: ask a yes or no beside it whether it does.
|
|
53
|
+
*/
|
|
54
|
+
question: string;
|
|
55
|
+
}
|
|
41
56
|
/** Any question. */
|
|
42
|
-
export type DecisionQuestion = YesNoQuestion | ChoiceQuestion | ScoreQuestion;
|
|
57
|
+
export type DecisionQuestion = YesNoQuestion | ChoiceQuestion | ScoreQuestion | BoxQuestion;
|
|
43
58
|
/** Questions by the names their answers come back under. */
|
|
44
59
|
export type DecisionQuestions = Record<string, DecisionQuestion>;
|
|
45
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* What every question of a decision is about: text, or any JSON value. An object or a list may hold
|
|
62
|
+
* pictures, as image parts (`{ type: 'image', source }`) or image assets, anywhere in it; a question
|
|
63
|
+
* names each by its path in backticks, such as `photo` or `frames[2].frame`.
|
|
64
|
+
*/
|
|
46
65
|
export type DecisionState = string | Record<string, unknown> | readonly unknown[];
|
|
66
|
+
/**
|
|
67
|
+
* Where something is in a picture: each edge a fraction, from 0 to 1, of the picture's height (`top`,
|
|
68
|
+
* `bottom`) or width (`left`, `right`), measured from its top-left corner.
|
|
69
|
+
*/
|
|
70
|
+
export interface DecisionBox {
|
|
71
|
+
/** The top edge, from the picture's top. */
|
|
72
|
+
top: number;
|
|
73
|
+
/** The bottom edge, from the picture's top: at least `top`. */
|
|
74
|
+
bottom: number;
|
|
75
|
+
/** The left edge, from the picture's left. */
|
|
76
|
+
left: number;
|
|
77
|
+
/** The right edge, from the picture's left: at least `left`. */
|
|
78
|
+
right: number;
|
|
79
|
+
}
|
|
47
80
|
/** A yes-or-no question's answer. */
|
|
48
81
|
export interface YesNoAnswer {
|
|
49
82
|
/** Discriminates the answer. */
|
|
@@ -86,15 +119,27 @@ export interface ScoreAnswer {
|
|
|
86
119
|
*/
|
|
87
120
|
confidence: number;
|
|
88
121
|
}
|
|
122
|
+
/** A box question's answer. */
|
|
123
|
+
export interface BoxAnswer {
|
|
124
|
+
/** Discriminates the answer. */
|
|
125
|
+
type: 'box';
|
|
126
|
+
/** Where it is. */
|
|
127
|
+
box: DecisionBox;
|
|
128
|
+
/**
|
|
129
|
+
* How sure the answer is, from 0 to 1: the provider's own, such as TypeSafe AI's Jev gives, or how
|
|
130
|
+
* far several readings of the box agree, for a model asked more than once.
|
|
131
|
+
*/
|
|
132
|
+
confidence: number;
|
|
133
|
+
}
|
|
89
134
|
/** Any answer. */
|
|
90
|
-
export type DecisionAnswer = YesNoAnswer | ChoiceAnswer | ScoreAnswer;
|
|
135
|
+
export type DecisionAnswer = YesNoAnswer | ChoiceAnswer | ScoreAnswer | BoxAnswer;
|
|
91
136
|
/** The options of a choice question, as a union of their names. */
|
|
92
137
|
type OptionsOf<O> = O extends readonly (infer K)[] ? K : keyof O;
|
|
93
138
|
/** The answer a question gets: a choice's answer names only its options. */
|
|
94
139
|
export type AnswerOf<Q> = Q extends YesNoQuestion ? YesNoAnswer : Q extends {
|
|
95
140
|
type: 'choice';
|
|
96
141
|
options: infer O;
|
|
97
|
-
} ? ChoiceAnswer<Extract<OptionsOf<O>, string>> : Q extends ScoreQuestion ? ScoreAnswer : DecisionAnswer;
|
|
142
|
+
} ? ChoiceAnswer<Extract<OptionsOf<O>, string>> : Q extends ScoreQuestion ? ScoreAnswer : Q extends BoxQuestion ? BoxAnswer : DecisionAnswer;
|
|
98
143
|
/** Answers by their questions' names, each typed by its question. */
|
|
99
144
|
export type DecisionAnswers<Q extends DecisionQuestions> = {
|
|
100
145
|
[K in keyof Q]: AnswerOf<Q[K]>;
|
|
@@ -152,12 +197,12 @@ export type DecisionProviderQuestion = YesNoQuestion | {
|
|
|
152
197
|
name: string;
|
|
153
198
|
description?: string;
|
|
154
199
|
}>;
|
|
155
|
-
} | ScoreQuestion;
|
|
200
|
+
} | ScoreQuestion | BoxQuestion;
|
|
156
201
|
/** What a provider is asked. */
|
|
157
202
|
export interface DecisionProviderRequest {
|
|
158
203
|
/** The model to ask. */
|
|
159
204
|
model: string;
|
|
160
|
-
/** What every question is about. */
|
|
205
|
+
/** What every question is about, with any pictures it holds, which `statePictures()` lists. */
|
|
161
206
|
state: DecisionState;
|
|
162
207
|
/** The questions, by name. */
|
|
163
208
|
questions: Record<string, DecisionProviderQuestion>;
|
|
@@ -172,6 +217,10 @@ export type DecisionDistribution = {
|
|
|
172
217
|
} | {
|
|
173
218
|
type: 'score';
|
|
174
219
|
probabilities: readonly number[];
|
|
220
|
+
} | {
|
|
221
|
+
type: 'box';
|
|
222
|
+
box: DecisionBox;
|
|
223
|
+
confidence: number;
|
|
175
224
|
};
|
|
176
225
|
/** What a provider answers. */
|
|
177
226
|
export interface DecisionProviderResult {
|
|
@@ -194,6 +243,8 @@ export interface DecisionProviderCapabilities {
|
|
|
194
243
|
maxLevels: number;
|
|
195
244
|
/** Most questions one decision may ask, when it has a limit. */
|
|
196
245
|
maxQuestions?: number;
|
|
246
|
+
/** Whether it reads the pictures a state holds. A provider without it is not asked about a picture. */
|
|
247
|
+
images?: boolean;
|
|
197
248
|
/** The model asked when a request names none. */
|
|
198
249
|
defaultModel: string;
|
|
199
250
|
/** The models it serves, when it lists them. */
|
|
@@ -232,30 +283,7 @@ export interface DecisionProvider {
|
|
|
232
283
|
/** Answers every question of a decision. */
|
|
233
284
|
decide(request: DecisionProviderRequest, context: DecisionCallContext): Promise<DecisionProviderResult>;
|
|
234
285
|
}
|
|
235
|
-
|
|
236
|
-
export type DecisionErrorCode = 'DECISION_NO_PROVIDER' | 'DECISION_UNSUPPORTED' | 'DECISION_INVALID' | 'DECISION_FAILED';
|
|
237
|
-
/** Raised for a decision no provider can serve, a question that is malformed, or a provider's failure. */
|
|
238
|
-
export declare class DecisionError extends Error {
|
|
239
|
-
/**
|
|
240
|
-
* A stable code: `DECISION_NO_PROVIDER`, `DECISION_UNSUPPORTED` (a provider cannot take the
|
|
241
|
-
* question), `DECISION_INVALID` (the request, or an answer, is malformed), or `DECISION_FAILED`.
|
|
242
|
-
*/
|
|
243
|
-
readonly code: DecisionErrorCode;
|
|
244
|
-
/** Whether asking again, or another provider, may help: a timeout or an overloaded service may. */
|
|
245
|
-
readonly retryable: boolean;
|
|
246
|
-
/** The HTTP status a provider answered with, when it did. */
|
|
247
|
-
readonly status?: number | undefined;
|
|
248
|
-
constructor(message: string,
|
|
249
|
-
/**
|
|
250
|
-
* A stable code: `DECISION_NO_PROVIDER`, `DECISION_UNSUPPORTED` (a provider cannot take the
|
|
251
|
-
* question), `DECISION_INVALID` (the request, or an answer, is malformed), or `DECISION_FAILED`.
|
|
252
|
-
*/
|
|
253
|
-
code: DecisionErrorCode,
|
|
254
|
-
/** Whether asking again, or another provider, may help: a timeout or an overloaded service may. */
|
|
255
|
-
retryable?: boolean,
|
|
256
|
-
/** The HTTP status a provider answered with, when it did. */
|
|
257
|
-
status?: number | undefined);
|
|
258
|
-
}
|
|
286
|
+
export { DecisionError, type DecisionErrorCode } from './errors.js';
|
|
259
287
|
/** A yes-or-no answer's confidence: how far its probability is from an even chance, `|2p - 1|`. */
|
|
260
288
|
export declare function yesNoConfidence(probability: number): number;
|
|
261
289
|
/**
|
|
@@ -275,6 +303,11 @@ export declare function scoreConfidence(probabilities: readonly number[]): numbe
|
|
|
275
303
|
* `DECISION_INVALID` for probabilities that do not fit the question.
|
|
276
304
|
*/
|
|
277
305
|
export declare function decisionAnswer(question: DecisionProviderQuestion, distribution: DecisionDistribution): DecisionAnswer;
|
|
306
|
+
/**
|
|
307
|
+
* How far two boxes overlap, from 0 to 1: the area they share over the area they cover together, the
|
|
308
|
+
* intersection over union by which a box is judged against where the thing is.
|
|
309
|
+
*/
|
|
310
|
+
export declare function boxOverlap(a: DecisionBox, b: DecisionBox): number;
|
|
278
311
|
/** What to do with an answer: act on it, have a person review it, or neither. */
|
|
279
312
|
export type GateOutcome = 'act' | 'review' | 'abstain';
|
|
280
313
|
/** The confidences a gate acts at, and has reviewed at. */
|
|
@@ -345,6 +378,7 @@ export interface CalibrationReport {
|
|
|
345
378
|
/**
|
|
346
379
|
* The probability an answer gives its own answer: a yes or no's `max(p, 1 - p)`, a choice's for its
|
|
347
380
|
* option, and a score's for its level. Its confidence measures how far that is above an even spread.
|
|
381
|
+
* A box has no probabilities, so its confidence stands for one.
|
|
348
382
|
*/
|
|
349
383
|
export declare function answerProbability(answer: DecisionAnswer): number;
|
|
350
384
|
/**
|
|
@@ -355,11 +389,13 @@ export declare function answerProbability(answer: DecisionAnswer): number;
|
|
|
355
389
|
*/
|
|
356
390
|
export declare function calibrate(samples: readonly CalibrationSample[], options?: CalibrateOptions): CalibrationReport;
|
|
357
391
|
/**
|
|
358
|
-
* Whether an answer is right: a yes-or-no or a choice by its answer,
|
|
359
|
-
* level, or by its weighted score within `tolerance` levels when one is given
|
|
392
|
+
* Whether an answer is right: a yes-or-no or a choice by its answer, a score by its most probable
|
|
393
|
+
* level, or by its weighted score within `tolerance` levels when one is given, and a box by how far it
|
|
394
|
+
* overlaps the expected one, `boxOverlap()`, at least `overlap` (one half by default).
|
|
360
395
|
*/
|
|
361
|
-
export declare function answeredRight(answer: DecisionAnswer, expected: boolean | string | number, options?: {
|
|
396
|
+
export declare function answeredRight(answer: DecisionAnswer, expected: boolean | string | number | DecisionBox, options?: {
|
|
362
397
|
tolerance?: number;
|
|
398
|
+
overlap?: number;
|
|
363
399
|
}): boolean;
|
|
364
400
|
/** A decision model's price, in US dollars per million tokens. */
|
|
365
401
|
export interface DecisionPrice {
|
|
@@ -493,8 +529,11 @@ export interface DecisionConformanceCase {
|
|
|
493
529
|
state: DecisionState;
|
|
494
530
|
/** The question. */
|
|
495
531
|
question: DecisionQuestion;
|
|
496
|
-
/**
|
|
497
|
-
|
|
532
|
+
/**
|
|
533
|
+
* The right answer: a yes or no, a choice's option, a score's level, or where a box should be, which
|
|
534
|
+
* an answer overlapping it by half or more matches.
|
|
535
|
+
*/
|
|
536
|
+
expected: boolean | string | number | DecisionBox;
|
|
498
537
|
}
|
|
499
538
|
/** Options for `runDecisionConformance()`. */
|
|
500
539
|
export interface DecisionConformanceOptions {
|
|
@@ -505,6 +544,11 @@ export interface DecisionConformanceOptions {
|
|
|
505
544
|
* as it is told passes without them; a real model is held to them.
|
|
506
545
|
*/
|
|
507
546
|
cases?: readonly DecisionConformanceCase[];
|
|
547
|
+
/**
|
|
548
|
+
* A picture for the checks of a provider that reads pictures or answers boxes: it is asked a yes or
|
|
549
|
+
* no about the picture, and a box around what it shows. Without one, those checks are skipped.
|
|
550
|
+
*/
|
|
551
|
+
picture?: ImageContent;
|
|
508
552
|
}
|
|
509
553
|
/** How a provider did against the decision contract. */
|
|
510
554
|
export interface DecisionConformanceReport {
|
|
@@ -514,12 +558,13 @@ export interface DecisionConformanceReport {
|
|
|
514
558
|
passed: string[];
|
|
515
559
|
/** Checks that failed, each with why. */
|
|
516
560
|
failures: string[];
|
|
561
|
+
/** Checks that were not run, each with why, such as a box with no picture given to ask about. */
|
|
562
|
+
skipped: string[];
|
|
517
563
|
}
|
|
518
564
|
/**
|
|
519
565
|
* Checks a provider against the decision contract: its capabilities are coherent; each question type
|
|
520
|
-
* it declares comes back as probabilities that fit the question;
|
|
521
|
-
*
|
|
522
|
-
* throws; failures are listed.
|
|
566
|
+
* it declares comes back as probabilities, or a box, that fit the question; a provider that reads
|
|
567
|
+
* pictures answers about one; several questions in one decision each get their own answer; a stopped
|
|
568
|
+
* call is not answered; and every case is answered right. Never throws; failures are listed.
|
|
523
569
|
*/
|
|
524
570
|
export declare function runDecisionConformance(provider: DecisionProvider, options?: DecisionConformanceOptions): Promise<DecisionConformanceReport>;
|
|
525
|
-
export {};
|