Skip to content

feat: add PrimingGroup and MLPipeline SDK methods#420

Open
brandon-wada wants to merge 22 commits intomainfrom
priming_sdk
Open

feat: add PrimingGroup and MLPipeline SDK methods#420
brandon-wada wants to merge 22 commits intomainfrom
priming_sdk

Conversation

@brandon-wada
Copy link
Copy Markdown
Collaborator

@brandon-wada brandon-wada commented Mar 27, 2026

Summary

Adds SDK methods for the new PrimingGroup public API. Priming Groups are groupings of detectors that share a common semantic query and can utilize a common base model. In the future, priming groups may allow for cross detector learning and other multi-task and meta-learning techniques.

In introducing priming groups, we also expose MLPipelines. Every detector has one active MLPipeline but constantly runs multiple MLPipelines under the hood that are compared against the top performing active model. Whenever Groundlight determines that there is a better performing model, we will switch that model to be the active model.

Currently, a priming group can be created from an existing and trained MLPipeline, which one can get given an active detector. The new PrimingGroup will use a frozen version of the MLPipeline for the first inference on a new detector created using the PrimingGroups. If shadow pipelines are disallowed on the PrimingGroup, that frozen model will be used indefinitely by the detector. Otherwise, the frozen model will be used until one of the other MLPipelines on the detector is evaluated to be a better fit.

We add the following methods in this PR

  • list_detector_pipelines(detector) — list all MLPipelines for a detector
  • create_priming_group(name, source_ml_pipeline_id, ...) — create a PrimingGroup seeded from an existing trained model
  • list_priming_groups() — list all priming groups owned by this account
  • get_priming_group(priming_group_id) — retrieve by ID
  • delete_priming_group(priming_group_id) — soft-delete

We also add new pydantic models: MLPipeline, PrimingGroup in generated/model.py.

Usage

```python
gl = Groundlight()

pipelines = gl.list_detector_pipelines(detector)
active = next(p for p in pipelines if p.is_active_pipeline and p.cached_vizlogic_key)

pg = gl.create_priming_group(
name="door-detector-primer",
source_ml_pipeline_id=active.id,
canonical_query="Is the door open?",
disable_shadow_pipelines=True,
)

new_detector = gl.create_detector(
name="new-door-detector",
query="Is the door open?",
priming_group_id=pg.id,
)
```

brandon-wada and others added 20 commits March 27, 2026 19:47
New Groundlight client methods:
- list_detector_pipelines(detector) -> List[MLPipeline]
- list_priming_groups() -> List[PrimingGroup]
- create_priming_group(name, source_ml_pipeline_id, canonical_query, disable_shadow_pipelines) -> PrimingGroup
- get_priming_group(priming_group_id) -> PrimingGroup
- delete_priming_group(priming_group_id)

New pydantic models in generated/model.py: MLPipeline, PrimingGroup,
PaginatedMLPipelineList, PaginatedPrimingGroupList.

PrimingGroups let users seed new detectors with a pre-trained model binary
so they skip the cold-start period. Detectors created with priming_group_id
(already supported in create_detector) will start with the primed model active.
@brandon-wada brandon-wada requested a review from timmarkhuff April 7, 2026 20:50
Resolved conflict in experimental_api.py by keeping both the
edge_base_url utility method from main and the new ML Pipeline /
PrimingGroup methods from this branch.

Made-with: Cursor
@timmarkhuff
Copy link
Copy Markdown
Contributor

timmarkhuff commented Apr 7, 2026

This line in the description confuses me:

active = next(p for p in pipelines if p.is_active_pipeline and p.cached_vizlogic_key)

Should we have a function for that? gl.get_active_pipeline(pipelines)?

...actually, I realize now that this code block is invalid. I get this error:

