modulex-python 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.
- modulex/__init__.py +39 -0
- modulex/_base.py +281 -0
- modulex/_client.py +237 -0
- modulex/_compat.py +39 -0
- modulex/_config.py +26 -0
- modulex/_exceptions.py +131 -0
- modulex/_streaming.py +118 -0
- modulex/py.typed +0 -0
- modulex/resources/__init__.py +1 -0
- modulex/resources/api_keys.py +39 -0
- modulex/resources/auth.py +38 -0
- modulex/resources/chats.py +62 -0
- modulex/resources/composer.py +134 -0
- modulex/resources/credentials.py +197 -0
- modulex/resources/dashboard.py +110 -0
- modulex/resources/deployments.py +92 -0
- modulex/resources/executions.py +97 -0
- modulex/resources/integrations.py +110 -0
- modulex/resources/knowledge.py +343 -0
- modulex/resources/notifications.py +39 -0
- modulex/resources/organizations.py +72 -0
- modulex/resources/schedules.py +172 -0
- modulex/resources/subscriptions.py +38 -0
- modulex/resources/system.py +28 -0
- modulex/resources/templates.py +115 -0
- modulex/resources/workflows.py +156 -0
- modulex/types/__init__.py +294 -0
- modulex/types/api_keys.py +19 -0
- modulex/types/auth.py +62 -0
- modulex/types/chats.py +55 -0
- modulex/types/composer.py +27 -0
- modulex/types/credentials.py +79 -0
- modulex/types/dashboard.py +54 -0
- modulex/types/executions.py +104 -0
- modulex/types/integrations.py +29 -0
- modulex/types/knowledge.py +75 -0
- modulex/types/notifications.py +16 -0
- modulex/types/organizations.py +43 -0
- modulex/types/schedules.py +48 -0
- modulex/types/shared.py +39 -0
- modulex/types/subscriptions.py +59 -0
- modulex/types/templates.py +50 -0
- modulex/types/workflows.py +253 -0
- modulex_python-0.1.0.dist-info/METADATA +435 -0
- modulex_python-0.1.0.dist-info/RECORD +47 -0
- modulex_python-0.1.0.dist-info/WHEEL +4 -0
- modulex_python-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""Workflow-related type definitions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from typing_extensions import TypedDict
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LLMConfig(TypedDict, total=False):
|
|
11
|
+
"""LLM configuration."""
|
|
12
|
+
|
|
13
|
+
integration_name: str
|
|
14
|
+
provider_id: str
|
|
15
|
+
model_id: str
|
|
16
|
+
temperature: float
|
|
17
|
+
credential_id: str | None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ToolDefinition(TypedDict, total=False):
|
|
21
|
+
"""Tool definition for workflow nodes."""
|
|
22
|
+
|
|
23
|
+
integration_name: str
|
|
24
|
+
service_name: str
|
|
25
|
+
credential_id: str | None
|
|
26
|
+
parameter_defaults: dict[str, Any]
|
|
27
|
+
parameter_overrides: dict[str, Any]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class RetryConfig(TypedDict, total=False):
|
|
31
|
+
"""Node retry configuration."""
|
|
32
|
+
|
|
33
|
+
max_attempts: int
|
|
34
|
+
initial_interval: float
|
|
35
|
+
backoff_factor: float
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class LLMNodeConfig(TypedDict, total=False):
|
|
39
|
+
"""LLM node configuration."""
|
|
40
|
+
|
|
41
|
+
llm: LLMConfig
|
|
42
|
+
system_prompt: str
|
|
43
|
+
user_prompt: str
|
|
44
|
+
structured_output_schema: dict[str, Any] | None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ToolNodeConfig(TypedDict, total=False):
|
|
48
|
+
"""Tool node configuration."""
|
|
49
|
+
|
|
50
|
+
tool: ToolDefinition
|
|
51
|
+
input_mapping: dict[str, Any]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class AgentNodeConfig(TypedDict, total=False):
|
|
55
|
+
"""Agent node configuration."""
|
|
56
|
+
|
|
57
|
+
llm: LLMConfig
|
|
58
|
+
tools: list[ToolDefinition]
|
|
59
|
+
system_prompt: str
|
|
60
|
+
user_prompt: str
|
|
61
|
+
max_iterations: int
|
|
62
|
+
input_mapping: dict[str, Any]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ExpressionBranch(TypedDict, total=False):
|
|
66
|
+
"""Conditional expression branch."""
|
|
67
|
+
|
|
68
|
+
name: str
|
|
69
|
+
condition: str
|
|
70
|
+
target: str
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class LoopConfig(TypedDict, total=False):
|
|
74
|
+
"""Loop configuration for conditional nodes."""
|
|
75
|
+
|
|
76
|
+
loop_id: str
|
|
77
|
+
mode: str
|
|
78
|
+
iterations: int
|
|
79
|
+
collection: str
|
|
80
|
+
condition: str
|
|
81
|
+
body_target: str
|
|
82
|
+
body_end: str
|
|
83
|
+
exit_target: str
|
|
84
|
+
max_iterations: int
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class ConditionalNodeConfig(TypedDict, total=False):
|
|
88
|
+
"""Conditional node configuration."""
|
|
89
|
+
|
|
90
|
+
condition_type: str
|
|
91
|
+
routes: dict[str, str]
|
|
92
|
+
expression_branches: list[ExpressionBranch]
|
|
93
|
+
loop_config: LoopConfig
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class InterruptNodeConfig(TypedDict, total=False):
|
|
97
|
+
"""Interrupt node configuration."""
|
|
98
|
+
|
|
99
|
+
message: str
|
|
100
|
+
resume_schema: dict[str, Any]
|
|
101
|
+
examples: list[Any]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class TransformerOperation(TypedDict, total=False):
|
|
105
|
+
"""Transformer operation."""
|
|
106
|
+
|
|
107
|
+
type: str
|
|
108
|
+
path: str
|
|
109
|
+
condition: str
|
|
110
|
+
template: str
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class TransformerNodeConfig(TypedDict, total=False):
|
|
114
|
+
"""Transformer node configuration."""
|
|
115
|
+
|
|
116
|
+
source: str
|
|
117
|
+
operations: list[TransformerOperation]
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class GuardrailsNodeConfig(TypedDict, total=False):
|
|
121
|
+
"""Guardrails node configuration."""
|
|
122
|
+
|
|
123
|
+
rules: list[dict[str, Any]]
|
|
124
|
+
on_violation: str
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class KnowledgeNodeConfig(TypedDict, total=False):
|
|
128
|
+
"""Knowledge node configuration."""
|
|
129
|
+
|
|
130
|
+
credential_id: str
|
|
131
|
+
provider_type: str
|
|
132
|
+
query: str
|
|
133
|
+
collection_name: str
|
|
134
|
+
top_k: int
|
|
135
|
+
min_score: float
|
|
136
|
+
filters: dict[str, Any]
|
|
137
|
+
embedding_config: dict[str, Any]
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class NodeDefinition(TypedDict, total=False):
|
|
141
|
+
"""Workflow node definition."""
|
|
142
|
+
|
|
143
|
+
id: str
|
|
144
|
+
type: str
|
|
145
|
+
name: str
|
|
146
|
+
description: str
|
|
147
|
+
enabled: bool
|
|
148
|
+
x: int
|
|
149
|
+
y: int
|
|
150
|
+
retry_config: RetryConfig
|
|
151
|
+
llm_config: LLMNodeConfig
|
|
152
|
+
tool_config: ToolNodeConfig
|
|
153
|
+
agent_config: AgentNodeConfig
|
|
154
|
+
function_config: dict[str, Any]
|
|
155
|
+
conditional_config: ConditionalNodeConfig
|
|
156
|
+
interrupt_config: InterruptNodeConfig
|
|
157
|
+
transformer_config: TransformerNodeConfig
|
|
158
|
+
guardrails_config: GuardrailsNodeConfig
|
|
159
|
+
knowledge_config: KnowledgeNodeConfig
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class EdgeDefinition(TypedDict):
|
|
163
|
+
"""Workflow edge definition."""
|
|
164
|
+
|
|
165
|
+
source: str
|
|
166
|
+
target: str
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class StateField(TypedDict, total=False):
|
|
170
|
+
"""State schema field."""
|
|
171
|
+
|
|
172
|
+
type: str
|
|
173
|
+
description: str
|
|
174
|
+
reducer: str
|
|
175
|
+
required: bool
|
|
176
|
+
default: Any
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class StateSchema(TypedDict, total=False):
|
|
180
|
+
"""Workflow state schema."""
|
|
181
|
+
|
|
182
|
+
fields: dict[str, StateField]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class WorkflowMetadata(TypedDict, total=False):
|
|
186
|
+
"""Workflow metadata."""
|
|
187
|
+
|
|
188
|
+
name: str
|
|
189
|
+
description: str
|
|
190
|
+
version: str
|
|
191
|
+
author: str
|
|
192
|
+
tags: list[str]
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class WorkflowConfig(TypedDict, total=False):
|
|
196
|
+
"""Workflow configuration."""
|
|
197
|
+
|
|
198
|
+
default_llm: LLMConfig
|
|
199
|
+
default_tools: list[ToolDefinition]
|
|
200
|
+
recursion_limit: int
|
|
201
|
+
checkpointing: str
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class WorkflowDefinition(TypedDict, total=False):
|
|
205
|
+
"""Complete workflow definition schema."""
|
|
206
|
+
|
|
207
|
+
metadata: WorkflowMetadata
|
|
208
|
+
config: WorkflowConfig
|
|
209
|
+
state_schema: StateSchema
|
|
210
|
+
nodes: list[NodeDefinition]
|
|
211
|
+
edges: list[EdgeDefinition]
|
|
212
|
+
entry_point: str
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class WorkflowResponse(TypedDict, total=False):
|
|
216
|
+
"""Workflow response from API."""
|
|
217
|
+
|
|
218
|
+
id: str
|
|
219
|
+
organization_id: str
|
|
220
|
+
name: str
|
|
221
|
+
description: str
|
|
222
|
+
version: str
|
|
223
|
+
tags: list[str]
|
|
224
|
+
category: str
|
|
225
|
+
status: str
|
|
226
|
+
visibility: str
|
|
227
|
+
workflow_schema: WorkflowDefinition
|
|
228
|
+
input: dict[str, Any]
|
|
229
|
+
config: dict[str, Any]
|
|
230
|
+
edit_version: int
|
|
231
|
+
last_edited_by: str | None
|
|
232
|
+
last_edited_at: str | None
|
|
233
|
+
created_at: str
|
|
234
|
+
updated_at: str
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class WorkflowListResponse(TypedDict, total=False):
|
|
238
|
+
"""Response from /workflows list."""
|
|
239
|
+
|
|
240
|
+
workflows: list[WorkflowResponse]
|
|
241
|
+
total: int
|
|
242
|
+
page: int
|
|
243
|
+
page_size: int
|
|
244
|
+
total_pages: int
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class BuilderDetailsResponse(TypedDict, total=False):
|
|
248
|
+
"""Response from /workflows/builder/details."""
|
|
249
|
+
|
|
250
|
+
node_types: dict[str, Any]
|
|
251
|
+
categories: dict[str, Any]
|
|
252
|
+
counts: dict[str, Any]
|
|
253
|
+
cached: bool
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: modulex-python
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the ModuleX AI workflow orchestration platform
|
|
5
|
+
Project-URL: Homepage, https://modulex.dev
|
|
6
|
+
Project-URL: Documentation, https://docs.modulex.dev
|
|
7
|
+
Project-URL: Repository, https://github.com/ModuleXAI/modulex-python
|
|
8
|
+
Project-URL: Issues, https://github.com/ModuleXAI/modulex-python/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/ModuleXAI/modulex-python/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: ModuleX <contact@modulex.dev>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: ai,modulex,orchestration,sdk,workflow
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Framework :: AsyncIO
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Requires-Dist: httpx-sse>=0.4
|
|
27
|
+
Requires-Dist: httpx>=0.27
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: mypy>=1.13; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: python-dotenv>=1.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: respx>=0.22; extra == 'dev'
|
|
35
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# ModuleX Python SDK
|
|
39
|
+
|
|
40
|
+
The official Python SDK for the [ModuleX](https://modulex.dev) AI workflow orchestration platform.
|
|
41
|
+
|
|
42
|
+
[](https://github.com/ModuleXAI/modulex-python/actions/workflows/ci.yml)
|
|
43
|
+
[](https://pypi.org/project/modulex-python/)
|
|
44
|
+
[](https://pypi.org/project/modulex-python/)
|
|
45
|
+
[](LICENSE)
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install modulex-python
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
import asyncio
|
|
57
|
+
from modulex import Modulex
|
|
58
|
+
|
|
59
|
+
async def main():
|
|
60
|
+
async with Modulex(
|
|
61
|
+
api_key="mx_live_...",
|
|
62
|
+
organization_id="your-org-id",
|
|
63
|
+
) as client:
|
|
64
|
+
# Get current user
|
|
65
|
+
me = await client.auth.me()
|
|
66
|
+
print(f"Hello, {me['username']}!")
|
|
67
|
+
|
|
68
|
+
# List workflows
|
|
69
|
+
workflows = await client.workflows.list(status="active")
|
|
70
|
+
for wf in workflows["workflows"]:
|
|
71
|
+
print(f" {wf['name']}")
|
|
72
|
+
|
|
73
|
+
asyncio.run(main())
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Authentication
|
|
77
|
+
|
|
78
|
+
Get your API key from the [ModuleX Dashboard](https://app.modulex.dev). Keys use the `mx_live_` prefix.
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from modulex import Modulex
|
|
82
|
+
|
|
83
|
+
# Pass API key directly
|
|
84
|
+
client = Modulex(api_key="mx_live_...")
|
|
85
|
+
|
|
86
|
+
# Or use environment variable
|
|
87
|
+
import os
|
|
88
|
+
client = Modulex(api_key=os.environ["MODULEX_API_KEY"])
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Organization Context
|
|
92
|
+
|
|
93
|
+
Most endpoints require an organization context. Set it at the client level or override per-request:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
# Set default org for all requests
|
|
97
|
+
client = Modulex(api_key="mx_live_...", organization_id="org-uuid")
|
|
98
|
+
|
|
99
|
+
# Override for a specific request
|
|
100
|
+
workflows = await client.workflows.list(organization_id="other-org-uuid")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Configuration
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
client = Modulex(
|
|
107
|
+
api_key="mx_live_...",
|
|
108
|
+
organization_id="org-uuid", # Default organization
|
|
109
|
+
base_url="https://api.modulex.dev", # API base URL
|
|
110
|
+
timeout=30.0, # Request timeout (seconds)
|
|
111
|
+
max_retries=3, # Retry count for transient errors
|
|
112
|
+
)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Resources
|
|
116
|
+
|
|
117
|
+
### Workflows
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
# List workflows
|
|
121
|
+
workflows = await client.workflows.list(status="active", search="email")
|
|
122
|
+
|
|
123
|
+
# Auto-paginate all workflows
|
|
124
|
+
async for wf in client.workflows.list_all(status="active"):
|
|
125
|
+
print(wf["name"])
|
|
126
|
+
|
|
127
|
+
# Create workflow
|
|
128
|
+
workflow = await client.workflows.create(
|
|
129
|
+
workflow_schema={
|
|
130
|
+
"metadata": {"name": "My Workflow", "version": "1.0"},
|
|
131
|
+
"config": {},
|
|
132
|
+
"state_schema": {"fields": {}},
|
|
133
|
+
"nodes": [],
|
|
134
|
+
"edges": [],
|
|
135
|
+
"entry_point": "start",
|
|
136
|
+
},
|
|
137
|
+
name="My Workflow",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Update & delete
|
|
141
|
+
await client.workflows.update("workflow-id", name="New Name", status="active")
|
|
142
|
+
await client.workflows.delete("workflow-id")
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Executions
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
# Run a workflow
|
|
149
|
+
result = await client.executions.run(
|
|
150
|
+
workflow_id="workflow-uuid",
|
|
151
|
+
input={"messages": [{"role": "user", "content": "Hello!"}]},
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Direct LLM call
|
|
155
|
+
result = await client.executions.run(
|
|
156
|
+
llm={
|
|
157
|
+
"integration_name": "openai",
|
|
158
|
+
"provider_id": "openai",
|
|
159
|
+
"model_id": "gpt-4o-mini",
|
|
160
|
+
"temperature": 0.4,
|
|
161
|
+
},
|
|
162
|
+
input={"messages": [{"role": "user", "content": "Hello!"}]},
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Get execution state
|
|
166
|
+
state = await client.executions.get_state(thread_id="thread-uuid")
|
|
167
|
+
|
|
168
|
+
# Resume after interrupt
|
|
169
|
+
await client.executions.resume(
|
|
170
|
+
thread_id="thread-uuid",
|
|
171
|
+
run_id="run-uuid",
|
|
172
|
+
resume_value="user input",
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Cancel execution
|
|
176
|
+
await client.executions.cancel(run_id="run-uuid", reason="No longer needed")
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### SSE Streaming
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
# Listen to workflow execution events
|
|
183
|
+
async for event in client.executions.listen(run_id="run-uuid"):
|
|
184
|
+
if event.event == "node_update":
|
|
185
|
+
print(f"Node {event.data['node_id']}: {event.data['status']}")
|
|
186
|
+
elif event.event == "done":
|
|
187
|
+
print(f"Completed in {event.data['total_execution_time_ms']}ms")
|
|
188
|
+
elif event.event == "error":
|
|
189
|
+
print(f"Error: {event.data['error_message']}")
|
|
190
|
+
|
|
191
|
+
# Listen to chat list updates
|
|
192
|
+
async for event in client.chats.stream():
|
|
193
|
+
if event.event == "chat_list_updated":
|
|
194
|
+
print(f"Chat list changed: {event.data}")
|
|
195
|
+
|
|
196
|
+
# Listen to composer events
|
|
197
|
+
async for event in client.composer.listen("chat-id", "run-id"):
|
|
198
|
+
print(f"{event.event}: {event.data}")
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Credentials
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
# Add an API key credential
|
|
205
|
+
cred = await client.credentials.create(
|
|
206
|
+
integration_name="openai",
|
|
207
|
+
auth_data={"api_key": "sk-..."},
|
|
208
|
+
display_name="Production OpenAI",
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# Test a credential
|
|
212
|
+
result = await client.credentials.test(cred["credential_id"])
|
|
213
|
+
print(f"Valid: {result['is_valid']}")
|
|
214
|
+
|
|
215
|
+
# List credentials
|
|
216
|
+
creds = await client.credentials.list(integration_name="openai")
|
|
217
|
+
|
|
218
|
+
# Add MCP server
|
|
219
|
+
mcp = await client.credentials.create_mcp_server(
|
|
220
|
+
server_url="https://mcp-server.example.com",
|
|
221
|
+
headers={"Authorization": "Bearer ..."},
|
|
222
|
+
)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Knowledge Bases
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
# Create a knowledge base
|
|
229
|
+
kb = await client.knowledge.create(
|
|
230
|
+
name="Docs",
|
|
231
|
+
embedding_config={"provider": "openai", "model": "text-embedding-3-small"},
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# Upload a document
|
|
235
|
+
doc = await client.knowledge.upload_document(
|
|
236
|
+
knowledge_base_id=kb["id"],
|
|
237
|
+
file_path="/path/to/doc.pdf",
|
|
238
|
+
metadata={"department": "engineering"},
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# Search
|
|
242
|
+
results = await client.knowledge.search(
|
|
243
|
+
knowledge_base_id=kb["id"],
|
|
244
|
+
query="How does deployment work?",
|
|
245
|
+
top_k=5,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
# Retrieve RAG context
|
|
249
|
+
context = await client.knowledge.retrieve_context(
|
|
250
|
+
knowledge_base_id=kb["id"],
|
|
251
|
+
query="deployment steps",
|
|
252
|
+
max_tokens=2000,
|
|
253
|
+
)
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Schedules
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
# Create a cron schedule
|
|
260
|
+
schedule = await client.schedules.create(
|
|
261
|
+
workflow_id="workflow-uuid",
|
|
262
|
+
name="Daily Report",
|
|
263
|
+
schedule_type="cron",
|
|
264
|
+
cron_expression="0 9 * * 1-5",
|
|
265
|
+
timezone="America/New_York",
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# Pause/resume
|
|
269
|
+
await client.schedules.pause(schedule["id"])
|
|
270
|
+
await client.schedules.resume(schedule["id"])
|
|
271
|
+
|
|
272
|
+
# View run history
|
|
273
|
+
runs = await client.schedules.list_runs(schedule["id"])
|
|
274
|
+
stats = await client.schedules.run_stats(schedule["id"], days=30)
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Templates
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
# Browse templates
|
|
281
|
+
templates = await client.templates.list()
|
|
282
|
+
|
|
283
|
+
# Use a template
|
|
284
|
+
result = await client.templates.use("template-id")
|
|
285
|
+
print(f"Created workflow: {result['workflow']['id']}")
|
|
286
|
+
|
|
287
|
+
# Like a template
|
|
288
|
+
await client.templates.like("template-id")
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Deployments
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
# Deploy a workflow
|
|
295
|
+
deployment = await client.deployments.create(
|
|
296
|
+
workflow_id="workflow-uuid",
|
|
297
|
+
deployment_note="v1.0 release",
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# Activate a deployment
|
|
301
|
+
await client.deployments.activate("workflow-uuid", deployment["id"])
|
|
302
|
+
|
|
303
|
+
# Deactivate live deployment
|
|
304
|
+
await client.deployments.deactivate("workflow-uuid")
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Composer
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
# Start a composer session
|
|
311
|
+
result = await client.composer.chat(
|
|
312
|
+
message="Add an LLM node that summarizes the input",
|
|
313
|
+
workflow_id="workflow-uuid",
|
|
314
|
+
llm={"integration_name": "anthropic", "model_id": "claude-sonnet-4-20250514"},
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
# Listen to composer events
|
|
318
|
+
async for event in client.composer.listen(result["composer_chat_id"], result["run_id"]):
|
|
319
|
+
if event.event == "workflow_change":
|
|
320
|
+
print(f"Workflow modified: {event.data}")
|
|
321
|
+
elif event.event == "done":
|
|
322
|
+
break
|
|
323
|
+
|
|
324
|
+
# Save or revert changes
|
|
325
|
+
await client.composer.save(result["composer_chat_id"])
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Other Resources
|
|
329
|
+
|
|
330
|
+
```python
|
|
331
|
+
# Organizations
|
|
332
|
+
await client.organizations.create(name="My Org")
|
|
333
|
+
await client.organizations.invite("user@example.com", role="member")
|
|
334
|
+
llms = await client.organizations.llms()
|
|
335
|
+
|
|
336
|
+
# Dashboard
|
|
337
|
+
logs = await client.dashboard.logs(category="CREDENTIALS")
|
|
338
|
+
overview = await client.dashboard.analytics_overview()
|
|
339
|
+
users = await client.dashboard.users(search="john")
|
|
340
|
+
|
|
341
|
+
# Subscriptions
|
|
342
|
+
plans = await client.subscriptions.organization_plans()
|
|
343
|
+
billing = await client.subscriptions.organization_billing()
|
|
344
|
+
|
|
345
|
+
# Notifications
|
|
346
|
+
notifications = await client.notifications.list()
|
|
347
|
+
|
|
348
|
+
# Integrations
|
|
349
|
+
integrations = await client.integrations.browse(type="tool")
|
|
350
|
+
providers = await client.integrations.llm_providers()
|
|
351
|
+
|
|
352
|
+
# System
|
|
353
|
+
health = await client.system.health()
|
|
354
|
+
timezones = await client.system.timezones()
|
|
355
|
+
|
|
356
|
+
# API Keys
|
|
357
|
+
key = await client.api_keys.create(name="CI/CD Key")
|
|
358
|
+
await client.api_keys.revoke(key["id"])
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
## Error Handling
|
|
362
|
+
|
|
363
|
+
```python
|
|
364
|
+
from modulex import (
|
|
365
|
+
Modulex,
|
|
366
|
+
ModulexError,
|
|
367
|
+
AuthenticationError,
|
|
368
|
+
NotFoundError,
|
|
369
|
+
RateLimitError,
|
|
370
|
+
ValidationError,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
try:
|
|
374
|
+
workflow = await client.workflows.get("invalid-id")
|
|
375
|
+
except NotFoundError:
|
|
376
|
+
print("Workflow not found")
|
|
377
|
+
except RateLimitError as e:
|
|
378
|
+
print(f"Rate limited. Retry after {e.retry_after}s")
|
|
379
|
+
except AuthenticationError:
|
|
380
|
+
print("Invalid API key")
|
|
381
|
+
except ValidationError as e:
|
|
382
|
+
print(f"Validation error: {e.message}")
|
|
383
|
+
except ModulexError as e:
|
|
384
|
+
print(f"API error ({e.status_code}): {e.message}")
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### Exception Hierarchy
|
|
388
|
+
|
|
389
|
+
| Exception | HTTP Status | Description |
|
|
390
|
+
|-----------|-------------|-------------|
|
|
391
|
+
| `ModulexError` | — | Base exception |
|
|
392
|
+
| `BadRequestError` | 400 | Malformed request |
|
|
393
|
+
| `AuthenticationError` | 401 | Invalid/missing auth |
|
|
394
|
+
| `PermissionError` | 403 | Insufficient permissions |
|
|
395
|
+
| `NotFoundError` | 404 | Resource not found |
|
|
396
|
+
| `ConflictError` | 409 | Resource conflict |
|
|
397
|
+
| `ValidationError` | 422 | Validation error |
|
|
398
|
+
| `RateLimitError` | 429 | Rate limit exceeded |
|
|
399
|
+
| `InternalError` | 500 | Server error |
|
|
400
|
+
| `ExternalServiceError` | 502 | External service failure |
|
|
401
|
+
| `ServiceUnavailableError` | 503 | Service unavailable |
|
|
402
|
+
| `StreamError` | — | SSE stream error |
|
|
403
|
+
| `TimeoutError` | — | Request timeout |
|
|
404
|
+
|
|
405
|
+
## Type Hints
|
|
406
|
+
|
|
407
|
+
All types are available for import:
|
|
408
|
+
|
|
409
|
+
```python
|
|
410
|
+
from modulex.types import (
|
|
411
|
+
WorkflowDefinition,
|
|
412
|
+
NodeDefinition,
|
|
413
|
+
EdgeDefinition,
|
|
414
|
+
LLMConfig,
|
|
415
|
+
RunResponse,
|
|
416
|
+
SSEEvent,
|
|
417
|
+
)
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
## Documentation
|
|
421
|
+
|
|
422
|
+
For full API documentation, visit [docs.modulex.dev](https://docs.modulex.dev).
|
|
423
|
+
|
|
424
|
+
## Contributing
|
|
425
|
+
|
|
426
|
+
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on:
|
|
427
|
+
|
|
428
|
+
- Setting up the development environment
|
|
429
|
+
- Running tests (unit and integration)
|
|
430
|
+
- Code style and commit conventions
|
|
431
|
+
- Pull request process
|
|
432
|
+
|
|
433
|
+
## License
|
|
434
|
+
|
|
435
|
+
This project is licensed under the MIT License — see the [LICENSE](LICENSE) file for details.
|