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
21 small wins to finish your pathNext lesson

Scores and attack results

Lesson 18 read the conversation. The verdict from part 2 and the summary of the whole run are stored too, in their own tables.

A score belongs to the row it judged, so you fetch it by that row's id.

Example
replies = db.get_message_pieces(role="assistant")
scores = db.get_prompt_scores(prompt_ids=[str(p.id) for p in replies])
for score in scores:
    print(score.score_value, "|", score.score_type, "|", score.scorer_class_identifier.class_name)

The scorer's own identity is stored beside the verdict. Six months later, when a report says a reply was a leak, that field is how you find out what rule made the call.

get_scores() with no arguments returns nothing. Not an error, not everything: an empty list, while the scores plainly exist. It wants a filter. Its near-namesake get_prompt_scores is the one that takes row ids, and the two read like a pair without behaving like one.

The run as a whole

The result object from execute_async is also written down, so a campaign can be summarised without keeping anything in memory.

Example
for stored in db.get_attack_results():
    print(stored.outcome.name, "|", stored.executed_turns, "turn(s) |", stored.objective)

One row per attack, carrying the objective, the outcome and the conversation it belongs to. This is the table a report is built from; the message pieces are the detail you drill into when a row looks wrong.

From a result back to the evidence

Example
stored = db.get_attack_results()[0]
messages = db.get_conversation_messages(conversation_id=stored.conversation_id)
print(stored.outcome.name, "->", messages[-1].get_value())

Outcome, conversation id, conversation. That is the whole chain from a one-line summary to the exact text that caused it, and it is what makes a PyRIT finding something a developer can act on.

Try it yourself
  • Call db.get_scores() with no arguments and confirm it is empty.
  • Run two attacks with different objectives and print both stored results.
  • Print db.get_unique_attack_class_names().

This is what real progress feels like.