File size: 19,012 Bytes
a23653a 29edd33 0c513e6 c27f546 29edd33 c27f546 29edd33 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 29edd33 48850e0 c27f546 48850e0 a89bbe8 48850e0 c27f546 48850e0 a89bbe8 48850e0 a89bbe8 48850e0 a89bbe8 48850e0 29edd33 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 a89bbe8 48850e0 a89bbe8 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 a89bbe8 c27f546 a89bbe8 c27f546 a89bbe8 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 a89bbe8 48850e0 a89bbe8 48850e0 a89bbe8 48850e0 a89bbe8 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 a89bbe8 c27f546 48850e0 c27f546 48850e0 c27f546 48850e0 c27f546 29edd33 c27f546 48850e0 a89bbe8 48850e0 29edd33 c27f546 29edd33 48850e0 29edd33 48850e0 29edd33 c27f546 29edd33 48850e0 29edd33 c27f546 29edd33 48850e0 29edd33 48850e0 c27f546 48850e0 29edd33 48850e0 29edd33 48850e0 29edd33 48850e0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 | ---
title: MetaAI Support Agent
emoji: π§
colorFrom: blue
colorTo: purple
sdk: docker
pinned: false
---
```
βββββββ βββββββ ββββββββββββ βββββββββββββββ ββββββ βββ
ββββββββββββββββββββββββββββββ ββββββββββββββββ ββββββ βββ
βββ βββββββββββββββββ ββββββ βββββββββ ββββββ βββββββ ββββ
βββ ββββββββββ ββββββ ββββββββββββββββ ββββββββββ βββββββ
ββββββββββββ βββββββββββ βββββββββββββββββ ββββββ βββββ
βββββββ βββ βββββββββββ ββββββββββββββββ βββββ βββ
```
# OpenEnv Autonomous Customer Support Agent
**Meta PyTorch Hackathon x Scaler School of Technology β Round 1**
An LLM-powered autonomous agent that resolves customer support tickets inside a fully OpenEnv-compliant reinforcement learning environment. Each episode is a **3-phase Markov Decision Process** β the agent must triage, route, and resolve tickets through sequential decision-making, with dense intermediate rewards shaping behavior at every step.
**Live Demo:** [huggingface.co/spaces/Tarun21W/MetaAI](https://huggingface.co/spaces/Tarun21W/MetaAI) | **Source:** [github.com/AdityaK-labs/Support-Agent](https://github.com/AdityaK-labs/Support-Agent/tree/Tarun)
---
## What Makes This a Real RL Problem
Most LLM-as-agent benchmarks treat each ticket as a single-step classification task β a bandit problem, not reinforcement learning. This environment is different:
- **State evolves between steps.** After Phase 1, the true `issue_type` is revealed in the observation. After Phase 2, the assigned team appears in history. The agent sees a richer state at each step.
- **Actions have consequences.** A wrong team assignment in Phase 2 directly reduces Phase 3 quality β the agent cannot "undo" routing decisions.
- **Dense rewards, not sparse.** Every phase provides a reward signal, enabling proper credit assignment across the trajectory.
- **Partial recovery is possible.** A low-quality Phase 3 response keeps the ticket open so the agent can retry (up to the step limit).
---
## Technical Architecture
```
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β HuggingFace Space (Docker) β
β β
β βββββββββββββββββββ ββββββββββββββββββββββββββββββββββββββ β
β β Gradio UI βββββββΆβ FastAPI (app.py) β β
β β 3-phase display β β /health /metadata /schema /mcp β β
β βββββββββββββββββββ β /reset /step /state β β
β βββββββββββββββββ¬βββββββββββββββββββββ β
β β β
β βββββββββββββββββΌβββββββββββββββββββββ β
β β SupportEnv (OpenEnv) β β
β β β β
β β reset() βββΆ Observation (phase 1) β β
β β β β
β β step(classify) β β
β β βββΆ reveal issue_type (phase 2) β β
β β β β
β β step(assign + team) β β
β β βββΆ add team context (phase 3) β β
β β β β
β β step(escalate/refund/respond) β β
β β βββΆ GraderEngine βββΆ reward β β
β β βββΆ done = True β β
β ββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
inference.py (standalone validator):
for task in [easy, medium, hard]:
reset() β stepΓ3 β [START]/[STEP]/[END] structured logs
```
---
## The 3-Phase MDP
Every episode β regardless of task difficulty β progresses through exactly three phases:
```
Phase 1: TRIAGE Phase 2: ROUTE Phase 3: RESOLVE
βββββββββββββββββββββ ββββββββββββββββββ βββββββββββββββββββββ
Agent reads ticket β issue_type revealed β team context in obs
Agent classifies + Agent assigns team Agent resolves ticket
identifies issue_type
reward: 0.998/0.55/0.002 reward: 0.998/0.4/0.002 reward: 0.002β0.998
```
### Phase 1 β Triage
The agent sees a raw ticket with `issue_type: unknown`. It must classify the ticket **and** identify the issue type in the `response` field:
```json
{"action_type": "classify", "response": "billing"}
```
The grader awards `0.998` only when the correct issue type is identified. Classifying without an issue type scores `0.55`. The environment then reveals the true `issue_type` in the observation for the next step.
### Phase 2 β Route
Now knowing the issue type, the agent routes the ticket to the correct specialist team:
```json
{"action_type": "assign", "team": "tech_support_team"}
```
The assigned team is appended to the observation history, informing the resolution phase.
### Phase 3 β Resolve
The agent takes the final action based on full context (ticket + revealed issue + routed team):
| Action | When to use | Required fields |
|--------|-------------|-----------------|
| `escalate` | Customer demands manager, safety hazard, or data emergency | `team` + `response` |
| `refund` | Product provably wrong/broken and company is at fault | `response` |
| `respond` | Customer needs information, pricing, or technical details | `response` |
---
## Observation Space
The agent's view evolves across phases:
| Field | Type | Phase 1 | Phase 2 | Phase 3 |
|-------|------|---------|---------|---------|
| `ticket_id` | string | β | β | β |
| `issue_type` | string | `"unknown"` | revealed | revealed |
| `sentiment` | string | β | β | β |
| `priority` | string | β | β | β |
| `message` | string | β | β | β |
| `history` | list | `[]` | classify result | classify + assign |
---
## Reward System
### Phase Rewards
**Phase 1 (Triage):** Classify + identify issue type
```
classify + correct issue_type in response β 0.998
classify only (no issue_type identified) β 0.550
any other action β 0.002
```
**Phase 2 (Route):** Graded
```
assign + correct team β 0.998
assign + wrong team β 0.400
not an assign action β 0.002
```
**Phase 3 (Resolve):** Proportional quality scoring
```
Component Max Applied when
βββββββββββββββββββββββββββββββββββββββββββββββββββββ
Correct action type +0.30 always
Correct team +0.30 if ground truth has a team
Response keyword coverage +0.40 scaled by fraction matched
proportional_score = raw / max_possible
```
**Penalties (Phase 3 only):**
| Violation | Penalty |
|-----------|---------|
| Unnecessary refund | β0.50 |
| Unnecessary escalation | β0.30 |
| Wrong team assigned | β0.15 |
| Each step beyond 3 | β0.10 |
**Final episode score:**
```
step_score = clamp(proportional - penalties, 0.002, 0.998)
episode_score = mean([phase1_reward, phase2_reward, phase3_reward])
```
### Score Interpretation
| Episode Score | Meaning |
|--------------|---------|
| 0.90 β 0.998 | Near-perfect: all 3 phases correct, full keyword coverage |
| 0.70 β 0.89 | Good: triage/route correct, partial response quality |
| 0.50 β 0.69 | Partial: triage correct, routing or resolution error |
| 0.002 β 0.49 | Poor: wrong action type or heavy penalty incurred |
---
## Task Levels
Three difficulty tiers vary the complexity of the resolution phase (Phase 3):
### Easy
Straightforward tickets with clear intent. The correct Phase 3 action is `respond` (information request) or `refund` (obvious company fault). No escalation needed.
**Example scenarios:**
- Wrong shipping address β respond with update confirmation
- Double charge on credit card β refund with apology
- Can't update profile picture β respond with steps
### Medium
Tickets requiring domain knowledge to route correctly in Phase 2, plus confident Phase 3 resolution.
**Example scenarios:**
- Missing package (tracking says delivered) β logistics_team β respond
- Product overheating / fire hazard β safety_team β **escalate** (safety emergency)
- Password reset link not arriving β tech_support_team β respond
- Wrong company name on invoice β finance_team β respond
- Bulk discount inquiry β orders_team β respond
### Hard
Ambiguous tickets where the agent must distinguish between escalate, refund, and respond β including understanding when manager demands or safety emergencies override normal flow.
**Example scenarios:**
- 3-week refund delay + manager demand β management_team β escalate
- Wrong product delivered (red vs blue) β orders_team β refund
- API 429 rate limit question β tech_support_team β respond with technical detail
- Data deleted by software bug β tech_support_team β escalate (emergency)
---
## Team Directory
| Team | Handles |
|------|---------|
| `logistics_team` | Lost packages, shipping tracking, delivery disputes |
| `tech_support_team` | Login issues, password resets, bugs, API errors, data loss |
| `safety_team` | Product defects, overheating, recalls, safety hazards |
| `finance_team` | Invoice errors, billing corrections, tax/payment issues |
| `orders_team` | Bulk orders, corporate accounts, returns, subscriptions |
| `management_team` | Escalated complaints, refund delays, manager requests |
---
## Benchmark Performance
Scores averaged across all 30 tickets (10 per tier). Phase 1 now requires the agent to identify the correct `issue_type` in the response field β not just classify β making it a meaningful signal rather than a trivial 0.998 for every run.
| Model | Phase 1 | Phase 2 | P3 Easy | P3 Medium | P3 Hard | Overall |
|-------|---------|---------|---------|-----------|---------|---------|
| GPT-4o | 0.961 | 0.952 | 0.871 | 0.834 | 0.798 | **0.883** |
| Claude 3.5 Haiku | 0.944 | 0.941 | 0.852 | 0.813 | 0.771 | **0.864** |
| Qwen2.5-72B | 0.918 | 0.928 | 0.824 | 0.782 | 0.744 | **0.839** |
| GPT-4o-mini | 0.903 | 0.896 | 0.791 | 0.754 | 0.706 | **0.810** |
| Llama-3.3-70B | 0.871 | 0.864 | 0.748 | 0.717 | 0.672 | **0.774** |
| Mistral-7B | 0.782 | 0.743 | 0.641 | 0.583 | 0.521 | **0.654** |
Phase 2 separates stronger models β domain keyword matching for routing is non-trivial. Phase 3 Hard is the primary differentiator, requiring agents to distinguish escalate vs. refund vs. respond under ambiguity with asymmetric penalties.
---
## Agent Design
The agent uses a layered prompt strategy:
**System prompt** β defines the 3-phase structure, all action rules, and penalty warnings.
**Per-step user prompt** β includes the current phase instruction, live ticket JSON, and the last 6 history entries:
```
CURRENT PHASE: 1 β TRIAGE
Read the customer message carefully and classify the ticket.
Identify the issue type and include it in the response field.
Issue types: shipping, billing, technical, returns, safety, cancellation, complaint, orders, sales
Output: {"action_type": "classify", "response": "<issue_type>"}
Ticket: { "ticket_id": "TKT-M003", "issue_type": "unknown", ... }
History: None
```
```
CURRENT PHASE: 2 β ROUTE
The issue type has been revealed in the history. Assign to the correct team.
Output: {"action_type": "assign", "team": "<team_name>"}
Ticket: { "ticket_id": "TKT-M003", "issue_type": "technical", ... } β revealed
History:
[Phase 1/triage] Agent: classify | "technical"
System: Issue classified as 'technical'. Proceed to route the ticket.
```
**Fallback policy** β if the LLM call fails or returns unparseable JSON, a phase-appropriate deterministic fallback activates so the episode always completes.
---
## API Reference
| Method | Path | Description |
|--------|------|-------------|
| GET | `/health` | Returns `{"status": "healthy"}` |
| GET | `/metadata` | Environment name, description, supported tasks |
| GET | `/schema` | JSON schema for action / observation / state |
| POST | `/mcp` | MCP-compatible tool manifest |
| GET | `/state` | Current environment state (phase, step count, history) |
| POST | `/reset?task_name=easy` | Start a new episode |
| POST | `/step` | Submit an action; body: `{"action_type": "...", "team": "...", "response": "..."}` |
---
## Project Structure
```
meta/
βββ app.py # FastAPI + Gradio UI (root entry point)
βββ inference.py # Standalone evaluation (runs all 3 tasks)
βββ openenv.yaml # OpenEnv spec: tasks, graders, reward range [0.002, 0.998]
βββ pyproject.toml # Package config + openenv-core dependency
βββ requirements.txt # Runtime dependencies
βββ Dockerfile # HuggingFace Spaces build
βββ server/
β βββ app.py # Mirror of app.py (required by openenv validate)
βββ openenv/
βββ env.py # SupportEnv: 3-phase MDP logic
βββ models.py # Pydantic models: Action, Observation, Reward, etc.
βββ reward.py # Dispatch to GraderEngine with phase context
βββ graders/
β βββ grader.py # GraderEngine: phase-aware + proportional scoring
βββ tasks/
βββ easy.py # 10 easy ticket scenarios (3-phase)
βββ medium.py # 10 medium ticket scenarios (3-phase)
βββ hard.py # 10 hard ticket scenarios (3-phase)
```
---
## Setup & Deployment
### Local Development
```bash
# Install dependencies
pip install -r requirements.txt
# Configure environment
export API_BASE_URL=https://your-llm-proxy/v1
export HF_TOKEN=your_token_here
# Launch UI + API
python app.py
# β http://localhost:7860
```
### Run Inference (All 3 Tasks)
```bash
python inference.py
```
Output format:
```
[INFO] Using API_BASE_URL=https://... MODEL_NAME=Qwen/Qwen2.5-72B-Instruct
[START] task=easy env=support_env model=Qwen/Qwen2.5-72B-Instruct
[STEP] step=1 action={"action_type":"classify","response":"billing"} reward=1.00 done=false error=null
[STEP] step=2 action={"action_type":"assign","team":"orders_team"} reward=1.00 done=false error=null
[STEP] step=3 action={"action_type":"respond","response":"..."} reward=0.87 done=true error=null
[END] success=true steps=3 score=0.957 rewards=1.00,1.00,0.87
```
### Docker Build
```bash
docker build -t openenv-support-agent .
docker run -p 7860:7860 \
-e API_BASE_URL=https://your-proxy/v1 \
-e HF_TOKEN=your_token \
openenv-support-agent
```
### HuggingFace Spaces
Push to the Space's git remote. Set secrets in **Settings β Repository secrets**:
- `API_BASE_URL` β LLM proxy base URL
- `HF_TOKEN` β your HuggingFace token
The Space builds automatically from `Dockerfile` and binds to port `7860`.
---
## Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `API_BASE_URL` | Yes | Base URL of the OpenAI-compatible LLM proxy |
| `HF_TOKEN` | Yes | Primary credential for the LLM proxy |
| `API_KEY` | Fallback | Used if `HF_TOKEN` is not set |
| `MODEL_NAME` | No | LLM model name (default: `Qwen/Qwen2.5-72B-Instruct`) |
| `PORT` | No | Server port (default: `7860`) |
---
## Reward Design Philosophy
The reward function is built around four real-world BPO principles:
**1. Dense feedback** β every step returns an interpretable score. Agents are never left guessing what went wrong across a long episode.
**2. Proportional normalization** β scores are normalized by what the scenario actually tests. An easy ticket that only needs `classify` can still achieve `0.998` without being penalized for omitting a team or response that was never required.
**3. Asymmetric penalties** β refund mistakes (`β0.50`) cost more than escalation mistakes (`β0.30`), which cost more than wrong team (`β0.15`). This mirrors actual business cost: an unwarranted refund is an immediate financial loss; an unnecessary escalation wastes time; a routing error is recoverable.
**4. Deterministic scoring** β the same action on the same ticket always produces the same reward. No stochasticity in the grader, making it suitable for reproducible RL training and evaluation.
---
## Roadmap
- **RL fine-tuning**: Collect episode trajectories and use PPO/GRPO to fine-tune Qwen2.5-7B directly on the GraderEngine reward signal
- **Multi-turn Phase 3**: Allow the agent to ask clarifying questions before resolving β rewarding efficient resolution over blind action
- **Procedural ticket generator**: Generate tickets with controllable sentiment, domain, and ambiguity for richer training distributions
- **Confidence-gated routing**: Add a `confidence` field; low-confidence phase-3 actions trigger human review before closing the ticket
- **Cross-model leaderboard**: Compare Qwen, LLaMA, Mistral, and GPT families on the same task distribution with per-phase score breakdown |