Python: [BREAKING] changed AIFunction to FunctionTool and @ai_function to @tool (#3413)

* changed AIFunction to FunctionTool and @ai_function to @tool

* test and mypy fixes

* mypy fix

* switch function tool to always_require

* fix noop

* fix github copilot imports

* test fixes

* fix ollama test

* fixes for tests

* fix tests

* reverted change to always_require and extended timeout

* fix test
This commit is contained in:
Eduard van Valkenburg
2026-01-28 15:53:53 +01:00
committed by GitHub
Unverified
parent 15b43f2abe
commit a7d924a7d2
255 changed files with 1202 additions and 1290 deletions
+22 -18
View File
@@ -1,25 +1,29 @@
# Tools Examples
This folder contains examples demonstrating how to use AI functions (tools) with the Agent Framework. AI functions allow agents to interact with external systems, perform computations, and execute custom logic.
This folder contains examples demonstrating how to use local tools with the Agent Framework. Local tools allow agents to interact with external systems, perform computations, and execute custom logic.
Note: Several examples set `approval_mode="never_require"` to keep the samples concise. For production scenarios,
keep `approval_mode="always_require"` unless you are confident in the tool behavior and approval flow. See
`function_tool_with_approval.py` and `function_tool_with_approval_and_threads.py` for end-to-end approval handling.
## Examples
| File | Description |
|------|-------------|
| [`ai_function_declaration_only.py`](ai_function_declaration_only.py) | Demonstrates how to create function declarations without implementations. Useful for testing agent reasoning about tool usage or when tools are defined elsewhere. Shows how agents request tool calls even when the tool won't be executed. |
| [`ai_function_from_dict_with_dependency_injection.py`](ai_function_from_dict_with_dependency_injection.py) | Shows how to create AI functions from dictionary definitions using dependency injection. The function implementation is injected at runtime during deserialization, enabling dynamic tool creation and configuration. Note: This serialization/deserialization feature is in active development. |
| [`ai_function_recover_from_failures.py`](ai_function_recover_from_failures.py) | Demonstrates graceful error handling when tools raise exceptions. Shows how agents receive error information and can recover from failures, deciding whether to retry or respond differently based on the exception. |
| [`ai_function_with_approval.py`](ai_function_with_approval.py) | Shows how to implement user approval workflows for function calls without using threads. Demonstrates both streaming and non-streaming approval patterns where users can approve or reject function executions before they run. |
| [`ai_function_with_approval_and_threads.py`](ai_function_with_approval_and_threads.py) | Demonstrates tool approval workflows using threads for automatic conversation history management. Shows how threads simplify approval workflows by automatically storing and retrieving conversation context. Includes both approval and rejection examples. |
| [`ai_function_with_kwargs.py`](ai_function_with_kwargs.py) | Demonstrates how to inject custom arguments (context) into an AI function from the agent's run method. Useful for passing runtime information like access tokens or user IDs that the tool needs but the model shouldn't see. |
| [`ai_function_with_thread_injection.py`](ai_function_with_thread_injection.py) | Shows how to access the current `thread` object inside an AI function via `**kwargs`. |
| [`ai_function_with_max_exceptions.py`](ai_function_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. |
| [`ai_function_with_max_invocations.py`](ai_function_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. |
| [`ai_functions_in_class.py`](ai_functions_in_class.py) | Shows how to use `ai_function` 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. |
| [`function_tool_declaration_only.py`](function_tool_declaration_only.py) | Demonstrates how to create function declarations without implementations. Useful for testing agent reasoning about tool usage or when tools are defined elsewhere. Shows how agents request tool calls even when the tool won't be executed. |
| [`function_tool_from_dict_with_dependency_injection.py`](function_tool_from_dict_with_dependency_injection.py) | Shows how to create local tools from dictionary definitions using dependency injection. The function implementation is injected at runtime during deserialization, enabling dynamic tool creation and configuration. Note: This serialization/deserialization feature is in active development. |
| [`function_tool_recover_from_failures.py`](function_tool_recover_from_failures.py) | Demonstrates graceful error handling when tools raise exceptions. Shows how agents receive error information and can recover from failures, deciding whether to retry or respond differently based on the exception. |
| [`function_tool_with_approval.py`](function_tool_with_approval.py) | Shows how to implement user approval workflows for function calls without using threads. Demonstrates both streaming and non-streaming approval patterns where users can approve or reject function executions before they run. |
| [`function_tool_with_approval_and_threads.py`](function_tool_with_approval_and_threads.py) | Demonstrates tool approval workflows using threads for automatic conversation history management. Shows how threads simplify approval workflows by automatically storing and retrieving conversation context. Includes both approval and rejection examples. |
| [`function_tool_with_kwargs.py`](function_tool_with_kwargs.py) | Demonstrates how to inject custom arguments (context) into a local tool from the agent's run method. Useful for passing runtime information like access tokens or user IDs that the tool needs but the model shouldn't see. |
| [`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. |
| [`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
### AI Function Features
### Local Tool Features
- **Function Declarations**: Define tool schemas without implementations for testing or external tools
- **Dependency Injection**: Create tools from configurations with runtime-injected implementations
@@ -33,10 +37,10 @@ This folder contains examples demonstrating how to use AI functions (tools) with
#### Basic Tool Definition
```python
from agent_framework import ai_function
from agent_framework import tool
from typing import Annotated
@ai_function
@tool(approval_mode="never_require")
def my_tool(param: Annotated[str, "Description"]) -> str:
"""Tool description for the AI."""
return f"Result: {param}"
@@ -45,7 +49,7 @@ def my_tool(param: Annotated[str, "Description"]) -> str:
#### Tool with Approval
```python
@ai_function(approval_mode="always_require")
@tool(approval_mode="always_require")
def sensitive_operation(data: Annotated[str, "Data to process"]) -> str:
"""This requires user approval before execution."""
return f"Processed: {data}"
@@ -54,12 +58,12 @@ def sensitive_operation(data: Annotated[str, "Data to process"]) -> str:
#### Tool with Invocation Limits
```python
@ai_function(max_invocations=3)
@tool(max_invocations=3)
def limited_tool() -> str:
"""Can only be called 3 times total."""
return "Result"
@ai_function(max_invocation_exceptions=2)
@tool(max_invocation_exceptions=2)
def fragile_tool() -> str:
"""Can only fail 2 times before being disabled."""
return "Result"
@@ -115,7 +119,7 @@ Two approaches for handling approvals:
Each example is a standalone Python script that can be run directly:
```bash
uv run python ai_function_with_approval.py
uv run python function_tool_with_approval.py
```
Make sure you have the necessary environment variables configured (like `OPENAI_API_KEY` or Azure credentials) before running the examples.
@@ -4,15 +4,17 @@ import asyncio
from typing import Annotated
from agent_framework.openai import OpenAIResponsesClient
from agent_framework import tool
"""
This sample demonstrates how to configure function invocation settings
for an client and use a simple ai_function as a tool in an agent.
for an client and use a simple tool as a tool in an agent.
This behavior is the same for all chat client types.
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
@tool(approval_mode="never_require")
def add(
x: Annotated[int, "First number"],
y: Annotated[int, "Second number"],
@@ -1,6 +1,6 @@
# Copyright (c) Microsoft. All rights reserved.
from agent_framework import AIFunction
from agent_framework import FunctionTool
from agent_framework.openai import OpenAIResponsesClient
"""
@@ -8,13 +8,13 @@ Example of how to create a function that only consists of a declaration without
This is useful when you want the agent to use tools that are defined elsewhere or when you want
to test the agent's ability to reason about tool usage without executing them.
The only difference is that you provide an AIFunction without a function.
The only difference is that you provide a FunctionTool without a function.
If you need a input_model, you can still provide that as well.
"""
async def main():
function_declaration = AIFunction[None, None](
function_declaration = FunctionTool(
name="get_current_time",
description="Get the current time in ISO 8601 format.",
)
@@ -1,31 +1,31 @@
# Copyright (c) Microsoft. All rights reserved.
# type: ignore
"""
AIFunction Tool with Dependency Injection Example
Local Tool with Dependency Injection Example
This example demonstrates how to create an AIFunction tool using the agent framework's
This example demonstrates how to create a FunctionTool using the agent framework's
dependency injection system. Instead of providing the function at initialization time,
the actual callable function is injected during deserialization from a dictionary definition.
Note:
The serialization and deserialization feature used in this example is currently
in active development. The API may change in future versions as we continue
in active development. The API may change in future versions as we continue
to improve and extend its functionality. Please refer to the latest documentation
for any updates to the dependency injection patterns.
Usage:
Run this script to see how an AIFunction tool can be created from a dictionary
Run this script to see how a FunctionTool can be created from a dictionary
definition with the function injected at runtime. The agent will use this tool
to perform arithmetic operations.
"""
import asyncio
from agent_framework import AIFunction
from agent_framework import FunctionTool
from agent_framework.openai import OpenAIResponsesClient
definition = {
"type": "ai_function",
"type": "function_tool",
"name": "add_numbers",
"description": "Add two numbers together.",
"input_model": {
@@ -47,15 +47,15 @@ async def main() -> None:
"""Add two numbers together."""
return a + b
# Create the AIFunction tool using dependency injection
# Create the FunctionTool using dependency injection
# The 'definition' dictionary contains the serialized tool configuration,
# while the actual function implementation is provided via dependencies.
#
# Dependency structure: {"ai_function": {"name:add_numbers": {"func": func}}}
# - "ai_function": matches the tool type identifier
# Dependency structure: {"function_tool": {"name:add_numbers": {"func": func}}}
# - "function_tool": matches the tool type identifier
# - "name:add_numbers": instance-specific injection targeting tools with name="add_numbers"
# - "func": the parameter name that will receive the injected function
tool = AIFunction.from_dict(definition, dependencies={"ai_function": {"name:add_numbers": {"func": func}}})
tool = FunctionTool.from_dict(definition, dependencies={"function_tool": {"name:add_numbers": {"func": func}}})
agent = OpenAIResponsesClient().as_agent(
name="FunctionToolAgent", instructions="You are a helpful assistant.", tools=tool
@@ -4,6 +4,7 @@ import asyncio
from typing import Annotated
from agent_framework import FunctionCallContent, FunctionResultContent
from agent_framework import tool
from agent_framework.openai import OpenAIResponsesClient
"""
@@ -13,13 +14,16 @@ Shows how a tool that throws an exception creates gracefull recovery and can kee
The LLM decides whether to retry the call or to respond with something else, based on the exception.
"""
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
@tool(approval_mode="never_require")
def greet(name: Annotated[str, "Name to greet"]) -> str:
"""Greet someone."""
return f"Hello, {name}!"
@tool(approval_mode="never_require")
# we trick the AI into calling this function with 0 as denominator to trigger the exception
@tool(approval_mode="never_require")
def safe_divide(
a: Annotated[int, "Numerator"],
b: Annotated[int, "Denominator"],
@@ -4,7 +4,7 @@ import asyncio
from random import randrange
from typing import TYPE_CHECKING, Annotated, Any
from agent_framework import AgentResponse, ChatAgent, ChatMessage, ai_function
from agent_framework import AgentResponse, ChatAgent, ChatMessage, tool
from agent_framework.openai import OpenAIResponsesClient
if TYPE_CHECKING:
@@ -20,7 +20,8 @@ It shows how to handle function call approvals without using threads.
conditions = ["sunny", "cloudy", "raining", "snowing", "clear"]
@ai_function
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
@tool(approval_mode="never_require")
def get_weather(location: Annotated[str, "The city and state, e.g. San Francisco, CA"]) -> str:
"""Get the current weather for a given location."""
# Simulate weather data
@@ -28,7 +29,7 @@ def get_weather(location: Annotated[str, "The city and state, e.g. San Francisco
# Define a simple weather tool that requires approval
@ai_function(approval_mode="always_require")
@tool(approval_mode="always_require")
def get_weather_detail(location: Annotated[str, "The city and state, e.g. San Francisco, CA"]) -> str:
"""Get the current weather for a given location."""
# Simulate weather data
@@ -3,7 +3,7 @@
import asyncio
from typing import Annotated
from agent_framework import ChatAgent, ChatMessage, ai_function
from agent_framework import ChatAgent, ChatMessage, tool
from agent_framework.azure import AzureOpenAIChatClient
"""
@@ -15,7 +15,7 @@ the thread stores and retrieves them automatically.
"""
@ai_function(approval_mode="always_require")
@tool(approval_mode="always_require")
def add_to_calendar(
event_name: Annotated[str, "Name of the event"], date: Annotated[str, "Date of the event"]
) -> str:
@@ -3,7 +3,7 @@
import asyncio
from typing import Annotated, Any
from agent_framework import ai_function
from agent_framework import tool
from agent_framework.openai import OpenAIResponsesClient
from pydantic import Field
@@ -20,7 +20,8 @@ or provide.
# Define the function tool with **kwargs to accept injected arguments
@ai_function
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
**kwargs: Any,
@@ -3,7 +3,7 @@
import asyncio
from typing import Annotated
from agent_framework import FunctionCallContent, FunctionResultContent, ai_function
from agent_framework import FunctionCallContent, FunctionResultContent, tool
from agent_framework.openai import OpenAIResponsesClient
"""
@@ -14,7 +14,7 @@ limited number of times.
# we trick the AI into calling this function with 0 as denominator to trigger the exception
@ai_function(max_invocation_exceptions=1)
@tool(max_invocation_exceptions=1)
def safe_divide(
a: Annotated[int, "Numerator"],
b: Annotated[int, "Denominator"],
@@ -3,7 +3,7 @@
import asyncio
from typing import Annotated
from agent_framework import FunctionCallContent, FunctionResultContent, ai_function
from agent_framework import FunctionCallContent, FunctionResultContent, tool
from agent_framework.openai import OpenAIResponsesClient
"""
@@ -12,7 +12,7 @@ This sample shows a tool that can only be invoked once.
"""
@ai_function(max_invocations=1)
@tool(max_invocations=1)
def unicorn_function(times: Annotated[int, "The number of unicorns to return."]) -> str:
"""This function returns precious unicorns!"""
return f"{'🦄' * times}"
@@ -3,7 +3,7 @@
import asyncio
from typing import Annotated, Any
from agent_framework import AgentThread, ai_function
from agent_framework import AgentThread, tool
from agent_framework.openai import OpenAIChatClient
from pydantic import Field
@@ -16,7 +16,8 @@ and accessing that thread in AI function.
# Define the function tool with **kwargs
@ai_function
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
@tool(approval_mode="never_require")
async def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
**kwargs: Any,
@@ -3,20 +3,20 @@
import asyncio
from typing import Annotated
from agent_framework import ai_function
from agent_framework import tool
from agent_framework.openai import OpenAIResponsesClient
"""
This sample demonstrates using ai_function within a class,
This sample demonstrates using tool within a class,
showing how to manage state within the class that affects tool behavior.
And how to use ai_function-decorated methods as tools in an agent in order to adjust the behavior of a tool.
And how to use tool-decorated methods as tools in an agent in order to adjust the behavior of a tool.
"""
class MyFunctionClass:
def __init__(self, safe: bool = False) -> None:
"""Simple class with two ai_functions: divide and add.
"""Simple class with two tools: divide and add.
The safe parameter controls whether divide raises on division by zero or returns `infinity` for divide by zero.
"""
@@ -42,8 +42,8 @@ class MyFunctionClass:
async def main():
# Creating my function class with safe division enabled
tools = MyFunctionClass(safe=True)
# Applying the ai_function decorator to one of the methods of the class
add_function = ai_function(description="Add two numbers.")(tools.add)
# Applying the tool decorator to one of the methods of the class
add_function = tool(description="Add two numbers.")(tools.add)
agent = OpenAIResponsesClient().as_agent(
name="ToolAgent",