35.3 Suggested replies with policy constraints

Overview and links for this section of the guide.

Why Policies Matter

Customer support isn't just about being helpful—it's about being correctly helpful. An AI that promises refunds you can't honor, or gives wrong instructions, creates bigger problems than no AI at all.

Common policy violations AI might commit:

  • Promising refunds outside the return window
  • Sharing internal information (discount codes, employee names)
  • Making commitments for actions it can't perform
  • Giving technical advice that could damage customer's system

Encoding Business Rules

Policies should be explicit in the system instruction:

// policy-constraints.ts
export const SUPPORT_POLICIES = {
  refunds: {
    rules: [
      'Refunds are only available within 30 days of purchase',
      'Digital products are non-refundable once downloaded',
      'Subscription refunds are pro-rated for unused time',
      'Never promise a refund - only say "I\'ve submitted a refund request"',
    ],
    escalate_if: [
      'Order is older than 30 days',
      'Customer claims fraud or unauthorized purchase',
      'Amount exceeds $500',
    ]
  },
  
  shipping: {
    rules: [
      'Standard shipping: 5-7 business days',
      'Express shipping: 2-3 business days',
      'Cannot change shipping address once order is shipped',
      'Provide tracking link when available',
    ],
    escalate_if: [
      'Package marked delivered but customer says not received',
      'International shipping issues',
      'Shipping to PO Box (not supported)',
    ]
  },
  
  tone: {
    always: [
      'Be empathetic and apologize for any inconvenience',
      'Use the customer\'s name if known',
      'End with a clear next step or question',
    ],
    never: [
      'Blame the customer',
      'Use technical jargon',
      'Make promises you cannot keep',
      'Share internal processes or employee information',
    ]
  }
};

export function buildPolicyPrompt(): string {
  return `## Support Policies (You MUST follow these)

### Refund Policy
${SUPPORT_POLICIES.refunds.rules.map(r => `- ${r}`).join('\n')}

**Escalate to human if:**
${SUPPORT_POLICIES.refunds.escalate_if.map(r => `- ${r}`).join('\n')}

### Shipping Policy
${SUPPORT_POLICIES.shipping.rules.map(r => `- ${r}`).join('\n')}

**Escalate to human if:**
${SUPPORT_POLICIES.shipping.escalate_if.map(r => `- ${r}`).join('\n')}

### Tone Guidelines
**Always:**
${SUPPORT_POLICIES.tone.always.map(r => `- ${r}`).join('\n')}

**Never:**
${SUPPORT_POLICIES.tone.never.map(r => `- ${r}`).join('\n')}
`;
}

Grounding with Help Articles

For "how to" questions, don't let the model hallucinate instructions. Retrieve the official help article and ground the response:

// help-article-retrieval.ts
interface HelpArticle {
  id: string;
  title: string;
  content: string;
  category: string;
  lastUpdated: Date;
}

class HelpCenterRAG {
  private articles: HelpArticle[];
  private embeddings: Map;
  
  constructor(articles: HelpArticle[]) {
    this.articles = articles;
    this.embeddings = new Map();
  }
  
  async findRelevantArticles(query: string, limit: number = 3): Promise {
    // Simple keyword matching (replace with embeddings for production)
    const queryWords = query.toLowerCase().split(/\s+/);
    
    const scored = this.articles.map(article => {
      const text = `${article.title} ${article.content}`.toLowerCase();
      const matches = queryWords.filter(word => text.includes(word)).length;
      return { article, score: matches / queryWords.length };
    });
    
    return scored
      .sort((a, b) => b.score - a.score)
      .slice(0, limit)
      .map(s => s.article);
  }
}

function buildGroundedResponsePrompt(
  email: string,
  articles: HelpArticle[],
  orderData: any
): string {
  return `You are a customer support agent. Answer the customer's question using ONLY the information provided below.

## Customer Email
${email}

## Customer's Order Data
${JSON.stringify(orderData, null, 2)}

## Relevant Help Articles
${articles.map(a => `
### ${a.title}
${a.content}
`).join('\n')}

## Instructions
1. Use ONLY the information from the help articles and order data
2. If the information doesn't answer the question, say "I don't have enough information to answer that. Let me connect you with a specialist."
3. Quote article steps exactly when giving instructions
4. Include the article title as a reference

## Your Response`;
}

Full Response Generator

// response-generator.ts
import { GoogleGenerativeAI } from '@google/generative-ai';

interface ResponseContext {
  email: string;
  classification: ClassificationResult;
  extractedData: ExtractedTicketData;
  orderData?: any;
  helpArticles?: HelpArticle[];
}

