Skip to content

🐛 fix: fixed pair bugs - #513

Merged
Nicola Franco (franconicola) merged 2 commits into
mainfrom
460-fix-pair-off-rail-prompts
Jul 26, 2026
Merged

🐛 fix: fixed pair bugs#513
Nicola Franco (franconicola) merged 2 commits into
mainfrom
460-fix-pair-off-rail-prompts

Conversation

@marcorusso97

Copy link
Copy Markdown
Contributor

Fix PAIR stream isolation, iterative refinement, scoring, and dashboard details

Summary

This pull request aligns the PAIR implementation more closely with the original algorithm and fixes several issues affecting iterative refinement, multi-stream execution, score handling, and dashboard visualization.

Changes

  • Implemented independent attacker conversation histories for each PAIR stream.
  • Added proper PAIR feedback containing:
    • the latest target response;
    • the original objective;
    • the target response prefix;
    • the current judge score;
    • the judge explanation.
  • Added bounded per-stream conversation history through keep_last_n.
  • Enforced strict JSON parsing for attacker outputs, preventing arbitrary prose from being sent to the target.
  • Added support for independent multi-stream execution.
  • Made batch_size control the number of concurrent stream workers.
  • Isolated scorer explanations per worker thread.
  • Added configurable n_streams, keep_last_n, and target_str settings.
  • Preserved decimal judge scores such as 7.5 throughout the attack pipeline and dashboard.
  • Added per-stream grouping and stream selection in the PAIR dashboard.
  • Assigned best-attempt markers independently for each stream.
  • Fixed dashboard stream selection so changing the dropdown renders the selected stream.
  • Updated PAIR documentation with the new stream and concurrency behavior.
  • Extended prompt parser support with an optional plaintext fallback.

Testing

  • Added tests for independent stream histories.
  • Added tests verifying concurrent stream execution with batch_size > 1.
  • Added tests for per-stream dashboard parsing and best-score assignment.
  • Added regression coverage for decimal scores.

Copilot AI review requested due to automatic review settings July 23, 2026 14:05
@marcorusso97 Marco Russo (marcorusso97) linked an issue Jul 23, 2026 that may be closed by this pull request
@franconicola
Nicola Franco (franconicola) temporarily deployed to 460-fix-pair-off-rail-prompts - Docs PR #513 July 23, 2026 14:05 — with Render Destroyed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the PAIR attack implementation to support true multi-stream execution with per-stream attacker histories, stricter attacker-output parsing, preserved decimal scoring, and a stream-aware dashboard view.

Changes:

  • Added independent per-stream PAIR conversations with bounded history (n_streams, keep_last_n) and stream-level concurrency (batch_size).
  • Enforced stricter attacker-output parsing (JSON-first) and preserved decimal judge scores end-to-end.
  • Updated dashboard parsing/rendering and documentation to reflect per-stream traces and stream selection.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
hackagent/attacks/techniques/pair/attack.py Implements per-stream conversations, concurrency via worker pool, thread-local scorer explanations, and decimal score handling.
hackagent/attacks/techniques/pair/config.py Adds PAIR config knobs (keep_last_n, target_str, translate_prompts) and updates attacker prompt template accordingly.
hackagent/attacks/shared/prompt_parser.py Extends prompt parsing API with an optional plaintext fallback toggle.
hackagent/server/dashboard/attack_cards/_pair.py Parses per-stream traces, preserves decimal scores, and renders a stream selector with per-stream “best” markers.
docs/docs/attacks/pair.md Documents the new stream/concurrency behavior and additional configuration parameters.
tests/unit/attacks/pair/test_attack.py Adds regression tests for per-stream history isolation, parallel stream execution, and decimal score preservation.
tests/unit/attacks/pair/test_config.py Adds config round-trip coverage for the new PAIR settings and prompt formatting placeholders.
tests/unit/server/dashboard/test_pair_card.py Adds dashboard parsing test for per-stream grouping, decimal scores, and best-attempt selection.
Comments suppressed due to low confidence (1)

hackagent/attacks/techniques/pair/attack.py:796

  • _judge_response uses a legacy judge fallback when _score_response returns 1, but the fallback path returns parsed_legacy without updating the thread-local scorer explanation. As a result, _get_scorer_explanation() may return the previous AutoDAN assessment (or an empty string) even though the score came from the legacy judge, which makes the recorded/dashboard explanation inconsistent with the actual score source.
    def _judge_response(self, goal: str, prompt: str, response: str) -> float:
        """Backward-compatible scorer hook expected by existing PAIR tests.

        The ``prompt`` argument is currently unused by the AutoDAN-based
        scorer path, but is preserved for API compatibility.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +917 to +924
try:
stream_worker_count = max(1, int(self.config.get("batch_size", 1)))
except (TypeError, ValueError):
stream_worker_count = 1
stream_worker_count = min(n_streams, stream_worker_count)
# Best-result selection is shared across streams; model calls and
# histories are not. Only protect that tiny shared critical section.
best_result_lock = threading.Lock()
@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 57.33696% with 157 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
hackagent/server/dashboard/attack_cards/_pair.py 22.64% 82 Missing ⚠️
hackagent/attacks/techniques/pair/attack.py 69.81% 67 Missing ⚠️
hackagent/router/tracking/tracker.py 73.33% 8 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copilot AI review requested due to automatic review settings July 24, 2026 09:50

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Comment on lines 562 to +567
if content:
prompt = extract_prompt(content)
if prompt:
return prompt
parsed = extract_prompt_and_improvement(content, allow_plaintext=False)
# PAIR's attacker output is an assistant JSON turn. Reject
# non-JSON prose instead of accidentally sending its analysis
# to the target as a candidate jailbreak.
if parsed and parsed.get("prompt") and '"improvement"' in content:
Comment on lines +487 to +492
def _trim_stream_history(self, messages: List[Dict[str, str]]) -> None:
"""Keep the system message plus the latest PAIR conversation turns."""
keep_last_n = max(1, int(self.config.get("keep_last_n", 4)))
if len(messages) <= 1 + 2 * keep_last_n:
return
messages[:] = [messages[0], *messages[-2 * keep_last_n :]]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@franconicola
Nicola Franco (franconicola) merged commit bee7dfd into main Jul 26, 2026
25 checks passed
@franconicola
Nicola Franco (franconicola) deleted the 460-fix-pair-off-rail-prompts branch July 26, 2026 12:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix PAIR off-rail prompts

3 participants