-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsafety.py
More file actions
120 lines (100 loc) ยท 4.79 KB
/
Copy pathsafety.py
File metadata and controls
120 lines (100 loc) ยท 4.79 KB
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
import yaml
from functools import lru_cache
from pathlib import Path
from backend.agents import *
from backend.agents.nodes import *
_SAFETY_POLICY_DIR = Path(__file__).resolve().parent / "security_checks"
@lru_cache(maxsize=1)
def _load_output_policy() -> dict:
path = _SAFETY_POLICY_DIR / "output_policy.yaml"
data = yaml.safe_load(path.read_text())
# Validate we got the right file
if not isinstance(data, dict) or "blocked_output_patterns" not in data:
raise ValueError(
f"[Safety] Wrong file loaded. Keys found: "
f"{list(data.keys()) if isinstance(data, dict) else type(data)}. "
f"Path: {path.resolve()}"
)
return data
class SafetyAgent(BaseAgent):
def _keyword_check(self, text: str) -> tuple[bool, str]:
"""Returns (is_blocked, reason). Fast โ no LLM."""
lower = text.lower()
for kw in _HARD_BLOCK_KEYWORDS:
if kw in lower:
return True, f"keyword match: '{kw}'"
return False, ""
def _build_safety_prompt(self, solution_text: str, policy: dict) -> str:
blocked = "\n".join(f"- {p}" for p in policy.get("blocked_output_patterns", []))
allowed = "\n".join(f"- {p}" for p in policy.get("always_allowed", []))
return f"""You are the output safety checker for a JEE mathematics tutor.
Review the solver's response below and decide if it is safe to show to a student.
BLOCKED patterns (any of these โ passed=false):
{blocked}
ALWAYS ALLOWED (do not flag these):
{allowed}
Rules:
- Mathematical content, LaTeX, step-by-step algebra, theorems โ ALWAYS safe.
- Only block when there is a clear policy violation.
- When in doubt, PASS โ false positives frustrate students.
Solver output to review:
{solution_text[:2000]}"""
def safety_agent(self, state: AgentState) -> dict:
try:
solver_out = state.get("solver_output") or {}
solution_text = solver_out.get("solution", "")
if not solution_text:
return {"safety_passed": True}
# โโ Stage 1: keyword fast path โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
is_blocked, kw_reason = self._keyword_check(solution_text)
if is_blocked:
policy = _load_output_policy()
message = policy.get("on_violation", {}).get(
"replacement_message",
"The solution could not be displayed due to a policy violation.",
)
payload(
state, "safety_agent",
summary = f"BLOCKED โ keyword: {kw_reason}",
fields = {"Reason": kw_reason},
)
logger.warning(f"[Safety] Keyword block | {kw_reason}")
return {
"safety_passed": False,
"safety_reason": kw_reason,
"final_response": message,
}
# โโ Stage 2: LLM policy check โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
policy = _load_output_policy()
prompt = self._build_safety_prompt(solution_text, policy)
result: SafetyOutput = self.llm.with_structured_output(SafetyOutput).invoke(
[HumanMessage(content=prompt)]
)
updates: dict = {
"safety_passed": result.passed,
"safety_reason": result.reason,
}
if not result.passed:
replacement = policy.get("on_violation", {}).get(
"replacement_message",
"The solution could not be displayed due to a policy violation.",
)
updates["final_response"] = replacement
logger.warning(
f"[Safety] LLM blocked output | "
f"violation={result.violation_type} | reason={result.reason}"
)
payload(
state, "safety_agent",
summary = f"{'PASSED' if result.passed else 'BLOCKED'} | {result.violation_type or 'ok'}",
fields = {
"Passed": str(result.passed),
"Violation": result.violation_type,
"Reason": result.reason,
},
)
logger.info(f"[Safety] passed={result.passed}")
return {**updates, "agent_payload_log": state.get("agent_payload_log") or []}
except Exception as e:
logger.error(f"[Safety] failed: {e}")
raise Agent_Exception(e, sys)