simplai-sdk 0.1.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.
Files changed (42) hide show
  1. billing/__init__.py +6 -0
  2. billing/api.py +55 -0
  3. billing/client.py +14 -0
  4. billing/schema.py +15 -0
  5. constants/__init__.py +90 -0
  6. core/__init__.py +53 -0
  7. core/agents/__init__.py +42 -0
  8. core/agents/execution/__init__.py +49 -0
  9. core/agents/execution/api.py +283 -0
  10. core/agents/execution/client.py +1139 -0
  11. core/agents/models.py +99 -0
  12. core/workflows/WORKFLOW_ARCHITECTURE.md +417 -0
  13. core/workflows/__init__.py +31 -0
  14. core/workflows/bulk/__init__.py +14 -0
  15. core/workflows/bulk/api.py +202 -0
  16. core/workflows/bulk/client.py +115 -0
  17. core/workflows/bulk/schema.py +58 -0
  18. core/workflows/models.py +49 -0
  19. core/workflows/scheduling/__init__.py +9 -0
  20. core/workflows/scheduling/api.py +179 -0
  21. core/workflows/scheduling/client.py +128 -0
  22. core/workflows/scheduling/schema.py +74 -0
  23. core/workflows/tool_execution/__init__.py +16 -0
  24. core/workflows/tool_execution/api.py +172 -0
  25. core/workflows/tool_execution/client.py +195 -0
  26. core/workflows/tool_execution/schema.py +40 -0
  27. exceptions/__init__.py +21 -0
  28. simplai_sdk/__init__.py +7 -0
  29. simplai_sdk/simplai.py +239 -0
  30. simplai_sdk-0.1.0.dist-info/METADATA +728 -0
  31. simplai_sdk-0.1.0.dist-info/RECORD +42 -0
  32. simplai_sdk-0.1.0.dist-info/WHEEL +5 -0
  33. simplai_sdk-0.1.0.dist-info/licenses/LICENSE +21 -0
  34. simplai_sdk-0.1.0.dist-info/top_level.txt +7 -0
  35. traces/__init__.py +1 -0
  36. traces/agents/__init__.py +55 -0
  37. traces/agents/api.py +350 -0
  38. traces/agents/client.py +697 -0
  39. traces/agents/models.py +249 -0
  40. traces/workflows/__init__.py +0 -0
  41. utils/__init__.py +0 -0
  42. utils/config.py +117 -0
