In the previous post, we created a Microsoft Agent Framework agent and ran the same agent definition against different models in Microsoft Foundry. The agent could answer questions using the model's existing knowledge, but it could not access any data or operations from our application.
In this post, we are going to give the agent a function tool. We will expose a normal C# function that searches a small meeting room directory, let the model decide when to call it, and return a structured result to the agent.
What we are building
- Create a .NET console application.
- Define a C# function that finds available meeting rooms.
- Describe the function and its parameters for the model.
- Expose the function as an Agent Framework tool.
- Ask the agent a question that requires the tool.
- Return structured room data for the agent to use in its response.
Before you start
You will need:
- .NET 10 SDK. Agent Framework supports .NET 8 or later; I am using .NET 10 for this example.
- An Azure subscription.
- A Microsoft Foundry project.
- A model deployment that supports function calling.
- An identity with permission to use the Foundry project and model.
This post starts from the Foundry setup used in the previous article. If you already have a project endpoint and model deployment, you can reuse them.
1) Create the .NET project
Create a new console application:
dotnet new console -n AgentWithTools --framework net10.0
cd AgentWithTools
2) Install Microsoft Agent Framework packages
Install the Foundry integration and Azure authentication packages:
dotnet add package Microsoft.Agents.AI.Foundry --prerelease
dotnet add package Azure.AI.Projects --prerelease
dotnet add package Azure.Identity
The function tool APIs are provided through
Microsoft.Extensions.AI, which is brought in by the
Agent Framework packages.
3) Define the meeting room data
We will keep the data in memory so that we can focus on how tools work. In a real application, the same function could call Microsoft Graph, a database, or another business API.
First, define the records returned by our function:
record MeetingRoom(
string Name,
string City,
int Capacity,
bool HasTeamsRoom,
bool IsAvailable,
string Location);
record RoomSearchResult(
string City,
int MinimumCapacity,
bool RequiresTeamsRoom,
IReadOnlyList<MeetingRoom> Rooms);
Then add a few sample rooms:
MeetingRoom[] meetingRooms =
[
new("Thames", "London", 6, true, true, "2nd floor"),
new("Regent", "London", 10, true, true, "3rd floor"),
new("Windsor", "London", 12, false, true, "3rd floor"),
new("Harbour", "Sydney", 10, true, true, "5th floor"),
new("Cascade", "Redmond", 8, true, false, "1st floor")
];
4) Create the function tool
A function tool starts as a normal C# function. It can receive strongly typed parameters, execute application code, and return a normal .NET object.
[Description("Find available meeting rooms that match a city, minimum capacity, and Microsoft Teams requirement.")]
RoomSearchResult FindAvailableMeetingRooms(
[Description("The city where the meeting room must be located.")] string city,
[Description("The minimum number of people the room must accommodate.")] int minimumCapacity,
[Description("Whether the room must have Microsoft Teams meeting equipment.")] bool requiresTeamsRoom)
{
Console.WriteLine($"[Tool] Searching for rooms in {city} for {minimumCapacity} people.");
MeetingRoom[] matches = meetingRooms
.Where(room => room.City.Equals(city, StringComparison.OrdinalIgnoreCase))
.Where(room => room.Capacity >= minimumCapacity)
.Where(room => room.IsAvailable)
.Where(room => !requiresTeamsRoom || room.HasTeamsRoom)
.ToArray();
return new RoomSearchResult(city, minimumCapacity, requiresTeamsRoom, matches);
}
The Description attributes are important. Agent
Framework uses the function signature and descriptions to build the tool
schema sent to the model. This tells the model what the tool does and what
values it should provide for city,
minimumCapacity, and
requiresTeamsRoom.
The descriptions do not contain the implementation. The model only sees the tool contract. The C# function itself continues to run inside our application.
5) Make the tool available to the agent
Use AIFunctionFactory.Create to turn the C# function
into an AIFunction. We can then pass it to the agent
using the tools parameter:
AIAgent agent = projectClient.AsAIAgent(
model: modelDeployment,
instructions: instructions,
name: "MeetingRoomAssistant",
tools: [AIFunctionFactory.Create(FindAvailableMeetingRooms)]);
We are not calling FindAvailableMeetingRooms directly.
We give the model a description of the tool, and the model decides whether it
needs the tool based on the user's request.
The instructions also tell the agent when it should use the tool and prevent it from recommending rooms that were not returned by our application:
const string instructions = """
You help employees find meeting rooms.
Always use the meeting room tool when the user asks for a room.
Only recommend rooms returned by the tool and briefly explain why they match.
""";
6) Complete working example
Here is the complete Program.cs:
using System.ComponentModel;
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
string endpoint = "YOUR_FOUNDRY_PROJECT_ENDPOINT";
string modelDeployment = "YOUR_MODEL_DEPLOYMENT_NAME";
MeetingRoom[] meetingRooms =
[
new("Thames", "London", 6, true, true, "2nd floor"),
new("Regent", "London", 10, true, true, "3rd floor"),
new("Windsor", "London", 12, false, true, "3rd floor"),
new("Harbour", "Sydney", 10, true, true, "5th floor"),
new("Cascade", "Redmond", 8, true, false, "1st floor")
];
[Description("Find available meeting rooms that match a city, minimum capacity, and Microsoft Teams requirement.")]
RoomSearchResult FindAvailableMeetingRooms(
[Description("The city where the meeting room must be located.")] string city,
[Description("The minimum number of people the room must accommodate.")] int minimumCapacity,
[Description("Whether the room must have Microsoft Teams meeting equipment.")] bool requiresTeamsRoom)
{
Console.WriteLine($"[Tool] Searching for rooms in {city} for {minimumCapacity} people.");
MeetingRoom[] matches = meetingRooms
.Where(room => room.City.Equals(city, StringComparison.OrdinalIgnoreCase))
.Where(room => room.Capacity >= minimumCapacity)
.Where(room => room.IsAvailable)
.Where(room => !requiresTeamsRoom || room.HasTeamsRoom)
.ToArray();
return new RoomSearchResult(city, minimumCapacity, requiresTeamsRoom, matches);
}
DefaultAzureCredential credential = new(new DefaultAzureCredentialOptions
{
ExcludeManagedIdentityCredential = true
});
AIProjectClient projectClient = new(new Uri(endpoint), credential);
const string instructions = """
You help employees find meeting rooms.
Always use the meeting room tool when the user asks for a room.
Only recommend rooms returned by the tool and briefly explain why they match.
""";
AIAgent agent = projectClient.AsAIAgent(
model: modelDeployment,
instructions: instructions,
name: "MeetingRoomAssistant",
tools: [AIFunctionFactory.Create(FindAvailableMeetingRooms)]);
const string prompt = "Find an available meeting room in London for 8 people. It must have Microsoft Teams equipment.";
Console.WriteLine(await agent.RunAsync(prompt));
record MeetingRoom(
string Name,
string City,
int Capacity,
bool HasTeamsRoom,
bool IsAvailable,
string Location);
record RoomSearchResult(
string City,
int MinimumCapacity,
bool RequiresTeamsRoom,
IReadOnlyList<MeetingRoom> Rooms);
Change these values:
-
YOUR_FOUNDRY_PROJECT_ENDPOINT: the project endpoint from Microsoft Foundry. -
YOUR_MODEL_DEPLOYMENT_NAME: the deployment name of a model that supports function calling.
7) Authenticate and run the application
The sample uses DefaultAzureCredential. For local
development, sign in with the Azure CLI:
az login
Then run the console application:
dotnet run
The console first shows the line written by our C# function, followed by the agent's response:
[Tool] Searching for rooms in London for 8 people.
The Regent room is available on the 3rd floor. It seats 10 people and has Microsoft Teams equipment.
The exact wording of the final response can vary. The room itself comes from our function result rather than the model's training data.
8) What happens during the tool call
The request goes through the following steps:
- The user asks for a room in London for eight people with Teams.
- The model sees that the meeting room tool can answer the request.
-
The model selects the tool and supplies
London,8, andtrueas arguments. - Agent Framework invokes the C# function in our application.
- The function returns a structured
RoomSearchResult. - The model uses that result to create the final response.
The model decides when to request a tool call, but our application remains responsible for executing the function and controlling what it can do.
Why return a structured result?
Our function returns RoomSearchResult instead of a
preformatted sentence. This keeps business data separate from presentation.
The tool supplies facts such as room name, capacity, equipment, and location,
while the agent turns those facts into a useful answer.
This also makes the tool easier to extend later. We could add:
- A room identifier.
- Available time slots.
- Accessibility information.
- A booking URL.
The same pattern works when the function calls a real service. We can replace the in-memory array with Microsoft Graph or an internal API without changing how the agent invokes the tool.
A note about tool safety
This example only reads sample data. Tools that create, update, delete, send, or approve something need additional safeguards. Validate every argument in application code, authorize the current user, and require human approval for sensitive actions.
We will cover human approval and tool interception later in this series. For now, keeping the first tool read-only lets us concentrate on the core function calling flow.
More information
The Microsoft Agent Framework documentation has more details about using function tools with an agent.
Wrapping up
In this post, we turned a normal C# function into a Microsoft Agent Framework tool. The model used the function description and parameters to decide when to call it, Agent Framework executed it locally, and the structured result was used to produce the final answer.
This is the basic pattern for connecting an agent to capabilities owned by your application. In the next post, we will move the integration boundary outside the application and connect the agent to tools exposed by an MCP server.
Hope this helps!


No comments:
Post a Comment