execlave-sdk 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
execlave/__init__.py ADDED
@@ -0,0 +1,40 @@
1
+ """
2
+ Execlave Python SDK
3
+
4
+ Official SDK for integrating AI agents with the Execlave governance platform.
5
+ Provides tracing, prompt management, and governance capabilities.
6
+ """
7
+
8
+ from .client import Execlave, ExeclaveClient
9
+ from .agent import Agent
10
+ from .trace import Trace
11
+ from .errors import (
12
+ ExeclaveError,
13
+ ExeclaveAuthError,
14
+ AgentPausedError,
15
+ PolicyBlockedError,
16
+ PolicyDeniedError,
17
+ ApprovalTimeoutError,
18
+ EnforcementUnavailableError,
19
+ QuotaExceededError,
20
+ PlanLimitExceededError,
21
+ )
22
+ from .connectors import run_openai_chat, run_langchain
23
+
24
+ __all__ = [
25
+ "Execlave",
26
+ "ExeclaveClient", # backward compat alias
27
+ "Agent",
28
+ "Trace",
29
+ "ExeclaveError",
30
+ "ExeclaveAuthError",
31
+ "AgentPausedError",
32
+ "PolicyBlockedError",
33
+ "PolicyDeniedError",
34
+ "ApprovalTimeoutError",
35
+ "EnforcementUnavailableError",
36
+ "QuotaExceededError",
37
+ "PlanLimitExceededError",
38
+ "run_openai_chat",
39
+ "run_langchain",
40
+ ]
execlave/agent.py ADDED
@@ -0,0 +1,204 @@
1
+ """Agent object returned from register_agent()."""
2
+
3
+ from typing import Any, Optional
4
+
5
+
6
+ class Agent:
7
+ """
8
+ Represents a registered agent. Provides prompt management methods.
9
+ """
10
+
11
+ def __init__(self, exe: Any, data: dict):
12
+ self._exe = exe
13
+ self.id: str = data.get("id", "")
14
+ self.agent_id: str = data.get("agentId", "")
15
+ self.name: str = data.get("name", "")
16
+ self.environment: str = data.get("environment", "")
17
+ self.status: str = data.get("status", "active")
18
+ self._data = data
19
+
20
+ @property
21
+ def is_paused(self) -> bool:
22
+ return self.status == "paused"
23
+
24
+ def deploy_prompt(
25
+ self,
26
+ prompt_template: str,
27
+ system_message: str | None = None,
28
+ model_name: str | None = None,
29
+ model_parameters: dict | None = None,
30
+ change_type: str = "minor",
31
+ change_description: str | None = None,
32
+ description: str | None = None,
33
+ version_tag: str | None = None,
34
+ environment: str | None = None,
35
+ require_approval: bool = True,
36
+ ) -> "PromptVersion":
37
+ """
38
+ Deploy a new prompt version for this agent.
39
+
40
+ Args:
41
+ prompt_template: The prompt template string with {placeholders}
42
+ system_message: Optional system message
43
+ model_name: Model to use (e.g. 'gpt-4-turbo')
44
+ model_parameters: Dict with temperature, max_tokens, etc.
45
+ change_type: 'major' | 'minor' | 'patch'
46
+ change_description: Description of what changed
47
+ description: Alias for change_description (for JS SDK parity)
48
+ version_tag: Semantic version tag (e.g. 'v1.6.0')
49
+ environment: Target environment
50
+ require_approval: Whether to require approval before deployment
51
+
52
+ Returns:
53
+ PromptVersion object
54
+ """
55
+ params = model_parameters or {}
56
+ resolved_description = change_description or description
57
+ payload = {
58
+ "agentId": self.id,
59
+ "promptTemplate": prompt_template,
60
+ "systemMessage": system_message,
61
+ "modelName": model_name,
62
+ "temperature": params.get("temperature"),
63
+ "maxTokens": params.get("max_tokens"),
64
+ "otherParams": {k: v for k, v in params.items() if k not in ("temperature", "max_tokens")},
65
+ "changeSummary": resolved_description or change_type,
66
+ "versionTag": version_tag,
67
+ }
68
+ # Remove None values
69
+ payload = {k: v for k, v in payload.items() if v is not None}
70
+
71
+ resp = self._exe._request("POST", self._exe._api_path("/prompt-versions"), json=payload)
72
+ version_data = resp.get("data", {})
73
+ return PromptVersion(self._exe, version_data)
74
+
75
+ def get_current_prompt(self) -> Optional["PromptVersion"]:
76
+ """
77
+ Fetch the currently deployed prompt version for this agent.
78
+
79
+ Returns:
80
+ PromptVersion or None if no version is deployed.
81
+ """
82
+ resp = self._exe._request(
83
+ "GET",
84
+ self._exe._api_path(f"/prompt-versions?agentId={self.id}&deployed=true"),
85
+ )
86
+ versions = resp.get("data", [])
87
+ for v in versions:
88
+ if v.get("isDeployed"):
89
+ return PromptVersion(self._exe, v)
90
+ return None
91
+
92
+ def list_prompt_versions(self) -> list["PromptVersion"]:
93
+ """
94
+ List all prompt versions for this agent.
95
+
96
+ Returns:
97
+ List of PromptVersion objects.
98
+ """
99
+ resp = self._exe._request(
100
+ "GET",
101
+ self._exe._api_path(f"/prompt-versions?agentId={self.id}"),
102
+ )
103
+ return [PromptVersion(self._exe, v) for v in resp.get("data", [])]
104
+
105
+ def refresh_status(self) -> str:
106
+ """
107
+ Poll the current agent status from the API.
108
+
109
+ Updates self.status in-place and returns it.
110
+ """
111
+ resp = self._exe._request(
112
+ "GET",
113
+ self._exe._api_path(f"/agents/{self.id}/status-poll"),
114
+ )
115
+ data = resp.get("data", {})
116
+ self.status = data.get("status", self.status)
117
+ return self.status
118
+
119
+ def promote_to_production(
120
+ self,
121
+ version_id: str,
122
+ require_approval: bool = False,
123
+ deployment_notes: str | None = None,
124
+ ) -> "PromptVersion":
125
+ """
126
+ Promote a specific prompt version to production.
127
+
128
+ Args:
129
+ version_id: The ID of the version to promote.
130
+ require_approval: Whether to require admin approval.
131
+ deployment_notes: Notes for the deployment.
132
+
133
+ Returns:
134
+ PromptVersion object for the promoted version.
135
+ """
136
+ payload: dict = {
137
+ "environment": "production",
138
+ "requireApproval": require_approval,
139
+ }
140
+ if deployment_notes is not None:
141
+ payload["deploymentNotes"] = deployment_notes
142
+
143
+ resp = self._exe._request(
144
+ "POST",
145
+ self._exe._api_path(f"/prompt-versions/{version_id}/deploy"),
146
+ json=payload,
147
+ )
148
+ return PromptVersion(self._exe, resp.get("data", {}))
149
+
150
+ def rollback_to_version(self, version_number: int, reason: str = "Rollback requested") -> "PromptVersion":
151
+ """
152
+ Rollback to a specific prompt version by version number.
153
+
154
+ Args:
155
+ version_number: The version number to rollback to
156
+ reason: Reason for the rollback
157
+ """
158
+ # Find the version by number
159
+ versions = self._exe._request("GET", self._exe._api_path(f"/prompt-versions?agentId={self.id}"))
160
+ target = None
161
+ for v in versions.get("data", []):
162
+ if v.get("versionNumber") == version_number:
163
+ target = v
164
+ break
165
+
166
+ if not target:
167
+ from .errors import ExeclaveError
168
+ raise ExeclaveError(f"Version {version_number} not found for agent {self.agent_id}")
169
+
170
+ resp = self._exe._request("POST", self._exe._api_path(f"/prompt-versions/{target['id']}/rollback"), json={"reason": reason})
171
+ return PromptVersion(self._exe, resp.get("data", {}))
172
+
173
+
174
+ class PromptVersion:
175
+ """Represents a prompt version."""
176
+
177
+ def __init__(self, exe: Any, data: dict):
178
+ self._exe = exe
179
+ self.id: str = data.get("id", "")
180
+ self.version_number: int = data.get("versionNumber", 0)
181
+ self.version_tag: str = data.get("versionTag", "")
182
+ self.approval_status: str = data.get("approvalStatus", "pending")
183
+ self.is_active: bool = data.get("isActive", False)
184
+ self._data = data
185
+
186
+ def promote_to_production(
187
+ self,
188
+ require_approval: bool = True,
189
+ deployment_notes: str | None = None,
190
+ ) -> "PromptVersion":
191
+ """
192
+ Deploy this version to production.
193
+
194
+ Args:
195
+ require_approval: Whether approval is required first
196
+ deployment_notes: Notes about this deployment
197
+ """
198
+ resp = self._exe._request("POST", self._exe._api_path(f"/prompt-versions/{self.id}/deploy"), json={
199
+ "environment": "production"
200
+ })
201
+ data = resp.get("data", {})
202
+ self.approval_status = data.get("approvalStatus", self.approval_status)
203
+ self.is_active = data.get("isActive", self.is_active)
204
+ return self