@mastra/evals 0.0.0-commonjs-20250227130920

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.
Files changed (95) hide show
  1. package/.turbo/turbo-build.log +37 -0
  2. package/CHANGELOG.md +777 -0
  3. package/LICENSE +44 -0
  4. package/README.md +185 -0
  5. package/dist/_tsup-dts-rollup.d.cts +764 -0
  6. package/dist/_tsup-dts-rollup.d.ts +764 -0
  7. package/dist/chunk-4VNS5WPM.js +37 -0
  8. package/dist/chunk-TXXJUIES.js +15 -0
  9. package/dist/dist-MSGH6PAG.js +17947 -0
  10. package/dist/index.cjs +19675 -0
  11. package/dist/index.d.cts +3 -0
  12. package/dist/index.d.ts +3 -0
  13. package/dist/index.js +86 -0
  14. package/dist/magic-string.es-5UDOWOAZ.js +1296 -0
  15. package/dist/metrics/judge/index.cjs +17 -0
  16. package/dist/metrics/judge/index.d.cts +1 -0
  17. package/dist/metrics/judge/index.d.ts +1 -0
  18. package/dist/metrics/judge/index.js +2 -0
  19. package/dist/metrics/llm/index.cjs +2482 -0
  20. package/dist/metrics/llm/index.d.cts +11 -0
  21. package/dist/metrics/llm/index.d.ts +11 -0
  22. package/dist/metrics/llm/index.js +2459 -0
  23. package/dist/metrics/nlp/index.cjs +202 -0
  24. package/dist/metrics/nlp/index.d.cts +5 -0
  25. package/dist/metrics/nlp/index.d.ts +5 -0
  26. package/dist/metrics/nlp/index.js +189 -0
  27. package/eslint.config.js +6 -0
  28. package/package.json +89 -0
  29. package/src/attachListeners.ts +40 -0
  30. package/src/constants.ts +1 -0
  31. package/src/evaluation.test.ts +29 -0
  32. package/src/evaluation.ts +58 -0
  33. package/src/index.ts +2 -0
  34. package/src/metrics/index.ts +3 -0
  35. package/src/metrics/judge/index.ts +14 -0
  36. package/src/metrics/llm/answer-relevancy/index.test.ts +156 -0
  37. package/src/metrics/llm/answer-relevancy/index.ts +58 -0
  38. package/src/metrics/llm/answer-relevancy/metricJudge.ts +56 -0
  39. package/src/metrics/llm/answer-relevancy/prompts.ts +214 -0
  40. package/src/metrics/llm/bias/index.test.ts +152 -0
  41. package/src/metrics/llm/bias/index.ts +52 -0
  42. package/src/metrics/llm/bias/metricJudge.ts +53 -0
  43. package/src/metrics/llm/bias/prompts.ts +109 -0
  44. package/src/metrics/llm/context-position/index.test.ts +275 -0
  45. package/src/metrics/llm/context-position/index.ts +69 -0
  46. package/src/metrics/llm/context-position/metricJudge.ts +55 -0
  47. package/src/metrics/llm/context-position/prompts.ts +135 -0
  48. package/src/metrics/llm/context-precision/index.test.ts +213 -0
  49. package/src/metrics/llm/context-precision/index.ts +68 -0
  50. package/src/metrics/llm/context-precision/metricJudge.ts +55 -0
  51. package/src/metrics/llm/context-precision/prompts.ts +139 -0
  52. package/src/metrics/llm/context-relevancy/index.test.ts +162 -0
  53. package/src/metrics/llm/context-relevancy/index.ts +59 -0
  54. package/src/metrics/llm/context-relevancy/metricJudge.ts +51 -0
  55. package/src/metrics/llm/context-relevancy/prompts.ts +111 -0
  56. package/src/metrics/llm/contextual-recall/index.test.ts +90 -0
  57. package/src/metrics/llm/contextual-recall/index.ts +56 -0
  58. package/src/metrics/llm/contextual-recall/metricJudge.ts +52 -0
  59. package/src/metrics/llm/contextual-recall/prompts.ts +82 -0
  60. package/src/metrics/llm/faithfulness/index.test.ts +254 -0
  61. package/src/metrics/llm/faithfulness/index.ts +59 -0
  62. package/src/metrics/llm/faithfulness/metricJudge.ts +62 -0
  63. package/src/metrics/llm/faithfulness/prompts.ts +166 -0
  64. package/src/metrics/llm/hallucination/index.test.ts +214 -0
  65. package/src/metrics/llm/hallucination/index.ts +59 -0
  66. package/src/metrics/llm/hallucination/metricJudge.ts +44 -0
  67. package/src/metrics/llm/hallucination/prompts.ts +143 -0
  68. package/src/metrics/llm/index.ts +11 -0
  69. package/src/metrics/llm/prompt-alignment/index.test.ts +335 -0
  70. package/src/metrics/llm/prompt-alignment/index.ts +116 -0
  71. package/src/metrics/llm/prompt-alignment/metricJudge.ts +43 -0
  72. package/src/metrics/llm/prompt-alignment/prompts.ts +224 -0
  73. package/src/metrics/llm/summarization/index.test.ts +274 -0
  74. package/src/metrics/llm/summarization/index.ts +72 -0
  75. package/src/metrics/llm/summarization/metricJudge.ts +101 -0
  76. package/src/metrics/llm/summarization/prompts.ts +252 -0
  77. package/src/metrics/llm/toxicity/index.test.ts +84 -0
  78. package/src/metrics/llm/toxicity/index.ts +54 -0
  79. package/src/metrics/llm/toxicity/metricJudge.ts +39 -0
  80. package/src/metrics/llm/toxicity/prompts.ts +87 -0
  81. package/src/metrics/llm/types.ts +7 -0
  82. package/src/metrics/llm/utils.ts +20 -0
  83. package/src/metrics/nlp/completeness/index.test.ts +98 -0
  84. package/src/metrics/nlp/completeness/index.ts +121 -0
  85. package/src/metrics/nlp/content-similarity/index.test.ts +76 -0
  86. package/src/metrics/nlp/content-similarity/index.ts +49 -0
  87. package/src/metrics/nlp/index.ts +5 -0
  88. package/src/metrics/nlp/keyword-coverage/index.test.ts +85 -0
  89. package/src/metrics/nlp/keyword-coverage/index.ts +49 -0
  90. package/src/metrics/nlp/textual-difference/index.test.ts +88 -0
  91. package/src/metrics/nlp/textual-difference/index.ts +38 -0
  92. package/src/metrics/nlp/tone/index.test.ts +100 -0
  93. package/src/metrics/nlp/tone/index.ts +55 -0
  94. package/tsconfig.json +5 -0
  95. package/vitest.config.ts +12 -0
