proxilion 0.0.1__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.
- proxilion/__init__.py +136 -0
- proxilion/audit/__init__.py +133 -0
- proxilion/audit/base_exporters.py +527 -0
- proxilion/audit/compliance/__init__.py +130 -0
- proxilion/audit/compliance/base.py +457 -0
- proxilion/audit/compliance/eu_ai_act.py +603 -0
- proxilion/audit/compliance/iso27001.py +544 -0
- proxilion/audit/compliance/soc2.py +491 -0
- proxilion/audit/events.py +493 -0
- proxilion/audit/explainability.py +1173 -0
- proxilion/audit/exporters/__init__.py +58 -0
- proxilion/audit/exporters/aws_s3.py +636 -0
- proxilion/audit/exporters/azure_storage.py +608 -0
- proxilion/audit/exporters/cloud_base.py +468 -0
- proxilion/audit/exporters/gcp_storage.py +570 -0
- proxilion/audit/exporters/multi_exporter.py +498 -0
- proxilion/audit/hash_chain.py +652 -0
- proxilion/audit/logger.py +543 -0
- proxilion/caching/__init__.py +49 -0
- proxilion/caching/tool_cache.py +633 -0
- proxilion/context/__init__.py +73 -0
- proxilion/context/context_window.py +556 -0
- proxilion/context/message_history.py +505 -0
- proxilion/context/session.py +735 -0
- proxilion/contrib/__init__.py +51 -0
- proxilion/contrib/anthropic.py +609 -0
- proxilion/contrib/google.py +1012 -0
- proxilion/contrib/langchain.py +641 -0
- proxilion/contrib/mcp.py +893 -0
- proxilion/contrib/openai.py +646 -0
- proxilion/core.py +3058 -0
- proxilion/decorators.py +966 -0
- proxilion/engines/__init__.py +287 -0
- proxilion/engines/base.py +266 -0
- proxilion/engines/casbin_engine.py +412 -0
- proxilion/engines/opa_engine.py +493 -0
- proxilion/engines/simple.py +437 -0
- proxilion/exceptions.py +887 -0
- proxilion/guards/__init__.py +54 -0
- proxilion/guards/input_guard.py +522 -0
- proxilion/guards/output_guard.py +634 -0
- proxilion/observability/__init__.py +198 -0
- proxilion/observability/cost_tracker.py +866 -0
- proxilion/observability/hooks.py +683 -0
- proxilion/observability/metrics.py +798 -0
- proxilion/observability/session_cost_tracker.py +1063 -0
- proxilion/policies/__init__.py +67 -0
- proxilion/policies/base.py +304 -0
- proxilion/policies/builtin.py +486 -0
- proxilion/policies/registry.py +376 -0
- proxilion/providers/__init__.py +201 -0
- proxilion/providers/adapter.py +468 -0
- proxilion/providers/anthropic_adapter.py +330 -0
- proxilion/providers/gemini_adapter.py +391 -0
- proxilion/providers/openai_adapter.py +294 -0
- proxilion/py.typed +0 -0
- proxilion/resilience/__init__.py +81 -0
- proxilion/resilience/degradation.py +615 -0
- proxilion/resilience/fallback.py +555 -0
- proxilion/resilience/retry.py +554 -0
- proxilion/scheduling/__init__.py +57 -0
- proxilion/scheduling/priority_queue.py +419 -0
- proxilion/scheduling/scheduler.py +459 -0
- proxilion/security/__init__.py +244 -0
- proxilion/security/agent_trust.py +968 -0
- proxilion/security/behavioral_drift.py +794 -0
- proxilion/security/cascade_protection.py +869 -0
- proxilion/security/circuit_breaker.py +428 -0
- proxilion/security/cost_limiter.py +690 -0
- proxilion/security/idor_protection.py +460 -0
- proxilion/security/intent_capsule.py +849 -0
- proxilion/security/intent_validator.py +495 -0
- proxilion/security/memory_integrity.py +767 -0
- proxilion/security/rate_limiter.py +509 -0
- proxilion/security/scope_enforcer.py +680 -0
- proxilion/security/sequence_validator.py +636 -0
- proxilion/security/trust_boundaries.py +784 -0
- proxilion/streaming/__init__.py +70 -0
- proxilion/streaming/detector.py +761 -0
- proxilion/streaming/transformer.py +674 -0
- proxilion/timeouts/__init__.py +55 -0
- proxilion/timeouts/decorators.py +477 -0
- proxilion/timeouts/manager.py +545 -0
- proxilion/tools/__init__.py +69 -0
- proxilion/tools/decorators.py +493 -0
- proxilion/tools/registry.py +732 -0
- proxilion/types.py +339 -0
- proxilion/validation/__init__.py +93 -0
- proxilion/validation/pydantic_schema.py +351 -0
- proxilion/validation/schema.py +651 -0
- proxilion-0.0.1.dist-info/METADATA +872 -0
- proxilion-0.0.1.dist-info/RECORD +94 -0
- proxilion-0.0.1.dist-info/WHEEL +4 -0
- proxilion-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Decorators for tool registration.
|
|
3
|
+
|
|
4
|
+
Provides convenient decorators for registering functions as tools
|
|
5
|
+
with automatic schema inference from type hints.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import functools
|
|
11
|
+
import inspect
|
|
12
|
+
import logging
|
|
13
|
+
from collections.abc import Callable
|
|
14
|
+
from typing import Any, TypeVar, Union, get_args, get_origin, get_type_hints
|
|
15
|
+
|
|
16
|
+
from proxilion.tools.registry import (
|
|
17
|
+
RiskLevel,
|
|
18
|
+
ToolCategory,
|
|
19
|
+
ToolDefinition,
|
|
20
|
+
ToolRegistry,
|
|
21
|
+
get_global_registry,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def infer_schema_from_function(func: Callable[..., Any]) -> dict[str, Any]:
|
|
30
|
+
"""
|
|
31
|
+
Infer a JSON Schema from function signature and type hints.
|
|
32
|
+
|
|
33
|
+
Analyzes the function's parameters and type annotations to generate
|
|
34
|
+
a JSON Schema compatible with OpenAI/Anthropic tool definitions.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
func: The function to analyze.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
A JSON Schema dictionary describing the function's parameters.
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
>>> def search(query: str, max_results: int = 10) -> list[dict]:
|
|
44
|
+
... pass
|
|
45
|
+
>>> schema = infer_schema_from_function(search)
|
|
46
|
+
>>> schema["type"]
|
|
47
|
+
'object'
|
|
48
|
+
>>> "query" in schema["properties"]
|
|
49
|
+
True
|
|
50
|
+
"""
|
|
51
|
+
schema: dict[str, Any] = {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"properties": {},
|
|
54
|
+
"required": [],
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
sig = inspect.signature(func)
|
|
58
|
+
|
|
59
|
+
# Try to get type hints, handling potential errors
|
|
60
|
+
try:
|
|
61
|
+
hints = get_type_hints(func)
|
|
62
|
+
except Exception:
|
|
63
|
+
hints = {}
|
|
64
|
+
|
|
65
|
+
for param_name, param in sig.parameters.items():
|
|
66
|
+
# Skip self, cls, *args, **kwargs
|
|
67
|
+
if param_name in ("self", "cls"):
|
|
68
|
+
continue
|
|
69
|
+
if param.kind in (
|
|
70
|
+
inspect.Parameter.VAR_POSITIONAL,
|
|
71
|
+
inspect.Parameter.VAR_KEYWORD,
|
|
72
|
+
):
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
# Get type annotation
|
|
76
|
+
type_hint = hints.get(param_name, param.annotation)
|
|
77
|
+
if type_hint is inspect.Parameter.empty:
|
|
78
|
+
type_hint = Any
|
|
79
|
+
|
|
80
|
+
# Convert type to JSON Schema
|
|
81
|
+
prop_schema = _type_to_schema(type_hint)
|
|
82
|
+
|
|
83
|
+
# Add description from docstring if available
|
|
84
|
+
param_desc = _extract_param_description(func, param_name)
|
|
85
|
+
if param_desc:
|
|
86
|
+
prop_schema["description"] = param_desc
|
|
87
|
+
|
|
88
|
+
# Add default value if present
|
|
89
|
+
if param.default is not inspect.Parameter.empty:
|
|
90
|
+
if param.default is not None:
|
|
91
|
+
prop_schema["default"] = param.default
|
|
92
|
+
else:
|
|
93
|
+
# No default means required
|
|
94
|
+
schema["required"].append(param_name)
|
|
95
|
+
|
|
96
|
+
schema["properties"][param_name] = prop_schema
|
|
97
|
+
|
|
98
|
+
# Remove empty required list
|
|
99
|
+
if not schema["required"]:
|
|
100
|
+
del schema["required"]
|
|
101
|
+
|
|
102
|
+
return schema
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _type_to_schema(type_hint: Any) -> dict[str, Any]:
|
|
106
|
+
"""
|
|
107
|
+
Convert a Python type hint to JSON Schema.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
type_hint: The Python type hint.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
A JSON Schema dictionary for the type.
|
|
114
|
+
"""
|
|
115
|
+
# Handle None/NoneType
|
|
116
|
+
if type_hint is None or type_hint is type(None):
|
|
117
|
+
return {"type": "null"}
|
|
118
|
+
|
|
119
|
+
# Handle basic types
|
|
120
|
+
if type_hint is str:
|
|
121
|
+
return {"type": "string"}
|
|
122
|
+
if type_hint is int:
|
|
123
|
+
return {"type": "integer"}
|
|
124
|
+
if type_hint is float:
|
|
125
|
+
return {"type": "number"}
|
|
126
|
+
if type_hint is bool:
|
|
127
|
+
return {"type": "boolean"}
|
|
128
|
+
if type_hint is Any:
|
|
129
|
+
return {} # Any type
|
|
130
|
+
|
|
131
|
+
# Handle generic types
|
|
132
|
+
origin = get_origin(type_hint)
|
|
133
|
+
args = get_args(type_hint)
|
|
134
|
+
|
|
135
|
+
# Handle Optional (Union[X, None])
|
|
136
|
+
if origin is Union:
|
|
137
|
+
# Filter out NoneType
|
|
138
|
+
non_none_args = [a for a in args if a is not type(None)]
|
|
139
|
+
if len(non_none_args) == 1:
|
|
140
|
+
# Optional[X] -> X with nullable
|
|
141
|
+
schema = _type_to_schema(non_none_args[0])
|
|
142
|
+
# JSON Schema draft 2020-12 uses "type": ["string", "null"]
|
|
143
|
+
# But for compatibility, we'll just return the base type
|
|
144
|
+
return schema
|
|
145
|
+
else:
|
|
146
|
+
# Union of multiple types
|
|
147
|
+
return {"oneOf": [_type_to_schema(a) for a in non_none_args]}
|
|
148
|
+
|
|
149
|
+
# Handle list/List
|
|
150
|
+
if origin is list:
|
|
151
|
+
schema: dict[str, Any] = {"type": "array"}
|
|
152
|
+
if args:
|
|
153
|
+
schema["items"] = _type_to_schema(args[0])
|
|
154
|
+
return schema
|
|
155
|
+
|
|
156
|
+
# Handle dict/Dict
|
|
157
|
+
if origin is dict:
|
|
158
|
+
schema = {"type": "object"}
|
|
159
|
+
if len(args) >= 2:
|
|
160
|
+
schema["additionalProperties"] = _type_to_schema(args[1])
|
|
161
|
+
return schema
|
|
162
|
+
|
|
163
|
+
# Handle tuple/Tuple
|
|
164
|
+
if origin is tuple:
|
|
165
|
+
if args:
|
|
166
|
+
if len(args) == 2 and args[1] is Ellipsis:
|
|
167
|
+
# Tuple[X, ...] is like List[X]
|
|
168
|
+
return {"type": "array", "items": _type_to_schema(args[0])}
|
|
169
|
+
else:
|
|
170
|
+
# Fixed-length tuple
|
|
171
|
+
return {
|
|
172
|
+
"type": "array",
|
|
173
|
+
"prefixItems": [_type_to_schema(a) for a in args],
|
|
174
|
+
"minItems": len(args),
|
|
175
|
+
"maxItems": len(args),
|
|
176
|
+
}
|
|
177
|
+
return {"type": "array"}
|
|
178
|
+
|
|
179
|
+
# Handle set/Set
|
|
180
|
+
if origin is set or origin is frozenset:
|
|
181
|
+
schema = {"type": "array", "uniqueItems": True}
|
|
182
|
+
if args:
|
|
183
|
+
schema["items"] = _type_to_schema(args[0])
|
|
184
|
+
return schema
|
|
185
|
+
|
|
186
|
+
# Handle Literal
|
|
187
|
+
try:
|
|
188
|
+
from typing import Literal
|
|
189
|
+
|
|
190
|
+
if origin is Literal:
|
|
191
|
+
return {"enum": list(args)}
|
|
192
|
+
except ImportError:
|
|
193
|
+
pass
|
|
194
|
+
|
|
195
|
+
# Handle classes with __annotations__ (dataclasses, etc.)
|
|
196
|
+
if hasattr(type_hint, "__annotations__"):
|
|
197
|
+
properties = {}
|
|
198
|
+
required = []
|
|
199
|
+
annotations = getattr(type_hint, "__annotations__", {})
|
|
200
|
+
for field_name, field_type in annotations.items():
|
|
201
|
+
properties[field_name] = _type_to_schema(field_type)
|
|
202
|
+
# For dataclasses, check if field has default
|
|
203
|
+
if hasattr(type_hint, "__dataclass_fields__"):
|
|
204
|
+
field_info = type_hint.__dataclass_fields__.get(field_name)
|
|
205
|
+
if field_info and field_info.default is field_info.default_factory:
|
|
206
|
+
required.append(field_name)
|
|
207
|
+
else:
|
|
208
|
+
required.append(field_name)
|
|
209
|
+
|
|
210
|
+
schema = {"type": "object", "properties": properties}
|
|
211
|
+
if required:
|
|
212
|
+
schema["required"] = required
|
|
213
|
+
return schema
|
|
214
|
+
|
|
215
|
+
# Default to string for unknown types
|
|
216
|
+
return {"type": "string"}
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _extract_param_description(func: Callable[..., Any], param_name: str) -> str | None:
|
|
220
|
+
"""
|
|
221
|
+
Extract parameter description from function docstring.
|
|
222
|
+
|
|
223
|
+
Supports Google, NumPy, and Sphinx docstring formats.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
func: The function with docstring.
|
|
227
|
+
param_name: The parameter name to look for.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
The parameter description or None.
|
|
231
|
+
"""
|
|
232
|
+
docstring = func.__doc__
|
|
233
|
+
if not docstring:
|
|
234
|
+
return None
|
|
235
|
+
|
|
236
|
+
lines = docstring.split("\n")
|
|
237
|
+
|
|
238
|
+
# Try Google style: "param_name: description" or "param_name (type): description"
|
|
239
|
+
for i, line in enumerate(lines):
|
|
240
|
+
stripped = line.strip()
|
|
241
|
+
# Google style
|
|
242
|
+
if stripped.startswith(f"{param_name}:") or stripped.startswith(
|
|
243
|
+
f"{param_name} ("
|
|
244
|
+
):
|
|
245
|
+
# Extract description after colon
|
|
246
|
+
if ":" in stripped:
|
|
247
|
+
desc = stripped.split(":", 1)[1].strip()
|
|
248
|
+
# Check for continuation on next lines
|
|
249
|
+
j = i + 1
|
|
250
|
+
while j < len(lines):
|
|
251
|
+
next_line = lines[j]
|
|
252
|
+
if next_line.strip() and not next_line.strip().startswith(
|
|
253
|
+
tuple("abcdefghijklmnopqrstuvwxyz_")
|
|
254
|
+
):
|
|
255
|
+
# Continuation
|
|
256
|
+
desc += " " + next_line.strip()
|
|
257
|
+
j += 1
|
|
258
|
+
else:
|
|
259
|
+
break
|
|
260
|
+
return desc
|
|
261
|
+
|
|
262
|
+
# Sphinx style: ":param param_name: description"
|
|
263
|
+
if stripped.startswith(f":param {param_name}:"):
|
|
264
|
+
desc = stripped.split(":", 2)[2].strip()
|
|
265
|
+
return desc
|
|
266
|
+
|
|
267
|
+
# NumPy style: "param_name : type\n description"
|
|
268
|
+
if stripped == param_name or stripped.startswith(f"{param_name} :"):
|
|
269
|
+
if i + 1 < len(lines):
|
|
270
|
+
next_line = lines[i + 1].strip()
|
|
271
|
+
if next_line and not next_line.startswith(("-", "=")):
|
|
272
|
+
return next_line
|
|
273
|
+
|
|
274
|
+
return None
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def tool(
|
|
278
|
+
name: str | None = None,
|
|
279
|
+
description: str | None = None,
|
|
280
|
+
category: ToolCategory = ToolCategory.CUSTOM,
|
|
281
|
+
risk_level: RiskLevel = RiskLevel.LOW,
|
|
282
|
+
requires_approval: bool = False,
|
|
283
|
+
timeout: float | None = None,
|
|
284
|
+
registry: ToolRegistry | None = None,
|
|
285
|
+
enabled: bool = True,
|
|
286
|
+
**metadata: Any,
|
|
287
|
+
) -> Callable[[F], F]:
|
|
288
|
+
"""
|
|
289
|
+
Decorator to register a function as a tool.
|
|
290
|
+
|
|
291
|
+
Automatically infers the parameter schema from type hints and
|
|
292
|
+
registers the function with the specified registry.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
name: Tool name (defaults to function name).
|
|
296
|
+
description: Tool description (defaults to function docstring).
|
|
297
|
+
category: Tool category for organization.
|
|
298
|
+
risk_level: Risk level for authorization decisions.
|
|
299
|
+
requires_approval: Whether tool requires explicit approval.
|
|
300
|
+
timeout: Execution timeout in seconds.
|
|
301
|
+
registry: Registry to register with (defaults to global).
|
|
302
|
+
enabled: Whether tool is enabled by default.
|
|
303
|
+
**metadata: Additional metadata to attach.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
Decorator function.
|
|
307
|
+
|
|
308
|
+
Example:
|
|
309
|
+
>>> @tool(
|
|
310
|
+
... name="search_web",
|
|
311
|
+
... description="Search the web for information",
|
|
312
|
+
... category=ToolCategory.SEARCH,
|
|
313
|
+
... risk_level=RiskLevel.LOW,
|
|
314
|
+
... )
|
|
315
|
+
... def search_web(query: str, max_results: int = 10) -> list[dict]:
|
|
316
|
+
... '''
|
|
317
|
+
... Search the web.
|
|
318
|
+
...
|
|
319
|
+
... Args:
|
|
320
|
+
... query: The search query.
|
|
321
|
+
... max_results: Maximum results to return.
|
|
322
|
+
... '''
|
|
323
|
+
... return perform_search(query, max_results)
|
|
324
|
+
"""
|
|
325
|
+
|
|
326
|
+
def decorator(func: F) -> F:
|
|
327
|
+
# Determine tool name
|
|
328
|
+
tool_name = name if name is not None else func.__name__
|
|
329
|
+
|
|
330
|
+
# Determine description
|
|
331
|
+
tool_description = description
|
|
332
|
+
if tool_description is None:
|
|
333
|
+
# Extract from docstring
|
|
334
|
+
if func.__doc__:
|
|
335
|
+
# Get first paragraph of docstring
|
|
336
|
+
doc_lines = func.__doc__.strip().split("\n\n")[0].split("\n")
|
|
337
|
+
tool_description = " ".join(line.strip() for line in doc_lines)
|
|
338
|
+
else:
|
|
339
|
+
tool_description = f"Execute {tool_name}"
|
|
340
|
+
|
|
341
|
+
# Infer parameter schema
|
|
342
|
+
parameters = infer_schema_from_function(func)
|
|
343
|
+
|
|
344
|
+
# Create tool definition
|
|
345
|
+
tool_def = ToolDefinition(
|
|
346
|
+
name=tool_name,
|
|
347
|
+
description=tool_description,
|
|
348
|
+
parameters=parameters,
|
|
349
|
+
category=category,
|
|
350
|
+
risk_level=risk_level,
|
|
351
|
+
requires_approval=requires_approval,
|
|
352
|
+
timeout=timeout,
|
|
353
|
+
handler=func,
|
|
354
|
+
metadata=metadata,
|
|
355
|
+
enabled=enabled,
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
# Register with registry
|
|
359
|
+
target_registry = registry if registry is not None else get_global_registry()
|
|
360
|
+
target_registry.register(tool_def)
|
|
361
|
+
|
|
362
|
+
logger.debug(f"Registered tool: {tool_name}")
|
|
363
|
+
|
|
364
|
+
# Preserve function metadata
|
|
365
|
+
@functools.wraps(func)
|
|
366
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
367
|
+
return func(*args, **kwargs)
|
|
368
|
+
|
|
369
|
+
# Attach tool definition to wrapper
|
|
370
|
+
wrapper._tool_definition = tool_def # type: ignore
|
|
371
|
+
wrapper._tool_name = tool_name # type: ignore
|
|
372
|
+
|
|
373
|
+
return wrapper # type: ignore
|
|
374
|
+
|
|
375
|
+
return decorator
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def register_tool(
|
|
379
|
+
func: Callable[..., Any],
|
|
380
|
+
name: str | None = None,
|
|
381
|
+
description: str | None = None,
|
|
382
|
+
category: ToolCategory = ToolCategory.CUSTOM,
|
|
383
|
+
risk_level: RiskLevel = RiskLevel.LOW,
|
|
384
|
+
requires_approval: bool = False,
|
|
385
|
+
timeout: float | None = None,
|
|
386
|
+
registry: ToolRegistry | None = None,
|
|
387
|
+
enabled: bool = True,
|
|
388
|
+
**metadata: Any,
|
|
389
|
+
) -> ToolDefinition:
|
|
390
|
+
"""
|
|
391
|
+
Register a function as a tool without using decorator syntax.
|
|
392
|
+
|
|
393
|
+
Useful for registering existing functions or lambdas as tools.
|
|
394
|
+
|
|
395
|
+
Args:
|
|
396
|
+
func: The function to register.
|
|
397
|
+
name: Tool name (defaults to function name).
|
|
398
|
+
description: Tool description (defaults to function docstring).
|
|
399
|
+
category: Tool category for organization.
|
|
400
|
+
risk_level: Risk level for authorization decisions.
|
|
401
|
+
requires_approval: Whether tool requires explicit approval.
|
|
402
|
+
timeout: Execution timeout in seconds.
|
|
403
|
+
registry: Registry to register with (defaults to global).
|
|
404
|
+
enabled: Whether tool is enabled by default.
|
|
405
|
+
**metadata: Additional metadata to attach.
|
|
406
|
+
|
|
407
|
+
Returns:
|
|
408
|
+
The created ToolDefinition.
|
|
409
|
+
|
|
410
|
+
Example:
|
|
411
|
+
>>> def my_function(x: int) -> int:
|
|
412
|
+
... return x * 2
|
|
413
|
+
>>> tool_def = register_tool(
|
|
414
|
+
... my_function,
|
|
415
|
+
... name="double",
|
|
416
|
+
... description="Double a number",
|
|
417
|
+
... )
|
|
418
|
+
"""
|
|
419
|
+
# Determine tool name
|
|
420
|
+
tool_name = name if name is not None else func.__name__
|
|
421
|
+
|
|
422
|
+
# Determine description
|
|
423
|
+
tool_description = description
|
|
424
|
+
if tool_description is None:
|
|
425
|
+
if func.__doc__:
|
|
426
|
+
doc_lines = func.__doc__.strip().split("\n\n")[0].split("\n")
|
|
427
|
+
tool_description = " ".join(line.strip() for line in doc_lines)
|
|
428
|
+
else:
|
|
429
|
+
tool_description = f"Execute {tool_name}"
|
|
430
|
+
|
|
431
|
+
# Infer parameter schema
|
|
432
|
+
parameters = infer_schema_from_function(func)
|
|
433
|
+
|
|
434
|
+
# Create tool definition
|
|
435
|
+
tool_def = ToolDefinition(
|
|
436
|
+
name=tool_name,
|
|
437
|
+
description=tool_description,
|
|
438
|
+
parameters=parameters,
|
|
439
|
+
category=category,
|
|
440
|
+
risk_level=risk_level,
|
|
441
|
+
requires_approval=requires_approval,
|
|
442
|
+
timeout=timeout,
|
|
443
|
+
handler=func,
|
|
444
|
+
metadata=metadata,
|
|
445
|
+
enabled=enabled,
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
# Register with registry
|
|
449
|
+
target_registry = registry if registry is not None else get_global_registry()
|
|
450
|
+
target_registry.register(tool_def)
|
|
451
|
+
|
|
452
|
+
logger.debug(f"Registered tool: {tool_name}")
|
|
453
|
+
|
|
454
|
+
return tool_def
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def unregister_tool(
|
|
458
|
+
name: str,
|
|
459
|
+
registry: ToolRegistry | None = None,
|
|
460
|
+
) -> bool:
|
|
461
|
+
"""
|
|
462
|
+
Unregister a tool by name.
|
|
463
|
+
|
|
464
|
+
Args:
|
|
465
|
+
name: The tool name to unregister.
|
|
466
|
+
registry: Registry to unregister from (defaults to global).
|
|
467
|
+
|
|
468
|
+
Returns:
|
|
469
|
+
True if the tool was found and unregistered.
|
|
470
|
+
"""
|
|
471
|
+
target_registry = registry if registry is not None else get_global_registry()
|
|
472
|
+
return target_registry.unregister(name)
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def get_tool_definition(func: Callable[..., Any]) -> ToolDefinition | None:
|
|
476
|
+
"""
|
|
477
|
+
Get the tool definition attached to a decorated function.
|
|
478
|
+
|
|
479
|
+
Args:
|
|
480
|
+
func: The decorated function.
|
|
481
|
+
|
|
482
|
+
Returns:
|
|
483
|
+
The ToolDefinition or None if not a registered tool.
|
|
484
|
+
|
|
485
|
+
Example:
|
|
486
|
+
>>> @tool(name="my_tool")
|
|
487
|
+
... def my_func():
|
|
488
|
+
... pass
|
|
489
|
+
>>> tool_def = get_tool_definition(my_func)
|
|
490
|
+
>>> tool_def.name
|
|
491
|
+
'my_tool'
|
|
492
|
+
"""
|
|
493
|
+
return getattr(func, "_tool_definition", None)
|