agnt5 0.1.0__cp39-abi3-macosx_11_0_arm64.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.
- agnt5/__init__.py +307 -0
- agnt5/__pycache__/__init__.cpython-311.pyc +0 -0
- agnt5/__pycache__/agent.cpython-311.pyc +0 -0
- agnt5/__pycache__/context.cpython-311.pyc +0 -0
- agnt5/__pycache__/durable.cpython-311.pyc +0 -0
- agnt5/__pycache__/extraction.cpython-311.pyc +0 -0
- agnt5/__pycache__/memory.cpython-311.pyc +0 -0
- agnt5/__pycache__/reflection.cpython-311.pyc +0 -0
- agnt5/__pycache__/runtime.cpython-311.pyc +0 -0
- agnt5/__pycache__/task.cpython-311.pyc +0 -0
- agnt5/__pycache__/tool.cpython-311.pyc +0 -0
- agnt5/__pycache__/tracing.cpython-311.pyc +0 -0
- agnt5/__pycache__/types.cpython-311.pyc +0 -0
- agnt5/__pycache__/workflow.cpython-311.pyc +0 -0
- agnt5/_core.abi3.so +0 -0
- agnt5/agent.py +1086 -0
- agnt5/context.py +406 -0
- agnt5/durable.py +1050 -0
- agnt5/extraction.py +410 -0
- agnt5/llm/__init__.py +179 -0
- agnt5/llm/__pycache__/__init__.cpython-311.pyc +0 -0
- agnt5/llm/__pycache__/anthropic.cpython-311.pyc +0 -0
- agnt5/llm/__pycache__/azure.cpython-311.pyc +0 -0
- agnt5/llm/__pycache__/base.cpython-311.pyc +0 -0
- agnt5/llm/__pycache__/google.cpython-311.pyc +0 -0
- agnt5/llm/__pycache__/mistral.cpython-311.pyc +0 -0
- agnt5/llm/__pycache__/openai.cpython-311.pyc +0 -0
- agnt5/llm/__pycache__/together.cpython-311.pyc +0 -0
- agnt5/llm/anthropic.py +319 -0
- agnt5/llm/azure.py +348 -0
- agnt5/llm/base.py +315 -0
- agnt5/llm/google.py +373 -0
- agnt5/llm/mistral.py +330 -0
- agnt5/llm/model_registry.py +467 -0
- agnt5/llm/models.json +227 -0
- agnt5/llm/openai.py +334 -0
- agnt5/llm/together.py +377 -0
- agnt5/memory.py +746 -0
- agnt5/reflection.py +514 -0
- agnt5/runtime.py +699 -0
- agnt5/task.py +476 -0
- agnt5/testing.py +451 -0
- agnt5/tool.py +516 -0
- agnt5/tracing.py +624 -0
- agnt5/types.py +210 -0
- agnt5/workflow.py +897 -0
- agnt5-0.1.0.dist-info/METADATA +93 -0
- agnt5-0.1.0.dist-info/RECORD +49 -0
- agnt5-0.1.0.dist-info/WHEEL +4 -0
agnt5/tool.py
ADDED
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tool implementation for the AGNT5 SDK.
|
|
3
|
+
|
|
4
|
+
Tools are functions that agents can call to perform specific tasks.
|
|
5
|
+
They support automatic schema generation, validation, and error handling.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Callable, Any, Dict, Optional, List, Union, get_type_hints
|
|
9
|
+
import inspect
|
|
10
|
+
import asyncio
|
|
11
|
+
import functools
|
|
12
|
+
import json
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
import logging
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
|
|
17
|
+
from .types import ToolConfig
|
|
18
|
+
from .context import get_context
|
|
19
|
+
from .durable import durable, DurableContext
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Tool:
|
|
26
|
+
"""
|
|
27
|
+
A tool that can be called by agents.
|
|
28
|
+
|
|
29
|
+
Example:
|
|
30
|
+
```python
|
|
31
|
+
from agnt5 import Tool
|
|
32
|
+
|
|
33
|
+
# Create a tool from a function
|
|
34
|
+
def calculate_sum(a: int, b: int) -> int:
|
|
35
|
+
'''Calculate the sum of two numbers.'''
|
|
36
|
+
return a + b
|
|
37
|
+
|
|
38
|
+
tool = Tool(calculate_sum)
|
|
39
|
+
|
|
40
|
+
# Or use the decorator
|
|
41
|
+
@tool
|
|
42
|
+
def search_web(query: str) -> str:
|
|
43
|
+
'''Search the web for information.'''
|
|
44
|
+
return f"Results for: {query}"
|
|
45
|
+
```
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
func: Callable,
|
|
51
|
+
*,
|
|
52
|
+
name: Optional[str] = None,
|
|
53
|
+
description: Optional[str] = None,
|
|
54
|
+
config: Optional[ToolConfig] = None,
|
|
55
|
+
):
|
|
56
|
+
"""Initialize a Tool."""
|
|
57
|
+
self.func = func
|
|
58
|
+
self.is_async = inspect.iscoroutinefunction(func)
|
|
59
|
+
|
|
60
|
+
# Extract metadata from function
|
|
61
|
+
self.name = name or func.__name__
|
|
62
|
+
self.description = description or inspect.getdoc(func) or f"Tool: {self.name}"
|
|
63
|
+
|
|
64
|
+
# Extract parameters schema
|
|
65
|
+
self.parameters = self._extract_parameters()
|
|
66
|
+
self.returns = self._extract_returns()
|
|
67
|
+
|
|
68
|
+
# Configuration
|
|
69
|
+
if config:
|
|
70
|
+
self.config = config
|
|
71
|
+
else:
|
|
72
|
+
self.config = ToolConfig(
|
|
73
|
+
name=self.name,
|
|
74
|
+
description=self.description,
|
|
75
|
+
parameters=self.parameters,
|
|
76
|
+
returns=self.returns,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def _extract_parameters(self) -> Dict[str, Any]:
|
|
80
|
+
"""Extract parameter schema from function signature."""
|
|
81
|
+
sig = inspect.signature(self.func)
|
|
82
|
+
hints = get_type_hints(self.func)
|
|
83
|
+
|
|
84
|
+
properties = {}
|
|
85
|
+
required = []
|
|
86
|
+
|
|
87
|
+
for param_name, param in sig.parameters.items():
|
|
88
|
+
if param_name == "self":
|
|
89
|
+
continue
|
|
90
|
+
|
|
91
|
+
# Get type hint
|
|
92
|
+
param_type = hints.get(param_name, Any)
|
|
93
|
+
|
|
94
|
+
# Convert to JSON schema type
|
|
95
|
+
schema_type = self._python_type_to_json_schema(param_type)
|
|
96
|
+
properties[param_name] = schema_type
|
|
97
|
+
|
|
98
|
+
# Check if required
|
|
99
|
+
if param.default == param.empty:
|
|
100
|
+
required.append(param_name)
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
"type": "object",
|
|
104
|
+
"properties": properties,
|
|
105
|
+
"required": required,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
def _extract_returns(self) -> Optional[Dict[str, Any]]:
|
|
109
|
+
"""Extract return type schema."""
|
|
110
|
+
hints = get_type_hints(self.func)
|
|
111
|
+
return_type = hints.get("return", Any)
|
|
112
|
+
|
|
113
|
+
if return_type is None or return_type is type(None):
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
return self._python_type_to_json_schema(return_type)
|
|
117
|
+
|
|
118
|
+
def _python_type_to_json_schema(self, python_type: type) -> Dict[str, Any]:
|
|
119
|
+
"""Convert Python type to JSON schema."""
|
|
120
|
+
# Handle basic types
|
|
121
|
+
type_mapping = {
|
|
122
|
+
str: {"type": "string"},
|
|
123
|
+
int: {"type": "integer"},
|
|
124
|
+
float: {"type": "number"},
|
|
125
|
+
bool: {"type": "boolean"},
|
|
126
|
+
list: {"type": "array"},
|
|
127
|
+
dict: {"type": "object"},
|
|
128
|
+
Any: {"type": "any"},
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# Handle Optional types
|
|
132
|
+
origin = getattr(python_type, "__origin__", None)
|
|
133
|
+
if origin is Union:
|
|
134
|
+
args = python_type.__args__
|
|
135
|
+
# Check if it's Optional (Union with None)
|
|
136
|
+
if len(args) == 2 and type(None) in args:
|
|
137
|
+
non_none_type = args[0] if args[1] is type(None) else args[1]
|
|
138
|
+
schema = self._python_type_to_json_schema(non_none_type)
|
|
139
|
+
schema["nullable"] = True
|
|
140
|
+
return schema
|
|
141
|
+
|
|
142
|
+
# Handle List types
|
|
143
|
+
if origin is list:
|
|
144
|
+
args = getattr(python_type, "__args__", ())
|
|
145
|
+
if args:
|
|
146
|
+
item_type = args[0]
|
|
147
|
+
return {
|
|
148
|
+
"type": "array",
|
|
149
|
+
"items": self._python_type_to_json_schema(item_type),
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
# Handle Dict types
|
|
153
|
+
if origin is dict:
|
|
154
|
+
return {"type": "object"}
|
|
155
|
+
|
|
156
|
+
# Default mapping
|
|
157
|
+
return type_mapping.get(python_type, {"type": "any"})
|
|
158
|
+
|
|
159
|
+
async def invoke(self, **kwargs) -> Any:
|
|
160
|
+
"""
|
|
161
|
+
Invoke the tool with the given arguments.
|
|
162
|
+
|
|
163
|
+
This method provides automatic durability, retries, and state management.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
**kwargs: Tool arguments
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Tool output
|
|
170
|
+
"""
|
|
171
|
+
# Check if this is a durable tool (has durability enabled)
|
|
172
|
+
if getattr(self.config, 'enable_durability', True):
|
|
173
|
+
return await self._invoke_durable(kwargs)
|
|
174
|
+
else:
|
|
175
|
+
return await self._invoke_legacy(kwargs)
|
|
176
|
+
|
|
177
|
+
async def invoke_durable(self, ctx: DurableContext, **kwargs) -> Any:
|
|
178
|
+
"""
|
|
179
|
+
Invoke the tool with a durable context for enhanced state management.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
ctx: Durable execution context
|
|
183
|
+
**kwargs: Tool arguments
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
Tool output
|
|
187
|
+
"""
|
|
188
|
+
# Store tool invocation in durable state
|
|
189
|
+
await ctx.state.set(f"tool_{self.name}_input", kwargs)
|
|
190
|
+
await ctx.state.set(f"tool_{self.name}_started_at", datetime.now().isoformat())
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
result = await self._invoke_durable(kwargs, ctx)
|
|
194
|
+
await ctx.state.set(f"tool_{self.name}_result", result)
|
|
195
|
+
await ctx.state.set(f"tool_{self.name}_completed_at", datetime.now().isoformat())
|
|
196
|
+
return result
|
|
197
|
+
except Exception as e:
|
|
198
|
+
await ctx.state.set(f"tool_{self.name}_error", str(e))
|
|
199
|
+
await ctx.state.set(f"tool_{self.name}_failed_at", datetime.now().isoformat())
|
|
200
|
+
raise
|
|
201
|
+
|
|
202
|
+
async def _invoke_durable(self, kwargs: Dict[str, Any], ctx: Optional[DurableContext] = None) -> Any:
|
|
203
|
+
"""
|
|
204
|
+
Execute tool with full durability using durable function.
|
|
205
|
+
"""
|
|
206
|
+
@durable.function(
|
|
207
|
+
name=f"tool_{self.name}",
|
|
208
|
+
max_retries=self.config.max_retries,
|
|
209
|
+
timeout=self.config.timeout,
|
|
210
|
+
deterministic=True,
|
|
211
|
+
)
|
|
212
|
+
async def durable_tool_execution(durable_ctx: DurableContext, tool_kwargs: Dict[str, Any]) -> Any:
|
|
213
|
+
# Store execution metadata
|
|
214
|
+
await durable_ctx.state.set("tool_name", self.name)
|
|
215
|
+
await durable_ctx.state.set("tool_description", self.description)
|
|
216
|
+
await durable_ctx.state.set("tool_input", tool_kwargs)
|
|
217
|
+
await durable_ctx.state.set("execution_started", datetime.now().isoformat())
|
|
218
|
+
|
|
219
|
+
# Validate arguments within durable context
|
|
220
|
+
self._validate_arguments(tool_kwargs)
|
|
221
|
+
|
|
222
|
+
# Execute the tool function
|
|
223
|
+
try:
|
|
224
|
+
if self.is_async:
|
|
225
|
+
result = await self.func(**tool_kwargs)
|
|
226
|
+
else:
|
|
227
|
+
# Run sync function in thread pool
|
|
228
|
+
loop = asyncio.get_event_loop()
|
|
229
|
+
result = await loop.run_in_executor(None, lambda: self.func(**tool_kwargs))
|
|
230
|
+
|
|
231
|
+
# Store successful result
|
|
232
|
+
await durable_ctx.state.set("tool_result", result)
|
|
233
|
+
await durable_ctx.state.set("execution_completed", datetime.now().isoformat())
|
|
234
|
+
await durable_ctx.state.set("execution_status", "success")
|
|
235
|
+
|
|
236
|
+
logger.debug(f"Tool '{self.name}' completed successfully")
|
|
237
|
+
return result
|
|
238
|
+
|
|
239
|
+
except Exception as e:
|
|
240
|
+
# Store error information
|
|
241
|
+
await durable_ctx.state.set("tool_error", str(e))
|
|
242
|
+
await durable_ctx.state.set("execution_failed", datetime.now().isoformat())
|
|
243
|
+
await durable_ctx.state.set("execution_status", "failed")
|
|
244
|
+
|
|
245
|
+
logger.error(f"Tool '{self.name}' failed: {e}")
|
|
246
|
+
raise
|
|
247
|
+
|
|
248
|
+
# Execute the durable function
|
|
249
|
+
return await durable_tool_execution(kwargs)
|
|
250
|
+
|
|
251
|
+
async def _invoke_legacy(self, kwargs: Dict[str, Any]) -> Any:
|
|
252
|
+
"""
|
|
253
|
+
Legacy invocation method for backward compatibility.
|
|
254
|
+
"""
|
|
255
|
+
ctx = get_context()
|
|
256
|
+
|
|
257
|
+
# Validate arguments
|
|
258
|
+
self._validate_arguments(kwargs)
|
|
259
|
+
|
|
260
|
+
# Log invocation
|
|
261
|
+
logger.debug(f"Invoking tool '{self.name}' with args: {kwargs}")
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
# Execute with timeout if configured
|
|
265
|
+
if self.config.timeout:
|
|
266
|
+
return await self._invoke_with_timeout(kwargs)
|
|
267
|
+
else:
|
|
268
|
+
return await self._invoke_direct(kwargs)
|
|
269
|
+
except Exception as e:
|
|
270
|
+
logger.error(f"Tool '{self.name}' failed: {e}")
|
|
271
|
+
|
|
272
|
+
# Retry if configured
|
|
273
|
+
if self.config.max_retries > 1:
|
|
274
|
+
return await self._invoke_with_retry(kwargs)
|
|
275
|
+
else:
|
|
276
|
+
raise
|
|
277
|
+
|
|
278
|
+
async def _invoke_direct(self, kwargs: Dict[str, Any]) -> Any:
|
|
279
|
+
"""Direct invocation of the tool."""
|
|
280
|
+
if self.is_async:
|
|
281
|
+
return await self.func(**kwargs)
|
|
282
|
+
else:
|
|
283
|
+
# Run sync function in thread pool
|
|
284
|
+
loop = asyncio.get_event_loop()
|
|
285
|
+
return await loop.run_in_executor(None, self.func, **kwargs)
|
|
286
|
+
|
|
287
|
+
async def _invoke_with_timeout(self, kwargs: Dict[str, Any]) -> Any:
|
|
288
|
+
"""Invoke with timeout."""
|
|
289
|
+
try:
|
|
290
|
+
return await asyncio.wait_for(
|
|
291
|
+
self._invoke_direct(kwargs),
|
|
292
|
+
timeout=self.config.timeout,
|
|
293
|
+
)
|
|
294
|
+
except asyncio.TimeoutError:
|
|
295
|
+
raise TimeoutError(f"Tool '{self.name}' timed out after {self.config.timeout}s")
|
|
296
|
+
|
|
297
|
+
async def _invoke_with_retry(self, kwargs: Dict[str, Any]) -> Any:
|
|
298
|
+
"""Invoke with retry logic."""
|
|
299
|
+
last_error = None
|
|
300
|
+
|
|
301
|
+
for attempt in range(self.config.max_retries):
|
|
302
|
+
try:
|
|
303
|
+
return await self._invoke_with_timeout(kwargs)
|
|
304
|
+
except Exception as e:
|
|
305
|
+
last_error = e
|
|
306
|
+
if attempt < self.config.max_retries - 1:
|
|
307
|
+
await asyncio.sleep(self.config.retry_delay * (attempt + 1))
|
|
308
|
+
logger.warning(f"Tool '{self.name}' attempt {attempt + 1} failed, retrying...")
|
|
309
|
+
|
|
310
|
+
raise last_error
|
|
311
|
+
|
|
312
|
+
def _validate_arguments(self, kwargs: Dict[str, Any]) -> None:
|
|
313
|
+
"""Validate tool arguments against schema."""
|
|
314
|
+
properties = self.parameters.get("properties", {})
|
|
315
|
+
required = self.parameters.get("required", [])
|
|
316
|
+
|
|
317
|
+
# Check required parameters
|
|
318
|
+
for param in required:
|
|
319
|
+
if param not in kwargs:
|
|
320
|
+
raise ValueError(f"Missing required parameter: {param}")
|
|
321
|
+
|
|
322
|
+
# Check for unknown parameters
|
|
323
|
+
for key in kwargs:
|
|
324
|
+
if key not in properties:
|
|
325
|
+
raise ValueError(f"Unknown parameter: {key}")
|
|
326
|
+
|
|
327
|
+
def get_schema(self) -> Dict[str, Any]:
|
|
328
|
+
"""Get OpenAI function schema for this tool."""
|
|
329
|
+
return {
|
|
330
|
+
"type": "function",
|
|
331
|
+
"function": {
|
|
332
|
+
"name": self.name,
|
|
333
|
+
"description": self.description,
|
|
334
|
+
"parameters": self.parameters,
|
|
335
|
+
},
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
def __call__(self, **kwargs) -> Any:
|
|
339
|
+
"""Allow synchronous calling of the tool."""
|
|
340
|
+
return asyncio.run(self.invoke(**kwargs))
|
|
341
|
+
|
|
342
|
+
def __repr__(self) -> str:
|
|
343
|
+
durability_status = "durable" if getattr(self.config, 'enable_durability', True) else "non-durable"
|
|
344
|
+
return f"Tool(name='{self.name}', {durability_status}, retries={self.config.max_retries})"
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def tool(
|
|
348
|
+
func: Optional[Callable] = None,
|
|
349
|
+
*,
|
|
350
|
+
name: Optional[str] = None,
|
|
351
|
+
description: Optional[str] = None,
|
|
352
|
+
timeout: Optional[float] = None,
|
|
353
|
+
max_retries: int = 3,
|
|
354
|
+
retry_delay: float = 1.0,
|
|
355
|
+
enable_durability: bool = True,
|
|
356
|
+
) -> Union[Tool, Callable[[Callable], Tool]]:
|
|
357
|
+
"""
|
|
358
|
+
Decorator to create a durable tool from a function.
|
|
359
|
+
|
|
360
|
+
Tools created with this decorator automatically get:
|
|
361
|
+
- Durability and state persistence
|
|
362
|
+
- Automatic retries with exponential backoff
|
|
363
|
+
- Timeout handling
|
|
364
|
+
- Comprehensive logging and monitoring
|
|
365
|
+
- Integration with durable flows and contexts
|
|
366
|
+
|
|
367
|
+
Example:
|
|
368
|
+
```python
|
|
369
|
+
@tool
|
|
370
|
+
def calculate_sum(a: int, b: int) -> int:
|
|
371
|
+
'''Calculate the sum of two numbers.'''
|
|
372
|
+
return a + b
|
|
373
|
+
|
|
374
|
+
@tool(name="web_search", timeout=30.0, max_retries=5)
|
|
375
|
+
async def search(query: str) -> str:
|
|
376
|
+
'''Search the web for information.'''
|
|
377
|
+
# This tool will automatically retry on failures
|
|
378
|
+
# and persist its state across interruptions
|
|
379
|
+
return f"Results for: {query}"
|
|
380
|
+
|
|
381
|
+
@tool(enable_durability=False)
|
|
382
|
+
def simple_tool(value: str) -> str:
|
|
383
|
+
'''A simple tool without durability (for backwards compatibility).'''
|
|
384
|
+
return f"Processed: {value}"
|
|
385
|
+
```
|
|
386
|
+
"""
|
|
387
|
+
def decorator(f: Callable) -> Tool:
|
|
388
|
+
config = ToolConfig(
|
|
389
|
+
name=name or f.__name__,
|
|
390
|
+
description=description or inspect.getdoc(f) or f"Tool: {f.__name__}",
|
|
391
|
+
timeout=timeout,
|
|
392
|
+
max_retries=max_retries,
|
|
393
|
+
retry_delay=retry_delay,
|
|
394
|
+
enable_durability=enable_durability,
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
tool_instance = Tool(f, config=config)
|
|
398
|
+
|
|
399
|
+
# If durability is enabled, register as a durable function
|
|
400
|
+
if enable_durability:
|
|
401
|
+
# Create a durable function wrapper for the tool
|
|
402
|
+
durable_func = durable.function(
|
|
403
|
+
name=f"tool_{config.name}",
|
|
404
|
+
max_retries=max_retries,
|
|
405
|
+
timeout=timeout,
|
|
406
|
+
deterministic=True,
|
|
407
|
+
)(f)
|
|
408
|
+
|
|
409
|
+
# Store reference to durable function in tool
|
|
410
|
+
tool_instance._durable_func = durable_func
|
|
411
|
+
|
|
412
|
+
return tool_instance
|
|
413
|
+
|
|
414
|
+
if func is None:
|
|
415
|
+
# Called with arguments: @tool(name="...", ...)
|
|
416
|
+
return decorator
|
|
417
|
+
else:
|
|
418
|
+
# Called without arguments: @tool
|
|
419
|
+
return decorator(func)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def durable_tool(
|
|
423
|
+
func: Optional[Callable] = None,
|
|
424
|
+
*,
|
|
425
|
+
name: Optional[str] = None,
|
|
426
|
+
description: Optional[str] = None,
|
|
427
|
+
timeout: Optional[float] = None,
|
|
428
|
+
max_retries: int = 3,
|
|
429
|
+
retry_delay: float = 1.0,
|
|
430
|
+
) -> Union[Tool, Callable[[Callable], Tool]]:
|
|
431
|
+
"""
|
|
432
|
+
Explicit decorator for creating durable tools.
|
|
433
|
+
|
|
434
|
+
This is an alias for @tool with enable_durability=True (the default).
|
|
435
|
+
Use this when you want to be explicit about durability.
|
|
436
|
+
"""
|
|
437
|
+
return tool(
|
|
438
|
+
func,
|
|
439
|
+
name=name,
|
|
440
|
+
description=description,
|
|
441
|
+
timeout=timeout,
|
|
442
|
+
max_retries=max_retries,
|
|
443
|
+
retry_delay=retry_delay,
|
|
444
|
+
enable_durability=True,
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
class ToolKit:
|
|
449
|
+
"""
|
|
450
|
+
A collection of related tools.
|
|
451
|
+
|
|
452
|
+
Example:
|
|
453
|
+
```python
|
|
454
|
+
from agnt5 import ToolKit
|
|
455
|
+
|
|
456
|
+
class FileTools(ToolKit):
|
|
457
|
+
'''Tools for file operations.'''
|
|
458
|
+
|
|
459
|
+
@tool
|
|
460
|
+
def read_file(self, path: str) -> str:
|
|
461
|
+
'''Read contents of a file.'''
|
|
462
|
+
with open(path, 'r') as f:
|
|
463
|
+
return f.read()
|
|
464
|
+
|
|
465
|
+
@tool
|
|
466
|
+
def write_file(self, path: str, content: str) -> None:
|
|
467
|
+
'''Write content to a file.'''
|
|
468
|
+
with open(path, 'w') as f:
|
|
469
|
+
f.write(content)
|
|
470
|
+
|
|
471
|
+
# Use the toolkit
|
|
472
|
+
file_tools = FileTools()
|
|
473
|
+
tools = file_tools.get_tools()
|
|
474
|
+
```
|
|
475
|
+
"""
|
|
476
|
+
|
|
477
|
+
def __init__(self):
|
|
478
|
+
"""Initialize the toolkit."""
|
|
479
|
+
self._tools: Dict[str, Tool] = {}
|
|
480
|
+
self._discover_tools()
|
|
481
|
+
|
|
482
|
+
def _discover_tools(self) -> None:
|
|
483
|
+
"""Discover tools defined as methods."""
|
|
484
|
+
for name in dir(self):
|
|
485
|
+
if name.startswith("_"):
|
|
486
|
+
continue
|
|
487
|
+
|
|
488
|
+
attr = getattr(self, name)
|
|
489
|
+
if isinstance(attr, Tool):
|
|
490
|
+
self._tools[attr.name] = attr
|
|
491
|
+
elif hasattr(attr, "__tool__"):
|
|
492
|
+
# Method decorated with @tool
|
|
493
|
+
tool_instance = attr.__tool__
|
|
494
|
+
# Bind method to self
|
|
495
|
+
bound_func = functools.partial(tool_instance.func, self)
|
|
496
|
+
tool_instance.func = bound_func
|
|
497
|
+
self._tools[tool_instance.name] = tool_instance
|
|
498
|
+
|
|
499
|
+
def get_tools(self) -> List[Tool]:
|
|
500
|
+
"""Get all tools in this toolkit."""
|
|
501
|
+
return list(self._tools.values())
|
|
502
|
+
|
|
503
|
+
def get_tool(self, name: str) -> Optional[Tool]:
|
|
504
|
+
"""Get a specific tool by name."""
|
|
505
|
+
return self._tools.get(name)
|
|
506
|
+
|
|
507
|
+
def get_durable_tools(self) -> List[Tool]:
|
|
508
|
+
"""Get all durable tools in this toolkit."""
|
|
509
|
+
return [
|
|
510
|
+
tool for tool in self._tools.values()
|
|
511
|
+
if getattr(tool.config, 'enable_durability', True)
|
|
512
|
+
]
|
|
513
|
+
|
|
514
|
+
def get_tool_schemas(self) -> List[Dict[str, Any]]:
|
|
515
|
+
"""Get OpenAI function schemas for all tools."""
|
|
516
|
+
return [tool.get_schema() for tool in self._tools.values()]
|