From d44b0dca9570c321ceb14c66a065ac7245b6cffb Mon Sep 17 00:00:00 2001 From: Giles Odigwe Date: Mon, 9 Feb 2026 13:54:41 -0800 Subject: [PATCH 1/2] added explanation doctrings --- .../packages/core/agent_framework/_tools.py | 25 +++++++++++++++++-- .../agent_and_run_level_middleware.py | 15 ++++++++++- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/python/packages/core/agent_framework/_tools.py b/python/packages/core/agent_framework/_tools.py index 7e22b78827..92dc7aa2cf 100644 --- a/python/packages/core/agent_framework/_tools.py +++ b/python/packages/core/agent_framework/_tools.py @@ -646,7 +646,10 @@ def __init__( 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. @@ -1280,7 +1283,11 @@ def tool( ``Field`` class for more advanced configuration. 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__`` @@ -1341,6 +1348,20 @@ async def async_get_weather(location: str) -> str: # Simulate async operation return f"Weather in {location}" + + # 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 c90dd1936b..3436bf9a89 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,20 @@ 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 +Middleware Execution Order: + When both agent-level and run-level 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() """ From 676cef9c77db43252ba8f9c85c0e1961bf499ca0 Mon Sep 17 00:00:00 2001 From: Giles Odigwe Date: Mon, 9 Feb 2026 14:08:31 -0800 Subject: [PATCH 2/2] copilot comments --- python/packages/core/agent_framework/_tools.py | 12 +++++++++--- .../middleware/agent_and_run_level_middleware.py | 10 ++++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/python/packages/core/agent_framework/_tools.py b/python/packages/core/agent_framework/_tools.py index 92dc7aa2cf..e84c353cb1 100644 --- a/python/packages/core/agent_framework/_tools.py +++ b/python/packages/core/agent_framework/_tools.py @@ -640,7 +640,7 @@ 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. @@ -652,7 +652,13 @@ def __init__( 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__( @@ -1295,7 +1301,7 @@ def tool( description: A description of the function. If not provided, the function's docstring will be used. 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. 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 3436bf9a89..b3f7d97c56 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,8 +31,9 @@ 3. Run-level context middleware for specific use cases (high priority, debugging) 4. Run-level caching middleware for expensive operations -Middleware Execution Order: - When both agent-level and run-level middleware are configured, they execute in this order: +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() @@ -45,6 +46,11 @@ - 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. """