Skip to content

Commit 8e96af0

Browse files
committed
still in progress
1 parent d7a852b commit 8e96af0

File tree

2 files changed

+470
-29
lines changed

2 files changed

+470
-29
lines changed

packages/toolbox-core/src/toolbox_core/tool.py

Lines changed: 60 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -84,69 +84,100 @@ def __init__(
8484
self.__annotations__ = {p.name: p.annotation for p in visible_params}
8585
# TODO: self.__qualname__ ??
8686

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]:
8890
"""
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.
9792
9893
Returns:
99-
The string result returned by the remote tool execution.
94+
A dictionary containing all args with callables resolved.
10095
10196
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.
10498
"""
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():
108101
try:
109-
resolved_bound_params[name] = (
102+
resolved_params[name] = (
110103
value_or_callable()
111104
if callable(value_or_callable)
112105
else value_or_callable
113106
)
114107
except Exception as e:
115108
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}"
117110
) 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)
118131

119-
# Check for conflicts between resolved bound params and kwargs
132+
# Check for conflicts between resolved bound params and keyword arguments
120133
conflicts = resolved_bound_params.keys() & kwargs.keys()
121134
if conflicts:
122135
raise TypeError(
123136
f"Tool '{self.__name__}': Cannot provide value during call for already bound argument(s): {', '.join(conflicts)}"
124137
)
138+
139+
# Merge resolved bound parameters with provided keyword arguments
125140
merged_kwargs = {**resolved_bound_params, **kwargs}
126141

127142
# Bind *args and merged_kwargs using the *original* full signature
128-
# This ensures all parameters (bound and call-time) are accounted for.
129143
full_signature = Signature(
130144
parameters=self.__original_params, return_annotation=str
131145
)
132146
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)
139148
except TypeError as e:
140149
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}"
142151
) from e
143152

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)
145176

146177
# Make the API call
147178
async with self.__session.post(
148179
self.__url,
149-
payload=all_args.arguments,
180+
payload=arguments_payload,
150181
) as resp:
151182
try:
152183
ret = await resp.json()
@@ -274,4 +305,4 @@ async def __call__(self, *args: Any, **kwargs: Any) -> str:
274305
# ValueError: If strict is True and the parameter name is invalid or
275306
# already bound.
276307
# """
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

Comments
 (0)