SupportOps-Env / README.md
Gaurav711's picture
Fix Dockerfile template syntax and add Hugging Face Spaces metadata frontmatter
ae91a5d
|
Raw
History Blame Contribute Delete
10.9 kB
---
title: SupportOps Env
emoji: 🎫
colorFrom: blue
colorTo: indigo
sdk: docker
app_port: 7860
pinned: false
---
# 🎫 Support Ticket Triage β€” OpenEnv
A real-world [OpenEnv](https://huggingface.co/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.
```json
{
"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.
```json
{
"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
```json
{
"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:
```bash
chmod +x run_all.sh
./run_all.sh
```
### Local development
```bash
# 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
```bash
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
```bash
# 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.