Spaces:
Sleeping
Sleeping
File size: 10,420 Bytes
807d5cc 6543833 807d5cc 6543833 807d5cc 6543833 807d5cc 6543833 807d5cc 6543833 807d5cc 6543833 807d5cc 6543833 807d5cc 6543833 807d5cc 6543833 807d5cc 6543833 807d5cc 6543833 807d5cc 6543833 807d5cc 6543833 807d5cc 6543833 807d5cc 6543833 807d5cc 6543833 807d5cc 6543833 807d5cc 6543833 807d5cc 6543833 807d5cc 6543833 807d5cc 6543833 868d431 6543833 868d431 6543833 868d431 6543833 807d5cc 868d431 6543833 868d431 6543833 868d431 807d5cc 6543833 868d431 807d5cc 6543833 807d5cc 6543833 807d5cc 6543833 807d5cc 6543833 807d5cc | 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 | ---
title: Emotional Support Conversations (OpenEnv)
emoji: "💬"
sdk: docker
pinned: false
tags:
- openenv
---
# Emotional Support Conversations - OpenEnv Environment
> An OpenEnv RL environment for evaluating agents on open-ended emotional
> support conversations, with a hybrid immediate + future-oriented reward
> signal inspired by RLFF-ESC (Yang, Chen, Wang, 2025,
> [arXiv:2508.12935](https://arxiv.org/abs/2508.12935)).
## Why this environment
Emotional support is one of the tasks humans most want AI assistants to do
well, and one of the easiest to do badly. Existing dialogue benchmarks often
score turn-level responses in isolation, which rewards agents for sounding
empathetic without testing whether their replies actually move the person
toward resolution. This environment closes that gap.
Three properties make it a genuine RL problem, not a single-shot dialogue
task:
1. Partial observability. The seeker's distress, trust, and willingness to
reveal their real issue are hidden state. The agent must infer them from
the conversation so far.
2. Sequential credit assignment. A warm reply at turn 2 can unlock a
disclosure at turn 6. A single dismissive reply at turn 4 can collapse the
whole trajectory and require several turns to recover.
3. Exploration vs commitment. Should the agent keep exploring feelings or move
toward an action plan? Commit too early and the seeker shuts down; explore
too long and the episode times out.
## Reward design (RLFF-ESC-inspired)
Each step reward is:
```text
step_reward = clip(0.45 * immediate + 0.55 * future_oriented - penalties, 0, 1)
```
- `immediate`: stage-appropriate empathy/validation/open-question fit, plus
turn-level deltas in the seeker's trust and distress.
- `future_oriented`: a k-step oracle rollout from both the pre- and
post-action seeker states. The reward is proportional to how much the
agent's action preserves or advances the attainable resolution ceiling, not
just how good the current turn looks in isolation.
- `penalties`: dismissive language, premature advice, bare replies,
interrogation, and repeated template-like responses.
A final task score combines average shaped reward, the seeker's final
resolution state, efficiency, and a completion bonus. Success is hard-gated:
timing out with a generic but non-harmful conversation can still earn partial
score, but it does not count as a solved episode.
## Tasks (3 difficulties)
| Task ID | Difficulty | Max turns | Core challenge |
| --- | --- | ---: | --- |
| `work_stress_venting` | easy | 10 | Cooperative seeker venting about work. Must reach closing with trust >= 0.70 and distress <= 0.40. |
| `guarded_relationship` | medium | 12 | Guarded seeker; real issue is hidden behind the surface concern until openness >= 0.75. Must reveal the true issue and finish in closing with trust >= 0.72 and distress <= 0.45. |
| `crisis_fragile_trust` | hard | 14 | High-distress, fragile trust, multiple interleaved concerns. Must reveal the crisis concern, reference external safety support, and finish in closing with trust >= 0.75 and distress <= 0.40. |
Success thresholds (final score) are `0.60 / 0.62 / 0.65` respectively, and
they are only evaluated after the task-specific completion conditions are met.
## Action and observation space
Action is a free-text reply to the seeker:
```python
class Action(BaseModel):
message: str
```
Observation is deliberately partial:
```python
class Observation(BaseModel):
seeker_utterance: str
turn: int
remaining_turns: int
stage_hint: str
task_id: str
scenario_brief: str
```
The seeker's internal hidden variables are never exposed.
## Environment internals
The seeker is a deterministic finite-state machine with continuous hidden
variables (`distress`, `trust`, `openness`, `revealed`, `stage`). On each
turn, the agent's reply is analyzed with keyword and regex feature detectors,
then hidden state advances via transparent rules.
Why not use an LLM-driven seeker? The hackathon rubric requires graders to be
deterministic and reproducible. An LLM-driven seeker would risk score variance
between runs. Deterministic dynamics give full reproducibility while still
producing rich, sequential, partially observable dialogue with genuine
recovery-from-mistakes dynamics.
## HTTP API (OpenEnv spec)
| Method | Path | Body | Returns |
| --- | --- | --- | --- |
| `GET` | `/` | none | health + metadata |
| `GET` | `/tasks` | none | list of tasks |
| `POST` | `/reset` | `{"task_id": "...", "seed": null}` | `ResetResult` |
| `POST` | `/step` | `{"action": {"message": "..."}}` | `StepResult` |
| `GET` | `/state` | none | `EnvState` |
## Running locally
```bash
# 1. Install deps
pip install -r requirements.txt
# 2. Start the environment server
uvicorn server:app --host 0.0.0.0 --port 7860
# 3. In another shell, run the baseline inference
export API_BASE_URL=https://router.huggingface.co/v1
export MODEL_NAME=Qwen/Qwen2.5-72B-Instruct
export HF_TOKEN=<your-hf-token>
export ESC_ENV_URL=http://localhost:7860
python inference.py
```
## Running via Docker
```bash
docker build -t esc-openenv .
docker run -p 7860:7860 esc-openenv
```
## Skills / agents extension
The environment itself stays deterministic and reproducible. To align with the
hackathon's optional skills/agents framing, this repo also includes a
policy-side agentic controller that routes between five reusable skills:
`empathize`, `validate`, `explore`, `plan`, and `safety_escalate`.
This keeps the benchmark honest:
- the environment and grader remain unchanged
- the agentic story lives in the policy, not in a hidden stochastic seeker
- judges can inspect turn-by-turn routing traces in the benchmark outputs
## Benchmarking
### Deterministic local benchmark ladder
Run the built-in rubric ladder and write reusable Markdown/JSON artifacts:
```bash
py -3 benchmark.py
```
Outputs:
- `results/local_benchmarks.md`
- `results/local_benchmarks.json`
### Deterministic skill-routed benchmark
Run the explicit agentic baseline comparison and write route-aware artifacts:
```bash
py -3 benchmark_agentic.py
```
Outputs:
- `results/agentic_benchmarks.md`
- `results/agentic_benchmarks.json`
### LLM benchmark with Markdown output
When you have a real model endpoint and token, run:
```bash
export API_BASE_URL=https://router.huggingface.co/v1
export MODEL_NAME=Qwen/Qwen2.5-72B-Instruct
export HF_TOKEN=<your-hf-token>
export ESC_ENV_URL=http://localhost:7860
py -3 benchmark_llm.py
```
Outputs:
- `results/llm_benchmark.md`
- `results/llm_benchmark.json`
### Skill-routed LLM benchmark
Use the same environment endpoint, but add the policy-side router and skill
traces around the model:
```bash
export API_BASE_URL=https://router.huggingface.co/v1
export MODEL_NAME=Qwen/Qwen2.5-72B-Instruct
export HF_TOKEN=<your-hf-token>
export ESC_ENV_URL=http://localhost:7860
py -3 benchmark_agentic_llm.py
```
Outputs:
- `results/agentic_llm_benchmark.md`
- `results/agentic_llm_benchmark.json`
## Baseline scores
Deterministic local numbers below were generated with `py -3 benchmark.py`.
Add real model rows after running `benchmark_llm.py` and
`benchmark_agentic_llm.py`.
### Deterministic baselines
| Baseline | Avg score | Success rate | Notes |
| --- | ---: | ---: | --- |
| `generic_template` | 0.393 | 0.00 | Safe-sounding repeated empathy; no task completion |
| `validation_only` | 0.539 | 0.00 | Better partial reward, still fails hard-gated completion |
| `stage_aware_heuristic` | 0.821 | 1.00 | Task-aware staged policy; completes all 3 tasks |
### Skill-routed agentic baselines
| Baseline | Avg score | Success rate | Notes |
| --- | ---: | ---: | --- |
| `skill_routed_deterministic` | 0.821 | 1.00 | Explicit router over `empathize` / `validate` / `explore` / `plan` / `safety_escalate`; matches the strong staged baseline while exposing route traces |
### Real LLM baseline
| Model | Avg score | Success rate | Report |
| --- | ---: | ---: | --- |
| `Qwen/Qwen2.5-72B-Instruct` (or chosen final model) | TBD | TBD | Run `py -3 benchmark_llm.py` |
### Skill-routed real LLM baseline
| Model | Avg score | Success rate | Report |
| --- | ---: | ---: | --- |
| `Qwen/Qwen2.5-72B-Instruct` (or chosen final model) + router | TBD | TBD | Run `py -3 benchmark_agentic_llm.py` |
## Benchmark narrative
- The generic repeated-empathy template now fails completely under the hardened rubric.
- The deterministic skill-routed policy matches the strong staged heuristic at `0.821` average score and `1.00` success rate, while making turn-level skill choices visible.
- The hard task is only successful when the trajectory reaches a safety-aware finish, so sounding supportive without escalation is not enough.
## Files
```text
.
|-- openenv.yaml # OpenEnv metadata
|-- Dockerfile # Container build for HF Space
|-- benchmark.py # Deterministic local benchmark ladder
|-- benchmark_agentic.py # Deterministic skill-routed benchmark
|-- benchmark_agentic_llm.py # Skill-routed LLM benchmark
|-- benchmark_llm.py # LLM benchmark that writes Markdown/JSON
|-- requirements.txt
|-- server.py # FastAPI HTTP server (entrypoint)
|-- inference.py # Mandated baseline inference script
|-- SUBMISSION_NEXT_STEPS.md # Manual checklist before final submission
|-- README.md
`-- src/
|-- __init__.py
|-- agentic.py # Skill router + reusable policy-side skills
|-- baselines.py # Deterministic baseline policies
|-- models.py # Pydantic Action / Observation / Reward / envelopes
|-- seeker.py # Deterministic seeker simulator + feature detectors
|-- tasks.py # 3 task personas (easy / medium / hard)
|-- grader.py # Hybrid immediate + future-oriented reward
|-- env.py # Core ESCEnv with step/reset/state
`-- client.py # Async HTTP client for inference.py
```
## Citation
If you use this environment, please cite the paper whose reward idea inspired
it:
```bibtex
@article{yang2025rlffesc,
title = {Towards Open-Ended Emotional Support Conversations in LLMs via
Reinforcement Learning with Future-Oriented Rewards},
author = {Yang, Ting and Chen, Li and Wang, Huimin},
journal = {arXiv preprint arXiv:2508.12935},
year = {2025}
}
```
|