Bemærk
Adgang til denne side kræver godkendelse. Du kan prøve at logge på eller ændre mapper.
Adgang til denne side kræver godkendelse. Du kan prøve at ændre mapper.
This tutorial step shows you how to use function tools with an agent, where the agent is built on the Azure OpenAI Chat Completion service.
Important
Not all agent types support function tools. Some might only support custom built-in tools, without allowing the caller to provide their own functions. This step uses a ChatClientAgent, which does support function tools.
Prerequisites
For prerequisites and installing NuGet packages, see the Create and run a simple agent step in this tutorial.
Create the agent with function tools
Function tools are just custom code that you want the agent to be able to call when needed.
You can turn any C# method into a function tool, by using the AIFunctionFactory.Create method to create an AIFunction instance from the method.
If you need to provide additional descriptions about the function or its parameters to the agent, so that it can more accurately choose between different functions, you can use the System.ComponentModel.DescriptionAttribute attribute on the method and its parameters.
Here is an example of a simple function tool that fakes getting the weather for a given location. It is decorated with description attributes to provide additional descriptions about itself and its location parameter to the agent.
using System.ComponentModel;
[Description("Get the weather for a given location.")]
static string GetWeather([Description("The location to get the weather for.")] string location)
=> $"The weather in {location} is cloudy with a high of 15°C.";
When creating the agent, you can now provide the function tool to the agent, by passing a list of tools to the AsAIAgent method.
using System;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI;
AIAgent agent = new AzureOpenAIClient(
new Uri("https://<myresource>.openai.azure.com"),
new DefaultAzureCredential())
.GetChatClient("gpt-4o-mini")
.AsAIAgent(instructions: "You are a helpful assistant", tools: [AIFunctionFactory.Create(GetWeather)]);
Warning
DefaultAzureCredential is convenient for development but requires careful consideration in production. In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
Now you can just run the agent as normal, and the agent will be able to call the GetWeather function tool when needed.
Console.WriteLine(await agent.RunAsync("What is the weather like in Amsterdam?"));
Tip
See the .NET samples for complete runnable examples.
Important
Not all agent types support function tools. Some might only support custom built-in tools, without allowing the caller to provide their own functions. This step uses agents created via chat clients, which do support function tools.
Prerequisites
For prerequisites and installing Python packages, see the Create and run a simple agent step in this tutorial.
Create the agent with function tools
Function tools are just custom code that you want the agent to be able to call when needed.
You can turn any Python function into a function tool by passing it to the agent's tools parameter when creating the agent.
If you need to provide additional descriptions about the function or its parameters to the agent, so that it can more accurately choose between different functions, you can use Python's type annotations with Annotated and Pydantic's Field to provide descriptions.
Here is an example of a simple function tool that fakes getting the weather for a given location. It uses type annotations to provide additional descriptions about the function and its location parameter to the agent.
from typing import Annotated
from pydantic import Field
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
) -> str:
"""Get the weather for a given location."""
return f"The weather in {location} is cloudy with a high of 15°C."
You can also use the @tool decorator to explicitly specify the function's name and description:
from typing import Annotated
from pydantic import Field
from agent_framework import tool
@tool(name="weather_tool", description="Retrieves weather information for any location")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
) -> str:
return f"The weather in {location} is cloudy with a high of 15°C."
If you don't specify the name and description parameters in the @tool decorator, the framework will automatically use the function's name and docstring as fallbacks.
Use explicit schemas with @tool
When you need full control over the schema exposed to the model, pass the schema parameter to @tool.
You can provide either a Pydantic model or a raw JSON schema dictionary.
# Load environment variables from .env file
load_dotenv()
# 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",
"""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.",
Pass runtime-only context to a tool
Use normal function parameters for values the model should supply. Use FunctionInvocationContext for runtime-only values such as function_invocation_kwargs or the current session. The injected context parameter is hidden from the schema exposed to the model.
import asyncio
from typing import Annotated
from agent_framework import FunctionInvocationContext, tool
from agent_framework.openai import OpenAIResponsesClient
from dotenv import load_dotenv
from pydantic import Field
# Define the function tool with explicit invocation context.
# The context parameter can also be declared as an untyped ``ctx`` parameter.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
ctx: FunctionInvocationContext,
) -> str:
"""Get the weather for a given location."""
# Extract the injected argument from the explicit context
user_id = ctx.kwargs.get("user_id", "unknown")
# Simulate using the user_id for logging or personalization
print(f"Getting weather for user: {user_id}")
return f"The weather in {location} is cloudy with a high of 15°C."
async def main() -> None:
agent = OpenAIResponsesClient().as_agent(
name="WeatherAgent",
instructions="You are a helpful weather assistant.",
tools=[get_weather],
)
# Pass the runtime context explicitly when running the agent.
response = await agent.run(
"What is the weather like in Amsterdam?",
function_invocation_kwargs={"user_id": "user_123"},
)
For more detail on ctx.kwargs, ctx.session, and function middleware, see Runtime Context.
Create declaration-only tools
If a tool is implemented outside the framework (for example, client-side in a UI), you can declare it without an implementation using FunctionTool(..., func=None).
The model can still reason about and call the tool, and your application can provide the result later.
# Load environment variables from .env file
load_dotenv()
# A declaration-only tool: the schema is sent to the LLM, but the framework
# has no implementation to execute. The caller must supply the result.
get_user_location = FunctionTool(
name="get_user_location",
func=None,
description="Get the user's current city. Only the client application can resolve this.",
input_model={
"type": "object",
"properties": {
"reason": {"type": "string", "description": "Why the location is needed"},
When creating the agent, you can now provide the function tool to the agent, by passing it to the tools parameter.
import asyncio
from agent_framework.azure import AzureOpenAIChatClient
from azure.identity import AzureCliCredential
agent = AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
instructions="You are a helpful assistant",
tools=get_weather
)
Now you can just run the agent as normal, and the agent will be able to call the get_weather function tool when needed.
async def main():
result = await agent.run("What is the weather like in Amsterdam?")
print(result.text)
asyncio.run(main())
Create a class with multiple function tools
When several tools share dependencies or mutable state, wrap them in a class and pass bound methods to the agent. Use class attributes for values the model should not provide, such as service clients, feature flags, or cached state.
import asyncio
from typing import Annotated
from agent_framework import tool
from agent_framework.openai import OpenAIResponsesClient
from dotenv import load_dotenv
class MyFunctionClass:
def __init__(self, safe: bool = False) -> None:
"""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.
"""
self.safe = safe
def divide(
self,
a: Annotated[int, "Numerator"],
b: Annotated[int, "Denominator"],
) -> str:
"""Divide two numbers, safe to use also with 0 as denominator."""
result = "∞" if b == 0 and self.safe else a / b
return f"{a} / {b} = {result}"
def add(
self,
x: Annotated[int, "First number"],
y: Annotated[int, "Second number"],
) -> str:
return f"{x} + {y} = {x + y}"
async def main():
# Creating my function class with safe division enabled
tools = MyFunctionClass(safe=True)
# 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",
instructions="Use the provided tools.",
)
print("=" * 60)
print("Step 1: Call divide(10, 0) - tool returns infinity")
query = "Divide 10 by 0"
response = await agent.run(
query,
tools=[add_function, tools.divide],
)
print(f"Response: {response.text}")
print("=" * 60)
print("Step 2: Call set safe to False and call again")
# Disabling safe mode to allow exceptions
tools.safe = False
response = await agent.run(query, tools=[add_function, tools.divide])
This pattern is a good fit for long-lived tool state. Use FunctionInvocationContext instead when the value changes per invocation.