diff --git a/python/packages/core/agent_framework/_tools.py b/python/packages/core/agent_framework/_tools.py index 9019e1dc4f..21d2dc94c6 100644 --- a/python/packages/core/agent_framework/_tools.py +++ b/python/packages/core/agent_framework/_tools.py @@ -640,16 +640,25 @@ def __init__( name: The name of the function. description: A description of the function. approval_mode: Whether or not approval is required to run this tool. - Default is that approval is required. + Default is that approval is NOT required (``"never_require"``). max_invocations: The maximum number of times this function can be invoked. If None, there is no limit. Should be at least 1. max_invocation_exceptions: The maximum number of exceptions allowed during invocations. If None, there is no limit. Should be at least 1. additional_properties: Additional properties to set on the function. - func: The function to wrap. + func: The function to wrap. When ``None``, creates a declaration-only tool + that has no implementation. Declaration-only tools are useful when you want + the agent to reason about tool usage without executing them, or when the + actual implementation exists elsewhere (e.g., client-side rendering). input_model: The Pydantic model that defines the input parameters for the function. This can also be a JSON schema dictionary. - If not provided, it will be inferred from the function signature. + If not provided and ``func`` is not ``None``, it will be inferred from + the function signature. When ``func`` is ``None`` and ``input_model`` is + not provided, the tool will use an empty input model (no parameters) in + its JSON schema. For declaration-only tools that should declare + parameters, explicitly provide ``input_model`` (either a Pydantic + ``BaseModel`` or a JSON schema dictionary) so the model can reason about + the expected arguments. **kwargs: Additional keyword arguments. """ super().__init__( @@ -1286,7 +1295,11 @@ def tool( to bypass automatic inference from the function signature. Args: - func: The function to decorate. + func: The function to decorate. This parameter enables the decorator to be used + both with and without parentheses: ``@tool`` directly decorates the function, + while ``@tool()`` or ``@tool(name="custom")`` returns a decorator. For + declaration-only tools (no implementation), use :class:`FunctionTool` directly + with ``func=None``—see the example below. Keyword Args: name: The name of the function. If not provided, the function's ``__name__`` @@ -1301,7 +1314,7 @@ def tool( When provided, the schema is used instead of inferring one from the function's signature. Defaults to ``None`` (infer from signature). approval_mode: Whether or not approval is required to run this tool. - Default is that approval is required. + Default is that approval is NOT required (``"never_require"``). max_invocations: The maximum number of times this function can be invoked. If None, there is no limit, should be at least 1. max_invocation_exceptions: The maximum number of exceptions allowed during invocations. @@ -1369,6 +1382,20 @@ def get_weather(location: str, unit: str = "celsius") -> str: '''Get weather for a location.''' return f"Weather in {location}: 22 {unit}" + + # Declaration-only tool (no implementation) + # Use FunctionTool directly when you need a tool declaration without + # an executable function. The agent can request this tool, but it won't + # be executed automatically. Useful for testing agent reasoning or when + # the implementation is handled externally (e.g., client-side rendering). + from agent_framework import FunctionTool + + declaration_only_tool = FunctionTool( + name="get_current_time", + description="Get the current time in ISO 8601 format.", + func=None, # Explicitly no implementation - makes declaration_only=True + ) + """ def decorator(func: Callable[..., ReturnT | Awaitable[ReturnT]]) -> FunctionTool[Any, ReturnT]: diff --git a/python/samples/getting_started/middleware/agent_and_run_level_middleware.py b/python/samples/getting_started/middleware/agent_and_run_level_middleware.py index b76e0ac520..70408472ad 100644 --- a/python/samples/getting_started/middleware/agent_and_run_level_middleware.py +++ b/python/samples/getting_started/middleware/agent_and_run_level_middleware.py @@ -31,7 +31,26 @@ 3. Run-level context middleware for specific use cases (high priority, debugging) 4. Run-level caching middleware for expensive operations -Execution order: Agent middleware (outermost) -> Run middleware (innermost) -> Agent execution +Agent Middleware Execution Order: + When both agent-level and run-level *agent* middleware are configured, they execute + in this order: + + 1. Agent-level middleware (outermost) - executes first, in the order they were registered + 2. Run-level middleware (innermost) - executes next, in the order they were passed to run() + 3. Agent execution - the actual agent logic runs last + + For example, with agent middleware [A1, A2] and run middleware [R1, R2]: + Request -> A1 -> A2 -> R1 -> R2 -> Agent -> R2 -> R1 -> A2 -> A1 -> Response + + This means: + - Agent middleware wraps ALL run middleware and the agent + - Run middleware wraps only the agent for that specific run + - Each middleware can modify the context before AND after calling next() + + Note: Function and chat middleware (e.g., ``function_logging_middleware``) execute + during tool invocation *inside* the agent execution, not in the outer agent-middleware + chain shown above. They follow the same ordering principle: agent-level function/chat + middleware runs before run-level function/chat middleware. """