(fn: Callable[..., Any])
| 172 | """ |
| 173 | |
| 174 | def decorator(fn: Callable[..., Any]) -> Tool: |
| 175 | tool_name = name if name is not None else getattr(fn, "__name__", "unknown") |
| 176 | |
| 177 | sig = inspect.signature(fn) |
| 178 | param_names = list(sig.parameters.keys()) |
| 179 | hints = get_type_hints(fn) |
| 180 | num_params = len(param_names) |
| 181 | |
| 182 | # Detect handler signature: |
| 183 | # - 0 params: handler() |
| 184 | # - 1 param, ToolInvocation: handler(invocation) |
| 185 | # - 1 param, Pydantic: handler(params) |
| 186 | # - 2 params: handler(params, invocation) |
| 187 | ptype = params_type |
| 188 | first_param_type = hints.get(param_names[0]) if param_names else None |
| 189 | |
| 190 | if num_params == 0: |
| 191 | takes_params = False |
| 192 | takes_invocation = False |
| 193 | elif num_params == 1 and first_param_type is ToolInvocation: |
| 194 | takes_params = False |
| 195 | takes_invocation = True |
| 196 | else: |
| 197 | takes_params = True |
| 198 | takes_invocation = num_params >= 2 |
| 199 | if ptype is None and _is_pydantic_model(first_param_type): |
| 200 | ptype = first_param_type |
| 201 | |
| 202 | # Generate schema from Pydantic model |
| 203 | schema = None |
| 204 | if ptype is not None and _is_pydantic_model(ptype): |
| 205 | schema = ptype.model_json_schema() |
| 206 | |
| 207 | async def wrapped_handler(invocation: ToolInvocation) -> ToolResult: |
| 208 | try: |
| 209 | # Build args based on detected signature |
| 210 | call_args = [] |
| 211 | if takes_params: |
| 212 | args = invocation.arguments or {} |
| 213 | if ptype is not None and _is_pydantic_model(ptype): |
| 214 | call_args.append(ptype.model_validate(args)) |
| 215 | else: |
| 216 | call_args.append(args) |
| 217 | if takes_invocation: |
| 218 | call_args.append(invocation) |
| 219 | |
| 220 | result = fn(*call_args) |
| 221 | |
| 222 | if inspect.isawaitable(result): |
| 223 | result = await result |
| 224 | |
| 225 | return _normalize_result(result) |
| 226 | |
| 227 | except Exception as exc: |
| 228 | # Don't expose detailed error information to the LLM for security reasons. |
| 229 | # The actual error is stored in the 'error' field for debugging. |
| 230 | return ToolResult( |
| 231 | text_result_for_llm=( |
no test coverage detected
searching dependent graphs…