Overview
PostProcessor allows you to modify or enhance AI-generated responses before they’re sent to users. Use postprocessors to add disclaimers, format output, inject dynamic content, or apply branding.
import { PostProcessor , UserDataInstance } from 'lua-cli' ;
const addDisclaimer = new PostProcessor ({
name: 'add-disclaimer' ,
description: 'Add legal disclaimer to responses' ,
execute : async ( user : UserDataInstance , message : string , response : string , channel : string ) => {
return {
modifiedResponse: response + " \n\n _Disclaimer: This is AI-generated content for informational purposes only._"
};
}
});
export default addDisclaimer ;
New in v3.0.0: Response postprocessing for formatting, branding, and enhancement. Use with LuaAgent.
Use Cases
Disclaimers Add legal or informational disclaimers
Formatting Apply consistent formatting and styling
Branding Add company branding or signatures
Dynamic Content Inject user-specific information
Constructor
new PostProcessor(config)
Creates a new response postprocessor.
config
PostProcessorConfig
required
Postprocessor configuration object
Configuration Parameters
Required Fields
Unique postprocessor name Examples : 'add-disclaimer', 'format-response'
Function that processes AI responses Signature: (user: UserDataInstance, message: string, response: string, channel: string) => Promise<PostProcessorResponse>Parameters:
user - User data instance with profile and custom data
message - Original user message (string)
response - AI-generated response to modify
channel - Channel identifier (e.g., ‘whatsapp’, ‘web’, ‘api’)
Recommended: Use the Lua Runtime API instead of the function parameters:
User: User.get() to retrieve the current user
Channel: Lua.request.channel for the current channel
Webhook: Lua.request.webhook?.payload for raw webhook data (WhatsApp, Slack, Teams, etc.)
The function parameters may be removed in a future version.
Optional Fields
Postprocessor description for documentation
Execution order (lower numbers run first) Default: 100
PostProcessorResponse
Your execute function must return:
interface PostProcessorResponse {
// Modified response text (required)
modifiedResponse : string ;
}
The response object only contains modifiedResponse. The modified response becomes the input for the next postprocessor in the chain.
Complete Examples
Add Legal Disclaimer
import { PostProcessor , UserDataInstance } from 'lua-cli' ;
const legalDisclaimer = new PostProcessor ({
name: 'legal-disclaimer' ,
description: 'Add legal disclaimer to medical advice' ,
execute : async ( user : UserDataInstance , message : string , response : string , channel : string ) => {
// Check if response contains medical information
const medicalKeywords = [ 'symptom' , 'diagnosis' , 'treatment' , 'medication' , 'doctor' ];
const hasMedicalContent = medicalKeywords . some ( keyword =>
response . toLowerCase (). includes ( keyword )
);
if ( hasMedicalContent ) {
return {
modifiedResponse: response +
" \n\n ⚠️ **Medical Disclaimer:** This information is for educational purposes only and should not be considered medical advice. Please consult with a qualified healthcare professional for medical concerns."
};
}
return { modifiedResponse: response };
}
});
export default legalDisclaimer ;
Add Company Branding
import { PostProcessor , UserDataInstance , env } from 'lua-cli' ;
const brandingFooter = new PostProcessor ({
name: 'branding-footer' ,
description: 'Add company branding to responses' ,
execute : async ( user : UserDataInstance , message : string , response : string , channel : string ) => {
const companyName = env ( 'COMPANY_NAME' ) || 'Our Company' ;
const supportEmail = env ( 'SUPPORT_EMAIL' ) || '[email protected] ' ;
const footer = ` \n\n --- \n ` +
`_Powered by ${ companyName } _ \n ` +
`Need help? Contact us at ${ supportEmail } ` ;
return {
modifiedResponse: response + footer
};
}
});
export default brandingFooter ;
import { PostProcessor , UserDataInstance } from 'lua-cli' ;
const responseFormatter = new PostProcessor ({
name: 'response-formatter' ,
description: 'Apply consistent formatting to responses' ,
execute : async ( user : UserDataInstance , message : string , response : string , channel : string ) => {
let formatted = response ;
// Capitalize first letter of sentences
formatted = formatted . replace ( / ( ^| \. )( \w ) / g , ( match , p1 , p2 ) => {
return p1 + p2 . toUpperCase ();
});
// Add proper spacing after punctuation
formatted = formatted . replace ( / ( [ .!? ] )( \w ) / g , '$1 $2' );
// Format numbers with commas
formatted = formatted . replace ( / \b ( \d {1,3} (?: , ? \d {3} ) * ) \b / g , ( match ) => {
return match . replace ( /,/ g , '' ). replace ( / \B (?=( \d {3} ) + (?! \d )) / g , ',' );
});
// Highlight important terms (example)
const importantTerms = [ 'important' , 'urgent' , 'required' , 'deadline' ];
importantTerms . forEach ( term => {
const regex = new RegExp ( ` \\ b( ${ term } ) \\ b` , 'gi' );
formatted = formatted . replace ( regex , '**$1**' );
});
return { modifiedResponse: formatted };
}
});
export default responseFormatter ;
Add User-Specific Context
import { PostProcessor , UserDataInstance } from 'lua-cli' ;
const personalizer = new PostProcessor ({
name: 'personalizer' ,
description: 'Add personalized greeting and context' ,
execute : async ( user : UserDataInstance , message : string , response : string , channel : string ) => {
const userName = user . name || 'there' ;
const userData = await user . data ;
const isVIP = userData . vipStatus === true ;
// Add personalized greeting
let personalized = `Hi ${ userName } ! ${ response } ` ;
// Add VIP note if applicable
if ( isVIP ) {
personalized += ` \n\n ✨ As a VIP member, you have priority support and exclusive benefits.` ;
}
return { modifiedResponse: personalized };
}
});
export default personalizer ;
Add Call-to-Action
import { PostProcessor , UserDataInstance } from 'lua-cli' ;
const ctaInjector = new PostProcessor ({
name: 'cta-injector' ,
description: 'Add contextual call-to-action based on response content' ,
execute : async ( user : UserDataInstance , message : string , response : string , channel : string ) => {
let cta = '' ;
// Product-related response
if ( response . toLowerCase (). includes ( 'product' ) || response . toLowerCase (). includes ( 'price' )) {
cta = ' \n\n 📦 [View Our Products](https://example.com/products)' ;
}
// Support-related response
else if ( response . toLowerCase (). includes ( 'issue' ) || response . toLowerCase (). includes ( 'problem' )) {
cta = ' \n\n 🎫 [Create Support Ticket](https://example.com/support)' ;
}
// General information
else {
cta = ' \n\n 💬 Have more questions? Just ask!' ;
}
return { modifiedResponse: response + cta };
}
});
export default ctaInjector ;
Translation Wrapper
import { PostProcessor , UserDataInstance } from 'lua-cli' ;
const translator = new PostProcessor ({
name: 'translator' ,
description: 'Translate responses based on user language preference' ,
execute : async ( user : UserDataInstance , message : string , response : string , channel : string ) => {
const userData = await user . data ;
const userLanguage = userData . language || 'en' ;
// Skip if already in user's language
if ( userLanguage === 'en' ) {
return { modifiedResponse: response };
}
// Translate response (using external translation API)
try {
const translated = await translateText ( response , userLanguage );
return { modifiedResponse: translated };
} catch ( error ) {
console . error ( 'Translation failed:' , error );
// Fall back to original
return { modifiedResponse: response };
}
}
});
async function translateText ( text : string , targetLanguage : string ) : Promise < string > {
// Implementation using translation API (Google Translate, DeepL, etc.)
// This is a placeholder
return text ;
}
export default translator ;
Sentiment Adjuster
import { PostProcessor , UserDataInstance } from 'lua-cli' ;
const sentimentAdjuster = new PostProcessor ({
name: 'sentiment-adjuster' ,
description: 'Adjust tone based on user message content' ,
execute : async ( user : UserDataInstance , message : string , response : string , channel : string ) => {
// Check if user message indicates frustration
const frustrationKeywords = [ 'frustrated' , 'annoyed' , 'angry' , 'not working' , 'broken' ];
const isFrustrated = frustrationKeywords . some ( keyword =>
message . toLowerCase (). includes ( keyword )
);
if ( isFrustrated ) {
// Add empathetic phrasing
const empathetic = "I understand this can be frustrating. " + response ;
return { modifiedResponse: empathetic };
}
return { modifiedResponse: response };
}
});
export default sentimentAdjuster ;
Link Enricher
import { PostProcessor , UserDataInstance } from 'lua-cli' ;
const linkEnricher = new PostProcessor ({
name: 'link-enricher' ,
description: 'Convert plain URLs to formatted links' ,
execute : async ( user : UserDataInstance , message : string , response : string , channel : string ) => {
// Convert URLs to markdown links
const urlRegex = / ( https ? : \/\/ [ ^ \s ] + ) / g ;
const enriched = response . replace ( urlRegex , ( url ) => {
const domain = new URL ( url ). hostname . replace ( 'www.' , '' );
return `[ ${ domain } ]( ${ url } )` ;
});
// Add tracking parameters to links
const trackedLinks = enriched . replace ( / \( ( https ? : \/\/ [ ^ \) ] + ) \) / g , ( match , url ) => {
const tracked = ` ${ url }${ url . includes ( '?' ) ? '&' : '?' } utm_source=ai_agent&utm_medium=chat` ;
return `( ${ tracked } )` ;
});
return { modifiedResponse: trackedLinks };
}
});
export default linkEnricher ;
Using with LuaAgent
Postprocessors are added to your agent configuration:
import { LuaAgent } from 'lua-cli' ;
import translator from './postprocessors/translator' ;
import responseFormatter from './postprocessors/formatter' ;
import legalDisclaimer from './postprocessors/disclaimer' ;
import brandingFooter from './postprocessors/branding' ;
export const agent = new LuaAgent ({
name: 'my-agent' ,
persona: '...' ,
skills: [ ... ],
postProcessors: [
translator ,
responseFormatter ,
legalDisclaimer ,
brandingFooter
]
});
Postprocessors execute in order of their priority value (lowest first). Priority is set when creating the postprocessor via the API. If not specified, default priority is 100.
Execution Flow
AI Agent Response
↓
PostProcessor 1 (priority: 1)
↓
PostProcessor 2 (priority: 10)
↓
PostProcessor 3 (priority: 90)
↓
PostProcessor 4 (priority: 100)
↓
Final Response to User
Each postprocessor receives the output of the previous one in the chain.
Best Practices
✅ Chain Postprocessors Thoughtfully
Order matters - structure flows logically priority : 1 , // Translation (first)
priority : 10 , // Formatting
priority : 50 , // Content injection
priority : 100 , // Disclaimer/branding (last)
✅ Preserve Original Meaning
Don’t alter the core message // ✅ Add to response
return {
modifiedResponse: response + " \n\n Additional info..."
};
// ❌ Don't completely replace
return {
modifiedResponse: "Something completely different"
};
✅ Handle Errors Gracefully
Fall back to original response on errors execute : async ( user , message , response , channel ) => {
try {
return { modifiedResponse: processResponse ( response ) };
} catch ( error ) {
console . error ( 'Postprocessor error:' , error );
return { modifiedResponse: response }; // Return original
}
}
Postprocessors should be quick (< 50ms) Avoid heavy computations or external API calls when possible.
Testing Postprocessors
lua test
# Select: PostProcessor → your-postprocessor-name
# Provide test response
Common Patterns
Conditional Processing
execute : async ( user : UserDataInstance , message : string , response : string , channel : string ) => {
const userData = await user . data ;
// Only apply to certain user types
if ( userData . accountType === 'enterprise' ) {
return {
modifiedResponse: response + " \n\n *Enterprise Support Available 24/7*"
};
}
return { modifiedResponse: response };
}
Regex Replacements
execute : async ( user : UserDataInstance , message : string , response : string , channel : string ) => {
// Replace placeholders
let modified = response
. replace ( /{{user_name}}/ g , user . name || 'User' )
. replace ( /{{company}}/ g , env ( 'COMPANY_NAME' ))
. replace ( /{{date}}/ g , new Date (). toLocaleDateString ());
return { modifiedResponse: modified };
}
execute : async ( user : UserDataInstance , message : string , response : string , channel : string ) => {
// Add markdown formatting
let formatted = response
. replace ( / \*\* ( [ ^ * ] + ) \*\* / g , '**$1**' ) // Bold
. replace ( /_ ( [ ^ _ ] + ) _/ g , '_$1_' ) // Italic
. replace ( /` ( [ ^ ` ] + ) `/ g , '`$1`' ); // Code
return { modifiedResponse: formatted };
}
See Also