kailash 0.1.0__py3-none-any.whl → 0.1.2__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 (38) hide show
  1. kailash/__init__.py +1 -1
  2. kailash/nodes/__init__.py +2 -1
  3. kailash/nodes/ai/__init__.py +26 -0
  4. kailash/nodes/ai/ai_providers.py +1272 -0
  5. kailash/nodes/ai/embedding_generator.py +853 -0
  6. kailash/nodes/ai/llm_agent.py +1166 -0
  7. kailash/nodes/api/auth.py +3 -3
  8. kailash/nodes/api/graphql.py +2 -2
  9. kailash/nodes/api/http.py +391 -44
  10. kailash/nodes/api/rate_limiting.py +2 -2
  11. kailash/nodes/api/rest.py +464 -56
  12. kailash/nodes/base.py +71 -12
  13. kailash/nodes/code/python.py +2 -1
  14. kailash/nodes/data/__init__.py +7 -0
  15. kailash/nodes/data/readers.py +28 -26
  16. kailash/nodes/data/retrieval.py +178 -0
  17. kailash/nodes/data/sharepoint_graph.py +7 -7
  18. kailash/nodes/data/sources.py +65 -0
  19. kailash/nodes/data/sql.py +4 -2
  20. kailash/nodes/data/writers.py +6 -3
  21. kailash/nodes/logic/operations.py +2 -1
  22. kailash/nodes/mcp/__init__.py +11 -0
  23. kailash/nodes/mcp/client.py +558 -0
  24. kailash/nodes/mcp/resource.py +682 -0
  25. kailash/nodes/mcp/server.py +571 -0
  26. kailash/nodes/transform/__init__.py +16 -1
  27. kailash/nodes/transform/chunkers.py +78 -0
  28. kailash/nodes/transform/formatters.py +96 -0
  29. kailash/runtime/docker.py +6 -6
  30. kailash/sdk_exceptions.py +24 -10
  31. kailash/tracking/metrics_collector.py +2 -1
  32. kailash/utils/templates.py +6 -6
  33. {kailash-0.1.0.dist-info → kailash-0.1.2.dist-info}/METADATA +349 -49
  34. {kailash-0.1.0.dist-info → kailash-0.1.2.dist-info}/RECORD +38 -27
  35. {kailash-0.1.0.dist-info → kailash-0.1.2.dist-info}/WHEEL +0 -0
  36. {kailash-0.1.0.dist-info → kailash-0.1.2.dist-info}/entry_points.txt +0 -0
  37. {kailash-0.1.0.dist-info → kailash-0.1.2.dist-info}/licenses/LICENSE +0 -0
  38. {kailash-0.1.0.dist-info → kailash-0.1.2.dist-info}/top_level.txt +0 -0
kailash/nodes/api/auth.py CHANGED
@@ -21,7 +21,7 @@ from kailash.nodes.base import Node, NodeParameter, register_node
21
21
  from kailash.sdk_exceptions import NodeExecutionError, NodeValidationError
22
22
 
23
23
 
24
- @register_node(alias="BasicAuth")
24
+ @register_node()
25
25
  class BasicAuthNode(Node):
26
26
  """Node for adding Basic Authentication to API requests.
27
27
 
@@ -116,7 +116,7 @@ class BasicAuthNode(Node):
116
116
  return {"headers": headers, "auth_type": "basic"}
117
117
 
118
118
 
119
- @register_node(alias="OAuth2")
119
+ @register_node()
120
120
  class OAuth2Node(Node):
121
121
  """Node for handling OAuth 2.0 authentication flows.
122
122
 
@@ -421,7 +421,7 @@ class OAuth2Node(Node):
421
421
  }
422
422
 
423
423
 
424
- @register_node(alias="APIKey")
424
+ @register_node()
425
425
  class APIKeyNode(Node):
426
426
  """Node for API key authentication.
427
427
 
@@ -18,7 +18,7 @@ from kailash.nodes.base_async import AsyncNode
18
18
  from kailash.sdk_exceptions import NodeExecutionError, NodeValidationError
19
19
 
20
20
 
21
- @register_node(alias="GraphQLClient")
21
+ @register_node()
22
22
  class GraphQLClientNode(Node):
23
23
  """Node for interacting with GraphQL APIs.
24
24
 
@@ -331,7 +331,7 @@ class GraphQLClientNode(Node):
331
331
  }
332
332
 
333
333
 
334
- @register_node(alias="AsyncGraphQLClient")
334
+ @register_node()
335
335
  class AsyncGraphQLClientNode(AsyncNode):
336
336
  """Asynchronous node for interacting with GraphQL APIs.
337
337
 
kailash/nodes/api/http.py CHANGED
@@ -1,16 +1,12 @@
1
- """HTTP client nodes for making requests to external APIs.
1
+ """Enhanced HTTP client nodes with authentication and advanced features.
2
2
 
3
- This module provides nodes for making HTTP requests to external services.
4
- Both synchronous and asynchronous versions are provided to support different workflow
5
- execution modes.
6
-
7
- Key Components:
8
- - HTTPRequestNode: Synchronous HTTP client node
9
- - AsyncHTTPRequestNode: Asynchronous HTTP client node
10
- - Authentication helpers and utilities
3
+ This module provides an enhanced version of HTTPRequestNode that incorporates
4
+ the best features from both the original HTTPRequestNode and HTTPClientNode.
11
5
  """
12
6
 
13
7
  import asyncio
8
+ import base64
9
+ import time
14
10
  from enum import Enum
15
11
  from typing import Any, Dict, Optional
16
12
 
@@ -59,31 +55,35 @@ class HTTPResponse(BaseModel):
59
55
  url: str
60
56
 
61
57
 
62
- @register_node(alias="HTTPRequest")
58
+ @register_node()
63
59
  class HTTPRequestNode(Node):
64
- """Node for making HTTP requests to external APIs.
60
+ """Enhanced node for making HTTP requests to external APIs.
65
61
 
66
62
  This node provides a flexible interface for making HTTP requests with support for:
67
- - All common HTTP methods (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS)
68
- - JSON, form, and multipart request bodies
69
- - Custom headers and query parameters
70
- - Response parsing (JSON, text, binary)
71
- - Basic error handling and retries
63
+ * All common HTTP methods (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS)
64
+ * Multiple authentication methods (Bearer, Basic, API Key, OAuth2)
65
+ * JSON, form, and multipart request bodies
66
+ * Custom headers and query parameters
67
+ * Response parsing (JSON, text, binary)
68
+ * Error handling and retries with recovery suggestions
69
+ * Rate limiting support
70
+ * Request/response logging
72
71
 
73
72
  Design Purpose:
74
- - Enable workflow integration with external HTTP APIs
75
- - Provide a consistent interface for HTTP operations
76
- - Support common authentication patterns
77
- - Handle response parsing and error handling
73
+ * Enable workflow integration with external HTTP APIs
74
+ * Provide a consistent interface for HTTP operations
75
+ * Support common authentication patterns
76
+ * Handle response parsing and error handling
77
+ * Offer enterprise-grade features like rate limiting
78
78
 
79
79
  Upstream Usage:
80
- - Workflow: Creates and configures node for API integration
81
- - Specialized API nodes: May extend this node for specific APIs
80
+ * Workflow: Creates and configures node for API integration
81
+ * Specialized API nodes: May extend this node for specific APIs
82
82
 
83
83
  Downstream Consumers:
84
- - Data processing nodes: Consume API response data
85
- - Decision nodes: Route workflow based on API responses
86
- - Custom nodes: Process API-specific data formats
84
+ * Data processing nodes: Consume API response data
85
+ * Decision nodes: Route workflow based on API responses
86
+ * Custom nodes: Process API-specific data formats
87
87
  """
88
88
 
89
89
  def __init__(self, **kwargs):
@@ -101,6 +101,13 @@ class HTTPRequestNode(Node):
101
101
  verify_ssl (bool, optional): Whether to verify SSL certificates
102
102
  retry_count (int, optional): Number of times to retry failed requests
103
103
  retry_backoff (float, optional): Backoff factor for retries
104
+ auth_type (str, optional): Authentication type (bearer, basic, api_key, oauth2)
105
+ auth_token (str, optional): Authentication token/key
106
+ auth_username (str, optional): Username for basic auth
107
+ auth_password (str, optional): Password for basic auth
108
+ api_key_header (str, optional): Header name for API key auth
109
+ rate_limit_delay (float, optional): Delay between requests for rate limiting
110
+ log_requests (bool, optional): Whether to log request/response details
104
111
  **kwargs: Additional parameters passed to base Node
105
112
  """
