@effect-agent/ai-decision 0.1.0-beta.102
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 +47 -0
- package/dist/DecisionModel-BQpmPcHb.mjs +96 -0
- package/dist/DecisionModel-BQpmPcHb.mjs.map +1 -0
- package/dist/DecisionModel.d.mts +37 -0
- package/dist/DecisionModel.mjs +3 -0
- package/dist/DecisionQuery.d.mts +59 -0
- package/dist/DecisionQuery.mjs +48 -0
- package/dist/DecisionQuery.mjs.map +1 -0
- package/dist/DecisionSchema.d.mts +313 -0
- package/dist/DecisionSchema.mjs +177 -0
- package/dist/DecisionSchema.mjs.map +1 -0
- package/dist/DecisionSet.d.mts +27 -0
- package/dist/DecisionSet.mjs +20 -0
- package/dist/DecisionSet.mjs.map +1 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +5 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/package.json +1 -0
- package/src/DecisionModel.ts +144 -0
- package/src/DecisionQuery.ts +56 -0
- package/src/DecisionSchema.ts +295 -0
- package/src/DecisionSet.ts +36 -0
- package/src/index.ts +4 -0
- package/src/internal/schema.ts +98 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request and response schemas for provider-neutral decision evaluations.
|
|
3
|
+
*
|
|
4
|
+
* @since 0.1.0
|
|
5
|
+
*/
|
|
6
|
+
import * as Schema from "effect/Schema";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Text or JSON objects and arrays used as state and question instructions.
|
|
10
|
+
*
|
|
11
|
+
* @category schemas
|
|
12
|
+
* @since 0.1.0
|
|
13
|
+
*/
|
|
14
|
+
export const Content = Schema.Union([Schema.String, Schema.JsonObject, Schema.Array(Schema.Json)]);
|
|
15
|
+
|
|
16
|
+
/** @category models
|
|
17
|
+
* @since 0.1.0
|
|
18
|
+
*/
|
|
19
|
+
export type Content = typeof Content.Type;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A choice between named options. A null rubric uses the option name alone.
|
|
23
|
+
*
|
|
24
|
+
* @category schemas
|
|
25
|
+
* @since 0.1.0
|
|
26
|
+
*/
|
|
27
|
+
export const ChoiceQuestion = Schema.Struct({
|
|
28
|
+
type: Schema.Literal("choice"),
|
|
29
|
+
instructions: Content,
|
|
30
|
+
criteria: Schema.Record(Schema.String, Schema.NullOr(Schema.String)).check(
|
|
31
|
+
Schema.isMinProperties(1),
|
|
32
|
+
),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/** @category models
|
|
36
|
+
* @since 0.1.0
|
|
37
|
+
*/
|
|
38
|
+
export type ChoiceQuestion = typeof ChoiceQuestion.Type;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A rating along at least two ordered, zero-indexed level descriptions.
|
|
42
|
+
*
|
|
43
|
+
* @category schemas
|
|
44
|
+
* @since 0.1.0
|
|
45
|
+
*/
|
|
46
|
+
export const ScoreQuestion = Schema.Struct({
|
|
47
|
+
type: Schema.Literal("score"),
|
|
48
|
+
instructions: Content,
|
|
49
|
+
criteria: Schema.Array(Schema.String).check(Schema.isMinLength(2)),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
/** @category models
|
|
53
|
+
* @since 0.1.0
|
|
54
|
+
*/
|
|
55
|
+
export type ScoreQuestion = typeof ScoreQuestion.Type;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A yes/no judgment, with optional descriptions of either outcome.
|
|
59
|
+
*
|
|
60
|
+
* @category schemas
|
|
61
|
+
* @since 0.1.0
|
|
62
|
+
*/
|
|
63
|
+
export const ProbabilityQuestion = Schema.Struct({
|
|
64
|
+
type: Schema.Literal("probability"),
|
|
65
|
+
instructions: Content,
|
|
66
|
+
criteria: Schema.optionalKey(
|
|
67
|
+
Schema.Struct({
|
|
68
|
+
true: Schema.optionalKey(Schema.String),
|
|
69
|
+
false: Schema.optionalKey(Schema.String),
|
|
70
|
+
}),
|
|
71
|
+
),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
/** @category models
|
|
75
|
+
* @since 0.1.0
|
|
76
|
+
*/
|
|
77
|
+
export type ProbabilityQuestion = typeof ProbabilityQuestion.Type;
|
|
78
|
+
|
|
79
|
+
/** @category schemas
|
|
80
|
+
* @since 0.1.0
|
|
81
|
+
*/
|
|
82
|
+
export const Question = Schema.Union([ChoiceQuestion, ScoreQuestion, ProbabilityQuestion]);
|
|
83
|
+
|
|
84
|
+
/** @category models
|
|
85
|
+
* @since 0.1.0
|
|
86
|
+
*/
|
|
87
|
+
export type Question = typeof Question.Type;
|
|
88
|
+
|
|
89
|
+
/** @category schemas
|
|
90
|
+
* @since 0.1.0
|
|
91
|
+
*/
|
|
92
|
+
export const Questions = Schema.Record(Schema.String, Question);
|
|
93
|
+
|
|
94
|
+
/** @category models
|
|
95
|
+
* @since 0.1.0
|
|
96
|
+
*/
|
|
97
|
+
export type Questions = typeof Questions.Type;
|
|
98
|
+
|
|
99
|
+
/** @category schemas
|
|
100
|
+
* @since 0.1.0
|
|
101
|
+
*/
|
|
102
|
+
export const EvaluateRequest = Schema.Struct({
|
|
103
|
+
state: Content,
|
|
104
|
+
questions: Questions,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Evaluation input. Keep question literals with `satisfies Questions` when
|
|
109
|
+
* storing questions separately from the call to `evaluate`.
|
|
110
|
+
*
|
|
111
|
+
* @category models
|
|
112
|
+
* @since 0.1.0
|
|
113
|
+
*/
|
|
114
|
+
export type EvaluateRequest<Q extends Questions = Questions> = Omit<
|
|
115
|
+
typeof EvaluateRequest.Type,
|
|
116
|
+
"questions"
|
|
117
|
+
> & { readonly questions: Q };
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* A finite probability or confidence in the inclusive range [0, 1].
|
|
121
|
+
*
|
|
122
|
+
* @category schemas
|
|
123
|
+
* @since 0.1.0
|
|
124
|
+
*/
|
|
125
|
+
export const Probability = Schema.Finite.check(Schema.isBetween({ minimum: 0, maximum: 1 }));
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* A selected option and its full distribution. Provider-specific confidence
|
|
129
|
+
* statistics belong to evaluation metadata, not the shared answer contract.
|
|
130
|
+
* Probabilities retain the provider's reported precision and are not normalized.
|
|
131
|
+
*
|
|
132
|
+
* @category schemas
|
|
133
|
+
* @since 0.1.0
|
|
134
|
+
*/
|
|
135
|
+
export const ChoiceAnswer = Schema.Struct({
|
|
136
|
+
type: Schema.Literal("choice"),
|
|
137
|
+
choice: Schema.String,
|
|
138
|
+
probabilities: Schema.Record(Schema.String, Probability),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Known option unions infer literal choices and required probability keys.
|
|
143
|
+
* Open string or template-pattern option types allow absent dictionary entries.
|
|
144
|
+
*
|
|
145
|
+
* @category models
|
|
146
|
+
* @since 0.1.0
|
|
147
|
+
*/
|
|
148
|
+
export type ChoiceAnswer<Choice extends string = string> = Omit<
|
|
149
|
+
typeof ChoiceAnswer.Type,
|
|
150
|
+
"choice" | "probabilities"
|
|
151
|
+
> & {
|
|
152
|
+
readonly choice: Choice;
|
|
153
|
+
readonly probabilities: {
|
|
154
|
+
readonly [K in Choice]: {} extends Pick<Record<Choice, unknown>, K>
|
|
155
|
+
? number | undefined
|
|
156
|
+
: number;
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* A fractional, probability-weighted level, with the supplied rubric as legend.
|
|
162
|
+
*
|
|
163
|
+
* @category schemas
|
|
164
|
+
* @since 0.1.0
|
|
165
|
+
*/
|
|
166
|
+
export const ScoreAnswer = Schema.Struct({
|
|
167
|
+
type: Schema.Literal("score"),
|
|
168
|
+
score: Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
169
|
+
legend: Schema.Record(Schema.String, Schema.String),
|
|
170
|
+
probabilities: Schema.Record(Schema.String, Probability),
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Score levels are keyed at runtime; an arbitrary legend or probability lookup
|
|
175
|
+
* may be absent.
|
|
176
|
+
*
|
|
177
|
+
* @category models
|
|
178
|
+
* @since 0.1.0
|
|
179
|
+
*/
|
|
180
|
+
export type ScoreAnswer = Omit<typeof ScoreAnswer.Type, "legend" | "probabilities"> & {
|
|
181
|
+
readonly legend: { readonly [level: string]: string | undefined };
|
|
182
|
+
readonly probabilities: { readonly [level: string]: number | undefined };
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* The probability of yes. A probability answer has no separate confidence field.
|
|
187
|
+
*
|
|
188
|
+
* @category schemas
|
|
189
|
+
* @since 0.1.0
|
|
190
|
+
*/
|
|
191
|
+
export const ProbabilityAnswer = Schema.Struct({
|
|
192
|
+
type: Schema.Literal("probability"),
|
|
193
|
+
probability: Probability,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
/** @category models
|
|
197
|
+
* @since 0.1.0
|
|
198
|
+
*/
|
|
199
|
+
export type ProbabilityAnswer = typeof ProbabilityAnswer.Type;
|
|
200
|
+
|
|
201
|
+
/** @category schemas
|
|
202
|
+
* @since 0.1.0
|
|
203
|
+
*/
|
|
204
|
+
export const Answer = Schema.Union([ChoiceAnswer, ScoreAnswer, ProbabilityAnswer]);
|
|
205
|
+
|
|
206
|
+
/** @category models
|
|
207
|
+
* @since 0.1.0
|
|
208
|
+
*/
|
|
209
|
+
export type Answer = ChoiceAnswer | ScoreAnswer | ProbabilityAnswer;
|
|
210
|
+
|
|
211
|
+
/** @category schemas
|
|
212
|
+
* @since 0.1.0
|
|
213
|
+
*/
|
|
214
|
+
export const Usage = Schema.Struct({
|
|
215
|
+
inputTokens: Schema.NullOr(Schema.Natural),
|
|
216
|
+
outputTokens: Schema.NullOr(Schema.Natural),
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
/** @category models
|
|
220
|
+
* @since 0.1.0
|
|
221
|
+
*/
|
|
222
|
+
export type Usage = typeof Usage.Type;
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Provider-namespaced evidence that has no shared interpretation. Consumers
|
|
226
|
+
* decode a namespace with its provider's schema before using its contents.
|
|
227
|
+
*
|
|
228
|
+
* @category schemas
|
|
229
|
+
* @since 0.1.0
|
|
230
|
+
*/
|
|
231
|
+
export const ProviderMetadata = Schema.Record(Schema.String, Schema.JsonObject);
|
|
232
|
+
|
|
233
|
+
/** @category models
|
|
234
|
+
* @since 0.1.0
|
|
235
|
+
*/
|
|
236
|
+
export type ProviderMetadata = typeof ProviderMetadata.Type;
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* The wire response shape. `DecisionModel.evaluate` additionally validates
|
|
240
|
+
* answer IDs, question kinds, criteria, distributions, and score correlations.
|
|
241
|
+
*
|
|
242
|
+
* @category schemas
|
|
243
|
+
* @since 0.1.0
|
|
244
|
+
*/
|
|
245
|
+
export const EvaluateResponse = Schema.Struct({
|
|
246
|
+
provider: Schema.NonEmptyString,
|
|
247
|
+
model: Schema.String,
|
|
248
|
+
answers: Schema.Record(Schema.String, Answer),
|
|
249
|
+
usage: Usage,
|
|
250
|
+
providerMetadata: Schema.optionalKey(ProviderMetadata),
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
type ChoiceAnswerFor<Criteria> = Criteria extends unknown
|
|
254
|
+
? Omit<typeof ChoiceAnswer.Type, "choice" | "probabilities"> & {
|
|
255
|
+
readonly choice: `${Extract<keyof Criteria, string | number>}`;
|
|
256
|
+
readonly probabilities: {
|
|
257
|
+
readonly [
|
|
258
|
+
K in keyof Criteria as K extends string | number ? `${K}` : never
|
|
259
|
+
]: {} extends Pick<Criteria, K> ? number | undefined : number;
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
: never;
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Infer a question's answer, preserving optional choice criteria in its probabilities.
|
|
266
|
+
*
|
|
267
|
+
* @category models
|
|
268
|
+
* @since 0.1.0
|
|
269
|
+
*/
|
|
270
|
+
export type AnswerFor<Q extends Question> = Q extends ChoiceQuestion
|
|
271
|
+
? ChoiceAnswerFor<Q["criteria"]>
|
|
272
|
+
: Q extends ScoreQuestion
|
|
273
|
+
? ScoreAnswer
|
|
274
|
+
: ProbabilityAnswer;
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* One answer for each required question. Optional properties and open string,
|
|
278
|
+
* numeric, or template-pattern indexes require checking an entry for absence.
|
|
279
|
+
*
|
|
280
|
+
* @category models
|
|
281
|
+
* @since 0.1.0
|
|
282
|
+
*/
|
|
283
|
+
export type Answers<Q extends Questions> = {
|
|
284
|
+
readonly [K in keyof Q as K extends string | number ? `${K}` : never]: {} extends Pick<Q, K>
|
|
285
|
+
? AnswerFor<NonNullable<Q[K]>> | undefined
|
|
286
|
+
: AnswerFor<Q[K]>;
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
/** @category models
|
|
290
|
+
* @since 0.1.0
|
|
291
|
+
*/
|
|
292
|
+
export type EvaluateResponse<Q extends Questions = Questions> = Omit<
|
|
293
|
+
typeof EvaluateResponse.Type,
|
|
294
|
+
"answers"
|
|
295
|
+
> & { readonly answers: Answers<Q> };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reusable question collections with a schema-defined input. The set owns no
|
|
3
|
+
* provider, live resources, routing policy, or execution state.
|
|
4
|
+
*
|
|
5
|
+
* @since 0.1.0
|
|
6
|
+
*/
|
|
7
|
+
import type * as Schema from "effect/Schema";
|
|
8
|
+
|
|
9
|
+
import type * as DecisionSchema from "./DecisionSchema.ts";
|
|
10
|
+
|
|
11
|
+
/** @category models
|
|
12
|
+
* @since 0.1.0
|
|
13
|
+
*/
|
|
14
|
+
export interface DecisionSet<Input extends Schema.Top, Questions extends DecisionSchema.Questions> {
|
|
15
|
+
/** The encoded input becomes model-visible state: a string, JSON object, or JSON array. */
|
|
16
|
+
readonly input: Input;
|
|
17
|
+
/** Independent questions evaluated together against that state. */
|
|
18
|
+
readonly questions: Questions;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Describe a reusable assessment. Construction performs no encoding or model
|
|
23
|
+
* I/O. Input encoding and question validation occur inside model.evaluate.
|
|
24
|
+
* Treat nested instruction content as readonly; evaluation snapshots it before
|
|
25
|
+
* calling the provider. Dynamic question records are supported.
|
|
26
|
+
*
|
|
27
|
+
* @category constructors
|
|
28
|
+
* @since 0.1.0
|
|
29
|
+
*/
|
|
30
|
+
export const make = <Input extends Schema.Top, const Questions extends DecisionSchema.Questions>(
|
|
31
|
+
options: DecisionSet<Input, Questions>,
|
|
32
|
+
): DecisionSet<Input, Questions> =>
|
|
33
|
+
Object.freeze({
|
|
34
|
+
input: options.input,
|
|
35
|
+
questions: Object.freeze({ ...options.questions }),
|
|
36
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
import type * as SchemaAST from "effect/SchemaAST";
|
|
3
|
+
|
|
4
|
+
import * as DecisionSchema from "../DecisionSchema.ts";
|
|
5
|
+
|
|
6
|
+
// Permit floating-point serialization error without changing provider values.
|
|
7
|
+
const tolerance = 1e-6;
|
|
8
|
+
|
|
9
|
+
const probabilitySum = Schema.makeFilter(
|
|
10
|
+
(probabilities: Readonly<Record<string, number>>) =>
|
|
11
|
+
Math.abs(Object.values(probabilities).reduce((sum, value) => sum + value, 0) - 1) <= tolerance,
|
|
12
|
+
{ expected: "probabilities summing to 1 (within 1e-6)" },
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const distribution = (
|
|
16
|
+
keys: ReadonlyArray<string>,
|
|
17
|
+
sumCheck: SchemaAST.Check<Readonly<Record<string, number>>> = probabilitySum,
|
|
18
|
+
) => Schema.Record(Schema.Literals(keys), DecisionSchema.Probability).check(sumCheck);
|
|
19
|
+
|
|
20
|
+
const answerFor = (
|
|
21
|
+
question: DecisionSchema.Question,
|
|
22
|
+
choiceProbabilitySum: SchemaAST.Check<Readonly<Record<string, number>>> | undefined,
|
|
23
|
+
) => {
|
|
24
|
+
switch (question.type) {
|
|
25
|
+
case "choice": {
|
|
26
|
+
const keys = Object.keys(question.criteria);
|
|
27
|
+
|
|
28
|
+
return Schema.Struct({
|
|
29
|
+
...DecisionSchema.ChoiceAnswer.fields,
|
|
30
|
+
choice: Schema.Literals(keys),
|
|
31
|
+
probabilities: distribution(keys, choiceProbabilitySum),
|
|
32
|
+
}).check(
|
|
33
|
+
Schema.makeFilter(
|
|
34
|
+
({ choice, probabilities }) =>
|
|
35
|
+
Object.values(probabilities).every((value) => value <= probabilities[choice]),
|
|
36
|
+
{ expected: "a highest-probability choice" },
|
|
37
|
+
),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
case "score": {
|
|
41
|
+
const maxLevel = question.criteria.length - 1;
|
|
42
|
+
|
|
43
|
+
const levels = question.criteria.map(
|
|
44
|
+
(description, index) => [String(index), description] as const,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
return Schema.Struct({
|
|
48
|
+
...DecisionSchema.ScoreAnswer.fields,
|
|
49
|
+
score: Schema.Finite.check(Schema.isBetween({ minimum: 0, maximum: maxLevel })),
|
|
50
|
+
legend: Schema.Struct(
|
|
51
|
+
Object.fromEntries(
|
|
52
|
+
levels.map(([key, description]) => [key, Schema.Literal(description)]),
|
|
53
|
+
),
|
|
54
|
+
),
|
|
55
|
+
probabilities: distribution(levels.map(([key]) => key)),
|
|
56
|
+
}).check(
|
|
57
|
+
Schema.makeFilter(
|
|
58
|
+
({ score, probabilities }) =>
|
|
59
|
+
Math.abs(
|
|
60
|
+
score -
|
|
61
|
+
Object.entries(probabilities).reduce(
|
|
62
|
+
(sum, [level, probability]) => sum + Number(level) * probability,
|
|
63
|
+
0,
|
|
64
|
+
),
|
|
65
|
+
) <=
|
|
66
|
+
tolerance * Math.max(1, maxLevel),
|
|
67
|
+
{ expected: "the probability-weighted score (within 1e-6 per level)" },
|
|
68
|
+
),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
case "probability":
|
|
72
|
+
return DecisionSchema.ProbabilityAnswer;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// The overload describes the dependent type enforced by the literal keys and
|
|
77
|
+
// per-question schemas below. No JSON value is asserted to have that type.
|
|
78
|
+
export function responseFor<const Q extends DecisionSchema.Questions>(
|
|
79
|
+
questions: Q,
|
|
80
|
+
choiceProbabilitySum?: SchemaAST.Check<Readonly<Record<string, number>>>,
|
|
81
|
+
): Schema.Codec<DecisionSchema.EvaluateResponse<Q>>;
|
|
82
|
+
|
|
83
|
+
export function responseFor(
|
|
84
|
+
questions: DecisionSchema.Questions,
|
|
85
|
+
choiceProbabilitySum?: SchemaAST.Check<Readonly<Record<string, number>>>,
|
|
86
|
+
): Schema.Top {
|
|
87
|
+
return Schema.Struct({
|
|
88
|
+
...DecisionSchema.EvaluateResponse.fields,
|
|
89
|
+
answers: Schema.Struct(
|
|
90
|
+
Object.fromEntries(
|
|
91
|
+
Object.entries(questions).map(([id, question]) => [
|
|
92
|
+
id,
|
|
93
|
+
answerFor(question, choiceProbabilitySum),
|
|
94
|
+
]),
|
|
95
|
+
),
|
|
96
|
+
),
|
|
97
|
+
});
|
|
98
|
+
}
|