Python: Added explicit schema handling to @tool decorator (#3734)

* Added explicit schema handling to @tool decorator

* Resolved comments
This commit is contained in:
Dmytro Struk
2026-02-09 13:36:32 -08:00
committed by GitHub
Unverified
parent e4ca3e60f8
commit 80cb6edc8d
4 changed files with 225 additions and 0 deletions
@@ -19,6 +19,7 @@ keep `approval_mode="always_require"` unless you are confident in the tool behav
| [`function_tool_with_thread_injection.py`](function_tool_with_thread_injection.py) | Shows how to access the current `thread` object inside a local tool via `**kwargs`. |
| [`function_tool_with_max_exceptions.py`](function_tool_with_max_exceptions.py) | Shows how to limit the number of times a tool can fail with exceptions using `max_invocation_exceptions`. Useful for preventing expensive tools from being called repeatedly when they keep failing. |
| [`function_tool_with_max_invocations.py`](function_tool_with_max_invocations.py) | Demonstrates limiting the total number of times a tool can be invoked using `max_invocations`. Useful for rate-limiting expensive operations or ensuring tools are only called a specific number of times per conversation. |
| [`function_tool_with_explicit_schema.py`](function_tool_with_explicit_schema.py) | Demonstrates how to provide an explicit Pydantic model or JSON schema dictionary to the `@tool` decorator via the `schema` parameter, bypassing automatic inference from the function signature. |
| [`tool_in_class.py`](tool_in_class.py) | Shows how to use the `tool` decorator with class methods to create stateful tools. Demonstrates how class state can control tool behavior dynamically, allowing you to adjust tool functionality at runtime by modifying class properties. |
## Key Concepts
@@ -26,6 +27,7 @@ keep `approval_mode="always_require"` unless you are confident in the tool behav
### Local Tool Features
- **Function Declarations**: Define tool schemas without implementations for testing or external tools
- **Explicit Schema**: Provide a Pydantic model or JSON schema dict to control the tool's parameter schema directly
- **Dependency Injection**: Create tools from configurations with runtime-injected implementations
- **Error Handling**: Gracefully handle and recover from tool execution failures
- **Approval Workflows**: Require user approval before executing sensitive or important operations
@@ -55,6 +57,23 @@ def sensitive_operation(data: Annotated[str, "Data to process"]) -> str:
return f"Processed: {data}"
```
#### Tool with Explicit Schema
```python
from pydantic import BaseModel, Field
from agent_framework import tool
from typing import Annotated
class WeatherInput(BaseModel):
location: Annotated[str, Field(description="City name")]
unit: str = "celsius"
@tool(schema=WeatherInput)
def get_weather(location: str, unit: str = "celsius") -> str:
"""Get the weather for a location."""
return f"Weather in {location}: 22 {unit}"
```
#### Tool with Invocation Limits
```python
@@ -0,0 +1,81 @@
# Copyright (c) Microsoft. All rights reserved.
"""
Function Tool with Explicit Schema Example
This example demonstrates how to provide an explicit schema to the @tool decorator
using the `schema` parameter, bypassing the automatic inference from the function
signature. This is useful when you want full control over the tool's parameter
schema that the AI model sees, or when the function signature does not accurately
represent the desired schema.
Two approaches are shown:
1. Using a Pydantic BaseModel subclass as the schema
2. Using a raw JSON schema dictionary as the schema
"""
import asyncio
from typing import Annotated
from agent_framework import tool
from agent_framework.openai import OpenAIResponsesClient
from pydantic import BaseModel, Field
# Approach 1: Pydantic model as explicit schema
class WeatherInput(BaseModel):
"""Input schema for the weather tool."""
location: Annotated[str, Field(description="The city name to get weather for")]
unit: Annotated[str, Field(description="Temperature unit: celsius or fahrenheit")] = "celsius"
@tool(
name="get_weather",
description="Get the current weather for a given location.",
schema=WeatherInput,
approval_mode="never_require",
)
def get_weather(location: str, unit: str = "celsius") -> str:
"""Get the current weather for a location."""
return f"The weather in {location} is 22 degrees {unit}."
# Approach 2: JSON schema dictionary as explicit schema
get_current_time_schema = {
"type": "object",
"properties": {
"timezone": {"type": "string", "description": "The timezone to get the current time for", "default": "UTC"},
},
}
@tool(
name="get_current_time",
description="Get the current time in a given timezone.",
schema=get_current_time_schema,
approval_mode="never_require",
)
def get_current_time(timezone: str = "UTC") -> str:
"""Get the current time."""
from datetime import datetime
from zoneinfo import ZoneInfo
return f"The current time in {timezone} is {datetime.now(ZoneInfo(timezone)).isoformat()}"
async def main():
agent = OpenAIResponsesClient().as_agent(
name="AssistantAgent",
instructions="You are a helpful assistant. Use the available tools to answer questions.",
tools=[get_weather, get_current_time],
)
query = "What is the weather in Seattle and what time is it?"
print(f"User: {query}")
result = await agent.run(query)
print(f"Result: {result.text}")
if __name__ == "__main__":
asyncio.run(main())