← Back to DevBytes

Building a Customer Support Bot with OpenAI Agents SDK: Complete Guide

What Is the OpenAI Agents SDK?

The OpenAI Agents SDK is a lightweight, open-source framework designed to streamline the development of agentic AI applications. Rather than forcing developers to build orchestration logic from scratch, the SDK provides first-class primitives for defining agent behaviors, managing conversation state, chaining multi-step workflows, and integrating external tools — all while leveraging OpenAI's latest language models.

At its core, the SDK introduces three key abstractions:

For customer support specifically, the SDK allows you to model the entire support workflow as a network of collaborating agents, each responsible for a specific domain, while the SDK handles the underlying complexity of context management, tool execution, and error recovery.

Why It Matters for Customer Support

Building a production-grade customer support bot has historically required significant engineering effort. Traditional approaches often rely on rigid decision trees, keyword matching, or fine-tuned models that are expensive to maintain and brittle when faced with novel user queries. The OpenAI Agents SDK changes this calculus in several fundamental ways:

In short, the SDK allows you to build a support bot that is more capable, easier to maintain, and faster to iterate on than traditional approaches — all while keeping the developer experience clean and Pythonic.

How to Use the SDK: A Step-by-Step Implementation

Step 1 — Environment Setup and Installation

Begin by installing the SDK and its core dependencies. The SDK requires Python 3.10 or later. Use a virtual environment to keep your project isolated.

# Create and activate a virtual environment
python3 -m venv support-bot-env
source support-bot-env/bin/activate

# Install the OpenAI Agents SDK
pip install openai-agents

# Install additional utilities (optional but recommended)
pip install python-dotenv httpx

Next, create a .env file in your project root to store your OpenAI API key securely:

OPENAI_API_KEY=sk-your-key-here
OPENAI_MODEL=gpt-4o-mini  # or gpt-4o for higher accuracy

Load the environment variables at the entry point of your application:

import os
from dotenv import load_dotenv

load_dotenv()

api_key = os.getenv("OPENAI_API_KEY")
model = os.getenv("OPENAI_MODEL", "gpt-4o-mini")

Step 2 — Defining the Core Abstractions

Let's build a support bot for an e-commerce platform. We'll define a set of tools that our agents can use to interact with customer data and orders.

Create a tools module (tools.py):

import json
from datetime import datetime, timedelta
from agents import function_tool

# Simulated in-memory data store
CUSTOMERS = {
    "C001": {"name": "Alice Johnson", "email": "alice@example.com", "tier": "gold"},
    "C002": {"name": "Bob Smith", "email": "bob@example.com", "tier": "standard"},
}

ORDERS = {
    "ORD-1001": {"customer_id": "C001", "status": "shipped", "total": 59.99,
                 "items": ["Wireless Mouse", "USB-C Hub"], "shipped_date": "2025-03-10"},
    "ORD-1002": {"customer_id": "C001", "status": "processing", "total": 129.99,
                 "items": ["Mechanical Keyboard"], "shipped_date": None},
    "ORD-1003": {"customer_id": "C002", "status": "delivered", "total": 24.99,
                 "items": ["Mouse Pad"], "shipped_date": "2025-03-05"},
}

RETURNS = {
    "RET-5001": {"order_id": "ORD-1003", "status": "approved", "refund_amount": 24.99},
}

@function_tool
def lookup_customer(customer_id: str) -> str:
    """Look up a customer by their customer ID. Returns customer details."""
    customer = CUSTOMERS.get(customer_id)
    if not customer:
        return json.dumps({"error": f"Customer {customer_id} not found."})
    return json.dumps(customer)

@function_tool
def lookup_order(order_id: str) -> str:
    """Look up an order by order ID. Returns order details including status and items."""
    order = ORDERS.get(order_id)
    if not order:
        return json.dumps({"error": f"Order {order_id} not found."})
    return json.dumps(order)

@function_tool
def get_orders_by_customer(customer_id: str) -> str:
    """Get all orders for a given customer ID."""
    customer_orders = [o for o in ORDERS.values() if o["customer_id"] == customer_id]
    if not customer_orders:
        return json.dumps({"error": f"No orders found for customer {customer_id}."})
    return json.dumps(customer_orders)

@function_tool
def check_return_status(return_id: str) -> str:
    """Check the status of a return/refund request."""
    ret = RETURNS.get(return_id)
    if not ret:
        return json.dumps({"error": f"Return {return_id} not found."})
    return json.dumps(ret)

