ai-sdk-guardrails 2.0.0 → 3.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.
package/README.md CHANGED
@@ -1,41 +1,53 @@
1
1
  # AI SDK Guardrails
2
2
 
3
- Stop unnecessary AI calls. Optimize performance, improve quality, and prevent inappropriate AI responses with intelligent middleware for the Vercel AI SDK.
3
+ A powerful middleware for the Vercel AI SDK that adds safety, quality control, and cost management to your AI applications by intercepting prompts and responses.
4
4
 
5
- [![npm version](https://badge.fury.io/js/ai-sdk-guardrails.svg)](https://www.npmjs.com/package/ai-sdk-guardrails)
6
- [![Downloads](https://img.shields.io/npm/dm/ai-sdk-guardrails.svg)](https://www.npmjs.com/package/ai-sdk-guardrails)
7
- [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
8
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ Block harmful inputs, filter low-quality outputs, and gain observability, all in just a few lines of code.
9
6
 
10
- ## Requirements
7
+ ![Guardrails Demo](./media/guardrail-example.gif)
11
8
 
12
- This library requires the Vercel AI SDK (v5 and above) for its composable middleware architecture:
9
+ ## TL;DR
13
10
 
14
- ```bash
15
- pnpm add ai@latest ai-sdk-guardrails
16
- ```
17
-
18
- ## Why Use AI SDK Guardrails?
19
-
20
- Every AI call consumes resources and carries risk. One inappropriate response can damage your reputation whilst inefficient requests waste valuable API resources. AI SDK Guardrails helps you:
21
-
22
- ![Guardrails](./media/guardrail-example.gif)
23
-
24
- ### ⚡ **Optimize API Usage**
11
+ Quickly add input and output validation to any AI SDK-compatible model.
25
12
 
26
- Block inefficient requests before they reach your AI model. Validate inputs, enforce length limits, detect spam, and prevent unnecessary calls that would have failed anyway.
27
-
28
- ### 🎯 **Improve Response Quality**
29
-
30
- Ensure every AI response meets your standards. Filter out hallucinations, check factuality, enforce formatting requirements, and maintain consistent quality across your application.
13
+ ```typescript
14
+ import { openai } from '@ai-sdk/openai';
15
+ import { generateText } from 'ai';
16
+ import {
17
+ wrapWithGuardrails,
18
+ defineInputGuardrail,
19
+ defineOutputGuardrail,
20
+ } from 'ai-sdk-guardrails';
31
21
 
32
- ### 🛡️ **Prevent Embarrassing Responses**
22
+ // 1. Define your guardrails
23
+ const inputGuard = defineInputGuardrail({
24
+ name: 'length-check',
25
+ execute: async ({ prompt }) =>
26
+ prompt.length > 100
27
+ ? { tripwireTriggered: true, message: 'Input too long' }
28
+ : { tripwireTriggered: false },
29
+ });
33
30
 
34
- Stop your AI from saying things you'll regret. Block inappropriate content, filter sensitive information, detect bias, and maintain professional standards in all interactions.
31
+ const outputGuard = defineOutputGuardrail({
32
+ name: 'quality-check',
33
+ execute: async ({ result }) =>
34
+ result.text.length < 10
35
+ ? { tripwireTriggered: true, message: 'Response too short' }
36
+ : { tripwireTriggered: false },
37
+ });
35
38
 
36
- ### **Focus AI Generations**
39
+ // 2. Wrap your model
40
+ const guardedModel = wrapWithGuardrails(openai('gpt-4o'), {
41
+ inputGuardrails: [inputGuard],
42
+ outputGuardrails: [outputGuard],
43
+ });
37
44
 
38
- Guide your AI to stay on topic and provide useful responses. Prevent prompt injection, enforce business policies, and ensure responses align with your application's purpose.
45
+ // 3. Use it! Guardrails will run automatically.
46
+ const { text } = await generateText({
47
+ model: guardedModel,
48
+ prompt: 'A prompt that is definitely not too long.',
49
+ });
50
+ ```
39
51
 
40
52
  ## How It Works
41
53
 
@@ -69,44 +81,71 @@ flowchart LR
69
81
 
70
82
  That's it! Input guardrails optimize resource usage by stopping inefficient requests. Output guardrails ensure quality by filtering responses.
71
83
 
72
- ## Installation
84
+ ## 📦 Installation
73
85
 
74
86
  ```bash
75
87
  npm install ai-sdk-guardrails
88
+
76
89
  # or
77
- pnpm add ai-sdk-guardrails
78
- # or
90
+
79
91
  yarn add ai-sdk-guardrails
92
+
93
+ # or
94
+
95
+ pnpm add ai-sdk-guardrails
80
96
  ```
81
97
 
82
- ## Quick Start
98
+ ## 🚀 Quick Start
83
99
 
84
100
  Add smart validation to your AI applications in just 3 steps:
85
101
 
86
102
  ### 1. Prevent Unnecessary AI Calls
87
103
 
88
104
  ```typescript
89
- import { generateText, wrapLanguageModel } from 'ai';
105
+ import { generateText } from 'ai';
90
106
  import { openai } from '@ai-sdk/openai';
91
- import { createInputGuardrailsMiddleware } from 'ai-sdk-guardrails';
92
- import { blockedKeywords } from 'ai-sdk-guardrails/guardrails/input';
107
+ import {
108
+ wrapWithInputGuardrails,
109
+ defineInputGuardrail,
110
+ } from 'ai-sdk-guardrails';
111
+ import { extractTextContent } from 'ai-sdk-guardrails/guardrails/input';
93
112
 
94
113
  // Block inefficient requests before calling the AI model
95
- const optimizedModel = wrapLanguageModel({
96
- model: openai('gpt-4'),
97
- middleware: [
98
- createInputGuardrailsMiddleware({
99
- inputGuardrails: [blockedKeywords(['spam', 'test', 'hello'])],
100
- }),
101
- ],
114
+ const lengthGuard = defineInputGuardrail({
115
+ name: 'blocked-keywords',
116
+ execute: async (context) => {
117
+ const { prompt } = extractTextContent(context);
118
+ const blockedWords = ['spam', 'test', 'hello'];
119
+
120
+ const foundWord = blockedWords.find((word) =>
121
+ prompt.toLowerCase().includes(word.toLowerCase()),
122
+ );
123
+
124
+ if (foundWord) {
125
+ return {
126
+ tripwireTriggered: true,
127
+ message: `Blocked keyword detected: ${foundWord}`,
128
+ severity: 'medium',
129
+ };
130
+ }
131
+
132
+ return { tripwireTriggered: false };
133
+ },
102
134
  });
103
135
 
104
- // This would normally waste an API call for a useless response
105
- const result = await generateText({
106
- model: optimizedModel,
107
- prompt: 'hello', // ❌ Blocked - prevents unnecessary API call
136
+ const optimizedModel = wrapWithInputGuardrails(openai('gpt-4'), {
137
+ inputGuardrails: [lengthGuard],
108
138
  });
109
- // → Throws GuardrailError: "Blocked keyword detected: hello"
139
+
140
+ // This would normally waste an API call for a useless response
141
+ try {
142
+ const result = await generateText({
143
+ model: optimizedModel,
144
+ prompt: 'hello', // ❌ Blocked - prevents unnecessary API call
145
+ });
146
+ } catch (error) {
147
+ console.log('Blocked request, saved money!');
148
+ }
110
149
 
111
150
  // This generates valuable content
112
151
  const goodResult = await generateText({
@@ -118,25 +157,45 @@ const goodResult = await generateText({
118
157
  ### 2. Ensure Quality Output
119
158
 
120
159
  ```typescript
121
- import { createOutputGuardrailsMiddleware } from 'ai-sdk-guardrails';
122
- import { sensitiveInfoDetector } from 'ai-sdk-guardrails/guardrails/output';
123
-
124
- const qualityModel = wrapLanguageModel({
125
- model: openai('gpt-4'),
126
- middleware: [
127
- // Optimize by filtering inefficient inputs
128
- createInputGuardrailsMiddleware({
129
- inputGuardrails: [blockedKeywords(['spam', 'test'])],
130
- }),
131
-
132
- // Ensure quality outputs
133
- createOutputGuardrailsMiddleware({
134
- outputGuardrails: [sensitiveInfoDetector()], // Prevents data leaks
135
- onOutputBlocked: (results) => {
136
- console.log('Prevented embarrassing response:', results[0]?.message);
137
- },
138
- }),
139
- ],
160
+ import {
161
+ wrapWithOutputGuardrails,
162
+ defineOutputGuardrail,
163
+ } from 'ai-sdk-guardrails';
164
+ import { extractContent } from 'ai-sdk-guardrails/guardrails/output';
165
+
166
+ const qualityGuard = defineOutputGuardrail({
167
+ name: 'sensitive-info-detector',
168
+ execute: async (context) => {
169
+ const { text } = extractContent(context.result);
170
+
171
+ // Simple sensitive info patterns
172
+ const sensitivePatterns = [
173
+ /\b\d{3}-\d{2}-\d{4}\b/, // SSN
174
+ /\b[\w\.-]+@[\w\.-]+\.\w+\b/, // Email
175
+ /\b\d{3}-\d{3}-\d{4}\b/, // Phone
176
+ ];
177
+
178
+ const foundPattern = sensitivePatterns.find((pattern) =>
179
+ pattern.test(text),
180
+ );
181
+
182
+ if (foundPattern) {
183
+ return {
184
+ tripwireTriggered: true,
185
+ message: 'Sensitive information detected in response',
186
+ severity: 'high',
187
+ };
188
+ }
189
+
190
+ return { tripwireTriggered: false };
191
+ },
192
+ });
193
+
194
+ const qualityModel = wrapWithOutputGuardrails(openai('gpt-4'), {
195
+ outputGuardrails: [qualityGuard],
196
+ onOutputBlocked: (results) => {
197
+ console.log('Prevented sensitive data leak:', results[0]?.message);
198
+ },
140
199
  });
141
200
 
142
201
  const result = await generateText({
@@ -144,388 +203,227 @@ const result = await generateText({
144
203
  prompt: 'Create a user profile example',
145
204
  });
146
205
  // Automatically blocks responses containing emails, phone numbers, or SSNs
147
- // Prevents: "Here's a profile: john.doe@email.com, (555) 123-4567, SSN: 123-45-6789"
148
- // Returns: "Here's a profile: [contact information], [phone number], [SSN removed]"
149
206
  ```
150
207
 
151
208
  ### 3. Custom Business Logic
152
209
 
153
210
  ```typescript
154
- import { defineInputGuardrail } from 'ai-sdk-guardrails';
155
- import { extractTextContent } from 'ai-sdk-guardrails/guardrails/input';
156
-
157
- // Prevent inefficient homework help requests
158
- const homeworkDetector = defineInputGuardrail({
159
- name: 'homework-detector',
160
- execute: async (context) => {
161
- const { prompt } = extractTextContent(context);
162
-
163
- if (prompt.includes('solve this equation') || prompt.includes('homework')) {
211
+ const businessHoursGuard = defineInputGuardrail({
212
+ name: 'business-hours-only',
213
+ execute: async () => {
214
+ const hour = new Date().getUTCHours();
215
+ // Only allow requests between 9 AM and 5 PM UTC
216
+ if (hour < 9 || hour > 17) {
164
217
  return {
165
218
  tripwireTriggered: true,
166
- message: 'Homework help blocked - prevents inefficient API usage',
167
- suggestion: 'Ask about learning concepts instead',
219
+ message:
220
+ 'Requests are only permitted during business hours (9:00-17:00 UTC).',
221
+ severity: 'low',
168
222
  };
169
223
  }
170
-
171
224
  return { tripwireTriggered: false };
172
225
  },
173
226
  });
174
227
 
175
- const smartEducationModel = wrapLanguageModel({
176
- model: openai('gpt-4'),
177
- middleware: [
178
- createInputGuardrailsMiddleware({
179
- inputGuardrails: [homeworkDetector],
180
- }),
181
- ],
228
+ const smartEducationModel = wrapWithInputGuardrails(openai('gpt-4'), {
229
+ inputGuardrails: [businessHoursGuard],
182
230
  });
183
231
  ```
184
232
 
185
233
  **That's it!** Your AI application now optimizes resource usage, ensures quality, and prevents inappropriate responses automatically.
186
234
 
187
- ### Smart API Optimization Strategy
235
+ ## Features
188
236
 
189
- Build intelligent resource management by combining multiple validation layers:
237
+ - 🛡️ **Input & Output Guardrails**: Enforce custom safety, compliance, and quality policies on both prompts and LLM responses.
238
+ - 💰 **Cost Control**: Block invalid or wasteful prompts before they are sent to your LLM provider, saving you money.
239
+ - 🎯 **Quality Improvement**: Automatically filter, flag, or retry low-quality or irrelevant model outputs.
240
+ - 🔄 **Streaming Support**: Works seamlessly with both streaming (streamText) and standard (generateText) API responses.
241
+ - 📊 **Observability Hooks**: Built-in callbacks (onInputBlocked, onOutputBlocked, etc.) for logging and monitoring.
242
+ - ⚙️ **Configurable Execution**: Run guardrails in parallel or sequentially and set custom timeouts.
243
+ - 🚀 **AI SDK Native**: Designed from the ground up to integrate cleanly with AI SDK middleware patterns.
190
244
 
191
- ```typescript
192
- // Layer 1: Immediate optimization - block inefficient requests
193
- const optimizationLayer = [
194
- defineInputGuardrail({
195
- name: 'length-validator',
196
- description: 'Prevents expensive long requests that often fail',
197
- execute: async (params) => {
198
- const { prompt } = extractTextContent(params);
199
- if (typeof prompt === 'string') {
200
- // Block extremely short requests that waste resources
201
- if (prompt.trim().length < 10) {
202
- return {
203
- tripwireTriggered: true,
204
- message: 'Request too short - inefficient API usage',
205
- severity: 'medium',
206
- };
207
- }
208
- // Block extremely long requests that often hit limits anyway
209
- if (prompt.length > 8000) {
210
- return {
211
- tripwireTriggered: true,
212
- message: 'Request too long - would likely hit token limits',
213
- severity: 'medium',
214
- suggestion: 'Break into smaller, focused requests',
215
- };
216
- }
217
- }
218
- return { tripwireTriggered: false };
219
- },
220
- }),
221
-
222
- defineInputGuardrail({
223
- name: 'spam-detector',
224
- description: 'Blocks repetitive or low-value requests',
225
- execute: async (params) => {
226
- const { prompt } = extractTextContent(params);
227
-
228
- // Detect repetitive patterns that waste money
229
- const spamPatterns = [
230
- /^(.)\1{10,}$/, // Repeated characters
231
- /^(test|hello|hi|hey)$/i, // Common spam words
232
- /(.{1,20})\1{3,}/g, // Repetitive phrases
233
- ];
234
-
235
- if (
236
- typeof prompt === 'string' &&
237
- spamPatterns.some((pattern) => pattern.test(prompt))
238
- ) {
239
- return {
240
- tripwireTriggered: true,
241
- message:
242
- 'Spam-like content blocked - preventing unnecessary API calls',
243
- severity: 'high',
244
- };
245
- }
246
- return { tripwireTriggered: false };
247
- },
248
- }),
249
- ];
250
-
251
- // Layer 2: Quality assurance - ensure responses are useful
252
- const qualityAssuranceLayer = [
253
- defineOutputGuardrail({
254
- name: 'response-value-checker',
255
- description: 'Ensures responses provide actual value',
256
- execute: async (context) => {
257
- const { text } = extractContent(context.result);
258
-
259
- // Check for low-value responses that waste resources
260
- const lowValueIndicators = [
261
- text.length < 20, // Too short to be useful
262
- /^(I don't know|I cannot|Sorry, I can't)/.test(text), // Refusal without help
263
- text.split(' ').length < 5, // Minimal effort response
264
- ];
265
-
266
- if (
267
- lowValueIndicators.some((indicator) => indicator === true || indicator)
268
- ) {
269
- return {
270
- tripwireTriggered: true,
271
- message: 'Low-value response detected - inefficient use of resources',
272
- severity: 'medium',
273
- suggestion: 'Rephrase request for more specific, actionable help',
274
- };
275
- }
276
-
277
- return { tripwireTriggered: false };
278
- },
279
- }),
280
- ];
281
-
282
- // Smart model that optimizes performance and ensures quality
283
- const smartModel = wrapLanguageModel({
284
- model: openai('gpt-4'),
285
- middleware: [
286
- createInputGuardrailsMiddleware({
287
- inputGuardrails: optimizationLayer,
288
- throwOnBlocked: true, // Stop wasteful requests immediately
289
- }),
290
-
291
- createOutputGuardrailsMiddleware({
292
- outputGuardrails: qualityAssuranceLayer,
293
- throwOnBlocked: false, // Log quality issues but don't break flow
294
- onOutputBlocked: (results) => {
295
- // Track quality metrics for optimization
296
- console.log(
297
- 'Quality issue detected - optimizing for next time:',
298
- results[0]?.message,
299
- );
300
- },
301
- }),
302
- ],
303
- });
245
+ ## 📚 API Overview
304
246
 
305
- // Example usage with cost tracking
306
- const result = await generateText({
307
- model: smartModel,
308
- prompt: 'Write a comprehensive guide to software testing best practices',
309
- experimental_telemetry: {
310
- isEnabled: true,
311
- functionId: 'cost-optimised-generation',
312
- metadata: {
313
- resource_optimization: true,
314
- quality_checks: true,
315
- },
316
- },
317
- });
318
- ```
247
+ | Function | Description |
248
+ | ---------------------------- | ----------------------------------------------------------------------------- |
249
+ | `defineInputGuardrail()` | Creates a guardrail to validate, inspect, or block prompts. |
250
+ | `defineOutputGuardrail()` | Creates a guardrail to validate, filter, or re-route LLM outputs. |
251
+ | `wrapWithGuardrails()` | ⭐ **Recommended** - The easiest way to add both input and output guardrails. |
252
+ | `wrapWithInputGuardrails()` | Attaches input-only guardrails to a model. |
253
+ | `wrapWithOutputGuardrails()` | Attaches output-only guardrails to a model. |
254
+ | `InputBlockedError`, etc. | Custom, structured error types for easy try/catch handling. |
319
255
 
320
- ## Error Handling and Response Strategies
256
+ ## 🧠 Design Philosophy
321
257
 
322
- When guardrails block requests or filter responses, your application needs to handle these situations gracefully. Understanding the middleware flow helps you implement proper error handling:
258
+ - **Helper-First**: Simple, chainable utility functions provide a great developer experience for fast adoption.
259
+ - 🧩 **Composable**: Multiple guardrails can be chained together and will run in your specified order (or in parallel).
260
+ - 🧾 **Type-Safe**: Full TypeScript support with contextual typing for guardrail inputs, outputs, and metadata.
261
+ - 🧪 **Sensible Defaults**: Get started quickly with zero-config default behaviors that can be easily overridden.
323
262
 
324
- ### Middleware Flow and Decision Points
263
+ ## Architecture Overview
264
+
265
+ The library leverages the Vercel AI SDK's middleware architecture to provide composable guardrails that integrate seamlessly with your existing AI applications:
325
266
 
326
267
  ```mermaid
327
- flowchart TB
328
- A[Your Application] --> B[wrapLanguageModel]
329
- B --> C[Input Middleware]
330
- C --> D{Validation<br/>Passed?}
331
- D -->|❌ Blocked| E[GuardrailError<br/>+ Callbacks]
332
- D -->|✅ Approved| F[AI SDK Core]
333
- F --> G[AI Model Call]
334
- G --> H[Output Middleware]
335
- H --> I{Quality<br/>Check?}
336
- I -->|⚠️ Issues| J[Filtered Response<br/>+ Callbacks]
337
- I -->|✅ Clean| K[Final Response]
338
-
339
- style C fill:#e1f5fe
340
- style H fill:#f3e5f5
341
- style E fill:#ffebee
342
- style J fill:#fff3e0
343
- ```
268
+ graph TB
269
+ subgraph "Your Application"
270
+ App[Your App Code]
271
+ Config[Guardrail Configuration]
272
+ end
344
273
 
345
- The diagram shows two key decision points where your error handling strategies activate:
274
+ subgraph "AI SDK Guardrails Middleware"
275
+ InputMW[Input Guardrails Middleware]
276
+ OutputMW[Output Guardrails Middleware]
346
277
 
347
- - **Input validation failures** → Trigger callbacks or throw errors
348
- - **Output quality issues** → Filter responses and log concerns
278
+ subgraph "Input Guardrails Layer"
279
+ Length[Length Validation]
280
+ Spam[Spam Detection]
281
+ PII[PII Detection]
282
+ Business[Business Rules]
283
+ Custom1[Custom Guards]
284
+ end
349
285
 
350
- Here's how to implement proper error handling for each scenario:
286
+ subgraph "Output Guardrails Layer"
287
+ Quality[Quality Assurance]
288
+ Sensitive[Sensitive Info Filter]
289
+ Professional[Professional Tone]
290
+ Factual[Factual Validation]
291
+ Custom2[Custom Guards]
292
+ end
293
+ end
351
294
 
352
- ### Input Guardrail Errors
295
+ subgraph "AI SDK Core"
296
+ Wrapper[wrapLanguageModel]
297
+ Generator[generateText/Object/Stream]
298
+ end
353
299
 
354
- Input guardrails can either throw errors or trigger callbacks, depending on your configuration:
300
+ subgraph "External Services"
301
+ AI[AI Model Provider]
302
+ Log[Logging & Telemetry]
303
+ end
355
304
 
356
- ```typescript
357
- const protectedModel = wrapLanguageModel({
358
- model: openai('gpt-4'),
359
- middleware: [
360
- createInputGuardrailsMiddleware({
361
- inputGuardrails: [lengthLimitGuardrail, spamFilterGuardrail],
362
-
363
- // Option 1: Handle via callbacks (recommended for production)
364
- throwOnBlocked: false,
365
- onInputBlocked: (results) => {
366
- results.forEach((result) => {
367
- console.warn(
368
- `Blocked by ${result.context?.guardrailName}: ${result.message}`,
369
- );
370
-
371
- // Log to monitoring system
372
- analytics.track('guardrail_blocked', {
373
- guardrail: result.context?.guardrailName,
374
- severity: result.severity,
375
- suggestion: result.suggestion,
376
- });
377
-
378
- // Notify user with helpful message
379
- notifyUser(
380
- result.suggestion || 'Please refine your request and try again',
381
- );
382
- });
383
- },
384
- }),
385
- ],
386
- });
305
+ App --> Config
306
+ Config --> InputMW
307
+ InputMW --> Length
308
+ InputMW --> Spam
309
+ InputMW --> PII
310
+ InputMW --> Business
311
+ InputMW --> Custom1
387
312
 
388
- try {
389
- const result = await generateText({
390
- model: protectedModel,
391
- prompt: userInput,
392
- });
313
+ InputMW -->|Valid Request| Wrapper
314
+ InputMW -->|Blocked Request| Log
393
315
 
394
- // Handle successful response
395
- if (result.text) {
396
- return result.text;
397
- } else {
398
- // Request was blocked but handled gracefully
399
- return "I'm sorry, I couldn't process that request. Please try rephrasing.";
400
- }
401
- } catch (error) {
402
- // Handle any unexpected errors
403
- console.error('Unexpected error:', error);
404
- return "I'm experiencing technical difficulties. Please try again later.";
405
- }
406
- ```
316
+ Wrapper --> Generator
317
+ Generator --> AI
318
+ AI --> OutputMW
407
319
 
408
- ### Output Guardrail Handling
320
+ OutputMW --> Quality
321
+ OutputMW --> Sensitive
322
+ OutputMW --> Professional
323
+ OutputMW --> Factual
324
+ OutputMW --> Custom2
409
325
 
410
- Output guardrails typically filter or modify responses rather than throwing errors:
326
+ OutputMW -->|Clean Response| App
327
+ OutputMW -->|Quality Issues| Log
411
328
 
412
- ```typescript
413
- const qualityModel = wrapLanguageModel({
414
- model: openai('gpt-4'),
415
- middleware: [
416
- createOutputGuardrailsMiddleware({
417
- outputGuardrails: [sensitiveInfoDetector, professionalToneChecker],
418
- throwOnBlocked: false, // Usually false for output guardrails
419
- onOutputBlocked: (results) => {
420
- results.forEach((result) => {
421
- // Log quality issues for continuous improvement
422
- console.log(`Quality issue: ${result.message}`);
423
-
424
- // Track metrics
425
- metrics.increment('output_filtered', {
426
- guardrail: result.context?.guardrailName,
427
- severity: result.severity,
428
- });
429
-
430
- // Optionally regenerate with stronger guidance
431
- if (result.severity === 'high') {
432
- scheduleRegeneration(result.suggestion);
433
- }
434
- });
435
- },
436
- }),
437
- ],
438
- });
329
+ style InputMW fill:#e1f5fe
330
+ style OutputMW fill:#f3e5f5
331
+ style AI fill:#fff3e0
332
+ style App fill:#e8f5e8
439
333
  ```
440
334
 
441
- ### User-Friendly Error Messages
442
-
443
- Transform technical guardrail messages into user-friendly guidance:
444
-
445
- ```typescript
446
- function createUserFriendlyMessage(guardrailResult: GuardrailResult): string {
447
- const guardrailName = guardrailResult.context?.guardrailName;
335
+ ## 🍳 Recipes & Use Cases
448
336
 
449
- switch (guardrailName) {
450
- case 'content-length-limit':
451
- return 'Your message is too long. Please keep it under 500 characters for the best response.';
337
+ Guardrails can enforce any custom logic. Here are a few common patterns.
452
338
 
453
- case 'blocked-keywords':
454
- return "I can't help with that topic. Try asking about something else I can assist with.";
339
+ ### Rate Limiting
455
340
 
456
- case 'rate-limit':
457
- return "You're sending requests too quickly. Please wait a moment before trying again.";
341
+ Pass a userId in the metadata of your generateText call to enforce per-user rate limits.
458
342
 
459
- case 'math-homework-detector':
460
- return "I'm designed to help you understand concepts, not solve homework directly. Ask me to explain the topic instead!";
461
-
462
- default:
463
- return (
464
- guardrailResult.suggestion ||
465
- 'Please refine your request and try again.'
466
- );
467
- }
468
- }
343
+ ```typescript
344
+ const rateLimitGuard = defineInputGuardrail({
345
+ name: 'user-rate-limit',
346
+ execute: async ({ metadata }) => {
347
+ const userId = metadata?.userId ?? 'anonymous';
348
+ const allowed = await checkRateLimit(userId); // Your rate-limiting logic
349
+
350
+ return allowed
351
+ ? { tripwireTriggered: false }
352
+ : {
353
+ tripwireTriggered: true,
354
+ message: `Rate limit exceeded for user: ${userId}`,
355
+ };
356
+ },
357
+ });
469
358
  ```
470
359
 
471
- ### Best Practices for Error Handling
360
+ ### LLM-as-Judge for Quality Scoring
472
361
 
473
- 1. **Use callbacks over exceptions** for better user experience
474
- 2. **Log guardrail events** for monitoring and improvement
475
- 3. **Provide helpful suggestions** rather than just blocking
476
- 4. **Track metrics** to understand usage patterns
477
- 5. **Implement fallback responses** for graceful degradation
478
- 6. **Consider retry logic** with exponential backoff for rate limits
362
+ Use a cheaper, faster model to "judge" the output of a more powerful one.
479
363
 
480
- ## Understanding the Benefits
481
-
482
- ### Resource Optimization Through Input Validation
364
+ ```typescript
365
+ const qualityJudge = defineOutputGuardrail({
366
+ name: 'llm-quality-judge',
367
+ execute: async ({ result }) => {
368
+ // Use a cheap model to score the primary model's output
369
+ const judgement = await generateText({
370
+ model: openai('gpt-3.5-turbo'),
371
+ prompt: `Is the following response helpful and safe? Answer YES or NO. \n\nResponse: "${result.text}"`,
372
+ });
373
+
374
+ const isSafe = judgement.text.includes('YES');
375
+ return isSafe
376
+ ? { tripwireTriggered: false }
377
+ : {
378
+ tripwireTriggered: true,
379
+ message: `Output failed LLM-as-judge quality check.`,
380
+ metadata: { originalText: result.text },
381
+ };
382
+ },
383
+ });
384
+ ```
483
385
 
484
- Input guardrails act as intelligent gatekeepers that prevent inefficient API calls that would likely fail or provide little value:
386
+ ### Advanced Input Validation
485
387
 
486
388
  ```typescript
487
- import { defineInputGuardrail } from 'ai-sdk-guardrails';
488
389
  import { extractTextContent } from 'ai-sdk-guardrails/guardrails/input';
489
390
 
490
- // Prevent inefficient calls for common time-wasters
491
- const resourceOptimizationGuardrail = defineInputGuardrail({
492
- name: 'resource-optimization',
493
- description: 'Prevents inefficient API calls that provide little value',
391
+ const comprehensiveInputGuard = defineInputGuardrail({
392
+ name: 'comprehensive-input-validation',
494
393
  execute: async (context) => {
495
394
  const { prompt } = extractTextContent(context);
496
395
 
497
- // Block requests that typically result in low-value responses
498
- const timeWasters = [
499
- /^(hi|hello|hey|test)$/i,
500
- /^.{1,5}$/, // Too short
501
- /just testing/i,
502
- /can you hear me/i,
503
- ];
504
-
505
- const foundWaste = timeWasters.find((pattern) =>
506
- pattern.test(prompt || ''),
507
- );
508
- if (foundWaste) {
396
+ // Length validation
397
+ if (prompt.length < 10) {
509
398
  return {
510
399
  tripwireTriggered: true,
511
- message: `Blocked time-wasting request - prevented unnecessary API call`,
400
+ message: 'Input too short - likely to produce low-value response',
512
401
  severity: 'medium',
513
- metadata: {
514
- pattern: foundWaste.source,
515
- api_calls_prevented: 1,
516
- },
402
+ suggestion: 'Please provide more detailed input for better results',
517
403
  };
518
404
  }
519
405
 
520
- // Block requests that often exceed token limits
521
- if (prompt && prompt.length > 12000) {
406
+ if (prompt.length > 4000) {
522
407
  return {
523
408
  tripwireTriggered: true,
524
- message:
525
- 'Request likely to exceed token limits - preventing API failure',
409
+ message: 'Input too long - may exceed token limits',
410
+ severity: 'high',
411
+ suggestion: 'Break your request into smaller, focused parts',
412
+ };
413
+ }
414
+
415
+ // Content quality checks
416
+ const spamPatterns = [
417
+ /^(.)\1{10,}$/, // Repeated characters
418
+ /^(test|hello|hi|hey)$/i, // Common spam words
419
+ ];
420
+
421
+ const foundSpam = spamPatterns.find((pattern) => pattern.test(prompt));
422
+ if (foundSpam) {
423
+ return {
424
+ tripwireTriggered: true,
425
+ message: 'Low-quality input detected',
526
426
  severity: 'high',
527
- suggestion:
528
- 'Break into smaller, focused requests for better results and efficiency',
529
427
  };
530
428
  }
531
429
 
@@ -534,18 +432,13 @@ const resourceOptimizationGuardrail = defineInputGuardrail({
534
432
  });
535
433
  ```
536
434
 
537
- ### Quality Assurance Through Output Validation
538
-
539
- Output guardrails ensure every response meets your quality standards before reaching users:
435
+ ### Professional Output Quality Control
540
436
 
541
437
  ```typescript
542
- import { defineOutputGuardrail } from 'ai-sdk-guardrails';
543
438
  import { extractContent } from 'ai-sdk-guardrails/guardrails/output';
544
439
 
545
- // Ensure responses are professional and useful
546
- const professionalQualityGuardrail = defineOutputGuardrail({
440
+ const professionalQualityGuard = defineOutputGuardrail({
547
441
  name: 'professional-quality-control',
548
- description: 'Ensures responses meet professional standards',
549
442
  execute: async (context) => {
550
443
  const { text } = extractContent(context.result);
551
444
 
@@ -561,7 +454,7 @@ const professionalQualityGuardrail = defineOutputGuardrail({
561
454
  qualityIssues.push('Contains unprofessional language');
562
455
  }
563
456
 
564
- // Check for placeholder text that indicates incomplete response
457
+ // Check for placeholder text
565
458
  const placeholders = ['[insert', '[add', '[your', 'TODO:', 'FIXME:'];
566
459
  const hasPlaceholders = placeholders.some((placeholder) =>
567
460
  text.includes(placeholder),
@@ -600,263 +493,115 @@ const professionalQualityGuardrail = defineOutputGuardrail({
600
493
  });
601
494
  ```
602
495
 
603
- ### Streaming Intelligence: Real-Time Quality Control
496
+ ## 🔄 Streaming Support
604
497
 
605
- For streaming responses, maintain quality while preserving the real-time experience:
606
-
607
- ```mermaid
608
- sequenceDiagram
609
- participant U as User
610
- participant I as Input Guardrails
611
- participant A as AI Model
612
- participant S as Stream
613
- participant O as Output Guardrails
614
-
615
- U->>I: Request
616
- I->>I: ✅ Validate & Approve
617
- I->>A: Start Stream
618
- A->>S: Stream chunks
619
- loop Real-time streaming
620
- S-->>U: Chunk 1, 2, 3...
621
- end
622
- S->>O: Complete response
623
- O->>O: 🛡️ Quality check
624
- Note over O: Post-completion validation
625
- O-->>U: Quality feedback (if needed)
626
- ```
498
+ Guardrails work with streams out-of-the-box. Output guardrails will run after the complete response has been streamed and generated.
627
499
 
628
500
  ```typescript
629
- import { streamText, wrapLanguageModel } from 'ai';
630
- import {
631
- createOutputGuardrailsMiddleware,
632
- defineOutputGuardrail,
633
- } from 'ai-sdk-guardrails';
634
- import { extractContent } from 'ai-sdk-guardrails/guardrails/output';
635
-
636
- // Monitor streaming quality without interrupting the experience
637
- const streamingQualityGuardrail = defineOutputGuardrail({
638
- name: 'streaming-quality-monitor',
639
- description: 'Monitors streaming content for quality and appropriateness',
640
- execute: async (context) => {
641
- const { text } = extractContent(context.result);
501
+ import { streamText } from 'ai';
642
502
 
643
- // Quality checks that run after streaming completes
644
- const qualityMetrics = {
645
- coherence: calculateCoherence(text),
646
- completeness:
647
- text.endsWith('.') || text.endsWith('!') || text.endsWith('?'),
648
- appropriateness: !containsInappropriateContent(text),
649
- value: text.length > 50 && !isGenericResponse(text),
650
- };
651
-
652
- const issues = Object.entries(qualityMetrics)
653
- .filter(([_, passed]) => !passed)
654
- .map(([metric, _]) => metric);
655
-
656
- if (issues.length > 0) {
657
- return {
658
- tripwireTriggered: true,
659
- message: `Streaming quality issues: ${issues.join(', ')}`,
660
- severity: 'low', // Don't break user experience, just log
661
- metadata: {
662
- quality_metrics: qualityMetrics,
663
- stream_complete: true,
664
- },
665
- };
666
- }
503
+ const guardedModel = wrapWithGuardrails(openai('gpt-4o'), {
504
+ outputGuardrails: [qualityJudge],
505
+ });
667
506
 
668
- return { tripwireTriggered: false };
669
- },
507
+ const { textStream } = await streamText({
508
+ model: guardedModel,
509
+ prompt: 'Tell me a short story about a robot.',
670
510
  });
671
511
 
672
- // Helper functions for quality assessment
673
- function calculateCoherence(text: string): boolean {
674
- // Simple coherence check - more sophisticated versions could use embeddings
675
- const sentences = text.split(/[.!?]+/).filter((s) => s.trim());
676
- return sentences.length > 1 && sentences.every((s) => s.trim().length > 10);
512
+ // Stream the response to the client
513
+ for await (const delta of textStream) {
514
+ process.stdout.write(delta);
677
515
  }
678
516
 
679
- function containsInappropriateContent(text: string): boolean {
680
- const inappropriate = ['inappropriate', 'offensive', 'harmful'];
681
- return inappropriate.some((term) => text.toLowerCase().includes(term));
682
- }
517
+ // The qualityJudge guardrail will run after the stream is complete.
518
+ ```
683
519
 
684
- function isGenericResponse(text: string): boolean {
685
- const genericPhrases = [
686
- 'I cannot help',
687
- "I don't have information",
688
- 'I cannot provide',
689
- "Sorry, I can't",
690
- ];
691
- return genericPhrases.some((phrase) => text.includes(phrase));
692
- }
520
+ ## 🛠️ Error Handling
693
521
 
694
- const qualityStreamingModel = wrapLanguageModel({
695
- model: openai('gpt-4'),
696
- middleware: [
697
- createOutputGuardrailsMiddleware({
698
- outputGuardrails: [streamingQualityGuardrail],
699
- throwOnBlocked: false, // Log issues but don't interrupt streaming
700
- onOutputBlocked: (results) => {
701
- // Log for continuous improvement
702
- console.log('Stream quality feedback:', results[0]?.metadata);
703
- },
704
- }),
705
- ],
706
- });
522
+ When `throwOnBlocked: true` (the default), you can catch structured errors to handle blocks gracefully.
707
523
 
708
- // Stream with quality monitoring
709
- const enhancedStream = await streamText({
710
- model: qualityStreamingModel,
711
- prompt: 'Explain the benefits of automated testing',
712
- maxTokens: 1000,
713
- });
524
+ ```typescript
525
+ import { generateText } from 'ai';
526
+ import { isGuardrailsError } from 'ai-sdk-guardrails';
714
527
 
715
- // Users see real-time streaming
716
- for await (const chunk of enhancedStream.textStream) {
717
- process.stdout.write(chunk);
528
+ try {
529
+ const result = await generateText({
530
+ model: guardedModel,
531
+ prompt: 'A prompt that might be blocked...',
532
+ });
533
+ } catch (error) {
534
+ if (isGuardrailsError(error)) {
535
+ // Error was thrown by one of our guardrails
536
+ console.error('Guardrail check failed:', error.message);
537
+ console.error('Triggered Guards:', error.results);
538
+ } else {
539
+ // Some other error occurred
540
+ console.error('An unexpected error occurred:', error);
541
+ }
718
542
  }
719
-
720
- // Quality analysis happens post-stream for continuous improvement
721
- console.log('\nStream completed with quality monitoring');
722
543
  ```
723
544
 
724
- ## Production-Ready Performance and Quality Controls
725
-
726
- ### Smart Input Filtering
545
+ ### User-Friendly Error Messages
727
546
 
728
- Access comprehensive input validation that optimizes performance while maintaining user experience:
547
+ Transform technical guardrail messages into user-friendly guidance:
729
548
 
730
549
  ```typescript
731
- import {
732
- lengthLimit,
733
- rateLimitGuardrail,
734
- piiDetector,
735
- spamFilter,
736
- } from 'ai-sdk-guardrails/guardrails/input';
737
-
738
- const optimizedInputs = [
739
- // Prevent resource waste
740
- lengthLimit({
741
- minLength: 10, // Block "hi", "test", etc.
742
- maxLength: 4000, // Prevent token limit overruns
743
- encoding: 'tiktoken',
744
- }),
745
-
746
- spamFilter({
747
- detectRepetition: true,
748
- blockCommonWaste: ['test', 'hello', 'hi'],
749
- sensitivity: 'medium',
750
- }),
751
-
752
- // Smart rate limiting that adapts to user behavior
753
- rateLimitGuardrail({
754
- requestsPerMinute: 30,
755
- burstAllowance: 5,
756
- adaptiveThrottling: true, // Reduces limits for low-quality requests
757
- }),
758
-
759
- // Block requests containing sensitive data (often leads to refusals)
760
- piiDetector({
761
- redactionMode: 'block', // Stop the request entirely
762
- includeFinancial: true,
763
- strictMode: true,
764
- }),
765
- ];
766
- ```
550
+ function createUserFriendlyMessage(guardrailResult): string {
551
+ const guardrailName = guardrailResult.context?.guardrailName;
552
+
553
+ switch (guardrailName) {
554
+ case 'content-length-limit':
555
+ return 'Your message is too long. Please keep it under 500 characters for the best response.';
767
556
 
768
- ### Advanced Output Quality Assurance
557
+ case 'blocked-keywords':
558
+ return "I can't help with that topic. Try asking about something else I can assist with.";
769
559
 
770
- Choose from sophisticated output validation that ensures professional results:
560
+ case 'user-rate-limit':
561
+ return "You're sending requests too quickly. Please wait a moment before trying again.";
771
562
 
772
- ```typescript
773
- import {
774
- sensitiveInfoDetector,
775
- qualityAssurance,
776
- professionalToneChecker,
777
- factualnessValidator,
778
- } from 'ai-sdk-guardrails/guardrails/output';
779
-
780
- const qualityOutputs = [
781
- // Prevent embarrassing information leaks
782
- sensitiveInfoDetector({
783
- strictMode: true,
784
- blockOnDetection: true,
785
- customPatterns: ['internal-', 'confidential'],
786
- }),
787
-
788
- // Ensure responses meet quality standards
789
- qualityAssurance({
790
- minHelpfulness: 0.7,
791
- maxRepetition: 0.3,
792
- requireCompleteness: true,
793
- blockGenericResponses: true,
794
- }),
795
-
796
- // Maintain professional tone
797
- professionalToneChecker({
798
- blockUnprofessional: true,
799
- requireCourteousLanguage: true,
800
- forbidSlang: true,
801
- }),
802
-
803
- // Validate factual accuracy (uses AI-powered checking)
804
- factualnessValidator({
805
- confidence: 0.8,
806
- checkCitations: true,
807
- blockUncertain: false, // Log but don't block uncertain claims
808
- }),
809
- ];
563
+ default:
564
+ return (
565
+ guardrailResult.suggestion ||
566
+ 'Please refine your request and try again.'
567
+ );
568
+ }
569
+ }
810
570
  ```
811
571
 
812
572
  ## Complete AI SDK Integration
813
573
 
814
- The library seamlessly integrates with all AI SDK functions through the middleware architecture:
574
+ The library seamlessly integrates with all AI SDK functions:
815
575
 
816
576
  ```typescript
817
- // Create your performance-optimized, quality-assured model once
818
- const productionModel = wrapLanguageModel({
819
- model: openai('gpt-4'),
820
- middleware: [
821
- createInputGuardrailsMiddleware({
822
- inputGuardrails: optimizedInputs,
823
- }),
824
- createOutputGuardrailsMiddleware({
825
- outputGuardrails: qualityOutputs,
826
- }),
827
- ],
577
+ // Create your production-ready model once
578
+ const productionModel = wrapWithGuardrails(openai('gpt-4'), {
579
+ inputGuardrails: [lengthGuard, spamGuard, rateLimitGuard],
580
+ outputGuardrails: [qualityGuard, sensitiveInfoGuard],
581
+ throwOnBlocked: false,
582
+ onInputBlocked: (results) => {
583
+ console.log('Input blocked:', results[0]?.message);
584
+ },
585
+ onOutputBlocked: (results) => {
586
+ console.log('Output filtered:', results[0]?.message);
587
+ },
828
588
  });
829
589
 
830
- // Use with any AI SDK function - same optimization and quality everywhere
590
+ // Use with any AI SDK function
831
591
  const textResult = await generateText({
832
592
  model: productionModel,
833
593
  prompt: 'Write a professional email response',
834
- experimental_telemetry: {
835
- isEnabled: true,
836
- functionId: 'performance-optimized-text',
837
- metadata: { optimization: 'performance+quality' },
838
- },
839
594
  });
840
595
 
841
596
  const objectResult = await generateObject({
842
597
  model: productionModel,
843
598
  prompt: 'Create a user profile',
844
599
  schema: userProfileSchema,
845
- experimental_telemetry: {
846
- isEnabled: true,
847
- functionId: 'performance-optimized-object',
848
- metadata: { optimization: 'performance+quality' },
849
- },
850
600
  });
851
601
 
852
602
  const textStream = await streamText({
853
603
  model: productionModel,
854
604
  prompt: 'Explain our product features',
855
- experimental_telemetry: {
856
- isEnabled: true,
857
- functionId: 'performance-optimized-stream',
858
- metadata: { optimization: 'performance+quality' },
859
- },
860
605
  });
861
606
  ```
862
607
 
@@ -895,124 +640,12 @@ tsx examples/basic-guardrails.ts 1 # Run first example only
895
640
  tsx examples/streaming-guardrails.ts 3 # Run third streaming example
896
641
  ```
897
642
 
898
- All examples feature interactive menus with arrow key navigation, multi-selection with checkboxes, and automatic return to the main menu. They demonstrate practical performance optimization and quality assurance patterns.
899
-
900
- ## Architecture Overview
901
-
902
- The library leverages the Vercel AI SDK's middleware architecture to provide composable guardrails that integrate seamlessly with your existing AI applications:
903
-
904
- ```mermaid
905
- graph TB
906
- subgraph "Your Application"
907
- App[Your App Code]
908
- Config[Guardrail Configuration]
909
- end
910
-
911
- subgraph "AI SDK Guardrails Middleware"
912
- InputMW[Input Guardrails Middleware]
913
- OutputMW[Output Guardrails Middleware]
914
-
915
- subgraph "Input Guardrails Layer"
916
- Length[Length Validation]
917
- Spam[Spam Detection]
918
- PII[PII Detection]
919
- Business[Business Rules]
920
- Custom1[Custom Guards]
921
- end
922
-
923
- subgraph "Output Guardrails Layer"
924
- Quality[Quality Assurance]
925
- Sensitive[Sensitive Info Filter]
926
- Professional[Professional Tone]
927
- Factual[Factual Validation]
928
- Custom2[Custom Guards]
929
- end
930
- end
931
-
932
- subgraph "AI SDK Core"
933
- Wrapper[wrapLanguageModel]
934
- Generator[generateText/Object/Stream]
935
- end
936
-
937
- subgraph "External Services"
938
- AI[AI Model Provider]
939
- Log[Logging & Telemetry]
940
- end
941
-
942
- App --> Config
943
- Config --> InputMW
944
- InputMW --> Length
945
- InputMW --> Spam
946
- InputMW --> PII
947
- InputMW --> Business
948
- InputMW --> Custom1
949
-
950
- InputMW -->|Valid Request| Wrapper
951
- InputMW -->|Blocked Request| Log
952
-
953
- Wrapper --> Generator
954
- Generator --> AI
955
- AI --> OutputMW
956
-
957
- OutputMW --> Quality
958
- OutputMW --> Sensitive
959
- OutputMW --> Professional
960
- OutputMW --> Factual
961
- OutputMW --> Custom2
962
-
963
- OutputMW -->|Clean Response| App
964
- OutputMW -->|Quality Issues| Log
965
-
966
- style InputMW fill:#e1f5fe
967
- style OutputMW fill:#f3e5f5
968
- style AI fill:#fff3e0
969
- style App fill:#e8f5e8
970
- ```
971
-
972
- ### Middleware Execution Flow
973
-
974
- The guardrails execute in a specific order to maximize efficiency and ensure quality:
975
-
976
- ```mermaid
977
- sequenceDiagram
978
- participant App as Your Application
979
- participant IM as Input Middleware
980
- participant SDK as AI SDK Core
981
- participant AI as AI Model
982
- participant OM as Output Middleware
983
- participant Log as Telemetry
984
-
985
- App->>IM: User Request
986
- IM->>IM: ⚡ Resource Validation
987
- alt Request Blocked
988
- IM->>Log: 📊 Log Blocked Request
989
- IM->>App: ❌ GuardrailError
990
- else Request Approved
991
- IM->>SDK: ✅ Validated Request
992
- SDK->>AI: API Call
993
- AI->>SDK: Raw Response
994
- SDK->>OM: Process Response
995
- OM->>OM: 🛡️ Quality Validation
996
- alt Quality Issues
997
- OM->>Log: 📊 Log Quality Issues
998
- OM->>App: ⚠️ Filtered Response
999
- else High Quality
1000
- OM->>App: ✅ Clean Response
1001
- end
1002
- end
1003
- ```
1004
-
1005
- This architecture ensures that:
1006
-
1007
- - **Resource optimization** happens first to prevent unnecessary API calls
1008
- - **Quality assurance** happens last to ensure professional responses
1009
- - **Telemetry** captures both blocked requests and quality metrics
1010
- - **Composability** allows mixing and matching guardrails as needed
643
+ All examples feature interactive menus with arrow key navigation, multi-selection with checkboxes, and automatic return to the main menu.
1011
644
 
1012
- ## Contributing
645
+ ## 🤝 Contributing
1013
646
 
1014
- We welcome contributions! Please open issues and pull requests on [GitHub](https://github.com/jagreehal/ai-sdk-guardrails).
647
+ Contributions of all sizes are welcome! Please open issues and pull requests on [GitHub](https://github.com/jagreehal/ai-sdk-guardrails).
1015
648
 
1016
- ## License
649
+ ## 📄 License
1017
650
 
1018
- MIT © [Jag Reehal](https://github.com/jagreehal)
651
+ MIT © [Jag Reehal](https://github.com/jagreehal) – See LICENSE for full details.