Skip to content
Learn Agentic AI12 min read0 views

Building a Customer Support Multi-Agent System: Router, FAQ, Billing, and Escalation

Build a complete customer support multi-agent system with four specialized agents — a router, FAQ handler, billing specialist, and escalation agent — using the OpenAI Agents SDK with shared context and graceful fallbacks.

Architecture Overview

We are building a customer support system with four agents:

  1. Router Agent — Classifies incoming requests and routes to specialists
  2. FAQ Agent — Answers common questions from a knowledge base
  3. Billing Agent — Handles invoices, payments, refunds, and subscription changes
  4. Escalation Agent — Takes over when automated agents cannot resolve an issue

This is a practical, production-oriented architecture. Each agent has a clear responsibility boundary, its own tools, and explicit handoff rules.

Step 1: Define the Shared Context

All agents share a context object that tracks the customer, the conversation state, and escalation metadata:

from dataclasses import dataclass, field
from datetime import datetime

@dataclass
class SupportContext:
    # Customer info (populated by router)
    customer_id: str = ""
    customer_name: str = ""
    customer_email: str = ""
    subscription_plan: str = ""

    # Interaction tracking
    issue_category: str = ""
    resolution_status: str = "open"  # open, resolved, escalated
    interaction_log: list[str] = field(default_factory=list)

    # Escalation data
    escalation_reason: str = ""
    escalation_priority: str = "normal"  # low, normal, high, urgent

    def log(self, agent_name: str, action: str):
        timestamp = datetime.now().strftime("%H:%M:%S")
        self.interaction_log.append(f"[{timestamp}] {agent_name}: {action}")

Step 2: Build the FAQ Agent

The FAQ agent searches a knowledge base and returns answers. It is the simplest specialist:

from agents import Agent, RunContextWrapper, function_tool

FAQ_DATABASE = {
    "password_reset": "To reset your password, go to Settings > Security > Reset Password. You will receive an email with a reset link.",
    "supported_browsers": "We support Chrome 90+, Firefox 88+, Safari 15+, and Edge 90+.",
    "data_export": "Go to Settings > Data > Export. Select your date range and format (CSV or JSON). Exports are ready within 5 minutes.",
    "api_rate_limits": "Free plans: 100 requests/hour. Pro plans: 10,000 requests/hour. Enterprise: custom limits.",
    "two_factor_auth": "Go to Settings > Security > Two-Factor Authentication. We support authenticator apps and SMS codes.",
}

@function_tool
def search_faq(
    ctx: RunContextWrapper[SupportContext],
    query: str,
) -> str:
    """Search the FAQ knowledge base for answers."""
    ctx.context.log("FAQ Agent", f"Searched FAQ: {query}")
    query_lower = query.lower()
    results = []
    for key, answer in FAQ_DATABASE.items():
        if any(word in query_lower for word in key.split("_")):
            results.append(f"**{key.replace('_', ' ').title()}**: {answer}")
    if results:
        return "\n\n".join(results)
    return "No FAQ articles found matching your query."

@function_tool
def mark_resolved(
    ctx: RunContextWrapper[SupportContext],
) -> str:
    """Mark the current issue as resolved."""
    ctx.context.resolution_status = "resolved"
    ctx.context.log("FAQ Agent", "Marked issue as resolved")
    return "Issue marked as resolved."

faq_agent = Agent(
    name="FAQ Agent",
    model="gpt-4o-mini",
    instructions="""You answer common customer questions using the FAQ
    knowledge base. Search for relevant articles and provide clear,
    helpful answers.

    Rules:
    - Always search the FAQ before answering
    - If the FAQ has the answer, provide it and mark the issue resolved
    - If the FAQ does not have the answer, say so clearly and suggest
      the user might need billing help or escalation
    - Never make up information not in the FAQ""",
    tools=[search_faq, mark_resolved],
)

Step 3: Build the Billing Agent

The billing agent handles financial operations. It has access to tools the FAQ agent does not:

@function_tool
def get_invoices(
    ctx: RunContextWrapper[SupportContext],
    customer_id: str,
) -> str:
    """Retrieve recent invoices for a customer."""
    ctx.context.log("Billing Agent", f"Retrieved invoices for {customer_id}")
    return f"""Invoices for {customer_id}:
- INV-2024-001: $49.00 (Pro Plan - March 2026) - PAID
- INV-2024-002: $49.00 (Pro Plan - February 2026) - PAID
- INV-2024-003: $49.00 (Pro Plan - January 2026) - PAID"""

@function_tool
def process_refund(
    ctx: RunContextWrapper[SupportContext],
    invoice_id: str,
    reason: str,
) -> str:
    """Process a refund for a specific invoice."""
    ctx.context.log("Billing Agent", f"Processed refund for {invoice_id}: {reason}")
    ctx.context.resolution_status = "resolved"
    return f"Refund of $49.00 initiated for {invoice_id}. Will appear in 5-10 business days."

@function_tool
def change_subscription(
    ctx: RunContextWrapper[SupportContext],
    new_plan: str,
) -> str:
    """Change the customer's subscription plan."""
    ctx.context.log("Billing Agent", f"Changed plan to {new_plan}")
    ctx.context.subscription_plan = new_plan
    return f"Subscription changed to {new_plan}. New billing starts next cycle."

billing_agent = Agent(
    name="Billing Agent",
    instructions="""You handle billing, payments, invoices, refunds,
    and subscription changes.

    Rules:
    - Always verify the customer ID from context before making changes
    - For refunds, confirm the invoice ID and reason with the user
    - For plan changes, explain the pricing difference before proceeding
    - Maximum refund without escalation: $200. For larger amounts,
      hand off to Escalation Agent
    - Never discuss other customers' billing information""",
    tools=[get_invoices, process_refund, change_subscription],
)

