From 61817e3c1e719a2f77a384a6f06bc9cf86f6ba35 Mon Sep 17 00:00:00 2001 From: nlathia Date: Tue, 17 Feb 2026 11:30:25 +0000 Subject: [PATCH] Add procedure apis --- .../_procedure_version_experiment_set.py | 45 ++++++++++ .../_procedure_version_experiment_unset.py | 16 ++++ src/gradient_labs/_procedure_version_list.py | 27 ++++++ .../_procedure_version_live_set.py | 22 +++++ .../_procedure_version_live_unset.py | 16 ++++ src/gradient_labs/client.py | 87 +++++++++++++++++++ src/gradient_labs/procedure.py | 50 +++++++++++ 7 files changed, 263 insertions(+) create mode 100644 src/gradient_labs/_procedure_version_experiment_set.py create mode 100644 src/gradient_labs/_procedure_version_experiment_unset.py create mode 100644 src/gradient_labs/_procedure_version_list.py create mode 100644 src/gradient_labs/_procedure_version_live_set.py create mode 100644 src/gradient_labs/_procedure_version_live_unset.py diff --git a/src/gradient_labs/_procedure_version_experiment_set.py b/src/gradient_labs/_procedure_version_experiment_set.py new file mode 100644 index 0000000..ed73832 --- /dev/null +++ b/src/gradient_labs/_procedure_version_experiment_set.py @@ -0,0 +1,45 @@ +from dataclasses import dataclass +from dataclasses_json import dataclass_json + +from ._http_client import HttpClient + + +@dataclass_json +@dataclass(frozen=True) +class SetProcedureExperimentVersionParams: + """Parameters for setting an experimental version of a procedure. + + Experimental versions allow gradual rollout of new procedure versions + with daily conversation limits.""" + + # max_daily_conversations limits how many conversations per day can use this version. + # Allows gradual rollout of a new procedure version. + max_daily_conversations: int + + # replace: if True, an existing experiment (if any) will be replaced with a new one. + # Otherwise, if another experiment already exists, an error will be returned. + replace: bool = False + + +def set_procedure_experiment_version( + *, + client: HttpClient, + procedure_id: str, + version: int, + params: SetProcedureExperimentVersionParams, +) -> None: + """set_procedure_experiment_version marks the specified procedure version as experimental. + + Experimental versions are used for A/B testing and are served to a limited number + of conversations per day. If an experiment already exists, it will only be replaced + if the 'replace' flag is set to True. + + Note: requires a `Management` API key.""" + body = { + "max_daily_conversations": params.max_daily_conversations, + "replace": params.replace, + } + client.post( + path=f"procedures/{procedure_id}/versions/{version}/set-experiment", + body=body, + ) diff --git a/src/gradient_labs/_procedure_version_experiment_unset.py b/src/gradient_labs/_procedure_version_experiment_unset.py new file mode 100644 index 0000000..8ebd12b --- /dev/null +++ b/src/gradient_labs/_procedure_version_experiment_unset.py @@ -0,0 +1,16 @@ +from ._http_client import HttpClient + + +def unset_procedure_experiment_version( + *, client: HttpClient, procedure_id: str, version: int +) -> None: + """unset_procedure_experiment_version removes the specified version of a procedure from being + marked as experimental. + + Once unset, the version will no longer be used for A/B testing or served as an experiment. + + Note: requires a `Management` API key.""" + client.post( + path=f"procedures/{procedure_id}/versions/{version}/unset-experiment", + body={}, + ) diff --git a/src/gradient_labs/_procedure_version_list.py b/src/gradient_labs/_procedure_version_list.py new file mode 100644 index 0000000..08da342 --- /dev/null +++ b/src/gradient_labs/_procedure_version_list.py @@ -0,0 +1,27 @@ +from dataclasses import dataclass +from typing import List +from dataclasses_json import dataclass_json + +from ._http_client import HttpClient +from .procedure import ProcedureVersion + + +@dataclass_json +@dataclass(frozen=True) +class ListProcedureVersionsResponse: + """Response containing list of procedure versions.""" + + versions: List[ProcedureVersion] + + +def list_procedure_versions( + *, client: HttpClient, procedure_id: str +) -> ListProcedureVersionsResponse: + """list_procedure_versions lists existing non-ephemeral versions of a procedure. + + Note: requires a `Management` API key.""" + body = client.get( + path=f"procedures/{procedure_id}/versions", + body={}, + ) + return ListProcedureVersionsResponse.from_dict(body) diff --git a/src/gradient_labs/_procedure_version_live_set.py b/src/gradient_labs/_procedure_version_live_set.py new file mode 100644 index 0000000..c0614b5 --- /dev/null +++ b/src/gradient_labs/_procedure_version_live_set.py @@ -0,0 +1,22 @@ +from ._http_client import HttpClient + + +def set_procedure_live_version( + *, client: HttpClient, procedure_id: str, version: int +) -> None: + """set_procedure_live_version promotes a specific version of a procedure to be the + live (production) version that the AI agent will use for new conversations. + + The live version is the default version used by the agent when no experimental + versions are active. If the specified version is currently marked as experimental, + it will be promoted to live and will no longer be considered experimental. + + This is typically used after testing a procedure version in experimental mode + and confirming it works correctly. Once live, the procedure version will be + used for all new conversations that match the procedure's criteria. + + Note: requires a `Management` API key.""" + client.post( + path=f"procedures/{procedure_id}/versions/{version}/set-live", + body={}, + ) diff --git a/src/gradient_labs/_procedure_version_live_unset.py b/src/gradient_labs/_procedure_version_live_unset.py new file mode 100644 index 0000000..b17d63a --- /dev/null +++ b/src/gradient_labs/_procedure_version_live_unset.py @@ -0,0 +1,16 @@ +from ._http_client import HttpClient + + +def unset_procedure_live_version( + *, client: HttpClient, procedure_id: str, version: int +) -> None: + """unset_procedure_live_version removes the specified version of a procedure from being the live revision. + + Once unset, the version will no longer be used by default by the agent. + This does not delete the version or affect its experimental status (if any). + + Note: requires a `Management` API key.""" + client.post( + path=f"procedures/{procedure_id}/versions/{version}/unset-live", + body={}, + ) diff --git a/src/gradient_labs/client.py b/src/gradient_labs/client.py index abdd942..f6f3a6d 100644 --- a/src/gradient_labs/client.py +++ b/src/gradient_labs/client.py @@ -40,6 +40,17 @@ from ._procedure_read import read_procedure from ._procedure_list import list_procedures, ProcedureListParams, ProcedureListResponse from ._procedure_set_limit import set_procedure_limit, ProcedureLimitParams +from ._procedure_version_list import ( + list_procedure_versions, + ListProcedureVersionsResponse, +) +from ._procedure_version_live_set import set_procedure_live_version +from ._procedure_version_live_unset import unset_procedure_live_version +from ._procedure_version_experiment_set import ( + set_procedure_experiment_version, + SetProcedureExperimentVersionParams, +) +from ._procedure_version_experiment_unset import unset_procedure_experiment_version from ._tool_create import create_tool from ._tool_delete import delete_tool @@ -329,6 +340,82 @@ def set_procedure_limit( params=params, ) + def list_procedure_versions( + self, *, procedure_id: str + ) -> ListProcedureVersionsResponse: + """list_procedure_versions lists existing non-ephemeral versions of a procedure. + + Each procedure can have multiple versions, with one marked as "live" (production) + and optionally one marked as "experimental" for controlled testing. + + Note: requires a `Management` API key.""" + return list_procedure_versions( + client=self.http_client, + procedure_id=procedure_id, + ) + + def set_procedure_live_version(self, *, procedure_id: str, version: int) -> None: + """set_procedure_live_version promotes a specific version to be the live (production) version. + + The live version is the default version used by the agent when no experimental + versions are active. If the specified version is currently marked as experimental, + it will be promoted to live and will no longer be considered experimental. + + Note: requires a `Management` API key.""" + set_procedure_live_version( + client=self.http_client, + procedure_id=procedure_id, + version=version, + ) + + def unset_procedure_live_version(self, *, procedure_id: str, version: int) -> None: + """unset_procedure_live_version removes the specified version from being the live revision. + + Once unset, the version will no longer be used by default by the agent. + This does not delete the version or affect its experimental status (if any). + + Note: requires a `Management` API key.""" + unset_procedure_live_version( + client=self.http_client, + procedure_id=procedure_id, + version=version, + ) + + def set_procedure_experiment_version( + self, + *, + procedure_id: str, + version: int, + params: SetProcedureExperimentVersionParams, + ) -> None: + """set_procedure_experiment_version marks a version as experimental for A/B testing. + + Experimental versions are served to a limited number of conversations per day. + If an experiment already exists, it will only be replaced if the 'replace' + flag is set to True. + + Note: requires a `Management` API key.""" + set_procedure_experiment_version( + client=self.http_client, + procedure_id=procedure_id, + version=version, + params=params, + ) + + def unset_procedure_experiment_version( + self, *, procedure_id: str, version: int + ) -> None: + """unset_procedure_experiment_version removes experimental status from a version. + + Once unset, the version will no longer be used for A/B testing or served as an experiment. + + Note: requires a `Management` API key.""" + unset_procedure_experiment_version( + client=self.http_client, + procedure_id=procedure_id, + version=version, + ) + def create_tool(self, *, tool: Tool) -> Tool: """create_tool creates a new tool. diff --git a/src/gradient_labs/procedure.py b/src/gradient_labs/procedure.py index 518c1a0..fc6db4a 100644 --- a/src/gradient_labs/procedure.py +++ b/src/gradient_labs/procedure.py @@ -69,3 +69,53 @@ class Procedure: # max_daily_conversations is the maximum number of conversations that a procedure # can be used in on a given day, when it is rate limited. max_daily_conversations: int + + +@dataclass_json +@dataclass(frozen=True) +class ExperimentalConfig: + """Configuration for experimental procedure versions.""" + + # max_daily_conversations is the maximum number of conversations per day + # that can use this experimental version. + max_daily_conversations: int + + +@dataclass_json +@dataclass(frozen=True) +class ProcedureVersion: + """A specific version of a procedure.""" + + # name is the user-given name of the procedure at the time of this version. + name: str + + # description of the procedure at the time of this version. + description: str + + # version is a numeric identifier for the version. It is incremented every + # time a new version of the procedure is saved. + version: int + + # author is the ID of the user who created this version of the procedure. + author: str + + # created is the time at which this version of the procedure was created. + created: datetime = field( + metadata=config( + encoder=datetime.isoformat, + decoder=datetime.fromisoformat, + mm_field=fields.DateTime(format="iso"), + ) + ) + + # experimental indicates whether this is an experimental version that is used before "live", + # within the daily limit defined in experimental_config. + experimental: bool + + # experimental_config defines how the experimental version is limited. + # Relevant only if experimental == True. + experimental_config: ExperimentalConfig | None + + # live indicates whether this is the "production" version that is used by the agent by default, + # if there are no experimental versions or all of them have exceeded their limit. + live: bool