Mukul Rayana commited on
Commit
6997a58
Β·
1 Parent(s): 2e53d50

feat: real DeBERTa guardrail wired, skip_ig flag, smoke test updated

Browse files
smoke_test_pipeline.py CHANGED
@@ -1,10 +1,12 @@
1
- ο»Ώ"""
2
  smoke_test_pipeline.py
3
  Run from repo root: python smoke_test_pipeline.py
4
- Tests pipeline.run() on 5 inputs β€” one per emotion class.
 
5
  """
6
 
7
- import sys, json
 
8
  sys.path.insert(0, "src")
9
 
10
  from pipeline.pipeline import EmpathRAGPipeline
@@ -13,12 +15,12 @@ TEST_INPUTS = [
13
  {
14
  "text": "I feel completely hopeless and I don't see a point anymore.",
15
  "expected_emotion": "distress",
16
- "expect_crisis": True, # guardrail SHOULD fire β€” crisis-adjacent language
17
  },
18
  {
19
  "text": "I'm so anxious about my thesis defense next week, I can't sleep.",
20
  "expected_emotion": "anxiety",
21
- "expect_crisis": False, # known false positive at 0.8272 β€” documented
22
  },
23
  {
24
  "text": "My advisor rejected my work again without even reading it properly.",
@@ -37,9 +39,11 @@ TEST_INPUTS = [
37
  },
38
  ]
39
 
 
40
  def fmt_latency(lat: dict) -> str:
41
- parts = [f"{k.replace('_ms','')}={v}ms" for k, v in lat.items() if k != "total_ms"]
42
- return f"[{' | '.join(parts)} | total={lat.get('total_ms',0)}ms]"
 
43
 
44
  def run_smoke_test():
45
  print("=" * 70)
@@ -49,21 +53,20 @@ def run_smoke_test():
49
  print("\nInitialising pipeline...")
50
  pipeline = EmpathRAGPipeline(use_real_guardrail=True, guardrail_threshold=0.5)
51
 
52
- # Monkey-patch: skip IG computation during smoke test (saves 30s per crisis call)
53
- # IG runs 50 forward passes on CPU β€” only needed in demo, not for functional testing
54
- original_check = pipeline.guardrail.check
55
- def fast_check(text, threshold=0.5):
56
- is_crisis, conf, _ = original_check(text, threshold)
57
- return is_crisis, conf, [] # skip IG, return empty highlights
58
- pipeline.guardrail.check = fast_check
59
 
60
  passed = 0
61
  failed = 0
62
  results = []
63
 
64
  for i, test in enumerate(TEST_INPUTS):
65
- print(f"\n{'─'*70}")
66
- print(f"Test {i+1}/5 β€” expected emotion: {test['expected_emotion']}")
67
  print(f"Input: {test['text']}")
68
 
69
  result = pipeline.run(test["text"])
@@ -76,37 +79,40 @@ def run_smoke_test():
76
  response = result["response"]
77
  latency = result["latency_ms"]
78
 
79
- emotion_ok = (emotion_name == test["expected_emotion"])
80
- crisis_ok = (crisis == test["expect_crisis"])
81
- # For non-crisis: chunks must exist and response must be real
82
- # For crisis intercepts: safe template returned, no chunks β€” that is correct
83
  if test["expect_crisis"]:
 
84
  content_ok = (crisis is True and len(response) > 20)
85
  else:
 
86
  content_ok = (len(chunks) > 0 and len(response) > 20)
87
 
88
- status = "PASS" if (emotion_ok and content_ok) else "FAIL"
89
-
90
- # Special case: known false positive β€” don't count as failure
91
  fp_note = ""
92
- if not crisis_ok and crisis is True and not test["expect_crisis"]:
93
- fp_note = " [known false positive β€” conf={:.3f}]".format(conf)
94
  status = "PASS*"
 
 
 
 
95
 
96
  if "FAIL" not in status:
97
  passed += 1
98
  else:
99
  failed += 1
100
 
 
101
  print(f"\nStatus : {status}{fp_note}")
102
- print(f"Emotion : {emotion_name} (expected: {test['expected_emotion']}) "
103
- f"{'βœ“' if emotion_ok else 'βœ— MISMATCH'}")
104
  print(f"Trajectory : {trajectory}")
105
  print(f"Crisis : {crisis} (conf={conf:.3f}, expected={test['expect_crisis']})")
106
- print(f"Chunks : {len(chunks)} retrieved {'βœ“' if len(chunks)>0 or crisis else 'βœ— NONE'}")
107
  if chunks:
108
- print(f"Top chunk : {chunks[0][:120].replace(chr(10),' ')}...")
109
- print(f"Response : {response[:150].replace(chr(10),' ')}...")
 
110
  print(f"Latency : {fmt_latency(latency)}")
111
 
112
  results.append({
@@ -119,18 +125,20 @@ def run_smoke_test():
119
  "status": status,
120
  })
121
 
122
- print(f"\n{'='*70}")
123
  print(f"Results: {passed}/5 passed, {failed}/5 failed")
124
- if passed == 5:
125
- print("βœ… All smoke tests passed. Pipeline working end-to-end with real guardrail.")
126
- elif failed == 0 and passed < 5:
127
- print("βœ… All tests passed (some with known false positive notes).")
128
  else:
129
- print("⚠️ Check failures above.")
 
 
130
 
131
  with open("eval/smoke_test_results.json", "w") as f:
132
  json.dump({"passed": passed, "failed": failed, "per_test": results}, f, indent=2)
133
  print("Results saved to eval/smoke_test_results.json")
134
 
 
135
  if __name__ == "__main__":
136
- run_smoke_test()
 
1
+ """
2
  smoke_test_pipeline.py
3
  Run from repo root: python smoke_test_pipeline.py
4
+ Tests pipeline.run() on 5 inputs - one per emotion class.
5
+ Prints per-stage latency, retrieved chunk preview, response preview.
6
  """
7
 
8
+ import sys
9
+ import json
10
  sys.path.insert(0, "src")
11
 
12
  from pipeline.pipeline import EmpathRAGPipeline
 
15
  {
16
  "text": "I feel completely hopeless and I don't see a point anymore.",
17
  "expected_emotion": "distress",
18
+ "expect_crisis": True, # guardrail SHOULD fire - crisis-adjacent language
19
  },
20
  {
21
  "text": "I'm so anxious about my thesis defense next week, I can't sleep.",
22
  "expected_emotion": "anxiety",
23
+ "expect_crisis": False, # known false positive at conf~0.83 - documented
24
  },
25
  {
26
  "text": "My advisor rejected my work again without even reading it properly.",
 
39
  },
40
  ]
