Files
gh-jezweb-claude-skills-ski…/templates/text-agents/agent-guardrails-output.ts
2025-11-30 08:25:09 +08:00

228 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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,
};