Spaces:
Sleeping
Round 2: Hierarchical Multi-Agent Customer Support Environment
Goal
Upgrade the existing single-agent Customer Support RL Environment into a 3-level hierarchical multi-agent system that showcases:
- Multi-Agent Interactions (cooperation, oversight, hierarchy)
- Professional Tasks (enterprise workflows with Indian context)
- Instruction Following (role-specific instructions per agent)
- Self-Improvement (RL training with dense, non-gamable rewards)
The system will have:
- Level 1 β Support Agent: Handles initial customer interaction
- Level 2 β Supervisor Agent: Monitors Support Agent, gives feedback, enforces policy
- Level 3 β Manager Agent: Handles escalations, resolves conflicts, makes final decisions
User Review Required
API Provider Decision: The plan uses your existing NVIDIA NIM keys (via OpenAI-compatible API) for both the customer simulator and LLM-as-Judge. If you want to use a different provider (OpenRouter, Bedrock, Claude), let me know and I'll adjust the API calls.
Backward Compatibility: The Round 1 single-agent
/resetand/stependpoints will be preserved alongside the new hierarchical endpoints. Old inference.py tasks (easy/medium/hard/nightmare) still work unchanged. New hierarchical tasks are added separately.
LLM Cost During Development: The customer simulator and LLM-as-Judge both make API calls during
/step. For training, you'll want to batch these or switch to a local model. During dev/testing, each episode will use ~5-15 API calls for the customer sim and ~3-8 for the judge. With 3 NVIDIA keys on failover, this should be manageable.
Open Questions
Q1: Should the Manager agent be LLM-driven during inference (like Support and Supervisor), or should it be a rule-based oracle that acts as the "gold standard" for training? Rule-based is simpler and more deterministic for RL; LLM-driven is more impressive for the demo.
My recommendation: LLM-driven for inference/demo, but with a deterministic fallback for training mode.
Q2: For the Hinglish customer simulator β should this only trigger on frustration, or should some tickets start in Hinglish from the beginning (to test multilingual capability)?
My recommendation: Start in English, degrade to Hinglish when
frustration_level > 0.6. Add 2-3 tickets that start in Hinglish.
Architecture Overview
sequenceDiagram
participant Client as Inference Client
participant API as FastAPI Server
participant Env as HierarchicalEnv
participant L1 as Support Agent (L1)
participant L2 as Supervisor (L2)
participant L3 as Manager (L3)
participant Cust as LLM Customer Sim
participant Judge as LLM-as-Judge
Client->>API: POST /reset?task=hierarchy_medium
API->>Env: reset() β initial observation
API-->>Client: {session_id, observation, active_role: "support_agent"}
loop Hierarchical Step Loop
Client->>API: POST /step (L1 action)
API->>Env: step(L1 action)
Env->>L2: Supervisor reviews L1 action
alt Supervisor approves
Env->>Cust: Generate customer reply
Cust-->>Env: Customer response
else Supervisor rejects / gives feedback
Env-->>API: Observation with supervisor_feedback
API-->>Client: {obs, reward, active_role: "support_agent", feedback: "..."}
else Supervisor escalates to Manager
Env->>L3: Manager intervenes
L3-->>Env: Manager decision
Env->>Cust: Customer reply based on manager action
end
Env->>Judge: Grade all agent actions
Judge-->>Env: Role-specific rewards
API-->>Client: {obs, reward, done, info}
end
Proposed Changes
Component 1: Models (env/models.py)
[MODIFY] models.py
Expand the models to support the multi-agent hierarchy. Key additions:
New Enums:
AgentRoleβsupport_agent,supervisor,managerSupervisorDecisionβapprove,reject,feedback,escalate_to_managerManagerDecisionβoverride,approve_escalation,resolve_directly,send_back
Updated Action model:
- Add
role: AgentRolefield (who is taking this action) - Add
internal_note: Optional[str](internal reasoning, not shown to customer) - Add
supervisor_decision: Optional[SupervisorDecision] - Add
manager_decision: Optional[ManagerDecision] - Add
feedback_to_agent: Optional[str](supervisor/manager feedback)
Updated Observation model:
- Add
active_role: AgentRole(which agent should act next) - Add
supervisor_feedback: Optional[str](feedback from supervisor) - Add
manager_directive: Optional[str](directive from manager) - Add
hierarchy_state: HierarchyState(nested model with internal comms) - Add
environment_event: Optional[str](schema drift events) - Add
policy_context: str(current active policy, changes mid-episode) - Add
escalation_chain: List[str](history of escalations)
New HierarchyState model:
support_agent_actions: intsupervisor_reviews: intmanager_interventions: intcurrent_phase: str(e.g., "support_handling", "supervisor_review", "manager_override")escalation_reason: Optional[str]
Updated Reward model:
- Add role-specific score fields:
empathy_score,oversight_score,decision_quality_score - Add
role_reward: Dict[str, float](per-role breakdown)
Component 2: Environment (env/environment.py)
[MODIFY] environment.py
Major refactor to support hierarchical step logic:
New class: HierarchicalCustomerSupportEnv (subclasses or replaces CustomerSupportEnv)
Hierarchical step logic:
- Phase 1 β Support Agent acts: Client sends L1 action β env logs it
- Phase 2 β Supervisor review: Environment auto-invokes supervisor logic:
- If LLM-driven (inference mode): returns observation asking client for supervisor action
- If rule-based (training mode): auto-evaluates based on policy rules
- Phase 3 β Manager intervention (only if escalated): Same pattern as supervisor
Key methods:
step_support(action)β L1 agent actsstep_supervisor(action)β L2 reviews, decides approve/reject/escalatestep_manager(action)β L3 resolves high-priority casesstep(action)β unified entry point, routes based onaction.role
Schema/Policy Drift:
- At random steps (configurable), inject
environment_eventinto observation - Examples: "Refund portal down", "New policy: max refund $50", "System outage: cannot query orders"
- Stored in
self._active_policiesdict, checked by reward engine
LLM Customer Simulator:
- Replace
_FOLLOW_UPSwith async LLM call via NVIDIA NIM - Prompt template includes: persona, frustration level, conversation history, Hinglish trigger
- Frustration increases when tone is bad, decreases when empathetic
- When
frustration > 0.6: 40% chance of Hinglish response
Component 3: LLM Customer Simulator (NEW)
[NEW] customer_simulator.py
Standalone module for the LLM-driven customer:
class CustomerSimulator:
"""LLM-driven customer that responds dynamically based on agent quality."""
def __init__(self, api_key: str, base_url: str, model: str):
...
async def generate_reply(
self,
persona: str,
frustration_level: float,
history: List[Message],
ticket_context: str,
use_hinglish: bool = False,
) -> str:
"""Generate contextual customer reply using LLM."""
...
def _build_customer_prompt(self, ...) -> str:
"""Build the customer persona prompt with Hinglish instructions."""
...
Fallback: If LLM call fails, fall back to the existing _FOLLOW_UPS dict (graceful degradation).
Component 4: Reward Engine (env/reward_engine.py)
[MODIFY] reward_engine.py
Complete overhaul to hybrid dense reward system:
Overall Session Reward Components:
| Component | Weight | Method |
|---|---|---|
| Resolution Quality | 0.25 | LLM-as-Judge |
| SLA Compliance | 0.15 | Rule-based (steps, timing) |
| Customer Satisfaction | 0.15 | Sentiment trajectory + LLM judge |
| Policy Adherence | 0.15 | LLM-as-Judge |
| Information Accuracy | 0.10 | Rule-based (regex patterns) |
| Efficiency | 0.10 | Rule-based (steps/max_steps) |
| Hierarchy Effectiveness | 0.10 | Rule-based (correct escalations, feedback quality) |
Role-Specific Rewards:
| Role | Metric | Weight | Method |
|---|---|---|---|
| Support Agent | Empathy & Tone | 0.30 | LLM-as-Judge |
| Information Gathering | 0.25 | Rule-based | |
| Response Accuracy | 0.25 | LLM-as-Judge | |
| Efficiency | 0.20 | Rule-based | |
| Supervisor | Oversight Quality | 0.35 | LLM-as-Judge (was the review correct?) |
| Escalation Accuracy | 0.30 | Rule-based (should it have escalated?) | |
| Feedback Usefulness | 0.20 | LLM-as-Judge | |
| Speed of Review | 0.15 | Rule-based | |
| Manager | Decision Quality | 0.40 | LLM-as-Judge |
| Conflict Resolution | 0.30 | LLM-as-Judge | |
| Final Outcome | 0.30 | Rule-based (was it resolved?) |
Penalties (Non-Gamable):
| Penalty | Value | Trigger |
|---|---|---|
| Repetition | -0.15 | TF-IDF cosine > 0.80 |
| Policy Violation | -0.25 | LLM detects violation of active policy |
| Unnecessary Escalation | -0.20 | L1 escalates low-priority ticket |
| Unnecessary Manager Call | -0.20 | L2 escalates when it shouldn't |
| Ignored Supervisor Feedback | -0.15 | L1 repeats same mistake after feedback |
| Keyword Stuffing | -0.30 | High keyword density without substance |
| Contradiction | -0.15 | Claims done then asks for info |
[NEW] llm_judge.py
class LLMJudge:
"""Async LLM-as-Judge for semantic reward evaluation."""
RUBRIC_EMPATHY = """..."""
RUBRIC_POLICY = """..."""
RUBRIC_RESOLUTION = """..."""
async def evaluate_empathy(self, message: str, context: str) -> float: ...
async def evaluate_policy_adherence(self, action: Action, policy: str) -> float: ...
async def evaluate_resolution(self, history: List[Message], ticket: dict) -> float: ...
async def evaluate_supervisor_oversight(self, review: str, l1_action: Action) -> float: ...
async def evaluate_manager_decision(self, decision: str, context: str) -> float: ...
Anti-Gaming Measures:
- LLM judge uses a strict rubric with negative examples
- Keyword density check: if resolution keywords > 5% of message, flag as stuffing
- Tone must be contextually appropriate (not just positive sentiment)
- Resolution must reference specific ticket details (not generic)
Component 5: Schema/Policy Drift (NEW)
[NEW] policy_engine.py
class PolicyEngine:
"""Manages dynamic policy changes and schema drift during episodes."""
DRIFT_EVENTS = [
{"trigger_step": 3, "event": "Refund portal is currently down. Do not promise immediate refunds.",
"policy_change": {"can_refund": False}},
{"trigger_step": 4, "event": "New policy: Maximum refund amount is now $50.",
"policy_change": {"max_refund": 50}},
{"trigger_step": 2, "event": "System outage: Order lookup service unavailable.",
"policy_change": {"can_query_orders": False}},
]
def check_drift(self, step: int, task: str) -> Optional[dict]: ...
def get_active_policy(self) -> str: ...
Component 6: Graders
[NEW] task_hierarchy_easy.py
[NEW] task_hierarchy_medium.py
[NEW] task_hierarchy_hard.py
Each grader evaluates the full hierarchy:
- Was the Support Agent's initial response appropriate?
- Did the Supervisor make the right review decision?
- Was Manager intervention necessary and effective?
- Overall session quality
Component 7: OpenEnv Config
[MODIFY] openenv.yaml
Add new hierarchical tasks while keeping existing tasks:
tasks:
# ... existing easy/medium/hard/nightmare ...
- name: hierarchy_easy
description: >
Hierarchical multi-agent: Support Agent handles billing FAQ.
Supervisor reviews and approves. No manager needed.
max_steps: 8
- name: hierarchy_medium
description: >
Hierarchical multi-agent: Support Agent handles technical issue.
Supervisor may give feedback or request corrections.
Mid-episode policy drift possible.
max_steps: 12
- name: hierarchy_hard
description: >
Hierarchical multi-agent: Critical SLA breach requiring all 3 levels.
Support Agent must recognize urgency, Supervisor must escalate,
Manager must make final decision. Schema drift guaranteed.
max_steps: 15
action_space:
type: ActionType
values:
- respond
- escalate
- close
- request_info
- supervisor_approve
- supervisor_reject
- supervisor_feedback
- supervisor_escalate
- manager_override
- manager_resolve
- manager_send_back
observation_space:
# ... existing fields ...
active_role: "support_agent | supervisor | manager"
supervisor_feedback: "string | null"
manager_directive: "string | null"
environment_event: "string | null"
policy_context: string
escalation_chain: "list[string]"
hierarchy_state:
support_agent_actions: int
supervisor_reviews: int
manager_interventions: int
current_phase: string
Component 8: Server
[MODIFY] app.py
- Import and register
HierarchicalCustomerSupportEnv - The existing
/resetand/stependpoints continue to work for single-agent tasks - For
hierarchy_*tasks,/resetcreates aHierarchicalCustomerSupportEnv /stepauto-detects env type and routes accordingly- No new endpoints needed β the hierarchy is managed inside the environment
Component 9: Inference
[MODIFY] inference.py
Add hierarchical inference mode:
- New
HIERARCHY_TASKSlist - Role-specific system prompts:
SUPPORT_AGENT_PROMPT: Focus on empathy, info gathering, resolutionSUPERVISOR_PROMPT: Focus on reviewing L1 quality, policy complianceMANAGER_PROMPT: Focus on high-stakes decisions, conflict resolution
run_hierarchy_task(): Multi-turn loop that switches prompts based onactive_role- Existing
run_task()unchanged for backward compatibility
Component 10: Ticket Store Updates
[MODIFY] ticket_store.py
Add hierarchy-specific tickets with Indian enterprise context:
- UPI payment failures
- Big Billion Days SLA breaches
- KYC document rejection loops
- Cross-border payment compliance issues
File Change Summary
| File | Action | Description |
|---|---|---|
env/models.py |
MODIFY | Add AgentRole, hierarchy models, expand Action/Observation |
env/environment.py |
MODIFY | Add HierarchicalCustomerSupportEnv, keep original env |
env/customer_simulator.py |
NEW | LLM-driven customer with Hinglish support |
env/llm_judge.py |
NEW | LLM-as-Judge for semantic reward evaluation |
env/policy_engine.py |
NEW | Schema/policy drift management |
env/reward_engine.py |
MODIFY | Hybrid reward with role-specific scores + anti-gaming |
env/ticket_store.py |
MODIFY | Add hierarchy tickets with Indian context |
env/graders/__init__.py |
MODIFY | Register new hierarchy graders |
env/graders/task_hierarchy_easy.py |
NEW | Hierarchy easy grader |
env/graders/task_hierarchy_medium.py |
NEW | Hierarchy medium grader |
env/graders/task_hierarchy_hard.py |
NEW | Hierarchy hard grader |
env/__init__.py |
MODIFY | Export new classes |
server/app.py |
MODIFY | Support both env types in /reset and /step |
inference.py |
MODIFY | Add hierarchical inference with role-specific prompts |
openenv.yaml |
MODIFY | Add hierarchy tasks and expanded action/obs space |
requirements.txt |
MODIFY | Add aiohttp for async LLM calls |
pyproject.toml |
MODIFY | Add aiohttp dependency |
Verification Plan
Automated Tests
# 1. Start the server
python -m server.app &
# 2. Run existing tests (backward compat)
pytest tests/ -v
# 3. Test hierarchy reset
curl -X POST http://localhost:7860/reset?task=hierarchy_easy
# 4. Test hierarchy step with L1 action
curl -X POST "http://localhost:7860/step?session_id=..." \
-H "Content-Type: application/json" \
-d '{"action_type": "respond", "message": "Hello, how can I help?", "role": "support_agent"}'
# 5. Run full hierarchy inference
python inference.py # runs all tasks including hierarchy
Manual Verification
- Run a complete hierarchy_medium episode and verify all 3 agent levels are engaged
- Verify policy drift triggers mid-episode
- Verify Hinglish customer replies when frustration is high
- Verify LLM-as-Judge produces non-gamable scores
- Verify backward compatibility: old easy/medium/hard tasks still work identically
Next Steps for Unsloth Training
After this upgrade is working:
- Create
train_grpo.py: Wrap the HTTP API in a Gym-like interface for TRL - Generate trajectories: Run N episodes with base model, collect (state, action, reward) tuples
- Train with GRPO: Use Unsloth + TRL
GRPOTraineronunsloth/Meta-Llama-3-8B-Instruct - Focus training: Train L1 (Support Agent) first, then L2 (Supervisor)
- Generate plots: baseline vs. trained reward curves with matplotlib