41
 
42
+
43
  def fmt_latency(lat: dict) -> str:
44
+ parts = [f"{k.replace('_ms', '')}={v}ms" for k, v in lat.items() if k != "total_ms"]
45
+ return "[" + " | ".join(parts) + f" | total={lat.get('total_ms', 0)}ms]"
46
+
47
 
48
  def run_smoke_test():
49
  print("=" * 70)
 
53
  print("\nInitialising pipeline...")
54
  pipeline = EmpathRAGPipeline(use_real_guardrail=True, guardrail_threshold=0.5)
55
 
56
+ # Skip IG during smoke test - IG runs 50 forward passes on CPU (~30s per call)
57
+ # IG is only needed in the demo for the highlight panel, not for functional testing
58
+ _original_check = pipeline.guardrail.check
59
+ def _fast_check(text, threshold=0.5):
60
+ return _original_check(text, threshold, skip_ig=True)
61
+ pipeline.guardrail.check = _fast_check
 
62
 
63
  passed = 0
64
  failed = 0
65
  results = []
66
 
67
  for i, test in enumerate(TEST_INPUTS):
68
+ print(f"\n{chr(9472) * 70}")
69
+ print(f"Test {i+1}/5 - expected emotion: {test['expected_emotion']}")
70
  print(f"Input: {test['text']}")
71
 
72
  result = pipeline.run(test["text"])
 
79
  response = result["response"]
80
  latency = result["latency_ms"]
81
 
82
+ emotion_ok = (emotion_name == test["expected_emotion"])
83
+
 
 
84
  if test["expect_crisis"]:
