daita-agents 0.2.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.
- daita/__init__.py +216 -0
- daita/agents/__init__.py +33 -0
- daita/agents/base.py +743 -0
- daita/agents/substrate.py +1141 -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 +481 -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 +779 -0
- daita/core/reliability.py +381 -0
- daita/core/scaling.py +459 -0
- daita/core/tools.py +554 -0
- daita/core/tracing.py +770 -0
- daita/core/workflow.py +1144 -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 +291 -0
- daita/llm/base.py +530 -0
- daita/llm/factory.py +101 -0
- daita/llm/gemini.py +355 -0
- daita/llm/grok.py +219 -0
- daita/llm/mock.py +172 -0
- daita/llm/openai.py +220 -0
- daita/plugins/__init__.py +141 -0
- daita/plugins/base.py +37 -0
- daita/plugins/base_db.py +167 -0
- daita/plugins/elasticsearch.py +849 -0
- daita/plugins/mcp.py +481 -0
- daita/plugins/mongodb.py +520 -0
- daita/plugins/mysql.py +362 -0
- daita/plugins/postgresql.py +342 -0
- daita/plugins/redis_messaging.py +500 -0
- daita/plugins/rest.py +537 -0
- daita/plugins/s3.py +770 -0
- daita/plugins/slack.py +729 -0
- daita/utils/__init__.py +18 -0
- daita_agents-0.2.0.dist-info/METADATA +409 -0
- daita_agents-0.2.0.dist-info/RECORD +69 -0
- daita_agents-0.2.0.dist-info/WHEEL +5 -0
- daita_agents-0.2.0.dist-info/entry_points.txt +2 -0
- daita_agents-0.2.0.dist-info/licenses/LICENSE +56 -0
- daita_agents-0.2.0.dist-info/top_level.txt +1 -0
daita/core/tools.py
ADDED
|
@@ -0,0 +1,554 @@
|
|
|
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, Literal, get_origin, get_args, get_type_hints
|
|
15
|
+
import asyncio
|
|
16
|
+
import inspect
|
|
17
|
+
import logging
|
|
18
|
+
import re
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _parse_docstring_params(func: Callable) -> Dict[str, str]:
|
|
24
|
+
"""
|
|
25
|
+
Extract parameter descriptions from Google/NumPy style docstrings.
|
|
26
|
+
|
|
27
|
+
Supports formats:
|
|
28
|
+
param: description
|
|
29
|
+
param (type): description
|
|
30
|
+
param : description
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
func: Function to extract docstring from
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Dict mapping parameter names to their descriptions
|
|
37
|
+
"""
|
|
38
|
+
docstring = func.__doc__ or ""
|
|
39
|
+
|
|
40
|
+
# Find Args: section
|
|
41
|
+
args_match = re.search(
|
|
42
|
+
r'Args?:(.*?)(?=Returns?:|Raises?:|Example:|Notes?:|$)',
|
|
43
|
+
docstring,
|
|
44
|
+
re.DOTALL | re.IGNORECASE
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
if not args_match:
|
|
48
|
+
return {}
|
|
49
|
+
|
|
50
|
+
descriptions = {}
|
|
51
|
+
args_section = args_match.group(1)
|
|
52
|
+
|
|
53
|
+
# Match parameter entries: " param_name: description" or " param_name (type): description"
|
|
54
|
+
for match in re.finditer(
|
|
55
|
+
r'^\s+(\w+)(?:\s*\([^)]+\))?\s*:\s*(.+?)(?=^\s+\w+\s*(?:\([^)]+\))?\s*:|$)',
|
|
56
|
+
args_section,
|
|
57
|
+
re.MULTILINE | re.DOTALL
|
|
58
|
+
):
|
|
59
|
+
param_name = match.group(1)
|
|
60
|
+
description = ' '.join(match.group(2).split()) # Clean up whitespace
|
|
61
|
+
descriptions[param_name] = description
|
|
62
|
+
|
|
63
|
+
return descriptions
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _type_hint_to_json_schema(hint: Any) -> Dict[str, Any]:
|
|
67
|
+
"""
|
|
68
|
+
Convert Python type hints to JSON Schema.
|
|
69
|
+
|
|
70
|
+
Supports:
|
|
71
|
+
- Basic types: int, float, str, bool
|
|
72
|
+
- Optional[T]: Makes field not required, uses T's schema
|
|
73
|
+
- Literal["a", "b"]: Enum constraint
|
|
74
|
+
- List[T]: Array with item type
|
|
75
|
+
- Dict[K, V]: Object type
|
|
76
|
+
- Union[A, B]: anyOf schema
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
hint: Type hint to convert
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
JSON Schema dict for the type
|
|
83
|
+
"""
|
|
84
|
+
origin = get_origin(hint)
|
|
85
|
+
args = get_args(hint)
|
|
86
|
+
|
|
87
|
+
# Handle Union types (includes Optional)
|
|
88
|
+
if origin is Union:
|
|
89
|
+
# Check if it's Optional (Union with None)
|
|
90
|
+
non_none_args = [arg for arg in args if arg is not type(None)]
|
|
91
|
+
|
|
92
|
+
if len(non_none_args) == 1:
|
|
93
|
+
# Optional[T] - just return T's schema, optionality handled by 'required'
|
|
94
|
+
return _type_hint_to_json_schema(non_none_args[0])
|
|
95
|
+
else:
|
|
96
|
+
# Union[A, B, ...] - use anyOf
|
|
97
|
+
return {
|
|
98
|
+
"anyOf": [_type_hint_to_json_schema(arg) for arg in non_none_args]
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# Handle Literal types
|
|
102
|
+
if origin is Literal:
|
|
103
|
+
# Determine type from first value
|
|
104
|
+
first_val = args[0] if args else ""
|
|
105
|
+
val_type = type(first_val).__name__
|
|
106
|
+
schema_type = {
|
|
107
|
+
"int": "integer",
|
|
108
|
+
"float": "number",
|
|
109
|
+
"str": "string",
|
|
110
|
+
"bool": "boolean"
|
|
111
|
+
}.get(val_type, "string")
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
"type": schema_type,
|
|
115
|
+
"enum": list(args)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# Handle List types
|
|
119
|
+
if origin is list or origin is List:
|
|
120
|
+
schema = {"type": "array"}
|
|
121
|
+
if args:
|
|
122
|
+
schema["items"] = _type_hint_to_json_schema(args[0])
|
|
123
|
+
return schema
|
|
124
|
+
|
|
125
|
+
# Handle Dict types
|
|
126
|
+
if origin is dict or origin is Dict:
|
|
127
|
+
return {"type": "object"}
|
|
128
|
+
|
|
129
|
+
# Handle basic Python types
|
|
130
|
+
basic_type_map = {
|
|
131
|
+
int: "integer",
|
|
132
|
+
float: "number",
|
|
133
|
+
str: "string",
|
|
134
|
+
bool: "boolean",
|
|
135
|
+
list: "array",
|
|
136
|
+
dict: "object"
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if hint in basic_type_map:
|
|
140
|
+
return {"type": basic_type_map[hint]}
|
|
141
|
+
|
|
142
|
+
# Default fallback
|
|
143
|
+
return {"type": "string"}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _extract_parameters_from_function(func: Callable) -> Dict[str, Any]:
|
|
147
|
+
"""
|
|
148
|
+
Extract JSON Schema from function signature with type hints and docstring.
|
|
149
|
+
|
|
150
|
+
Supports:
|
|
151
|
+
- Type hints: int, str, float, bool, Optional, Literal, List, Dict, Union
|
|
152
|
+
- Docstring parameter descriptions (Google/NumPy style)
|
|
153
|
+
- Default values (automatically marks as optional)
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
func: Python function to analyze
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
JSON Schema dict with properties and required fields
|
|
160
|
+
"""
|
|
161
|
+
sig = inspect.signature(func)
|
|
162
|
+
properties = {}
|
|
163
|
+
required = []
|
|
164
|
+
|
|
165
|
+
# Get type hints (resolves string annotations)
|
|
166
|
+
try:
|
|
167
|
+
type_hints = get_type_hints(func)
|
|
168
|
+
except Exception:
|
|
169
|
+
# Fallback if get_type_hints fails
|
|
170
|
+
type_hints = {}
|
|
171
|
+
|
|
172
|
+
# Get parameter descriptions from docstring
|
|
173
|
+
param_descriptions = _parse_docstring_params(func)
|
|
174
|
+
|
|
175
|
+
for param_name, param in sig.parameters.items():
|
|
176
|
+
if param_name in ('self', 'cls'):
|
|
177
|
+
continue
|
|
178
|
+
|
|
179
|
+
# Get type hint for this parameter
|
|
180
|
+
type_hint = type_hints.get(param_name, param.annotation)
|
|
181
|
+
|
|
182
|
+
# Check if parameter is Optional (has None in Union)
|
|
183
|
+
is_optional = False
|
|
184
|
+
if type_hint != inspect.Parameter.empty:
|
|
185
|
+
origin = get_origin(type_hint)
|
|
186
|
+
args = get_args(type_hint)
|
|
187
|
+
if origin is Union and type(None) in args:
|
|
188
|
+
is_optional = True
|
|
189
|
+
|
|
190
|
+
# Convert type hint to JSON Schema
|
|
191
|
+
if type_hint != inspect.Parameter.empty:
|
|
192
|
+
schema = _type_hint_to_json_schema(type_hint)
|
|
193
|
+
else:
|
|
194
|
+
schema = {"type": "string"}
|
|
195
|
+
|
|
196
|
+
# Get description from docstring or use generic
|
|
197
|
+
description = param_descriptions.get(param_name, f"Parameter: {param_name}")
|
|
198
|
+
schema["description"] = description
|
|
199
|
+
|
|
200
|
+
properties[param_name] = schema
|
|
201
|
+
|
|
202
|
+
# Mark as required if no default value and not Optional
|
|
203
|
+
has_default = param.default != inspect.Parameter.empty
|
|
204
|
+
if not has_default and not is_optional:
|
|
205
|
+
required.append(param_name)
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
"type": "object",
|
|
209
|
+
"properties": properties,
|
|
210
|
+
"required": required
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _make_async_handler(func: Callable) -> Callable[[Dict[str, Any]], Awaitable[Any]]:
|
|
215
|
+
"""
|
|
216
|
+
Convert any function to async handler format.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
func: Sync or async function to convert
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
Async handler that accepts Dict[str, Any] and returns result
|
|
223
|
+
"""
|
|
224
|
+
if asyncio.iscoroutinefunction(func):
|
|
225
|
+
async def handler(args: Dict[str, Any]) -> Any:
|
|
226
|
+
return await func(**args)
|
|
227
|
+
else:
|
|
228
|
+
async def handler(args: Dict[str, Any]) -> Any:
|
|
229
|
+
return func(**args)
|
|
230
|
+
return handler
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def tool(
|
|
234
|
+
func: Optional[Callable] = None,
|
|
235
|
+
*,
|
|
236
|
+
name: Optional[str] = None,
|
|
237
|
+
description: Optional[str] = None,
|
|
238
|
+
timeout_seconds: Optional[int] = None,
|
|
239
|
+
category: Optional[str] = None,
|
|
240
|
+
**kwargs
|
|
241
|
+
) -> Union['AgentTool', Callable]:
|
|
242
|
+
"""
|
|
243
|
+
Convert a function into an AgentTool.
|
|
244
|
+
Works as both decorator and function call.
|
|
245
|
+
|
|
246
|
+
Automatically extracts parameter schema from type hints and docstring.
|
|
247
|
+
|
|
248
|
+
Usage as decorator:
|
|
249
|
+
@tool
|
|
250
|
+
async def calculator(a: int, b: int) -> int:
|
|
251
|
+
'''Add two numbers'''
|
|
252
|
+
return a + b
|
|
253
|
+
|
|
254
|
+
Usage with options:
|
|
255
|
+
@tool(timeout_seconds=30, category="math")
|
|
256
|
+
async def calculator(a: int, b: int) -> int:
|
|
257
|
+
return a + b
|
|
258
|
+
|
|
259
|
+
Usage as function:
|
|
260
|
+
calc_tool = tool(calculator)
|
|
261
|
+
calc_tool = tool(calculator, timeout_seconds=30)
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
func: Function to convert (when used as direct call)
|
|
265
|
+
name: Tool name (defaults to function name)
|
|
266
|
+
description: Tool description (defaults to docstring first line)
|
|
267
|
+
timeout_seconds: Execution timeout in seconds
|
|
268
|
+
category: Tool category for organization
|
|
269
|
+
**kwargs: Additional AgentTool fields
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
AgentTool instance or decorator function
|
|
273
|
+
"""
|
|
274
|
+
def create_tool(f: Callable) -> 'AgentTool':
|
|
275
|
+
tool_name = name or f.__name__
|
|
276
|
+
tool_description = description or (f.__doc__ or f"Execute {tool_name}").strip().split('\n')[0]
|
|
277
|
+
tool_parameters = _extract_parameters_from_function(f)
|
|
278
|
+
handler = _make_async_handler(f)
|
|
279
|
+
|
|
280
|
+
return AgentTool(
|
|
281
|
+
name=tool_name,
|
|
282
|
+
description=tool_description,
|
|
283
|
+
parameters=tool_parameters,
|
|
284
|
+
handler=handler,
|
|
285
|
+
timeout_seconds=timeout_seconds,
|
|
286
|
+
category=category,
|
|
287
|
+
source="custom",
|
|
288
|
+
**kwargs
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
# Used as @tool (no parentheses)
|
|
292
|
+
if func is not None:
|
|
293
|
+
return create_tool(func)
|
|
294
|
+
|
|
295
|
+
# Used as @tool(...) (with arguments)
|
|
296
|
+
return create_tool
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
@dataclass
|
|
300
|
+
class AgentTool:
|
|
301
|
+
"""
|
|
302
|
+
Universal tool definition for agent-LLM integration.
|
|
303
|
+
|
|
304
|
+
Represents any callable function that an agent can use, regardless of source.
|
|
305
|
+
Designed to be compatible with industry standards (LangChain, AutoGen, etc).
|
|
306
|
+
|
|
307
|
+
Example:
|
|
308
|
+
```python
|
|
309
|
+
tool = AgentTool(
|
|
310
|
+
name="search_database",
|
|
311
|
+
description="Search for records in the database",
|
|
312
|
+
parameters={
|
|
313
|
+
"query": {
|
|
314
|
+
"type": "string",
|
|
315
|
+
"description": "SQL query to execute",
|
|
316
|
+
"required": True
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
handler=async_search_function
|
|
320
|
+
)
|
|
321
|
+
```
|
|
322
|
+
"""
|
|
323
|
+
|
|
324
|
+
# Core fields (required)
|
|
325
|
+
name: str
|
|
326
|
+
description: str
|
|
327
|
+
parameters: Dict[str, Any] # JSON Schema format
|
|
328
|
+
handler: Callable[[Dict[str, Any]], Awaitable[Any]] # async function
|
|
329
|
+
|
|
330
|
+
# Optional metadata
|
|
331
|
+
category: Optional[str] = None # "database", "storage", "api", etc
|
|
332
|
+
source: str = "custom" # "plugin", "mcp", "custom"
|
|
333
|
+
plugin_name: Optional[str] = None # Which plugin provides this tool
|
|
334
|
+
|
|
335
|
+
# Safety features
|
|
336
|
+
timeout_seconds: Optional[int] = None # Execution timeout
|
|
337
|
+
|
|
338
|
+
def to_openai_function(self) -> Dict[str, Any]:
|
|
339
|
+
"""
|
|
340
|
+
Convert to OpenAI function calling format.
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
OpenAI function definition dict
|
|
344
|
+
|
|
345
|
+
Reference:
|
|
346
|
+
https://platform.openai.com/docs/guides/function-calling
|
|
347
|
+
"""
|
|
348
|
+
return {
|
|
349
|
+
"type": "function",
|
|
350
|
+
"function": {
|
|
351
|
+
"name": self.name,
|
|
352
|
+
"description": self.description,
|
|
353
|
+
"parameters": self.parameters # Already in correct JSON Schema format
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
def to_anthropic_tool(self) -> Dict[str, Any]:
|
|
358
|
+
"""
|
|
359
|
+
Convert to Anthropic tool format.
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
Anthropic tool definition dict
|
|
363
|
+
|
|
364
|
+
Reference:
|
|
365
|
+
https://docs.anthropic.com/claude/docs/tool-use
|
|
366
|
+
"""
|
|
367
|
+
return {
|
|
368
|
+
"name": self.name,
|
|
369
|
+
"description": self.description,
|
|
370
|
+
"input_schema": self.parameters # Already in correct JSON Schema format
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
def to_llm_function(self) -> Dict[str, Any]:
|
|
374
|
+
"""
|
|
375
|
+
Generic LLM function format (works for most providers).
|
|
376
|
+
|
|
377
|
+
For provider-specific formats, use to_openai_function() or to_anthropic_tool().
|
|
378
|
+
This format is used for prompt-based tool calling.
|
|
379
|
+
"""
|
|
380
|
+
return {
|
|
381
|
+
"name": self.name,
|
|
382
|
+
"description": self.description,
|
|
383
|
+
"parameters": self.parameters # Already in correct JSON Schema format
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
def to_prompt_description(self) -> str:
|
|
387
|
+
"""
|
|
388
|
+
Generate human-readable tool description for prompt injection.
|
|
389
|
+
|
|
390
|
+
Used in prompt-based tool calling (non-native function calling).
|
|
391
|
+
|
|
392
|
+
Returns:
|
|
393
|
+
Formatted tool description string
|
|
394
|
+
"""
|
|
395
|
+
params_desc = []
|
|
396
|
+
properties = self.parameters.get("properties", {})
|
|
397
|
+
required_params = self.parameters.get("required", [])
|
|
398
|
+
|
|
399
|
+
for param_name, param_info in properties.items():
|
|
400
|
+
required = " (required)" if param_name in required_params else " (optional)"
|
|
401
|
+
param_type = param_info.get("type", "any")
|
|
402
|
+
param_desc = param_info.get("description", "")
|
|
403
|
+
params_desc.append(
|
|
404
|
+
f" - {param_name} ({param_type}){required}: {param_desc}"
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
params_str = "\n".join(params_desc) if params_desc else " (no parameters)"
|
|
408
|
+
|
|
409
|
+
return f"{self.name}: {self.description}\nParameters:\n{params_str}"
|
|
410
|
+
|
|
411
|
+
async def execute(self, arguments: Dict[str, Any]) -> Any:
|
|
412
|
+
"""
|
|
413
|
+
Execute the tool with given arguments.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
arguments: Tool arguments matching the parameter schema
|
|
417
|
+
|
|
418
|
+
Returns:
|
|
419
|
+
Tool execution result
|
|
420
|
+
|
|
421
|
+
Raises:
|
|
422
|
+
RuntimeError: If tool execution fails or times out
|
|
423
|
+
"""
|
|
424
|
+
if not callable(self.handler):
|
|
425
|
+
raise RuntimeError(f"Tool '{self.name}' has non-callable handler")
|
|
426
|
+
|
|
427
|
+
# Execute with timeout if specified
|
|
428
|
+
if self.timeout_seconds:
|
|
429
|
+
try:
|
|
430
|
+
result = await asyncio.wait_for(
|
|
431
|
+
self.handler(arguments),
|
|
432
|
+
timeout=self.timeout_seconds
|
|
433
|
+
)
|
|
434
|
+
return result
|
|
435
|
+
except asyncio.TimeoutError:
|
|
436
|
+
raise RuntimeError(
|
|
437
|
+
f"Tool '{self.name}' execution timed out after {self.timeout_seconds}s"
|
|
438
|
+
)
|
|
439
|
+
else:
|
|
440
|
+
# No timeout
|
|
441
|
+
return await self.handler(arguments)
|
|
442
|
+
|
|
443
|
+
@classmethod
|
|
444
|
+
def from_mcp_tool(cls, mcp_tool, mcp_registry) -> 'AgentTool':
|
|
445
|
+
"""
|
|
446
|
+
Create AgentTool from an MCP tool.
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
mcp_tool: MCPTool instance from MCP plugin
|
|
450
|
+
mcp_registry: MCPToolRegistry for routing calls
|
|
451
|
+
|
|
452
|
+
Returns:
|
|
453
|
+
AgentTool instance that wraps the MCP tool
|
|
454
|
+
"""
|
|
455
|
+
# Create handler that routes to MCP registry
|
|
456
|
+
async def mcp_handler(arguments: Dict[str, Any]) -> Any:
|
|
457
|
+
return await mcp_registry.call_tool(mcp_tool.name, arguments)
|
|
458
|
+
|
|
459
|
+
return cls(
|
|
460
|
+
name=mcp_tool.name,
|
|
461
|
+
description=mcp_tool.description,
|
|
462
|
+
parameters=mcp_tool.input_schema.get("properties", {}),
|
|
463
|
+
handler=mcp_handler,
|
|
464
|
+
source="mcp",
|
|
465
|
+
category="mcp"
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
class ToolRegistry:
|
|
470
|
+
"""
|
|
471
|
+
Registry for managing tools from multiple sources.
|
|
472
|
+
|
|
473
|
+
Used internally by agents to aggregate tools from plugins, MCP servers,
|
|
474
|
+
and custom functions.
|
|
475
|
+
"""
|
|
476
|
+
|
|
477
|
+
def __init__(self):
|
|
478
|
+
"""Initialize empty tool registry"""
|
|
479
|
+
self.tools: List[AgentTool] = []
|
|
480
|
+
self._tool_map: Dict[str, AgentTool] = {}
|
|
481
|
+
|
|
482
|
+
def register(self, tool: AgentTool) -> None:
|
|
483
|
+
"""
|
|
484
|
+
Register a tool.
|
|
485
|
+
|
|
486
|
+
Args:
|
|
487
|
+
tool: AgentTool to register
|
|
488
|
+
"""
|
|
489
|
+
if tool.name in self._tool_map:
|
|
490
|
+
logger.warning(
|
|
491
|
+
f"Tool '{tool.name}' already registered. "
|
|
492
|
+
f"Overwriting (old source: {self._tool_map[tool.name].source}, "
|
|
493
|
+
f"new source: {tool.source})"
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
self.tools.append(tool)
|
|
497
|
+
self._tool_map[tool.name] = tool
|
|
498
|
+
|
|
499
|
+
logger.debug(f"Registered tool: {tool.name} (source: {tool.source})")
|
|
500
|
+
|
|
501
|
+
def register_many(self, tools: List[AgentTool]) -> None:
|
|
502
|
+
"""
|
|
503
|
+
Register multiple tools at once.
|
|
504
|
+
|
|
505
|
+
Args:
|
|
506
|
+
tools: List of AgentTool instances
|
|
507
|
+
"""
|
|
508
|
+
for tool in tools:
|
|
509
|
+
self.register(tool)
|
|
510
|
+
|
|
511
|
+
def get(self, name: str) -> Optional[AgentTool]:
|
|
512
|
+
"""
|
|
513
|
+
Get tool by name.
|
|
514
|
+
|
|
515
|
+
Args:
|
|
516
|
+
name: Tool name
|
|
517
|
+
|
|
518
|
+
Returns:
|
|
519
|
+
AgentTool instance or None if not found
|
|
520
|
+
"""
|
|
521
|
+
return self._tool_map.get(name)
|
|
522
|
+
|
|
523
|
+
async def execute(self, name: str, arguments: Dict[str, Any]) -> Any:
|
|
524
|
+
"""
|
|
525
|
+
Execute a tool by name.
|
|
526
|
+
|
|
527
|
+
Args:
|
|
528
|
+
name: Tool name
|
|
529
|
+
arguments: Tool arguments
|
|
530
|
+
|
|
531
|
+
Returns:
|
|
532
|
+
Tool execution result
|
|
533
|
+
|
|
534
|
+
Raises:
|
|
535
|
+
RuntimeError: If tool not found or execution fails
|
|
536
|
+
"""
|
|
537
|
+
tool = self.get(name)
|
|
538
|
+
if not tool:
|
|
539
|
+
available = [t.name for t in self.tools]
|
|
540
|
+
raise RuntimeError(
|
|
541
|
+
f"Tool '{name}' not found. Available tools: {', '.join(available)}"
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
return await tool.execute(arguments)
|
|
545
|
+
|
|
546
|
+
@property
|
|
547
|
+
def tool_count(self) -> int:
|
|
548
|
+
"""Total number of registered tools"""
|
|
549
|
+
return len(self.tools)
|
|
550
|
+
|
|
551
|
+
@property
|
|
552
|
+
def tool_names(self) -> List[str]:
|
|
553
|
+
"""List of all tool names"""
|
|
554
|
+
return list(self._tool_map.keys())
|