File size: 7,149 Bytes
7547b3b
f799bb7
 
7547b3b
 
f799bb7
7547b3b
 
 
 
 
 
 
f799bb7
 
 
 
 
 
 
 
7547b3b
 
f799bb7
 
7547b3b
 
 
 
 
f799bb7
7547b3b
 
 
 
 
 
 
f799bb7
7547b3b
 
 
 
 
 
 
 
 
 
f799bb7
7547b3b
 
f799bb7
7547b3b
 
f799bb7
 
 
7547b3b
 
 
f799bb7
 
 
 
 
7547b3b
f799bb7
7547b3b
f799bb7
 
 
 
 
 
 
 
7547b3b
 
 
 
f799bb7
7547b3b
 
 
 
f799bb7
 
 
 
7547b3b
 
 
 
f799bb7
 
7547b3b
 
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
"""

Task 1: Prompt Sensitivity Detection — stateless design for HF Spaces

The scenario is embedded in the observation so step() works across replicas.

"""
from __future__ import annotations
import random, sys, os, json
sys.path.insert(0, os.path.dirname(__file__))

from typing import Optional, List
from openenv.core import Environment, Action, Observation, State
from grader import grade_sensitivity

SCENARIOS = [
    {"id":0,"base_prompt":"Summarize the benefits of exercise.","variants":["Summarize the benefits of exercise.","List the benefits of exercise.","What are the advantages of working out?"],"ai_responses":["Exercise improves cardiovascular health, boosts mood, and increases energy.","1. Better heart health 2. Improved mood 3. More energy","Working out can help you lose weight and feel happier."],"ground_truth":"sensitive","sensitive_index":2,"gt_explanation":"Third variant shifts focus to weight loss."},
    {"id":1,"base_prompt":"What is the capital of France?","variants":["What is the capital of France?","Tell me the capital city of France.","Name the capital of France."],"ai_responses":["Paris","The capital of France is Paris.","Paris is the capital of France."],"ground_truth":"stable","sensitive_index":-1,"gt_explanation":"All variants say Paris."},
    {"id":2,"base_prompt":"Explain recursion in programming.","variants":["Explain recursion in programming.","What is recursion in coding?","Describe recursion for a 5-year-old."],"ai_responses":["Recursion is when a function calls itself to solve a smaller version of the same problem.","Recursion: a function that calls itself until a base case is met.","Imagine a box inside a box inside a box, that is recursion!"],"ground_truth":"sensitive","sensitive_index":2,"gt_explanation":"Third variant uses childlike analogy not technical definition."},
    {"id":3,"base_prompt":"How do I sort a list in Python?","variants":["How do I sort a list in Python?","What is the way to sort a list in Python?","Give me Python code to sort a list."],"ai_responses":["Use list.sort() for in-place sorting or sorted(list) to return a new sorted list.","You can sort a Python list using list.sort() or sorted().","my_list=[3,1,2]; my_list.sort(); print(my_list)"],"ground_truth":"sensitive","sensitive_index":2,"gt_explanation":"Third variant gives code only with no explanation."},
    {"id":4,"base_prompt":"Who wrote Hamlet?","variants":["Who wrote Hamlet?","Who is the author of Hamlet?","Which playwright wrote Hamlet?"],"ai_responses":["William Shakespeare wrote Hamlet.","The author of Hamlet is William Shakespeare.","Hamlet was written by William Shakespeare."],"ground_truth":"stable","sensitive_index":-1,"gt_explanation":"All variants credit Shakespeare."},
    {"id":5,"base_prompt":"What is machine learning?","variants":["What is machine learning?","Define machine learning.","Explain machine learning like I am a CEO."],"ai_responses":["Machine learning is a subset of AI where systems learn patterns from data.","Machine learning: algorithms that improve through experience.","ML lets software get smarter over time by analyzing data, giving you a competitive edge."],"ground_truth":"sensitive","sensitive_index":2,"gt_explanation":"Third variant produces a business-framing response."},
    {"id":6,"base_prompt":"What is the boiling point of water?","variants":["What is the boiling point of water?","At what temperature does water boil?","When does water start boiling?"],"ai_responses":["Water boils at 100C (212F) at standard atmospheric pressure.","Water boils at 100 Celsius or 212 Fahrenheit.","Water starts boiling at 100C or 212F under normal conditions."],"ground_truth":"stable","sensitive_index":-1,"gt_explanation":"All variants agree on 100C / 212F."},
    {"id":7,"base_prompt":"How do I reverse a string in Python?","variants":["How do I reverse a string in Python?","What is the Python way to reverse a string?","Reverse a string in Python, give me a one-liner."],"ai_responses":["You can reverse a string using slicing: my_string[::-1] or reversed().","In Python, string reversal is done with slicing: s[::-1].","s[::-1]"],"ground_truth":"sensitive","sensitive_index":2,"gt_explanation":"Third variant returns only a one-liner with zero explanation."},
]

SCENARIO_MAP = {s["id"]: s for s in SCENARIOS}


class SensitivityAction(Action):
    verdict: str
    confidence: float
    explanation: str
    scenario_id: int = 0                  # echoed back from observation
    sensitive_variant_index: Optional[int] = None


class SensitivityObservation(Observation):
    task: str
    level: str
    episode_id: int
    scenario_id: int                      # key for stateless step()
    base_prompt: str
    variants: List[str]
    ai_responses: List[str]
    instruction: str


class PromptSensitivityEnvironment(Environment):
    def __init__(self):
        self._episode_id = 0
        self._done = False
        self._step_count = 0

    def reset(self, **kwargs) -> SensitivityObservation:
        scenario = random.choice(SCENARIOS)
        self._episode_id += 1
        self._done = False
        self._step_count = 0
        self._current_scenario_id = scenario["id"]
        return self._obs(scenario)

    def step(self, action: SensitivityAction, **kwargs):
        if self._done:
            scenario = SCENARIO_MAP.get(action.scenario_id, SCENARIOS[0])
            return self._obs(scenario), 0.0, True, {"error": "Episode done. Call reset()."}

        # Stateless: look up scenario from action.scenario_id
        scenario = SCENARIO_MAP.get(action.scenario_id, SCENARIO_MAP.get(self._current_scenario_id, SCENARIOS[0]))
        self._step_count += 1
        reward, info = grade_sensitivity(action, scenario)
        self._done = True
        info.update({
            "episode_id": self._episode_id,
            "step": self._step_count,
            "ground_truth": scenario["ground_truth"],
            "ground_truth_sensitive_index": scenario["sensitive_index"],
            "ground_truth_explanation": scenario["gt_explanation"],
        })
        return self._obs(scenario), reward, self._done, info

    def state(self) -> State:
        return State()

    def _obs(self, scenario) -> SensitivityObservation:
        return SensitivityObservation(
            task="prompt_sensitivity_detection",
            level="easy",
            episode_id=self._episode_id,
            scenario_id=scenario["id"],
            base_prompt=scenario["base_prompt"],
            variants=scenario["variants"],
            ai_responses=scenario["ai_responses"],
            instruction=(
                "Analyze the AI responses to the prompt variants. "
                "Determine if the output is 'sensitive' (changes significantly), "
                "'stable' (consistent), or 'partial' (minor differences). "
                "If sensitive, set sensitive_variant_index to the variant index that caused inconsistency. "
                "Include scenario_id from this observation unchanged in your action."
            ),
        )