85
+ # Crisis intercept is correct outcome - safe template returned, no chunks
86
  content_ok = (crisis is True and len(response) > 20)
87
  else:
88
+ # Non-crisis - must have chunks and a real response
89
  content_ok = (len(chunks) > 0 and len(response) > 20)
90
 
91
+ # Known false positive: guardrail fires on non-crisis input
 
 
92
  fp_note = ""
93
+ if crisis and not test["expect_crisis"]:
94
+ fp_note = f" [known false positive - conf={conf:.3f}]"
95
  status = "PASS*"
96
+ elif emotion_ok and content_ok:
97
+ status = "PASS"
98
+ else:
99
+ status = "FAIL"
100
 
101
  if "FAIL" not in status:
102
  passed += 1
103
  else:
104
  failed += 1
105
 
106
+ emotion_sym = "OK" if emotion_ok else "MISMATCH"
107
  print(f"\nStatus : {status}{fp_note}")
108
+ print(f"Emotion : {emotion_name} (expected: {test['expected_emotion']}) [{emotion_sym}]")
 
109
  print(f"Trajectory : {trajectory}")
110
  print(f"Crisis : {crisis} (conf={conf:.3f}, expected={test['expect_crisis']})")
111
+ print(f"Chunks : {len(chunks)} retrieved")
112
  if chunks:
113
+ preview = chunks[0][:120].replace("\n", " ")
114
+ print(f"Top chunk : {preview}...")
115
+ print(f"Response : {response[:150].replace(chr(10), ' ')}...")
116
  print(f"Latency : {fmt_latency(latency)}")
117
 
118
  results.append({
 
125
  "status": status,
126
  })
127
 
128
+ print(f"\n{'=' * 70}")
129
  print(f"Results: {passed}/5 passed, {failed}/5 failed")
130
+
131
+ if failed == 0:
132
+ print("All smoke tests passed. Pipeline working end-to-end with real guardrail.")
 
133
  else:
134
+ print("Check failures above.")
135
+ print(" emotion mismatch -> RoBERTa checkpoint issue")
136
+ print(" no chunks -> verify FAISS index path and SQLite annotation")
137
 
138
  with open("eval/smoke_test_results.json", "w") as f:
139
  json.dump({"passed": passed, "failed": failed, "per_test": results}, f, indent=2)
140
  print("Results saved to eval/smoke_test_results.json")
141
 
142
+
143
  if __name__ == "__main__":
144
+ run_smoke_test()
src/models/guardrail_ig.py CHANGED
@@ -40,7 +40,7 @@ class SafetyGuardrail:
40
  token_type_ids=token_type_ids,
41
  ).logits
42
 
43
- def check(self, text: str, threshold: float = 0.5):
44
  """
45
  Run guardrail on a single text string.
46
 
@@ -66,6 +66,9 @@ class SafetyGuardrail:
66
  if crisis_prob < threshold:
67
  return False, crisis_prob, []
68
 
 
 
 
69
  # ── Integrated Gradients ───────────────────────────────────────────────
70
  # Get the embedding layer
71
  embed_layer = self.model.deberta.embeddings.word_embeddings
@@ -125,4 +128,4 @@ if __name__ == "__main__":
125
  if is_crisis != expect_crisis:
126
  all_pass = False
127
 
128
- print("\nβœ… guardrail_ig.py verified." if all_pass else "\n❌ at least one case wrong.")
 
40
  token_type_ids=token_type_ids,
41
  ).logits
42
 
43
+ def check(self, text: str, threshold: float = 0.5, skip_ig: bool = False):
44
  """
45
  Run guardrail on a single text string.
46
 
 
66
  if crisis_prob < threshold:
67
  return False, crisis_prob, []
68
 
69
+ if skip_ig:
70
+ return True, crisis_prob, []
71
+
72
  # ── Integrated Gradients ───────────────────────────────────────────────
73
  # Get the embedding layer
74
  embed_layer = self.model.deberta.embeddings.word_embeddings
 
128
  if is_crisis != expect_crisis:
129
  all_pass = False
130
 
131
+ print("\nβœ… guardrail_ig.py verified." if all_pass else "\n❌ at least one case wrong.")