|
32 | 32 | Condition, |
33 | 33 | Detector, |
34 | 34 | EdgeModelInfo, |
| 35 | + MLPipeline, |
35 | 36 | ModeEnum, |
36 | 37 | PaginatedRuleList, |
37 | 38 | PayloadTemplate, |
| 39 | + PrimingGroup, |
38 | 40 | Rule, |
39 | 41 | WebhookAction, |
40 | 42 | ) |
41 | 43 | from urllib3.response import HTTPResponse |
42 | 44 |
|
| 45 | +from groundlight.exceptions import NotFoundError |
43 | 46 | from groundlight.images import parse_supported_image_types |
44 | 47 | from groundlight.internalapi import _generate_request_id |
45 | 48 | from groundlight.optional_imports import Image, np |
@@ -817,3 +820,160 @@ def make_generic_api_request( # noqa: PLR0913 # pylint: disable=too-many-argume |
817 | 820 | auth_settings=["ApiToken"], |
818 | 821 | _preload_content=False, # This returns the urllib3 response rather than trying any type of processing |
819 | 822 | ) |
| 823 | + |
| 824 | + # --------------------------------------------------------------------------- |
| 825 | + # ML Pipeline methods |
| 826 | + # --------------------------------------------------------------------------- |
| 827 | + |
| 828 | + def list_detector_pipelines(self, detector: Union[str, Detector]) -> List[MLPipeline]: |
| 829 | + """ |
| 830 | + Lists all ML pipelines associated with a given detector. |
| 831 | +
|
| 832 | + Each detector can have multiple pipelines (active, edge, shadow, etc.). This method returns |
| 833 | + all of them, which is useful when selecting a source pipeline to seed a new PrimingGroup. |
| 834 | +
|
| 835 | + **Example usage**:: |
| 836 | +
|
| 837 | + gl = ExperimentalApi() |
| 838 | + detector = gl.get_detector("det_abc123") |
| 839 | + pipelines = gl.list_detector_pipelines(detector) |
| 840 | + for p in pipelines: |
| 841 | + if p.is_active_pipeline: |
| 842 | + print(f"Active pipeline: {p.id}, config={p.pipeline_config}") |
| 843 | +
|
| 844 | + :param detector: A Detector object or detector ID string. |
| 845 | + :return: A list of MLPipeline objects for this detector. |
| 846 | + """ |
| 847 | + detector_id = detector.id if isinstance(detector, Detector) else detector |
| 848 | + url = f"{self.api_client.configuration.host}/v1/detectors/{detector_id}/pipelines" |
| 849 | + response = requests.get( |
| 850 | + url, headers=self.api_client._headers(), verify=self.api_client.configuration.verify_ssl |
| 851 | + ) |
| 852 | + if response.status_code == 404: |
| 853 | + raise NotFoundError(f"Detector '{detector_id}' not found.") |
| 854 | + response.raise_for_status() |
| 855 | + data = response.json() |
| 856 | + return [MLPipeline(**item) for item in data.get("results", [])] |
| 857 | + |
| 858 | + # --------------------------------------------------------------------------- |
| 859 | + # PrimingGroup methods |
| 860 | + # --------------------------------------------------------------------------- |
| 861 | + |
| 862 | + def list_priming_groups(self) -> List[PrimingGroup]: |
| 863 | + """ |
| 864 | + Lists all PrimingGroups owned by the authenticated user's account. |
| 865 | +
|
| 866 | + PrimingGroups let you seed new detectors with a pre-trained model so they start with a |
| 867 | + meaningful head start instead of a blank slate. |
| 868 | +
|
| 869 | + **Example usage**:: |
| 870 | +
|
| 871 | + gl = ExperimentalApi() |
| 872 | + groups = gl.list_priming_groups() |
| 873 | + for g in groups: |
| 874 | + print(f"{g.name}: {g.id}") |
| 875 | +
|
| 876 | + :return: A list of PrimingGroup objects. |
| 877 | + """ |
| 878 | + url = f"{self.api_client.configuration.host}/v1/priming-groups" |
| 879 | + response = requests.get( |
| 880 | + url, headers=self.api_client._headers(), verify=self.api_client.configuration.verify_ssl |
| 881 | + ) |
| 882 | + response.raise_for_status() |
| 883 | + data = response.json() |
| 884 | + return [PrimingGroup(**item) for item in data.get("results", [])] |
| 885 | + |
| 886 | + def create_priming_group( |
| 887 | + self, |
| 888 | + name: str, |
| 889 | + source_ml_pipeline_id: str, |
| 890 | + canonical_query: Optional[str] = None, |
| 891 | + disable_shadow_pipelines: bool = False, |
| 892 | + ) -> PrimingGroup: |
| 893 | + """ |
| 894 | + Creates a new PrimingGroup seeded from an existing ML pipeline. |
| 895 | +
|
| 896 | + The trained model binary from the source pipeline is copied into the new PrimingGroup. |
| 897 | + Detectors subsequently created with this PrimingGroup's ID will start with that model |
| 898 | + already loaded, bypassing the cold-start period. |
| 899 | +
|
| 900 | + **Example usage**:: |
| 901 | +
|
| 902 | + gl = ExperimentalApi() |
| 903 | + detector = gl.get_detector("det_abc123") |
| 904 | + pipelines = gl.list_detector_pipelines(detector) |
| 905 | + active = next(p for p in pipelines if p.is_active_pipeline) |
| 906 | +
|
| 907 | + priming_group = gl.create_priming_group( |
| 908 | + name="door-detector-primer", |
| 909 | + source_ml_pipeline_id=active.id, |
| 910 | + canonical_query="Is the door open?", |
| 911 | + disable_shadow_pipelines=True, |
| 912 | + ) |
| 913 | + print(f"Created priming group: {priming_group.id}") |
| 914 | +
|
| 915 | + :param name: A short, descriptive name for the priming group. |
| 916 | + :param source_ml_pipeline_id: The ID of an MLPipeline whose trained model will seed this group. |
| 917 | + The pipeline must belong to a detector in your account. |
| 918 | + :param canonical_query: An optional description of the visual question this group answers. |
| 919 | + :param disable_shadow_pipelines: If True, detectors created in this group will not receive |
| 920 | + default shadow pipelines, ensuring the primed model stays active. |
| 921 | + :return: The created PrimingGroup object. |
| 922 | + """ |
| 923 | + url = f"{self.api_client.configuration.host}/v1/priming-groups" |
| 924 | + payload: dict = { |
| 925 | + "name": name, |
| 926 | + "source_ml_pipeline_id": source_ml_pipeline_id, |
| 927 | + "disable_shadow_pipelines": disable_shadow_pipelines, |
| 928 | + } |
| 929 | + if canonical_query is not None: |
| 930 | + payload["canonical_query"] = canonical_query |
| 931 | + response = requests.post( |
| 932 | + url, json=payload, headers=self.api_client._headers(), verify=self.api_client.configuration.verify_ssl |
| 933 | + ) |
| 934 | + response.raise_for_status() |
| 935 | + return PrimingGroup(**response.json()) |
| 936 | + |
| 937 | + def get_priming_group(self, priming_group_id: str) -> PrimingGroup: |
| 938 | + """ |
| 939 | + Retrieves a PrimingGroup by ID. |
| 940 | +
|
| 941 | + **Example usage**:: |
| 942 | +
|
| 943 | + gl = ExperimentalApi() |
| 944 | + pg = gl.get_priming_group("pgp_abc123") |
| 945 | + print(f"Priming group name: {pg.name}") |
| 946 | +
|
| 947 | + :param priming_group_id: The ID of the PrimingGroup to retrieve. |
| 948 | + :return: The PrimingGroup object. |
| 949 | + """ |
| 950 | + url = f"{self.api_client.configuration.host}/v1/priming-groups/{priming_group_id}" |
| 951 | + response = requests.get( |
| 952 | + url, headers=self.api_client._headers(), verify=self.api_client.configuration.verify_ssl |
| 953 | + ) |
| 954 | + if response.status_code == 404: |
| 955 | + raise NotFoundError(f"PrimingGroup '{priming_group_id}' not found.") |
| 956 | + response.raise_for_status() |
| 957 | + return PrimingGroup(**response.json()) |
| 958 | + |
| 959 | + def delete_priming_group(self, priming_group_id: str) -> None: |
| 960 | + """ |
| 961 | + Deletes (soft-deletes) a PrimingGroup owned by the authenticated user. |
| 962 | +
|
| 963 | + This does not delete any detectors that were created using this priming group — |
| 964 | + it only removes the priming group itself. Detectors already created remain unaffected. |
| 965 | +
|
| 966 | + **Example usage**:: |
| 967 | +
|
| 968 | + gl = ExperimentalApi() |
| 969 | + gl.delete_priming_group("pgp_abc123") |
| 970 | +
|
| 971 | + :param priming_group_id: The ID of the PrimingGroup to delete. |
| 972 | + """ |
| 973 | + url = f"{self.api_client.configuration.host}/v1/priming-groups/{priming_group_id}" |
| 974 | + response = requests.delete( |
| 975 | + url, headers=self.api_client._headers(), verify=self.api_client.configuration.verify_ssl |
| 976 | + ) |
| 977 | + if response.status_code == 404: |
| 978 | + raise NotFoundError(f"PrimingGroup '{priming_group_id}' not found.") |
| 979 | + response.raise_for_status() |
0 commit comments