Repository navigation
FEAT GUI: Add paginated seed browsing API #2748
Description
Is your feature request related to a problem? Please describe.
A dataset name list cannot support a scalable prompt table or a reliable selection for chat/scenarios. The GUI needs bounded retrieval, searchable metadata, and complete logical examples rather than disconnected media pieces.
Work item 2 of 17 in #2744. Completed by #2989, building on the summary API in #2762. The implemented contract below is the reference for the remaining GUI work; it supersedes draft route/response proposals and descriptions of intermediate PR revisions.
Describe the solution you'd like
Add typed, read-only dataset browsing/detail APIs on top of existing seed memory.
- Support bounded server-side pagination with deterministic ordering and stable seed/group identifiers. A page boundary must not split a logical example.
- Support prompt-text search and existing metadata filters, including modality, harm category, and seed type. Document and test filter-combination and category-matching semantics.
- Return compact previews for listing and full content/metadata for an expanded example. Do not send media bytes in the list response.
- Preserve related pieces, roles, and sequence information. A modality match on one member must not silently remove the other members of the selected example.
- Distinguish SeedPrompt, SeedObjective, templates, and simulated-conversation configuration. Browsing must not render templates or generate conversations.
- Retain provenance and identifiers needed by later import/scenario requests. Return repository-standard errors for invalid filters, missing examples, and invalid paging inputs.
Shipped contract for GUI contributors
Use the merged browsing guide, REST models, and generated OpenAPI schema, not the earlier reconstruction-based PR description.
GET /api/datasetsreturns{ items: DatasetInfo[] };loaded_only=truerequests the memory-backed catalog. Reuse each returnedselection_keyas an opaque identity.dataset:unnamedcovers both NULL and empty names and is distinct from a named dataset such as__unnamed__.GET /api/datasets/seeds?selection_key=<key>lists examples.GET /api/datasets/seeds/{example_id}?selection_key=<key>gets one example. The dataset key is a query parameter, not a path segment before/seeds. Encode it with the shared client's query builder.- A logical example is dataset-scoped.
example_idisprompt_group_id, or the persisted seed ID for an ungrouped seed. Keepselection_keywith selected example IDs; detail returns members only from that selected dataset, never other datasets that share a group ID. - List inputs are
limit(default 20, range 1-100), opaquecursor,search, and repeated singularmodality,seed_type, andharm_categoryparameters. Values within a facet are OR-matched; different facets are AND-matched across the complete logical example. - The response has
items,pagination, and matching-exampletotal. Usepagination.has_moreandnext_cursor. In this endpoint,prev_cursorechoes the cursor used for the current request, not a cursor that automatically fetches the previous page. Keep visited-page cursors for Previous/Back. Reset the cursor when dataset or filters change; invalid or filter-mismatched cursors return 400 rather than silently restarting. - Examples sort by their earliest member
date_added, descending, then canonical textual UUID descending. Preserve server order; do not sort one page as if it were the full result set. searchmatches stored text prompt and objective values, not simulated-conversation JSON or media paths.%,_, and[are literal characters. SQLite's case-insensitive matching is ASCII-only; do not promise Unicode case folding. Configuration records remain discoverable withseed_type=simulated_conversation. Broader configuration-text search would be separate follow-up work, not missing work for the next GUI milestone.- List entries expose
example_id,name,preview,preview_truncated,modalities,seed_types,piece_count,objective_count,harm_categories, andhas_unlabeled_harm. The API supplies bounded/safe previews; copying full content uses detailvalue, not the preview. There are nois_templateoris_configurationresponse flags. - Detail
membersare lightweight storedSeedRecordprojections, notSeedUnionor execution-ready seeds. Preserve stored IDs/hashes, nullable role/sequence,parameters, typed objectiveconditions, and provenance. Simulated-conversation fields remain in raw JSONvalue, not reconstructed top-level properties. Browsing neither opens template/media files nor renders or executes configuration; missing files do not silently remove members. Existing seed-reconstruction APIs are unchanged. - A syntactically valid dataset key with no stored examples returns an empty list. A missing example within the selected dataset returns 404. Provider-only entries are not automatically loaded by browsing. Invalid request shapes use the existing API validation/error contract.
The next work is #2747 (catalog/navigation), followed by #2745 (table/details). #2751 and #2757 reuse this dataset-scoped selection identity for import and scenario requests. No memory schema migration or new seed store was added.
Acceptance criteria:
- Multiple pages do not duplicate or omit rows under a stable dataset, and grouped media stays together.
- Search/filter results and counts use the documented semantics.
- Full text is available even when the list preview is truncated.
- Empty results, unnamed datasets, mixed types, missing IDs, and invalid requests have deliberate behavior.
- Queries are bounded at the database layer rather than slicing a fully materialized seed list.
- Tests use local fixtures; reads perform no downloads, writes, model calls, or template execution.
Describe alternatives you've considered, if relevant
Avoid client-side paging of entire datasets, separate GUI storage, or schema migrations. Narrow memory query helpers are acceptable, but selection/presentation policy belongs in the service rather than memory.
Additional context
Start with the dataset routes/service/models, MemoryInterface.get_seed_examples_async() and get_seed_example_async(), the shared SeedRecord model, and pyrit\common\pagination.py. Follow doc\code\framework.md and applicable instructions. The browsing read model is intentionally separate from reconstruction for execution; GUI consumers must not replace it with get_seeds()/get_seeds_async() or copy old DTOs.
Activity
jacksonsr451 commentedon Sep 24, 2026
Hi Roman Lutz (@romanlutz), I'd like to work on this issue as you suggested in #2746.
I'll first review the existing implementation from #2762 and the relevant memory, service, REST model, and test layers so the paginated seed browsing API builds cleanly on the dataset summary contract already merged into
main.I'll follow the scope and acceptance criteria defined here and avoid overlapping with related work. Please let me know if there are any additional implementation constraints or ongoing changes I should account for before I start.
romanlutz commentedon Sep 25, 2026
Done!
jacksonsr451 commentedon Sep 25, 2026
Hi Roman Lutz (@romanlutz), I've completed an initial review of the current
main, including the dataset summary contract from #2762, the seed/group models,MemoryInterface.get_seeds(), and the existing pagination infrastructure.Before I start implementing, I'd like to confirm a few contract details that don't appear to be fully determined by the current code:
Pagination and ordering: should dataset browsing use the existing opaque cursor style, or offset-based pagination? If cursor-based, what should define the deterministic order of logical examples?
Logical example identity: is
prompt_group_id, falling back to the seedidfor ungrouped rows, the intended stable example identity for browsing, consistent with the counting approach introduced in FEAT Add dataset summary API #2762?Filter semantics: for multiple modality, harm-category, or seed-type values, should values within the same filter use OR or AND semantics? When different filter categories are combined, may different members of the same logical example satisfy different predicates, with the complete group then returned?
Prompt-text search: should search apply only to
SeedPrompt.value, or should textualSeedObjectivevalues and stored template/simulated-conversation configuration also participate? Templates would only be searched as stored text and never rendered.List/detail contract: is there an expected preview length/field set and a specific set of provenance fields that later import/scenario work will require, or should I derive the minimal representation from the currently persisted seed metadata?
My intended query strategy is to select and paginate matching logical-example identifiers at the database layer first, then fetch all members of only those selected examples. That should keep queries bounded while ensuring filters never split or strip members from a multimodal/multi-turn logical example.
Once these semantics are confirmed, I'll encode them in tests before implementing the service/API behavior.
romanlutz commentedon Sep 26, 2026
Thanks Jackson Severino da Rocha (@jacksonsr451). Your approach of paging logical-example IDs first, then fetching their members, is the right direction. Let's use the following contract for this issue:
Pagination and ordering: Use opaque, filter-bound keyset cursors and the existing pagination response shape. Order examples by their earliest stored
date_added, descending, then by logical-example ID, descending, to break ties. Calculate that timestamp from the complete example, not just the members matching a filter. Bind the cursor to the dataset selection and effective filters. An invalid or mismatched cursor should return a validation error rather than silently restarting at page one. We need consistent paging for an unchanged dataset, not snapshot isolation across concurrent edits.Logical-example identity: Yes: use
prompt_group_id, falling back to the seedidfor ungrouped records, consistent with FEAT Add dataset summary API #2762. Use the dataset'sselection_key, not its display name, for dataset selection. Preserve the original seed IDs and nullable group ID in the response; don't generate new IDs or combine records just because their content hashes match.Filter semantics: Use OR within a filter and AND between filters, evaluated at the logical-example level. Different members may satisfy different filters. For example, an image member can satisfy
modality=image_pathwhile its linked text member satisfiesharm_category=violence. Return the complete example, including members that did not match. Match harm categories as whole category values, case-insensitively, rather than substrings. Missing categories mean unlabeled, not safe. Counts should use the same predicates and count logical examples rather than individual pieces.This differs from the existing
get_seeds()harm-category behavior, which requires all requested categories. Please keep that existing behavior unchanged and implement the browsing semantics through a narrow read helper.Text search: Search stored
valuetext for text-type seeds, including objectives, unrendered templates, and persisted simulated-conversation configuration. Use case-insensitive literal substring matching, with%and_treated as characters rather than query wildcards. Don't search media contents, media paths, or arbitrary metadata in this milestone. Nothing should render templates, load referenced template files, or generate conversations. Clearly label configuration/template results so they aren't mistaken for ready-to-send prompts.List and detail responses: Derive these from persisted seed data, without adding a new storage model. For the list, include the stable example reference, name where available, a bounded preview, seed types/modalities, piece/objective counts, and harm categories with the unlabeled flag. Start with the existing 100-character text-preview convention and indicate truncation; use a media/type label when there is no text.
Detail should return all members with full stored text/configuration, original seed IDs, group IDs, roles, sequence values, and stored value hashes where available. Preserve existing provenance such as dataset name, source, authors, groups, date added, and added by, along with relevant metadata and typed fields such as template parameters and objective conditions. Reference media rather than embedding its bytes, and don't expose absolute paths or credential-bearing URLs in previews.
Please keep the query work in portable memory read helpers and the response formatting in the backend service/model layer. One implementation detail worth watching: constructing a
SeedGroupcan assign group IDs, and reconstructing legacy simulated-conversation seeds can load template files. Browsing should expose stored records without those side effects.Tests covering tied timestamps, complete-group retrieval, cross-member filter matches, unnamed datasets, literal search characters, and invalid cursors would give us a solid foundation for the later GUI and import work.
romanlutz commentedon Oct 3, 2026
Jackson Severino da Rocha (@jacksonsr451) are you still intersted in this? If not, I can free it up for other contributors. If I don't hear back I'll free it up by 10/5
jacksonsr451 commentedon Oct 4, 2026
Hi Roman Lutz (@romanlutz), yes, I'm still working on this. The implementation is nearly complete and I'm finishing the final API contract validation now. I expect to open the PR shortly. Thanks for checking in!