106
113
  super().__init__(**kwargs)
@@ -116,7 +123,7 @@ class HTTPRequestNode(Node):
116
123
  "url": NodeParameter(
117
124
  name="url",
118
125
  type=str,
119
- required=True,
126
+ required=False,
120
127
  description="URL to send the request to",
121
128
  ),
122
129
  "method": NodeParameter(
@@ -189,6 +196,55 @@ class HTTPRequestNode(Node):
189
196
  default=0.5,
190
197
  description="Backoff factor for retries",
191
198
  ),
199
+ "auth_type": NodeParameter(
200
+ name="auth_type",
201
+ type=str,
202
+ required=False,
203
+ default=None,
204
+ description="Authentication type: bearer, basic, api_key, oauth2",
205
+ ),
206
+ "auth_token": NodeParameter(
207
+ name="auth_token",
208
+ type=str,
209
+ required=False,
210
+ default=None,
211
+ description="Authentication token/key for bearer, api_key, or oauth2",
212
+ ),
213
+ "auth_username": NodeParameter(
214
+ name="auth_username",
215
+ type=str,
216
+ required=False,
217
+ default=None,
218
+ description="Username for basic authentication",
219
+ ),
220
+ "auth_password": NodeParameter(
221
+ name="auth_password",
222
+ type=str,
223
+ required=False,
224
+ default=None,
225
+ description="Password for basic authentication",
226
+ ),
227
+ "api_key_header": NodeParameter(
228
+ name="api_key_header",
229
+ type=str,
230
+ required=False,
231
+ default="X-API-Key",
232
+ description="Header name for API key authentication",
233
+ ),
234
+ "rate_limit_delay": NodeParameter(
235
+ name="rate_limit_delay",
236
+ type=float,
237
+ required=False,
238
+ default=0,
239
+ description="Delay between requests to respect rate limits (seconds)",
240
+ ),
241
+ "log_requests": NodeParameter(
242
+ name="log_requests",
243
+ type=bool,
244
+ required=False,
245
+ default=False,
246
+ description="Log request and response details for debugging",
247
+ ),
192
248
  }
193
249
 
194
250
  def get_output_schema(self) -> Dict[str, NodeParameter]:
@@ -218,6 +274,49 @@ class HTTPRequestNode(Node):
218
274
  ),
219
275
  }
220
276
 
277
+ def _apply_authentication(
278
+ self,
279
+ headers: dict,
280
+ auth_type: Optional[str],
281
+ auth_token: Optional[str],
282
+ auth_username: Optional[str],
283
+ auth_password: Optional[str],
284
+ api_key_header: str,
285
+ ) -> dict:
286
+ """Apply authentication to request headers.
287
+
288
+ Args:
289
+ headers: Existing headers dictionary
290
+ auth_type: Type of authentication (bearer, basic, api_key, oauth2)
291
+ auth_token: Token for bearer/api_key/oauth2 authentication
292
+ auth_username: Username for basic authentication
293
+ auth_password: Password for basic authentication
294
+ api_key_header: Header name for API key authentication
295
+
296
+ Returns:
297
+ Updated headers dictionary with authentication
298
+ """
299
+ if not auth_type:
300
+ return headers
301
+
302
+ auth_headers = headers.copy()
303
+
304
+ if auth_type.lower() == "bearer" and auth_token:
305
+ auth_headers["Authorization"] = f"Bearer {auth_token}"
306
+
307
+ elif auth_type.lower() == "basic" and auth_username and auth_password:
308
+ credentials = f"{auth_username}:{auth_password}"
309
+ encoded = base64.b64encode(credentials.encode()).decode()
310
+ auth_headers["Authorization"] = f"Basic {encoded}"
311
+
312
+ elif auth_type.lower() == "api_key" and auth_token:
313
+ auth_headers[api_key_header] = auth_token
314
+
315
+ elif auth_type.lower() == "oauth2" and auth_token:
316
+ auth_headers["Authorization"] = f"Bearer {auth_token}"
317
+
318
+ return auth_headers
319
+
221
320
  def run(self, **kwargs) -> Dict[str, Any]:
