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
core/agents/models.py ADDED
@@ -0,0 +1,99 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from enum import Enum
5
+ from typing import Any, Dict, List, Optional
6
+
7
+
8
+ class AgentStatus(str, Enum):
9
+ """Known agent response status values returned by the Simplai agent API.
10
+
11
+ The API returns messageStatus as an integer:
12
+ - 0 or None = PENDING/PROCESSING
13
+ - 1 = PROCESSING
14
+ - 2 = COMPLETED
15
+ - Other values = FAILED/ERROR
16
+ """
17
+
18
+ PENDING = "PENDING"
19
+ PROCESSING = "PROCESSING"
20
+ COMPLETED = "COMPLETED"
21
+ FAILED = "FAILED"
22
+ TIMEOUT = "TIMEOUT"
23
+ UNKNOWN = "UNKNOWN"
24
+
25
+ @classmethod
26
+ def from_raw(cls, value: Any) -> "AgentStatus":
27
+ """Convert a raw status value from the API into an AgentStatus.
28
+
29
+ Handles both integer messageStatus and string status values.
30
+ """
31
+ # Handle integer messageStatus (0, 1, 2, etc.)
32
+ if isinstance(value, int):
33
+ if value == 0:
34
+ return cls.PROCESSING # 0 typically means processing
35
+ elif value == 1:
36
+ return cls.PROCESSING
37
+ elif value == 2:
38
+ return cls.COMPLETED
39
+ else:
40
+ return cls.FAILED
41
+
42
+ # Handle None/empty
43
+ if value is None:
44
+ return cls.PROCESSING
45
+
46
+ # Handle string values
47
+ if isinstance(value, str):
48
+ upper = value.upper()
49
+ for member in cls:
50
+ if member.value == upper:
51
+ return member
52
+
53
+ return cls.UNKNOWN
54
+
55
+
56
+ @dataclass
57
+ class AgentMessage:
58
+ """Represents a message in agent conversation."""
59
+
60
+ role: str # "user" or "assistant"
61
+ content: str
62
+ message_id: Optional[str] = None
63
+
64
+
65
+ @dataclass
66
+ class AgentResult:
67
+ """Final result of an agent conversation."""
68
+
69
+ conversation_id: str
70
+ message_id: str
71
+ status: AgentStatus
72
+ response: str
73
+ payload: Dict[str, Any] # Full response from API
74
+ trace_id: Optional[str] = None # Trace ID for tracing (from response)
75
+ node_id: Optional[str] = None # Node ID for tracing (from response)
76
+
77
+ @property
78
+ def succeeded(self) -> bool:
79
+ """True if the agent conversation completed successfully."""
80
+ return self.status == AgentStatus.COMPLETED
81
+
82
+
83
+ @dataclass
84
+ class AgentStreamChunk:
85
+ """Represents a chunk in streaming agent response."""
86
+
87
+ content: str
88
+ conversation_id: Optional[str] = None
89
+ message_id: Optional[str] = None
90
+ is_complete: bool = False
91
+ metadata: Optional[Dict[str, Any]] = None
92
+ trace_id: Optional[str] = None # Trace ID from first chunk (for request-level traces)
93
+ node_id: Optional[str] = None # Node ID from first chunk (for tree-based traces)
94
+ tree_id: Optional[str] = None # Tree ID from first chunk (for tree-based traces)
95
+
96
+
97
+ class AgentExecutionError(Exception):
98
+ """Raised when an agent conversation fails, times out, or cannot be executed."""
99
+
@@ -0,0 +1,417 @@
1
+ # Workflow Execution Architecture
2
+
3
+ ## Overview
4
+ The workflow execution system is organized in layers, from high-level API functions down to low-level HTTP client methods. Both synchronous and asynchronous execution paths are supported.
5
+
6
+ ## Architecture Layers
7
+
8
+ ```
9
+ ┌─────────────────────────────────────────────────────────────┐
10
+ │ API Layer (api.py) │
11
+ │ - execute_workflow() (sync high-level) │
12
+ │ - aexecute_workflow() (async high-level) │
13
+ │ - get_execution_status() (sync status check) │
14
+ │ - aget_execution_status() (async status check) │
15
+ └────────────────────┬────────────────────────────────────────┘
16
+
17
+
18
+ ┌─────────────────────────────────────────────────────────────┐
19
+ │ Client Layer (client.py) │
20
+ │ - SimplaiClient class │
21
+ │ ├─ execute_once() / aexecute_once() │
22
+ │ ├─ get_status_once() / aget_status_once() │
23
+ │ └─ execute_and_wait() / aexecute_and_wait() │
24
+ └────────────────────┬────────────────────────────────────────┘
25
+
26
+
27
+ ┌─────────────────────────────────────────────────────────────┐
28
+ │ HTTP Layer (httpx) │
29
+ │ - _request_with_retries_sync() │
30
+ │ - _request_with_retries_async() │
31
+ └──────────────────────────────────────────────────────────────┘
32
+ ```
33
+
34
+ ## Module Responsibilities
35
+
36
+ ### 1. **constants.py** - Configuration
37
+ - `DEFAULT_BASE_URL`: Base API endpoint
38
+ - `EXECUTE_PATH`: Endpoint for workflow execution
39
+ - `STATUS_PATH_TEMPLATE`: Endpoint template for status checks
40
+
41
+ ### 2. **models.py** - Data Models
42
+ - `ExecutionStatus`: Enum for workflow states (PENDING, RUNNING, SUCCEEDED, etc.)
43
+ - `WorkflowResult`: Result container with execution_id, status, and payload
44
+ - `WorkflowExecutionError`: Exception for workflow failures
45
+
46
+ ### 3. **client.py** - HTTP Client & Polling Logic
47
+ - `SimplaiClient`: Reusable HTTP client with retry logic
48
+ - Handles both sync and async HTTP requests
49
+ - Implements polling mechanism for waiting on workflow completion
50
+
51
+ ### 4. **api.py** - High-Level API
52
+ - Convenience functions that wrap `SimplaiClient`
53
+ - Supports "sync" and "async" modes
54
+ - Provides simple interface for users
55
+
56
+ ---
57
+
58
+ ## Synchronous Workflow Execution Flow
59
+
60
+ ### Mode: "sync" (Polling until completion)
61
+
62
+ ```
63
+ User calls:
64
+ execute_workflow(workflow_id, inputs, mode="sync")
65
+
66
+ ├─► Creates SimplaiClient instance
67
+
68
+ ├─► Calls client.execute_and_wait()
69
+ │ │
70
+ │ ├─► Step 1: Execute workflow
71
+ │ │ └─► client.execute_once()
72
+ │ │ └─► POST /interact/api/v2/tool/execute
73
+ │ │ └─► Returns execution_id
74
+ │ │
75
+ │ └─► Step 2: Poll until completion
76
+ │ │
77
+ │ ├─► Loop:
78
+ │ │ ├─► client.get_status_once(execution_id)
79
+ │ │ │ └─► GET /interact/api/v2/tool/executions/{id}/status
80
+ │ │ │
81
+ │ │ ├─► Check if terminal status:
82
+ │ │ │ - SUCCEEDED → Return WorkflowResult
83
+ │ │ │ - FAILED/CANCELED/TIMEOUT → Raise WorkflowExecutionError
84
+ │ │ │ - PENDING/RUNNING → Continue polling
85
+ │ │ │
86
+ │ │ └─► time.sleep(poll_interval) # Wait before next poll
87
+ │ │
88
+ │ └─► Return WorkflowResult or raise exception
89
+
90
+ └─► Returns WorkflowResult to user
91
+ ```
92
+
93
+ ### Mode: "async" (Fire and forget)
94
+
95
+ ```
96
+ User calls:
97
+ execute_workflow(workflow_id, inputs, mode="async")
98
+
99
+ ├─► Creates SimplaiClient instance
100
+
101
+ └─► Calls client.execute_once()
102
+ └─► POST /interact/api/v2/tool/execute
103
+ └─► Returns execution_id (string)
104
+
105
+ Returns: execution_id (user must poll manually)
106
+ ```
107
+
108
+ ---
109
+
110
+ ## Asynchronous Workflow Execution Flow
111
+
112
+ ### Mode: "sync" (Polling until completion)
113
+
114
+ ```
115
+ User calls:
116
+ await aexecute_workflow(workflow_id, inputs, mode="sync")
117
+
118
+ ├─► Creates SimplaiClient instance
119
+
120
+ ├─► Calls await client.aexecute_and_wait()
121
+ │ │
122
+ │ ├─► Step 1: Execute workflow
123
+ │ │ └─► await client.aexecute_once()
124
+ │ │ └─► POST /interact/api/v2/tool/execute (async)
125
+ │ │ └─► Returns execution_id
126
+ │ │
127
+ │ └─► Step 2: Poll until completion
128
+ │ │
129
+ │ ├─► Loop:
130
+ │ │ ├─► await client.aget_status_once(execution_id)
131
+ │ │ │ └─► GET /interact/api/v2/tool/executions/{id}/status (async)
132
+ │ │ │
133
+ │ │ ├─► Check if terminal status:
134
+ │ │ │ - SUCCEEDED → Return WorkflowResult
135
+ │ │ │ - FAILED/CANCELED/TIMEOUT → Raise WorkflowExecutionError
136
+ │ │ │ - PENDING/RUNNING → Continue polling
137
+ │ │ │
138
+ │ │ └─► await asyncio.sleep(poll_interval) # Non-blocking wait
139
+ │ │
140
+ │ └─► Return WorkflowResult or raise exception
141
+
142
+ └─► Returns WorkflowResult to user
143
+ ```
144
+
145
+ ### Mode: "async" (Fire and forget)
146
+
147
+ ```
148
+ User calls:
149
+ await aexecute_workflow(workflow_id, inputs, mode="async")
150
+
151
+ ├─► Creates SimplaiClient instance
152
+
153
+ └─► Calls await client.aexecute_once()
154
+ └─► POST /interact/api/v2/tool/execute (async)
155
+ └─► Returns execution_id (string)
156
+
157
+ Returns: execution_id (user must poll manually)
158
+ ```
159
+
160
+ ---
161
+
162
+ ## Function Reference
163
+
164
+ ### High-Level API Functions (api.py)
165
+
166
+ #### `execute_workflow()` - Synchronous
167
+ ```python
168
+ def execute_workflow(
169
+ workflow_id: str,
170
+ inputs: Dict[str, Any],
171
+ *,
172
+ api_key: str,
173
+ mode: str = "sync", # "sync" or "async"
174
+ timeout: Optional[float] = None,
175
+ poll_interval: float = 2.0,
176
+ base_url: str = DEFAULT_BASE_URL,
177
+ ) -> WorkflowResult | str:
178
+ ```
179
+ - **mode="sync"**: Polls until completion, returns `WorkflowResult`
180
+ - **mode="async"**: Returns immediately with `execution_id` (string)
181
+
182
+ #### `aexecute_workflow()` - Asynchronous
183
+ ```python
184
+ async def aexecute_workflow(...) -> WorkflowResult | str:
185
+ ```
186
+ - Same as `execute_workflow()` but async/await compatible
187
+ - Uses `await` for non-blocking operations
188
+
189
+ #### `get_execution_status()` - Synchronous Status Check
190
+ ```python
191
+ def get_execution_status(
192
+ execution_id: str,
193
+ *,
194
+ api_key: str,
195
+ base_url: str = DEFAULT_BASE_URL,
196
+ ) -> Dict[str, Any]:
197
+ ```
198
+ - One-off status check (doesn't poll)
199
+ - Returns raw status dictionary
200
+
201
+ #### `aget_execution_status()` - Asynchronous Status Check
202
+ ```python
203
+ async def aget_execution_status(...) -> Dict[str, Any]:
204
+ ```
205
+ - Async version of `get_execution_status()`
206
+
207
+ ---
208
+
209
+ ### Client Methods (client.py)
210
+
211
+ #### `SimplaiClient.execute_once()` - Sync Execute
212
+ ```python
213
+ def execute_once(
214
+ self,
215
+ workflow_id: str,
216
+ inputs: Dict[str, Any],
217
+ ) -> str:
218
+ ```
219
+ - Sends POST request to execute workflow
220
+ - Returns `execution_id`
221
+ - Uses `_request_with_retries_sync()` with retry logic
222
+
223
+ #### `SimplaiClient.aexecute_once()` - Async Execute
224
+ ```python
225
+ async def aexecute_once(...) -> str:
226
+ ```
227
+ - Async version of `execute_once()`
228
+ - Uses `_request_with_retries_async()`
229
+
230
+ #### `SimplaiClient.get_status_once()` - Sync Status Check
231
+ ```python
232
+ def get_status_once(self, execution_id: str) -> Dict[str, Any]:
233
+ ```
234
+ - Sends GET request to check status
235
+ - Returns raw status dictionary
236
+ - Uses `_request_with_retries_sync()`
237
+
238
+ #### `SimplaiClient.aget_status_once()` - Async Status Check
239
+ ```python
240
+ async def aget_status_once(...) -> Dict[str, Any]:
241
+ ```
242
+ - Async version of `get_status_once()`
243
+ - Uses `_request_with_retries_async()`
244
+
245
+ #### `SimplaiClient.execute_and_wait()` - Sync Polling
246
+ ```python
247
+ def execute_and_wait(
248
+ self,
249
+ workflow_id: str,
250
+ inputs: Dict[str, Any],
251
+ *,
252
+ poll_interval: float = 2.0,
253
+ timeout: Optional[float] = None,
254
+ ) -> WorkflowResult:
255
+ ```
256
+ **Flow:**
257
+ 1. Calls `execute_once()` to start workflow
258
+ 2. Enters polling loop:
259
+ - Calls `get_status_once()` to check status
260
+ - Checks if status is terminal (SUCCEEDED, FAILED, CANCELED, TIMEOUT)
261
+ - If terminal and SUCCEEDED → return `WorkflowResult`
262
+ - If terminal and not SUCCEEDED → raise `WorkflowExecutionError`
263
+ - If not terminal → `time.sleep(poll_interval)` and loop again
264
+ 3. Respects `timeout` parameter (raises error if exceeded)
265
+
266
+ #### `SimplaiClient.aexecute_and_wait()` - Async Polling
267
+ ```python
268
+ async def aexecute_and_wait(...) -> WorkflowResult:
269
+ ```
270
+ - Async version of `execute_and_wait()`
271
+ - Uses `await asyncio.sleep()` instead of `time.sleep()`
272
+ - Non-blocking, allows other coroutines to run
273
+
274
+ ---
275
+
276
+ ## Polling Mechanism Details
277
+
278
+ ### Terminal States
279
+ The polling loop stops when it encounters one of these states:
280
+ - `SUCCEEDED`: Workflow completed successfully → Returns `WorkflowResult`
281
+ - `FAILED`: Workflow failed → Raises `WorkflowExecutionError`
282
+ - `CANCELED`: Workflow was canceled → Raises `WorkflowExecutionError`
283
+ - `TIMEOUT`: Workflow timed out → Raises `WorkflowExecutionError`
284
+
285
+ ### Non-Terminal States
286
+ - `PENDING`: Workflow queued but not started
287
+ - `RUNNING`: Workflow currently executing
288
+ - `UNKNOWN`: Status couldn't be parsed
289
+
290
+ ### Polling Behavior
291
+ - **Default interval**: 2.0 seconds between polls
292
+ - **Timeout**: Optional maximum wait time
293
+ - **Sync**: Uses `time.sleep()` (blocks thread)
294
+ - **Async**: Uses `asyncio.sleep()` (non-blocking, allows concurrency)
295
+
296
+ ---
297
+
298
+ ## Error Handling
299
+
300
+ ### Retry Logic
301
+ Both sync and async request methods implement retry with exponential backoff:
302
+ - **Max retries**: 3 (configurable)
303
+ - **Backoff**: `backoff_factor * (2^attempt)` seconds
304
+ - **Retries on**: 5xx server errors, transport errors
305
+ - **No retry on**: 4xx client errors (immediate error)
306
+
307
+ ### Error Types
308
+ - `WorkflowExecutionError`: Raised for:
309
+ - Workflow failures (FAILED, CANCELED, TIMEOUT status)
310
+ - HTTP client errors (4xx)
311
+ - Timeout exceeded
312
+ - Invalid JSON responses
313
+ - Missing execution_id in response
314
+
315
+ ---
316
+
317
+ ## Usage Examples
318
+
319
+ ### Synchronous Execution (Blocking)
320
+ ```python
321
+ from simplai_sdk import execute_workflow
322
+
323
+ # Poll until completion
324
+ result = execute_workflow(
325
+ workflow_id="my-workflow",
326
+ inputs={"key": "value"},
327
+ api_key="my-api-key",
328
+ mode="sync",
329
+ timeout=60.0,
330
+ poll_interval=2.0
331
+ )
332
+ print(result.payload) # Access result data
333
+
334
+ # Fire and forget (get execution_id)
335
+ execution_id = execute_workflow(
336
+ workflow_id="my-workflow",
337
+ inputs={"key": "value"},
338
+ api_key="my-api-key",
339
+ mode="async"
340
+ )
341
+ ```
342
+
343
+ ### Asynchronous Execution (Non-blocking)
344
+ ```python
345
+ from simplai_sdk import aexecute_workflow
346
+ import asyncio
347
+
348
+ async def main():
349
+ # Poll until completion (non-blocking)
350
+ result = await aexecute_workflow(
351
+ workflow_id="my-workflow",
352
+ inputs={"key": "value"},
353
+ api_key="my-api-key",
354
+ mode="sync",
355
+ timeout=60.0,
356
+ poll_interval=2.0
357
+ )
358
+ print(result.payload)
359
+
360
+ # Fire and forget
361
+ execution_id = await aexecute_workflow(
362
+ workflow_id="my-workflow",
363
+ inputs={"key": "value"},
364
+ api_key="my-api-key",
365
+ mode="async"
366
+ )
367
+
368
+ asyncio.run(main())
369
+ ```
370
+
371
+ ### Manual Status Checking
372
+ ```python
373
+ from simplai_sdk import get_execution_status, aget_execution_status
374
+
375
+ # Sync
376
+ status = get_execution_status(
377
+ execution_id="exec-123",
378
+ api_key="my-api-key"
379
+ )
380
+
381
+ # Async
382
+ status = await aget_execution_status(
383
+ execution_id="exec-123",
384
+ api_key="my-api-key"
385
+ )
386
+ ```
387
+
388
+ ---
389
+
390
+ ## Key Differences: Sync vs Async
391
+
392
+ | Aspect | Synchronous | Asynchronous |
393
+ |--------|------------|--------------|
394
+ | **Blocking** | Blocks thread during I/O | Non-blocking, allows concurrency |
395
+ | **Sleep** | `time.sleep()` | `asyncio.sleep()` |
396
+ | **HTTP Client** | `httpx.Client` | `httpx.AsyncClient` |
397
+ | **Request Method** | `client.request()` | `await client.request()` |
398
+ | **Use Case** | Simple scripts, blocking operations | Concurrent operations, web servers |
399
+ | **Performance** | One workflow at a time | Can handle multiple workflows concurrently |
400
+
401
+ ---
402
+
403
+ ## Summary
404
+
405
+ The workflow execution system provides:
406
+ 1. **High-level API** (`api.py`) - Simple functions for users
407
+ 2. **Client layer** (`client.py`) - Reusable client with polling logic
408
+ 3. **Models** (`models.py`) - Type-safe data structures
409
+ 4. **Constants** (`constants.py`) - Configuration values
410
+
411
+ Both sync and async paths follow the same logical flow:
412
+ 1. Execute workflow → Get `execution_id`
413
+ 2. Poll status → Check for terminal state
414
+ 3. Return result or raise error
415
+
416
+ The async path uses `await` and `asyncio.sleep()` to allow concurrent execution of multiple workflows, while the sync path blocks until completion.
417
+
@@ -0,0 +1,31 @@
1
+ """Backwards-compatibility shim for earlier imports.
2
+
3
+ The implementation has been split into smaller modules for readability:
4
+ - tool_execution: workflow execution APIs
5
+ - bulk: bulk run APIs
6
+ """
7
+
8
+ from .tool_execution import (
9
+ execute_workflow,
10
+ get_tool_result,
11
+ execute_and_wait_workflow,
12
+ cancel_execution,
13
+ )
14
+
15
+ from .bulk import (
16
+ trigger_bulk_run,
17
+ get_bulk_run_status,
18
+ cancel_bulk_run,
19
+ download_bulk_run_result,
20
+ )
21
+
22
+ __all__ = [
23
+ "execute_workflow",
24
+ "get_tool_result",
25
+ "execute_and_wait_workflow",
26
+ "cancel_execution",
27
+ "trigger_bulk_run",
28
+ "get_bulk_run_status",
29
+ "cancel_bulk_run",
30
+ "download_bulk_run_result",
31
+ ]
@@ -0,0 +1,14 @@
1
+ """Public SDK interface for bulk workflow execution."""
2
+
3
+ from .client import trigger_bulk_run
4
+ from .client import get_bulk_run_status
5
+ from .client import cancel_bulk_run
6
+ from .client import download_bulk_run_result
7
+ __all__ = [
8
+ "trigger_bulk_run",
9
+ "get_bulk_run_status",
10
+ "cancel_bulk_run",
11
+ "download_bulk_run_result",
12
+ ]
13
+
14
+