Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,13 @@ jobs:
uv run --frozen --no-sync python -m build_scripts.pydoc2json pyrit --submodules -o doc/_api/pyrit_all.json
uv run --frozen --no-sync python -m build_scripts.gen_api_md

- name: Check documentation links
# The latest slot contains main or the tested PR merge. Frozen release
# docs retain their historical build tolerance.
if: steps.cache.outputs.cache-hit != 'true' && matrix.slug == 'latest'
working-directory: doc
run: uv run --frozen --no-sync jupyter-book build --site --strict --check-links

- name: Build the static HTML site
if: steps.cache.outputs.cache-hit != 'true'
# --all builds frontmatter exports (PDF, per doc/myst.yml).
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ pre-commit:
ty:
uv run --frozen --extra all --link-mode=copy -m ty check $(PYMODULE) $(UNIT_TESTS)

# Build strict HTML and the RSS feed after generating the API reference.
# Build strict HTML with link checks and the RSS feed after API generation.
# --all would also select the configured PDF export and require LaTeX.
docs-build: docs-api
cd doc && uv run jupyter-book build --html --strict
cd doc && uv run jupyter-book build --html --strict --check-links
uv run python -m build_scripts.generate_rss

# PDF is a separate, checked export requiring latexmk and xelatex.
Expand Down
8 changes: 4 additions & 4 deletions doc/code/memory/3_memory_data_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ There are several types of data you can retrieve from memory at any point in tim

## MessagePiece and Message

One of the most fundamental data structures in PyRIT is [MessagePiece](../../../pyrit/models/message_piece.py) and [Message](../../../pyrit/models/message.py). These classes provide the foundation for multi-modal interaction tracking throughout the framework.
One of the most fundamental data structures in PyRIT is [MessagePiece](../../../pyrit/models/messages/message_piece.py) and [Message](../../../pyrit/models/messages/message.py). These classes provide the foundation for multi-modal interaction tracking throughout the framework.

### MessagePiece