222
321
  """Execute an HTTP request.
223
322
 
@@ -233,6 +332,13 @@ class HTTPRequestNode(Node):
233
332
  verify_ssl (bool, optional): Whether to verify SSL certificates
234
333
  retry_count (int, optional): Number of times to retry failed requests
235
334
  retry_backoff (float, optional): Backoff factor for retries
335
+ auth_type (str, optional): Authentication type
336
+ auth_token (str, optional): Authentication token
337
+ auth_username (str, optional): Username for basic auth
338
+ auth_password (str, optional): Password for basic auth
339
+ api_key_header (str, optional): Header name for API key
340
+ rate_limit_delay (float, optional): Rate limit delay
341
+ log_requests (bool, optional): Log request/response details
236
342
 
237
343
  Returns:
238
344
  Dictionary containing:
@@ -244,6 +350,8 @@ class HTTPRequestNode(Node):
244
350
  NodeExecutionError: If the request fails or returns an error status
245
351
  """
246
352
  url = kwargs.get("url")
353
+ if not url:
354
+ raise NodeValidationError("URL parameter is required")
247
355
  method = kwargs.get("method", "GET").upper()
248
356
  headers = kwargs.get("headers", {})
249
357
  params = kwargs.get("params", {})
@@ -254,6 +362,24 @@ class HTTPRequestNode(Node):
254
362
  verify_ssl = kwargs.get("verify_ssl", True)
255
363
  retry_count = kwargs.get("retry_count", 0)
256
364
  retry_backoff = kwargs.get("retry_backoff", 0.5)
365
+ auth_type = kwargs.get("auth_type")
366
+ auth_token = kwargs.get("auth_token")
367
+ auth_username = kwargs.get("auth_username")
368
+ auth_password = kwargs.get("auth_password")
369
+ api_key_header = kwargs.get("api_key_header", "X-API-Key")
370
+ rate_limit_delay = kwargs.get("rate_limit_delay", 0)
371
+ log_requests = kwargs.get("log_requests", False)
372
+
373
+ # Apply authentication to headers
374
+ if auth_type:
375
+ headers = self._apply_authentication(
376
+ headers,
377
+ auth_type,
378
+ auth_token,
379
+ auth_username,
380
+ auth_password,
381
+ api_key_header,
382
+ )
257
383
 
258
384
  # Validate method
259
385
  try:
@@ -273,6 +399,10 @@ class HTTPRequestNode(Node):
273
399
  f"Supported formats: {', '.join([f.value for f in ResponseFormat])}"
274
400
  )
275
401
 
402
+ # Apply rate limit delay if configured
403
+ if rate_limit_delay > 0:
404
+ time.sleep(rate_limit_delay)
405
+
276
406
  # Prepare request kwargs