$ python detector_priming_scratchpad.py 
Traceback (most recent call last):
  File "detector_priming_scratchpad.py", line 7, in <module>
    active = next(p for p in pipelines if p.is_active_pipeline and p.cached_vizlogic_key)
  File "detector_priming_scratchpad.py", line 7, in <genexpr>
    active = next(p for p in pipelines if p.is_active_pipeline and p.cached_vizlogic_key)
  File "pydantic/main.py", line 1026, in __getattr__
    raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}')
AttributeError: 'MLPipeline' object has no attribute 'cached_vizlogic_key'

@pytest.mark.expensive
def test_get_priming_group_unknown_raises(gl_experimental: ExperimentalApi):
with pytest.raises(NotFoundError):
gl_experimental.get_priming_group("pgp_doesnotexist000000000000")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a typo? pg instead of pgp

@timmarkhuff
Copy link
Copy Markdown
Contributor

There are multiple docstrings that say something to the effect of:

PrimingGroup IDs are provided by Groundlight representatives. If you would like
                        to use a priming_group_id, please reach out to your Groundlight representative.

Should this be updated now that users can self-service?

@timmarkhuff
Copy link
Copy Markdown
Contributor

From Claude:

  1. Both list_detector_pipelines and list_priming_groups only return the first page. If there are more items than the default page size, results are silently truncated. Consider at least documenting this, or exposing page/page_size parameters like list_rules does.
  2. Every test in test/unit/test_priming_groups.py hits real API endpoints via the gl_experimental and detector fixtures (which create real detectors against the server). This file should be at test/integration/test_priming_groups.py.
  3. parse_obj is deprecated in Pydantic v2. Other code in the same file already uses model_validate (e.g., line 315). All of these should use model_validate consistently.

@timmarkhuff
Copy link
Copy Markdown
Contributor

It seems there is no way to connect the dots between a detector and it's related priming group. Below is a detector that I created on a particular priming group, but there is no way (that I can find) to know that they are connected.

{
  "id": "pg_3C39sYIOAs1Joc0h3plHuw3V8ua",
  "name": "Car Detector - Priming Group",
  "is_global": false,
  "canonical_query": "Draw a box around each car in the image",
  "active_pipeline_config": null,
  "priming_group_specific_shadow_pipeline_configs": [],
  "disable_shadow_pipelines": true,
  "created_at": "2026-04-07T23:13:13.338272Z"
}
{
  "id": "det_3C3A1WilrnS9cT7dO7MPJ5hbr1d",
  "type": "detector",
  "created_at": "2026-04-07T23:14:24.750278Z",
  "name": "Car Detector - 2026-04-07 23:14:24",
  "query": "Draw a box around each car in the image",
  "group_name": "Default",
  "confidence_threshold": 0.9,
  "patience_time": 30.0,
  "metadata": null,
  "mode": "BOUNDING_BOX",
  "mode_configuration": {
    "class_name": "car",
    "max_num_bboxes": 10.0
  },
  "status": "ON",
  "escalation_type": "STANDARD"
}

Maybe Detector should be updated with priming_group_id and/or the PrimingGroup should be updated to include detectors[]

@timmarkhuff
Copy link
Copy Markdown
Contributor

Are there any safeguards to ensure that users don't try to associate detectors of different types to the same PrimingGroup? I ran a little test and there doesn't seem to be. I assume we want the service to reject this, because detectors of different types can't (necessarily) use the same pipelines.

@timmarkhuff
Copy link
Copy Markdown
Contributor

The PR description says "gl = Groundlight()" but it should say "gl = ExperimentalApi"

@timmarkhuff
Copy link
Copy Markdown
Contributor

I just did a bit of testing and found that when I prime a detector from a mature detector, the primed detector doesn't get the mature weights downloaded to run on edge. In other words, the primed detector seems naive on edge, and doesn't return confident answers. I'm not exactly sure what the solution for this should be, but I think we should consider this before merging this PR.

@timmarkhuff timmarkhuff self-requested a review April 8, 2026 00:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants