@dogpile/sdk 0.1.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/CHANGELOG.md +37 -0
- package/LICENSE +16 -0
- package/README.md +842 -0
- package/dist/browser/index.d.ts +8 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +4493 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/openai-compatible.d.ts +44 -0
- package/dist/providers/openai-compatible.d.ts.map +1 -0
- package/dist/providers/openai-compatible.js +305 -0
- package/dist/providers/openai-compatible.js.map +1 -0
- package/dist/runtime/broadcast.d.ts +18 -0
- package/dist/runtime/broadcast.d.ts.map +1 -0
- package/dist/runtime/broadcast.js +335 -0
- package/dist/runtime/broadcast.js.map +1 -0
- package/dist/runtime/cancellation.d.ts +6 -0
- package/dist/runtime/cancellation.d.ts.map +1 -0
- package/dist/runtime/cancellation.js +35 -0
- package/dist/runtime/cancellation.js.map +1 -0
- package/dist/runtime/coordinator.d.ts +18 -0
- package/dist/runtime/coordinator.d.ts.map +1 -0
- package/dist/runtime/coordinator.js +434 -0
- package/dist/runtime/coordinator.js.map +1 -0
- package/dist/runtime/decisions.d.ts +5 -0
- package/dist/runtime/decisions.d.ts.map +1 -0
- package/dist/runtime/decisions.js +31 -0
- package/dist/runtime/decisions.js.map +1 -0
- package/dist/runtime/defaults.d.ts +63 -0
- package/dist/runtime/defaults.d.ts.map +1 -0
- package/dist/runtime/defaults.js +426 -0
- package/dist/runtime/defaults.js.map +1 -0
- package/dist/runtime/engine.d.ts +79 -0
- package/dist/runtime/engine.d.ts.map +1 -0
- package/dist/runtime/engine.js +723 -0
- package/dist/runtime/engine.js.map +1 -0
- package/dist/runtime/model.d.ts +14 -0
- package/dist/runtime/model.d.ts.map +1 -0
- package/dist/runtime/model.js +82 -0
- package/dist/runtime/model.js.map +1 -0
- package/dist/runtime/sequential.d.ts +18 -0
- package/dist/runtime/sequential.d.ts.map +1 -0
- package/dist/runtime/sequential.js +277 -0
- package/dist/runtime/sequential.js.map +1 -0
- package/dist/runtime/shared.d.ts +18 -0
- package/dist/runtime/shared.d.ts.map +1 -0
- package/dist/runtime/shared.js +288 -0
- package/dist/runtime/shared.js.map +1 -0
- package/dist/runtime/termination.d.ts +77 -0
- package/dist/runtime/termination.d.ts.map +1 -0
- package/dist/runtime/termination.js +355 -0
- package/dist/runtime/termination.js.map +1 -0
- package/dist/runtime/tools.d.ts +314 -0
- package/dist/runtime/tools.d.ts.map +1 -0
- package/dist/runtime/tools.js +969 -0
- package/dist/runtime/tools.js.map +1 -0
- package/dist/runtime/validation.d.ts +23 -0
- package/dist/runtime/validation.d.ts.map +1 -0
- package/dist/runtime/validation.js +656 -0
- package/dist/runtime/validation.js.map +1 -0
- package/dist/types.d.ts +2434 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +81 -0
- package/dist/types.js.map +1 -0
- package/package.json +157 -0
- package/src/browser/index.ts +7 -0
- package/src/index.ts +195 -0
- package/src/providers/openai-compatible.ts +406 -0
- package/src/runtime/broadcast.test.ts +355 -0
- package/src/runtime/broadcast.ts +428 -0
- package/src/runtime/cancellation.ts +40 -0
- package/src/runtime/coordinator.test.ts +468 -0
- package/src/runtime/coordinator.ts +581 -0
- package/src/runtime/decisions.ts +38 -0
- package/src/runtime/defaults.ts +547 -0
- package/src/runtime/engine.ts +880 -0
- package/src/runtime/model.ts +117 -0
- package/src/runtime/sequential.test.ts +262 -0
- package/src/runtime/sequential.ts +357 -0
- package/src/runtime/shared.test.ts +265 -0
- package/src/runtime/shared.ts +367 -0
- package/src/runtime/termination.ts +463 -0
- package/src/runtime/tools.ts +1518 -0
- package/src/runtime/validation.ts +771 -0
- package/src/types.ts +2729 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BudgetStopReason,
|
|
3
|
+
BudgetTerminationCondition,
|
|
4
|
+
ConvergenceTerminationCondition,
|
|
5
|
+
FirstOfTerminationCondition,
|
|
6
|
+
FirstOfTerminationConditions,
|
|
7
|
+
FirstOfTerminationOutput,
|
|
8
|
+
JudgeEvaluationDecision,
|
|
9
|
+
JudgeStopReason,
|
|
10
|
+
JudgeTerminationCondition,
|
|
11
|
+
JsonObject,
|
|
12
|
+
NormalizedStopReason,
|
|
13
|
+
TerminationStopRecord,
|
|
14
|
+
StopTerminationDecision,
|
|
15
|
+
TerminationCondition,
|
|
16
|
+
TerminationDecision,
|
|
17
|
+
TerminationEvaluationContext,
|
|
18
|
+
TranscriptEntry
|
|
19
|
+
} from "../types.js";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create a budget termination condition.
|
|
23
|
+
*
|
|
24
|
+
* The returned object is JSON-serializable and can be used directly in
|
|
25
|
+
* `terminate` or composed with {@link firstOf}.
|
|
26
|
+
*/
|
|
27
|
+
export function budget(options: Omit<BudgetTerminationCondition, "kind">): BudgetTerminationCondition {
|
|
28
|
+
return {
|
|
29
|
+
kind: "budget",
|
|
30
|
+
...options
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create a convergence termination condition.
|
|
36
|
+
*
|
|
37
|
+
* The condition fires when the run has produced `stableTurns` sufficiently
|
|
38
|
+
* similar protocol outputs.
|
|
39
|
+
*/
|
|
40
|
+
export function convergence(options: Omit<ConvergenceTerminationCondition, "kind">): ConvergenceTerminationCondition {
|
|
41
|
+
return {
|
|
42
|
+
kind: "convergence",
|
|
43
|
+
...options
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Create a judge termination condition.
|
|
49
|
+
*
|
|
50
|
+
* The rubric is stored as serializable configuration so callers can replay or
|
|
51
|
+
* persist traces without SDK-owned state.
|
|
52
|
+
*/
|
|
53
|
+
export function judge(options: Omit<JudgeTerminationCondition, "kind">): JudgeTerminationCondition {
|
|
54
|
+
return {
|
|
55
|
+
kind: "judge",
|
|
56
|
+
...options
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Compose termination conditions so whichever child fires first wins.
|
|
62
|
+
*
|
|
63
|
+
* Conditions are evaluated in the order supplied by the caller. At least one
|
|
64
|
+
* condition is required so the composite is always meaningful and the public
|
|
65
|
+
* type remains a non-empty tuple.
|
|
66
|
+
*/
|
|
67
|
+
export function firstOf(...conditions: FirstOfTerminationConditions): FirstOfTerminationCondition {
|
|
68
|
+
if (conditions.length === 0) {
|
|
69
|
+
throw new RangeError("firstOf requires at least one termination condition.");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
kind: "firstOf",
|
|
74
|
+
conditions
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Evaluate a serializable termination condition against the current run state.
|
|
80
|
+
*
|
|
81
|
+
* Budget, convergence, judge, and firstOf conditions are enforced from their
|
|
82
|
+
* own normalized inputs so one stop class cannot accidentally satisfy another.
|
|
83
|
+
*/
|
|
84
|
+
export function evaluateTermination(
|
|
85
|
+
condition: TerminationCondition,
|
|
86
|
+
context: TerminationEvaluationContext
|
|
87
|
+
): TerminationDecision {
|
|
88
|
+
switch (condition.kind) {
|
|
89
|
+
case "budget":
|
|
90
|
+
return evaluateBudget(condition, context);
|
|
91
|
+
case "firstOf":
|
|
92
|
+
return evaluateFirstOf(condition, context).decision;
|
|
93
|
+
case "convergence":
|
|
94
|
+
return evaluateConvergence(condition, context);
|
|
95
|
+
case "judge":
|
|
96
|
+
return evaluateJudge(condition, context);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Evaluate an ordered firstOf composition and return the winning child, if any.
|
|
102
|
+
*/
|
|
103
|
+
export function evaluateFirstOf(
|
|
104
|
+
condition: FirstOfTerminationCondition,
|
|
105
|
+
context: TerminationEvaluationContext
|
|
106
|
+
): FirstOfTerminationOutput {
|
|
107
|
+
const evaluated: TerminationDecision[] = [];
|
|
108
|
+
|
|
109
|
+
for (const [index, child] of condition.conditions.entries()) {
|
|
110
|
+
const decision = evaluateTermination(child, context);
|
|
111
|
+
evaluated.push(decision);
|
|
112
|
+
|
|
113
|
+
if (decision.type === "stop") {
|
|
114
|
+
return {
|
|
115
|
+
kind: "firstOf-output",
|
|
116
|
+
decision,
|
|
117
|
+
winningConditionIndex: index,
|
|
118
|
+
evaluated
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
kind: "firstOf-output",
|
|
125
|
+
decision: { type: "continue", condition },
|
|
126
|
+
winningConditionIndex: null,
|
|
127
|
+
evaluated
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Evaluate a termination condition and return a trace-ready stop record.
|
|
133
|
+
*
|
|
134
|
+
* Protocol runners use this helper so the first policy decision that halts a
|
|
135
|
+
* run is recorded exactly once on the terminal event.
|
|
136
|
+
*/
|
|
137
|
+
export function evaluateTerminationStop(
|
|
138
|
+
condition: TerminationCondition,
|
|
139
|
+
context: TerminationEvaluationContext
|
|
140
|
+
): TerminationStopRecord | null {
|
|
141
|
+
if (condition.kind === "firstOf") {
|
|
142
|
+
const output = evaluateFirstOf(condition, context);
|
|
143
|
+
if (output.decision.type !== "stop" || output.winningConditionIndex === null) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const winningCondition = condition.conditions[output.winningConditionIndex];
|
|
148
|
+
if (!winningCondition) {
|
|
149
|
+
throw new RangeError("firstOf stop referenced a missing winning condition.");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return stopRecord(condition, output.decision, {
|
|
153
|
+
kind: "firstOf-stop",
|
|
154
|
+
winningConditionIndex: output.winningConditionIndex,
|
|
155
|
+
winningCondition,
|
|
156
|
+
firedCondition: output.decision.condition,
|
|
157
|
+
evaluated: output.evaluated
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const decision = evaluateTermination(condition, context);
|
|
162
|
+
if (decision.type !== "stop") {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return stopRecord(condition, decision);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Combine independently evaluated termination decisions with SDK precedence.
|
|
171
|
+
*
|
|
172
|
+
* Budget caps win over judge decisions, and judge decisions win over
|
|
173
|
+
* convergence. This keeps simultaneous stops deterministic while preserving
|
|
174
|
+
* each evaluator's normalized stop reason on the returned decision.
|
|
175
|
+
*/
|
|
176
|
+
export function combineTerminationDecisions(
|
|
177
|
+
decisions: readonly TerminationDecision[]
|
|
178
|
+
): TerminationDecision {
|
|
179
|
+
const stopDecisions = decisions.filter((decision): decision is StopTerminationDecision => decision.type === "stop");
|
|
180
|
+
if (stopDecisions.length === 0) {
|
|
181
|
+
const firstDecision = decisions[0];
|
|
182
|
+
if (!firstDecision) {
|
|
183
|
+
throw new RangeError("combineTerminationDecisions requires at least one decision.");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return firstDecision;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return stopDecisions.reduce((winner, candidate) =>
|
|
190
|
+
stopPrecedence(candidate.normalizedReason) < stopPrecedence(winner.normalizedReason) ? candidate : winner
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Evaluate cost, token, iteration, and timeout caps for a budget condition.
|
|
196
|
+
*/
|
|
197
|
+
export function evaluateBudget(
|
|
198
|
+
condition: BudgetTerminationCondition,
|
|
199
|
+
context: TerminationEvaluationContext
|
|
200
|
+
): TerminationDecision {
|
|
201
|
+
const iteration = context.iteration ?? context.transcript.length;
|
|
202
|
+
const elapsedMs = context.elapsedMs ?? 0;
|
|
203
|
+
|
|
204
|
+
const costStop = stopIfReached(condition, "maxUsd", "cost", context.cost.usd);
|
|
205
|
+
if (costStop) {
|
|
206
|
+
return costStop;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const tokenStop = stopIfReached(condition, "maxTokens", "tokens", context.cost.totalTokens);
|
|
210
|
+
if (tokenStop) {
|
|
211
|
+
return tokenStop;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const iterationStop = stopIfReached(condition, "maxIterations", "iterations", iteration);
|
|
215
|
+
if (iterationStop) {
|
|
216
|
+
return iterationStop;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const timeoutStop = stopIfReached(condition, "timeoutMs", "timeout", elapsedMs);
|
|
220
|
+
if (timeoutStop) {
|
|
221
|
+
return timeoutStop;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return { type: "continue", condition };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Evaluate protocol-level convergence from recent coordination outputs.
|
|
229
|
+
*
|
|
230
|
+
* This intentionally ignores budget caps and judge quality state. Budget and
|
|
231
|
+
* judge conditions can be composed with convergence through `firstOf`, but a
|
|
232
|
+
* convergence condition itself only reads protocol output signals.
|
|
233
|
+
*/
|
|
234
|
+
export function evaluateConvergence(
|
|
235
|
+
condition: ConvergenceTerminationCondition,
|
|
236
|
+
context: TerminationEvaluationContext
|
|
237
|
+
): TerminationDecision {
|
|
238
|
+
const stableTurns = Math.max(1, Math.ceil(condition.stableTurns));
|
|
239
|
+
if (context.transcript.length < stableTurns) {
|
|
240
|
+
return { type: "continue", condition };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const recentEntries = context.transcript.slice(-stableTurns);
|
|
244
|
+
const recentOutputs = recentEntries.map((entry) => entry.output);
|
|
245
|
+
const similarities = consecutiveSimilarities(recentEntries);
|
|
246
|
+
const observedSimilarity = similarities.length === 0 ? 1 : Math.min(...similarities);
|
|
247
|
+
|
|
248
|
+
if (observedSimilarity < condition.minSimilarity) {
|
|
249
|
+
return { type: "continue", condition };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
type: "stop",
|
|
254
|
+
condition,
|
|
255
|
+
reason: "convergence",
|
|
256
|
+
normalizedReason: "convergence",
|
|
257
|
+
detail: {
|
|
258
|
+
protocol: context.protocol,
|
|
259
|
+
stableTurns,
|
|
260
|
+
minSimilarity: condition.minSimilarity,
|
|
261
|
+
observedSimilarity,
|
|
262
|
+
outputs: recentOutputs
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Evaluate caller-owned judge state without reading budget or convergence data.
|
|
269
|
+
*
|
|
270
|
+
* Explicit accept/reject verdicts always halt. Score-only decisions halt when
|
|
271
|
+
* they meet `minScore`; when `minScore` is omitted, any score-only decision is
|
|
272
|
+
* treated as the judge's terminal decision.
|
|
273
|
+
*/
|
|
274
|
+
export function evaluateJudge(
|
|
275
|
+
condition: JudgeTerminationCondition,
|
|
276
|
+
context: TerminationEvaluationContext
|
|
277
|
+
): TerminationDecision {
|
|
278
|
+
const decision = context.judgeDecision ?? scoreDecisionFromQuality(context.quality);
|
|
279
|
+
if (!decision) {
|
|
280
|
+
return { type: "continue", condition };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
switch (decision.type) {
|
|
284
|
+
case "accept":
|
|
285
|
+
return judgeStop(condition, "accepted", decision);
|
|
286
|
+
case "reject":
|
|
287
|
+
return judgeStop(condition, "rejected", decision);
|
|
288
|
+
case "score": {
|
|
289
|
+
const minScore = condition.minScore;
|
|
290
|
+
if (minScore !== undefined && decision.score < minScore) {
|
|
291
|
+
return { type: "continue", condition };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return judgeStop(condition, "score-threshold", decision, minScore);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function stopIfReached(
|
|
300
|
+
condition: BudgetTerminationCondition,
|
|
301
|
+
cap: "maxUsd" | "maxTokens" | "maxIterations" | "timeoutMs",
|
|
302
|
+
reason: BudgetStopReason,
|
|
303
|
+
observed: number
|
|
304
|
+
): StopTerminationDecision | null {
|
|
305
|
+
const limit = condition[cap];
|
|
306
|
+
if (limit === undefined || observed < limit) {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
type: "stop",
|
|
312
|
+
condition,
|
|
313
|
+
reason: "budget",
|
|
314
|
+
normalizedReason: normalizeBudgetStopReason(reason),
|
|
315
|
+
budgetReason: reason,
|
|
316
|
+
detail: {
|
|
317
|
+
cap,
|
|
318
|
+
limit,
|
|
319
|
+
observed
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function scoreDecisionFromQuality(quality: number | undefined): JudgeEvaluationDecision | null {
|
|
325
|
+
if (quality === undefined) {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
type: "score",
|
|
331
|
+
score: quality
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function judgeStop(
|
|
336
|
+
condition: JudgeTerminationCondition,
|
|
337
|
+
judgeReason: JudgeStopReason,
|
|
338
|
+
decision: JudgeEvaluationDecision,
|
|
339
|
+
minScore?: number
|
|
340
|
+
): StopTerminationDecision {
|
|
341
|
+
return {
|
|
342
|
+
type: "stop",
|
|
343
|
+
condition,
|
|
344
|
+
reason: "judge",
|
|
345
|
+
normalizedReason: normalizeJudgeStopReason(judgeReason),
|
|
346
|
+
judgeReason,
|
|
347
|
+
detail: judgeStopDetail(decision, minScore)
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function normalizeBudgetStopReason(reason: BudgetStopReason): NormalizedStopReason {
|
|
352
|
+
switch (reason) {
|
|
353
|
+
case "cost":
|
|
354
|
+
return "budget:cost";
|
|
355
|
+
case "tokens":
|
|
356
|
+
return "budget:tokens";
|
|
357
|
+
case "iterations":
|
|
358
|
+
return "budget:iterations";
|
|
359
|
+
case "timeout":
|
|
360
|
+
return "budget:timeout";
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function normalizeJudgeStopReason(reason: JudgeStopReason): NormalizedStopReason {
|
|
365
|
+
switch (reason) {
|
|
366
|
+
case "accepted":
|
|
367
|
+
return "judge:accepted";
|
|
368
|
+
case "rejected":
|
|
369
|
+
return "judge:rejected";
|
|
370
|
+
case "score-threshold":
|
|
371
|
+
return "judge:score-threshold";
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function stopPrecedence(reason: NormalizedStopReason): number {
|
|
376
|
+
if (reason.startsWith("budget:")) {
|
|
377
|
+
return 0;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (reason.startsWith("judge:")) {
|
|
381
|
+
return 1;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return 2;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function judgeStopDetail(decision: JudgeEvaluationDecision, minScore?: number): JsonObject {
|
|
388
|
+
return {
|
|
389
|
+
decision: decision.type,
|
|
390
|
+
...(decision.score !== undefined ? { score: decision.score } : {}),
|
|
391
|
+
...(minScore !== undefined ? { minScore } : {}),
|
|
392
|
+
...(decision.rationale !== undefined ? { rationale: decision.rationale } : {}),
|
|
393
|
+
...(decision.metadata !== undefined ? { metadata: decision.metadata } : {})
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function stopRecord(
|
|
398
|
+
rootCondition: TerminationCondition,
|
|
399
|
+
decision: StopTerminationDecision,
|
|
400
|
+
firstOfRecord?: NonNullable<TerminationStopRecord["firstOf"]>
|
|
401
|
+
): TerminationStopRecord {
|
|
402
|
+
return {
|
|
403
|
+
kind: "termination-stop",
|
|
404
|
+
rootCondition,
|
|
405
|
+
firedCondition: decision.condition,
|
|
406
|
+
reason: decision.reason,
|
|
407
|
+
normalizedReason: decision.normalizedReason,
|
|
408
|
+
...(decision.budgetReason !== undefined ? { budgetReason: decision.budgetReason } : {}),
|
|
409
|
+
...(decision.judgeReason !== undefined ? { judgeReason: decision.judgeReason } : {}),
|
|
410
|
+
...(decision.detail !== undefined ? { detail: decision.detail } : {}),
|
|
411
|
+
...(firstOfRecord !== undefined ? { firstOf: firstOfRecord } : {})
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function consecutiveSimilarities(entries: readonly TranscriptEntry[]): readonly number[] {
|
|
416
|
+
const similarities: number[] = [];
|
|
417
|
+
|
|
418
|
+
for (let index = 1; index < entries.length; index += 1) {
|
|
419
|
+
const previous = entries[index - 1];
|
|
420
|
+
const current = entries[index];
|
|
421
|
+
if (previous && current) {
|
|
422
|
+
similarities.push(outputSimilarity(previous.output, current.output));
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return similarities;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function outputSimilarity(left: string, right: string): number {
|
|
430
|
+
const normalizedLeft = normalizeOutput(left);
|
|
431
|
+
const normalizedRight = normalizeOutput(right);
|
|
432
|
+
|
|
433
|
+
if (normalizedLeft === normalizedRight) {
|
|
434
|
+
return 1;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const leftTokens = tokenize(normalizedLeft);
|
|
438
|
+
const rightTokens = tokenize(normalizedRight);
|
|
439
|
+
if (leftTokens.length === 0 || rightTokens.length === 0) {
|
|
440
|
+
return 0;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const leftSet = new Set(leftTokens);
|
|
444
|
+
const rightSet = new Set(rightTokens);
|
|
445
|
+
let intersection = 0;
|
|
446
|
+
|
|
447
|
+
for (const token of leftSet) {
|
|
448
|
+
if (rightSet.has(token)) {
|
|
449
|
+
intersection += 1;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const union = new Set([...leftSet, ...rightSet]).size;
|
|
454
|
+
return union === 0 ? 0 : intersection / union;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function normalizeOutput(output: string): string {
|
|
458
|
+
return output.trim().toLowerCase();
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function tokenize(output: string): readonly string[] {
|
|
462
|
+
return output.split(/[^a-z0-9]+/u).filter((token) => token.length > 0);
|
|
463
|
+
}
|