@corbits/system-one 0.3.1 → 0.4.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 +72 -22
- package/dist/adapter.d.ts +1 -1
- package/dist/adapter.js +41 -7
- package/dist/assets.d.ts +36 -0
- package/dist/assets.js +232 -0
- package/dist/client.d.ts +5 -0
- package/dist/client.js +67 -2
- package/dist/config.d.ts +4 -0
- package/dist/config.js +4 -0
- package/dist/decisions.d.ts +99 -0
- package/dist/decisions.js +166 -0
- package/dist/evaluate.d.ts +15 -0
- package/dist/evaluate.js +30 -6
- package/dist/extraction.d.ts +104 -0
- package/dist/extraction.js +332 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -1
- package/dist/schemas.d.ts +117 -7
- package/dist/schemas.js +96 -6
- package/package.json +17 -6
package/dist/config.js
CHANGED
|
@@ -61,6 +61,8 @@ export const DEFAULT_TIMEOUT_MS = 1500;
|
|
|
61
61
|
* distinguishes caller-routed traffic from the official endpoint —
|
|
62
62
|
* `'gateway'` is reserved for the proxy quirks path. The model always
|
|
63
63
|
* resolves here: a `custom` target without one sends `typesafe-ai/jev`.
|
|
64
|
+
* `auth` is `"none"` only when the caller set `auth: "none"` on a custom
|
|
65
|
+
* endpoint; every other target still requires a key.
|
|
64
66
|
*/
|
|
65
67
|
export function resolveEndpoint(endpoint) {
|
|
66
68
|
if (endpoint?.kind === "custom") {
|
|
@@ -71,6 +73,7 @@ export function resolveEndpoint(endpoint) {
|
|
|
71
73
|
url: endpoint.url,
|
|
72
74
|
backend: "custom",
|
|
73
75
|
model: endpoint.model ?? GATEWAY_QUIRKS.model,
|
|
76
|
+
auth: endpoint.auth === "none" ? "none" : "key",
|
|
74
77
|
};
|
|
75
78
|
}
|
|
76
79
|
const kind = endpoint?.kind ?? "official";
|
|
@@ -83,5 +86,6 @@ export function resolveEndpoint(endpoint) {
|
|
|
83
86
|
url,
|
|
84
87
|
backend: kind === "gateway" ? "gateway" : "corbits-system-one",
|
|
85
88
|
model: endpoint?.model ?? quirks.model,
|
|
89
|
+
auth: "key",
|
|
86
90
|
};
|
|
87
91
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Image } from "./assets.js";
|
|
2
|
+
import type { EvaluateDeps } from "./client.js";
|
|
3
|
+
import { type EvaluateOptions } from "./evaluate.js";
|
|
4
|
+
import { type DecisionQuestion, type Description, type EvaluateConfig, type FallbackResult } from "./schemas.js";
|
|
5
|
+
type ChoiceCriteria = Record<string, Description | null>;
|
|
6
|
+
type NoulCriteria = {
|
|
7
|
+
true?: Description;
|
|
8
|
+
false?: Description;
|
|
9
|
+
};
|
|
10
|
+
export declare function choice<const Criteria extends ChoiceCriteria>(instructions: unknown, criteria: Criteria): {
|
|
11
|
+
type: "choice";
|
|
12
|
+
instructions: unknown;
|
|
13
|
+
criteria: Criteria;
|
|
14
|
+
};
|
|
15
|
+
export declare function noul(instructions: unknown, criteria?: NoulCriteria): {
|
|
16
|
+
type: "noul";
|
|
17
|
+
instructions: unknown;
|
|
18
|
+
criteria?: NoulCriteria;
|
|
19
|
+
};
|
|
20
|
+
export declare function score<const Criteria extends readonly Description[]>(instructions: unknown, criteria: Criteria): {
|
|
21
|
+
type: "score";
|
|
22
|
+
instructions: unknown;
|
|
23
|
+
criteria: Criteria;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Locate what `instructions` describe. The answer's `box` edges are fractions
|
|
27
|
+
* of the image. Box questions need the `jev-preview` model; pass it as
|
|
28
|
+
* `model` (or on the endpoint). This client does not reject other models.
|
|
29
|
+
*/
|
|
30
|
+
export declare function box(instructions: unknown): {
|
|
31
|
+
type: "box";
|
|
32
|
+
instructions: unknown;
|
|
33
|
+
};
|
|
34
|
+
type ChoiceAnswer<Criteria extends ChoiceCriteria> = {
|
|
35
|
+
type: "choice";
|
|
36
|
+
choice: keyof Criteria & string;
|
|
37
|
+
probabilities: Record<keyof Criteria & string, number>;
|
|
38
|
+
confidence: number;
|
|
39
|
+
};
|
|
40
|
+
type NoulAnswer = {
|
|
41
|
+
type: "noul";
|
|
42
|
+
noul: number;
|
|
43
|
+
};
|
|
44
|
+
type ScoreAnswer = {
|
|
45
|
+
type: "score";
|
|
46
|
+
score: number;
|
|
47
|
+
legend: Record<string, string>;
|
|
48
|
+
probabilities: Record<string, number>;
|
|
49
|
+
confidence: number;
|
|
50
|
+
};
|
|
51
|
+
type BoxAnswer = {
|
|
52
|
+
type: "box";
|
|
53
|
+
box: {
|
|
54
|
+
top: number;
|
|
55
|
+
bottom: number;
|
|
56
|
+
left: number;
|
|
57
|
+
right: number;
|
|
58
|
+
};
|
|
59
|
+
confidence: number | null;
|
|
60
|
+
};
|
|
61
|
+
type AnswerOf<QuestionSpec> = QuestionSpec extends {
|
|
62
|
+
type: "choice";
|
|
63
|
+
criteria: infer Criteria extends ChoiceCriteria;
|
|
64
|
+
} ? ChoiceAnswer<Criteria> : QuestionSpec extends {
|
|
65
|
+
type: "noul";
|
|
66
|
+
} ? NoulAnswer : QuestionSpec extends {
|
|
67
|
+
type: "score";
|
|
68
|
+
} ? ScoreAnswer : QuestionSpec extends {
|
|
69
|
+
type: "box";
|
|
70
|
+
} ? BoxAnswer : never;
|
|
71
|
+
export type SystemOneAnswers<Questions extends Record<string, unknown>> = {
|
|
72
|
+
[Key in keyof Questions]: AnswerOf<Questions[Key]>;
|
|
73
|
+
};
|
|
74
|
+
export type SystemOneSuccess<Questions extends Record<string, unknown>> = {
|
|
75
|
+
fallback: false;
|
|
76
|
+
answers: SystemOneAnswers<Questions>;
|
|
77
|
+
modelId: string;
|
|
78
|
+
backend: "corbits-system-one" | "gateway" | "custom";
|
|
79
|
+
latencyMs: number;
|
|
80
|
+
usage?: {
|
|
81
|
+
inputTokens: number;
|
|
82
|
+
outputTokens: number;
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
export type SystemOneResult<Questions extends Record<string, unknown>> = SystemOneSuccess<Questions> | FallbackResult;
|
|
86
|
+
export type SystemOneInput<Questions extends Record<string, DecisionQuestion>> = {
|
|
87
|
+
state: unknown;
|
|
88
|
+
questions: Questions;
|
|
89
|
+
assets?: Record<string, Image>;
|
|
90
|
+
model?: string;
|
|
91
|
+
config?: EvaluateConfig;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Keyed decisions call. `choice`, `noul`, `score`, and `box` compile to the
|
|
95
|
+
* question list `evaluate` already sends. Transport failures return
|
|
96
|
+
* `fallback` and `reason` and do not throw.
|
|
97
|
+
*/
|
|
98
|
+
export declare function systemOne<const Questions extends Record<string, DecisionQuestion>>(input: SystemOneInput<Questions>, options?: Pick<EvaluateOptions, "deps" | "onTelemetry">): Promise<SystemOneResult<Questions>>;
|
|
99
|
+
export type { EvaluateDeps };
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { type } from "arktype";
|
|
2
|
+
import { contentTypeOf, Image, serializeRequest } from "./assets.js";
|
|
3
|
+
import { evaluate } from "./evaluate.js";
|
|
4
|
+
import { SystemOneError } from "./errors.js";
|
|
5
|
+
import { DecisionQuestionRecord, QuestionList, } from "./schemas.js";
|
|
6
|
+
export function choice(instructions, criteria) {
|
|
7
|
+
if (Array.isArray(criteria)) {
|
|
8
|
+
throw new SystemOneError("parse-error", "Choice criteria must be a map of labels to descriptions, not a list.");
|
|
9
|
+
}
|
|
10
|
+
return { type: "choice", instructions, criteria };
|
|
11
|
+
}
|
|
12
|
+
export function noul(instructions, criteria) {
|
|
13
|
+
const question = { type: "noul", instructions };
|
|
14
|
+
if (criteria !== undefined)
|
|
15
|
+
question.criteria = criteria;
|
|
16
|
+
return question;
|
|
17
|
+
}
|
|
18
|
+
export function score(instructions, criteria) {
|
|
19
|
+
if (!Array.isArray(criteria)) {
|
|
20
|
+
throw new SystemOneError("parse-error", "Score criteria must be a list of descriptions indexed by score from zero, not a map.");
|
|
21
|
+
}
|
|
22
|
+
return { type: "score", instructions, criteria };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Locate what `instructions` describe. The answer's `box` edges are fractions
|
|
26
|
+
* of the image. Box questions need the `jev-preview` model; pass it as
|
|
27
|
+
* `model` (or on the endpoint). This client does not reject other models.
|
|
28
|
+
*/
|
|
29
|
+
export function box(instructions) {
|
|
30
|
+
return { type: "box", instructions };
|
|
31
|
+
}
|
|
32
|
+
function withModel(config, model) {
|
|
33
|
+
const next = {};
|
|
34
|
+
if (config?.apiKey !== undefined)
|
|
35
|
+
next.apiKey = config.apiKey;
|
|
36
|
+
if (config?.timeoutMs !== undefined)
|
|
37
|
+
next.timeoutMs = config.timeoutMs;
|
|
38
|
+
const endpoint = config?.endpoint;
|
|
39
|
+
if (model !== undefined) {
|
|
40
|
+
next.endpoint = { ...(endpoint ?? { kind: "official" }), model };
|
|
41
|
+
}
|
|
42
|
+
else if (endpoint !== undefined) {
|
|
43
|
+
next.endpoint = endpoint;
|
|
44
|
+
}
|
|
45
|
+
return next;
|
|
46
|
+
}
|
|
47
|
+
function answersFrom(decisions) {
|
|
48
|
+
const answers = {};
|
|
49
|
+
for (const decision of decisions) {
|
|
50
|
+
if (decision.type === "choice") {
|
|
51
|
+
answers[decision.id] = {
|
|
52
|
+
type: "choice",
|
|
53
|
+
choice: decision.choice,
|
|
54
|
+
probabilities: decision.probabilities,
|
|
55
|
+
confidence: decision.confidence,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
else if (decision.type === "noul") {
|
|
59
|
+
answers[decision.id] = { type: "noul", noul: decision.noul };
|
|
60
|
+
}
|
|
61
|
+
else if (decision.type === "score") {
|
|
62
|
+
answers[decision.id] = {
|
|
63
|
+
type: "score",
|
|
64
|
+
score: decision.score,
|
|
65
|
+
legend: decision.legend,
|
|
66
|
+
probabilities: decision.probabilities,
|
|
67
|
+
confidence: decision.confidence,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
answers[decision.id] = {
|
|
72
|
+
type: "box",
|
|
73
|
+
box: decision.box,
|
|
74
|
+
confidence: decision.confidence,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return answers;
|
|
79
|
+
}
|
|
80
|
+
async function loadImages(assets) {
|
|
81
|
+
const parts = [];
|
|
82
|
+
const wire = {};
|
|
83
|
+
let index = 0;
|
|
84
|
+
for (const [alias, asset] of Object.entries(assets)) {
|
|
85
|
+
let bytes;
|
|
86
|
+
if (asset.data instanceof Blob) {
|
|
87
|
+
bytes = new Uint8Array(await asset.data.arrayBuffer());
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
bytes = asset.data;
|
|
91
|
+
}
|
|
92
|
+
const contentType = contentTypeOf(bytes);
|
|
93
|
+
if (contentType !== "image/png" && contentType !== "image/jpeg") {
|
|
94
|
+
throw new SystemOneError("parse-error", `Image "${alias}" must be PNG or JPEG.`);
|
|
95
|
+
}
|
|
96
|
+
const name = `asset-${index}`;
|
|
97
|
+
parts.push({
|
|
98
|
+
name,
|
|
99
|
+
bytes,
|
|
100
|
+
contentType,
|
|
101
|
+
});
|
|
102
|
+
wire[alias] = { type: "image", part: name };
|
|
103
|
+
index += 1;
|
|
104
|
+
}
|
|
105
|
+
return { assets: wire, parts };
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Keyed decisions call. `choice`, `noul`, `score`, and `box` compile to the
|
|
109
|
+
* question list `evaluate` already sends. Transport failures return
|
|
110
|
+
* `fallback` and `reason` and do not throw.
|
|
111
|
+
*/
|
|
112
|
+
export async function systemOne(input, options) {
|
|
113
|
+
const parsed = DecisionQuestionRecord(input.questions);
|
|
114
|
+
if (parsed instanceof type.errors) {
|
|
115
|
+
throw new SystemOneError("parse-error", `systemOne: invalid questions: ${parsed.summary}`);
|
|
116
|
+
}
|
|
117
|
+
const serialized = serializeRequest({ state: input.state, questions: input.questions }, input.assets);
|
|
118
|
+
let payload;
|
|
119
|
+
try {
|
|
120
|
+
payload = JSON.parse(serialized.json);
|
|
121
|
+
}
|
|
122
|
+
catch (cause) {
|
|
123
|
+
throw new SystemOneError("parse-error", `systemOne: request is not JSON: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
124
|
+
}
|
|
125
|
+
const questions = QuestionList(Object.entries(payload.questions !== null &&
|
|
126
|
+
typeof payload.questions === "object" &&
|
|
127
|
+
!Array.isArray(payload.questions)
|
|
128
|
+
? payload.questions
|
|
129
|
+
: {}).map(([id, question]) => typeof question === "object" && question !== null
|
|
130
|
+
? { id, ...question }
|
|
131
|
+
: { id }));
|
|
132
|
+
if (questions instanceof type.errors) {
|
|
133
|
+
throw new SystemOneError("parse-error", `systemOne: invalid questions: ${questions.summary}`);
|
|
134
|
+
}
|
|
135
|
+
const state = payload.state;
|
|
136
|
+
if (state === undefined) {
|
|
137
|
+
throw new SystemOneError("parse-error", "systemOne: state is required");
|
|
138
|
+
}
|
|
139
|
+
const images = serialized.assets === undefined
|
|
140
|
+
? undefined
|
|
141
|
+
: await loadImages(serialized.assets);
|
|
142
|
+
const config = withModel(input.config, input.model);
|
|
143
|
+
const result = await evaluate({
|
|
144
|
+
state: state,
|
|
145
|
+
questions,
|
|
146
|
+
...(input.config !== undefined || input.model !== undefined
|
|
147
|
+
? { config }
|
|
148
|
+
: {}),
|
|
149
|
+
}, {
|
|
150
|
+
...(options?.deps !== undefined ? { deps: options.deps } : {}),
|
|
151
|
+
...(options?.onTelemetry !== undefined
|
|
152
|
+
? { onTelemetry: options.onTelemetry }
|
|
153
|
+
: {}),
|
|
154
|
+
...(images !== undefined ? { images } : {}),
|
|
155
|
+
});
|
|
156
|
+
if (result.fallback)
|
|
157
|
+
return result;
|
|
158
|
+
return {
|
|
159
|
+
fallback: false,
|
|
160
|
+
answers: answersFrom(result.decisions),
|
|
161
|
+
modelId: result.modelId,
|
|
162
|
+
backend: result.backend,
|
|
163
|
+
latencyMs: result.latencyMs,
|
|
164
|
+
...(result.usage !== undefined ? { usage: result.usage } : {}),
|
|
165
|
+
};
|
|
166
|
+
}
|
package/dist/evaluate.d.ts
CHANGED
|
@@ -10,6 +10,18 @@ export declare function checkAnswers(questions: Question[], answers: Record<stri
|
|
|
10
10
|
export type EvaluateOptions = {
|
|
11
11
|
onTelemetry?: (event: SystemOneTelemetryEvent) => void;
|
|
12
12
|
deps?: EvaluateDeps;
|
|
13
|
+
/** Image parts already checked as PNG or JPEG. Omitted for JSON requests. */
|
|
14
|
+
images?: {
|
|
15
|
+
assets: Record<string, {
|
|
16
|
+
type: "image";
|
|
17
|
+
part: string;
|
|
18
|
+
}>;
|
|
19
|
+
parts: {
|
|
20
|
+
name: string;
|
|
21
|
+
bytes: Uint8Array;
|
|
22
|
+
contentType: "image/png" | "image/jpeg";
|
|
23
|
+
}[];
|
|
24
|
+
};
|
|
13
25
|
};
|
|
14
26
|
/**
|
|
15
27
|
* Runs one typed-decision evaluation against System One (or its gateway)
|
|
@@ -20,6 +32,9 @@ export type EvaluateOptions = {
|
|
|
20
32
|
* `TYPESAFE_API_KEY` for official, `AI_GATEWAY_API_KEY` then
|
|
21
33
|
* `VERCEL_OIDC_TOKEN` for gateway — then the `SYSTEM_ONE_API_KEY` alias,
|
|
22
34
|
* then a fail-closed `'no-key'` fallback with zero fetch calls.
|
|
35
|
+
* A custom endpoint with `auth: "none"` skips that check and sends no
|
|
36
|
+
* `Authorization` header, even if a key is present. Official and gateway
|
|
37
|
+
* always require a key; a custom endpoint without `auth: "none"` does too.
|
|
23
38
|
* A `timeoutMs` that is not a finite number >= 0 falls back to
|
|
24
39
|
* `DEFAULT_TIMEOUT_MS`. Transport failures map to matching fallback reasons
|
|
25
40
|
* (`'timeout'`, `'network'`, `'http-error'`, or `'parse-error'` for a 2xx
|
package/dist/evaluate.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createDefaultScheduler } from "@intx/inference";
|
|
|
3
3
|
import { postEvaluate } from "./client.js";
|
|
4
4
|
import { DEFAULT_TIMEOUT_MS, GATEWAY_API_KEY_ENV, GATEWAY_OIDC_ENV, resolveEndpoint, SYSTEM_ONE_API_KEY_ENV, TYPESAFE_API_KEY_ENV, } from "./config.js";
|
|
5
5
|
import { SystemOneError, TransportError, transportFallbackReason, } from "./errors.js";
|
|
6
|
-
import { APPROXIMATE_SUM_TOLERANCE as SUM_TOLERANCE, EvaluateInput, toDecision, toWireQuestions, WireRequest, WireResponseBody, } from "./schemas.js";
|
|
6
|
+
import { APPROXIMATE_SUM_TOLERANCE as SUM_TOLERANCE, EvaluateInput, toDecision, toWireQuestions, WireRequest, WireResponseBody, boxEdgesOutOfRange, } from "./schemas.js";
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
8
8
|
// evaluate
|
|
9
9
|
// ---------------------------------------------------------------------------
|
|
@@ -94,7 +94,12 @@ function checkAnswer(question, id, answer) {
|
|
|
94
94
|
return `answer "${id}" is missing noul`;
|
|
95
95
|
return undefined;
|
|
96
96
|
}
|
|
97
|
-
if (answer.
|
|
97
|
+
if (question.type === "box" && answer.type === "box") {
|
|
98
|
+
if (answer.box === undefined)
|
|
99
|
+
return `answer "${id}" is missing box`;
|
|
100
|
+
return boxEdgesOutOfRange(id, answer.box);
|
|
101
|
+
}
|
|
102
|
+
if (answer.confidence == null) {
|
|
98
103
|
return `answer "${id}" is missing confidence`;
|
|
99
104
|
}
|
|
100
105
|
if (question.type === "choice" && answer.type === "choice") {
|
|
@@ -183,6 +188,9 @@ function envKey(name) {
|
|
|
183
188
|
* `TYPESAFE_API_KEY` for official, `AI_GATEWAY_API_KEY` then
|
|
184
189
|
* `VERCEL_OIDC_TOKEN` for gateway — then the `SYSTEM_ONE_API_KEY` alias,
|
|
185
190
|
* then a fail-closed `'no-key'` fallback with zero fetch calls.
|
|
191
|
+
* A custom endpoint with `auth: "none"` skips that check and sends no
|
|
192
|
+
* `Authorization` header, even if a key is present. Official and gateway
|
|
193
|
+
* always require a key; a custom endpoint without `auth: "none"` does too.
|
|
186
194
|
* A `timeoutMs` that is not a finite number >= 0 falls back to
|
|
187
195
|
* `DEFAULT_TIMEOUT_MS`. Transport failures map to matching fallback reasons
|
|
188
196
|
* (`'timeout'`, `'network'`, `'http-error'`, or `'parse-error'` for a 2xx
|
|
@@ -195,7 +203,8 @@ function envKey(name) {
|
|
|
195
203
|
*/
|
|
196
204
|
export async function evaluate(input, options) {
|
|
197
205
|
const deps = options?.deps ?? {
|
|
198
|
-
fetch
|
|
206
|
+
// Browsers reject fetch called as a method of another object.
|
|
207
|
+
fetch: (input, init) => fetch(input, init),
|
|
199
208
|
scheduler: createDefaultScheduler(),
|
|
200
209
|
};
|
|
201
210
|
const start = deps.scheduler.now();
|
|
@@ -221,7 +230,12 @@ export async function evaluate(input, options) {
|
|
|
221
230
|
const rawApiKey = parsed.config?.apiKey;
|
|
222
231
|
let apiKey;
|
|
223
232
|
let keySources;
|
|
224
|
-
|
|
233
|
+
const keyless = endpoint.auth === "none";
|
|
234
|
+
if (keyless) {
|
|
235
|
+
apiKey = undefined;
|
|
236
|
+
keySources = "";
|
|
237
|
+
}
|
|
238
|
+
else if (rawApiKey !== undefined && rawApiKey !== "") {
|
|
225
239
|
apiKey = rawApiKey;
|
|
226
240
|
keySources = "config.apiKey";
|
|
227
241
|
}
|
|
@@ -271,15 +285,19 @@ export async function evaluate(input, options) {
|
|
|
271
285
|
modelId: model,
|
|
272
286
|
};
|
|
273
287
|
emit(startEvent);
|
|
274
|
-
if (apiKey === undefined || apiKey === "") {
|
|
288
|
+
if (!keyless && (apiKey === undefined || apiKey === "")) {
|
|
275
289
|
return fallback("no-key", {
|
|
276
290
|
detail: `no API key supplied; set config.apiKey or ${keySources}`,
|
|
277
291
|
});
|
|
278
292
|
}
|
|
293
|
+
const images = options?.images;
|
|
279
294
|
const wire = WireRequest({
|
|
280
295
|
model,
|
|
281
296
|
state: parsed.state,
|
|
282
297
|
questions: toWireQuestions(parsed.questions),
|
|
298
|
+
...(images !== undefined && images.parts.length > 0
|
|
299
|
+
? { assets: images.assets }
|
|
300
|
+
: {}),
|
|
283
301
|
});
|
|
284
302
|
if (wire instanceof type.errors) {
|
|
285
303
|
throw new SystemOneError("parse-error", `evaluate: internal wire serialization failed: ${wire.summary}`);
|
|
@@ -287,7 +305,13 @@ export async function evaluate(input, options) {
|
|
|
287
305
|
let data;
|
|
288
306
|
let transportLatencyMs;
|
|
289
307
|
try {
|
|
290
|
-
const response = await postEvaluate(endpoint.url, wire, {
|
|
308
|
+
const response = await postEvaluate(endpoint.url, wire, {
|
|
309
|
+
timeoutMs,
|
|
310
|
+
...(apiKey === undefined ? {} : { apiKey }),
|
|
311
|
+
...(images !== undefined && images.parts.length > 0
|
|
312
|
+
? { imageParts: images.parts }
|
|
313
|
+
: {}),
|
|
314
|
+
}, deps);
|
|
291
315
|
data = response.data;
|
|
292
316
|
transportLatencyMs = response.latencyMs;
|
|
293
317
|
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { FallbackResult } from "./schemas.js";
|
|
2
|
+
export type TextField = {
|
|
3
|
+
type: "text";
|
|
4
|
+
instructions?: string;
|
|
5
|
+
};
|
|
6
|
+
export type DateField = {
|
|
7
|
+
type: "date";
|
|
8
|
+
instructions?: string;
|
|
9
|
+
};
|
|
10
|
+
export type NumberField = {
|
|
11
|
+
type: "number";
|
|
12
|
+
decimals?: number;
|
|
13
|
+
unit?: string;
|
|
14
|
+
instructions?: string;
|
|
15
|
+
};
|
|
16
|
+
export type ListItemKind = "text" | "number" | "date";
|
|
17
|
+
export type ListField<Items extends ListItemKind = ListItemKind> = {
|
|
18
|
+
type: "list";
|
|
19
|
+
items: Items;
|
|
20
|
+
};
|
|
21
|
+
export type EstimateField = {
|
|
22
|
+
type: "estimate";
|
|
23
|
+
min?: number;
|
|
24
|
+
max?: number;
|
|
25
|
+
step?: number;
|
|
26
|
+
instructions?: string;
|
|
27
|
+
};
|
|
28
|
+
export type ExtractionField = TextField | DateField | NumberField | ListField | EstimateField;
|
|
29
|
+
export type TextAnswer = {
|
|
30
|
+
value: string | null;
|
|
31
|
+
start?: number;
|
|
32
|
+
end?: number;
|
|
33
|
+
};
|
|
34
|
+
export type DateAnswer = {
|
|
35
|
+
value: string | null;
|
|
36
|
+
};
|
|
37
|
+
export type NumberAnswer = {
|
|
38
|
+
value: number | null;
|
|
39
|
+
unit?: string;
|
|
40
|
+
};
|
|
41
|
+
type ListValue<Items extends string> = Items extends "number" ? number : string;
|
|
42
|
+
export type ListAnswer<Items extends string = "text"> = {
|
|
43
|
+
value: ListValue<Items>[] | null;
|
|
44
|
+
};
|
|
45
|
+
export type EstimateAnswer = {
|
|
46
|
+
median: number | null;
|
|
47
|
+
interval?: {
|
|
48
|
+
p10: number;
|
|
49
|
+
p90: number;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
type AnswerOf<Field> = Field extends {
|
|
53
|
+
type: "text";
|
|
54
|
+
} ? TextAnswer : Field extends {
|
|
55
|
+
type: "date";
|
|
56
|
+
} ? DateAnswer : Field extends {
|
|
57
|
+
type: "number";
|
|
58
|
+
} ? NumberAnswer : Field extends {
|
|
59
|
+
type: "list";
|
|
60
|
+
items: infer Items extends string;
|
|
61
|
+
} ? ListAnswer<Items> : Field extends {
|
|
62
|
+
type: "estimate";
|
|
63
|
+
} ? EstimateAnswer : never;
|
|
64
|
+
export type ExtractionAnswers<Fields extends Record<string, ExtractionField>> = {
|
|
65
|
+
[Key in keyof Fields]: AnswerOf<Fields[Key]>;
|
|
66
|
+
};
|
|
67
|
+
export type ExtractionSuccess<Fields extends Record<string, ExtractionField>> = {
|
|
68
|
+
fallback: false;
|
|
69
|
+
answers: ExtractionAnswers<Fields>;
|
|
70
|
+
modelId: string;
|
|
71
|
+
latencyMs: number;
|
|
72
|
+
};
|
|
73
|
+
export type ExtractionResult<Fields extends Record<string, ExtractionField>> = ExtractionSuccess<Fields> | FallbackResult;
|
|
74
|
+
export type ExtractInput<Fields extends Record<string, ExtractionField>> = {
|
|
75
|
+
state: unknown;
|
|
76
|
+
fields: Fields;
|
|
77
|
+
};
|
|
78
|
+
export type ExtractFetch = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
79
|
+
export type ExtractOptions = {
|
|
80
|
+
accountId?: string;
|
|
81
|
+
token?: string;
|
|
82
|
+
fetch?: ExtractFetch;
|
|
83
|
+
};
|
|
84
|
+
declare function text(instructions?: string): TextField;
|
|
85
|
+
declare function date(instructions?: string): DateField;
|
|
86
|
+
declare function number(options?: {
|
|
87
|
+
decimals?: number;
|
|
88
|
+
unit?: string;
|
|
89
|
+
instructions?: string;
|
|
90
|
+
}): NumberField;
|
|
91
|
+
declare function list<const Items extends ListItemKind>(items: Items): ListField<Items>;
|
|
92
|
+
declare function estimate(options?: {
|
|
93
|
+
min?: number;
|
|
94
|
+
max?: number;
|
|
95
|
+
step?: number;
|
|
96
|
+
instructions?: string;
|
|
97
|
+
}): EstimateField;
|
|
98
|
+
export { date, estimate, list, number, text };
|
|
99
|
+
/**
|
|
100
|
+
* Keyed extraction against Cloudflare Workers AI Clairic. Missing credentials,
|
|
101
|
+
* non-2xx responses, and bodies that are not JSON return `fallback` and do
|
|
102
|
+
* not throw.
|
|
103
|
+
*/
|
|
104
|
+
export declare function extract<const Fields extends Record<string, ExtractionField>>(input: ExtractInput<Fields>, options?: ExtractOptions): Promise<ExtractionResult<Fields>>;
|