Skip to content

Commit c9310f3

Browse files
authored
[FEAT] Adds openai example for an ecommerce agent (#12)
* rfac: update openai example graph * rfac: adds eof
1 parent 010cdd5 commit c9310f3

File tree

2 files changed

+202
-140
lines changed

2 files changed

+202
-140
lines changed

examples/code/openai_agents/agent.py

Lines changed: 137 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import asyncio
44
import random
55
import uuid
6+
from typing import Literal
67

78
from pydantic import BaseModel
89

@@ -11,7 +12,7 @@
1112
HandoffOutputItem,
1213
ItemHelpers,
1314
MessageOutputItem,
14-
RunContextWrapper,
15+
RunContextWrapper,
1516
Runner,
1617
ToolCallItem,
1718
ToolCallOutputItem,
@@ -22,148 +23,192 @@
2223
)
2324
from agents.extensions.handoff_prompt import RECOMMENDED_PROMPT_PREFIX
2425

25-
### CONTEXT
2626

27+
class EcommerceAgentContext(BaseModel):
28+
"""Holds conversation state for e-commerce support."""
29+
customer_name: str | None = None
30+
order_id: str | None = None
31+
product_sku: str | None = None
32+
last_inquiry_type: Literal["order", "product", "other"] | None = None
2733

28-
class AirlineAgentContext(BaseModel):
29-
passenger_name: str | None = None
30-
confirmation_number: str | None = None
31-
seat_number: str | None = None
32-
flight_number: str | None = None
3334

35+
@function_tool
36+
async def get_order_status(order_id: str) -> str:
37+
"""
38+
Looks up the status of a given order ID.
3439
35-
### TOOLS
36-
37-
38-
@function_tool(
39-
name_override="faq_lookup_tool", description_override="Lookup frequently asked questions."
40-
)
41-
async def faq_lookup_tool(question: str) -> str:
42-
if "bag" in question or "baggage" in question:
43-
return (
44-
"You are allowed to bring one bag on the plane. "
45-
"It must be under 50 pounds and 22 inches x 14 inches x 9 inches."
46-
)
47-
elif "seats" in question or "plane" in question:
48-
return (
49-
"There are 120 seats on the plane. "
50-
"There are 22 business class seats and 98 economy seats. "
51-
"Exit rows are rows 4 and 16. "
52-
"Rows 5-8 are Economy Plus, with extra legroom. "
53-
)
54-
elif "wifi" in question:
55-
return "We have free wifi on the plane, join Airline-Wifi"
56-
return "I'm sorry, I don't know the answer to that question."
57-
40+
Args:
41+
order_id: The unique identifier for the order (e.g., ORD-12345).
42+
"""
43+
print(f"--- Tool: Simulating lookup for order: {order_id} ---")
44+
if not order_id or not order_id.startswith("ORD-"):
45+
return "Invalid order ID format. Please provide an ID like 'ORD-12345'."
46+
47+
possible_statuses = ["Processing", "Shipped", "Delivered", "Delayed", "Cancelled"]
48+
status = possible_statuses[hash(order_id) % len(possible_statuses)]
49+
50+
if status == "Shipped":
51+
tracking = f"TRK-{random.randint(100000000, 999999999)}"
52+
return f"Order {order_id} has been Shipped. Tracking number: {tracking}"
53+
elif status == "Delivered":
54+
return f"Order {order_id} was Delivered successfully."
55+
elif status == "Processing":
56+
return f"Order {order_id} is currently Processing. Expected ship date is in 2 business days."
57+
elif status == "Delayed":
58+
return f"Order {order_id} is currently Delayed due to high volume. We apologize for the inconvenience."
59+
else: # Cancelled
60+
return f"Order {order_id} has been Cancelled."
5861

5962
@function_tool
60-
async def update_seat(
61-
context: RunContextWrapper[AirlineAgentContext], confirmation_number: str, new_seat: str
62-
) -> str:
63+
async def get_product_info(product_sku: str) -> str:
6364
"""
64-
Update the seat for a given confirmation number.
65+
Provides information about a product based on its SKU.
6566
6667
Args:
67-
confirmation_number: The confirmation number for the flight.
68-
new_seat: The new seat to update to.
68+
product_sku: The Stock Keeping Unit (SKU) of the product (e.g., SKU-TECH-001).
6969
"""
70-
# Update the context based on the customer's input
71-
context.context.confirmation_number = confirmation_number
72-
context.context.seat_number = new_seat
73-
# Ensure that the flight number has been set by the incoming handoff
74-
assert context.context.flight_number is not None, "Flight number is required"
75-
return f"Updated seat to {new_seat} for confirmation number {confirmation_number}"
70+
print(f"--- Tool: Simulating lookup for product SKU: {product_sku} ---")
71+
if not product_sku or not product_sku.startswith("SKU-"):
72+
return "Invalid SKU format. Please provide an SKU like 'SKU-TECH-001'."
73+
74+
products = {
75+
"SKU-TECH-001": {"name": "Wireless Mouse", "price": 25.99, "stock": 150, "desc": "A reliable ergonomic wireless mouse."},
76+
"SKU-TECH-002": {"name": "Mechanical Keyboard", "price": 79.99, "stock": 50, "desc": "A backlit mechanical keyboard with blue switches."},
77+
"SKU-HOME-001": {"name": "Coffee Mug", "price": 12.50, "stock": 0, "desc": "A ceramic coffee mug with our logo."},
78+
}
7679

