examples/processors-response-validator/README.md
This example shows how to create a custom output processor that validates AI responses after generation but before they are returned to users. This processor checks that responses contain required keywords and can reject responses that don't meet validation criteria, ensuring quality and compliance.
A custom output processor in Mastra implements the Processor interface with the processOutputResult method for final result validation. This processor examines the complete response and validates it contains all specified keywords.
import type { Processor, MastraMessageV2 } from '@mastra/core/processors';
export class ResponseValidator implements Processor {
readonly id = 'response-validator';
readonly name = 'Response Validator';
constructor(private requiredKeywords: string[] = []) {}
processOutputResult({
messages,
abort,
}: {
messages: MastraMessageV2[];
abort: (reason?: string) => never;
}): MastraMessageV2[] {
const responseText = messages
.map(msg =>
msg.content.parts
.filter(part => part.type === 'text')
.map(part => (part.type === 'text' ? part.text : ''))
.join(''),
)
.join('');
// Check for required keywords
for (const keyword of this.requiredKeywords) {
if (!responseText.toLowerCase().includes(keyword.toLowerCase())) {
abort(`Response missing required keyword: ${keyword}`);
}
}
return messages;
}
}
import { Agent } from '@mastra/core/agent';
import { ResponseValidator } from '../processors/response-validator';
export const validatedAgent = new Agent({
id: 'validated-agent',
name: 'Validated Agent',
instructions: 'You are a helpful assistant. Always mention the key concepts in your responses.',
model: 'openai/gpt-5.1',
outputProcessors: [
new ResponseValidator(['artificial intelligence', 'machine learning']), // Require both keywords
],
});
This example shows a response that contains all required keywords and passes validation successfully.
import { Agent } from '@mastra/core/agent';
import { ResponseValidator } from './mastra/processors/response-validator';
// Create agent that requires AI-related keywords
export const agent = new Agent({
id: 'validated-agent',
name: 'Validated Agent',
instructions:
'You are an AI expert. Always mention artificial intelligence and machine learning when discussing AI topics.',
model: 'openai/gpt-5.1',
outputProcessors: [new ResponseValidator(['artificial intelligence', 'machine learning'])],
});
const result = await agent.generate('Explain how AI systems learn from data.');
console.log('✅ Response passed validation:');
console.log(result.text);
The response passes validation because it contains both required keywords:
✅ Response passed validation:
"Artificial intelligence systems learn from data through machine learning algorithms. These systems use various techniques like neural networks to identify patterns in datasets. Machine learning enables artificial intelligence to improve performance on specific tasks without explicit programming for each scenario."
This example shows what happens when a response is missing one or more required keywords.
import { Agent } from '@mastra/core/agent';
import { ResponseValidator } from './mastra/processors/response-validator';
// Reuse same agent but require security-related keywords
export const agent = new Agent({
id: 'validated-agent',
name: 'Validated Agent',
instructions: 'You are a helpful assistant.',
model: 'openai/gpt-5.1',
outputProcessors: [
new ResponseValidator(['security', 'privacy', 'encryption']), // Require all three
],
});
const result = await agent.generate('How do I protect my data online?');
if (result.tripwire) {
console.log('❌ Response failed validation:');
console.log(result.tripwire.reason);
} else {
console.log('✅ Response passed validation:');
console.log(result.text);
}
The response fails validation because it doesn't contain all required keywords:
❌ Response failed validation:
Response missing required keyword: encryption
// The response might have contained "security" and "privacy" but was missing "encryption"
This example demonstrates validation failure when none of the required keywords are present in the response.
import { Agent } from '@mastra/core/agent';
import { ResponseValidator } from './mastra/processors/response-validator';
// Reuse same agent but require financial keywords
export const agent = new Agent({
id: 'validated-agent',
name: 'Validated Agent',
instructions: 'You are a general assistant.',
model: 'openai/gpt-5.1',
outputProcessors: [new ResponseValidator(['blockchain', 'cryptocurrency', 'bitcoin'])],
});
const result = await agent.generate("What's the weather like today?");
if (result.tripwire) {
console.log('❌ Response failed validation:');
console.log(result.tripwire.reason);
} else {
console.log('✅ Response passed validation:');
console.log(result.text);
}
The response fails validation because it contains none of the required financial keywords:
❌ Response failed validation:
Response missing required keyword: blockchain
// The weather response would have no connection to financial concepts
You can create more sophisticated validators with custom logic:
import type { Processor, MastraMessageV2 } from '@mastra/core/processors';
export class AdvancedResponseValidator implements Processor {
readonly id = 'advanced-response-validator';
readonly name = 'Advanced Response Validator';
constructor(
private config: {
requiredKeywords?: string[];
forbiddenWords?: string[];
minLength?: number;
maxLength?: number;
requireAllKeywords?: boolean;
} = {},
) {}
processOutputResult({
messages,
abort,
}: {
messages: MastraMessageV2[];
abort: (reason?: string) => never;
}): MastraMessageV2[] {
const responseText = messages
.map(msg =>
msg.content.parts
.filter(part => part.type === 'text')
.map(part => (part.type === 'text' ? part.text : ''))
.join(''),
)
.join('');
const lowerText = responseText.toLowerCase();
// Length validation
if (this.config.minLength && responseText.length < this.config.minLength) {
abort(`Response too short: ${responseText.length} characters (min: ${this.config.minLength})`);
}
if (this.config.maxLength && responseText.length > this.config.maxLength) {
abort(`Response too long: ${responseText.length} characters (max: ${this.config.maxLength})`);
}
// Forbidden words check
if (this.config.forbiddenWords) {
for (const word of this.config.forbiddenWords) {
if (lowerText.includes(word.toLowerCase())) {
abort(`Response contains forbidden word: ${word}`);
}
}
}
// Required keywords check
if (this.config.requiredKeywords) {
if (this.config.requireAllKeywords !== false) {
// Require ALL keywords (default behavior)
for (const keyword of this.config.requiredKeywords) {
if (!lowerText.includes(keyword.toLowerCase())) {
abort(`Response missing required keyword: ${keyword}`);
}
}
} else {
// Require AT LEAST ONE keyword
const hasAnyKeyword = this.config.requiredKeywords.some(keyword => lowerText.includes(keyword.toLowerCase()));
if (!hasAnyKeyword) {
abort(`Response missing any of required keywords: ${this.config.requiredKeywords.join(', ')}`);
}
}
}
return messages;
}
}
// Usage example
export const advancedAgent = new Agent({
id: 'advanced-validated-agent',
name: 'Advanced Validated Agent',
instructions: 'You are a technical writer.',
model: 'openai/gpt-5.1',
outputProcessors: [
new AdvancedResponseValidator({
requiredKeywords: ['technical', 'implementation'],
forbiddenWords: ['maybe', 'probably', 'might'],
minLength: 100,
maxLength: 1000,
requireAllKeywords: true,
}),
],
});
When using ResponseValidator, the processor:
result.tripwire = trueresult.tripwire.reason specifies which keyword was missingresult.tripwire instead of using try/catch blocksThis processor is particularly useful for applications that need to guarantee response content meets specific criteria, whether for compliance, quality assurance, or educational purposes.