chainix 0.1.0__tar.gz
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.
- chainix-0.1.0/PKG-INFO +311 -0
- chainix-0.1.0/README.md +288 -0
- chainix-0.1.0/chainix/__init__.py +4 -0
- chainix-0.1.0/chainix/main.py +223 -0
- chainix-0.1.0/chainix.egg-info/PKG-INFO +311 -0
- chainix-0.1.0/chainix.egg-info/SOURCES.txt +9 -0
- chainix-0.1.0/chainix.egg-info/dependency_links.txt +1 -0
- chainix-0.1.0/chainix.egg-info/requires.txt +1 -0
- chainix-0.1.0/chainix.egg-info/top_level.txt +1 -0
- chainix-0.1.0/setup.cfg +4 -0
- chainix-0.1.0/setup.py +25 -0
chainix-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: chainix
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A client library for executing asynchronous chains with custom function callbacks
|
|
5
|
+
Home-page: https://github.com/ChainixDev/chainix-python
|
|
6
|
+
Author: Chainix
|
|
7
|
+
Author-email: jack@chainix.ai
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.7
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: requests>=2.25.0
|
|
14
|
+
Dynamic: author
|
|
15
|
+
Dynamic: author-email
|
|
16
|
+
Dynamic: classifier
|
|
17
|
+
Dynamic: description
|
|
18
|
+
Dynamic: description-content-type
|
|
19
|
+
Dynamic: home-page
|
|
20
|
+
Dynamic: requires-dist
|
|
21
|
+
Dynamic: requires-python
|
|
22
|
+
Dynamic: summary
|
|
23
|
+
|
|
24
|
+
# Chainix
|
|
25
|
+
|
|
26
|
+
A Python client library for executing chains with chainix.ai
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install chainix
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from chainix import AsyncChainClient
|
|
38
|
+
|
|
39
|
+
# Initialize the client
|
|
40
|
+
client = AsyncChainClient(
|
|
41
|
+
chain_id="your-chains-id-here",
|
|
42
|
+
api_key="your-api-key-here",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Define your custom functions
|
|
46
|
+
def refund(args):
|
|
47
|
+
try:
|
|
48
|
+
order_id = args['order id']
|
|
49
|
+
print(f"Issuing a refund for order: {order_id}")
|
|
50
|
+
|
|
51
|
+
# Your business logic here
|
|
52
|
+
# ... process refund ...
|
|
53
|
+
amount = 500
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
'success': True,
|
|
57
|
+
'vars_to_update': {
|
|
58
|
+
'refund_amount': amount
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
except Exception as e:
|
|
62
|
+
print(f"Failed to process refund: {e}")
|
|
63
|
+
return {
|
|
64
|
+
'success': False,
|
|
65
|
+
'vars_to_update': {}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def cancel_order(args):
|
|
70
|
+
try:
|
|
71
|
+
order_id = args['order id']
|
|
72
|
+
print(f"Cancelling order: {order_id}")
|
|
73
|
+
|
|
74
|
+
# Your business logic here
|
|
75
|
+
# ... perform cancellation ...
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
'success': True,
|
|
79
|
+
'vars_to_update': {}
|
|
80
|
+
}
|
|
81
|
+
except Exception as e:
|
|
82
|
+
print(f"Failed to cancel order: {e}")
|
|
83
|
+
return {
|
|
84
|
+
'success': False,
|
|
85
|
+
'vars_to_update': {}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# Register your functions (use the actual function IDs from the chain on chainix.ai)
|
|
90
|
+
client.register_function("your-cancel-function-id", cancel_order)
|
|
91
|
+
client.register_function("your-refund-function-id", refund)
|
|
92
|
+
|
|
93
|
+
# Or, bulk register your functions
|
|
94
|
+
functions = {
|
|
95
|
+
"your-cancel-function-id": cancel_order,
|
|
96
|
+
"your-refund-function-id": refund,
|
|
97
|
+
}
|
|
98
|
+
client.register_functions(functions)
|
|
99
|
+
|
|
100
|
+
# Execute a chain
|
|
101
|
+
# Provide all initial variables needed to start the chain
|
|
102
|
+
result = client.run_chain({
|
|
103
|
+
'message body': 'Hi, please cancel my order',
|
|
104
|
+
'order id': '33433',
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
print("Chain completed:", result)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Function Requirements
|
|
111
|
+
|
|
112
|
+
All registered functions **must** follow these requirements:
|
|
113
|
+
|
|
114
|
+
### Function Signature
|
|
115
|
+
Your functions should accept a single dictionary argument containing the inputs for that function call step:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
def my_function(args: dict) -> dict:
|
|
119
|
+
# Your business logic here
|
|
120
|
+
pass
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**How it works:**
|
|
124
|
+
1. You define variables in your chain on chainix.ai (e.g., `order id`, `user email`, `action`)
|
|
125
|
+
2. You create function call steps in your chain and specify which variables should be passed as inputs to each step
|
|
126
|
+
3. When the chain reaches a function call step, it stops and calls your registered function via it's id
|
|
127
|
+
4. Your function receives a dictionary where each key is a variable you specified as an input for that step, and each value is the current value of that variable in the chain
|
|
128
|
+
|
|
129
|
+
**Example:** If you have a function call step with `order id` and `user email` as inputs, your function will receive:
|
|
130
|
+
```python
|
|
131
|
+
{
|
|
132
|
+
'order id': '12345',
|
|
133
|
+
'user email': 'user@example.com'
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Return Value
|
|
138
|
+
Your functions **must** return a dictionary with exactly two keys:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
{
|
|
142
|
+
'success': bool, # True if function executed successfully, False otherwise
|
|
143
|
+
'vars_to_update': dict # Dictionary of variables to update in the chain (can be empty)
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Important**: The keys in `vars_to_update` must exactly match the variable names you defined in your chain on chainix.ai. Only variables that exist in your chain can be updated. If you try to update a variable that doesn't exist in your chain, the chain will fail.
|
|
148
|
+
|
|
149
|
+
### Example Function Structure
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
def process_order(args):
|
|
153
|
+
try:
|
|
154
|
+
# Extract arguments
|
|
155
|
+
order_id = args['order id']
|
|
156
|
+
action = args.get('action', 'process')
|
|
157
|
+
|
|
158
|
+
# Your business logic here
|
|
159
|
+
if action == 'cancel':
|
|
160
|
+
# ... cancellation logic ...
|
|
161
|
+
return {
|
|
162
|
+
'success': True,
|
|
163
|
+
'vars_to_update': {
|
|
164
|
+
'order status': 'cancelled', # Must match variable name in your chain
|
|
165
|
+
'cancellation date': '2024-01-01' # Must match variable name in your chain
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
elif action == 'fulfill':
|
|
169
|
+
# ... fulfillment logic ...
|
|
170
|
+
return {
|
|
171
|
+
'success': True,
|
|
172
|
+
'vars_to_update': {
|
|
173
|
+
'order status': 'fulfilled', # Must match variable name in your chain
|
|
174
|
+
'fulfillment date': '2024-01-01' # Must match variable name in your chain
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
else:
|
|
178
|
+
return {
|
|
179
|
+
'success': False,
|
|
180
|
+
'vars_to_update': {}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
except Exception as e:
|
|
184
|
+
print(f"Error processing order: {e}")
|
|
185
|
+
return {
|
|
186
|
+
'success': False,
|
|
187
|
+
'vars_to_update': {}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Function Registration
|
|
192
|
+
|
|
193
|
+
You can register functions individually or in bulk:
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
# Individual registration
|
|
197
|
+
client.register_function("function-id-1", my_function)
|
|
198
|
+
|
|
199
|
+
# Bulk registration
|
|
200
|
+
functions = {
|
|
201
|
+
"function-id-1": cancel_order,
|
|
202
|
+
"function-id-2": refund,
|
|
203
|
+
"function-id-3": process_order,
|
|
204
|
+
}
|
|
205
|
+
client.register_functions(functions)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Configuration
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
client = AsyncChainClient(
|
|
212
|
+
chain_id="your-chain-id", # Your unique chain identifier
|
|
213
|
+
api_key="your-api-key", # Your API key for authentication
|
|
214
|
+
base_url="https://chainix.ai", # Base URL (optional, defaults to chainix.ai)
|
|
215
|
+
max_wait_time=300, # Max wait time in seconds (optional, default 300)
|
|
216
|
+
poll_interval=5, # How often to check status in seconds (optional, default 5, minimum 3)
|
|
217
|
+
verbose=True # Whether to print status messages (optional, default True)
|
|
218
|
+
)
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Silent Mode
|
|
222
|
+
|
|
223
|
+
For production environments or when you don't want status messages, you can disable verbose output:
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
client = AsyncChainClient(
|
|
227
|
+
chain_id="your-chain-id",
|
|
228
|
+
api_key="your-api-key",
|
|
229
|
+
verbose=False # Runs silently
|
|
230
|
+
)
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Running Chains
|
|
234
|
+
|
|
235
|
+
### Basic Usage
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
result = client.run_chain(
|
|
239
|
+
initial_variables={
|
|
240
|
+
'message body': 'Hi, please cancel my order',
|
|
241
|
+
'user email': 'user@example.com',
|
|
242
|
+
'order id': '12345',
|
|
243
|
+
}
|
|
244
|
+
)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Test Mode
|
|
248
|
+
|
|
249
|
+
You can run chains in test mode for development and debugging:
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
result = client.run_chain(
|
|
253
|
+
initial_variables={
|
|
254
|
+
'message body': 'Hi, please cancel my order',
|
|
255
|
+
'user email': 'user@example.com',
|
|
256
|
+
'order id': '12345'
|
|
257
|
+
},
|
|
258
|
+
test=True # Runs in test mode
|
|
259
|
+
)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Error Handling
|
|
263
|
+
|
|
264
|
+
The client automatically handles several types of errors:
|
|
265
|
+
|
|
266
|
+
- **Network errors**: Automatically retries with backoff
|
|
267
|
+
- **Function execution errors**: Functions that throw exceptions are treated as failed (`success: False`)
|
|
268
|
+
- **Invalid function returns**: If functions don't return the required structure, the chain will stop with a clear error message
|
|
269
|
+
|
|
270
|
+
### Best Practices
|
|
271
|
+
|
|
272
|
+
1. **Always wrap the body of your custom function in try-catch blocks**, catch any errors and set success to false in the return dictionary
|
|
273
|
+
2. **Return meaningful error information** when functions fail
|
|
274
|
+
3. **Validate input arguments** at the start of your functions
|
|
275
|
+
4. **Use exact variable names** in `vars_to_update` that match your variables names on the chain configuration on chainix.ai
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
def robust_function(args):
|
|
279
|
+
try:
|
|
280
|
+
# Validate inputs
|
|
281
|
+
if 'required_field' not in args:
|
|
282
|
+
raise ValueError("Missing required_field")
|
|
283
|
+
|
|
284
|
+
# Your business logic
|
|
285
|
+
result = perform_business_logic(args)
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
'success': True,
|
|
289
|
+
'vars_to_update': {
|
|
290
|
+
'operation result': result,
|
|
291
|
+
'timestamp': datetime.now().isoformat()
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
except ValueError as e:
|
|
296
|
+
print(f"Validation error: {e}")
|
|
297
|
+
return {
|
|
298
|
+
'success': False,
|
|
299
|
+
'vars_to_update': {'error type': 'validation_error'}
|
|
300
|
+
}
|
|
301
|
+
except Exception as e:
|
|
302
|
+
print(f"Unexpected error: {e}")
|
|
303
|
+
return {
|
|
304
|
+
'success': False,
|
|
305
|
+
'vars_to_update': {'error type': 'unexpected_error'}
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## License
|
|
310
|
+
|
|
311
|
+
MIT License
|
chainix-0.1.0/README.md
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# Chainix
|
|
2
|
+
|
|
3
|
+
A Python client library for executing chains with chainix.ai
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install chainix
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from chainix import AsyncChainClient
|
|
15
|
+
|
|
16
|
+
# Initialize the client
|
|
17
|
+
client = AsyncChainClient(
|
|
18
|
+
chain_id="your-chains-id-here",
|
|
19
|
+
api_key="your-api-key-here",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# Define your custom functions
|
|
23
|
+
def refund(args):
|
|
24
|
+
try:
|
|
25
|
+
order_id = args['order id']
|
|
26
|
+
print(f"Issuing a refund for order: {order_id}")
|
|
27
|
+
|
|
28
|
+
# Your business logic here
|
|
29
|
+
# ... process refund ...
|
|
30
|
+
amount = 500
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
'success': True,
|
|
34
|
+
'vars_to_update': {
|
|
35
|
+
'refund_amount': amount
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
except Exception as e:
|
|
39
|
+
print(f"Failed to process refund: {e}")
|
|
40
|
+
return {
|
|
41
|
+
'success': False,
|
|
42
|
+
'vars_to_update': {}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def cancel_order(args):
|
|
47
|
+
try:
|
|
48
|
+
order_id = args['order id']
|
|
49
|
+
print(f"Cancelling order: {order_id}")
|
|
50
|
+
|
|
51
|
+
# Your business logic here
|
|
52
|
+
# ... perform cancellation ...
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
'success': True,
|
|
56
|
+
'vars_to_update': {}
|
|
57
|
+
}
|
|
58
|
+
except Exception as e:
|
|
59
|
+
print(f"Failed to cancel order: {e}")
|
|
60
|
+
return {
|
|
61
|
+
'success': False,
|
|
62
|
+
'vars_to_update': {}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# Register your functions (use the actual function IDs from the chain on chainix.ai)
|
|
67
|
+
client.register_function("your-cancel-function-id", cancel_order)
|
|
68
|
+
client.register_function("your-refund-function-id", refund)
|
|
69
|
+
|
|
70
|
+
# Or, bulk register your functions
|
|
71
|
+
functions = {
|
|
72
|
+
"your-cancel-function-id": cancel_order,
|
|
73
|
+
"your-refund-function-id": refund,
|
|
74
|
+
}
|
|
75
|
+
client.register_functions(functions)
|
|
76
|
+
|
|
77
|
+
# Execute a chain
|
|
78
|
+
# Provide all initial variables needed to start the chain
|
|
79
|
+
result = client.run_chain({
|
|
80
|
+
'message body': 'Hi, please cancel my order',
|
|
81
|
+
'order id': '33433',
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
print("Chain completed:", result)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Function Requirements
|
|
88
|
+
|
|
89
|
+
All registered functions **must** follow these requirements:
|
|
90
|
+
|
|
91
|
+
### Function Signature
|
|
92
|
+
Your functions should accept a single dictionary argument containing the inputs for that function call step:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
def my_function(args: dict) -> dict:
|
|
96
|
+
# Your business logic here
|
|
97
|
+
pass
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**How it works:**
|
|
101
|
+
1. You define variables in your chain on chainix.ai (e.g., `order id`, `user email`, `action`)
|
|
102
|
+
2. You create function call steps in your chain and specify which variables should be passed as inputs to each step
|
|
103
|
+
3. When the chain reaches a function call step, it stops and calls your registered function via it's id
|
|
104
|
+
4. Your function receives a dictionary where each key is a variable you specified as an input for that step, and each value is the current value of that variable in the chain
|
|
105
|
+
|
|
106
|
+
**Example:** If you have a function call step with `order id` and `user email` as inputs, your function will receive:
|
|
107
|
+
```python
|
|
108
|
+
{
|
|
109
|
+
'order id': '12345',
|
|
110
|
+
'user email': 'user@example.com'
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Return Value
|
|
115
|
+
Your functions **must** return a dictionary with exactly two keys:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
{
|
|
119
|
+
'success': bool, # True if function executed successfully, False otherwise
|
|
120
|
+
'vars_to_update': dict # Dictionary of variables to update in the chain (can be empty)
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Important**: The keys in `vars_to_update` must exactly match the variable names you defined in your chain on chainix.ai. Only variables that exist in your chain can be updated. If you try to update a variable that doesn't exist in your chain, the chain will fail.
|
|
125
|
+
|
|
126
|
+
### Example Function Structure
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
def process_order(args):
|
|
130
|
+
try:
|
|
131
|
+
# Extract arguments
|
|
132
|
+
order_id = args['order id']
|
|
133
|
+
action = args.get('action', 'process')
|
|
134
|
+
|
|
135
|
+
# Your business logic here
|
|
136
|
+
if action == 'cancel':
|
|
137
|
+
# ... cancellation logic ...
|
|
138
|
+
return {
|
|
139
|
+
'success': True,
|
|
140
|
+
'vars_to_update': {
|
|
141
|
+
'order status': 'cancelled', # Must match variable name in your chain
|
|
142
|
+
'cancellation date': '2024-01-01' # Must match variable name in your chain
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
elif action == 'fulfill':
|
|
146
|
+
# ... fulfillment logic ...
|
|
147
|
+
return {
|
|
148
|
+
'success': True,
|
|
149
|
+
'vars_to_update': {
|
|
150
|
+
'order status': 'fulfilled', # Must match variable name in your chain
|
|
151
|
+
'fulfillment date': '2024-01-01' # Must match variable name in your chain
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else:
|
|
155
|
+
return {
|
|
156
|
+
'success': False,
|
|
157
|
+
'vars_to_update': {}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
except Exception as e:
|
|
161
|
+
print(f"Error processing order: {e}")
|
|
162
|
+
return {
|
|
163
|
+
'success': False,
|
|
164
|
+
'vars_to_update': {}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Function Registration
|
|
169
|
+
|
|
170
|
+
You can register functions individually or in bulk:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
# Individual registration
|
|
174
|
+
client.register_function("function-id-1", my_function)
|
|
175
|
+
|
|
176
|
+
# Bulk registration
|
|
177
|
+
functions = {
|
|
178
|
+
"function-id-1": cancel_order,
|
|
179
|
+
"function-id-2": refund,
|
|
180
|
+
"function-id-3": process_order,
|
|
181
|
+
}
|
|
182
|
+
client.register_functions(functions)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Configuration
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
client = AsyncChainClient(
|
|
189
|
+
chain_id="your-chain-id", # Your unique chain identifier
|
|
190
|
+
api_key="your-api-key", # Your API key for authentication
|
|
191
|
+
base_url="https://chainix.ai", # Base URL (optional, defaults to chainix.ai)
|
|
192
|
+
max_wait_time=300, # Max wait time in seconds (optional, default 300)
|
|
193
|
+
poll_interval=5, # How often to check status in seconds (optional, default 5, minimum 3)
|
|
194
|
+
verbose=True # Whether to print status messages (optional, default True)
|
|
195
|
+
)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Silent Mode
|
|
199
|
+
|
|
200
|
+
For production environments or when you don't want status messages, you can disable verbose output:
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
client = AsyncChainClient(
|
|
204
|
+
chain_id="your-chain-id",
|
|
205
|
+
api_key="your-api-key",
|
|
206
|
+
verbose=False # Runs silently
|
|
207
|
+
)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Running Chains
|
|
211
|
+
|
|
212
|
+
### Basic Usage
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
result = client.run_chain(
|
|
216
|
+
initial_variables={
|
|
217
|
+
'message body': 'Hi, please cancel my order',
|
|
218
|
+
'user email': 'user@example.com',
|
|
219
|
+
'order id': '12345',
|
|
220
|
+
}
|
|
221
|
+
)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Test Mode
|
|
225
|
+
|
|
226
|
+
You can run chains in test mode for development and debugging:
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
result = client.run_chain(
|
|
230
|
+
initial_variables={
|
|
231
|
+
'message body': 'Hi, please cancel my order',
|
|
232
|
+
'user email': 'user@example.com',
|
|
233
|
+
'order id': '12345'
|
|
234
|
+
},
|
|
235
|
+
test=True # Runs in test mode
|
|
236
|
+
)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Error Handling
|
|
240
|
+
|
|
241
|
+
The client automatically handles several types of errors:
|
|
242
|
+
|
|
243
|
+
- **Network errors**: Automatically retries with backoff
|
|
244
|
+
- **Function execution errors**: Functions that throw exceptions are treated as failed (`success: False`)
|
|
245
|
+
- **Invalid function returns**: If functions don't return the required structure, the chain will stop with a clear error message
|
|
246
|
+
|
|
247
|
+
### Best Practices
|
|
248
|
+
|
|
249
|
+
1. **Always wrap the body of your custom function in try-catch blocks**, catch any errors and set success to false in the return dictionary
|
|
250
|
+
2. **Return meaningful error information** when functions fail
|
|
251
|
+
3. **Validate input arguments** at the start of your functions
|
|
252
|
+
4. **Use exact variable names** in `vars_to_update` that match your variables names on the chain configuration on chainix.ai
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
def robust_function(args):
|
|
256
|
+
try:
|
|
257
|
+
# Validate inputs
|
|
258
|
+
if 'required_field' not in args:
|
|
259
|
+
raise ValueError("Missing required_field")
|
|
260
|
+
|
|
261
|
+
# Your business logic
|
|
262
|
+
result = perform_business_logic(args)
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
'success': True,
|
|
266
|
+
'vars_to_update': {
|
|
267
|
+
'operation result': result,
|
|
268
|
+
'timestamp': datetime.now().isoformat()
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
except ValueError as e:
|
|
273
|
+
print(f"Validation error: {e}")
|
|
274
|
+
return {
|
|
275
|
+
'success': False,
|
|
276
|
+
'vars_to_update': {'error type': 'validation_error'}
|
|
277
|
+
}
|
|
278
|
+
except Exception as e:
|
|
279
|
+
print(f"Unexpected error: {e}")
|
|
280
|
+
return {
|
|
281
|
+
'success': False,
|
|
282
|
+
'vars_to_update': {'error type': 'unexpected_error'}
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## License
|
|
287
|
+
|
|
288
|
+
MIT License
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import time
|
|
3
|
+
from typing import Dict, Callable, Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AsyncChainClient:
|
|
7
|
+
"""
|
|
8
|
+
Client for executing asynchronous chains with custom function callbacks.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
chain_id: str,
|
|
14
|
+
api_key: str,
|
|
15
|
+
base_url: str = "https://chainix.ai",
|
|
16
|
+
max_wait_time: int = 300,
|
|
17
|
+
poll_interval: int = 5,
|
|
18
|
+
verbose: bool = True
|
|
19
|
+
):
|
|
20
|
+
"""
|
|
21
|
+
Initialize the AsyncChainClient.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
chain_id: Unique identifier for your chain
|
|
25
|
+
api_key: API key for authentication
|
|
26
|
+
base_url: Base URL of the chain server
|
|
27
|
+
max_wait_time: Maximum time to wait for chain completion in seconds
|
|
28
|
+
poll_interval: Time between status polls in seconds
|
|
29
|
+
verbose: Whether to print status messages (default True)
|
|
30
|
+
"""
|
|
31
|
+
self.chain_id = chain_id
|
|
32
|
+
self.api_key = api_key
|
|
33
|
+
self.base_url = base_url.rstrip('/')
|
|
34
|
+
self.max_wait_time = max_wait_time
|
|
35
|
+
self.poll_interval = max(poll_interval, 3)
|
|
36
|
+
self.functions: Dict[str, Callable] = {}
|
|
37
|
+
self.verbose = verbose
|
|
38
|
+
|
|
39
|
+
# Set up default headers
|
|
40
|
+
self.headers = {
|
|
41
|
+
'X-API-Key': self.api_key,
|
|
42
|
+
'Content-Type': 'application/json'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
def _log(self, message: str):
|
|
46
|
+
"""Print message if verbose mode is enabled."""
|
|
47
|
+
if self.verbose:
|
|
48
|
+
print(message)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def register_function(self, function_id: str, function: Callable[[Dict[str, Any]], Any]) -> None:
|
|
52
|
+
"""
|
|
53
|
+
Register a function that can be called during chain execution.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
function_id: Unique identifier for the function
|
|
57
|
+
function: Callable that takes a dictionary of arguments and returns a result
|
|
58
|
+
"""
|
|
59
|
+
self.functions[function_id] = function
|
|
60
|
+
|
|
61
|
+
def register_functions(self, functions: Dict[str, Callable[[Dict[str, Any]], Any]]) -> None:
|
|
62
|
+
"""
|
|
63
|
+
Register multiple functions at once.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
functions: Dictionary mapping function IDs to callable functions
|
|
67
|
+
"""
|
|
68
|
+
self.functions.update(functions)
|
|
69
|
+
|
|
70
|
+
def poll_chain_status(self, task_id: str) -> Dict[str, Any]:
|
|
71
|
+
"""
|
|
72
|
+
Poll the server for chain status until completion or timeout.
|
|
73
|
+
"""
|
|
74
|
+
start_time = time.time()
|
|
75
|
+
status_url = f'{self.base_url}/api/chain-status/{self.chain_id}/{task_id}'
|
|
76
|
+
|
|
77
|
+
while time.time() - start_time < self.max_wait_time:
|
|
78
|
+
try:
|
|
79
|
+
status_response = requests.get(status_url, headers=self.headers)
|
|
80
|
+
|
|
81
|
+
if not status_response.ok:
|
|
82
|
+
try:
|
|
83
|
+
error_data = status_response.json()
|
|
84
|
+
if error_data.get('detail'):
|
|
85
|
+
self._log(f"Error polling chain status: {error_data['detail']}")
|
|
86
|
+
else:
|
|
87
|
+
self._log(f"Error response: {error_data}")
|
|
88
|
+
except:
|
|
89
|
+
self._log(f"Error {status_response.status_code}: {status_response.text}")
|
|
90
|
+
time.sleep(self.poll_interval)
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
status_data = status_response.json()
|
|
94
|
+
self._log(f"Async status: {status_data.get('status', 'unknown')}")
|
|
95
|
+
|
|
96
|
+
if status_data.get('status') == 'complete':
|
|
97
|
+
return status_data.get('result', {})
|
|
98
|
+
elif status_data.get('status') == 'failed':
|
|
99
|
+
raise Exception(f"Chain failed: {status_data.get('result')}")
|
|
100
|
+
|
|
101
|
+
# Chain is still running, wait
|
|
102
|
+
time.sleep(self.poll_interval)
|
|
103
|
+
except requests.RequestException as e:
|
|
104
|
+
self._log(f"Error polling chain status: {e}")
|
|
105
|
+
time.sleep(self.poll_interval)
|
|
106
|
+
|
|
107
|
+
raise TimeoutError(f"Chain with run id {task_id} did not complete within {self.max_wait_time} seconds.")
|
|
108
|
+
|
|
109
|
+
def run_chain(
|
|
110
|
+
self,
|
|
111
|
+
initial_variables: Dict[str, Any],
|
|
112
|
+
test: bool = False,
|
|
113
|
+
) -> Dict[str, Any]:
|
|
114
|
+
"""
|
|
115
|
+
Execute an asynchronous chain with the provided initial_variables.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
initial_variables: Dictionary of initial variables for the chain
|
|
119
|
+
test: Whether this is a test run
|
|
120
|
+
vars_to_update: Optional dictionary of variables to update during execution
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Dictionary containing the final chain result
|
|
124
|
+
"""
|
|
125
|
+
url = f'{self.base_url}/api/run-chain/{self.chain_id}'
|
|
126
|
+
|
|
127
|
+
payload = {
|
|
128
|
+
'initial_variables': initial_variables,
|
|
129
|
+
'initial_run': True,
|
|
130
|
+
'test': test,
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
vars_to_update = {}
|
|
134
|
+
|
|
135
|
+
if vars_to_update:
|
|
136
|
+
payload['vars_to_update'] = vars_to_update
|
|
137
|
+
|
|
138
|
+
response = requests.post(url, json=payload, headers=self.headers)
|
|
139
|
+
if not response.ok:
|
|
140
|
+
try:
|
|
141
|
+
error_data = response.json()
|
|
142
|
+
if error_data.get('detail'):
|
|
143
|
+
self._log(f"Error: {error_data['detail']}")
|
|
144
|
+
self._log(f"Response: {error_data}")
|
|
145
|
+
else:
|
|
146
|
+
self._log(f"Response: {error_data}")
|
|
147
|
+
except:
|
|
148
|
+
self._log(f"Error {response.status_code}: {response.text}")
|
|
149
|
+
return {}
|
|
150
|
+
|
|
151
|
+
initial_response = response.json()
|
|
152
|
+
task_id = initial_response.get('data', {}).get('taskId')
|
|
153
|
+
if not task_id:
|
|
154
|
+
raise Exception("No task ID received from initial request.")
|
|
155
|
+
|
|
156
|
+
self._log(f"Chain started with task ID: {task_id}")
|
|
157
|
+
response = self.poll_chain_status(task_id)
|
|
158
|
+
|
|
159
|
+
while not response.get('complete', False):
|
|
160
|
+
# extract the run ID
|
|
161
|
+
run_id = response.get('run_id')
|
|
162
|
+
# the run is asking for a function call
|
|
163
|
+
func_info = response.get('function')
|
|
164
|
+
if not func_info:
|
|
165
|
+
raise Exception("No function call information found in the response.")
|
|
166
|
+
|
|
167
|
+
returned_function_id = func_info.get('id')
|
|
168
|
+
arguments = func_info.get('args')
|
|
169
|
+
|
|
170
|
+
if returned_function_id not in self.functions:
|
|
171
|
+
raise Exception(f"Function '{returned_function_id}' not implemented.")
|
|
172
|
+
|
|
173
|
+
function_to_call = self.functions[returned_function_id]
|
|
174
|
+
try:
|
|
175
|
+
result = function_to_call(arguments)
|
|
176
|
+
except Exception as e:
|
|
177
|
+
self._log(f"Function '{returned_function_id}' failed: {e}")
|
|
178
|
+
success = False
|
|
179
|
+
function_vars_to_update = {}
|
|
180
|
+
|
|
181
|
+
else:
|
|
182
|
+
if not isinstance(result, dict):
|
|
183
|
+
raise Exception(f"Function '{returned_function_id}' must return a dictionary, got {type(result)}")
|
|
184
|
+
|
|
185
|
+
if 'success' not in result:
|
|
186
|
+
raise Exception(f"Function '{returned_function_id}' must return a dictionary with 'success' key to indicate if the function ran successfully.")
|
|
187
|
+
|
|
188
|
+
if 'vars_to_update' not in result:
|
|
189
|
+
raise Exception(f"Function '{returned_function_id}' must return a dictionary with 'vars_to_update' key. If there are no variables to update it should return an empty dictionary.")
|
|
190
|
+
|
|
191
|
+
success = result['success']
|
|
192
|
+
function_vars_to_update = result['vars_to_update']
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
payload = {
|
|
196
|
+
'run_id': run_id,
|
|
197
|
+
'success': success,
|
|
198
|
+
'initial_run': False,
|
|
199
|
+
'vars_to_update': function_vars_to_update
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
submit_resp = requests.post(url, json=payload, headers=self.headers)
|
|
203
|
+
if not submit_resp.ok:
|
|
204
|
+
try:
|
|
205
|
+
error_data = submit_resp.json()
|
|
206
|
+
if error_data.get('detail'):
|
|
207
|
+
self._log(f"Error submitting function result: {error_data['detail']}")
|
|
208
|
+
else:
|
|
209
|
+
self._log(f"Error response: {error_data}")
|
|
210
|
+
except:
|
|
211
|
+
self._log(f"Error {submit_resp.status_code}: {submit_resp.text}")
|
|
212
|
+
return {}
|
|
213
|
+
|
|
214
|
+
submit_response = submit_resp.json()
|
|
215
|
+
new_task_id = submit_response.get('data', {}).get('taskId')
|
|
216
|
+
if new_task_id:
|
|
217
|
+
self._log(f"Chain continued with task ID: {new_task_id}")
|
|
218
|
+
response = self.poll_chain_status(new_task_id)
|
|
219
|
+
else:
|
|
220
|
+
response = submit_response
|
|
221
|
+
|
|
222
|
+
self._log("Chain complete!")
|
|
223
|
+
return response
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: chainix
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A client library for executing asynchronous chains with custom function callbacks
|
|
5
|
+
Home-page: https://github.com/ChainixDev/chainix-python
|
|
6
|
+
Author: Chainix
|
|
7
|
+
Author-email: jack@chainix.ai
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.7
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: requests>=2.25.0
|
|
14
|
+
Dynamic: author
|
|
15
|
+
Dynamic: author-email
|
|
16
|
+
Dynamic: classifier
|
|
17
|
+
Dynamic: description
|
|
18
|
+
Dynamic: description-content-type
|
|
19
|
+
Dynamic: home-page
|
|
20
|
+
Dynamic: requires-dist
|
|
21
|
+
Dynamic: requires-python
|
|
22
|
+
Dynamic: summary
|
|
23
|
+
|
|
24
|
+
# Chainix
|
|
25
|
+
|
|
26
|
+
A Python client library for executing chains with chainix.ai
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install chainix
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from chainix import AsyncChainClient
|
|
38
|
+
|
|
39
|
+
# Initialize the client
|
|
40
|
+
client = AsyncChainClient(
|
|
41
|
+
chain_id="your-chains-id-here",
|
|
42
|
+
api_key="your-api-key-here",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Define your custom functions
|
|
46
|
+
def refund(args):
|
|
47
|
+
try:
|
|
48
|
+
order_id = args['order id']
|
|
49
|
+
print(f"Issuing a refund for order: {order_id}")
|
|
50
|
+
|
|
51
|
+
# Your business logic here
|
|
52
|
+
# ... process refund ...
|
|
53
|
+
amount = 500
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
'success': True,
|
|
57
|
+
'vars_to_update': {
|
|
58
|
+
'refund_amount': amount
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
except Exception as e:
|
|
62
|
+
print(f"Failed to process refund: {e}")
|
|
63
|
+
return {
|
|
64
|
+
'success': False,
|
|
65
|
+
'vars_to_update': {}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def cancel_order(args):
|
|
70
|
+
try:
|
|
71
|
+
order_id = args['order id']
|
|
72
|
+
print(f"Cancelling order: {order_id}")
|
|
73
|
+
|
|
74
|
+
# Your business logic here
|
|
75
|
+
# ... perform cancellation ...
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
'success': True,
|
|
79
|
+
'vars_to_update': {}
|
|
80
|
+
}
|
|
81
|
+
except Exception as e:
|
|
82
|
+
print(f"Failed to cancel order: {e}")
|
|
83
|
+
return {
|
|
84
|
+
'success': False,
|
|
85
|
+
'vars_to_update': {}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# Register your functions (use the actual function IDs from the chain on chainix.ai)
|
|
90
|
+
client.register_function("your-cancel-function-id", cancel_order)
|
|
91
|
+
client.register_function("your-refund-function-id", refund)
|
|
92
|
+
|
|
93
|
+
# Or, bulk register your functions
|
|
94
|
+
functions = {
|
|
95
|
+
"your-cancel-function-id": cancel_order,
|
|
96
|
+
"your-refund-function-id": refund,
|
|
97
|
+
}
|
|
98
|
+
client.register_functions(functions)
|
|
99
|
+
|
|
100
|
+
# Execute a chain
|
|
101
|
+
# Provide all initial variables needed to start the chain
|
|
102
|
+
result = client.run_chain({
|
|
103
|
+
'message body': 'Hi, please cancel my order',
|
|
104
|
+
'order id': '33433',
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
print("Chain completed:", result)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Function Requirements
|
|
111
|
+
|
|
112
|
+
All registered functions **must** follow these requirements:
|
|
113
|
+
|
|
114
|
+
### Function Signature
|
|
115
|
+
Your functions should accept a single dictionary argument containing the inputs for that function call step:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
def my_function(args: dict) -> dict:
|
|
119
|
+
# Your business logic here
|
|
120
|
+
pass
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**How it works:**
|
|
124
|
+
1. You define variables in your chain on chainix.ai (e.g., `order id`, `user email`, `action`)
|
|
125
|
+
2. You create function call steps in your chain and specify which variables should be passed as inputs to each step
|
|
126
|
+
3. When the chain reaches a function call step, it stops and calls your registered function via it's id
|
|
127
|
+
4. Your function receives a dictionary where each key is a variable you specified as an input for that step, and each value is the current value of that variable in the chain
|
|
128
|
+
|
|
129
|
+
**Example:** If you have a function call step with `order id` and `user email` as inputs, your function will receive:
|
|
130
|
+
```python
|
|
131
|
+
{
|
|
132
|
+
'order id': '12345',
|
|
133
|
+
'user email': 'user@example.com'
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Return Value
|
|
138
|
+
Your functions **must** return a dictionary with exactly two keys:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
{
|
|
142
|
+
'success': bool, # True if function executed successfully, False otherwise
|
|
143
|
+
'vars_to_update': dict # Dictionary of variables to update in the chain (can be empty)
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Important**: The keys in `vars_to_update` must exactly match the variable names you defined in your chain on chainix.ai. Only variables that exist in your chain can be updated. If you try to update a variable that doesn't exist in your chain, the chain will fail.
|
|
148
|
+
|
|
149
|
+
### Example Function Structure
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
def process_order(args):
|
|
153
|
+
try:
|
|
154
|
+
# Extract arguments
|
|
155
|
+
order_id = args['order id']
|
|
156
|
+
action = args.get('action', 'process')
|
|
157
|
+
|
|
158
|
+
# Your business logic here
|
|
159
|
+
if action == 'cancel':
|
|
160
|
+
# ... cancellation logic ...
|
|
161
|
+
return {
|
|
162
|
+
'success': True,
|
|
163
|
+
'vars_to_update': {
|
|
164
|
+
'order status': 'cancelled', # Must match variable name in your chain
|
|
165
|
+
'cancellation date': '2024-01-01' # Must match variable name in your chain
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
elif action == 'fulfill':
|
|
169
|
+
# ... fulfillment logic ...
|
|
170
|
+
return {
|
|
171
|
+
'success': True,
|
|
172
|
+
'vars_to_update': {
|
|
173
|
+
'order status': 'fulfilled', # Must match variable name in your chain
|
|
174
|
+
'fulfillment date': '2024-01-01' # Must match variable name in your chain
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
else:
|
|
178
|
+
return {
|
|
179
|
+
'success': False,
|
|
180
|
+
'vars_to_update': {}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
except Exception as e:
|
|
184
|
+
print(f"Error processing order: {e}")
|
|
185
|
+
return {
|
|
186
|
+
'success': False,
|
|
187
|
+
'vars_to_update': {}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Function Registration
|
|
192
|
+
|
|
193
|
+
You can register functions individually or in bulk:
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
# Individual registration
|
|
197
|
+
client.register_function("function-id-1", my_function)
|
|
198
|
+
|
|
199
|
+
# Bulk registration
|
|
200
|
+
functions = {
|
|
201
|
+
"function-id-1": cancel_order,
|
|
202
|
+
"function-id-2": refund,
|
|
203
|
+
"function-id-3": process_order,
|
|
204
|
+
}
|
|
205
|
+
client.register_functions(functions)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Configuration
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
client = AsyncChainClient(
|
|
212
|
+
chain_id="your-chain-id", # Your unique chain identifier
|
|
213
|
+
api_key="your-api-key", # Your API key for authentication
|
|
214
|
+
base_url="https://chainix.ai", # Base URL (optional, defaults to chainix.ai)
|
|
215
|
+
max_wait_time=300, # Max wait time in seconds (optional, default 300)
|
|
216
|
+
poll_interval=5, # How often to check status in seconds (optional, default 5, minimum 3)
|
|
217
|
+
verbose=True # Whether to print status messages (optional, default True)
|
|
218
|
+
)
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Silent Mode
|
|
222
|
+
|
|
223
|
+
For production environments or when you don't want status messages, you can disable verbose output:
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
client = AsyncChainClient(
|
|
227
|
+
chain_id="your-chain-id",
|
|
228
|
+
api_key="your-api-key",
|
|
229
|
+
verbose=False # Runs silently
|
|
230
|
+
)
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Running Chains
|
|
234
|
+
|
|
235
|
+
### Basic Usage
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
result = client.run_chain(
|
|
239
|
+
initial_variables={
|
|
240
|
+
'message body': 'Hi, please cancel my order',
|
|
241
|
+
'user email': 'user@example.com',
|
|
242
|
+
'order id': '12345',
|
|
243
|
+
}
|
|
244
|
+
)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Test Mode
|
|
248
|
+
|
|
249
|
+
You can run chains in test mode for development and debugging:
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
result = client.run_chain(
|
|
253
|
+
initial_variables={
|
|
254
|
+
'message body': 'Hi, please cancel my order',
|
|
255
|
+
'user email': 'user@example.com',
|
|
256
|
+
'order id': '12345'
|
|
257
|
+
},
|
|
258
|
+
test=True # Runs in test mode
|
|
259
|
+
)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Error Handling
|
|
263
|
+
|
|
264
|
+
The client automatically handles several types of errors:
|
|
265
|
+
|
|
266
|
+
- **Network errors**: Automatically retries with backoff
|
|
267
|
+
- **Function execution errors**: Functions that throw exceptions are treated as failed (`success: False`)
|
|
268
|
+
- **Invalid function returns**: If functions don't return the required structure, the chain will stop with a clear error message
|
|
269
|
+
|
|
270
|
+
### Best Practices
|
|
271
|
+
|
|
272
|
+
1. **Always wrap the body of your custom function in try-catch blocks**, catch any errors and set success to false in the return dictionary
|
|
273
|
+
2. **Return meaningful error information** when functions fail
|
|
274
|
+
3. **Validate input arguments** at the start of your functions
|
|
275
|
+
4. **Use exact variable names** in `vars_to_update` that match your variables names on the chain configuration on chainix.ai
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
def robust_function(args):
|
|
279
|
+
try:
|
|
280
|
+
# Validate inputs
|
|
281
|
+
if 'required_field' not in args:
|
|
282
|
+
raise ValueError("Missing required_field")
|
|
283
|
+
|
|
284
|
+
# Your business logic
|
|
285
|
+
result = perform_business_logic(args)
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
'success': True,
|
|
289
|
+
'vars_to_update': {
|
|
290
|
+
'operation result': result,
|
|
291
|
+
'timestamp': datetime.now().isoformat()
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
except ValueError as e:
|
|
296
|
+
print(f"Validation error: {e}")
|
|
297
|
+
return {
|
|
298
|
+
'success': False,
|
|
299
|
+
'vars_to_update': {'error type': 'validation_error'}
|
|
300
|
+
}
|
|
301
|
+
except Exception as e:
|
|
302
|
+
print(f"Unexpected error: {e}")
|
|
303
|
+
return {
|
|
304
|
+
'success': False,
|
|
305
|
+
'vars_to_update': {'error type': 'unexpected_error'}
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## License
|
|
310
|
+
|
|
311
|
+
MIT License
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests>=2.25.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
chainix
|
chainix-0.1.0/setup.cfg
ADDED
chainix-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r", encoding="utf-8") as fh:
|
|
4
|
+
long_description = fh.read()
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name='chainix',
|
|
8
|
+
version='0.1.0',
|
|
9
|
+
author='Chainix',
|
|
10
|
+
author_email='jack@chainix.ai',
|
|
11
|
+
description='A client library for executing asynchronous chains with custom function callbacks',
|
|
12
|
+
long_description=long_description,
|
|
13
|
+
long_description_content_type="text/markdown",
|
|
14
|
+
url="https://github.com/ChainixDev/chainix-python",
|
|
15
|
+
packages=find_packages(),
|
|
16
|
+
classifiers=[
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
],
|
|
21
|
+
python_requires=">=3.7",
|
|
22
|
+
install_requires=[
|
|
23
|
+
'requests>=2.25.0',
|
|
24
|
+
],
|
|
25
|
+
)
|