The memory every run writes to
Lesson 3 installed PyRIT. Before anything can be sent, PyRIT needs somewhere to write it down, and it will not let you skip that step.
Every prompt, every reply and every score goes into a database. Starting PyRIT means choosing which one.
from pyrit.setup import IN_MEMORY, initialize_pyrit_async
await initialize_pyrit_async(memory_db_type=IN_MEMORY, silent=True)
print("ready")IN_MEMORY is a SQLite database that lives in the process and disappears when it ends, which is what every lesson here wants. Lesson 20 swaps it for one on disk.
silent=True is not cosmetic. Without it the call prints two lines of start-up banner to standard output, and one of those two lines only appears the first time you start PyRIT in a given process. Anything comparing output between runs breaks on that.
The memory object
One call gets you the database. It is a process-wide single object, which matters in a moment.
from pyrit.memory import CentralMemory
db = CentralMemory.get_memory_instance()
print(type(db).__name__)An in-memory choice still gives you SQLiteMemory; the only difference is that the file never touches the disk. Everything PyRIT records goes through this one object.
Putting a row in by hand
Nothing writes to memory on its own; an attack will do it from lesson 6. To have something to look at now, add one message yourself. It needs a conversation to belong to, and PyRIT will not invent one for you.
from pyrit.models import Message
note = Message.from_prompt(prompt="hello", role="user")
try:
db.add_message_to_memory(request=note)
except ValueError as e:
print("ValueError:", str(e).partition(" has ")[2])Every row in the database belongs to a conversation, because that is how a reply is later matched to the prompt that caused it. Set one and the write goes through.
Starting PyRIT again does not empty it
This one costs people an afternoon. CentralMemory hands back the same object every time, and calling initialize_pyrit_async a second time does not replace it, so yesterday's rows are still there.
note.get_piece().conversation_id = "probe-1"
db.add_message_to_memory(request=note)
print("before:", len(db.get_message_pieces()))
await initialize_pyrit_async(memory_db_type=IN_MEMORY, silent=True)
again = CentralMemory.get_memory_instance()
print("after starting again:", len(db.get_message_pieces()))
print("same object?", again is db)The row survived. If you have ever printed everything in memory and found rows from a run you thought you had thrown away, this is why.
The reset, and a helper worth having
db.reset_database()
print("after reset:", len(db.get_message_pieces()))Starting silently and then emptying the database is the opening of every lesson from here on, so it is worth a name. Put this in pretend_pyrit.py; the rest of the file arrives in lesson 5.
async def arena():
await initialize_pyrit_async(memory_db_type=IN_MEMORY, silent=True)
memory = CentralMemory.get_memory_instance()
memory.reset_database()
return memory- Delete
silent=Trueand run the first snippet twice in one session. The two outputs differ. - Add two messages, reset, and check the count is zero.
- Call
db.print_schema()and look at the tables PyRIT keeps.
This is what real progress feels like.