@@ -0,0 +1,335 @@
1
+ import { createOpenAI } from '@ai-sdk/openai';
2
+ import { describe, it, expect } from 'vitest';
3
+
4
+ import type { TestCase } from '../utils';
5
+
6
+ import { PromptAlignmentMetric } from './index';
7
+
8
+ export type PromptAlignmentTestCase = TestCase & {
9
+ instructions: string[];
10
+ expectedResult: TestCase['expectedResult'] & {
11
+ scoreDetails: {
12
+ totalInstructions: number;
13
+ applicableInstructions: number;
14
+ followedInstructions: number;
15
+ naInstructions: number;
16
+ };
17
+ };
18
+ };
19
+
20
+ const openai = createOpenAI({
21
+ apiKey: process.env.OPENAI_API_KEY,
22
+ });
23
+
24
+ const model = openai('gpt-4o');
25
+
26
+ const testCases: PromptAlignmentTestCase[] = [
27
+ {
28
+ // Perfect alignment (score: 1.0)
29
+ instructions: ['Reply in all uppercase'],
30
+ input: 'What is the weather?',
31
+ output: 'THE WEATHER IS SUNNY TODAY.',
32
+ expectedResult: {
33
+ score: 1.0,
34
+ reason: 'The output follows the uppercase instruction perfectly',
35
+ scoreDetails: {
36
+ totalInstructions: 1,
37
+ applicableInstructions: 1,
38
+ followedInstructions: 1,
39
+ naInstructions: 0,
40
+ },
41
+ },
42
+ },
43
+ {
44
+ // Zero alignment (score: 0)
45
+ instructions: ['Reply in all uppercase'],
46
+ input: 'What is the weather?',
47
+ output: 'The weather is sunny today.',
48
+ expectedResult: {
49
+ score: 0,
50
+ reason: 'The output does not follow the uppercase instruction',
51
+ scoreDetails: {
52
+ totalInstructions: 1,
53
+ applicableInstructions: 1,
54
+ followedInstructions: 0,
55
+ naInstructions: 0,
56
+ },
57
+ },
58
+ },
59
+ {
60
+ // Multiple instructions - all followed (score: 1.0)
61
+ instructions: ['Reply in all uppercase', 'End with an exclamation mark'],
62
+ input: 'What is the weather?',
63
+ output: 'THE WEATHER IS SUNNY TODAY!',
64
+ expectedResult: {
65
+ score: 1.0,
66
+ reason: 'The output follows both uppercase and exclamation mark instructions',
67
+ scoreDetails: {
68
+ totalInstructions: 2,
69
+ applicableInstructions: 2,
70
+ followedInstructions: 2,
71
+ naInstructions: 0,
72
+ },
73
+ },
74
+ },
75
+ {
76
+ // Multiple instructions - partial follow (score: 0.5)
77
+ instructions: ['Reply in all uppercase', 'End with an exclamation mark'],
78
+ input: 'What is the weather?',
79
+ output: 'THE WEATHER IS SUNNY TODAY.',
80
+ expectedResult: {
81
+ score: 0.5,
82
+ reason: 'The output follows the uppercase instruction but lacks an exclamation mark',
83
+ scoreDetails: {
84
+ totalInstructions: 2,
85
+ applicableInstructions: 2,
86
+ followedInstructions: 1,
87
+ naInstructions: 0,
88
+ },
89
+ },
90
+ },
91
+ {
92
+ // Complex multiple instructions (score: 1.0)
93
+ instructions: ['Start with "Answer:"', 'Use exactly four sentences', 'End with a period'],
94
+ input: 'Describe the seasons.',
95
+ output: 'Answer: Spring brings flowers. Summer brings heat. Fall brings colors. Winter brings snow.',
96
+ expectedResult: {
97
+ score: 1.0,
98
+ reason: 'The output follows all formatting instructions precisely',
99
+ scoreDetails: {
100
+ totalInstructions: 3,
101
+ applicableInstructions: 3,
102
+ followedInstructions: 3,
103
+ naInstructions: 0,
104
+ },
105
+ },
106
+ },
107
+ {
108
+ // Empty output (score: 0)
109
+ instructions: ['Reply in all uppercase'],
110
+ input: 'What is the weather?',
111
+ output: '',
112
+ expectedResult: {
113
+ score: 0,
114
+ reason: 'Empty output cannot follow any instructions',
115
+ scoreDetails: {
116
+ totalInstructions: 1,
117
+ applicableInstructions: 1,
118
+ followedInstructions: 0,
119
+ naInstructions: 0,
120
+ },
121
+ },
122
+ },
123
+ {
124
+ // All instructions not applicable (score: 0)
125
+ instructions: ['Include transaction details', 'Show account balance', 'List recent payments'],
126
+ input: 'What is the weather in Paris?',
127
+ output: 'It is currently 22°C and sunny in Paris.',
128
+ expectedResult: {
129
+ score: 0.0,
130
+ reason: 'No instruction alignment possible - all instructions are not applicable for a weather query',
131
+ scoreDetails: {
132
+ totalInstructions: 3,
133
+ applicableInstructions: 0,
134
+ followedInstructions: 0,
135
+ naInstructions: 3,
136
+ },
137
+ },
138
+ },
139
+ {
140
+ // Mix of applicable and non-applicable instructions in email context (score: 0)
141
+ instructions: [
142
+ 'Include email signature',
143
+ 'Check account balance',
144
+ 'Use professional tone',
145
+ 'List transaction history',
146
+ ],
147
+ input: 'Write an email to schedule a meeting',
148
+ output: 'hey lets meet tmrw',
149
+ expectedResult: {
150
+ score: 0,
151
+ reason:
152
+ 'The output does not follow any of the applicable instructions - missing signature and uses informal tone',
153
+ scoreDetails: {
154
+ totalInstructions: 4,
155
+ applicableInstructions: 2,
156
+ followedInstructions: 0,
157
+ naInstructions: 2,
158
+ },
159
+ },
160
+ },
161
+ {
162
+ // Mix of yes, no, and n/a with domain-specific instructions (score: 0.5)
163
+ instructions: [
164
+ 'Include temperature in weather reports',
165
+ 'Mention wind conditions',
166
+ 'Show account balance',
167
+ 'Use proper English',
168
+ 'List recent payments',
169
+ 'End with a period',
170
+ ],
171
+ input: 'What is the weather in Paris?',
172
+ output: 'The temperature is 22°C in Paris.',
173
+ expectedResult: {
174
+ score: 0.75,
175
+ reason:
176
+ 'Three out of four applicable instructions followed (temperature, English, and period correct, but missing wind conditions)',
177
+ scoreDetails: {
178
+ totalInstructions: 6,
179
+ applicableInstructions: 4,
180
+ followedInstructions: 3,
181
+ naInstructions: 2,
182
+ },
183
+ },
184
+ },
185
+ {
186
+ instructions: [
187
+ 'Be concise and direct in your responses while maintaining a professional and friendly tone.',
188
+ 'When providing weather information, include temperature, conditions, and any relevant weather alerts.',
189
+ 'For transaction analysis, focus on patterns, unusual spending, and actionable insights.',
190
+ 'Always prioritize user privacy and security - never share sensitive information.',
191
+ 'Use natural, conversational language while maintaining professionalism.',
192
+ 'Provide specific, actionable recommendations when appropriate.',
193
+ 'Acknowledge uncertainty when present and avoid making assumptions.',
194
+ 'Break down complex information into digestible parts.',
195
+ 'Ask clarifying questions when user intent is unclear.',
196
+ 'Maintain consistent formatting in responses for better readability.',
197
+ ],
198
+ input: 'Get me some financial info',
199
+ output: `I'm here to help with weather information. If you need weather details for a specific location, please let me know the location, and I'll provide the current weather conditions for you.`,
200
+ expectedResult: {
201
+ score: 0.83,
202
+ reason: 'No instruction alignment possible - all instructions are not applicable for a weather query',
203
+ scoreDetails: {
204
+ totalInstructions: 10,
205
+ applicableInstructions: 6,
206
+ followedInstructions: 5,
207
+ naInstructions: 4,
208
+ },
209
+ },
210
+ },
211
+ ];
212
+
213
+ const SECONDS = 10000;
214
+
215
+ describe(
216
+ 'PromptAlignmentMetric',
217
+ () => {
218
+ it('should measure perfect alignment with single instruction', async () => {
219
+ const testCase = testCases[0]!;
220
+ const metric = new PromptAlignmentMetric(model, {
221
+ instructions: testCase.instructions,
222
+ });
223
+
224
+ const result = await metric.measure(testCase.input, testCase.output);
225
+ expect(result.score).toBe(testCase.expectedResult.score);
226
+ expect(result.info.scoreDetails).toEqual(testCase.expectedResult.scoreDetails);
227
+ });
228
+
229
+ it('should measure zero alignment with single instruction', async () => {
230
+ const testCase = testCases[1]!;
231
+ const metric = new PromptAlignmentMetric(model, {
232
+ instructions: testCase.instructions,
233
+ });
234
+
235
+ const result = await metric.measure(testCase.input, testCase.output);
236
+
237
+ expect(result.score).toBe(testCase.expectedResult.score);
238
+ expect(result.info.scoreDetails).toEqual(testCase.expectedResult.scoreDetails);
239
+ });
240
+
241
+ it('should measure perfect alignment with multiple instructions', async () => {
242
+ const testCase = testCases[2]!;
243
+ const metric = new PromptAlignmentMetric(model, {
244
+ instructions: testCase.instructions,
245
+ });
246
+
247
+ const result = await metric.measure(testCase.input, testCase.output);
248
+
249
+ expect(result.score).toBe(testCase.expectedResult.score);
250
+ expect(result.info.scoreDetails).toEqual(testCase.expectedResult.scoreDetails);
251
+ });
252
+
253
+ it('should measure partial alignment with multiple instructions', async () => {
254
+ const testCase = testCases[3]!;
255
+ const metric = new PromptAlignmentMetric(model, {
256
+ instructions: testCase.instructions,
257
+ });
258
+
259
+ const result = await metric.measure(testCase.input, testCase.output);
260
+
261
+ expect(result.score).toBe(testCase.expectedResult.score);
262
+ expect(result.info.scoreDetails).toEqual(testCase.expectedResult.scoreDetails);
263
+ });
264
+
265
+ it('should measure alignment with complex formatting instructions', async () => {
266
+ const testCase = testCases[4]!;
267
+ const metric = new PromptAlignmentMetric(model, {
268
+ instructions: testCase.instructions,
269
+ });
270
+
271
+ const result = await metric.measure(testCase.input, testCase.output);
272
+
273
+ expect(result.score).toBe(testCase.expectedResult.score);
274
+ expect(result.info.scoreDetails).toEqual(testCase.expectedResult.scoreDetails);
275
+ });
276
+
277
+ it('should handle empty output', async () => {
278
+ const testCase = testCases[5]!;
279
+ const metric = new PromptAlignmentMetric(model, {
280
+ instructions: testCase.instructions,
281
+ });
282
+
283
+ const result = await metric.measure(testCase.input, testCase.output);
284
+ expect(result.score).toBe(testCase.expectedResult.score);
285
+ expect(result.info.scoreDetails).toEqual(testCase.expectedResult.scoreDetails);
286
+ });
287
+
288
+ it('should handle all instructions being not applicable', async () => {
289
+ const testCase = testCases[6]!;
290
+ const metric = new PromptAlignmentMetric(model, {
291
+ instructions: testCase.instructions,
292
+ });
293
+
294
+ const result = await metric.measure(testCase.input, testCase.output);
295
+ expect(result.score).toBe(testCase.expectedResult.score);
296
+ expect(result.info.scoreDetails).toEqual(testCase.expectedResult.scoreDetails);
297
+ });
298
+
299
+ it('should handle mix of applicable and not applicable instructions', async () => {
300
+ const testCase = testCases[7]!;
301
+ const metric = new PromptAlignmentMetric(model, {
302
+ instructions: testCase.instructions,
303
+ });
304
+
305
+ const result = await metric.measure(testCase.input, testCase.output);
306
+ expect(result.score).toBe(testCase.expectedResult.score);
307
+ expect(result.info.scoreDetails).toEqual(testCase.expectedResult.scoreDetails);
308
+ });
309
+
310
+ it('should calculate correct score with mix of yes, no, and n/a verdicts', async () => {
311
+ const testCase = testCases[8]!;
312
+ const metric = new PromptAlignmentMetric(model, {
313
+ instructions: testCase.instructions,
314
+ });
315
+
316
+ const result = await metric.measure(testCase.input, testCase.output);
317
+ expect(result.score).toBeCloseTo(testCase.expectedResult.score, 2);
318
+ expect(result.info.scoreDetails).toEqual(testCase.expectedResult.scoreDetails);
319
+ });
320
+
321
+ it('should calculate correct score with complex formatting instructions', async () => {
322
+ const testCase = testCases[9]!;
323
+ const metric = new PromptAlignmentMetric(model, {
324
+ instructions: testCase.instructions,
325
+ });
326
+
327
+ const result = await metric.measure(testCase.input, testCase.output);
328
+ expect(result.score).toBeCloseTo(testCase.expectedResult.score, 2);
329
+ expect(result.info.scoreDetails).toEqual(testCase.expectedResult.scoreDetails);
330
+ });
331
+ },
332
+ {
333
+ timeout: 15 * SECONDS,
334
+ },
335
+ );
@@ -0,0 +1,116 @@
1
+ import { Metric } from '@mastra/core/eval';
2
+ import type { LanguageModel } from '@mastra/core/llm';
3
+
4
+ import type { MetricResultWithReason } from '../types';
5
+ import { roundToTwoDecimals } from '../utils';
6
+
7
+ import { PromptAlignmentJudge } from './metricJudge';
8
+
9
+ export interface PromptAlignmentMetricOptions {
10
+ scale?: number;
11
+ instructions: string[];
12
+ }
13
+
14
+ export interface PromptAlignmentScore {
15
+ score: number;
16
+ totalInstructions: number;
17
+ applicableInstructions: number;
18
+ followedInstructions: number;
19
+ naInstructions: number;
20
+ }
21
+
22
+ export interface PromptAlignmentMetricResult extends MetricResultWithReason {
23
+ info: MetricResultWithReason['info'] & {
24
+ scoreDetails: {
25
+ totalInstructions: number;
26
+ applicableInstructions: number;
27
+ followedInstructions: number;
28
+ naInstructions: number;
29
+ };
30
+ };
31
+ }
32
+
33
+ export class PromptAlignmentMetric extends Metric {
34
+ private instructions: string[];
35
+ private judge: PromptAlignmentJudge;
36
+ private scale: number;
37
+
38
+ constructor(model: LanguageModel, { instructions, scale = 1 }: PromptAlignmentMetricOptions) {
39
+ super();
40
+
41
+ this.instructions = instructions;
42
+ this.judge = new PromptAlignmentJudge(model);
43
+ this.scale = scale;
44
+ }
45
+
46
+ async measure(input: string, output: string): Promise<PromptAlignmentMetricResult> {
47
+ const verdicts = await this.judge.evaluate(input, output, this.instructions);
48
+ const scoreDetails = this.calculateScore(verdicts);
49
+ const reason = await this.judge.getReason({
50
+ input,
51
+ output,
52
+ score: scoreDetails.score,
53
+ verdicts,
54
+ scale: this.scale,
55
+ });
56
+
57
+ return {
58
+ score: scoreDetails.score,
59
+ info: {
60
+ reason,
61
+ scoreDetails: {
62
+ totalInstructions: scoreDetails.totalInstructions,
63
+ applicableInstructions: scoreDetails.applicableInstructions,
64
+ followedInstructions: scoreDetails.followedInstructions,
65
+ naInstructions: scoreDetails.naInstructions,
66
+ },
67
+ },
68
+ };
69
+ }
70
+
71
+ private calculateScore(evaluation: { verdict: string; reason: string }[]): PromptAlignmentScore {
72
+ const totalInstructions = evaluation?.length || 0;
73
+
74
+ // Handle empty evaluation case
75
+ if (totalInstructions === 0) {
76
+ return {
77
+ score: 0,
78
+ totalInstructions: 0,
79
+ applicableInstructions: 0,
80
+ followedInstructions: 0,
81
+ naInstructions: 0,
82
+ };
83
+ }
84
+
85
+ // Count different verdict types
86
+ const counts = evaluation.reduce(
87
+ (acc, { verdict }) => {
88
+ const normalizedVerdict = verdict.trim().toLowerCase();
89
+ if (normalizedVerdict === 'n/a') {
90
+ acc.naCount++;
91
+ } else if (normalizedVerdict === 'yes') {
92
+ acc.alignmentCount++;
93
+ acc.applicableCount++;
94
+ } else if (normalizedVerdict === 'no') {
95
+ acc.applicableCount++;
96
+ }
97
+ return acc;
98
+ },
99
+ { naCount: 0, alignmentCount: 0, applicableCount: 0 },
100
+ );
101
+
102
+ // Calculate final score
103
+ const score =
104
+ counts.applicableCount > 0
105
+ ? roundToTwoDecimals((counts.alignmentCount / counts.applicableCount) * this.scale)
106
+ : 0;
107
+
108
+ return {
109
+ score,
110
+ totalInstructions,
111
+ applicableInstructions: counts.applicableCount,
112
+ followedInstructions: counts.alignmentCount,
113
+ naInstructions: counts.naCount,
114
+ };
115
+ }
116
+ }
@@ -0,0 +1,43 @@
1
+ import type { LanguageModel } from '@mastra/core/llm';
2
+ import { z } from 'zod';
3
+
4
+ import { MastraAgentJudge } from '../../judge';
5
+
6
+ import { generateEvaluatePrompt, PROMPT_ALIGNMENT_AGENT_INSTRUCTIONS, generateReasonPrompt } from './prompts';
7
+
8
+ export class PromptAlignmentJudge extends MastraAgentJudge {
9
+ constructor(model: LanguageModel) {
10
+ super('Prompt Alignment', PROMPT_ALIGNMENT_AGENT_INSTRUCTIONS, model);
11
+ }
12
+
13
+ async evaluate(
14
+ input: string,
15
+ actualOutput: string,
16
+ instructions: string[],
17
+ ): Promise<{ verdict: string; reason: string }[]> {
18
+ const prompt = generateEvaluatePrompt({ input, output: actualOutput, instructions });
19
+ const result = await this.agent.generate(prompt, {
20
+ output: z.object({
21
+ verdicts: z.array(
22
+ z.object({
23
+ verdict: z.string(),
24
+ reason: z.string(),
25
+ }),
26
+ ),
27
+ }),
28
+ });
29
+ return result.object.verdicts;
30
+ }
31
+
32
+ async getReason(args: {
33
+ input: string;
34
+ output: string;
35
+ score: number;
36
+ verdicts: { verdict: string; reason: string }[];
37
+ scale: number;
38
+ }): Promise<string> {
39
+ const prompt = generateReasonPrompt(args);
40
+ const result = await this.agent.generate(prompt, { output: z.object({ reason: z.string() }) });
41
+ return result.object.reason;
42
+ }
43
+ }