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.
- billing/__init__.py +6 -0
- billing/api.py +55 -0
- billing/client.py +14 -0
- billing/schema.py +15 -0
- constants/__init__.py +90 -0
- core/__init__.py +53 -0
- core/agents/__init__.py +42 -0
- core/agents/execution/__init__.py +49 -0
- core/agents/execution/api.py +283 -0
- core/agents/execution/client.py +1139 -0
- core/agents/models.py +99 -0
- core/workflows/WORKFLOW_ARCHITECTURE.md +417 -0
- core/workflows/__init__.py +31 -0
- core/workflows/bulk/__init__.py +14 -0
- core/workflows/bulk/api.py +202 -0
- core/workflows/bulk/client.py +115 -0
- core/workflows/bulk/schema.py +58 -0
- core/workflows/models.py +49 -0
- core/workflows/scheduling/__init__.py +9 -0
- core/workflows/scheduling/api.py +179 -0
- core/workflows/scheduling/client.py +128 -0
- core/workflows/scheduling/schema.py +74 -0
- core/workflows/tool_execution/__init__.py +16 -0
- core/workflows/tool_execution/api.py +172 -0
- core/workflows/tool_execution/client.py +195 -0
- core/workflows/tool_execution/schema.py +40 -0
- exceptions/__init__.py +21 -0
- simplai_sdk/__init__.py +7 -0
- simplai_sdk/simplai.py +239 -0
- simplai_sdk-0.1.0.dist-info/METADATA +728 -0
- simplai_sdk-0.1.0.dist-info/RECORD +42 -0
- simplai_sdk-0.1.0.dist-info/WHEEL +5 -0
- simplai_sdk-0.1.0.dist-info/licenses/LICENSE +21 -0
- simplai_sdk-0.1.0.dist-info/top_level.txt +7 -0
- traces/__init__.py +1 -0
- traces/agents/__init__.py +55 -0
- traces/agents/api.py +350 -0
- traces/agents/client.py +697 -0
- traces/agents/models.py +249 -0
- traces/workflows/__init__.py +0 -0
- utils/__init__.py +0 -0
- 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
|
+
|