--- 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": ""} 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": ""} 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