Skip to content

customer-support.md

Customer Support Agent

A multi-tool agent for handling customer inquiries with order lookup, FAQ search, and ticket creation.

Overview

This example demonstrates:

  • Multiple coordinated tools
  • Sentiment-aware middleware
  • Error handling and fallbacks
  • Real-world business logic

Full Code

typescript
import { Agent, OpenAIProvider, defineTool, createMiddleware } from 'agentforge';
import { z } from 'zod';

// ============================================
// Mock Database
// ============================================

const orders = new Map([
  ['ORD-001', { id: 'ORD-001', status: 'shipped', item: 'Laptop' }],
  ['ORD-002', { id: 'ORD-002', status: 'processing', item: 'Headphones' }],
  ['ORD-003', { id: 'ORD-003', status: 'delivered', item: 'Keyboard' }],
]);

const faqDatabase = [
  { question: 'What is your return policy?', answer: 'We offer 30-day returns.' },
  { question: 'How long does shipping take?', answer: '5-7 business days.' },
  { question: 'Do you ship internationally?', answer: 'Yes, to 50+ countries.' },
];

const tickets: Array<{ id: string; issue: string; priority: string }> = [];

// ============================================
// Tools
// ============================================

const orderLookupTool = defineTool({
  name: 'lookup_order',
  description: 'Look up the status of a customer order by order ID',
  parameters: z.object({
    orderId: z.string().describe('The order ID (e.g., ORD-001)'),
  }),
  execute: async ({ orderId }) => {
    const order = orders.get(orderId.toUpperCase());
    
    if (!order) {
      return { found: false, message: `Order ${orderId} not found.` };
    }

    return {
      found: true,
      order: {
        id: order.id,
        status: order.status,
        item: order.item,
        estimatedDelivery: order.status === 'shipped' ? '2-3 days' : null,
      },
    };
  },
});

const faqSearchTool = defineTool({
  name: 'search_faq',
  description: 'Search the FAQ database for answers to common questions',
  parameters: z.object({
    query: z.string().describe('The search query or question'),
  }),
  execute: async ({ query }) => {
    const matches = faqDatabase.filter(
      (faq) =>
        faq.question.toLowerCase().includes(query.toLowerCase()) ||
        faq.answer.toLowerCase().includes(query.toLowerCase())
    );

    return matches.length > 0
      ? { found: true, results: matches }
      : { found: false, message: 'No FAQ entries found.' };
  },
});

const createTicketTool = defineTool({
  name: 'create_ticket',
  description: 'Create a support ticket for issues that need human attention',
  parameters: z.object({
    issue: z.string().describe('Description of the customer issue'),
    priority: z.enum(['low', 'medium', 'high']).describe('Ticket priority'),
  }),
  execute: async ({ issue, priority }) => {
    const ticketId = `TKT-${String(tickets.length + 1).padStart(4, '0')}`;
    tickets.push({ id: ticketId, issue, priority });

    return {
      success: true,
      ticketId,
      message: `Ticket ${ticketId} created. Response within 24 hours.`,
    };
  },
});

// ============================================
// Middleware
// ============================================

const sentimentMiddleware = createMiddleware({
  name: 'sentiment',
  beforeRequest: async (context) => {
    const lastMessage = context.messages.at(-1);
    
    if (lastMessage?.role === 'user') {
      const content = lastMessage.content.toLowerCase();
      const negativeWords = ['angry', 'frustrated', 'terrible', 'awful'];
      const isNegative = negativeWords.some((w) => content.includes(w));
      
      context.metadata.sentiment = isNegative ? 'negative' : 'neutral';
    }

    return context;
  },
});

// ============================================
// Agent
// ============================================

const agent = new Agent({
  provider: new OpenAIProvider({
    apiKey: process.env.OPENAI_API_KEY!,
    model: 'gpt-4-turbo',
  }),
  tools: [orderLookupTool, faqSearchTool, createTicketTool],
  middleware: [sentimentMiddleware],
  systemPrompt: `You are a friendly customer support agent.

Your capabilities:
- Look up order status using order IDs
- Search the FAQ database
- Create support tickets for complex issues

Guidelines:
- Be empathetic and understanding
- Offer specific solutions when possible
- Create tickets for issues you cannot resolve
- If a customer seems frustrated, acknowledge their feelings`,
  memory: {
    maxMessages: 20,
    strategy: 'sliding-window',
  },
});

// ============================================
// Usage
// ============================================

async function handleCustomerQuery(query: string) {
  console.log(`Customer: ${query}\n`);
  
  try {
    const response = await agent.run(query);
    console.log(`Support: ${response.content}\n`);
    
    if (response.toolResults?.length) {
      console.log(`[Used ${response.toolResults.length} tool(s)]`);
    }
  } catch (error) {
    console.error('Error:', error);
  }
}

// Example queries
await handleCustomerQuery("Hi, I'd like to check on my order ORD-001");
await handleCustomerQuery("What's your return policy?");
await handleCustomerQuery("I'm frustrated - my package hasn't arrived!");

Key Patterns

1. Tool Composition

The agent decides which tools to use based on the query:

  • Order questions → lookup_order
  • General questions → search_faq
  • Complex issues → create_ticket

2. Sentiment-Aware Middleware

typescript
const sentimentMiddleware = createMiddleware({
  name: 'sentiment',
  beforeRequest: async (context) => {
    // Detect negative sentiment
    context.metadata.sentiment = detectSentiment(lastMessage);
    return context;
  },
});

The agent can access context.metadata.sentiment to adjust its tone.

3. Graceful Tool Failures

typescript
execute: async ({ orderId }) => {
  const order = orders.get(orderId);
  
  if (!order) {
    return { found: false, message: 'Order not found.' };
  }

  return { found: true, order };
}

Return structured responses instead of throwing errors so the LLM can handle gracefully.

Try It

bash
# Clone the repo
git clone https://github.com/mpalmer79/agentforge.git
cd agentforge

# Install dependencies
npm install

# Set your API key
export OPENAI_API_KEY=your-key

# Run the example
npx ts-node examples/customer-support/index.ts

Released under the MIT License.