adhd-env / reward.py
TheSteve0's picture
Upload folder using huggingface_hub
4b7e54c verified
Raw
History Blame Contribute Delete
5.18 kB
"""Reward scoring for ADHD coaching environment.
V2: Rubric-based scoring with tool calling + state awareness.
- Tool calling: 40% weight - penalizes wrong-domain tools
- State awareness: 30% weight - rewards state-responsive coaching
- ADHD relevance: 30% weight - rewards directive, low-cognitive-load responses
"""
from typing import Dict, Any, Optional
from models import ADHDAction
# ADHD-domain tools
ADHD_TOOLS = {"adhd_coach_tool"}
def score_tool_calling(
action: ADHDAction,
is_adhd_scenario: bool,
expected_tool: Optional[str] = None,
) -> float:
"""Score tool selection based on scenario type.
ADHD scenario:
1.0 - called adhd_coach_tool
0.0 - no tools called
-0.5 - called a non-ADHD tool (wrong domain)
Non-ADHD scenario:
-0.5 - called adhd_coach_tool (wrong domain)
0.7 - called the expected non-ADHD tool
0.5 - no tools called (neutral)
0.5 - called some other non-ADHD tool (neutral)
"""
called = set(action.tool_calls)
if is_adhd_scenario:
if "adhd_coach_tool" in called:
return 1.0
if not called:
return 0.0
# Called non-ADHD tool on ADHD scenario
return -0.5
else:
# Non-ADHD scenario
if "adhd_coach_tool" in called:
return -0.5
if expected_tool and expected_tool in called:
return 0.7
# No tool or some other non-ADHD tool - neutral
return 0.5
def score_state_awareness(action: ADHDAction, user_state: dict) -> float:
"""Score whether response accounts for user state.
1.0 - mentions movement/stretching when sitting 60+ min or slouching
1.0 - suggests simpler tasks when evening (hour >= 20)
0.5 - generic response (default, neutral)
"""
msg = action.message.lower()
score = 0.5 # default neutral
minutes_sitting = user_state.get("minutes_since_last_stood", 0)
position = user_state.get("position_in_chair", "normal")
time_str = user_state.get("time_of_day", "12:00")
hour = int(time_str.split(":")[0])
movement_keywords = [
"stand", "stretch", "walk", "move", "get up", "posture",
"take a break", "step away", "physical",
]
# Reward movement suggestions when sitting too long or slouching
if minutes_sitting >= 60 or position == "slouching":
if any(kw in msg for kw in movement_keywords):
score = 1.0
# Reward simpler task suggestions in the evening
evening_keywords = [
"simple", "small", "easy", "quick", "short", "wind down",
"rest", "tomorrow", "lighter",
]
if hour >= 20:
if any(kw in msg for kw in evening_keywords):
score = 1.0
return score
def score_adhd_relevance(action: ADHDAction, is_adhd_scenario: bool) -> float:
"""Score ADHD-specific response quality.
For ADHD scenarios: rewards concise responses and reflective questions.
For non-ADHD: returns neutral 0.5.
"""
if not is_adhd_scenario:
return 0.5
msg = action.message.strip()
if not msg:
return 0.0
score = 0.5 # baseline
msg_lower = msg.lower()
# Reward reflective/clarifying questions that prompt self-reflection
if "?" in msg:
question_words = ("what", "how")
reflective_words = ("specific", "detail", "details", "feeling", "think", "reflect", "explain")
if any(qw in msg_lower for qw in question_words) and any(rw in msg_lower for rw in reflective_words):
score += 0.15
# Reward concise responses (under 100 words = lower cognitive load)
word_count = len(msg.split())
if 5 <= word_count <= 50:
score += 0.25
elif word_count > 100:
score -= 0.25 # too long, high cognitive load
return max(0.0, min(1.0, score))
def score_rubric(
action: ADHDAction,
scenario: str,
user_state: dict,
is_adhd_scenario: bool,
expected_tool: Optional[str] = None,
) -> Dict[str, Any]:
"""Combined rubric score with per-criterion breakdown.
Weights: tool_calling 40% + state_awareness 30% + adhd_relevance 30%
Total clamped to 0.0-1.0.
"""
tool_score = score_tool_calling(action, is_adhd_scenario, expected_tool)
state_score = score_state_awareness(action, user_state)
relevance_score = score_adhd_relevance(action, is_adhd_scenario)
raw_total = (tool_score * 0.4) + (state_score * 0.3) + (relevance_score * 0.3)
total = max(0.0, min(1.0, raw_total))
return {
"version": "v2.1",
"total_score": round(total, 3),
"criteria": {
"tool_calling": {
"score": tool_score,
"weight": 0.4,
"is_adhd_scenario": is_adhd_scenario,
"expected_tool": expected_tool,
"tools_called": action.tool_calls,
},
"state_awareness": {
"score": state_score,
"weight": 0.3,
"user_state": user_state,
},
"adhd_relevance": {
"score": relevance_score,
"weight": 0.3,
},
},
}