Expand Down Expand Up @@ -97,7 +97,7 @@ Seeds are organized into [`SeedGroup`](../../../pyrit/models/seeds/seed_group.py

## Scores

[`Score`](../../../pyrit/models/score.py) objects represent evaluations of prompts or responses. Scores are generated by scorer components and attached to `MessagePieces` to track the success or characteristics of attacks. When a prompt is scored, it is added to the database and can be queried later.
[`Score`](../../../pyrit/models/score/score.py) objects represent evaluations of prompts or responses. Scores are generated by scorer components and attached to `MessagePieces` to track the success or characteristics of attacks. When a prompt is scored, it is added to the database and can be queried later.

**Key Fields:**

Expand Down Expand Up @@ -150,7 +150,7 @@ During execution the ID is available as `AttackContext.attack_result_id`, and `g

## ComponentIdentifiers

[`ComponentIdentifier`](../../../pyrit/identifiers/component_identifier.py) is an immutable snapshot of a component's behavioral configuration. A single type is used for all components — targets, scorers, converters, and attacks — enabling uniform storage and composition.
[`ComponentIdentifier`](../../../pyrit/models/identifiers/component_identifier.py) is an immutable snapshot of a component's behavioral configuration. A single type is used for all components — targets, scorers, converters, and attacks — enabling uniform storage and composition.

**Key Fields:**

Expand All @@ -170,7 +170,7 @@ For atomic attacks, `AtomicAttackIdentifier.build` composes a tree of identifier

### Eval Hashing

[`EvaluationIdentifier`](../../../pyrit/identifiers/evaluation_identifier.py) subclasses wrap a `ComponentIdentifier` and compute a separate **eval hash** that strips operational params (like endpoint URLs) so the same logical configuration on different deployments produces the same hash. This enables grouping equivalent runs for evaluation comparison.
[`EvaluationIdentifier`](../../../pyrit/models/identifiers/evaluation_identifier.py) subclasses wrap a `ComponentIdentifier` and compute a separate **eval hash** that strips operational params (like endpoint URLs) so the same logical configuration on different deployments produces the same hash. This enables grouping equivalent runs for evaluation comparison.

What feeds the eval hash is declared **on the strongly-typed identifier fields themselves**, via `Evaluate.*` markers attached as [`typing.Annotated`](https://docs.python.org/3/library/typing.html#typing.Annotated) metadata. This keeps the identifier classes the single source of truth — you change what an eval hash includes by editing the field, not a separate rules table:

Expand Down
2 changes: 1 addition & 1 deletion doc/code/scoring/0_scoring.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"- Was there harmful content in the response? How bad was it?\n",
"\n",
"A scorer takes a response (or a whole conversation) and returns one or more\n",
"[`Score`](../../../pyrit/models/score.py) objects. Scorers are used three ways:\n",
"[`Score`](../../../pyrit/models/score/score.py) objects. Scorers are used three ways:\n",
"directly (this page), automatically inside an [attack](../executor/1_single_turn.ipynb#prompt-sending),\n",
"and over many stored responses with the [batch scorer](#batch-scoring).\n",
"\n",
Expand Down
2 changes: 1 addition & 1 deletion doc/code/scoring/0_scoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# - Was there harmful content in the response? How bad was it?
#
# A scorer takes a response (or a whole conversation) and returns one or more
# [`Score`](../../../pyrit/models/score.py) objects. Scorers are used three ways:
# [`Score`](../../../pyrit/models/score/score.py) objects. Scorers are used three ways:
# directly (this page), automatically inside an [attack](../executor/1_single_turn.ipynb#prompt-sending),
# and over many stored responses with the [batch scorer](#batch-scoring).
#
Expand Down
17 changes: 12 additions & 5 deletions doc/contributing/7_notebooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ For HTML only, run from `doc`:

```powershell
Set-Location doc
uv run --frozen --no-sync jupyter-book build --html --strict
uv run --frozen --no-sync jupyter-book build --html --strict --check-links
```

Do not add `--all` to an HTML-only build. It selects the PDF export declared in
Expand All @@ -55,6 +55,13 @@ version, `--strict` fails on errors and reports warnings separately; a successfu
strict build is not necessarily warning-free. Do not suppress errors to make a
build pass.

`--check-links` checks link destinations, including missing Python source files
that otherwise remain ordinary hyperlinks. External URL checks require network
access; auth-required and robot-policy exceptions are listed in `doc/myst.yml`.
The docs workflow runs strict link checking for the `latest` slot, including PR
validation, before rendering. Frozen release versions retain their existing build
behavior.

### PDF prerequisites and validation

The `plain_latex_book` export requires `latexmk`, `xelatex`, and the template's
Expand Down Expand Up @@ -83,7 +90,7 @@ as well; compilation does not prove that every interactive HTML element has a
correct print representation.

Where GNU Make is available, `make docs-build` prepares the API reference, builds
strict HTML, and generates RSS. `make docs-build-pdf` prepares the API reference
and runs the checked PDF export. `make docs-build-all` builds HTML first and then
checks the PDF export. These local targets do not change the hosted multiversion
publication workflow.
strict HTML with link checking, and generates RSS. `make docs-build-pdf` prepares
the API reference and runs the checked PDF export. `make docs-build-all` builds
HTML first and then checks the PDF export. These local targets do not change the
hosted multiversion publication workflow.
1 change: 1 addition & 0 deletions doc/getting_started/install_docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ cp .env_container_settings_example .env.container.settings
Edit the `.env` and `.env.local` files to add your API keys and configuration values. See [populating secrets](./populating_secrets.md) for details.
```

(3-set-source-build-provenance)=
### 3. Set Source Build Provenance

From `PyRIT/docker`, derive the required build inputs from the checkout:
Expand Down
12 changes: 12 additions & 0 deletions doc/myst.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ project:
- "http://api.openai.com/**"
- "https://cognitiveservices.azure.com/**"
- "https://www.youtube.com/watch?v=jq9DcEL3cHE"
# Wikimedia's robot policy returns 403 to automated link checks. Keep the
# canonical attribution URL and limit the exception to this source image.
- rule: link-resolves
severity: ignore
keys:
- "https://commons.wikimedia.org/wiki/File:Gorch_Fock_unter_Segeln_Kieler_Foerde_2006.jpg"
# The live crates.io homepage rejects automated requests with 403/404.
# Do not exclude package pages or other registry URLs.
- rule: link-resolves
severity: ignore
keys:
- "https://crates.io"
toc:
- file: index.md
- file: security.md
Expand Down
2 changes: 1 addition & 1 deletion doc/scanner/garak.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -1695,7 +1695,7 @@
"\n",
"**Aggregate techniques:** `DEFAULT` runs Rust. `ALL` runs all seven languages.\n",
"\n",
"> **Note:** Rust and its crates.io registry are the default because this registry is much smaller.\n",
"> **Note:** Rust and its [crates.io](https://crates.io) registry are the default because this registry is much smaller.\n",
"> If you select another language, PyRIT downloads its registry on demand. The raw package names\n",
"> are loaded into memory only for the scorer and are never sent as prompts."
]
Expand Down
2 changes: 1 addition & 1 deletion doc/scanner/garak.py
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@
#
# **Aggregate techniques:** `DEFAULT` runs Rust. `ALL` runs all seven languages.
#
# > **Note:** Rust and its crates.io registry are the default because this registry is much smaller.
# > **Note:** Rust and its [crates.io](https://crates.io) registry are the default because this registry is much smaller.
# > If you select another language, PyRIT downloads its registry on demand. The raw package names
# > are loaded into memory only for the scorer and are never sent as prompts.