@function_tool
def escalate_to_human_agent(customer_id: str, issue_summary: str) -> str:
    """Escalate the conversation to a human support agent. Use as a last resort."""
    # In production, this would create a ticket in Zendesk, Freshdesk, etc.
    ticket_id = f"TKT-{datetime.now().strftime('%Y%m%d%H%M%S')}"
    print(f"[ESCALATION] Ticket {ticket_id} created for {customer_id}: {issue_summary}")
    return json.dumps({"ticket_id": ticket_id, "message": "Your issue has been escalated. A human agent will follow up within 4 hours."})

Step 3 — Creating Specialized Agents

Now we define three agents: a generalist front-line agent, a billing specialist, and a technical support specialist. Each agent has a focused system prompt and the tools relevant to its domain.

Create an agents module (agents_config.py):

from agents import Agent, Runner, set_default_openai_key
from tools import (
    lookup_customer,
    lookup_order,
    get_orders_by_customer,
    check_return_status,
    escalate_to_human_agent,
)
import os

set_default_openai_key(os.getenv("OPENAI_API_KEY"))

# ─── Front-line Support Agent ───
frontline_agent = Agent(
    name="FrontlineSupport",
    instructions="""You are the first point of contact for customer support.
    Your role is to:
    1. Greet the customer warmly and ask for their customer ID or order ID.
    2. Use the lookup tools to find their information.
    3. Answer general questions about order status, shipping, and basic policies.
    4. If the issue involves billing, refunds, or payment disputes, hand off to the BillingAgent.
    5. If the issue involves technical product problems or account access, hand off to the TechSupportAgent.
    6. If you cannot resolve the issue after two attempts, escalate to a human agent.
    Always be polite, empathetic, and concise.""",
    tools=[lookup_customer, lookup_order, get_orders_by_customer, escalate_to_human_agent],
    handoffs=[],  # Will be populated after agent definitions
)

# ─── Billing Specialist Agent ───
billing_agent = Agent(
    name="BillingAgent",
    instructions="""You handle all billing, payment, refund, and return inquiries.
    Your role is to:
    1. Verify the customer's identity using their customer ID.
    2. Look up relevant orders and return requests.
    3. Explain refund policies (30-day return window, refunds processed within 5-7 business days).
    4. Check the status of existing return/refund requests.
    5. If the customer wants to initiate a new return, collect the order ID and reason, then escalate to a human agent for processing.
    6. If the issue is beyond billing (e.g., technical), hand back to FrontlineSupport.
    Be transparent about timelines and set clear expectations.""",
    tools=[lookup_customer, lookup_order, check_return_status, escalate_to_human_agent],
    handoffs=[],
)

# ─── Technical Support Specialist Agent ───
tech_support_agent = Agent(
    name="TechSupportAgent",
    instructions="""You handle technical product issues, account problems, and troubleshooting.
    Your role is to:
    1. Verify the customer's identity and the product they are having trouble with.
    2. Provide step-by-step troubleshooting guidance.
    3. If the issue requires a replacement or warranty claim, collect the order ID and escalate.
    4. If the issue is billing-related, hand back to BillingAgent.
    Stay calm, patient, and guide the customer one step at a time.""",
    tools=[lookup_customer, lookup_order, escalate_to_human_agent],
    handoffs=[],
)

# Wire up handoffs (agents can reference each other after creation)
frontline_agent.handoffs = [billing_agent, tech_support_agent]
billing_agent.handoffs = [frontline_agent, tech_support_agent]
tech_support_agent.handoffs = [frontline_agent, billing_agent]

Step 4 — Building the Main Application Loop

With the agents configured, we need an application entry point that accepts user input, routes it through the agent runner, and displays responses. The SDK's Runner class handles the orchestration loop internally.

Create the main application (main.py):

import asyncio
import os
from agents import Runner, trace
from agents_config import frontline_agent

async def main():
    print("=" * 60)
    print("  Customer Support Bot — OpenAI Agents SDK")
    print("  Type 'quit' to exit, 'reset' to start over.")
    print("=" * 60)

    # Conversation history (maintained by the runner)
    conversation_input = None

    while True:
        # Get user input
        user_input = input("\nšŸ‘¤ You: ").strip()
        if user_input.lower() in ("quit", "exit"):
            print("šŸ¤– Agent: Thank you for contacting us. Have a great day!")
            break
        if user_input.lower() == "reset":
            conversation_input = None
            print("šŸ¤– Agent: Conversation reset. How can I help you today?")
            continue

        # Run the agent with tracing enabled
        with trace("Customer Support Conversation"):
            result = await Runner.run(
                starting_agent=frontline_agent,
                input=user_input,
                context=conversation_input,
            )

        # Store the updated conversation context
        conversation_input = result.to_input_list()

        # Display the agent's response
        print(f"\nšŸ¤– {result.agent.name}: {result.final_output}")

        # If a handoff occurred, show which agent handled the response
        if result.handoffs:
            handoff_names = [h.agent.name for h in result.handoffs]
            print(f"   [Handoff chain: {' -> '.join(handoff_names)}]")

