@pauly4010/evalai-sdk 1.3.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 +289 -0
- package/LICENSE +21 -0
- package/README.md +565 -0
- package/dist/assertions.d.ts +189 -0
- package/dist/assertions.js +596 -0
- package/dist/batch.d.ts +68 -0
- package/dist/batch.js +178 -0
- package/dist/cache.d.ts +65 -0
- package/dist/cache.js +135 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.js +181 -0
- package/dist/client.d.ts +358 -0
- package/dist/client.js +802 -0
- package/dist/context.d.ts +134 -0
- package/dist/context.js +215 -0
- package/dist/errors.d.ts +80 -0
- package/dist/errors.js +285 -0
- package/dist/export.d.ts +195 -0
- package/dist/export.js +334 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +111 -0
- package/dist/integrations/anthropic.d.ts +72 -0
- package/dist/integrations/anthropic.js +159 -0
- package/dist/integrations/openai.d.ts +69 -0
- package/dist/integrations/openai.js +156 -0
- package/dist/local.d.ts +39 -0
- package/dist/local.js +146 -0
- package/dist/logger.d.ts +128 -0
- package/dist/logger.js +227 -0
- package/dist/pagination.d.ts +74 -0
- package/dist/pagination.js +135 -0
- package/dist/snapshot.d.ts +176 -0
- package/dist/snapshot.js +322 -0
- package/dist/streaming.d.ts +173 -0
- package/dist/streaming.js +268 -0
- package/dist/testing.d.ts +204 -0
- package/dist/testing.js +252 -0
- package/dist/types.d.ts +715 -0
- package/dist/types.js +54 -0
- package/dist/workflows.d.ts +378 -0
- package/dist/workflows.js +628 -0
- package/package.json +102 -0
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Enhanced Assertion Library
|
|
4
|
+
* Tier 1.3: Pre-Built Assertion Library with 20+ built-in assertions
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { expect } from '@ai-eval-platform/sdk';
|
|
9
|
+
*
|
|
10
|
+
* const output = "Hello, how can I help you today?";
|
|
11
|
+
*
|
|
12
|
+
* expect(output).toContainKeywords(['help', 'today']);
|
|
13
|
+
* expect(output).toHaveSentiment('positive');
|
|
14
|
+
* expect(output).toMatchPattern(/help/i);
|
|
15
|
+
* expect(output).toHaveLength({ min: 10, max: 100 });
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.Expectation = exports.AssertionError = void 0;
|
|
20
|
+
exports.expect = expect;
|
|
21
|
+
exports.runAssertions = runAssertions;
|
|
22
|
+
exports.containsKeywords = containsKeywords;
|
|
23
|
+
exports.matchesPattern = matchesPattern;
|
|
24
|
+
exports.hasLength = hasLength;
|
|
25
|
+
exports.containsJSON = containsJSON;
|
|
26
|
+
exports.notContainsPII = notContainsPII;
|
|
27
|
+
exports.hasSentiment = hasSentiment;
|
|
28
|
+
exports.similarTo = similarTo;
|
|
29
|
+
exports.withinRange = withinRange;
|
|
30
|
+
exports.isValidEmail = isValidEmail;
|
|
31
|
+
exports.isValidURL = isValidURL;
|
|
32
|
+
exports.hasNoHallucinations = hasNoHallucinations;
|
|
33
|
+
exports.matchesSchema = matchesSchema;
|
|
34
|
+
exports.hasReadabilityScore = hasReadabilityScore;
|
|
35
|
+
exports.containsLanguage = containsLanguage;
|
|
36
|
+
exports.hasFactualAccuracy = hasFactualAccuracy;
|
|
37
|
+
exports.respondedWithinTime = respondedWithinTime;
|
|
38
|
+
exports.hasNoToxicity = hasNoToxicity;
|
|
39
|
+
exports.followsInstructions = followsInstructions;
|
|
40
|
+
exports.containsAllRequiredFields = containsAllRequiredFields;
|
|
41
|
+
exports.hasValidCodeSyntax = hasValidCodeSyntax;
|
|
42
|
+
class AssertionError extends Error {
|
|
43
|
+
constructor(message, expected, actual) {
|
|
44
|
+
super(message);
|
|
45
|
+
this.expected = expected;
|
|
46
|
+
this.actual = actual;
|
|
47
|
+
this.name = "AssertionError";
|
|
48
|
+
Object.setPrototypeOf(this, AssertionError.prototype);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.AssertionError = AssertionError;
|
|
52
|
+
/**
|
|
53
|
+
* Fluent assertion builder
|
|
54
|
+
*/
|
|
55
|
+
class Expectation {
|
|
56
|
+
constructor(value) {
|
|
57
|
+
this.value = value;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Assert value equals expected
|
|
61
|
+
* @example expect(output).toEqual("Hello")
|
|
62
|
+
*/
|
|
63
|
+
toEqual(expected, message) {
|
|
64
|
+
const passed = JSON.stringify(this.value) === JSON.stringify(expected);
|
|
65
|
+
return {
|
|
66
|
+
name: "toEqual",
|
|
67
|
+
passed,
|
|
68
|
+
expected,
|
|
69
|
+
actual: this.value,
|
|
70
|
+
message: message || (passed ? "Values are equal" : `Expected ${JSON.stringify(expected)}, got ${JSON.stringify(this.value)}`)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Assert value contains substring
|
|
75
|
+
* @example expect(output).toContain("help")
|
|
76
|
+
*/
|
|
77
|
+
toContain(substring, message) {
|
|
78
|
+
const text = String(this.value);
|
|
79
|
+
const passed = text.includes(substring);
|
|
80
|
+
return {
|
|
81
|
+
name: "toContain",
|
|
82
|
+
passed,
|
|
83
|
+
expected: substring,
|
|
84
|
+
actual: text,
|
|
85
|
+
message: message || (passed ? `Text contains "${substring}"` : `Text does not contain "${substring}"`)
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Assert value contains all keywords
|
|
90
|
+
* @example expect(output).toContainKeywords(['help', 'support'])
|
|
91
|
+
*/
|
|
92
|
+
toContainKeywords(keywords, message) {
|
|
93
|
+
const text = String(this.value).toLowerCase();
|
|
94
|
+
const missingKeywords = keywords.filter(k => !text.includes(k.toLowerCase()));
|
|
95
|
+
const passed = missingKeywords.length === 0;
|
|
96
|
+
return {
|
|
97
|
+
name: "toContainKeywords",
|
|
98
|
+
passed,
|
|
99
|
+
expected: keywords,
|
|
100
|
+
actual: text,
|
|
101
|
+
message: message || (passed ? `Contains all keywords` : `Missing keywords: ${missingKeywords.join(', ')}`)
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Assert value does not contain substring
|
|
106
|
+
* @example expect(output).toNotContain("error")
|
|
107
|
+
*/
|
|
108
|
+
toNotContain(substring, message) {
|
|
109
|
+
const text = String(this.value);
|
|
110
|
+
const passed = !text.includes(substring);
|
|
111
|
+
return {
|
|
112
|
+
name: "toNotContain",
|
|
113
|
+
passed,
|
|
114
|
+
expected: `not containing "${substring}"`,
|
|
115
|
+
actual: text,
|
|
116
|
+
message: message || (passed ? `Text does not contain "${substring}"` : `Text contains "${substring}"`)
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Assert value does not contain PII (emails, phone numbers, SSN)
|
|
121
|
+
* @example expect(output).toNotContainPII()
|
|
122
|
+
*/
|
|
123
|
+
toNotContainPII(message) {
|
|
124
|
+
const text = String(this.value);
|
|
125
|
+
const emailPattern = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/;
|
|
126
|
+
const phonePattern = /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/;
|
|
127
|
+
const ssnPattern = /\b\d{3}-\d{2}-\d{4}\b/;
|
|
128
|
+
const foundPII = [];
|
|
129
|
+
if (emailPattern.test(text))
|
|
130
|
+
foundPII.push('email');
|
|
131
|
+
if (phonePattern.test(text))
|
|
132
|
+
foundPII.push('phone number');
|
|
133
|
+
if (ssnPattern.test(text))
|
|
134
|
+
foundPII.push('SSN');
|
|
135
|
+
const passed = foundPII.length === 0;
|
|
136
|
+
return {
|
|
137
|
+
name: "toNotContainPII",
|
|
138
|
+
passed,
|
|
139
|
+
expected: "no PII",
|
|
140
|
+
actual: foundPII.length > 0 ? `Found: ${foundPII.join(', ')}` : "no PII",
|
|
141
|
+
message: message || (passed ? "No PII detected" : `PII detected: ${foundPII.join(', ')}`)
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Assert value matches regular expression
|
|
146
|
+
* @example expect(output).toMatchPattern(/\d{3}-\d{3}-\d{4}/)
|
|
147
|
+
*/
|
|
148
|
+
toMatchPattern(pattern, message) {
|
|
149
|
+
const text = String(this.value);
|
|
150
|
+
const passed = pattern.test(text);
|
|
151
|
+
return {
|
|
152
|
+
name: "toMatchPattern",
|
|
153
|
+
passed,
|
|
154
|
+
expected: pattern.toString(),
|
|
155
|
+
actual: text,
|
|
156
|
+
message: message || (passed ? `Matches pattern ${pattern}` : `Does not match pattern ${pattern}`)
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Assert value is valid JSON
|
|
161
|
+
* @example expect(output).toBeValidJSON()
|
|
162
|
+
*/
|
|
163
|
+
toBeValidJSON(message) {
|
|
164
|
+
let passed = false;
|
|
165
|
+
let parsedJson = null;
|
|
166
|
+
try {
|
|
167
|
+
parsedJson = JSON.parse(String(this.value));
|
|
168
|
+
passed = true;
|
|
169
|
+
}
|
|
170
|
+
catch (e) {
|
|
171
|
+
passed = false;
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
name: "toBeValidJSON",
|
|
175
|
+
passed,
|
|
176
|
+
expected: "valid JSON",
|
|
177
|
+
actual: passed ? parsedJson : this.value,
|
|
178
|
+
message: message || (passed ? "Valid JSON" : "Invalid JSON")
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Assert JSON matches schema
|
|
183
|
+
* @example expect(output).toMatchJSON({ status: 'success' })
|
|
184
|
+
*/
|
|
185
|
+
toMatchJSON(schema, message) {
|
|
186
|
+
let passed = false;
|
|
187
|
+
let parsedJson = null;
|
|
188
|
+
try {
|
|
189
|
+
parsedJson = JSON.parse(String(this.value));
|
|
190
|
+
const requiredKeys = Object.keys(schema);
|
|
191
|
+
const actualKeys = Object.keys(parsedJson);
|
|
192
|
+
passed = requiredKeys.every(key => actualKeys.includes(key));
|
|
193
|
+
}
|
|
194
|
+
catch (e) {
|
|
195
|
+
passed = false;
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
name: "toMatchJSON",
|
|
199
|
+
passed,
|
|
200
|
+
expected: schema,
|
|
201
|
+
actual: parsedJson,
|
|
202
|
+
message: message || (passed ? "JSON matches schema" : "JSON does not match schema")
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Assert value has expected sentiment
|
|
207
|
+
* @example expect(output).toHaveSentiment('positive')
|
|
208
|
+
*/
|
|
209
|
+
toHaveSentiment(expected, message) {
|
|
210
|
+
const text = String(this.value).toLowerCase();
|
|
211
|
+
const positiveWords = ['good', 'great', 'excellent', 'amazing', 'wonderful', 'fantastic', 'love', 'best', 'happy', 'helpful'];
|
|
212
|
+
const negativeWords = ['bad', 'terrible', 'awful', 'horrible', 'worst', 'hate', 'poor', 'disappointing', 'sad', 'useless'];
|
|
213
|
+
const positiveCount = positiveWords.filter(w => text.includes(w)).length;
|
|
214
|
+
const negativeCount = negativeWords.filter(w => text.includes(w)).length;
|
|
215
|
+
let actual;
|
|
216
|
+
if (positiveCount > negativeCount)
|
|
217
|
+
actual = 'positive';
|
|
218
|
+
else if (negativeCount > positiveCount)
|
|
219
|
+
actual = 'negative';
|
|
220
|
+
else
|
|
221
|
+
actual = 'neutral';
|
|
222
|
+
const passed = actual === expected;
|
|
223
|
+
return {
|
|
224
|
+
name: "toHaveSentiment",
|
|
225
|
+
passed,
|
|
226
|
+
expected,
|
|
227
|
+
actual,
|
|
228
|
+
message: message || (passed ? `Sentiment is ${expected}` : `Expected ${expected}, got ${actual}`)
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Assert string length is within range
|
|
233
|
+
* @example expect(output).toHaveLength({ min: 10, max: 100 })
|
|
234
|
+
*/
|
|
235
|
+
toHaveLength(range, message) {
|
|
236
|
+
const length = String(this.value).length;
|
|
237
|
+
const passed = (range.min === undefined || length >= range.min) &&
|
|
238
|
+
(range.max === undefined || length <= range.max);
|
|
239
|
+
return {
|
|
240
|
+
name: "toHaveLength",
|
|
241
|
+
passed,
|
|
242
|
+
expected: range,
|
|
243
|
+
actual: length,
|
|
244
|
+
message: message || (passed ? `Length ${length} is within range` : `Length ${length} not in range`)
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Assert no hallucinations (all ground truth facts present)
|
|
249
|
+
* @example expect(output).toNotHallucinate(['fact1', 'fact2'])
|
|
250
|
+
*/
|
|
251
|
+
toNotHallucinate(groundTruth, message) {
|
|
252
|
+
const text = String(this.value).toLowerCase();
|
|
253
|
+
const missingFacts = groundTruth.filter(fact => !text.includes(fact.toLowerCase()));
|
|
254
|
+
const passed = missingFacts.length === 0;
|
|
255
|
+
return {
|
|
256
|
+
name: "toNotHallucinate",
|
|
257
|
+
passed,
|
|
258
|
+
expected: "all ground truth facts",
|
|
259
|
+
actual: missingFacts.length > 0 ? `Missing: ${missingFacts.join(', ')}` : "all facts present",
|
|
260
|
+
message: message || (passed ? "No hallucinations detected" : `Missing facts: ${missingFacts.join(', ')}`)
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Assert response latency is within limit
|
|
265
|
+
* @example expect(durationMs).toBeFasterThan(1000)
|
|
266
|
+
*/
|
|
267
|
+
toBeFasterThan(maxMs, message) {
|
|
268
|
+
const duration = Number(this.value);
|
|
269
|
+
const passed = duration <= maxMs;
|
|
270
|
+
return {
|
|
271
|
+
name: "toBeFasterThan",
|
|
272
|
+
passed,
|
|
273
|
+
expected: `<= ${maxMs}ms`,
|
|
274
|
+
actual: `${duration}ms`,
|
|
275
|
+
message: message || (passed ? `${duration}ms within limit` : `${duration}ms exceeds ${maxMs}ms`)
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Assert value is truthy
|
|
280
|
+
* @example expect(result).toBeTruthy()
|
|
281
|
+
*/
|
|
282
|
+
toBeTruthy(message) {
|
|
283
|
+
const passed = Boolean(this.value);
|
|
284
|
+
return {
|
|
285
|
+
name: "toBeTruthy",
|
|
286
|
+
passed,
|
|
287
|
+
expected: "truthy value",
|
|
288
|
+
actual: this.value,
|
|
289
|
+
message: message || (passed ? "Value is truthy" : "Value is falsy")
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Assert value is falsy
|
|
294
|
+
* @example expect(error).toBeFalsy()
|
|
295
|
+
*/
|
|
296
|
+
toBeFalsy(message) {
|
|
297
|
+
const passed = !Boolean(this.value);
|
|
298
|
+
return {
|
|
299
|
+
name: "toBeFalsy",
|
|
300
|
+
passed,
|
|
301
|
+
expected: "falsy value",
|
|
302
|
+
actual: this.value,
|
|
303
|
+
message: message || (passed ? "Value is falsy" : "Value is truthy")
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Assert value is greater than expected
|
|
308
|
+
* @example expect(score).toBeGreaterThan(0.8)
|
|
309
|
+
*/
|
|
310
|
+
toBeGreaterThan(expected, message) {
|
|
311
|
+
const value = Number(this.value);
|
|
312
|
+
const passed = value > expected;
|
|
313
|
+
return {
|
|
314
|
+
name: "toBeGreaterThan",
|
|
315
|
+
passed,
|
|
316
|
+
expected: `> ${expected}`,
|
|
317
|
+
actual: value,
|
|
318
|
+
message: message || (passed ? `${value} > ${expected}` : `${value} <= ${expected}`)
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Assert value is less than expected
|
|
323
|
+
* @example expect(errorRate).toBeLessThan(0.05)
|
|
324
|
+
*/
|
|
325
|
+
toBeLessThan(expected, message) {
|
|
326
|
+
const value = Number(this.value);
|
|
327
|
+
const passed = value < expected;
|
|
328
|
+
return {
|
|
329
|
+
name: "toBeLessThan",
|
|
330
|
+
passed,
|
|
331
|
+
expected: `< ${expected}`,
|
|
332
|
+
actual: value,
|
|
333
|
+
message: message || (passed ? `${value} < ${expected}` : `${value} >= ${expected}`)
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Assert value is between min and max
|
|
338
|
+
* @example expect(score).toBeBetween(0, 1)
|
|
339
|
+
*/
|
|
340
|
+
toBeBetween(min, max, message) {
|
|
341
|
+
const value = Number(this.value);
|
|
342
|
+
const passed = value >= min && value <= max;
|
|
343
|
+
return {
|
|
344
|
+
name: "toBeBetween",
|
|
345
|
+
passed,
|
|
346
|
+
expected: `between ${min} and ${max}`,
|
|
347
|
+
actual: value,
|
|
348
|
+
message: message || (passed ? `${value} is within range` : `${value} is outside range`)
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Assert value contains code block
|
|
353
|
+
* @example expect(output).toContainCode()
|
|
354
|
+
*/
|
|
355
|
+
toContainCode(message) {
|
|
356
|
+
const text = String(this.value);
|
|
357
|
+
const hasCodeBlock = /```[\s\S]*?```/.test(text) || /<code>[\s\S]*?<\/code>/.test(text);
|
|
358
|
+
return {
|
|
359
|
+
name: "toContainCode",
|
|
360
|
+
passed: hasCodeBlock,
|
|
361
|
+
expected: "code block",
|
|
362
|
+
actual: text,
|
|
363
|
+
message: message || (hasCodeBlock ? "Contains code block" : "No code block found")
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Assert value is professional tone (no profanity)
|
|
368
|
+
* @example expect(output).toBeProfessional()
|
|
369
|
+
*/
|
|
370
|
+
toBeProfessional(message) {
|
|
371
|
+
const text = String(this.value).toLowerCase();
|
|
372
|
+
const profanity = ['damn', 'hell', 'shit', 'fuck', 'ass', 'bitch', 'crap'];
|
|
373
|
+
const foundProfanity = profanity.filter(word => text.includes(word));
|
|
374
|
+
const passed = foundProfanity.length === 0;
|
|
375
|
+
return {
|
|
376
|
+
name: "toBeProfessional",
|
|
377
|
+
passed,
|
|
378
|
+
expected: "professional tone",
|
|
379
|
+
actual: foundProfanity.length > 0 ? `Found: ${foundProfanity.join(', ')}` : "professional",
|
|
380
|
+
message: message || (passed ? "Professional tone" : `Unprofessional language: ${foundProfanity.join(', ')}`)
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Assert value has proper grammar (basic checks)
|
|
385
|
+
* @example expect(output).toHaveProperGrammar()
|
|
386
|
+
*/
|
|
387
|
+
toHaveProperGrammar(message) {
|
|
388
|
+
const text = String(this.value);
|
|
389
|
+
const issues = [];
|
|
390
|
+
// Check for double spaces
|
|
391
|
+
if (/ +/.test(text))
|
|
392
|
+
issues.push('double spaces');
|
|
393
|
+
// Check for missing periods at end
|
|
394
|
+
if (text.length > 10 && !/[.!?]$/.test(text.trim()))
|
|
395
|
+
issues.push('missing ending punctuation');
|
|
396
|
+
// Check for lowercase sentence starts
|
|
397
|
+
if (/\.\s+[a-z]/.test(text))
|
|
398
|
+
issues.push('lowercase after period');
|
|
399
|
+
const passed = issues.length === 0;
|
|
400
|
+
return {
|
|
401
|
+
name: "toHaveProperGrammar",
|
|
402
|
+
passed,
|
|
403
|
+
expected: "proper grammar",
|
|
404
|
+
actual: issues.length > 0 ? `Issues: ${issues.join(', ')}` : "proper grammar",
|
|
405
|
+
message: message || (passed ? "Proper grammar" : `Grammar issues: ${issues.join(', ')}`)
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
exports.Expectation = Expectation;
|
|
410
|
+
/**
|
|
411
|
+
* Create an expectation for fluent assertions
|
|
412
|
+
*
|
|
413
|
+
* @example
|
|
414
|
+
* ```typescript
|
|
415
|
+
* const output = "Hello, how can I help you?";
|
|
416
|
+
*
|
|
417
|
+
* expect(output).toContain("help");
|
|
418
|
+
* expect(output).toHaveSentiment('positive');
|
|
419
|
+
* expect(output).toHaveLength({ min: 10, max: 100 });
|
|
420
|
+
* ```
|
|
421
|
+
*/
|
|
422
|
+
function expect(value) {
|
|
423
|
+
return new Expectation(value);
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Run multiple assertions and collect results
|
|
427
|
+
*
|
|
428
|
+
* @example
|
|
429
|
+
* ```typescript
|
|
430
|
+
* const results = runAssertions([
|
|
431
|
+
* () => expect(output).toContain("help"),
|
|
432
|
+
* () => expect(output).toHaveSentiment('positive'),
|
|
433
|
+
* () => expect(output).toHaveLength({ min: 10 })
|
|
434
|
+
* ]);
|
|
435
|
+
*
|
|
436
|
+
* const allPassed = results.every(r => r.passed);
|
|
437
|
+
* ```
|
|
438
|
+
*/
|
|
439
|
+
function runAssertions(assertions) {
|
|
440
|
+
return assertions.map((assertion) => {
|
|
441
|
+
try {
|
|
442
|
+
return assertion();
|
|
443
|
+
}
|
|
444
|
+
catch (error) {
|
|
445
|
+
return {
|
|
446
|
+
name: "unknown",
|
|
447
|
+
passed: false,
|
|
448
|
+
expected: null,
|
|
449
|
+
actual: null,
|
|
450
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
// Standalone assertion functions
|
|
456
|
+
function containsKeywords(text, keywords) {
|
|
457
|
+
return keywords.every(keyword => text.toLowerCase().includes(keyword.toLowerCase()));
|
|
458
|
+
}
|
|
459
|
+
function matchesPattern(text, pattern) {
|
|
460
|
+
return pattern.test(text);
|
|
461
|
+
}
|
|
462
|
+
function hasLength(text, range) {
|
|
463
|
+
const length = text.length;
|
|
464
|
+
if (range.min !== undefined && length < range.min)
|
|
465
|
+
return false;
|
|
466
|
+
if (range.max !== undefined && length > range.max)
|
|
467
|
+
return false;
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
function containsJSON(text) {
|
|
471
|
+
try {
|
|
472
|
+
JSON.parse(text);
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
catch {
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
function notContainsPII(text) {
|
|
480
|
+
// Simple PII detection patterns
|
|
481
|
+
const piiPatterns = [
|
|
482
|
+
/\b\d{3}-\d{2}-\d{4}\b/, // SSN
|
|
483
|
+
/\b\d{3}\.\d{3}\.\d{4}\b/, // SSN with dots
|
|
484
|
+
/\b\d{10}\b/, // Phone number
|
|
485
|
+
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/, // Email
|
|
486
|
+
/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/, // IP address
|
|
487
|
+
];
|
|
488
|
+
return !piiPatterns.some(pattern => pattern.test(text));
|
|
489
|
+
}
|
|
490
|
+
function hasSentiment(text, expected) {
|
|
491
|
+
// This is a simplified implementation
|
|
492
|
+
const positiveWords = ['good', 'great', 'excellent', 'awesome'];
|
|
493
|
+
const negativeWords = ['bad', 'terrible', 'awful', 'poor'];
|
|
494
|
+
const words = text.toLowerCase().split(/\s+/);
|
|
495
|
+
const positiveCount = words.filter(word => positiveWords.includes(word)).length;
|
|
496
|
+
const negativeCount = words.filter(word => negativeWords.includes(word)).length;
|
|
497
|
+
if (expected === 'positive')
|
|
498
|
+
return positiveCount > negativeCount;
|
|
499
|
+
if (expected === 'negative')
|
|
500
|
+
return negativeCount > positiveCount;
|
|
501
|
+
return positiveCount === negativeCount; // neutral
|
|
502
|
+
}
|
|
503
|
+
function similarTo(text1, text2, threshold = 0.8) {
|
|
504
|
+
// Simple similarity check - in a real app, you'd use a proper string similarity algorithm
|
|
505
|
+
const words1 = new Set(text1.toLowerCase().split(/\s+/));
|
|
506
|
+
const words2 = new Set(text2.toLowerCase().split(/\s+/));
|
|
507
|
+
const intersection = new Set([...words1].filter(word => words2.has(word)));
|
|
508
|
+
const union = new Set([...words1, ...words2]);
|
|
509
|
+
return intersection.size / union.size >= threshold;
|
|
510
|
+
}
|
|
511
|
+
function withinRange(value, min, max) {
|
|
512
|
+
return value >= min && value <= max;
|
|
513
|
+
}
|
|
514
|
+
function isValidEmail(email) {
|
|
515
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
516
|
+
}
|
|
517
|
+
function isValidURL(url) {
|
|
518
|
+
try {
|
|
519
|
+
new URL(url);
|
|
520
|
+
return true;
|
|
521
|
+
}
|
|
522
|
+
catch {
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
function hasNoHallucinations(text, groundTruth) {
|
|
527
|
+
// This is a simplified implementation
|
|
528
|
+
return groundTruth.every(truth => text.includes(truth));
|
|
529
|
+
}
|
|
530
|
+
function matchesSchema(value, schema) {
|
|
531
|
+
// This is a simplified implementation
|
|
532
|
+
if (typeof value !== 'object' || value === null)
|
|
533
|
+
return false;
|
|
534
|
+
return Object.keys(schema).every(key => key in value);
|
|
535
|
+
}
|
|
536
|
+
function hasReadabilityScore(text, minScore) {
|
|
537
|
+
// This is a simplified implementation
|
|
538
|
+
const words = text.split(/\s+/).length;
|
|
539
|
+
const sentences = text.split(/[.!?]+/).length;
|
|
540
|
+
const score = 206.835 - 1.015 * (words / sentences) - 84.6 * (syllables(text) / words);
|
|
541
|
+
return score >= minScore;
|
|
542
|
+
}
|
|
543
|
+
function syllables(word) {
|
|
544
|
+
// Simple syllable counter
|
|
545
|
+
word = word.toLowerCase();
|
|
546
|
+
if (word.length <= 3)
|
|
547
|
+
return 1;
|
|
548
|
+
return word.replace(/[^aeiouy]+/g, ' ').trim().split(/\s+/).length;
|
|
549
|
+
}
|
|
550
|
+
function containsLanguage(text, language) {
|
|
551
|
+
// This is a simplified implementation
|
|
552
|
+
// In a real app, you'd use a language detection library
|
|
553
|
+
const languageKeywords = {
|
|
554
|
+
'en': ['the', 'and', 'you', 'that', 'was', 'for', 'are', 'with'],
|
|
555
|
+
'es': ['el', 'la', 'los', 'las', 'de', 'que', 'y', 'en'],
|
|
556
|
+
'fr': ['le', 'la', 'les', 'de', 'et', 'à', 'un', 'une'],
|
|
557
|
+
};
|
|
558
|
+
const keywords = languageKeywords[language.toLowerCase()] || [];
|
|
559
|
+
return keywords.some(keyword => text.toLowerCase().includes(keyword));
|
|
560
|
+
}
|
|
561
|
+
function hasFactualAccuracy(text, facts) {
|
|
562
|
+
// This is a simplified implementation
|
|
563
|
+
return facts.every(fact => text.includes(fact));
|
|
564
|
+
}
|
|
565
|
+
function respondedWithinTime(startTime, maxMs) {
|
|
566
|
+
return Date.now() - startTime <= maxMs;
|
|
567
|
+
}
|
|
568
|
+
function hasNoToxicity(text) {
|
|
569
|
+
// This is a simplified implementation
|
|
570
|
+
const toxicWords = ['hate', 'stupid', 'idiot', 'dumb'];
|
|
571
|
+
return !toxicWords.some(word => text.toLowerCase().includes(word));
|
|
572
|
+
}
|
|
573
|
+
function followsInstructions(text, instructions) {
|
|
574
|
+
return instructions.every(instruction => {
|
|
575
|
+
if (instruction.startsWith('!')) {
|
|
576
|
+
return !text.includes(instruction.slice(1));
|
|
577
|
+
}
|
|
578
|
+
return text.includes(instruction);
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
function containsAllRequiredFields(obj, requiredFields) {
|
|
582
|
+
return requiredFields.every(field => field in obj);
|
|
583
|
+
}
|
|
584
|
+
function hasValidCodeSyntax(code, language) {
|
|
585
|
+
// This is a simplified implementation
|
|
586
|
+
// In a real app, you'd use a proper parser for each language
|
|
587
|
+
try {
|
|
588
|
+
if (language === 'json')
|
|
589
|
+
JSON.parse(code);
|
|
590
|
+
// Add more language validations as needed
|
|
591
|
+
return true;
|
|
592
|
+
}
|
|
593
|
+
catch {
|
|
594
|
+
return false;
|
|
595
|
+
}
|
|
596
|
+
}
|
package/dist/batch.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request batching for improved performance
|
|
3
|
+
* Combines multiple API requests into fewer network calls
|
|
4
|
+
*/
|
|
5
|
+
export interface BatchRequest {
|
|
6
|
+
id: string;
|
|
7
|
+
method: string;
|
|
8
|
+
endpoint: string;
|
|
9
|
+
body?: any;
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
}
|
|
12
|
+
export interface BatchResponse {
|
|
13
|
+
id: string;
|
|
14
|
+
status: number;
|
|
15
|
+
data?: any;
|
|
16
|
+
error?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Batch processor for API requests
|
|
20
|
+
*/
|
|
21
|
+
export declare class RequestBatcher {
|
|
22
|
+
private executeBatch;
|
|
23
|
+
private queue;
|
|
24
|
+
private batchTimer;
|
|
25
|
+
private readonly maxBatchSize;
|
|
26
|
+
private readonly batchDelay;
|
|
27
|
+
private requestCounter;
|
|
28
|
+
constructor(executeBatch: (requests: BatchRequest[]) => Promise<BatchResponse[]>, options?: {
|
|
29
|
+
maxBatchSize?: number;
|
|
30
|
+
batchDelay?: number;
|
|
31
|
+
});
|
|
32
|
+
/**
|
|
33
|
+
* Add request to batch queue
|
|
34
|
+
*/
|
|
35
|
+
enqueue(method: string, endpoint: string, body?: any, headers?: Record<string, string>): Promise<any>;
|
|
36
|
+
/**
|
|
37
|
+
* Schedule batch processing after delay
|
|
38
|
+
*/
|
|
39
|
+
private scheduleBatch;
|
|
40
|
+
/**
|
|
41
|
+
* Process current batch
|
|
42
|
+
*/
|
|
43
|
+
private processBatch;
|
|
44
|
+
/**
|
|
45
|
+
* Flush all pending requests immediately
|
|
46
|
+
*/
|
|
47
|
+
flush(): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Clear queue without processing
|
|
50
|
+
*/
|
|
51
|
+
clear(): void;
|
|
52
|
+
/**
|
|
53
|
+
* Get queue statistics
|
|
54
|
+
*/
|
|
55
|
+
getStats(): {
|
|
56
|
+
queueSize: number;
|
|
57
|
+
maxBatchSize: number;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* @internal - Internal SDK logic, not part of public API
|
|
62
|
+
* Check if requests can be batched together
|
|
63
|
+
*/
|
|
64
|
+
export declare function canBatch(method: string, endpoint: string): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Batch multiple async operations with concurrency limit
|
|
67
|
+
*/
|
|
68
|
+
export declare function batchProcess<T, R>(items: T[], processor: (item: T) => Promise<R>, concurrency?: number): Promise<R[]>;
|