kailash 0.1.3__py3-none-any.whl → 0.1.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 +1 -1
- kailash/api/__init__.py +11 -1
- kailash/api/gateway.py +394 -0
- kailash/api/mcp_integration.py +478 -0
- kailash/api/workflow_api.py +29 -13
- kailash/nodes/ai/__init__.py +4 -4
- kailash/nodes/ai/agents.py +4 -4
- kailash/nodes/ai/ai_providers.py +18 -22
- kailash/nodes/ai/embedding_generator.py +34 -38
- kailash/nodes/ai/llm_agent.py +351 -356
- kailash/nodes/base.py +60 -64
- kailash/nodes/code/python.py +61 -42
- kailash/nodes/data/__init__.py +10 -10
- kailash/nodes/data/readers.py +27 -29
- kailash/nodes/data/retrieval.py +1 -1
- kailash/nodes/data/sharepoint_graph.py +23 -25
- kailash/nodes/data/sql.py +24 -26
- kailash/nodes/data/writers.py +41 -44
- kailash/nodes/logic/__init__.py +9 -3
- kailash/nodes/logic/async_operations.py +14 -14
- kailash/nodes/logic/operations.py +18 -22
- kailash/nodes/mcp/client.py +29 -33
- kailash/nodes/transform/formatters.py +1 -1
- kailash/tracking/metrics_collector.py +6 -7
- kailash/utils/export.py +2 -2
- kailash/utils/templates.py +16 -16
- {kailash-0.1.3.dist-info → kailash-0.1.4.dist-info}/METADATA +103 -28
- {kailash-0.1.3.dist-info → kailash-0.1.4.dist-info}/RECORD +32 -30
- {kailash-0.1.3.dist-info → kailash-0.1.4.dist-info}/WHEEL +0 -0
- {kailash-0.1.3.dist-info → kailash-0.1.4.dist-info}/entry_points.txt +0 -0
- {kailash-0.1.3.dist-info → kailash-0.1.4.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.1.3.dist-info → kailash-0.1.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,478 @@
|
|
1
|
+
"""MCP (Model Context Protocol) integration for Kailash workflows.
|
2
|
+
|
3
|
+
This module provides integration between MCP servers and Kailash workflows,
|
4
|
+
allowing workflows to access MCP tools and resources.
|
5
|
+
|
6
|
+
Design Philosophy:
|
7
|
+
MCP servers provide tools and context that can be used by workflows.
|
8
|
+
This integration allows workflows to discover and use MCP tools dynamically,
|
9
|
+
bridging the gap between workflow automation and AI-powered tools.
|
10
|
+
|
11
|
+
Example:
|
12
|
+
Basic MCP integration:
|
13
|
+
|
14
|
+
>>> from kailash.api.mcp_integration import MCPIntegration
|
15
|
+
>>> # from kailash.workflow import Workflow # doctest: +SKIP
|
16
|
+
>>> # Create MCP integration
|
17
|
+
>>> mcp = MCPIntegration("tools_server")
|
18
|
+
>>> # Add tools
|
19
|
+
>>> # mcp.add_tool("search", search_function) # doctest: +SKIP
|
20
|
+
>>> # mcp.add_tool("calculate", calculator_function) # doctest: +SKIP
|
21
|
+
>>> # Use in workflow
|
22
|
+
>>> # workflow = Workflow("mcp_workflow") # doctest: +SKIP
|
23
|
+
>>> # workflow.register_mcp_server(mcp) # doctest: +SKIP
|
24
|
+
>>> # Nodes can now access MCP tools
|
25
|
+
>>> # node = MCPToolNode(tool_name="search") # doctest: +SKIP
|
26
|
+
>>> # workflow.add_node("search_data", node) # doctest: +SKIP
|
27
|
+
"""
|
28
|
+
|
29
|
+
import asyncio
|
30
|
+
import logging
|
31
|
+
from typing import Any, Callable, Dict, List, Optional, Union
|
32
|
+
|
33
|
+
from pydantic import BaseModel, Field
|
34
|
+
|
35
|
+
from ..nodes.base_async import AsyncNode
|
36
|
+
|
37
|
+
logger = logging.getLogger(__name__)
|
38
|
+
|
39
|
+
|
40
|
+
class MCPTool(BaseModel):
|
41
|
+
"""Definition of an MCP tool."""
|
42
|
+
|
43
|
+
name: str
|
44
|
+
description: str
|
45
|
+
parameters: Dict[str, Any] = Field(default_factory=dict)
|
46
|
+
function: Optional[Callable] = None
|
47
|
+
async_function: Optional[Callable] = None
|
48
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
49
|
+
|
50
|
+
|
51
|
+
class MCPResource(BaseModel):
|
52
|
+
"""Definition of an MCP resource."""
|
53
|
+
|
54
|
+
name: str
|
55
|
+
uri: str
|
56
|
+
description: str
|
57
|
+
mime_type: str = "text/plain"
|
58
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
59
|
+
|
60
|
+
|
61
|
+
class MCPIntegration:
|
62
|
+
"""MCP server integration for Kailash workflows.
|
63
|
+
|
64
|
+
Provides:
|
65
|
+
- Tool registration and discovery
|
66
|
+
- Resource management
|
67
|
+
- Async/sync tool execution
|
68
|
+
- Context sharing between workflows and MCP
|
69
|
+
|
70
|
+
Attributes:
|
71
|
+
name: MCP server name
|
72
|
+
tools: Registry of available tools
|
73
|
+
resources: Registry of available resources
|
74
|
+
"""
|
75
|
+
|
76
|
+
def __init__(
|
77
|
+
self, name: str, description: str = "", capabilities: List[str] = None
|
78
|
+
):
|
79
|
+
"""Initialize MCP integration.
|
80
|
+
|
81
|
+
Args:
|
82
|
+
name: MCP server name
|
83
|
+
description: Server description
|
84
|
+
capabilities: List of server capabilities
|
85
|
+
"""
|
86
|
+
self.name = name
|
87
|
+
self.description = description
|
88
|
+
self.capabilities = capabilities or ["tools", "resources"]
|
89
|
+
self.tools: Dict[str, MCPTool] = {}
|
90
|
+
self.resources: Dict[str, MCPResource] = {}
|
91
|
+
self._context: Dict[str, Any] = {}
|
92
|
+
|
93
|
+
def add_tool(
|
94
|
+
self,
|
95
|
+
name: str,
|
96
|
+
function: Union[Callable, Callable[..., asyncio.Future]],
|
97
|
+
description: str = "",
|
98
|
+
parameters: Dict[str, Any] = None,
|
99
|
+
):
|
100
|
+
"""Add a tool to the MCP server.
|
101
|
+
|
102
|
+
Args:
|
103
|
+
name: Tool name
|
104
|
+
function: Tool implementation (sync or async)
|
105
|
+
description: Tool description
|
106
|
+
parameters: Tool parameter schema
|
107
|
+
"""
|
108
|
+
tool = MCPTool(
|
109
|
+
name=name,
|
110
|
+
description=description,
|
111
|
+
parameters=parameters or {},
|
112
|
+
function=function if not asyncio.iscoroutinefunction(function) else None,
|
113
|
+
async_function=function if asyncio.iscoroutinefunction(function) else None,
|
114
|
+
)
|
115
|
+
|
116
|
+
self.tools[name] = tool
|
117
|
+
logger.info(f"Added tool '{name}' to MCP server '{self.name}'")
|
118
|
+
|
119
|
+
def add_resource(
|
120
|
+
self, name: str, uri: str, description: str = "", mime_type: str = "text/plain"
|
121
|
+
):
|
122
|
+
"""Add a resource to the MCP server.
|
123
|
+
|
124
|
+
Args:
|
125
|
+
name: Resource name
|
126
|
+
uri: Resource URI
|
127
|
+
description: Resource description
|
128
|
+
mime_type: MIME type of the resource
|
129
|
+
"""
|
130
|
+
resource = MCPResource(
|
131
|
+
name=name, uri=uri, description=description, mime_type=mime_type
|
132
|
+
)
|
133
|
+
|
134
|
+
self.resources[name] = resource
|
135
|
+
logger.info(f"Added resource '{name}' to MCP server '{self.name}'")
|
136
|
+
|
137
|
+
async def execute_tool(self, tool_name: str, parameters: Dict[str, Any]) -> Any:
|
138
|
+
"""Execute a tool asynchronously.
|
139
|
+
|
140
|
+
Args:
|
141
|
+
tool_name: Name of the tool to execute
|
142
|
+
parameters: Tool parameters
|
143
|
+
|
144
|
+
Returns:
|
145
|
+
Tool execution result
|
146
|
+
"""
|
147
|
+
tool = self.tools.get(tool_name)
|
148
|
+
if not tool:
|
149
|
+
raise ValueError(f"Tool '{tool_name}' not found")
|
150
|
+
|
151
|
+
# Add context to parameters only if function accepts it
|
152
|
+
import inspect
|
153
|
+
|
154
|
+
if tool.async_function:
|
155
|
+
sig = inspect.signature(tool.async_function)
|
156
|
+
else:
|
157
|
+
sig = inspect.signature(tool.function)
|
158
|
+
|
159
|
+
if "_context" in sig.parameters:
|
160
|
+
params = {**parameters, "_context": self._context}
|
161
|
+
else:
|
162
|
+
params = parameters
|
163
|
+
|
164
|
+
if tool.async_function:
|
165
|
+
return await tool.async_function(**params)
|
166
|
+
elif tool.function:
|
167
|
+
# Run sync function in executor
|
168
|
+
loop = asyncio.get_event_loop()
|
169
|
+
from functools import partial
|
170
|
+
|
171
|
+
func = partial(tool.function, **params)
|
172
|
+
return await loop.run_in_executor(None, func)
|
173
|
+
else:
|
174
|
+
raise ValueError(f"Tool '{tool_name}' has no implementation")
|
175
|
+
|
176
|
+
def execute_tool_sync(self, tool_name: str, parameters: Dict[str, Any]) -> Any:
|
177
|
+
"""Execute a tool synchronously.
|
178
|
+
|
179
|
+
Args:
|
180
|
+
tool_name: Name of the tool to execute
|
181
|
+
parameters: Tool parameters
|
182
|
+
|
183
|
+
Returns:
|
184
|
+
Tool execution result
|
185
|
+
"""
|
186
|
+
tool = self.tools.get(tool_name)
|
187
|
+
if not tool:
|
188
|
+
raise ValueError(f"Tool '{tool_name}' not found")
|
189
|
+
|
190
|
+
if tool.function:
|
191
|
+
# Check if function accepts _context parameter
|
192
|
+
import inspect
|
193
|
+
|
194
|
+
sig = inspect.signature(tool.function)
|
195
|
+
if "_context" in sig.parameters:
|
196
|
+
params = {**parameters, "_context": self._context}
|
197
|
+
else:
|
198
|
+
params = parameters
|
199
|
+
return tool.function(**params)
|
200
|
+
else:
|
201
|
+
raise ValueError(f"Tool '{tool_name}' requires async execution")
|
202
|
+
|
203
|
+
def get_resource(self, resource_name: str) -> Optional[MCPResource]:
|
204
|
+
"""Get a resource by name.
|
205
|
+
|
206
|
+
Args:
|
207
|
+
resource_name: Resource name
|
208
|
+
|
209
|
+
Returns:
|
210
|
+
Resource if found
|
211
|
+
"""
|
212
|
+
return self.resources.get(resource_name)
|
213
|
+
|
214
|
+
def set_context(self, key: str, value: Any):
|
215
|
+
"""Set context value.
|
216
|
+
|
217
|
+
Args:
|
218
|
+
key: Context key
|
219
|
+
value: Context value
|
220
|
+
"""
|
221
|
+
self._context[key] = value
|
222
|
+
|
223
|
+
def get_context(self, key: str) -> Any:
|
224
|
+
"""Get context value.
|
225
|
+
|
226
|
+
Args:
|
227
|
+
key: Context key
|
228
|
+
|
229
|
+
Returns:
|
230
|
+
Context value
|
231
|
+
"""
|
232
|
+
return self._context.get(key)
|
233
|
+
|
234
|
+
def list_tools(self) -> List[Dict[str, Any]]:
|
235
|
+
"""List all available tools.
|
236
|
+
|
237
|
+
Returns:
|
238
|
+
List of tool definitions
|
239
|
+
"""
|
240
|
+
return [
|
241
|
+
{
|
242
|
+
"name": tool.name,
|
243
|
+
"description": tool.description,
|
244
|
+
"parameters": tool.parameters,
|
245
|
+
}
|
246
|
+
for tool in self.tools.values()
|
247
|
+
]
|
248
|
+
|
249
|
+
def list_resources(self) -> List[Dict[str, Any]]:
|
250
|
+
"""List all available resources.
|
251
|
+
|
252
|
+
Returns:
|
253
|
+
List of resource definitions
|
254
|
+
"""
|
255
|
+
return [
|
256
|
+
{
|
257
|
+
"name": resource.name,
|
258
|
+
"uri": resource.uri,
|
259
|
+
"description": resource.description,
|
260
|
+
"mime_type": resource.mime_type,
|
261
|
+
}
|
262
|
+
for resource in self.resources.values()
|
263
|
+
]
|
264
|
+
|
265
|
+
def to_mcp_protocol(self) -> Dict[str, Any]:
|
266
|
+
"""Convert to MCP protocol format.
|
267
|
+
|
268
|
+
Returns:
|
269
|
+
MCP server definition
|
270
|
+
"""
|
271
|
+
return {
|
272
|
+
"name": self.name,
|
273
|
+
"description": self.description,
|
274
|
+
"capabilities": self.capabilities,
|
275
|
+
"tools": self.list_tools(),
|
276
|
+
"resources": self.list_resources(),
|
277
|
+
}
|
278
|
+
|
279
|
+
|
280
|
+
class MCPToolNode(AsyncNode):
|
281
|
+
"""Node that executes MCP tools within a workflow.
|
282
|
+
|
283
|
+
This node allows workflows to use tools provided by MCP servers,
|
284
|
+
bridging the gap between workflow automation and AI-powered tools.
|
285
|
+
|
286
|
+
Example:
|
287
|
+
Using an MCP tool in a workflow:
|
288
|
+
|
289
|
+
>>> # Create node for specific tool
|
290
|
+
>>> search_node = MCPToolNode(
|
291
|
+
... mcp_server="tools",
|
292
|
+
... tool_name="web_search"
|
293
|
+
... )
|
294
|
+
>>> # Add to workflow
|
295
|
+
>>> # workflow.add_node("search", search_node) # doctest: +SKIP
|
296
|
+
>>> # Execute with parameters
|
297
|
+
>>> # result = workflow.run({"search": {"query": "Kailash SDK documentation"}}) # doctest: +SKIP
|
298
|
+
"""
|
299
|
+
|
300
|
+
def __init__(
|
301
|
+
self, mcp_server: str, tool_name: str, parameter_mapping: Dict[str, str] = None
|
302
|
+
):
|
303
|
+
"""Initialize MCP tool node.
|
304
|
+
|
305
|
+
Args:
|
306
|
+
mcp_server: Name of the MCP server
|
307
|
+
tool_name: Name of the tool to execute
|
308
|
+
parameter_mapping: Map input keys to tool parameters
|
309
|
+
"""
|
310
|
+
super().__init__()
|
311
|
+
self.mcp_server = mcp_server
|
312
|
+
self.tool_name = tool_name
|
313
|
+
self.parameter_mapping = parameter_mapping or {}
|
314
|
+
self._mcp_integration: Optional[MCPIntegration] = None
|
315
|
+
|
316
|
+
def set_mcp_integration(self, mcp: MCPIntegration):
|
317
|
+
"""Set the MCP integration instance."""
|
318
|
+
self._mcp_integration = mcp
|
319
|
+
|
320
|
+
def get_parameters(self) -> Dict[str, Any]:
|
321
|
+
"""Get node parameters.
|
322
|
+
|
323
|
+
Returns:
|
324
|
+
Dictionary of parameters
|
325
|
+
"""
|
326
|
+
# For MCP tools, parameters are dynamic based on the tool
|
327
|
+
return {}
|
328
|
+
|
329
|
+
def validate_inputs(self, **kwargs) -> Dict[str, Any]:
|
330
|
+
"""Validate runtime inputs.
|
331
|
+
|
332
|
+
For MCPToolNode, we accept any inputs since the parameters
|
333
|
+
are dynamic based on the MCP tool being used.
|
334
|
+
|
335
|
+
Args:
|
336
|
+
**kwargs: Runtime inputs
|
337
|
+
|
338
|
+
Returns:
|
339
|
+
All inputs as-is
|
340
|
+
"""
|
341
|
+
# For MCP tools, pass through all inputs without validation
|
342
|
+
# The actual validation happens in the MCP tool itself
|
343
|
+
return kwargs
|
344
|
+
|
345
|
+
def run(self, **kwargs) -> Any:
|
346
|
+
"""Run the node synchronously.
|
347
|
+
|
348
|
+
Args:
|
349
|
+
**kwargs: Input parameters
|
350
|
+
|
351
|
+
Returns:
|
352
|
+
Execution result
|
353
|
+
"""
|
354
|
+
if not self._mcp_integration:
|
355
|
+
raise RuntimeError("MCP integration not set")
|
356
|
+
|
357
|
+
# Map parameters if needed
|
358
|
+
tool_params = {}
|
359
|
+
for input_key, tool_key in self.parameter_mapping.items():
|
360
|
+
if input_key in kwargs:
|
361
|
+
tool_params[tool_key] = kwargs[input_key]
|
362
|
+
|
363
|
+
# Add unmapped parameters
|
364
|
+
for key, value in kwargs.items():
|
365
|
+
if key not in self.parameter_mapping:
|
366
|
+
tool_params[key] = value
|
367
|
+
|
368
|
+
# Use synchronous execution
|
369
|
+
result = self._mcp_integration.execute_tool_sync(self.tool_name, tool_params)
|
370
|
+
|
371
|
+
# Ensure result is wrapped in a dict for consistency
|
372
|
+
if not isinstance(result, dict):
|
373
|
+
return {"result": result}
|
374
|
+
return result
|
375
|
+
|
376
|
+
async def async_run(self, **kwargs) -> Dict[str, Any]:
|
377
|
+
"""Run the node asynchronously.
|
378
|
+
|
379
|
+
Args:
|
380
|
+
**kwargs: Input parameters
|
381
|
+
|
382
|
+
Returns:
|
383
|
+
Dictionary of outputs
|
384
|
+
"""
|
385
|
+
if not self._mcp_integration:
|
386
|
+
raise RuntimeError("MCP integration not set")
|
387
|
+
|
388
|
+
# Map parameters if needed
|
389
|
+
tool_params = {}
|
390
|
+
for input_key, tool_key in self.parameter_mapping.items():
|
391
|
+
if input_key in kwargs:
|
392
|
+
tool_params[tool_key] = kwargs[input_key]
|
393
|
+
|
394
|
+
# Add unmapped parameters
|
395
|
+
for key, value in kwargs.items():
|
396
|
+
if key not in self.parameter_mapping:
|
397
|
+
tool_params[key] = value
|
398
|
+
|
399
|
+
# Execute tool asynchronously
|
400
|
+
result = await self._mcp_integration.execute_tool(self.tool_name, tool_params)
|
401
|
+
|
402
|
+
# Ensure result is wrapped in a dict for consistency
|
403
|
+
if not isinstance(result, dict):
|
404
|
+
return {"result": result}
|
405
|
+
return result
|
406
|
+
|
407
|
+
|
408
|
+
def create_example_mcp_server() -> MCPIntegration:
|
409
|
+
"""Create an example MCP server with common tools."""
|
410
|
+
|
411
|
+
mcp = MCPIntegration("example_tools", "Example MCP server with utility tools")
|
412
|
+
|
413
|
+
# Add search tool
|
414
|
+
async def web_search(query: str, max_results: int = 10, **kwargs):
|
415
|
+
"""Simulate web search."""
|
416
|
+
return {
|
417
|
+
"query": query,
|
418
|
+
"results": [
|
419
|
+
{"title": f"Result {i}", "url": f"https://example.com/{i}"}
|
420
|
+
for i in range(max_results)
|
421
|
+
],
|
422
|
+
}
|
423
|
+
|
424
|
+
mcp.add_tool(
|
425
|
+
"web_search",
|
426
|
+
web_search,
|
427
|
+
"Search the web",
|
428
|
+
{
|
429
|
+
"query": {"type": "string", "required": True},
|
430
|
+
"max_results": {"type": "integer", "default": 10},
|
431
|
+
},
|
432
|
+
)
|
433
|
+
|
434
|
+
# Add calculator tool
|
435
|
+
def calculate(expression: str, **kwargs):
|
436
|
+
"""Evaluate mathematical expression."""
|
437
|
+
try:
|
438
|
+
# Safe evaluation of mathematical expressions
|
439
|
+
import ast
|
440
|
+
import operator as op
|
441
|
+
|
442
|
+
# Operators would be used for safe eval implementation
|
443
|
+
# Currently using simple eval for the expression
|
444
|
+
_ = {
|
445
|
+
ast.Add: op.add,
|
446
|
+
ast.Sub: op.sub,
|
447
|
+
ast.Mult: op.mul,
|
448
|
+
ast.Div: op.truediv,
|
449
|
+
ast.Pow: op.pow,
|
450
|
+
}
|
451
|
+
|
452
|
+
def eval_expr(expr):
|
453
|
+
return eval(expr, {"__builtins__": {}}, {})
|
454
|
+
|
455
|
+
result = eval_expr(expression)
|
456
|
+
return {"expression": expression, "result": result}
|
457
|
+
except Exception as e:
|
458
|
+
return {"error": str(e)}
|
459
|
+
|
460
|
+
mcp.add_tool(
|
461
|
+
"calculate",
|
462
|
+
calculate,
|
463
|
+
"Evaluate mathematical expressions",
|
464
|
+
{"expression": {"type": "string", "required": True}},
|
465
|
+
)
|
466
|
+
|
467
|
+
# Add resources
|
468
|
+
mcp.add_resource(
|
469
|
+
"documentation", "https://docs.kailash.io", "Kailash SDK documentation"
|
470
|
+
)
|
471
|
+
|
472
|
+
mcp.add_resource(
|
473
|
+
"examples",
|
474
|
+
"https://github.com/kailash/examples",
|
475
|
+
"Example workflows and patterns",
|
476
|
+
)
|
477
|
+
|
478
|
+
return mcp
|
kailash/api/workflow_api.py
CHANGED
@@ -116,7 +116,7 @@ class WorkflowAPI:
|
|
116
116
|
"""Setup API routes dynamically based on workflow."""
|
117
117
|
|
118
118
|
# Main execution endpoint
|
119
|
-
@self.app.post("/execute"
|
119
|
+
@self.app.post("/execute")
|
120
120
|
async def execute_workflow(
|
121
121
|
request: WorkflowRequest, background_tasks: BackgroundTasks
|
122
122
|
):
|
@@ -143,18 +143,34 @@ class WorkflowAPI:
|
|
143
143
|
@self.app.get("/workflow/info")
|
144
144
|
async def get_workflow_info():
|
145
145
|
"""Get workflow metadata and structure."""
|
146
|
-
|
146
|
+
workflow = self.workflow_graph
|
147
|
+
|
148
|
+
# Get node information
|
149
|
+
nodes = []
|
150
|
+
for node_id, node_instance in workflow.nodes.items():
|
151
|
+
nodes.append({"id": node_id, "type": node_instance.node_type})
|
152
|
+
|
153
|
+
# Get edge information
|
154
|
+
edges = []
|
155
|
+
for conn in workflow.connections:
|
156
|
+
edges.append(
|
157
|
+
{
|
158
|
+
"source": conn.source_node,
|
159
|
+
"target": conn.target_node,
|
160
|
+
"source_output": conn.source_output,
|
161
|
+
"target_input": conn.target_input,
|
162
|
+
}
|
163
|
+
)
|
164
|
+
|
147
165
|
return {
|
148
|
-
"
|
149
|
-
"
|
150
|
-
"
|
151
|
-
"
|
152
|
-
"
|
153
|
-
|
154
|
-
|
155
|
-
"
|
156
|
-
n for n in graph_data.nodes() if graph_data.out_degree(n) == 0
|
157
|
-
],
|
166
|
+
"workflow_id": workflow.workflow_id,
|
167
|
+
"name": workflow.name,
|
168
|
+
"description": workflow.description,
|
169
|
+
"version": workflow.version,
|
170
|
+
"nodes": nodes,
|
171
|
+
"edges": edges,
|
172
|
+
"node_count": len(nodes),
|
173
|
+
"edge_count": len(edges),
|
158
174
|
}
|
159
175
|
|
160
176
|
# Health check
|
@@ -179,7 +195,7 @@ class WorkflowAPI:
|
|
179
195
|
|
180
196
|
# Execute workflow with inputs
|
181
197
|
results = await asyncio.to_thread(
|
182
|
-
self.runtime.execute, self.workflow_graph, request.inputs
|
198
|
+
self.runtime.execute, self.workflow_graph, parameters=request.inputs
|
183
199
|
)
|
184
200
|
|
185
201
|
# Handle tuple return from runtime
|
kailash/nodes/ai/__init__.py
CHANGED
@@ -13,8 +13,8 @@ from .ai_providers import (
|
|
13
13
|
get_available_providers,
|
14
14
|
get_provider,
|
15
15
|
)
|
16
|
-
from .embedding_generator import
|
17
|
-
from .llm_agent import
|
16
|
+
from .embedding_generator import EmbeddingGeneratorNode
|
17
|
+
from .llm_agent import LLMAgentNode
|
18
18
|
from .models import (
|
19
19
|
ModelPredictor,
|
20
20
|
NamedEntityRecognizer,
|
@@ -30,9 +30,9 @@ __all__ = [
|
|
30
30
|
"RetrievalAgent",
|
31
31
|
"FunctionCallingAgent",
|
32
32
|
"PlanningAgent",
|
33
|
-
"
|
33
|
+
"LLMAgentNode",
|
34
34
|
# Embedding and Vector Operations
|
35
|
-
"
|
35
|
+
"EmbeddingGeneratorNode",
|
36
36
|
# Provider Infrastructure
|
37
37
|
"LLMProvider",
|
38
38
|
"OllamaProvider",
|
kailash/nodes/ai/agents.py
CHANGED
@@ -333,7 +333,7 @@ class PlanningAgent(Node):
|
|
333
333
|
# Data processing workflow
|
334
334
|
potential_steps = [
|
335
335
|
{
|
336
|
-
"tool": "
|
336
|
+
"tool": "CSVReaderNode",
|
337
337
|
"description": "Read input data",
|
338
338
|
"parameters": {"file_path": "input.csv"},
|
339
339
|
},
|
@@ -348,7 +348,7 @@ class PlanningAgent(Node):
|
|
348
348
|
"parameters": {"group_by": "category", "operation": "sum"},
|
349
349
|
},
|
350
350
|
{
|
351
|
-
"tool": "
|
351
|
+
"tool": "CSVWriterNode",
|
352
352
|
"description": "Write results",
|
353
353
|
"parameters": {"file_path": "output.csv"},
|
354
354
|
},
|
@@ -357,7 +357,7 @@ class PlanningAgent(Node):
|
|
357
357
|
# Text analysis workflow
|
358
358
|
potential_steps = [
|
359
359
|
{
|
360
|
-
"tool": "
|
360
|
+
"tool": "TextReaderNode",
|
361
361
|
"description": "Read text data",
|
362
362
|
"parameters": {"file_path": "text.txt"},
|
363
363
|
},
|
@@ -372,7 +372,7 @@ class PlanningAgent(Node):
|
|
372
372
|
"parameters": {"max_length": 200},
|
373
373
|
},
|
374
374
|
{
|
375
|
-
"tool": "
|
375
|
+
"tool": "JSONWriterNode",
|
376
376
|
"description": "Save analysis results",
|
377
377
|
"parameters": {"file_path": "analysis.json"},
|
378
378
|
},
|
kailash/nodes/ai/ai_providers.py
CHANGED
@@ -1159,18 +1159,17 @@ def get_provider(
|
|
1159
1159
|
ValueError: If the provider name is not recognized or doesn't support the requested type.
|
1160
1160
|
|
1161
1161
|
Examples:
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
Get chat-only provider
|
1172
|
-
|
1173
|
-
chat_provider = get_provider("anthropic", "chat")
|
1162
|
+
>>> # Get any provider
|
1163
|
+
>>> provider = get_provider("openai")
|
1164
|
+
>>> if provider.supports_chat():
|
1165
|
+
... # Use for chat
|
1166
|
+
... pass
|
1167
|
+
>>> if provider.supports_embeddings():
|
1168
|
+
... # Use for embeddings
|
1169
|
+
... pass
|
1170
|
+
|
1171
|
+
>>> # Get chat-only provider
|
1172
|
+
>>> chat_provider = get_provider("anthropic", "chat")
|
1174
1173
|
response = chat_provider.chat(messages, model="claude-3-sonnet")
|
1175
1174
|
|
1176
1175
|
Get embedding-only provider:
|
@@ -1223,18 +1222,15 @@ def get_available_providers(
|
|
1223
1222
|
Dict mapping provider names to their availability and capabilities.
|
1224
1223
|
|
1225
1224
|
Examples:
|
1225
|
+
>>> # Get all providers
|
1226
|
+
>>> all_providers = get_available_providers()
|
1227
|
+
>>> for name, info in all_providers.items():
|
1228
|
+
... print(f"{name}: Available={info['available']}, Chat={info['chat']}, Embeddings={info['embeddings']}")
|
1226
1229
|
|
1227
|
-
Get
|
1228
|
-
|
1229
|
-
all_providers = get_available_providers()
|
1230
|
-
for name, info in all_providers.items():
|
1231
|
-
print(f"{name}: Available={info['available']}, Chat={info['chat']}, Embeddings={info['embeddings']}")
|
1232
|
-
|
1233
|
-
Get only chat providers:
|
1234
|
-
|
1235
|
-
chat_providers = get_available_providers("chat")
|
1230
|
+
>>> # Get only chat providers
|
1231
|
+
>>> chat_providers = get_available_providers("chat")
|
1236
1232
|
|
1237
|
-
Get only embedding providers
|
1233
|
+
>>> # Get only embedding providers
|
1238
1234
|
|
1239
1235
|
embed_providers = get_available_providers("embeddings")
|
1240
1236
|
"""
|