80+
info = products.get(product_sku)
7781

78-
### HOOKS
82+
if info:
83+
stock_status = f"In Stock ({info['stock']} available)" if info['stock'] > 0 else "Out of Stock"
84+
return (
85+
f"Product: {info['name']} (SKU: {product_sku})\n"
86+
f"Description: {info['desc']}\n"
87+
f"Price: ${info['price']:.2f}\n"
88+
f"Availability: {stock_status}"
89+
)
90+
else:
91+
return f"Sorry, I could not find any information for product SKU: {product_sku}."
7992

8093

81-
async def on_seat_booking_handoff(context: RunContextWrapper[AirlineAgentContext]) -> None:
82-
flight_number = f"FLT-{random.randint(100, 999)}"
83-
context.context.flight_number = flight_number
94+
async def on_order_status_handoff(context: RunContextWrapper[EcommerceAgentContext]) -> None:
95+
print("--- Hook: Handing off to Order Status Agent ---")
8496

8597

86-
### AGENTS
98+
triage_agent: Agent[EcommerceAgentContext]
8799

88-
faq_agent = Agent[AirlineAgentContext](
89-
name="FAQ Agent",
90-
handoff_description="A helpful agent that can answer questions about the airline.",
100+
order_status_agent = Agent[EcommerceAgentContext](
101+
name="Order Status Agent",
102+
handoff_description="Handles inquiries about the status of existing orders.",
91103
instructions=f"""{RECOMMENDED_PROMPT_PREFIX}
92-
You are an FAQ agent. If you are speaking to a customer, you probably were transferred to from the triage agent.
93-
Use the following routine to support the customer.
94-
# Routine
95-
1. Identify the last question asked by the customer.
96-
2. Use the faq lookup tool to answer the question. Do not rely on your own knowledge.
97-
3. If you cannot answer the question, transfer back to the triage agent.""",
98-
tools=[faq_lookup_tool],
104+
You are a specialized agent responsible for providing order status updates.
105+
Your goal is to assist customers who have questions about their orders.
106+
107+
# Routine:
108+
1. Check if the user has provided an order ID. If not, politely ask for it (e.g., "Could you please provide your order ID, usually starting with 'ORD-'?").
109+
2. Once you have the order ID, use the `get_order_status` tool to look it up.
110+
3. Provide the status information clearly to the customer.
111+
4. If the customer asks about something *other* than order status (e.g., product details, returns, general questions), hand the conversation back to the Triage Agent. Do not attempt to answer unrelated questions yourself.
112+
""",
113+
tools=[get_order_status],
114+
handoffs=[], # Will be set after triage_agent is defined
99115
)
100116

101-
seat_booking_agent = Agent[AirlineAgentContext](
102-
name="Seat Booking Agent",
103-
handoff_description="A helpful agent that can update a seat on a flight.",
117+
product_info_agent = Agent[EcommerceAgentContext](
118+
name="Product Info Agent",
119+
handoff_description="Provides details about specific products based on their SKU.",
104120
instructions=f"""{RECOMMENDED_PROMPT_PREFIX}
105-
You are a seat booking agent. If you are speaking to a customer, you probably were transferred to from the triage agent.
106-
Use the following routine to support the customer.
107-
# Routine
108-
1. Ask for their confirmation number.
109-
2. Ask the customer what their desired seat number is.
110-
3. Use the update seat tool to update the seat on the flight.
111-
If the customer asks a question that is not related to the routine, transfer back to the triage agent. """,
112-
tools=[update_seat],
121+
You are a specialized agent responsible for providing product information.
122+
Your goal is to assist customers looking for details about products we sell.
123+
124+
# Routine:
125+
1. Check if the user has provided a product SKU. If not, politely ask for it (e.g., "Do you have the product SKU, usually starting with 'SKU-'?"). You can also try to infer it if they describe a product mentioned by the `get_product_info` tool.
126+
2. Once you have the SKU, use the `get_product_info` tool to look it up.
127+
3. Provide the product details (description, price, availability) clearly to the customer.
128+
4. If the customer asks about something *other* than product information (e.g., order status, returns, general questions), hand the conversation back to the Triage Agent. Do not attempt to answer unrelated questions yourself.
129+
""",
130+
tools=[get_product_info],
131+
handoffs=[], # Will be set after triage_agent is defined
113132
)
114133

115-
triage_agent = Agent[AirlineAgentContext](
134+
triage_agent = Agent[EcommerceAgentContext](
116135
name="Triage Agent",
117-
handoff_description="A triage agent that can delegate a customer's request to the appropriate agent.",
136+
handoff_description="The main customer support agent that directs inquiries to the correct specialist.",
118137
instructions=(
119138
f"{RECOMMENDED_PROMPT_PREFIX} "
120-
"You are a helpful triaging agent. You can use your tools to delegate questions to other appropriate agents."
139+
"You are the primary E-commerce Support Agent. Your main role is to understand the customer's needs and delegate the query to the appropriate specialist agent using your tools.\n"
140+
"Available Specialists:\n"
141+
"- **Order Status Agent:** Handles questions about existing order status.\n"
142+
"- **Product Info Agent:** Provides details about specific products.\n\n"
143+
"Analyze the customer's message. If it's clearly about an order status, hand off to the Order Status Agent. If it's clearly about product details, hand off to the Product Info Agent. If you are unsure, or it's a general question, try to clarify or answer briefly if possible, but prioritize handing off to specialists for their specific tasks."
144+
" If a specialist agent hands back to you, understand the context and see if another specialist is needed or if you can handle the request now."
121145
),
122146
handoffs=[
123-
faq_agent,
124-
handoff(agent=seat_booking_agent, on_handoff=on_seat_booking_handoff),
147+
order_status_agent,
148+
product_info_agent,
149+
handoff(agent=order_status_agent, on_handoff=on_order_status_handoff),
125150
],
126151
)
127152

128-
faq_agent.handoffs.append(triage_agent)
129-
seat_booking_agent.handoffs.append(triage_agent)
130-
131-
132-
### RUN
153+
order_status_agent.handoffs.append(triage_agent)
154+
product_info_agent.handoffs.append(triage_agent)
133155

134156

135157
async def main():
136-
current_agent: Agent[AirlineAgentContext] = triage_agent
158+
current_agent: Agent[EcommerceAgentContext] = triage_agent
137159
input_items: list[TResponseInputItem] = []
138-
context = AirlineAgentContext()
160+
context = EcommerceAgentContext() # Initialize the context
139161

140-
# Normally, each input from the user would be an API request to your app, and you can wrap the request in a trace()
141-
# Here, we'll just use a random UUID for the conversation ID
142162
conversation_id = uuid.uuid4().hex[:16]
163+
print(f"Starting E-commerce Support Conversation (ID: {conversation_id})")
164+
print("Enter 'quit' to exit.")
165+
print(f"Agent: {current_agent.name}: How can I help you today?") # Initial greeting
143166

144167
while True:
145-
user_input = input("Enter your message: ")
146-
with trace("Customer service", group_id=conversation_id):
168+
user_input = input("You: ")
169+
if user_input.lower() == 'quit':
170+
print("Ending conversation.")
171+
break
172+
173+
with trace("E-commerce Support Turn", group_id=conversation_id):
147174
input_items.append({"content": user_input, "role": "user"})
148-
result = await Runner.run(current_agent, input_items, context=context)
175+
176+
result = await Runner.run(
177+
current_agent,
178+
input_items,
179+
context=context
180+
)
149181

150182
for new_item in result.new_items:
151183
agent_name = new_item.agent.name
152184
if isinstance(new_item, MessageOutputItem):
153-
print(f"{agent_name}: {ItemHelpers.text_message_output(new_item)}")
185+
message = ItemHelpers.text_message_output(new_item)
186+
print(f"Agent: {agent_name}: {message}")
154187
elif isinstance(new_item, HandoffOutputItem):
155188
print(
156-
f"Handed off from {new_item.source_agent.name} to {new_item.target_agent.name}"
189+
f"--- System: Handed off from {new_item.source_agent.name} to {new_item.target_agent.name} ---"
157190
)
191+
if new_item.target_agent is order_status_agent:
192+
context.last_inquiry_type = "order"
193+
elif new_item.target_agent is product_info_agent:
194+
context.last_inquiry_type = "product"
195+
else:
196+
context.last_inquiry_type = "other"
197+
158198
elif isinstance(new_item, ToolCallItem):
159-
print(f"{agent_name}: Calling a tool")
199+
tool_name = new_item.tool_call.function.name
200+
args = new_item.tool_call.function.arguments
201+
print(f"--- System: {agent_name} calling tool `{tool_name}` with args: {args} ---")
160202
elif isinstance(new_item, ToolCallOutputItem):
161-
print(f"{agent_name}: Tool call output: {new_item.output}")
203+
pass # Often the agent summarizes this in its next message
162204
else:
163-
print(f"{agent_name}: Skipping item: {new_item.__class__.__name__}")
205+
print(f"--- System: {agent_name} produced item: {new_item.__class__.__name__} ---")
206+
164207
input_items = result.to_input_list()
165208
current_agent = result.last_agent
166209

167-
168210
if __name__ == "__main__":
169-
asyncio.run(main())
211+
try:
212+
asyncio.run(main())
213+
except KeyboardInterrupt:
214+
print("\nExiting...")

0 commit comments

Comments
 (0)