if __name__ == "__main__":
    asyncio.run(main())

Step 5 — Running the Support Bot

Execute the bot from your terminal. The SDK automatically manages the agent selection, tool calls, and handoffs based on the conversation context.

python main.py

Here is an example interaction showing the bot's behavior:

============================================================
  Customer Support Bot — OpenAI Agents SDK
  Type 'quit' to exit, 'reset' to start over.
============================================================

šŸ‘¤ You: Hi, I need help with my order.

šŸ¤– FrontlineSupport: Hello! I'd be happy to help you with your order. Could you please provide me with your customer ID or order ID so I can look up the details?

šŸ‘¤ You: My customer ID is C001.

šŸ¤– FrontlineSupport: Thank you, Alice! I can see you have two orders:
1. ORD-1001 — Shipped on March 10 (Wireless Mouse, USB-C Hub)
2. ORD-1002 — Currently processing (Mechanical Keyboard)

What would you like to know about your orders?

šŸ‘¤ You: I want a refund for the keyboard. It's defective.

šŸ¤– FrontlineSupport: I understand that's frustrating. Let me transfer you to our billing specialist who can assist with the refund process.

   [Handoff chain: FrontlineSupport -> BillingAgent]

šŸ¤– BillingAgent: Hi Alice, I'm sorry to hear about the defective keyboard. I can help with the refund. Let me check the details.

Your order ORD-1002 (Mechanical Keyboard) is still in processing status. Since it hasn't shipped yet, I can initiate a cancellation and full refund right away. Would you like me to proceed with the cancellation, or would you prefer to receive a replacement unit?

Step 6 — Adding Observability and Guardrails

Production support bots need monitoring and safety constraints. The SDK provides hooks for both. Below is an example of adding input validation and output guardrails.

Create a guardrails module (guardrails.py):

from agents import Guardrail, InputGuardrailResult, OutputGuardrailResult

class PIIRedactionGuardrail(Guardrail):
    """Prevent the agent from outputting sensitive PII like full credit card numbers."""

    async def check_input(self, agent, input_text: str) -> InputGuardrailResult:
        # Simple check for patterns like 16-digit numbers
        import re
        potential_cc = re.findall(r'\b\d{16}\b', input_text)
        if potential_cc:
            return InputGuardrailResult(
                triggered=True,
                message="Input appears to contain a credit card number. Please provide only the last 4 digits."
            )
        return InputGuardrailResult(triggered=False)

    async def check_output(self, agent, output_text: str) -> OutputGuardrailResult:
        # Ensure the agent never outputs full credit card numbers
        import re
        exposed_cc = re.findall(r'\b\d{16}\b', output_text)
        if exposed_cc:
            return OutputGuardrailResult(
                triggered=True,
                message="Output contained sensitive PII. Response has been blocked."
            )
        return OutputGuardrailResult(triggered=False)


class SentimentGuardrail(Guardrail):
    """Detect and flag highly negative customer sentiment for priority routing."""

    async def check_input(self, agent, input_text: str) -> InputGuardrailResult:
        negative_keywords = ["furious", "lawsuit", "lawyer", "terrible", "unacceptable"]
        if any(word in input_text.lower() for word in negative_keywords):
            return InputGuardrailResult(
                triggered=True,
                message="High-negative sentiment detected. Escalating to senior agent."
            )
        return InputGuardrailResult(triggered=False)

    async def check_output(self, agent, output_text: str) -> OutputGuardrailResult:
        # No output-side action needed for sentiment
        return OutputGuardrailResult(triggered=False)

To attach guardrails to an agent, pass them during construction:

from guardrails import PIIRedactionGuardrail, SentimentGuardrail

frontline_agent = Agent(
    name="FrontlineSupport",
    instructions="...",
    tools=[...],
    handoffs=[...],
    guardrails=[PIIRedactionGuardrail(), SentimentGuardrail()],
)

Step 7 — Deploying as a Web Service (FastAPI Example)

For a production deployment, you'll want to expose your bot as an API endpoint. Here's a minimal FastAPI integration:

