kailash 0.6.2__py3-none-any.whl → 0.6.4__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 (131) hide show
  1. kailash/__init__.py +3 -3
  2. kailash/api/custom_nodes_secure.py +3 -3
  3. kailash/api/gateway.py +1 -1
  4. kailash/api/studio.py +2 -3
  5. kailash/api/workflow_api.py +3 -4
  6. kailash/core/resilience/bulkhead.py +460 -0
  7. kailash/core/resilience/circuit_breaker.py +92 -10
  8. kailash/edge/discovery.py +86 -0
  9. kailash/mcp_server/__init__.py +334 -0
  10. kailash/mcp_server/advanced_features.py +1022 -0
  11. kailash/{mcp → mcp_server}/ai_registry_server.py +29 -4
  12. kailash/mcp_server/auth.py +789 -0
  13. kailash/mcp_server/client.py +712 -0
  14. kailash/mcp_server/discovery.py +1593 -0
  15. kailash/mcp_server/errors.py +673 -0
  16. kailash/mcp_server/oauth.py +1727 -0
  17. kailash/mcp_server/protocol.py +1126 -0
  18. kailash/mcp_server/registry_integration.py +587 -0
  19. kailash/mcp_server/server.py +1747 -0
  20. kailash/{mcp → mcp_server}/servers/ai_registry.py +2 -2
  21. kailash/mcp_server/transports.py +1169 -0
  22. kailash/mcp_server/utils/cache.py +510 -0
  23. kailash/middleware/auth/auth_manager.py +3 -3
  24. kailash/middleware/communication/api_gateway.py +2 -9
  25. kailash/middleware/communication/realtime.py +1 -1
  26. kailash/middleware/mcp/client_integration.py +1 -1
  27. kailash/middleware/mcp/enhanced_server.py +2 -2
  28. kailash/nodes/__init__.py +2 -0
  29. kailash/nodes/admin/audit_log.py +6 -6
  30. kailash/nodes/admin/permission_check.py +8 -8
  31. kailash/nodes/admin/role_management.py +32 -28
  32. kailash/nodes/admin/schema.sql +6 -1
  33. kailash/nodes/admin/schema_manager.py +13 -13
  34. kailash/nodes/admin/security_event.py +16 -20
  35. kailash/nodes/admin/tenant_isolation.py +3 -3
  36. kailash/nodes/admin/transaction_utils.py +3 -3
  37. kailash/nodes/admin/user_management.py +21 -22
  38. kailash/nodes/ai/a2a.py +11 -11
  39. kailash/nodes/ai/ai_providers.py +9 -12
  40. kailash/nodes/ai/embedding_generator.py +13 -14
  41. kailash/nodes/ai/intelligent_agent_orchestrator.py +19 -19
  42. kailash/nodes/ai/iterative_llm_agent.py +3 -3
  43. kailash/nodes/ai/llm_agent.py +213 -36
  44. kailash/nodes/ai/self_organizing.py +2 -2
  45. kailash/nodes/alerts/discord.py +4 -4
  46. kailash/nodes/api/graphql.py +6 -6
  47. kailash/nodes/api/http.py +12 -17
  48. kailash/nodes/api/rate_limiting.py +4 -4
  49. kailash/nodes/api/rest.py +15 -15
  50. kailash/nodes/auth/mfa.py +3 -4
  51. kailash/nodes/auth/risk_assessment.py +2 -2
  52. kailash/nodes/auth/session_management.py +5 -5
  53. kailash/nodes/auth/sso.py +143 -0
  54. kailash/nodes/base.py +6 -2
  55. kailash/nodes/base_async.py +16 -2
  56. kailash/nodes/base_with_acl.py +2 -2
  57. kailash/nodes/cache/__init__.py +9 -0
  58. kailash/nodes/cache/cache.py +1172 -0
  59. kailash/nodes/cache/cache_invalidation.py +870 -0
  60. kailash/nodes/cache/redis_pool_manager.py +595 -0
  61. kailash/nodes/code/async_python.py +2 -1
  62. kailash/nodes/code/python.py +196 -35
  63. kailash/nodes/compliance/data_retention.py +6 -6
  64. kailash/nodes/compliance/gdpr.py +5 -5
  65. kailash/nodes/data/__init__.py +10 -0
  66. kailash/nodes/data/optimistic_locking.py +906 -0
  67. kailash/nodes/data/readers.py +8 -8
  68. kailash/nodes/data/redis.py +349 -0
  69. kailash/nodes/data/sql.py +314 -3
  70. kailash/nodes/data/streaming.py +21 -0
  71. kailash/nodes/enterprise/__init__.py +8 -0
  72. kailash/nodes/enterprise/audit_logger.py +285 -0
  73. kailash/nodes/enterprise/batch_processor.py +22 -3
  74. kailash/nodes/enterprise/data_lineage.py +1 -1
  75. kailash/nodes/enterprise/mcp_executor.py +205 -0
  76. kailash/nodes/enterprise/service_discovery.py +150 -0
  77. kailash/nodes/enterprise/tenant_assignment.py +108 -0
  78. kailash/nodes/logic/async_operations.py +2 -2
  79. kailash/nodes/logic/convergence.py +1 -1
  80. kailash/nodes/logic/operations.py +1 -1
  81. kailash/nodes/monitoring/__init__.py +11 -1
  82. kailash/nodes/monitoring/health_check.py +456 -0
  83. kailash/nodes/monitoring/log_processor.py +817 -0
  84. kailash/nodes/monitoring/metrics_collector.py +627 -0
  85. kailash/nodes/monitoring/performance_benchmark.py +137 -11
  86. kailash/nodes/rag/advanced.py +7 -7
  87. kailash/nodes/rag/agentic.py +49 -2
  88. kailash/nodes/rag/conversational.py +3 -3
  89. kailash/nodes/rag/evaluation.py +3 -3
  90. kailash/nodes/rag/federated.py +3 -3
  91. kailash/nodes/rag/graph.py +3 -3
  92. kailash/nodes/rag/multimodal.py +3 -3
  93. kailash/nodes/rag/optimized.py +5 -5
  94. kailash/nodes/rag/privacy.py +3 -3
  95. kailash/nodes/rag/query_processing.py +6 -6
  96. kailash/nodes/rag/realtime.py +1 -1
  97. kailash/nodes/rag/registry.py +2 -6
  98. kailash/nodes/rag/router.py +1 -1
  99. kailash/nodes/rag/similarity.py +7 -7
  100. kailash/nodes/rag/strategies.py +4 -4
  101. kailash/nodes/security/abac_evaluator.py +6 -6
  102. kailash/nodes/security/behavior_analysis.py +5 -6
  103. kailash/nodes/security/credential_manager.py +1 -1
  104. kailash/nodes/security/rotating_credentials.py +11 -11
  105. kailash/nodes/security/threat_detection.py +8 -8
  106. kailash/nodes/testing/credential_testing.py +2 -2
  107. kailash/nodes/transform/processors.py +5 -5
  108. kailash/runtime/local.py +162 -14
  109. kailash/runtime/parameter_injection.py +425 -0
  110. kailash/runtime/parameter_injector.py +657 -0
  111. kailash/runtime/testing.py +2 -2
  112. kailash/testing/fixtures.py +2 -2
  113. kailash/workflow/builder.py +99 -18
  114. kailash/workflow/builder_improvements.py +207 -0
  115. kailash/workflow/input_handling.py +170 -0
  116. {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/METADATA +21 -8
  117. {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/RECORD +126 -101
  118. kailash/mcp/__init__.py +0 -53
  119. kailash/mcp/client.py +0 -445
  120. kailash/mcp/server.py +0 -292
  121. kailash/mcp/server_enhanced.py +0 -449
  122. kailash/mcp/utils/cache.py +0 -267
  123. /kailash/{mcp → mcp_server}/client_new.py +0 -0
  124. /kailash/{mcp → mcp_server}/utils/__init__.py +0 -0
  125. /kailash/{mcp → mcp_server}/utils/config.py +0 -0
  126. /kailash/{mcp → mcp_server}/utils/formatters.py +0 -0
  127. /kailash/{mcp → mcp_server}/utils/metrics.py +0 -0
  128. {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/WHEEL +0 -0
  129. {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/entry_points.txt +0 -0
  130. {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/licenses/LICENSE +0 -0
  131. {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/top_level.txt +0 -0
@@ -106,7 +106,7 @@ class LLMAgentNode(Node):
106
106
  Examples:
107
107
  >>> # Basic Q&A agent with OpenAI
108
108
  >>> agent = LLMAgentNode()
109
- >>> result = agent.run(
109
+ >>> result = agent.execute(
110
110
  ... provider="openai",
111
111
  ... model="gpt-4",
112
112
  ... messages=[
@@ -118,7 +118,7 @@ class LLMAgentNode(Node):
118
118
 
119
119
  >>> # Tool-calling agent
120
120
  >>> tool_agent = LLMAgentNode()
121
- >>> result = tool_agent.run(
121
+ >>> result = tool_agent.execute(
122
122
  ... provider="anthropic",
123
123
  ... model="claude-3-sonnet",
124
124
  ... messages=[{"role": "user", "content": "Create a report and email it"}],
@@ -139,7 +139,7 @@ class LLMAgentNode(Node):
139
139
 
140
140
  >>> # RAG agent with MCP integration
141
141
  >>> rag_agent = LLMAgentNode()
142
- >>> result = rag_agent.run(
142
+ >>> result = rag_agent.execute(
143
143
  ... provider="azure",
144
144
  ... model="gpt-4-turbo",
145
145
  ... messages=[{"role": "user", "content": "What are the compliance requirements?"}],
@@ -349,6 +349,20 @@ class LLMAgentNode(Node):
349
349
  required=False,
350
350
  description="Override default model pricing (per 1K tokens)",
351
351
  ),
352
+ "auto_execute_tools": NodeParameter(
353
+ name="auto_execute_tools",
354
+ type=bool,
355
+ required=False,
356
+ default=True,
357
+ description="Automatically execute tool calls from LLM",
358
+ ),
359
+ "tool_execution_config": NodeParameter(
360
+ name="tool_execution_config",
361
+ type=dict,
362
+ required=False,
363
+ default={},
364
+ description="Configuration for tool execution behavior",
365
+ ),
352
366
  }
353
367
 
354
368
  def run(self, **kwargs) -> dict[str, Any]:
@@ -393,7 +407,7 @@ class LLMAgentNode(Node):
393
407
  Basic usage with OpenAI:
394
408
 
395
409
  >>> agent = LLMAgentNode()
396
- >>> result = agent.run(
410
+ >>> result = agent.execute(
397
411
  ... provider="openai",
398
412
  ... model="gpt-4",
399
413
  ... messages=[
@@ -411,7 +425,7 @@ class LLMAgentNode(Node):
411
425
 
412
426
  Using Ollama with custom model:
413
427
 
414
- >>> result = agent.run(
428
+ >>> result = agent.execute(
415
429
  ... provider="ollama",
416
430
  ... model="llama3.1:8b-instruct-q8_0",
417
431
  ... messages=[
@@ -427,7 +441,7 @@ class LLMAgentNode(Node):
427
441
 
428
442
  With system prompt and conversation memory:
429
443
 
430
- >>> result = agent.run(
444
+ >>> result = agent.execute(
431
445
  ... provider="anthropic",
432
446
  ... model="claude-3-sonnet-20240229",
433
447
  ... system_prompt="You are a helpful coding assistant.",
@@ -444,7 +458,7 @@ class LLMAgentNode(Node):
444
458
 
445
459
  With tool calling:
446
460
 
447
- >>> result = agent.run(
461
+ >>> result = agent.execute(
448
462
  ... provider="openai",
449
463
  ... model="gpt-4-turbo",
450
464
  ... messages=[
@@ -475,7 +489,7 @@ class LLMAgentNode(Node):
475
489
 
476
490
  With RAG (Retrieval Augmented Generation):
477
491
 
478
- >>> result = agent.run(
492
+ >>> result = agent.execute(
479
493
  ... provider="openai",
480
494
  ... model="gpt-4",
481
495
  ... messages=[
@@ -498,7 +512,7 @@ class LLMAgentNode(Node):
498
512
 
499
513
  With MCP (Model Context Protocol) integration:
500
514
 
501
- >>> result = agent.run(
515
+ >>> result = agent.execute(
502
516
  ... provider="anthropic",
503
517
  ... model="claude-3-opus-20240229",
504
518
  ... messages=[
@@ -522,7 +536,7 @@ class LLMAgentNode(Node):
522
536
 
523
537
  Advanced configuration with all features:
524
538
 
525
- >>> result = agent.run(
539
+ >>> result = agent.execute(
526
540
  ... provider="openai",
527
541
  ... model="gpt-4-turbo",
528
542
  ... messages=[
@@ -559,7 +573,7 @@ class LLMAgentNode(Node):
559
573
 
560
574
  Error handling:
561
575
 
562
- >>> result = agent.run(
576
+ >>> result = agent.execute(
563
577
  ... provider="openai",
564
578
  ... model="gpt-4",
565
579
  ... messages=[{"role": "user", "content": "Hello"}]
@@ -589,6 +603,8 @@ class LLMAgentNode(Node):
589
603
  streaming = kwargs.get("streaming", False)
590
604
  timeout = kwargs.get("timeout", 120)
591
605
  max_retries = kwargs.get("max_retries", 3)
606
+ auto_execute_tools = kwargs.get("auto_execute_tools", True)
607
+ tool_execution_config = kwargs.get("tool_execution_config", {})
592
608
 
593
609
  # Check monitoring parameters
594
610
  enable_monitoring = kwargs.get("enable_monitoring", self.enable_monitoring)
@@ -616,10 +632,11 @@ class LLMAgentNode(Node):
616
632
  mcp_context_data = self._retrieve_mcp_context(mcp_servers, mcp_context)
617
633
 
618
634
  # Discover MCP tools if enabled
635
+ discovered_mcp_tools = []
619
636
  if auto_discover_tools and mcp_servers:
620
- mcp_tools = self._discover_mcp_tools(mcp_servers)
637
+ discovered_mcp_tools = self._discover_mcp_tools(mcp_servers)
621
638
  # Merge MCP tools with existing tools
622
- tools = self._merge_tools(tools, mcp_tools)
639
+ tools = self._merge_tools(tools, discovered_mcp_tools)
623
640
 
624
641
  # Perform RAG retrieval if configured
625
642
  rag_context = self._perform_rag_retrieval(
@@ -657,6 +674,54 @@ class LLMAgentNode(Node):
657
674
  provider, model, enriched_messages, tools, generation_config
658
675
  )
659
676
 
677
+ # Handle tool execution if enabled and tools were called
678
+ if auto_execute_tools and response.get("tool_calls"):
679
+ tool_execution_rounds = 0
680
+ max_rounds = tool_execution_config.get("max_rounds", 5)
681
+
682
+ # Keep executing tools until no more tool calls or max rounds reached
683
+ while response.get("tool_calls") and tool_execution_rounds < max_rounds:
684
+ tool_execution_rounds += 1
685
+
686
+ # Execute all tool calls
687
+ tool_results = self._execute_tool_calls(
688
+ response["tool_calls"],
689
+ tools,
690
+ mcp_tools=discovered_mcp_tools, # Track which tools are MCP
691
+ )
692
+
693
+ # Add assistant message with tool calls
694
+ enriched_messages.append(
695
+ {
696
+ "role": "assistant",
697
+ "content": response.get("content"),
698
+ "tool_calls": response["tool_calls"],
699
+ }
700
+ )
701
+
702
+ # Add tool results as tool messages
703
+ for tool_result in tool_results:
704
+ enriched_messages.append(
705
+ {
706
+ "role": "tool",
707
+ "tool_call_id": tool_result["tool_call_id"],
708
+ "content": tool_result["content"],
709
+ }
710
+ )
711
+
712
+ # Get next response from LLM with tool results
713
+ if provider == "mock":
714
+ response = self._mock_llm_response(
715
+ enriched_messages, tools, generation_config
716
+ )
717
+ else:
718
+ response = self._provider_llm_response(
719
+ provider, model, enriched_messages, tools, generation_config
720
+ )
721
+
722
+ # Update final response metadata
723
+ response["tool_execution_rounds"] = tool_execution_rounds
724
+
660
725
  # Update conversation memory
661
726
  if conversation_id:
662
727
  self._update_conversation_memory(
@@ -718,6 +783,7 @@ class LLMAgentNode(Node):
718
783
  "mcp_resources_used": len(mcp_context_data),
719
784
  "rag_documents_retrieved": len(rag_context.get("documents", [])),
720
785
  "tools_available": len(tools),
786
+ "tools_executed": response.get("tool_execution_rounds", 0),
721
787
  "memory_tokens": conversation_memory.get("token_count", 0),
722
788
  },
723
789
  "metadata": {
@@ -985,7 +1051,7 @@ class LLMAgentNode(Node):
985
1051
  import asyncio
986
1052
  from datetime import datetime
987
1053
 
988
- from kailash.mcp import MCPClient
1054
+ from kailash.mcp_server import MCPClient
989
1055
 
990
1056
  # Initialize MCP client if not already done
991
1057
  if not hasattr(self, "_mcp_client"):
@@ -1182,7 +1248,7 @@ class LLMAgentNode(Node):
1182
1248
 
1183
1249
  if use_real_mcp:
1184
1250
  try:
1185
- from kailash.mcp import MCPClient
1251
+ from kailash.mcp_server import MCPClient
1186
1252
 
1187
1253
  # Initialize MCP client if not already done
1188
1254
  if not hasattr(self, "_mcp_client"):
@@ -1515,6 +1581,13 @@ class LLMAgentNode(Node):
1515
1581
  """Generate mock LLM response for testing."""
1516
1582
  last_user_message = ""
1517
1583
  has_images = False
1584
+ has_tool_results = False
1585
+
1586
+ # Check if we have tool results in the conversation
1587
+ for msg in messages:
1588
+ if msg.get("role") == "tool":
1589
+ has_tool_results = True
1590
+ break
1518
1591
 
1519
1592
  for msg in reversed(messages):
1520
1593
  if msg.get("role") == "user":
@@ -1533,37 +1606,51 @@ class LLMAgentNode(Node):
1533
1606
  break
1534
1607
 
1535
1608
  # Generate contextual mock response
1536
- if has_images:
1609
+ if has_tool_results:
1610
+ # We've executed tools, provide final response
1611
+ response_content = "Based on the tool execution results, I've completed the requested task. The tools have been successfully executed and the operation is complete."
1612
+ tool_calls = [] # No more tool calls after execution
1613
+ elif has_images:
1537
1614
  response_content = "I can see the image(s) you've provided. Based on my analysis, [Mock vision response for testing]"
1615
+ tool_calls = []
1538
1616
  elif "analyze" in last_user_message.lower():
1539
1617
  response_content = "Based on the provided data and context, I can see several key patterns: 1) Customer engagement has increased by 15% this quarter, 2) Product A shows the highest conversion rate, and 3) There are opportunities for improvement in the onboarding process."
1618
+ tool_calls = []
1540
1619
  elif (
1541
1620
  "create" in last_user_message.lower()
1542
1621
  or "generate" in last_user_message.lower()
1543
1622
  ):
1544
1623
  response_content = "I'll help you create that. Based on the requirements and available tools, I recommend a structured approach with the following steps..."
1624
+ # Simulate tool calls if tools are available and no tools executed yet
1625
+ tool_calls = []
1626
+ if (
1627
+ tools
1628
+ and not has_tool_results
1629
+ and any(
1630
+ keyword in last_user_message.lower()
1631
+ for keyword in ["create", "send", "execute", "run"]
1632
+ )
1633
+ ):
1634
+ for tool in tools[:2]: # Limit to first 2 tools
1635
+ tool_name = tool.get("function", {}).get(
1636
+ "name", tool.get("name", "unknown")
1637
+ )
1638
+ tool_calls.append(
1639
+ {
1640
+ "id": f"call_{hash(tool_name) % 10000}",
1641
+ "type": "function",
1642
+ "function": {
1643
+ "name": tool_name,
1644
+ "arguments": json.dumps({"mock": "arguments"}),
1645
+ },
1646
+ }
1647
+ )
1545
1648
  elif "?" in last_user_message:
1546
1649
  response_content = f"Regarding your question about '{last_user_message[:50]}...', here's what I found from the available context and resources..."
1650
+ tool_calls = []
1547
1651
  else:
1548
1652
  response_content = f"I understand you want me to work with: '{last_user_message[:100]}...'. Based on the context provided, I can help you achieve this goal."
1549
-
1550
- # Simulate tool calls if tools are available
1551
- tool_calls = []
1552
- if tools and any(
1553
- keyword in last_user_message.lower()
1554
- for keyword in ["create", "send", "execute", "run"]
1555
- ):
1556
- for tool in tools[:2]: # Limit to first 2 tools
1557
- tool_calls.append(
1558
- {
1559
- "id": f"call_{hash(tool['name']) % 10000}",
1560
- "type": "function",
1561
- "function": {
1562
- "name": tool["name"],
1563
- "arguments": json.dumps({"mock": "arguments"}),
1564
- },
1565
- }
1566
- )
1653
+ tool_calls = []
1567
1654
 
1568
1655
  return {
1569
1656
  "id": f"msg_{hash(last_user_message) % 100000}",
@@ -1767,7 +1854,7 @@ class LLMAgentNode(Node):
1767
1854
  server_config = mcp_tool.get("function", {}).get("mcp_server_config", {})
1768
1855
 
1769
1856
  try:
1770
- from kailash.mcp import MCPClient
1857
+ from kailash.mcp_server import MCPClient
1771
1858
 
1772
1859
  # Initialize MCP client if not already done
1773
1860
  if not hasattr(self, "_mcp_client"):
@@ -1789,6 +1876,96 @@ class LLMAgentNode(Node):
1789
1876
  self.logger.error(f"MCP tool execution failed: {e}")
1790
1877
  return {"error": str(e), "success": False, "tool_name": tool_name}
1791
1878
 
1879
+ def _execute_tool_calls(
1880
+ self,
1881
+ tool_calls: list[dict[str, Any]],
1882
+ available_tools: list[dict[str, Any]],
1883
+ mcp_tools: list[dict[str, Any]] | None = None,
1884
+ ) -> list[dict[str, Any]]:
1885
+ """Execute all tool calls from LLM response.
1886
+
1887
+ Args:
1888
+ tool_calls: List of tool calls from LLM response
1889
+ available_tools: All available tools (MCP + regular)
1890
+ mcp_tools: Tools that came from MCP discovery
1891
+
1892
+ Returns:
1893
+ List of tool results formatted for LLM consumption
1894
+ """
1895
+ tool_results = []
1896
+ mcp_tools = mcp_tools or []
1897
+
1898
+ # Create lookup for MCP tools
1899
+ mcp_tool_names = {
1900
+ tool.get("function", {}).get("name"): tool for tool in mcp_tools
1901
+ }
1902
+
1903
+ for tool_call in tool_calls:
1904
+ try:
1905
+ tool_name = tool_call.get("function", {}).get("name")
1906
+ tool_id = tool_call.get("id")
1907
+
1908
+ # Check if this is an MCP tool
1909
+ if tool_name in mcp_tool_names:
1910
+ # Execute via MCP
1911
+ result = self._run_async_in_sync_context(
1912
+ self._execute_mcp_tool_call(tool_call, mcp_tools)
1913
+ )
1914
+ else:
1915
+ # Execute regular tool (future implementation)
1916
+ result = self._execute_regular_tool(tool_call, available_tools)
1917
+
1918
+ # Format successful result
1919
+ tool_results.append(
1920
+ {
1921
+ "tool_call_id": tool_id,
1922
+ "content": (
1923
+ json.dumps(result)
1924
+ if isinstance(result, dict)
1925
+ else str(result)
1926
+ ),
1927
+ }
1928
+ )
1929
+
1930
+ except Exception as e:
1931
+ # Format error result
1932
+ tool_name = tool_call.get("function", {}).get("name", "unknown")
1933
+ self.logger.error(f"Tool execution failed for {tool_name}: {e}")
1934
+ tool_results.append(
1935
+ {
1936
+ "tool_call_id": tool_call.get("id", "unknown"),
1937
+ "content": json.dumps(
1938
+ {"error": str(e), "tool": tool_name, "status": "failed"}
1939
+ ),
1940
+ }
1941
+ )
1942
+
1943
+ return tool_results
1944
+
1945
+ def _execute_regular_tool(
1946
+ self, tool_call: dict[str, Any], available_tools: list[dict[str, Any]]
1947
+ ) -> dict[str, Any]:
1948
+ """Execute a regular (non-MCP) tool.
1949
+
1950
+ Args:
1951
+ tool_call: Tool call from LLM
1952
+ available_tools: List of available tools
1953
+
1954
+ Returns:
1955
+ Tool execution result
1956
+ """
1957
+ tool_name = tool_call.get("function", {}).get("name")
1958
+ tool_args = json.loads(tool_call.get("function", {}).get("arguments", "{}"))
1959
+
1960
+ # For now, return a mock result
1961
+ # In future, this could execute actual Python functions
1962
+ return {
1963
+ "status": "success",
1964
+ "tool": tool_name,
1965
+ "result": f"Executed {tool_name} with args: {tool_args}",
1966
+ "note": "Regular tool execution not yet implemented",
1967
+ }
1968
+
1792
1969
  # Monitoring methods
1793
1970
  def _get_pricing(self, model: str) -> Dict[str, float]:
1794
1971
  """Get pricing for current model."""
@@ -2013,4 +2190,4 @@ class LLMAgentNode(Node):
2013
2190
 
2014
2191
  async def async_run(self, **kwargs) -> Dict[str, Any]:
2015
2192
  """Async execution method for enterprise integration."""
2016
- return self.run(**kwargs)
2193
+ return self.execute(**kwargs)
@@ -101,7 +101,7 @@ class AgentPoolManagerNode(Node):
101
101
  >>> assert "agent_id" in params
102
102
  >>>
103
103
  >>> # Test simple registration
104
- >>> result = pool_manager.run(
104
+ >>> result = pool_manager.execute(
105
105
  ... action="register",
106
106
  ... agent_id="test_agent",
107
107
  ... capabilities=["analysis"]
@@ -1481,7 +1481,7 @@ class SolutionEvaluatorNode(Node):
1481
1481
  Examples:
1482
1482
  >>> evaluator = SolutionEvaluatorNode()
1483
1483
  >>>
1484
- >>> result = evaluator.run(
1484
+ >>> result = evaluator.execute(
1485
1485
  ... solution={
1486
1486
  ... "approach": "Clustering analysis",
1487
1487
  ... "findings": ["3 distinct customer segments identified"],
@@ -100,7 +100,7 @@ class DiscordAlertNode(AlertNode):
100
100
  Examples:
101
101
  >>> # Simple text alert
102
102
  >>> node = DiscordAlertNode()
103
- >>> result = node.run(
103
+ >>> result = node.execute(
104
104
  ... webhook_url="${DISCORD_ALERTS_WEBHOOK}",
105
105
  ... title="Deployment Complete",
106
106
  ... message="Version 1.2.3 deployed successfully",
@@ -108,7 +108,7 @@ class DiscordAlertNode(AlertNode):
108
108
  ... )
109
109
  >>>
110
110
  >>> # Rich embed with context
111
- >>> result = node.run(
111
+ >>> result = node.execute(
112
112
  ... webhook_url=webhook_url,
113
113
  ... title="Error in Data Pipeline",
114
114
  ... message="Failed to process customer data",
@@ -123,7 +123,7 @@ class DiscordAlertNode(AlertNode):
123
123
  ... )
124
124
  >>>
125
125
  >>> # Mention users and post to thread
126
- >>> result = node.run(
126
+ >>> result = node.execute(
127
127
  ... webhook_url=webhook_url,
128
128
  ... title="Critical: Database Connection Lost",
129
129
  ... alert_type="critical",
@@ -438,7 +438,7 @@ class DiscordAlertNode(AlertNode):
438
438
 
439
439
  for attempt in range(max_retries):
440
440
  try:
441
- response = self.http_client.run(
441
+ response = self.http_client.execute(
442
442
  url=url,
443
443
  method="POST",
444
444
  json_data=payload,
@@ -309,7 +309,7 @@ class GraphQLClientNode(Node):
309
309
  "retry_backoff": retry_backoff,
310
310
  }
311
311
 
312
- http_result = self.http_node.run(**http_params)
312
+ http_result = self.http_node.execute(**http_params)
313
313
 
314
314
  # Process GraphQL-specific response
315
315
  response = http_result["response"]
@@ -387,26 +387,26 @@ class AsyncGraphQLClientNode(AsyncNode):
387
387
  async_run method for better performance.
388
388
 
389
389
  Args:
390
- Same as GraphQLClientNode.run()
390
+ Same as GraphQLClientNode.execute()
391
391
 
392
392
  Returns:
393
- Same as GraphQLClientNode.run()
393
+ Same as GraphQLClientNode.execute()
394
394
 
395
395
  Raises:
396
396
  NodeValidationError: If required parameters are missing or invalid
397
397
  NodeExecutionError: If the request fails
398
398
  """
399
399
  # Forward to the synchronous GraphQL node
400
- return self.graphql_node.run(**kwargs)
400
+ return self.graphql_node.execute(**kwargs)
401
401
 
402
402
  async def async_run(self, **kwargs) -> dict[str, Any]:
403
403
  """Execute a GraphQL query or mutation asynchronously.
404
404
 
405
405
  Args:
406
- Same as GraphQLClientNode.run()
406
+ Same as GraphQLClientNode.execute()
407
407
 
408
408
  Returns:
409
- Same as GraphQLClientNode.run()
409
+ Same as GraphQLClientNode.execute()
410
410
 
411
411
  Raises:
412
412
  NodeValidationError: If required parameters are missing or invalid
kailash/nodes/api/http.py CHANGED
@@ -12,16 +12,11 @@ from typing import Any
12
12
 
13
13
  import aiohttp
14
14
  import requests
15
- from pydantic import BaseModel
16
-
17
15
  from kailash.nodes.base import Node, NodeParameter, register_node
18
16
  from kailash.nodes.base_async import AsyncNode
19
17
  from kailash.sdk_exceptions import NodeExecutionError, NodeValidationError
20
- from kailash.utils.resource_manager import (
21
- AsyncResourcePool,
22
- ResourcePool,
23
- managed_resource,
24
- )
18
+ from kailash.utils.resource_manager import AsyncResourcePool, ResourcePool, managed_resource
19
+ from pydantic import BaseModel
25
20
 
26
21
 
27
22
  class HTTPMethod(str, Enum):
@@ -146,7 +141,7 @@ class HTTPRequestNode(Node):
146
141
  Examples:
147
142
  >>> # Simple GET request
148
143
  >>> node = HTTPRequestNode()
149
- >>> result = node.run(
144
+ >>> result = node.execute(
150
145
  ... url="https://api.example.com/users",
151
146
  ... method="GET",
152
147
  ... headers={"Accept": "application/json"}
@@ -155,7 +150,7 @@ class HTTPRequestNode(Node):
155
150
  >>> assert isinstance(result["content"], dict)
156
151
  >>>
157
152
  >>> # POST request with JSON body
158
- >>> result = node.run(
153
+ >>> result = node.execute(
159
154
  ... url="https://api.example.com/users",
160
155
  ... method="POST",
161
156
  ... json_data={"name": "John", "email": "john@example.com"},
@@ -165,7 +160,7 @@ class HTTPRequestNode(Node):
165
160
  >>> assert result["headers"]["content-type"].startswith("application/json")
166
161
  >>>
167
162
  >>> # Form data submission
168
- >>> result = node.run(
163
+ >>> result = node.execute(
169
164
  ... url="https://api.example.com/form",
170
165
  ... method="POST",
171
166
  ... data={"field1": "value1", "field2": "value2"},
@@ -173,7 +168,7 @@ class HTTPRequestNode(Node):
173
168
  ... )
174
169
  >>>
175
170
  >>> # File upload with multipart
176
- >>> result = node.run(
171
+ >>> result = node.execute(
177
172
  ... url="https://api.example.com/upload",
178
173
  ... method="POST",
179
174
  ... files={"file": ("data.csv", b"col1,col2\\n1,2", "text/csv")},
@@ -181,7 +176,7 @@ class HTTPRequestNode(Node):
181
176
  ... )
182
177
  >>>
183
178
  >>> # Error handling example
184
- >>> result = node.run(
179
+ >>> result = node.execute(
185
180
  ... url="https://api.example.com/protected",
186
181
  ... method="GET"
187
182
  ... )
@@ -775,26 +770,26 @@ class AsyncHTTPRequestNode(AsyncNode):
775
770
  async_run method for better performance.
776
771
 
777
772
  Args:
778
- Same as HTTPRequestNode.run()
773
+ Same as HTTPRequestNode.execute()
779
774
 
780
775
  Returns:
781
- Same as HTTPRequestNode.run()
776
+ Same as HTTPRequestNode.execute()
782
777
 
783
778
  Raises:
784
779
  NodeExecutionError: If the request fails or returns an error status
785
780
  """
786
781
  # For compatibility, create a requests.Session() and use it
787
782
  http_node = HTTPRequestNode(**self.config)
788
- return http_node.run(**kwargs)
783
+ return http_node.execute(**kwargs)
789
784
 
790
785
  async def async_run(self, **kwargs) -> dict[str, Any]:
791
786
  """Execute an HTTP request asynchronously.
792
787
 
793
788
  Args:
794
- Same as HTTPRequestNode.run()
789
+ Same as HTTPRequestNode.execute()
795
790
 
796
791
  Returns:
797
- Same as HTTPRequestNode.run()
792
+ Same as HTTPRequestNode.execute()
798
793
 
799
794
  Raises:
800
795
  NodeExecutionError: If the request fails or returns an error status
@@ -360,7 +360,7 @@ class RateLimitedAPINode(Node):
360
360
 
361
361
  # If rate limiting is disabled, just pass through
362
362
  if not respect_rate_limits:
363
- result = self.wrapped_node.run(**kwargs)
363
+ result = self.wrapped_node.execute(**kwargs)
364
364
  result["rate_limit_metadata"] = {"rate_limiting_active": False}
365
365
  return result
366
366
 
@@ -375,7 +375,7 @@ class RateLimitedAPINode(Node):
375
375
  if self.rate_limiter.consume():
376
376
  start_time = time.time()
377
377
  try:
378
- result = self.wrapped_node.run(**kwargs)
378
+ result = self.wrapped_node.execute(**kwargs)
379
379
  execution_time = time.time() - start_time
380
380
 
381
381
  # Add rate limiting metadata
@@ -482,9 +482,9 @@ class AsyncRateLimitedAPINode(AsyncNode):
482
482
  **kwargs: Parameters for the wrapped node
483
483
 
484
484
  Returns:
485
- Same as RateLimitedAPINode.run()
485
+ Same as RateLimitedAPINode.execute()
486
486
  """
487
- return self.sync_node.run(**kwargs)
487
+ return self.sync_node.execute(**kwargs)
488
488
 
489
489
  async def async_run(self, **kwargs) -> dict[str, Any]:
490
490
  """Execute the wrapped async node with rate limiting.