Expand Down
11 changes: 10 additions & 1 deletion tests/unit/build_scripts/test_docs_build_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import jupytext
import pytest
import yaml

REPO_ROOT = Path(__file__).resolve().parents[3]
COMMONS_SOURCE = "https://commons.wikimedia.org/wiki/File:Gorch_Fock_unter_Segeln_Kieler_Foerde_2006.jpg"
Expand All @@ -26,7 +27,7 @@ def _target(target: str) -> tuple[list[str], list[str]]:
def test_html_build_prepares_api_without_selecting_pdf() -> None:
dependencies, commands = _target("docs-build")
assert dependencies == ["docs-api"]
assert "cd doc && uv run jupyter-book build --html --strict" in commands
assert "cd doc && uv run jupyter-book build --html --strict --check-links" in commands
assert all("--all" not in command and "--pdf" not in command for command in commands)
assert commands[-1] == "uv run python -m build_scripts.generate_rss"
_, api_commands = _target("docs-api")
Expand All @@ -42,6 +43,14 @@ def test_pdf_targets_share_checked_export_and_all_builds_html_first() -> None:
assert pdf_commands == all_commands == ["uv run python -m build_scripts.build_docs_pdf"]


@pytest.mark.parametrize("url", [COMMONS_SOURCE, "https://crates.io"])
def test_robot_exceptions_only_skip_the_verified_url(url: str) -> None:
config = yaml.safe_load((REPO_ROOT / "doc" / "myst.yml").read_text(encoding="utf-8"))
rules = config["project"]["error_rules"]
exception = next(rule for rule in rules if url in rule.get("keys", []))
assert exception == {"rule": "link-resolves", "severity": "ignore", "keys": [url]}


@pytest.mark.parametrize(
("path", "grids"),
[
Expand Down
10 changes: 10 additions & 0 deletions tests/unit/build_scripts/test_docs_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,16 @@ def test_workflow_preserves_full_build_and_frozen_release_dependencies() -> None
assert all("continue-on-error" not in step for job in jobs.values() for step in job["steps"])


def test_workflow_checks_current_docs_links_before_rendering() -> None:
steps = _workflow()["jobs"]["build"]["steps"]
check = _step(job="build", name="Check documentation links")
assert check["if"] == "steps.cache.outputs.cache-hit != 'true' && matrix.slug == 'latest'"
assert check["working-directory"] == "doc"
assert check["run"] == "uv run --frozen --no-sync jupyter-book build --site --strict --check-links"
assert steps.index(_step(job="build", name="Generate API reference (pydoc2json + gen_api_md)")) < steps.index(check)
assert steps.index(check) < steps.index(_step(job="build", name="Build the static HTML site"))


def test_docs_and_dependency_inputs_trigger_pr_validation_and_main_publication() -> None:
events = _workflow()["on"]
assert events["push"]["branches"] == ["main", "releases/v*"]
Expand Down
Loading