ai-sdk-guardrails 1.0.0 → 2.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 +839 -550
- package/dist/chunk-HHQ3CIFN.js +12 -0
- package/dist/chunk-LLCOPUS6.js +159 -0
- package/dist/errors-BTTWMQEI.js +24 -0
- package/dist/guardrails/input.cjs +15 -9
- package/dist/guardrails/input.d.cts +3 -3
- package/dist/guardrails/input.d.ts +3 -3
- package/dist/guardrails/input.js +17 -9
- package/dist/guardrails/output.cjs +31 -7
- package/dist/guardrails/output.d.cts +2 -2
- package/dist/guardrails/output.d.ts +2 -2
- package/dist/guardrails/output.js +33 -7
- package/dist/index.cjs +694 -187
- package/dist/index.d.cts +193 -1
- package/dist/index.d.ts +193 -1
- package/dist/index.js +541 -14
- package/dist/{index-CWIb12lh.d.cts → types-B9h_0Gyl.d.cts} +31 -31
- package/dist/{index-CWIb12lh.d.ts → types-B9h_0Gyl.d.ts} +31 -31
- package/package.json +16 -12
- package/dist/chunk-BNGJDZMX.js +0 -218
package/README.md
CHANGED
|
@@ -1,84 +1,74 @@
|
|
|
1
1
|
# AI SDK Guardrails
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Stop unnecessary AI calls. Optimize performance, improve quality, and prevent inappropriate AI responses with intelligent middleware for the Vercel AI SDK.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/ai-sdk-guardrails)
|
|
6
6
|
[](https://www.npmjs.com/package/ai-sdk-guardrails)
|
|
7
7
|
[](https://www.typescriptlang.org/)
|
|
8
8
|
[](https://opensource.org/licenses/MIT)
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Requirements
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
This library requires the Vercel AI SDK (v5 and above) for its composable middleware architecture:
|
|
13
13
|
|
|
14
|
-
|
|
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:
|
|
15
21
|
|
|
16
|
-
|
|
22
|
+

|
|
17
23
|
|
|
18
|
-
### ⚡
|
|
24
|
+
### ⚡ **Optimize API Usage**
|
|
19
25
|
|
|
20
|
-
|
|
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.
|
|
21
27
|
|
|
22
|
-
### 🎯
|
|
28
|
+
### 🎯 **Improve Response Quality**
|
|
23
29
|
|
|
24
|
-
|
|
30
|
+
Ensure every AI response meets your standards. Filter out hallucinations, check factuality, enforce formatting requirements, and maintain consistent quality across your application.
|
|
25
31
|
|
|
26
|
-
###
|
|
32
|
+
### 🛡️ **Prevent Embarrassing Responses**
|
|
27
33
|
|
|
28
|
-
|
|
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.
|
|
35
|
+
|
|
36
|
+
### ⚡ **Focus AI Generations**
|
|
37
|
+
|
|
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.
|
|
29
39
|
|
|
30
40
|
## How It Works
|
|
31
41
|
|
|
42
|
+
### Without Guardrails (Inefficient, Poor Quality)
|
|
43
|
+
|
|
32
44
|
```mermaid
|
|
33
|
-
|
|
34
|
-
A[User Input] --> B
|
|
35
|
-
|
|
36
|
-
B -->|❌ Block| D[GuardrailError]
|
|
37
|
-
|
|
38
|
-
C --> E[AI Response]
|
|
39
|
-
E --> F{Output Guardrails}
|
|
40
|
-
F -->|✅ Pass| G[Safe Response]
|
|
41
|
-
F -->|❌ Block| H[GuardrailError]
|
|
42
|
-
|
|
43
|
-
subgraph "Input Guardrails"
|
|
44
|
-
I1[Length Limit]
|
|
45
|
-
I2[Blocked Keywords]
|
|
46
|
-
I3[PII Detection]
|
|
47
|
-
I4[Prompt Injection]
|
|
48
|
-
I5[Rate Limiting]
|
|
49
|
-
end
|
|
45
|
+
flowchart LR
|
|
46
|
+
A[User Input<br/>'hello'] --> B[AI Model] --> C[Response<br/>⚠️ Wastes resources<br/>😞 Often useless]
|
|
47
|
+
```
|
|
50
48
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
49
|
+
### With Input Guardrails (Save Resources)
|
|
50
|
+
|
|
51
|
+
```mermaid
|
|
52
|
+
flowchart LR
|
|
53
|
+
A[User Input<br/>'hello'] --> B[Input Guardrails] --> C[❌ STOPPED<br/>✅ No API call made]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### With Output Guardrails (Ensure Quality)
|
|
58
57
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
B
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
F -.-> O5
|
|
70
|
-
|
|
71
|
-
classDef inputNode fill:#e1f5fe
|
|
72
|
-
classDef outputNode fill:#f3e5f5
|
|
73
|
-
classDef blockNode fill:#ffebee
|
|
74
|
-
classDef passNode fill:#e8f5e8
|
|
75
|
-
|
|
76
|
-
class I1,I2,I3,I4,I5 inputNode
|
|
77
|
-
class O1,O2,O3,O4,O5 outputNode
|
|
78
|
-
class D,H blockNode
|
|
79
|
-
class G passNode
|
|
58
|
+
```mermaid
|
|
59
|
+
flowchart LR
|
|
60
|
+
A[AI Response<br/>'Here's my SSN: 123-45-6789'] --> B[Output Guardrails] --> C[❌ BLOCKED<br/>🛡️ Privacy protected]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Complete Protection
|
|
64
|
+
|
|
65
|
+
```mermaid
|
|
66
|
+
flowchart LR
|
|
67
|
+
A[User Input] --> B[Input Guardrails] --> C[AI Model] --> D[Output Guardrails] --> E[Clean Response]
|
|
80
68
|
```
|
|
81
69
|
|
|
70
|
+
That's it! Input guardrails optimize resource usage by stopping inefficient requests. Output guardrails ensure quality by filtering responses.
|
|
71
|
+
|
|
82
72
|
## Installation
|
|
83
73
|
|
|
84
74
|
```bash
|
|
@@ -91,639 +81,938 @@ yarn add ai-sdk-guardrails
|
|
|
91
81
|
|
|
92
82
|
## Quick Start
|
|
93
83
|
|
|
94
|
-
|
|
84
|
+
Add smart validation to your AI applications in just 3 steps:
|
|
85
|
+
|
|
86
|
+
### 1. Prevent Unnecessary AI Calls
|
|
95
87
|
|
|
96
88
|
```typescript
|
|
97
|
-
import {
|
|
98
|
-
import { blockedKeywords } from 'ai-sdk-guardrails/guardrails/input';
|
|
99
|
-
import { outputLengthLimit } from 'ai-sdk-guardrails/guardrails/output';
|
|
89
|
+
import { generateText, wrapLanguageModel } from 'ai';
|
|
100
90
|
import { openai } from '@ai-sdk/openai';
|
|
91
|
+
import { createInputGuardrailsMiddleware } from 'ai-sdk-guardrails';
|
|
92
|
+
import { blockedKeywords } from 'ai-sdk-guardrails/guardrails/input';
|
|
101
93
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
94
|
+
// 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
|
+
],
|
|
102
|
+
});
|
|
103
|
+
|
|
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
|
|
108
|
+
});
|
|
109
|
+
// → Throws GuardrailError: "Blocked keyword detected: hello"
|
|
112
110
|
|
|
113
|
-
|
|
111
|
+
// This generates valuable content
|
|
112
|
+
const goodResult = await generateText({
|
|
113
|
+
model: optimizedModel,
|
|
114
|
+
prompt: 'Write a product description for our new software', // ✅ This creates value
|
|
115
|
+
});
|
|
114
116
|
```
|
|
115
117
|
|
|
116
|
-
|
|
118
|
+
### 2. Ensure Quality Output
|
|
117
119
|
|
|
118
|
-
|
|
120
|
+
```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
|
+
],
|
|
140
|
+
});
|
|
119
141
|
|
|
120
|
-
|
|
142
|
+
const result = await generateText({
|
|
143
|
+
model: qualityModel,
|
|
144
|
+
prompt: 'Create a user profile example',
|
|
145
|
+
});
|
|
146
|
+
// 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
|
+
```
|
|
121
150
|
|
|
122
|
-
|
|
123
|
-
- Enforce length limits
|
|
124
|
-
- Detect prompt injection attempts
|
|
125
|
-
- Remove personally identifiable information (PII)
|
|
126
|
-
- Implement rate limiting
|
|
151
|
+
### 3. Custom Business Logic
|
|
127
152
|
|
|
128
153
|
```typescript
|
|
129
|
-
import {
|
|
130
|
-
|
|
131
|
-
const mathHomeworkDetector = createInputGuardrail(
|
|
132
|
-
'math-homework-detector',
|
|
133
|
-
'Prevents direct homework solving requests',
|
|
134
|
-
(context) => {
|
|
135
|
-
const { prompt } = context;
|
|
136
|
-
const homeworkPatterns = [
|
|
137
|
-
/solve this equation/i,
|
|
138
|
-
/what is \d+ [\+\-\*\/] \d+/i,
|
|
139
|
-
/calculate the answer/i,
|
|
140
|
-
];
|
|
154
|
+
import { defineInputGuardrail } from 'ai-sdk-guardrails';
|
|
155
|
+
import { extractTextContent } from 'ai-sdk-guardrails/guardrails/input';
|
|
141
156
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
157
|
+
// Prevent inefficient homework help requests
|
|
158
|
+
const homeworkDetector = defineInputGuardrail({
|
|
159
|
+
name: 'homework-detector',
|
|
160
|
+
execute: async (context) => {
|
|
161
|
+
const { prompt } = extractTextContent(context);
|
|
145
162
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
163
|
+
if (prompt.includes('solve this equation') || prompt.includes('homework')) {
|
|
164
|
+
return {
|
|
165
|
+
tripwireTriggered: true,
|
|
166
|
+
message: 'Homework help blocked - prevents inefficient API usage',
|
|
167
|
+
suggestion: 'Ask about learning concepts instead',
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return { tripwireTriggered: false };
|
|
153
172
|
},
|
|
154
|
-
);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const smartEducationModel = wrapLanguageModel({
|
|
176
|
+
model: openai('gpt-4'),
|
|
177
|
+
middleware: [
|
|
178
|
+
createInputGuardrailsMiddleware({
|
|
179
|
+
inputGuardrails: [homeworkDetector],
|
|
180
|
+
}),
|
|
181
|
+
],
|
|
182
|
+
});
|
|
155
183
|
```
|
|
156
184
|
|
|
157
|
-
|
|
185
|
+
**That's it!** Your AI application now optimizes resource usage, ensures quality, and prevents inappropriate responses automatically.
|
|
158
186
|
|
|
159
|
-
|
|
187
|
+
### Smart API Optimization Strategy
|
|
160
188
|
|
|
161
|
-
|
|
162
|
-
- Block sensitive information
|
|
163
|
-
- Enforce formatting requirements
|
|
164
|
-
- Validate against schemas
|
|
165
|
-
- Check confidence levels
|
|
189
|
+
Build intelligent resource management by combining multiple validation layers:
|
|
166
190
|
|
|
167
191
|
```typescript
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
+
];
|
|
194
250
|
|
|
195
|
-
|
|
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
|
+
}
|
|
196
276
|
|
|
197
|
-
|
|
277
|
+
return { tripwireTriggered: false };
|
|
278
|
+
},
|
|
279
|
+
}),
|
|
280
|
+
];
|
|
198
281
|
|
|
199
|
-
|
|
200
|
-
|
|
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
|
+
});
|
|
201
304
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
onOutputBlocked: (error) => {
|
|
213
|
-
console.error('Stream blocked:', error.reason);
|
|
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,
|
|
214
315
|
},
|
|
215
316
|
},
|
|
216
|
-
);
|
|
217
|
-
|
|
218
|
-
// The stream will automatically stop if guardrails trigger
|
|
219
|
-
for await (const chunk of stream.textStream) {
|
|
220
|
-
process.stdout.write(chunk);
|
|
221
|
-
}
|
|
317
|
+
});
|
|
222
318
|
```
|
|
223
319
|
|
|
224
|
-
##
|
|
320
|
+
## Error Handling and Response Strategies
|
|
225
321
|
|
|
226
|
-
|
|
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:
|
|
227
323
|
|
|
228
|
-
|
|
229
|
-
import {
|
|
230
|
-
lengthLimit,
|
|
231
|
-
blockedWords,
|
|
232
|
-
blockedKeywords,
|
|
233
|
-
profanityFilter,
|
|
234
|
-
promptInjectionDetector,
|
|
235
|
-
piiDetector,
|
|
236
|
-
toxicityDetector,
|
|
237
|
-
mathHomeworkDetector,
|
|
238
|
-
codeGenerationLimiter,
|
|
239
|
-
} from 'ai-sdk-guardrails/guardrails/input';
|
|
324
|
+
### Middleware Flow and Decision Points
|
|
240
325
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
]
|
|
326
|
+
```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
|
+
```
|
|
248
344
|
|
|
249
|
-
|
|
250
|
-
const securityGuardrails = [promptInjectionDetector(), piiDetector()];
|
|
345
|
+
The diagram shows two key decision points where your error handling strategies activate:
|
|
251
346
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
mathHomeworkDetector(),
|
|
255
|
-
codeGenerationLimiter(['javascript', 'python']),
|
|
256
|
-
];
|
|
257
|
-
```
|
|
347
|
+
- **Input validation failures** → Trigger callbacks or throw errors
|
|
348
|
+
- **Output quality issues** → Filter responses and log concerns
|
|
258
349
|
|
|
259
|
-
|
|
350
|
+
Here's how to implement proper error handling for each scenario:
|
|
260
351
|
|
|
261
|
-
|
|
262
|
-
import {
|
|
263
|
-
lengthLimit,
|
|
264
|
-
blockedContent,
|
|
265
|
-
jsonValidation,
|
|
266
|
-
confidenceThreshold,
|
|
267
|
-
toxicityFilter,
|
|
268
|
-
schemaValidation,
|
|
269
|
-
tokenUsageLimit,
|
|
270
|
-
performanceMonitor,
|
|
271
|
-
hallucinationDetector,
|
|
272
|
-
biasDetector,
|
|
273
|
-
factualAccuracyChecker,
|
|
274
|
-
privacyLeakageDetector,
|
|
275
|
-
contentConsistencyChecker,
|
|
276
|
-
complianceChecker,
|
|
277
|
-
} from 'ai-sdk-guardrails/guardrails/output';
|
|
352
|
+
### Input Guardrail Errors
|
|
278
353
|
|
|
279
|
-
|
|
280
|
-
const qualityGuardrails = [
|
|
281
|
-
lengthLimit(500),
|
|
282
|
-
confidenceThreshold(0.8),
|
|
283
|
-
hallucinationDetector(0.7),
|
|
284
|
-
factualAccuracyChecker(true), // require sources
|
|
285
|
-
];
|
|
354
|
+
Input guardrails can either throw errors or trigger callbacks, depending on your configuration:
|
|
286
355
|
|
|
287
|
-
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
+
});
|
|
294
387
|
|
|
295
|
-
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
388
|
+
try {
|
|
389
|
+
const result = await generateText({
|
|
390
|
+
model: protectedModel,
|
|
391
|
+
prompt: userInput,
|
|
392
|
+
});
|
|
393
|
+
|
|
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
|
+
}
|
|
300
406
|
```
|
|
301
407
|
|
|
302
|
-
|
|
408
|
+
### Output Guardrail Handling
|
|
303
409
|
|
|
304
|
-
|
|
410
|
+
Output guardrails typically filter or modify responses rather than throwing errors:
|
|
305
411
|
|
|
306
412
|
```typescript
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
})
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
return {
|
|
331
|
-
tripwireTriggered: !isFactual,
|
|
332
|
-
message: isFactual
|
|
333
|
-
? `Factual content (score: ${evalResult.score})`
|
|
334
|
-
: `Factual accuracy too low (score: ${evalResult.score}, required: ${minScore})`,
|
|
335
|
-
severity: isFactual ? 'low' : 'high',
|
|
336
|
-
metadata: {
|
|
337
|
-
factualityScore: evalResult.score,
|
|
338
|
-
rationale: evalResult.metadata?.rationale,
|
|
339
|
-
expected,
|
|
340
|
-
minScore,
|
|
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
|
+
});
|
|
341
435
|
},
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
};
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// Usage
|
|
350
|
-
const result = await generateTextWithGuardrails(
|
|
351
|
-
{
|
|
352
|
-
model: openai('gpt-4-turbo'),
|
|
353
|
-
prompt: 'Which country has the highest population?',
|
|
354
|
-
},
|
|
355
|
-
{
|
|
356
|
-
outputGuardrails: [
|
|
357
|
-
createFactualityGuardrail({
|
|
358
|
-
expected: 'China',
|
|
359
|
-
minScore: 0.7,
|
|
360
|
-
}),
|
|
361
|
-
],
|
|
362
|
-
},
|
|
363
|
-
);
|
|
436
|
+
}),
|
|
437
|
+
],
|
|
438
|
+
});
|
|
364
439
|
```
|
|
365
440
|
|
|
366
|
-
|
|
441
|
+
### User-Friendly Error Messages
|
|
367
442
|
|
|
368
|
-
|
|
443
|
+
Transform technical guardrail messages into user-friendly guidance:
|
|
369
444
|
|
|
370
445
|
```typescript
|
|
371
|
-
|
|
446
|
+
function createUserFriendlyMessage(guardrailResult: GuardrailResult): string {
|
|
447
|
+
const guardrailName = guardrailResult.context?.guardrailName;
|
|
372
448
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if (error instanceof GuardrailError) {
|
|
377
|
-
// Access detailed error information
|
|
378
|
-
console.log('Guardrail triggered:', error.guardrailName);
|
|
379
|
-
console.log('Reason:', error.reason);
|
|
380
|
-
console.log('Type:', error.type); // 'input' or 'output'
|
|
381
|
-
|
|
382
|
-
// Get all issues
|
|
383
|
-
error.issues.forEach((issue) => {
|
|
384
|
-
console.log(`${issue.guardrail}: ${issue.message}`);
|
|
385
|
-
console.log('Severity:', issue.severity);
|
|
386
|
-
console.log('Suggestion:', issue.suggestion);
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
// Use helper methods
|
|
390
|
-
const summary = error.getSummary();
|
|
391
|
-
console.log(`Total issues: ${summary.totalIssues}`);
|
|
392
|
-
console.log(
|
|
393
|
-
`Guardrails triggered: ${summary.guardrailsTriggered.join(', ')}`,
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
```
|
|
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.';
|
|
398
452
|
|
|
399
|
-
|
|
453
|
+
case 'blocked-keywords':
|
|
454
|
+
return "I can't help with that topic. Try asking about something else I can assist with.";
|
|
400
455
|
|
|
401
|
-
|
|
456
|
+
case 'rate-limit':
|
|
457
|
+
return "You're sending requests too quickly. Please wait a moment before trying again.";
|
|
402
458
|
|
|
403
|
-
|
|
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!";
|
|
404
461
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
// Implement your custom logic
|
|
413
|
-
const validationResult = await validateBusinessRules({
|
|
414
|
-
content: prompt,
|
|
415
|
-
history: messages,
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
return {
|
|
419
|
-
tripwireTriggered: !validationResult.isValid,
|
|
420
|
-
message: validationResult.error,
|
|
421
|
-
metadata: validationResult.details,
|
|
422
|
-
severity: validationResult.severity,
|
|
423
|
-
suggestion: validationResult.suggestion,
|
|
424
|
-
};
|
|
425
|
-
},
|
|
426
|
-
);
|
|
462
|
+
default:
|
|
463
|
+
return (
|
|
464
|
+
guardrailResult.suggestion ||
|
|
465
|
+
'Please refine your request and try again.'
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
427
469
|
```
|
|
428
470
|
|
|
429
|
-
###
|
|
471
|
+
### Best Practices for Error Handling
|
|
430
472
|
|
|
431
|
-
|
|
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
|
|
432
479
|
|
|
433
|
-
|
|
434
|
-
// Development guardrails (more lenient)
|
|
435
|
-
const devGuardrails = {
|
|
436
|
-
inputGuardrails: [lengthLimit(2000)],
|
|
437
|
-
throwOnBlocked: false,
|
|
438
|
-
onInputBlocked: (error) => console.warn('Dev warning:', error),
|
|
439
|
-
};
|
|
440
|
-
|
|
441
|
-
// Production guardrails (strict)
|
|
442
|
-
const prodGuardrails = {
|
|
443
|
-
inputGuardrails: [lengthLimit(500), blockedKeywords(['test', 'debug'])],
|
|
444
|
-
outputGuardrails: [confidenceThreshold(0.9), complianceChecker(['GDPR'])],
|
|
445
|
-
throwOnBlocked: true,
|
|
446
|
-
};
|
|
447
|
-
|
|
448
|
-
// Use based on environment
|
|
449
|
-
const guardrails =
|
|
450
|
-
process.env.NODE_ENV === 'production' ? prodGuardrails : devGuardrails;
|
|
451
|
-
```
|
|
480
|
+
## Understanding the Benefits
|
|
452
481
|
|
|
453
|
-
###
|
|
482
|
+
### Resource Optimization Through Input Validation
|
|
454
483
|
|
|
455
|
-
|
|
484
|
+
Input guardrails act as intelligent gatekeepers that prevent inefficient API calls that would likely fail or provide little value:
|
|
456
485
|
|
|
457
486
|
```typescript
|
|
458
|
-
import {
|
|
487
|
+
import { defineInputGuardrail } from 'ai-sdk-guardrails';
|
|
488
|
+
import { extractTextContent } from 'ai-sdk-guardrails/guardrails/input';
|
|
489
|
+
|
|
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',
|
|
494
|
+
execute: async (context) => {
|
|
495
|
+
const { prompt } = extractTextContent(context);
|
|
496
|
+
|
|
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
|
+
];
|
|
459
504
|
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
505
|
+
const foundWaste = timeWasters.find((pattern) =>
|
|
506
|
+
pattern.test(prompt || ''),
|
|
507
|
+
);
|
|
508
|
+
if (foundWaste) {
|
|
509
|
+
return {
|
|
510
|
+
tripwireTriggered: true,
|
|
511
|
+
message: `Blocked time-wasting request - prevented unnecessary API call`,
|
|
512
|
+
severity: 'medium',
|
|
513
|
+
metadata: {
|
|
514
|
+
pattern: foundWaste.source,
|
|
515
|
+
api_calls_prevented: 1,
|
|
516
|
+
},
|
|
517
|
+
};
|
|
518
|
+
}
|
|
465
519
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
async (context) => {
|
|
469
|
-
const { object } = extractContent(context.result);
|
|
470
|
-
try {
|
|
471
|
-
userSchema.parse(object);
|
|
472
|
-
return { tripwireTriggered: false };
|
|
473
|
-
} catch (error) {
|
|
520
|
+
// Block requests that often exceed token limits
|
|
521
|
+
if (prompt && prompt.length > 12000) {
|
|
474
522
|
return {
|
|
475
523
|
tripwireTriggered: true,
|
|
476
|
-
message:
|
|
524
|
+
message:
|
|
525
|
+
'Request likely to exceed token limits - preventing API failure',
|
|
477
526
|
severity: 'high',
|
|
478
|
-
|
|
527
|
+
suggestion:
|
|
528
|
+
'Break into smaller, focused requests for better results and efficiency',
|
|
479
529
|
};
|
|
480
530
|
}
|
|
481
|
-
},
|
|
482
|
-
);
|
|
483
531
|
|
|
484
|
-
|
|
485
|
-
{
|
|
486
|
-
model: openai('gpt-4-turbo'),
|
|
487
|
-
prompt: 'Create a user profile for John Doe, age 30',
|
|
488
|
-
schema: userSchema,
|
|
489
|
-
},
|
|
490
|
-
{
|
|
491
|
-
outputGuardrails: [schemaValidator],
|
|
532
|
+
return { tripwireTriggered: false };
|
|
492
533
|
},
|
|
493
|
-
);
|
|
534
|
+
});
|
|
494
535
|
```
|
|
495
536
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
### 1. Layer Your Defence
|
|
537
|
+
### Quality Assurance Through Output Validation
|
|
499
538
|
|
|
500
|
-
|
|
539
|
+
Output guardrails ensure every response meets your quality standards before reaching users:
|
|
501
540
|
|
|
502
541
|
```typescript
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
542
|
+
import { defineOutputGuardrail } from 'ai-sdk-guardrails';
|
|
543
|
+
import { extractContent } from 'ai-sdk-guardrails/guardrails/output';
|
|
544
|
+
|
|
545
|
+
// Ensure responses are professional and useful
|
|
546
|
+
const professionalQualityGuardrail = defineOutputGuardrail({
|
|
547
|
+
name: 'professional-quality-control',
|
|
548
|
+
description: 'Ensures responses meet professional standards',
|
|
549
|
+
execute: async (context) => {
|
|
550
|
+
const { text } = extractContent(context.result);
|
|
511
551
|
|
|
512
|
-
|
|
513
|
-
customBusinessRules(),
|
|
514
|
-
],
|
|
515
|
-
outputGuardrails: [
|
|
516
|
-
// Ensure quality
|
|
517
|
-
confidenceThreshold(0.7),
|
|
552
|
+
const qualityIssues = [];
|
|
518
553
|
|
|
519
|
-
//
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
```
|
|
554
|
+
// Check for unprofessional language
|
|
555
|
+
const unprofessionalTerms = ['lol', 'wtf', 'omg', 'ur', 'u r'];
|
|
556
|
+
const hasUnprofessional = unprofessionalTerms.some((term) =>
|
|
557
|
+
text.toLowerCase().includes(term),
|
|
558
|
+
);
|
|
525
559
|
|
|
526
|
-
|
|
560
|
+
if (hasUnprofessional) {
|
|
561
|
+
qualityIssues.push('Contains unprofessional language');
|
|
562
|
+
}
|
|
527
563
|
|
|
528
|
-
|
|
564
|
+
// Check for placeholder text that indicates incomplete response
|
|
565
|
+
const placeholders = ['[insert', '[add', '[your', 'TODO:', 'FIXME:'];
|
|
566
|
+
const hasPlaceholders = placeholders.some((placeholder) =>
|
|
567
|
+
text.includes(placeholder),
|
|
568
|
+
);
|
|
529
569
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
const userMessage =
|
|
534
|
-
{
|
|
535
|
-
'content-length-limit': 'Your message is too long. Please shorten it.',
|
|
536
|
-
'blocked-keywords': 'Your request contains restricted content.',
|
|
537
|
-
'pii-detector': 'Please remove personal information from your request.',
|
|
538
|
-
}[error.guardrailName] || 'Your request could not be processed.';
|
|
539
|
-
|
|
540
|
-
return userMessage;
|
|
541
|
-
};
|
|
542
|
-
```
|
|
570
|
+
if (hasPlaceholders) {
|
|
571
|
+
qualityIssues.push('Contains placeholder text - incomplete response');
|
|
572
|
+
}
|
|
543
573
|
|
|
544
|
-
|
|
574
|
+
// Check for excessive repetition
|
|
575
|
+
const sentences = text.split(/[.!?]+/).filter((s) => s.trim());
|
|
576
|
+
const uniqueSentences = new Set(
|
|
577
|
+
sentences.map((s) => s.trim().toLowerCase()),
|
|
578
|
+
);
|
|
579
|
+
const repetitionRatio = uniqueSentences.size / sentences.length;
|
|
545
580
|
|
|
546
|
-
|
|
581
|
+
if (sentences.length > 3 && repetitionRatio < 0.6) {
|
|
582
|
+
qualityIssues.push('Excessive repetition detected');
|
|
583
|
+
}
|
|
547
584
|
|
|
548
|
-
|
|
585
|
+
if (qualityIssues.length > 0) {
|
|
586
|
+
return {
|
|
587
|
+
tripwireTriggered: true,
|
|
588
|
+
message: `Quality issues found: ${qualityIssues.join(', ')}`,
|
|
589
|
+
severity: 'medium',
|
|
590
|
+
suggestion: 'Request a more professional, complete response',
|
|
591
|
+
metadata: {
|
|
592
|
+
issues: qualityIssues,
|
|
593
|
+
quality_score: repetitionRatio,
|
|
594
|
+
},
|
|
595
|
+
};
|
|
596
|
+
}
|
|
549
597
|
|
|
550
|
-
|
|
551
|
-
|
|
598
|
+
return { tripwireTriggered: false };
|
|
599
|
+
},
|
|
600
|
+
});
|
|
552
601
|
```
|
|
553
602
|
|
|
554
|
-
|
|
603
|
+
### Streaming Intelligence: Real-Time Quality Control
|
|
555
604
|
|
|
556
|
-
|
|
557
|
-
- Let you write a summary of changes
|
|
558
|
-
- Create a changeset file
|
|
605
|
+
For streaming responses, maintain quality while preserving the real-time experience:
|
|
559
606
|
|
|
560
|
-
|
|
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
|
+
```
|
|
561
627
|
|
|
562
|
-
|
|
628
|
+
```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';
|
|
563
635
|
|
|
564
|
-
|
|
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);
|
|
565
642
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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
|
+
};
|
|
569
651
|
|
|
570
|
-
|
|
571
|
-
|
|
652
|
+
const issues = Object.entries(qualityMetrics)
|
|
653
|
+
.filter(([_, passed]) => !passed)
|
|
654
|
+
.map(([metric, _]) => metric);
|
|
572
655
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
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
|
+
}
|
|
667
|
+
|
|
668
|
+
return { tripwireTriggered: false };
|
|
669
|
+
},
|
|
670
|
+
});
|
|
576
671
|
|
|
577
|
-
|
|
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);
|
|
677
|
+
}
|
|
578
678
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
679
|
+
function containsInappropriateContent(text: string): boolean {
|
|
680
|
+
const inappropriate = ['inappropriate', 'offensive', 'harmful'];
|
|
681
|
+
return inappropriate.some((term) => text.toLowerCase().includes(term));
|
|
682
|
+
}
|
|
582
683
|
|
|
583
|
-
|
|
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
|
+
}
|
|
584
693
|
|
|
585
|
-
|
|
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
|
+
});
|
|
586
707
|
|
|
587
|
-
|
|
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
|
+
});
|
|
588
714
|
|
|
589
|
-
|
|
715
|
+
// Users see real-time streaming
|
|
716
|
+
for await (const chunk of enhancedStream.textStream) {
|
|
717
|
+
process.stdout.write(chunk);
|
|
718
|
+
}
|
|
590
719
|
|
|
591
|
-
|
|
592
|
-
|
|
720
|
+
// Quality analysis happens post-stream for continuous improvement
|
|
721
|
+
console.log('\nStream completed with quality monitoring');
|
|
593
722
|
```
|
|
594
723
|
|
|
595
|
-
|
|
724
|
+
## Production-Ready Performance and Quality Controls
|
|
596
725
|
|
|
597
|
-
|
|
598
|
-
|
|
726
|
+
### Smart Input Filtering
|
|
727
|
+
|
|
728
|
+
Access comprehensive input validation that optimizes performance while maintaining user experience:
|
|
729
|
+
|
|
730
|
+
```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
|
+
];
|
|
599
766
|
```
|
|
600
767
|
|
|
601
|
-
|
|
602
|
-
- Write: "Initial release of AI SDK Guardrails"
|
|
768
|
+
### Advanced Output Quality Assurance
|
|
603
769
|
|
|
604
|
-
|
|
770
|
+
Choose from sophisticated output validation that ensures professional results:
|
|
605
771
|
|
|
606
|
-
```
|
|
607
|
-
|
|
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
|
+
];
|
|
608
810
|
```
|
|
609
811
|
|
|
610
|
-
|
|
812
|
+
## Complete AI SDK Integration
|
|
611
813
|
|
|
612
|
-
|
|
814
|
+
The library seamlessly integrates with all AI SDK functions through the middleware architecture:
|
|
613
815
|
|
|
614
|
-
|
|
615
|
-
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
816
|
+
```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
|
+
],
|
|
828
|
+
});
|
|
619
829
|
|
|
620
|
-
|
|
830
|
+
// Use with any AI SDK function - same optimization and quality everywhere
|
|
831
|
+
const textResult = await generateText({
|
|
832
|
+
model: productionModel,
|
|
833
|
+
prompt: 'Write a professional email response',
|
|
834
|
+
experimental_telemetry: {
|
|
835
|
+
isEnabled: true,
|
|
836
|
+
functionId: 'performance-optimized-text',
|
|
837
|
+
metadata: { optimization: 'performance+quality' },
|
|
838
|
+
},
|
|
839
|
+
});
|
|
621
840
|
|
|
622
|
-
|
|
841
|
+
const objectResult = await generateObject({
|
|
842
|
+
model: productionModel,
|
|
843
|
+
prompt: 'Create a user profile',
|
|
844
|
+
schema: userProfileSchema,
|
|
845
|
+
experimental_telemetry: {
|
|
846
|
+
isEnabled: true,
|
|
847
|
+
functionId: 'performance-optimized-object',
|
|
848
|
+
metadata: { optimization: 'performance+quality' },
|
|
849
|
+
},
|
|
850
|
+
});
|
|
623
851
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
852
|
+
const textStream = await streamText({
|
|
853
|
+
model: productionModel,
|
|
854
|
+
prompt: 'Explain our product features',
|
|
855
|
+
experimental_telemetry: {
|
|
856
|
+
isEnabled: true,
|
|
857
|
+
functionId: 'performance-optimized-stream',
|
|
858
|
+
metadata: { optimization: 'performance+quality' },
|
|
859
|
+
},
|
|
860
|
+
});
|
|
628
861
|
```
|
|
629
862
|
|
|
630
|
-
|
|
863
|
+
## Examples
|
|
631
864
|
|
|
632
|
-
|
|
865
|
+
Explore focused examples that demonstrate practical performance optimization and quality assurance:
|
|
633
866
|
|
|
634
|
-
|
|
635
|
-
- Set up GitHub Actions for automated publishing (optional)
|
|
867
|
+
### Core Examples
|
|
636
868
|
|
|
637
|
-
**
|
|
869
|
+
- **[Basic Composition](examples/basic-composition.ts)** - Simple input/output validation for efficiency and quality
|
|
870
|
+
- **[Basic Guardrails](examples/basic-guardrails.ts)** - Foundation patterns for input/output validation
|
|
871
|
+
- **[Business Logic](examples/business-logic.ts)** - Custom business rules, work hours, and professional standards
|
|
872
|
+
- **[LLM-as-Judge](examples/llm-as-judge.ts)** - AI-powered quality evaluation and scoring
|
|
638
873
|
|
|
639
|
-
|
|
874
|
+
### Additional Examples
|
|
640
875
|
|
|
641
|
-
|
|
876
|
+
- **[Object Guardrails](examples/object-guardrails.ts)** - Schema validation and structured output quality
|
|
877
|
+
- **[Streaming Guardrails](examples/streaming-guardrails.ts)** - Real-time quality monitoring
|
|
878
|
+
- **[Rate Limiting](examples/rate-limit-guardrail.ts)** - Smart rate limiting that prevents resource overuse
|
|
879
|
+
- **[Autoevals Integration](examples/autoevals-guardrails.ts)** - Advanced AI-powered evaluation
|
|
642
880
|
|
|
643
|
-
|
|
644
|
-
import {
|
|
645
|
-
generateTextWithGuardrails,
|
|
646
|
-
generateObjectWithGuardrails,
|
|
647
|
-
streamTextWithGuardrails,
|
|
648
|
-
streamObjectWithGuardrails,
|
|
649
|
-
embedWithGuardrails,
|
|
650
|
-
} from 'ai-sdk-guardrails';
|
|
881
|
+
### Running Examples
|
|
651
882
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
const textStream = await streamTextWithGuardrails(
|
|
666
|
-
{ model, prompt: 'Long response' },
|
|
667
|
-
{ outputGuardrails: [outputLengthLimit(1000)] },
|
|
668
|
-
);
|
|
669
|
-
|
|
670
|
-
// Stream objects with guardrails
|
|
671
|
-
const objectStream = await streamObjectWithGuardrails(
|
|
672
|
-
{ model, prompt: 'Stream data', schema: dataSchema },
|
|
673
|
-
{ outputGuardrails: [schemaValidation(dataSchema)] },
|
|
674
|
-
);
|
|
675
|
-
|
|
676
|
-
// Embed with input validation
|
|
677
|
-
const embedResult = await embedWithGuardrails(
|
|
678
|
-
{ model, value: 'Text to embed' },
|
|
679
|
-
{ inputGuardrails: [piiDetector()] },
|
|
680
|
-
);
|
|
883
|
+
```bash
|
|
884
|
+
# Install dependencies
|
|
885
|
+
pnpm install
|
|
886
|
+
|
|
887
|
+
# Interactive examples with better UX
|
|
888
|
+
tsx examples/basic-composition.ts # Start here - simplest example
|
|
889
|
+
tsx examples/basic-guardrails.ts # Core patterns with 8 examples
|
|
890
|
+
tsx examples/business-logic.ts # Business-specific rules
|
|
891
|
+
tsx examples/llm-as-judge.ts # AI-powered quality control
|
|
892
|
+
|
|
893
|
+
# Or run specific examples directly
|
|
894
|
+
tsx examples/basic-guardrails.ts 1 # Run first example only
|
|
895
|
+
tsx examples/streaming-guardrails.ts 3 # Run third streaming example
|
|
681
896
|
```
|
|
682
897
|
|
|
683
|
-
|
|
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.
|
|
684
899
|
|
|
685
|
-
|
|
900
|
+
## Architecture Overview
|
|
686
901
|
|
|
687
|
-
|
|
688
|
-
// Types are automatically inferred
|
|
689
|
-
const result = await generateTextWithGuardrails(
|
|
690
|
-
{
|
|
691
|
-
model: openai('gpt-4-turbo'),
|
|
692
|
-
prompt: 'Hello',
|
|
693
|
-
},
|
|
694
|
-
{
|
|
695
|
-
inputGuardrails: [
|
|
696
|
-
/* your guardrails */
|
|
697
|
-
],
|
|
698
|
-
},
|
|
699
|
-
);
|
|
902
|
+
The library leverages the Vercel AI SDK's middleware architecture to provide composable guardrails that integrate seamlessly with your existing AI applications:
|
|
700
903
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
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
|
|
705
970
|
```
|
|
706
971
|
|
|
707
|
-
|
|
972
|
+
### Middleware Execution Flow
|
|
708
973
|
|
|
709
|
-
|
|
974
|
+
The guardrails execute in a specific order to maximize efficiency and ensure quality:
|
|
710
975
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
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
|
+
```
|
|
716
1004
|
|
|
717
|
-
|
|
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
|
|
718
1011
|
|
|
719
1012
|
## Contributing
|
|
720
1013
|
|
|
721
|
-
We welcome contributions! Please
|
|
1014
|
+
We welcome contributions! Please open issues and pull requests on [GitHub](https://github.com/jagreehal/ai-sdk-guardrails).
|
|
722
1015
|
|
|
723
1016
|
## License
|
|
724
1017
|
|
|
725
1018
|
MIT © [Jag Reehal](https://github.com/jagreehal)
|
|
726
|
-
|
|
727
|
-
---
|
|
728
|
-
|
|
729
|
-
Built with ❤️ for the AI community. Star the repo if you find it helpful!
|