daita-agents 0.1.0__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.
Potentially problematic release.
This version of daita-agents might be problematic. Click here for more details.
- daita/__init__.py +208 -0
- daita/agents/__init__.py +33 -0
- daita/agents/base.py +722 -0
- daita/agents/substrate.py +895 -0
- daita/cli/__init__.py +145 -0
- daita/cli/__main__.py +7 -0
- daita/cli/ascii_art.py +44 -0
- daita/cli/core/__init__.py +0 -0
- daita/cli/core/create.py +254 -0
- daita/cli/core/deploy.py +473 -0
- daita/cli/core/deployments.py +309 -0
- daita/cli/core/import_detector.py +219 -0
- daita/cli/core/init.py +382 -0
- daita/cli/core/logs.py +239 -0
- daita/cli/core/managed_deploy.py +709 -0
- daita/cli/core/run.py +648 -0
- daita/cli/core/status.py +421 -0
- daita/cli/core/test.py +239 -0
- daita/cli/core/webhooks.py +172 -0
- daita/cli/main.py +588 -0
- daita/cli/utils.py +541 -0
- daita/config/__init__.py +62 -0
- daita/config/base.py +159 -0
- daita/config/settings.py +184 -0
- daita/core/__init__.py +262 -0
- daita/core/decision_tracing.py +701 -0
- daita/core/exceptions.py +480 -0
- daita/core/focus.py +251 -0
- daita/core/interfaces.py +76 -0
- daita/core/plugin_tracing.py +550 -0
- daita/core/relay.py +695 -0
- daita/core/reliability.py +381 -0
- daita/core/scaling.py +444 -0
- daita/core/tools.py +402 -0
- daita/core/tracing.py +770 -0
- daita/core/workflow.py +1084 -0
- daita/display/__init__.py +1 -0
- daita/display/console.py +160 -0
- daita/execution/__init__.py +58 -0
- daita/execution/client.py +856 -0
- daita/execution/exceptions.py +92 -0
- daita/execution/models.py +317 -0
- daita/llm/__init__.py +60 -0
- daita/llm/anthropic.py +166 -0
- daita/llm/base.py +373 -0
- daita/llm/factory.py +101 -0
- daita/llm/gemini.py +152 -0
- daita/llm/grok.py +114 -0
- daita/llm/mock.py +135 -0
- daita/llm/openai.py +109 -0
- daita/plugins/__init__.py +141 -0
- daita/plugins/base.py +37 -0
- daita/plugins/base_db.py +167 -0
- daita/plugins/elasticsearch.py +844 -0
- daita/plugins/mcp.py +481 -0
- daita/plugins/mongodb.py +510 -0
- daita/plugins/mysql.py +351 -0
- daita/plugins/postgresql.py +331 -0
- daita/plugins/redis_messaging.py +500 -0
- daita/plugins/rest.py +529 -0
- daita/plugins/s3.py +761 -0
- daita/plugins/slack.py +729 -0
- daita/utils/__init__.py +18 -0
- daita_agents-0.1.0.dist-info/METADATA +350 -0
- daita_agents-0.1.0.dist-info/RECORD +69 -0
- daita_agents-0.1.0.dist-info/WHEEL +5 -0
- daita_agents-0.1.0.dist-info/entry_points.txt +2 -0
- daita_agents-0.1.0.dist-info/licenses/LICENSE +56 -0
- daita_agents-0.1.0.dist-info/top_level.txt +1 -0
daita/core/tools.py
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Universal tool abstraction for Daita agents.
|
|
3
|
+
|
|
4
|
+
Tools are LLM-callable functions that can come from:
|
|
5
|
+
- Plugins (database queries, S3 operations, API calls)
|
|
6
|
+
- MCP servers (external tools via Model Context Protocol)
|
|
7
|
+
- Custom functions (user-defined Python functions)
|
|
8
|
+
|
|
9
|
+
This abstraction is provider-agnostic and supports both prompt-based
|
|
10
|
+
and native function calling (OpenAI, Anthropic, etc).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from typing import Callable, Dict, Any, List, Optional, Awaitable, Union
|
|
15
|
+
import asyncio
|
|
16
|
+
import inspect
|
|
17
|
+
import logging
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class AgentTool:
|
|
24
|
+
"""
|
|
25
|
+
Universal tool definition for agent-LLM integration.
|
|
26
|
+
|
|
27
|
+
Represents any callable function that an agent can use, regardless of source.
|
|
28
|
+
Designed to be compatible with industry standards (LangChain, AutoGen, etc).
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
```python
|
|
32
|
+
tool = AgentTool(
|
|
33
|
+
name="search_database",
|
|
34
|
+
description="Search for records in the database",
|
|
35
|
+
parameters={
|
|
36
|
+
"query": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"description": "SQL query to execute",
|
|
39
|
+
"required": True
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
handler=async_search_function
|
|
43
|
+
)
|
|
44
|
+
```
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
# Core fields (required)
|
|
48
|
+
name: str
|
|
49
|
+
description: str
|
|
50
|
+
parameters: Dict[str, Any] # JSON Schema format
|
|
51
|
+
handler: Callable[[Dict[str, Any]], Awaitable[Any]] # async function
|
|
52
|
+
|
|
53
|
+
# Optional metadata
|
|
54
|
+
category: Optional[str] = None # "database", "storage", "api", etc
|
|
55
|
+
source: str = "custom" # "plugin", "mcp", "custom"
|
|
56
|
+
plugin_name: Optional[str] = None # Which plugin provides this tool
|
|
57
|
+
|
|
58
|
+
# Safety features
|
|
59
|
+
timeout_seconds: Optional[int] = None # Execution timeout
|
|
60
|
+
|
|
61
|
+
def to_openai_function(self) -> Dict[str, Any]:
|
|
62
|
+
"""
|
|
63
|
+
Convert to OpenAI function calling format.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
OpenAI function definition dict
|
|
67
|
+
|
|
68
|
+
Reference:
|
|
69
|
+
https://platform.openai.com/docs/guides/function-calling
|
|
70
|
+
"""
|
|
71
|
+
return {
|
|
72
|
+
"type": "function",
|
|
73
|
+
"function": {
|
|
74
|
+
"name": self.name,
|
|
75
|
+
"description": self.description,
|
|
76
|
+
"parameters": {
|
|
77
|
+
"type": "object",
|
|
78
|
+
"properties": self.parameters,
|
|
79
|
+
"required": [
|
|
80
|
+
k for k, v in self.parameters.items()
|
|
81
|
+
if v.get("required", False)
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
def to_anthropic_tool(self) -> Dict[str, Any]:
|
|
88
|
+
"""
|
|
89
|
+
Convert to Anthropic tool format.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Anthropic tool definition dict
|
|
93
|
+
|
|
94
|
+
Reference:
|
|
95
|
+
https://docs.anthropic.com/claude/docs/tool-use
|
|
96
|
+
"""
|
|
97
|
+
return {
|
|
98
|
+
"name": self.name,
|
|
99
|
+
"description": self.description,
|
|
100
|
+
"input_schema": {
|
|
101
|
+
"type": "object",
|
|
102
|
+
"properties": self.parameters,
|
|
103
|
+
"required": [
|
|
104
|
+
k for k, v in self.parameters.items()
|
|
105
|
+
if v.get("required", False)
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
def to_llm_function(self) -> Dict[str, Any]:
|
|
111
|
+
"""
|
|
112
|
+
Generic LLM function format (works for most providers).
|
|
113
|
+
|
|
114
|
+
For provider-specific formats, use to_openai_function() or to_anthropic_tool().
|
|
115
|
+
This format is used for prompt-based tool calling.
|
|
116
|
+
"""
|
|
117
|
+
return {
|
|
118
|
+
"name": self.name,
|
|
119
|
+
"description": self.description,
|
|
120
|
+
"parameters": {
|
|
121
|
+
"type": "object",
|
|
122
|
+
"properties": self.parameters,
|
|
123
|
+
"required": [
|
|
124
|
+
k for k, v in self.parameters.items()
|
|
125
|
+
if v.get("required", False)
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
def to_prompt_description(self) -> str:
|
|
131
|
+
"""
|
|
132
|
+
Generate human-readable tool description for prompt injection.
|
|
133
|
+
|
|
134
|
+
Used in prompt-based tool calling (non-native function calling).
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Formatted tool description string
|
|
138
|
+
"""
|
|
139
|
+
params_desc = []
|
|
140
|
+
for param_name, param_info in self.parameters.items():
|
|
141
|
+
required = " (required)" if param_info.get("required") else " (optional)"
|
|
142
|
+
param_type = param_info.get("type", "any")
|
|
143
|
+
param_desc = param_info.get("description", "")
|
|
144
|
+
params_desc.append(
|
|
145
|
+
f" - {param_name} ({param_type}){required}: {param_desc}"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
params_str = "\n".join(params_desc) if params_desc else " (no parameters)"
|
|
149
|
+
|
|
150
|
+
return f"{self.name}: {self.description}\nParameters:\n{params_str}"
|
|
151
|
+
|
|
152
|
+
async def execute(self, arguments: Dict[str, Any]) -> Any:
|
|
153
|
+
"""
|
|
154
|
+
Execute the tool with given arguments.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
arguments: Tool arguments matching the parameter schema
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Tool execution result
|
|
161
|
+
|
|
162
|
+
Raises:
|
|
163
|
+
RuntimeError: If tool execution fails or times out
|
|
164
|
+
"""
|
|
165
|
+
if not callable(self.handler):
|
|
166
|
+
raise RuntimeError(f"Tool '{self.name}' has non-callable handler")
|
|
167
|
+
|
|
168
|
+
# Execute with timeout if specified
|
|
169
|
+
if self.timeout_seconds:
|
|
170
|
+
try:
|
|
171
|
+
result = await asyncio.wait_for(
|
|
172
|
+
self.handler(arguments),
|
|
173
|
+
timeout=self.timeout_seconds
|
|
174
|
+
)
|
|
175
|
+
return result
|
|
176
|
+
except asyncio.TimeoutError:
|
|
177
|
+
raise RuntimeError(
|
|
178
|
+
f"Tool '{self.name}' execution timed out after {self.timeout_seconds}s"
|
|
179
|
+
)
|
|
180
|
+
else:
|
|
181
|
+
# No timeout
|
|
182
|
+
return await self.handler(arguments)
|
|
183
|
+
|
|
184
|
+
@classmethod
|
|
185
|
+
def from_function(
|
|
186
|
+
cls,
|
|
187
|
+
func: Callable,
|
|
188
|
+
name: Optional[str] = None,
|
|
189
|
+
description: Optional[str] = None,
|
|
190
|
+
parameters: Optional[Dict[str, Any]] = None,
|
|
191
|
+
**kwargs
|
|
192
|
+
) -> 'AgentTool':
|
|
193
|
+
"""
|
|
194
|
+
Create AgentTool from a Python function.
|
|
195
|
+
|
|
196
|
+
Automatically extracts name, docstring, and parameter info if not provided.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
func: Python function (sync or async)
|
|
200
|
+
name: Tool name (defaults to function name)
|
|
201
|
+
description: Tool description (defaults to docstring)
|
|
202
|
+
parameters: Parameter schema (auto-detected from type hints if not provided)
|
|
203
|
+
**kwargs: Additional AgentTool fields (category, timeout_seconds, etc)
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
AgentTool instance
|
|
207
|
+
|
|
208
|
+
Example:
|
|
209
|
+
```python
|
|
210
|
+
async def search_docs(query: str, limit: int = 10):
|
|
211
|
+
'''Search documentation with a query'''
|
|
212
|
+
return search_results
|
|
213
|
+
|
|
214
|
+
# Auto-detection from function signature
|
|
215
|
+
tool = AgentTool.from_function(search_docs)
|
|
216
|
+
|
|
217
|
+
# Or with explicit parameters
|
|
218
|
+
tool = AgentTool.from_function(
|
|
219
|
+
search_docs,
|
|
220
|
+
parameters={
|
|
221
|
+
"query": {
|
|
222
|
+
"type": "string",
|
|
223
|
+
"description": "Search query",
|
|
224
|
+
"required": True
|
|
225
|
+
},
|
|
226
|
+
"limit": {
|
|
227
|
+
"type": "integer",
|
|
228
|
+
"description": "Max results",
|
|
229
|
+
"required": False
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
)
|
|
233
|
+
```
|
|
234
|
+
"""
|
|
235
|
+
# Use function name if not provided
|
|
236
|
+
tool_name = name or func.__name__
|
|
237
|
+
|
|
238
|
+
# Use docstring if no description
|
|
239
|
+
tool_description = description or (func.__doc__ or f"Execute {tool_name}").strip()
|
|
240
|
+
|
|
241
|
+
# Auto-detect parameters from type hints if not provided
|
|
242
|
+
if parameters is None:
|
|
243
|
+
parameters = {}
|
|
244
|
+
sig = inspect.signature(func)
|
|
245
|
+
|
|
246
|
+
for param_name, param in sig.parameters.items():
|
|
247
|
+
# Skip self/cls parameters
|
|
248
|
+
if param_name in ('self', 'cls'):
|
|
249
|
+
continue
|
|
250
|
+
|
|
251
|
+
# Map Python types to JSON Schema types
|
|
252
|
+
param_type = "string" # Default type
|
|
253
|
+
if param.annotation != inspect.Parameter.empty:
|
|
254
|
+
if param.annotation in (int, 'int'):
|
|
255
|
+
param_type = "integer"
|
|
256
|
+
elif param.annotation in (float, 'float'):
|
|
257
|
+
param_type = "number"
|
|
258
|
+
elif param.annotation in (bool, 'bool'):
|
|
259
|
+
param_type = "boolean"
|
|
260
|
+
elif param.annotation in (list, List):
|
|
261
|
+
param_type = "array"
|
|
262
|
+
elif param.annotation in (dict, Dict):
|
|
263
|
+
param_type = "object"
|
|
264
|
+
|
|
265
|
+
# Determine if required (no default value)
|
|
266
|
+
is_required = param.default == inspect.Parameter.empty
|
|
267
|
+
|
|
268
|
+
parameters[param_name] = {
|
|
269
|
+
"type": param_type,
|
|
270
|
+
"description": f"Parameter: {param_name}",
|
|
271
|
+
"required": is_required
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
# Wrap sync functions to make them async
|
|
275
|
+
if asyncio.iscoroutinefunction(func):
|
|
276
|
+
handler = func
|
|
277
|
+
else:
|
|
278
|
+
async def async_wrapper(args: Dict[str, Any]) -> Any:
|
|
279
|
+
return func(**args)
|
|
280
|
+
handler = async_wrapper
|
|
281
|
+
|
|
282
|
+
return cls(
|
|
283
|
+
name=tool_name,
|
|
284
|
+
description=tool_description,
|
|
285
|
+
parameters=parameters,
|
|
286
|
+
handler=handler,
|
|
287
|
+
source="custom",
|
|
288
|
+
**kwargs
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
@classmethod
|
|
292
|
+
def from_mcp_tool(cls, mcp_tool, mcp_registry) -> 'AgentTool':
|
|
293
|
+
"""
|
|
294
|
+
Create AgentTool from an MCP tool.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
mcp_tool: MCPTool instance from MCP plugin
|
|
298
|
+
mcp_registry: MCPToolRegistry for routing calls
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
AgentTool instance that wraps the MCP tool
|
|
302
|
+
"""
|
|
303
|
+
# Create handler that routes to MCP registry
|
|
304
|
+
async def mcp_handler(arguments: Dict[str, Any]) -> Any:
|
|
305
|
+
return await mcp_registry.call_tool(mcp_tool.name, arguments)
|
|
306
|
+
|
|
307
|
+
return cls(
|
|
308
|
+
name=mcp_tool.name,
|
|
309
|
+
description=mcp_tool.description,
|
|
310
|
+
parameters=mcp_tool.input_schema.get("properties", {}),
|
|
311
|
+
handler=mcp_handler,
|
|
312
|
+
source="mcp",
|
|
313
|
+
category="mcp"
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
class ToolRegistry:
|
|
318
|
+
"""
|
|
319
|
+
Registry for managing tools from multiple sources.
|
|
320
|
+
|
|
321
|
+
Used internally by agents to aggregate tools from plugins, MCP servers,
|
|
322
|
+
and custom functions.
|
|
323
|
+
"""
|
|
324
|
+
|
|
325
|
+
def __init__(self):
|
|
326
|
+
"""Initialize empty tool registry"""
|
|
327
|
+
self.tools: List[AgentTool] = []
|
|
328
|
+
self._tool_map: Dict[str, AgentTool] = {}
|
|
329
|
+
|
|
330
|
+
def register(self, tool: AgentTool) -> None:
|
|
331
|
+
"""
|
|
332
|
+
Register a tool.
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
tool: AgentTool to register
|
|
336
|
+
"""
|
|
337
|
+
if tool.name in self._tool_map:
|
|
338
|
+
logger.warning(
|
|
339
|
+
f"Tool '{tool.name}' already registered. "
|
|
340
|
+
f"Overwriting (old source: {self._tool_map[tool.name].source}, "
|
|
341
|
+
f"new source: {tool.source})"
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
self.tools.append(tool)
|
|
345
|
+
self._tool_map[tool.name] = tool
|
|
346
|
+
|
|
347
|
+
logger.debug(f"Registered tool: {tool.name} (source: {tool.source})")
|
|
348
|
+
|
|
349
|
+
def register_many(self, tools: List[AgentTool]) -> None:
|
|
350
|
+
"""
|
|
351
|
+
Register multiple tools at once.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
tools: List of AgentTool instances
|
|
355
|
+
"""
|
|
356
|
+
for tool in tools:
|
|
357
|
+
self.register(tool)
|
|
358
|
+
|
|
359
|
+
def get(self, name: str) -> Optional[AgentTool]:
|
|
360
|
+
"""
|
|
361
|
+
Get tool by name.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
name: Tool name
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
AgentTool instance or None if not found
|
|
368
|
+
"""
|
|
369
|
+
return self._tool_map.get(name)
|
|
370
|
+
|
|
371
|
+
async def execute(self, name: str, arguments: Dict[str, Any]) -> Any:
|
|
372
|
+
"""
|
|
373
|
+
Execute a tool by name.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
name: Tool name
|
|
377
|
+
arguments: Tool arguments
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
Tool execution result
|
|
381
|
+
|
|
382
|
+
Raises:
|
|
383
|
+
RuntimeError: If tool not found or execution fails
|
|
384
|
+
"""
|
|
385
|
+
tool = self.get(name)
|
|
386
|
+
if not tool:
|
|
387
|
+
available = [t.name for t in self.tools]
|
|
388
|
+
raise RuntimeError(
|
|
389
|
+
f"Tool '{name}' not found. Available tools: {', '.join(available)}"
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
return await tool.execute(arguments)
|
|
393
|
+
|
|
394
|
+
@property
|
|
395
|
+
def tool_count(self) -> int:
|
|
396
|
+
"""Total number of registered tools"""
|
|
397
|
+
return len(self.tools)
|
|
398
|
+
|
|
399
|
+
@property
|
|
400
|
+
def tool_names(self) -> List[str]:
|
|
401
|
+
"""List of all tool names"""
|
|
402
|
+
return list(self._tool_map.keys())
|