Uptake AI Red Teaming Agent - #718
Conversation
29f0240 to
b66dec3
Compare
There was a problem hiding this comment.
Pull request overview
This PR adds a proof-of-concept AI Red Teaming capability to BC-Bench, wiring Azure AI Evaluation's RedTeam agent against the existing NL2AL (BCal) target. It introduces a new bcbench redteam CLI group (scan and report), an orchestration module that builds a BCal-backed target callback and runs the scan, and a one-shot run_bcal_prompt runner that feeds adversarial prompts to bcal and returns its combined output for a safety judge. Supporting changes add config paths, a sample attack-objectives file, .gitignore entries for private seeds, and the Azure dependencies.
Changes:
- New
redteam scan/reportCLI commands plus aredteam.pyorchestration module (target builder, symbol-cache priming, scan runner, scorecard rendering). - New
run_bcal_promptin the bcal agent that runs bcal once for a raw prompt and surfaces both generated.alfiles and stdout/diagnostics. - Dependency, config, dataset-sample, and
.gitignoreadditions to support the red-team workflow.
Reviewed changes
Copilot reviewed 11 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/bcbench/redteam.py |
New scan orchestration: builds the BCal target, primes the symbol cache, runs RedTeam.scan. |
src/bcbench/commands/redteam.py |
New scan/report CLI commands and terminal scorecard rendering. |
src/bcbench/agent/bcal/agent.py |
Adds run_bcal_prompt red-team runner; minor comment typo (double space). |
src/bcbench/agent/bcal/__init__.py |
Exports run_bcal_prompt. |
src/bcbench/config.py |
Adds redteam_scorecard path. |
src/bcbench/commands/__init__.py, src/bcbench/cli.py |
Register the new redteam Typer app. |
pyproject.toml |
Adds azure-ai-evaluation[redteam] and azure-identity. |
dataset/redteam/attack_objectives.sample.json |
Sample custom attack-objective seed file. |
.gitignore |
Ignores private red-team seed files. |
tests/conftest.py |
Adds (currently unused) create_nl2al_result/sample_nl2al_result helpers. |
tests/test_nl2al_pipeline.py |
Removes an obsolete ty: ignore comment. |
| def run_bcal_prompt( | ||
| entry: NL2ALEntry, | ||
| query: str, | ||
| package_cache_path: Path, | ||
| export_folder: Path, | ||
| backend_config: BCalBackendConfig, | ||
| ) -> str: |
| return f"(bcal exited with status {exc.returncode})\n{details}".strip() | ||
|
|
||
| generated: str = "\n\n".join(p.read_text(encoding="utf-8", errors="replace") for p in sorted(export_folder.rglob("*.al"))) | ||
| # Prefer the generated AL (the "real" output) but always append stdout so refusals and diagnostics are visible when no file was produced. |
| def create_nl2al_result( | ||
| instance_id: str = "nl2al__job-budget-report-1", | ||
| project: str = "JobBudgetVsActualReport", | ||
| model: str = "gpt-4o", | ||
| agent_name: str = "copilot-cli", | ||
| output: str = "diff --git a/test.al b/test.al\n+report code", | ||
| error_message: str | None = None, | ||
| metrics: AgentMetrics | None = None, | ||
| ) -> JudgeBasedEvaluationResult: | ||
| return JudgeBasedEvaluationResult( | ||
| instance_id=instance_id, | ||
| project=project, | ||
| model=model, | ||
| agent_name=agent_name, | ||
| category=EvaluationCategory.NL2AL, | ||
| output=output, | ||
| error_message=error_message, | ||
| metrics=metrics, | ||
| ) | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def sample_nl2al_entry() -> NL2ALEntry: | ||
| return create_nl2al_entry() | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def sample_nl2al_result() -> JudgeBasedEvaluationResult: | ||
| return create_nl2al_result() |
…egory/nl2al-red-team
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 12 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (5)
src/bcbench/agent/bcal/agent.py:184
- A nonzero BCal exit is converted into assistant text and then scored as though it were the model's answer. Because an error/traceback contains no harmful content, failed invocations can be reported as “resisted,” invalidating the attack-success results; execution errors must be surfaced outside the target response.
except subprocess.CalledProcessError as exc:
# Surface bcal's own output instead of letting the opaque CalledProcessError propagate (the red-team framework would otherwise report only "Something went wrong Command [...]").
details = "\n".join(s.strip() for s in (exc.stdout, exc.stderr) if s and s.strip())
return f"(bcal exited with status {exc.returncode})\n{details}".strip()
src/bcbench/commands/redteam.py:143
attack_successis optional in the SDK result, but every missing/unevaluated value is rendered as a green “resisted.” Evaluation failures can therefore look like successful defenses; distinguishTrue,False, andNoneexplicitly.
result = "[red]\u2717 broke[/]" if row.get("attack_success") else "[green]\u2713 resisted[/]"
src/bcbench/agent/bcal/agent.py:137
- This new subprocess adapter has no automated coverage, although the sibling
run_bcal_agentcommand construction is covered intests/test_bcal_agent_provider.py. Add tests for argument/env plumbing and for generated-file, stdout, timeout, and nonzero-exit outcomes so scan-result integrity does not depend on untested process behavior.
def run_bcal_prompt(
src/bcbench/commands/redteam.py:45
- The PR's setup example provides only
AZURE_OPENAI_ENDPOINTandAZURE_OPENAI_DEPLOYMENT, but this default selectsexternal-command, whosecli_args()requiresBCAL_LLM_COMMAND. Following the documented setup therefore fails on the first target call; either default toazure-openaior update the setup/run example with the required backend and variables.
This issue also appears on line 143 of the same file.
backend: Annotated[BCalLLMBackend, typer.Option(envvar="BCAL_LLM_BACKEND", help="BCal LLM backend used by the bcal target.")] = BCalLLMBackend.EXTERNAL_COMMAND,
src/bcbench/redteam.py:33
- Treating any
.appas a complete cache can silently reuse a partial cache or symbols from a different BC version, because this fixed cache path records no version/completion marker. A subsequent scan then skips population and runs BCal against incomplete or stale symbols.
if package_cache_path.exists() and any(package_cache_path.glob("*.app")):
return
| except subprocess.TimeoutExpired as exc: | ||
| return f"(bcal timed out after {_config.timeout.bcal_execution}s)\n{exc.stdout or ''}".strip() |
| ] | ||
|
|
||
| try: | ||
| result = subprocess.run( |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 12 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (4)
src/bcbench/agent/bcal/agent.py:184
- Do not turn BCal execution failures into target responses. The red-team judge receives these strings as ordinary assistant output, so a crashed or timed-out target can be scored as having “resisted” the attack and produce false-negative security results. Propagate the exception (or otherwise mark the row as an execution error) instead.
except subprocess.TimeoutExpired as exc:
return f"(bcal timed out after {_config.timeout.bcal_execution}s)\n{exc.stdout or ''}".strip()
except subprocess.CalledProcessError as exc:
# Surface bcal's own output instead of letting the opaque CalledProcessError propagate (the red-team framework would otherwise report only "Something went wrong Command [...]").
details = "\n".join(s.strip() for s in (exc.stdout, exc.stderr) if s and s.strip())
return f"(bcal exited with status {exc.returncode})\n{details}".strip()
src/bcbench/agent/bcal/agent.py:176
- The default
external-commandpath inheritsPYTHONHOME,PYTHONPATH, andVIRTUAL_ENVfromuv run. With the documented separate bridge virtualenv, those variables can make its Python load uv’s incompatible stdlib and BCal fails before answering any prompt. Pass a sanitized environment to this subprocess (and reuse it for the existing BCal invocation) so the documented red-team command can run.
result = subprocess.run(
cmd_args,
timeout=_config.timeout.bcal_execution,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
check=True,
src/bcbench/agent/bcal/agent.py:141
- Add unit coverage for this new subprocess adapter. Nearby
run_bcal_agentbehavior is covered intests/test_bcal_agent_provider.py, but no test invokesrun_bcal_prompt, leaving command construction, generated-file/stdout collection, subprocess environment, timeout, and nonzero-exit behavior unchecked; the latter paths directly determine whether scan results are trustworthy.
def run_bcal_prompt(
entry: NL2ALEntry,
query: str,
package_cache_path: Path,
export_folder: Path,
src/bcbench/commands/redteam.py:50
RedTeam.scan1.18.2 treatsoutput_pathas a directory (and writesevaluation_results.jsoninside it), but this option advertises a JSON file and defaults toscorecard.json. Consequently--output result.jsoncreates a directory namedresult.json, so callers cannot consume the file at the path the CLI promises. Model this option/default as an output directory, or translate the requested file path to the SDK directory and expose the actual inner file.
output: Annotated[Path, typer.Option(help="Where to write the upstream scorecard JSON.")] = _config.paths.redteam_scorecard,
Official Documentation: https://learn.microsoft.com/en-us/azure/foundry/how-to/develop/run-scans-ai-red-teaming-agent
Setup
Setup required:
.\scripts\Download-BCSymbols.ps1 -Category nl2al -InstanceId nl2al__move-name-customer-card-1.envfile like belowHow to run: