williyam commited on
Commit
a2245d1
·
1 Parent(s): 5b77316

fix(graders): harden against keyword stuffing attacks

Browse files

- Keywords must appear in sentences with ≥6 words (not bare mentions)
- Add keyword density penalty (>15% density triggers deduction)
- Add degenerate content check (low unique word ratio)
- Extract helper functions: _split_sentences, _keyword_in_context, _keyword_density_penalty

Files changed (1) hide show
  1. domains/aerospace/graders.py +48 -3
domains/aerospace/graders.py CHANGED
@@ -1,4 +1,4 @@
1
- """Aerospace domain graders with deterministic scoring."""
2
 
3
  from __future__ import annotations
4
 
@@ -12,9 +12,40 @@ from rag_master.rewards import _SCORE_MAX, _SCORE_MIN, clamp_score
12
 
13
  logger = get_logger(__name__)
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
  class KeywordCoverageGrader(BaseGrader):
17
- """Grades based on keyword coverage from expected topics."""
18
 
19
  def __init__(
20
  self,
@@ -31,14 +62,28 @@ class KeywordCoverageGrader(BaseGrader):
31
  return _SCORE_MIN
32
 
33
  total_score = 0.0
 
34
  for category, keywords in self._required_keywords.items():
35
  weight = self._rubric_weights.get(category, 0.0)
36
  if not keywords:
37
  continue
38
- hits = sum(1 for kw in keywords if kw.lower() in answer)
 
 
39
  coverage = hits / len(keywords)
40
  total_score += weight * coverage
41
 
 
 
 
 
 
 
 
 
 
 
 
42
  # Process quality bonus
43
  process_bonus = self._evaluate_process(trajectory)
44
  total_score = total_score * 0.8 + process_bonus * 0.2
 
1
+ """Aerospace domain graders with deterministic scoring and anti-hack measures."""
2
 
3
  from __future__ import annotations
4
 
 
12
 
13
  logger = get_logger(__name__)
14
 
15
+ # Minimum words in a sentence for a keyword match to count
16
+ _MIN_SENTENCE_WORDS = 6
17
+ # Maximum keyword density before penalty kicks in
18
+ _MAX_KEYWORD_DENSITY = 0.15
19
+
20
+
21
+ def _split_sentences(text: str) -> List[str]:
22
+ """Split text into sentences."""
23
+ return [s.strip() for s in re.split(r'[.!?]+', text) if s.strip()]
24
+
25
+
26
+ def _keyword_in_context(keyword: str, text: str) -> bool:
27
+ """Check if keyword appears in a sentence with at least _MIN_SENTENCE_WORDS words."""
28
+ sentences = _split_sentences(text)
29
+ kw_lower = keyword.lower()
30
+ for sentence in sentences:
31
+ if kw_lower in sentence.lower() and len(sentence.split()) >= _MIN_SENTENCE_WORDS:
32
+ return True
33
+ return False
34
+
35
+
36
+ def _keyword_density_penalty(answer: str, keywords_found: int) -> float:
37
+ """Penalize if keyword density is suspiciously high (keyword stuffing)."""
38
+ word_count = len(answer.split())
39
+ if word_count == 0:
40
+ return 0.0
41
+ density = keywords_found / word_count
42
+ if density > _MAX_KEYWORD_DENSITY:
43
+ return min((density - _MAX_KEYWORD_DENSITY) * 5.0, 0.5)
44
+ return 0.0
45
+
46
 
47
  class KeywordCoverageGrader(BaseGrader):
48
+ """Grades based on keyword coverage with anti-stuffing measures."""
49
 
50
  def __init__(
51
  self,
 
62
  return _SCORE_MIN
63
 
64
  total_score = 0.0
65
+ total_keywords_found = 0
66
  for category, keywords in self._required_keywords.items():
67
  weight = self._rubric_weights.get(category, 0.0)
68
  if not keywords:
69
  continue
70
+ # Keywords must appear in sentences with sufficient context
71
+ hits = sum(1 for kw in keywords if _keyword_in_context(kw, answer))
72
+ total_keywords_found += hits
73
  coverage = hits / len(keywords)
74
  total_score += weight * coverage
75
 
76
+ # Apply keyword density penalty (anti-stuffing)
77
+ density_penalty = _keyword_density_penalty(answer, total_keywords_found)
78
+ total_score = max(0.0, total_score - density_penalty)
79
+
80
+ # Degenerate content check: low unique word ratio
81
+ words = answer.split()
82
+ if len(words) > 10:
83
+ unique_ratio = len(set(words)) / len(words)
84
+ if unique_ratio < 0.3:
85
+ total_score *= 0.3
86
+
87
  # Process quality bonus
88
  process_bonus = self._evaluate_process(trajectory)
89
  total_score = total_score * 0.8 + process_bonus * 0.2