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.
- kailash/__init__.py +3 -3
- kailash/api/custom_nodes_secure.py +3 -3
- kailash/api/gateway.py +1 -1
- kailash/api/studio.py +2 -3
- kailash/api/workflow_api.py +3 -4
- kailash/core/resilience/bulkhead.py +460 -0
- kailash/core/resilience/circuit_breaker.py +92 -10
- kailash/edge/discovery.py +86 -0
- kailash/mcp_server/__init__.py +334 -0
- kailash/mcp_server/advanced_features.py +1022 -0
- kailash/{mcp → mcp_server}/ai_registry_server.py +29 -4
- kailash/mcp_server/auth.py +789 -0
- kailash/mcp_server/client.py +712 -0
- kailash/mcp_server/discovery.py +1593 -0
- kailash/mcp_server/errors.py +673 -0
- kailash/mcp_server/oauth.py +1727 -0
- kailash/mcp_server/protocol.py +1126 -0
- kailash/mcp_server/registry_integration.py +587 -0
- kailash/mcp_server/server.py +1747 -0
- kailash/{mcp → mcp_server}/servers/ai_registry.py +2 -2
- kailash/mcp_server/transports.py +1169 -0
- kailash/mcp_server/utils/cache.py +510 -0
- kailash/middleware/auth/auth_manager.py +3 -3
- kailash/middleware/communication/api_gateway.py +2 -9
- kailash/middleware/communication/realtime.py +1 -1
- kailash/middleware/mcp/client_integration.py +1 -1
- kailash/middleware/mcp/enhanced_server.py +2 -2
- kailash/nodes/__init__.py +2 -0
- kailash/nodes/admin/audit_log.py +6 -6
- kailash/nodes/admin/permission_check.py +8 -8
- kailash/nodes/admin/role_management.py +32 -28
- kailash/nodes/admin/schema.sql +6 -1
- kailash/nodes/admin/schema_manager.py +13 -13
- kailash/nodes/admin/security_event.py +16 -20
- kailash/nodes/admin/tenant_isolation.py +3 -3
- kailash/nodes/admin/transaction_utils.py +3 -3
- kailash/nodes/admin/user_management.py +21 -22
- kailash/nodes/ai/a2a.py +11 -11
- kailash/nodes/ai/ai_providers.py +9 -12
- kailash/nodes/ai/embedding_generator.py +13 -14
- kailash/nodes/ai/intelligent_agent_orchestrator.py +19 -19
- kailash/nodes/ai/iterative_llm_agent.py +3 -3
- kailash/nodes/ai/llm_agent.py +213 -36
- kailash/nodes/ai/self_organizing.py +2 -2
- kailash/nodes/alerts/discord.py +4 -4
- kailash/nodes/api/graphql.py +6 -6
- kailash/nodes/api/http.py +12 -17
- kailash/nodes/api/rate_limiting.py +4 -4
- kailash/nodes/api/rest.py +15 -15
- kailash/nodes/auth/mfa.py +3 -4
- kailash/nodes/auth/risk_assessment.py +2 -2
- kailash/nodes/auth/session_management.py +5 -5
- kailash/nodes/auth/sso.py +143 -0
- kailash/nodes/base.py +6 -2
- kailash/nodes/base_async.py +16 -2
- kailash/nodes/base_with_acl.py +2 -2
- kailash/nodes/cache/__init__.py +9 -0
- kailash/nodes/cache/cache.py +1172 -0
- kailash/nodes/cache/cache_invalidation.py +870 -0
- kailash/nodes/cache/redis_pool_manager.py +595 -0
- kailash/nodes/code/async_python.py +2 -1
- kailash/nodes/code/python.py +196 -35
- kailash/nodes/compliance/data_retention.py +6 -6
- kailash/nodes/compliance/gdpr.py +5 -5
- kailash/nodes/data/__init__.py +10 -0
- kailash/nodes/data/optimistic_locking.py +906 -0
- kailash/nodes/data/readers.py +8 -8
- kailash/nodes/data/redis.py +349 -0
- kailash/nodes/data/sql.py +314 -3
- kailash/nodes/data/streaming.py +21 -0
- kailash/nodes/enterprise/__init__.py +8 -0
- kailash/nodes/enterprise/audit_logger.py +285 -0
- kailash/nodes/enterprise/batch_processor.py +22 -3
- kailash/nodes/enterprise/data_lineage.py +1 -1
- kailash/nodes/enterprise/mcp_executor.py +205 -0
- kailash/nodes/enterprise/service_discovery.py +150 -0
- kailash/nodes/enterprise/tenant_assignment.py +108 -0
- kailash/nodes/logic/async_operations.py +2 -2
- kailash/nodes/logic/convergence.py +1 -1
- kailash/nodes/logic/operations.py +1 -1
- kailash/nodes/monitoring/__init__.py +11 -1
- kailash/nodes/monitoring/health_check.py +456 -0
- kailash/nodes/monitoring/log_processor.py +817 -0
- kailash/nodes/monitoring/metrics_collector.py +627 -0
- kailash/nodes/monitoring/performance_benchmark.py +137 -11
- kailash/nodes/rag/advanced.py +7 -7
- kailash/nodes/rag/agentic.py +49 -2
- kailash/nodes/rag/conversational.py +3 -3
- kailash/nodes/rag/evaluation.py +3 -3
- kailash/nodes/rag/federated.py +3 -3
- kailash/nodes/rag/graph.py +3 -3
- kailash/nodes/rag/multimodal.py +3 -3
- kailash/nodes/rag/optimized.py +5 -5
- kailash/nodes/rag/privacy.py +3 -3
- kailash/nodes/rag/query_processing.py +6 -6
- kailash/nodes/rag/realtime.py +1 -1
- kailash/nodes/rag/registry.py +2 -6
- kailash/nodes/rag/router.py +1 -1
- kailash/nodes/rag/similarity.py +7 -7
- kailash/nodes/rag/strategies.py +4 -4
- kailash/nodes/security/abac_evaluator.py +6 -6
- kailash/nodes/security/behavior_analysis.py +5 -6
- kailash/nodes/security/credential_manager.py +1 -1
- kailash/nodes/security/rotating_credentials.py +11 -11
- kailash/nodes/security/threat_detection.py +8 -8
- kailash/nodes/testing/credential_testing.py +2 -2
- kailash/nodes/transform/processors.py +5 -5
- kailash/runtime/local.py +162 -14
- kailash/runtime/parameter_injection.py +425 -0
- kailash/runtime/parameter_injector.py +657 -0
- kailash/runtime/testing.py +2 -2
- kailash/testing/fixtures.py +2 -2
- kailash/workflow/builder.py +99 -18
- kailash/workflow/builder_improvements.py +207 -0
- kailash/workflow/input_handling.py +170 -0
- {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/METADATA +21 -8
- {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/RECORD +126 -101
- kailash/mcp/__init__.py +0 -53
- kailash/mcp/client.py +0 -445
- kailash/mcp/server.py +0 -292
- kailash/mcp/server_enhanced.py +0 -449
- kailash/mcp/utils/cache.py +0 -267
- /kailash/{mcp → mcp_server}/client_new.py +0 -0
- /kailash/{mcp → mcp_server}/utils/__init__.py +0 -0
- /kailash/{mcp → mcp_server}/utils/config.py +0 -0
- /kailash/{mcp → mcp_server}/utils/formatters.py +0 -0
- /kailash/{mcp → mcp_server}/utils/metrics.py +0 -0
- {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/WHEEL +0 -0
- {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/entry_points.txt +0 -0
- {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/top_level.txt +0 -0
kailash/nodes/ai/llm_agent.py
CHANGED
@@ -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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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
|
-
|
637
|
+
discovered_mcp_tools = self._discover_mcp_tools(mcp_servers)
|
621
638
|
# Merge MCP tools with existing tools
|
622
|
-
tools = self._merge_tools(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.
|
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.
|
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
|
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.
|
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.
|
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.
|
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.
|
1484
|
+
>>> result = evaluator.execute(
|
1485
1485
|
... solution={
|
1486
1486
|
... "approach": "Clustering analysis",
|
1487
1487
|
... "findings": ["3 distinct customer segments identified"],
|
kailash/nodes/alerts/discord.py
CHANGED
@@ -100,7 +100,7 @@ class DiscordAlertNode(AlertNode):
|
|
100
100
|
Examples:
|
101
101
|
>>> # Simple text alert
|
102
102
|
>>> node = DiscordAlertNode()
|
103
|
-
>>> result = node.
|
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.
|
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.
|
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.
|
441
|
+
response = self.http_client.execute(
|
442
442
|
url=url,
|
443
443
|
method="POST",
|
444
444
|
json_data=payload,
|
kailash/nodes/api/graphql.py
CHANGED
@@ -309,7 +309,7 @@ class GraphQLClientNode(Node):
|
|
309
309
|
"retry_backoff": retry_backoff,
|
310
310
|
}
|
311
311
|
|
312
|
-
http_result = self.http_node.
|
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.
|
390
|
+
Same as GraphQLClientNode.execute()
|
391
391
|
|
392
392
|
Returns:
|
393
|
-
Same as GraphQLClientNode.
|
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.
|
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.
|
406
|
+
Same as GraphQLClientNode.execute()
|
407
407
|
|
408
408
|
Returns:
|
409
|
-
Same as GraphQLClientNode.
|
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
|
-
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
773
|
+
Same as HTTPRequestNode.execute()
|
779
774
|
|
780
775
|
Returns:
|
781
|
-
Same as HTTPRequestNode.
|
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.
|
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.
|
789
|
+
Same as HTTPRequestNode.execute()
|
795
790
|
|
796
791
|
Returns:
|
797
|
-
Same as HTTPRequestNode.
|
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.
|
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.
|
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.
|
485
|
+
Same as RateLimitedAPINode.execute()
|
486
486
|
"""
|
487
|
-
return self.sync_node.
|
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.
|