-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbase_tool.py
More file actions
111 lines (93 loc) · 3.84 KB
/
Copy pathbase_tool.py
File metadata and controls
111 lines (93 loc) · 3.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import json
import inspect
from abc import ABC, abstractmethod
from typing import Dict, List, Optional, Union
# Global tool registry
TOOL_REGISTRY = {}
def register_tool(name: str):
"""Decorator to register a tool in the global registry."""
def decorator(cls):
cls.name = name
TOOL_REGISTRY[name] = cls
return cls
return decorator
class BaseTool(ABC):
"""Base class for all system tools matching Qwen-Agent API protocol."""
name: str = ''
description: str = ''
_static_parameters: dict = {}
def __init__(self, cfg: Optional[dict] = None):
self.cfg = cfg or {}
@property
def parameters(self) -> dict:
"""Dynamically autodetect and build Qwen-Agent schema from type annotations if not explicitly set."""
# 1. If explicit static parameters are defined on the class, use them
static_p = getattr(self, '_static_parameters', None)
if not static_p:
static_p = self.__class__.__dict__.get('parameters')
if static_p:
return static_p
# 2. Try autodetecting from 'execute' or 'run' methods using reflection and type annotations
target_method = None
for m_name in ['execute', 'run']:
if hasattr(self, m_name):
target_method = getattr(self, m_name)
break
if not target_method:
return {"type": "object", "properties": {}, "required": []}
try:
sig = inspect.signature(target_method)
properties = {}
required = []
type_map = {
str: "string",
int: "integer",
float: "number",
bool: "boolean",
list: "array",
dict: "object"
}
doc = target_method.__doc__ or ""
for param_name, param in sig.parameters.items():
if param_name in ['self', 'args', 'kwargs']:
continue
# Map type annotations
anno = param.annotation
p_type = "string" # fallback
if anno in type_map:
p_type = type_map[anno]
elif hasattr(anno, "__origin__") and anno.__origin__ in [list, List]:
p_type = "array"
elif hasattr(anno, "__origin__") and anno.__origin__ in [dict, Dict]:
p_type = "object"
# Extract parameter description from docstring if available
desc = f"Parameter {param_name}"
for line in doc.split("\n"):
if param_name in line and ":" in line:
desc = line.split(":", 1)[1].strip()
break
properties[param_name] = {
"type": p_type,
"description": desc
}
if param.default == inspect.Parameter.empty:
required.append(param_name)
return {
"type": "object",
"properties": properties,
"required": required
}
except Exception:
return {"type": "object", "properties": {}, "required": []}
@abstractmethod
def call(self, params: Union[str, dict], **kwargs) -> str:
"""Executes the tool with the given parameters and returns a string result."""
raise NotImplementedError
@property
def function(self) -> dict:
"""Returns the schema description of the tool compatible with OpenAI/Qwen."""
return {
'name': self.name,
'description': self.description,
'parameters': self.parameters,
}