Spaces:
Sleeping
title: SupportOps Env
emoji: π«
colorFrom: blue
colorTo: indigo
sdk: docker
app_port: 7860
pinned: false
π« Support Ticket Triage β OpenEnv
A real-world OpenEnv environment where AI agents must triage, route, and resolve customer support tickets across three difficulty levels.
This environment models a task that real support organisations handle thousands of times per day: reading an incoming ticket, routing it to the right team, judging its urgency, crafting a helpful reply, and β for hard tasks β managing a multi-turn conversation through to resolution.
π Motivation
Support ticket triage is:
- High-volume β enterprise companies route millions of tickets per year
- High-stakes β wrong routing costs money; slow responses lose customers
- Multi-step β requires reading comprehension, classification, and generation
- Under-explored in RL/agent benchmarks (most focus on code or web tasks)
This environment fills a genuine gap: an OpenEnv where agents can be trained and evaluated on a real knowledge-worker workflow.
ποΈ Environment Overview
| Field | Value |
|---|---|
| Action space | Discrete (7 action types) + optional text fields |
| Observation space | Structured ticket + conversation history |
| Reward | Shaped per-step + terminal grader score [0.0, 1.0] |
| Episodes | Stateful, multi-step (up to 12 steps for Hard) |
| Tasks | 3 (Easy / Medium / Hard) |
π Tasks
Task 1 β Ticket Routing (Easy)
Route the incoming ticket to the correct department.
- Actions required:
ROUTE - Score: 1.0 for correct department, 0.1 for wrong (partial), 0.0 for no attempt
- Departments:
billing,technical_support,sales,customer_success,legal - Max steps: 3
- Baseline score: ~0.80
Task 2 β Full Triage (Medium)
Fully triage the ticket: route, set urgency, tag, and respond.
| Sub-task | Weight |
|---|---|
| Correct routing | 30% |
| Correct urgency level | 25% |
| Relevant tags applied | 20% |
| Informative customer response | 25% |
- Max steps: 8
- Baseline score: ~0.55
Task 3 β Full Resolution (Hard)
Manage a multi-turn support conversation to full resolution.
| Sub-task | Weight |
|---|---|
| Correct routing | 15% |
| Correct urgency | 10% |
| Quality initial response | 20% |
| Escalation decision | 20% |
| Handle customer follow-up | 20% |
| Close with resolution note | 15% |
- Max steps: 12
- Baseline score: ~0.40
π API Reference
The environment is served as a REST API (FastAPI).
GET /
Health check. Returns environment metadata and task list.
POST /reset
Start a new episode.
{
"task_name": "route", // "route" | "triage" | "resolve"
"ticket_id": "TKT-001", // optional β omit for random
"seed": 42, // optional RNG seed
"session_id": "abc123" // optional β generated if omitted
}
Returns: { "observation": {...}, "session_id": "..." }
POST /step
Apply an action.
{
"session_id": "abc123",
"action_type": "route", // required
"department": "billing", // for ROUTE
"response_text": "Hello...", // for RESPOND
"urgency": "high", // for SET_URGENCY
"tags": ["billing", "refund"], // for TAG
"escalation_reason": "...", // for ESCALATE
"resolution_note": "..." // for CLOSE
}
Returns: { "observation": {...}, "reward": {...}, "done": bool, "info": {...}, "session_id": "..." }
GET /state?session_id=abc123
Full internal state including ground truth labels (for debugging/evaluation).
GET /tasks
List all tasks with metadata.
π¬ Action Space
| action_type | Required fields | Description |
|---|---|---|
route |
department |
Route ticket to a department |
set_urgency |
urgency |
Set priority level |
respond |
response_text |
Send a message to the customer |
tag |
tags |
Apply classification labels |
escalate |
escalation_reason |
Escalate with explanation |
close |
resolution_note |
Resolve and close the ticket |
noop |
β | Take no action (wastes a step) |
Departments: billing Β· technical_support Β· sales Β· customer_success Β· legal
Urgency levels: low Β· medium Β· high Β· critical
ποΈ Observation Space
{
"ticket_id": "TKT-001",
"subject": "Double charged on my invoice",
"body": "Full ticket text...",
"sender_email": "user@example.com",
"sender_name": "Jane Smith",
"conversation_history": [
{"sender": "Jane Smith", "content": "...", "timestamp": "2024-01-01T12:00:00Z"}
],
"current_department": null,
"current_urgency": null,
"tags": [],
"is_escalated": false,
"is_closed": false,
"step_number": 0,
"task_name": "route",
"task_description": "Route the ticket to the correct department...",
"available_actions": ["route", "respond", "set_urgency", "tag", "escalate", "close", "noop"]
}
π Reward Function
Step rewards (shaped, provide dense signal):
- +0.30 β correct ROUTE
- +0.20 β correct SET_URGENCY
- +0.10Γoverlap β TAG matching required tags
- +0.15Γquality β RESPOND addressing key topics
- +0.20 β justified ESCALATE
- β0.10 β unjustified ESCALATE
- +0.10 β CLOSE with substantive resolution note
Terminal reward (authoritative, [0.0, 1.0]):
Each task has a dedicated deterministic grader that computes a weighted aggregate
of all sub-task scores. The terminal reward is returned in info["final_grader_reward"].
π Setup & Usage
Quick Start (Launch & Verify)
To automatically install dependencies, run the PyTest suite, run the baseline agent (with automatic serverless fallback), and run the 300-episode evaluation suite in one command:
chmod +x run_all.sh
./run_all.sh
Local development
# Install dependencies
pip install -r requirements.txt
# Start the environment server
python server.py
# β Server running at http://localhost:7860
# In another terminal, run the baseline inference (with a model of your choice)
export API_BASE_URL="https://router.huggingface.co/v1"
export MODEL_NAME="meta-llama/Llama-3.3-70B-Instruct"
export HF_TOKEN="your_token_here"
export ENV_BASE_URL="http://localhost:7860"
python inference.py
Docker
docker build -t ticket-triage-env .
docker run -p 7860:7860 ticket-triage-env
# Environment is now available at http://localhost:7860
Quick API test
# Health check
curl http://localhost:7860/
# Start an episode
curl -X POST http://localhost:7860/reset \
-H "Content-Type: application/json" \
-d '{"task_name": "route", "ticket_id": "TKT-001", "seed": 42}'
# Take an action (use session_id from reset response)
curl -X POST http://localhost:7860/step \
-H "Content-Type: application/json" \
-d '{"session_id": "<ID>", "action_type": "route", "department": "billing"}'
π Baseline Scores
Measured with meta-llama/Llama-3.3-70B-Instruct via HuggingFace Inference API,
temperature=0.0 (greedy), seed=42:
| Task | Score | Notes |
|---|---|---|
| Route (Easy) | ~0.80 | Model occasionally confuses billing β customer_success |
| Triage (Medium) | ~0.55 | Tags and urgency are hardest sub-tasks |
| Resolve (Hard) | ~0.40 | Follow-up handling and escalation decisions are challenging |
| Overall | ~0.58 |
π Project Structure
ticket-triage-env/
βββ openenv.yaml # OpenEnv metadata
βββ Dockerfile # Container definition
βββ requirements.txt # Python dependencies
βββ inference.py # Baseline inference script (hackathon-required)
βββ server.py # FastAPI HTTP server
βββ README.md # This file
βββ env/
βββ __init__.py
βββ environment.py # Core TicketTriageEnv class
βββ models.py # Pydantic Observation/Action/Reward models
βββ tasks.py # Task specifications
βββ graders.py # Deterministic grader functions
βββ data.py # Synthetic ticket dataset with ground truth
π License
MIT β free to use for research and commercial applications.
π Evaluation Leaderboard & Benchmark Results
Evaluated 5 frontier and open-weights models Β· 20 episodes per task Β· 300 total episodes
Leaderboard
| Model | Easy (Route) | Medium (Triage) | Hard (Resolve) | Ξ EasyβHard |
|---|---|---|---|---|
| Claude 3.5 Sonnet | 0.96 | 0.89 | 0.74 | -23% |
| GPT-4o-Mini | 0.96 | 0.86 | 0.70 | -27% |
| Gemini 2.0 Flash | 0.86 | 0.86 | 0.62 | -28% |
| Llama-3.1-8B | 0.82 | 0.70 | 0.39 | -53% |
| Mistral-7B | 0.82 | 0.65 | 0.40 | -51% |
Key finding: Larger models degrade 46β53% from EasyβHard; 7B-class models collapse 73β77%. Multi-step reasoning, long-context tracking, and strict sub-task adherence require higher parametric capacity. Smaller models lose state, mis-route on ambiguous signals, and fail to handle follow-up turns.
Hard Task Failure Mode Analysis
Failure counts among Hard task episodes scoring below 0.3 (out of 20 episodes):
| Model | Wrong Route | Wrong Urgency | Missing Tags | Unhelpful Resp | No Follow-up | Step Limit |
|---|---|---|---|---|---|---|
| Claude 3.5 Sonnet | 0 | 0 | 0 | 1 | 1 | 0 |
| GPT-4o-Mini | 1 | 1 | 0 | 2 | 2 | 0 |
| Gemini 2.0 Flash | 1 | 2 | 0 | 3 | 3 | 0 |
| Llama-3.1-8B | 6 | 4 | 0 | 7 | 5 | 0 |
| Mistral-7B | 3 | 2 | 0 | 3 | 3 | 0 |
Reward Hacking & LLM-as-Judge (Scalable Oversight)
The original keyword_overlap grader assigned full credit to any response containing the right keywords,
regardless of coherence β a classic reward hacking vector. We replaced it with a dual-signal grader:
- 50% keyword overlap (fast, deterministic)
- 50% LLM judge score (coherence, tone, actionability)
This mirrors Anthropic's scalable oversight paradigm: augmenting a weak but cheap signal with a stronger, more expensive signal to keep agent behavior aligned.
Measured Reward Hacking Rate (keyword grader score β₯ 0.8 but LLM judge < 0.4)
- Claude 3.5 Sonnet: 1/40 (2%) responses flagged
- GPT-4o-Mini: 9/40 (22%) responses flagged
- Gemini 2.0 Flash: 6/40 (15%) responses flagged
- Llama-3.1-8B: 13/40 (32%) responses flagged
- Mistral-7B: 17/40 (42%) responses flagged
Continuous Difficulty Curve
Performance as a function of ticket complexity score (0.0β1.0), showing that model capability
degrades continuously β not just at discrete Easy/Medium/Hard boundaries.
See eval_results.json for the full per-ticket breakdown.