FEAT: Add remove_seeds_from_memory_async to memory - #2243
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds first-class support for deleting seed prompts from PyRIT memory by introducing remove_seeds_from_memory_async, mirroring the existing get_seeds filter surface so users no longer need backend-specific SQL for seed cleanup.
Changes:
- Extracts seed filter construction into a shared private helper (
_build_seed_filter_conditions) used by both seed querying and deletion. - Adds
remove_seeds_from_memory_asyncto delete matchingSeedEntryrows transactionally and return the number removed (with a no-filter safety guard). - Adds unit tests and documentation demonstrating a “preview via
get_seeds, then delete viaremove_seeds_from_memory_async” workflow.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 13 comments.
| File | Description |
|---|---|
pyrit/memory/memory_interface.py |
Adds shared seed-filter builder and new async seed removal API implemented via SQLAlchemy. |
tests/unit/memory/memory_interface/test_interface_remove_seeds.py |
Adds unit coverage for common filter cases and safety behavior. |
doc/code/memory/8_seed_database.py |
Documents seed removal usage and recommended preview-then-delete workflow. |
doc/code/memory/8_seed_database.ipynb |
Syncs the same documentation updates into the notebook format. |
2d45b57 to
0e3ae02
Compare
|
Thanks for the review! Addressed all points in the latest push:
|
|
@microsoft-github-policy-service agree company="Microsoft" |
| with closing(self.get_session()) as session: | ||
| try: | ||
| query = session.query(SeedEntry).filter(and_(*conditions)) | ||
| count = query.delete(synchronize_session=False) |
There was a problem hiding this comment.
What are we doing to file-backed image, audio, and video seeds? This removes the database row but leaves the serialized file behind. We should either clean up owned artifacts or clearly document that this only removes database records.
622947d to
4f77352
Compare
|
@hannahwestra25 Thanks again for the review — I've pushed updates addressing all your comments:
The PR description has been updated with the new method and a "Follow-up work" section. Let me know if you'd like the exact-match mode folded into this PR instead of a follow-up. |
50b8a51 to
6652dcf
Compare
276576a to
1bfc7cc
Compare
Adds two memory APIs to remove seed prompts using the same filter parameters as get_seeds (value, exact, value_sha256, dataset_name, dataset_name_pattern, data_types, harm_categories, added_by, authors, groups, source, seed_type, parameters, metadata, prompt_group_ids): - remove_seeds_from_memory removes the individual seeds that match. - remove_seed_groups_from_memory expands any match to its whole prompt_group_id so partial groups are never left behind. Implementation: - Extract filter-building logic from get_seeds into a shared _build_seed_filter_conditions helper so query and removal cannot drift. - value matching is controlled by an exact bool flag (replacing the earlier value_exact string, per review). The remove methods default to exact=True (full-string equality) so a short or common value cannot over-delete; get_seeds keeps substring matching. Pass exact=False to opt into substring deletion. The intentional difference is documented. - Require at least one filter (raises ValueError if none provided). - Single transaction with rollback on SQLAlchemyError. - Group removal selects matching prompt_group_ids with a server-side subquery (not a materialized IN(...) list) so a broad filter cannot exceed the backend bound-parameter limit; NULL group ids are excluded, so ungrouped seeds are skipped. - Methods are synchronous, mirroring get_seeds: they do only DB work and never await I/O. Only database records are removed; file-backed seeds (image_path, audio_path, video_path) leave the serialized file on disk (documented). Unit tests cover every individual filter, exact-by-default vs opt-in substring matching, multi-filter narrowing, no-filter ValueError, zero-match, rollback-on-error, and for groups: whole-group removal, groups spanning datasets, non-matching groups preserved, and the ungrouped (NULL group id) skip behavior. Docs: add a "Removing Seeds from the Database" section to the seed database notebook (8_seed_database.py/.ipynb) demonstrating the preview-then-remove workflow, whole-group removal, exact-by-default matching, the ungrouped-skip, and the file-backed caveat. Resolves AB#5688 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f9e3cc10-5c77-4384-92d9-d27dad9baea2
1bfc7cc to
7dbf60f
Compare
Description
PyRIT memory can add seed prompt datasets (via
add_seed_datasets_to_memory_async/add_seeds_to_memory_async) and query them withget_seeds, but there is no counterpart for removing seeds. Today users have to drop into raw SQL, which is error-prone and differs across the supported backends (SQLite, Azure SQL).This PR adds two removal methods that accept the same filtering parameters as
get_seeds(dataset_name,dataset_name_pattern,added_by,harm_categories,authors,groups,source,value_sha256,data_types,seed_type,parameters,metadata,prompt_group_ids), plus anexactflag that controls howvalueis matched:remove_seeds_from_memory— removes the individual seeds that match the filters and returns the count removed.remove_seed_groups_from_memory— removes entire seed groups: any match is expanded to its wholeprompt_group_idso partial groups are never left behind (e.g. filtering by one modality removes the whole multimodal group).Recommended workflow — preview, then delete with the same filters:
Implementation
get_seedsinto a shared private helper (_build_seed_filter_conditions) so query and removal build conditions from a single source and cannot drift.remove_seed_groups_from_memoryexpands matches to whole groups with a server-side subquery (prompt_group_id.in_(select(...))) rather than materializing the group IDs into Python and sending them back as anIN (...)list. This avoids exceeding the backend bound-parameter limit (e.g. Azure SQL) on broad filters. Seeds with aNULLprompt_group_id(e.g. added individually rather than as a group) are excluded and skipped — useremove_seeds_from_memoryfor those.get_seeds: they perform only database work and never await I/O, so making themasyncwould just wrap blocking calls in a coroutine and stall the event loop.Safety
ValueErrorto prevent accidentally wiping the entire seed database.exactflag. For deletion, matching by substring is a footgun (a short/common value can over-match), so the remove methods default toexact=True(full-string equality).get_seedskeeps its substring behavior, so its helper default staysexact=False; passexact=Falseto the remove methods to opt into substring deletion when you really want it. This intentional difference is documented in the docstrings and docs, along with the group-integrity consequences of deleting individual seeds.image_path,audio_path,video_path) the serialized file on disk is left in place; this is documented so callers can delete those files separately if needed.Tests and Documentation
Tests —
tests/unit/memory/memory_interface/test_interface_remove_seeds.pycovers, forremove_seeds_from_memory: every individual filter (dataset_name,dataset_name_pattern,added_by,source,harm_categories,authors,groups,parameters,metadata,data_types,seed_type,value_sha256,prompt_group_ids), exact-by-default matching vs opt-inexact=Falsesubstring, multi-filter narrowing, no-filterValueError, zero-match, and rollback-on-error; and forremove_seed_groups_from_memory: whole-group removal, groups spanning datasets, non-matching groups preserved, ungrouped (NULLgroup id) matches skipped, no-filterValueError, zero-match, and rollback-on-error. File-backed deletion semantics are documented rather than unit-tested (testing real serialized-file deletion is brittle); happy to add an explicit file-cleanup implementation + test if preferred.Documentation — added a "Removing Seeds from the Database" section to
doc/code/memory/8_seed_database.py(and the synced.ipynb) demonstrating the preview-then-remove workflow, whole-group removal, the exact-by-default matching (andexact=Falseopt-in for substring), the ungrouped-seed skip, and the file-backed caveat.