|
3 | 3 | import asyncio |
4 | 4 | import dataclasses |
5 | 5 | import inspect |
6 | | -import json |
7 | 6 | from collections.abc import Awaitable |
8 | 7 | from dataclasses import dataclass, field |
9 | 8 | from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, cast |
|
12 | 11 | from pydantic import BaseModel, TypeAdapter, ValidationError |
13 | 12 | from typing_extensions import NotRequired, TypeAlias, TypedDict |
14 | 13 |
|
15 | | -from . import _debug |
16 | 14 | from .agent_output import AgentOutputSchemaBase |
17 | 15 | from .agent_tool_input import ( |
18 | 16 | AgentAsToolInput, |
|
47 | 45 | FunctionToolResult, |
48 | 46 | Tool, |
49 | 47 | ToolErrorFunction, |
50 | | - _extract_tool_argument_json_error, |
| 48 | + _build_handled_function_tool_error_handler, |
| 49 | + _build_wrapped_function_tool, |
| 50 | + _log_function_tool_invocation, |
| 51 | + _parse_function_tool_json_input, |
51 | 52 | default_tool_error_function, |
52 | 53 | ) |
53 | 54 | from .tool_context import ToolContext |
54 | | -from .tracing import SpanError |
55 | | -from .util import _error_tracing, _transforms |
| 55 | +from .util import _transforms |
56 | 56 | from .util._types import MaybeAwaitable |
57 | 57 |
|
58 | 58 | if TYPE_CHECKING: |
@@ -547,43 +547,34 @@ def _is_supported_parameters(value: Any) -> bool: |
547 | 547 | include_json_schema=include_schema, |
548 | 548 | ) |
549 | 549 |
|
550 | | - def _normalize_tool_input(parsed: Any) -> Any: |
| 550 | + def _normalize_tool_input(parsed: Any, tool_name: str) -> Any: |
551 | 551 | # Prefer JSON mode so structured params (datetime/UUID/Decimal, etc.) serialize cleanly. |
552 | 552 | try: |
553 | 553 | return params_adapter.dump_python(parsed, mode="json") |
554 | 554 | except Exception as exc: |
555 | 555 | raise ModelBehaviorError( |
556 | | - f"Failed to serialize structured tool input for {tool_name_resolved}: {exc}" |
| 556 | + f"Failed to serialize structured tool input for {tool_name}: {exc}" |
557 | 557 | ) from exc |
558 | 558 |
|
559 | 559 | async def _run_agent_impl(context: ToolContext, input_json: str) -> Any: |
560 | 560 | from .run import DEFAULT_MAX_TURNS, Runner |
561 | 561 | from .tool_context import ToolContext |
562 | 562 |
|
563 | | - try: |
564 | | - json_data = json.loads(input_json) if input_json else {} |
565 | | - except Exception as exc: |
566 | | - if _debug.DONT_LOG_TOOL_DATA: |
567 | | - logger.debug(f"Invalid JSON input for tool {tool_name_resolved}") |
568 | | - else: |
569 | | - logger.debug(f"Invalid JSON input for tool {tool_name_resolved}: {input_json}") |
570 | | - raise ModelBehaviorError( |
571 | | - f"Invalid JSON input for tool {tool_name_resolved}: {input_json}" |
572 | | - ) from exc |
573 | | - |
574 | | - if _debug.DONT_LOG_TOOL_DATA: |
575 | | - logger.debug(f"Invoking tool {tool_name_resolved}") |
576 | | - else: |
577 | | - logger.debug(f"Invoking tool {tool_name_resolved} with input {input_json}") |
| 563 | + tool_name = ( |
| 564 | + context.tool_name if isinstance(context, ToolContext) else tool_name_resolved |
| 565 | + ) |
| 566 | + json_data = _parse_function_tool_json_input( |
| 567 | + tool_name=tool_name, |
| 568 | + input_json=input_json, |
| 569 | + ) |
| 570 | + _log_function_tool_invocation(tool_name=tool_name, input_json=input_json) |
578 | 571 |
|
579 | 572 | try: |
580 | 573 | parsed_params = params_adapter.validate_python(json_data) |
581 | 574 | except ValidationError as exc: |
582 | | - raise ModelBehaviorError( |
583 | | - f"Invalid JSON input for tool {tool_name_resolved}: {exc}" |
584 | | - ) from exc |
| 575 | + raise ModelBehaviorError(f"Invalid JSON input for tool {tool_name}: {exc}") from exc |
585 | 576 |
|
586 | | - params_data = _normalize_tool_input(parsed_params) |
| 577 | + params_data = _normalize_tool_input(parsed_params, tool_name) |
587 | 578 | resolved_input = await resolve_agent_tool_input( |
588 | 579 | params=params_data, |
589 | 580 | schema_info=schema_info if should_capture_tool_input else None, |
@@ -804,48 +795,17 @@ async def dispatch_stream_events() -> None: |
804 | 795 |
|
805 | 796 | return run_result.final_output |
806 | 797 |
|
807 | | - async def _run_agent_tool(context: ToolContext, input_json: str) -> Any: |
808 | | - try: |
809 | | - return await _run_agent_impl(context, input_json) |
810 | | - except Exception as exc: |
811 | | - if failure_error_function is None: |
812 | | - raise |
813 | | - |
814 | | - result = failure_error_function(context, exc) |
815 | | - if inspect.isawaitable(result): |
816 | | - result = await result |
817 | | - |
818 | | - json_decode_error = _extract_tool_argument_json_error(exc) |
819 | | - if json_decode_error is not None: |
820 | | - span_error_message = "Error running tool" |
821 | | - span_error_detail = str(json_decode_error) |
822 | | - else: |
823 | | - span_error_message = "Error running tool (non-fatal)" |
824 | | - span_error_detail = str(exc) |
825 | | - |
826 | | - _error_tracing.attach_error_to_current_span( |
827 | | - SpanError( |
828 | | - message=span_error_message, |
829 | | - data={ |
830 | | - "tool_name": tool_name_resolved, |
831 | | - "error": span_error_detail, |
832 | | - }, |
833 | | - ) |
834 | | - ) |
835 | | - if _debug.DONT_LOG_TOOL_DATA: |
836 | | - logger.debug(f"Tool {tool_name_resolved} failed") |
837 | | - else: |
838 | | - logger.error( |
839 | | - f"Tool {tool_name_resolved} failed: {input_json} {exc}", |
840 | | - exc_info=exc, |
841 | | - ) |
842 | | - return result |
843 | | - |
844 | | - run_agent_tool = FunctionTool( |
| 798 | + run_agent_tool = _build_wrapped_function_tool( |
845 | 799 | name=tool_name_resolved, |
846 | 800 | description=tool_description_resolved, |
847 | 801 | params_json_schema=params_schema, |
848 | | - on_invoke_tool=_run_agent_tool, |
| 802 | + invoke_tool_impl=_run_agent_impl, |
| 803 | + on_handled_error=_build_handled_function_tool_error_handler( |
| 804 | + span_message="Error running tool (non-fatal)", |
| 805 | + span_message_for_json_decode_error="Error running tool", |
| 806 | + log_label="Tool", |
| 807 | + ), |
| 808 | + failure_error_function=failure_error_function, |
849 | 809 | strict_json_schema=True, |
850 | 810 | is_enabled=is_enabled, |
851 | 811 | needs_approval=needs_approval, |
|
0 commit comments