Spaces:
Sleeping
Download README.md from kunalkachru23/support_triage_env: direct link, hf CLI and curl.
- Browser
- Download file 12.6 kB
-
https://huggingface.co/spaces/kunalkachru23/support_triage_env/resolve/main/README.md
- Command line
-
hf download hf://spaces/kunalkachru23/support_triage_env/README.md
-
curl -L -o README.md https://huggingface.co/spaces/kunalkachru23/support_triage_env/resolve/main/README.md
title: Support Triage Env Environment Server
emoji: π
colorFrom: green
colorTo: pink
sdk: docker
pinned: false
app_port: 8000
base_path: /web
tags:
- openenv
Support Triage Env Environment
An OpenEnv execution environment that simulates a realistic customer-support workflow: the agent triages an incoming email, optionally requests missing information, and drafts a policy-compliant reply.
This is built for agent evaluation (not a toy/game): it models concrete operator actions (intent classification, clarification questions, escalation decisions) and provides shaped rewards and deterministic graders across 3 tasks (easy β hard).
Quick Start
Use the environment via the standard OpenEnv reset() / step() API. Each task episode advances through stages using task_stage:
from support_triage_env import SupportTriageAction, SupportTriageEnv
with SupportTriageEnv(base_url="http://localhost:8000") as env:
# Pick a task; Task 1 is the easiest.
result = env.reset(seed=1, task_id="task_1")
print("stage:", result.observation.task_stage)
# Stage 1: classify intent
result = env.step(
SupportTriageAction(
task_id="task_1",
task_stage="classify",
intent_category="billing_refund",
finalize=False,
)
)
# Stage 2: finalize a policy-compliant reply
result = env.step(
SupportTriageAction(
task_id="task_1",
task_stage="finalize",
reply_template_id="tpl_refund_ack_next",
draft_reply="Thanks for reaching out. We received your request. Next step: please confirm details.",
finalize=True,
)
)
print("done:", result.done, "reward:", result.reward)
That's it! The SupportTriageEnv.from_docker_image() method handles:
- Starting the Docker container
- Waiting for the server to be ready
- Connecting to the environment
- Container cleanup when you call
close()
Building the Docker Image
Before using the environment, you need to build the Docker image:
# From project root
docker build -t support_triage_env-env:latest -f server/Dockerfile .
Deploying to Hugging Face Spaces
You can easily deploy your OpenEnv environment to Hugging Face Spaces using the openenv push command:
# From the environment directory (where openenv.yaml is located)
openenv push
# Or specify options
openenv push --namespace my-org --private
The openenv push command will:
- Validate that the directory is an OpenEnv environment (checks for
openenv.yaml) - Prepare a custom build for Hugging Face Docker space (enables web interface)
- Upload to Hugging Face (ensuring you're logged in)
Prerequisites
- Authenticate with Hugging Face: The command will prompt for login if not already authenticated
Options
--directory,-d: Directory containing the OpenEnv environment (defaults to current directory)--repo-id,-r: Repository ID in format 'username/repo-name' (defaults to 'username/env-name' from openenv.yaml)--base-image,-b: Base Docker image to use (overrides Dockerfile FROM)--private: Deploy the space as private (default: public)
Examples
# Push to your personal namespace (defaults to username/env-name from openenv.yaml)
openenv push
# Push to a specific repository
openenv push --repo-id my-org/my-env
# Push with a custom base image
openenv push --base-image ghcr.io/meta-pytorch/openenv-base:latest
# Push as a private space
openenv push --private
# Combine options
openenv push --repo-id my-org/my-env --base-image custom-base:latest --private
After deployment, your space will be available at:
https://huggingface.co/spaces/<repo-id>
The deployed space includes:
- Web Interface at
/web- Interactive UI for exploring the environment - API Documentation at
/docs- Full OpenAPI/Swagger interface - Health Check at
/health- Container health monitoring - WebSocket at
/ws- Persistent session endpoint for low-latency interactions
Environment Details
Action Space (SupportTriageAction / SupportAction)
The environment uses a typed, stage-based action schema:
task_id(str): one oftask_1,task_2,task_3task_stage(str):classify,request_info, orfinalizeintent_category(str|null): predicted intent category (used duringclassify)reply_template_id(str|null): selected policy template (used duringfinalize)draft_reply(str): drafted reply text (scored with deterministic keyword/rubric checks)missing_info_questions(list[str]): questions asked for missing details (Task 2request_info)escalate_to_human(bool): escalation decision (Task 3finalize)finalize(bool): must betrueto end the episode onfinalize
Observation Space (SupportTriageObservation)
task_id(str)task_stage(str): the next expected stageemail_subject(str)email_body(str)thread_history(list[str]): includes customer-provided details for multi-turn taskscurrent_requirements(list[str]): which deterministic requirements are still unsatisfiedepisode_seed(int|null): seed used to generate the simulated email/threaddone(bool),reward(float), andmetadata(dict) are standard OpenEnv fields
Reward Shaping (partial signals)
Rewards are shaped at each stage and delivered in the reward field of step() observations:
classifygives partial reward for correct intent classification.request_info(Task 2) rewards asking for the correct missing fields.finalizereturns the final shaped reward (0.0β1.0) based on a deterministic rubric:- correctness of template selection
- presence of required reply sections (acknowledgement + next step keywords)
- correctness of Task 2 value references / Task 3 escalation + safety compliance
Episodes have a max step budget per task; exceeding it ends the episode with a penalty.
Tasks and Grading
Grading is deterministic and returns a score in [0.0, 1.0]:
task_1(easy): intent routing + one-shot policy reply- checks: intent category, reply template id, and whether reply contains acknowledgement + next-step keywords
task_2(medium): missing information collection over 2 turns- checks: intent category, correct missing-info questions, correct reply template id
- also checks that the final reply references the simulated
order_idandaccount emailprovided inthread_history
task_3(hard): high-risk case + escalation decision- uses a deterministic high-risk flag derived from
episode_seed - checks: intent category, correct template id, correct
escalate_to_humandecision - also checks reply safety/compliance keywords (escalation language for high-risk vs self-serve security steps otherwise)
- uses a deterministic high-risk flag derived from
Evaluation Endpoints
GET /tasks: lists tasks and the supported action/observation schema contractPOST /baseline: runs the baseline agent across all 3 tasks and returns scoresPOST /grader: deterministically scores a provided episode transcript
Baseline Scores
Example run from the /baseline endpoint with the default seed 123:
task_1(easy):0.50task_2(medium):1.00task_3(hard):1.00overall:0.83
If OPENAI_API_KEY is set, /baseline will use the OpenAI-backed agent; otherwise it uses the deterministic heuristic policy to keep the environment runnable for development.
Advanced Usage
Connecting to an Existing Server
If you already have a Support Triage Env environment server running, you can connect directly:
from support_triage_env import SupportTriageEnv
# Connect to existing server
support_triage_envenv = SupportTriageEnv(base_url="<ENV_HTTP_URL_HERE>")
# Use as normal
result = support_triage_envenv.reset(seed=1, task_id="task_1")
result = support_triage_envenv.step(
SupportTriageAction(
task_id="task_1",
task_stage="classify",
intent_category="billing_refund",
finalize=False,
)
)
result = support_triage_envenv.step(
SupportTriageAction(
task_id="task_1",
task_stage="finalize",
reply_template_id="tpl_refund_ack_next",
draft_reply="Thanks for reaching out. We received your request. Next step: please confirm details.",
finalize=True,
)
)
Note: When connecting to an existing server, support_triage_envenv.close() will NOT stop the server.
Using the Context Manager
The client supports context manager usage for automatic connection management:
from support_triage_env import SupportTriageAction, SupportTriageEnv
# Connect with context manager (auto-connects and closes)
with SupportTriageEnv(base_url="http://localhost:8000") as env:
result = env.reset(seed=1, task_id="task_1")
print("stage:", result.observation.task_stage)
result = env.step(
SupportTriageAction(
task_id="task_1",
task_stage="classify",
intent_category="billing_refund",
finalize=False,
)
)
result = env.step(
SupportTriageAction(
task_id="task_1",
task_stage="finalize",
reply_template_id="tpl_refund_ack_next",
draft_reply="Thanks for reaching out. We received your request. Next step: please confirm details.",
finalize=True,
)
)
print("done:", result.done, "reward:", result.reward)
The client uses WebSocket connections for:
- Lower latency: No HTTP connection overhead per request
- Persistent session: Server maintains your environment state
- Efficient for episodes: Better for many sequential steps
Concurrent WebSocket Sessions
The server supports multiple concurrent WebSocket connections. To enable this,
modify server/app.py to use factory mode:
# In server/app.py - use factory mode for concurrent sessions
app = create_app(
SupportTriageEnvironment, # Pass class, not instance
SupportTriageAction,
SupportTriageObservation,
max_concurrent_envs=4, # Allow 4 concurrent sessions
)
Then multiple clients can connect simultaneously:
from support_triage_env import SupportTriageAction, SupportTriageEnv
from concurrent.futures import ThreadPoolExecutor
def run_episode(client_id: int):
with SupportTriageEnv(base_url="http://localhost:8000") as env:
result = env.reset(seed=1, task_id="task_1")
result = env.step(
SupportTriageAction(
task_id="task_1",
task_stage="classify",
intent_category="billing_refund",
finalize=False,
)
)
result = env.step(
SupportTriageAction(
task_id="task_1",
task_stage="finalize",
reply_template_id="tpl_refund_ack_next",
draft_reply="Thanks. We received your request. Next step: please confirm details.",
finalize=True,
)
)
return client_id, float(result.reward)
# Run 4 episodes concurrently
with ThreadPoolExecutor(max_workers=4) as executor:
results = list(executor.map(run_episode, range(4)))
Development & Testing
Direct Environment Testing
Test the environment logic directly without starting the HTTP server:
# From the server directory
python3 server/support_triage_env_environment.py
This verifies that:
- Environment resets correctly
- Step executes actions properly
- State tracking works
- Rewards are calculated correctly
Running Locally
Run the server locally for development:
uvicorn server.app:app --reload
Project Structure
support_triage_env/
βββ .dockerignore # Docker build exclusions
βββ __init__.py # Module exports
βββ README.md # This file
βββ openenv.yaml # OpenEnv manifest
βββ pyproject.toml # Project metadata and dependencies
βββ uv.lock # Locked dependencies (generated)
βββ client.py # SupportTriageEnv client
βββ models.py # Action and Observation models
βββ server/
βββ __init__.py # Server module exports
βββ support_triage_env_environment.py # Core environment logic
βββ app.py # FastAPI application (HTTP + WebSocket endpoints)
βββ Dockerfile # Container image definition