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:
- Agent ā A configurable entity that combines a system prompt, a model, and a set of tools. Each agent can be thought of as a specialized "worker" with a defined personality and capability set.
- Tool ā A callable function that an agent can invoke at runtime. Tools bridge the gap between LLM reasoning and real-world actions such as querying a database, sending an email, or looking up a customer's order.
- Handoff ā A mechanism that allows one agent to transfer control to another agent. This is critical for building support bots that escalate issues from a generalist front-line agent to a specialized billing or technical support agent.
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:
- Natural language understanding out of the box ā Instead of writing thousands of intent-matching rules, you define a system prompt and let the model interpret the user's request. The SDK automatically handles nuance, slang, and ambiguous phrasing.
- Modular agent architecture ā You can decompose your support workflow into small, focused agents (e.g., OrderStatusAgent, BillingAgent, ReturnsAgent) and wire them together with handoffs. This keeps each agent's prompt concise and its behavior predictable.
- Automatic tool execution ā When an agent decides it needs to look up a customer's account, it simply calls a tool. The SDK manages the function invocation, passes the result back to the model, and continues the conversation seamlessly.
- Observability and guardrails ā The SDK provides hooks for tracing, logging, and validating agent outputs, making it practical to deploy in regulated environments where every customer interaction must be auditable.
- Reduced latency and cost ā By routing queries to specialized agents with shorter, more focused prompts, you reduce the number of tokens consumed per interaction and improve response times.
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:
- "Hand off to BillingAgent if the customer mentions refund, payment, invoice, or billing."
- "Hand off to TechSupportAgent if the customer reports a product defect, error message, or account lockout."
- "Escalate to a human agent if the customer explicitly asks for one, or if you have failed to resolve the issue after two attempts."
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:
- Order status inquiry (happy path)
- Refund request with handoff to billing
- Technical issue with handoff to tech support
- Escalation to human agent
- Edge cases: missing order ID, invalid customer ID, ambiguous requests
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.