Spaces:
Sleeping
Sleeping
Commit ·
b7d3a14
1
Parent(s): 6aebb94
fix: max_completion_length 512→128, firewall circuit-breaker
Browse files- max_completion_length 512→128: completions/clipped_ratio was 1.0 on
every step (all payloads truncated). 128 tokens is enough for any
injection payload and cuts per-step generation time ~4x.
- FirewallWrapper: convert _scan_user from @staticmethod to instance
method; add _fw_user_broken circuit-breaker so after the first
llamafirewall scan error the fallback to PG2 is silent for the rest
of the session (was logging on every step, ~10+ times per training
step).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- env/defenses/llama_firewall.py +29 -19
- train/grpo_train.py +1 -1
env/defenses/llama_firewall.py
CHANGED
|
@@ -57,6 +57,9 @@ class FirewallWrapper:
|
|
| 57 |
# Reuse an existing PromptGuard instance when llamafirewall's internal
|
| 58 |
# scanner fails (e.g. transformers version mismatch on Colab T4).
|
| 59 |
self._pg2_fallback = prompt_guard_fallback
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
def _ensure_loaded(self) -> None:
|
| 62 |
if self._fw is not None:
|
|
@@ -97,7 +100,7 @@ class FirewallWrapper:
|
|
| 97 |
self._ensure_loaded()
|
| 98 |
fw = self._fw
|
| 99 |
|
| 100 |
-
user_flagged, user_score, user_reason = self._scan_user(fw, user_query
|
| 101 |
if self._has_agent_alignment:
|
| 102 |
asst_flagged, asst_score, asst_reason = self._scan_assistant(fw, agent_output, tool_call)
|
| 103 |
else:
|
|
@@ -110,27 +113,34 @@ class FirewallWrapper:
|
|
| 110 |
|
| 111 |
return DefenseVerdict(flagged=flagged, score=score, reason=reason)
|
| 112 |
|
| 113 |
-
|
| 114 |
-
def _scan_user(fw: Any, text: str, pg2_fallback: Optional[Any] = None) -> tuple[bool, float, Optional[str]]:
|
| 115 |
if not text or not text.strip():
|
| 116 |
return False, 0.0, None
|
| 117 |
-
try:
|
| 118 |
-
from llamafirewall import ScanDecision, UserMessage # type: ignore
|
| 119 |
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
@staticmethod
|
| 136 |
def _scan_assistant(fw: Any, text: str, tool_call: Optional[Any]) -> tuple[bool, float, Optional[str]]:
|
|
|
|
| 57 |
# Reuse an existing PromptGuard instance when llamafirewall's internal
|
| 58 |
# scanner fails (e.g. transformers version mismatch on Colab T4).
|
| 59 |
self._pg2_fallback = prompt_guard_fallback
|
| 60 |
+
# Circuit-breaker: after the first llamafirewall scan failure, skip
|
| 61 |
+
# further attempts and route directly to PG2 to avoid per-step log noise.
|
| 62 |
+
self._fw_user_broken = False
|
| 63 |
|
| 64 |
def _ensure_loaded(self) -> None:
|
| 65 |
if self._fw is not None:
|
|
|
|
| 100 |
self._ensure_loaded()
|
| 101 |
fw = self._fw
|
| 102 |
|
| 103 |
+
user_flagged, user_score, user_reason = self._scan_user(fw, user_query)
|
| 104 |
if self._has_agent_alignment:
|
| 105 |
asst_flagged, asst_score, asst_reason = self._scan_assistant(fw, agent_output, tool_call)
|
| 106 |
else:
|
|
|
|
| 113 |
|
| 114 |
return DefenseVerdict(flagged=flagged, score=score, reason=reason)
|
| 115 |
|
| 116 |
+
def _scan_user(self, fw: Any, text: str) -> tuple[bool, float, Optional[str]]:
|
|
|
|
| 117 |
if not text or not text.strip():
|
| 118 |
return False, 0.0, None
|
|
|
|
|
|
|
| 119 |
|
| 120 |
+
# If llamafirewall's user scanner has failed before, go straight to PG2.
|
| 121 |
+
if not self._fw_user_broken:
|
| 122 |
+
try:
|
| 123 |
+
from llamafirewall import ScanDecision, UserMessage # type: ignore
|
| 124 |
+
|
| 125 |
+
result = _run_coro(fw.scan(UserMessage(content=text)))
|
| 126 |
+
flagged = result.decision == ScanDecision.BLOCK
|
| 127 |
+
score = float(getattr(result, "score", 1.0 if flagged else 0.0))
|
| 128 |
+
reason = f"fw_user:{result.reason}" if flagged and getattr(result, "reason", None) else None
|
| 129 |
+
return flagged, score, reason
|
| 130 |
+
except Exception as exc:
|
| 131 |
+
logger.warning(
|
| 132 |
+
"FirewallWrapper user scan error: %s — switching to PG2 fallback for this session", exc
|
| 133 |
+
)
|
| 134 |
+
self._fw_user_broken = True
|
| 135 |
+
|
| 136 |
+
# PG2 fallback path (used on first failure and all subsequent calls).
|
| 137 |
+
if self._pg2_fallback is not None:
|
| 138 |
+
try:
|
| 139 |
+
verdict = self._pg2_fallback.scan(text)
|
| 140 |
+
return verdict.flagged, verdict.score, f"fw_pg2fb:{verdict.reason}"
|
| 141 |
+
except Exception as fb_exc:
|
| 142 |
+
logger.warning("FirewallWrapper PG2 fallback also failed: %s", fb_exc)
|
| 143 |
+
return False, 0.0, None
|
| 144 |
|
| 145 |
@staticmethod
|
| 146 |
def _scan_assistant(fw: Any, text: str, tool_call: Optional[Any]) -> tuple[bool, float, Optional[str]]:
|
train/grpo_train.py
CHANGED
|
@@ -242,7 +242,7 @@ def main() -> None:
|
|
| 242 |
max_steps=args.steps,
|
| 243 |
per_device_train_batch_size=args.batch_size,
|
| 244 |
num_generations=args.num_generations,
|
| 245 |
-
max_completion_length=
|
| 246 |
beta=0.04, # KL coefficient (CLAUDE.md §5.2)
|
| 247 |
learning_rate=5e-6,
|
| 248 |
save_steps=args.save_every,
|
|
|
|
| 242 |
max_steps=args.steps,
|
| 243 |
per_device_train_batch_size=args.batch_size,
|
| 244 |
num_generations=args.num_generations,
|
| 245 |
+
max_completion_length=128,
|
| 246 |
beta=0.04, # KL coefficient (CLAUDE.md §5.2)
|
| 247 |
learning_rate=5e-6,
|
| 248 |
save_steps=args.save_every,
|