interface GeneratedResponse {
  subject: string;
  body: string;
  confidence: number;
  referencedArticles: string[];
  requiresApproval: boolean;
}

export class SupportResponseGenerator {
  private model: any;
  
  constructor(apiKey: string) {
    const genAI = new GoogleGenerativeAI(apiKey);
    this.model = genAI.getGenerativeModel({
      model: 'gemini-1.5-flash',
      systemInstruction: this.buildSystemInstruction(),
    });
  }
  
  private buildSystemInstruction(): string {
    return `You are a friendly, professional customer support agent for an e-commerce company.

${buildPolicyPrompt()}

## Response Format
- Start with empathy (acknowledge the issue)
- Provide the solution or next steps
- End with an offer to help further
- Keep responses concise (under 200 words unless complex)
- Use proper grammar and formatting
`;
  }
  
  async generateResponse(context: ResponseContext): Promise {
    const { email, classification, extractedData, orderData, helpArticles } = context;
    
    // Build the prompt with all available context
    let prompt = `## Customer Email
${email}

## Classification
Category: ${classification.category}
Sentiment: ${extractedData.sentiment}
Urgency: ${extractedData.urgency}

## Extracted Information
Order IDs: ${extractedData.order_ids.join(', ') || 'None found'}
Issue: ${extractedData.issue_description}
`;

    if (orderData) {
      prompt += `
## Order Data from System
${JSON.stringify(orderData, null, 2)}
`;
    }
    
    if (helpArticles && helpArticles.length > 0) {
      prompt += `
## Relevant Help Articles
${helpArticles.map(a => `### ${a.title}\n${a.content}`).join('\n\n')}
`;
    }
    
    prompt += `
## Generate Response
Write a professional email response. Output JSON:
{
  "subject": "Re: [appropriate subject line]",
  "body": "[the email body]",
  "confidence": 0.0-1.0,
  "referenced_articles": ["article titles used"]
}`;

    const result = await this.model.generateContent(prompt);
    const text = result.response.text();
    const parsed = JSON.parse(text.match(/\{[\s\S]*\}/)?.[0] || '{}');
    
    // Determine if approval is needed
    const requiresApproval = 
      parsed.confidence < 0.8 ||
      extractedData.sentiment === 'angry' ||
      classification.category.includes('REFUND');
    
    return {
      subject: parsed.subject || 'Re: Your recent inquiry',
      body: parsed.body,
      confidence: parsed.confidence || 0.5,
      referencedArticles: parsed.referenced_articles || [],
      requiresApproval,
    };
  }
}

// Example usage
const generator = new SupportResponseGenerator(process.env.GEMINI_API_KEY!);

const response = await generator.generateResponse({
  email: "Where is my order #12345? I ordered it a week ago!",
  classification: { category: 'SHIPPING_STATUS', confidence: 0.95, reasoning: '...' },
  extractedData: {
    order_ids: ['12345'],
    sentiment: 'frustrated',
    urgency: 'medium',
    issue_description: 'Customer asking about order delivery status'
  },
  orderData: {
    id: '12345',
    status: 'shipped',
    trackingNumber: '1Z999AA10123456784',
    estimatedDelivery: '2024-01-15'
  }
});

console.log(response);
// {
//   subject: "Re: Your Order #12345 Status",
//   body: "Hi there,\n\nI understand how frustrating it can be to wait for...",
//   confidence: 0.9,
//   referencedArticles: [],
//   requiresApproval: false
// }

Tone Adjustment

Adjust response tone based on customer sentiment:

// tone-adjustment.ts
const TONE_MODIFIERS = {
  angry: {
    prefix: 'I sincerely apologize for this frustrating experience. ',
    suffix: ' I understand this has been difficult, and I want to make this right.',
    style: 'extra empathetic, acknowledge their frustration explicitly'
  },
  frustrated: {
    prefix: 'I\'m sorry for any inconvenience. ',
    suffix: ' Please don\'t hesitate to reach out if you need anything else.',
    style: 'warm and reassuring, focus on resolution'
  },
  neutral: {
    prefix: '',
    suffix: ' Is there anything else I can help you with?',
    style: 'professional and efficient'
  },
  happy: {
    prefix: 'Thanks for reaching out! ',
    suffix: ' We appreciate your business!',
    style: 'friendly and upbeat'
  }
};

function adjustTone(response: string, sentiment: string): string {
  const modifier = TONE_MODIFIERS[sentiment] || TONE_MODIFIERS.neutral;
  return modifier.prefix + response + modifier.suffix;
}
Never Promise What You Can't Deliver

Use hedging language: "I've submitted a refund request" not "You'll get a refund." This protects the company and sets correct expectations.

Where to go next