Initial commit
This commit is contained in:
227
templates/text-agents/agent-guardrails-output.ts
Normal file
227
templates/text-agents/agent-guardrails-output.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
/**
|
||||
* Output Guardrails for Content Filtering
|
||||
*
|
||||
* Demonstrates:
|
||||
* - Creating output guardrails
|
||||
* - Filtering PII (phone numbers, emails, etc.)
|
||||
* - Blocking inappropriate content
|
||||
* - Handling structured output guardrails
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
Agent,
|
||||
run,
|
||||
OutputGuardrail,
|
||||
OutputGuardrailTripwireTriggered,
|
||||
} from '@openai/agents';
|
||||
|
||||
// ========================================
|
||||
// Output Guardrails
|
||||
// ========================================
|
||||
|
||||
const piiGuardrail: OutputGuardrail = {
|
||||
name: 'PII Detection',
|
||||
execute: async ({ agentOutput }) => {
|
||||
// Detect phone numbers
|
||||
const phoneRegex = /\b\d{3}[-. ]?\d{3}[-. ]?\d{4}\b/;
|
||||
const hasPhoneNumber = phoneRegex.test(agentOutput as string);
|
||||
|
||||
// Detect email addresses
|
||||
const emailRegex = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/;
|
||||
const hasEmail = emailRegex.test(agentOutput as string);
|
||||
|
||||
// Detect SSN patterns
|
||||
const ssnRegex = /\b\d{3}-\d{2}-\d{4}\b/;
|
||||
const hasSSN = ssnRegex.test(agentOutput as string);
|
||||
|
||||
const piiDetected = hasPhoneNumber || hasEmail || hasSSN;
|
||||
|
||||
return {
|
||||
tripwireTriggered: piiDetected,
|
||||
outputInfo: {
|
||||
phoneNumber: hasPhoneNumber,
|
||||
email: hasEmail,
|
||||
ssn: hasSSN,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const profanityGuardrail: OutputGuardrail = {
|
||||
name: 'Profanity Filter',
|
||||
execute: async ({ agentOutput }) => {
|
||||
// Simple profanity detection (use a real library in production)
|
||||
const bannedWords = ['badword1', 'badword2', 'offensive'];
|
||||
const text = (agentOutput as string).toLowerCase();
|
||||
|
||||
const found = bannedWords.filter(word => text.includes(word));
|
||||
|
||||
return {
|
||||
tripwireTriggered: found.length > 0,
|
||||
outputInfo: {
|
||||
foundWords: found,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// Structured Output Guardrail
|
||||
// ========================================
|
||||
|
||||
const structuredPIIGuardrail: OutputGuardrail = {
|
||||
name: 'Structured PII Check',
|
||||
execute: async ({ agentOutput }) => {
|
||||
// For structured output, check specific fields
|
||||
const output = agentOutput as any;
|
||||
|
||||
const phoneRegex = /\b\d{3}[-. ]?\d{3}[-. ]?\d{4}\b/;
|
||||
|
||||
const piiInResponse = output.response
|
||||
? phoneRegex.test(output.response)
|
||||
: false;
|
||||
const piiInReasoning = output.reasoning
|
||||
? phoneRegex.test(output.reasoning)
|
||||
: false;
|
||||
|
||||
return {
|
||||
tripwireTriggered: piiInResponse || piiInReasoning,
|
||||
outputInfo: {
|
||||
phone_in_response: piiInResponse,
|
||||
phone_in_reasoning: piiInReasoning,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// Agents with Output Guardrails
|
||||
// ========================================
|
||||
|
||||
// Text agent with PII filtering
|
||||
const customerServiceAgent = new Agent({
|
||||
name: 'Customer Service',
|
||||
instructions: 'You help customers with their questions. Be helpful and professional.',
|
||||
outputGuardrails: [piiGuardrail, profanityGuardrail],
|
||||
});
|
||||
|
||||
// Structured output agent with PII filtering
|
||||
const infoExtractorAgent = new Agent({
|
||||
name: 'Info Extractor',
|
||||
instructions: 'Extract user information from the input.',
|
||||
outputType: z.object({
|
||||
reasoning: z.string(),
|
||||
response: z.string(),
|
||||
userName: z.string().nullable(),
|
||||
}),
|
||||
outputGuardrails: [structuredPIIGuardrail],
|
||||
});
|
||||
|
||||
// ========================================
|
||||
// Example Usage
|
||||
// ========================================
|
||||
|
||||
async function testTextOutputGuardrails() {
|
||||
console.log('\n🛡️ Testing Text Output Guardrails\n');
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
input: 'What are your business hours?',
|
||||
shouldPass: true,
|
||||
},
|
||||
{
|
||||
input: 'My phone number is 650-123-4567, can you call me?',
|
||||
shouldPass: false,
|
||||
},
|
||||
{
|
||||
input: 'Tell me about your products',
|
||||
shouldPass: true,
|
||||
},
|
||||
];
|
||||
|
||||
for (const test of testCases) {
|
||||
console.log('='.repeat(60));
|
||||
console.log('Input:', test.input);
|
||||
console.log('Expected:', test.shouldPass ? 'PASS' : 'BLOCK');
|
||||
console.log('='.repeat(60));
|
||||
|
||||
try {
|
||||
const result = await run(customerServiceAgent, test.input);
|
||||
console.log('✅ PASSED guardrails');
|
||||
console.log('Response:', result.finalOutput);
|
||||
|
||||
} catch (error) {
|
||||
if (error instanceof OutputGuardrailTripwireTriggered) {
|
||||
console.log('❌ BLOCKED by output guardrail');
|
||||
console.log('Guardrail:', error.guardrailName);
|
||||
console.log('Details:', JSON.stringify(error.outputInfo, null, 2));
|
||||
console.log('\nUser-facing message: "Sorry, I cannot provide that information for privacy reasons."');
|
||||
} else {
|
||||
console.error('⚠️ Unexpected error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n');
|
||||
}
|
||||
}
|
||||
|
||||
async function testStructuredOutputGuardrails() {
|
||||
console.log('\n🛡️ Testing Structured Output Guardrails\n');
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
input: 'My name is Alice Johnson',
|
||||
shouldPass: true,
|
||||
},
|
||||
{
|
||||
input: 'I am Bob Smith and my number is 555-1234',
|
||||
shouldPass: false,
|
||||
},
|
||||
];
|
||||
|
||||
for (const test of testCases) {
|
||||
console.log('='.repeat(60));
|
||||
console.log('Input:', test.input);
|
||||
console.log('Expected:', test.shouldPass ? 'PASS' : 'BLOCK');
|
||||
console.log('='.repeat(60));
|
||||
|
||||
try {
|
||||
const result = await run(infoExtractorAgent, test.input);
|
||||
console.log('✅ PASSED guardrails');
|
||||
console.log('Response:', JSON.stringify(result.finalOutput, null, 2));
|
||||
|
||||
} catch (error) {
|
||||
if (error instanceof OutputGuardrailTripwireTriggered) {
|
||||
console.log('❌ BLOCKED by output guardrail');
|
||||
console.log('Guardrail:', error.guardrailName);
|
||||
console.log('Details:', JSON.stringify(error.outputInfo, null, 2));
|
||||
} else {
|
||||
console.error('⚠️ Unexpected error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n');
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
await testTextOutputGuardrails();
|
||||
await testStructuredOutputGuardrails();
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Uncomment to run
|
||||
// main();
|
||||
|
||||
export {
|
||||
customerServiceAgent,
|
||||
infoExtractorAgent,
|
||||
piiGuardrail,
|
||||
profanityGuardrail,
|
||||
structuredPIIGuardrail,
|
||||
};
|
||||
Reference in New Issue
Block a user