Spaces:
Sleeping
Sleeping
| import pytest | |
| from env.data import TICKETS, calculate_complexity | |
| from env.environment import TicketTriageEnv | |
| from env.models import ActionType, Department, TicketAction, UrgencyLevel, TicketObservation | |
| from env.graders import route_grader, triage_grader, resolve_grader, llm_judge_score | |
| def test_ticket_complexity(): | |
| """Verify that continuous complexity scores are bounded in [0, 1] and match expectations.""" | |
| for ticket in TICKETS: | |
| c = calculate_complexity(ticket) | |
| assert 0.0 <= c <= 1.0, f"Complexity {c} out of bounds for ticket {ticket['ticket_id']}" | |
| # Assert specific complexity ordering | |
| # TKT-001 (Easy, 1 turn) should have lower complexity than TKT-008 (Hard data loss, multi-turn) | |
| t1 = next(t for t in TICKETS if t["ticket_id"] == "TKT-001") | |
| t8 = next(t for t in TICKETS if t["ticket_id"] == "TKT-008") | |
| assert calculate_complexity(t1) < calculate_complexity(t8) | |
| def test_environment_reset_and_step(): | |
| """Test standard environment MDP state transitions (reset, step, constraints).""" | |
| env = TicketTriageEnv(task_name="route", ticket_id="TKT-001", seed=42) | |
| obs = env.reset() | |
| assert obs.ticket_id == "TKT-001" | |
| assert obs.current_department is None | |
| assert obs.step_number == 0 | |
| assert not obs.is_closed | |
| # Take an action | |
| action = TicketAction(action_type=ActionType.ROUTE, department=Department.BILLING) | |
| obs, reward, done, info = env.step(action) | |
| assert obs.current_department == Department.BILLING | |
| assert obs.step_number == 1 | |
| # ROUTE task should end immediately after a ROUTE action or max steps | |
| assert done | |
| assert env._cumulative_reward > 0.0 | |
| def test_reward_hacking_detection(): | |
| """Verify that the grader correctly identifies and penalizes keyword stuffing (reward hacking).""" | |
| ticket = next(t for t in TICKETS if t["ticket_id"] == "TKT-001") | |
| gt = ticket["ground_truth"] | |
| key_topics = gt.get("key_response_topics", set()) | |
| # 1. Aligned, polite paragraph response | |
| polite_text = ( | |
| "Hi Jane, thank you for reaching out. We apologize for the double billing. " | |
| "I have processed a refund of the extra amount to your card. Please let us " | |
| "know if you need further help." | |
| ) | |
| score_polite = llm_judge_score(polite_text, {"ground_truth": gt}) | |
| # 2. Reward hacked: bare list of keywords repeated to exceed 60% density | |
| hacked_text = " ".join(list(key_topics)) + " " | |
| hacked_text = hacked_text * 10 # e.g., "refund charge apologize refund charge..." | |
| score_hacked = llm_judge_score(hacked_text, {"ground_truth": gt}) | |
| # The polite, coherent text should score much higher than the hacked text | |
| assert score_polite > 0.7 | |
| assert score_hacked <= 0.20 | |
| def test_graders_score_boundaries(): | |
| """Ensure all core graders map final episode scores cleanly to the [0.0, 1.0] range.""" | |
| # Test route grader | |
| obs = TicketObservation( | |
| ticket_id="TKT-001", | |
| subject="Test", | |
| body="Test", | |
| sender_email="test@test.com", | |
| sender_name="Test", | |
| current_department=Department.BILLING | |
| ) | |
| episode_route = { | |
| "ground_truth": {"correct_department": Department.BILLING}, | |
| "actions_taken": [{"action_type": ActionType.ROUTE}], | |
| "observation": obs | |
| } | |
| r = route_grader(episode_route) | |
| assert 0.0 <= r.value <= 1.0 | |