@@ -0,0 +1,128 @@
1
+ """Public SDK interface for scheduled workflow execution."""
2
+
3
+ from typing import Dict, List, Optional, Union
4
+
5
+ from .api import schedule_run_api, cancel_schedule_run_api
6
+
7
+
8
+ async def schedule_run(
9
+ workflow_id: str,
10
+ scheduled_run_type: str,
11
+ cron_expression: str,
12
+ time_zone: str,
13
+ input_mapping: List[Dict[str, str]],
14
+ api_key: str,
15
+ file_url: Optional[str] = None,
16
+ batch_size: Optional[int] = None,
17
+ ) -> Dict[str, str]:
18
+ """
19
+ Schedule a workflow run (supports both BULK and MANUAL types).
20
+
21
+ Args:
22
+ workflow_id: The workflow ID to execute
23
+ scheduled_run_type: Type of scheduled run - 'BULK' or 'MANUAL'
24
+ cron_expression: Cron expression for scheduling (e.g., "47 22 11 2 1 ?")
25
+ time_zone: Time zone for the schedule (e.g., "Asia/Kolkata")
26
+ input_mapping: List of dictionaries with input field mappings.
27
+ For BULK: [{"app_input_field": "number_1", "file_input_field": "A"}]
28
+ For MANUAL: [{"app_input_field": "interview_id", "value": "0148eee6-a867-4c7a-8d42-f039a5ef6adf"}]
29
+ api_key: API key for authentication
30
+ file_url: URL/link to the input file (required for BULK, ignored for MANUAL)
31
+ batch_size: Number of records to process per batch (required for BULK, optional for MANUAL)
32
+
33
+ Returns:
34
+ Dictionary with unique_id, job_master_id, trace_id, and status:
35
+ {
36
+ "unique_id": str,
37
+ "job_master_id": str,
38
+ "trace_id": str,
39
+ "status": str
40
+ }
41
+
42
+ Example - BULK scheduled run:
43
+ await schedule_run(
44
+ workflow_id="695cafaffa8b738fc69146aa",
45
+ scheduled_run_type="BULK",
46
+ cron_expression="47 22 11 2 1 ?",
47
+ time_zone="Asia/Kolkata",
48
+ input_mapping=[
49
+ {"app_input_field": "number_1", "file_input_field": "A"},
50
+ {"app_input_field": "number_2", "file_input_field": "B"}
51
+ ],
52
+ api_key="your_api_key",
53
+ file_url="s3://media-simplai/2025-10-03T16:04:23.490089839-bulk_test.csv",
54
+ batch_size=1,
55
+ )
56
+
57
+ Example - MANUAL scheduled run:
58
+ await schedule_run(
59
+ workflow_id="6915a7785ac679cd4de1d4d5",
60
+ scheduled_run_type="MANUAL",
61
+ cron_expression="47 22 11 2 1 *",
62
+ time_zone="Asia/Kolkata",
63
+ input_mapping=[
64
+ {"app_input_field": "interview_id", "value": "0148eee6-a867-4c7a-8d42-f039a5ef6adf"}
65
+ ],
66
+ api_key="your_api_key",
67
+ )
68
+ """
69
+ response = await schedule_run_api(
70
+ workflow_id=workflow_id,
71
+ scheduled_run_type=scheduled_run_type,
72
+ cron_expression=cron_expression,
73
+ time_zone=time_zone,
74
+ input_mapping=input_mapping,
75
+ api_key=api_key,
76
+ file_url=file_url,
77
+ batch_size=batch_size,
78
+ )
79
+
80
+ result: Dict[str, str] = {
81
+ "status": response.status,
82
+ }
83
+
84
+ if response.result:
85
+ result["unique_id"] = response.result.unique_id
86
+ result["job_master_id"] = response.result.job_master_id
87
+
88
+ if response.trace_id:
89
+ result["trace_id"] = response.trace_id
90
+
91
+ return result
92
+
93
+
94
+ async def cancel_schedule_run(
95
+ job_master_id: Union[int, str],
96
+ unique_id: str,
97
+ api_key: str,
98
+ ) -> Dict[str, str]:
99
+ """
100
+ Cancel a scheduled run by job_master_id and unique_id.
101
+
102
+ Args:
103
+ job_master_id: Job master identifier (can be int or str)
104
+ unique_id: Unique identifier for the scheduled run
105
+ api_key: API key for authentication
106
+
107
+ Returns:
108
+ Dictionary with status and trace_id:
109
+ {
110
+ "status": str,
111
+ "trace_id": str
112
+ }
113
+ """
114
+ response = await cancel_schedule_run_api(
115
+ job_master_id=job_master_id,
116
+ unique_id=unique_id,
117
+ api_key=api_key,
118
+ )
119
+
120
+ result: Dict[str, str] = {
121
+ "status": response.status,
122
+ }
123
+
124
+ if response.trace_id:
125
+ result["trace_id"] = response.trace_id
126
+
127
+ return result
128
+
@@ -0,0 +1,74 @@
1
+ """Pydantic schemas for scheduled workflow execution API requests and responses."""
2
+
3
+ from typing import Any, Dict, List, Optional, Union
4
+ from pydantic import BaseModel, Field
5
+
6
+
7
+ class BulkInputFieldMapping(BaseModel):
8
+ """Schema for bulk input field mapping (file-based)."""
9
+ app_input_field: str = Field(..., description="The workflow input field name")
10
+ file_input_field: str = Field(..., description="The file column/field name")
11
+
12
+
13
+ class ManualInputFieldMapping(BaseModel):
14
+ """Schema for manual input field mapping (value-based)."""
15
+ app_input_field: str = Field(..., description="The workflow input field name")
16
+ value: str = Field(..., description="The static value for the input field")
17
+
18
+
19
+ class ScheduleRunRequest(BaseModel):
20
+ """Request schema for scheduling a workflow run (supports both BULK and MANUAL)."""
21
+ tool_id: str = Field(..., description="The workflow ID")
22
+ scheduled_run_type: str = Field(..., description="Type of scheduled run: 'BULK' or 'MANUAL'")
23
+ cron_expression: str = Field(..., description="Cron expression for scheduling")
24
+ time_zone: str = Field(..., description="Time zone for the schedule (e.g., 'Asia/Kolkata')")
25
+
26
+ # BULK-specific fields (optional, required when scheduled_run_type is 'BULK')
27
+ file_url: Optional[str] = Field(None, description="URL/link to the input file (required for BULK)")
28
+ batch_size: Optional[int] = Field(None, description="Number of records to process per batch (required for BULK)")
29
+
30
+ # Input field mappings - can be either bulk or manual format
31
+ input_fields_mapping: List[Dict[str, str]] = Field(
32
+ ...,
33
+ description="List of input field mappings. For BULK: [{'app_input_field': 'x', 'file_input_field': 'A'}]. For MANUAL: [{'app_input_field': 'x', 'value': 'y'}]"
34
+ )
35
+
36
+ class Config:
37
+ populate_by_name = True
38
+
39
+
40
+ class ScheduleRunResult(BaseModel):
41
+ """Nested result object in schedule run response."""
42
+ unique_id: str = Field(..., description="Unique identifier for the scheduled run")
43
+ job_master_id: str = Field(..., description="Job master identifier")
44
+
45
+
46
+ class ScheduleRunResponse(BaseModel):
47
+ """Response schema from schedule run API."""
48
+ app_id: str = Field(..., alias="app_id", description="The workflow/app ID")
49
+ status: str = Field(..., description="Execution status (e.g., ACCEPTED)")
50
+ execution_mode: Optional[str] = Field(None, description="Execution mode (e.g., ASYNC)")
51
+ result: Optional[ScheduleRunResult] = Field(None, description="Result object with unique_id and job_master_id")
52
+ trace_id: Optional[str] = Field(None, description="Trace identifier for the scheduled run")
53
+
54
+ class Config:
55
+ populate_by_name = True
56
+
57
+
58
+ class ScheduleCancelRequest(BaseModel):
59
+ """Request schema for canceling a scheduled run."""
60
+ job_master_id: Union[int, str] = Field(..., description="Job master identifier")
61
+ unique_id: str = Field(..., description="Unique identifier for the scheduled run")
62
+
63
+ class Config:
64
+ populate_by_name = True
65
+
66
+
67
+ class ScheduleCancelResponse(BaseModel):
68
+ """Response schema from schedule cancel API."""
69
+ status: str = Field(..., description="Cancellation status (e.g., CANCELLED)")
70
+ trace_id: Optional[str] = Field(None, description="Trace identifier for the cancelled run")
71
+
72
+ class Config:
73
+ populate_by_name = True
74
+
@@ -0,0 +1,16 @@
1
+ """Public SDK interface for workflow execution."""
2
+
3
+ from .client import (
4
+ execute_workflow,
5
+ get_tool_result,
6
+ execute_and_wait_workflow,
7
+ cancel_execution,
8
+ )
9
+
10
+ __all__ = [
11
+ "execute_workflow",
12
+ "get_tool_result",
13
+ "execute_and_wait_workflow",
14
+ "cancel_execution",
15
+ ]
16
+
@@ -0,0 +1,172 @@
1
+ """Low-level HTTP API layer for workflow execution."""
2
+
3
+ import json
4
+ from typing import Any, Dict, Optional
5
+ import httpx
6
+
7
+ from .schema import (
8
+ ExecuteWorkflowRequest,
9
+ ExecuteWorkflowResponse,
10
+ ExecutionStatusResponse,
11
+ CancelExecutionResponse,
12
+ )
13
+ from exceptions import APIException
14
+
15
+
16
+ from constants import WORKFLOW_BASE_URL as BASE_URL
17
+
18
+ async def execute_workflow_api(
19
+ workflow_id: str,
20
+ inputs: Dict[str, Any],
21
+ api_key: str,
22
+ ) -> ExecuteWorkflowResponse:
23
+ """
24
+ Execute a workflow via the REST API.
25
+
26
+ Args:
27
+ workflow_id: The workflow ID to execute
28
+ inputs: Input parameters for the workflow
29
+ api_key: API key for authentication
30
+
31
+ Returns:
32
+ ExecuteWorkflowResponse with execution_id and status
33
+
34
+ Raises:
35
+ APIException: If the API request fails
36
+ """
37
+ url = f"{BASE_URL}/execute"
38
+ headers = {
39
+ "Accept": "application/json",
40
+ "Content-Type": "application/json",
41
+ "PIM-SID": api_key,
42
+ }
43
+
44
+ request_body = ExecuteWorkflowRequest(
45
+ app_id=workflow_id,
46
+ inputs=inputs,
47
+ )
48
+
49
+
50
+ async with httpx.AsyncClient() as client:
51
+ try:
52
+ response = await client.post(
53
+ url,
54
+ headers=headers,
55
+ json=request_body.model_dump(),
56
+ timeout=30.0,
57
+ )
58
+ response.raise_for_status()
59
+ data = response.json()
60
+
61
+ return ExecuteWorkflowResponse(
62
+ execution_id=data.get("execution_id", ""),
63
+ status=data.get("status", ""),
64
+ )
65
+ except httpx.HTTPStatusError as e:
66
+ raise APIException(
67
+ f"API request failed with status {e.response.status_code}: {e.response.text}",
68
+ status_code=e.response.status_code,
69
+ )
70
+ except httpx.RequestError as e:
71
+ raise APIException(f"Request failed: {str(e)}")
72
+
73
+
74
+ async def get_execution_status_api(
75
+ execution_id: str,
76
+ api_key: str,
77
+ ) -> ExecutionStatusResponse:
78
+ """
79
+ Get the execution status and result via the REST API.
80
+
81
+ Args:
82
+ execution_id: The execution ID to check
83
+ api_key: API key for authentication
84
+
85
+ Returns:
86
+ ExecutionStatusResponse with executionId, traceId, status, and parsed result
87
+
88
+ Raises:
89
+ APIException: If the API request fails
90
+ """
91
+ url = f"{BASE_URL}/executions/{execution_id}/status"
92
+ headers = {
93
+ "Accept": "application/json",
94
+ "Content-Type": "application/json",
95
+ "PIM-SID": api_key,
96
+ }
97
+
98
+ async with httpx.AsyncClient() as client:
99
+ try:
100
+ response = await client.get(
101
+ url,
102
+ headers=headers,
103
+ timeout=30.0,
104
+ )
105
+ response.raise_for_status()
106
+ data = response.json()
107
+
108
+ # Extract and parse result from raw_response.api_response.result
109
+ raw_response = data.get("raw_response", {})
110
+ api_response = raw_response.get("api_response") if raw_response else None
111
+
112
+ return ExecutionStatusResponse(
113
+ executionId=data.get("executionId", ""),
114
+ traceId=data.get("traceId"),
115
+ status=data.get("status", ""),
116
+ api_response=api_response,
117
+ )
118
+ except httpx.HTTPStatusError as e:
119
+ raise APIException(
120
+ f"API request failed with status {e.response.status_code}: {e.response.text}",
121
+ status_code=e.response.status_code,
122
+ )
123
+ except httpx.RequestError as e:
124
+ raise APIException(f"Request failed: {str(e)}")
125
+
126
+
127
+ async def cancel_execution_api(
128
+ execution_id: str,
129
+ api_key: str,
130
+ ) -> CancelExecutionResponse:
131
+ """
132
+ Cancel an execution via the REST API.
133
+
134
+ Args:
135
+ execution_id: The execution ID to cancel
136
+ api_key: API key for authentication
137
+
138
+ Returns:
139
+ CancelExecutionResponse with execution_id and status
140
+
141
+ Raises:
142
+ APIException: If the API request fails
143
+ """
144
+ url = f"{BASE_URL}/executions/{execution_id}/cancel"
145
+ headers = {
146
+ "Accept": "application/json",
147
+ "Content-Type": "application/json",
148
+ "PIM-SID": api_key,
149
+ }
150
+
151
+ async with httpx.AsyncClient() as client:
152
+ try:
153
+ response = await client.post(
154
+ url,
155
+ headers=headers,
156
+ timeout=30.0,
157
+ )
158
+ response.raise_for_status()
159
+ data = response.json()
160
+
161
+ return CancelExecutionResponse(
162
+ execution_id=data.get("execution_id", ""),
163
+ status=data.get("status", ""),
164
+ )
165
+ except httpx.HTTPStatusError as e:
166
+ raise APIException(
167
+ f"API request failed with status {e.response.status_code}: {e.response.text}",
168
+ status_code=e.response.status_code,
169
+ )
170
+ except httpx.RequestError as e:
171
+ raise APIException(f"Request failed: {str(e)}")
172
+
@@ -0,0 +1,195 @@
1
+ """Public SDK interface for workflow execution."""
2
+
3
+ import asyncio
4
+ import time
5
+ from typing import Any, Dict, Optional
6
+
7
+ from constants import TERMINAL_STATES
8
+
9
+ from .api import execute_workflow_api, get_execution_status_api, cancel_execution_api
10
+ from exceptions import TimeoutException
11
+
12
+
13
+
14
+ async def execute_workflow(
15
+ workflow_id: str,
16
+ inputs: Dict[str, Any],
17
+ api_key: str,
18
+ ) -> Dict[str, str]:
19
+ """
20
+ Execute a workflow and return the execution ID.
21
+
22
+ Args:
23
+ workflow_id: The workflow ID to execute
24
+ inputs: Input parameters for the workflow
25
+ api_key: API key for authentication
26
+
27
+ Returns:
28
+ Dictionary with execution_id and status:
29
+ {
30
+ "execution_id": str,
31
+ "status": str
32
+ }
33
+ """
34
+ response = await execute_workflow_api(
35
+ workflow_id=workflow_id,
36
+ inputs=inputs,
37
+ api_key=api_key,
38
+ )
39
+
40
+ return {
41
+ "execution_id": response.execution_id,
42
+ "status": response.status,
43
+ }
44
+
45
+
46
+ async def get_tool_result(
47
+ execution_id: str,
48
+ api_key: str,
49
+ ) -> Dict[str, Any]:
50
+ """
51
+ Get the execution result by execution ID.
52
+
53
+ Args:
54
+ execution_id: The execution ID to check
55
+ api_key: API key for authentication
56
+
57
+ Returns:
58
+ Dictionary with executionId, traceId, status, and result:
59
+ {
60
+ "executionId": str,
61
+ "traceId": str (optional),
62
+ "status": str,
63
+ "result": Any (parsed JSON if possible)
64
+ }
65
+ """
66
+ response = await get_execution_status_api(
67
+ execution_id=execution_id,
68
+ api_key=api_key,
69
+ )
70
+
71
+ result = {
72
+ "executionId": response.executionId,
73
+ "status": response.status,
74
+ }
75
+
76
+ if response.traceId:
77
+ result["traceId"] = response.traceId
78
+
79
+ if response.api_response is not None:
80
+ result["api_response"] = response.api_response
81
+
82
+ return result
83
+
84
+
85
+ async def execute_and_wait_workflow(
86
+ workflow_id: str,
87
+ inputs: Dict[str, Any],
88
+ api_key: str,
89
+ timeout: float = 60.0,
90
+ poll_interval: float = 2.0,
91
+ ) -> Dict[str, Any]:
92
+ """
93
+ Execute a workflow and wait for completion with polling.
94
+
95
+ Args:
96
+ workflow_id: The workflow ID to execute
97
+ inputs: Input parameters for the workflow
98
+ api_key: API key for authentication
99
+ timeout: Maximum time to wait in seconds (default: 60.0)
100
+ poll_interval: Time between polls in seconds (default: 2.0)
101
+
102
+ Returns:
103
+ Dictionary with executionId, traceId, status, and result:
104
+ {
105
+ "executionId": str,
106
+ "traceId": str (optional),
107
+ "status": str,
108
+ "result": Any (parsed JSON if possible)
109
+ }
110
+
111
+ Raises:
112
+ TimeoutException: If the execution doesn't complete within the timeout
113
+ """
114
+ # Step 1: Execute the workflow
115
+ execution_info = await execute_workflow(
116
+ workflow_id=workflow_id,
117
+ inputs=inputs,
118
+ api_key=api_key,
119
+ )
120
+
121
+ execution_id = execution_info["execution_id"]
122
+ start_time = time.time()
123
+
124
+ # Step 2: Poll for completion
125
+ while True:
126
+ elapsed_time = time.time() - start_time
127
+
128
+ # Check timeout
129
+ if elapsed_time >= timeout:
130
+ raise TimeoutException(
131
+ f"Workflow execution timed out after {timeout} seconds. "
132
+ f"Execution ID: {execution_id}"
133
+ )
134
+
135
+ # Get execution status
136
+ result = await get_tool_result(
137
+ execution_id=execution_id,
138
+ api_key=api_key,
139
+ )
140
+
141
+ status = result.get("status", "").upper()
142
+
143
+ # Check if we've reached a terminal state
144
+ if status in TERMINAL_STATES:
145
+ return result
146
+
147
+ # If still pending, wait before next poll
148
+ if status == "PENDING":
149
+ # Calculate remaining time and sleep for poll_interval or remaining time, whichever is smaller
150
+ remaining_time = timeout - elapsed_time
151
+ sleep_time = min(poll_interval, remaining_time)
152
+ if sleep_time > 0:
153
+ await asyncio.sleep(sleep_time)
154
+ else:
155
+ # No time left, will timeout on next iteration
156
+ continue
157
+
158
+ # For any other non-terminal state, continue polling
159
+ remaining_time = timeout - elapsed_time
160
+ sleep_time = min(poll_interval, remaining_time)
161
+ if sleep_time > 0:
162
+ await asyncio.sleep(sleep_time)
163
+ else:
164
+ # No time left, will timeout on next iteration
165
+ continue
166
+
167
+
168
+ async def cancel_execution(
169
+ execution_id: str,
170
+ api_key: str,
171
+ ) -> Dict[str, str]:
172
+ """
173
+ Cancel an execution by execution ID.
174
+
175
+ Args:
176
+ execution_id: The execution ID to cancel
177
+ api_key: API key for authentication
178
+
179
+ Returns:
180
+ Dictionary with execution_id and status:
181
+ {
182
+ "execution_id": str,
183
+ "status": str
184
+ }
185
+ """
186
+ response = await cancel_execution_api(
187
+ execution_id=execution_id,
188
+ api_key=api_key,
189
+ )
190
+
191
+ return {
192
+ "execution_id": response.execution_id,
193
+ "status": response.status,
194
+ }
195
+
@@ -0,0 +1,40 @@
1
+ """Pydantic schemas for workflow execution API requests and responses."""
2
+
3
+ from typing import Any, Dict, Optional
4
+ from pydantic import BaseModel, Field
5
+
6
+
7
+ class ExecuteWorkflowRequest(BaseModel):
8
+ """Request schema for executing a workflow."""
9
+ app_id: str = Field(..., description="The workflow ID")
10
+ inputs: Dict[str, Any] = Field(default_factory=dict, description="Input parameters for the workflow")
11
+
12
+
13
+ class ExecuteWorkflowResponse(BaseModel):
14
+ """Response schema from execute workflow API."""
15
+ execution_id: str = Field(..., alias="execution_id")
16
+ status: str = Field(..., description="Execution status (e.g., PENDING)")
17
+
18
+ class Config:
19
+ populate_by_name = True
20
+
21
+
22
+ class ExecutionStatusResponse(BaseModel):
23
+ """Response schema from get execution status API."""
24
+ executionId: str = Field(..., description="The execution ID")
25
+ traceId: Optional[str] = Field(None, description="The trace ID")
26
+ status: str = Field(..., description="Execution status (e.g., COMPLETED, FAILED, CANCELLED, PENDING)")
27
+ api_response: Optional[Dict[str, Any]] = Field(None, description="Raw response from the API")
28
+
29
+ class Config:
30
+ populate_by_name = True
31
+
32
+
33
+ class CancelExecutionResponse(BaseModel):
34
+ """Response schema from cancel execution API."""
35
+ execution_id: str = Field(..., alias="execution_id", description="The execution ID")
36
+ status: str = Field(..., description="Execution status (e.g., COMPLETE)")
37
+
38
+ class Config:
39
+ populate_by_name = True
40
+
exceptions/__init__.py ADDED
@@ -0,0 +1,21 @@
1
+ """Custom exceptions for the SimplAI Python SDK."""
2
+
3
+ from typing import Optional
4
+
5
+
6
+ class SimplAIException(Exception):
7
+ """Base exception for all SimplAI SDK exceptions."""
8
+ pass
9
+
10
+
11
+ class TimeoutException(SimplAIException):
12
+ """Raised when a workflow execution times out."""
13
+ pass
14
+
15
+
16
+ class APIException(SimplAIException):
17
+ """Raised when an API request fails."""
18
+ def __init__(self, message: str, status_code: Optional[int] = None):
19
+ super().__init__(message)
20
+ self.status_code = status_code
21
+
@@ -0,0 +1,7 @@
1
+ from .simplai import SimplAI
2
+ from traces.agents import get_trace_tree
3
+
4
+ __all__ = [
5
+ "SimplAI",
6
+ "get_trace_tree",
7
+ ]