277
407
  request_kwargs = {
278
408
  "url": url,
@@ -289,7 +419,13 @@ class HTTPRequestNode(Node):
289
419
  request_kwargs["data"] = data
290
420
 
291
421
  # Execute request with retries
292
- self.logger.info(f"Making {method} request to {url}")
422
+ if log_requests:
423
+ self.logger.info(f"Request: {method} {url}")
424
+ self.logger.info(f"Headers: {headers}")
425
+ if data or json_data:
426
+ self.logger.info(f"Body: {json_data or data}")
427
+ else:
428
+ self.logger.info(f"Making {method} request to {url}")
293
429
 
294
430
  response = None
295
431
  last_error = None
@@ -300,17 +436,19 @@ class HTTPRequestNode(Node):
300
436
  self.logger.info(
301
437
  f"Retry attempt {attempt}/{retry_count} after {wait_time:.2f}s"
302
438
  )
303
- import time
304
-
305
439
  time.sleep(wait_time)
306
440
 
307
441
  try:
308
- import time
309
-
310
442
  start_time = time.time()
311
443
  response = self.session.request(method=method.value, **request_kwargs)
312
444
  response_time = (time.time() - start_time) * 1000 # Convert to ms
313
445
 
446
+ # Log response if enabled
447
+ if log_requests:
448
+ self.logger.info(f"Response: {response.status_code}")
449
+ self.logger.info(f"Headers: {dict(response.headers)}")
450
+ self.logger.info(f"Body: {response.text[:500]}...")
451
+
314
452
  # Success, break the retry loop
315
453
  break
316
454
 
@@ -320,9 +458,21 @@ class HTTPRequestNode(Node):
320
458
 
321
459
  # Last attempt, no more retries
322
460
  if attempt == retry_count:
323
- raise NodeExecutionError(
324
- f"HTTP request failed after {retry_count + 1} attempts: {str(e)}"
325
- ) from e
461
+ # Enhanced error response with recovery suggestions
462
+ return {
463
+ "response": None,
464
+ "status_code": None,
465
+ "success": False,
466
+ "error": str(e),
467
+ "error_type": type(e).__name__,
468
+ "recovery_suggestions": [
469
+ "Check network connectivity",
470
+ "Verify URL is correct and accessible",
471
+ "Check authentication credentials",
472
+ "Increase timeout or retry settings",
473
+ "Check API rate limits",
474
+ ],
475
+ }
326
476
 
327
477
  # Parse response based on format
328
478
  content_type = response.headers.get("Content-Type", "")
@@ -363,16 +513,70 @@ class HTTPRequestNode(Node):
363
513
  # Return results
364
514
  success = 200 <= response.status_code < 300
365
515
 
366
- return {
516
+ # Add recovery suggestions for error responses
517
+ result = {
367
518
  "response": http_response,
368
519
  "status_code": response.status_code,
369
520
  "success": success,
370
521
  }
371
522
 
523
+ if not success:
524
+ result["recovery_suggestions"] = self._get_recovery_suggestions(
525
+ response.status_code
526
+ )
527
+
528
+ return result
529
+
530
+ def _get_recovery_suggestions(self, status_code: int) -> list:
531
+ """Get recovery suggestions based on status code.
372
532
 
373
- @register_node(alias="AsyncHTTPRequest")
533
+ Args:
534
+ status_code: HTTP status code
535
+
536
+ Returns:
537
+ List of recovery suggestions
538
+ """
539
+ if status_code == 401:
540
+ return [
541
+ "Check authentication credentials",
542
+ "Verify API key or token is valid",
543
+ "Ensure authentication method matches API requirements",
544
+ ]
545
+ elif status_code == 403:
546
+ return [
547
+ "Verify you have permission to access this resource",
548
+ "Check API key permissions/scopes",
549
+ "Ensure IP address is whitelisted if required",
550
+ ]
551
+ elif status_code == 404:
552
+ return [
553
+ "Verify the URL path is correct",
554
+ "Check if resource ID exists",
555
+ "Ensure API version in URL is correct",
556
+ ]
557
+ elif status_code == 429:
558
+ return [
559
+ "API rate limit exceeded - wait before retrying",
560
+ "Implement rate limiting in your requests",
561
+ "Check rate limit headers for reset time",
562
+ ]
563
+ elif status_code >= 500:
564
+ return [
565
+ "Server error - retry after a delay",
566
+ "Check API service status page",
567
+ "Contact API support if issue persists",
568
+ ]
569
+ else:
570
+ return [
571
+ "Check API documentation for this status code",
572
+ "Verify request format and parameters",
573
+ "Review response body for error details",
574
+ ]
575
+
576
+
577
+ @register_node()
374
578
  class AsyncHTTPRequestNode(AsyncNode):
375
- """Asynchronous node for making HTTP requests to external APIs.
579
+ """Asynchronous enhanced node for making HTTP requests to external APIs.
376
580
 
377
581
  This node provides the same functionality as HTTPRequestNode but uses
378
582
  asynchronous I/O for better performance, especially for concurrent requests.
@@ -418,6 +622,49 @@ class AsyncHTTPRequestNode(AsyncNode):
418
622
  # Same output schema as the synchronous version
419
623
  return HTTPRequestNode().get_output_schema()
420
624
 
625
+ def _apply_authentication(
626
+ self,
627
+ headers: dict,
628
+ auth_type: Optional[str],
629
+ auth_token: Optional[str],
630
+ auth_username: Optional[str],
631
+ auth_password: Optional[str],
632
+ api_key_header: str,
633
+ ) -> dict:
634
+ """Apply authentication to request headers.
635
+
636
+ Args:
637
+ headers: Existing headers dictionary
638
+ auth_type: Type of authentication (bearer, basic, api_key, oauth2)
639
+ auth_token: Token for bearer/api_key/oauth2 authentication
640
+ auth_username: Username for basic authentication
641
+ auth_password: Password for basic authentication
642
+ api_key_header: Header name for API key authentication
643
+
644
+ Returns:
645
+ Updated headers dictionary with authentication
646
+ """
647
+ if not auth_type:
648
+ return headers
649
+
650
+ auth_headers = headers.copy()
651
+
652
+ if auth_type.lower() == "bearer" and auth_token:
653
+ auth_headers["Authorization"] = f"Bearer {auth_token}"
654
+
655
+ elif auth_type.lower() == "basic" and auth_username and auth_password:
656
+ credentials = f"{auth_username}:{auth_password}"
657
+ encoded = base64.b64encode(credentials.encode()).decode()
658
+ auth_headers["Authorization"] = f"Basic {encoded}"
659
+
660
+ elif auth_type.lower() == "api_key" and auth_token:
661
+ auth_headers[api_key_header] = auth_token
662
+
663
+ elif auth_type.lower() == "oauth2" and auth_token:
664
+ auth_headers["Authorization"] = f"Bearer {auth_token}"
665
+
666
+ return auth_headers
667
+
421
668
  def run(self, **kwargs) -> Dict[str, Any]:
422
669
  """Synchronous version of the request, for compatibility.
423
670
 
@@ -450,6 +697,8 @@ class AsyncHTTPRequestNode(AsyncNode):
450
697
  NodeExecutionError: If the request fails or returns an error status
451
698
  """
452
699
  url = kwargs.get("url")
700
+ if not url:
701
+ raise NodeValidationError("URL parameter is required")
453
702
  method = kwargs.get("method", "GET").upper()
454
703
  headers = kwargs.get("headers", {})
455
704
  params = kwargs.get("params", {})
@@ -460,6 +709,24 @@ class AsyncHTTPRequestNode(AsyncNode):
460
709
  verify_ssl = kwargs.get("verify_ssl", True)
461
710
  retry_count = kwargs.get("retry_count", 0)
462
711
  retry_backoff = kwargs.get("retry_backoff", 0.5)
712
+ auth_type = kwargs.get("auth_type")
713
+ auth_token = kwargs.get("auth_token")
714
+ auth_username = kwargs.get("auth_username")
715
+ auth_password = kwargs.get("auth_password")
716
+ api_key_header = kwargs.get("api_key_header", "X-API-Key")
717
+ rate_limit_delay = kwargs.get("rate_limit_delay", 0)
718
+ log_requests = kwargs.get("log_requests", False)
719
+
720
+ # Apply authentication to headers
721
+ if auth_type:
722
+ headers = self._apply_authentication(
723
+ headers,
724
+ auth_type,
725
+ auth_token,
726
+ auth_username,
727
+ auth_password,
728
+ api_key_header,
729
+ )
463
730
 
464
731
  # Validate method
465
732
  try:
@@ -479,6 +746,10 @@ class AsyncHTTPRequestNode(AsyncNode):
479
746
  f"Supported formats: {', '.join([f.value for f in ResponseFormat])}"
480
747
  )
481
748
 
749
+ # Apply rate limit delay if configured
750
+ if rate_limit_delay > 0:
751
+ await asyncio.sleep(rate_limit_delay)
752
+
482
753
  # Create session if needed
483
754
  if self._session is None:
484
755
  self._session = aiohttp.ClientSession()
@@ -499,7 +770,13 @@ class AsyncHTTPRequestNode(AsyncNode):
499
770
  request_kwargs["data"] = data
500
771
 
501
772
  # Execute request with retries
502
- self.logger.info(f"Making async {method} request to {url}")
773
+ if log_requests:
774
+ self.logger.info(f"Request: {method} {url}")
775
+ self.logger.info(f"Headers: {headers}")
776
+ if data or json_data:
777
+ self.logger.info(f"Body: {json_data or data}")
778
+ else:
779
+ self.logger.info(f"Making async {method} request to {url}")
503
780
 
504
781
  response = None
505
782
  last_error = None
@@ -513,8 +790,6 @@ class AsyncHTTPRequestNode(AsyncNode):
513
790
  await asyncio.sleep(wait_time)
514
791
 
515
792
  try:
516
- import time
517
-
518
793
  start_time = time.time()
519
794
 
520
795
  async with self._session.request(
@@ -525,6 +800,13 @@ class AsyncHTTPRequestNode(AsyncNode):
525
800
  # Get content type
526
801
  content_type = response.headers.get("Content-Type", "")
527
802
 
803
+ # Log response if enabled
804
+ if log_requests:
805
+ self.logger.info(f"Response: {response.status}")
806
+ self.logger.info(f"Headers: {dict(response.headers)}")
807
+ text_preview = await response.text()
808
+ self.logger.info(f"Body: {text_preview[:500]}...")
809
+
528
810
  # Determine response format
529
811
  actual_format = response_format
530
812
  if actual_format == ResponseFormat.AUTO:
@@ -564,27 +846,92 @@ class AsyncHTTPRequestNode(AsyncNode):
564
846
  # Return results
565
847
  success = 200 <= response.status < 300
566
848
 
567
- return {
849
+ result = {
568
850
  "response": http_response,
569
851
  "status_code": response.status,
570
852
  "success": success,
571
853
  }
572
854
 
855
+ if not success:
856
+ result["recovery_suggestions"] = self._get_recovery_suggestions(
857
+ response.status
858
+ )
859
+
860
+ return result
861
+
573
862
  except (aiohttp.ClientError, asyncio.TimeoutError) as e:
574
863
  last_error = e
575
864
  self.logger.warning(f"Async request failed: {str(e)}")
576
865
 
577
866
  # Last attempt, no more retries
578
867
  if attempt == retry_count:
579
- raise NodeExecutionError(
580
- f"Async HTTP request failed after {retry_count + 1} attempts: {str(e)}"
581
- ) from e
868
+ # Enhanced error response with recovery suggestions
869
+ return {
870
+ "response": None,
871
+ "status_code": None,
872
+ "success": False,
873
+ "error": str(e),
874
+ "error_type": type(e).__name__,
875
+ "recovery_suggestions": [
876
+ "Check network connectivity",
877
+ "Verify URL is correct and accessible",
878
+ "Check authentication credentials",
879
+ "Increase timeout or retry settings",
880
+ "Check API rate limits",
881
+ ],
882
+ }
582
883
 
583
884
  # Should not reach here, but just in case
584
885
  raise NodeExecutionError(
585
886
  f"Async HTTP request failed after {retry_count + 1} attempts."
586
887
  )
587
888
 
889
+ def _get_recovery_suggestions(self, status_code: int) -> list:
890
+ """Get recovery suggestions based on status code.
891
+
892
+ Args:
893
+ status_code: HTTP status code
894
+
895
+ Returns:
896
+ List of recovery suggestions
897
+ """
898
+ if status_code == 401:
899
+ return [
900
+ "Check authentication credentials",
901
+ "Verify API key or token is valid",
902
+ "Ensure authentication method matches API requirements",
903
+ ]
904
+ elif status_code == 403:
905
+ return [
906
+ "Verify you have permission to access this resource",
907
+ "Check API key permissions/scopes",
908
+ "Ensure IP address is whitelisted if required",
909
+ ]
910
+ elif status_code == 404:
911
+ return [
912
+ "Verify the URL path is correct",
913
+ "Check if resource ID exists",
914
+ "Ensure API version in URL is correct",
915
+ ]
916
+ elif status_code == 429:
917
+ return [
918
+ "API rate limit exceeded - wait before retrying",
919
+ "Implement rate limiting in your requests",
920
+ "Check rate limit headers for reset time",
921
+ ]
922
+ elif status_code >= 500:
923
+ return [
924
+ "Server error - retry after a delay",
925
+ "Check API service status page",
926
+ "Contact API support if issue persists",
927
+ ]
928
+ else:
929
+ return [
930
+ "Check API documentation for this status code",
931
+ "Verify request format and parameters",
932
+ "Review response body for error details",
933
+ ]
934
+
588
935
  async def __aenter__(self):
589
936
  """Context manager support for 'async with' statements."""
590
937
  if self._session is None:
@@ -259,7 +259,7 @@ def create_rate_limiter(config: RateLimitConfig) -> RateLimiter:
259
259
  raise ValueError(f"Unsupported rate limiting strategy: {config.strategy}")
260
260
 
261
261
 
262
- @register_node(alias="RateLimitedAPI")
262
+ @register_node()
263
263
  class RateLimitedAPINode(Node):
264
264
  """Wrapper node that adds rate limiting to any API node.
265
265
 
@@ -427,7 +427,7 @@ class RateLimitedAPINode(Node):
427
427
  )
428
428
 
429
429
 
430
- @register_node(alias="AsyncRateLimitedAPI")
430
+ @register_node()
431
431
  class AsyncRateLimitedAPINode(AsyncNode):
432
432
  """Asynchronous wrapper node that adds rate limiting to any async API node.
433
433