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 +354 -721
- package/dist/index.cjs +67 -4
- package/dist/index.d.cts +80 -2
- package/dist/index.d.ts +80 -2
- package/dist/index.js +63 -3
- package/package.json +11 -11
package/README.md
CHANGED
|
@@ -1,41 +1,53 @@
|
|
|
1
1
|
# AI SDK Guardrails
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
6
|
-
[](https://www.npmjs.com/package/ai-sdk-guardrails)
|
|
7
|
-
[](https://www.typescriptlang.org/)
|
|
8
|
-
[](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
|
-
|
|
7
|
+

|
|
11
8
|
|
|
12
|
-
|
|
9
|
+
## ⚡ TL;DR
|
|
13
10
|
|
|
14
|
-
|
|
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
|
-

|
|
23
|
-
|
|
24
|
-
### ⚡ **Optimize API Usage**
|
|
11
|
+
Quickly add input and output validation to any AI SDK-compatible model.
|
|
25
12
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
39
|
+
// 2. Wrap your model
|
|
40
|
+
const guardedModel = wrapWithGuardrails(openai('gpt-4o'), {
|
|
41
|
+
inputGuardrails: [inputGuard],
|
|
42
|
+
outputGuardrails: [outputGuard],
|
|
43
|
+
});
|
|
37
44
|
|
|
38
|
-
|
|
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
|
-
|
|
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
|
|
105
|
+
import { generateText } from 'ai';
|
|
90
106
|
import { openai } from '@ai-sdk/openai';
|
|
91
|
-
import {
|
|
92
|
-
|
|
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
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
model: optimizedModel,
|
|
107
|
-
prompt: 'hello', // ❌ Blocked - prevents unnecessary API call
|
|
136
|
+
const optimizedModel = wrapWithInputGuardrails(openai('gpt-4'), {
|
|
137
|
+
inputGuardrails: [lengthGuard],
|
|
108
138
|
});
|
|
109
|
-
|
|
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 {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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:
|
|
167
|
-
|
|
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 =
|
|
176
|
-
|
|
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
|
-
|
|
235
|
+
## ✨ Features
|
|
188
236
|
|
|
189
|
-
|
|
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
|
-
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
##
|
|
256
|
+
## 🧠 Design Philosophy
|
|
321
257
|
|
|
322
|
-
|
|
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
|
-
|
|
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
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
|
|
274
|
+
subgraph "AI SDK Guardrails Middleware"
|
|
275
|
+
InputMW[Input Guardrails Middleware]
|
|
276
|
+
OutputMW[Output Guardrails Middleware]
|
|
346
277
|
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
|
|
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
|
-
|
|
295
|
+
subgraph "AI SDK Core"
|
|
296
|
+
Wrapper[wrapLanguageModel]
|
|
297
|
+
Generator[generateText/Object/Stream]
|
|
298
|
+
end
|
|
353
299
|
|
|
354
|
-
|
|
300
|
+
subgraph "External Services"
|
|
301
|
+
AI[AI Model Provider]
|
|
302
|
+
Log[Logging & Telemetry]
|
|
303
|
+
end
|
|
355
304
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
|
|
389
|
-
|
|
390
|
-
model: protectedModel,
|
|
391
|
-
prompt: userInput,
|
|
392
|
-
});
|
|
313
|
+
InputMW -->|Valid Request| Wrapper
|
|
314
|
+
InputMW -->|Blocked Request| Log
|
|
393
315
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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
|
-
|
|
320
|
+
OutputMW --> Quality
|
|
321
|
+
OutputMW --> Sensitive
|
|
322
|
+
OutputMW --> Professional
|
|
323
|
+
OutputMW --> Factual
|
|
324
|
+
OutputMW --> Custom2
|
|
409
325
|
|
|
410
|
-
|
|
326
|
+
OutputMW -->|Clean Response| App
|
|
327
|
+
OutputMW -->|Quality Issues| Log
|
|
411
328
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
454
|
-
return "I can't help with that topic. Try asking about something else I can assist with.";
|
|
339
|
+
### Rate Limiting
|
|
455
340
|
|
|
456
|
-
|
|
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
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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
|
-
###
|
|
360
|
+
### LLM-as-Judge for Quality Scoring
|
|
472
361
|
|
|
473
|
-
|
|
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
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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
|
-
|
|
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
|
-
|
|
491
|
-
|
|
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
|
-
//
|
|
498
|
-
|
|
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:
|
|
400
|
+
message: 'Input too short - likely to produce low-value response',
|
|
512
401
|
severity: 'medium',
|
|
513
|
-
|
|
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
|
-
|
|
521
|
-
if (prompt && prompt.length > 12000) {
|
|
406
|
+
if (prompt.length > 4000) {
|
|
522
407
|
return {
|
|
523
408
|
tripwireTriggered: true,
|
|
524
|
-
message:
|
|
525
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
496
|
+
## 🔄 Streaming Support
|
|
604
497
|
|
|
605
|
-
|
|
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
|
|
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
|
-
|
|
644
|
-
|
|
645
|
-
|
|
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
|
-
|
|
669
|
-
|
|
507
|
+
const { textStream } = await streamText({
|
|
508
|
+
model: guardedModel,
|
|
509
|
+
prompt: 'Tell me a short story about a robot.',
|
|
670
510
|
});
|
|
671
511
|
|
|
672
|
-
//
|
|
673
|
-
|
|
674
|
-
|
|
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
|
-
|
|
680
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
709
|
-
|
|
710
|
-
|
|
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
|
-
|
|
716
|
-
|
|
717
|
-
|
|
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
|
-
|
|
725
|
-
|
|
726
|
-
### Smart Input Filtering
|
|
545
|
+
### User-Friendly Error Messages
|
|
727
546
|
|
|
728
|
-
|
|
547
|
+
Transform technical guardrail messages into user-friendly guidance:
|
|
729
548
|
|
|
730
549
|
```typescript
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
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
|
-
|
|
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
|
-
|
|
560
|
+
case 'user-rate-limit':
|
|
561
|
+
return "You're sending requests too quickly. Please wait a moment before trying again.";
|
|
771
562
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
}
|
|
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
|
|
574
|
+
The library seamlessly integrates with all AI SDK functions:
|
|
815
575
|
|
|
816
576
|
```typescript
|
|
817
|
-
// Create your
|
|
818
|
-
const productionModel =
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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.
|