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" |
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
1bfc7cc to
7dbf60f
Compare
7dbf60f to
1873066
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
1873066 to
c6060d9
Compare
Add executed outputs to the remove_seeds example cell in 8_seed_database and remove the 'deleting one modality' bullet that describes a still-valid group rather than an error, per review feedback. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f9e3cc10-5c77-4384-92d9-d27dad9baea2
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.