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.

Files changed (69) hide show
  1. daita/__init__.py +208 -0
  2. daita/agents/__init__.py +33 -0
  3. daita/agents/base.py +722 -0
  4. daita/agents/substrate.py +895 -0
  5. daita/cli/__init__.py +145 -0
  6. daita/cli/__main__.py +7 -0
  7. daita/cli/ascii_art.py +44 -0
  8. daita/cli/core/__init__.py +0 -0
  9. daita/cli/core/create.py +254 -0
  10. daita/cli/core/deploy.py +473 -0
  11. daita/cli/core/deployments.py +309 -0
  12. daita/cli/core/import_detector.py +219 -0
  13. daita/cli/core/init.py +382 -0
  14. daita/cli/core/logs.py +239 -0
  15. daita/cli/core/managed_deploy.py +709 -0
  16. daita/cli/core/run.py +648 -0
  17. daita/cli/core/status.py +421 -0
  18. daita/cli/core/test.py +239 -0
  19. daita/cli/core/webhooks.py +172 -0
  20. daita/cli/main.py +588 -0
  21. daita/cli/utils.py +541 -0
  22. daita/config/__init__.py +62 -0
  23. daita/config/base.py +159 -0
  24. daita/config/settings.py +184 -0
  25. daita/core/__init__.py +262 -0
  26. daita/core/decision_tracing.py +701 -0
  27. daita/core/exceptions.py +480 -0
  28. daita/core/focus.py +251 -0
  29. daita/core/interfaces.py +76 -0
  30. daita/core/plugin_tracing.py +550 -0
  31. daita/core/relay.py +695 -0
  32. daita/core/reliability.py +381 -0
  33. daita/core/scaling.py +444 -0
  34. daita/core/tools.py +402 -0
  35. daita/core/tracing.py +770 -0
  36. daita/core/workflow.py +1084 -0
  37. daita/display/__init__.py +1 -0
  38. daita/display/console.py +160 -0
  39. daita/execution/__init__.py +58 -0
  40. daita/execution/client.py +856 -0
  41. daita/execution/exceptions.py +92 -0
  42. daita/execution/models.py +317 -0
  43. daita/llm/__init__.py +60 -0
  44. daita/llm/anthropic.py +166 -0
  45. daita/llm/base.py +373 -0
  46. daita/llm/factory.py +101 -0
  47. daita/llm/gemini.py +152 -0
  48. daita/llm/grok.py +114 -0
  49. daita/llm/mock.py +135 -0
  50. daita/llm/openai.py +109 -0
  51. daita/plugins/__init__.py +141 -0
  52. daita/plugins/base.py +37 -0
  53. daita/plugins/base_db.py +167 -0
  54. daita/plugins/elasticsearch.py +844 -0
  55. daita/plugins/mcp.py +481 -0
  56. daita/plugins/mongodb.py +510 -0
  57. daita/plugins/mysql.py +351 -0
  58. daita/plugins/postgresql.py +331 -0
  59. daita/plugins/redis_messaging.py +500 -0
  60. daita/plugins/rest.py +529 -0
  61. daita/plugins/s3.py +761 -0
  62. daita/plugins/slack.py +729 -0
  63. daita/utils/__init__.py +18 -0
  64. daita_agents-0.1.0.dist-info/METADATA +350 -0
  65. daita_agents-0.1.0.dist-info/RECORD +69 -0
  66. daita_agents-0.1.0.dist-info/WHEEL +5 -0
  67. daita_agents-0.1.0.dist-info/entry_points.txt +2 -0
  68. daita_agents-0.1.0.dist-info/licenses/LICENSE +56 -0
  69. 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())