Skip to main content

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

name
string
required
Unique postprocessor nameExamples: 'add-disclaimer', 'format-response'
execute
function
required
Function that processes AI responsesSignature: (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

description
string
Postprocessor description for documentation
priority
number
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

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;

Format Response

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;
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

Order matters - structure flows logically
priority: 1,   // Translation (first)
priority: 10,  // Formatting
priority: 50,  // Content injection
priority: 100, // Disclaimer/branding (last)
Don’t alter the core message
// ✅ Add to response
return {
  modifiedResponse: response + "\n\nAdditional info..."
};

// ❌ Don't completely replace
return {
  modifiedResponse: "Something completely different"
};
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 };
}

Markdown Formatting

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