Create api.py:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from agents import Runner, trace
from agents_config import frontline_agent
import uvicorn

app = FastAPI(title="Customer Support Bot API")

class ChatRequest(BaseModel):
    message: str
    conversation_id: str | None = None

class ChatResponse(BaseModel):
    reply: str
    agent_name: str
    conversation_id: str

# In-memory conversation store (use Redis in production)
conversations = {}

@app.post("/chat", response_model=ChatResponse)
async def chat(request: ChatRequest):
    conv_id = request.conversation_id or f"conv_{len(conversations) + 1}"

    # Retrieve or initialize conversation context
    context = conversations.get(conv_id, None)

    with trace(f"Support Conversation {conv_id}"):
        result = await Runner.run(
            starting_agent=frontline_agent,
            input=request.message,
            context=context,
        )

    # Store updated context
    conversations[conv_id] = result.to_input_list()

    return ChatResponse(
        reply=result.final_output,
        agent_name=result.agent.name,
        conversation_id=conv_id,
    )

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

Run the API server:

uvicorn api:app --reload

Test the endpoint with curl:

curl -X POST http://localhost:8000/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "Where is my order ORD-1001?"}'

Best Practices for Production

1. Design Agent Boundaries Carefully

Each agent should own a single domain of responsibility. A good rule of thumb: if you can describe the agent's purpose in one sentence without using "and" or "or", you've scoped it correctly. Overloaded agents produce inconsistent behavior and consume more tokens.

2. Write Explicit Handoff Conditions

In your system prompts, be explicit about when a handoff should occur. Ambiguous instructions like "if needed" lead to unpredictable routing. Instead, use concrete triggers:

3. Implement Idempotent Tools

Tools should be safe to call multiple times. If a tool creates a side effect (e.g., initiating a refund), ensure it checks for duplicate requests. Use idempotency keys or state checks to prevent accidental double-processing.

4. Use Tracing and Logging from Day One

The SDK's trace context manager is invaluable for debugging. In production, export traces to an observability backend (e.g., OpenAI's dashboard, or a custom OpenTelemetry exporter). Log every handoff, tool call, and guardrail trigger for audit compliance.

5. Set Token Limits and Timeouts

Protect against runaway conversations by configuring max tokens per turn and overall conversation limits:

from agents import AgentConfig

config = AgentConfig(
    max_tokens_per_turn=1024,
    max_conversation_turns=20,
    timeout_seconds=30,
)

frontline_agent = Agent(
    name="FrontlineSupport",
    instructions="...",
    tools=[...],
    config=config,
)

6. Version Your Prompts and Tools

Treat your system prompts and tool definitions as code. Store them in version control, tag releases, and run regression tests before deploying changes. A small change in wording can significantly alter agent behavior.

7. Handle Errors Gracefully

Wrap tool calls in try/except blocks and return structured error messages that the agent can interpret. Never let raw Python exceptions bubble up to the user.

@function_tool
def lookup_order(order_id: str) -> str:
    try:
        order = ORDERS.get(order_id)
        if not order:
            return json.dumps({"error": f"Order {order_id} not found."})
        return json.dumps(order)
    except Exception as e:
        return json.dumps({"error": f"Unable to look up order: {str(e)}"})

8. Implement Rate Limiting and Authentication

When deploying as an API, protect your endpoints with API keys, JWT tokens, or OAuth. Use rate limiting to prevent abuse and control costs. The SDK doesn't enforce auth — that's your responsibility at the application layer.

9. Test with Realistic Scenarios

Create a test suite that covers the most common customer journeys:

10. Monitor and Iterate

After deployment, track metrics such as resolution rate, average conversation length, handoff frequency, and customer satisfaction scores. Use this data to refine your prompts, add new tools, or split overburdened agents into smaller specialists.

Conclusion

The OpenAI Agents SDK provides a powerful, flexible foundation for building customer support bots that can understand natural language, orchestrate multi-step workflows, and hand off seamlessly between specialized agents. By modeling your support organization as a network of focused agents — each with clear responsibilities, well-defined tools, and explicit handoff rules — you can create a support experience that is faster, more consistent, and more scalable than traditional chatbot approaches. The SDK's built-in support for tracing, guardrails, and context management further reduces the gap between prototype and production. As you deploy and iterate, remember that the quality of your system prompts and the granularity of your agent boundaries will be the primary determinants of success. Start small, test thoroughly, and let the SDK handle the complexity of orchestration while you focus on delivering an exceptional customer experience.

— Ad —

Google AdSense will appear here after approval

← Back to all articles