PyRITpyrit 1.1.0 · Python 3.10+
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
36 small wins to finish your pathNext lesson

A stand-in adversary

An adaptive attack needs a second model, the adversary, to write each next probe from the target's last reply. The adversary has a contract that trips up every first attempt: it must answer in JSON.

What the adversary must return

Example
db = await arena()
adversary = Adversary()
reply = await adversary._send_prompt_to_target_async(
    normalized_conversation=[Message.from_prompt(prompt="I cannot share that.", role="user")])
print(reply[0].get_value())

The adversary is sent the defender's last reply and must return one JSON object with next_message, rationale and last_response_summary. Only next_message is forwarded to the system under test; the other two are the attack's own notes. Return plain text instead and the attack retries ten times, then fails with Status Code: 500, Message: Invalid JSON encountered.

The Adversary stand-in

python
class Adversary(PromptTarget):
    _DEFAULT_CONFIGURATION = CHAT

    def __init__(self, *, probes=None, custom_configuration=None):
        super().__init__(custom_configuration=custom_configuration)
        self.probes = list(probes or [...])
        self.turn = 0

    async def _send_prompt_to_target_async(self, *, normalized_conversation):
        heard = normalized_conversation[-1].get_value()
        probe = self.probes[min(self.turn, len(self.probes) - 1)]
        self.turn += 1
        body = json.dumps({
            "next_message": probe,
            "rationale": f"probe {self.turn} of {len(self.probes)}",
            "last_response_summary": heard[:60],
        })
        return [Message.from_prompt(prompt=body, role="assistant")]

A real adversary is a model prompted to write the next probe. The stand-in reads its next probe from a fixed list, so the attack is repeatable and needs no key, and it still returns the JSON shape the attack parses. Declaring _DEFAULT_CONFIGURATION = CHAT makes it a multi-turn target, which the adversary has to be.

Try it yourself
  • Return a plain string from a copy of the adversary and watch the attack retry then fail.
  • Print the rationale field from the adversary's reply.
  • Give the adversary a different list of probes and read them back in order.

Slow is fine. Stopping is the only problem.