Spaces:
Sleeping
Sleeping
File size: 3,893 Bytes
af222c8 56a15bc af222c8 69345ca af222c8 69345ca af222c8 69345ca 9ad188a af222c8 69345ca af222c8 69345ca af222c8 69345ca af222c8 69345ca af222c8 9ad188a af222c8 69345ca 9ad188a | 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 | import re
_POSITIVE = {
"glad",
"love",
"lucky",
"happy",
"great",
"grateful",
"fun",
"wonderful",
"nice",
"amazing",
"delighted",
"pleased",
"yes",
"solid",
}
_NEGATIVE = {
"tired",
"hard",
"sorry",
"unfortunately",
"bad",
"awful",
"regrettably",
"difficult",
"frustrating",
"no",
"stop",
}
_AFFECT_TARGET = {
"HAPPY": 1.0,
"FRUSTRATED": -0.5,
"NEUTRAL": 0.0,
"SURPRISED": 0.0,
}
_GESTURE_OPENER_PATTERNS = {
"THUMBS_UP": re.compile(r"^\s*(yes|yeah|totally|for sure|absolutely|sure)\b", re.I),
"THUMBS_DOWN": re.compile(r"^\s*(no|nah|not really|i'd rather not)\b", re.I),
"OPEN_PALM": re.compile(r"^\s*(hi|hey|hello)\b", re.I),
"VICTORY": re.compile(r"^\s*(yes|awesome|great|fantastic|amazing|woo)\b", re.I),
"I_LOVE_YOU": re.compile(r"^\s*(love|i love|adore|care)\b", re.I),
}
def _tokens(text: str) -> set[str]:
return set(re.findall(r"\b[a-z]+\b", text.lower()))
def _sentiment_score(text: str) -> float:
toks = _tokens(text)
pos = len(toks & _POSITIVE)
neg = len(toks & _NEGATIVE)
if pos == 0 and neg == 0:
return 0.0
return (pos - neg) / (pos + neg)
def _affect_alignment(response: str, affect: str | None) -> float:
if not affect:
return 0.0
target = _AFFECT_TARGET.get(affect, 0.0)
score = _sentiment_score(response)
# distance in [0, 2] → similarity in [0, 1]
return max(0.0, 1.0 - abs(score - target) / 2.0)
def _gesture_alignment(response: str, gesture_tag: str | None) -> float:
if not gesture_tag:
return 0.0
pattern = _GESTURE_OPENER_PATTERNS.get(gesture_tag)
if pattern is None:
return 0.5 # gesture has no testable opener; give partial credit
return 1.0 if pattern.search(response) else 0.0
def _gaze_alignment(
chunks: list[dict], gaze_bucket: str | None
) -> tuple[float, int, int]:
if not gaze_bucket or not chunks:
return 0.0, 0, len(chunks) if chunks else 0
matches = sum(1 for c in chunks if c.get("bucket") == gaze_bucket)
return matches / len(chunks), matches, len(chunks)
def _affect_breakdown(response: str) -> tuple[int, int]:
toks = _tokens(response)
return len(toks & _POSITIVE), len(toks & _NEGATIVE)
def compute_multimodal_alignment(
response: str,
affect: str | None,
gesture_tag: str | None,
gaze_bucket: str | None,
chunks: list[dict],
) -> dict:
scores: dict[str, float] = {}
explain: dict[str, dict] = {}
if affect:
scores["affect_alignment"] = _affect_alignment(response, affect)
pos, neg = _affect_breakdown(response)
explain["affect"] = {
"target": affect,
"pos_words": pos,
"neg_words": neg,
"sentiment": round(_sentiment_score(response), 4),
}
if gesture_tag:
scores["gesture_alignment"] = _gesture_alignment(response, gesture_tag)
pattern = _GESTURE_OPENER_PATTERNS.get(gesture_tag)
explain["gesture"] = {
"tag": gesture_tag,
"has_pattern": pattern is not None,
"matched": bool(pattern.search(response)) if pattern else None,
}
if gaze_bucket:
score, matches, total = _gaze_alignment(chunks, gaze_bucket)
scores["gaze_alignment"] = score
explain["gaze"] = {
"bucket": gaze_bucket,
"matched_chunks": matches,
"total_chunks": total,
}
overall = sum(scores.values()) / len(scores) if scores else 0.0
return {
"overall_score": round(overall, 4),
"affect_alignment": round(scores.get("affect_alignment", 0.0), 4),
"gesture_alignment": round(scores.get("gesture_alignment", 0.0), 4),
"gaze_alignment": round(scores.get("gaze_alignment", 0.0), 4),
"explain": explain,
}
|