ai-sdk-guardrails 1.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.
@@ -0,0 +1,628 @@
1
+ import {
2
+ createOutputGuardrail
3
+ } from "../chunk-BNGJDZMX.js";
4
+
5
+ // src/guardrails/output.ts
6
+ function extractContent(result) {
7
+ if ("object" in result && result.object != null) {
8
+ const objectResult = result;
9
+ return {
10
+ text: objectResult.text || "",
11
+ object: objectResult.object,
12
+ usage: objectResult.usage,
13
+ finishReason: objectResult.finishReason,
14
+ generationTimeMs: objectResult.experimental_providerMetadata?.generationTimeMs,
15
+ reasoning: objectResult.reasoning || objectResult.experimental_providerMetadata?.reasoning
16
+ };
17
+ } else if ("text" in result) {
18
+ const textResult = result;
19
+ return {
20
+ text: textResult.text || "",
21
+ object: null,
22
+ usage: textResult.usage,
23
+ finishReason: textResult.finishReason,
24
+ generationTimeMs: textResult.experimental_providerMetadata?.generationTimeMs,
25
+ reasoning: textResult.reasoning || textResult.experimental_providerMetadata?.reasoning
26
+ };
27
+ } else if ("textStream" in result) {
28
+ return {
29
+ text: "",
30
+ object: null,
31
+ usage: void 0,
32
+ finishReason: void 0,
33
+ generationTimeMs: void 0
34
+ };
35
+ } else if ("objectStream" in result) {
36
+ return {
37
+ text: "",
38
+ object: null,
39
+ usage: void 0,
40
+ finishReason: void 0,
41
+ generationTimeMs: void 0
42
+ };
43
+ } else if ("embeddings" in result || "then" in result) {
44
+ return {
45
+ text: "",
46
+ object: null,
47
+ usage: void 0,
48
+ finishReason: void 0,
49
+ generationTimeMs: void 0
50
+ };
51
+ }
52
+ return {
53
+ text: "",
54
+ object: null,
55
+ usage: void 0,
56
+ finishReason: void 0,
57
+ generationTimeMs: void 0
58
+ };
59
+ }
60
+ var lengthLimit = (maxLength) => createOutputGuardrail(
61
+ "output-length-limit",
62
+ (context, accumulatedText) => {
63
+ const { text, object, usage, finishReason, generationTimeMs } = extractContent(context.result);
64
+ const content = accumulatedText || text || (object ? JSON.stringify(object) : "");
65
+ return {
66
+ tripwireTriggered: content.length > maxLength,
67
+ message: `Output length ${content.length} exceeds limit of ${maxLength}`,
68
+ severity: "medium",
69
+ metadata: {
70
+ contentLength: content.length,
71
+ maxLength,
72
+ hasObject: !!object,
73
+ usage,
74
+ finishReason,
75
+ generationTimeMs,
76
+ tokensPerMs: usage?.totalTokens && generationTimeMs ? usage.totalTokens / generationTimeMs : void 0
77
+ }
78
+ };
79
+ }
80
+ );
81
+ var blockedContent = (words) => createOutputGuardrail(
82
+ "blocked-content",
83
+ (context) => {
84
+ const { text, object } = extractContent(context.result);
85
+ const content = (text || (object ? JSON.stringify(object) : "")).toLowerCase();
86
+ const blockedWord = words.find(
87
+ (word) => content.includes(word.toLowerCase())
88
+ );
89
+ return {
90
+ tripwireTriggered: !!blockedWord,
91
+ message: blockedWord ? `Blocked content detected: ${blockedWord}` : void 0,
92
+ severity: "high",
93
+ metadata: {
94
+ blockedWord,
95
+ allWords: words,
96
+ contentLength: content.length
97
+ }
98
+ };
99
+ }
100
+ );
101
+ var outputLengthLimit = (maxLength) => createOutputGuardrail(
102
+ "output-length-limit",
103
+ (context, accumulatedText) => {
104
+ const { text, object } = extractContent(context.result);
105
+ const content = accumulatedText || text || (object ? JSON.stringify(object) : "");
106
+ return {
107
+ tripwireTriggered: content.length > maxLength,
108
+ message: `Output length ${content.length} exceeds limit of ${maxLength}`,
109
+ severity: "medium",
110
+ metadata: {
111
+ contentLength: content.length,
112
+ maxLength,
113
+ hasObject: !!object
114
+ }
115
+ };
116
+ }
117
+ );
118
+ var blockedOutputContent = (words) => createOutputGuardrail(
119
+ "blocked-output-content",
120
+ (context) => {
121
+ const { text, object } = extractContent(context.result);
122
+ const content = (text || (object ? JSON.stringify(object) : "")).toLowerCase();
123
+ const blockedWord = words.find(
124
+ (word) => content.includes(word.toLowerCase())
125
+ );
126
+ return {
127
+ tripwireTriggered: !!blockedWord,
128
+ message: blockedWord ? `Blocked output content detected: ${blockedWord}` : void 0,
129
+ severity: "high",
130
+ metadata: {
131
+ blockedWord,
132
+ allWords: words,
133
+ contentLength: content.length
134
+ }
135
+ };
136
+ }
137
+ );
138
+ var jsonValidation = () => createOutputGuardrail(
139
+ "json-validation",
140
+ (context) => {
141
+ const { text, object } = extractContent(context.result);
142
+ if (object) return { tripwireTriggered: false };
143
+ try {
144
+ JSON.parse(text);
145
+ return { tripwireTriggered: false };
146
+ } catch (error) {
147
+ return {
148
+ tripwireTriggered: true,
149
+ message: "Output is not valid JSON",
150
+ severity: "medium",
151
+ metadata: {
152
+ error: error instanceof Error ? error.message : String(error),
153
+ textLength: text.length
154
+ }
155
+ };
156
+ }
157
+ }
158
+ );
159
+ var confidenceThreshold = (minConfidence) => createOutputGuardrail(
160
+ "confidence-threshold",
161
+ (context) => {
162
+ const { text, object, usage, finishReason, generationTimeMs, reasoning } = extractContent(context.result);
163
+ const content = text || (object ? JSON.stringify(object) : "");
164
+ const hasUncertainty = content.toLowerCase().includes("i think") || content.toLowerCase().includes("maybe") || content.toLowerCase().includes("probably") || content.toLowerCase().includes("uncertain") || content.toLowerCase().includes("not sure");
165
+ const finishReasonPenalty = finishReason === "length" ? 0.2 : 0;
166
+ const baseConfidence = hasUncertainty ? 0.5 : 0.9;
167
+ const confidence = Math.max(0, baseConfidence - finishReasonPenalty);
168
+ return {
169
+ tripwireTriggered: confidence < minConfidence,
170
+ message: `Output confidence ${confidence} below threshold ${minConfidence}`,
171
+ severity: "medium",
172
+ metadata: {
173
+ confidence,
174
+ minConfidence,
175
+ hasUncertainty,
176
+ textLength: content.length,
177
+ usage,
178
+ finishReason,
179
+ generationTimeMs,
180
+ finishReasonPenalty,
181
+ reasoning
182
+ }
183
+ };
184
+ }
185
+ );
186
+ var toxicityFilter = (threshold = 0.7) => createOutputGuardrail(
187
+ "toxicity-filter",
188
+ (context) => {
189
+ const { text, object } = extractContent(context.result);
190
+ const content = text || (object ? JSON.stringify(object) : "");
191
+ const toxicWords = ["toxic", "harmful", "offensive", "inappropriate"];
192
+ const detectedWords = toxicWords.filter(
193
+ (word) => content.toLowerCase().includes(word)
194
+ );
195
+ const toxicityScore = detectedWords.length * 0.3;
196
+ return {
197
+ tripwireTriggered: toxicityScore > threshold,
198
+ message: `Content toxicity score ${toxicityScore} exceeds threshold ${threshold}`,
199
+ severity: "high",
200
+ metadata: {
201
+ toxicityScore,
202
+ threshold,
203
+ detectedWords,
204
+ contentLength: content.length
205
+ }
206
+ };
207
+ }
208
+ );
209
+ var customValidation = (name, validator, message) => createOutputGuardrail(name, (context) => {
210
+ const { text, object, usage, finishReason, generationTimeMs } = extractContent(context.result);
211
+ const validatorInput = {
212
+ text,
213
+ object,
214
+ usage,
215
+ finishReason,
216
+ generationTimeMs
217
+ };
218
+ const blocked = validator(validatorInput);
219
+ return {
220
+ tripwireTriggered: blocked,
221
+ message: blocked ? message : void 0,
222
+ severity: "medium",
223
+ metadata: {
224
+ validatorName: name,
225
+ hasText: !!text,
226
+ hasObject: !!object,
227
+ usage,
228
+ finishReason,
229
+ generationTimeMs
230
+ }
231
+ };
232
+ });
233
+ var schemaValidation = (schema) => createOutputGuardrail(
234
+ "schema-validation",
235
+ (context) => {
236
+ const { object, usage, finishReason, generationTimeMs } = extractContent(
237
+ context.result
238
+ );
239
+ if (!object) {
240
+ return {
241
+ tripwireTriggered: true,
242
+ message: "No object to validate",
243
+ severity: "medium",
244
+ metadata: {
245
+ hasObject: false,
246
+ usage,
247
+ finishReason,
248
+ generationTimeMs
249
+ }
250
+ };
251
+ }
252
+ try {
253
+ schema.parse(object);
254
+ return {
255
+ tripwireTriggered: false,
256
+ metadata: {
257
+ hasObject: true,
258
+ validationPassed: true,
259
+ usage,
260
+ finishReason,
261
+ generationTimeMs
262
+ }
263
+ };
264
+ } catch (error) {
265
+ return {
266
+ tripwireTriggered: true,
267
+ message: `Schema validation failed: ${error instanceof Error ? error.message : String(error)}`,
268
+ severity: "high",
269
+ metadata: {
270
+ hasObject: true,
271
+ validationPassed: false,
272
+ error: error instanceof Error ? error.message : String(error),
273
+ usage,
274
+ finishReason,
275
+ generationTimeMs
276
+ }
277
+ };
278
+ }
279
+ }
280
+ );
281
+ var tokenUsageLimit = (maxTokens) => createOutputGuardrail(
282
+ "token-usage-limit",
283
+ (context) => {
284
+ const { text, object, usage, generationTimeMs } = extractContent(
285
+ context.result
286
+ );
287
+ const totalTokens = usage?.totalTokens || 0;
288
+ const content = text || (object ? JSON.stringify(object) : "");
289
+ return {
290
+ tripwireTriggered: totalTokens > maxTokens,
291
+ message: `Token usage ${totalTokens} exceeds limit of ${maxTokens}`,
292
+ severity: "medium",
293
+ metadata: {
294
+ totalTokens,
295
+ maxTokens,
296
+ promptTokens: usage?.promptTokens,
297
+ completionTokens: usage?.completionTokens,
298
+ contentLength: content.length,
299
+ generationTimeMs,
300
+ tokensPerMs: totalTokens && generationTimeMs ? totalTokens / generationTimeMs : void 0
301
+ }
302
+ };
303
+ }
304
+ );
305
+ var performanceMonitor = (maxGenerationTimeMs) => createOutputGuardrail(
306
+ "performance-monitor",
307
+ (context) => {
308
+ const { text, object, usage, generationTimeMs } = extractContent(
309
+ context.result
310
+ );
311
+ const actualGenerationTimeMs = generationTimeMs || 0;
312
+ const content = text || (object ? JSON.stringify(object) : "");
313
+ return {
314
+ tripwireTriggered: actualGenerationTimeMs > maxGenerationTimeMs,
315
+ message: `Generation time ${actualGenerationTimeMs}ms exceeds limit of ${maxGenerationTimeMs}ms`,
316
+ severity: "low",
317
+ metadata: {
318
+ generationTimeMs: actualGenerationTimeMs,
319
+ maxGenerationTimeMs,
320
+ contentLength: content.length,
321
+ usage,
322
+ tokensPerMs: usage?.totalTokens && actualGenerationTimeMs ? usage.totalTokens / actualGenerationTimeMs : void 0,
323
+ charactersPerMs: actualGenerationTimeMs ? content.length / actualGenerationTimeMs : void 0
324
+ }
325
+ };
326
+ }
327
+ );
328
+ var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
329
+ var hallucinationDetector = (confidenceThreshold2 = 0.7) => createOutputGuardrail(
330
+ "hallucination-detector",
331
+ (context, accumulatedText) => {
332
+ const { text, object, usage, finishReason, generationTimeMs } = extractContent(context.result);
333
+ const content = accumulatedText || (object && isObject(object) ? JSON.stringify(object) : text || "");
334
+ const uncertaintyIndicators = [
335
+ "i think",
336
+ "i believe",
337
+ "probably",
338
+ "likely",
339
+ "might be",
340
+ "could be",
341
+ "not sure",
342
+ "uncertain",
343
+ "possibly",
344
+ "perhaps",
345
+ "maybe",
346
+ "seems like",
347
+ "appears to be",
348
+ "my understanding is",
349
+ "if i recall correctly"
350
+ ];
351
+ const factualClaims = [
352
+ "according to",
353
+ "studies show",
354
+ "research indicates",
355
+ "data suggests",
356
+ "statistics show",
357
+ "proven fact",
358
+ "scientific evidence",
359
+ "documented",
360
+ "confirmed by",
361
+ "established that"
362
+ ];
363
+ const uncertaintyCount = uncertaintyIndicators.filter(
364
+ (indicator) => content.toLowerCase().includes(indicator)
365
+ ).length;
366
+ const factualClaimCount = factualClaims.filter(
367
+ (claim) => content.toLowerCase().includes(claim)
368
+ ).length;
369
+ const hallucinationScore = uncertaintyCount * 0.3 + factualClaimCount * 0.2;
370
+ const isHallucination = hallucinationScore > confidenceThreshold2;
371
+ return {
372
+ tripwireTriggered: isHallucination,
373
+ message: isHallucination ? `Potential hallucination detected (score: ${hallucinationScore})` : void 0,
374
+ severity: hallucinationScore > 0.8 ? "high" : "medium",
375
+ metadata: {
376
+ hallucinationScore,
377
+ confidenceThreshold: confidenceThreshold2,
378
+ uncertaintyCount,
379
+ factualClaimCount,
380
+ contentLength: content.length,
381
+ usage,
382
+ finishReason,
383
+ generationTimeMs
384
+ },
385
+ suggestion: "Please verify factual claims and consider requesting sources"
386
+ };
387
+ }
388
+ );
389
+ var biasDetector = () => createOutputGuardrail("bias-detector", (context) => {
390
+ const { text, object } = extractContent(context.result);
391
+ const content = text || (object ? JSON.stringify(object) : "");
392
+ const lowerContent = content.toLowerCase();
393
+ const biasPatterns = {
394
+ gender: [
395
+ "men are better at",
396
+ "women are better at",
397
+ "typical male",
398
+ "typical female",
399
+ "boys will be boys",
400
+ "women should",
401
+ "men should",
402
+ "ladies",
403
+ "gentlemen"
404
+ ],
405
+ racial: [
406
+ "people of that race",
407
+ "those people",
408
+ "their culture",
409
+ "natural talent",
410
+ "genetic predisposition",
411
+ "inherent ability",
412
+ "cultural background"
413
+ ],
414
+ age: [
415
+ "young people today",
416
+ "older people can't",
417
+ "millennials are",
418
+ "boomers are",
419
+ "too old to",
420
+ "too young to"
421
+ ],
422
+ socioeconomic: [
423
+ "poor people are",
424
+ "rich people are",
425
+ "welfare recipients",
426
+ "privileged class",
427
+ "working class",
428
+ "upper class"
429
+ ]
430
+ };
431
+ const detectedBias = [];
432
+ const matches = [];
433
+ for (const [category, patterns] of Object.entries(biasPatterns)) {
434
+ const found = patterns.filter(
435
+ (pattern) => lowerContent.includes(pattern)
436
+ );
437
+ if (found.length > 0) {
438
+ detectedBias.push(category);
439
+ matches.push(...found);
440
+ }
441
+ }
442
+ return {
443
+ tripwireTriggered: detectedBias.length > 0,
444
+ message: detectedBias.length > 0 ? `Potential bias detected in categories: ${detectedBias.join(", ")}` : void 0,
445
+ severity: "medium",
446
+ metadata: {
447
+ biasCategories: detectedBias,
448
+ biasPatterns: matches,
449
+ contentLength: content.length
450
+ },
451
+ suggestion: "Consider reviewing content for potential bias and using more inclusive language"
452
+ };
453
+ });
454
+ var factualAccuracyChecker = (requireSources = false) => createOutputGuardrail(
455
+ "factual-accuracy-checker",
456
+ (context) => {
457
+ const { text, object, generationTimeMs } = extractContent(context.result);
458
+ const content = text || (object ? JSON.stringify(object) : "");
459
+ const factualClaims = [
460
+ "according to",
461
+ "studies show",
462
+ "research indicates",
463
+ "data suggests",
464
+ "statistics show",
465
+ "proven fact",
466
+ "scientific evidence",
467
+ "documented",
468
+ "confirmed by",
469
+ "established that",
470
+ "published in",
471
+ "survey found"
472
+ ];
473
+ const sourceCitations = [
474
+ "source:",
475
+ "reference:",
476
+ "citation:",
477
+ "published in",
478
+ "journal of",
479
+ "university of",
480
+ "institute of",
481
+ "doi:",
482
+ "isbn:",
483
+ "url:",
484
+ "http"
485
+ ];
486
+ const factualClaimCount = factualClaims.filter(
487
+ (claim) => content.toLowerCase().includes(claim)
488
+ ).length;
489
+ const sourceCitationCount = sourceCitations.filter(
490
+ (source) => content.toLowerCase().includes(source)
491
+ ).length;
492
+ const hasUnfoundedClaims = requireSources && factualClaimCount > 0 && sourceCitationCount === 0;
493
+ return {
494
+ tripwireTriggered: hasUnfoundedClaims,
495
+ message: hasUnfoundedClaims ? `Factual claims detected without sources (${factualClaimCount} claims, ${sourceCitationCount} sources)` : void 0,
496
+ severity: "medium",
497
+ metadata: {
498
+ factualClaimCount,
499
+ sourceCitationCount,
500
+ requireSources,
501
+ contentLength: content.length,
502
+ generationTimeMs
503
+ },
504
+ suggestion: "Please provide sources for factual claims or clarify that claims are general knowledge"
505
+ };
506
+ }
507
+ );
508
+ var privacyLeakageDetector = () => createOutputGuardrail(
509
+ "privacy-leakage-detector",
510
+ (context) => {
511
+ const { text, object } = extractContent(context.result);
512
+ const content = text || (object ? JSON.stringify(object) : "");
513
+ const privacyPatterns = {
514
+ personal: /\b(john|jane|smith|doe|password|secret|private|confidential)\b/gi,
515
+ contact: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
516
+ phone: /\b\d{3}-\d{3}-\d{4}\b/g,
517
+ financial: /\b(credit card|ssn|social security|bank account|routing number)\b/gi,
518
+ location: /\b(address|street|apartment|zip code|postal code)\b/gi
519
+ };
520
+ const detectedLeakage = [];
521
+ const matches = [];
522
+ for (const [category, pattern] of Object.entries(privacyPatterns)) {
523
+ const found = content.match(pattern);
524
+ if (found) {
525
+ detectedLeakage.push(category);
526
+ matches.push(...found);
527
+ }
528
+ }
529
+ return {
530
+ tripwireTriggered: detectedLeakage.length > 0,
531
+ message: detectedLeakage.length > 0 ? `Potential privacy leakage detected: ${detectedLeakage.join(", ")}` : void 0,
532
+ severity: "critical",
533
+ metadata: {
534
+ leakageCategories: detectedLeakage,
535
+ matchCount: matches.length,
536
+ contentLength: content.length
537
+ },
538
+ suggestion: "Review output for any personal or sensitive information that should be removed"
539
+ };
540
+ }
541
+ );
542
+ var contentConsistencyChecker = (referenceContent) => createOutputGuardrail(
543
+ "content-consistency-checker",
544
+ (context) => {
545
+ const { text, object } = extractContent(context.result);
546
+ const content = text || (object ? JSON.stringify(object) : "");
547
+ if (!referenceContent) {
548
+ return { tripwireTriggered: false };
549
+ }
550
+ const contentWords = content.toLowerCase().split(/\s+/);
551
+ const referenceWords = referenceContent.toLowerCase().split(/\s+/);
552
+ const commonWords = contentWords.filter(
553
+ (word) => referenceWords.includes(word)
554
+ );
555
+ const consistencyScore = commonWords.length / Math.max(contentWords.length, referenceWords.length);
556
+ const isInconsistent = consistencyScore < 0.3;
557
+ return {
558
+ tripwireTriggered: isInconsistent,
559
+ message: isInconsistent ? `Content consistency score too low: ${consistencyScore.toFixed(2)}` : void 0,
560
+ severity: "medium",
561
+ metadata: {
562
+ consistencyScore,
563
+ contentLength: content.length,
564
+ referenceLength: referenceContent.length,
565
+ commonWordCount: commonWords.length
566
+ },
567
+ suggestion: "Ensure output maintains consistency with reference content"
568
+ };
569
+ }
570
+ );
571
+ var complianceChecker = (regulations = []) => createOutputGuardrail(
572
+ "compliance-checker",
573
+ (context) => {
574
+ const { text, object } = extractContent(context.result);
575
+ const content = text || (object ? JSON.stringify(object) : "");
576
+ const compliancePatterns = {
577
+ gdpr: ["personal data", "data processing", "consent", "data subject"],
578
+ hipaa: ["patient", "medical", "health information", "protected health"],
579
+ pci: ["credit card", "payment", "cardholder", "card number"],
580
+ sox: ["financial", "audit", "internal controls", "financial reporting"],
581
+ coppa: ["children", "under 13", "parental consent", "minor"]
582
+ };
583
+ const violations = [];
584
+ for (const regulation of regulations) {
585
+ const patterns = compliancePatterns[regulation.toLowerCase()];
586
+ if (patterns) {
587
+ const found = patterns.some(
588
+ (pattern) => content.toLowerCase().includes(pattern)
589
+ );
590
+ if (found) {
591
+ violations.push(regulation.toUpperCase());
592
+ }
593
+ }
594
+ }
595
+ return {
596
+ tripwireTriggered: violations.length > 0,
597
+ message: violations.length > 0 ? `Potential compliance violations detected: ${violations.join(", ")}` : void 0,
598
+ severity: "high",
599
+ metadata: {
600
+ violations,
601
+ regulations,
602
+ contentLength: content.length,
603
+ environment: context.input?.environment
604
+ },
605
+ suggestion: "Review output for compliance with applicable regulations"
606
+ };
607
+ }
608
+ );
609
+ export {
610
+ biasDetector,
611
+ blockedContent,
612
+ blockedOutputContent,
613
+ complianceChecker,
614
+ confidenceThreshold,
615
+ contentConsistencyChecker,
616
+ customValidation,
617
+ extractContent,
618
+ factualAccuracyChecker,
619
+ hallucinationDetector,
620
+ jsonValidation,
621
+ lengthLimit,
622
+ outputLengthLimit,
623
+ performanceMonitor,
624
+ privacyLeakageDetector,
625
+ schemaValidation,
626
+ tokenUsageLimit,
627
+ toxicityFilter
628
+ };