@@ -84,69 +84,100 @@ def __init__(
84
84
self .__annotations__ = {p .name : p .annotation for p in visible_params }
85
85
# TODO: self.__qualname__ ??
86
86
87
- async def __call__ (self , * args : Any , ** kwargs : Any ) -> str :
87
+ def _resolve_callable_args (
88
+ self , callable_args : dict [str , Union [Any , Callable [[], Any ]]]
89
+ ) -> dict [str , Any ]:
88
90
"""
89
- Asynchronously calls the remote tool with the provided arguments and bound parameters.
90
-
91
- Validates arguments against the tool's signature (excluding bound parameters),
92
- then sends bound parameters and call arguments as a JSON payload in a POST request to the tool's invoke URL.
93
-
94
- Args:
95
- *args: Positional arguments for the tool (for non-bound parameters).
96
- **kwargs: Keyword arguments for the tool (for non-bound parameters).
91
+ Evaluates any callable arguments.
97
92
98
93
Returns:
99
- The string result returned by the remote tool execution .
94
+ A dictionary containing all args with callables resolved .
100
95
101
96
Raises:
102
- TypeError: If a bound parameter conflicts with a parameter provided at call time.
103
- Exception: If the remote tool call results in an error.
97
+ RuntimeError: If evaluating a callable argument fails.
104
98
"""
105
- # Resolve bound parameters by evaluating callables
106
- resolved_bound_params : dict [str , Any ] = {}
107
- for name , value_or_callable in self .__bound_params .items ():
99
+ resolved_params : dict [str , Any ] = {}
100
+ for name , value_or_callable in callable_args .items ():
108
101
try :
109
- resolved_bound_params [name ] = (
102
+ resolved_params [name ] = (
110
103
value_or_callable ()
111
104
if callable (value_or_callable )
112
105
else value_or_callable
113
106
)
114
107
except Exception as e :
115
108
raise RuntimeError (
116
- f"Error evaluating bound parameter '{ name } ' for tool '{ self .__name__ } ': { e } "
109
+ f"Error evaluating argument '{ name } ' for tool '{ self .__name__ } ': { e } "
117
110
) from e
111
+ return resolved_params
112
+
113
+ def _prepare_arguments (self , * args : Any , ** kwargs : Any ) -> dict [str , Any ]:
114
+ """
115
+ Resolves bound parameters, merges with call arguments, and binds them
116
+ to the tool's full signature.
117
+
118
+ Args:
119
+ *args: Positional arguments provided at call time.
120
+ **kwargs: Keyword arguments provided at call time.
121
+
122
+ Returns:
123
+ A dictionary of all arguments ready to be sent to the API.
124
+
125
+ Raises:
126
+ TypeError: If call-time arguments conflict with bound parameters,
127
+ or if arguments don't match the tool's signature.
128
+ RuntimeError: If evaluating a bound parameter fails.
129
+ """
130
+ resolved_bound_params = self ._resolve_callable_args (self .__bound_params )
118
131
119
- # Check for conflicts between resolved bound params and kwargs
132
+ # Check for conflicts between resolved bound params and keyword arguments
120
133
conflicts = resolved_bound_params .keys () & kwargs .keys ()
121
134
if conflicts :
122
135
raise TypeError (
123
136
f"Tool '{ self .__name__ } ': Cannot provide value during call for already bound argument(s): { ', ' .join (conflicts )} "
124
137
)
138
+
139
+ # Merge resolved bound parameters with provided keyword arguments
125
140
merged_kwargs = {** resolved_bound_params , ** kwargs }
126
141
127
142
# Bind *args and merged_kwargs using the *original* full signature
128
- # This ensures all parameters (bound and call-time) are accounted for.
129
143
full_signature = Signature (
130
144
parameters = self .__original_params , return_annotation = str
131
145
)
132
146
try :
133
- # We use merged_kwargs here; args fill positional slots first.
134
- # Bound parameters passed positionally via *args is complex and less intuitive,
135
- # so we primarily expect bound params to be treated like pre-filled keywords.
136
- # If a user *really* wanted to bind a purely positional param, they could,
137
- # but providing it again via *args at call time would be an error caught by bind().
138
- all_args = full_signature .bind (* args , ** merged_kwargs )
147
+ bound_args = full_signature .bind (* args , ** merged_kwargs )
139
148
except TypeError as e :
140
149
raise TypeError (
141
- f"Argument binding error for tool '{ self .__name__ } ' (check bound params and call arguments ): { e } "
150
+ f"Argument binding error for tool '{ self .__name__ } ' (check arguments against signature { full_signature } and bound params { list ( self . __bound_params . keys ()) } ): { e } "
142
151
) from e
143
152
144
- all_args .apply_defaults ()
153
+ # Apply default values for any missing arguments
154
+ bound_args .apply_defaults ()
155
+ return bound_args .arguments
156
+
157
+ async def __call__ (self , * args : Any , ** kwargs : Any ) -> str :
158
+ """
159
+ Asynchronously calls the remote tool with the provided arguments and bound parameters.
160
+
161
+ Validates arguments against the tool's signature (excluding bound parameters),
162
+ then sends bound parameters and call arguments as a JSON payload in a POST request to the tool's invoke URL.
163
+
164
+ Args:
165
+ *args: Positional arguments for the tool (for non-bound parameters).
166
+ **kwargs: Keyword arguments for the tool (for non-bound parameters).
167
+
168
+ Returns:
169
+ The string result returned by the remote tool execution.
170
+
171
+ Raises:
172
+ TypeError: If a bound parameter conflicts with a parameter provided at call time.
173
+ Exception: If the remote tool call results in an error.
174
+ """
175
+ arguments_payload = self ._prepare_arguments (* args , ** kwargs )
145
176
146
177
# Make the API call
147
178
async with self .__session .post (
148
179
self .__url ,
149
- payload = all_args . arguments ,
180
+ payload = arguments_payload ,
150
181
) as resp :
151
182
try :
152
183
ret = await resp .json ()
@@ -274,4 +305,4 @@ async def __call__(self, *args: Any, **kwargs: Any) -> str:
274
305
# ValueError: If strict is True and the parameter name is invalid or
275
306
# already bound.
276
307
# """
277
- # return self.bind_params({param_name: param_value}, strict=strict)
308
+ # return self.bind_params({param_name: param_value}, strict=strict)
0 commit comments