project_meta / env /tasks.py
V1ShAl1613's picture
feat: complete OpenEnv Customer Support Ticket Resolution environment
7b73919
Raw History Blame Contribute Delete
9.37 kB
"""Task definitions for the Customer Support environment.
Each task includes a pool of realistic tickets and clear objectives.
"""
from __future__ import annotations
from typing import Any, Dict, List
from env.models import IssueCategory, Sentiment, Ticket
# ── Ticket pools ──────────────────────────────────────────────────────
_EASY_TICKETS: List[Dict[str, Any]] = [
{
"ticket_id": "EASY-001",
"customer_name": "Alice Johnson",
"issue_text": (
"My order #9821 was supposed to arrive last Monday but I still "
"haven't received it. The tracking page says 'in transit' for 5 days. "
"Can you check what happened?"
),
"category": IssueCategory.DELIVERY_DELAY,
"sentiment": Sentiment.NEUTRAL,
"order_value": 45.99,
"priority": "normal",
},
{
"ticket_id": "EASY-002",
"customer_name": "Bob Martinez",
"issue_text": (
"I received the laptop stand today but the top plate is cracked and "
"one of the screws is missing. The box was also dented. I need a "
"replacement."
),
"category": IssueCategory.DAMAGED_PRODUCT,
"sentiment": Sentiment.ANGRY,
"order_value": 89.50,
"priority": "high",
},
{
"ticket_id": "EASY-003",
"customer_name": "Caroline Pham",
"issue_text": (
"I'd like a refund for order #3345. I returned the shoes two weeks "
"ago and the tracking shows they were delivered back to your warehouse "
"on March 28."
),
"category": IssueCategory.REFUND_REQUEST,
"sentiment": Sentiment.NEUTRAL,
"order_value": 120.00,
"priority": "normal",
},
{
"ticket_id": "EASY-004",
"customer_name": "David Kim",
"issue_text": (
"I was charged twice for order #7712. My credit card statement shows "
"two charges of $54.99 on April 1st. Please fix this."
),
"category": IssueCategory.BILLING_ERROR,
"sentiment": Sentiment.ANGRY,
"order_value": 54.99,
"priority": "high",
},
{
"ticket_id": "EASY-005",
"customer_name": "Emma Wilson",
"issue_text": (
"Hi! I just wanted to know if the wireless headphones model WH-300 "
"are compatible with my MacBook Pro M2. Thanks!"
),
"category": IssueCategory.PRODUCT_INQUIRY,
"sentiment": Sentiment.HAPPY,
"order_value": 0.0,
"priority": "low",
},
]
_MEDIUM_TICKETS: List[Dict[str, Any]] = [
{
"ticket_id": "MED-001",
"customer_name": "Frank Torres",
"issue_text": (
"THIS IS UNACCEPTABLE! I ordered a birthday gift for my daughter and "
"it never arrived. The party was yesterday and she had nothing to "
"open. I want a full refund AND compensation for this disaster. "
"Order #5567, $199.99."
),
"category": IssueCategory.DELIVERY_DELAY,
"sentiment": Sentiment.ANGRY,
"order_value": 199.99,
"priority": "critical",
},
{
"ticket_id": "MED-002",
"customer_name": "Grace Lee",
"issue_text": (
"The ceramic vase I ordered arrived shattered. There was barely any "
"bubble wrap in the box. This was supposed to be an anniversary gift. "
"I'm very disappointed. Order #8834, paid $75."
),
"category": IssueCategory.DAMAGED_PRODUCT,
"sentiment": Sentiment.ANGRY,
"order_value": 75.00,
"priority": "high",
},
{
"ticket_id": "MED-003",
"customer_name": "Henry Chen",
"issue_text": (
"I returned three items from order #2291 but only received a partial "
"refund of $30. The total should be $95.50. Can you look into this? "
"I have the return receipts."
),
"category": IssueCategory.REFUND_REQUEST,
"sentiment": Sentiment.NEUTRAL,
"order_value": 95.50,
"priority": "normal",
},
]
_HARD_TICKETS: List[Dict[str, Any]] = [
{
"ticket_id": "HARD-001",
"customer_name": "Isabella Rodriguez",
"issue_text": (
"I'm FURIOUS! My $450 espresso machine arrived with a huge dent on "
"the side, the water tank is cracked, AND I was charged twice! "
"Order #1199. I've been a loyal customer for 5 years and this is "
"how you treat me? I want a FULL refund, a replacement, AND someone "
"from management to call me. If this isn't resolved today I'm "
"disputing with my bank and posting on every review site."
),
"category": IssueCategory.DAMAGED_PRODUCT,
"sentiment": Sentiment.ANGRY,
"order_value": 450.00,
"priority": "critical",
},
{
"ticket_id": "HARD-002",
"customer_name": "James O'Brien",
"issue_text": (
"Multiple issues: (1) Order #4421 β€” gaming keyboard β€” keys are "
"sticky out of the box. (2) Order #4422 β€” mouse pad β€” wrong item "
"sent, I got a phone case instead. (3) My account shows a pending "
"charge of $200 that I didn't authorize. I need ALL of these fixed. "
"Total value across orders is $310."
),
"category": IssueCategory.BILLING_ERROR,
"sentiment": Sentiment.ANGRY,
"order_value": 310.00,
"priority": "critical",
},
{
"ticket_id": "HARD-003",
"customer_name": "Karen Nakamura",
"issue_text": (
"I placed a bulk order of 50 custom T-shirts for a company event "
"(order #6600, $1,250). Half of them have the wrong logo printed "
"and the rest are the wrong sizes. The event is in 3 days. I need "
"either a rush reprint or a full refund plus compensation for the "
"expedited order I'll have to place elsewhere. This has put my job "
"at risk."
),
"category": IssueCategory.DAMAGED_PRODUCT,
"sentiment": Sentiment.ANGRY,
"order_value": 1250.00,
"priority": "critical",
},
]
# ── Task definitions ──────────────────────────────────────────────────
TASKS = {
"easy_classification": {
"id": "easy_classification",
"name": "Ticket Classification",
"difficulty": "easy",
"description": (
"Classify the customer support ticket into the correct issue "
"category. The agent must submit a single 'classify' action with "
"the right category label."
),
"objective": (
"Read the ticket and classify it into one of: delivery_delay, "
"damaged_product, refund_request, billing_error, product_inquiry, "
"account_issue."
),
"required_actions": ["classify"],
"max_steps": 3,
"tickets": _EASY_TICKETS,
},
"medium_response": {
"id": "medium_response",
"name": "Response Generation",
"difficulty": "medium",
"description": (
"Classify the ticket AND generate an empathetic, helpful response "
"to the customer. Graded on classification accuracy and response "
"quality (empathy, specificity, professionalism)."
),
"objective": (
"1. Classify the ticket correctly. "
"2. Generate a professional, empathetic response that acknowledges "
"the customer's issue and outlines next steps."
),
"required_actions": ["classify", "respond"],
"max_steps": 5,
"tickets": _MEDIUM_TICKETS,
},
"hard_resolution": {
"id": "hard_resolution",
"name": "Full Resolution Workflow",
"difficulty": "hard",
"description": (
"Execute the full customer support workflow: classify the ticket, "
"generate a response, and take the appropriate resolution action "
"(refund, escalate, or resolve). Graded on all dimensions."
),
"objective": (
"1. Classify the ticket. "
"2. Generate a professional response. "
"3. Take the correct resolution action (refund / escalate / resolve) "
"based on the ticket's severity, order value, and customer sentiment."
),
"required_actions": ["classify", "respond", "refund|escalate|resolve"],
"max_steps": 10,
"tickets": _HARD_TICKETS,
},
}
def get_task(task_id: str) -> dict:
"""Return task definition by ID."""
if task_id not in TASKS:
raise ValueError(
f"Unknown task '{task_id}'. Available: {list(TASKS.keys())}"
)
return TASKS[task_id]
def get_ticket_for_task(task_id: str, ticket_index: int = 0) -> Ticket:
"""Return a Ticket model for the given task and index."""
task = get_task(task_id)
tickets = task["tickets"]
idx = ticket_index % len(tickets)
return Ticket(**tickets[idx])