decision-api 1.2.0 → 2.0.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 +116 -5
- package/dist/create-decision-api.d.ts +86 -16
- package/dist/create-decision-api.js +136 -46
- package/dist/eval.d.ts +115 -0
- package/dist/eval.js +182 -0
- package/dist/images.d.ts +58 -0
- package/dist/images.js +160 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -1
- package/dist/models.d.ts +37 -4
- package/dist/models.js +86 -16
- package/dist/questions.d.ts +30 -28
- package/dist/questions.js +21 -1
- package/dist/testing.d.ts +27 -0
- package/dist/testing.js +74 -0
- package/package.json +9 -1
package/dist/models.js
CHANGED
|
@@ -2,27 +2,83 @@
|
|
|
2
2
|
// that provider's wire format maps onto the shared question shapes
|
|
3
3
|
// (`questions.ts`).
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
+
import { toAssets } from "./images.js";
|
|
5
6
|
import { DecisionResponseSchema } from "./questions.js";
|
|
6
7
|
/**
|
|
7
|
-
* USD per million input tokens; output is free on every
|
|
8
|
-
* answer is a few tokens of enum rather than generated
|
|
9
|
-
* from TypeSafe's launch material (the docs publish no
|
|
10
|
-
* are the providers' list prices (2026-10).
|
|
8
|
+
* `inputUsdPerMTok` is USD per million input tokens; output is free on every
|
|
9
|
+
* provider, because an answer is a few tokens of enum rather than generated
|
|
10
|
+
* prose. Jev's rate is from TypeSafe's launch material (the docs publish no
|
|
11
|
+
* price sheet); the rest are the providers' list prices (2026-10). Images are
|
|
12
|
+
* billed as input tokens at the same rate.
|
|
13
|
+
*
|
|
14
|
+
* `images` marks the models this client sends images to. Jev reads them
|
|
15
|
+
* anywhere in the state, instructions and criteria (1.13 and later:
|
|
16
|
+
* https://docs.typesafe.ai/images). Clef and gpt-6-luna read images too, but
|
|
17
|
+
* in wire formats this client does not speak yet.
|
|
18
|
+
*
|
|
19
|
+
* `maxQuestions`, `maxChoices` (options in one choice) and `maxLevels` (levels
|
|
20
|
+
* in one score) are the limits a call is checked against before it is sent,
|
|
21
|
+
* where a provider documents or was measured to enforce one. Jev's come from
|
|
22
|
+
* its API schema (https://docs.typesafe.ai/api); Clef's question limit was
|
|
23
|
+
* measured (it refuses more than 64).
|
|
11
24
|
*/
|
|
12
25
|
export const DECISION_MODELS = {
|
|
13
|
-
jev: {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
26
|
+
jev: {
|
|
27
|
+
provider: 'typesafe',
|
|
28
|
+
inputUsdPerMTok: 0.042,
|
|
29
|
+
images: true,
|
|
30
|
+
maxChoices: 255,
|
|
31
|
+
maxLevels: 10
|
|
32
|
+
},
|
|
33
|
+
clef: {
|
|
34
|
+
provider: 'cloudflare',
|
|
35
|
+
inputUsdPerMTok: 0.24,
|
|
36
|
+
images: false,
|
|
37
|
+
maxQuestions: 64
|
|
38
|
+
},
|
|
39
|
+
'clef-flash': {
|
|
40
|
+
provider: 'cloudflare',
|
|
41
|
+
inputUsdPerMTok: 0.09,
|
|
42
|
+
images: false
|
|
43
|
+
},
|
|
44
|
+
'gpt-6-luna': { provider: 'openai', inputUsdPerMTok: 0.1, images: false },
|
|
17
45
|
// OpenRouter models are named by their OpenRouter slug, so adding one is
|
|
18
46
|
// one line here: https://openrouter.ai/rankings/decisions
|
|
19
47
|
'microsoft/microsoft-decision-1': {
|
|
20
48
|
provider: 'openrouter',
|
|
21
|
-
inputUsdPerMTok: 0.042
|
|
49
|
+
inputUsdPerMTok: 0.042,
|
|
50
|
+
images: false
|
|
22
51
|
},
|
|
23
|
-
// TypeSafe's Jev 1.13 on OpenRouter, served on OpenRouter's
|
|
24
|
-
|
|
52
|
+
// TypeSafe's Jev 1.13 on OpenRouter, served on OpenRouter's
|
|
53
|
+
// zero-data-retention list. OpenRouter forwards `assets` to Jev as written:
|
|
54
|
+
// https://openrouter.ai/docs/guides/community/multimodal-decisions
|
|
55
|
+
'typesafe/jev-1.13': {
|
|
56
|
+
provider: 'openrouter',
|
|
57
|
+
inputUsdPerMTok: 0.042,
|
|
58
|
+
images: true,
|
|
59
|
+
maxChoices: 255,
|
|
60
|
+
maxLevels: 10
|
|
61
|
+
}
|
|
25
62
|
};
|
|
63
|
+
/** Why a model would refuse these questions, or undefined when it takes them. */
|
|
64
|
+
export function overModelLimit(model, questions) {
|
|
65
|
+
const limits = DECISION_MODELS[model];
|
|
66
|
+
const count = Object.keys(questions).length;
|
|
67
|
+
if (limits.maxQuestions !== undefined && count > limits.maxQuestions) {
|
|
68
|
+
return `${count} questions, over ${model}'s ${limits.maxQuestions}`;
|
|
69
|
+
}
|
|
70
|
+
for (const [key, question] of Object.entries(questions)) {
|
|
71
|
+
if (question.type === 'noul')
|
|
72
|
+
continue;
|
|
73
|
+
const [size, limit, what] = question.type === 'choice'
|
|
74
|
+
? [Object.keys(question.criteria).length, limits.maxChoices, 'options']
|
|
75
|
+
: [question.criteria.length, limits.maxLevels, 'levels'];
|
|
76
|
+
if (limit !== undefined && size > limit) {
|
|
77
|
+
return `"${key}" has ${size} ${what}, over ${model}'s ${limit}`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
26
82
|
/** One POST: where it goes, and the headers and body it carries. */
|
|
27
83
|
export function buildDecisionRequest(model, input, keys) {
|
|
28
84
|
const provider = DECISION_MODELS[model].provider;
|
|
@@ -34,18 +90,30 @@ export function buildDecisionRequest(model, input, keys) {
|
|
|
34
90
|
'content-type': 'application/json'
|
|
35
91
|
};
|
|
36
92
|
};
|
|
93
|
+
const { state, questions, images } = input;
|
|
94
|
+
// A text-only request goes out exactly as it did before images existed.
|
|
95
|
+
const assets = images.length > 0 ? { assets: toAssets(images) } : {};
|
|
37
96
|
if (provider === 'typesafe') {
|
|
38
97
|
return {
|
|
39
98
|
url: 'https://api.typesafe.ai/v1/systemone',
|
|
40
|
-
headers:
|
|
41
|
-
|
|
99
|
+
headers: {
|
|
100
|
+
...headers(keys.typesafe),
|
|
101
|
+
// Images are opt-in per request while TypeSafe calls them a preview.
|
|
102
|
+
...(images.length > 0 && { 'typesafe-danger-zone-images': 'true' })
|
|
103
|
+
},
|
|
104
|
+
body: JSON.stringify({
|
|
105
|
+
state,
|
|
106
|
+
questions,
|
|
107
|
+
...assets,
|
|
108
|
+
model: 'jev-latest'
|
|
109
|
+
})
|
|
42
110
|
};
|
|
43
111
|
}
|
|
44
112
|
if (provider === 'cloudflare') {
|
|
45
113
|
return {
|
|
46
114
|
url: `https://api.cloudflare.com/client/v4/accounts/${keys.cloudflare?.accountId}/ai/run/@cf/cloudflare/${model}`,
|
|
47
115
|
headers: headers(keys.cloudflare?.apiToken),
|
|
48
|
-
body: JSON.stringify({
|
|
116
|
+
body: JSON.stringify({ state, questions, model })
|
|
49
117
|
};
|
|
50
118
|
}
|
|
51
119
|
if (provider === 'openrouter') {
|
|
@@ -53,13 +121,13 @@ export function buildDecisionRequest(model, input, keys) {
|
|
|
53
121
|
return {
|
|
54
122
|
url: 'https://openrouter.ai/api/alpha/decisions',
|
|
55
123
|
headers: headers(keys.openrouter),
|
|
56
|
-
body: JSON.stringify({ ...
|
|
124
|
+
body: JSON.stringify({ state, questions, ...assets, model })
|
|
57
125
|
};
|
|
58
126
|
}
|
|
59
127
|
return {
|
|
60
128
|
url: 'https://api.openai.com/v1/decisions',
|
|
61
129
|
headers: headers(keys.openai),
|
|
62
|
-
body: JSON.stringify(toOpenAiRequest(model,
|
|
130
|
+
body: JSON.stringify(toOpenAiRequest(model, { state, questions }))
|
|
63
131
|
};
|
|
64
132
|
}
|
|
65
133
|
/** The provider's response read into the shared shape, unvalidated against the questions. */
|
|
@@ -114,6 +182,7 @@ const OpenAiProbabilitiesSchema = z.array(z.object({
|
|
|
114
182
|
probability: z.number()
|
|
115
183
|
}));
|
|
116
184
|
const OpenAiResponseSchema = z.object({
|
|
185
|
+
model: z.string().optional(),
|
|
117
186
|
answers: z.array(z.discriminatedUnion('type', [
|
|
118
187
|
z.object({
|
|
119
188
|
type: z.literal('predicate'),
|
|
@@ -159,6 +228,7 @@ function fromOpenAiResponse(response) {
|
|
|
159
228
|
];
|
|
160
229
|
});
|
|
161
230
|
return {
|
|
231
|
+
model: response.model,
|
|
162
232
|
answers: Object.fromEntries(answers),
|
|
163
233
|
usage: {
|
|
164
234
|
input_tokens: response.usage.input_tokens,
|
package/dist/questions.d.ts
CHANGED
|
@@ -1,73 +1,62 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import type { DecisionText } from './images.ts';
|
|
2
3
|
/** Probability that the condition holds. No separate confidence: this IS it. */
|
|
3
4
|
export type DecisionNoul = {
|
|
4
5
|
type: 'noul';
|
|
5
|
-
instructions:
|
|
6
|
+
instructions: DecisionText;
|
|
6
7
|
/** Optional, but a `true`/`false` gloss is what keeps a noul from drifting. */
|
|
7
8
|
criteria?: {
|
|
8
|
-
true:
|
|
9
|
-
false:
|
|
9
|
+
true: DecisionText;
|
|
10
|
+
false: DecisionText;
|
|
10
11
|
};
|
|
11
12
|
};
|
|
12
13
|
/** One option from a defined set, with the distribution across all of them. */
|
|
13
14
|
export type DecisionChoice<Option extends string = string> = {
|
|
14
15
|
type: 'choice';
|
|
15
|
-
instructions:
|
|
16
|
+
instructions: DecisionText;
|
|
16
17
|
/** Option -> what that option means. Include a no-match option when one may fit nothing. */
|
|
17
|
-
criteria: Record<Option,
|
|
18
|
+
criteria: Record<Option, DecisionText>;
|
|
18
19
|
};
|
|
19
20
|
/** A position on ordered, concretely described levels. */
|
|
20
21
|
export type DecisionScore = {
|
|
21
22
|
type: 'score';
|
|
22
|
-
instructions:
|
|
23
|
+
instructions: DecisionText;
|
|
23
24
|
/** Two or more levels, lowest first; each must stand on its own. */
|
|
24
|
-
criteria:
|
|
25
|
+
criteria: DecisionText[];
|
|
25
26
|
};
|
|
26
27
|
export type DecisionQuestion = DecisionNoul | DecisionChoice | DecisionScore;
|
|
27
|
-
type DecisionNoulAnswer = {
|
|
28
|
+
export type DecisionNoulAnswer = {
|
|
28
29
|
type: 'noul';
|
|
29
30
|
noul: number;
|
|
30
31
|
};
|
|
31
|
-
type DecisionChoiceAnswer<Option extends string = string> = {
|
|
32
|
+
export type DecisionChoiceAnswer<Option extends string = string> = {
|
|
32
33
|
type: 'choice';
|
|
33
34
|
choice: Option;
|
|
34
35
|
confidence: number;
|
|
35
36
|
probabilities: Record<Option, number>;
|
|
36
37
|
};
|
|
37
|
-
type DecisionScoreAnswer = {
|
|
38
|
+
export type DecisionScoreAnswer = {
|
|
38
39
|
type: 'score';
|
|
39
40
|
score: number;
|
|
40
41
|
confidence: number;
|
|
41
42
|
probabilities: Record<string, number>;
|
|
42
43
|
};
|
|
44
|
+
export type DecisionAnswer = DecisionNoulAnswer | DecisionChoiceAnswer | DecisionScoreAnswer;
|
|
43
45
|
/** The answer a given question shape produces, so call sites need no casts. */
|
|
44
|
-
type AnswerFor<Q> = Q extends {
|
|
46
|
+
export type AnswerFor<Q> = Q extends {
|
|
45
47
|
type: 'noul';
|
|
46
48
|
} ? DecisionNoulAnswer : Q extends {
|
|
47
49
|
type: 'choice';
|
|
48
|
-
criteria: Record<infer Option extends string,
|
|
50
|
+
criteria: Record<infer Option extends string, DecisionText>;
|
|
49
51
|
} ? DecisionChoiceAnswer<Option> : Q extends {
|
|
50
52
|
type: 'score';
|
|
51
53
|
} ? DecisionScoreAnswer : never;
|
|
52
54
|
export type DecisionAnswers<Q extends Record<string, DecisionQuestion>> = {
|
|
53
55
|
[K in keyof Q]: AnswerFor<Q[K]>;
|
|
54
56
|
};
|
|
55
|
-
declare const AnswerSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
56
|
-
type: z.ZodLiteral<"noul">;
|
|
57
|
-
noul: z.ZodNumber;
|
|
58
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
59
|
-
type: z.ZodLiteral<"choice">;
|
|
60
|
-
choice: z.ZodString;
|
|
61
|
-
confidence: z.ZodNumber;
|
|
62
|
-
probabilities: z.ZodRecord<z.ZodString, z.ZodNumber>;
|
|
63
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
64
|
-
type: z.ZodLiteral<"score">;
|
|
65
|
-
score: z.ZodNumber;
|
|
66
|
-
confidence: z.ZodNumber;
|
|
67
|
-
probabilities: z.ZodRecord<z.ZodString, z.ZodNumber>;
|
|
68
|
-
}, z.core.$strip>], "type">;
|
|
69
57
|
/** What every vendor's response is read into. */
|
|
70
58
|
export declare const DecisionResponseSchema: z.ZodObject<{
|
|
59
|
+
model: z.ZodOptional<z.ZodString>;
|
|
71
60
|
answers: z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
72
61
|
type: z.ZodLiteral<"noul">;
|
|
73
62
|
noul: z.ZodNumber;
|
|
@@ -89,5 +78,18 @@ export declare const DecisionResponseSchema: z.ZodObject<{
|
|
|
89
78
|
}, z.core.$strip>;
|
|
90
79
|
export type DecisionResponse = z.infer<typeof DecisionResponseSchema>;
|
|
91
80
|
/** The contract depends on the question, not just the answer's own tag. */
|
|
92
|
-
export declare function matchesQuestion(question: DecisionQuestion, answer:
|
|
93
|
-
|
|
81
|
+
export declare function matchesQuestion(question: DecisionQuestion, answer: DecisionAnswer | undefined): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* The same question about each item of a list, under keys `${prefix}_${index}`.
|
|
84
|
+
* Spread `questions` into a call (next to other questions if you like) and
|
|
85
|
+
* pass its answers to `read`, which pairs each item with its typed answer.
|
|
86
|
+
*/
|
|
87
|
+
export declare function perItem<Item, const Q extends DecisionQuestion, const Prefix extends string>(items: readonly Item[], prefix: Prefix, ask: (item: Item, index: number) => Q): {
|
|
88
|
+
questions: Record<`${Prefix}_${number}`, Q>;
|
|
89
|
+
/** Every key is there: a call answers all its questions or `null`. */
|
|
90
|
+
read: (answers: { readonly [K in `${Prefix}_${number}`]: AnswerFor<Q>; }) => {
|
|
91
|
+
item: Item;
|
|
92
|
+
index: number;
|
|
93
|
+
answer: AnswerFor<Q>;
|
|
94
|
+
}[];
|
|
95
|
+
};
|
package/dist/questions.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// The question and answer shapes every call speaks. They are System One's own
|
|
2
2
|
// (TypeSafe's Jev, Cloudflare's Clef); a provider with another wire format
|
|
3
|
-
// translates to and from them in `models.ts`.
|
|
3
|
+
// translates to and from them in `models.ts`. Instructions and criteria take
|
|
4
|
+
// images (`DecisionText`) on the models that read them.
|
|
4
5
|
import { z } from 'zod';
|
|
5
6
|
const ProbabilitySchema = z.number().min(0).max(1);
|
|
6
7
|
const AnswerSchema = z.discriminatedUnion('type', [
|
|
@@ -20,6 +21,8 @@ const AnswerSchema = z.discriminatedUnion('type', [
|
|
|
20
21
|
]);
|
|
21
22
|
/** What every vendor's response is read into. */
|
|
22
23
|
export const DecisionResponseSchema = z.object({
|
|
24
|
+
/** The model that answered, when the provider says (it may resolve an alias). */
|
|
25
|
+
model: z.string().optional(),
|
|
23
26
|
answers: z.record(z.string(), AnswerSchema),
|
|
24
27
|
usage: z.object({
|
|
25
28
|
input_tokens: z.number().int().nonnegative(),
|
|
@@ -49,3 +52,20 @@ function matchesOptions(options, probabilities) {
|
|
|
49
52
|
return (Object.keys(probabilities).length === options.length &&
|
|
50
53
|
options.every((option) => Object.hasOwn(probabilities, option)));
|
|
51
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* The same question about each item of a list, under keys `${prefix}_${index}`.
|
|
57
|
+
* Spread `questions` into a call (next to other questions if you like) and
|
|
58
|
+
* pass its answers to `read`, which pairs each item with its typed answer.
|
|
59
|
+
*/
|
|
60
|
+
export function perItem(items, prefix, ask) {
|
|
61
|
+
const questions = Object.fromEntries(items.map((item, index) => [`${prefix}_${index}`, ask(item, index)]));
|
|
62
|
+
return {
|
|
63
|
+
questions,
|
|
64
|
+
/** Every key is there: a call answers all its questions or `null`. */
|
|
65
|
+
read: (answers) => items.map((item, index) => ({
|
|
66
|
+
item,
|
|
67
|
+
index,
|
|
68
|
+
answer: answers[`${prefix}_${index}`]
|
|
69
|
+
}))
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { DecisionError } from './create-decision-api.ts';
|
|
2
|
+
import { type DecisionModel } from './models.ts';
|
|
3
|
+
import { type DecisionAnswer, type DecisionQuestion } from './questions.ts';
|
|
4
|
+
/** One call the code under test made: its input (context included) and model. */
|
|
5
|
+
export type FakeDecisionCall = {
|
|
6
|
+
state: unknown;
|
|
7
|
+
questions: Record<string, DecisionQuestion>;
|
|
8
|
+
model: DecisionModel | undefined;
|
|
9
|
+
[field: string]: unknown;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Answers by question key: a number is a noul's probability or a score, a
|
|
13
|
+
* string is the choice, or give the full answer.
|
|
14
|
+
*/
|
|
15
|
+
export type FakeAnswers = Record<string, number | string | DecisionAnswer>;
|
|
16
|
+
type Respond = (call: FakeDecisionCall) => FakeAnswers | DecisionError | null | Promise<FakeAnswers | DecisionError | null>;
|
|
17
|
+
/**
|
|
18
|
+
* A fake `decide`, typed as the real one: `fakeDecide<typeof decide>(...)`.
|
|
19
|
+
* `respond` answers each call; `null` fails it as `request-failed`, and a
|
|
20
|
+
* `DecisionError` fails it with that error. An answer that does not fit its
|
|
21
|
+
* question, or a missing one, throws in the test, since the real client would
|
|
22
|
+
* never return it. `calls` records every call.
|
|
23
|
+
*/
|
|
24
|
+
export declare function fakeDecide<Api>(respond: Respond): Api & {
|
|
25
|
+
calls: FakeDecisionCall[];
|
|
26
|
+
};
|
|
27
|
+
export {};
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// A stand-in for `createDecisionApi`'s client in tests: no network, typed as
|
|
2
|
+
// the real one, and as strict about answers as the real one.
|
|
3
|
+
import { DecisionError } from "./create-decision-api.js";
|
|
4
|
+
import { DECISION_MODELS } from "./models.js";
|
|
5
|
+
import { matchesQuestion } from "./questions.js";
|
|
6
|
+
/**
|
|
7
|
+
* A fake `decide`, typed as the real one: `fakeDecide<typeof decide>(...)`.
|
|
8
|
+
* `respond` answers each call; `null` fails it as `request-failed`, and a
|
|
9
|
+
* `DecisionError` fails it with that error. An answer that does not fit its
|
|
10
|
+
* question, or a missing one, throws in the test, since the real client would
|
|
11
|
+
* never return it. `calls` records every call.
|
|
12
|
+
*/
|
|
13
|
+
export function fakeDecide(respond) {
|
|
14
|
+
const calls = [];
|
|
15
|
+
const result = async (input, model) => {
|
|
16
|
+
const call = { ...input, model };
|
|
17
|
+
calls.push(call);
|
|
18
|
+
const used = model ?? 'jev';
|
|
19
|
+
const response = await respond(call);
|
|
20
|
+
if (response === null || response instanceof DecisionError) {
|
|
21
|
+
const error = response ?? new DecisionError('request-failed', used, 'fakeDecide');
|
|
22
|
+
return { ok: false, error };
|
|
23
|
+
}
|
|
24
|
+
const usage = {
|
|
25
|
+
model: used,
|
|
26
|
+
provider: DECISION_MODELS[used].provider,
|
|
27
|
+
inputTokens: 0,
|
|
28
|
+
outputTokens: 0,
|
|
29
|
+
costUsd: 0,
|
|
30
|
+
latencyMs: 0,
|
|
31
|
+
attempts: 1
|
|
32
|
+
};
|
|
33
|
+
return {
|
|
34
|
+
ok: true,
|
|
35
|
+
answers: expand(call.questions, response),
|
|
36
|
+
usage
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
const decide = async (input, model) => {
|
|
40
|
+
const outcome = await result(input, model);
|
|
41
|
+
return outcome.ok ? outcome.answers : null;
|
|
42
|
+
};
|
|
43
|
+
return Object.assign(decide, { result, calls });
|
|
44
|
+
}
|
|
45
|
+
function expand(questions, given) {
|
|
46
|
+
return Object.fromEntries(Object.entries(questions).map(([key, question]) => {
|
|
47
|
+
const answer = toAnswer(question, given[key]);
|
|
48
|
+
if (!matchesQuestion(question, answer)) {
|
|
49
|
+
throw new Error(`fakeDecide: the answer to "${key}" (${JSON.stringify(given[key])}) does not fit its ${question.type} question`);
|
|
50
|
+
}
|
|
51
|
+
return [key, answer];
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
/** Shorthand to a full answer: all the probability on the given value. */
|
|
55
|
+
function toAnswer(question, value) {
|
|
56
|
+
if (value === undefined || typeof value === 'object')
|
|
57
|
+
return value;
|
|
58
|
+
if (question.type === 'noul') {
|
|
59
|
+
return typeof value === 'number' ? { type: 'noul', noul: value } : undefined;
|
|
60
|
+
}
|
|
61
|
+
const options = question.type === 'choice'
|
|
62
|
+
? Object.keys(question.criteria)
|
|
63
|
+
: question.criteria.map((_, index) => String(index));
|
|
64
|
+
const picked = String(question.type === 'score' ? Math.round(Number(value)) : value);
|
|
65
|
+
const probabilities = Object.fromEntries(options.map((option) => [option, option === picked ? 1 : 0]));
|
|
66
|
+
if (question.type === 'choice') {
|
|
67
|
+
return typeof value === 'string'
|
|
68
|
+
? { type: 'choice', choice: value, confidence: 1, probabilities }
|
|
69
|
+
: undefined;
|
|
70
|
+
}
|
|
71
|
+
return typeof value === 'number'
|
|
72
|
+
? { type: 'score', score: value, confidence: 1, probabilities }
|
|
73
|
+
: undefined;
|
|
74
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "decision-api",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "One typed client for decision models (TypeSafe Jev, Cloudflare Clef, OpenAI gpt-6-luna, OpenRouter models such as Microsoft-Decision-1) that answer questions with calibrated probabilities",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Kristoffer Hansen",
|
|
@@ -22,6 +22,14 @@
|
|
|
22
22
|
".": {
|
|
23
23
|
"types": "./dist/index.d.ts",
|
|
24
24
|
"default": "./dist/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./eval": {
|
|
27
|
+
"types": "./dist/eval.d.ts",
|
|
28
|
+
"default": "./dist/eval.js"
|
|
29
|
+
},
|
|
30
|
+
"./testing": {
|
|
31
|
+
"types": "./dist/testing.d.ts",
|
|
32
|
+
"default": "./dist/testing.js"
|
|
25
33
|
}
|
|
26
34
|
},
|
|
27
35
|
"files": [
|