Step 4: Build the Escalation Agent

The escalation agent handles cases that automated agents cannot resolve. It collects information and creates a support ticket:

@function_tool
def create_support_ticket(
    ctx: RunContextWrapper[SupportContext],
    summary: str,
    priority: str,
    details: str,
) -> str:
    """Create a support ticket for human review."""
    ctx.context.escalation_reason = summary
    ctx.context.escalation_priority = priority
    ctx.context.resolution_status = "escalated"
    ctx.context.log("Escalation Agent", f"Created ticket: {summary} [{priority}]")
    ticket_id = f"TKT-{datetime.now().strftime('%Y%m%d%H%M%S')}"
    return f"Support ticket {ticket_id} created (Priority: {priority}). A human agent will respond within {'1 hour' if priority == 'urgent' else '24 hours'}."

@function_tool
def get_interaction_history(
    ctx: RunContextWrapper[SupportContext],
) -> str:
    """Get the full interaction log for this session."""
    return "\n".join(ctx.context.interaction_log) or "No interactions logged."

escalation_agent = Agent(
    name="Escalation Agent",
    instructions="""You handle cases that automated agents cannot
    resolve. Your job is to:

    1. Acknowledge the customer's frustration
    2. Summarize what has been tried so far (check interaction history)
    3. Collect any additional details needed
    4. Create a support ticket with appropriate priority

    Priority guidelines:
    - urgent: service outage, security issue, payment processing failure
    - high: significant feature broken, large refund request
    - normal: general issues requiring human judgment
    - low: feature requests, minor cosmetic issues

    Always give the customer the ticket ID and expected response time.""",
    tools=[create_support_ticket, get_interaction_history],
)

Step 5: Build the Router

The router ties everything together. It identifies the customer and routes to the right specialist:

See AI Voice Agents Handle Real Calls

Book a free demo or calculate how much you can save with AI voice automation.

from agents import handoff

@function_tool
def identify_customer(
    ctx: RunContextWrapper[SupportContext],
    email: str,
) -> str:
    """Look up a customer by email address."""
    # Simulated lookup
    ctx.context.customer_id = "cust_88421"
    ctx.context.customer_name = "Sarah Chen"
    ctx.context.customer_email = email
    ctx.context.subscription_plan = "Pro"
    ctx.context.log("Router", f"Identified customer: Sarah Chen ({email})")
    return "Customer found: Sarah Chen (Pro plan)"

router = Agent(
    name="Support Router",
    model="gpt-4o-mini",
    instructions="""You are the first point of contact for customer
    support. For every conversation:

    1. If you know the customer's email, identify them first
    2. Classify the request:
       - FAQ: general how-to questions, feature questions, documentation
       - Billing: payments, invoices, refunds, plan changes
       - Escalation: complaints, complex issues, anything you are unsure about
    3. Hand off to the appropriate specialist

    If a customer seems frustrated or mentions wanting to cancel, route
    to Escalation regardless of the topic.

    Never try to resolve issues yourself. Route immediately.""",
    tools=[identify_customer],
    handoffs=[
        handoff(faq_agent, tool_description_override="Route to FAQ for general questions and how-to help"),
        handoff(billing_agent, tool_description_override="Route to Billing for payments, invoices, and subscriptions"),
        handoff(escalation_agent, tool_description_override="Route to Escalation for complaints, complex issues, or frustrated customers"),
    ],
)

Step 6: Add Cross-Agent Handoffs

Specialists need to escalate or redirect when a request does not match their expertise:

# FAQ agent can escalate or redirect to billing
faq_agent.handoffs = [
    handoff(billing_agent, tool_description_override="Transfer to Billing if the user has a payment or subscription question"),
    handoff(escalation_agent, tool_description_override="Escalate if you cannot find the answer in the FAQ"),
]

# Billing agent can escalate
billing_agent.handoffs = [
    handoff(escalation_agent, tool_description_override="Escalate for refunds over $200 or complex billing disputes"),
]

Running the System

from agents import Runner

context = SupportContext()

result = Runner.run_sync(
    router,
    "Hi, my email is sarah@example.com. I was charged twice this month and I want a refund.",
    context=context,
)

print(result.final_output)
print("\n--- Interaction Log ---")
for entry in context.interaction_log:
    print(entry)
print(f"Status: {context.resolution_status}")

This produces a natural conversation flow: Router identifies the customer, recognizes a billing issue, hands off to the Billing Agent, which retrieves invoices, confirms the duplicate charge, and processes the refund.

Production Considerations

Timeout protection. Set max_turns=15 on the Runner to prevent infinite agent loops.

Logging. The interaction_log in the context captures every agent action, creating an audit trail for compliance and quality review.

Graceful degradation. If any specialist fails, the escalation agent is always available as a backstop. Every agent has an escalation handoff.

FAQ

How do I add a new specialist agent to this system?

Create the new agent with its tools and instructions. Add a handoff to it from the router. Add the routing criteria to the router's instructions. No other agents need to change unless they should be able to redirect to the new specialist.

How do I handle returning customers with ongoing issues?

Persist the SupportContext to your session store (Redis or database) keyed by customer ID. When the customer returns, load the context and pass it to the Runner. The agents will see the interaction history and previous resolution status.

What happens if the router misroutes a request?

Each specialist agent has handoffs to other agents and the escalation path. If the FAQ agent receives a billing question, it can hand off to the Billing Agent directly. The system self-corrects through inter-specialist handoffs.


#CustomerSupport #MultiAgentSystems #OpenAIAgentsSDK #ProductionArchitecture #AgentDesign #AgenticAI #LearnAI #AIEngineering

Share this article
C

CallSphere Team

Expert insights on AI voice agents and customer communication automation.

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.